-
Notifications
You must be signed in to change notification settings - Fork 571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rework GoStructInitializationInspection #2826
base: master
Are you sure you want to change the base?
Conversation
adbba13
to
2ad2b06
Compare
It's not really good idea to rename an inspection at least for two reasons:
So please avoid inspections renaming until you have a really good reason |
@grenki please review the changes |
1016569
to
c8af522
Compare
|
||
private static boolean areElementsNamesMatchesDefinitions(@NotNull List<String> elementsNames, | ||
@NotNull List<String> fieldDefinitionsNames) { | ||
return range(0, elementsNames.size()).allMatch(i -> isNullOrEqual(elementsNames.get(i), getByIndex(fieldDefinitionsNames, i))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reuse GoPsiImplUtil#getByIndex
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Method is private and i don't see it as part of GoPsiImpl
interface cause it's not related to psi operations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we must have public util method like that. But while we haven't suitable util class it can be in GoPsiImplUtli
|
||
private static void replaceElementsByNamed(@NotNull List<GoElement> elements, | ||
@NotNull List<String> fieldDefinitionNames, | ||
@NotNull Project project) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Place project as first parameter, please
if (startElement instanceof GoElement) { | ||
startElement.replace(GoElementFactory.createLiteralValueElement(project, myStructField, startElement.getText())); | ||
} | ||
GoLiteralValue literal = ObjectUtils.tryCast(descriptor.getStartElement(), GoLiteralValue.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
startElement can be null and invalid. You could check it
|
||
List<String> elementsNames = getNames(literalValue.getElementList()); | ||
if (hasNull(elementsNames.toArray()) && areElementsNamesMatchesDefinitions(elementsNames, getFieldDefinitionsNames(structType))) { | ||
holder.registerProblem(literalValue, "Unnamed field initializations", ProblemHighlightType.GENERIC_ERROR_OR_WARNING, QUICK_FIX); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think better report problem for each element and it could be weak warning, because it can be valid code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, reporting the literal was one of the main point. Why do you think that report each element is better? We have to name all elements in literal to get valid code anyway, so what is the point in renaming one element.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And I believe that if we have mixed named and unnamed elements in the literal the code will not compile at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reporting each element without keys
- shows user only error element,
- it doesn't mean that quickfix must rename only this element
- big part of code which highlights as error looks bad :(
Anyway we mustn't highlight as error valid code like this: https://play.golang.org/p/4cP6PcSWCZ
And if you want to change problem highlighting type you could change level of inspection in gogland.xml
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me as user it would be a little strange, that invoke fix on element affect the parent, but if you fine with it, I don't mind, anyway my main desire was to invoke quickfix on literal.
GoType refType = GoPsiImplUtil.getLiteralType(parent, false); | ||
if (refType instanceof GoStructType) { | ||
processStructType(holder, o, (GoStructType)refType); | ||
public void visitCompositeLit(@NotNull GoCompositeLit compositeLit) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And you could check literal value. If you look to go.bnf you can see that literal value not always has composite literal parent.
For example it's valid code
type S struct{int}
_ = []S{ {1}, {2}, {3}} // parent of literal value {1} is GoValue {1}
0179518
to
925c529
Compare
} | ||
@Contract("_, null -> false; null, _ -> false") | ||
private static boolean isSliceElementValue(@Nullable GoLiteralValue literalValue, @Nullable GoType parentLitType) { | ||
return parentLitType instanceof GoArrayOrSliceType && literalValue != null && literalValue.getParent() instanceof GoValue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this operation (getting direct parent from nullable by class) I would like to move GoStructLiteralCompletion#parent
to GoPsiTreeUtil
since there are already plenty occurrences of this code in the plugin project, but I have some doubts.
There are already numerous methods for getting parent and, in particular, PsiTreeUtil#findFirstParent
which, as I think, can become confusing with the new one. What do you think @grenki?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's great idea! It very useful and convenient method.
PsiTreeUtil#findFirstParent
and PsiTreeUtil#getParentOfType
are hard to use in this case
Ping :) |
} | ||
} | ||
|
||
private static void replaceElementsByNamed(@NotNull Project project, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
naming: better add keys to elements
Better to use names which are consistent with class names and its structure: we have GoNamedElement class and GoElement class hasn't name but it has GoKey and GoValue children
if (value == null) continue; | ||
|
||
GoElement namedElement = GoElementFactory.createLiteralValueElement(project, fieldDefinitionName, value.getText()); | ||
element.replace(namedElement); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can add key and colon to element as children, but your way is also suitable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, indeed. But I could not find a way to add colon and key to the element without several boilerplate lines of code so I would like to stay with the current solution.
List<String> elementsNames = getNames(elements); | ||
if (!areElementsNamesMatchesDefinitions(elementsNames, getFieldDefinitionsNames(structType))) return; | ||
|
||
zip(elementsNames, elements).forEach(pair -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for loop more readable then this lambda
private static GoStructType getStructType(@Nullable GoLiteralValue literalValue) { | ||
GoCompositeLit lit = PsiTreeUtil.getParentOfType(literalValue, GoCompositeLit.class); | ||
GoType litType = lit != null ? lit.getType() : null; | ||
return isSliceElementValue(literalValue, litType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's wrong type calculating: you take composite lit type or type of array instead of literal value type. It can be a lot of nested literal values without composite literals
in this example intention inserts fields of B
but literal type is S
type S struct {
r, t int
}
type B struct{
S
}
var _ = []B{ S: {2, 3<caret>}}
Method GoPsiImplUtil.getLiteralType
is so strange. But it can give you type of literal value. And you wouldn't need isSliceElementValue
and getStructType
methods
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Example does not compile (should be S: S{2, 3<caret>}
I guess), but quick fix on fixed version works. Maybe we should not highlight such cases at all? (when there is no composite literal for literal value). Anyway, someday there will be read code on these cases :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for invalid example. I mean something like this
type S struct {
r, t int
}
var _ = [][]S{
{
{2, 2},
},
}
var _ = map[string] []S{
"s": {
{1, 2},
},
}
} | ||
|
||
@NotNull | ||
private static List<String> getNames(@NotNull List<GoElement> elements) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it isn't names :)
} | ||
@Contract("_, null -> false; null, _ -> false") | ||
private static boolean isSliceElementValue(@Nullable GoLiteralValue literalValue, @Nullable GoType parentLitType) { | ||
return parentLitType instanceof GoArrayOrSliceType && literalValue != null && literalValue.getParent() instanceof GoValue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's great idea! It very useful and convenient method.
PsiTreeUtil#findFirstParent
and PsiTreeUtil#getParentOfType
are hard to use in this case
GoValue value = fieldDefinitionName != null && element.getKey() == null ? element.getValue() : null; | ||
if (value == null) continue; | ||
|
||
GoElement namedElement = GoElementFactory.createLiteralValueElement(project, fieldDefinitionName, value.getText()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can inline namedElement
it is element with key, not named
63d8b74
to
87177eb
Compare
PTAL |
return definition != null ? list(definition.getName()) : map(declaration.getFieldDefinitionList(), GoNamedElement::getName); | ||
} | ||
|
||
private static void registerProblemsForElementsWithoutKeys(@NotNull List<GoElement> elements, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
better to make this method member of anonymous visitor
*/ | ||
@SuppressWarnings("WeakerAccess") public Boolean reportImportedStructs; | ||
|
||
@NotNull | ||
@Override | ||
protected GoVisitor buildGoVisitor(@NotNull ProblemsHolder holder, @NotNull LocalInspectionToolSession session) { | ||
return new GoVisitor() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
redundant line
public JComponent createOptionsPanel() { | ||
return new SingleCheckboxOptionsPanel("Report for local type definitions as well", this, "reportLocalStructs"); | ||
@Contract("null -> null") | ||
private static GoStructType getLiteralStructType(@Nullable GoLiteralValue literalValue) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this example is invalid
type S struct {
r, t int
}
type B struct{
S
}
var _ = []B{ S: {2, 3}}
processLiteralValue(holder, element, structType.getFieldDeclarationList()); | ||
} | ||
@Nullable | ||
private static String getFieldDefinitionName(@Nullable GoValue value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
better to return named element in which key resolves:
PsiElement field = fieldName != null ? fieldName.resolve() : null;
return field instanceof GoAnonymousFieldDefinition || field instanceof GoFieldDefinition
? ObjectUtils.tryCast(field, GoNamedElement.class) : null;
if you return field definition you wouldn't use getDefinition
method in getFieldDefinitionType
public void testInnerLiteralMap() { doQuickfixTest(); } | ||
public void testInnerLiteralFieldWithKey() { doQuickfixTest(); } | ||
public void testMultipleInnerLiterals() { doQuickfixTest(); } | ||
public void testInnerLiteralWithoutType() { doQuickfixTest(); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alignment
} | ||
|
||
func main() { | ||
var _ = []B{ S: {<weak_warning descr="Unnamed field initializations">1<caret></weak_warning>}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
invalid test:
[]B {
// S is invalid key because in array literals we use integer keys (indicies)
S : { // expected literal type is B (parent literal has []B type)
1 // expected key is `S` (type is B)
},
}
<weak_warning descr="Unnamed field initialization">"string"</weak_warning>, | ||
<weak_warning descr="Unnamed field initialization">"string"</weak_warning>, | ||
<weak_warning descr="Unnamed field initialization">nil</weak_warning>, | ||
<weak_warning descr="Unnamed field initializations">"string"</weak_warning>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
initialization
is better
} | ||
|
||
@NotNull | ||
private static List<String> getKeys(@NotNull List<GoElement> elements) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better to use fields in which keys resolved instead names.
} | ||
|
||
@NotNull | ||
private static List<String> getFieldDefinitionsNames(@Nullable GoStructType type) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
better return fields objects instead names
return fieldDefinition != null ? fieldDefinition.getIdentifier().getText() : null; | ||
private static GoType getUnderlyingType(@NotNull GoFieldDefinition fieldDefinition) { | ||
GoType type = fieldDefinition.getGoType(null); | ||
return type != null ? GoPsiImplUtil.getUnderlyingType(type) : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type.getUnderlyingType()
11bf872
to
0bec774
Compare
Force updated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, I think it can be merged after fixes last comments
Place new fixes in separate commit please
GoNamedElement anonymousDefinition = ObjectUtils.tryCast(declaration.getAnonymousFieldDefinition(), GoNamedElement.class); | ||
return anonymousDefinition != null | ||
? list(anonymousDefinition) | ||
: map(declaration.getFieldDefinitionList(), definition -> ObjectUtils.tryCast(definition, GoNamedElement.class)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can return List<? extends GonamedElement>
and don't cast definittions
GoFieldName fieldName = key != null ? key.getFieldName() : null; | ||
PsiElement field = fieldName != null ? fieldName.resolve() : null; | ||
return field instanceof GoAnonymousFieldDefinition || field instanceof GoFieldDefinition ? ObjectUtils | ||
.tryCast(field, GoNamedElement.class) : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bad formatting
GoKey key = PsiTreeUtil.getPrevSiblingOfType(value, GoKey.class); | ||
GoFieldName fieldName = key != null ? key.getFieldName() : null; | ||
PsiElement field = fieldName != null ? fieldName.resolve() : null; | ||
return field instanceof GoAnonymousFieldDefinition || field instanceof GoFieldDefinition ? ObjectUtils |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extract to util class and reuse GoMoveToStructInitializationIntention#isFieldDefinition
- Use generic return type in GoStructInitializationInspection#getFieldDefinitions - Move GoMoveToStructInitializationIntention#isFieldDefinition to GoPsiImplUtil
PTAL |
fixes #2819