[KAPT] KT-44706 Support @JvmRecord-annotated classes

Don't parse Java stubs in tests if no validation is needed

Merge-request: KT-MR-14308
Merged-by: Pavel Mikhailovskii <Pavel.Mikhailovskii@jetbrains.com>
This commit is contained in:
Pavel Mikhailovskii
2024-02-29 12:44:56 +00:00
committed by Space Team
parent 6288bdbd3f
commit 12552e4e04
8 changed files with 354 additions and 51 deletions
@@ -0,0 +1,122 @@
/**
* public final annotation class A : kotlin/Annotation {
*
* // signature: <init>()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<T#0 (* T *)> : I, java/lang/Record {
*
* // signature: <init>(ILjava/lang/Object;)V
* public constructor(x: kotlin/Int, y: T#0)
*
* // signature: <init>(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<T#0>
*
* // 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<T extends java.lang.Object>(@A() int x, T y) implements I {
@org.jetbrains.annotations.NotNull()
public final R<T> 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;
}
}
@@ -0,0 +1,15 @@
// JDK_KIND: FULL_JDK_21
// NO_VALIDATION
annotation class A
interface I {
fun foo(): Int
}
@A
@JvmRecord
data class R<T>(@A val x: Int, val y: T): I {
constructor(y: T) : this(0, y)
override fun foo(): Int = 0
}
@@ -0,0 +1,138 @@
@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
/**
* public final annotation class A : kotlin/Annotation {
*
* // signature: <init>()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<T#0 (* T *)> : I, java/lang/Record {
*
* // signature: <init>(ILjava/lang/Object;)V
* public constructor(x: kotlin/Int, y: T#0)
*
* // signature: <init>(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<T#0>
*
* // 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<T extends java.lang.Object> 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<T> 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;
}
}
@@ -389,6 +389,12 @@ public class IrClassFileToSourceStubConverterTestGenerated extends AbstractIrCla
runTest("plugins/kapt3/kapt3-compiler/testData/converter/jvmOverloads.kt"); 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 @Test
@TestMetadata("jvmRepeatableAnnotation.kt") @TestMetadata("jvmRepeatableAnnotation.kt")
public void testJvmRepeatableAnnotation() { public void testJvmRepeatableAnnotation() {
@@ -218,33 +218,40 @@ private class StubGenerator(
printComment(psiClass) printComment(psiClass)
calculateMetadata(psiClass)?.let { printMetadata(it) }
printModifiers(psiClass)
val classWord = when { val classWord = when {
psiClass.isAnnotationType -> "@interface" psiClass.isAnnotationType -> "@interface"
psiClass.isInterface -> "interface" psiClass.isInterface -> "interface"
psiClass.isEnum -> "enum" psiClass.isEnum -> "enum"
psiClass.isRecord -> "record"
else -> "class" else -> "class"
} }
calculateMetadata(psiClass)?.let { printMetadata(it) }
printModifiers(psiClass)
printWithNoIndent(classWord, " ", simpleName) printWithNoIndent(classWord, " ", simpleName)
printTypeParams(psiClass.typeParameters) printTypeParams(psiClass.typeParameters)
psiClass.extendsList if (psiClass.isRecord) {
?.referencedTypes printParameters(psiClass.constructors.first())
?.asList() }
?.let { if (!psiClass.isInterface) it.take(1) else it }
?.takeIf { it.isNotEmpty() } if (!psiClass.isRecord) {
?.let { superClasses -> psiClass.extendsList
printWithNoIndent(" extends ") ?.referencedTypes
superClasses.forEachIndexed { index, type -> ?.asList()
if (index > 0) printWithNoIndent(", ") ?.let { if (!psiClass.isInterface) it.take(1) else it }
printType(type) ?.takeIf { it.isNotEmpty() }
?.let { superClasses ->
printWithNoIndent(" extends ")
superClasses.forEachIndexed { index, type ->
if (index > 0) printWithNoIndent(", ")
printType(type)
}
} }
} }
psiClass.implementsList psiClass.implementsList
?.referencedTypes ?.referencedTypes
?.filterNot { it.canonicalText.startsWith("kotlin.collections.") } ?.filterNot { it.qualifiedName.startsWith("kotlin.collections.") || it.qualifiedName == "java.lang.Record" }
?.takeIf { it.isNotEmpty() } ?.takeIf { it.isNotEmpty() }
?.let { interfaces -> ?.let { interfaces ->
printWithNoIndent(" implements ") printWithNoIndent(" implements ")
@@ -253,6 +260,7 @@ private class StubGenerator(
printType(type) printType(type)
} }
} }
printlnWithNoIndent(" {") printlnWithNoIndent(" {")
pushIndent() pushIndent()
@@ -278,9 +286,12 @@ private class StubGenerator(
.onEach { lineMappings.registerField(psiClass, it) } .onEach { lineMappings.registerField(psiClass, it) }
.associateWith { MemberData(it.name, it.signature, lineMappings.getPosition(psiClass, it)) } .associateWith { MemberData(it.name, it.signature, lineMappings.getPosition(psiClass, it)) }
fieldsPositions.keys.sortedWith(MembersPositionComparator(classPosition, fieldsPositions)).forEachIndexed { index, field -> if (!psiClass.isRecord) {
if (index > 0) printlnWithNoIndent() fieldsPositions.keys.sortedWith(MembersPositionComparator(classPosition, fieldsPositions))
printField(field) .forEachIndexed { index, field ->
if (index > 0) printlnWithNoIndent()
printField(field)
}
} }
val methodsPositions = psiClass.methods val methodsPositions = psiClass.methods
@@ -292,12 +303,13 @@ private class StubGenerator(
.onEach { lineMappings.registerMethod(psiClass, it) } .onEach { lineMappings.registerMethod(psiClass, it) }
.associateWith { MemberData(it.name, it.signature, lineMappings.getPosition(psiClass, it)) } .associateWith { MemberData(it.name, it.signature, lineMappings.getPosition(psiClass, it)) }
if (methodsPositions.isNotEmpty()) printlnWithNoIndent()
methodsPositions.keys.sortedWith(MembersPositionComparator(classPosition, methodsPositions)) methodsPositions.keys.sortedWith(MembersPositionComparator(classPosition, methodsPositions))
.forEachIndexed { index, method -> .forEach { method ->
lineMappings.registerSignature(javacSignature(method), method) lineMappings.registerSignature(javacSignature(method), method)
if (index > 0) printlnWithNoIndent() if (!psiClass.isRecord || method != psiClass.constructors.firstOrNull()) {
printMethod(method) printlnWithNoIndent()
printMethod(method)
}
} }
if (psiClass.innerClasses.isNotEmpty() && (fieldsPositions.isNotEmpty() || methodsPositions.isNotEmpty())) println() if (psiClass.innerClasses.isNotEmpty() && (fieldsPositions.isNotEmpty() || methodsPositions.isNotEmpty())) println()
@@ -350,14 +362,8 @@ private class StubGenerator(
printType(it) printType(it)
printWithNoIndent(" ") printWithNoIndent(" ")
} }
printWithNoIndent(method.name, "(") printWithNoIndent(method.name)
method.parameterList.parameters.filter { isValidIdentifier(paramName(it)) }.forEachIndexed { index, param -> printParameters(method)
if (index > 0) printWithNoIndent(", ")
printModifiers(param)
printType(param.type)
printWithNoIndent(" ", paramName(param))
}
printWithNoIndent(")")
(method as? PsiAnnotationMethod)?.defaultValue?.let { (method as? PsiAnnotationMethod)?.defaultValue?.let {
printWithNoIndent(" default ") printWithNoIndent(" default ")
printAnnotationMemberValue(it) printAnnotationMemberValue(it)
@@ -378,10 +384,10 @@ private class StubGenerator(
pushIndent() pushIndent()
if (method.isConstructor && !psiClass.isEnum) { if (method.isConstructor && !psiClass.isEnum) {
val superConstructor = method.containingClass?.superClass?.constructors?.firstOrNull { !it.isPrivate } val delegateTo = (if (psiClass.isRecord) psiClass else psiClass.superClass)?.constructors?.firstOrNull { !it.isPrivate }
if (superConstructor != null) { if (delegateTo != null) {
print("super(") print(if (psiClass.isRecord) "this(" else "super(")
val args = superConstructor.parameterList.parameters.map { defaultValue(it.type) } val args = delegateTo.parameterList.parameters.map { defaultValue(it.type) }
args.forEachIndexed { index, arg -> args.forEachIndexed { index, arg ->
if (index > 0) printWithNoIndent(", ") if (index > 0) printWithNoIndent(", ")
printWithNoIndent(arg) 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 { private fun javacSignature(method: PsiMethod) = printToString {
print(method.name, "(") print(method.name, "(")
method.parameterList.parameters.forEachIndexed{ index, parameter -> 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") 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)) { if ((modifier != PsiModifier.FINAL && modifier != PsiModifier.ABSTRACT) || !(modifierListOwner is PsiClass && modifierListOwner.isEnum)) {
printWithNoIndent(modifier, " ") printWithNoIndent(modifier, " ")
} }
@@ -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.KtLifetimeTokenProvider
import org.jetbrains.kotlin.analysis.api.lifetime.KtReadActionConfinementLifetimeTokenProvider import org.jetbrains.kotlin.analysis.api.lifetime.KtReadActionConfinementLifetimeTokenProvider
import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISession 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.cli.common.config.addKotlinSourceRoots
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.kapt3.base.KaptContext import org.jetbrains.kotlin.kapt3.base.KaptContext
@@ -47,12 +46,12 @@ internal class Kapt4Facade(private val testServices: TestServices) :
val configuration = configurationProvider.getCompilerConfiguration(module) val configuration = configurationProvider.getCompilerConfiguration(module)
configuration.addKotlinSourceRoots(module.files.filter { it.isKtFile }.map { it.realFile().absolutePath }) configuration.addKotlinSourceRoots(module.files.filter { it.isKtFile }.map { it.realFile().absolutePath })
val options = testServices.kaptOptionsProvider[module] val options = testServices.kaptOptionsProvider[module]
val (context, stubMap) = run( val (context, stubs) = run(
configuration, configuration,
options, options,
configurationProvider.testRootDisposable configurationProvider.testRootDisposable
) )
return Kapt4ContextBinaryArtifact(context, stubMap.values.filterNotNull()) return Kapt4ContextBinaryArtifact(context, stubs)
} }
private fun TestFile.realFile(): File { private fun TestFile.realFile(): File {
@@ -69,7 +68,7 @@ private fun run(
configuration: CompilerConfiguration, configuration: CompilerConfiguration,
options: KaptOptions, options: KaptOptions,
projectDisposable: Disposable, projectDisposable: Disposable,
): Pair<KaptContext, Map<KtLightClass, KaptStub?>> { ): Pair<KaptContext, List<KaptStub>> {
val standaloneAnalysisAPISession = buildStandaloneAnalysisAPISession(projectDisposable) { val standaloneAnalysisAPISession = buildStandaloneAnalysisAPISession(projectDisposable) {
(project as MockProject).registerService( (project as MockProject).registerService(
KtLifetimeTokenProvider::class.java, 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( internal data class Kapt4ContextBinaryArtifact(
@@ -41,26 +41,24 @@ internal class Kapt4Handler(testServices: TestServices) : AnalysisHandler<Kapt4C
get() = Kapt4ContextBinaryArtifact.Kind get() = Kapt4ContextBinaryArtifact.Kind
override fun processModule(module: TestModule, info: Kapt4ContextBinaryArtifact) { override fun processModule(module: TestModule, info: Kapt4ContextBinaryArtifact) {
val validate = KaptTestDirectives.NO_VALIDATION !in module.directives val stubs = info.kaptStubs.map { it.source }
val actualRaw = stubs.joinToString(ClassFileToSourceKaptStubHandler.FILE_SEPARATOR)
val (kaptContext) = info
val convertedFiles = getJavaFiles(info)
kaptContext.javaLog.interceptorData.files = convertedFiles.associateBy { it.sourceFile }
if (validate) kaptContext.compiler.enterTrees(convertedFiles)
val actualRaw = convertedFiles
.sortedBy { it.sourceFile.name }
.joinToString(ClassFileToSourceKaptStubHandler.FILE_SEPARATOR) { (it.sourceFile as KaptJavaFileObject).file!!.readText() }
val actual = StringUtil.convertLineSeparators(actualRaw.trim { it <= ' ' }) val actual = StringUtil.convertLineSeparators(actualRaw.trim { it <= ' ' })
.trimTrailingWhitespacesAndAddNewlineAtEOF() .trimTrailingWhitespacesAndAddNewlineAtEOF()
.let { removeMetadataAnnotationContents(it) } .let { removeMetadataAnnotationContents(it) }
val validate = KaptTestDirectives.NO_VALIDATION !in module.directives
if (validate) {
val (kaptContext) = info
val convertedFiles = getJavaFiles(info)
kaptContext.javaLog.interceptorData.files = convertedFiles.associateBy { it.sourceFile }
kaptContext.compiler.enterTrees(convertedFiles)
}
assertions.assertAll( assertions.assertAll(
{ assertions.checkTxt(module, actual) }, { assertions.checkTxt(module, actual) },
{ {
if (kaptContext.compiler.shouldStop(CompileStates.CompileState.ENTER)) { if (info.kaptContext.compiler.shouldStop(CompileStates.CompileState.ENTER)) {
checkJavaCompilerErrors(module, kaptContext, actual) checkJavaCompilerErrors(module, info.kaptContext, actual)
} }
} }
) )
@@ -389,6 +389,12 @@ public class KotlinKapt4ContextTestGenerated extends AbstractKotlinKapt4ContextT
runTest("plugins/kapt4/../kapt3/kapt3-compiler/testData/converter/jvmOverloads.kt"); runTest("plugins/kapt4/../kapt3/kapt3-compiler/testData/converter/jvmOverloads.kt");
} }
@Test
@TestMetadata("jvmRecord.kt")
public void testJvmRecord() {
runTest("plugins/kapt4/../kapt3/kapt3-compiler/testData/converter/jvmRecord.kt");
}
@Test @Test
@TestMetadata("jvmRepeatableAnnotation.kt") @TestMetadata("jvmRepeatableAnnotation.kt")
public void testJvmRepeatableAnnotation() { public void testJvmRepeatableAnnotation() {