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

Fix groovy multivariable declaration #4757

Merged
merged 26 commits into from
Dec 17, 2024

Conversation

Laurens-W
Copy link
Contributor

@Laurens-W Laurens-W commented Dec 9, 2024

What's changed?

When parsing multivariable declarations now takes into account that no traditional identifier is available for any variable after the first

What's your motivation?

Anything in particular you'd like reviewers to focus on?

Anyone you would like to review specifically?

@timtebeek @jevanlingen @sambsnyd @knutwannheden

Have you considered any alternatives or workarounds?

Any additional context

The Groovy AST returns multivariable declarations as separate DeclarationExpressions within a block. Therefore we cannot easily map them to J.VariableDeclarations

Checklist

  • I've added unit tests to cover both positive and negative cases
  • I've read and applied the recipe conventions and best practices
  • I've used the IntelliJ IDEA auto-formatter on affected files

@Laurens-W Laurens-W added the bug Something isn't working label Dec 9, 2024
@Laurens-W Laurens-W self-assigned this Dec 9, 2024
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@timtebeek
Copy link
Contributor

Saving others a click with the failures seen: https://ge.openrewrite.org/s/nr7naekjegaas/tests/overview?outcome=FAILED

wildcardWithUpperBound()
diamondOperator()
singleVariableDeclarationStaticallyTyped()
wildcardWithLowerBound()
genericMaps()

First one fails with

org.opentest4j.AssertionFailedError: [When parsing and printing the source code back to text without modifications, the printed source didn't match the original source code. This means there is a bug in the parser implementation itself. Please open an issue to report this, providing a sample of the code that generated this error for "file.groovy":	
diff --git a/file.groovy b/file.groovy	
index 3861186..ac7fbf9 100644	
--- a/file.groovy	
+++ b/file.groovy	
@@ -1 +1 @@ 	
-List<? extends String> l	
\ No newline at end of file	
+List<?< extends String> l	
\ No newline at end of file	
expected: "List<? extends String> l"	
 but was: "List<?< extends String> l"	

@timtebeek timtebeek marked this pull request as draft December 9, 2024 19:05
@Laurens-W
Copy link
Contributor Author

I simplified a little too far, reverted that partially and now they work (locally)

@timtebeek
Copy link
Contributor

One remaining failure it seems in org.openrewrite.groovy.tree.AssignmentTest/simpleWithFinal()

java.lang.AssertionError: Source file was parsed into an LST that contains non-whitespace characters in its whitespace. This is indicative of a bug in the parser. 	
final~~(non-whitespace)~~> def <~~x = "s"	

Include subpart of remainder to give an indication where the issue occurred
@Laurens-W Laurens-W marked this pull request as ready for review December 10, 2024 14:35
@Laurens-W Laurens-W linked an issue Dec 10, 2024 that may be closed by this pull request
Comment on lines +62 to +66
"""
final def x = "x"
def final y = "y"
final z = "z"
"""
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps good to call out here that we now model final def and def final as two unique concatenated Identifiers, which seems odd to me:

----G.CompilationUnit | "G.CompilationUnit(typesInUse=org.openrewrite.java.internal.TypesInUse@745c2004, padding=org.openrewrite.groovy.tree.G..."
    |---J.VariableDeclarations | "final def x = "x""
    |   |---J.Identifier | "final def"
    |   \-------J.VariableDeclarations.NamedVariable | "x = "x""
    |           |---J.Identifier | "x"
    |           \-------J.Literal | ""x""
    |---J.VariableDeclarations | "final y = "y""
    |   |---J.Identifier | "final"
    |   \-------J.VariableDeclarations.NamedVariable | "y = "y""
    |           |---J.Identifier | "y"
    |           \-------J.Literal | ""y""
    \---J.VariableDeclarations | "final z = "z""
        |---J.Identifier | "final"
        \-------J.VariableDeclarations.NamedVariable | "z = "z""
                |---J.Identifier | "z"
                \-------J.Literal | ""z""

Note that I don't expect this to occur often, if at all, and it still prints OK; but figured comment it here for a closer look by others reviewing as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

While this isn't perfect either, I think I would opt for mapping all of final, def, and var to modifiers (J.Modifier) and instead deprecating the RedundantDef marker, while still supporting it in the printer to remain backwards compatible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Implemented this. I'm not sure if we can actually mark the RedundantDef marker as deprecated though as that may also apply to declared methods, so I left that in.

As we discussed we may want to find a way to combine consecutive DeclarationExpressions into one J.VariableDeclarations. That could be a further improvement and I'm willing to work on that, but want to return focus to the parenthesis issue as well :)

Copy link
Contributor

Choose a reason for hiding this comment

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

At least marking the RedundantDef marker as @deprecated should not be a problem. If you have the option to remove the maybeRedundantDef method as well, all the better. As long as you keep the RedundantDef marker in printer, you are still backwards compatible.

__
Also notice the RedundantDef marker has ben added lately (05/12/2024). So the sooner we no longer use it, the less serialised LSTs do have this marker.

Copy link
Contributor

Choose a reason for hiding this comment

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

@sambsnyd Since you recently added that RedundantDef, do you also want to chime in here and tell us what your thoughts are about this?

Comment on lines +62 to +66
"""
final def x = "x"
def final y = "y"
final z = "z"
"""
Copy link
Contributor

Choose a reason for hiding this comment

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

While this isn't perfect either, I think I would opt for mapping all of final, def, and var to modifiers (J.Modifier) and instead deprecating the RedundantDef marker, while still supporting it in the printer to remain backwards compatible.

github-actions[bot]

This comment was marked as spam.

statements.set(i, variableDeclarations.withTypeExpression(variableDeclarations.getTypeExpression().withPrefix(originalPrefix)));
}
} else if (!currentStatement.getPrefix().equals(originalPrefix)) {
if (!currentStatement.getPrefix().equals(originalPrefix)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It is very unclear to me what these changes here have to do with the parsing issue. Is a comment required here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Due to the way DeclarationExpressions were parsed into J.VariableDeclarations we needed a specific check here. After my changes the parsing is more in-line with other types and so we no longer need that exceptional case here!

Copy link
Contributor

@knutwannheden knutwannheden left a comment

Choose a reason for hiding this comment

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

I think we should be able to proceed with this. Is it difficult to solve this without the MultiVariable marker?

@Laurens-W
Copy link
Contributor Author

I think we should be able to proceed with this. Is it difficult to solve this without the MultiVariable marker?

I haven't looked into it too much yet but I'd imagine somewhere after this block:

for (ASTNode value : entry.getValue()) {
if (value instanceof InnerClassNode) {
// Inner classes will be visited as part of visiting their containing class
continue;
}
JRightPadded<Statement> statement = convertTopLevelStatement(unit, value);
if (statements.isEmpty() && pkg == null && statement.getElement() instanceof J.Import) {
prefix = statement.getElement().getPrefix();
statement = statement.withElement(statement.getElement().withPrefix(EMPTY));
}
statements.add(statement);
}
we have the required context to sweep consecutive VariableDeclarations together

@timtebeek timtebeek merged commit 5f2d11c into main Dec 17, 2024
2 checks passed
@timtebeek timtebeek deleted the fix-groovy-multivariable-declaration branch December 17, 2024 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Groovy parser does not support multiple variable declarations
4 participants