You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2024/07/23 04:30:07 UTC

(groovy) branch master updated: GROOVY-11432: Support method references/method pointers in annotation attributes

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

paulk 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 a16f1a9ce6 GROOVY-11432: Support method references/method pointers in annotation attributes
a16f1a9ce6 is described below

commit a16f1a9ce6291ee6330427d8f0f22a392252f90e
Author: Paul King <pa...@asert.com.au>
AuthorDate: Thu Jul 4 20:59:59 2024 +1000

    GROOVY-11432: Support method references/method pointers in annotation attributes
---
 .../apache/groovy/parser/antlr4/AstBuilder.java    | 10 ++-
 .../groovy/classgen/AnnotationVisitor.java         |  2 +-
 .../groovy/runtime/DefaultGroovyMethods.java       | 11 ++++
 src/test/groovy/inspect/InspectorTest.java         |  2 +-
 .../transform/TupleConstructorTransformTest.groovy | 72 ++++++++++++++++++++++
 5 files changed, 94 insertions(+), 3 deletions(-)

diff --git a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
index be763c47bd..dc457b76a1 100644
--- a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
+++ b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
@@ -156,6 +156,8 @@ import static org.apache.groovy.parser.antlr4.GroovyParser.*;
 import static org.apache.groovy.parser.antlr4.util.PositionConfigureUtils.configureAST;
 import static org.apache.groovy.parser.antlr4.util.PositionConfigureUtils.configureEndPosition;
 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.callThisX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.closureX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
@@ -4229,7 +4231,13 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
         AnnotationNode annotationNode = new AnnotationNode(makeClassNode(annotationName));
         List<Tuple2<String, Expression>> annotationElementValues = this.visitElementValues(ctx.elementValues());
 
-        annotationElementValues.forEach(e -> annotationNode.addMember(e.getV1(), e.getV2()));
+        annotationElementValues.forEach(e -> {
+            Expression v2 = e.getV2();
+            if (v2 instanceof MethodPointerExpression) {
+                v2 = closureX(Parameter.EMPTY_ARRAY, block(stmt(callThisX("withMethodClosure", v2))));
+            }
+            annotationNode.addMember(e.getV1(), v2);
+        });
         configureAST(annotationNode.getClassNode(), ctx.annotationName());
         return configureAST(annotationNode, ctx);
     }
diff --git a/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java b/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java
index dd9790db4a..fc99c529e4 100644
--- a/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java
+++ b/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java
@@ -181,7 +181,7 @@ public class AnnotationVisitor {
             visitConstantExpression(attrName, getConstantExpression(valueExpr, attrType), ClassHelper.getWrapper(attrType));
         } else if (ClassHelper.isClassType(attrType)) {
             if (!(valueExpr instanceof ClassExpression || valueExpr instanceof ClosureExpression)) {
-                addError("Only classes and closures can be used for attribute '" + attrName + "'", valueExpr);
+                addError("Only classes, closures, method references and method pointers can be used for attribute '" + attrName + "'", valueExpr);
             }
         } else if (attrType.isDerivedFrom(ClassHelper.Enum_Type)) {
             if (valueExpr instanceof PropertyExpression) {
diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
index cd366b4e15..231920eb7d 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -15448,6 +15448,17 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
         return response.getV1();
     }
 
+    /**
+     * A utility method for calling a method closure on an object.
+     *
+     * @param self the object
+     * @param mc the method closure
+     * @return the result of calling the method closure
+     */
+    public static Object withMethodClosure(Object self, MethodClosure mc) {
+        return mc.call(self);
+    }
+
     //--------------------------------------------------------------------------
     // withCollectedKeys
 
diff --git a/src/test/groovy/inspect/InspectorTest.java b/src/test/groovy/inspect/InspectorTest.java
index 7942f67193..97becb731e 100644
--- a/src/test/groovy/inspect/InspectorTest.java
+++ b/src/test/groovy/inspect/InspectorTest.java
@@ -182,7 +182,7 @@ public class InspectorTest implements Serializable {
             "inspect", "invokeMethod", "is", "isCase", "isNotCase", "iterator", "metaClass", "print", "print",
             "printf", "printf", "println", "println", "println", "putAt", "respondsTo", "respondsTo",
             "setMetaClass", "split", "sprintf", "sprintf", "tap", "toString", "use", "use", "use", "with",
-            "with", "withTraits", "stream", "sleep", "sleep", "macro", "macro", "macro", "macro"
+            "with", "withMethodClosure", "withTraits", "stream", "sleep", "sleep", "macro", "macro", "macro", "macro"
         };
         assertEquals("Incorrect number of methods found examining: " + Arrays.stream(metaMethods)
                 .map(mm -> ((String[])mm)[Inspector.MEMBER_NAME_IDX]).collect(Collectors.toList()), names.length, metaMethods.length);
diff --git a/src/test/org/codehaus/groovy/transform/TupleConstructorTransformTest.groovy b/src/test/org/codehaus/groovy/transform/TupleConstructorTransformTest.groovy
index a50a986125..b790f9208c 100644
--- a/src/test/org/codehaus/groovy/transform/TupleConstructorTransformTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/TupleConstructorTransformTest.groovy
@@ -622,4 +622,76 @@ final class TupleConstructorTransformTest {
             assert string == 'Foo(x:3, y:3, z:3)'
         '''
     }
+
+    // GROOVY-11432
+    @Test
+    void testMethodPointerInAnnotation() {
+        assertScript shell, '''
+        @TupleConstructor(post=Person.&validate)
+        class Person {
+            final String first, last
+            void validate() { assert first && last }
+        }
+
+        new Person('John', 'Smith')
+        '''
+
+        assertScript shell, '''
+        @TupleConstructor(post=Person.&validate)
+        @CompileStatic
+        class Person {
+            final String first, last
+            void validate() { assert first && last }
+        }
+
+        new Person('John', 'Smith')
+        '''
+
+        def err = shouldFail shell, '''
+        @TupleConstructor(post=Person.&validate)
+        class Person {
+            final String first, last
+            void validate() { assert first && last }
+        }
+
+        new Person('John', null)
+        '''
+        assert err instanceof AssertionError
+    }
+
+    // GROOVY-11432
+    @Test
+    void testMethodReferenceInAnnotation() {
+        assertScript shell, '''
+        @TupleConstructor(post=Person::validate)
+        class Person {
+            final String first, last
+            void validate() { assert first && last }
+        }
+
+        new Person('John', 'Smith')
+        '''
+
+        assertScript shell, '''
+        @TupleConstructor(post=Person::validate)
+        @CompileStatic
+        class Person {
+            final String first, last
+            void validate() { assert first && last }
+        }
+
+        new Person('John', 'Smith')
+        '''
+
+        def err = shouldFail shell, '''
+        @TupleConstructor(post=Person::validate)
+        class Person {
+            final String first, last
+            void validate() { assert first && last }
+        }
+
+        new Person('', 'Smith')
+        '''
+        assert err instanceof AssertionError
+    }
 }