From 85962d8312ff940db7e772cf526186358fe4de72 Mon Sep 17 00:00:00 2001 From: "Denis.Zharkov" Date: Wed, 25 Nov 2020 18:20:55 +0300 Subject: [PATCH] Add check that @JvmRecord classes cannot inherit other classes ^KT-43677 In Progress --- .../kotlin/resolve/jvm/JdkClasses.kt | 10 ++++ .../checkers/JvmRecordApplicabilityChecker.kt | 12 +++++ .../diagnostics/DefaultErrorMessagesJvm.java | 1 + .../resolve/jvm/diagnostics/ErrorsJvm.java | 1 + .../jvmRecord/supertypesCheck.kt | 19 +++++++ .../jvmRecord/supertypesCheck.txt | 54 +++++++++++++++++++ .../DiagnosticsWithJdk15TestGenerated.java | 5 ++ 7 files changed, 102 insertions(+) create mode 100644 compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/JdkClasses.kt create mode 100644 compiler/testData/diagnostics/testsWithJava15/jvmRecord/supertypesCheck.kt create mode 100644 compiler/testData/diagnostics/testsWithJava15/jvmRecord/supertypesCheck.txt diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/JdkClasses.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/JdkClasses.kt new file mode 100644 index 00000000000..57a07930bf5 --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/JdkClasses.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.resolve.jvm + +import org.jetbrains.kotlin.name.FqName + +val JAVA_LANG_RECORD_FQ_NAME = FqName("java.lang.Record") 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 cde02cbecff..9adc7a58165 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 @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.resolve.jvm.checkers import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.psi.KtClassOrObject @@ -14,6 +15,8 @@ import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.resolve.jvm.JAVA_LANG_RECORD_FQ_NAME import org.jetbrains.kotlin.resolve.jvm.annotations.JVM_RECORD_ANNOTATION_FQ_NAME import org.jetbrains.kotlin.resolve.jvm.annotations.isJvmRecord import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm @@ -63,5 +66,14 @@ object JvmRecordApplicabilityChecker : DeclarationChecker { return } } + + for (supertype in descriptor.typeConstructor.supertypes) { + val classDescriptor = supertype.constructor.declarationDescriptor as? ClassDescriptor ?: continue + if (classDescriptor.kind == ClassKind.INTERFACE || classDescriptor.fqNameSafe == JAVA_LANG_RECORD_FQ_NAME) continue + + val reportSupertypeOn = declaration.nameIdentifier ?: declaration + context.trace.report(ErrorsJvm.JVM_RECORD_EXTENDS_CLASS.on(reportSupertypeOn, supertype)) + 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 5b18c715464..a3331c99177 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 @@ -156,6 +156,7 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension { MAP.put(JVM_RECORD_WITHOUT_PRIMARY_CONSTRUCTOR_PARAMETERS, "Primary constructor with parameters is required for @JvmRecord class"); MAP.put(JVM_RECORD_NOT_VAL_PARAMETER, "Constructor parameter of @JvmRecord class should be a val"); MAP.put(JVM_RECORD_NOT_LAST_VARARG_PARAMETER, "Only the last constructor parameter of @JvmRecord may be a vararg"); + MAP.put(JVM_RECORD_EXTENDS_CLASS, "Record cannot inherit an other class but java.lang.Record" , RENDER_TYPE); 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 63068b5a065..dd2c8e72897 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 @@ -119,6 +119,7 @@ public interface ErrorsJvm { DiagnosticFactory0 JVM_RECORD_WITHOUT_PRIMARY_CONSTRUCTOR_PARAMETERS = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 JVM_RECORD_NOT_VAL_PARAMETER = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 JVM_RECORD_NOT_LAST_VARARG_PARAMETER = DiagnosticFactory0.create(ERROR); + DiagnosticFactory1 JVM_RECORD_EXTENDS_CLASS = DiagnosticFactory1.create(ERROR); DiagnosticFactory0 NON_JVM_DEFAULT_OVERRIDES_JAVA_DEFAULT = DiagnosticFactory0.create(WARNING, DECLARATION_SIGNATURE); diff --git a/compiler/testData/diagnostics/testsWithJava15/jvmRecord/supertypesCheck.kt b/compiler/testData/diagnostics/testsWithJava15/jvmRecord/supertypesCheck.kt new file mode 100644 index 00000000000..0dcb9c4ea0f --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJava15/jvmRecord/supertypesCheck.kt @@ -0,0 +1,19 @@ +// !LANGUAGE: +JvmRecordSupport + +abstract class Abstract +interface I + +@JvmRecord +class A1(val x: String) : Abstract(), I + +@JvmRecord +class A2(val x: String) : Any(), I + +@JvmRecord +class A3(val x: String) : Record(), I + +@JvmRecord +class A4(val x: String) : java.lang.Record(), I + +@JvmRecord +class A5(val x: String) : I diff --git a/compiler/testData/diagnostics/testsWithJava15/jvmRecord/supertypesCheck.txt b/compiler/testData/diagnostics/testsWithJava15/jvmRecord/supertypesCheck.txt new file mode 100644 index 00000000000..ae493fa8619 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJava15/jvmRecord/supertypesCheck.txt @@ -0,0 +1,54 @@ +package + +@kotlin.jvm.JvmRecord public final class A1 : Abstract, I, java.lang.Record { + public constructor A1(/*0*/ x: kotlin.String) + public final val x: kotlin.String + 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 { + public constructor A2(/*0*/ x: kotlin.String) + public final val x: kotlin.String + 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 { + public constructor A3(/*0*/ x: kotlin.String) + public final val x: kotlin.String + 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 { + public constructor A4(/*0*/ x: kotlin.String) + public final val x: kotlin.String + 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 { + public constructor A5(/*0*/ x: kotlin.String) + public final val x: kotlin.String + 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 +} + +public abstract class Abstract { + public constructor Abstract() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface I { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsWithJdk15TestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsWithJdk15TestGenerated.java index b54808d7415..372d0000bdb 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsWithJdk15TestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsWithJdk15TestGenerated.java @@ -49,5 +49,10 @@ public class DiagnosticsWithJdk15TestGenerated extends AbstractDiagnosticsWithJd public void testDisabledFeature() throws Exception { runTest("compiler/testData/diagnostics/testsWithJava15/jvmRecord/disabledFeature.kt"); } + + @TestMetadata("supertypesCheck.kt") + public void testSupertypesCheck() throws Exception { + runTest("compiler/testData/diagnostics/testsWithJava15/jvmRecord/supertypesCheck.kt"); + } } }