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

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