[FIR LC] KT-58330 Introduce SymbolPsiClassObjectAccessExpression

A PSI expression created with `createExpressionFromText` has dummy
parent, which might break some inspections (for example, it breaks
`JamAttributeElement` class in Spring Framework Plugin in intellij)

Modify `AbstractSymbolLightClassesParentingTestBase` to check values of
annotations in light classes; implement `accept` in some of them

^KT-58330 Fixed

Merge-request: KT-MR-9903
Merged-by: Roman Golyshev <roman.golyshev@jetbrains.com>
This commit is contained in:
Roman Golyshev
2023-05-03 15:21:18 +00:00
committed by Space Team
parent 957d8ee3d2
commit 0dbf218f0b
7 changed files with 69 additions and 9 deletions
@@ -6,8 +6,8 @@
package org.jetbrains.kotlin.light.classes.symbol.annotations
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.NlsSafe
import com.intellij.psi.*
import com.intellij.psi.impl.light.LightTypeElement
import org.jetbrains.kotlin.asJava.elements.KtLightElementBase
import org.jetbrains.kotlin.light.classes.symbol.toArrayIfNotEmptyOrDefault
import org.jetbrains.kotlin.psi.KtElement
@@ -63,6 +63,20 @@ internal class SymbolPsiExpression(
override fun getText(): String = psiExpression.text
}
internal class SymbolPsiClassObjectAccessExpression(
override val kotlinOrigin: KtElement?,
lightParent: PsiElement,
private val psiType: PsiType,
) : SymbolPsiAnnotationMemberValue(kotlinOrigin, lightParent), PsiClassObjectAccessExpression {
override fun getType(): PsiType = psiType
override fun getOperand(): PsiTypeElement = LightTypeElementWithParent(this, type)
override fun getText(): String = type.getCanonicalText(false) + ".class"
}
private class LightTypeElementWithParent(private val lightParent: PsiElement, type: PsiType) : LightTypeElement(lightParent.manager, type) {
override fun getParent(): PsiElement = lightParent
}
internal class SymbolPsiReference(
override val kotlinOrigin: KtElement?,
lightParent: PsiElement,
@@ -5,7 +5,9 @@
package org.jetbrains.kotlin.light.classes.symbol.annotations
import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiAnnotationParameterList
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.analysis.api.annotations.KtNamedAnnotationValue
import org.jetbrains.kotlin.asJava.classes.lazyPub
import org.jetbrains.kotlin.asJava.elements.KtLightElementBase
@@ -15,6 +17,14 @@ internal sealed class SymbolLightAbstractAnnotationParameterList(
parent: SymbolLightAbstractAnnotation,
) : KtLightElementBase(parent), PsiAnnotationParameterList {
override val kotlinOrigin: KtElement? get() = (parent as SymbolLightAbstractAnnotation).kotlinOrigin?.valueArgumentList
override fun accept(visitor: PsiElementVisitor) {
if (visitor is JavaElementVisitor) {
visitor.visitAnnotationParameterList(this)
} else {
visitor.visitElement(this)
}
}
}
@Suppress("NOTHING_TO_INLINE")
@@ -38,4 +38,12 @@ internal class SymbolNameValuePairForAnnotationArgument(
override fun getLiteralValue(): String? = (value as? PsiLiteralExpression)?.value?.toString()
override fun getName(): String = constantValue.name.asString()
override fun accept(visitor: PsiElementVisitor) {
if (visitor is JavaElementVisitor) {
visitor.visitNameValuePair(this)
} else {
visitor.visitElement(this)
}
}
}
@@ -205,22 +205,20 @@ private fun KtEnumEntryAnnotationValue.asPsiReferenceExpression(parent: PsiEleme
return SymbolPsiReference(sourcePsi, parent, psiReference)
}
private fun KtKClassAnnotationValue.toAnnotationMemberValue(parent: PsiElement): PsiExpression? {
private fun KtKClassAnnotationValue.toAnnotationMemberValue(parent: PsiElement): SymbolPsiClassObjectAccessExpression? {
val typeString = when (this) {
is KtKClassAnnotationValue.KtNonLocalKClassAnnotationValue -> classId.asSingleFqName().asString()
is KtKClassAnnotationValue.KtLocalKClassAnnotationValue -> null
is KtKClassAnnotationValue.KtErrorClassAnnotationValue -> unresolvedQualifierName
} ?: return null
val canonicalText = psiType(
val psiType = psiType(
kotlinFqName = typeString,
context = parent,
boxPrimitiveType = false, /* TODO value.arrayNestedness > 0*/
).let(TypeConversionUtil::erasure).getCanonicalText(false)
).let(TypeConversionUtil::erasure)
return parent.project.withElementFactorySafe {
createExpressionFromText("$canonicalText.class", parent)
}
return SymbolPsiClassObjectAccessExpression(sourcePsi, parent, psiType)
}
private fun KtConstantValue.asStringForPsiExpression(): String =
@@ -165,6 +165,30 @@ open class AbstractSymbolLightClassesParentingTestBase(
}
}
override fun visitNameValuePair(pair: PsiNameValuePair) {
checkParentAndVisitChildren(pair) {
value?.let(::checkAnnotationMemberValue)
}
}
override fun visitAnnotationParameterList(list: PsiAnnotationParameterList) {
checkParentAndVisitChildren(list) { visitor ->
attributes.forEach { it.accept(visitor) }
}
}
private fun checkAnnotationMemberValue(memberValue: PsiAnnotationMemberValue) {
checkParentAndVisitChildren(memberValue) {
if (this is PsiClassObjectAccessExpression) {
checkDeclarationParent(this.operand)
}
if (this is PsiArrayInitializerMemberValue) {
this.initializers.forEach(::checkAnnotationMemberValue)
}
}
}
private fun checkDeclarationParent(declaration: PsiElement) {
val expectedParent = declarationStack.lastOrNull() ?: return
val parent = declaration.parent
@@ -217,6 +241,10 @@ open class AbstractSymbolLightClassesParentingTestBase(
assertions.assertTrue(modifierList.annotations.any { it == annotation }) {
"$annotation is not found in ${modifierList.annotations}"
}
checkParentAndVisitChildren(annotation, notCheckItself = true) { visitor ->
parameterList.accept(visitor)
}
}
}
}
@@ -23,6 +23,8 @@ abstract class AbstractSymbolLightClassesParentingTestByFqName(
val ktFile = ktFiles.first()
val lightClass = findLightClass(fqName, ktFile.project) ?: return
lightClass.accept(createLightElementsVisitor(module.directives, testServices.assertions))
ignoreExceptionIfIgnoreDirectivePresent(module) {
lightClass.accept(createLightElementsVisitor(module.directives, testServices.assertions))
}
}
}
@@ -96,7 +96,7 @@ abstract class AbstractSymbolLightClassesTestBase(
project: Project
): String
private inline fun ignoreExceptionIfIgnoreDirectivePresent(module: TestModule, action: () -> Unit) {
protected fun ignoreExceptionIfIgnoreDirectivePresent(module: TestModule, action: () -> Unit) {
try {
action()
} catch (e: Throwable) {