diff --git a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/javac/KaptTreeMaker.kt b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/javac/KaptTreeMaker.kt index 5f93c2e67e2..5ff19a547be 100644 --- a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/javac/KaptTreeMaker.kt +++ b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/javac/KaptTreeMaker.kt @@ -27,6 +27,7 @@ import com.sun.tools.javac.util.Name import com.sun.tools.javac.util.Names import org.jetbrains.kotlin.codegen.AsmUtil import org.jetbrains.kotlin.kapt3.KaptContext +import org.jetbrains.kotlin.name.FqName import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.Type.* import org.jetbrains.org.objectweb.asm.tree.ClassNode @@ -45,6 +46,12 @@ class KaptTreeMaker(context: Context, private val kaptContext: KaptContext<*>) : fun FqName(internalOrFqName: String): JCTree.JCExpression { val path = getQualifiedName(internalOrFqName).convertSpecialFqName().split('.') assert(path.isNotEmpty()) + return FqName(path) + } + + fun FqName(fqName: FqName) = FqName(fqName.pathSegments().map { it.asString() }) + + private fun FqName(path: List): JCTree.JCExpression { if (path.size == 1) return SimpleName(path.single()) var expr = Select(SimpleName(path[0]), name(path[1])) 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 4280ff12d10..90d43a12d31 100644 --- a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt +++ b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/stubs/ClassFileToSourceStubConverter.kt @@ -25,6 +25,8 @@ import com.sun.tools.javac.tree.JCTree.* import com.sun.tools.javac.tree.TreeMaker import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor +import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.kapt3.* import org.jetbrains.kotlin.kapt3.javac.KaptTreeMaker import org.jetbrains.kotlin.kapt3.javac.KaptJavaFileObject @@ -32,7 +34,10 @@ import org.jetbrains.kotlin.kapt3.util.* 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.descriptorUtil.fqNameOrNull import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny +import org.jetbrains.kotlin.resolve.source.PsiSourceElement import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Type @@ -202,7 +207,7 @@ class ClassFileToSourceStubConverter( val modifiers = convertModifiers(flags, if (isEnum) ElementKind.ENUM else ElementKind.CLASS, - packageFqName, clazz.visibleAnnotations, clazz.invisibleAnnotations) + packageFqName, clazz.visibleAnnotations, clazz.invisibleAnnotations, descriptor.annotations) val isDefaultImpls = clazz.name.endsWith("${descriptor.name.asString()}\$DefaultImpls") && isPublic(clazz.access) && isFinal(clazz.access) @@ -360,7 +365,8 @@ class ClassFileToSourceStubConverter( val origin = kaptContext.origins[field] val descriptor = origin?.descriptor - val modifiers = convertModifiers(field.access, ElementKind.FIELD, packageFqName, field.visibleAnnotations, field.invisibleAnnotations) + val modifiers = convertModifiers(field.access, ElementKind.FIELD, packageFqName, + field.visibleAnnotations, field.invisibleAnnotations, descriptor?.annotations ?: Annotations.EMPTY) val name = getValidIdentifierName(field.name) ?: return null val type = Type.getType(field.desc) @@ -410,7 +416,7 @@ class ClassFileToSourceStubConverter( (method.access.toLong() and VISIBILITY_MODIFIERS.inv()) else method.access.toLong(), - ElementKind.METHOD, packageFqName, visibleAnnotations, method.invisibleAnnotations) + ElementKind.METHOD, packageFqName, visibleAnnotations, method.invisibleAnnotations, descriptor.annotations) val asmReturnType = Type.getReturnType(method.desc) val jcReturnType = if (isConstructor) null else treeMaker.Type(asmReturnType) @@ -432,7 +438,8 @@ class ClassFileToSourceStubConverter( ElementKind.PARAMETER, packageFqName, info.visibleAnnotations, - info.invisibleAnnotations) + info.invisibleAnnotations, + Annotations.EMPTY /* TODO */) val name = treeMaker.name(getValidIdentifierName(info.name) ?: "p${index}_" + info.name.hashCode().ushr(1)) val type = treeMaker.Type(info.type) @@ -573,21 +580,28 @@ class ClassFileToSourceStubConverter( kind: ElementKind, packageFqName: String, visibleAnnotations: List?, - invisibleAnnotations: List? - ): JCModifiers = convertModifiers(access.toLong(), kind, packageFqName, visibleAnnotations, invisibleAnnotations) + invisibleAnnotations: List?, + descriptorAnnotations: Annotations + ): JCModifiers = convertModifiers(access.toLong(), kind, packageFqName, visibleAnnotations, invisibleAnnotations, descriptorAnnotations) private fun convertModifiers( access: Long, kind: ElementKind, packageFqName: String, visibleAnnotations: List?, - invisibleAnnotations: List? + invisibleAnnotations: List?, + descriptorAnnotations: Annotations ): JCModifiers { + fun findDescriptorAnnotation(anno: AnnotationNode): AnnotationDescriptor? { + val annoFqName = treeMaker.getQualifiedName(Type.getType(anno.desc)) + return descriptorAnnotations.findAnnotation(FqName(annoFqName)) + } + var annotations = visibleAnnotations?.fold(JavacList.nil()) { list, anno -> - convertAnnotation(anno, packageFqName)?.let { list.prepend(it) } ?: list + convertAnnotation(anno, packageFqName, findDescriptorAnnotation(anno))?.let { list.prepend(it) } ?: list } ?: JavacList.nil() annotations = invisibleAnnotations?.fold(annotations) { list, anno -> - convertAnnotation(anno, packageFqName)?.let { list.prepend(it) } ?: list + convertAnnotation(anno, packageFqName, findDescriptorAnnotation(anno))?.let { list.prepend(it) } ?: list } ?: annotations val flags = when (kind) { @@ -601,7 +615,12 @@ class ClassFileToSourceStubConverter( return treeMaker.Modifiers(flags, annotations) } - private fun convertAnnotation(annotation: AnnotationNode, packageFqName: String? = "", filtered: Boolean = true): JCAnnotation? { + private fun convertAnnotation( + annotation: AnnotationNode, + packageFqName: String? = "", + annotationDescriptor: AnnotationDescriptor? = null, + filtered: Boolean = true + ): JCAnnotation? { val annotationType = Type.getType(annotation.desc) val fqName = treeMaker.getQualifiedName(annotationType) @@ -609,15 +628,62 @@ class ClassFileToSourceStubConverter( if (BLACKLISTED_ANNOTATIONS.any { fqName.startsWith(it) }) return null } + val ktAnnotation = (annotationDescriptor?.source as? PsiSourceElement)?.psi as? KtAnnotationEntry + 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() } + } + 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 - treeMaker.Assign(treeMaker.SimpleName(name), convertLiteralExpression(value)) + 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) } + return treeMaker.Annotation(name, values) } + private fun tryParseReferenceToAndroidResource(expression: KtExpression?): JCExpression? { + val bindingContext = kaptContext.bindingContext + + val expressionToResolve = when (expression) { + is KtDotQualifiedExpression -> expression.selectorExpression + else -> expression + } + + val resolvedCall = expressionToResolve.getResolvedCall(bindingContext) ?: return null + val fqName = resolvedCall.resultingDescriptor.fqNameOrNull() ?: return null + val jcExpression = treeMaker.FqName(fqName) + + return if (doesLookLikeAndroidIdentifier(jcExpression)) jcExpression else null + } + + private fun doesLookLikeAndroidIdentifier(expression: JCExpression): Boolean { + // 'expression' here is always a fqName, so we can forget about imports here + val rId = (expression as? JCFieldAccess)?.selected ?: return false + val r = (rId as? JCFieldAccess)?.selected ?: return false + + return when { + r is JCIdent && r.name.toString() == "R" -> true + r is JCFieldAccess && r.name.toString() == "R" -> true + else -> false + } + } + private fun convertValueOfPrimitiveTypeOrString(value: Any?): JCExpression? { return when (value) { is Char -> treeMaker.Literal(TypeTag.CHAR, value.toInt()) diff --git a/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/ClassFileToSourceStubConverterTestGenerated.java b/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/ClassFileToSourceStubConverterTestGenerated.java index 15d4465fc5a..d3fd5c4af92 100644 --- a/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/ClassFileToSourceStubConverterTestGenerated.java +++ b/plugins/kapt3/test/org/jetbrains/kotlin/kapt3/test/ClassFileToSourceStubConverterTestGenerated.java @@ -192,6 +192,12 @@ public class ClassFileToSourceStubConverterTestGenerated extends AbstractClassFi doTest(fileName); } + @TestMetadata("kt18791.kt") + public void testKt18791() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/kt18791.kt"); + doTest(fileName); + } + @TestMetadata("kt19700.kt") public void testKt19700() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("plugins/kapt3/testData/converter/kt19700.kt"); diff --git a/plugins/kapt3/testData/converter/kt18791.kt b/plugins/kapt3/testData/converter/kt18791.kt new file mode 100644 index 00000000000..c5a9c833d84 --- /dev/null +++ b/plugins/kapt3/testData/converter/kt18791.kt @@ -0,0 +1,51 @@ +//FILE: lib/R.java +package lib; + +public class R { + public static class id { + public final static int textView = 100; + } +} + +//FILE: app/R.java +package app; + +public class R { + public static class layout { + public final static int mainActivity = 100; + } +} + +//FILE: app/B.java +package app; + +public class B { + public static class id { + public final static int textView = 200; + } +} + +//FILE: test.kt +package app + +import lib.R as LibR +import lib.R.id.textView + +annotation class Bind(val id: Int) + +class MyActivity { + @Bind(LibR.id.textView) + fun foo() {} + + @Bind(lib.R.id.textView) + fun foo2() {} + + @Bind(app.R.layout.mainActivity) + fun foo3() {} + + @Bind(R.layout.mainActivity) + fun foo4() {} + + @Bind(B.id.textView) + fun notResource() {} +} \ No newline at end of file diff --git a/plugins/kapt3/testData/converter/kt18791.txt b/plugins/kapt3/testData/converter/kt18791.txt new file mode 100644 index 00000000000..f56e2542bf0 --- /dev/null +++ b/plugins/kapt3/testData/converter/kt18791.txt @@ -0,0 +1,40 @@ +package app; + +@kotlin.Metadata() +@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) +public abstract @interface Bind { + + public abstract int id(); +} + +//////////////////// + +package app; + +@kotlin.Metadata() +public final class MyActivity { + + @Bind(id = lib.R.id.textView) + public final void foo() { + } + + @Bind(id = lib.R.id.textView) + public final void foo2() { + } + + @Bind(id = app.R.layout.mainActivity) + public final void foo3() { + } + + @Bind(id = app.R.layout.mainActivity) + public final void foo4() { + } + + @Bind(id = 200) + public final void notResource() { + } + + public MyActivity() { + super(); + } +}