Add class writing support for record and nest attributes

* https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-4.html#jvms-4.7.28
* https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-4.html#jvms-4.7.29
* https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-4.html#jvms-4.7.30

PiperOrigin-RevId: 397416507
diff --git a/java/com/google/turbine/bytecode/Attribute.java b/java/com/google/turbine/bytecode/Attribute.java
index 7b415a7..581de3d 100644
--- a/java/com/google/turbine/bytecode/Attribute.java
+++ b/java/com/google/turbine/bytecode/Attribute.java
@@ -42,6 +42,9 @@
     RUNTIME_INVISIBLE_TYPE_ANNOTATIONS("RuntimeInvisibleTypeAnnotations"),
     METHOD_PARAMETERS("MethodParameters"),
     MODULE("Module"),
+    NEST_HOST("NestHost"),
+    NEST_MEMBERS("NestMembers"),
+    RECORD("Record"),
     TURBINE_TRANSITIVE_JAR("TurbineTransitiveJar");
 
     private final String signature;
@@ -311,6 +314,88 @@
     }
   }
 
+  /** A JVMS §4.7.28 NestHost attribute. */
+  class NestHost implements Attribute {
+
+    private final String hostClass;
+
+    public NestHost(String hostClass) {
+      this.hostClass = hostClass;
+    }
+
+    String hostClass() {
+      return hostClass;
+    }
+
+    @Override
+    public Kind kind() {
+      return Kind.NEST_HOST;
+    }
+  }
+
+  /** A JVMS §4.7.29 NestHost attribute. */
+  class NestMembers implements Attribute {
+
+    private final ImmutableList<String> classes;
+
+    public NestMembers(ImmutableList<String> classes) {
+      this.classes = classes;
+    }
+
+    ImmutableList<String> classes() {
+      return classes;
+    }
+
+    @Override
+    public Kind kind() {
+      return Kind.NEST_MEMBERS;
+    }
+  }
+
+  /** A JVMS §4.7.30 Record attribute. */
+  class Record implements Attribute {
+
+    private final ImmutableList<Component> components;
+
+    public Record(ImmutableList<Component> components) {
+      this.components = components;
+    }
+
+    @Override
+    public Kind kind() {
+      return Kind.RECORD;
+    }
+
+    ImmutableList<Component> components() {
+      return components;
+    }
+
+    /** A JVMS §4.7.30 Record component info. */
+    static class Component {
+      private final String name;
+      private final String descriptor;
+      private final List<Attribute> attributes;
+
+      Component(String name, String descriptor, List<Attribute> attributes) {
+        this.name = name;
+        this.descriptor = descriptor;
+        this.attributes = attributes;
+      }
+
+      String name() {
+        return name;
+      }
+
+      String descriptor() {
+        return descriptor;
+      }
+
+      List<Attribute> attributes() {
+        return attributes;
+      }
+    }
+  }
+
   /** A custom attribute for recording the original jar of repackaged transitive classes. */
   class TurbineTransitiveJar implements Attribute {
 
diff --git a/java/com/google/turbine/bytecode/AttributeWriter.java b/java/com/google/turbine/bytecode/AttributeWriter.java
index b92d2e7..ed7b2ab 100644
--- a/java/com/google/turbine/bytecode/AttributeWriter.java
+++ b/java/com/google/turbine/bytecode/AttributeWriter.java
@@ -42,59 +42,66 @@
 public class AttributeWriter {
 
   private final ConstantPool pool;
-  private final ByteArrayDataOutput output;
 
-  public AttributeWriter(ConstantPool pool, ByteArrayDataOutput output) {
+  public AttributeWriter(ConstantPool pool) {
     this.pool = pool;
-    this.output = output;
   }
 
   /** Writes a single attribute. */
-  public void write(Attribute attribute) {
+  public void write(ByteArrayDataOutput output, Attribute attribute) {
     switch (attribute.kind()) {
       case SIGNATURE:
-        writeSignatureAttribute((Signature) attribute);
+        writeSignatureAttribute(output, (Signature) attribute);
         break;
       case EXCEPTIONS:
-        writeExceptionsAttribute((ExceptionsAttribute) attribute);
+        writeExceptionsAttribute(output, (ExceptionsAttribute) attribute);
         break;
       case INNER_CLASSES:
-        writeInnerClasses((InnerClasses) attribute);
+        writeInnerClasses(output, (InnerClasses) attribute);
         break;
       case CONSTANT_VALUE:
-        writeConstantValue((ConstantValue) attribute);
+        writeConstantValue(output, (ConstantValue) attribute);
         break;
       case RUNTIME_VISIBLE_ANNOTATIONS:
       case RUNTIME_INVISIBLE_ANNOTATIONS:
-        writeAnnotation((Attribute.Annotations) attribute);
+        writeAnnotation(output, (Attribute.Annotations) attribute);
         break;
       case ANNOTATION_DEFAULT:
-        writeAnnotationDefault((Attribute.AnnotationDefault) attribute);
+        writeAnnotationDefault(output, (Attribute.AnnotationDefault) attribute);
         break;
       case RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS:
       case RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS:
-        writeParameterAnnotations((Attribute.ParameterAnnotations) attribute);
+        writeParameterAnnotations(output, (Attribute.ParameterAnnotations) attribute);
         break;
       case DEPRECATED:
-        writeDeprecated(attribute);
+        writeDeprecated(output, attribute);
         break;
       case RUNTIME_INVISIBLE_TYPE_ANNOTATIONS:
       case RUNTIME_VISIBLE_TYPE_ANNOTATIONS:
-        writeTypeAnnotation((Attribute.TypeAnnotations) attribute);
+        writeTypeAnnotation(output, (Attribute.TypeAnnotations) attribute);
         break;
       case METHOD_PARAMETERS:
-        writeMethodParameters((Attribute.MethodParameters) attribute);
+        writeMethodParameters(output, (Attribute.MethodParameters) attribute);
         break;
       case MODULE:
-        writeModule((Attribute.Module) attribute);
+        writeModule(output, (Attribute.Module) attribute);
+        break;
+      case NEST_HOST:
+        writeNestHost(output, (Attribute.NestHost) attribute);
+        break;
+      case NEST_MEMBERS:
+        writeNestMembers(output, (Attribute.NestMembers) attribute);
+        break;
+      case RECORD:
+        writeRecord(output, (Attribute.Record) attribute);
         break;
       case TURBINE_TRANSITIVE_JAR:
-        writeTurbineTransitiveJar((Attribute.TurbineTransitiveJar) attribute);
+        writeTurbineTransitiveJar(output, (Attribute.TurbineTransitiveJar) attribute);
         break;
     }
   }
 
-  private void writeInnerClasses(InnerClasses attribute) {
+  private void writeInnerClasses(ByteArrayDataOutput output, InnerClasses attribute) {
     output.writeShort(pool.utf8(attribute.kind().signature()));
     output.writeInt(attribute.inners.size() * 8 + 2);
     output.writeShort(attribute.inners.size());
@@ -106,7 +113,7 @@
     }
   }
 
-  private void writeExceptionsAttribute(ExceptionsAttribute attribute) {
+  private void writeExceptionsAttribute(ByteArrayDataOutput output, ExceptionsAttribute attribute) {
     output.writeShort(pool.utf8(attribute.kind().signature()));
     output.writeInt(2 + attribute.exceptions.size() * 2);
     output.writeShort(attribute.exceptions.size());
@@ -115,13 +122,13 @@
     }
   }
 
-  private void writeSignatureAttribute(Signature attribute) {
+  private void writeSignatureAttribute(ByteArrayDataOutput output, Signature attribute) {
     output.writeShort(pool.utf8(attribute.kind().signature()));
     output.writeInt(2);
     output.writeShort(pool.utf8(attribute.signature));
   }
 
-  public void writeConstantValue(ConstantValue attribute) {
+  public void writeConstantValue(ByteArrayDataOutput output, ConstantValue attribute) {
     output.writeShort(pool.utf8(attribute.kind().signature()));
     output.writeInt(2);
     Const.Value value = attribute.value;
@@ -158,7 +165,7 @@
     }
   }
 
-  public void writeAnnotation(Annotations attribute) {
+  public void writeAnnotation(ByteArrayDataOutput output, Annotations attribute) {
     output.writeShort(pool.utf8(attribute.kind().signature()));
     ByteArrayDataOutput tmp = ByteStreams.newDataOutput();
     tmp.writeShort(attribute.annotations().size());
@@ -170,7 +177,8 @@
     output.write(data);
   }
 
-  public void writeAnnotationDefault(Attribute.AnnotationDefault attribute) {
+  public void writeAnnotationDefault(
+      ByteArrayDataOutput output, Attribute.AnnotationDefault attribute) {
     output.writeShort(pool.utf8(attribute.kind().signature()));
     ByteArrayDataOutput tmp = ByteStreams.newDataOutput();
     new AnnotationWriter(pool, tmp).writeElementValue(attribute.value());
@@ -179,7 +187,8 @@
     output.write(data);
   }
 
-  public void writeParameterAnnotations(Attribute.ParameterAnnotations attribute) {
+  public void writeParameterAnnotations(
+      ByteArrayDataOutput output, Attribute.ParameterAnnotations attribute) {
     output.writeShort(pool.utf8(attribute.kind().signature()));
     ByteArrayDataOutput tmp = ByteStreams.newDataOutput();
     tmp.writeByte(attribute.annotations().size());
@@ -194,12 +203,12 @@
     output.write(data);
   }
 
-  private void writeDeprecated(Attribute attribute) {
+  private void writeDeprecated(ByteArrayDataOutput output, Attribute attribute) {
     output.writeShort(pool.utf8(attribute.kind().signature()));
     output.writeInt(0);
   }
 
-  private void writeTypeAnnotation(TypeAnnotations attribute) {
+  private void writeTypeAnnotation(ByteArrayDataOutput output, TypeAnnotations attribute) {
     output.writeShort(pool.utf8(attribute.kind().signature()));
     ByteArrayDataOutput tmp = ByteStreams.newDataOutput();
     tmp.writeShort(attribute.annotations().size());
@@ -211,7 +220,7 @@
     output.write(data);
   }
 
-  private void writeMethodParameters(MethodParameters attribute) {
+  private void writeMethodParameters(ByteArrayDataOutput output, MethodParameters attribute) {
     output.writeShort(pool.utf8(attribute.kind().signature()));
     output.writeInt(attribute.parameters().size() * 4 + 1);
     output.writeByte(attribute.parameters().size());
@@ -221,7 +230,7 @@
     }
   }
 
-  private void writeModule(Attribute.Module attribute) {
+  private void writeModule(ByteArrayDataOutput output, Attribute.Module attribute) {
     ModuleInfo module = attribute.module();
 
     ByteArrayDataOutput tmp = ByteStreams.newDataOutput();
@@ -277,7 +286,40 @@
     output.write(data);
   }
 
-  private void writeTurbineTransitiveJar(TurbineTransitiveJar attribute) {
+  private void writeNestHost(ByteArrayDataOutput output, Attribute.NestHost attribute) {
+    output.writeShort(pool.utf8(attribute.kind().signature()));
+    output.writeInt(2);
+    output.writeShort(pool.classInfo(attribute.hostClass()));
+  }
+
+  private void writeNestMembers(ByteArrayDataOutput output, Attribute.NestMembers attribute) {
+    output.writeShort(pool.utf8(attribute.kind().signature()));
+    output.writeInt(2 + attribute.classes().size() * 2);
+    output.writeShort(attribute.classes().size());
+    for (String classes : attribute.classes()) {
+      output.writeShort(pool.classInfo(classes));
+    }
+  }
+
+  private void writeRecord(ByteArrayDataOutput output, Attribute.Record attribute) {
+    output.writeShort(pool.utf8(attribute.kind().signature()));
+    ByteArrayDataOutput tmp = ByteStreams.newDataOutput();
+    tmp.writeShort(attribute.components().size());
+    for (Attribute.Record.Component c : attribute.components()) {
+      tmp.writeShort(pool.utf8(c.name()));
+      tmp.writeShort(pool.utf8(c.descriptor()));
+      tmp.writeShort(c.attributes().size());
+      for (Attribute a : c.attributes()) {
+        write(tmp, a);
+      }
+    }
+    byte[] data = tmp.toByteArray();
+    output.writeInt(data.length);
+    output.write(data);
+  }
+
+  private void writeTurbineTransitiveJar(
+      ByteArrayDataOutput output, TurbineTransitiveJar attribute) {
     output.writeShort(pool.utf8(attribute.kind().signature()));
     output.writeInt(2);
     output.writeShort(pool.utf8(attribute.transitiveJar));
diff --git a/java/com/google/turbine/bytecode/ClassFile.java b/java/com/google/turbine/bytecode/ClassFile.java
index af5ee12..ed28329 100644
--- a/java/com/google/turbine/bytecode/ClassFile.java
+++ b/java/com/google/turbine/bytecode/ClassFile.java
@@ -42,6 +42,9 @@
   private final List<InnerClass> innerClasses;
   private final ImmutableList<TypeAnnotationInfo> typeAnnotations;
   private final @Nullable ModuleInfo module;
+  private final @Nullable String nestHost;
+  private final ImmutableList<String> nestMembers;
+  private final @Nullable RecordInfo record;
   private final @Nullable String transitiveJar;
 
   public ClassFile(
@@ -56,6 +59,9 @@
       List<InnerClass> innerClasses,
       ImmutableList<TypeAnnotationInfo> typeAnnotations,
       @Nullable ModuleInfo module,
+      @Nullable String nestHost,
+      ImmutableList<String> nestMembers,
+      @Nullable RecordInfo record,
       @Nullable String transitiveJar) {
     this.access = access;
     this.name = name;
@@ -68,6 +74,9 @@
     this.innerClasses = innerClasses;
     this.typeAnnotations = typeAnnotations;
     this.module = module;
+    this.nestHost = nestHost;
+    this.nestMembers = nestMembers;
+    this.record = record;
     this.transitiveJar = transitiveJar;
   }
 
@@ -126,6 +135,18 @@
     return module;
   }
 
+  public @Nullable String nestHost() {
+    return nestHost;
+  }
+
+  public ImmutableList<String> nestMembers() {
+    return nestMembers;
+  }
+
+  public @Nullable RecordInfo record() {
+    return record;
+  }
+
   /** The original jar of a repackaged transitive class. */
   public @Nullable String transitiveJar() {
     return transitiveJar;
@@ -937,4 +958,61 @@
       }
     }
   }
+
+  /** A JVMS §4.7.30 Record attribute. */
+  public static class RecordInfo {
+
+    /** A JVMS §4.7.30 Record component attribute. */
+    public static class RecordComponentInfo {
+
+      private final String name;
+      private final String descriptor;
+      private final @Nullable String signature;
+      private final ImmutableList<AnnotationInfo> annotations;
+      private final ImmutableList<TypeAnnotationInfo> typeAnnotations;
+
+      public RecordComponentInfo(
+          String name,
+          String descriptor,
+          @Nullable String signature,
+          ImmutableList<AnnotationInfo> annotations,
+          ImmutableList<TypeAnnotationInfo> typeAnnotations) {
+        this.name = name;
+        this.descriptor = descriptor;
+        this.signature = signature;
+        this.annotations = annotations;
+        this.typeAnnotations = typeAnnotations;
+      }
+
+      public String name() {
+        return name;
+      }
+
+      public String descriptor() {
+        return descriptor;
+      }
+
+      public @Nullable String signature() {
+        return signature;
+      }
+
+      public ImmutableList<AnnotationInfo> annotations() {
+        return annotations;
+      }
+
+      public ImmutableList<TypeAnnotationInfo> typeAnnotations() {
+        return typeAnnotations;
+      }
+    }
+
+    public RecordInfo(ImmutableList<RecordComponentInfo> recordComponents) {
+      this.recordComponents = recordComponents;
+    }
+
+    private final ImmutableList<RecordComponentInfo> recordComponents;
+
+    public ImmutableList<RecordComponentInfo> recordComponents() {
+      return recordComponents;
+    }
+  }
 }
diff --git a/java/com/google/turbine/bytecode/ClassReader.java b/java/com/google/turbine/bytecode/ClassReader.java
index e3ade24..6518230 100644
--- a/java/com/google/turbine/bytecode/ClassReader.java
+++ b/java/com/google/turbine/bytecode/ClassReader.java
@@ -148,6 +148,9 @@
         innerclasses,
         ImmutableList.of(),
         module,
+        /* nestHost= */ null,
+        /* nestMembers= */ ImmutableList.of(),
+        /* record= */ null,
         transitiveJar);
   }
 
diff --git a/java/com/google/turbine/bytecode/ClassWriter.java b/java/com/google/turbine/bytecode/ClassWriter.java
index de975f2..8b17e04 100644
--- a/java/com/google/turbine/bytecode/ClassWriter.java
+++ b/java/com/google/turbine/bytecode/ClassWriter.java
@@ -31,10 +31,6 @@
 
   private static final int MAGIC = 0xcafebabe;
   private static final int MINOR_VERSION = 0;
-  // use the lowest classfile version possible given the class file features
-  // TODO(cushon): is there a reason to support --release?
-  private static final int MAJOR_VERSION = 52;
-  private static final int MODULE_MAJOR_VERSION = 53;
 
   /** Writes a {@link ClassFile} to bytecode. */
   public static byte[] writeClass(ClassFile classfile) {
@@ -79,7 +75,7 @@
       ConstantPool pool, ByteArrayDataOutput body, List<Attribute> attributes) {
     body.writeShort(attributes.size());
     for (Attribute attribute : attributes) {
-      new AttributeWriter(pool, body).write(attribute);
+      new AttributeWriter(pool).write(body, attribute);
     }
   }
 
@@ -119,11 +115,25 @@
     ByteArrayDataOutput result = ByteStreams.newDataOutput();
     result.writeInt(MAGIC);
     result.writeShort(MINOR_VERSION);
-    result.writeShort(classfile.module() != null ? MODULE_MAJOR_VERSION : MAJOR_VERSION);
+    result.writeShort(majorVersion(classfile));
     writeConstantPool(pool, result);
     result.write(body.toByteArray());
     return result.toByteArray();
   }
 
+  // use the lowest classfile version possible given the class file features
+  // TODO(cushon): is there a reason to support --release?
+  private static int majorVersion(ClassFile classfile) {
+    if (classfile.nestHost() != null
+        || !classfile.nestMembers().isEmpty()
+        || classfile.record() != null) {
+      return 60;
+    }
+    if (classfile.module() != null) {
+      return 53;
+    }
+    return 52;
+  }
+
   private ClassWriter() {}
 }
diff --git a/java/com/google/turbine/bytecode/LowerAttributes.java b/java/com/google/turbine/bytecode/LowerAttributes.java
index 5ae42af..54937fc 100644
--- a/java/com/google/turbine/bytecode/LowerAttributes.java
+++ b/java/com/google/turbine/bytecode/LowerAttributes.java
@@ -45,12 +45,36 @@
     if (classfile.module() != null) {
       attributes.add(new Attribute.Module(classfile.module()));
     }
+    if (classfile.nestHost() != null) {
+      attributes.add(new Attribute.NestHost(classfile.nestHost()));
+    }
+    if (!classfile.nestMembers().isEmpty()) {
+      attributes.add(new Attribute.NestMembers(classfile.nestMembers()));
+    }
+    if (classfile.record() != null) {
+      attributes.add(recordAttribute(classfile.record()));
+    }
     if (classfile.transitiveJar() != null) {
       attributes.add(new Attribute.TurbineTransitiveJar(classfile.transitiveJar()));
     }
     return attributes;
   }
 
+  private static Attribute recordAttribute(ClassFile.RecordInfo record) {
+    ImmutableList.Builder<Attribute.Record.Component> components = ImmutableList.builder();
+    for (ClassFile.RecordInfo.RecordComponentInfo component : record.recordComponents()) {
+      List<Attribute> attributes = new ArrayList<>();
+      if (component.signature() != null) {
+        attributes.add(new Attribute.Signature(component.signature()));
+      }
+      addAllAnnotations(attributes, component.annotations());
+      addAllTypeAnnotations(attributes, component.typeAnnotations());
+      components.add(
+          new Attribute.Record.Component(component.name(), component.descriptor(), attributes));
+    }
+    return new Attribute.Record(components.build());
+  }
+
   /** Collects the {@link Attribute}s for a {@link MethodInfo}. */
   static List<Attribute> methodAttributes(ClassFile.MethodInfo method) {
     List<Attribute> attributes = new ArrayList<>();
diff --git a/java/com/google/turbine/deps/Transitive.java b/java/com/google/turbine/deps/Transitive.java
index 673c372..e80484b 100644
--- a/java/com/google/turbine/deps/Transitive.java
+++ b/java/com/google/turbine/deps/Transitive.java
@@ -105,6 +105,9 @@
         innerClasses.build(),
         cf.typeAnnotations(),
         /* module= */ null,
+        /* nestHost= */ null,
+        /* nestMembers= */ ImmutableList.of(),
+        /* record= */ null,
         /* transitiveJar = */ transitiveJar);
   }
 
diff --git a/java/com/google/turbine/lower/Lower.java b/java/com/google/turbine/lower/Lower.java
index ad1be7c..05f0ec2 100644
--- a/java/com/google/turbine/lower/Lower.java
+++ b/java/com/google/turbine/lower/Lower.java
@@ -187,6 +187,9 @@
             innerClasses.build(),
             /* typeAnnotations= */ ImmutableList.of(),
             moduleInfo,
+            /* nestHost= */ null,
+            /* nestMembers= */ ImmutableList.of(),
+            /* record= */ null,
             /* transitiveJar= */ null);
     symbols.addAll(sig.classes);
     return ClassWriter.writeClass(classfile);
@@ -282,6 +285,9 @@
             inners,
             typeAnnotations,
             /* module= */ null,
+            /* nestHost= */ null,
+            /* nestMembers= */ ImmutableList.of(),
+            /* record= */ null,
             /* transitiveJar= */ null);
 
     symbols.addAll(sig.classes);
diff --git a/javatests/com/google/turbine/bytecode/ClassWriterTest.java b/javatests/com/google/turbine/bytecode/ClassWriterTest.java
index f488bbe..eceea21 100644
--- a/javatests/com/google/turbine/bytecode/ClassWriterTest.java
+++ b/javatests/com/google/turbine/bytecode/ClassWriterTest.java
@@ -21,6 +21,7 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.io.ByteArrayDataOutput;
 import com.google.common.io.ByteStreams;
 import com.google.common.jimfs.Configuration;
@@ -46,6 +47,7 @@
 import org.junit.runners.JUnit4;
 import org.objectweb.asm.ModuleVisitor;
 import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.RecordComponentVisitor;
 
 @RunWith(JUnit4.class)
 public class ClassWriterTest {
@@ -154,4 +156,102 @@
     assertThat(AsmUtils.textify(inputBytes, /* skipDebug= */ true))
         .isEqualTo(AsmUtils.textify(outputBytes, /* skipDebug= */ true));
   }
+
+  @Test
+  public void record() {
+
+    org.objectweb.asm.ClassWriter cw = new org.objectweb.asm.ClassWriter(0);
+
+    cw.visit(
+        Opcodes.V16,
+        Opcodes.ACC_FINAL | Opcodes.ACC_SUPER | Opcodes.ACC_RECORD,
+        "R",
+        /* signature= */ null,
+        "java/lang/Record",
+        /* interfaces= */ null);
+
+    RecordComponentVisitor rv =
+        cw.visitRecordComponent("x", "Ljava/util/List;", "Ljava/util/List<Ljava/lang/Integer;>;");
+    rv.visitAnnotation("LA;", true);
+    rv.visitTypeAnnotation(318767104, null, "LA;", true);
+    cw.visitRecordComponent("y", "I", null);
+
+    byte[] expectedBytes = cw.toByteArray();
+
+    ClassFile classFile =
+        new ClassFile(
+            /* access= */ Opcodes.ACC_FINAL | Opcodes.ACC_SUPER | Opcodes.ACC_RECORD,
+            /* name= */ "R",
+            /* signature= */ null,
+            /* superClass= */ "java/lang/Record",
+            /* interfaces= */ ImmutableList.of(),
+            /* methods= */ ImmutableList.of(),
+            /* fields= */ ImmutableList.of(),
+            /* annotations= */ ImmutableList.of(),
+            /* innerClasses= */ ImmutableList.of(),
+            /* typeAnnotations= */ ImmutableList.of(),
+            /* module= */ null,
+            /* nestHost= */ null,
+            /* nestMembers= */ ImmutableList.of(),
+            /* record= */ new ClassFile.RecordInfo(
+                ImmutableList.of(
+                    new ClassFile.RecordInfo.RecordComponentInfo(
+                        "x",
+                        "Ljava/util/List;",
+                        "Ljava/util/List<Ljava/lang/Integer;>;",
+                        ImmutableList.of(
+                            new ClassFile.AnnotationInfo("LA;", true, ImmutableMap.of())),
+                        ImmutableList.of(
+                            new ClassFile.TypeAnnotationInfo(
+                                ClassFile.TypeAnnotationInfo.TargetType.FIELD,
+                                ClassFile.TypeAnnotationInfo.EMPTY_TARGET,
+                                ClassFile.TypeAnnotationInfo.TypePath.root(),
+                                new ClassFile.AnnotationInfo("LA;", true, ImmutableMap.of())))),
+                    new ClassFile.RecordInfo.RecordComponentInfo(
+                        "y", "I", null, ImmutableList.of(), ImmutableList.of()))),
+            /* transitiveJar= */ null);
+
+    byte[] actualBytes = ClassWriter.writeClass(classFile);
+
+    assertThat(AsmUtils.textify(actualBytes, /* skipDebug= */ true))
+        .isEqualTo(AsmUtils.textify(expectedBytes, /* skipDebug= */ true));
+  }
+
+  @Test
+  public void nestHost() {
+
+    org.objectweb.asm.ClassWriter cw = new org.objectweb.asm.ClassWriter(0);
+
+    cw.visit(Opcodes.V16, Opcodes.ACC_SUPER, "N", null, null, null);
+
+    cw.visitNestHost("H");
+    cw.visitNestMember("A");
+    cw.visitNestMember("B");
+    cw.visitNestMember("C");
+
+    byte[] expectedBytes = cw.toByteArray();
+
+    ClassFile classFile =
+        new ClassFile(
+            /* access= */ Opcodes.ACC_SUPER,
+            /* name= */ "N",
+            /* signature= */ null,
+            /* superClass= */ null,
+            /* interfaces= */ ImmutableList.of(),
+            /* methods= */ ImmutableList.of(),
+            /* fields= */ ImmutableList.of(),
+            /* annotations= */ ImmutableList.of(),
+            /* innerClasses= */ ImmutableList.of(),
+            /* typeAnnotations= */ ImmutableList.of(),
+            /* module= */ null,
+            /* nestHost= */ "H",
+            /* nestMembers= */ ImmutableList.of("A", "B", "C"),
+            /* record= */ null,
+            /* transitiveJar= */ null);
+
+    byte[] actualBytes = ClassWriter.writeClass(classFile);
+
+    assertThat(AsmUtils.textify(actualBytes, /* skipDebug= */ true))
+        .isEqualTo(AsmUtils.textify(expectedBytes, /* skipDebug= */ true));
+  }
 }