Skip to content

Commit

Permalink
GROOVY-11432: Support method references/method pointers in annotation…
Browse files Browse the repository at this point in the history
… attributes
  • Loading branch information
paulk-asert committed Jul 23, 2024
1 parent caaf68c commit a16f1a9
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 3 deletions.
10 changes: 9 additions & 1 deletion src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@
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;
Expand Down Expand Up @@ -4229,7 +4231,13 @@ public AnnotationNode visitAnnotation(final AnnotationContext ctx) {
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ protected void visitExpression(final String attrName, final Expression valueExpr
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15448,6 +15448,17 @@ public static <T, U extends T, V extends T> T with(
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

Expand Down
2 changes: 1 addition & 1 deletion src/test/groovy/inspect/InspectorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ public void testMetaMethods() {
"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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

0 comments on commit a16f1a9

Please sign in to comment.