diff --git a/plugins/kapt3/kapt3-compiler/testData/converter/jvmRecord.fir.txt b/plugins/kapt3/kapt3-compiler/testData/converter/jvmRecord.fir.txt new file mode 100644 index 00000000000..04ea65c84f4 --- /dev/null +++ b/plugins/kapt3/kapt3-compiler/testData/converter/jvmRecord.fir.txt @@ -0,0 +1,122 @@ +/** + * public final annotation class A : kotlin/Annotation { + * + * // signature: ()V + * public constructor() + * + * // module name: main + * } + */ +@kotlin.Metadata() +@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) +public abstract @interface A { +} + + +//////////////////// + +/** + * public abstract interface I : kotlin/Any { + * + * // signature: foo()I + * public abstract fun foo(): kotlin/Int + * + * // module name: main + * } + */ +@kotlin.Metadata() +public abstract interface I { + + public abstract int foo(); +} + + +//////////////////// + +/** + * public final data class R : I, java/lang/Record { + * + * // signature: (ILjava/lang/Object;)V + * public constructor(x: kotlin/Int, y: T#0) + * + * // signature: (Ljava/lang/Object;)V + * public (* secondary *) constructor(y: T#0) + * + * // signature: component1()I + * public final (* synthesized *) operator fun component1(): kotlin/Int + * + * // signature: equals(Ljava/lang/Object;)Z + * public open (* synthesized *) operator fun equals(other: kotlin/Any?): kotlin/Boolean + * + * // signature: foo()I + * public open fun foo(): kotlin/Int + * + * // signature: hashCode()I + * public open (* synthesized *) fun hashCode(): kotlin/Int + * + * public final (* synthesized *) operator fun component2(): T#0 + * + * public final (* synthesized *) fun copy(x: kotlin/Int (* = ... *), y: T#0 (* = ... *)): R + * + * // signature: toString()Ljava/lang/String; + * public open (* synthesized *) fun toString(): kotlin/String + * + * // field: x:I + * public final val x: kotlin/Int + * public final get + * + * // field: y:Ljava/lang/Object; + * public final val y: T#0 + * public final get + * + * // module name: main + * } + */ +@kotlin.Metadata() +@A() +@kotlin.jvm.JvmRecord() +public record R(@A() int x, T y) implements I { + + @org.jetbrains.annotations.NotNull() + public final R copy(@A() int x, T y) { + return null; + } + + public boolean equals(@org.jetbrains.annotations.Nullable() java.lang.Object other) { + return false; + } + + public int hashCode() { + return 0; + } + + @org.jetbrains.annotations.NotNull() + public java.lang.String toString() { + return null; + } + + public final int component1() { + return 0; + } + + public final int x() { + return 0; + } + + public final T component2() { + return null; + } + + public final T y() { + return null; + } + + public R(T y) { + this(0, null); + } + + @java.lang.Override() + public int foo() { + return 0; + } +} diff --git a/plugins/kapt3/kapt3-compiler/testData/converter/jvmRecord.kt b/plugins/kapt3/kapt3-compiler/testData/converter/jvmRecord.kt new file mode 100644 index 00000000000..d75f780f807 --- /dev/null +++ b/plugins/kapt3/kapt3-compiler/testData/converter/jvmRecord.kt @@ -0,0 +1,15 @@ +// JDK_KIND: FULL_JDK_21 +// NO_VALIDATION +annotation class A + +interface I { + fun foo(): Int +} + +@A +@JvmRecord +data class R(@A val x: Int, val y: T): I { + constructor(y: T) : this(0, y) + + override fun foo(): Int = 0 +} \ No newline at end of file diff --git a/plugins/kapt3/kapt3-compiler/testData/converter/jvmRecord.txt b/plugins/kapt3/kapt3-compiler/testData/converter/jvmRecord.txt new file mode 100644 index 00000000000..85e8b703e30 --- /dev/null +++ b/plugins/kapt3/kapt3-compiler/testData/converter/jvmRecord.txt @@ -0,0 +1,138 @@ +@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) +/** + * public final annotation class A : kotlin/Annotation { + * + * // signature: ()V + * public constructor() + * + * // module name: main + * } + */ +@kotlin.Metadata() +public abstract @interface A { +} + +//////////////////// + + +/** + * public abstract interface I : kotlin/Any { + * + * // signature: foo()I + * public abstract fun foo(): kotlin/Int + * + * // module name: main + * } + */ +@kotlin.Metadata() +public abstract interface I { + + public abstract int foo(); +} + +//////////////////// + + +@A() +/** + * public final data class R : I, java/lang/Record { + * + * // signature: (ILjava/lang/Object;)V + * public constructor(x: kotlin/Int, y: T#0) + * + * // signature: (Ljava/lang/Object;)V + * public (* secondary *) constructor(y: T#0) + * + * // signature: component1()I + * public final (* synthesized *) operator fun component1(): kotlin/Int + * + * // signature: component2()Ljava/lang/Object; + * public final (* synthesized *) operator fun component2(): T#0 + * + * // signature: copy(ILjava/lang/Object;)LR; + * public final (* synthesized *) fun copy(x: kotlin/Int (* = ... *), y: T#0 (* = ... *)): R + * + * // signature: equals(Ljava/lang/Object;)Z + * public open (* synthesized *) operator fun equals(other: kotlin/Any?): kotlin/Boolean + * + * // signature: foo()I + * public open fun foo(): kotlin/Int + * + * // signature: hashCode()I + * public open (* synthesized *) fun hashCode(): kotlin/Int + * + * // signature: toString()Ljava/lang/String; + * public open (* synthesized *) fun toString(): kotlin/String + * + * // field: x:I + * // getter: x()I + * public final val x: kotlin/Int + * public final get + * + * // field: y:Ljava/lang/Object; + * // getter: y()Ljava/lang/Object; + * public final val y: T#0 + * public final get + * + * // module name: main + * } + */ +@kotlin.Metadata() +@kotlin.jvm.JvmRecord() +public final class R extends java.lang.Record implements I { + private final int x = 0; + private final T y = null; + + public R(@A() + int x, T y) { + super(); + } + + public final int x() { + return 0; + } + + public final T y() { + return null; + } + + public R(T y) { + super(); + } + + @java.lang.Override() + public int foo() { + return 0; + } + + public final int component1() { + return 0; + } + + public final T component2() { + return null; + } + + @org.jetbrains.annotations.NotNull() + public final R copy(@A() + int x, T y) { + return null; + } + + @java.lang.Override() + public boolean equals(@org.jetbrains.annotations.Nullable() + java.lang.Object other) { + return false; + } + + @java.lang.Override() + public int hashCode() { + return 0; + } + + @java.lang.Override() + @org.jetbrains.annotations.NotNull() + public java.lang.String toString() { + return null; + } +} diff --git a/plugins/kapt3/kapt3-compiler/tests-gen/org/jetbrains/kotlin/kapt3/test/runners/IrClassFileToSourceStubConverterTestGenerated.java b/plugins/kapt3/kapt3-compiler/tests-gen/org/jetbrains/kotlin/kapt3/test/runners/IrClassFileToSourceStubConverterTestGenerated.java index 055eb51ac69..fbab60c95a6 100644 --- a/plugins/kapt3/kapt3-compiler/tests-gen/org/jetbrains/kotlin/kapt3/test/runners/IrClassFileToSourceStubConverterTestGenerated.java +++ b/plugins/kapt3/kapt3-compiler/tests-gen/org/jetbrains/kotlin/kapt3/test/runners/IrClassFileToSourceStubConverterTestGenerated.java @@ -389,6 +389,12 @@ public class IrClassFileToSourceStubConverterTestGenerated extends AbstractIrCla runTest("plugins/kapt3/kapt3-compiler/testData/converter/jvmOverloads.kt"); } + @Test + @TestMetadata("jvmRecord.kt") + public void testJvmRecord() { + runTest("plugins/kapt3/kapt3-compiler/testData/converter/jvmRecord.kt"); + } + @Test @TestMetadata("jvmRepeatableAnnotation.kt") public void testJvmRepeatableAnnotation() { diff --git a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/StubGenerator.kt b/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/StubGenerator.kt index 219d9162ed3..6cf2fb6dd66 100644 --- a/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/StubGenerator.kt +++ b/plugins/kapt4/src/org/jetbrains/kotlin/kapt4/StubGenerator.kt @@ -218,33 +218,40 @@ private class StubGenerator( printComment(psiClass) + calculateMetadata(psiClass)?.let { printMetadata(it) } + printModifiers(psiClass) val classWord = when { psiClass.isAnnotationType -> "@interface" psiClass.isInterface -> "interface" psiClass.isEnum -> "enum" + psiClass.isRecord -> "record" else -> "class" } - calculateMetadata(psiClass)?.let { printMetadata(it) } - printModifiers(psiClass) printWithNoIndent(classWord, " ", simpleName) printTypeParams(psiClass.typeParameters) - psiClass.extendsList - ?.referencedTypes - ?.asList() - ?.let { if (!psiClass.isInterface) it.take(1) else it } - ?.takeIf { it.isNotEmpty() } - ?.let { superClasses -> - printWithNoIndent(" extends ") - superClasses.forEachIndexed { index, type -> - if (index > 0) printWithNoIndent(", ") - printType(type) + if (psiClass.isRecord) { + printParameters(psiClass.constructors.first()) + } + + if (!psiClass.isRecord) { + psiClass.extendsList + ?.referencedTypes + ?.asList() + ?.let { if (!psiClass.isInterface) it.take(1) else it } + ?.takeIf { it.isNotEmpty() } + ?.let { superClasses -> + printWithNoIndent(" extends ") + superClasses.forEachIndexed { index, type -> + if (index > 0) printWithNoIndent(", ") + printType(type) + } } - } + } psiClass.implementsList ?.referencedTypes - ?.filterNot { it.canonicalText.startsWith("kotlin.collections.") } + ?.filterNot { it.qualifiedName.startsWith("kotlin.collections.") || it.qualifiedName == "java.lang.Record" } ?.takeIf { it.isNotEmpty() } ?.let { interfaces -> printWithNoIndent(" implements ") @@ -253,6 +260,7 @@ private class StubGenerator( printType(type) } } + printlnWithNoIndent(" {") pushIndent() @@ -278,9 +286,12 @@ private class StubGenerator( .onEach { lineMappings.registerField(psiClass, it) } .associateWith { MemberData(it.name, it.signature, lineMappings.getPosition(psiClass, it)) } - fieldsPositions.keys.sortedWith(MembersPositionComparator(classPosition, fieldsPositions)).forEachIndexed { index, field -> - if (index > 0) printlnWithNoIndent() - printField(field) + if (!psiClass.isRecord) { + fieldsPositions.keys.sortedWith(MembersPositionComparator(classPosition, fieldsPositions)) + .forEachIndexed { index, field -> + if (index > 0) printlnWithNoIndent() + printField(field) + } } val methodsPositions = psiClass.methods @@ -292,12 +303,13 @@ private class StubGenerator( .onEach { lineMappings.registerMethod(psiClass, it) } .associateWith { MemberData(it.name, it.signature, lineMappings.getPosition(psiClass, it)) } - if (methodsPositions.isNotEmpty()) printlnWithNoIndent() methodsPositions.keys.sortedWith(MembersPositionComparator(classPosition, methodsPositions)) - .forEachIndexed { index, method -> + .forEach { method -> lineMappings.registerSignature(javacSignature(method), method) - if (index > 0) printlnWithNoIndent() - printMethod(method) + if (!psiClass.isRecord || method != psiClass.constructors.firstOrNull()) { + printlnWithNoIndent() + printMethod(method) + } } if (psiClass.innerClasses.isNotEmpty() && (fieldsPositions.isNotEmpty() || methodsPositions.isNotEmpty())) println() @@ -350,14 +362,8 @@ private class StubGenerator( printType(it) printWithNoIndent(" ") } - printWithNoIndent(method.name, "(") - method.parameterList.parameters.filter { isValidIdentifier(paramName(it)) }.forEachIndexed { index, param -> - if (index > 0) printWithNoIndent(", ") - printModifiers(param) - printType(param.type) - printWithNoIndent(" ", paramName(param)) - } - printWithNoIndent(")") + printWithNoIndent(method.name) + printParameters(method) (method as? PsiAnnotationMethod)?.defaultValue?.let { printWithNoIndent(" default ") printAnnotationMemberValue(it) @@ -378,10 +384,10 @@ private class StubGenerator( pushIndent() if (method.isConstructor && !psiClass.isEnum) { - val superConstructor = method.containingClass?.superClass?.constructors?.firstOrNull { !it.isPrivate } - if (superConstructor != null) { - print("super(") - val args = superConstructor.parameterList.parameters.map { defaultValue(it.type) } + val delegateTo = (if (psiClass.isRecord) psiClass else psiClass.superClass)?.constructors?.firstOrNull { !it.isPrivate } + if (delegateTo != null) { + print(if (psiClass.isRecord) "this(" else "super(") + val args = delegateTo.parameterList.parameters.map { defaultValue(it.type) } args.forEachIndexed { index, arg -> if (index > 0) printWithNoIndent(", ") printWithNoIndent(arg) @@ -396,6 +402,17 @@ private class StubGenerator( } } + private fun Printer.printParameters(method: PsiMethod) { + printWithNoIndent("(") + method.parameterList.parameters.filter { isValidIdentifier(paramName(it)) }.forEachIndexed { index, param -> + if (index > 0) printWithNoIndent(", ") + printModifiers(param) + printType(param.type) + printWithNoIndent(" ", paramName(param)) + } + printWithNoIndent(")") + } + private fun javacSignature(method: PsiMethod) = printToString { print(method.name, "(") method.parameterList.parameters.forEachIndexed{ index, parameter -> @@ -525,6 +542,7 @@ private class StubGenerator( onError("Support for interface methods with bodies in Kapt requires -Xjvm-default=all or -Xjvm-default=all-compatibility compiler option") } + if (modifier == PsiModifier.FINAL && modifierListOwner is PsiClass && modifierListOwner.isRecord) continue if ((modifier != PsiModifier.FINAL && modifier != PsiModifier.ABSTRACT) || !(modifierListOwner is PsiClass && modifierListOwner.isEnum)) { printWithNoIndent(modifier, " ") } diff --git a/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Facade.kt b/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Facade.kt index f59cdfb885e..7ec6686844c 100644 --- a/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Facade.kt +++ b/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Facade.kt @@ -12,7 +12,6 @@ import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenProvider import org.jetbrains.kotlin.analysis.api.lifetime.KtReadActionConfinementLifetimeTokenProvider import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISession -import org.jetbrains.kotlin.asJava.classes.KtLightClass import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoots import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.kapt3.base.KaptContext @@ -47,12 +46,12 @@ internal class Kapt4Facade(private val testServices: TestServices) : val configuration = configurationProvider.getCompilerConfiguration(module) configuration.addKotlinSourceRoots(module.files.filter { it.isKtFile }.map { it.realFile().absolutePath }) val options = testServices.kaptOptionsProvider[module] - val (context, stubMap) = run( + val (context, stubs) = run( configuration, options, configurationProvider.testRootDisposable ) - return Kapt4ContextBinaryArtifact(context, stubMap.values.filterNotNull()) + return Kapt4ContextBinaryArtifact(context, stubs) } private fun TestFile.realFile(): File { @@ -69,7 +68,7 @@ private fun run( configuration: CompilerConfiguration, options: KaptOptions, projectDisposable: Disposable, -): Pair> { +): Pair> { val standaloneAnalysisAPISession = buildStandaloneAnalysisAPISession(projectDisposable) { (project as MockProject).registerService( KtLifetimeTokenProvider::class.java, @@ -111,7 +110,8 @@ private fun run( } } - return context to generateStubs(module, files, options, logger, metadataRenderer = { renderMetadata(it) }) + val stubsMap = generateStubs(module, files, options, logger, metadataRenderer = { renderMetadata(it) }) + return context to stubsMap.entries.sortedBy { it.key.qualifiedName }.mapNotNull { it.value } } internal data class Kapt4ContextBinaryArtifact( diff --git a/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Handler.kt b/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Handler.kt index 3fdc758b693..36df0b365e6 100644 --- a/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Handler.kt +++ b/plugins/kapt4/test/org/jetbrains/kotlin/kapt4/Kapt4Handler.kt @@ -41,26 +41,24 @@ internal class Kapt4Handler(testServices: TestServices) : AnalysisHandler