diff --git a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java index b86d0c4d..05608aae 100644 --- a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java +++ b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java @@ -33,6 +33,7 @@ import com.google.turbine.binder.sym.FieldSymbol; import com.google.turbine.binder.sym.MethodSymbol; import com.google.turbine.binder.sym.ParamSymbol; +import com.google.turbine.binder.sym.RecordComponentSymbol; import com.google.turbine.binder.sym.TyVarSymbol; import com.google.turbine.bytecode.ClassFile; import com.google.turbine.bytecode.ClassFile.AnnotationInfo; @@ -42,6 +43,7 @@ import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.EnumConstValue; import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.Kind; import com.google.turbine.bytecode.ClassFile.MethodInfo.ParameterInfo; +import com.google.turbine.bytecode.ClassFile.RecordInfo; import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo; import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo.TargetType; import com.google.turbine.bytecode.ClassReader; @@ -612,9 +614,38 @@ public ImmutableList methods() { return methods.get(); } + private final Supplier> components = + Suppliers.memoize( + new Supplier>() { + @Override + public ImmutableList get() { + var record = classFile.get().record(); + if (record == null) { + return ImmutableList.of(); + } + ImmutableList.Builder result = ImmutableList.builder(); + for (RecordInfo.RecordComponentInfo component : record.recordComponents()) { + Type type = + BytecodeBinder.bindTy( + new SigParser(firstNonNull(component.signature(), component.descriptor())) + .parseType(), + makeScope(env, sym, ImmutableMap.of()), + typeAnnotationsForTarget(component.typeAnnotations(), TargetType.FIELD)); + result.add( + new RecordComponentInfo( + new RecordComponentSymbol(sym, component.name()), + type, + BytecodeBinder.bindAnnotations( + component.annotations(), makeScope(env, sym, ImmutableMap.of())), + /* access= */ 0)); + } + return result.build(); + } + }); + @Override public ImmutableList components() { - return ImmutableList.of(); + return components.get(); } private final Supplier<@Nullable AnnotationMetadata> annotationMetadata = diff --git a/java/com/google/turbine/bytecode/ClassReader.java b/java/com/google/turbine/bytecode/ClassReader.java index 8c398ca7..3a94cd95 100644 --- a/java/com/google/turbine/bytecode/ClassReader.java +++ b/java/com/google/turbine/bytecode/ClassReader.java @@ -36,6 +36,7 @@ import com.google.turbine.bytecode.ClassFile.ModuleInfo.ProvideInfo; import com.google.turbine.bytecode.ClassFile.ModuleInfo.RequireInfo; import com.google.turbine.bytecode.ClassFile.ModuleInfo.UseInfo; +import com.google.turbine.bytecode.ClassFile.RecordInfo; import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo; import com.google.turbine.model.Const; import com.google.turbine.model.TurbineFlag; @@ -112,6 +113,7 @@ private ClassFile read() { ImmutableList.Builder typeAnnotations = ImmutableList.builder(); ClassFile.ModuleInfo module = null; String transitiveJar = null; + RecordInfo record = null; int attributesCount = reader.u2(); for (int j = 0; j < attributesCount; j++) { int attributeNameIndex = reader.u2(); @@ -141,6 +143,9 @@ private ClassFile read() { case "TurbineTransitiveJar": transitiveJar = readTurbineTransitiveJar(constantPool); break; + case "Record": + record = readRecord(constantPool); + break; default: reader.skip(reader.u4()); break; @@ -163,7 +168,7 @@ private ClassFile read() { module, /* nestHost= */ null, /* nestMembers= */ ImmutableList.of(), - /* record= */ null, + record, transitiveJar); } @@ -688,4 +693,47 @@ private String readTurbineTransitiveJar(ConstantPoolReader constantPool) { int unusedLength = reader.u4(); return constantPool.utf8(reader.u2()); } + + private RecordInfo readRecord(ConstantPoolReader constantPool) { + int unusedLength = reader.u4(); + int componentsCount = reader.u2(); + + ImmutableList.Builder components = ImmutableList.builder(); + for (int i = 0; i < componentsCount; i++) { + String name = constantPool.utf8(reader.u2()); + String descriptor = constantPool.utf8(reader.u2()); + + int attributesCount = reader.u2(); + ImmutableList.Builder annotations = ImmutableList.builder(); + ImmutableList.Builder typeAnnotations = ImmutableList.builder(); + String signature = null; + for (int j = 0; j < attributesCount; j++) { + String attributeName = constantPool.utf8(reader.u2()); + switch (attributeName) { + case "RuntimeInvisibleAnnotations": + readAnnotations(annotations, constantPool, RuntimeVisibility.INVISIBLE); + break; + case "RuntimeVisibleAnnotations": + readAnnotations(annotations, constantPool, RuntimeVisibility.VISIBLE); + break; + case "RuntimeInvisibleTypeAnnotations": + readTypeAnnotations(typeAnnotations, constantPool, RuntimeVisibility.INVISIBLE); + break; + case "RuntimeVisibleTypeAnnotations": + readTypeAnnotations(typeAnnotations, constantPool, RuntimeVisibility.VISIBLE); + break; + case "Signature": + signature = readSignature(constantPool); + break; + default: + reader.skip(reader.u4()); + break; + } + } + components.add( + new RecordInfo.RecordComponentInfo( + name, descriptor, signature, annotations.build(), typeAnnotations.build())); + } + return new RecordInfo(components.build()); + } } diff --git a/javatests/com/google/turbine/lower/IntegrationTestSupport.java b/javatests/com/google/turbine/lower/IntegrationTestSupport.java index 45aac53b..7241cf66 100644 --- a/javatests/com/google/turbine/lower/IntegrationTestSupport.java +++ b/javatests/com/google/turbine/lower/IntegrationTestSupport.java @@ -144,10 +144,7 @@ public static Map removeUnsupportedAttributes(Map annotations, RoundEnvironment roundEnv) { + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.ERROR, + roundEnv + .getElementsAnnotatedWith(processingEnv.getElementUtils().getTypeElement("foo.R")) + .stream() + .flatMap(e -> processingEnv.getElementUtils().getAllAnnotationMirrors(e).stream()) + .flatMap(a -> a.getElementValues().values().stream()) + .flatMap( + x -> + MoreElements.asType( + MoreTypes.asDeclared((TypeMirror) x.getValue()).asElement()) + .getRecordComponents() + .stream()) + .map(x -> x.getSimpleName()) + .collect(toImmutableList()) + .toString()); + return false; + } + } + + @Test + public void bytecodeRecord_componentsAvailable() throws IOException { + Map library = + IntegrationTestSupport.runTurbine( + ImmutableMap.of( + "MyRecord.java", "package foo; public record MyRecord(int x, int y) {}"), + ImmutableList.of()); + Path libJar = temporaryFolder.newFile("lib.jar").toPath(); + try (OutputStream os = Files.newOutputStream(libJar); + JarOutputStream jos = new JarOutputStream(os)) { + jos.putNextEntry(new JarEntry("foo/MyRecord.class")); + jos.write(requireNonNull(library.get("foo/MyRecord"))); + } + + ImmutableList units = + parseUnit( + "=== Y.java ===", // + "package foo;", + "@interface R { Class value(); }", + "@R(MyRecord.class)", + "class Y {}"); + + TurbineLog log = new TurbineLog(); + BindingResult unused = + Binder.bind( + log, + units, + ClassPathBinder.bindClasspath(ImmutableList.of(libJar)), + ProcessorInfo.create( + ImmutableList.of(new RecordFromADistanceProcessor()), + getClass().getClassLoader(), + ImmutableMap.of(), + SourceVersion.latestSupported()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + ImmutableList messages = + log.diagnostics().stream().map(TurbineDiagnostic::message).collect(toImmutableList()); + assertThat(messages).contains("[x, y]"); + } + @Test public void missingElementValue() { ImmutableList units =