Making LightAnnotation work without clsDelegate (KT-20924, KT-22883)

This commit is contained in:
Nicolay Mitropolsky
2018-03-31 21:32:21 +03:00
committed by xiexed
parent 03e46ce0ca
commit eea66bea73
14 changed files with 772 additions and 854 deletions
@@ -16,12 +16,10 @@
package org.jetbrains.kotlin.idea
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiConstantEvaluationHelper
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiExpression
import com.intellij.psi.impl.ConstantExpressionEvaluator
import org.jetbrains.kotlin.asJava.elements.KtLightAnnotationForSourceEntry
import org.jetbrains.kotlin.asJava.elements.KtLightElementBase
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.psi.KtExpression
@@ -36,8 +34,7 @@ class KotlinLightConstantExpressionEvaluator : ConstantExpressionEvaluator {
return if (constantValue is ArrayValue) {
val items = constantValue.value.map { evalConstantValue(it) }
items.singleOrNull() ?: items
}
else constantValue.value
} else constantValue.value
}
override fun computeConstantExpression(expression: PsiElement, throwExceptionOnOverflow: Boolean): Any? {
@@ -45,32 +42,23 @@ class KotlinLightConstantExpressionEvaluator : ConstantExpressionEvaluator {
}
override fun computeExpression(
expression: PsiElement,
throwExceptionOnOverflow: Boolean,
auxEvaluator: PsiConstantEvaluationHelper.AuxEvaluator?
expression: PsiElement,
throwExceptionOnOverflow: Boolean,
auxEvaluator: PsiConstantEvaluationHelper.AuxEvaluator?
): Any? {
if (expression !is KtLightAnnotationForSourceEntry.LightExpressionValue<*>) return null
val expressionToCompute = expression.originalExpression ?: return null
return when (expressionToCompute) {
is KtExpression -> {
val resolutionFacade = expressionToCompute.getResolutionFacade()
val evaluator = FrontendConstantExpressionEvaluator(
resolutionFacade.moduleDescriptor, expressionToCompute.languageVersionSettings
)
val evaluatorTrace = DelegatingBindingTrace(resolutionFacade.analyze(expressionToCompute), "Evaluating annotation argument")
val constant = evaluator.evaluateExpression(expressionToCompute, evaluatorTrace) ?: return null
if (constant.isError) return null
evalConstantValue(constant.toConstantValue(TypeUtils.NO_EXPECTED_TYPE))
}
is PsiExpression -> {
JavaPsiFacade.getInstance(expressionToCompute.project)
.constantEvaluationHelper
.computeExpression(expressionToCompute, throwExceptionOnOverflow, auxEvaluator)
}
else -> null
val expressionToCompute = when (expression) {
is KtLightElementBase -> expression.kotlinOrigin as? KtExpression ?: return null
else -> return null
}
val resolutionFacade = expressionToCompute.getResolutionFacade()
val evaluator = FrontendConstantExpressionEvaluator(
resolutionFacade.moduleDescriptor, expressionToCompute.languageVersionSettings
)
val evaluatorTrace = DelegatingBindingTrace(resolutionFacade.analyze(expressionToCompute), "Evaluating annotation argument")
val constant = evaluator.evaluateExpression(expressionToCompute, evaluatorTrace) ?: return null
if (constant.isError) return null
return evalConstantValue(constant.toConstantValue(TypeUtils.NO_EXPECTED_TYPE))
}
}
@@ -17,8 +17,12 @@
package org.jetbrains.kotlin.asJava
import com.intellij.openapi.application.WriteAction
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProviderImpl
import com.intellij.psi.*
import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.testFramework.LightProjectDescriptor
import junit.framework.TestCase
import org.jetbrains.kotlin.asJava.elements.KtLightAnnotationForSourceEntry
@@ -78,8 +82,9 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
val annotation = myFixture.findClass("AnnotatedClass").methods.first { it.name == "bar" }.parameterList.parameters.single()
.expectAnnotations(2).single { it.qualifiedName == "Qualifier" }
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiExpression
assertTextRangeAndValue("\"foo\"", "foo", annotationAttributeVal)
TestCase.assertEquals(PsiType.getJavaLangString(psiManager, GlobalSearchScope.projectScope(project)), annotationAttributeVal.type)
}
fun testAnnotationsInAnnotationsDeclarations() {
@@ -102,6 +107,107 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
assertTextAndRange("InnerAnnotation()", annotationAttributeVal)
}
fun testConstants() {
myFixture.addClass(
"""
public @interface StringAnnotation {
String value();
}
""".trimIndent()
)
myFixture.addClass(
"""
public class Constants {
public static final String MY_CONSTANT = "67";
}
""".trimIndent()
)
myFixture.configureByText(
"AnnotatedClass.kt", """
@StringAnnotation(Constants.MY_CONSTANT)
open class AnnotatedClass
""".trimIndent()
)
myFixture.testHighlighting("AnnotatedClass.kt")
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiLiteral
assertTextAndRange("Constants.MY_CONSTANT", annotationAttributeVal)
TestCase.assertEquals("67", annotationAttributeVal.value)
TestCase.assertEquals(
PsiType.getJavaLangString(psiManager, GlobalSearchScope.projectScope(project)),
(annotationAttributeVal as PsiExpression).type
)
}
fun testLiteralReplace() {
myFixture.addClass(
"""
public @interface StringAnnotation {
String value();
}
""".trimIndent()
)
myFixture.configureByText(
"AnnotatedClass.kt", """
@StringAnnotation("oldValue")
open class AnnotatedClass
""".trimIndent()
)
myFixture.testHighlighting("AnnotatedClass.kt")
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiLiteral
assertTextAndRange("\"oldValue\"", annotationAttributeVal)
TestCase.assertEquals("oldValue", annotationAttributeVal.value)
runWriteAction {
CommandProcessor.getInstance().runUndoTransparentAction {
annotationAttributeVal.replace(
JavaPsiFacade.getElementFactory(project).createExpressionFromText("\"newValue\"", annotationAttributeVal)
)
}
}
myFixture.checkResult(
"""
@StringAnnotation("newValue")
open class AnnotatedClass
""".trimIndent()
)
}
fun testReferences() {
myFixture.addClass(
"""
public @interface StringAnnotation {
String value();
}
""".trimIndent()
)
myFixture.configureByText(
"AnnotatedClass.kt", """
@StringAnnotation("someText")
open class AnnotatedClass
""".trimIndent()
)
myFixture.testHighlighting("AnnotatedClass.kt")
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiLiteral
assertTextAndRange("\"someText\"", annotationAttributeVal)
TestCase.assertTrue(
"String literal references should be available via light literal",
annotationAttributeVal.references.any {
/* FileReferences are injected in every string, so we use them as indicator that KtElement references are available there */
it is FileReference
})
}
fun testAnnotationsInAnnotationsArrayDeclarations() {
myFixture.addClass("""
public @interface OuterAnnotation {
@@ -241,6 +347,10 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
val annotation = myFixture.findClass("AnnotatedClass").expectAnnotations(1).single()
val annotationAttributeVal = annotation.findAttributeValue("anno2") as PsiArrayInitializerMemberValue
annotationAttributeVal.parent.assertInstanceOf<PsiNameValuePair>().let { pair ->
TestCase.assertEquals("anno2", pair.name)
}
assertTextAndRange("arrayOf(Anno2(1), Anno2(2))", annotationAttributeVal)
annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
assertTextAndRange("Anno2(1)", innerAnnotationAttributeVal)
@@ -248,6 +358,7 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
innerAnnotationAttributeVal as PsiAnnotation
val value = innerAnnotationAttributeVal.findAttributeValue("v").assertInstanceOf<PsiLiteral>()
assertTextAndRange("1", value)
TestCase.assertEquals(PsiType.INT, value.assertInstanceOf<PsiExpression>().type)
}
@@ -278,7 +389,7 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
val annotations = myFixture.findClass("MyAnnotated").expectAnnotations(1)
annotations[0].let { annotation ->
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
assertTextAndRange("(Inner())", annotationAttributeVal)
assertTextAndRange("Inner()", annotationAttributeVal)
annotationAttributeVal as PsiArrayInitializerMemberValue
annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
assertTextAndRange("Inner()", innerAnnotationAttributeVal)
@@ -423,7 +534,7 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
}
annotations[1].let { annotation ->
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
assertTextAndRange("(\"1\")", annotationAttributeVal)
assertTextAndRange("\"1\"", annotationAttributeVal)
annotationAttributeVal as PsiArrayInitializerMemberValue
annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
assertTextAndRange("\"1\"", innerAnnotationAttributeVal)
@@ -483,7 +594,7 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
val annotation = annotations.first()
assertTextAndRange("", annotation.findAttributeValue("i")!!)
assertTextAndRange("true", annotation.findAttributeValue("i")!!)
}
fun testMissingDefault() {
@@ -17,8 +17,12 @@
package org.jetbrains.kotlin.asJava
import com.intellij.openapi.application.WriteAction
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProviderImpl
import com.intellij.psi.*
import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.testFramework.LightProjectDescriptor
import junit.framework.TestCase
import org.jetbrains.kotlin.asJava.elements.KtLightAnnotationForSourceEntry
@@ -78,9 +82,10 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
val annotation = myFixture.findClass("AnnotatedClass").methods.first { it.name == "bar" }.parameterList.parameters.single()
.expectAnnotations(2).single { it.qualifiedName == "Qualifier" }
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiExpression
TestCase.assertTrue(annotationAttributeVal.isPhysical)
assertTextRangeAndValue("\"foo\"", "foo", annotationAttributeVal)
TestCase.assertEquals(PsiType.getJavaLangString(psiManager, GlobalSearchScope.projectScope(project)), annotationAttributeVal.type)
}
fun testAnnotationsInAnnotationsDeclarations() {
@@ -103,6 +108,107 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
assertTextAndRange("InnerAnnotation()", annotationAttributeVal)
}
fun testConstants() {
myFixture.addClass(
"""
public @interface StringAnnotation {
String value();
}
""".trimIndent()
)
myFixture.addClass(
"""
public class Constants {
public static final String MY_CONSTANT = "67";
}
""".trimIndent()
)
myFixture.configureByText(
"AnnotatedClass.kt", """
@StringAnnotation(Constants.MY_CONSTANT)
open class AnnotatedClass
""".trimIndent()
)
myFixture.testHighlighting("AnnotatedClass.kt")
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiLiteral
assertTextAndRange("Constants.MY_CONSTANT", annotationAttributeVal)
TestCase.assertEquals("67", annotationAttributeVal.value)
TestCase.assertEquals(
PsiType.getJavaLangString(psiManager, GlobalSearchScope.projectScope(project)),
(annotationAttributeVal as PsiExpression).type
)
}
fun testLiteralReplace() {
myFixture.addClass(
"""
public @interface StringAnnotation {
String value();
}
""".trimIndent()
)
myFixture.configureByText(
"AnnotatedClass.kt", """
@StringAnnotation("oldValue")
open class AnnotatedClass
""".trimIndent()
)
myFixture.testHighlighting("AnnotatedClass.kt")
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiLiteral
assertTextAndRange("\"oldValue\"", annotationAttributeVal)
TestCase.assertEquals("oldValue", annotationAttributeVal.value)
runWriteAction {
CommandProcessor.getInstance().runUndoTransparentAction {
annotationAttributeVal.replace(
JavaPsiFacade.getElementFactory(project).createExpressionFromText("\"newValue\"", annotationAttributeVal)
)
}
}
myFixture.checkResult(
"""
@StringAnnotation("newValue")
open class AnnotatedClass
""".trimIndent()
)
}
fun testReferences() {
myFixture.addClass(
"""
public @interface StringAnnotation {
String value();
}
""".trimIndent()
)
myFixture.configureByText(
"AnnotatedClass.kt", """
@StringAnnotation("someText")
open class AnnotatedClass
""".trimIndent()
)
myFixture.testHighlighting("AnnotatedClass.kt")
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiLiteral
assertTextAndRange("\"someText\"", annotationAttributeVal)
TestCase.assertTrue(
"String literal references should be available via light literal",
annotationAttributeVal.references.any {
/* FileReferences are injected in every string, so we use them as indicator that KtElement references are available there */
it is FileReference
})
}
fun testAnnotationsInAnnotationsArrayDeclarations() {
myFixture.addClass("""
public @interface OuterAnnotation {
@@ -242,6 +348,10 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
val annotation = myFixture.findClass("AnnotatedClass").expectAnnotations(1).single()
val annotationAttributeVal = annotation.findAttributeValue("anno2") as PsiArrayInitializerMemberValue
annotationAttributeVal.parent.assertInstanceOf<PsiNameValuePair>().let { pair ->
TestCase.assertEquals("anno2", pair.name)
}
assertTextAndRange("arrayOf(Anno2(1), Anno2(2))", annotationAttributeVal)
annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
assertTextAndRange("Anno2(1)", innerAnnotationAttributeVal)
@@ -249,6 +359,7 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
innerAnnotationAttributeVal as PsiAnnotation
val value = innerAnnotationAttributeVal.findAttributeValue("v").assertInstanceOf<PsiLiteral>()
assertTextAndRange("1", value)
TestCase.assertEquals(PsiType.INT, value.assertInstanceOf<PsiExpression>().type)
}
@@ -279,7 +390,7 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
val annotations = myFixture.findClass("MyAnnotated").expectAnnotations(1)
annotations[0].let { annotation ->
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
assertTextAndRange("(Inner())", annotationAttributeVal)
assertTextAndRange("Inner()", annotationAttributeVal)
annotationAttributeVal as PsiArrayInitializerMemberValue
annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
assertTextAndRange("Inner()", innerAnnotationAttributeVal)
@@ -424,7 +535,7 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
}
annotations[1].let { annotation ->
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
assertTextAndRange("(\"1\")", annotationAttributeVal)
assertTextAndRange("\"1\"", annotationAttributeVal)
annotationAttributeVal as PsiArrayInitializerMemberValue
annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
assertTextAndRange("\"1\"", innerAnnotationAttributeVal)
@@ -484,7 +595,7 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
val annotation = annotations.first()
assertTextAndRange("", annotation.findAttributeValue("i")!!)
assertTextAndRange("true", annotation.findAttributeValue("i")!!)
}
fun testMissingDefault() {