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 61f6fd02b6 GROOVY-11492, GROOVY-11866: curly brace array for 
annotation attribute
61f6fd02b6 is described below

commit 61f6fd02b6426d4cdda82fc86a9d2b488deb7512
Author: Eric Milles <[email protected]>
AuthorDate: Sun Mar 1 10:15:09 2026 -0600

    GROOVY-11492, GROOVY-11866: curly brace array for annotation attribute
---
 .../groovy/classgen/AnnotationVisitor.java         | 44 ++++++++++++++--------
 .../groovy/gls/annotations/AnnotationTest.groovy   | 29 ++++++++++++++
 2 files changed, 57 insertions(+), 16 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java 
b/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java
index 352e941ff5..8f12c52051 100644
--- a/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java
+++ b/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java
@@ -32,6 +32,8 @@ import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.ListExpression;
 import org.codehaus.groovy.ast.expr.PropertyExpression;
 import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
 import org.codehaus.groovy.ast.stmt.ReturnStatement;
 import org.codehaus.groovy.control.ErrorCollector;
 import org.codehaus.groovy.control.SourceUnit;
@@ -41,6 +43,7 @@ import java.util.List;
 import java.util.Map;
 
 import static 
org.apache.groovy.ast.tools.ExpressionUtils.transformInlineConstants;
+import static org.codehaus.groovy.ast.tools.ClosureUtils.hasImplicitParameter;
 
 /**
  * An Annotation visitor responsible for:
@@ -89,8 +92,17 @@ public class AnnotationVisitor {
         for (Map.Entry<String, Expression> entry : 
node.getMembers().entrySet()) {
             String attrName = entry.getKey();
             ClassNode attrType = getAttributeType(node, attrName);
-            Expression attrExpr = transformInlineConstants(entry.getValue(), 
attrType);
+
+            Expression attrExpr = entry.getValue();
+            // GROOVY-11492: @Type(name={}) and @Type(name={""}) cannot be 
converted sooner
+            if (attrType.isArray() && 
!ClassHelper.isClassType(attrType.getComponentType())
+                    && attrExpr instanceof ClosureExpression c && 
hasImplicitParameter(c) && c.getCode() instanceof BlockStatement block) {
+                attrExpr = new 
ListExpression(block.getStatements().stream().map(s -> ((ExpressionStatement) 
s).getExpression()).toList());
+                attrExpr.setSourcePosition(c);
+            }
+            attrExpr = transformInlineConstants(attrExpr, attrType);
             entry.setValue(attrExpr);
+
             visitExpression(attrName, attrExpr, attrType);
         }
         VMPluginFactory.getPlugin().configureAnnotation(node);
@@ -160,37 +172,37 @@ public class AnnotationVisitor {
 
     protected void visitExpression(final String attrName, final Expression 
valueExpr, final ClassNode attrType) {
         if (attrType.isArray()) {
-            // check needed as @Test(attr = {"elem"}) passes through the parser
-            if (valueExpr instanceof ListExpression le) {
-                visitListExpression(attrName, le, attrType.getComponentType());
-            } else if (valueExpr instanceof ClosureExpression) {
-                addError("Annotation list attributes must use Groovy notation 
[el1, el2]", valueExpr);
-            } else {
+            ClassNode itemType = attrType.getComponentType();
+            if (valueExpr instanceof ListExpression listExpr) {
+                visitListExpression(attrName, listExpr, itemType);
+            } else if (!itemType.isArray()) {
                 // treat like a singleton list as per Java
-                ListExpression listExp = new ListExpression();
-                listExp.addExpression(valueExpr);
+                var listExpr = new ListExpression();
+                listExpr.addExpression(valueExpr);
                 if (annotation != null) {
-                    annotation.setMember(attrName, listExp);
+                    annotation.setMember(attrName, listExpr);
                 }
-                visitExpression(attrName, listExp, attrType);
+                visitListExpression(attrName, listExpr, itemType);
+            } else {
+                addError("Array-based attributes must use array or list 
notation", valueExpr);
             }
         } else if (ClassHelper.isPrimitiveType(attrType) || 
ClassHelper.isStringType(attrType)) {
             visitConstantExpression(attrName, getConstantExpression(valueExpr, 
attrType), ClassHelper.getWrapper(attrType));
         } else if (ClassHelper.isClassType(attrType)) {
             if (!(valueExpr instanceof ClassExpression || valueExpr instanceof 
ClosureExpression)) {
-                addError("Only classes, closures, method references and method 
pointers can be used for attribute '" + attrName + "'", valueExpr);
+                addError("Only classes, closures, and method references can be 
used for attribute '" + attrName + "'", valueExpr);
             }
         } else if (attrType.isDerivedFrom(ClassHelper.Enum_Type)) {
-            if (valueExpr instanceof PropertyExpression) {
-                visitEnumExpression(attrName, (PropertyExpression) valueExpr, 
attrType);
+            if (valueExpr instanceof PropertyExpression propExpr) {
+                visitEnumExpression(attrName, propExpr, attrType);
             } else if (valueExpr instanceof ConstantExpression) {
                 visitConstantExpression(attrName, 
getConstantExpression(valueExpr, attrType), attrType);
             } else {
                 addError("Expected enum value for attribute " + attrName, 
valueExpr);
             }
         } else if (isValidAnnotationClass(attrType)) {
-            if (valueExpr instanceof AnnotationConstantExpression) {
-                visitAnnotationExpression(attrName, 
(AnnotationConstantExpression) valueExpr, attrType);
+            if (valueExpr instanceof AnnotationConstantExpression annoExpr) {
+                visitAnnotationExpression(attrName, annoExpr, attrType);
             } else {
                 addError("Expected annotation of type '" + attrType.getName() 
+ "' for attribute " + attrName, valueExpr);
             }
diff --git a/src/test/groovy/gls/annotations/AnnotationTest.groovy 
b/src/test/groovy/gls/annotations/AnnotationTest.groovy
index 5b56d883ad..dd443012cc 100644
--- a/src/test/groovy/gls/annotations/AnnotationTest.groovy
+++ b/src/test/groovy/gls/annotations/AnnotationTest.groovy
@@ -759,6 +759,35 @@ final class AnnotationTest {
         '''
     }
 
+    // GROOVY-11492
+    @ParameterizedTest
+    @ValueSource(strings=['','"a"','"a","b"','"a","b",'])
+    void testAttributeValueConstants13(String values) {
+        assertScript shell, """
+            @Retention(RetentionPolicy.RUNTIME)
+            @Target(ElementType.TYPE)
+            @interface A {
+                String[] value()
+            }
+
+            @A({$values}) class B { }
+            String[] array = [$values]
+            assert B.getAnnotation(A).value() == array
+        """
+
+        assertScript shell, """
+            @Retention(RetentionPolicy.RUNTIME)
+            @Target(ElementType.TYPE)
+            @interface A {
+                String[] values()
+            }
+
+            @A(values={$values}) class B { }
+            String[] array = [$values]
+            assert B.getAnnotation(A).values() == array
+        """
+    }
+
     @Test
     void testRuntimeRetentionAtAllLevels() {
         assertScript shell, '''

Reply via email to