From eea66bea73936d66255df3aadf4db3adb2ac1e50 Mon Sep 17 00:00:00 2001 From: Nicolay Mitropolsky Date: Sat, 31 Mar 2018 21:32:21 +0300 Subject: [PATCH] Making LightAnnotation work without clsDelegate (KT-20924, KT-22883) --- .../elements/KtLightAnnotationsValues.kt | 94 +++++ .../elements/KtLightAnnotationsValues.kt.173 | 94 +++++ .../asJava/elements/KtLightIdentifier.kt | 4 +- .../KtLightPsiJavaCodeReferenceElement.kt | 42 ++ .../asJava/elements/lightAnnotations.kt | 348 +++++++---------- .../asJava/elements/lightAnnotations.kt.172 | 368 ----------------- .../asJava/elements/lightAnnotations.kt.173 | 369 +++++++----------- .../kotlin/asJava/lightClassUtils.kt | 7 +- .../KotlinLightConstantExpressionEvaluator.kt | 48 +-- .../kotlin/asJava/KtLightAnnotationTest.kt | 119 +++++- .../asJava/KtLightAnnotationTest.kt.173 | 119 +++++- .../uast/kotlin/KotlinUastLanguagePlugin.kt | 6 +- .../kotlin/KotlinUastLanguagePlugin.kt.172 | 4 +- .../kotlin/KotlinUastLanguagePlugin.kt.173 | 4 +- 14 files changed, 772 insertions(+), 854 deletions(-) create mode 100644 compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightAnnotationsValues.kt create mode 100644 compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightAnnotationsValues.kt.173 create mode 100644 compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightPsiJavaCodeReferenceElement.kt delete mode 100644 compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt.172 diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightAnnotationsValues.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightAnnotationsValues.kt new file mode 100644 index 00000000000..43dd696052d --- /dev/null +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightAnnotationsValues.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.asJava.elements + +import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.* +import com.intellij.psi.impl.LanguageConstantExpressionEvaluator +import com.intellij.psi.impl.light.LightIdentifier +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.asJava.LightClassGenerationSupport +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtValueArgument +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe + +class KtLightPsiArrayInitializerMemberValue( + override val kotlinOrigin: KtElement, + val lightParent: PsiElement, + val arguments: (KtLightPsiArrayInitializerMemberValue) -> List +) : KtLightElementBase(lightParent), PsiArrayInitializerMemberValue { + override fun getInitializers(): Array = arguments(this).toTypedArray() + + override fun getParent(): PsiElement = lightParent + + override fun isPhysical(): Boolean = false +} + +class KtLightPsiLiteral( + override val kotlinOrigin: KtExpression, + val lightParent: PsiElement +) : KtLightElementBase(lightParent), PsiLiteralExpression { + + override fun getValue(): Any? = + LanguageConstantExpressionEvaluator.INSTANCE.forLanguage(kotlinOrigin.language)?.computeConstantExpression(this, false) + + override fun getType(): PsiType? { + val bindingContext = LightClassGenerationSupport.getInstance(this.project).analyze(kotlinOrigin) + val kotlinType = bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, kotlinOrigin] ?: return null + val typeFqName = kotlinType.constructor.declarationDescriptor?.fqNameSafe?.asString() ?: return null + return when (typeFqName) { + "kotlin.Int" -> PsiType.INT + "kotlin.Long" -> PsiType.LONG + "kotlin.Short" -> PsiType.SHORT + "kotlin.Boolean" -> PsiType.BOOLEAN + "kotlin.Byte" -> PsiType.BYTE + "kotlin.Char" -> PsiType.CHAR + "kotlin.Double" -> PsiType.DOUBLE + "kotlin.Float" -> PsiType.FLOAT + "kotlin.String" -> PsiType.getJavaLangString(kotlinOrigin.manager, GlobalSearchScope.projectScope(kotlinOrigin.project)) + else -> null + } + } + + override fun getParent(): PsiElement = lightParent + + override fun isPhysical(): Boolean = false + + override fun replace(newElement: PsiElement): PsiElement { + val value = (newElement as? PsiLiteral)?.value as? String ?: return this + kotlinOrigin.replace(KtPsiFactory(this).createExpression("\"${StringUtil.escapeStringCharacters(value)}\"")) + return this + } + + override fun getReference(): PsiReference? = references.singleOrNull() + override fun getReferences(): Array = kotlinOrigin.references +} + +class KtLightPsiNameValuePair private constructor( + override val kotlinOrigin: KtElement, + val valueArgument: KtValueArgument, + lightParent: PsiElement +) : KtLightElementBase(lightParent), + PsiNameValuePair { + + constructor(valueArgument: KtValueArgument, lightParent: PsiElement) : this(valueArgument.asElement(), valueArgument, lightParent) + + override fun setValue(newValue: PsiAnnotationMemberValue): PsiAnnotationMemberValue = + throw UnsupportedOperationException("can't modify KtLightPsiNameValuePair") + + override fun getNameIdentifier(): PsiIdentifier? = LightIdentifier(kotlinOrigin.manager, valueArgument.name) + + override fun getName(): String? = valueArgument.getArgumentName()?.asName?.asString() + + override fun getValue(): PsiAnnotationMemberValue? = + valueArgument.getArgumentExpression()?.let { convertToLightAnnotationMemberValue(this, it) } + + override fun getLiteralValue(): String? = (getValue() as? PsiLiteralExpression)?.value?.toString() + +} \ No newline at end of file diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightAnnotationsValues.kt.173 b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightAnnotationsValues.kt.173 new file mode 100644 index 00000000000..4ee02af06d9 --- /dev/null +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightAnnotationsValues.kt.173 @@ -0,0 +1,94 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.asJava.elements + +import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.* +import com.intellij.psi.impl.LanguageConstantExpressionEvaluator +import com.intellij.psi.impl.light.LightIdentifier +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.asJava.LightClassGenerationSupport +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtValueArgument +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe + +class KtLightPsiArrayInitializerMemberValue( + override val kotlinOrigin: KtElement, + val lightParent: PsiElement, + val arguments: (KtLightPsiArrayInitializerMemberValue) -> List +) : KtLightElementBase(lightParent), PsiArrayInitializerMemberValue { + override fun getInitializers(): Array = arguments(this).toTypedArray() + + override fun getParent(): PsiElement = lightParent + + override fun isPhysical(): Boolean = true +} + +class KtLightPsiLiteral( + override val kotlinOrigin: KtExpression, + val lightParent: PsiElement +) : KtLightElementBase(lightParent), PsiLiteralExpression { + + override fun getValue(): Any? = + LanguageConstantExpressionEvaluator.INSTANCE.forLanguage(kotlinOrigin.language)?.computeConstantExpression(this, false) + + override fun getType(): PsiType? { + val bindingContext = LightClassGenerationSupport.getInstance(this.project).analyze(kotlinOrigin) + val kotlinType = bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, kotlinOrigin] ?: return null + val typeFqName = kotlinType.constructor.declarationDescriptor?.fqNameSafe?.asString() ?: return null + return when (typeFqName) { + "kotlin.Int" -> PsiType.INT + "kotlin.Long" -> PsiType.LONG + "kotlin.Short" -> PsiType.SHORT + "kotlin.Boolean" -> PsiType.BOOLEAN + "kotlin.Byte" -> PsiType.BYTE + "kotlin.Char" -> PsiType.CHAR + "kotlin.Double" -> PsiType.DOUBLE + "kotlin.Float" -> PsiType.FLOAT + "kotlin.String" -> PsiType.getJavaLangString(kotlinOrigin.manager, GlobalSearchScope.projectScope(kotlinOrigin.project)) + else -> null + } + } + + override fun getParent(): PsiElement = lightParent + + override fun isPhysical(): Boolean = true + + override fun replace(newElement: PsiElement): PsiElement { + val value = (newElement as? PsiLiteral)?.value as? String ?: return this + kotlinOrigin.replace(KtPsiFactory(this).createExpression("\"${StringUtil.escapeStringCharacters(value)}\"")) + return this + } + + override fun getReference(): PsiReference? = references.singleOrNull() + override fun getReferences(): Array = kotlinOrigin.references +} + +class KtLightPsiNameValuePair private constructor( + override val kotlinOrigin: KtElement, + val valueArgument: KtValueArgument, + lightParent: PsiElement +) : KtLightElementBase(lightParent), + PsiNameValuePair { + + constructor(valueArgument: KtValueArgument, lightParent: PsiElement) : this(valueArgument.asElement(), valueArgument, lightParent) + + override fun setValue(newValue: PsiAnnotationMemberValue): PsiAnnotationMemberValue = + throw UnsupportedOperationException("can't modify KtLightPsiNameValuePair") + + override fun getNameIdentifier(): PsiIdentifier? = LightIdentifier(kotlinOrigin.manager, valueArgument.name) + + override fun getName(): String? = valueArgument.getArgumentName()?.asName?.asString() + + override fun getValue(): PsiAnnotationMemberValue? = + valueArgument.getArgumentExpression()?.let { convertToLightAnnotationMemberValue(this, it) } + + override fun getLiteralValue(): String? = (getValue() as? PsiLiteralExpression)?.value?.toString() + +} \ No newline at end of file diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightIdentifier.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightIdentifier.kt index c6356e3e49a..a8689e32b7e 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightIdentifier.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightIdentifier.kt @@ -27,8 +27,8 @@ import org.jetbrains.kotlin.psi.KtSecondaryConstructor import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject open class KtLightIdentifier( - private val lightOwner: PsiNameIdentifierOwner, - private val ktDeclaration: KtNamedDeclaration? + private val lightOwner: PsiElement, + private val ktDeclaration: KtNamedDeclaration? ) : LightIdentifier(lightOwner.manager, ktDeclaration?.name ?: ""), PsiCompiledElement { val origin: PsiElement? get() = when (ktDeclaration) { diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightPsiJavaCodeReferenceElement.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightPsiJavaCodeReferenceElement.kt new file mode 100644 index 00000000000..0c54724faff --- /dev/null +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/KtLightPsiJavaCodeReferenceElement.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.asJava.elements + +import com.intellij.psi.* +import com.intellij.psi.scope.PsiScopeProcessor +import org.jetbrains.kotlin.asJava.classes.lazyPub + +class KtLightPsiJavaCodeReferenceElement( + private val ktElement: PsiElement, + private val reference: PsiReference, + private val clsDelegateProvider: () -> PsiJavaCodeReferenceElement +) : + PsiElement by ktElement, + PsiReference by reference, + PsiJavaCodeReferenceElement { + + private val delegate by lazyPub { clsDelegateProvider() } + + override fun advancedResolve(incompleteCode: Boolean): JavaResolveResult = delegate.advancedResolve(incompleteCode) + + override fun getReferenceNameElement(): PsiElement? = ktElement + + override fun getTypeParameters(): Array = delegate.typeParameters + + override fun getReferenceName(): String? = delegate.referenceName + + override fun isQualified(): Boolean = delegate.isQualified + + override fun processVariants(processor: PsiScopeProcessor) = delegate.processVariants(processor) + + override fun multiResolve(incompleteCode: Boolean): Array = delegate.multiResolve(incompleteCode) + + override fun getQualifiedName(): String = delegate.qualifiedName + + override fun getQualifier(): PsiElement? = delegate.qualifier + + override fun getParameterList(): PsiReferenceParameterList? = delegate.parameterList +} \ No newline at end of file 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 16b490a185c..ddf48c47b35 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,36 +16,41 @@ package org.jetbrains.kotlin.asJava.elements -import com.intellij.openapi.diagnostic.Attachment +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.diagnostic.Logger -import com.intellij.openapi.util.TextRange -import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.* +import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.annotations.NotNull import org.jetbrains.annotations.Nullable import org.jetbrains.kotlin.asJava.LightClassGenerationSupport import org.jetbrains.kotlin.asJava.classes.cannotModify import org.jetbrains.kotlin.asJava.classes.lazyPub +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor -import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.load.java.descriptors.JavaClassConstructorDescriptor import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType -import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.CompileTimeConstantUtils import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall -import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument -import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall -import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe +import org.jetbrains.kotlin.resolve.descriptorUtil.declaresOrInheritsDefaultValue import org.jetbrains.kotlin.resolve.source.getPsi -import org.jetbrains.kotlin.types.TypeUtils private val LOG = Logger.getInstance("#org.jetbrains.kotlin.asJava.elements.lightAnnotations") abstract class KtLightAbstractAnnotation(parent: PsiElement, computeDelegate: () -> PsiAnnotation) : - KtLightElementBase(parent), PsiAnnotation, KtLightElement { - override val clsDelegate by lazyPub(computeDelegate) + KtLightElementBase(parent), PsiAnnotation, KtLightElement { + + private val _clsDelegate: PsiAnnotation by lazyPub(computeDelegate) + + override val clsDelegate: PsiAnnotation + get() { + if (ApplicationManager.getApplication().isUnitTestMode && this !is KtLightNonSourceAnnotation) + LOG.error("KtLightAbstractAnnotation clsDelegate requested for ${this.javaClass}") + return _clsDelegate + } override fun getNameReferenceElement() = clsDelegate.nameReferenceElement @@ -64,8 +69,6 @@ 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, @@ -75,208 +78,78 @@ class KtLightAnnotationForSourceEntry( override fun getQualifiedName() = qualifiedName - open inner class LightElementValue( - val delegate: D, - private val parent: PsiElement, - valueOrigin: AnnotationValueOrigin - ) : PsiAnnotationMemberValue, PsiCompiledElement, PsiElement by delegate { - override fun getMirror(): PsiElement = delegate + override fun getName(): String? = null - val originalExpression: PsiElement? by lazyPub(valueOrigin) + override fun findAttributeValue(name: String?) = getAttributeValue(name, true) - fun getConstantValue(): Any? { - val expression = originalExpression as? KtExpression ?: return null - val annotationEntry = this@KtLightAnnotationForSourceEntry.kotlinOrigin - val context = LightClassGenerationSupport.getInstance(project).analyze(annotationEntry) - return context[BindingContext.COMPILE_TIME_VALUE, expression]?.getValue(TypeUtils.NO_EXPECTED_TYPE) - } + override fun findDeclaredAttributeValue(name: String?): PsiAnnotationMemberValue? = getAttributeValue(name, false) - override fun getReference() = references.singleOrNull() - override fun getReferences() = originalExpression?.references.orEmpty() - override fun getLanguage() = KotlinLanguage.INSTANCE - override fun getNavigationElement() = originalExpression - override fun isPhysical(): Boolean = false - override fun getTextRange() = originalExpression?.textRange ?: TextRange.EMPTY_RANGE - override fun getStartOffsetInParent() = originalExpression?.startOffsetInParent ?: 0 - override fun getParent() = parent - override fun getText() = originalExpression?.text.orEmpty() - override fun getContainingFile(): PsiFile? = if (originalExpression?.containingFile == kotlinOrigin.containingFile) - kotlinOrigin.containingFile else delegate.containingFile + private fun getAttributeValue(name: String?, useDefault: Boolean): PsiAnnotationMemberValue? { + val name = name ?: "value" - override fun replace(newElement: PsiElement): PsiElement { - val value = (newElement as? PsiLiteral)?.value as? String ?: return this - val origin = originalExpression + val resolvedCall = kotlinOrigin.getResolvedCall() ?: return null + val callEntry = resolvedCall.valueArguments.entries.find { (param, _) -> param.name.asString() == name } ?: return null - val exprToReplace = - if (origin is KtCallExpression /*arrayOf*/) { - unwrapArray(origin.valueArguments) - } - else { - origin as? KtExpression - } ?: return this - exprToReplace.replace(KtPsiFactory(this).createExpression("\"${StringUtil.escapeStringCharacters(value)}\"")) + val valueArguments = callEntry.value.arguments + val valueArgument = valueArguments.firstOrNull() + val argument = valueArgument?.getArgumentExpression() + if (argument != null) { + val arrayExpected = callEntry.key?.type?.let { KotlinBuiltIns.isArray(it) } ?: false - return this - } - } - - 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()?.takeIf { it.containingClass?.isAnnotationType == true }?.name ?: - "value" - - 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 -> error("$psi of type ${psi?.javaClass}") - } - } - - is ExpressionValueArgument -> { - val argExpression = resolvedArgument.valueArgument?.getArgumentExpression() - argExpression?.asKtCall() - ?: argExpression - ?: error("resolvedArgument ($resolvedArgument) has no arg expression") - } - - is VarargValueArgument -> - memberValue.unwrapArray(resolvedArgument.arguments) - ?: resolvedArgument.arguments.first().asElement().let { - (it as? KtValueArgument) - ?.takeIf { - it.getSpreadElement() != null || - it.getArgumentName() != null || - it.getArgumentExpression() is KtCollectionLiteralExpression - } - ?.getArgumentExpression() ?: it.parent - } - - else -> error("resolvedArgument: ${resolvedArgument.javaClass} cant be processed") - } - } - - private fun PsiElement.unwrapArray(arguments: List): PsiElement? { - val arrayInitializer = parent as? PsiArrayInitializerMemberValue ?: return null - val exprIndex = arrayInitializer.initializers.indexOf(this) - if (exprIndex < 0 || exprIndex >= arguments.size) return null - return arguments[exprIndex].getArgumentExpression() - } - - open inner class LightExpressionValue( - delegate: D, - parent: PsiElement, - valueOrigin: AnnotationValueOrigin - ) : LightElementValue(delegate, parent, valueOrigin), PsiExpression { - override fun getType(): PsiType? = delegate.type - } - - inner class LightPsiLiteral( - delegate: PsiLiteralExpression, - parent: PsiElement, - valueOrigin: AnnotationValueOrigin - ) : LightExpressionValue(delegate, parent, valueOrigin), PsiLiteralExpression { - override fun getValue() = delegate.value - } - - inner class LightClassLiteral( - delegate: PsiClassObjectAccessExpression, - parent: PsiElement, - valueOrigin: AnnotationValueOrigin - ) : LightExpressionValue(delegate, parent, valueOrigin), PsiClassObjectAccessExpression { - override fun getType() = delegate.type - override fun getOperand(): PsiTypeElement = delegate.operand - } - - inner class LightArrayInitializerValue( - delegate: PsiArrayInitializerMemberValue, - parent: PsiElement, - valueOrigin: AnnotationValueOrigin - ) : LightElementValue(delegate, parent, valueOrigin), PsiArrayInitializerMemberValue { - private val _initializers by lazyPub { - delegate.initializers.mapIndexed { i, memberValue -> - wrapAnnotationValue(memberValue, this, { - originalExpression.let { ktOrigin -> - when { - ktOrigin is KtCallExpression - && memberValue is PsiAnnotation - && isAnnotationConstructorCall(ktOrigin, memberValue) -> ktOrigin - ktOrigin is KtValueArgumentList -> ktOrigin.arguments.getOrNull(i)?.getArgumentExpression() - ktOrigin is KtCallElement -> ktOrigin.valueArguments.getOrNull(i)?.getArgumentExpression() - ktOrigin is KtCollectionLiteralExpression -> ktOrigin.getInnerExpressions().getOrNull(i) - delegate.initializers.size == 1 -> ktOrigin - else -> null - }.also { - if (it == null) - LOG.error("error wrapping ${memberValue.javaClass} for ${ktOrigin?.javaClass} in ${ktOrigin?.containingFile}", - Attachment( - "source_fragments.txt", - "origin: '${psiReport(ktOrigin)}', delegate: ${psiReport(delegate)}, parent: ${psiReport(parent)}" - ) - ) + if (arrayExpected && (argument is KtStringTemplateExpression || argument is KtConstantExpression || getAnnotationName(argument) != null)) + return KtLightPsiArrayInitializerMemberValue( + PsiTreeUtil.findCommonParent(valueArguments.map { it.getArgumentExpression() }) as KtElement, + this, + { self -> + valueArguments.mapNotNull { + it.getArgumentExpression()?.let { convertToLightAnnotationMemberValue(self, it) } } - } - }) - }.toTypedArray() + }) + + ktLightAnnotationParameterList.attributes.find { (it as KtLightPsiNameValuePair).valueArgument === valueArgument }?.let { + return it.value + } } - override fun getInitializers() = _initializers + if (useDefault && callEntry.key.declaresOrInheritsDefaultValue()) { + val psiElement = callEntry.key.source.getPsi() + when (psiElement) { + is KtParameter -> + return psiElement.defaultValue?.let { convertToLightAnnotationMemberValue(this, it) } + is PsiAnnotationMethod -> + return psiElement.defaultValue + } + } + return null } - private fun wrapAnnotationValue(value: PsiAnnotationMemberValue, parent: PsiElement, ktOrigin: AnnotationValueOrigin): PsiAnnotationMemberValue = - when { - value is PsiLiteralExpression -> LightPsiLiteral(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 -> { - val origin = ktOrigin() - val ktCallElement = origin?.asKtCall() - val qualifiedName = value.qualifiedName - if (qualifiedName != null && ktCallElement != null) - KtLightAnnotationForSourceEntry(qualifiedName, ktCallElement, parent, { value }) - else { - LOG.error("can't convert ${origin?.javaClass} to KtCallElement in ${origin?.containingFile} (value = ${value.javaClass})", - Attachment("source_fragments.txt", "origin: '${psiReport(origin)}', value: '${psiReport(value)}'")) - LightElementValue(value, parent, ktOrigin) // or maybe create a LightErrorAnnotationMemberValue instead? - } - } - else -> LightElementValue(value, parent, ktOrigin) - } - override fun getName() = null + override fun getNameReferenceElement(): PsiJavaCodeReferenceElement? { + val reference = (kotlinOrigin as? KtAnnotationEntry)?.typeReference?.reference + ?: (kotlinOrigin.calleeExpression as? KtNameReferenceExpression)?.reference + ?: return null + return KtLightPsiJavaCodeReferenceElement( + kotlinOrigin.navigationElement, + reference, + { super.getNameReferenceElement()!! }) + } - private fun wrapAnnotationValue(value: PsiAnnotationMemberValue): PsiAnnotationMemberValue = wrapAnnotationValue(value, this, { - getMemberValueAsCallArgument(value, kotlinOrigin) - }) - override fun findAttributeValue(name: String?) = clsDelegate.findAttributeValue(name)?.let { wrapAnnotationValue(it) } + private val ktLightAnnotationParameterList by lazyPub { KtLightAnnotationParameterList() } - override fun findDeclaredAttributeValue(name: String?) = clsDelegate.findDeclaredAttributeValue(name)?.let { wrapAnnotationValue(it) } + override fun getParameterList(): PsiAnnotationParameterList = ktLightAnnotationParameterList - override fun getParameterList(): PsiAnnotationParameterList = KtLightAnnotationParameterList(super.getParameterList()) - - inner class KtLightAnnotationParameterList(private val list: PsiAnnotationParameterList) : KtLightElementBase(this), + inner class KtLightAnnotationParameterList() : KtLightElementBase(this), PsiAnnotationParameterList { override val kotlinOrigin get() = null - override fun getAttributes(): Array = list.attributes.map { KtLightPsiNameValuePair(it) }.toTypedArray() - inner class KtLightPsiNameValuePair(private val psiNameValuePair: PsiNameValuePair) : KtLightElementBase(this), - PsiNameValuePair { - override fun setValue(newValue: PsiAnnotationMemberValue): PsiAnnotationMemberValue = psiNameValuePair.setValue(newValue) - override fun getNameIdentifier(): PsiIdentifier? = psiNameValuePair.nameIdentifier - override fun getLiteralValue(): String? = (value as? PsiLiteralValue)?.value?.toString() - override val kotlinOrigin: KtElement? = null - - override fun getValue(): PsiAnnotationMemberValue? = psiNameValuePair.value?.let { wrapAnnotationValue(it) } + private val _attributes: Array by lazyPub { + this@KtLightAnnotationForSourceEntry.kotlinOrigin.valueArguments.map { KtLightPsiNameValuePair(it as KtValueArgument, this) } + .toTypedArray() } + + override fun getAttributes(): Array = _attributes + } @@ -296,10 +169,10 @@ class KtLightAnnotationForSourceEntry( } class KtLightNonSourceAnnotation( - parent: PsiElement, clsDelegate: PsiAnnotation + parent: PsiElement, clsDelegate: PsiAnnotation ) : KtLightAbstractAnnotation(parent, { clsDelegate }) { override val kotlinOrigin: KtAnnotationEntry? get() = null - override fun getQualifiedName() = clsDelegate.qualifiedName + override fun getQualifiedName() = kotlinOrigin?.name ?: clsDelegate.qualifiedName override fun setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify() override fun findAttributeValue(attributeName: String?) = clsDelegate.findAttributeValue(attributeName) override fun findDeclaredAttributeValue(attributeName: String?) = clsDelegate.findDeclaredAttributeValue(attributeName) @@ -348,7 +221,9 @@ class KtLightNullabilityAnnotation(member: KtLightElement<*, PsiModifierListOwne override fun findAttributeValue(attributeName: String?) = null - override fun getQualifiedName(): String? = clsDelegate.qualifiedName + override fun getQualifiedName(): String? = Nullable::class.java.name + + override fun getNameReferenceElement(): PsiJavaCodeReferenceElement? = null override fun findDeclaredAttributeValue(attributeName: String?) = null } @@ -362,18 +237,67 @@ private fun KtElement.getResolvedCall(): ResolvedCall? { return this.getResolvedCall(context) } -private fun isAnnotationConstructorCall(callExpression: KtCallExpression, psiAnnotation: PsiAnnotation) = - (callExpression.getResolvedCall()?.resultingDescriptor as? ClassConstructorDescriptor)?.constructedClass?.fqNameUnsafe?.toString() == psiAnnotation.qualifiedName - -private fun PsiElement.asKtCall(): KtCallElement? = (this as? KtElement)?.getResolvedCall()?.call?.callElement as? KtCallElement - -private fun psiReport(psiElement: PsiElement?): String { - if (psiElement == null) return "null" - val text = try { - psiElement.text +fun convertToLightAnnotationMemberValue(lightParent: PsiElement, argument: KtExpression): PsiAnnotationMemberValue { + val argument = unwrapCall(argument) + when (argument) { + is KtStringTemplateExpression, is KtConstantExpression -> { + return KtLightPsiLiteral(argument, lightParent) + } + is KtCallExpression -> { + val arguments = argument.valueArguments + val annotationName = argument.calleeExpression?.let { getAnnotationName(it) } + if (annotationName != null) { + return KtLightAnnotationForSourceEntry( + annotationName, + argument, + lightParent, + { throw UnsupportedOperationException("cls delegate is not supported for nested annotations") }) + } + val resolvedCall = argument.getResolvedCall() + if (resolvedCall != null && CompileTimeConstantUtils.isArrayFunctionCall(resolvedCall)) + return KtLightPsiArrayInitializerMemberValue( + argument, + lightParent, + { self -> + arguments.mapNotNull { + it.getArgumentExpression()?.let { convertToLightAnnotationMemberValue(self, it) } + } + }) + } + is KtCollectionLiteralExpression -> { + val arguments = argument.getInnerExpressions() + if (arguments.isNotEmpty()) + return KtLightPsiArrayInitializerMemberValue( + argument, + lightParent, + { self -> arguments.mapNotNull { convertToLightAnnotationMemberValue(self, it) } }) + } } - catch (e: Exception) { - "${e.javaClass.simpleName}:${e.message}" + // everything else (like complex constant references) considered as PsiLiteral-s + return KtLightPsiLiteral(argument, lightParent) +} + +private val KtExpression.nameReference: KtNameReferenceExpression? + get() = when { + this is KtConstructorCalleeExpression -> constructorReferenceExpression as? KtNameReferenceExpression + else -> this as? KtNameReferenceExpression } - return "${psiElement.javaClass.canonicalName}[$text]" + +private fun unwrapCall(callee: KtExpression): KtExpression = when (callee) { + is KtDotQualifiedExpression -> callee.lastChild as? KtCallExpression ?: callee + else -> callee +} + +private fun getAnnotationName(callee: KtExpression): String? { + val callee = unwrapCall(callee) + val resultingDescriptor = callee.getResolvedCall()?.resultingDescriptor + if (resultingDescriptor is ClassConstructorDescriptor) { + val ktClass = resultingDescriptor.constructedClass.source.getPsi() as? KtClass + if (ktClass?.isAnnotation() == true) return ktClass.fqName?.toString() + } + if (resultingDescriptor is JavaClassConstructorDescriptor) { + val psiClass = resultingDescriptor.constructedClass.source.getPsi() as? PsiClass + if (psiClass?.isAnnotationType == true) return psiClass.qualifiedName + } + return null } \ No newline at end of file diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt.172 b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt.172 deleted file mode 100644 index 3629988fe07..00000000000 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt.172 +++ /dev/null @@ -1,368 +0,0 @@ -/* - * Copyright 2010-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.kotlin.asJava.elements - -import com.intellij.openapi.diagnostic.Attachment -import com.intellij.openapi.diagnostic.Logger -import com.intellij.openapi.util.TextRange -import com.intellij.openapi.util.text.StringUtil -import com.intellij.psi.* -import org.jetbrains.annotations.NotNull -import org.jetbrains.annotations.Nullable -import org.jetbrains.kotlin.asJava.LightClassGenerationSupport -import org.jetbrains.kotlin.asJava.classes.cannotModify -import org.jetbrains.kotlin.asJava.classes.lazyPub -import org.jetbrains.kotlin.descriptors.CallableDescriptor -import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor -import org.jetbrains.kotlin.idea.KotlinLanguage -import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall -import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument -import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument -import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall -import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe -import org.jetbrains.kotlin.resolve.source.getPsi -import org.jetbrains.kotlin.types.TypeUtils - -private val LOG = Logger.getInstance("#org.jetbrains.kotlin.asJava.elements.lightAnnotations") - -abstract class KtLightAbstractAnnotation(parent: PsiElement, computeDelegate: () -> PsiAnnotation) : - KtLightElementBase(parent), PsiAnnotation, KtLightElement { - override val clsDelegate by lazyPub(computeDelegate) - - override fun getNameReferenceElement() = clsDelegate.nameReferenceElement - - override fun getOwner() = parent as? PsiAnnotationOwner - - override fun getMetaData() = clsDelegate.metaData - - override fun getParameterList() = clsDelegate.parameterList - - open fun fqNameMatches(fqName: String): Boolean = qualifiedName == fqName -} - -private typealias AnnotationValueOrigin = () -> PsiElement? - -class KtLightAnnotationForSourceEntry( - private val qualifiedName: String, - override val kotlinOrigin: KtCallElement, - parent: PsiElement, - computeDelegate: () -> PsiAnnotation -) : KtLightAbstractAnnotation(parent, computeDelegate) { - - override fun getQualifiedName() = qualifiedName - - open inner class LightElementValue( - val delegate: D, - private val parent: PsiElement, - valueOrigin: AnnotationValueOrigin - ) : PsiAnnotationMemberValue, PsiCompiledElement, PsiElement by delegate { - override fun getMirror(): PsiElement = delegate - - val originalExpression: PsiElement? by lazyPub(valueOrigin) - - fun getConstantValue(): Any? { - val expression = originalExpression as? KtExpression ?: return null - val annotationEntry = this@KtLightAnnotationForSourceEntry.kotlinOrigin - val context = LightClassGenerationSupport.getInstance(project).analyze(annotationEntry) - return context[BindingContext.COMPILE_TIME_VALUE, expression]?.getValue(TypeUtils.NO_EXPECTED_TYPE) - } - - override fun getReference() = references.singleOrNull() - override fun getReferences() = originalExpression?.references.orEmpty() - override fun getLanguage() = KotlinLanguage.INSTANCE - override fun getNavigationElement() = originalExpression - override fun isPhysical(): Boolean = originalExpression?.containingFile == kotlinOrigin.containingFile - override fun getTextRange() = originalExpression?.textRange ?: TextRange.EMPTY_RANGE - override fun getStartOffsetInParent() = originalExpression?.startOffsetInParent ?: 0 - override fun getParent() = parent - override fun getText() = originalExpression?.text.orEmpty() - override fun getContainingFile(): PsiFile? = if (isPhysical) kotlinOrigin.containingFile else delegate.containingFile - - override fun replace(newElement: PsiElement): PsiElement { - val value = (newElement as? PsiLiteral)?.value as? String ?: return this - val origin = originalExpression - - val exprToReplace = - if (origin is KtCallExpression /*arrayOf*/) { - unwrapArray(origin.valueArguments) - } - else { - origin as? KtExpression - } ?: return this - exprToReplace.replace(KtPsiFactory(this).createExpression("\"${StringUtil.escapeStringCharacters(value)}\"")) - - return this - } - } - - 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()?.takeIf { it.containingClass?.isAnnotationType == true }?.name ?: - "value" - - 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 -> error("$psi of type ${psi?.javaClass}") - } - } - - is ExpressionValueArgument -> { - val argExpression = resolvedArgument.valueArgument?.getArgumentExpression() - argExpression?.asKtCall() - ?: argExpression - ?: error("resolvedArgument ($resolvedArgument) has no arg expression") - } - - is VarargValueArgument -> - memberValue.unwrapArray(resolvedArgument.arguments) - ?: resolvedArgument.arguments.first().asElement().let { - (it as? KtValueArgument) - ?.takeIf { - it.getSpreadElement() != null || - it.getArgumentName() != null || - it.getArgumentExpression() is KtCollectionLiteralExpression - } - ?.getArgumentExpression() ?: it.parent - } - - else -> error("resolvedArgument: ${resolvedArgument.javaClass} cant be processed") - } - } - - private fun PsiElement.unwrapArray(arguments: List): PsiElement? { - val arrayInitializer = parent as? PsiArrayInitializerMemberValue ?: return null - val exprIndex = arrayInitializer.initializers.indexOf(this) - if (exprIndex < 0 || exprIndex >= arguments.size) return null - return arguments[exprIndex].getArgumentExpression() - } - - open inner class LightExpressionValue( - delegate: D, - parent: PsiElement, - valueOrigin: AnnotationValueOrigin - ) : LightElementValue(delegate, parent, valueOrigin), PsiExpression { - override fun getType(): PsiType? = delegate.type - } - - inner class LightPsiLiteral( - delegate: PsiLiteralExpression, - parent: PsiElement, - valueOrigin: AnnotationValueOrigin - ) : LightExpressionValue(delegate, parent, valueOrigin), PsiLiteralExpression { - override fun getValue() = delegate.value - } - - inner class LightClassLiteral( - delegate: PsiClassObjectAccessExpression, - parent: PsiElement, - valueOrigin: AnnotationValueOrigin - ) : LightExpressionValue(delegate, parent, valueOrigin), PsiClassObjectAccessExpression { - override fun getType() = delegate.type - override fun getOperand(): PsiTypeElement = delegate.operand - } - - inner class LightArrayInitializerValue( - delegate: PsiArrayInitializerMemberValue, - parent: PsiElement, - valueOrigin: AnnotationValueOrigin - ) : LightElementValue(delegate, parent, valueOrigin), PsiArrayInitializerMemberValue { - private val _initializers by lazyPub { - delegate.initializers.mapIndexed { i, memberValue -> - wrapAnnotationValue(memberValue, this, { - originalExpression.let { ktOrigin -> - when { - ktOrigin is KtCallExpression - && memberValue is PsiAnnotation - && isAnnotationConstructorCall(ktOrigin, memberValue) -> ktOrigin - ktOrigin is KtValueArgumentList -> ktOrigin.arguments.getOrNull(i)?.getArgumentExpression() - ktOrigin is KtCallElement -> ktOrigin.valueArguments.getOrNull(i)?.getArgumentExpression() - ktOrigin is KtCollectionLiteralExpression -> ktOrigin.getInnerExpressions().getOrNull(i) - delegate.initializers.size == 1 -> ktOrigin - else -> null - }.also { - if (it == null) - LOG.error("error wrapping ${memberValue.javaClass} for ${ktOrigin?.javaClass} in ${ktOrigin?.containingFile}", - Attachment( - "source_fragments.txt", - "origin: '${psiReport(ktOrigin)}', delegate: ${psiReport(delegate)}, parent: ${psiReport(parent)}" - ) - ) - } - } - }) - }.toTypedArray() - } - - override fun getInitializers() = _initializers - } - - private fun wrapAnnotationValue(value: PsiAnnotationMemberValue, parent: PsiElement, ktOrigin: AnnotationValueOrigin): PsiAnnotationMemberValue = - when { - value is PsiLiteralExpression -> LightPsiLiteral(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 -> { - val origin = ktOrigin() - val ktCallElement = origin?.asKtCall() - val qualifiedName = value.qualifiedName - if (qualifiedName != null && ktCallElement != null) - KtLightAnnotationForSourceEntry(qualifiedName, ktCallElement, parent, { value }) - else { - LOG.error("can't convert ${origin?.javaClass} to KtCallElement in ${origin?.containingFile} (value = ${value.javaClass})", - Attachment("source_fragments.txt", "origin: '${psiReport(origin)}', value: '${psiReport(value)}'")) - LightElementValue(value, parent, ktOrigin) // or maybe create a LightErrorAnnotationMemberValue instead? - } - } - else -> LightElementValue(value, parent, ktOrigin) - } - - override fun isPhysical() = true - - override fun getName() = null - - private fun wrapAnnotationValue(value: PsiAnnotationMemberValue): PsiAnnotationMemberValue = wrapAnnotationValue(value, this, { - getMemberValueAsCallArgument(value, kotlinOrigin) - }) - - override fun findAttributeValue(name: String?) = clsDelegate.findAttributeValue(name)?.let { wrapAnnotationValue(it) } - - override fun findDeclaredAttributeValue(name: String?) = clsDelegate.findDeclaredAttributeValue(name)?.let { wrapAnnotationValue(it) } - - override fun getParameterList(): PsiAnnotationParameterList = KtLightAnnotationParameterList(super.getParameterList()) - - inner class KtLightAnnotationParameterList(private val list: PsiAnnotationParameterList) : KtLightElementBase(this), - PsiAnnotationParameterList { - override val kotlinOrigin get() = null - override fun getAttributes(): Array = list.attributes.map { KtLightPsiNameValuePair(it) }.toTypedArray() - - inner class KtLightPsiNameValuePair(private val psiNameValuePair: PsiNameValuePair) : KtLightElementBase(this), - PsiNameValuePair { - override fun setValue(newValue: PsiAnnotationMemberValue): PsiAnnotationMemberValue = psiNameValuePair.setValue(newValue) - override fun getNameIdentifier(): PsiIdentifier? = psiNameValuePair.nameIdentifier - override fun getLiteralValue(): String? = (value as? PsiLiteralValue)?.value?.toString() - override val kotlinOrigin: KtElement? = null - - override fun getValue(): PsiAnnotationMemberValue? = psiNameValuePair.value?.let { wrapAnnotationValue(it) } - } - } - - - override fun delete() = kotlinOrigin.delete() - - override fun toString() = "@$qualifiedName" - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || other::class.java != this::class.java) return false - return kotlinOrigin == (other as KtLightAnnotationForSourceEntry).kotlinOrigin - } - - override fun hashCode() = kotlinOrigin.hashCode() - - override fun setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify() -} - -class KtLightNonSourceAnnotation( - parent: PsiElement, clsDelegate: PsiAnnotation -) : KtLightAbstractAnnotation(parent, { clsDelegate }) { - override val kotlinOrigin: KtAnnotationEntry? get() = null - override fun getQualifiedName() = clsDelegate.qualifiedName - override fun setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify() - override fun findAttributeValue(attributeName: String?) = clsDelegate.findAttributeValue(attributeName) - override fun findDeclaredAttributeValue(attributeName: String?) = clsDelegate.findDeclaredAttributeValue(attributeName) -} - -class KtLightNonExistentAnnotation(parent: KtLightElement<*, *>) : KtLightElementBase(parent), PsiAnnotation { - override val kotlinOrigin get() = null - override fun toString() = this.javaClass.name - - override fun setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify() - - override fun getNameReferenceElement() = null - override fun findAttributeValue(attributeName: String?) = null - override fun getQualifiedName() = null - override fun getOwner() = parent as? PsiAnnotationOwner - override fun findDeclaredAttributeValue(attributeName: String?) = null - override fun getMetaData() = null - override fun getParameterList() = KtLightEmptyAnnotationParameterList(this) -} - -class KtLightEmptyAnnotationParameterList(parent: PsiElement) : KtLightElementBase(parent), PsiAnnotationParameterList { - override val kotlinOrigin get() = null - override fun getAttributes(): Array = emptyArray() -} - -class KtLightNullabilityAnnotation(member: KtLightElement<*, PsiModifierListOwner>, parent: PsiElement) : KtLightAbstractAnnotation(parent, { - // searching for last because nullability annotations are generated after backend generates source annotations - member.clsDelegate.modifierList?.annotations?.findLast { - isNullabilityAnnotation(it.qualifiedName) - } ?: KtLightNonExistentAnnotation(member) -}) { - override fun fqNameMatches(fqName: String): Boolean { - if (!isNullabilityAnnotation(fqName)) return false - - return super.fqNameMatches(fqName) - } - - override val kotlinOrigin get() = null - override fun setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify() - - override fun findAttributeValue(attributeName: String?) = null - - override fun getQualifiedName(): String? = clsDelegate.qualifiedName - - override fun findDeclaredAttributeValue(attributeName: String?) = null -} - -internal fun isNullabilityAnnotation(qualifiedName: String?) = qualifiedName in backendNullabilityAnnotations - -private val backendNullabilityAnnotations = arrayOf(Nullable::class.java.name, NotNull::class.java.name) - -private fun KtElement.getResolvedCall(): ResolvedCall? { - val context = LightClassGenerationSupport.getInstance(this.project).analyze(this) - return this.getResolvedCall(context) -} - -private fun isAnnotationConstructorCall(callExpression: KtCallExpression, psiAnnotation: PsiAnnotation) = - (callExpression.getResolvedCall()?.resultingDescriptor as? ClassConstructorDescriptor)?.constructedClass?.fqNameUnsafe?.toString() == psiAnnotation.qualifiedName - -private fun PsiElement.asKtCall(): KtCallElement? = (this as? KtElement)?.getResolvedCall()?.call?.callElement as? KtCallElement - -private fun psiReport(psiElement: PsiElement?): String { - if (psiElement == null) return "null" - val text = try { - psiElement.text - } - catch (e: Exception) { - "${e.javaClass.simpleName}:${e.message}" - } - return "${psiElement.javaClass.canonicalName}[$text]" -} \ No newline at end of file diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt.173 b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt.173 index 9bb99f0f0db..a5cb5d6f8f5 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt.173 +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt.173 @@ -16,36 +16,41 @@ package org.jetbrains.kotlin.asJava.elements -import com.intellij.openapi.diagnostic.Attachment +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.diagnostic.Logger -import com.intellij.openapi.util.TextRange -import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.* +import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.annotations.NotNull import org.jetbrains.annotations.Nullable import org.jetbrains.kotlin.asJava.LightClassGenerationSupport import org.jetbrains.kotlin.asJava.classes.cannotModify import org.jetbrains.kotlin.asJava.classes.lazyPub +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor -import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.load.java.descriptors.JavaClassConstructorDescriptor import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType -import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.CompileTimeConstantUtils import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall -import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument -import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall -import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe +import org.jetbrains.kotlin.resolve.descriptorUtil.declaresOrInheritsDefaultValue import org.jetbrains.kotlin.resolve.source.getPsi -import org.jetbrains.kotlin.types.TypeUtils private val LOG = Logger.getInstance("#org.jetbrains.kotlin.asJava.elements.lightAnnotations") abstract class KtLightAbstractAnnotation(parent: PsiElement, computeDelegate: () -> PsiAnnotation) : - KtLightElementBase(parent), PsiAnnotation, KtLightElement { - override val clsDelegate by lazyPub(computeDelegate) + KtLightElementBase(parent), PsiAnnotation, KtLightElement { + + private val _clsDelegate: PsiAnnotation by lazyPub(computeDelegate) + + override val clsDelegate: PsiAnnotation + get() { + if (ApplicationManager.getApplication().isUnitTestMode && this !is KtLightNonSourceAnnotation) + LOG.error("KtLightAbstractAnnotation clsDelegate requested for ${this.javaClass}") + return _clsDelegate + } override fun getNameReferenceElement() = clsDelegate.nameReferenceElement @@ -64,8 +69,6 @@ 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, @@ -75,209 +78,80 @@ class KtLightAnnotationForSourceEntry( override fun getQualifiedName() = qualifiedName - open inner class LightElementValue( - val delegate: D, - private val parent: PsiElement, - valueOrigin: AnnotationValueOrigin - ) : PsiAnnotationMemberValue, PsiCompiledElement, PsiElement by delegate { - override fun getMirror(): PsiElement = delegate - - val originalExpression: PsiElement? by lazyPub(valueOrigin) - - fun getConstantValue(): Any? { - val expression = originalExpression as? KtExpression ?: return null - val annotationEntry = this@KtLightAnnotationForSourceEntry.kotlinOrigin - val context = LightClassGenerationSupport.getInstance(project).analyze(annotationEntry) - return context[BindingContext.COMPILE_TIME_VALUE, expression]?.getValue(TypeUtils.NO_EXPECTED_TYPE) - } - - override fun getReference() = references.singleOrNull() - override fun getReferences() = originalExpression?.references.orEmpty() - override fun getLanguage() = KotlinLanguage.INSTANCE - override fun getNavigationElement() = originalExpression - override fun isPhysical(): Boolean = originalExpression?.containingFile == kotlinOrigin.containingFile - override fun getTextRange() = originalExpression?.textRange ?: TextRange.EMPTY_RANGE - override fun getStartOffsetInParent() = originalExpression?.startOffsetInParent ?: 0 - override fun getParent() = parent - override fun getText() = originalExpression?.text.orEmpty() - override fun getContainingFile(): PsiFile? = if (isPhysical) kotlinOrigin.containingFile else delegate.containingFile - - override fun replace(newElement: PsiElement): PsiElement { - val value = (newElement as? PsiLiteral)?.value as? String ?: return this - val origin = originalExpression - - val exprToReplace = - if (origin is KtCallExpression /*arrayOf*/) { - unwrapArray(origin.valueArguments) - } - else { - origin as? KtExpression - } ?: return this - exprToReplace.replace(KtPsiFactory(this).createExpression("\"${StringUtil.escapeStringCharacters(value)}\"")) - - return this - } - } - - 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()?.takeIf { it.containingClass?.isAnnotationType == true }?.name ?: - "value" - - 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 -> error("$psi of type ${psi?.javaClass}") - } - } - - is ExpressionValueArgument -> { - val argExpression = resolvedArgument.valueArgument?.getArgumentExpression() - argExpression?.asKtCall() - ?: argExpression - ?: error("resolvedArgument ($resolvedArgument) has no arg expression") - } - - is VarargValueArgument -> - memberValue.unwrapArray(resolvedArgument.arguments) - ?: resolvedArgument.arguments.first().asElement().let { - (it as? KtValueArgument) - ?.takeIf { - it.getSpreadElement() != null || - it.getArgumentName() != null || - it.getArgumentExpression() is KtCollectionLiteralExpression - } - ?.getArgumentExpression() ?: it.parent - } - - else -> error("resolvedArgument: ${resolvedArgument.javaClass} cant be processed") - } - } - - private fun PsiElement.unwrapArray(arguments: List): PsiElement? { - val arrayInitializer = parent as? PsiArrayInitializerMemberValue ?: return null - val exprIndex = arrayInitializer.initializers.indexOf(this) - if (exprIndex < 0 || exprIndex >= arguments.size) return null - return arguments[exprIndex].getArgumentExpression() - } - - open inner class LightExpressionValue( - delegate: D, - parent: PsiElement, - valueOrigin: AnnotationValueOrigin - ) : LightElementValue(delegate, parent, valueOrigin), PsiExpression { - override fun getType(): PsiType? = delegate.type - } - - inner class LightPsiLiteral( - delegate: PsiLiteralExpression, - parent: PsiElement, - valueOrigin: AnnotationValueOrigin - ) : LightExpressionValue(delegate, parent, valueOrigin), PsiLiteralExpression { - override fun getValue() = delegate.value - } - - inner class LightClassLiteral( - delegate: PsiClassObjectAccessExpression, - parent: PsiElement, - valueOrigin: AnnotationValueOrigin - ) : LightExpressionValue(delegate, parent, valueOrigin), PsiClassObjectAccessExpression { - override fun getType() = delegate.type - override fun getOperand(): PsiTypeElement = delegate.operand - } - - inner class LightArrayInitializerValue( - delegate: PsiArrayInitializerMemberValue, - parent: PsiElement, - valueOrigin: AnnotationValueOrigin - ) : LightElementValue(delegate, parent, valueOrigin), PsiArrayInitializerMemberValue { - private val _initializers by lazyPub { - delegate.initializers.mapIndexed { i, memberValue -> - wrapAnnotationValue(memberValue, this, { - originalExpression.let { ktOrigin -> - when { - ktOrigin is KtCallExpression - && memberValue is PsiAnnotation - && isAnnotationConstructorCall(ktOrigin, memberValue) -> ktOrigin - ktOrigin is KtValueArgumentList -> ktOrigin.arguments.getOrNull(i)?.getArgumentExpression() - ktOrigin is KtCallElement -> ktOrigin.valueArguments.getOrNull(i)?.getArgumentExpression() - ktOrigin is KtCollectionLiteralExpression -> ktOrigin.getInnerExpressions().getOrNull(i) - delegate.initializers.size == 1 -> ktOrigin - else -> null - }.also { - if (it == null) - LOG.error("error wrapping ${memberValue.javaClass} for ${ktOrigin?.javaClass} in ${ktOrigin?.containingFile}", - Attachment( - "source_fragments.txt", - "origin: '${psiReport(ktOrigin)}', delegate: ${psiReport(delegate)}, parent: ${psiReport(parent)}" - ) - ) - } - } - }) - }.toTypedArray() - } - - override fun getInitializers() = _initializers - } - - private fun wrapAnnotationValue(value: PsiAnnotationMemberValue, parent: PsiElement, ktOrigin: AnnotationValueOrigin): PsiAnnotationMemberValue = - when { - value is PsiLiteralExpression -> LightPsiLiteral(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 -> { - val origin = ktOrigin() - val ktCallElement = origin?.asKtCall() - val qualifiedName = value.qualifiedName - if (qualifiedName != null && ktCallElement != null) - KtLightAnnotationForSourceEntry(qualifiedName, ktCallElement, parent, { value }) - else { - LOG.error("can't convert ${origin?.javaClass} to KtCallElement in ${origin?.containingFile} (value = ${value.javaClass})", - Attachment("source_fragments.txt", "origin: '${psiReport(origin)}', value: '${psiReport(value)}'")) - LightElementValue(value, parent, ktOrigin) // or maybe create a LightErrorAnnotationMemberValue instead? - } - } - else -> LightElementValue(value, parent, ktOrigin) - } - override fun isPhysical() = true - override fun getName() = null + override fun getName(): String? = null - private fun wrapAnnotationValue(value: PsiAnnotationMemberValue): PsiAnnotationMemberValue = wrapAnnotationValue(value, this, { - getMemberValueAsCallArgument(value, kotlinOrigin) - }) + override fun findAttributeValue(name: String?) = getAttributeValue(name, true) - override fun findAttributeValue(name: String?) = clsDelegate.findAttributeValue(name)?.let { wrapAnnotationValue(it) } + override fun findDeclaredAttributeValue(name: String?): PsiAnnotationMemberValue? = getAttributeValue(name, false) - override fun findDeclaredAttributeValue(name: String?) = clsDelegate.findDeclaredAttributeValue(name)?.let { wrapAnnotationValue(it) } + private fun getAttributeValue(name: String?, useDefault: Boolean): PsiAnnotationMemberValue? { + val name = name ?: "value" - override fun getParameterList(): PsiAnnotationParameterList = KtLightAnnotationParameterList(super.getParameterList()) + val resolvedCall = kotlinOrigin.getResolvedCall() ?: return null + val callEntry = resolvedCall.valueArguments.entries.find { (param, _) -> param.name.asString() == name } ?: return null - inner class KtLightAnnotationParameterList(private val list: PsiAnnotationParameterList) : KtLightElementBase(this), + val valueArguments = callEntry.value.arguments + val valueArgument = valueArguments.firstOrNull() + val argument = valueArgument?.getArgumentExpression() + if (argument != null) { + val arrayExpected = callEntry.key?.type?.let { KotlinBuiltIns.isArray(it) } ?: false + + if (arrayExpected && (argument is KtStringTemplateExpression || argument is KtConstantExpression || getAnnotationName(argument) != null)) + return KtLightPsiArrayInitializerMemberValue( + PsiTreeUtil.findCommonParent(valueArguments.map { it.getArgumentExpression() }) as KtElement, + this, + { self -> + valueArguments.mapNotNull { + it.getArgumentExpression()?.let { convertToLightAnnotationMemberValue(self, it) } + } + }) + + ktLightAnnotationParameterList.attributes.find { (it as KtLightPsiNameValuePair).valueArgument === valueArgument }?.let { + return it.value + } + } + + if (useDefault && callEntry.key.declaresOrInheritsDefaultValue()) { + val psiElement = callEntry.key.source.getPsi() + when (psiElement) { + is KtParameter -> + return psiElement.defaultValue?.let { convertToLightAnnotationMemberValue(this, it) } + is PsiAnnotationMethod -> + return psiElement.defaultValue + } + } + return null + } + + + override fun getNameReferenceElement(): PsiJavaCodeReferenceElement? { + val reference = (kotlinOrigin as? KtAnnotationEntry)?.typeReference?.reference + ?: (kotlinOrigin.calleeExpression as? KtNameReferenceExpression)?.reference + ?: return null + return KtLightPsiJavaCodeReferenceElement( + kotlinOrigin.navigationElement, + reference, + { super.getNameReferenceElement()!! }) + } + + + private val ktLightAnnotationParameterList by lazyPub { KtLightAnnotationParameterList() } + + override fun getParameterList(): PsiAnnotationParameterList = ktLightAnnotationParameterList + + inner class KtLightAnnotationParameterList() : KtLightElementBase(this), PsiAnnotationParameterList { override val kotlinOrigin get() = null - override fun getAttributes(): Array = list.attributes.map { KtLightPsiNameValuePair(it) }.toTypedArray() - inner class KtLightPsiNameValuePair(private val psiNameValuePair: PsiNameValuePair) : KtLightElementBase(this), - PsiNameValuePair { - override fun setValue(newValue: PsiAnnotationMemberValue): PsiAnnotationMemberValue = psiNameValuePair.setValue(newValue) - override fun getNameIdentifier(): PsiIdentifier? = psiNameValuePair.nameIdentifier - override fun getLiteralValue(): String? = (value as? PsiLiteralValue)?.value?.toString() - override val kotlinOrigin: KtElement? = null - - override fun getValue(): PsiAnnotationMemberValue? = psiNameValuePair.value?.let { wrapAnnotationValue(it) } + private val _attributes: Array by lazyPub { + this@KtLightAnnotationForSourceEntry.kotlinOrigin.valueArguments.map { KtLightPsiNameValuePair(it as KtValueArgument, this) } + .toTypedArray() } + + override fun getAttributes(): Array = _attributes + } @@ -297,10 +171,10 @@ class KtLightAnnotationForSourceEntry( } class KtLightNonSourceAnnotation( - parent: PsiElement, clsDelegate: PsiAnnotation + parent: PsiElement, clsDelegate: PsiAnnotation ) : KtLightAbstractAnnotation(parent, { clsDelegate }) { override val kotlinOrigin: KtAnnotationEntry? get() = null - override fun getQualifiedName() = clsDelegate.qualifiedName + override fun getQualifiedName() = kotlinOrigin?.name ?: clsDelegate.qualifiedName override fun setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify() override fun findAttributeValue(attributeName: String?) = clsDelegate.findAttributeValue(attributeName) override fun findDeclaredAttributeValue(attributeName: String?) = clsDelegate.findDeclaredAttributeValue(attributeName) @@ -349,7 +223,9 @@ class KtLightNullabilityAnnotation(member: KtLightElement<*, PsiModifierListOwne override fun findAttributeValue(attributeName: String?) = null - override fun getQualifiedName(): String? = clsDelegate.qualifiedName + override fun getQualifiedName(): String? = Nullable::class.java.name + + override fun getNameReferenceElement(): PsiJavaCodeReferenceElement? = null override fun findDeclaredAttributeValue(attributeName: String?) = null } @@ -363,18 +239,67 @@ private fun KtElement.getResolvedCall(): ResolvedCall? { return this.getResolvedCall(context) } -private fun isAnnotationConstructorCall(callExpression: KtCallExpression, psiAnnotation: PsiAnnotation) = - (callExpression.getResolvedCall()?.resultingDescriptor as? ClassConstructorDescriptor)?.constructedClass?.fqNameUnsafe?.toString() == psiAnnotation.qualifiedName - -private fun PsiElement.asKtCall(): KtCallElement? = (this as? KtElement)?.getResolvedCall()?.call?.callElement as? KtCallElement - -private fun psiReport(psiElement: PsiElement?): String { - if (psiElement == null) return "null" - val text = try { - psiElement.text +fun convertToLightAnnotationMemberValue(lightParent: PsiElement, argument: KtExpression): PsiAnnotationMemberValue { + val argument = unwrapCall(argument) + when (argument) { + is KtStringTemplateExpression, is KtConstantExpression -> { + return KtLightPsiLiteral(argument, lightParent) + } + is KtCallExpression -> { + val arguments = argument.valueArguments + val annotationName = argument.calleeExpression?.let { getAnnotationName(it) } + if (annotationName != null) { + return KtLightAnnotationForSourceEntry( + annotationName, + argument, + lightParent, + { throw UnsupportedOperationException("cls delegate is not supported for nested annotations") }) + } + val resolvedCall = argument.getResolvedCall() + if (resolvedCall != null && CompileTimeConstantUtils.isArrayFunctionCall(resolvedCall)) + return KtLightPsiArrayInitializerMemberValue( + argument, + lightParent, + { self -> + arguments.mapNotNull { + it.getArgumentExpression()?.let { convertToLightAnnotationMemberValue(self, it) } + } + }) + } + is KtCollectionLiteralExpression -> { + val arguments = argument.getInnerExpressions() + if (arguments.isNotEmpty()) + return KtLightPsiArrayInitializerMemberValue( + argument, + lightParent, + { self -> arguments.mapNotNull { convertToLightAnnotationMemberValue(self, it) } }) + } } - catch (e: Exception) { - "${e.javaClass.simpleName}:${e.message}" + // everything else (like complex constant references) considered as PsiLiteral-s + return KtLightPsiLiteral(argument, lightParent) +} + +private val KtExpression.nameReference: KtNameReferenceExpression? + get() = when { + this is KtConstructorCalleeExpression -> constructorReferenceExpression as? KtNameReferenceExpression + else -> this as? KtNameReferenceExpression } - return "${psiElement.javaClass.canonicalName}[$text]" + +private fun unwrapCall(callee: KtExpression): KtExpression = when (callee) { + is KtDotQualifiedExpression -> callee.lastChild as? KtCallExpression ?: callee + else -> callee +} + +private fun getAnnotationName(callee: KtExpression): String? { + val callee = unwrapCall(callee) + val resultingDescriptor = callee.getResolvedCall()?.resultingDescriptor + if (resultingDescriptor is ClassConstructorDescriptor) { + val ktClass = resultingDescriptor.constructedClass.source.getPsi() as? KtClass + if (ktClass?.isAnnotation() == true) return ktClass.fqName?.toString() + } + if (resultingDescriptor is JavaClassConstructorDescriptor) { + val psiClass = resultingDescriptor.constructedClass.source.getPsi() as? PsiClass + if (psiClass?.isAnnotationType == true) return psiClass.qualifiedName + } + return null } \ No newline at end of file diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt index 19ebb0430b9..62d8235ed55 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt @@ -20,10 +20,7 @@ import com.intellij.psi.* import com.intellij.psi.search.GlobalSearchScope import org.jetbrains.kotlin.asJava.classes.KtLightClass import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade -import org.jetbrains.kotlin.asJava.elements.KtLightAnnotationForSourceEntry -import org.jetbrains.kotlin.asJava.elements.KtLightElement -import org.jetbrains.kotlin.asJava.elements.KtLightIdentifier -import org.jetbrains.kotlin.asJava.elements.KtLightMethod +import org.jetbrains.kotlin.asJava.elements.* import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.load.java.propertyNameByGetMethodName import org.jetbrains.kotlin.load.java.propertyNameBySetMethodName @@ -139,7 +136,7 @@ val PsiElement.unwrapped: PsiElement? get() = when { this is KtLightElement<*, *> -> kotlinOrigin this is KtLightIdentifier -> origin - this is KtLightAnnotationForSourceEntry.LightExpressionValue<*> -> originalExpression + this is KtLightElementBase -> kotlinOrigin else -> this } diff --git a/idea/src/org/jetbrains/kotlin/idea/KotlinLightConstantExpressionEvaluator.kt b/idea/src/org/jetbrains/kotlin/idea/KotlinLightConstantExpressionEvaluator.kt index e099b529b1c..94bc5830f93 100644 --- a/idea/src/org/jetbrains/kotlin/idea/KotlinLightConstantExpressionEvaluator.kt +++ b/idea/src/org/jetbrains/kotlin/idea/KotlinLightConstantExpressionEvaluator.kt @@ -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)) } } diff --git a/idea/tests/org/jetbrains/kotlin/asJava/KtLightAnnotationTest.kt b/idea/tests/org/jetbrains/kotlin/asJava/KtLightAnnotationTest.kt index fac4a44d2f4..39e59113e53 100644 --- a/idea/tests/org/jetbrains/kotlin/asJava/KtLightAnnotationTest.kt +++ b/idea/tests/org/jetbrains/kotlin/asJava/KtLightAnnotationTest.kt @@ -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().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() assertTextAndRange("1", value) + TestCase.assertEquals(PsiType.INT, value.assertInstanceOf().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() { diff --git a/idea/tests/org/jetbrains/kotlin/asJava/KtLightAnnotationTest.kt.173 b/idea/tests/org/jetbrains/kotlin/asJava/KtLightAnnotationTest.kt.173 index 0df3fdf84e6..21b1a031e45 100644 --- a/idea/tests/org/jetbrains/kotlin/asJava/KtLightAnnotationTest.kt.173 +++ b/idea/tests/org/jetbrains/kotlin/asJava/KtLightAnnotationTest.kt.173 @@ -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().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() assertTextAndRange("1", value) + TestCase.assertEquals(PsiType.INT, value.assertInstanceOf().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() { diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt index 5c13c82824f..706ed14eaf6 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt @@ -334,11 +334,11 @@ internal object KotlinConverter { is KtExpression -> KotlinConverter.convertExpression(element, givenParent, requiredType) is KtLambdaArgument -> element.getLambdaExpression()?.let { KotlinConverter.convertExpression(it, givenParent, requiredType) } - is KtLightAnnotationForSourceEntry.LightExpressionValue<*> -> { - val expression = element.originalExpression + is KtLightElementBase -> { + val expression = element.kotlinOrigin when (expression) { is KtExpression -> KotlinConverter.convertExpression(expression, givenParent, requiredType) - else -> el { UastEmptyExpression } + else -> el { UastEmptyExpression(givenParent) } } } is KtLiteralStringTemplateEntry, is KtEscapeStringTemplateEntry -> el(build(::KotlinStringULiteralExpression)) diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt.172 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt.172 index 55217620a1e..1c02130282e 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt.172 +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt.172 @@ -314,8 +314,8 @@ internal object KotlinConverter { is KtExpression -> KotlinConverter.convertExpression(element, givenParent, requiredType) is KtLambdaArgument -> element.getLambdaExpression()?.let { KotlinConverter.convertExpression(it, givenParent, requiredType) } - is KtLightAnnotationForSourceEntry.LightExpressionValue<*> -> { - val expression = element.originalExpression + is KtLightElementBase -> { + val expression = element.kotlinOrigin when (expression) { is KtExpression -> KotlinConverter.convertExpression(expression, givenParent, requiredType) else -> el { UastEmptyExpression } diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt.173 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt.173 index 6d49284e33f..6ab7aed3629 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt.173 +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt.173 @@ -329,8 +329,8 @@ internal object KotlinConverter { is KtExpression -> KotlinConverter.convertExpression(element, givenParent, requiredType) is KtLambdaArgument -> element.getLambdaExpression()?.let { KotlinConverter.convertExpression(it, givenParent, requiredType) } - is KtLightAnnotationForSourceEntry.LightExpressionValue<*> -> { - val expression = element.originalExpression + is KtLightElementBase -> { + val expression = element.kotlinOrigin when (expression) { is KtExpression -> KotlinConverter.convertExpression(expression, givenParent, requiredType) else -> el { UastEmptyExpression }