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'
+ ])
+ }
}