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 fe742e1db26..29a39ead0ec 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/KotlinUastLanguagePlugin.kt @@ -29,8 +29,6 @@ import org.jetbrains.kotlin.asJava.elements.* import org.jetbrains.kotlin.asJava.toLightClass import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.config.LanguageVersionSettings -import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor -import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.ConstructorDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.idea.KotlinLanguage @@ -226,14 +224,7 @@ class KotlinUastLanguagePlugin : UastLanguagePlugin { is KtAnnotationEntry -> el(build(::KotlinUAnnotation)) is KtCallExpression -> if (requiredType != null && UAnnotation::class.java.isAssignableFrom(requiredType)) { - el { - val classDescriptor = - (original.getResolvedCall(original.analyze())?.resultingDescriptor as? ClassConstructorDescriptor)?.constructedClass - if (classDescriptor?.kind == ClassKind.ANNOTATION_CLASS) - KotlinUNestedAnnotation(original, givenParent, classDescriptor) - else - null - } + el { KotlinUNestedAnnotation.tryCreate(original, givenParent) } } else null is KtLightAnnotationForSourceEntry -> convertElement(original.kotlinOrigin, givenParent, requiredType) else -> null 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 50398d478f2..20eb4fbaccd 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 @@ -29,8 +29,6 @@ import org.jetbrains.kotlin.asJava.elements.* import org.jetbrains.kotlin.asJava.toLightClass import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.config.LanguageVersionSettings -import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor -import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.ConstructorDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.idea.KotlinLanguage @@ -226,14 +224,7 @@ class KotlinUastLanguagePlugin : UastLanguagePlugin { is KtAnnotationEntry -> el(build(::KotlinUAnnotation)) is KtCallExpression -> if (requiredType != null && UAnnotation::class.java.isAssignableFrom(requiredType)) { - el { - val classDescriptor = - (original.getResolvedCall(original.analyze())?.resultingDescriptor as? ClassConstructorDescriptor)?.constructedClass - if (classDescriptor?.kind == ClassKind.ANNOTATION_CLASS) - KotlinUNestedAnnotation(original, givenParent, classDescriptor) - else - null - } + el { KotlinUNestedAnnotation.tryCreate(original, givenParent) } } else null is KtLightAnnotationForSourceEntry -> convertElement(original.kotlinOrigin, givenParent, requiredType) else -> null diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUAnnotation.kt b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUAnnotation.kt index 222038a1863..08113a6d2d3 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUAnnotation.kt +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUAnnotation.kt @@ -2,9 +2,11 @@ package org.jetbrains.uast.kotlin import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement import org.jetbrains.kotlin.asJava.toLightAnnotation +import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor +import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget import org.jetbrains.kotlin.name.FqNameUnsafe import org.jetbrains.kotlin.psi.* @@ -21,25 +23,26 @@ import org.jetbrains.uast.* import org.jetbrains.uast.kotlin.declarations.KotlinUIdentifier import org.jetbrains.uast.kotlin.declarations.KotlinUMethod -abstract class KotlinUAnnotationBase( - final override val psi: KtElement, +abstract class KotlinUAnnotationBase( + final override val sourcePsi: T, givenParent: UElement? ) : KotlinAbstractUElement(givenParent), UAnnotationEx, UAnchorOwner { abstract override val javaPsi: PsiAnnotation? - final override val sourcePsi = psi + final override val psi: PsiElement = sourcePsi protected abstract fun annotationUseSiteTarget(): AnnotationUseSiteTarget? - private val resolvedCall: ResolvedCall<*>? by lz { psi.getResolvedCall(psi.analyze()) } + private val resolvedCall: ResolvedCall<*>? get () = sourcePsi.getResolvedCall(sourcePsi.analyze()) - override val qualifiedName: String? - get() = annotationClassDescriptor.takeUnless(ErrorUtils::isError) + override val qualifiedName: String? by lz { + computeClassDescriptor().takeUnless(ErrorUtils::isError) ?.fqNameUnsafe ?.takeIf(FqNameUnsafe::isSafe) ?.toSafe() ?.toString() + } override val attributeValues: List by lz { resolvedCall?.valueArguments?.entries?.mapNotNull { @@ -55,12 +58,9 @@ abstract class KotlinUAnnotationBase( } ?: emptyList() } - protected abstract val annotationClassDescriptor: ClassDescriptor? + protected abstract fun computeClassDescriptor(): ClassDescriptor? - override fun resolve(): PsiClass? { - val descriptor = annotationClassDescriptor ?: return null - return descriptor.toSource()?.getMaybeLightElement(this) as? PsiClass - } + override fun resolve(): PsiClass? = computeClassDescriptor()?.toSource()?.getMaybeLightElement(this) as? PsiClass override fun findAttributeValue(name: String?): UExpression? = findDeclaredAttributeValue(name) ?: findAttributeDefaultValue(name ?: "value") @@ -82,7 +82,7 @@ abstract class KotlinUAnnotationBase( } private fun findAttributeDefaultValue(name: String): UExpression? { - val parameter = annotationClassDescriptor + val parameter = computeClassDescriptor() ?.unsubstitutedPrimaryConstructor ?.valueParameters ?.find { it.name.asString() == name } ?: return null @@ -103,18 +103,16 @@ abstract class KotlinUAnnotationBase( } class KotlinUAnnotation( - val annotationEntry: KtAnnotationEntry, + annotationEntry: KtAnnotationEntry, givenParent: UElement? -) : KotlinUAnnotationBase(annotationEntry, givenParent), UAnnotation { +) : KotlinUAnnotationBase(annotationEntry, givenParent), UAnnotation { override val javaPsi = annotationEntry.toLightAnnotation() - private val resolvedAnnotation: AnnotationDescriptor? by lz { annotationEntry.analyze()[BindingContext.ANNOTATION, annotationEntry] } + override fun computeClassDescriptor(): ClassDescriptor? = + sourcePsi.analyze()[BindingContext.ANNOTATION, sourcePsi]?.annotationClass - override val annotationClassDescriptor: ClassDescriptor? - get() = resolvedAnnotation?.annotationClass - - override fun annotationUseSiteTarget() = annotationEntry.useSiteTarget?.getAnnotationUseSiteTarget() + override fun annotationUseSiteTarget() = sourcePsi.useSiteTarget?.getAnnotationUseSiteTarget() override val uastAnchor by lazy { KotlinUIdentifier( @@ -128,14 +126,13 @@ class KotlinUAnnotation( } -class KotlinUNestedAnnotation( - private val original: KtCallExpression, - givenParent: UElement?, - private val classDescriptor: ClassDescriptor? -) : KotlinUAnnotationBase(original, givenParent) { +class KotlinUNestedAnnotation private constructor( + original: KtCallExpression, + givenParent: UElement? +) : KotlinUAnnotationBase(original, givenParent) { override val javaPsi: PsiAnnotation? by lazy { original.toLightAnnotation() } - override val annotationClassDescriptor: ClassDescriptor? - get() = classDescriptor + + override fun computeClassDescriptor(): ClassDescriptor? = classDescriptor(sourcePsi) override fun annotationUseSiteTarget(): AnnotationUseSiteTarget? = null @@ -147,6 +144,18 @@ class KotlinUNestedAnnotation( ) } + companion object { + fun tryCreate(original: KtCallExpression, givenParent: UElement?): KotlinUNestedAnnotation? { + if (classDescriptor(original)?.kind == ClassKind.ANNOTATION_CLASS) + return KotlinUNestedAnnotation(original, givenParent) + else + return null + } + + private fun classDescriptor(original: KtCallExpression) = + (original.getResolvedCall(original.analyze())?.resultingDescriptor as? ClassConstructorDescriptor)?.constructedClass + } + } diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUAnnotation.kt.173 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUAnnotation.kt.173 index 486c8ecaa91..72c0eed25da 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUAnnotation.kt.173 +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUAnnotation.kt.173 @@ -2,9 +2,11 @@ package org.jetbrains.uast.kotlin import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement import org.jetbrains.kotlin.asJava.toLightAnnotation +import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor +import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget import org.jetbrains.kotlin.name.FqNameUnsafe import org.jetbrains.kotlin.psi.* @@ -20,25 +22,26 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance import org.jetbrains.uast.* import org.jetbrains.uast.kotlin.declarations.KotlinUMethod -abstract class KotlinUAnnotationBase( - final override val psi: KtElement, +abstract class KotlinUAnnotationBase( + final override val sourcePsi: T, givenParent: UElement? ) : KotlinAbstractUElement(givenParent), UAnnotation { abstract override val javaPsi: PsiAnnotation? - final override val sourcePsi = psi + final override val psi: PsiElement = sourcePsi protected abstract fun annotationUseSiteTarget(): AnnotationUseSiteTarget? - private val resolvedCall: ResolvedCall<*>? by lz { psi.getResolvedCall(psi.analyze()) } + private val resolvedCall: ResolvedCall<*>? get () = sourcePsi.getResolvedCall(sourcePsi.analyze()) - override val qualifiedName: String? - get() = annotationClassDescriptor.takeUnless(ErrorUtils::isError) + override val qualifiedName: String? by lz { + computeClassDescriptor().takeUnless(ErrorUtils::isError) ?.fqNameUnsafe ?.takeIf(FqNameUnsafe::isSafe) ?.toSafe() ?.toString() + } override val attributeValues: List by lz { resolvedCall?.valueArguments?.entries?.mapNotNull { @@ -54,12 +57,9 @@ abstract class KotlinUAnnotationBase( } ?: emptyList() } - protected abstract val annotationClassDescriptor: ClassDescriptor? + protected abstract fun computeClassDescriptor(): ClassDescriptor? - override fun resolve(): PsiClass? { - val descriptor = annotationClassDescriptor ?: return null - return descriptor.toSource()?.getMaybeLightElement(this) as? PsiClass - } + override fun resolve(): PsiClass? = computeClassDescriptor()?.toSource()?.getMaybeLightElement(this) as? PsiClass override fun findAttributeValue(name: String?): UExpression? = findDeclaredAttributeValue(name) ?: findAttributeDefaultValue(name ?: "value") @@ -81,7 +81,7 @@ abstract class KotlinUAnnotationBase( } private fun findAttributeDefaultValue(name: String): UExpression? { - val parameter = annotationClassDescriptor + val parameter = computeClassDescriptor() ?.unsubstitutedPrimaryConstructor ?.valueParameters ?.find { it.name.asString() == name } ?: return null @@ -102,32 +102,41 @@ abstract class KotlinUAnnotationBase( } class KotlinUAnnotation( - val annotationEntry: KtAnnotationEntry, + annotationEntry: KtAnnotationEntry, givenParent: UElement? -) : KotlinUAnnotationBase(annotationEntry, givenParent), UAnnotation { +) : KotlinUAnnotationBase(annotationEntry, givenParent), UAnnotation { override val javaPsi = annotationEntry.toLightAnnotation() - private val resolvedAnnotation: AnnotationDescriptor? by lz { annotationEntry.analyze()[BindingContext.ANNOTATION, annotationEntry] } + override fun computeClassDescriptor(): ClassDescriptor? = + sourcePsi.analyze()[BindingContext.ANNOTATION, sourcePsi]?.annotationClass - override val annotationClassDescriptor: ClassDescriptor? - get() = resolvedAnnotation?.annotationClass - - override fun annotationUseSiteTarget() = annotationEntry.useSiteTarget?.getAnnotationUseSiteTarget() + override fun annotationUseSiteTarget() = sourcePsi.useSiteTarget?.getAnnotationUseSiteTarget() } -class KotlinUNestedAnnotation( +class KotlinUNestedAnnotation private constructor( original: KtCallExpression, - givenParent: UElement?, - private val classDescriptor: ClassDescriptor? -) : KotlinUAnnotationBase(original, givenParent) { + givenParent: UElement? +) : KotlinUAnnotationBase(original, givenParent) { override val javaPsi: PsiAnnotation? by lazy { original.toLightAnnotation() } - override val annotationClassDescriptor: ClassDescriptor? - get() = classDescriptor + + override fun computeClassDescriptor(): ClassDescriptor? = classDescriptor(sourcePsi) override fun annotationUseSiteTarget(): AnnotationUseSiteTarget? = null + companion object { + fun tryCreate(original: KtCallExpression, givenParent: UElement?): KotlinUNestedAnnotation? { + if (classDescriptor(original)?.kind == ClassKind.ANNOTATION_CLASS) + return KotlinUNestedAnnotation(original, givenParent) + else + return null + } + + private fun classDescriptor(original: KtCallExpression) = + (original.getResolvedCall(original.analyze())?.resultingDescriptor as? ClassConstructorDescriptor)?.constructedClass + } + } diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt index ed3267c7825..7cc4f3ed77d 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt @@ -43,9 +43,8 @@ class KotlinUFunctionCallExpression( constructor(psi: KtCallElement, uastParent: UElement?) : this(psi, uastParent, null) - private val resolvedCall by lz { - _resolvedCall ?: psi.getResolvedCall(psi.analyze()) - } + private val resolvedCall + get() = _resolvedCall ?: psi.getResolvedCall(psi.analyze()) override val receiverType by lz { val resolvedCall = this.resolvedCall ?: return@lz null diff --git a/plugins/uast-kotlin/tests/AbstractKotlinRenderLogTest.kt b/plugins/uast-kotlin/tests/AbstractKotlinRenderLogTest.kt index f65e2b0aac7..e5599b1fd4e 100644 --- a/plugins/uast-kotlin/tests/AbstractKotlinRenderLogTest.kt +++ b/plugins/uast-kotlin/tests/AbstractKotlinRenderLogTest.kt @@ -1,9 +1,15 @@ package org.jetbrains.uast.test.kotlin +import com.intellij.openapi.util.Conditions import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiRecursiveElementVisitor import com.intellij.psi.impl.source.tree.LeafPsiElement +import com.intellij.util.PairProcessor +import com.intellij.util.ref.DebugReflectionUtil +import junit.framework.TestCase +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.utils.addToStdlib.assertedCast @@ -37,6 +43,7 @@ abstract class AbstractKotlinRenderLogTest : AbstractKotlinUastTest(), RenderLog file.checkContainingFileForAllElements() file.checkJvmDeclarationsImplementations() + file.checkDescriptorsLeak() } private fun checkParentConsistency(file: UFile) { @@ -106,6 +113,15 @@ abstract class AbstractKotlinRenderLogTest : AbstractKotlinUastTest(), RenderLog }) } + private fun UFile.checkDescriptorsLeak() { + accept(object : UastVisitor { + override fun visitElement(node: UElement): Boolean { + checkDescriptorsLeak(node) + return false + } + }) + } + private fun UFile.checkJvmDeclarationsImplementations() { accept(object : UastVisitor { override fun visitElement(node: UElement): Boolean { @@ -132,6 +148,22 @@ abstract class AbstractKotlinRenderLogTest : AbstractKotlinUastTest(), RenderLog } } +private val descriptorsClasses = listOf(AnnotationDescriptor::class, DeclarationDescriptor::class) + +fun checkDescriptorsLeak(node: UElement) { + DebugReflectionUtil.walkObjects( + 10, + mapOf(node to node.javaClass.name), + Any::class.java, + Conditions.alwaysTrue(), + PairProcessor { value, backLink -> + descriptorsClasses.find { it.isInstance(value) }?.let { + TestCase.fail("""Leaked descriptor ${it.qualifiedName} in ${node.javaClass.name}\n$backLink""") + false + } ?: true + }) +} + private fun PsiFile.clearUastCaches() { accept(object : PsiRecursiveElementVisitor() { override fun visitElement(element: PsiElement) { diff --git a/plugins/uast-kotlin/tests/KotlinUastApiTest.kt b/plugins/uast-kotlin/tests/KotlinUastApiTest.kt index 9fa78d5d702..05ab8df7720 100644 --- a/plugins/uast-kotlin/tests/KotlinUastApiTest.kt +++ b/plugins/uast-kotlin/tests/KotlinUastApiTest.kt @@ -306,6 +306,7 @@ class KotlinUastApiTest : AbstractKotlinUastTest() { .toUElementOfType() ?: throw AssertionError("haven't got annotation from $referenceExpression(${referenceExpression?.javaClass})") + checkDescriptorsLeak(convertedUAnnotation) assertEquals("Annotation", convertedUAnnotation.qualifiedName) val lightAnnotation = convertedUAnnotation.getAsJavaPsiElement(PsiAnnotation::class.java) ?: throw AssertionError("can't get lightAnnotation from $convertedUAnnotation")