diff --git a/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/classes/FirLightClassForSymbol.kt b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/classes/FirLightClassForSymbol.kt index 5dd5b7751d9..aeca3b9d219 100644 --- a/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/classes/FirLightClassForSymbol.kt +++ b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/classes/FirLightClassForSymbol.kt @@ -10,12 +10,20 @@ import org.jetbrains.kotlin.analysis.api.isValid import org.jetbrains.kotlin.analysis.api.symbols.* import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolKind import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithVisibility +import org.jetbrains.kotlin.asJava.builder.LightMemberOriginForDeclaration +import org.jetbrains.kotlin.asJava.classes.METHOD_INDEX_BASE import org.jetbrains.kotlin.asJava.classes.lazyPub import org.jetbrains.kotlin.asJava.elements.KtLightField import org.jetbrains.kotlin.asJava.elements.KtLightMethod +import org.jetbrains.kotlin.builtins.StandardNames.DATA_CLASS_COPY +import org.jetbrains.kotlin.builtins.StandardNames.HASHCODE_NAME import org.jetbrains.kotlin.descriptors.Visibility import org.jetbrains.kotlin.light.classes.symbol.classes.* import org.jetbrains.kotlin.load.java.JvmAbi +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKind +import org.jetbrains.kotlin.util.OperatorNameConventions.EQUALS +import org.jetbrains.kotlin.util.OperatorNameConventions.TO_STRING import org.jetbrains.kotlin.utils.addToStdlib.applyIf internal class FirLightClassForSymbol( @@ -96,6 +104,12 @@ internal class FirLightClassForSymbol( filterNot { it is KtKotlinPropertySymbol && it.isConst } + }.applyIf(classOrObjectSymbol.isData) { + // Technically, synthetic members of `data` class, such as `componentN` or `copy`, are visible. + // They're just needed to be added later (to be in a backward-compatible order of members). + filterNot { function -> + function is KtFunctionSymbol && function.origin == KtSymbolOrigin.SOURCE_MEMBER_GENERATED + } } val suppressStatic = classOrObjectSymbol.classKind == KtClassKind.COMPANION_OBJECT @@ -106,6 +120,8 @@ internal class FirLightClassForSymbol( addMethodsFromCompanionIfNeeded(result) + addMethodsFromDataClass(result) + result } @@ -120,6 +136,62 @@ internal class FirLightClassForSymbol( } } + private fun addMethodsFromDataClass(result: MutableList) { + if (!classOrObjectSymbol.isData) return + + fun createMethodFromAny(ktFunctionSymbol: KtFunctionSymbol) { + // Similar to `copy`, synthetic members from `Any` should refer to `data` class as origin, not the function in `Any`. + val lightMemberOrigin = LightMemberOriginForDeclaration(this.kotlinOrigin!!, JvmDeclarationOriginKind.OTHER) + result.add( + FirLightSimpleMethodForSymbol( + functionSymbol = ktFunctionSymbol, + lightMemberOrigin = lightMemberOrigin, + containingClass = this, + isTopLevel = false, + methodIndex = METHOD_INDEX_BASE, + suppressStatic = false + ) + ) + } + + analyzeWithSymbolAsContext(classOrObjectSymbol) { + val componentAndCopyFunctions = mutableListOf() + val functionsFromAny = mutableMapOf() + // NB: componentN and copy are added during RAW FIR, but synthetic members from `Any` are not. + // Thus, using declared member scope is not sufficient to lookup "all" synthetic members. + classOrObjectSymbol.getMemberScope().getCallableSymbols().forEach { symbol -> + if (symbol is KtFunctionSymbol) { + val name = symbol.name + if (name.isCopy || name.isComponentN) { + componentAndCopyFunctions.add(symbol) + } + if (name.isFromAny) { + functionsFromAny[name] = symbol + } + } + } + createMethods(componentAndCopyFunctions.asSequence(), result) + // NB: functions from `Any` are not in an alphabetic order. + functionsFromAny[TO_STRING]?.let { createMethodFromAny(it) } + functionsFromAny[HASHCODE_NAME]?.let { createMethodFromAny(it) } + functionsFromAny[EQUALS]?.let { createMethodFromAny(it) } + } + } + + private val Name.isCopy: Boolean + get() = this == DATA_CLASS_COPY + + private val Name.isComponentN: Boolean + get() { + if (isSpecial) return false + if (!identifier.startsWith("component")) return false + val n = identifier.substring("component".length).toIntOrNull() + return n != null && n > 0 + } + + private val Name.isFromAny: Boolean + get() = this == EQUALS || this == HASHCODE_NAME || this == TO_STRING + private val _ownFields: List by lazyPub { val result = mutableListOf() diff --git a/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/methods/FirLightMethodForSymbol.kt b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/methods/FirLightMethodForSymbol.kt index 4a9d71604f4..19bea42bfe5 100644 --- a/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/methods/FirLightMethodForSymbol.kt +++ b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/methods/FirLightMethodForSymbol.kt @@ -54,7 +54,8 @@ internal abstract class FirLightMethodForSymbol( override fun getParameterList(): PsiParameterList = _parametersList - override val kotlinOrigin: KtDeclaration? = functionSymbol.psi as? KtDeclaration + override val kotlinOrigin: KtDeclaration? = + lightMemberOrigin?.originalElement ?: functionSymbol.psi as? KtDeclaration override fun isValid(): Boolean = super.isValid() && functionSymbol.isValid() }