[kotlin, uast] Support for type annotations in uast toPsiType converter (KTIJ-17990)

GitOrigin-RevId: e8f294316c5d97cf6a2209be5bc7803da49d0732
This commit is contained in:
Nicolay Mitropolsky
2021-07-13 18:13:46 +00:00
committed by intellij-monorepo-bot
parent f3b92ea371
commit 5c5ec4a543
5 changed files with 122 additions and 4 deletions
@@ -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 "<ERROR>"
val valueArguments = annotation.allValueArguments
val valueesList = SmartList<String>().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<String?, String?> {
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<PsiAnnotation>()
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)
@@ -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
}
@@ -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++
@@ -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
}
@@ -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<java.lang.String>) : void {...}]
UParameter (name = list) [@org.jetbrains.annotations.NotNull var list: java.util.List<java.lang.String>]
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<? extends String>
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]