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
+ }
}