Add initial support for @JvmRecord in backend

- Write relevant class files attributes
- Emit property accessors with records-convention: propertyName -> propertyName()

^KT-43677 In Progress
This commit is contained in:
Denis.Zharkov
2020-11-27 15:56:07 +03:00
parent 26d525fa3c
commit 1b575d7903
12 changed files with 93 additions and 18 deletions
@@ -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.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall; import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt; 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.JvmDeclarationOrigin;
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKt; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKt;
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmClassSignature; import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmClassSignature;
@@ -221,6 +222,10 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
access |= ACC_ENUM; access |= ACC_ENUM;
} }
if (JvmAnnotationUtilKt.isJvmRecord(descriptor)) {
access |= ACC_RECORD;
}
v.defineClass( v.defineClass(
myClass.getPsiOrParent(), myClass.getPsiOrParent(),
state.getClassFileVersion(), state.getClassFileVersion(),
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.resolve.InlineClassesUtilsKt;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.util.UnderscoreUtilKt; import org.jetbrains.kotlin.resolve.calls.util.UnderscoreUtilKt;
import org.jetbrains.kotlin.resolve.constants.ConstantValue; 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.diagnostics.JvmDeclarationOriginKt;
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodGenericSignature; import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodGenericSignature;
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature; 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)); v.getSerializationBindings().put(FIELD_FOR_PROPERTY, propertyDescriptor, new Pair<>(type, name));
if (isBackingFieldOwner) { if (isBackingFieldOwner) {
String signature = isDelegate ? null : typeMapper.mapFieldSignature(kotlinType, propertyDescriptor);
FieldVisitor fv = builder.newField( FieldVisitor fv = builder.newField(
JvmDeclarationOriginKt.OtherOrigin(propertyDescriptor), modifiers, name, type.getDescriptor(), JvmDeclarationOriginKt.OtherOrigin(propertyDescriptor), modifiers, name, type.getDescriptor(),
isDelegate ? null : typeMapper.mapFieldSignature(kotlinType, propertyDescriptor), defaultValue signature, defaultValue
); );
if (annotatedField != null) { if (annotatedField != null) {
@@ -450,6 +452,10 @@ public class PropertyCodegen {
AnnotationCodegen.forField(fv, memberCodegen, state, skipNullabilityAnnotations) AnnotationCodegen.forField(fv, memberCodegen, state, skipNullabilityAnnotations)
.genAnnotations(annotatedField, type, propertyDescriptor.getType(), null, additionalVisibleAnnotations); .genAnnotations(annotatedField, type, propertyDescriptor.getType(), null, additionalVisibleAnnotations);
} }
if (propertyDescriptor.getContainingDeclaration() instanceof ClassDescriptor && JvmAnnotationUtilKt.isJvmRecord((ClassDescriptor) propertyDescriptor.getContainingDeclaration())) {
builder.newRecordComponent(name, type.getDescriptor(), signature);
}
} }
} }
@@ -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.AsmTypes.OBJECT_TYPE
import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.resolve.jvm.annotations.isCompiledToJvmDefault 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.JvmMethodGenericSignature
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
@@ -583,12 +584,16 @@ class KotlinTypeMapper @JvmOverloads constructor(
return when { return when {
descriptor is PropertyAccessorDescriptor -> { descriptor is PropertyAccessorDescriptor -> {
val property = descriptor.correspondingProperty val property = descriptor.correspondingProperty
if (isAnnotationClass(property.containingDeclaration) && val containingDeclaration = property.containingDeclaration
if (isAnnotationClass(containingDeclaration) &&
(!property.hasJvmStaticAnnotation() && !descriptor.hasJvmStaticAnnotation()) (!property.hasJvmStaticAnnotation() && !descriptor.hasJvmStaticAnnotation())
) { ) {
return property.name.asString() return property.name.asString()
} }
if ((containingDeclaration as? ClassDescriptor)?.isJvmRecord() == true) return property.name.asString()
val isAccessor = property is AccessorForPropertyDescriptor val isAccessor = property is AccessorForPropertyDescriptor
val propertyName = if (isAccessor) val propertyName = if (isAccessor)
(property as AccessorForPropertyDescriptor).accessorSuffix (property as AccessorForPropertyDescriptor).accessorSuffix
@@ -29,6 +29,7 @@ enum class JvmTarget(override val description: String) : TargetPlatformVersion {
JVM_13("13"), JVM_13("13"),
JVM_14("14"), JVM_14("14"),
JVM_15("15"), JVM_15("15"),
JVM_15_PREVIEW("15_PREVIEW"),
; ;
val bytecodeVersion: Int by lazy { val bytecodeVersion: Int by lazy {
@@ -39,9 +40,10 @@ enum class JvmTarget(override val description: String) : TargetPlatformVersion {
JVM_10 -> Opcodes.V10 JVM_10 -> Opcodes.V10
JVM_11 -> Opcodes.V11 JVM_11 -> Opcodes.V11
JVM_12 -> Opcodes.V12 JVM_12 -> Opcodes.V12
JVM_13 -> Opcodes.V12 + 1 JVM_13 -> Opcodes.V13
JVM_14 -> Opcodes.V12 + 2 JVM_14 -> Opcodes.V14
JVM_15 -> Opcodes.V12 + 3 JVM_15 -> Opcodes.V15
JVM_15_PREVIEW -> Opcodes.V15 + (0xffff shl 16)
} }
} }
@@ -33,6 +33,7 @@ import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.metadata.jvm.serialization.JvmStringTable import org.jetbrains.kotlin.metadata.jvm.serialization.JvmStringTable
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.protobuf.MessageLite 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.JVM_SYNTHETIC_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.resolve.jvm.annotations.TRANSIENT_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 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 { (field.metadata as? MetadataSource.Property)?.let {
metadataSerializer.bindFieldMetadata(it, fieldType to fieldName) 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<IrFunction, SMAPAndMethodNode>() private val generatedInlineMethods = mutableMapOf<IrFunction, SMAPAndMethodNode>()
@@ -452,6 +458,7 @@ private val IrClass.flags: Int
isAnnotationClass -> Opcodes.ACC_ANNOTATION or Opcodes.ACC_INTERFACE or Opcodes.ACC_ABSTRACT isAnnotationClass -> Opcodes.ACC_ANNOTATION or Opcodes.ACC_INTERFACE or Opcodes.ACC_ABSTRACT
isInterface -> 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 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 else -> Opcodes.ACC_SUPER or modality.flags
} }
@@ -41,6 +41,7 @@ import org.jetbrains.kotlin.metadata.deserialization.getExtensionOrNull
import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
import org.jetbrains.kotlin.name.NameUtils 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.JvmMethodGenericSignature
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
@@ -83,7 +84,7 @@ class MethodSignatureMapper(private val context: JvmBackendContext) {
if (property != null) { if (property != null) {
val propertyName = property.name.asString() val propertyName = property.name.asString()
val propertyParent = property.parentAsClass val propertyParent = property.parentAsClass
if (propertyParent.isAnnotationClass) if (propertyParent.isAnnotationClass || propertyParent.hasAnnotation(JVM_RECORD_ANNOTATION_FQ_NAME))
return propertyName return propertyName
// The enum property getters <get-name> and <get-ordinal> have special names which also // The enum property getters <get-name> and <get-ordinal> have special names which also
@@ -0,0 +1,29 @@
// !LANGUAGE: +JvmRecordSupport
// JVM_TARGET: 15_PREVIEW
// FILE: JavaClass.java
public class JavaClass {
public static String box() {
MyRec m = new MyRec<String>("O", "K");
return m.x() + m.y();
}
}
// FILE: main.kt
@JvmRecord
class MyRec<R>(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()
}
@@ -17,4 +17,6 @@ abstract class AbstractJdk15BlackBoxCodegenTest : AbstractCustomJDKBlackBoxCodeg
override fun getAdditionalJavacArgs(): List<String> = ADDITIONAL_JAVAC_ARGS_FOR_15 override fun getAdditionalJavacArgs(): List<String> = ADDITIONAL_JAVAC_ARGS_FOR_15
override fun getAdditionalJvmArgs(): List<String> = listOf("--enable-preview") override fun getAdditionalJvmArgs(): List<String> = listOf("--enable-preview")
}
override fun verifyWithDex(): Boolean = false
}
@@ -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); KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/java15/box"), Pattern.compile("^(.+)\\.kt$"), null, true);
} }
@TestMetadata("recordDifferentPropertyOverride.kt") @TestMetadata("compiler/testData/codegen/java15/box/records")
public void testRecordDifferentPropertyOverride() throws Exception { @TestDataPath("$PROJECT_ROOT")
runTest("compiler/testData/codegen/java15/box/recordDifferentPropertyOverride.kt"); @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 testAllFilesPresentInRecords() throws Exception {
public void testRecordDifferentSyntheticProperty() throws Exception { KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/java15/box/records"), Pattern.compile("^(.+)\\.kt$"), null, true);
runTest("compiler/testData/codegen/java15/box/recordDifferentSyntheticProperty.kt"); }
}
@TestMetadata("recordPropertyAccess.kt") @TestMetadata("bytecodeShapeForJava.kt")
public void testRecordPropertyAccess() throws Exception { public void testBytecodeShapeForJava() throws Exception {
runTest("compiler/testData/codegen/java15/box/recordPropertyAccess.kt"); 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");
}
} }
} }