Skip to content

Commit 240bead

Browse files
Groovy: fix parsing of deconstructing assignment of existing variables (#7535)
1 parent 23c19e9 commit 240bead

2 files changed

Lines changed: 60 additions & 33 deletions

File tree

rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2227,34 +2227,10 @@ public void visitDeclarationExpression(DeclarationExpression expression) {
22272227
VariableExpression firstVar = (VariableExpression) tupleExpressions.get(0);
22282228
TypeTree typeExpr = visitVariableExpressionType(firstVar);
22292229

2230-
Space beforeOpenParen = sourceBefore("(");
2231-
2232-
List<JRightPadded<J.VariableDeclarations>> tupleVars = new ArrayList<>(tupleExpressions.size());
2233-
for (int i = 0; i < tupleExpressions.size(); i++) {
2234-
VariableExpression varExpr = (VariableExpression) tupleExpressions.get(i);
2235-
TypeTree innerType = visitVariableExpressionType(varExpr);
2236-
J.Identifier name = doVisit(varExpr);
2237-
J.VariableDeclarations.NamedVariable nv = new J.VariableDeclarations.NamedVariable(
2238-
randomId(),
2239-
name.getPrefix(),
2240-
Markers.EMPTY,
2241-
name.withPrefix(EMPTY),
2242-
emptyList(),
2243-
null,
2244-
typeMapping.variableType(name.getSimpleName(), innerType.getType()));
2245-
J.VariableDeclarations innerDecl = new J.VariableDeclarations(
2246-
randomId(), EMPTY, Markers.EMPTY,
2247-
emptyList(), emptyList(),
2248-
innerType, null,
2249-
singletonList(JRightPadded.build(nv)));
2250-
Space after = i < tupleExpressions.size() - 1 ? sourceBefore(",") : sourceBefore(")");
2251-
tupleVars.add(JRightPadded.<J.VariableDeclarations>build(innerDecl).withAfter(after));
2252-
}
2253-
2254-
G.TupleExpression tupleDeclarator = new G.TupleExpression(
2255-
randomId(), EMPTY, Markers.EMPTY,
2256-
JContainer.build(beforeOpenParen, tupleVars, Markers.EMPTY),
2257-
null);
2230+
List<VariableExpression> tupleVarExprs = tupleExpressions.stream()
2231+
.map(e -> (VariableExpression) e)
2232+
.collect(toList());
2233+
G.TupleExpression tupleDeclarator = parseTupleExpression(tupleVarExprs);
22582234

22592235
J.VariableDeclarations.NamedVariable namedVariable = new J.VariableDeclarations.NamedVariable(
22602236
randomId(), EMPTY, Markers.EMPTY,
@@ -3022,11 +2998,28 @@ public void visitThrowStatement(ThrowStatement statement) {
30222998
queue.add(new J.Throw(randomId(), fmt, Markers.EMPTY, doVisit(statement.getExpression())));
30232999
}
30243000

3025-
// the current understanding is that TupleExpression only exist as method invocation arguments.
3026-
// this is the reason behind the simplifying assumption that there is one expression, and it is
3027-
// a NamedArgumentListExpression.
30283001
@Override
30293002
public void visitTupleExpression(TupleExpression tuple) {
3003+
List<org.codehaus.groovy.ast.expr.Expression> expressions = tuple.getExpressions();
3004+
3005+
// A TupleExpression whose visible elements are all VariableExpression is the LHS of a
3006+
// destructuring assignment without `def`: (a, b) = expr.
3007+
// (Synthetic elements like the implicit outer-`this` are VariableExpression too but
3008+
// don't appear in source and must not influence this check.)
3009+
List<org.codehaus.groovy.ast.expr.Expression> visibleExpressions = expressions.stream()
3010+
.filter(GroovyParserVisitor.this::appearsInSource)
3011+
.collect(toList());
3012+
boolean isDestructuringLhs = !visibleExpressions.isEmpty() &&
3013+
visibleExpressions.stream().allMatch(e -> e instanceof VariableExpression);
3014+
if (isDestructuringLhs) {
3015+
List<VariableExpression> varExprs = visibleExpressions.stream()
3016+
.map(e -> (VariableExpression) e)
3017+
.collect(toList());
3018+
queue.add(parseTupleExpression(varExprs));
3019+
return;
3020+
}
3021+
3022+
// TupleExpression as method invocation arguments: each element is a NamedArgumentListExpression
30303023
int saveCursor = cursor;
30313024
Space beforeOpenParen = whitespace();
30323025

@@ -3039,8 +3032,8 @@ public void visitTupleExpression(TupleExpression tuple) {
30393032
cursor = saveCursor;
30403033
}
30413034

3042-
List<JRightPadded<Expression>> args = new ArrayList<>(tuple.getExpressions().size());
3043-
for (org.codehaus.groovy.ast.expr.Expression expression : tuple.getExpressions()) {
3035+
List<JRightPadded<Expression>> args = new ArrayList<>(expressions.size());
3036+
for (org.codehaus.groovy.ast.expr.Expression expression : expressions) {
30443037
// Skip synthetic args (e.g. the implicit outer-`this` Groovy adds when
30453038
// constructing a non-static inner class from within the enclosing class)
30463039
if (!appearsInSource(expression)) {
@@ -3084,6 +3077,27 @@ public void visitTupleExpression(TupleExpression tuple) {
30843077
queue.add(JContainer.build(beforeOpenParen, args, Markers.EMPTY));
30853078
}
30863079

3080+
private G.TupleExpression parseTupleExpression(List<VariableExpression> varExprs) {
3081+
Space beforeOpenParen = sourceBefore("(");
3082+
List<JRightPadded<J.VariableDeclarations>> tupleVars = new ArrayList<>(varExprs.size());
3083+
for (int i = 0; i < varExprs.size(); i++) {
3084+
VariableExpression varExpr = varExprs.get(i);
3085+
TypeTree innerType = visitVariableExpressionType(varExpr);
3086+
J.Identifier name = doVisit(varExpr);
3087+
J.VariableDeclarations.NamedVariable nv = new J.VariableDeclarations.NamedVariable(
3088+
randomId(), name.getPrefix(), Markers.EMPTY,
3089+
name.withPrefix(EMPTY), emptyList(), null,
3090+
typeMapping.variableType(name.getSimpleName(), innerType.getType()));
3091+
J.VariableDeclarations innerDecl = new J.VariableDeclarations(
3092+
randomId(), EMPTY, Markers.EMPTY, emptyList(), emptyList(),
3093+
innerType, null, singletonList(JRightPadded.build(nv)));
3094+
Space after = i < varExprs.size() - 1 ? sourceBefore(",") : sourceBefore(")");
3095+
tupleVars.add(JRightPadded.<J.VariableDeclarations>build(innerDecl).withAfter(after));
3096+
}
3097+
return new G.TupleExpression(randomId(), EMPTY, Markers.EMPTY,
3098+
JContainer.build(beforeOpenParen, tupleVars, Markers.EMPTY), null);
3099+
}
3100+
30873101
@Override
30883102
public void visitTryCatchFinally(TryCatchStatement node) {
30893103
Space prefix = sourceBefore("try");

rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/AssignmentTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,4 +343,17 @@ void destructuringAssignmentNoSpacesAroundEquals() {
343343
);
344344
}
345345

346+
@Test
347+
void destructuringAssignmentToExistingVariables() {
348+
rewriteRun(
349+
groovy(
350+
"""
351+
def key = 'k'
352+
def elemAttrs = [:]
353+
(key, elemAttrs) = ['x', [:]]
354+
"""
355+
)
356+
);
357+
}
358+
346359
}

0 commit comments

Comments
 (0)