diff --git a/analysis/light-classes-base/src/org/jetbrains/kotlin/asJava/LightClassUtil.kt b/analysis/light-classes-base/src/org/jetbrains/kotlin/asJava/LightClassUtil.kt index 8c7027e00fe..65193adea69 100644 --- a/analysis/light-classes-base/src/org/jetbrains/kotlin/asJava/LightClassUtil.kt +++ b/analysis/light-classes-base/src/org/jetbrains/kotlin/asJava/LightClassUtil.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.asJava +import com.intellij.openapi.util.NlsSafe import com.intellij.psi.PsiClass import com.intellij.psi.PsiField import com.intellij.psi.PsiMethod @@ -18,9 +19,12 @@ import org.jetbrains.kotlin.asJava.elements.KtLightElement import org.jetbrains.kotlin.asJava.elements.KtLightField import org.jetbrains.kotlin.asJava.elements.KtLightMethod import org.jetbrains.kotlin.asJava.elements.isSetter +import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget import org.jetbrains.kotlin.load.java.JvmAbi +import org.jetbrains.kotlin.name.JvmNames import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject +import org.jetbrains.kotlin.resolve.DataClassResolver import org.jetbrains.kotlin.utils.checkWithAttachment object LightClassUtil { @@ -43,12 +47,31 @@ object LightClassUtil { getLightClassAccessorMethods(accessor).firstOrNull() fun getLightClassAccessorMethods(accessor: KtPropertyAccessor): List { - val property = accessor.getNonStrictParentOfType() ?: return emptyList() - val wrappers = getPsiMethodWrappers(property) - return wrappers.filter { wrapper -> - (accessor.isGetter && !JvmAbi.isSetterName(wrapper.name)) || - (accessor.isSetter && JvmAbi.isSetterName(wrapper.name)) - }.toList() + val property = accessor.property + val customNameAnnoProvided = + accessor.annotationEntries.find { JvmNames.JVM_NAME.shortName() == it.shortName } != null || property.isSpecialNameProvided() + val propertyName = accessor.property.name ?: return emptyList() + val wrappers = getPsiMethodWrappers(property) { wrapper -> + val wrapperName = wrapper.name + if (customNameAnnoProvided) { + // cls with loaded text doesn't preserve annotation arguments thus we can't rely on the name + // accept everything but the the opposite accessors + if (accessor.isGetter) !JvmAbi.isSetterName(wrapperName) else !JvmAbi.isGetterName(wrapperName) + } else if (accessor.isGetter) { + val getterName = JvmAbi.getterName(propertyName) + wrapperName == getterName || + isMangled(wrapperName, getterName) + } else { + val setterName = JvmAbi.setterName(propertyName) + wrapperName == setterName || isMangled(wrapperName, setterName) + } + } + return wrappers.toList() + } + + private fun isMangled(wrapperName: @NlsSafe String, prefix: String): Boolean { + //see KT-54803 for other mangling strategies + return wrapperName.startsWith("$prefix$") } fun getLightFieldForCompanionObject(companionObject: KtClassOrObject): PsiField? { @@ -124,10 +147,14 @@ object LightClassUtil { return getPsiMethodWrappers(declaration).firstOrNull() } - private fun getPsiMethodWrappers(declaration: KtDeclaration, name: String? = null): Sequence = + private fun getPsiMethodWrappers( + declaration: KtDeclaration, + name: String? = null, + nameFilter: (KtLightMethod) -> Boolean = { name == null || name == it.name } + ): Sequence = getWrappingClasses(declaration).flatMap { it.methods.asSequence() } .filterIsInstance() - .filter { name == null || name == it.name } + .filter(nameFilter) .filter { it.kotlinOrigin === declaration || it.navigationElement === declaration } private fun getWrappingClass(declaration: KtDeclaration): PsiClass? { @@ -178,12 +205,42 @@ object LightClassUtil { return PsiTreeUtil.getParentOfType(declaration, KtFunction::class.java, KtProperty::class.java) == null } - private fun extractPropertyAccessors( - ktDeclaration: KtDeclaration, - specialGetter: PsiMethod?, specialSetter: PsiMethod? - ): PropertyAccessorsPsiMethods { + private fun KtDeclaration.isSpecialNameProvided(): Boolean { + return annotationEntries.any { anno -> + val target = if (JvmNames.JVM_NAME.shortName() == anno.shortName) anno.useSiteTarget?.getAnnotationUseSiteTarget() else null + target == AnnotationUseSiteTarget.PROPERTY_GETTER || target == AnnotationUseSiteTarget.PROPERTY_SETTER + } + } - val (setters, getters) = getPsiMethodWrappers(ktDeclaration).partition { it.isSetter } + private fun extractPropertyAccessors( + ktDeclaration: T, + specialGetter: PsiMethod?, + specialSetter: PsiMethod? + ): PropertyAccessorsPsiMethods where T : KtValVarKeywordOwner, T : KtNamedDeclaration { + + val accessorWrappers = when { + ktDeclaration is KtProperty && noAdditionalAccessorsExpected(ktDeclaration, specialSetter, specialGetter) -> { + emptySequence() + } + ktDeclaration.isSpecialNameProvided() -> { + getPsiMethodWrappers(ktDeclaration) + } + else -> { + val currentName = ktDeclaration.name + val getterName = currentName?.let { JvmAbi.getterName(currentName) } + val setterName = currentName?.let { JvmAbi.setterName(currentName) } + getPsiMethodWrappers(ktDeclaration) { wrapper -> + val wrapperName = wrapper.name + currentName == null || + currentName == wrapperName || + wrapperName == getterName || isMangled(wrapperName, getterName!!) || + wrapperName == setterName || isMangled(wrapperName, setterName!!) || + DataClassResolver.isComponentLike(wrapperName) + } + } + } + + val (setters, getters) = accessorWrappers.partition { it.isSetter } val allGetters = listOfNotNull(specialGetter) + getters.filterNot { it == specialGetter } val allSetters = listOfNotNull(specialSetter) + setters.filterNot { it == specialSetter } @@ -197,6 +254,22 @@ object LightClassUtil { ) } + private fun noAdditionalAccessorsExpected( + ktDeclaration: KtProperty, + specialSetter: PsiMethod?, + specialGetter: PsiMethod?, + ): Boolean { + val containingClassOrObject = ktDeclaration.containingClassOrObject + if ((containingClassOrObject as? KtObjectDeclaration)?.isCompanion() == true) { + return false + } + return if (ktDeclaration.isVar) { + specialSetter != null && specialGetter != null + } else { + specialGetter != null + } + } + class PropertyAccessorsPsiMethods( val getter: PsiMethod?, val setter: PsiMethod?, diff --git a/analysis/symbol-light-classes/testData/lightElements/mangledName.kt b/analysis/symbol-light-classes/testData/lightElements/mangledName.kt new file mode 100644 index 00000000000..ddc8607c064 --- /dev/null +++ b/analysis/symbol-light-classes/testData/lightElements/mangledName.kt @@ -0,0 +1,5 @@ +// EXPECTED: org.jetbrains.kotlin.light.classes.symbol.methods.SymbolLightAccessorMethod +// EXPECTED: org.jetbrains.kotlin.light.classes.symbol.fields.SymbolLightFieldForProperty +class Foo { + internal val p: Int = 42 +} \ No newline at end of file diff --git a/analysis/symbol-light-classes/testData/lightElements/mangledNameWithAnnotations.kt b/analysis/symbol-light-classes/testData/lightElements/mangledNameWithAnnotations.kt new file mode 100644 index 00000000000..0498ea96603 --- /dev/null +++ b/analysis/symbol-light-classes/testData/lightElements/mangledNameWithAnnotations.kt @@ -0,0 +1,6 @@ +// EXPECTED: org.jetbrains.kotlin.light.classes.symbol.methods.SymbolLightAccessorMethod +// EXPECTED: org.jetbrains.kotlin.light.classes.symbol.fields.SymbolLightFieldForProperty +class Foo { + @get:JvmName("getBar") + val p: Int = 42 +} \ No newline at end of file diff --git a/analysis/symbol-light-classes/testData/lightElements/propertyAccessor.kt b/analysis/symbol-light-classes/testData/lightElements/propertyAccessor.kt new file mode 100644 index 00000000000..3678e878678 --- /dev/null +++ b/analysis/symbol-light-classes/testData/lightElements/propertyAccessor.kt @@ -0,0 +1,3 @@ +// EXPECTED: org.jetbrains.kotlin.light.classes.symbol.methods.SymbolLightAccessorMethod +val p: Int = 42 + get \ No newline at end of file diff --git a/analysis/symbol-light-classes/testData/lightElements/propertyAccessorWithAnnotation.kt b/analysis/symbol-light-classes/testData/lightElements/propertyAccessorWithAnnotation.kt new file mode 100644 index 00000000000..ffa91d9eb2c --- /dev/null +++ b/analysis/symbol-light-classes/testData/lightElements/propertyAccessorWithAnnotation.kt @@ -0,0 +1,4 @@ +// EXPECTED: org.jetbrains.kotlin.light.classes.symbol.methods.SymbolLightAccessorMethod +val p: Int = 42 + @JvmName("getBar") + get \ No newline at end of file diff --git a/analysis/symbol-light-classes/testData/lightElements/propertyAccessorWithImplicitAnnotation.kt b/analysis/symbol-light-classes/testData/lightElements/propertyAccessorWithImplicitAnnotation.kt new file mode 100644 index 00000000000..6f87f8ba3b9 --- /dev/null +++ b/analysis/symbol-light-classes/testData/lightElements/propertyAccessorWithImplicitAnnotation.kt @@ -0,0 +1,4 @@ +// EXPECTED: org.jetbrains.kotlin.light.classes.symbol.methods.SymbolLightAccessorMethod +@get:JvmName("getBar") +val p: Int = 42 + get \ No newline at end of file diff --git a/analysis/symbol-light-classes/testData/lightElements/propertyWithExplicitAccessors.kt b/analysis/symbol-light-classes/testData/lightElements/propertyWithExplicitAccessors.kt new file mode 100644 index 00000000000..a657207b9d6 --- /dev/null +++ b/analysis/symbol-light-classes/testData/lightElements/propertyWithExplicitAccessors.kt @@ -0,0 +1,3 @@ +// EXPECTED: org.jetbrains.kotlin.light.classes.symbol.methods.SymbolLightAccessorMethod +val p: Int + get() = 42 \ No newline at end of file diff --git a/analysis/symbol-light-classes/testData/lightElements/propertyWithExplicitAccessorsAndAnnotation.kt b/analysis/symbol-light-classes/testData/lightElements/propertyWithExplicitAccessorsAndAnnotation.kt new file mode 100644 index 00000000000..47d6f665b23 --- /dev/null +++ b/analysis/symbol-light-classes/testData/lightElements/propertyWithExplicitAccessorsAndAnnotation.kt @@ -0,0 +1,4 @@ +// EXPECTED: org.jetbrains.kotlin.light.classes.symbol.methods.SymbolLightAccessorMethod +@get:JvmName("getBar") +val p: Int + get() = 42 \ No newline at end of file diff --git a/analysis/symbol-light-classes/testData/lightElements/propertyWithExplicitAccessorsAndAnnotationOnThem.kt b/analysis/symbol-light-classes/testData/lightElements/propertyWithExplicitAccessorsAndAnnotationOnThem.kt new file mode 100644 index 00000000000..21c68fc4406 --- /dev/null +++ b/analysis/symbol-light-classes/testData/lightElements/propertyWithExplicitAccessorsAndAnnotationOnThem.kt @@ -0,0 +1,4 @@ +// EXPECTED: org.jetbrains.kotlin.light.classes.symbol.methods.SymbolLightAccessorMethod +val p: Int + @JvmName("getBar") + get() = 42 \ No newline at end of file diff --git a/analysis/symbol-light-classes/testData/lightElements/propertyWithImplicitAccessors.kt b/analysis/symbol-light-classes/testData/lightElements/propertyWithImplicitAccessors.kt new file mode 100644 index 00000000000..158387ffe08 --- /dev/null +++ b/analysis/symbol-light-classes/testData/lightElements/propertyWithImplicitAccessors.kt @@ -0,0 +1,4 @@ +// EXPECTED: org.jetbrains.kotlin.light.classes.symbol.methods.SymbolLightAccessorMethod +// EXPECTED: org.jetbrains.kotlin.light.classes.symbol.fields.SymbolLightFieldForProperty +@get:JvmName("getBar") +val p: Int = 42 \ No newline at end of file diff --git a/analysis/symbol-light-classes/tests/org/jetbrains/kotlin/light/classes/symbol/base/AbstractLightClassUtilTest.kt b/analysis/symbol-light-classes/tests/org/jetbrains/kotlin/light/classes/symbol/base/AbstractLightClassUtilTest.kt new file mode 100644 index 00000000000..13864c94ad4 --- /dev/null +++ b/analysis/symbol-light-classes/tests/org/jetbrains/kotlin/light/classes/symbol/base/AbstractLightClassUtilTest.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.light.classes.symbol.base + +import org.jetbrains.kotlin.analysis.low.level.api.fir.test.configurators.AnalysisApiFirSourceTestConfigurator +import org.jetbrains.kotlin.analysis.test.framework.base.AbstractAnalysisApiBasedSingleModuleTest +import org.jetbrains.kotlin.analysis.test.framework.services.expressionMarkerProvider +import org.jetbrains.kotlin.asJava.toLightElements +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder +import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.assertions + +abstract class AbstractLightClassUtilTest : AbstractAnalysisApiBasedSingleModuleTest() { + override val configurator = AnalysisApiFirSourceTestConfigurator(analyseInDependentSession = false) + override fun doTestByFileStructure(ktFiles: List, module: TestModule, testServices: TestServices) { + val ktFile = ktFiles.single() + val declaration = testServices.expressionMarkerProvider.getElementOfTypeAtCaret(ktFile) + val lightElements = declaration.toLightElements() + testServices.assertions.assertFalse(lightElements.isEmpty()) + val directives = module.directives + val expectedLightElements = directives[Directives.EXPECTED] + testServices.assertions.assertEquals(expectedLightElements.size, lightElements.size) { + "Found ${lightElements.map { it.javaClass.name }}" + } + lightElements.forEachIndexed { index, element -> + testServices.assertions.assertEquals(expectedLightElements[index], element.javaClass.name) + } + } + + override fun configureTest(builder: TestConfigurationBuilder) { + super.configureTest(builder) + builder.useDirectives(Directives) + } + + private object Directives : SimpleDirectivesContainer() { + val EXPECTED by stringDirective(description = "Expected light classes") + } +} \ No newline at end of file diff --git a/analysis/symbol-light-classes/tests/org/jetbrains/kotlin/light/classes/symbol/base/LightClassUtilTestGenerated.java b/analysis/symbol-light-classes/tests/org/jetbrains/kotlin/light/classes/symbol/base/LightClassUtilTestGenerated.java new file mode 100644 index 00000000000..08b0f540607 --- /dev/null +++ b/analysis/symbol-light-classes/tests/org/jetbrains/kotlin/light/classes/symbol/base/LightClassUtilTestGenerated.java @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.light.classes.symbol.base; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.util.KtTestUtil; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.analysis.api.GenerateAnalysisApiTestsKt}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("analysis/symbol-light-classes/testData/lightElements") +@TestDataPath("$PROJECT_ROOT") +public class LightClassUtilTestGenerated extends AbstractLightClassUtilTest { + @Test + public void testAllFilesPresentInLightElements() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/symbol-light-classes/testData/lightElements"), Pattern.compile("^(.+)\\.(kt)$"), null, true); + } + + @Test + @TestMetadata("mangledName.kt") + public void testMangledName() throws Exception { + runTest("analysis/symbol-light-classes/testData/lightElements/mangledName.kt"); + } + + @Test + @TestMetadata("mangledNameWithAnnotations.kt") + public void testMangledNameWithAnnotations() throws Exception { + runTest("analysis/symbol-light-classes/testData/lightElements/mangledNameWithAnnotations.kt"); + } + + @Test + @TestMetadata("propertyAccessor.kt") + public void testPropertyAccessor() throws Exception { + runTest("analysis/symbol-light-classes/testData/lightElements/propertyAccessor.kt"); + } + + @Test + @TestMetadata("propertyAccessorWithAnnotation.kt") + public void testPropertyAccessorWithAnnotation() throws Exception { + runTest("analysis/symbol-light-classes/testData/lightElements/propertyAccessorWithAnnotation.kt"); + } + + @Test + @TestMetadata("propertyAccessorWithImplicitAnnotation.kt") + public void testPropertyAccessorWithImplicitAnnotation() throws Exception { + runTest("analysis/symbol-light-classes/testData/lightElements/propertyAccessorWithImplicitAnnotation.kt"); + } + + @Test + @TestMetadata("propertyWithExplicitAccessors.kt") + public void testPropertyWithExplicitAccessors() throws Exception { + runTest("analysis/symbol-light-classes/testData/lightElements/propertyWithExplicitAccessors.kt"); + } + + @Test + @TestMetadata("propertyWithExplicitAccessorsAndAnnotation.kt") + public void testPropertyWithExplicitAccessorsAndAnnotation() throws Exception { + runTest("analysis/symbol-light-classes/testData/lightElements/propertyWithExplicitAccessorsAndAnnotation.kt"); + } + + @Test + @TestMetadata("propertyWithExplicitAccessorsAndAnnotationOnThem.kt") + public void testPropertyWithExplicitAccessorsAndAnnotationOnThem() throws Exception { + runTest("analysis/symbol-light-classes/testData/lightElements/propertyWithExplicitAccessorsAndAnnotationOnThem.kt"); + } + + @Test + @TestMetadata("propertyWithImplicitAccessors.kt") + public void testPropertyWithImplicitAccessors() throws Exception { + runTest("analysis/symbol-light-classes/testData/lightElements/propertyWithImplicitAccessors.kt"); + } +} diff --git a/compiler/frontend.common/src/org/jetbrains/kotlin/resolve/DataClassResolver.kt b/compiler/frontend.common/src/org/jetbrains/kotlin/resolve/DataClassResolver.kt index 98563f6753f..db50ebe8ab4 100644 --- a/compiler/frontend.common/src/org/jetbrains/kotlin/resolve/DataClassResolver.kt +++ b/compiler/frontend.common/src/org/jetbrains/kotlin/resolve/DataClassResolver.kt @@ -16,7 +16,7 @@ object DataClassResolver { fun isComponentLike(name: Name): Boolean = isComponentLike(name.asString()) - private fun isComponentLike(name: String): Boolean { + fun isComponentLike(name: String): Boolean { if (!name.startsWith(DATA_CLASS_COMPONENT_PREFIX)) return false try { diff --git a/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/lightClasses.kt b/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/lightClasses.kt index 2018288ae07..319ae038d94 100644 --- a/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/lightClasses.kt +++ b/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/lightClasses.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.generators.tests.analysis.api import org.jetbrains.kotlin.generators.TestGroup import org.jetbrains.kotlin.generators.TestGroupSuite import org.jetbrains.kotlin.generators.util.TestGeneratorUtil +import org.jetbrains.kotlin.light.classes.symbol.base.AbstractLightClassUtilTest import org.jetbrains.kotlin.light.classes.symbol.base.AbstractSymbolLightClassesEquivalentTest import org.jetbrains.kotlin.light.classes.symbol.decompiled.* import org.jetbrains.kotlin.light.classes.symbol.source.* @@ -50,6 +51,12 @@ internal fun TestGroupSuite.generateSymbolLightClassesTests() { model("annotationsEquality", pattern = TestGeneratorUtil.KT) } } + + run { + testClass { + model("lightElements", pattern = TestGeneratorUtil.KT) + } + } } }