From 1b575d79030b98c35a9587bfbdec6c7394c021a8 Mon Sep 17 00:00:00 2001 From: "Denis.Zharkov" Date: Fri, 27 Nov 2020 15:56:07 +0300 Subject: [PATCH] Add initial support for @JvmRecord in backend - Write relevant class files attributes - Emit property accessors with records-convention: propertyName -> propertyName() ^KT-43677 In Progress --- .../codegen/ImplementationBodyCodegen.java | 5 +++ .../kotlin/codegen/PropertyCodegen.java | 8 +++- .../kotlin/codegen/state/KotlinTypeMapper.kt | 7 +++- .../org/jetbrains/kotlin/config/JvmTarget.kt | 8 ++-- .../backend/jvm/codegen/ClassCodegen.kt | 7 ++++ .../jvm/codegen/MethodSignatureMapper.kt | 3 +- .../box/records/bytecodeShapeForJava.kt | 29 ++++++++++++++ .../recordDifferentPropertyOverride.kt | 0 .../recordDifferentSyntheticProperty.kt | 0 .../box/{ => records}/recordPropertyAccess.kt | 0 .../AbstractJdk15BlackBoxCodegenTest.kt | 4 +- .../Jdk15BlackBoxCodegenTestGenerated.java | 40 ++++++++++++++----- 12 files changed, 93 insertions(+), 18 deletions(-) create mode 100644 compiler/testData/codegen/java15/box/records/bytecodeShapeForJava.kt rename compiler/testData/codegen/java15/box/{ => records}/recordDifferentPropertyOverride.kt (100%) rename compiler/testData/codegen/java15/box/{ => records}/recordDifferentSyntheticProperty.kt (100%) rename compiler/testData/codegen/java15/box/{ => records}/recordPropertyAccess.kt (100%) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java index c9c28a57deb..caa9fedb583 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java @@ -43,6 +43,7 @@ import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall; import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt; +import org.jetbrains.kotlin.resolve.jvm.annotations.JvmAnnotationUtilKt; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKt; import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmClassSignature; @@ -221,6 +222,10 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { access |= ACC_ENUM; } + if (JvmAnnotationUtilKt.isJvmRecord(descriptor)) { + access |= ACC_RECORD; + } + v.defineClass( myClass.getPsiOrParent(), state.getClassFileVersion(), diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/PropertyCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/PropertyCodegen.java index c10a8e61814..449fb9a82d3 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/PropertyCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/PropertyCodegen.java @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.resolve.InlineClassesUtilsKt; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.util.UnderscoreUtilKt; import org.jetbrains.kotlin.resolve.constants.ConstantValue; +import org.jetbrains.kotlin.resolve.jvm.annotations.JvmAnnotationUtilKt; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKt; import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodGenericSignature; import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature; @@ -435,9 +436,10 @@ public class PropertyCodegen { v.getSerializationBindings().put(FIELD_FOR_PROPERTY, propertyDescriptor, new Pair<>(type, name)); if (isBackingFieldOwner) { + String signature = isDelegate ? null : typeMapper.mapFieldSignature(kotlinType, propertyDescriptor); FieldVisitor fv = builder.newField( JvmDeclarationOriginKt.OtherOrigin(propertyDescriptor), modifiers, name, type.getDescriptor(), - isDelegate ? null : typeMapper.mapFieldSignature(kotlinType, propertyDescriptor), defaultValue + signature, defaultValue ); if (annotatedField != null) { @@ -450,6 +452,10 @@ public class PropertyCodegen { AnnotationCodegen.forField(fv, memberCodegen, state, skipNullabilityAnnotations) .genAnnotations(annotatedField, type, propertyDescriptor.getType(), null, additionalVisibleAnnotations); } + + if (propertyDescriptor.getContainingDeclaration() instanceof ClassDescriptor && JvmAnnotationUtilKt.isJvmRecord((ClassDescriptor) propertyDescriptor.getContainingDeclaration())) { + builder.newRecordComponent(name, type.getDescriptor(), signature); + } } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.kt index df20065d4be..b6c994a9b89 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.kt @@ -63,6 +63,7 @@ import org.jetbrains.kotlin.resolve.jvm.AsmTypes.DEFAULT_CONSTRUCTOR_MARKER import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.kotlin.resolve.jvm.annotations.isCompiledToJvmDefault +import org.jetbrains.kotlin.resolve.jvm.annotations.isJvmRecord import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodGenericSignature import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature @@ -583,12 +584,16 @@ class KotlinTypeMapper @JvmOverloads constructor( return when { descriptor is PropertyAccessorDescriptor -> { val property = descriptor.correspondingProperty - if (isAnnotationClass(property.containingDeclaration) && + val containingDeclaration = property.containingDeclaration + + if (isAnnotationClass(containingDeclaration) && (!property.hasJvmStaticAnnotation() && !descriptor.hasJvmStaticAnnotation()) ) { return property.name.asString() } + if ((containingDeclaration as? ClassDescriptor)?.isJvmRecord() == true) return property.name.asString() + val isAccessor = property is AccessorForPropertyDescriptor val propertyName = if (isAccessor) (property as AccessorForPropertyDescriptor).accessorSuffix diff --git a/compiler/config.jvm/src/org/jetbrains/kotlin/config/JvmTarget.kt b/compiler/config.jvm/src/org/jetbrains/kotlin/config/JvmTarget.kt index ea140a8d9af..28dd3296a6a 100644 --- a/compiler/config.jvm/src/org/jetbrains/kotlin/config/JvmTarget.kt +++ b/compiler/config.jvm/src/org/jetbrains/kotlin/config/JvmTarget.kt @@ -29,6 +29,7 @@ enum class JvmTarget(override val description: String) : TargetPlatformVersion { JVM_13("13"), JVM_14("14"), JVM_15("15"), + JVM_15_PREVIEW("15_PREVIEW"), ; val bytecodeVersion: Int by lazy { @@ -39,9 +40,10 @@ enum class JvmTarget(override val description: String) : TargetPlatformVersion { JVM_10 -> Opcodes.V10 JVM_11 -> Opcodes.V11 JVM_12 -> Opcodes.V12 - JVM_13 -> Opcodes.V12 + 1 - JVM_14 -> Opcodes.V12 + 2 - JVM_15 -> Opcodes.V12 + 3 + JVM_13 -> Opcodes.V13 + JVM_14 -> Opcodes.V14 + JVM_15 -> Opcodes.V15 + JVM_15_PREVIEW -> Opcodes.V15 + (0xffff shl 16) } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt index c19804ada95..21758e69cd7 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt @@ -33,6 +33,7 @@ import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader import org.jetbrains.kotlin.metadata.jvm.serialization.JvmStringTable import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.protobuf.MessageLite +import org.jetbrains.kotlin.resolve.jvm.annotations.JVM_RECORD_ANNOTATION_FQ_NAME import org.jetbrains.kotlin.resolve.jvm.annotations.JVM_SYNTHETIC_ANNOTATION_FQ_NAME import org.jetbrains.kotlin.resolve.jvm.annotations.TRANSIENT_ANNOTATION_FQ_NAME import org.jetbrains.kotlin.resolve.jvm.annotations.VOLATILE_ANNOTATION_FQ_NAME @@ -306,6 +307,11 @@ class ClassCodegen private constructor( (field.metadata as? MetadataSource.Property)?.let { metadataSerializer.bindFieldMetadata(it, fieldType to fieldName) } + + if (irClass.hasAnnotation(JVM_RECORD_ANNOTATION_FQ_NAME) && !field.isStatic) { + // TODO: Write annotations to the component + visitor.newRecordComponent(fieldName, fieldType.descriptor, fieldSignature) + } } private val generatedInlineMethods = mutableMapOf() @@ -452,6 +458,7 @@ private val IrClass.flags: Int isAnnotationClass -> Opcodes.ACC_ANNOTATION or Opcodes.ACC_INTERFACE or Opcodes.ACC_ABSTRACT isInterface -> Opcodes.ACC_INTERFACE or Opcodes.ACC_ABSTRACT isEnumClass -> Opcodes.ACC_ENUM or Opcodes.ACC_SUPER or modality.flags + hasAnnotation(JVM_RECORD_ANNOTATION_FQ_NAME) -> Opcodes.ACC_RECORD or Opcodes.ACC_SUPER or modality.flags else -> Opcodes.ACC_SUPER or modality.flags } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/MethodSignatureMapper.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/MethodSignatureMapper.kt index ee87513a15d..1e9427642f5 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/MethodSignatureMapper.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/MethodSignatureMapper.kt @@ -41,6 +41,7 @@ import org.jetbrains.kotlin.metadata.deserialization.getExtensionOrNull import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil import org.jetbrains.kotlin.name.NameUtils +import org.jetbrains.kotlin.resolve.jvm.annotations.JVM_RECORD_ANNOTATION_FQ_NAME import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodGenericSignature import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature @@ -83,7 +84,7 @@ class MethodSignatureMapper(private val context: JvmBackendContext) { if (property != null) { val propertyName = property.name.asString() val propertyParent = property.parentAsClass - if (propertyParent.isAnnotationClass) + if (propertyParent.isAnnotationClass || propertyParent.hasAnnotation(JVM_RECORD_ANNOTATION_FQ_NAME)) return propertyName // The enum property getters and have special names which also diff --git a/compiler/testData/codegen/java15/box/records/bytecodeShapeForJava.kt b/compiler/testData/codegen/java15/box/records/bytecodeShapeForJava.kt new file mode 100644 index 00000000000..53ea772abe1 --- /dev/null +++ b/compiler/testData/codegen/java15/box/records/bytecodeShapeForJava.kt @@ -0,0 +1,29 @@ +// !LANGUAGE: +JvmRecordSupport +// JVM_TARGET: 15_PREVIEW +// FILE: JavaClass.java +public class JavaClass { + public static String box() { + MyRec m = new MyRec("O", "K"); + return m.x() + m.y(); + } +} +// FILE: main.kt + +@JvmRecord +class MyRec(val x: String, val y: R) + +fun box(): String { + val recordComponents = MyRec::class.java.recordComponents + val x = recordComponents[0] + val y = recordComponents[1] + + if (x.name != "x") return "fail 1: ${x.name}" + if (x.type != String::class.java) return "fail 2: ${x.type}" + if (x.genericSignature != null) return "fail 3: ${x.genericSignature}" + + if (y.name != "y") return "fail 4: ${y.name}" + if (y.type != Any::class.java) return "fail 5: ${y.type}" + if (y.genericSignature != "TR;") return "fail 6: ${y.genericSignature}" + + return JavaClass.box() +} diff --git a/compiler/testData/codegen/java15/box/recordDifferentPropertyOverride.kt b/compiler/testData/codegen/java15/box/records/recordDifferentPropertyOverride.kt similarity index 100% rename from compiler/testData/codegen/java15/box/recordDifferentPropertyOverride.kt rename to compiler/testData/codegen/java15/box/records/recordDifferentPropertyOverride.kt diff --git a/compiler/testData/codegen/java15/box/recordDifferentSyntheticProperty.kt b/compiler/testData/codegen/java15/box/records/recordDifferentSyntheticProperty.kt similarity index 100% rename from compiler/testData/codegen/java15/box/recordDifferentSyntheticProperty.kt rename to compiler/testData/codegen/java15/box/records/recordDifferentSyntheticProperty.kt diff --git a/compiler/testData/codegen/java15/box/recordPropertyAccess.kt b/compiler/testData/codegen/java15/box/records/recordPropertyAccess.kt similarity index 100% rename from compiler/testData/codegen/java15/box/recordPropertyAccess.kt rename to compiler/testData/codegen/java15/box/records/recordPropertyAccess.kt diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/AbstractJdk15BlackBoxCodegenTest.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/AbstractJdk15BlackBoxCodegenTest.kt index 6e50e9a59be..b46c6b1f3cd 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/AbstractJdk15BlackBoxCodegenTest.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/AbstractJdk15BlackBoxCodegenTest.kt @@ -17,4 +17,6 @@ abstract class AbstractJdk15BlackBoxCodegenTest : AbstractCustomJDKBlackBoxCodeg override fun getAdditionalJavacArgs(): List = ADDITIONAL_JAVAC_ARGS_FOR_15 override fun getAdditionalJvmArgs(): List = listOf("--enable-preview") -} \ No newline at end of file + + override fun verifyWithDex(): Boolean = false +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/Jdk15BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/Jdk15BlackBoxCodegenTestGenerated.java index 3fb4b629608..c7a922ea017 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/Jdk15BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/Jdk15BlackBoxCodegenTestGenerated.java @@ -28,18 +28,36 @@ public class Jdk15BlackBoxCodegenTestGenerated extends AbstractJdk15BlackBoxCode KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/java15/box"), Pattern.compile("^(.+)\\.kt$"), null, true); } - @TestMetadata("recordDifferentPropertyOverride.kt") - public void testRecordDifferentPropertyOverride() throws Exception { - runTest("compiler/testData/codegen/java15/box/recordDifferentPropertyOverride.kt"); - } + @TestMetadata("compiler/testData/codegen/java15/box/records") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Records extends AbstractJdk15BlackBoxCodegenTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } - @TestMetadata("recordDifferentSyntheticProperty.kt") - public void testRecordDifferentSyntheticProperty() throws Exception { - runTest("compiler/testData/codegen/java15/box/recordDifferentSyntheticProperty.kt"); - } + public void testAllFilesPresentInRecords() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/java15/box/records"), Pattern.compile("^(.+)\\.kt$"), null, true); + } - @TestMetadata("recordPropertyAccess.kt") - public void testRecordPropertyAccess() throws Exception { - runTest("compiler/testData/codegen/java15/box/recordPropertyAccess.kt"); + @TestMetadata("bytecodeShapeForJava.kt") + public void testBytecodeShapeForJava() throws Exception { + runTest("compiler/testData/codegen/java15/box/records/bytecodeShapeForJava.kt"); + } + + @TestMetadata("recordDifferentPropertyOverride.kt") + public void testRecordDifferentPropertyOverride() throws Exception { + runTest("compiler/testData/codegen/java15/box/records/recordDifferentPropertyOverride.kt"); + } + + @TestMetadata("recordDifferentSyntheticProperty.kt") + public void testRecordDifferentSyntheticProperty() throws Exception { + runTest("compiler/testData/codegen/java15/box/records/recordDifferentSyntheticProperty.kt"); + } + + @TestMetadata("recordPropertyAccess.kt") + public void testRecordPropertyAccess() throws Exception { + runTest("compiler/testData/codegen/java15/box/records/recordPropertyAccess.kt"); + } } }