You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2022/02/10 20:22:49 UTC

[groovy] branch master updated: refactor category transform

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 fbc7120  refactor category transform
fbc7120 is described below

commit fbc7120480d45219c2139ca063757eb4f8439913
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Thu Feb 10 14:11:52 2022 -0600

    refactor category transform
---
 .../transform/CategoryASTTransformation.java       | 280 +++++++++------------
 1 file changed, 118 insertions(+), 162 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/transform/CategoryASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/CategoryASTTransformation.java
index 2afa263..26b1dea 100644
--- a/src/main/java/org/codehaus/groovy/transform/CategoryASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/CategoryASTTransformation.java
@@ -33,22 +33,18 @@ import org.codehaus.groovy.ast.expr.ClosureExpression;
 import org.codehaus.groovy.ast.expr.DeclarationExpression;
 import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.PropertyExpression;
-import org.codehaus.groovy.ast.expr.TupleExpression;
 import org.codehaus.groovy.ast.expr.VariableExpression;
 import org.codehaus.groovy.ast.stmt.BlockStatement;
 import org.codehaus.groovy.ast.stmt.CatchStatement;
 import org.codehaus.groovy.ast.stmt.ExpressionStatement;
 import org.codehaus.groovy.ast.stmt.ForStatement;
 import org.codehaus.groovy.classgen.VariableScopeVisitor;
-import org.codehaus.groovy.control.CompilePhase;
 import org.codehaus.groovy.control.SourceUnit;
-import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
 import org.codehaus.groovy.syntax.SyntaxException;
 import org.objectweb.asm.Opcodes;
 
 import java.util.HashSet;
 import java.util.LinkedList;
-import java.util.List;
 import java.util.Set;
 
 import static org.codehaus.groovy.ast.tools.ClosureUtils.getParametersSafe;
@@ -65,99 +61,131 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
  * <li>references to 'this' changed to the additional 'self' parameter</li>
  * </ul>
  */
-@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
-public class CategoryASTTransformation implements ASTTransformation, Opcodes {
-    // should not use a static variable because of possible changes to node metadata
-    // which would be visible to other compilation units
-    private final VariableExpression thisExpression = createThisExpression();
+@GroovyASTTransformation
+public class CategoryASTTransformation implements ASTTransformation {
 
-    private static VariableExpression createThisExpression() {
-        VariableExpression expr = new VariableExpression("$this");
-        expr.setClosureSharedVariable(true);
-        return expr;
-    }
-
-    /**
-     * Property invocations done on 'this' reference are transformed so that the invocations at runtime are
-     * done on the additional parameter 'self'
-     */
     @Override
-    public void visit(ASTNode[] nodes, final SourceUnit source) {
+    public void visit(final ASTNode[] nodes, final SourceUnit sourceUnit) {
         if (nodes.length != 2 || !(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof ClassNode)) {
-            source.getErrorCollector().addError(
-                    new SyntaxErrorMessage(new SyntaxException("@Category can only be added to a ClassNode but got: " + (nodes.length==2?nodes[1]:"nothing"),
-                        nodes[0].getLineNumber(), nodes[0].getColumnNumber()), source));
+            sourceUnit.addError(new SyntaxException("@Category can only be added to a ClassNode but got: " + (nodes.length == 2 ? nodes[1] : "nothing"), nodes[0].getLineNumber(), nodes[0].getColumnNumber()));
         }
 
-        AnnotationNode annotation = (AnnotationNode) nodes[0];
-        ClassNode parent = (ClassNode) nodes[1];
-
-        ClassNode targetClass = getTargetClass(source, annotation);
-        thisExpression.setType(targetClass);
+        ClassNode sourceClass = (ClassNode) nodes[1];
+        ClassNode targetClass; // the declared type of "self"
+        Expression value = ((AnnotationNode) nodes[0]).getMember("value");
+        if (value instanceof ClassExpression) {
+            targetClass = value.getType();
+        } else {
+            targetClass = ClassHelper.OBJECT_TYPE; // TODO: ClassHelper.make((Class<?>)groovy.lang.Category.class.getMethod("value").getDefaultValue());
+            sourceUnit.addErrorAndContinue(new SyntaxException("@Category must define 'value' which is the class to apply this category to", nodes[0]));
+        }
 
-        final LinkedList<Set<String>> varStack = new LinkedList<Set<String>>();
-        if (!ensureNoInstanceFieldOrProperty(source, parent)) return;
+        if (ensureNoInstanceFieldOrProperty(sourceClass, sourceUnit)) {
+            transformReferencesToThis(targetClass, sourceClass, sourceUnit);
+            new VariableScopeVisitor(sourceUnit, true).visitClass(sourceClass);
+        }
+    }
 
-        Set<String> names = new HashSet<String>();
-        for (FieldNode field : parent.getFields()) {
-            names.add(field.getName());
+    private static boolean ensureNoInstanceFieldOrProperty(final ClassNode sourceClass, final SourceUnit sourceUnit) {
+        boolean valid = true;
+        for (FieldNode fieldNode : sourceClass.getFields()) {
+            if (!fieldNode.isStatic() && fieldNode.getLineNumber() > 0) { // if < 1, probably an AST transform or internal code (like generated metaclass field, ...)
+                addUnsupportedInstanceMemberError(fieldNode.getName(), fieldNode,  sourceUnit);
+                valid = false;
+            }
         }
-        for (PropertyNode field : parent.getProperties()) {
-            names.add(field.getName());
+        for (PropertyNode propertyNode : sourceClass.getProperties()) {
+            if (!propertyNode.isStatic() && propertyNode.getLineNumber() > 0) { // if < 1, probably an AST transform or internal code (like generated metaclass field, ...)
+                addUnsupportedInstanceMemberError(propertyNode.getName(), propertyNode, sourceUnit);
+                valid = false;
+            }
         }
+        return valid;
+    }
+
+    private static void addUnsupportedInstanceMemberError(final String name, final ASTNode node, final SourceUnit unit) {
+        unit.addErrorAndContinue(new SyntaxException("The @Category transformation does not support instance " + (node instanceof FieldNode ? "fields" : "properties") + " but found [" + name + "]", node));
+    }
+
+    //--------------------------------------------------------------------------
+
+    private void transformReferencesToThis(final ClassNode targetClass, final ClassNode sourceClass, final SourceUnit sourceUnit) {
+        final Reference<Parameter> selfParameter = new Reference<>();
+        final LinkedList<Set<String>> varStack = new LinkedList<>();
+
+        Set<String> names = new HashSet<>();
+        for (FieldNode fn : sourceClass.getFields()) names.add(fn.getName());
+        for (PropertyNode pn : sourceClass.getProperties()) names.add(pn.getName());
         varStack.add(names);
 
-        final Reference parameter = new Reference();
-        final ClassCodeExpressionTransformer expressionTransformer = new ClassCodeExpressionTransformer() {
-            @Override
-            protected SourceUnit getSourceUnit() {
-                return source;
+        ClassCodeExpressionTransformer transformer = new ClassCodeExpressionTransformer() {
+            private void addVariablesToStack(final Parameter[] parameter) {
+                Set<String> names = new HashSet<>(varStack.getLast());
+                for (Parameter p : parameter) names.add(p.getName());
+                varStack.add(names);
             }
 
-            private void addVariablesToStack(Parameter[] params) {
-                Set<String> names = new HashSet<String>(varStack.getLast());
-                for (Parameter param : params) {
-                    names.add(param.getName());
-                }
-                varStack.add(names);
+            private Expression createThisExpression() {
+                VariableExpression ve = new VariableExpression("$this", targetClass);
+                ve.setClosureSharedVariable(true);
+                return ve;
             }
 
             @Override
-            public void visitCatchStatement(CatchStatement statement) {
-                varStack.getLast().add(statement.getVariable().getName());
-                super.visitCatchStatement(statement);
-                varStack.getLast().remove(statement.getVariable().getName());
+            protected SourceUnit getSourceUnit() {
+                return sourceUnit;
             }
 
             @Override
-            public void visitMethod(MethodNode node) {
-                addVariablesToStack(node.getParameters());
-                super.visitMethod(node);
-                varStack.removeLast();
+            public Expression transform(final Expression expression) {
+                if (expression instanceof VariableExpression) {
+                    VariableExpression ve = (VariableExpression) expression;
+                    if (ve.isThisExpression()) {
+                        Expression thisExpression = createThisExpression();
+                        thisExpression.setSourcePosition(ve);
+                        return thisExpression;
+                    } else if (!ve.isSuperExpression() && !varStack.getLast().contains(ve.getName())) {
+                        PropertyExpression pe = new PropertyExpression(createThisExpression(), ve.getName());
+                        pe.setSourcePosition(ve);
+                        return pe;
+                    }
+                } else if (expression instanceof ClosureExpression) {
+                    ClosureExpression ce = (ClosureExpression) expression;
+                    addVariablesToStack(hasImplicitParameter(ce) ? params(param(ClassHelper.OBJECT_TYPE, "it")) : getParametersSafe(ce));
+                    ce.getVariableScope().putReferencedLocalVariable(selfParameter.get());
+                    ce.getCode().visit(this);
+                    varStack.removeLast();
+                }
+                return super.transform(expression);
             }
 
             @Override
-            public void visitBlockStatement(BlockStatement block) {
-                Set<String> names = new HashSet<String>(varStack.getLast());
+            public void visitBlockStatement(final BlockStatement statement) {
+                Set<String> names = new HashSet<>(varStack.getLast());
                 varStack.add(names);
-                super.visitBlockStatement(block);
+                super.visitBlockStatement(statement);
                 varStack.remove(names);
             }
 
             @Override
-            public void visitClosureExpression(ClosureExpression ce) {
-                addVariablesToStack(getParametersSafe(ce));
-                super.visitClosureExpression(ce);
+            public void visitCatchStatement(final CatchStatement statement) {
+                varStack.getLast().add(statement.getVariable().getName());
+                super.visitCatchStatement(statement);
+                varStack.getLast().remove(statement.getVariable().getName());
+            }
+
+            @Override
+            public void visitClosureExpression(final ClosureExpression expression) {
+                addVariablesToStack(getParametersSafe(expression));
+                super.visitClosureExpression(expression);
                 varStack.removeLast();
             }
 
             @Override
-            public void visitDeclarationExpression(DeclarationExpression expression) {
+            public void visitDeclarationExpression(final DeclarationExpression expression) {
                 if (expression.isMultipleAssignmentDeclaration()) {
-                    TupleExpression te = expression.getTupleExpression();
-                    List<Expression> list = te.getExpressions();
-                    for (Expression arg : list) {
-                        VariableExpression ve = (VariableExpression) arg;
+                    for (Expression e : expression.getTupleExpression().getExpressions()) {
+                        VariableExpression ve = (VariableExpression) e;
                         varStack.getLast().add(ve.getName());
                     }
                 } else {
@@ -168,120 +196,48 @@ public class CategoryASTTransformation implements ASTTransformation, Opcodes {
             }
 
             @Override
-            public void visitForLoop(ForStatement forLoop) {
-                Expression exp = forLoop.getCollectionExpression();
-                exp.visit(this);
-                Parameter loopParam = forLoop.getVariable();
-                if (loopParam != null) {
-                    varStack.getLast().add(loopParam.getName());
+            public void visitExpressionStatement(final ExpressionStatement statement) {
+                // GROOVY-3543: visit the declaration expressions so that declaration variables get added on the varStack
+                if (statement.getExpression() instanceof DeclarationExpression) {
+                    statement.getExpression().visit(this);
                 }
-                super.visitForLoop(forLoop);
+                super.visitExpressionStatement(statement);
             }
 
             @Override
-            public void visitExpressionStatement(ExpressionStatement es) {
-                // GROOVY-3543: visit the declaration expressions so that declaration variables get added on the varStack
-                Expression exp = es.getExpression();
-                if (exp instanceof DeclarationExpression) {
-                    exp.visit(this);
+            public void visitForLoop(final ForStatement statement) {
+                Expression exp = statement.getCollectionExpression();
+                exp.visit(this);
+                Parameter loopParam = statement.getVariable();
+                if (loopParam != null) {
+                    varStack.getLast().add(loopParam.getName());
                 }
-                super.visitExpressionStatement(es);
+                super.visitForLoop(statement);
             }
 
             @Override
-            public Expression transform(Expression exp) {
-                if (exp instanceof VariableExpression) {
-                    VariableExpression ve = (VariableExpression) exp;
-                    if (ve.getName().equals("this"))
-                        return thisExpression;
-                    else {
-                        if (!varStack.getLast().contains(ve.getName())) {
-                            PropertyExpression transformed = new PropertyExpression(thisExpression, ve.getName());
-                            transformed.setSourcePosition(ve);
-                            return transformed;
-                        }
-                    }
-                } else if (exp instanceof PropertyExpression) {
-                    PropertyExpression pe = (PropertyExpression) exp;
-                    if (pe.getObjectExpression() instanceof VariableExpression) {
-                        VariableExpression vex = (VariableExpression) pe.getObjectExpression();
-                        if (vex.isThisExpression()) {
-                            pe.setObjectExpression(thisExpression);
-                            return pe;
-                        }
-                    }
-                } else if (exp instanceof ClosureExpression) {
-                    ClosureExpression ce = (ClosureExpression) exp;
-                    ce.getVariableScope().putReferencedLocalVariable((Parameter) parameter.get());
-                    addVariablesToStack(
-                            hasImplicitParameter(ce)
-                                    ? params(param(ClassHelper.OBJECT_TYPE, "it"))
-                                    : getParametersSafe(ce));
-                    ce.getCode().visit(this);
-                    varStack.removeLast();
-                }
-                return super.transform(exp);
+            public void visitMethod(final MethodNode node) {
+                addVariablesToStack(node.getParameters());
+                super.visitMethod(node);
+                varStack.removeLast();
             }
         };
 
-        for (MethodNode method : parent.getMethods()) {
+        for (MethodNode method : sourceClass.getMethods()) {
             if (!method.isStatic()) {
-                method.setModifiers(method.getModifiers() | Opcodes.ACC_STATIC);
-                final Parameter[] origParams = method.getParameters();
-                final Parameter[] newParams = new Parameter[origParams.length + 1];
                 Parameter p = new Parameter(targetClass, "$this");
                 p.setClosureSharedVariable(true);
-                newParams[0] = p;
-                parameter.set(p);
-                System.arraycopy(origParams, 0, newParams, 1, origParams.length);
-                method.setParameters(newParams);
+                selfParameter.set(p);
 
-                expressionTransformer.visitMethod(method);
-            }
-        }
-        new VariableScopeVisitor(source, true).visitClass(parent);
-    }
+                Parameter[] oldParams = method.getParameters();
+                Parameter[] newParams = new Parameter[oldParams.length + 1];
+                newParams[0] = p;
+                System.arraycopy(oldParams, 0, newParams, 1, oldParams.length);
 
-    private static boolean ensureNoInstanceFieldOrProperty(final SourceUnit source, final ClassNode parent) {
-        boolean valid = true;
-        for (FieldNode fieldNode : parent.getFields()) {
-            if (!fieldNode.isStatic() && fieldNode.getLineNumber()>0) {
-                // if <0, probably an AST transform or internal code (like generated metaclass field, ...)
-                addUnsupportedError(fieldNode,  source);
-                valid = false;
-            }
-        }
-        for (PropertyNode propertyNode : parent.getProperties()) {
-            if (!propertyNode.isStatic() && propertyNode.getLineNumber()>0) {
-                // if <0, probably an AST transform or internal code (like generated metaclass field, ...)
-                addUnsupportedError(propertyNode, source);
-                valid = false;
+                method.setModifiers(method.getModifiers() | Opcodes.ACC_STATIC);
+                method.setParameters(newParams);
+                transformer.visitMethod(method);
             }
         }
-        return valid;
-    }
-
-    private static void addUnsupportedError(ASTNode node, SourceUnit unit) {
-        unit.getErrorCollector().addErrorAndContinue("The @Category transformation does not support instance "+
-                (node instanceof FieldNode?"fields":"properties")
-                + " but found ["+getName(node)+"]", node, unit);
-    }
-
-    private static String getName(ASTNode node) {
-        if (node instanceof FieldNode) return ((FieldNode) node).getName();
-        if (node instanceof PropertyNode) return ((PropertyNode) node).getName();
-        return node.getText();
-    }
-
-    private static ClassNode getTargetClass(SourceUnit source, AnnotationNode annotation) {
-        Expression value = annotation.getMember("value");
-        if (!(value instanceof ClassExpression)) {
-            //noinspection ThrowableInstanceNeverThrown
-            source.getErrorCollector().addErrorAndContinue("@groovy.lang.Category must define 'value' which is the class to apply this category to", annotation, source);
-            return null;
-        } else {
-            ClassExpression ce = (ClassExpression) value;
-            return ce.getType();
-        }
     }
 }