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 c9f6d6f6a5 JSR 308: local variable type annotations
c9f6d6f6a5 is described below

commit c9f6d6f6a5914a3eee7c6ef4b83a607567f8e2af
Author: Eric Milles <[email protected]>
AuthorDate: Sat Mar 22 13:58:39 2025 -0500

    JSR 308: local variable type annotations
---
 src/antlr/GroovyParser.g4                          | 56 ++++++----------
 .../apache/groovy/parser/antlr4/AstBuilder.java    | 77 ++++++++--------------
 .../groovy/classgen/AsmClassGenerator.java         |  2 +-
 .../codehaus/groovy/classgen/ExtendedVerifier.java |  6 +-
 .../classgen/asm/BinaryExpressionHelper.java       |  1 +
 .../codehaus/groovy/classgen/asm/CompileStack.java | 24 ++++++-
 .../codehaus/groovy/control/ResolveVisitor.java    |  1 +
 .../groovy/classgen/asm/TypeAnnotationsTest.groovy | 31 ++++++++-
 8 files changed, 107 insertions(+), 91 deletions(-)

diff --git a/src/antlr/GroovyParser.g4 b/src/antlr/GroovyParser.g4
index a8bfc3e36b..af3566872f 100644
--- a/src/antlr/GroovyParser.g4
+++ b/src/antlr/GroovyParser.g4
@@ -325,60 +325,42 @@ variableInitializer
     :   enhancedStatementExpression
     ;
 
-emptyDims
-    :   (annotationsOpt LBRACK RBRACK)+
-    ;
-
-emptyDimsOpt
-    :   emptyDims?
-    ;
-
-standardType
-options { baseContext = type; }
+type
     :   annotationsOpt
         (
+            VOID // error
+        |
             primitiveType
         |
-            standardClassOrInterfaceType
+            referenceType
         )
-        emptyDimsOpt
+        dim0*
     ;
 
-type
-    :   annotationsOpt
-        (
-            (
-                primitiveType
-            |
-                // !!! Error Alternative !!!
-                 VOID
-            )
-        |
-                generalClassOrInterfaceType
-        )
-        emptyDimsOpt
+primitiveType
+    :   BuiltInPrimitiveType
     ;
 
-classOrInterfaceType
-    :   (   qualifiedClassName
-        |   qualifiedStandardClassName
-        ) typeArguments?
+referenceType
+    :   qualifiedClassName typeArguments?
     ;
 
-generalClassOrInterfaceType
-options { baseContext = classOrInterfaceType; }
-    :   qualifiedClassName typeArguments?
+standardType // see: returnType
+options { baseContext = type; }
+    :   annotationsOpt
+        (
+            primitiveType
+        |
+            standardClassOrInterfaceType
+        )
+        dim0*
     ;
 
 standardClassOrInterfaceType
-options { baseContext = classOrInterfaceType; }
+options { baseContext = referenceType; }
     :   qualifiedStandardClassName typeArguments?
     ;
 
-primitiveType
-    :   BuiltInPrimitiveType
-    ;
-
 typeArguments
     :   LT nls typeArgument (COMMA nls typeArgument)* nls GT
     ;
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 3655d4f3b6..ad2b5d4051 100644
--- a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
+++ b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
@@ -3939,27 +3939,6 @@ public class AstBuilder extends 
GroovyParserBaseVisitor<Object> {
                 .collect(Collectors.toList());
     }
 
-    @Override
-    public List<List<AnnotationNode>> visitEmptyDims(final EmptyDimsContext 
ctx) {
-        List<List<AnnotationNode>> dimList =
-                ctx.annotationsOpt().stream()
-                        .map(this::visitAnnotationsOpt)
-                        .collect(Collectors.toList());
-
-        Collections.reverse(dimList);
-
-        return dimList;
-    }
-
-    @Override
-    public List<List<AnnotationNode>> visitEmptyDimsOpt(final 
EmptyDimsOptContext ctx) {
-        if (!asBoolean(ctx.emptyDims())) {
-            return Collections.emptyList();
-        }
-
-        return this.visitEmptyDims(ctx.emptyDims());
-    }
-
     // type { 
------------------------------------------------------------------
 
     @Override
@@ -3968,49 +3947,56 @@ public class AstBuilder extends 
GroovyParserBaseVisitor<Object> {
             return ClassHelper.dynamicType();
         }
 
-        ClassNode classNode = null;
-
-        if (asBoolean(ctx.classOrInterfaceType())) {
-            if (isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR))
-                
ctx.classOrInterfaceType().putNodeMetaData(IS_INSIDE_INSTANCEOF_EXPR, 
Boolean.TRUE);
-            classNode = 
this.visitClassOrInterfaceType(ctx.classOrInterfaceType());
-        } else if (asBoolean(ctx.primitiveType())) {
-            classNode = this.visitPrimitiveType(ctx.primitiveType());
+        if (asBoolean(ctx.VOID())) {
+            throw createParsingFailedException("void is not allowed here", 
ctx);
         }
 
-        if (!asBoolean(classNode)) {
-            if (VOID_STR.equals(ctx.getText())) {
-                throw createParsingFailedException("void is not allowed here", 
ctx);
+        ClassNode classNode;
+
+        if (asBoolean(ctx.primitiveType())) {
+            classNode = this.visitPrimitiveType(ctx.primitiveType());
+        } else {
+            if (isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR)) {
+                ctx.referenceType().putNodeMetaData(IS_INSIDE_INSTANCEOF_EXPR, 
Boolean.TRUE);
             }
-            throw createParsingFailedException("Unsupported type: " + 
ctx.getText(), ctx);
+            classNode = this.visitReferenceType(ctx.referenceType());
         }
 
         
classNode.addTypeAnnotations(this.visitAnnotationsOpt(ctx.annotationsOpt()));
 
-        List<List<AnnotationNode>> dimList = 
this.visitEmptyDimsOpt(ctx.emptyDimsOpt());
-        if (asBoolean(dimList)) {
-            classNode = this.createArrayType(classNode, dimList);
+        if (asBoolean(ctx.dim0())) {
+            List<List<AnnotationNode>> typeAnnotations = new ArrayList<>();
+            for (var dim : ctx.dim0()) 
typeAnnotations.add(this.visitAnnotationsOpt(dim.annotationsOpt()));
+
+            classNode = this.createArrayType(classNode, typeAnnotations);
         }
 
         return configureAST(classNode, ctx);
     }
 
     @Override
-    public ClassNode visitClassOrInterfaceType(final 
ClassOrInterfaceTypeContext ctx) {
+    public ClassNode visitPrimitiveType(final PrimitiveTypeContext ctx) {
+        return 
configureAST(ClassHelper.make(ctx.getText()).getPlainNodeReference(false), ctx);
+    }
+
+    @Override
+    public ClassNode visitReferenceType(final ReferenceTypeContext ctx) {
         ClassNode classNode;
         if (asBoolean(ctx.qualifiedClassName())) {
-            if (isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR))
+            if (isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR)) {
                 
ctx.qualifiedClassName().putNodeMetaData(IS_INSIDE_INSTANCEOF_EXPR, 
Boolean.TRUE);
+            }
             classNode = this.visitQualifiedClassName(ctx.qualifiedClassName());
         } else {
-            if (isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR))
+            if (isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR)) {
                 
ctx.qualifiedStandardClassName().putNodeMetaData(IS_INSIDE_INSTANCEOF_EXPR, 
Boolean.TRUE);
+            }
             classNode = 
this.visitQualifiedStandardClassName(ctx.qualifiedStandardClassName());
         }
 
         if (asBoolean(ctx.typeArguments())) {
-            classNode.setGenericsTypes(
-                    this.visitTypeArguments(ctx.typeArguments()));
+            GenericsType[] generics = 
this.visitTypeArguments(ctx.typeArguments());
+            classNode.setGenericsTypes(generics);
         }
 
         return configureAST(classNode, ctx);
@@ -4069,11 +4055,6 @@ public class AstBuilder extends 
GroovyParserBaseVisitor<Object> {
         throw createParsingFailedException("Unsupported type argument: " + 
ctx.getText(), ctx);
     }
 
-    @Override
-    public ClassNode visitPrimitiveType(final PrimitiveTypeContext ctx) {
-        return 
configureAST(ClassHelper.make(ctx.getText()).getPlainNodeReference(false), ctx);
-    }
-
     // } type 
------------------------------------------------------------------
 
     @Override
@@ -4154,7 +4135,7 @@ public class AstBuilder extends 
GroovyParserBaseVisitor<Object> {
 
     @Override
     public List<AnnotationNode> visitAnnotationsOpt(final 
AnnotationsOptContext ctx) {
-        if (!asBoolean(ctx)) {
+        if (!asBoolean(ctx.annotation())) {
             return Collections.emptyList();
         }
 
@@ -4294,7 +4275,7 @@ public class AstBuilder extends 
GroovyParserBaseVisitor<Object> {
         ClassNode arrayType = elementType;
         for (int i = dimAnnotationsList.size() - 1; i >= 0; i -= 1) {
             arrayType = this.createArrayType(arrayType);
-            arrayType.addAnnotations(dimAnnotationsList.get(i));
+            arrayType.addTypeAnnotations(dimAnnotationsList.get(i));
         }
         return arrayType;
     }
diff --git a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java 
b/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
index 93736e8a72..9bd884ca42 100644
--- a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
+++ b/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
@@ -2215,7 +2215,7 @@ public class AsmClassGenerator extends ClassGenerator {
      * @param an the node with an annotation
      * @param av the visitor to use
      */
-    private void visitAnnotationAttributes(final AnnotationNode an, final 
AnnotationVisitor av) {
+    public void visitAnnotationAttributes(final AnnotationNode an, final 
AnnotationVisitor av) {
         Map<String, Object> constantAttrs = new HashMap<>();
         Map<String, PropertyExpression> enumAttrs = new HashMap<>();
         Map<String, Object> atAttrs = new HashMap<>();
diff --git a/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java 
b/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java
index 5e761c0240..c81f207d7b 100644
--- a/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java
+++ b/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java
@@ -226,7 +226,8 @@ public class ExtendedVerifier extends 
ClassCodeVisitorSupport {
     private void visitTypeAnnotations(final ClassNode node) {
         if ((node.isRedirectNode() || node.isPrimaryClassNode()) && 
!Boolean.TRUE.equals(node.putNodeMetaData("EXTENDED_VERIFIER_SEEN", 
Boolean.TRUE))) {
             visitAnnotations(node, node.getTypeAnnotations(), 
node.isGenericsPlaceHolder() ? TYPE_PARAMETER_TARGET : TYPE_USE_TARGET);
-            visitGenericsTypeAnnotations(node);
+            if (node.isArray()) visitTypeAnnotations(node.getComponentType());
+            else visitGenericsTypeAnnotations(node);
         }
     }
 
@@ -258,7 +259,7 @@ public class ExtendedVerifier extends 
ClassCodeVisitorSupport {
         }
     }
 
-    private void extractTypeUseAnnotations(final List<AnnotationNode> mixed, 
final ClassNode targetType, final int keepTarget) {
+    private void extractTypeUseAnnotations(final List<AnnotationNode> mixed, 
ClassNode targetType, final int keepTarget) {
         List<AnnotationNode> typeUseAnnos = new ArrayList<>();
         for (AnnotationNode anno : mixed) {
             if (anno.isTargetAllowed(TYPE_USE_TARGET)) {
@@ -266,6 +267,7 @@ public class ExtendedVerifier extends 
ClassCodeVisitorSupport {
             }
         }
         if (!typeUseAnnos.isEmpty()) {
+            while (targetType.isArray()) targetType = 
targetType.getComponentType();
             targetType.addTypeAnnotations(typeUseAnnos);
             for (AnnotationNode anno : typeUseAnnos) {
                 if (!anno.isTargetAllowed(keepTarget)) {
diff --git 
a/src/main/java/org/codehaus/groovy/classgen/asm/BinaryExpressionHelper.java 
b/src/main/java/org/codehaus/groovy/classgen/asm/BinaryExpressionHelper.java
index 2afc71cccd..7f875a8dde 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/BinaryExpressionHelper.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/BinaryExpressionHelper.java
@@ -414,6 +414,7 @@ public class BinaryExpressionHelper {
         if (lhsType.isArray() && rightExpression instanceof ListExpression) { 
// array = [ ... ]
             Expression array = new ArrayExpression(lhsType.getComponentType(), 
((ListExpression) rightExpression).getExpressions());
             array.setSourcePosition(rightExpression);
+            array.setType(lhsType);
             array.visit(acg);
         } else if (rightExpression instanceof EmptyExpression) { // define 
field
             /*  */ if (ClassHelper.isPrimitiveDouble(lhsType)) {
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/CompileStack.java 
b/src/main/java/org/codehaus/groovy/classgen/asm/CompileStack.java
index edfc98f603..f4eea5c081 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/CompileStack.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/CompileStack.java
@@ -19,6 +19,7 @@
 package org.codehaus.groovy.classgen.asm;
 
 import org.codehaus.groovy.GroovyBugError;
+import org.codehaus.groovy.ast.AnnotationNode;
 import org.codehaus.groovy.ast.ClassHelper;
 import org.codehaus.groovy.ast.ClassNode;
 import org.codehaus.groovy.ast.MethodNode;
@@ -28,6 +29,8 @@ import org.codehaus.groovy.ast.VariableScope;
 import org.codehaus.groovy.classgen.AsmClassGenerator;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.TypePath;
+import org.objectweb.asm.TypeReference;
 
 import java.util.Collection;
 import java.util.Deque;
@@ -373,8 +376,25 @@ public class CompileStack {
             }
             for (BytecodeVariable v : usedVariables) {
                 String type = BytecodeHelper.getTypeDescription(v.isHolder() ? 
ClassHelper.REFERENCE_TYPE : v.getType());
-                Label endLabel = v.getEndLabel() == null ? v.getStartLabel() : 
v.getEndLabel(); // only occurs for '_' placeholder
-                mv.visitLocalVariable(v.getName(), type, null, 
v.getStartLabel(), endLabel, v.getIndex());
+                Label startLabel = v.getStartLabel(), endLabel = 
v.getEndLabel();
+                if (endLabel == null) endLabel = startLabel; // only occurs 
for '_' placeholder
+                mv.visitLocalVariable(v.getName(), type, null, startLabel, 
endLabel, v.getIndex());
+                // JSR 308: local variable type annotations
+                ClassNode t = v.getType();
+                String typePath = ""; // ?
+                do {
+                    for (AnnotationNode a : t.getTypeAnnotations()) {
+                        type = 
BytecodeHelper.getTypeDescription(a.getClassNode());
+                        var av = 
mv.visitLocalVariableAnnotation(TypeReference.LOCAL_VARIABLE << 24, 
TypePath.fromString(typePath),
+                            new Label[]{startLabel}, new Label[]{endLabel}, 
new int[]{v.getIndex()}, type, a.hasRuntimeRetention());
+                        if (av != null) {
+                            controller.getAcg().visitAnnotationAttributes(a, 
av);
+                            av.visitEnd();
+                        }
+                    }
+                    typePath += "[";
+                    t = t.getComponentType();
+                } while (t != null); // array
             }
         }
 
diff --git a/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java 
b/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
index bd621a126a..516c4686a3 100644
--- a/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
+++ b/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
@@ -1211,6 +1211,7 @@ public class ResolveVisitor extends 
ClassCodeExpressionTransformer {
 
     private void visitTypeAnnotations(final ClassNode node) {
         visitAnnotations(node.getTypeAnnotations());
+        if (node.isArray()) visitTypeAnnotations(node.getComponentType());
     }
 
     @Override
diff --git 
a/src/test/org/codehaus/groovy/classgen/asm/TypeAnnotationsTest.groovy 
b/src/test/org/codehaus/groovy/classgen/asm/TypeAnnotationsTest.groovy
index 63bb8a57b0..972400480d 100644
--- a/src/test/org/codehaus/groovy/classgen/asm/TypeAnnotationsTest.groovy
+++ b/src/test/org/codehaus/groovy/classgen/asm/TypeAnnotationsTest.groovy
@@ -184,7 +184,7 @@ final class TypeAnnotationsTest extends 
AbstractBytecodeTestCase {
                     if (arg2 instanceof @TypeAnno2 Number) {
                         return (@TypeAnno3 Map<@TypeAnno4 ? extends X, ? super 
@TypeAnno5 Y>) null;
                     }
-                    //if (numbers.stream().sorted(@TypeAnno6 
Integer::compareTo).count() > 0) return null; // needs grammar tweak
+                    if (numbers.stream().sorted(/*@TypeAnno6*/ 
Integer::compareTo).count() > 0) return null; // TODO: grammar support
                     return arg2;
                 }
             }
@@ -378,4 +378,33 @@ final class TypeAnnotationsTest extends 
AbstractBytecodeTestCase {
                 '@LTypeUseAnno7;(value="foo") : CLASS_EXTENDS 1, 0;'
         ])
     }
+
+    /**
+     * @see 
https://docs.oracle.com/javase/specs/jls/se11/html/jls-9.html#jls-9.7.4
+     */
+    void testTypeAnnotationsForArray() {
+        def bytecode = compile(classNamePattern: 'Foo', method: 'bar', imports 
+ '''
+            @Retention(RUNTIME) @Target(TYPE_USE) @interface TypeAnno0 { }
+            @Retention(RUNTIME) @Target(TYPE_USE) @interface TypeAnno1 { }
+            @Retention(RUNTIME) @Target(TYPE_USE) @interface TypeAnno2 { }
+
+            class Foo {
+                def bar() {
+                    // Object[][] : @TypeAnno0
+                    // Object[]   : @TypeAnno1
+                    // Object     : @TypeAnno2
+                    def @TypeAnno2 Object @TypeAnno0 [] @TypeAnno1 [] baz = 
null
+                }
+            }
+        ''')
+        assert bytecode.hasStrictSequence([
+                'LOCALVARIABLE this LFoo; L0 L2 0',
+                'LOCALVARIABLE baz [[Ljava/lang/Object; L1 L2 1',
+                'LOCALVARIABLE @LTypeAnno0;() : LOCAL_VARIABLE, null [ L1 - L2 
- 1 ]',
+                'LOCALVARIABLE @LTypeAnno1;() : LOCAL_VARIABLE, [ [ L1 - L2 - 
1 ]',
+                'LOCALVARIABLE @LTypeAnno2;() : LOCAL_VARIABLE, [[ [ L1 - L2 - 
1 ]',
+                'MAXSTACK = 1',
+                'MAXLOCALS = 2'
+        ])
+    }
 }

Reply via email to