This is an automated email from the ASF dual-hosted git repository.

emilles pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new 186ff3f20c GROOVY-11567: `@Field`: rework statement navigation
186ff3f20c is described below

commit 186ff3f20ca6df634eb3e028e36d6b1b2e7dd809
Author: Eric Milles <[email protected]>
AuthorDate: Mon Feb 10 14:10:01 2025 -0600

    GROOVY-11567: `@Field`: rework statement navigation
---
 .../groovy/transform/FieldASTTransformation.java   | 217 +++++++++------------
 .../groovy/transform/FieldTransformTest.groovy     |  40 ++--
 2 files changed, 122 insertions(+), 135 deletions(-)

diff --git 
a/src/main/java/org/codehaus/groovy/transform/FieldASTTransformation.java 
b/src/main/java/org/codehaus/groovy/transform/FieldASTTransformation.java
index a804be8f6f..3672d081a3 100644
--- a/src/main/java/org/codehaus/groovy/transform/FieldASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/FieldASTTransformation.java
@@ -39,9 +39,7 @@ import org.codehaus.groovy.ast.expr.ClosureExpression;
 import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
 import org.codehaus.groovy.ast.expr.DeclarationExpression;
 import org.codehaus.groovy.ast.expr.Expression;
-import org.codehaus.groovy.ast.expr.TupleExpression;
 import org.codehaus.groovy.ast.expr.VariableExpression;
-import org.codehaus.groovy.ast.stmt.ExpressionStatement;
 import org.codehaus.groovy.classgen.VariableScopeVisitor;
 import org.codehaus.groovy.control.CompilePhase;
 import org.codehaus.groovy.control.SourceUnit;
@@ -53,7 +51,6 @@ import java.util.Iterator;
 import java.util.List;
 
 import static 
org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedConstructor;
-import static org.codehaus.groovy.ast.ClassHelper.make;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.assignX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName;
@@ -68,60 +65,65 @@ import static 
org.codehaus.groovy.ast.tools.GeneralUtils.varX;
  * Handles transformation for the @Field annotation.
  */
 @GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
-public class FieldASTTransformation extends ClassCodeExpressionTransformer 
implements ASTTransformation, Opcodes {
+public class FieldASTTransformation extends ClassCodeExpressionTransformer 
implements ASTTransformation {
+
+    private static final ClassNode MY_TYPE = ClassHelper.make(Field.class);
+    private static final ClassNode LAZY_TYPE = ClassHelper.make(Lazy.class);
+    private static final ClassNode OPTION_TYPE = 
ClassHelper.make(Option.class);
+    private static final ClassNode ASTTRANSFORMCLASS_TYPE = 
ClassHelper.make(GroovyASTTransformationClass.class);
 
-    private static final Class MY_CLASS = Field.class;
-    private static final ClassNode MY_TYPE = make(MY_CLASS);
-    private static final ClassNode LAZY_TYPE = make(Lazy.class);
-    private static final String MY_TYPE_NAME = "@" + 
MY_TYPE.getNameWithoutPackage();
-    private static final ClassNode ASTTRANSFORMCLASS_TYPE = 
make(GroovyASTTransformationClass.class);
-    private static final ClassNode OPTION_TYPE = make(Option.class);
-    private SourceUnit sourceUnit;
     private DeclarationExpression candidate;
-    private boolean insideScriptBody;
-    private String variableName;
     private FieldNode fieldNode;
+    private String variableName;
+
     private ClosureExpression currentClosure;
-    private ConstructorCallExpression currentAIC;
+    private boolean insideScriptBody;
+    private SourceUnit sourceUnit;
 
     @Override
-    public void visit(ASTNode[] nodes, SourceUnit source) {
-        sourceUnit = source;
+    protected SourceUnit getSourceUnit() {
+        return sourceUnit;
+    }
+
+    @Override
+    public void visit(final ASTNode[] nodes, final SourceUnit source) {
         if (nodes.length != 2 || !(nodes[0] instanceof AnnotationNode) || 
!(nodes[1] instanceof AnnotatedNode)) {
-            throw new GroovyBugError("Internal error: expecting 
[AnnotationNode, AnnotatedNode] but got: " + Arrays.asList(nodes));
+            throw new GroovyBugError("Internal error: expecting 
[AnnotationNode, AnnotatedNode] but got: " + Arrays.toString(nodes));
         }
 
-        AnnotatedNode parent = (AnnotatedNode) nodes[1];
-        AnnotationNode node = (AnnotationNode) nodes[0];
-        if (!MY_TYPE.equals(node.getClassNode())) return;
+        if (!MY_TYPE.equals(((AnnotationNode) nodes[0]).getClassNode())) 
return;
 
-        if (parent instanceof DeclarationExpression) {
-            DeclarationExpression de = (DeclarationExpression) parent;
-            ClassNode cNode = de.getDeclaringClass();
-            if (!cNode.isScript()) {
-                addError("Annotation " + MY_TYPE_NAME + " can only be used 
within a Script.", parent);
+        sourceUnit = source; // support for addError
+
+        if (nodes[1] instanceof DeclarationExpression) {
+            DeclarationExpression de = (DeclarationExpression) nodes[1];
+            ClassNode declaringClass = de.getDeclaringClass();
+            if (!declaringClass.isScript()) {
+                addError("Annotation @" + MY_TYPE.getNameWithoutPackage() + " 
can only be used within a Script.", de);
                 return;
             }
-            candidate = de;
             // GROOVY-4548: temp fix to stop CCE until proper support is added
             if (de.isMultipleAssignmentDeclaration()) {
-                addError("Annotation " + MY_TYPE_NAME + " not supported with 
multiple assignment notation.", parent);
+                addError("Annotation @" + MY_TYPE.getNameWithoutPackage() + " 
not supported with multiple assignment notation.", de);
                 return;
             }
+
             VariableExpression ve = de.getVariableExpression();
             variableName = ve.getName();
+            candidate = de;
+
             // set owner null here, it will be updated by addField
             fieldNode = new FieldNode(variableName, ve.getModifiers(), 
ve.getType(), null, de.getRightExpression());
             fieldNode.setSourcePosition(de);
-            cNode.addField(fieldNode);
-            // provide setter for CLI Builder purposes unless final
+            declaringClass.addField(fieldNode);
+
             if (fieldNode.isFinal()) {
                 if (!de.getAnnotations(OPTION_TYPE).isEmpty()) {
                     addError("Can't have a final field also annotated with @" 
+ OPTION_TYPE.getNameWithoutPackage(), de);
                 }
-            } else {
+            } else { // provide setter for CLI Builder purposes
                 String setterName = getSetterName(variableName);
-                cNode.addMethod(setterName, ACC_PUBLIC | ACC_SYNTHETIC, 
ClassHelper.VOID_TYPE, params(param(ve.getType(), variableName)), 
ClassNode.EMPTY_ARRAY, block(
+                declaringClass.addMethod(setterName, Opcodes.ACC_PUBLIC | 
Opcodes.ACC_SYNTHETIC, ClassHelper.VOID_TYPE, params(param(ve.getType(), 
variableName)), ClassNode.EMPTY_ARRAY, block(
                         stmt(assignX(propX(varX("this"), variableName), 
varX(variableName)))
                 ));
             }
@@ -137,15 +139,19 @@ public class FieldASTTransformation extends 
ClassCodeExpressionTransformer imple
                 }
             }
 
-            super.visitClass(cNode);
+            super.visitClass(declaringClass);
             // GROOVY-5207: So that Closures can see newly added fields
             // (not super efficient for a very large class with many @Fields 
but we chose simplicity
             // and understandability of this solution over more complex but 
efficient alternatives)
-            new VariableScopeVisitor(source).visitClass(cNode);
+            new VariableScopeVisitor(source).visitClass(declaringClass);
         }
     }
 
-    private static boolean acceptableTransform(AnnotationNode annotation) {
+    private static boolean notTransform(final ClassNode annotationType) {
+        return annotationType.getAnnotations(ASTTRANSFORMCLASS_TYPE).isEmpty();
+    }
+
+    private static boolean acceptableTransform(final AnnotationNode 
annotation) {
         // TODO also check for phase after sourceUnit.getPhase()? but will be 
ignored anyway?
         // TODO we should only copy those annotations with FIELD_TARGET but 
haven't visited annotations
         // and gathered target info at this phase, so we can't do this:
@@ -154,66 +160,81 @@ public class FieldASTTransformation extends 
ClassCodeExpressionTransformer imple
         return !annotation.getClassNode().equals(MY_TYPE);
     }
 
-    private static boolean notTransform(ClassNode annotationClassNode) {
-        return 
annotationClassNode.getAnnotations(ASTTRANSFORMCLASS_TYPE).isEmpty();
+    //
+
+    @Override
+    public void visitMethod(final MethodNode node) {
+        boolean old = insideScriptBody;
+        if (node.isScriptBody()) insideScriptBody = true;
+        super.visitMethod(node);
+        insideScriptBody = old;
     }
 
     @Override
-    public Expression transform(Expression expr) {
+    public Expression transform(final Expression expr) {
         if (expr == null) return null;
         if (expr instanceof DeclarationExpression) {
             DeclarationExpression de = (DeclarationExpression) expr;
-            if (de.getLeftExpression() == candidate.getLeftExpression()) {
+            if (de.getLeftExpression() == candidate.getVariableExpression()) {
                 if (insideScriptBody) {
-                    // TODO make EmptyExpression work
+                    // TODO: make EmptyExpression work
                     // partially works but not if only thing in script
-                    // return EmptyExpression.INSTANCE;
+                    //return EmptyExpression.INSTANCE;
                     return nullX();
                 }
-                addError("Annotation " + MY_TYPE_NAME + " can only be used 
within a Script body.", expr);
+                addError("Annotation @" + MY_TYPE.getNameWithoutPackage() + " 
can only be used within a Script body.", expr);
                 return expr;
             }
-        } else if (insideScriptBody && expr instanceof VariableExpression && 
currentClosure != null) {
+        } else if (expr instanceof ClosureExpression) {
+            var old = currentClosure; currentClosure = (ClosureExpression) 
expr;
+            // GROOVY-4700, GROOVY-5207, GROOVY-9554
+            visitClosureExpression(currentClosure);
+            currentClosure = old;
+        } else if (expr instanceof VariableExpression) {
             VariableExpression ve = (VariableExpression) expr;
-            if (ve.getName().equals(variableName)) {
+            // only need to check the variable name because the Groovy 
compiler already fails if a variable
+            // with the same name exists in the scope; this means a closure 
cannot shadow a class variable
+            if (insideScriptBody && currentClosure != null && 
ve.getName().equals(variableName)) {
                 adjustToClassVar(ve);
-                return ve;
             }
-        } else if (currentAIC != null && expr instanceof 
ArgumentListExpression) {
-            // if a match is found, the compiler will have already set up aic 
constructor to hav
-            // an argument which isn't needed since we'll be accessing the 
field; we must undo it
-            Expression skip = null;
-            List<Expression> origArgList = ((ArgumentListExpression) 
expr).getExpressions();
-            for (int i = 0; i < origArgList.size(); i++) {
-                Expression arg = origArgList.get(i);
-                if (matchesCandidate(arg)) {
-                    skip = arg;
-                    adjustConstructorAndFields(i, currentAIC.getType());
-                    break;
+        } else if (expr instanceof ConstructorCallExpression) {
+            ConstructorCallExpression cce = (ConstructorCallExpression) expr;
+            if (insideScriptBody && cce.isUsingAnonymousInnerClass()) {
+                // if a match is found, the compiler will have already set up 
AIC constructor to have
+                // an argument which isn't needed since we'll be accessing the 
field; we must undo it
+                List<Expression> arguments = ((ArgumentListExpression) 
cce.getArguments()).getExpressions();
+                for (int i = 0, n = arguments.size(); i < n; i += 1) { 
Expression argument = arguments.get(i);
+                    if (matchesCandidate(argument)) { // GROOVY-8112
+                        adjustConstructorAndFields(i, cce.getType());
+
+                        var copy = new 
ConstructorCallExpression(cce.getType(), adjustedArgList(argument, arguments));
+                        copy.setUsingAnonymousInnerClass(true);
+                        copy.setSourcePosition(cce);
+                        copy.copyNodeMetaData(cce);
+                        return copy;
+                    }
                 }
             }
-            if (skip != null) {
-                return adjustedArgList(skip, origArgList);
-            }
         }
         return expr.transformExpression(this);
     }
 
-    private boolean matchesCandidate(Expression arg) {
-        return arg instanceof VariableExpression && ((VariableExpression) 
arg).getAccessedVariable() == 
candidate.getVariableExpression().getAccessedVariable();
+    private boolean matchesCandidate(final Expression expr) {
+        return expr instanceof VariableExpression && ((VariableExpression) 
expr).getAccessedVariable() == 
candidate.getVariableExpression().getAccessedVariable();
     }
 
-    private Expression adjustedArgList(Expression skip, List<Expression> 
origArgs) {
-        List<Expression> newArgs = new ArrayList<Expression>(origArgs.size() - 
1);
-        for (Expression origArg : origArgs) {
-            if (skip != origArg) {
-                newArgs.add(origArg);
-            }
+    private void adjustToClassVar(final VariableExpression expr) {
+        expr.setAccessedVariable(fieldNode);
+        VariableScope variableScope = currentClosure.getVariableScope();
+        Iterator<Variable> iterator = 
variableScope.getReferencedLocalVariablesIterator();
+        while (iterator.hasNext()) {
+            Variable next = iterator.next();
+            if (next.getName().equals(variableName)) iterator.remove();
         }
-        return new ArgumentListExpression(newArgs);
+        variableScope.putReferencedClassVariable(fieldNode);
     }
 
-    private void adjustConstructorAndFields(int skipIndex, ClassNode type) {
+    private void adjustConstructorAndFields(final int skipIndex, final 
ClassNode type) {
         List<ConstructorNode> constructors = type.getDeclaredConstructors();
         if (constructors.size() == 1) {
             ConstructorNode constructor = constructors.get(0);
@@ -232,59 +253,13 @@ public class FieldASTTransformation extends 
ClassCodeExpressionTransformer imple
         }
     }
 
-    private void adjustToClassVar(VariableExpression expr) {
-        // we only need to check the variable name because the Groovy compiler
-        // already fails if a variable with the same name already exists in 
the scope.
-        // this means that a closure cannot shadow a class variable
-        expr.setAccessedVariable(fieldNode);
-        final VariableScope variableScope = currentClosure.getVariableScope();
-        final Iterator<Variable> iterator = 
variableScope.getReferencedLocalVariablesIterator();
-        while (iterator.hasNext()) {
-            Variable next = iterator.next();
-            if (next.getName().equals(variableName)) iterator.remove();
-        }
-        variableScope.putReferencedClassVariable(fieldNode);
-    }
-
-    @Override
-    public void visitClosureExpression(final ClosureExpression expression) {
-        ClosureExpression old = currentClosure;
-        currentClosure = expression;
-        super.visitClosureExpression(expression);
-        currentClosure = old;
-    }
-
-    @Override
-    public void visitConstructorCallExpression(final ConstructorCallExpression 
cce) {
-        if (!insideScriptBody || !cce.isUsingAnonymousInnerClass()) return;
-        ConstructorCallExpression old = currentAIC;
-        currentAIC = cce;
-        Expression newArgs = transform(cce.getArguments());
-        if (cce.getArguments() instanceof TupleExpression && newArgs 
instanceof TupleExpression) {
-            List<Expression> argList = ((TupleExpression) 
cce.getArguments()).getExpressions();
-            argList.clear();
-            argList.addAll(((TupleExpression) newArgs).getExpressions());
+    private Expression adjustedArgList(final Expression skip, final 
List<Expression> origArgs) {
+        List<Expression> newArgs = new ArrayList<>(origArgs.size() - 1);
+        for (Expression origArg : origArgs) {
+            if (skip != origArg) {
+                newArgs.add(transform(origArg));
+            }
         }
-        currentAIC = old;
-    }
-
-    @Override
-    public void visitMethod(MethodNode node) {
-        boolean oldInsideScriptBody = insideScriptBody;
-        if (node.isScriptBody()) insideScriptBody = true;
-        super.visitMethod(node);
-        insideScriptBody = oldInsideScriptBody;
-    }
-
-    @Override
-    public void visitExpressionStatement(ExpressionStatement es) {
-        Expression exp = es.getExpression();
-        exp.visit(this);
-        super.visitExpressionStatement(es);
-    }
-
-    @Override
-    protected SourceUnit getSourceUnit() {
-        return sourceUnit;
+        return new ArgumentListExpression(newArgs);
     }
 }
diff --git a/src/test/org/codehaus/groovy/transform/FieldTransformTest.groovy 
b/src/test/org/codehaus/groovy/transform/FieldTransformTest.groovy
index 05f80ab210..acb20fa6c5 100644
--- a/src/test/org/codehaus/groovy/transform/FieldTransformTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/FieldTransformTest.groovy
@@ -160,7 +160,7 @@ final class FieldTransformTest {
     }
 
     @Test // GROOVY-4700
-    void testFieldShouldBeAccessibleFromClosureWithoutAssignment() {
+    void testFieldShouldBeAccessibleFromClosure2() {
         assertScript shell, '''
             @Field xxx = 3
             foo = {
@@ -171,7 +171,7 @@ final class FieldTransformTest {
     }
 
     @Test // GROOVY-5207
-    void testFieldShouldBeAccessibleFromClosureForExternalClosures() {
+    void testFieldShouldBeAccessibleFromClosure3() {
         assertScript shell, '''
             @Field xxx = [:]
             @Field static yyy = [:]
@@ -184,6 +184,22 @@ final class FieldTransformTest {
         '''
     }
 
+    @Test // GROOVY-9554
+    void testFieldShouldBeAccessibleFromClosure4() {
+        assertScript shell, '''
+            @Field String abc
+            binding.variables.clear()
+            abc = 'abc'
+            assert !binding.hasVariable('abc')
+            ['D','E','F'].each {
+                abc += it
+            }
+            assert !binding.hasVariable('abc')
+            assert this.@abc == 'abcDEF'
+            assert abc == 'abcDEF'
+        '''
+    }
+
     @Test
     void testStaticFieldShouldBeAccessibleFromClosure() {
         assertScript shell, '''
@@ -263,19 +279,15 @@ final class FieldTransformTest {
         '''
     }
 
-    @Test // GROOVY-9554
-    void testClosureReferencesToField() {
+    @Test // GROOVY-11567
+    void testAnonymousInnerClassReferencesToField2() {
         assertScript shell, '''
-            @Field String abc
-            binding.variables.clear()
-            abc = 'abc'
-            assert !binding.hasVariable('abc')
-            ['D','E','F'].each {
-                abc += it
-            }
-            assert !binding.hasVariable('abc')
-            assert this.@abc == 'abcDEF'
-            assert abc == 'abcDEF'
+            @Field String foo = 'bar'
+            assert({ ->
+                new Object() {
+                    String toString() { foo + 'baz' }
+                }
+            }.call().toString() == 'barbaz')
         '''
     }
 

Reply via email to