[light classes] optimize accessors retrieval

Avoid expensive calls to `navigationElement` for methods
that cannot be getters/setters and would be filtered later.
Repeat partly naming generation strategy.

Merge-request: KT-MR-10689
Merged-by: Anna Kozlova <Anna.Kozlova@jetbrains.com>
This commit is contained in:
Anna Kozlova
2023-06-22 14:14:39 +00:00
committed by Space Team
parent 7e9a897ef3
commit 190d49a1e0
14 changed files with 257 additions and 15 deletions
@@ -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<PsiMethod> {
val property = accessor.getNonStrictParentOfType<KtProperty>() ?: 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<KtLightMethod> =
private fun getPsiMethodWrappers(
declaration: KtDeclaration,
name: String? = null,
nameFilter: (KtLightMethod) -> Boolean = { name == null || name == it.name }
): Sequence<KtLightMethod> =
getWrappingClasses(declaration).flatMap { it.methods.asSequence() }
.filterIsInstance<KtLightMethod>()
.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 <T> 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?,
@@ -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 <caret> p: Int = 42
}
@@ -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 <caret>p: Int = 42
}
@@ -0,0 +1,3 @@
// EXPECTED: org.jetbrains.kotlin.light.classes.symbol.methods.SymbolLightAccessorMethod
val p: Int = 42
g<caret>et
@@ -0,0 +1,4 @@
// EXPECTED: org.jetbrains.kotlin.light.classes.symbol.methods.SymbolLightAccessorMethod
val p: Int = 42
@JvmName("getBar")
g<caret>et
@@ -0,0 +1,4 @@
// EXPECTED: org.jetbrains.kotlin.light.classes.symbol.methods.SymbolLightAccessorMethod
@get:JvmName("getBar")
val p: Int = 42
g<caret>et
@@ -0,0 +1,3 @@
// EXPECTED: org.jetbrains.kotlin.light.classes.symbol.methods.SymbolLightAccessorMethod
val <caret>p: Int
get() = 42
@@ -0,0 +1,4 @@
// EXPECTED: org.jetbrains.kotlin.light.classes.symbol.methods.SymbolLightAccessorMethod
@get:JvmName("getBar")
val <caret>p: Int
get() = 42
@@ -0,0 +1,4 @@
// EXPECTED: org.jetbrains.kotlin.light.classes.symbol.methods.SymbolLightAccessorMethod
val <caret>p: Int
@JvmName("getBar")
get() = 42
@@ -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 <caret>p: Int = 42
@@ -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<KtFile>, module: TestModule, testServices: TestServices) {
val ktFile = ktFiles.single()
val declaration = testServices.expressionMarkerProvider.getElementOfTypeAtCaret<KtDeclaration>(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")
}
}
@@ -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");
}
}
@@ -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 {
@@ -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<AbstractLightClassUtilTest> {
model("lightElements", pattern = TestGeneratorUtil.KT)
}
}
}
}