diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/annotations/jvmAnnotationUtil.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/annotations/jvmAnnotationUtil.kt index 8ed167fb0ae..c0b9bdc1a7a 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/annotations/jvmAnnotationUtil.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/annotations/jvmAnnotationUtil.kt @@ -23,6 +23,7 @@ val JVM_OVERLOADS_FQ_NAME = FqName("kotlin.jvm.JvmOverloads") @JvmField val JVM_SYNTHETIC_ANNOTATION_FQ_NAME = FqName("kotlin.jvm.JvmSynthetic") +val JVM_RECORD_ANNOTATION_FQ_NAME = FqName("kotlin.jvm.JvmRecord") @JvmField val SYNCHRONIZED_ANNOTATION_FQ_NAME = FqName("kotlin.jvm.Synchronized") @@ -92,3 +93,5 @@ fun DeclarationDescriptor.findStrictfpAnnotation(): AnnotationDescriptor? = fun DeclarationDescriptor.findSynchronizedAnnotation(): AnnotationDescriptor? = annotations.findAnnotation(SYNCHRONIZED_ANNOTATION_FQ_NAME) + +fun ClassDescriptor.isJvmRecord(): Boolean = annotations.hasAnnotation(JVM_RECORD_ANNOTATION_FQ_NAME) 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 new file mode 100644 index 00000000000..cde02cbecff --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmRecordApplicabilityChecker.kt @@ -0,0 +1,67 @@ +/* + * 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.checkers + +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.psi.KtClassOrObject +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.jvm.annotations.JVM_RECORD_ANNOTATION_FQ_NAME +import org.jetbrains.kotlin.resolve.jvm.annotations.isJvmRecord +import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm + +object JvmRecordApplicabilityChecker : DeclarationChecker { + override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { + if (descriptor !is ClassDescriptor || declaration !is KtClassOrObject || !descriptor.isJvmRecord()) return + + val reportOn = + declaration.annotationEntries.firstOrNull { it.shortName == JVM_RECORD_ANNOTATION_FQ_NAME.shortName() } + ?: declaration + + if (!context.languageVersionSettings.supportsFeature(LanguageFeature.JvmRecordSupport)) { + context.trace.report( + Errors.UNSUPPORTED_FEATURE.on( + reportOn, + LanguageFeature.JvmRecordSupport to context.languageVersionSettings + ) + ) + return + } + + if (DescriptorUtils.isLocal(descriptor)) { + context.trace.report(ErrorsJvm.LOCAL_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 206dc2d47fd..5b18c715464 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 @@ -152,6 +152,11 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension { MAP.put(SUSPENSION_POINT_INSIDE_MONITOR, "A suspension point at {0} is inside a critical section", STRING); MAP.put(SUSPENSION_POINT_INSIDE_CRITICAL_SECTION, "The ''{0}'' suspension point is inside a critical section", NAME); + MAP.put(LOCAL_JVM_RECORD, "Local @JvmRecord classes are not allowed"); + 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"); + String MESSAGE_FOR_CONCURRENT_HASH_MAP_CONTAINS = "Method 'contains' from ConcurrentHashMap may have unexpected semantics: it calls 'containsValue' instead of 'containsKey'. " + "Use explicit form of the call to 'containsKey'/'containsValue'/'contains' or cast the value to kotlin.collections.Map instead. " + 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 d83fb0af60f..63068b5a065 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 @@ -115,6 +115,11 @@ public interface ErrorsJvm { DiagnosticFactory0 JVM_DEFAULT_THROUGH_INHERITANCE = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE); DiagnosticFactory0 USAGE_OF_JVM_DEFAULT_THROUGH_SUPER_CALL = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 LOCAL_JVM_RECORD = DiagnosticFactory0.create(ERROR); + 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); + DiagnosticFactory0 NON_JVM_DEFAULT_OVERRIDES_JAVA_DEFAULT = DiagnosticFactory0.create(WARNING, DECLARATION_SIGNATURE); DiagnosticFactory0 EXPLICIT_METADATA_IS_DISALLOWED = DiagnosticFactory0.create(ERROR); diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt index 0193fe88314..1154a8cfe6c 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt @@ -42,6 +42,7 @@ object JvmPlatformConfigurator : PlatformConfiguratorBase( SynchronizedOnInlineMethodChecker, DefaultCheckerInTailrec, FunctionDelegateMemberNameClashChecker, + JvmRecordApplicabilityChecker ), additionalCallCheckers = listOf( diff --git a/compiler/testData/diagnostics/testsWithJava15/jvmRecord/diagnostics.kt b/compiler/testData/diagnostics/testsWithJava15/jvmRecord/diagnostics.kt new file mode 100644 index 00000000000..11d96263680 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJava15/jvmRecord/diagnostics.kt @@ -0,0 +1,34 @@ +// !LANGUAGE: +JvmRecordSupport +// SKIP_TXT + +@JvmRecord +class A0 + +@JvmRecord +class A1 { + constructor() +} + +@JvmRecord +class A2() + +@JvmRecord +class A3(name: String) + +@JvmRecord +class A4(var name: String) + +@JvmRecord +class A5(vararg val name: String, y: Int) + +@JvmRecord +class A6( + val x: String, + val y: Int, + vararg val z: Double, +) + +fun main() { + @JvmRecord + class Local +} diff --git a/compiler/testData/diagnostics/testsWithJava15/jvmRecord/disabledFeature.kt b/compiler/testData/diagnostics/testsWithJava15/jvmRecord/disabledFeature.kt new file mode 100644 index 00000000000..51212af265b --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJava15/jvmRecord/disabledFeature.kt @@ -0,0 +1,9 @@ +// !LANGUAGE: -JvmRecordSupport +// SKIP_TXT + +@JvmRecord +class MyRec( + val x: String, + val y: Int, + vararg val z: Double, +) diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsWithJdk15Test.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsWithJdk15Test.kt index 12c227f4d59..186b4b836d9 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsWithJdk15Test.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsWithJdk15Test.kt @@ -15,6 +15,7 @@ */ package org.jetbrains.kotlin.checkers +import org.jetbrains.kotlin.test.ConfigurationKind import org.jetbrains.kotlin.test.TestJdkKind abstract class AbstractDiagnosticsWithJdk15Test : AbstractDiagnosticsTest() { @@ -22,4 +23,8 @@ abstract class AbstractDiagnosticsWithJdk15Test : AbstractDiagnosticsTest() { override fun getTestJdkKind(files: List): TestJdkKind { return TestJdkKind.FULL_JDK_15 } + + override fun extractConfigurationKind(files: List): ConfigurationKind { + return ConfigurationKind.ALL + } } diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsWithJdk15TestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsWithJdk15TestGenerated.java index c2c94de340e..b54808d7415 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsWithJdk15TestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsWithJdk15TestGenerated.java @@ -28,8 +28,26 @@ public class DiagnosticsWithJdk15TestGenerated extends AbstractDiagnosticsWithJd KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/testsWithJava15"), Pattern.compile("^(.+)\\.kt$"), null, true); } - @TestMetadata("simpleRecords.kt") - public void testSimpleRecords() throws Exception { - runTest("compiler/testData/diagnostics/testsWithJava15/simpleRecords.kt"); + @TestMetadata("compiler/testData/diagnostics/testsWithJava15/jvmRecord") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class JvmRecord extends AbstractDiagnosticsWithJdk15Test { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInJvmRecord() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/testsWithJava15/jvmRecord"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("diagnostics.kt") + public void testDiagnostics() throws Exception { + runTest("compiler/testData/diagnostics/testsWithJava15/jvmRecord/diagnostics.kt"); + } + + @TestMetadata("disabledFeature.kt") + public void testDisabledFeature() throws Exception { + runTest("compiler/testData/diagnostics/testsWithJava15/jvmRecord/disabledFeature.kt"); + } } } diff --git a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt index 682b69ae7bd..c64025f7364 100644 --- a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt +++ b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt @@ -141,6 +141,7 @@ enum class LanguageFeature( ForbidAnonymousReturnTypesInPrivateInlineFunctions(KOTLIN_1_5, kind = BUG_FIX), ForbidReferencingToUnderscoreNamedParameterOfCatchBlock(KOTLIN_1_5, kind = BUG_FIX), UseCorrectExecutionOrderForVarargArguments(KOTLIN_1_5, kind = BUG_FIX), + JvmRecordSupport(KOTLIN_1_5), // Temporarily disabled, see KT-27084/KT-22379 SoundSmartcastFromLoopConditionForLoopAssignedVariables(sinceVersion = null, kind = BUG_FIX), diff --git a/libraries/stdlib/common/src/kotlin/JvmAnnotationsH.kt b/libraries/stdlib/common/src/kotlin/JvmAnnotationsH.kt index a51356b3200..99956bdf2ee 100644 --- a/libraries/stdlib/common/src/kotlin/JvmAnnotationsH.kt +++ b/libraries/stdlib/common/src/kotlin/JvmAnnotationsH.kt @@ -113,6 +113,14 @@ public expect annotation class JvmWildcard() @OptionalExpectation public expect annotation class JvmInline() +/** + * Instructs compiler to mark the class as a record and generate relevant toString/equals/hashCode methods + */ +@Target(AnnotationTarget.CLASS) +@MustBeDocumented +@OptionalExpectation +public expect annotation class JvmRecord + /** * Marks the JVM backing field of the annotated property as `volatile`, meaning that writes to this field * are immediately made visible to other threads. @@ -157,4 +165,5 @@ public expect annotation class Synchronized() @MustBeDocumented @SinceKotlin("1.2") @OptionalExpectation -internal expect annotation class JvmPackageName(val name: String) \ No newline at end of file +internal expect annotation class JvmPackageName(val name: String) + diff --git a/libraries/stdlib/jvm/runtime/kotlin/jvm/annotations/JvmPlatformAnnotations.kt b/libraries/stdlib/jvm/runtime/kotlin/jvm/annotations/JvmPlatformAnnotations.kt index 92015204695..a96202d3ec1 100644 --- a/libraries/stdlib/jvm/runtime/kotlin/jvm/annotations/JvmPlatformAnnotations.kt +++ b/libraries/stdlib/jvm/runtime/kotlin/jvm/annotations/JvmPlatformAnnotations.kt @@ -144,4 +144,12 @@ public actual annotation class JvmWildcard @Retention(AnnotationRetention.RUNTIME) @MustBeDocumented @SinceKotlin("1.5") -public actual annotation class JvmInline \ No newline at end of file +public actual annotation class JvmInline + +/** + * Instructs compiler to mark the class as a record and generate relevant toString/equals/hashCode methods + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.SOURCE) +@MustBeDocumented +public actual annotation class JvmRecord diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index 97eb0e92f0c..8e87548bb66 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -3250,6 +3250,9 @@ public abstract interface annotation class kotlin/jvm/JvmName : java/lang/annota public abstract interface annotation class kotlin/jvm/JvmOverloads : java/lang/annotation/Annotation { } +public abstract interface annotation class kotlin/jvm/JvmRecord : java/lang/annotation/Annotation { +} + public abstract interface annotation class kotlin/jvm/JvmStatic : java/lang/annotation/Annotation { }