diff --git a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt index 90d43a12d31..a6429a844dc 100644 --- a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt +++ b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt @@ -35,7 +35,10 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument +import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny import org.jetbrains.kotlin.resolve.source.PsiSourceElement import org.jetbrains.kotlin.types.KotlinType @@ -557,7 +560,7 @@ class ClassFileToSourceStubConverter( return name.pathSegments().all { getValidIdentifierName(it.asString(), false) != null } } - fun getValidIdentifierName(name: String, canBeConstructor: Boolean = false): String? { + private fun getValidIdentifierName(name: String, canBeConstructor: Boolean = false): String? { if (canBeConstructor && name == "") { return name } @@ -632,29 +635,102 @@ class ClassFileToSourceStubConverter( val argMapping = ktAnnotation?.calleeExpression ?.getResolvedCall(kaptContext.bindingContext)?.valueArguments ?.mapKeys { it.key.name.asString() } - - fun getKtElementsForArgument(name: String): List? { - val args = argMapping?.get(name) ?: return null - return args.arguments.map { it.getArgumentExpression() } - } + ?: emptyMap() val useSimpleName = '.' in fqName && fqName.substringBeforeLast('.', "") == packageFqName - val name = if (useSimpleName) treeMaker.FqName(fqName.substring(packageFqName!!.length + 1)) else treeMaker.Type(annotationType) - val values = mapPairedValuesJList(annotation.values) { key, value -> - val name = getValidIdentifierName(key) ?: return@mapPairedValuesJList null - val convertedExpression = convertLiteralExpression(value) - fun assign(expr: JCExpression) = treeMaker.Assign(treeMaker.SimpleName(name), expr) - - if (value is Int) { // This may be a resource identifier - val ktExpression = getKtElementsForArgument(name)?.singleOrNull() - tryParseReferenceToAndroidResource(ktExpression)?.let { return@mapPairedValuesJList assign(it) } - } - - assign(convertedExpression) + val annotationFqName = when { + useSimpleName -> treeMaker.FqName(fqName.substring(packageFqName!!.length + 1)) + else -> treeMaker.Type(annotationType) } - return treeMaker.Annotation(name, values) + val constantValues = pairedListToMap(annotation.values) + + val values = if (argMapping.isNotEmpty()) { + argMapping.mapNotNull { (parameterName, arg) -> + if (arg is DefaultValueArgument) return@mapNotNull null + convertAnnotationArgumentWithName(constantValues[parameterName], arg, parameterName) + } + } else { + constantValues.mapNotNull { (parameterName, arg) -> + convertAnnotationArgumentWithName(arg, null, parameterName) + } + } + + return treeMaker.Annotation(annotationFqName, JavacList.from(values)) + } + + private fun convertAnnotationArgumentWithName(constantValue: Any?, value: ResolvedValueArgument?, name: String): JCExpression? { + val validName = getValidIdentifierName(name) ?: return null + val expr = convertAnnotationArgument(constantValue, value) ?: return null + return treeMaker.Assign(treeMaker.SimpleName(validName), expr) + } + + private fun convertAnnotationArgument(constantValue: Any?, value: ResolvedValueArgument?): JCExpression? { + val args = value?.arguments?.mapNotNull { it.getArgumentExpression() } ?: emptyList() + val singleArg by lazy { args.singleOrNull() } + + if (constantValue is Int) { + // This may be a resource identifier, we should not inline the id int + tryParseReferenceToAndroidResource(singleArg)?.let { return it } + } + + fun tryParseTypeExpression(expression: KtExpression?): JCExpression? { + return when (expression) { + is KtSimpleNameExpression -> treeMaker.SimpleName(expression.getReferencedName()) + is KtDotQualifiedExpression -> { + val selector = expression.selectorExpression as? KtSimpleNameExpression ?: return null + val receiver = tryParseTypeExpression(expression.receiverExpression) ?: return null + return treeMaker.Select(receiver, treeMaker.name(selector.getReferencedName())) + } + else -> null + } + } + + fun tryParseTypeLiteralExpression(expression: KtExpression?): JCExpression? { + val literalExpression = expression as? KtClassLiteralExpression ?: return null + val typeExpression = tryParseTypeExpression(literalExpression.receiverExpression) ?: return null + return treeMaker.Select(typeExpression, treeMaker.name("class")) + } + + // Unresolved class literal + if (constantValue == null && singleArg is KtClassLiteralExpression) { + tryParseTypeLiteralExpression(singleArg)?.let { return it } + } + + // Some of class literals in vararg list are unresolved + if (args.isNotEmpty() && args[0] is KtClassLiteralExpression && constantValue is List<*> && args.size != constantValue.size) { + val literalExpressions = mapJList(args, ::tryParseTypeLiteralExpression) + if (literalExpressions.size == args.size) { + return treeMaker.NewArray(null, null, literalExpressions) + } + } + + // Probably arrayOf(SomeUnresolvedType::class, ...) + if (constantValue is List<*>) { + val callArgs = when (singleArg) { + is KtCallExpression -> { + val resultingDescriptor = singleArg.getResolvedCall(kaptContext.bindingContext)?.resultingDescriptor + + if (resultingDescriptor is FunctionDescriptor && resultingDescriptor.fqNameSafe.asString() == "kotlin.arrayOf") + singleArg.valueArguments.map { it.getArgumentExpression() } + else + null + } + is KtCollectionLiteralExpression -> singleArg.getInnerExpressions() + else -> null + } + + // So we make sure something is absent in the constant value + if (callArgs != null && callArgs.size != constantValue.size) { + val literalExpressions = mapJList(callArgs, ::tryParseTypeLiteralExpression) + if (literalExpressions.size == callArgs.size) { + return treeMaker.NewArray(null, null, literalExpressions) + } + } + } + + return convertLiteralExpression(constantValue) } private fun tryParseReferenceToAndroidResource(expression: KtExpression?): JCExpression? { diff --git a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/util/javacListUtils.kt b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/util/javacListUtils.kt index d3526ef165b..1499ebb9c53 100644 --- a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/util/javacListUtils.kt +++ b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/util/javacListUtils.kt @@ -54,6 +54,16 @@ internal inline fun mapPairedValuesJList(valuePairs: List?, f: (String, return result.reverse() } +internal fun pairedListToMap(valuePairs: List?): Map { + val map = mutableMapOf() + + mapPairedValuesJList(valuePairs) { key, value -> + map.put(key, value) + } + + return map +} + internal operator fun JavacList.plus(other: JavacList): JavacList { return this.appendList(other) } \ No newline at end of file diff --git a/plugins/kapt3/testData/converter/nonExistentClassTypesConversion.kt b/plugins/kapt3/testData/converter/nonExistentClassTypesConversion.kt index c5f49bd0688..e840823ee2b 100644 --- a/plugins/kapt3/testData/converter/nonExistentClassTypesConversion.kt +++ b/plugins/kapt3/testData/converter/nonExistentClassTypesConversion.kt @@ -1,11 +1,14 @@ // CORRECT_ERROR_TYPES // NON_EXISTENT_CLASS // NO_VALIDATION +// WITH_RUNTIME import java.util.Calendar +import kotlin.reflect.KClass -@Suppress("UNRESOLVED_REFERENCE") -class Test { +@Suppress("UNRESOLVED_REFERENCE", "ANNOTATION_PARAMETER_MUST_BE_CONST", "NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION", "UNSUPPORTED_FEATURE") +@Anno(Blah::class, arrayOf(NoFoo1::class, NoBar1::class), [NoFoo2::class, String::class], Boolean::class, NoBar3::class) +class Test/* { lateinit var a: ABC val b: ABC? = null val c: List? = null @@ -43,4 +46,7 @@ class Test { fun MyType.f5(): java.lang.Class>? = null } -class MyType \ No newline at end of file +class MyType + +*/ +annotation class Anno(val a: KClass<*>, val b: Array>, val c: Array>, vararg val d: KClass<*>) \ No newline at end of file diff --git a/plugins/kapt3/testData/converter/nonExistentClassTypesConversion.txt b/plugins/kapt3/testData/converter/nonExistentClassTypesConversion.txt index 4ebb763d85f..52e01b67fa3 100644 --- a/plugins/kapt3/testData/converter/nonExistentClassTypesConversion.txt +++ b/plugins/kapt3/testData/converter/nonExistentClassTypesConversion.txt @@ -1,214 +1,29 @@ import java.util.Calendar; +import kotlin.reflect.KClass; @kotlin.Metadata() -public final class MyType { +@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) +public abstract @interface Anno { - public MyType() { - super(); - } + public abstract java.lang.Class a(); + + public abstract java.lang.Class[] b(); + + public abstract java.lang.Class[] c(); + + public abstract java.lang.Class[] d(); } //////////////////// import java.util.Calendar; +import kotlin.reflect.KClass; -@kotlin.Suppress(names = {"UNRESOLVED_REFERENCE"}) +@kotlin.Suppress(names = {"UNRESOLVED_REFERENCE", "ANNOTATION_PARAMETER_MUST_BE_CONST", "NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION", "UNSUPPORTED_FEATURE"}) @kotlin.Metadata() +@Anno(a = Blah.class, b = {NoFoo1.class, NoBar1.class}, c = {NoFoo2.class, String.class}, d = {Boolean.class, NoBar3.class}) public final class Test { - @org.jetbrains.annotations.NotNull() - public ABC a; - @org.jetbrains.annotations.Nullable() - private final ABC b = null; - @org.jetbrains.annotations.Nullable() - private final java.util.List c = null; - @org.jetbrains.annotations.Nullable() - private final java.util.List>>> d = null; - @org.jetbrains.annotations.NotNull() - public java.util.List> e; - @org.jetbrains.annotations.NotNull() - public ABC f; - @org.jetbrains.annotations.NotNull() - public java.util.List g; - @org.jetbrains.annotations.NotNull() - public ABC h; - @org.jetbrains.annotations.NotNull() - public Function2, CDE> i; - @org.jetbrains.annotations.NotNull() - public Function0 j; - @org.jetbrains.annotations.NotNull() - public Function2, CDE> k; - @org.jetbrains.annotations.NotNull() - public ABC.BCD.EFG l; - @org.jetbrains.annotations.NotNull() - private final error.NonExistentClass m = null; - @org.jetbrains.annotations.NotNull() - private final java.lang.String n = ""; - @org.jetbrains.annotations.NotNull() - public java.util.List>>>>>>>>> o11; - @org.jetbrains.annotations.NotNull() - public java.util.List>>>>>>>> o10; - @org.jetbrains.annotations.NotNull() - public java.util.Calendar.Builder p; - - @org.jetbrains.annotations.NotNull() - public final ABC getA() { - return null; - } - - public final void setA(@org.jetbrains.annotations.NotNull() - ABC p0) { - } - - @org.jetbrains.annotations.Nullable() - public final ABC getB() { - return null; - } - - @org.jetbrains.annotations.Nullable() - public final java.util.List getC() { - return null; - } - - @org.jetbrains.annotations.Nullable() - public final java.util.List>>> getD() { - return null; - } - - @org.jetbrains.annotations.NotNull() - public final java.util.List> getE() { - return null; - } - - public final void setE(@org.jetbrains.annotations.NotNull() - java.util.List> p0) { - } - - @org.jetbrains.annotations.NotNull() - public final ABC getF() { - return null; - } - - public final void setF(@org.jetbrains.annotations.NotNull() - ABC p0) { - } - - @org.jetbrains.annotations.NotNull() - public final java.util.List getG() { - return null; - } - - public final void setG(@org.jetbrains.annotations.NotNull() - java.util.List p0) { - } - - @org.jetbrains.annotations.NotNull() - public final ABC getH() { - return null; - } - - public final void setH(@org.jetbrains.annotations.NotNull() - ABC p0) { - } - - @org.jetbrains.annotations.NotNull() - public final Function2, CDE> getI() { - return null; - } - - public final void setI(@org.jetbrains.annotations.NotNull() - Function2, CDE> p0) { - } - - @org.jetbrains.annotations.NotNull() - public final Function0 getJ() { - return null; - } - - public final void setJ(@org.jetbrains.annotations.NotNull() - Function0 p0) { - } - - @org.jetbrains.annotations.NotNull() - public final Function2, CDE> getK() { - return null; - } - - public final void setK(@org.jetbrains.annotations.NotNull() - Function2, CDE> p0) { - } - - @org.jetbrains.annotations.NotNull() - public final ABC.BCD.EFG getL() { - return null; - } - - public final void setL(@org.jetbrains.annotations.NotNull() - ABC.BCD.EFG p0) { - } - - @org.jetbrains.annotations.NotNull() - public final error.NonExistentClass getM() { - return null; - } - - @org.jetbrains.annotations.NotNull() - public final java.lang.String getN() { - return null; - } - - @org.jetbrains.annotations.NotNull() - public final java.util.List>>>>>>>>> getO11() { - return null; - } - - public final void setO11(@org.jetbrains.annotations.NotNull() - java.util.List>>>>>>>>> p0) { - } - - @org.jetbrains.annotations.NotNull() - public final java.util.List>>>>>>>> getO10() { - return null; - } - - public final void setO10(@org.jetbrains.annotations.NotNull() - java.util.List>>>>>>>> p0) { - } - - @org.jetbrains.annotations.NotNull() - public final java.util.Calendar.Builder getP() { - return null; - } - - public final void setP(@org.jetbrains.annotations.NotNull() - java.util.Calendar.Builder p0) { - } - - @org.jetbrains.annotations.Nullable() - public final BCD f1(@org.jetbrains.annotations.NotNull() - ABC a) { - return null; - } - - public final void f2(@org.jetbrains.annotations.NotNull() - ABC> a) { - } - - public final long f3(@org.jetbrains.annotations.NotNull() - ABC a, int b) { - return 0L; - } - - @org.jetbrains.annotations.NotNull() - public final error.NonExistentClass f4() { - return null; - } - - @org.jetbrains.annotations.Nullable() - public final java.lang.Class> f5(@org.jetbrains.annotations.NotNull() - MyType $receiver) { - return null; - } public Test() { super();