From 5c5ec4a543c255a9c92870bd2dca576834506bf3 Mon Sep 17 00:00:00 2001 From: Nicolay Mitropolsky Date: Tue, 13 Jul 2021 18:13:46 +0000 Subject: [PATCH] [kotlin, uast] Support for type annotations in uast `toPsiType` converter (KTIJ-17990) GitOrigin-RevId: e8f294316c5d97cf6a2209be5bc7803da49d0732 --- .../internal/kotlinInternalUastUtils.kt | 62 ++++++++++++++++++- .../uast-kotlin/test/KotlinUastTypesTest.kt | 12 ++++ .../uast/test/common/kotlin/TypesTestBase.kt | 18 +++++- .../uast-kotlin/testData/AnnotatedTypes.kt | 8 +++ .../testData/AnnotatedTypes.types.txt | 26 ++++++++ 5 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 plugins/kotlin/uast/uast-kotlin/testData/AnnotatedTypes.kt create mode 100644 plugins/kotlin/uast/uast-kotlin/testData/AnnotatedTypes.types.txt diff --git a/plugins/kotlin/uast/uast-kotlin/src/org/jetbrains/uast/kotlin/internal/kotlinInternalUastUtils.kt b/plugins/kotlin/uast/uast-kotlin/src/org/jetbrains/uast/kotlin/internal/kotlinInternalUastUtils.kt index aae6cfbf9e73..8aa9954f68cd 100644 --- a/plugins/kotlin/uast/uast-kotlin/src/org/jetbrains/uast/kotlin/internal/kotlinInternalUastUtils.kt +++ b/plugins/kotlin/uast/uast-kotlin/src/org/jetbrains/uast/kotlin/internal/kotlinInternalUastUtils.kt @@ -2,6 +2,7 @@ package org.jetbrains.uast.kotlin +import com.intellij.openapi.diagnostic.Attachment import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.psi.* @@ -10,12 +11,15 @@ import com.intellij.psi.impl.compiled.ClsTypeElementImpl import com.intellij.psi.impl.compiled.SignatureParsing import com.intellij.psi.impl.compiled.StubBuildingVisitor import com.intellij.psi.util.PsiTypesUtil +import com.intellij.util.SmartList import org.jetbrains.kotlin.asJava.* import org.jetbrains.kotlin.asJava.classes.KtLightClass import org.jetbrains.kotlin.asJava.elements.FakeFileForLightClass import org.jetbrains.kotlin.builtins.isBuiltinFunctionalTypeOrSubtype import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.annotations.AnnotationArgumentVisitor +import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor import org.jetbrains.kotlin.descriptors.synthetic.SyntheticMemberDescriptor import org.jetbrains.kotlin.lexer.KtTokens @@ -42,8 +46,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.model.TypeVariableTypeConstr import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.tower.NewResolvedCallImpl -import org.jetbrains.kotlin.resolve.constants.IntegerLiteralTypeConstructor -import org.jetbrains.kotlin.resolve.constants.IntegerValueTypeConstructor +import org.jetbrains.kotlin.resolve.constants.* import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.sam.SamConstructorDescriptor import org.jetbrains.kotlin.resolve.source.getPsi @@ -168,7 +171,7 @@ internal fun KotlinType.toPsiType(lightDeclaration: PsiModifierListOwner?, conte } } } - if (psiType != null) return psiType + if (psiType != null) return psiType.annotate(buildAnnotationProvider(this, lightDeclaration ?: context)) } if (this.containsLocalTypes()) return UastErrorType @@ -205,6 +208,59 @@ internal fun KotlinType.toPsiType(lightDeclaration: PsiModifierListOwner?, conte return ClsTypeElementImpl(parent, typeText, '\u0000').type } +private fun renderAnnotation(annotation: AnnotationDescriptor): String { + val fqn = annotation.fqName?.asString() ?: return "" + val valueArguments = annotation.allValueArguments + val valueesList = SmartList().apply { + for ((k, v) in valueArguments.entries) { + add("${k.identifier} = ${renderConstantValue(v)}") + } + } + return "@$fqn(${valueesList.joinToString(", ")})" +} + +private fun renderConstantValue(value: ConstantValue<*>?): String? = value?.accept(object : AnnotationArgumentVisitor { + override fun visitLongValue(value: LongValue, data: String?): String = value.value.toString() + override fun visitIntValue(value: IntValue, data: String?): String = value.value.toString() + override fun visitErrorValue(value: ErrorValue?, data: String?): String? = null + override fun visitShortValue(value: ShortValue, data: String?): String = value.value.toString() + override fun visitByteValue(value: ByteValue, data: String?): String = value.value.toString() + override fun visitDoubleValue(value: DoubleValue, data: String?): String = value.value.toString() + override fun visitFloatValue(value: FloatValue, data: String?): String = value.value.toString() + override fun visitBooleanValue(value: BooleanValue, data: String?): String = value.value.toString() + override fun visitCharValue(value: CharValue, data: String?): String = "'${value.value}'" + override fun visitStringValue(value: StringValue, data: String?): String = "\"${value.value}\"" + override fun visitNullValue(value: NullValue?, data: String?): String = "null" + override fun visitEnumValue(value: EnumValue, data: String?): String = + value.value.run { first.asSingleFqName().asString() + "." + second.asString() } + + override fun visitArrayValue(value: ArrayValue, data: String?): String = + value.value.mapNotNull { renderConstantValue(it) }.joinToString(", ", "{", "}") + + override fun visitAnnotationValue(value: AnnotationValue, data: String?): String = renderAnnotation(value.value) + override fun visitKClassValue(value: KClassValue, data: String?): String = value.value.toString() + ".class" + override fun visitUByteValue(value: UByteValue, data: String?): String = value.value.toString() + override fun visitUShortValue(value: UShortValue, data: String?): String = value.value.toString() + override fun visitUIntValue(value: UIntValue, data: String?): String = value.value.toString() + override fun visitULongValue(value: ULongValue, data: String?): String = value.value.toString() +}, null) + +private fun buildAnnotationProvider(ktType: KotlinType, context: PsiElement): TypeAnnotationProvider { + val result = SmartList() + val psiElementFactory = PsiElementFactory.getInstance(context.project) + for (annotation in ktType.annotations) { + val annotationText = renderAnnotation(annotation) + try { + result.add(psiElementFactory.createAnnotationFromText(annotationText, context)) + } catch (e: Exception) { + Logger.getInstance("org.jetbrains.uast.kotlin.KotlinInternalUastUtils") + .error("failed to create annotation from text", e, Attachment("annotationText.txt", annotationText)) + } + } + if (result.isEmpty()) return TypeAnnotationProvider.EMPTY + return TypeAnnotationProvider.Static.create(result.toArray(PsiAnnotation.EMPTY_ARRAY)) +} + internal fun KtTypeReference?.toPsiType(source: UElement, boxed: Boolean = false): PsiType { if (this == null) return UastErrorType return (analyze()[BindingContext.TYPE, this] ?: return UastErrorType).toPsiType(source, this, boxed) diff --git a/plugins/kotlin/uast/uast-kotlin/test/KotlinUastTypesTest.kt b/plugins/kotlin/uast/uast-kotlin/test/KotlinUastTypesTest.kt index 1e41f679e8a3..5b1535561219 100644 --- a/plugins/kotlin/uast/uast-kotlin/test/KotlinUastTypesTest.kt +++ b/plugins/kotlin/uast/uast-kotlin/test/KotlinUastTypesTest.kt @@ -19,4 +19,16 @@ class KotlinUastTypesTest : AbstractKotlinTypesTest() { @Test fun testInferenceInsideUnresolvedConstructor() = doTest("InferenceInsideUnresolvedConstructor") @Test fun testInnerNonFixedTypeVariable() = doTest("InnerNonFixedTypeVariable") + + @Test fun testAnnotatedTypes() = doTest("AnnotatedTypes") +} + + +@Target(AnnotationTarget.TYPE) +annotation class MyAnnotation(val a: Int, val b: String, val c: AnnotationTarget) + +fun foo(list: List<@MyAnnotation(1, "str", AnnotationTarget.TYPE) String>) { + val a = list[2] + val b: @MyAnnotation(2, "boo", AnnotationTarget.FILE) String = "abc" + val c = b } \ No newline at end of file diff --git a/plugins/kotlin/uast/uast-kotlin/test/org/jetbrains/uast/test/common/kotlin/TypesTestBase.kt b/plugins/kotlin/uast/uast-kotlin/test/org/jetbrains/uast/test/common/kotlin/TypesTestBase.kt index 15a82f60fc4b..11f8a7912ad1 100644 --- a/plugins/kotlin/uast/uast-kotlin/test/org/jetbrains/uast/test/common/kotlin/TypesTestBase.kt +++ b/plugins/kotlin/uast/uast-kotlin/test/org/jetbrains/uast/test/common/kotlin/TypesTestBase.kt @@ -2,6 +2,8 @@ package org.jetbrains.uast.test.common.kotlin +import com.intellij.lang.jvm.annotation.JvmAnnotationConstantValue +import com.intellij.lang.jvm.annotation.JvmAnnotationEnumFieldValue import org.jetbrains.uast.UElement import org.jetbrains.uast.UExpression import org.jetbrains.uast.UFile @@ -42,7 +44,21 @@ interface TypesTestBase { builder.append(initialLine) if (node is UExpression) { val value = node.getExpressionType() - value?.let { builder.append(" : ").append(it) } + value?.let { psiType -> + builder.append(" : ") + psiType.annotations.takeIf { it.isNotEmpty() }?.joinTo(builder, ", ", "{", "}") { annotation -> + "@${annotation.qualifiedName}(${ + annotation.attributes.joinToString { attr -> + attr.attributeName + " = " + when (val v = attr.attributeValue) { + is JvmAnnotationConstantValue -> v.constantValue + is JvmAnnotationEnumFieldValue -> v.fieldName + else -> v + } + } + })" + } + builder.append(psiType) + } } builder.appendLine() level++ diff --git a/plugins/kotlin/uast/uast-kotlin/testData/AnnotatedTypes.kt b/plugins/kotlin/uast/uast-kotlin/testData/AnnotatedTypes.kt new file mode 100644 index 000000000000..13f3a9887b87 --- /dev/null +++ b/plugins/kotlin/uast/uast-kotlin/testData/AnnotatedTypes.kt @@ -0,0 +1,8 @@ +@Target(AnnotationTarget.TYPE) +annotation class MyAnnotation(val a: Int, val b: String, val c: AnnotationTarget) + +fun foo(list: List<@MyAnnotation(1, "str", AnnotationTarget.TYPE) String>) { + val a = list[2] + val b: @MyAnnotation(2, "boo", AnnotationTarget.FILE) String = "abc" + val c = b +} \ No newline at end of file diff --git a/plugins/kotlin/uast/uast-kotlin/testData/AnnotatedTypes.types.txt b/plugins/kotlin/uast/uast-kotlin/testData/AnnotatedTypes.types.txt new file mode 100644 index 000000000000..d6f14f838960 --- /dev/null +++ b/plugins/kotlin/uast/uast-kotlin/testData/AnnotatedTypes.types.txt @@ -0,0 +1,26 @@ +UFile (package = ) [public final class AnnotatedTypesKt {...] + UClass (name = AnnotatedTypesKt) [public final class AnnotatedTypesKt {...}] + UMethod (name = foo) [public static final fun foo(@org.jetbrains.annotations.NotNull list: java.util.List) : void {...}] + UParameter (name = list) [@org.jetbrains.annotations.NotNull var list: java.util.List] + UAnnotation (fqName = org.jetbrains.annotations.NotNull) [@org.jetbrains.annotations.NotNull] + UBlockExpression [{...}] : PsiType:void + UDeclarationsExpression [var a: java.lang.String = list[2]] + ULocalVariable (name = a) [var a: java.lang.String = list[2]] + UArrayAccessExpression [list[2]] : {@MyAnnotation(a = 1, b = str, c = TYPE)}PsiType:String + USimpleNameReferenceExpression (identifier = list) [list] : PsiType:List + ULiteralExpression (value = 2) [2] : PsiType:int + UDeclarationsExpression [var b: java.lang.String = "abc"] + ULocalVariable (name = b) [var b: java.lang.String = "abc"] + ULiteralExpression (value = "abc") ["abc"] : PsiType:String + UDeclarationsExpression [var c: java.lang.String = b] + ULocalVariable (name = c) [var c: java.lang.String = b] + USimpleNameReferenceExpression (identifier = b) [b] : {@MyAnnotation(a = 2, b = boo, c = FILE)}PsiType:String + UClass (name = MyAnnotation) [public abstract annotation MyAnnotation {...}] + UAnnotation (fqName = kotlin.annotation.Target) [@kotlin.annotation.Target(allowedTargets = AnnotationTarget.TYPE)] + UNamedExpression (name = allowedTargets) [allowedTargets = AnnotationTarget.TYPE] + UQualifiedReferenceExpression [AnnotationTarget.TYPE] : PsiType:AnnotationTarget + USimpleNameReferenceExpression (identifier = AnnotationTarget) [AnnotationTarget] + USimpleNameReferenceExpression (identifier = TYPE) [TYPE] : PsiType:AnnotationTarget + UAnnotationMethod (name = a) [public abstract fun a() : int = UastEmptyExpression] + UAnnotationMethod (name = b) [public abstract fun b() : java.lang.String = UastEmptyExpression] + UAnnotationMethod (name = c) [public abstract fun c() : kotlin.annotation.AnnotationTarget = UastEmptyExpression]