Add @JvmRecord annotation and relevant diagnostics
^KT-43677 In Progress
This commit is contained in:
+3
@@ -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)
|
||||
|
||||
+67
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -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. " +
|
||||
|
||||
@@ -115,6 +115,11 @@ public interface ErrorsJvm {
|
||||
DiagnosticFactory0<KtDeclaration> JVM_DEFAULT_THROUGH_INHERITANCE = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE);
|
||||
DiagnosticFactory0<PsiElement> USAGE_OF_JVM_DEFAULT_THROUGH_SUPER_CALL = DiagnosticFactory0.create(ERROR);
|
||||
|
||||
DiagnosticFactory0<PsiElement> LOCAL_JVM_RECORD = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> JVM_RECORD_WITHOUT_PRIMARY_CONSTRUCTOR_PARAMETERS = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> JVM_RECORD_NOT_VAL_PARAMETER = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> JVM_RECORD_NOT_LAST_VARARG_PARAMETER = DiagnosticFactory0.create(ERROR);
|
||||
|
||||
DiagnosticFactory0<KtDeclaration> NON_JVM_DEFAULT_OVERRIDES_JAVA_DEFAULT = DiagnosticFactory0.create(WARNING, DECLARATION_SIGNATURE);
|
||||
|
||||
DiagnosticFactory0<KtAnnotationEntry> EXPLICIT_METADATA_IS_DISALLOWED = DiagnosticFactory0.create(ERROR);
|
||||
|
||||
+1
@@ -42,6 +42,7 @@ object JvmPlatformConfigurator : PlatformConfiguratorBase(
|
||||
SynchronizedOnInlineMethodChecker,
|
||||
DefaultCheckerInTailrec,
|
||||
FunctionDelegateMemberNameClashChecker,
|
||||
JvmRecordApplicabilityChecker
|
||||
),
|
||||
|
||||
additionalCallCheckers = listOf(
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// !LANGUAGE: +JvmRecordSupport
|
||||
// SKIP_TXT
|
||||
|
||||
@JvmRecord
|
||||
class <!JVM_RECORD_WITHOUT_PRIMARY_CONSTRUCTOR_PARAMETERS!>A0<!>
|
||||
|
||||
@JvmRecord
|
||||
class <!JVM_RECORD_WITHOUT_PRIMARY_CONSTRUCTOR_PARAMETERS!>A1<!> {
|
||||
constructor()
|
||||
}
|
||||
|
||||
@JvmRecord
|
||||
class A2<!JVM_RECORD_WITHOUT_PRIMARY_CONSTRUCTOR_PARAMETERS!>()<!>
|
||||
|
||||
@JvmRecord
|
||||
class A3(<!JVM_RECORD_NOT_VAL_PARAMETER!><!UNUSED_PARAMETER!>name<!>: String<!>)
|
||||
|
||||
@JvmRecord
|
||||
class A4(<!JVM_RECORD_NOT_VAL_PARAMETER!>var name: String<!>)
|
||||
|
||||
@JvmRecord
|
||||
class A5(vararg val name: String, <!JVM_RECORD_NOT_VAL_PARAMETER!><!UNUSED_PARAMETER!>y<!>: Int<!>)
|
||||
|
||||
@JvmRecord
|
||||
class A6(
|
||||
val x: String,
|
||||
val y: Int,
|
||||
vararg val z: Double,
|
||||
)
|
||||
|
||||
fun main() {
|
||||
<!LOCAL_JVM_RECORD!>@JvmRecord<!>
|
||||
class Local
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// !LANGUAGE: -JvmRecordSupport
|
||||
// SKIP_TXT
|
||||
|
||||
<!UNSUPPORTED_FEATURE!>@JvmRecord<!>
|
||||
class MyRec(
|
||||
val x: String,
|
||||
val y: Int,
|
||||
vararg val z: Double,
|
||||
)
|
||||
+5
@@ -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<TestFile>): TestJdkKind {
|
||||
return TestJdkKind.FULL_JDK_15
|
||||
}
|
||||
|
||||
override fun extractConfigurationKind(files: List<TestFile>): ConfigurationKind {
|
||||
return ConfigurationKind.ALL
|
||||
}
|
||||
}
|
||||
|
||||
+21
-3
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
internal expect annotation class JvmPackageName(val name: String)
|
||||
|
||||
|
||||
@@ -144,4 +144,12 @@ public actual annotation class JvmWildcard
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
@SinceKotlin("1.5")
|
||||
public actual annotation class JvmInline
|
||||
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
|
||||
|
||||
+3
@@ -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 {
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user