[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 package org.jetbrains.kotlin.light.classes.symbol.annotations
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.util.NlsSafe
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.psi.impl.light.LightTypeElement
import org.jetbrains.kotlin.asJava.elements.KtLightElementBase import org.jetbrains.kotlin.asJava.elements.KtLightElementBase
import org.jetbrains.kotlin.light.classes.symbol.toArrayIfNotEmptyOrDefault import org.jetbrains.kotlin.light.classes.symbol.toArrayIfNotEmptyOrDefault
import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtElement
@@ -63,6 +63,20 @@ internal class SymbolPsiExpression(
override fun getText(): String = psiExpression.text 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( internal class SymbolPsiReference(
override val kotlinOrigin: KtElement?, override val kotlinOrigin: KtElement?,
lightParent: PsiElement, lightParent: PsiElement,
@@ -5,7 +5,9 @@
package org.jetbrains.kotlin.light.classes.symbol.annotations package org.jetbrains.kotlin.light.classes.symbol.annotations
import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiAnnotationParameterList import com.intellij.psi.PsiAnnotationParameterList
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.analysis.api.annotations.KtNamedAnnotationValue import org.jetbrains.kotlin.analysis.api.annotations.KtNamedAnnotationValue
import org.jetbrains.kotlin.asJava.classes.lazyPub import org.jetbrains.kotlin.asJava.classes.lazyPub
import org.jetbrains.kotlin.asJava.elements.KtLightElementBase import org.jetbrains.kotlin.asJava.elements.KtLightElementBase
@@ -15,6 +17,14 @@ internal sealed class SymbolLightAbstractAnnotationParameterList(
parent: SymbolLightAbstractAnnotation, parent: SymbolLightAbstractAnnotation,
) : KtLightElementBase(parent), PsiAnnotationParameterList { ) : KtLightElementBase(parent), PsiAnnotationParameterList {
override val kotlinOrigin: KtElement? get() = (parent as SymbolLightAbstractAnnotation).kotlinOrigin?.valueArgumentList 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") @Suppress("NOTHING_TO_INLINE")
@@ -38,4 +38,12 @@ internal class SymbolNameValuePairForAnnotationArgument(
override fun getLiteralValue(): String? = (value as? PsiLiteralExpression)?.value?.toString() override fun getLiteralValue(): String? = (value as? PsiLiteralExpression)?.value?.toString()
override fun getName(): String = constantValue.name.asString() 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) return SymbolPsiReference(sourcePsi, parent, psiReference)
} }
private fun KtKClassAnnotationValue.toAnnotationMemberValue(parent: PsiElement): PsiExpression? { private fun KtKClassAnnotationValue.toAnnotationMemberValue(parent: PsiElement): SymbolPsiClassObjectAccessExpression? {
val typeString = when (this) { val typeString = when (this) {
is KtKClassAnnotationValue.KtNonLocalKClassAnnotationValue -> classId.asSingleFqName().asString() is KtKClassAnnotationValue.KtNonLocalKClassAnnotationValue -> classId.asSingleFqName().asString()
is KtKClassAnnotationValue.KtLocalKClassAnnotationValue -> null is KtKClassAnnotationValue.KtLocalKClassAnnotationValue -> null
is KtKClassAnnotationValue.KtErrorClassAnnotationValue -> unresolvedQualifierName is KtKClassAnnotationValue.KtErrorClassAnnotationValue -> unresolvedQualifierName
} ?: return null } ?: return null
val canonicalText = psiType( val psiType = psiType(
kotlinFqName = typeString, kotlinFqName = typeString,
context = parent, context = parent,
boxPrimitiveType = false, /* TODO value.arrayNestedness > 0*/ boxPrimitiveType = false, /* TODO value.arrayNestedness > 0*/
).let(TypeConversionUtil::erasure).getCanonicalText(false) ).let(TypeConversionUtil::erasure)
return parent.project.withElementFactorySafe { return SymbolPsiClassObjectAccessExpression(sourcePsi, parent, psiType)
createExpressionFromText("$canonicalText.class", parent)
}
} }
private fun KtConstantValue.asStringForPsiExpression(): String = 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) { private fun checkDeclarationParent(declaration: PsiElement) {
val expectedParent = declarationStack.lastOrNull() ?: return val expectedParent = declarationStack.lastOrNull() ?: return
val parent = declaration.parent val parent = declaration.parent
@@ -217,6 +241,10 @@ open class AbstractSymbolLightClassesParentingTestBase(
assertions.assertTrue(modifierList.annotations.any { it == annotation }) { assertions.assertTrue(modifierList.annotations.any { it == annotation }) {
"$annotation is not found in ${modifierList.annotations}" "$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 ktFile = ktFiles.first()
val lightClass = findLightClass(fqName, ktFile.project) ?: return 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 project: Project
): String ): String
private inline fun ignoreExceptionIfIgnoreDirectivePresent(module: TestModule, action: () -> Unit) { protected fun ignoreExceptionIfIgnoreDirectivePresent(module: TestModule, action: () -> Unit) {
try { try {
action() action()
} catch (e: Throwable) { } catch (e: Throwable) {