From c8851c4f751d1a3d56f79667bd3e592f5148bca6 Mon Sep 17 00:00:00 2001 From: "Denis.Zharkov" Date: Mon, 30 Nov 2020 20:20:14 +0300 Subject: [PATCH] Prohibit @JvmRecord for non-data classes ^KT-43677 In Progress --- .../checkers/JvmRecordApplicabilityChecker.kt | 50 +++++++++++-------- .../diagnostics/DefaultErrorMessagesJvm.java | 1 + .../resolve/jvm/diagnostics/ErrorsJvm.java | 1 + .../box/records/bytecodeShapeForJava.kt | 2 +- .../java15/box/records/dataJvmRecord.kt | 16 ++++++ .../testsWithJava15/jvmRecord/diagnostics.kt | 26 +++++----- .../jvmRecord/irrelevantFields.kt | 10 ++-- .../jvmRecord/jvmRecordDescriptorStructure.kt | 4 +- .../jvmRecord/supertypesCheck.kt | 10 ++-- .../jvmRecord/supertypesCheck.txt | 20 ++++++-- .../Jdk15BlackBoxCodegenTestGenerated.java | 5 ++ 11 files changed, 92 insertions(+), 53 deletions(-) create mode 100644 compiler/testData/codegen/java15/box/records/dataJvmRecord.kt diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmRecordApplicabilityChecker.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmRecordApplicabilityChecker.kt index af4c2442bae..9bf745d6254 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmRecordApplicabilityChecker.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmRecordApplicabilityChecker.kt @@ -83,28 +83,6 @@ object JvmRecordApplicabilityChecker : DeclarationChecker { return } - val primaryConstructor = declaration.primaryConstructor - val parameters = primaryConstructor?.valueParameters ?: emptyList() - if (parameters.isEmpty()) { - (primaryConstructor?.valueParameterList ?: declaration.nameIdentifier)?.let { - context.trace.report(ErrorsJvm.JVM_RECORD_WITHOUT_PRIMARY_CONSTRUCTOR_PARAMETERS.on(it)) - return - } - } - - for (parameter in parameters) { - if (!parameter.hasValOrVar() || parameter.isMutable) { - context.trace.report(ErrorsJvm.JVM_RECORD_NOT_VAL_PARAMETER.on(parameter)) - return - } - } - - for (parameter in parameters.dropLast(1)) { - if (parameter.isVarArg) { - context.trace.report(ErrorsJvm.JVM_RECORD_NOT_LAST_VARARG_PARAMETER.on(parameter)) - return - } - } for (member in declaration.declarations) { if (member !is KtProperty) continue @@ -131,6 +109,34 @@ object JvmRecordApplicabilityChecker : DeclarationChecker { context.trace.report(ErrorsJvm.JVM_RECORD_EXTENDS_CLASS.on(reportSupertypeOn, supertype)) return } + + if (!descriptor.isData) { + context.trace.report(ErrorsJvm.NON_DATA_CLASS_JVM_RECORD.on(reportOn)) + return + } + + val primaryConstructor = declaration.primaryConstructor + val parameters = primaryConstructor?.valueParameters ?: emptyList() + if (parameters.isEmpty()) { + (primaryConstructor?.valueParameterList ?: declaration.nameIdentifier)?.let { + context.trace.report(ErrorsJvm.JVM_RECORD_WITHOUT_PRIMARY_CONSTRUCTOR_PARAMETERS.on(it)) + return + } + } + + for (parameter in parameters) { + if (!parameter.hasValOrVar() || parameter.isMutable) { + context.trace.report(ErrorsJvm.JVM_RECORD_NOT_VAL_PARAMETER.on(parameter)) + return + } + } + + for (parameter in parameters.dropLast(1)) { + if (parameter.isVarArg) { + context.trace.report(ErrorsJvm.JVM_RECORD_NOT_LAST_VARARG_PARAMETER.on(parameter)) + return + } + } } } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java index ecae1cde86e..3d14accdb69 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java @@ -163,6 +163,7 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension { MAP.put(INNER_JVM_RECORD, "@JvmRecord class should not be inner"); MAP.put(FIELD_IN_JVM_RECORD, "It's not allowed to have non-constructor properties with backing filed in @JvmRecord class"); MAP.put(DELEGATION_BY_IN_JVM_RECORD, "Delegation is not allowed for @JvmRecord classes"); + MAP.put(NON_DATA_CLASS_JVM_RECORD, "Only data classes are allowed to be marked as @JvmRecord"); String MESSAGE_FOR_CONCURRENT_HASH_MAP_CONTAINS = "Method 'contains' from ConcurrentHashMap may have unexpected semantics: it calls 'containsValue' instead of 'containsKey'. " + diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java index 9b2c0e9fc51..17068ba9c75 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java @@ -126,6 +126,7 @@ public interface ErrorsJvm { DiagnosticFactory0 INNER_JVM_RECORD = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 FIELD_IN_JVM_RECORD = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 DELEGATION_BY_IN_JVM_RECORD = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 NON_DATA_CLASS_JVM_RECORD = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 NON_JVM_DEFAULT_OVERRIDES_JAVA_DEFAULT = DiagnosticFactory0.create(WARNING, DECLARATION_SIGNATURE); diff --git a/compiler/testData/codegen/java15/box/records/bytecodeShapeForJava.kt b/compiler/testData/codegen/java15/box/records/bytecodeShapeForJava.kt index 53ea772abe1..45ba2eb96b4 100644 --- a/compiler/testData/codegen/java15/box/records/bytecodeShapeForJava.kt +++ b/compiler/testData/codegen/java15/box/records/bytecodeShapeForJava.kt @@ -10,7 +10,7 @@ public class JavaClass { // FILE: main.kt @JvmRecord -class MyRec(val x: String, val y: R) +data class MyRec(val x: String, val y: R) fun box(): String { val recordComponents = MyRec::class.java.recordComponents diff --git a/compiler/testData/codegen/java15/box/records/dataJvmRecord.kt b/compiler/testData/codegen/java15/box/records/dataJvmRecord.kt new file mode 100644 index 00000000000..5e606378551 --- /dev/null +++ b/compiler/testData/codegen/java15/box/records/dataJvmRecord.kt @@ -0,0 +1,16 @@ +// !LANGUAGE: +JvmRecordSupport +// JVM_TARGET: 15_PREVIEW + +@JvmRecord +data class MyRec(val x: String, val y: R) + +fun box(): String { + val m1 = MyRec("O", "K") + val m2 = MyRec("O", "K") + + if (m1 != m2) return "fail 1" + if (m1.hashCode() != m2.hashCode()) return "fail 2" + if (m1.toString() != "MyRec(x=O, y=K)") return "fail 3: $m1" + + return "OK" +} diff --git a/compiler/testData/diagnostics/testsWithJava15/jvmRecord/diagnostics.kt b/compiler/testData/diagnostics/testsWithJava15/jvmRecord/diagnostics.kt index 8aa985c3a64..0f966df7fdf 100644 --- a/compiler/testData/diagnostics/testsWithJava15/jvmRecord/diagnostics.kt +++ b/compiler/testData/diagnostics/testsWithJava15/jvmRecord/diagnostics.kt @@ -1,25 +1,25 @@ // !LANGUAGE: +JvmRecordSupport // SKIP_TXT -@JvmRecord -class A0 +@JvmRecord +class A0 -@JvmRecord -class A1 { +@JvmRecord +class A1 { constructor() } -@JvmRecord -class A2() +@JvmRecord +class A2() -@JvmRecord -class A3(name: String) +@JvmRecord +class A3(name: String) -@JvmRecord -class A4(var name: String) +@JvmRecord +class A4(var name: String) -@JvmRecord -class A5(vararg val name: String, y: Int) +@JvmRecord +class A5(vararg val name: String, y: Int) @JvmRecord open class A6(val x: String) @@ -35,7 +35,7 @@ class A5(vararg val name: String, @JvmRecord class A10( val x: String, val y: Int, diff --git a/compiler/testData/diagnostics/testsWithJava15/jvmRecord/irrelevantFields.kt b/compiler/testData/diagnostics/testsWithJava15/jvmRecord/irrelevantFields.kt index 8733a6eafe1..e7ab68514a0 100644 --- a/compiler/testData/diagnostics/testsWithJava15/jvmRecord/irrelevantFields.kt +++ b/compiler/testData/diagnostics/testsWithJava15/jvmRecord/irrelevantFields.kt @@ -6,15 +6,15 @@ interface I val i: I = object : I {} @JvmRecord -class MyRec1(val name: String) : I by i +data class MyRec1(val name: String) : I by i @JvmRecord -class MyRec2(val name: String) { +data class MyRec2(val name: String) { val x: Int = 0 } @JvmRecord -class MyRec3(val name: String) { +data class MyRec3(val name: String) { val y: String get() = field + "1" @@ -24,12 +24,12 @@ class MyRec3(val name: String) { } @JvmRecord -class MyRec4(val name: String) { +data class MyRec4(val name: String) { val z: Int by lazy { 1 } } @JvmRecord -class MyRec5(val name: String) { +data class MyRec5(val name: String) { val w: String get() = name + "1" } diff --git a/compiler/testData/diagnostics/testsWithJava15/jvmRecord/jvmRecordDescriptorStructure.kt b/compiler/testData/diagnostics/testsWithJava15/jvmRecord/jvmRecordDescriptorStructure.kt index e2f2df9e5db..03f7c4db25d 100644 --- a/compiler/testData/diagnostics/testsWithJava15/jvmRecord/jvmRecordDescriptorStructure.kt +++ b/compiler/testData/diagnostics/testsWithJava15/jvmRecord/jvmRecordDescriptorStructure.kt @@ -1,11 +1,11 @@ // !LANGUAGE: +JvmRecordSupport -@JvmRecord +@JvmRecord class BasicRecord(val x: String) @JvmRecord data class BasicDataRecord(val x: String) -@JvmRecord +@JvmRecord class BasicRecordWithSuperClass(val x: String) : Record() diff --git a/compiler/testData/diagnostics/testsWithJava15/jvmRecord/supertypesCheck.kt b/compiler/testData/diagnostics/testsWithJava15/jvmRecord/supertypesCheck.kt index 0dcb9c4ea0f..0bb0aeb8f93 100644 --- a/compiler/testData/diagnostics/testsWithJava15/jvmRecord/supertypesCheck.kt +++ b/compiler/testData/diagnostics/testsWithJava15/jvmRecord/supertypesCheck.kt @@ -4,16 +4,16 @@ abstract class Abstract interface I @JvmRecord -class A1(val x: String) : Abstract(), I +data class A1(val x: String) : Abstract(), I @JvmRecord -class A2(val x: String) : Any(), I +data class A2(val x: String) : Any(), I @JvmRecord -class A3(val x: String) : Record(), I +data class A3(val x: String) : Record(), I @JvmRecord -class A4(val x: String) : java.lang.Record(), I +data class A4(val x: String) : java.lang.Record(), I @JvmRecord -class A5(val x: String) : I +data class A5(val x: String) : I diff --git a/compiler/testData/diagnostics/testsWithJava15/jvmRecord/supertypesCheck.txt b/compiler/testData/diagnostics/testsWithJava15/jvmRecord/supertypesCheck.txt index ae493fa8619..aa23a5d9ad8 100644 --- a/compiler/testData/diagnostics/testsWithJava15/jvmRecord/supertypesCheck.txt +++ b/compiler/testData/diagnostics/testsWithJava15/jvmRecord/supertypesCheck.txt @@ -1,40 +1,50 @@ package -@kotlin.jvm.JvmRecord public final class A1 : Abstract, I, java.lang.Record { +@kotlin.jvm.JvmRecord public final data class A1 : Abstract, I, java.lang.Record { public constructor A1(/*0*/ x: kotlin.String) public final val x: kotlin.String + public final operator /*synthesized*/ fun component1(): kotlin.String + public final /*synthesized*/ fun copy(/*0*/ x: kotlin.String = ...): A1 public open override /*3*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean public open override /*3*/ /*synthesized*/ fun hashCode(): kotlin.Int public open override /*3*/ /*synthesized*/ fun toString(): kotlin.String } -@kotlin.jvm.JvmRecord public final class A2 : kotlin.Any, I, java.lang.Record { +@kotlin.jvm.JvmRecord public final data class A2 : kotlin.Any, I, java.lang.Record { public constructor A2(/*0*/ x: kotlin.String) public final val x: kotlin.String + public final operator /*synthesized*/ fun component1(): kotlin.String + public final /*synthesized*/ fun copy(/*0*/ x: kotlin.String = ...): A2 public open override /*3*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean public open override /*3*/ /*synthesized*/ fun hashCode(): kotlin.Int public open override /*3*/ /*synthesized*/ fun toString(): kotlin.String } -@kotlin.jvm.JvmRecord public final class A3 : java.lang.Record, I { +@kotlin.jvm.JvmRecord public final data class A3 : java.lang.Record, I { public constructor A3(/*0*/ x: kotlin.String) public final val x: kotlin.String + public final operator /*synthesized*/ fun component1(): kotlin.String + public final /*synthesized*/ fun copy(/*0*/ x: kotlin.String = ...): A3 public open override /*2*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean public open override /*2*/ /*synthesized*/ fun hashCode(): kotlin.Int public open override /*2*/ /*synthesized*/ fun toString(): kotlin.String } -@kotlin.jvm.JvmRecord public final class A4 : java.lang.Record, I { +@kotlin.jvm.JvmRecord public final data class A4 : java.lang.Record, I { public constructor A4(/*0*/ x: kotlin.String) public final val x: kotlin.String + public final operator /*synthesized*/ fun component1(): kotlin.String + public final /*synthesized*/ fun copy(/*0*/ x: kotlin.String = ...): A4 public open override /*2*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean public open override /*2*/ /*synthesized*/ fun hashCode(): kotlin.Int public open override /*2*/ /*synthesized*/ fun toString(): kotlin.String } -@kotlin.jvm.JvmRecord public final class A5 : I, java.lang.Record { +@kotlin.jvm.JvmRecord public final data class A5 : I, java.lang.Record { public constructor A5(/*0*/ x: kotlin.String) public final val x: kotlin.String + public final operator /*synthesized*/ fun component1(): kotlin.String + public final /*synthesized*/ fun copy(/*0*/ x: kotlin.String = ...): A5 public open override /*2*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean public open override /*2*/ /*synthesized*/ fun hashCode(): kotlin.Int public open override /*2*/ /*synthesized*/ fun toString(): kotlin.String diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/Jdk15BlackBoxCodegenTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/Jdk15BlackBoxCodegenTestGenerated.java index c7a922ea017..0508221571b 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/codegen/Jdk15BlackBoxCodegenTestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/Jdk15BlackBoxCodegenTestGenerated.java @@ -45,6 +45,11 @@ public class Jdk15BlackBoxCodegenTestGenerated extends AbstractJdk15BlackBoxCode runTest("compiler/testData/codegen/java15/box/records/bytecodeShapeForJava.kt"); } + @TestMetadata("dataJvmRecord.kt") + public void testDataJvmRecord() throws Exception { + runTest("compiler/testData/codegen/java15/box/records/dataJvmRecord.kt"); + } + @TestMetadata("recordDifferentPropertyOverride.kt") public void testRecordDifferentPropertyOverride() throws Exception { runTest("compiler/testData/codegen/java15/box/records/recordDifferentPropertyOverride.kt");