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/02 07:32:10 UTC

[groovy] branch GROOVY-10279 updated (20f8739 -> 6f974c3)

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

sunlan pushed a change to branch GROOVY-10279
in repository https://gitbox.apache.org/repos/asf/groovy.git.


 discard 20f8739  GROOVY-10279: Support native record
     new 6f974c3  GROOVY-10279: Support native record

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (20f8739)
            \
             N -- N -- N   refs/heads/GROOVY-10279 (6f974c3)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../java/org/codehaus/groovy/ast/decompiled/ClassSignatureParser.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

[groovy] 01/01: GROOVY-10279: Support native record

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sunlan pushed a commit to branch GROOVY-10279
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 6f974c31c7e4a24fa552592ab8b8c67aa13e882c
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sat Oct 2 15:26:09 2021 +0800

    GROOVY-10279: Support native record
---
 .github/workflows/gradle-snapshot-distribution.yml |  2 +-
 build.gradle                                       |  4 +-
 .../src/main/groovy/org.apache.groovy-core.gradle  | 10 ++--
 .../apache/groovy/parser/antlr4/AstBuilder.java    | 13 ++++-
 .../org/codehaus/groovy/ast/AnnotationNode.java    |  5 +-
 .../java/org/codehaus/groovy/ast/ClassNode.java    | 31 +++++++++-
 .../codehaus/groovy/ast/RecordComponentNode.java   | 67 ++++++++++++++++++++++
 .../groovy/ast/decompiled/AsmDecompiler.java       | 13 +++++
 .../ast/decompiled/ClassSignatureParser.java       | 10 ++++
 .../codehaus/groovy/ast/decompiled/ClassStub.java  | 15 ++++-
 .../groovy/ast/decompiled/DecompiledClassNode.java | 12 ++++
 .../groovy/classgen/AsmClassGenerator.java         | 22 +++++++
 .../org/codehaus/groovy/vmplugin/v16/Java16.java   | 23 ++++++++
 .../org/codehaus/groovy/vmplugin/v8/Java8.java     | 15 +++--
 .../org/codehaus/groovy/classgen/RecordTest.groovy | 51 ++++++++++++++++
 15 files changed, 276 insertions(+), 17 deletions(-)

diff --git a/.github/workflows/gradle-snapshot-distribution.yml b/.github/workflows/gradle-snapshot-distribution.yml
index 14eab42..db40690 100644
--- a/.github/workflows/gradle-snapshot-distribution.yml
+++ b/.github/workflows/gradle-snapshot-distribution.yml
@@ -23,7 +23,7 @@ jobs:
       fail-fast: true
       matrix:
         os: [ubuntu-18.04]
-        java: [11.0.9]
+        java: [16]
     runs-on: ${{ matrix.os }}
     steps:
       - uses: actions/checkout@v1
diff --git a/build.gradle b/build.gradle
index 15a566d..a8f3daa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -235,11 +235,11 @@ tasks.named('test') {
     dependsOn testExtensionModuleJar
 }
 
-if (!JavaVersion.current().java11Compatible) {
+if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_16)) {
     logger.lifecycle '''
 **************************************** WARNING ********************************************
 ******   You are running the build with an older JDK. NEVER try to release with 1.8.   ******
-******   You must use a JDK 11+ in order to compile all features of the language.      ******
+******   You must use a JDK 16+ in order to compile all features of the language.      ******
 *********************************************************************************************
 '''
 }
diff --git a/buildSrc/src/main/groovy/org.apache.groovy-core.gradle b/buildSrc/src/main/groovy/org.apache.groovy-core.gradle
index acf5b13..6ce573d 100644
--- a/buildSrc/src/main/groovy/org.apache.groovy-core.gradle
+++ b/buildSrc/src/main/groovy/org.apache.groovy-core.gradle
@@ -17,9 +17,11 @@
  *  under the License.
  */
 
+
+import org.apache.groovy.gradle.CoreExtension
 import org.apache.groovy.gradle.DgmConverter
 import org.apache.groovy.gradle.JarJarTask
-import org.apache.groovy.gradle.CoreExtension
+
 import javax.inject.Inject
 
 plugins {
@@ -50,7 +52,7 @@ sourceSets {
                 exclude '**/v10/*'
                 exclude '**/vm10/*'
             }
-            if (!JavaVersion.current().isJava11Compatible()) {
+            if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_16)) {
                 exclude '**/v16/*'
                 exclude '**/vm16/*'
             }
@@ -64,7 +66,7 @@ sourceSets {
                 exclude '**/v10/*'
                 exclude '**/vm10/*'
             }
-            if (!JavaVersion.current().isJava11Compatible()) {
+            if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_16)) {
                 exclude '**/v16/*'
                 exclude '**/vm16/*'
             }
@@ -87,7 +89,7 @@ sourceSets {
                 exclude '**/v10/*'
                 exclude '**/vm10/*'
             }
-            if (!JavaVersion.current().isJava11Compatible()) {
+            if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_16)) {
                 exclude '**/v16/*'
                 exclude '**/vm16/*'
             }
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 684c7e5..259086b 100644
--- a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
+++ b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
@@ -236,6 +236,7 @@ import org.codehaus.groovy.ast.NodeMetaDataHandler;
 import org.codehaus.groovy.ast.PackageNode;
 import org.codehaus.groovy.ast.Parameter;
 import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.RecordComponentNode;
 import org.codehaus.groovy.ast.expr.AnnotationConstantExpression;
 import org.codehaus.groovy.ast.expr.ArgumentListExpression;
 import org.codehaus.groovy.ast.expr.ArrayExpression;
@@ -1614,7 +1615,13 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
             classNode.setModifiers(classNode.getModifiers() | Opcodes.ACC_RECORD | Opcodes.ACC_FINAL);
             classNode.setInterfaces(this.visitTypeList(ctx.is));
 
-            this.transformRecordHeaderToProperties(ctx, classNode);
+            Parameter[] parameters = this.transformRecordHeaderToProperties(ctx, classNode);
+            List<RecordComponentNode> recordComponentNodeList =
+                    Arrays.stream(parameters)
+                            .map(p -> new RecordComponentNode(classNode, p.getName(), p.getOriginType(), p.getAnnotations()))
+                            .collect(Collectors.toList());
+            classNode.setRecordComponentNodes(recordComponentNodeList);
+
             this.initUsingGenerics(classNode);
         } else {
             throw createParsingFailedException("Unsupported class declaration: " + ctx.getText(), ctx);
@@ -1650,7 +1657,7 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
         return classNode;
     }
 
-    private void transformRecordHeaderToProperties(ClassDeclarationContext ctx, ClassNode classNode) {
+    private Parameter[] transformRecordHeaderToProperties(ClassDeclarationContext ctx, ClassNode classNode) {
         Parameter[] parameters = this.visitFormalParameters(ctx.formalParameters());
         classNode.putNodeMetaData(RECORD_HEADER, parameters);
         for (int i = 0; i < parameters.length; i++) {
@@ -1660,6 +1667,8 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
             PropertyNode propertyNode = declareProperty(parameterCtx, parameterModifierManager, parameter.getType(), classNode, i, parameter, parameter.getName(), parameter.getModifiers(), parameter.getInitialExpression());
             propertyNode.getField().putNodeMetaData(IS_RECORD_GENERATED, true);
         }
+
+        return parameters;
     }
 
     @SuppressWarnings("unchecked")
diff --git a/src/main/java/org/codehaus/groovy/ast/AnnotationNode.java b/src/main/java/org/codehaus/groovy/ast/AnnotationNode.java
index 8522036..da27918 100644
--- a/src/main/java/org/codehaus/groovy/ast/AnnotationNode.java
+++ b/src/main/java/org/codehaus/groovy/ast/AnnotationNode.java
@@ -38,10 +38,11 @@ public class AnnotationNode extends ASTNode {
     public static final int PACKAGE_TARGET = 1 << 7;
     public static final int TYPE_PARAMETER_TARGET = 1 << 8;
     public static final int TYPE_USE_TARGET = 1 << 9;
+    public static final int RECORD_COMPONENT_TARGET = 1 << 10;
     public static final int TYPE_TARGET = 1 + ANNOTATION_TARGET;    //GROOVY-7151
     private static final int ALL_TARGETS = TYPE_TARGET | CONSTRUCTOR_TARGET | METHOD_TARGET
             | FIELD_TARGET | PARAMETER_TARGET | LOCAL_VARIABLE_TARGET | ANNOTATION_TARGET
-            | PACKAGE_TARGET | TYPE_PARAMETER_TARGET | TYPE_USE_TARGET;
+            | PACKAGE_TARGET | TYPE_PARAMETER_TARGET | TYPE_USE_TARGET | RECORD_COMPONENT_TARGET;
 
     private final ClassNode classNode;
     private Map<String, Expression> members;
@@ -187,6 +188,8 @@ public class AnnotationNode extends ASTNode {
                 return "TYPE_PARAMETER";
             case TYPE_USE_TARGET:
                 return "TYPE_USE";
+            case RECORD_COMPONENT_TARGET:
+                return "RECORD_COMPONENT";
             default:
                 return "unknown target";
         }
diff --git a/src/main/java/org/codehaus/groovy/ast/ClassNode.java b/src/main/java/org/codehaus/groovy/ast/ClassNode.java
index 6d17e06..e0c04ea 100644
--- a/src/main/java/org/codehaus/groovy/ast/ClassNode.java
+++ b/src/main/java/org/codehaus/groovy/ast/ClassNode.java
@@ -59,7 +59,6 @@ import static org.objectweb.asm.Opcodes.ACC_ANNOTATION;
 import static org.objectweb.asm.Opcodes.ACC_ENUM;
 import static org.objectweb.asm.Opcodes.ACC_INTERFACE;
 import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
-import static org.objectweb.asm.Opcodes.ACC_RECORD;
 import static org.objectweb.asm.Opcodes.ACC_STATIC;
 import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
 
@@ -165,6 +164,7 @@ public class ClassNode extends AnnotatedNode {
     protected List<InnerClassNode> innerClasses;
     private List<ClassNode> permittedSubclasses = new ArrayList<>(4);
     private List<AnnotationNode> typeAnnotations = Collections.emptyList();
+    private List<RecordComponentNode> recordComponentNodes = Collections.emptyList();
 
     /**
      * The AST Transformations to be applied during compilation.
@@ -1366,7 +1366,34 @@ public class ClassNode extends AnnotatedNode {
      * @since 4.0.0
      */
     public boolean isRecord() {
-        return (getModifiers() & ACC_RECORD) != 0;
+        List<RecordComponentNode> recordComponentNodes = getRecordComponentNodes();
+        return null != recordComponentNodes && !recordComponentNodes.isEmpty();
+    }
+
+    /**
+     * Get the record components of record type
+     *
+     * @return {@code RecordComponentNode} instances
+     * @since 4.0.0
+     */
+    public List<RecordComponentNode> getRecordComponentNodes() {
+        if (redirect != null)
+            return redirect.getRecordComponentNodes();
+        lazyClassInit();
+        return recordComponentNodes;
+    }
+
+    /**
+     * Set the record components for record type
+     *
+     * @since 4.0.0
+     */
+    public void setRecordComponentNodes(List<RecordComponentNode> recordComponentNodes) {
+        if (redirect != null) {
+            redirect.setRecordComponentNodes(recordComponentNodes);
+        } else {
+            this.recordComponentNodes = recordComponentNodes;
+        }
     }
 
     public boolean isAbstract() {
diff --git a/src/main/java/org/codehaus/groovy/ast/RecordComponentNode.java b/src/main/java/org/codehaus/groovy/ast/RecordComponentNode.java
new file mode 100644
index 0000000..de5b6e6
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/ast/RecordComponentNode.java
@@ -0,0 +1,67 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.codehaus.groovy.ast;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents record component
+ *
+ * @since 4.0.0
+ */
+public class RecordComponentNode extends AnnotatedNode {
+    private final String name;
+    private final ClassNode type;
+
+    public RecordComponentNode(ClassNode declaringClass, String name, ClassNode type) {
+        this(declaringClass, name, type, Collections.emptyList());
+    }
+
+    public RecordComponentNode(ClassNode declaringClass, String name, ClassNode type, List<AnnotationNode> annotations) {
+        this.name = name;
+        this.type = type;
+        setDeclaringClass(declaringClass);
+        for (AnnotationNode annotationNode : annotations) {
+            addAnnotation(annotationNode);
+        }
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public ClassNode getType() {
+        return type;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof RecordComponentNode)) return false;
+        RecordComponentNode that = (RecordComponentNode) o;
+        return name.equals(that.name) && getDeclaringClass().equals(that.getDeclaringClass());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getDeclaringClass(), name);
+    }
+}
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 0c0cff5..706c1a4 100644
--- a/src/main/java/org/codehaus/groovy/ast/decompiled/AsmDecompiler.java
+++ b/src/main/java/org/codehaus/groovy/ast/decompiled/AsmDecompiler.java
@@ -25,6 +25,7 @@ import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.FieldVisitor;
 import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.RecordComponentVisitor;
 import org.objectweb.asm.Type;
 
 import java.io.BufferedInputStream;
@@ -192,6 +193,18 @@ public abstract class AsmDecompiler {
         }
 
         @Override
+        public RecordComponentVisitor visitRecordComponent(
+                final String name, final String descriptor, final String signature) {
+
+            RecordComponentStub recordComponentStub = new RecordComponentStub(name, descriptor, signature);
+            result.recordComponents.add(recordComponentStub);
+
+            return new RecordComponentVisitor(api) {
+                // TODO collect annotations
+            };
+        }
+
+        @Override
         public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, final Object value) {
             FieldStub stub = new FieldStub(name, access, desc, signature, value);
             if (result.fields == null) result.fields = new ArrayList<>(1);
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 a77f604..9fc855a 100644
--- a/src/main/java/org/codehaus/groovy/ast/decompiled/ClassSignatureParser.java
+++ b/src/main/java/org/codehaus/groovy/ast/decompiled/ClassSignatureParser.java
@@ -20,11 +20,14 @@ package org.codehaus.groovy.ast.decompiled;
 
 import org.codehaus.groovy.ast.ClassNode;
 import org.codehaus.groovy.ast.GenericsType;
+import org.codehaus.groovy.ast.RecordComponentNode;
+import org.objectweb.asm.Type;
 import org.objectweb.asm.signature.SignatureReader;
 import org.objectweb.asm.signature.SignatureVisitor;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 class ClassSignatureParser {
     static void configureClass(ClassNode classNode, ClassStub stub, AsmReferenceResolver resolver) {
@@ -48,6 +51,13 @@ class ClassSignatureParser {
                 permittedSubclasses.add(resolver.resolveClass(AsmDecompiler.fromInternalName(name)));
             }
         }
+
+        if (!stub.recordComponents.isEmpty()) {
+            classNode.setRecordComponentNodes(stub.recordComponents.stream().map(r -> {
+                ClassNode type = resolver.resolveType(Type.getType(r.descriptor)); // TODO apply generic types with `r.signature`
+                return new RecordComponentNode(classNode, r.name, type);
+            }).collect(Collectors.toList()));
+        }
     }
 
     private static void parseClassSignature(final ClassNode classNode, String signature, final AsmReferenceResolver resolver) {
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 e555e7b..1c03bb2 100644
--- a/src/main/java/org/codehaus/groovy/ast/decompiled/ClassStub.java
+++ b/src/main/java/org/codehaus/groovy/ast/decompiled/ClassStub.java
@@ -35,6 +35,7 @@ public class ClassStub extends MemberStub {
     List<MethodStub> methods;
     List<FieldStub> fields;
     final List<String> permittedSubclasses = new ArrayList<>(1);
+    final List<RecordComponentStub> recordComponents  = new ArrayList<>(1);
 
     // Used to store the real access modifiers for inner classes
     int innerClassModifiers = -1;
@@ -123,4 +124,16 @@ class EnumConstantWrapper {
         this.enumDesc = enumDesc;
         this.constant = constant;
     }
-}
\ No newline at end of file
+}
+
+class RecordComponentStub {
+    final String name;
+    final String descriptor;
+    final String signature;
+
+    public RecordComponentStub(String name, String descriptor, String signature) {
+        this.name = name;
+        this.descriptor = descriptor;
+        this.signature = signature;
+    }
+}
diff --git a/src/main/java/org/codehaus/groovy/ast/decompiled/DecompiledClassNode.java b/src/main/java/org/codehaus/groovy/ast/decompiled/DecompiledClassNode.java
index 8156670..e999fdf 100644
--- a/src/main/java/org/codehaus/groovy/ast/decompiled/DecompiledClassNode.java
+++ b/src/main/java/org/codehaus/groovy/ast/decompiled/DecompiledClassNode.java
@@ -26,6 +26,7 @@ import org.codehaus.groovy.ast.FieldNode;
 import org.codehaus.groovy.ast.GenericsType;
 import org.codehaus.groovy.ast.MethodNode;
 import org.codehaus.groovy.ast.MixinNode;
+import org.codehaus.groovy.ast.RecordComponentNode;
 import org.codehaus.groovy.classgen.Verifier;
 import org.codehaus.groovy.reflection.ReflectionUtils;
 import org.objectweb.asm.Opcodes;
@@ -198,6 +199,17 @@ public class DecompiledClassNode extends ClassNode {
         return false;
     }
 
+    /**
+     * Get the record components of record types
+     *
+     * @return {@code RecordComponentNode} instances
+     * @since 4.0.0
+     */
+    public List<RecordComponentNode> getRecordComponentNodes() {
+        lazyInitSupers();
+        return super.getRecordComponentNodes();
+    }
+
     @Override
     public Class getTypeClass() {
         return resolver.resolveJvmClass(getName());
diff --git a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java b/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
index aaba8cc..6fb4f78 100644
--- a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
+++ b/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
@@ -38,6 +38,7 @@ import org.codehaus.groovy.ast.ModuleNode;
 import org.codehaus.groovy.ast.PackageNode;
 import org.codehaus.groovy.ast.Parameter;
 import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.RecordComponentNode;
 import org.codehaus.groovy.ast.expr.AnnotationConstantExpression;
 import org.codehaus.groovy.ast.expr.ArgumentListExpression;
 import org.codehaus.groovy.ast.expr.ArrayExpression;
@@ -112,6 +113,7 @@ import org.objectweb.asm.FieldVisitor;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.RecordComponentVisitor;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.TypePath;
 import org.objectweb.asm.TypeReference;
@@ -381,6 +383,22 @@ public class AsmClassGenerator extends ClassGenerator {
                 }
             }
 
+            if (classNode.isRecord() && controller.getBytecodeVersion() >= Opcodes.V16 &&
+                    context.getCompileUnit().getConfig().isRecordsNative()) {
+                List<RecordComponentNode> recordComponentNodeList = classNode.getRecordComponentNodes();
+                for (int i = 0, n = recordComponentNodeList.size(); i < n; i++) {
+                    RecordComponentNode recordComponentNode = recordComponentNodeList.get(i);
+                    final ClassNode type = recordComponentNode.getType();
+                    RecordComponentVisitor recordComponentVisitor =
+                            classVisitor.visitRecordComponent(recordComponentNode.getName(),
+                                    BytecodeHelper.getTypeDescription(type),
+                                    BytecodeHelper.getGenericsSignature(type));
+
+                    visitAnnotations(recordComponentNode, recordComponentVisitor);
+                    TypeReference typeRef = newTypeParameterReference(CLASS_TYPE_PARAMETER, i);
+                    visitTypeAnnotations(recordComponentNode.getType(), recordComponentVisitor, typeRef, "", true);
+                }
+            }
             classVisitor.visitEnd();
         } catch (GroovyRuntimeException e) {
             e.setModule(classNode.getModule());
@@ -2101,6 +2119,8 @@ public class AsmClassGenerator extends ClassGenerator {
                 av = ((MethodVisitor) visitor).visitTypeAnnotation(typeRefInt, typePath, annotationDescriptor, an.hasRuntimeRetention());
             } else if (visitor instanceof FieldVisitor) {
                 av = ((FieldVisitor) visitor).visitTypeAnnotation(typeRefInt, typePath, annotationDescriptor, an.hasRuntimeRetention());
+            } else if (visitor instanceof RecordComponentVisitor) {
+                av = ((RecordComponentVisitor) visitor).visitTypeAnnotation(typeRefInt, typePath, annotationDescriptor, an.hasRuntimeRetention());
             } else {
                 throwException("Cannot create an AnnotationVisitor. Please report Groovy bug");
             }
@@ -2192,6 +2212,8 @@ public class AsmClassGenerator extends ClassGenerator {
             return ((FieldVisitor) visitor).visitAnnotation(annotationDescriptor, an.hasRuntimeRetention());
         } else if (targetNode instanceof ClassNode) {
             return ((ClassVisitor) visitor).visitAnnotation(annotationDescriptor, an.hasRuntimeRetention());
+        } else if (visitor instanceof RecordComponentVisitor) {
+            return ((RecordComponentVisitor) visitor).visitAnnotation(annotationDescriptor, true);
         }
         throwException("Cannot create an AnnotationVisitor. Please report Groovy bug");
         return null;
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 5eef73d..efb5231 100644
--- a/src/main/java/org/codehaus/groovy/vmplugin/v16/Java16.java
+++ b/src/main/java/org/codehaus/groovy/vmplugin/v16/Java16.java
@@ -19,6 +19,11 @@
 package org.codehaus.groovy.vmplugin.v16;
 
 import groovy.lang.GroovyRuntimeException;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.CompileUnit;
+import org.codehaus.groovy.ast.RecordComponentNode;
 import org.codehaus.groovy.vmplugin.v10.Java10;
 
 import java.lang.invoke.MethodHandle;
@@ -27,8 +32,10 @@ import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Additional Java 16 based functions will be added here as needed.
@@ -105,6 +112,22 @@ public class Java16 extends Java10 {
     }
 
     @Override
+    protected void makeRecordComponents(final CompileUnit cu, final ClassNode classNode, final Class<?> clazz) {
+        if (!clazz.isRecord()) return;
+        List<RecordComponentNode> recordComponentNodeList = Arrays.stream(clazz.getRecordComponents())
+                .map(r -> {
+                    List<AnnotationNode> annotationNodeList = Arrays.stream(r.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(), makeClassNode(cu, r.getGenericType(), r.getType()), annotationNodeList);
+                })
+                .collect(Collectors.toList());
+        classNode.setRecordComponentNodes(recordComponentNodeList);
+    }
+
+    @Override
     public Class<?>[] getPluginDefaultGroovyMethods() {
         return PLUGIN_DGM;
     }
diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java b/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java
index 9406d5d..ca32a69 100644
--- a/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java
+++ b/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java
@@ -181,8 +181,11 @@ public class Java8 implements VMPlugin {
             default:
                 // falls through
         }
-        if ("MODULE".equals(value.name())) { // JDK 9+
-            return AnnotationNode.TYPE_TARGET;
+        String name = value.name();
+        if ("MODULE".equals(name)) { // JDK 9+
+            return AnnotationNode.TYPE_TARGET; // TODO Add MODULE_TARGET too?
+        } else if ("RECORD_COMPONENT".equals(name)) { // JDK 16+
+            return AnnotationNode.RECORD_COMPONENT_TARGET;
         } else {
             throw new GroovyBugError("unsupported Target " + value);
         }
@@ -337,7 +340,7 @@ public class Java8 implements VMPlugin {
         }
     }
 
-    private void configureAnnotation(final AnnotationNode node, final Annotation annotation) {
+    protected void configureAnnotation(final AnnotationNode node, final Annotation annotation) {
         Class<?> type = annotation.annotationType();
         if (type == Retention.class) {
             Retention r = (Retention) annotation;
@@ -428,6 +431,7 @@ public class Java8 implements VMPlugin {
             if (sc != null) classNode.setUnresolvedSuperClass(makeClassNode(compileUnit, clazz.getGenericSuperclass(), sc));
             makeInterfaceTypes(compileUnit, classNode, clazz);
             makePermittedSubclasses(compileUnit, classNode, clazz);
+            makeRecordComponents(compileUnit, classNode, clazz);
             setAnnotationMetaData(clazz.getAnnotations(), classNode);
 
             PackageNode packageNode = classNode.getPackage();
@@ -494,6 +498,9 @@ public class Java8 implements VMPlugin {
         classNode.setPermittedSubclasses(permittedSubclasses);
     }
 
+    protected void makeRecordComponents(final CompileUnit cu, final ClassNode classNode, final Class<?> clazz) {
+    }
+
     private void makeInterfaceTypes(final CompileUnit cu, final ClassNode classNode, final Class<?> clazz) {
         Type[] interfaceTypes = clazz.getGenericInterfaces();
         final int n = interfaceTypes.length;
@@ -526,7 +533,7 @@ public class Java8 implements VMPlugin {
         return nodes;
     }
 
-    private ClassNode makeClassNode(final CompileUnit cu, final Type t, final Class<?> c) {
+    protected ClassNode makeClassNode(final CompileUnit cu, final Type t, final Class<?> c) {
         ClassNode back = null;
         if (cu != null) back = cu.getClass(c.getName());
         if (back == null) back = ClassHelper.make(c);
diff --git a/src/test/groovy/org/codehaus/groovy/classgen/RecordTest.groovy b/src/test/groovy/org/codehaus/groovy/classgen/RecordTest.groovy
index fc95835..ea13cba 100644
--- a/src/test/groovy/org/codehaus/groovy/classgen/RecordTest.groovy
+++ b/src/test/groovy/org/codehaus/groovy/classgen/RecordTest.groovy
@@ -18,6 +18,7 @@
  */
 package org.codehaus.groovy.classgen
 
+
 import groovy.transform.CompileStatic
 import org.codehaus.groovy.control.CompilerConfiguration
 import org.junit.Test
@@ -56,4 +57,54 @@ class RecordTest {
             assert java.lang.reflect.Modifier.isStatic(Test$Point.modifiers)
         '''
     }
+
+    @Test
+    void testNativeRecordOnJDK16plus() {
+        assumeTrue(isAtLeastJdk('16.0'))
+        assertScript(new GroovyShell(new CompilerConfiguration(targetBytecode: CompilerConfiguration.JDK16)), '''
+            import java.lang.annotation.Annotation
+            import java.lang.annotation.Documented
+            import java.lang.annotation.ElementType
+            import java.lang.annotation.Retention
+            import java.lang.annotation.RetentionPolicy
+            import java.lang.annotation.Target
+            import java.lang.reflect.RecordComponent
+            
+            @Documented
+            @Retention(RetentionPolicy.RUNTIME)
+            @Target([ElementType.RECORD_COMPONENT])
+            @interface NotNull {}
+            
+            record Person(@NotNull String name, int age, 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 NotNull.class == annotations[0].annotationType()
+            
+            assert 'age' == rcs[1].name && int.class == rcs[1].type
+            assert 'locations' == rcs[2].name && List.class == rcs[2].type
+            assert 'titles' == rcs[3].name && String[].class == rcs[3].type
+        ''')
+    }
+
+
+    @Test
+    void testResolveNativeRecordOnJDK16plus() {
+        assumeTrue(isAtLeastJdk('16.0'))
+
+        assertScript(new GroovyShell(new CompilerConfiguration(targetBytecode: CompilerConfiguration.JDK16)), '''
+            import org.codehaus.groovy.ast.*
+            
+            def cn = ClassHelper.make(jdk.net.UnixDomainPrincipal.class)
+            assert cn.isRecord()
+            def rcns = cn.getRecordComponentNodes()
+            assert 'user' == rcns[0].name && 'java.nio.file.attribute.UserPrincipal' == rcns[0].type.name
+            assert 'group' == rcns[1].name && 'java.nio.file.attribute.GroupPrincipal' == rcns[1].type.name
+        ''')
+
+    }
 }