From 71583dc94a3e96d48fb49acc77c95ca76e98eed0 Mon Sep 17 00:00:00 2001 From: Jinseong Jeon Date: Mon, 16 Jun 2025 22:53:32 -0700 Subject: [PATCH] KT UAST: resolve annotation on setter parameter To do so, for parameter without source PSI (i.e., synthetic), we keep nullable annotations from java PSI, while restoring user-given annotations on source. ^KTIJ-34365 fixed GitOrigin-RevId: b80b98e63bade9f5e78944c4e4c68aa0fcd01c69 --- .../declarations/AbstractKotlinUVariable.kt | 23 ++++++++-- .../kotlin/UastResolveApiFixtureTestBase.kt | 46 +++++++++++++++++++ .../uast/test/FirUastResolveApiFixtureTest.kt | 4 ++ .../FE1UastResolveApiFixtureTest.kt | 4 ++ 4 files changed, 73 insertions(+), 4 deletions(-) diff --git a/plugins/kotlin/uast/uast-kotlin-base/src/org/jetbrains/uast/kotlin/declarations/AbstractKotlinUVariable.kt b/plugins/kotlin/uast/uast-kotlin-base/src/org/jetbrains/uast/kotlin/declarations/AbstractKotlinUVariable.kt index 7e4b05e7ca5c..e48532fd3087 100644 --- a/plugins/kotlin/uast/uast-kotlin-base/src/org/jetbrains/uast/kotlin/declarations/AbstractKotlinUVariable.kt +++ b/plugins/kotlin/uast/uast-kotlin-base/src/org/jetbrains/uast/kotlin/declarations/AbstractKotlinUVariable.kt @@ -3,6 +3,8 @@ package org.jetbrains.uast.kotlin import com.intellij.psi.* import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.NotNull +import org.jetbrains.annotations.Nullable import org.jetbrains.kotlin.asJava.elements.KtLightAbstractAnnotation import org.jetbrains.kotlin.asJava.elements.KtLightElement import org.jetbrains.kotlin.psi.* @@ -70,11 +72,24 @@ abstract class AbstractKotlinUVariable( } private fun AbstractKotlinUVariable.buildAnnotations(): List { - val sourcePsi = sourcePsi ?: return javaPsi.annotations.map { WrappedUAnnotation(it, this) } val annotations = SmartList() - val hasInheritedGenericType = baseResolveProviderService.hasInheritedGenericType(sourcePsi) - if (!hasInheritedGenericType) { - annotations.add(KotlinNullabilityUAnnotation(baseResolveProviderService, sourcePsi, this)) + val sourcePsi = sourcePsi + if (sourcePsi != null) { + val hasInheritedGenericType = baseResolveProviderService.hasInheritedGenericType(sourcePsi) + if (!hasInheritedGenericType) { + annotations.add( + KotlinNullabilityUAnnotation(baseResolveProviderService, sourcePsi, this) + ) + } + } else { + javaPsi.annotations.filter { psiAnnotation -> + val fqName = psiAnnotation.qualifiedName + fqName == NotNull::class.qualifiedName || fqName == Nullable::class.qualifiedName + }.forEach { psiAnnotation -> + annotations.add( + WrappedUAnnotation(psiAnnotation, this) + ) + } } // NB: we can't use sourcePsi.annotationEntries directly due to annotation use-site targets. baseResolveProviderService.getPsiAnnotations(javaPsi).asSequence() diff --git a/plugins/kotlin/uast/uast-kotlin-base/tests/test/org/jetbrains/uast/test/common/kotlin/UastResolveApiFixtureTestBase.kt b/plugins/kotlin/uast/uast-kotlin-base/tests/test/org/jetbrains/uast/test/common/kotlin/UastResolveApiFixtureTestBase.kt index 6d49b64d4e0f..ed19fd03bf5c 100644 --- a/plugins/kotlin/uast/uast-kotlin-base/tests/test/org/jetbrains/uast/test/common/kotlin/UastResolveApiFixtureTestBase.kt +++ b/plugins/kotlin/uast/uast-kotlin-base/tests/test/org/jetbrains/uast/test/common/kotlin/UastResolveApiFixtureTestBase.kt @@ -1474,6 +1474,52 @@ interface UastResolveApiFixtureTestBase { ) } + fun checkResolveAnnotationOnSetparam(myFixture: JavaCodeInsightTestFixture) { + myFixture.configureByText( + "main.kt", + """ + package test.pkg + + annotation class Anno(val attr: Int) + + class Test { + @get:Anno(attr = 42) + @setparam:Anno(attr = 21) + var prop = 0 + } + """.trimIndent() + ) + + val uFile = myFixture.file.toUElement()!! + var cnt = 0 + uFile.accept( + object : AbstractUastVisitor() { + override fun visitMethod(node: UMethod): Boolean { + val txt = node.sourcePsi?.text + when (node.name) { + "getProp" -> { + cnt++ + val anno = node.uAnnotations.single() + val resolved = anno.resolve() + TestCase.assertNotNull(txt, resolved) + TestCase.assertEquals("test.pkg.Anno", resolved!!.qualifiedName) + } + "setProp" -> { + cnt++ + val param = node.uastParameters.single() + val anno = param.uAnnotations.single() + val resolved = anno.resolve() + TestCase.assertNotNull(txt, resolved) + TestCase.assertEquals("test.pkg.Anno", resolved!!.qualifiedName) + } + } + return super.visitMethod(node) + } + } + ) + TestCase.assertEquals(2, cnt) + } + fun checkResolveDataClassSyntheticMember(myFixture: JavaCodeInsightTestFixture, isK2: Boolean) { myFixture.configureByText( "main.kt", diff --git a/plugins/kotlin/uast/uast-kotlin-fir/tests/test/org/jetbrains/fir/uast/test/FirUastResolveApiFixtureTest.kt b/plugins/kotlin/uast/uast-kotlin-fir/tests/test/org/jetbrains/fir/uast/test/FirUastResolveApiFixtureTest.kt index f508d91a8749..890d02ea9038 100644 --- a/plugins/kotlin/uast/uast-kotlin-fir/tests/test/org/jetbrains/fir/uast/test/FirUastResolveApiFixtureTest.kt +++ b/plugins/kotlin/uast/uast-kotlin-fir/tests/test/org/jetbrains/fir/uast/test/FirUastResolveApiFixtureTest.kt @@ -218,6 +218,10 @@ class FirUastResolveApiFixtureTest : KotlinLightCodeInsightFixtureTestCase(), Ua checkOperatorMultiResolvable(myFixture) } + fun testResolveAnnotationOnSetparam() { + checkResolveAnnotationOnSetparam(myFixture) + } + fun testResolveDataClassSyntheticMember() { checkResolveDataClassSyntheticMember(myFixture, isK2 = true) } diff --git a/plugins/kotlin/uast/uast-kotlin/tests/test/org/jetbrains/uast/test/kotlin/comparison/FE1UastResolveApiFixtureTest.kt b/plugins/kotlin/uast/uast-kotlin/tests/test/org/jetbrains/uast/test/kotlin/comparison/FE1UastResolveApiFixtureTest.kt index e138fd8a7d44..b7d83d25e32c 100644 --- a/plugins/kotlin/uast/uast-kotlin/tests/test/org/jetbrains/uast/test/kotlin/comparison/FE1UastResolveApiFixtureTest.kt +++ b/plugins/kotlin/uast/uast-kotlin/tests/test/org/jetbrains/uast/test/kotlin/comparison/FE1UastResolveApiFixtureTest.kt @@ -178,6 +178,10 @@ class FE1UastResolveApiFixtureTest : KotlinLightCodeInsightFixtureTestCase(), Ua checkOperatorMultiResolvable(myFixture) } + fun testResolveAnnotationOnSetparam() { + checkResolveAnnotationOnSetparam(myFixture) + } + fun testResolveDataClassSyntheticMember() { checkResolveDataClassSyntheticMember(myFixture, isK2 = false) }