diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt index fb0443ab2da..e217c028c17 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt @@ -16,7 +16,6 @@ package org.jetbrains.kotlin.asJava.elements -import com.intellij.lang.Language import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.* @@ -53,6 +52,8 @@ abstract class KtLightAbstractAnnotation(parent: PsiElement, computeDelegate: () open fun fqNameMatches(fqName: String): Boolean = qualifiedName == fqName } +private typealias AnnotationValueOrigin = () -> PsiElement? + class KtLightAnnotationForSourceEntry( private val qualifiedName: String, override val kotlinOrigin: KtCallElement, @@ -65,9 +66,9 @@ class KtLightAnnotationForSourceEntry( open inner class LightElementValue( val delegate: D, private val parent: PsiElement, - private val originalExpressionProvider: () -> PsiElement + valueOrigin: AnnotationValueOrigin ) : PsiAnnotationMemberValue, PsiElement by delegate { - val originalExpression: PsiElement by lazyPub { originalExpressionProvider() } + val originalExpression: PsiElement? by lazyPub(valueOrigin) fun getConstantValue(): Any? { val expression = originalExpression as? KtExpression ?: return null @@ -77,12 +78,12 @@ class KtLightAnnotationForSourceEntry( } override fun getReference() = references.singleOrNull() - override fun getReferences() = originalExpression.references - override fun getLanguage(): Language = KotlinLanguage.INSTANCE + override fun getReferences() = originalExpression?.references.orEmpty() + override fun getLanguage() = KotlinLanguage.INSTANCE override fun getNavigationElement() = originalExpression - override fun getTextRange() = originalExpression.textRange ?: TextRange.EMPTY_RANGE + override fun getTextRange() = originalExpression?.textRange ?: TextRange.EMPTY_RANGE override fun getParent() = parent - override fun getText(): String = originalExpression.text + override fun getText() = originalExpression?.text.orEmpty() override fun replace(newElement: PsiElement): PsiElement { val value = (newElement as? PsiLiteral)?.value as? String ?: return this @@ -101,24 +102,23 @@ class KtLightAnnotationForSourceEntry( } } - private fun getMemberValueAsCallArgument(memberValue: PsiElement, callHolder: KtCallElement): PsiElement { - val resolvedCall = callHolder.getResolvedCall()!! + private fun getMemberValueAsCallArgument(memberValue: PsiElement, callHolder: KtCallElement): PsiElement? { + val resolvedCall = callHolder.getResolvedCall() ?: return null val annotationConstructor = resolvedCall.resultingDescriptor val parameterName = memberValue.getNonStrictParentOfType()?.name ?: memberValue.getNonStrictParentOfType()?.name ?: "value" - val parameter = annotationConstructor.valueParameters.singleOrNull { it.name.asString() == parameterName } - ?: error("single parameter '$parameterName' was not found in ${annotationConstructor.valueParameters.map { it.name.asString() }}") - val resolvedArgument = resolvedCall.valueArguments[parameter]!! + val parameter = annotationConstructor.valueParameters.singleOrNull { it.name.asString() == parameterName } ?: return null + val resolvedArgument = resolvedCall.valueArguments[parameter] ?: return null return when (resolvedArgument) { is DefaultValueArgument -> { val psi = parameter.source.getPsi() when (psi) { - is KtParameter -> psi.defaultValue!! - is PsiAnnotationMethod -> psi.defaultValue!! - else -> throw UnsupportedOperationException("$psi of type ${psi?.javaClass}") + is KtParameter -> psi.defaultValue + is PsiAnnotationMethod -> psi.defaultValue + else -> error("$psi of type ${psi?.javaClass}") } } @@ -135,7 +135,7 @@ class KtLightAnnotationForSourceEntry( it.asKtCall() ?: it } - else -> throw IllegalArgumentException("resolvedArgument: {$resolvedArgument} cant be processed") + else -> error("resolvedArgument: ${resolvedArgument.javaClass} cant be processed") } } @@ -149,24 +149,24 @@ class KtLightAnnotationForSourceEntry( open inner class LightExpressionValue( delegate: D, parent: PsiElement, - originalExpressionProvider: () -> PsiElement - ) : LightElementValue(delegate, parent, originalExpressionProvider), PsiExpression { + valueOrigin: AnnotationValueOrigin + ) : LightElementValue(delegate, parent, valueOrigin), PsiExpression { override fun getType(): PsiType? = delegate.type } inner class LightStringLiteral( delegate: PsiLiteralExpression, parent: PsiElement, - originalExpressionProvider: () -> PsiElement - ) : LightExpressionValue(delegate, parent, originalExpressionProvider), PsiLiteralExpression { + valueOrigin: AnnotationValueOrigin + ) : LightExpressionValue(delegate, parent, valueOrigin), PsiLiteralExpression { override fun getValue() = delegate.value } inner class LightClassLiteral( delegate: PsiClassObjectAccessExpression, parent: PsiElement, - originalExpressionProvider: () -> PsiElement - ) : LightExpressionValue(delegate, parent, originalExpressionProvider), PsiClassObjectAccessExpression { + valueOrigin: AnnotationValueOrigin + ) : LightExpressionValue(delegate, parent, valueOrigin), PsiClassObjectAccessExpression { override fun getType() = delegate.type override fun getOperand(): PsiTypeElement = delegate.operand } @@ -174,8 +174,8 @@ class KtLightAnnotationForSourceEntry( inner class LightArrayInitializerValue( delegate: PsiArrayInitializerMemberValue, parent: PsiElement, - originalExpressionProvider: () -> PsiElement - ) : LightElementValue(delegate, parent, originalExpressionProvider), PsiArrayInitializerMemberValue { + valueOrigin: AnnotationValueOrigin + ) : LightElementValue(delegate, parent, valueOrigin), PsiArrayInitializerMemberValue { private val _initializers by lazyPub { delegate.initializers.mapIndexed { i, it -> wrapAnnotationValue(it, this, { @@ -187,20 +187,20 @@ class KtLightAnnotationForSourceEntry( override fun getInitializers() = _initializers } - private fun wrapAnnotationValue(value: PsiAnnotationMemberValue, parent: PsiElement, ktOriginForElement: () -> PsiElement): PsiAnnotationMemberValue { + private fun wrapAnnotationValue(value: PsiAnnotationMemberValue, parent: PsiElement, ktOrigin: AnnotationValueOrigin): PsiAnnotationMemberValue { return when { - value is PsiLiteralExpression && value.value is String -> LightStringLiteral(value, parent, ktOriginForElement) - value is PsiClassObjectAccessExpression -> LightClassLiteral(value, parent, ktOriginForElement) - value is PsiExpression -> LightExpressionValue(value, parent, ktOriginForElement) - value is PsiArrayInitializerMemberValue -> LightArrayInitializerValue(value, parent, ktOriginForElement) + value is PsiLiteralExpression && value.value is String -> LightStringLiteral(value, parent, ktOrigin) + value is PsiClassObjectAccessExpression -> LightClassLiteral(value, parent, ktOrigin) + value is PsiExpression -> LightExpressionValue(value, parent, ktOrigin) + value is PsiArrayInitializerMemberValue -> LightArrayInitializerValue(value, parent, ktOrigin) value is PsiAnnotation -> KtLightAnnotationForSourceEntry( value.qualifiedName!!, - ktOriginForElement().let { - it.asKtCall() ?: throw UnsupportedOperationException("cant convert $it to KtCallElement") + ktOrigin().let { + it?.asKtCall() ?: throw UnsupportedOperationException("cant convert $it to KtCallElement") }, parent, { value } ) - else -> LightElementValue(value, parent, ktOriginForElement) + else -> LightElementValue(value, parent, ktOrigin) } } diff --git a/idea/tests/org/jetbrains/kotlin/asJava/KtLightAnnotationTest.kt b/idea/tests/org/jetbrains/kotlin/asJava/KtLightAnnotationTest.kt index d749074e522..d5958337b0b 100644 --- a/idea/tests/org/jetbrains/kotlin/asJava/KtLightAnnotationTest.kt +++ b/idea/tests/org/jetbrains/kotlin/asJava/KtLightAnnotationTest.kt @@ -287,6 +287,61 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() { } + fun testWrongNamesPassed() { + myFixture.configureByText("AnnotatedClass.kt", """ + annotation class Anno1(val i:Int , val j: Int) + + @Anno1(k = 3, l = 5) + class AnnotatedClass + """.trimIndent()) + + val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1) + val annotation = annotations.first() + TestCase.assertNull(annotation.findAttributeValue("k")) + TestCase.assertNull(annotation.findAttributeValue("l")) + TestCase.assertNull(annotation.findAttributeValue("i")) + TestCase.assertNull(annotation.findAttributeValue("j")) + } + + fun testWrongValuesPassed() { + myFixture.configureByText("AnnotatedClass.kt", """ + annotation class Anno1(val i: Int , val j: Int) + + @Anno1(i = true, j = false) + class AnnotatedClass + """.trimIndent()) + + val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1) + val annotation = annotations.first() + assertTextAndRange("true", annotation.findAttributeValue("i")!!) + assertTextAndRange("false", annotation.findAttributeValue("j")!!) + } + + fun testDuplicateParameters() { + myFixture.configureByText("AnnotatedClass.kt", """ + annotation class Anno1(val i:Int , val i: Boolean) + + @Anno1(i = true, i = 3) + class AnnotatedClass + """.trimIndent()) + + val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1) + val annotation = annotations.first() + assertTextAndRange("", annotation.findAttributeValue("i")!!) + } + + fun testMissingDefault() { + myFixture.configureByText("AnnotatedClass.kt", """ + annotation class Anno1(val i: Int = 0) + + @Anno1() + class AnnotatedClass + """.trimIndent()) + + val (annotation) = myFixture.findClass("AnnotatedClass").expectAnnotations(1) + assertTextAndRange("0", annotation.findAttributeValue("i")!!) + } + private fun assertTextAndRange(expected: String, psiElement: PsiElement) { TestCase.assertEquals(expected, psiElement.text) TestCase.assertEquals(expected, psiElement.textRange.substring(psiElement.containingFile.text))