Skip to content
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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

wbars
Copy link
Contributor

@wbars wbars commented Nov 13, 2016

fixes #2819

@wbars wbars force-pushed the go-2819 branch 3 times, most recently from adbba13 to 2ad2b06 Compare November 14, 2016 04:32
@zolotov
Copy link
Contributor

zolotov commented Nov 14, 2016

It's not really good idea to rename an inspection at least for two reasons:

  1. I'd prefer to save vcs-history and git-blame data
  2. The inspection is quite old and some users might have suppressed it with a comment //noinspection GoStructInitialization. The thing is that you changed the suppression id by renaming the class and old suppressions won't work anymore. The same thing for settings – some users could disable the inspection but since its ID was changed – the settings became invalid.

So please avoid inspections renaming until you have a really good reason

@zolotov
Copy link
Contributor

zolotov commented Nov 14, 2016

@grenki please review the changes

@zolotov zolotov self-assigned this Nov 14, 2016
@wbars wbars force-pushed the go-2819 branch 4 times, most recently from 1016569 to c8af522 Compare November 16, 2016 07:28

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)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reuse GoPsiImplUtil#getByIndex

Copy link
Contributor Author

@wbars wbars Nov 18, 2016

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.

Copy link
Contributor

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) {
Copy link
Contributor

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);
Copy link
Contributor

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);
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

@wbars wbars Nov 18, 2016

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.

Copy link
Contributor

@grenki grenki Nov 18, 2016

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

Copy link
Contributor Author

@wbars wbars Nov 18, 2016

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) {
Copy link
Contributor

@grenki grenki Nov 18, 2016

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}

@wbars wbars force-pushed the go-2819 branch 2 times, most recently from 0179518 to 925c529 Compare November 22, 2016 04:37
}
@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;
Copy link
Contributor Author

@wbars wbars Nov 22, 2016

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?

Copy link
Contributor

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

@wbars
Copy link
Contributor Author

wbars commented Nov 29, 2016

Ping :)

@zolotov
Copy link
Contributor

zolotov commented Dec 1, 2016

@wbars sorry for delay. Review is in progress. @grenki right?

}
}

private static void replaceElementsByNamed(@NotNull Project project,
Copy link
Contributor

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);
Copy link
Contributor

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

Copy link
Contributor Author

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 -> {
Copy link
Contributor

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)
Copy link
Contributor

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

Copy link
Contributor Author

@wbars wbars Dec 2, 2016

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 :)

Copy link
Contributor

@grenki grenki Dec 2, 2016

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) {
Copy link
Contributor

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;
Copy link
Contributor

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());
Copy link
Contributor

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

@wbars wbars force-pushed the go-2819 branch 5 times, most recently from 63d8b74 to 87177eb Compare December 8, 2016 10:53
@wbars
Copy link
Contributor Author

wbars commented Dec 8, 2016

PTAL

return definition != null ? list(definition.getName()) : map(declaration.getFieldDefinitionList(), GoNamedElement::getName);
}

private static void registerProblemsForElementsWithoutKeys(@NotNull List<GoElement> elements,
Copy link
Contributor

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() {

Copy link
Contributor

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) {
Copy link
Contributor

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) {
Copy link
Contributor

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(); }
Copy link
Contributor

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>}}
Copy link
Contributor

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>,
Copy link
Contributor

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) {
Copy link
Contributor

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) {
Copy link
Contributor

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type.getUnderlyingType()

@wbars wbars force-pushed the go-2819 branch 2 times, most recently from 11bf872 to 0bec774 Compare December 13, 2016 08:20
@wbars
Copy link
Contributor Author

wbars commented Dec 13, 2016

Force updated.

Copy link
Contributor

@grenki grenki left a 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));
Copy link
Contributor

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;
Copy link
Contributor

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
Copy link
Contributor

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
@wbars
Copy link
Contributor Author

wbars commented Dec 22, 2016

PTAL

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Highlight the whole struct literal when unnamed element is present
3 participants