You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2021/10/10 15:35:54 UTC

[groovy] branch master updated: GROOVY-10279: support type annotation for native record

This is an automated email from the ASF dual-hosted git repository.

sunlan 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 27232f3  GROOVY-10279: support type annotation for native record
27232f3 is described below

commit 27232f3a946021d02d4a3778b2d2542ccbf504a1
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sun Oct 10 21:57:09 2021 +0800

    GROOVY-10279: support type annotation for native record
---
 .../groovy/ast/decompiled/Annotations.java         | 13 +++++
 .../groovy/ast/decompiled/AsmDecompiler.java       |  6 +++
 .../ast/decompiled/ClassSignatureParser.java       |  4 +-
 .../codehaus/groovy/ast/decompiled/ClassStub.java  | 25 ++++++++-
 .../groovy/classgen/AsmClassGenerator.java         |  6 ++-
 .../org/codehaus/groovy/vmplugin/v16/Java16.java   |  8 ++-
 .../org/codehaus/groovy/classgen/RecordTest.groovy | 59 +++++++++++++++++++---
 7 files changed, 109 insertions(+), 12 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/ast/decompiled/Annotations.java b/src/main/java/org/codehaus/groovy/ast/decompiled/Annotations.java
index 04a3c23..1101017 100644
--- a/src/main/java/org/codehaus/groovy/ast/decompiled/Annotations.java
+++ b/src/main/java/org/codehaus/groovy/ast/decompiled/Annotations.java
@@ -109,6 +109,19 @@ class Annotations {
         return node;
     }
 
+    static <T extends ClassNode> T addTypeAnnotations(AnnotatedTypeStub stub, T node, AsmReferenceResolver resolver) {
+        List<TypeAnnotationStub> annotations = stub.getTypeAnnotations();
+        if (annotations != null) {
+            for (TypeAnnotationStub annotation : annotations) {
+                AnnotationNode annotationNode = createAnnotationNode(annotation, resolver);
+                if (annotationNode != null) {
+                    node.addTypeAnnotation(annotationNode);
+                }
+            }
+        }
+        return node;
+    }
+
     private static class DecompiledAnnotationNode extends AnnotationNode {
         private final Object initLock;
         private volatile boolean lazyInitDone;
diff --git a/src/main/java/org/codehaus/groovy/ast/decompiled/AsmDecompiler.java b/src/main/java/org/codehaus/groovy/ast/decompiled/AsmDecompiler.java
index 396181c..3a66c08 100644
--- a/src/main/java/org/codehaus/groovy/ast/decompiled/AsmDecompiler.java
+++ b/src/main/java/org/codehaus/groovy/ast/decompiled/AsmDecompiler.java
@@ -27,6 +27,7 @@ import org.objectweb.asm.FieldVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.RecordComponentVisitor;
 import org.objectweb.asm.Type;
+import org.objectweb.asm.TypePath;
 
 import java.io.BufferedInputStream;
 import java.io.IOException;
@@ -204,6 +205,11 @@ public abstract class AsmDecompiler {
                 public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
                     return readAnnotationMembers(recordComponentStub.addAnnotation(descriptor));
                 }
+
+                @Override
+                public AnnotationVisitor visitTypeAnnotation(final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+                    return readAnnotationMembers(recordComponentStub.addTypeAnnotation(descriptor));
+                }
             };
         }
 
diff --git a/src/main/java/org/codehaus/groovy/ast/decompiled/ClassSignatureParser.java b/src/main/java/org/codehaus/groovy/ast/decompiled/ClassSignatureParser.java
index f4fc855..c0ad604 100644
--- a/src/main/java/org/codehaus/groovy/ast/decompiled/ClassSignatureParser.java
+++ b/src/main/java/org/codehaus/groovy/ast/decompiled/ClassSignatureParser.java
@@ -65,7 +65,9 @@ class ClassSignatureParser {
                         }
                     });
                 }
-                RecordComponentNode recordComponentNode = new RecordComponentNode(classNode, r.name, typeHolder.getObject());
+                ClassNode cn = typeHolder.getObject();
+                Annotations.addTypeAnnotations(r, cn, resolver);
+                RecordComponentNode recordComponentNode = new RecordComponentNode(classNode, r.name, cn);
                 Annotations.addAnnotations(r, recordComponentNode, resolver);
                 return recordComponentNode;
             }).collect(Collectors.toList()));
diff --git a/src/main/java/org/codehaus/groovy/ast/decompiled/ClassStub.java b/src/main/java/org/codehaus/groovy/ast/decompiled/ClassStub.java
index d79986a..d11ae22 100644
--- a/src/main/java/org/codehaus/groovy/ast/decompiled/ClassStub.java
+++ b/src/main/java/org/codehaus/groovy/ast/decompiled/ClassStub.java
@@ -53,6 +53,10 @@ interface AnnotatedStub {
     List<AnnotationStub> getAnnotations();
 }
 
+interface AnnotatedTypeStub {
+    List<TypeAnnotationStub> getTypeAnnotations();
+}
+
 class MemberStub implements AnnotatedStub {
     List<AnnotationStub> annotations = null;
 
@@ -117,6 +121,12 @@ class AnnotationStub {
     }
 }
 
+class TypeAnnotationStub extends AnnotationStub {
+    public TypeAnnotationStub(String className) {
+        super(className);
+    }
+}
+
 class TypeWrapper {
     final String desc;
 
@@ -135,11 +145,12 @@ class EnumConstantWrapper {
     }
 }
 
-class RecordComponentStub implements AnnotatedStub {
+class RecordComponentStub implements AnnotatedStub, AnnotatedTypeStub {
     final String name;
     final String descriptor;
     final String signature;
     List<AnnotationStub> annotations;
+    List<TypeAnnotationStub> typeAnnotations;
 
     public RecordComponentStub(String name, String descriptor, String signature) {
         this.name = name;
@@ -158,4 +169,16 @@ class RecordComponentStub implements AnnotatedStub {
     public List<AnnotationStub> getAnnotations() {
         return annotations;
     }
+
+    public TypeAnnotationStub addTypeAnnotation(String desc) {
+        TypeAnnotationStub stub = new TypeAnnotationStub(desc);
+        if (typeAnnotations == null) typeAnnotations = new ArrayList<TypeAnnotationStub>(1);
+        typeAnnotations.add(stub);
+        return stub;
+    }
+
+    @Override
+    public List<TypeAnnotationStub> getTypeAnnotations() {
+        return typeAnnotations;
+    }
 }
diff --git a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java b/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
index 6406e09..e8f69cb 100644
--- a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
+++ b/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
@@ -412,7 +412,11 @@ public class AsmClassGenerator extends ClassGenerator {
                             BytecodeHelper.getTypeGenericsSignature(type));
 
             visitAnnotations(recordComponentNode, rcv);
-            TypeReference typeRef = newTypeParameterReference(CLASS_TYPE_PARAMETER, i);
+
+            // the int encoded value of the type reference is ALWAYS `318767104`
+            // TODO Get the magic number `318767104` via `TypeReference.newXXX()`
+            TypeReference typeRef = new TypeReference(318767104);
+
             visitTypeAnnotations(recordComponentNode.getType(), rcv, typeRef, "", true);
             rcv.visitEnd();
         }
diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v16/Java16.java b/src/main/java/org/codehaus/groovy/vmplugin/v16/Java16.java
index efb5231..c44c9a7 100644
--- a/src/main/java/org/codehaus/groovy/vmplugin/v16/Java16.java
+++ b/src/main/java/org/codehaus/groovy/vmplugin/v16/Java16.java
@@ -121,7 +121,13 @@ public class Java16 extends Java10 {
                         configureAnnotation(node, annotation);
                         return node;
                     }).collect(Collectors.toList());
-                    return new RecordComponentNode(classNode, r.getName(), makeClassNode(cu, r.getGenericType(), r.getType()), annotationNodeList);
+                    ClassNode type = makeClassNode(cu, r.getGenericType(), r.getType());
+                    type.addTypeAnnotations(Arrays.stream(r.getAnnotatedType().getAnnotations()).map(annotation -> {
+                        AnnotationNode node = new AnnotationNode(ClassHelper.make(annotation.annotationType()));
+                        configureAnnotation(node, annotation);
+                        return node;
+                    }).collect(Collectors.toList()));
+                    return new RecordComponentNode(classNode, r.getName(), type, annotationNodeList);
                 })
                 .collect(Collectors.toList());
         classNode.setRecordComponentNodes(recordComponentNodeList);
diff --git a/src/test/groovy/org/codehaus/groovy/classgen/RecordTest.groovy b/src/test/groovy/org/codehaus/groovy/classgen/RecordTest.groovy
index 2891d20..c58e904 100644
--- a/src/test/groovy/org/codehaus/groovy/classgen/RecordTest.groovy
+++ b/src/test/groovy/org/codehaus/groovy/classgen/RecordTest.groovy
@@ -84,21 +84,36 @@ class RecordTest {
             @Target([ElementType.RECORD_COMPONENT])
             @interface NotNull {}
             
-            record Person(@NotNull String name, int age, List<String> locations, String[] titles) {}
+            @Documented
+            @Retention(RetentionPolicy.RUNTIME)
+            @Target([ElementType.RECORD_COMPONENT, ElementType.TYPE_USE])
+            @interface NotNull2 {}
+            
+            record Person(@NotNull @NotNull2 String name, int age, @NotNull2 List<String> locations, String[] titles) {}
             
             RecordComponent[] rcs = Person.class.getRecordComponents()
             assert 4 == rcs.length
             
             assert 'name' == rcs[0].name && String.class == rcs[0].type
             Annotation[] annotations = rcs[0].getAnnotations()
-            assert 1 == annotations.length
+            assert 2 == annotations.length
             assert NotNull.class == annotations[0].annotationType()
+            assert NotNull2.class == annotations[1].annotationType()
+            def typeAnnotations = rcs[0].getAnnotatedType().getAnnotations()
+            assert 1 == typeAnnotations.length
+            assert NotNull2.class == typeAnnotations[0].annotationType()
             
             assert 'age' == rcs[1].name && int.class == rcs[1].type
             
             assert 'locations' == rcs[2].name && List.class == rcs[2].type
             assert 'Ljava/util/List<Ljava/lang/String;>;' == rcs[2].genericSignature
             assert 'java.util.List<java.lang.String>' == rcs[2].genericType.toString()
+            def annotations2 = rcs[2].getAnnotations()
+            assert 1 == annotations2.length
+            assert NotNull2.class == annotations2[0].annotationType()
+            def typeAnnotations2 = rcs[2].getAnnotatedType().getAnnotations()
+            assert 1 == typeAnnotations2.length
+            assert NotNull2.class == typeAnnotations2[0].annotationType()
             
             assert 'titles' == rcs[3].name && String[].class == rcs[3].type
         '''
@@ -118,13 +133,19 @@ class RecordTest {
             def b = new File(parentDir, 'Person.java')
             b.write '''
                 import java.lang.annotation.*;
+                import java.util.*;
                 
-                public record Person(@NotNull String name, int age, java.util.List<String> locations, String[] titles) {}
+                public record Person(@NotNull @NotNull2 String name, int age, @NotNull2 List<String> locations, String[] titles) {}
                 
                 @Documented
                 @Retention(RetentionPolicy.RUNTIME)
                 @Target({ElementType.RECORD_COMPONENT})
                 @interface NotNull {}
+                
+                @Documented
+                @Retention(RetentionPolicy.RUNTIME)
+                @Target({ElementType.RECORD_COMPONENT, ElementType.TYPE_USE})
+                @interface NotNull2 {}
             '''
 
             def loader = new GroovyClassLoader(this.class.classLoader)
@@ -134,46 +155,62 @@ class RecordTest {
 
             Class personClazz = loader.loadClass("Person")
             Class notNullClazz = loader.loadClass("NotNull")
+            Class notNull2Clazz = loader.loadClass("NotNull2")
 
             def rcs = personClazz.getRecordComponents()
             assert 4 == rcs.length
 
             assert 'name' == rcs[0].name && String.class == rcs[0].type
             def annotations = rcs[0].getAnnotations()
-            assert 1 == annotations.length
+            assert 2 == annotations.length
             assert notNullClazz == annotations[0].annotationType()
+            assert notNull2Clazz == annotations[1].annotationType()
+            def typeAnnotations = rcs[0].getAnnotatedType().getAnnotations()
+            assert 1 == typeAnnotations.length
+            assert notNull2Clazz == typeAnnotations[0].annotationType()
 
             assert 'age' == rcs[1].name && int.class == rcs[1].type
 
             assert 'locations' == rcs[2].name && List.class == rcs[2].type
             assert 'Ljava/util/List<Ljava/lang/String;>;' == rcs[2].genericSignature
             assert 'java.util.List<java.lang.String>' == rcs[2].genericType.toString()
+            def annotations2 = rcs[2].getAnnotations()
+            assert 1 == annotations2.length
+            assert notNull2Clazz == annotations2[0].annotationType()
+            def typeAnnotations2 = rcs[2].getAnnotatedType().getAnnotations()
+            assert 1 == typeAnnotations2.length
+            assert notNull2Clazz == typeAnnotations2[0].annotationType()
 
             assert 'titles' == rcs[3].name && String[].class == rcs[3].type
 
             ClassNode personClassNode = ClassHelper.make(personClazz)
             ClassNode notNullClassNode = ClassHelper.make(notNullClazz)
-            doTestNativeRecordClassNode(personClassNode, notNullClassNode)
+            ClassNode notNull2ClassNode = ClassHelper.make(notNull2Clazz)
+            doTestNativeRecordClassNode(personClassNode, notNullClassNode, notNull2ClassNode)
 
             def resource = loader.getResource(personClazz.getName().replace('.', '/') + '.class')
             def stub = AsmDecompiler.parseClass(resource)
             def unit = new CompilationUnit(loader)
             def personDecompiledClassNode = new DecompiledClassNode(stub, new AsmReferenceResolver(new ClassNodeResolver(), unit))
-            doTestNativeRecordClassNode(personDecompiledClassNode, notNullClassNode)
+            doTestNativeRecordClassNode(personDecompiledClassNode, notNullClassNode, notNull2ClassNode)
         } finally {
             parentDir.deleteDir()
             config.targetDirectory.deleteDir()
         }
     }
 
-    private static void doTestNativeRecordClassNode(ClassNode personClassNode, ClassNode notNullClassNode) {
+    private static void doTestNativeRecordClassNode(ClassNode personClassNode, ClassNode notNullClassNode, ClassNode notNull2ClassNode) {
         assert personClassNode.isRecord()
         def rcns = personClassNode.getRecordComponentNodes()
         assert 4 == rcns.size()
         assert 'name' == rcns[0].name && ClassHelper.STRING_TYPE == rcns[0].type
         List<AnnotationNode> annotationNodes = rcns[0].getAnnotations()
-        assert 1 == annotationNodes.size()
+        assert 2 == annotationNodes.size()
         assert notNullClassNode == annotationNodes[0].getClassNode()
+        assert notNull2ClassNode == annotationNodes[1].getClassNode()
+        def typeAnnotationNodes = rcns[0].getType().getTypeAnnotations()
+        assert 1 == typeAnnotationNodes.size()
+        assert notNull2ClassNode == typeAnnotationNodes[0].getClassNode()
 
         assert 'age' == rcns[1].name && ClassHelper.int_TYPE == rcns[1].type
 
@@ -181,6 +218,12 @@ class RecordTest {
         def genericsTypes = rcns[2].type.genericsTypes
         assert 1 == genericsTypes.size()
         assert ClassHelper.STRING_TYPE == genericsTypes[0].type
+        def annotationNodes2 = rcns[2].getAnnotations()
+        assert 1 == annotationNodes2.size()
+        assert notNull2ClassNode == annotationNodes2[0].getClassNode()
+        def typeAnnotationNodes2 = rcns[2].getType().getTypeAnnotations()
+        assert 1 == typeAnnotationNodes2.size()
+        assert notNull2ClassNode == typeAnnotationNodes2[0].getClassNode()
 
         assert 'titles' == rcns[3].name && ClassHelper.make(String[].class) == rcns[3].type
     }