From b08eb6cf4c7b74343659f8c04d269ee1bbf36a9e Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Thu, 18 Feb 2021 20:27:39 +0100 Subject: [PATCH] FIR IDE: specify behaviour of HL API getOverriddenSymbols - Split it into two functions getAllOverriddenSymbols and getDirectlyOverriddenSymbols - Implement tests for getOverriddenSymbols - temporary mute inheritance.kt light classes test --- .../asJava/ultraLightClasses/inheritance.kt | 2 - .../kotlin/generators/tests/GenerateTests.kt | 5 + .../KotlinFindUsagesSupportFirImpl.kt | 2 +- .../ChangeReturnTypeOnOverrideQuickFix.kt | 6 +- .../idea/frontend/api/KtAnalysisSession.kt | 29 ++++- .../KtSymbolDeclarationOverridesProvider.kt | 10 +- .../asJava/classes/FirLightClassForSymbol.kt | 2 +- ...KtFirSymbolDeclarationOverridesProvider.kt | 103 +++++++++++++----- .../overridenDeclarations/inOtherFile.kt | 19 ++++ .../intersectionOverride.kt | 34 ++++++ .../intersectionOverride2.kt | 34 ++++++ .../overridenDeclarations/javaAccessors.kt | 27 +++++ .../multipleInterfaces.kt | 39 +++++++ .../sequenceOfOverrides.kt | 30 +++++ ...stractOverriddenDeclarationProviderTest.kt | 75 +++++++++++++ ...iddenDeclarationProviderTestGenerated.java | 61 +++++++++++ 16 files changed, 435 insertions(+), 43 deletions(-) create mode 100644 idea/idea-frontend-fir/testData/components/overridenDeclarations/inOtherFile.kt create mode 100644 idea/idea-frontend-fir/testData/components/overridenDeclarations/intersectionOverride.kt create mode 100644 idea/idea-frontend-fir/testData/components/overridenDeclarations/intersectionOverride2.kt create mode 100644 idea/idea-frontend-fir/testData/components/overridenDeclarations/javaAccessors.kt create mode 100644 idea/idea-frontend-fir/testData/components/overridenDeclarations/multipleInterfaces.kt create mode 100644 idea/idea-frontend-fir/testData/components/overridenDeclarations/sequenceOfOverrides.kt create mode 100644 idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/components/AbstractOverriddenDeclarationProviderTest.kt create mode 100644 idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/components/OverriddenDeclarationProviderTestGenerated.java diff --git a/compiler/testData/asJava/ultraLightClasses/inheritance.kt b/compiler/testData/asJava/ultraLightClasses/inheritance.kt index 3b71ba7d286..dba936c8021 100644 --- a/compiler/testData/asJava/ultraLightClasses/inheritance.kt +++ b/compiler/testData/asJava/ultraLightClasses/inheritance.kt @@ -29,5 +29,3 @@ private class Private { override val overridesNothing: Boolean get() = false } - -// FIR_COMPARISON \ No newline at end of file diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 9f52ebe9f2b..b47784a444a 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -89,6 +89,7 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.AbstractSessionsInva import org.jetbrains.kotlin.idea.fir.low.level.api.trackers.AbstractProjectWideOutOfBlockKotlinModificationTrackerTest import org.jetbrains.kotlin.idea.folding.AbstractKotlinFoldingTest import org.jetbrains.kotlin.idea.frontend.api.components.AbstractExpectedExpressionTypeTest +import org.jetbrains.kotlin.idea.frontend.api.components.AbstractOverriddenDeclarationProviderTest import org.jetbrains.kotlin.idea.frontend.api.components.AbstractReturnExpressionTargetTest import org.jetbrains.kotlin.idea.frontend.api.fir.AbstractResolveCallTest import org.jetbrains.kotlin.idea.frontend.api.scopes.AbstractFileScopeTest @@ -1040,6 +1041,10 @@ fun main(args: Array) { testClass { model("components/expectedExpressionType") } + + testClass { + model("components/overridenDeclarations") + } } testGroup("idea/idea-frontend-fir/idea-fir-low-level-api/tests", "idea/testData") { diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/findUsages/KotlinFindUsagesSupportFirImpl.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/findUsages/KotlinFindUsagesSupportFirImpl.kt index b10233b1ff1..10027386a2f 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/findUsages/KotlinFindUsagesSupportFirImpl.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/findUsages/KotlinFindUsagesSupportFirImpl.kt @@ -70,7 +70,7 @@ class KotlinFindUsagesSupportFirImpl : KotlinFindUsagesSupport { val analyzeResult = analyseInModalWindow(declaration, KotlinBundle.message("find.usages.progress.text.declaration.superMethods")) { (declaration.getSymbol() as? KtCallableSymbol)?.let { callableSymbol -> ((callableSymbol as? KtSymbolWithKind)?.getContainingSymbol() as? KtClassOrObjectSymbol)?.let { containingClass -> - val overriddenSymbols = callableSymbol.getOverriddenSymbols(containingClass) + val overriddenSymbols = callableSymbol.getAllOverriddenSymbols() val renderToPsi = overriddenSymbols.mapNotNull { it.psi?.let { psi -> diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/ChangeReturnTypeOnOverrideQuickFix.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/ChangeReturnTypeOnOverrideQuickFix.kt index 96ed57e012b..b7e081c2621 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/ChangeReturnTypeOnOverrideQuickFix.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/ChangeReturnTypeOnOverrideQuickFix.kt @@ -118,10 +118,10 @@ object ChangeTypeQuickFix { callable: KtCallableSymbol, type: KtType ): KtCallableSymbol? { - val overriddenSymbols = callable.getOverriddenSymbols() + val overriddenSymbols = callable.getDirectlyOverriddenSymbols() return overriddenSymbols .singleOrNull { overridden -> - overridden.origin != KtSymbolOrigin.INTERSECTION_OVERRIDE && !type.isSubTypeOf(overridden.annotatedType.type) + !type.isSubTypeOf(overridden.annotatedType.type) } } @@ -131,7 +131,7 @@ object ChangeTypeQuickFix { private fun KtAnalysisSession.findLowerBoundOfOverriddenCallablesReturnTypes(symbol: KtCallableSymbol): KtType? { var lowestType: KtType? = null - for (overridden in symbol.getOverriddenSymbols()) { + for (overridden in symbol.getDirectlyOverriddenSymbols()) { val overriddenType = overridden.annotatedType.type when { lowestType == null || overriddenType isSubTypeOf lowestType -> { diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt index 147134cb497..73c559ada86 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt @@ -24,7 +24,6 @@ import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* -import kotlin.reflect.KClass /** * The entry point into all frontend-related work. Has the following contracts: @@ -59,12 +58,30 @@ abstract class KtAnalysisSession(final override val token: ValidityToken) : Vali abstract fun createContextDependentCopy(originalKtFile: KtFile, fakeKtElement: KtElement): KtAnalysisSession - //TODO get rid of it - fun KtCallableSymbol.getOverriddenSymbols(containingDeclaration: KtClassOrObjectSymbol): List = - symbolDeclarationOverridesProvider.getOverriddenSymbols(this, containingDeclaration) - fun KtCallableSymbol.getOverriddenSymbols(): List = - symbolDeclarationOverridesProvider.getOverriddenSymbols(this) + /** + * Return a list of **all** symbols which are overridden by symbol + * + * E.g, if we have `A.foo` overrides `B.foo` overrides `C.foo`, all two super declarations `B.foo`, `C.foo` will be returned + * + * Unwraps substituted overridden symbols (see [org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbolOrigin.INTERSECTION_OVERRIDE]) + * + * @see getDirectlyOverriddenSymbols + */ + fun KtCallableSymbol.getAllOverriddenSymbols(): List = + symbolDeclarationOverridesProvider.getAllOverriddenSymbols(this) + + /** + * Return a list of symbols which are **directly** overridden by symbol + ** + * E.g, if we have `A.foo` overrides `B.foo` overrides `C.foo`, only declarations directly overriden `B.foo` will be returned + * + * Unwraps substituted overridden symbols (see [org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbolOrigin.INTERSECTION_OVERRIDE]) + * + * @see getAllOverriddenSymbols + */ + fun KtCallableSymbol.getDirectlyOverriddenSymbols(): List = + symbolDeclarationOverridesProvider.getDirectlyOverriddenSymbols(this) fun KtCallableSymbol.getIntersectionOverriddenSymbols(): Collection = symbolDeclarationOverridesProvider.getIntersectionOverriddenSymbols(this) diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtSymbolDeclarationOverridesProvider.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtSymbolDeclarationOverridesProvider.kt index 6dc5415998e..e93c087fbac 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtSymbolDeclarationOverridesProvider.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtSymbolDeclarationOverridesProvider.kt @@ -11,20 +11,20 @@ import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbol abstract class KtSymbolDeclarationOverridesProvider : KtAnalysisSessionComponent() { /** - * Returns symbols that overridden by requested + * Returns symbols that are overridden by requested */ - abstract fun getOverriddenSymbols( + abstract fun getAllOverriddenSymbols( callableSymbol: T, - containingDeclaration: KtClassOrObjectSymbol ): List /** - * Returns symbols that overridden by requested + * Returns symbols that are overridden by requested */ - abstract fun getOverriddenSymbols( + abstract fun getDirectlyOverriddenSymbols( callableSymbol: T, ): List + /** * If [symbol] origin is [org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbolOrigin.INTERSECTION_OVERRIDE] * Then returns the symbols which [symbol] overrides, otherwise empty collection diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/asJava/classes/FirLightClassForSymbol.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/asJava/classes/FirLightClassForSymbol.kt index 61ac005aa08..78e0e2df9f5 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/asJava/classes/FirLightClassForSymbol.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/asJava/classes/FirLightClassForSymbol.kt @@ -34,7 +34,7 @@ internal class FirLightClassForSymbol( var visibility = (symbol as? KtSymbolWithVisibility)?.visibility analyzeWithSymbolAsContext(symbol) { - for (overriddenSymbol in symbol.getOverriddenSymbols(classOrObjectSymbol)) { + for (overriddenSymbol in symbol.getAllOverriddenSymbols()) { val newVisibility = (overriddenSymbol as? KtSymbolWithVisibility)?.visibility if (newVisibility != null) { visibility = newVisibility diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolDeclarationOverridesProvider.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolDeclarationOverridesProvider.kt index 7e2062f83dd..89b16deae63 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolDeclarationOverridesProvider.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolDeclarationOverridesProvider.kt @@ -13,7 +13,6 @@ import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirIntersectionOverrideFunctionSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirIntersectionOverridePropertySymbol -import org.jetbrains.kotlin.fir.unwrapFakeOverrides import org.jetbrains.kotlin.idea.frontend.api.ValidityToken import org.jetbrains.kotlin.idea.frontend.api.components.KtSymbolDeclarationOverridesProvider import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession @@ -27,53 +26,107 @@ internal class KtFirSymbolDeclarationOverridesProvider( override val token: ValidityToken, ) : KtSymbolDeclarationOverridesProvider(), KtFirAnalysisSessionComponent { + override fun getAllOverriddenSymbols( + callableSymbol: T, + ): List { + val overriddenElement = mutableSetOf>() + processOverrides(callableSymbol) { firTypeScope, firCallableDeclaration -> + firTypeScope.processAllOverriddenDeclarations(firCallableDeclaration) { overriddenDeclaration -> + overriddenDeclaration.symbol.collectIntersectionOverridesSymbolsTo(overriddenElement) + } + } + return overriddenElement.map { analysisSession.firSymbolBuilder.buildCallableSymbol(it.fir) } + } + + override fun getDirectlyOverriddenSymbols(callableSymbol: T): List { + val overriddenElement = mutableSetOf>() + processOverrides(callableSymbol) { firTypeScope, firCallableDeclaration -> + firTypeScope.processDirectOverriddenDeclarations(firCallableDeclaration) { overriddenDeclaration -> + overriddenDeclaration.symbol.collectIntersectionOverridesSymbolsTo(overriddenElement) + } + } + return overriddenElement.map { analysisSession.firSymbolBuilder.buildCallableSymbol(it.fir) } + } + private fun FirTypeScope.processCallableByName(declaration: FirDeclaration) = when (declaration) { is FirSimpleFunction -> processFunctionsByName(declaration.name) { } is FirProperty -> processPropertiesByName(declaration.name) { } else -> error { "Invalid FIR symbol to process: ${declaration::class}" } } - private fun FirTypeScope.processOverriddenDeclarations( + private fun FirTypeScope.processAllOverriddenDeclarations( declaration: FirDeclaration, - processor: (FirCallableDeclaration<*>) -> ProcessorAction + processor: (FirCallableDeclaration<*>) -> Unit ) = when (declaration) { - is FirSimpleFunction -> processOverriddenFunctions(declaration.symbol) { processor.invoke(it.fir) } - is FirProperty -> processOverriddenProperties(declaration.symbol) { processor.invoke(it.fir) } + is FirSimpleFunction -> processOverriddenFunctions(declaration.symbol) { symbol -> + processor.invoke(symbol.fir) + ProcessorAction.NEXT + } + is FirProperty -> processOverriddenProperties(declaration.symbol) { symbol -> + processor.invoke(symbol.fir) + ProcessorAction.NEXT + } else -> error { "Invalid FIR symbol to process: ${declaration::class}" } } - override fun getOverriddenSymbols( - callableSymbol: T, - containingDeclaration: KtClassOrObjectSymbol - ): List { + private fun FirTypeScope.processDirectOverriddenDeclarations( + declaration: FirDeclaration, + processor: (FirCallableDeclaration<*>) -> Unit + ) = when (declaration) { + is FirSimpleFunction -> processDirectOverriddenFunctionsWithBaseScope(declaration.symbol) { symbol, _ -> + processor.invoke(symbol.fir) + ProcessorAction.NEXT + } + is FirProperty -> processDirectOverriddenPropertiesWithBaseScope(declaration.symbol) { symbol, _ -> + processor.invoke(symbol.fir) + ProcessorAction.NEXT + } + else -> error { "Invalid FIR symbol to process: ${declaration::class}" } + } - check(callableSymbol is KtFirSymbol<*>) + private inline fun processOverrides( + callableSymbol: T, + crossinline process: (FirTypeScope, FirDeclaration) -> Unit + ) { + require(callableSymbol is KtFirSymbol<*>) + val containingDeclaration = with(analysisSession) { + (callableSymbol as? KtSymbolWithKind)?.getContainingSymbol() as? KtClassOrObjectSymbol + } ?: return check(containingDeclaration is KtFirClassOrObjectSymbol) - return containingDeclaration.firRef.withFir(FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE) { firContainer -> - callableSymbol.firRef.withFirUnsafe { firCallableElement -> + processOverrides(containingDeclaration, callableSymbol, process) + } + + private inline fun processOverrides( + containingDeclaration: KtFirClassOrObjectSymbol, + callableSymbol: KtFirSymbol<*>, + crossinline process: (FirTypeScope, FirDeclaration) -> Unit + ) { + containingDeclaration.firRef.withFir(FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE) { firContainer -> + callableSymbol.firRef.withFirUnsafe { firCallableDeclaration -> val firTypeScope = firContainer.unsubstitutedScope( firContainer.session, ScopeSession(), withForcedTypeCalculator = false ) - - val overriddenElement = mutableSetOf() - firTypeScope.processCallableByName(firCallableElement) - firTypeScope.processOverriddenDeclarations(firCallableElement) { overriddenDeclaration -> - val ktSymbol = analysisSession.firSymbolBuilder.buildCallableSymbol(overriddenDeclaration) - overriddenElement.add(ktSymbol) - ProcessorAction.NEXT - } - - overriddenElement.toList() + firTypeScope.processCallableByName(firCallableDeclaration) + process(firTypeScope, firCallableDeclaration) } } } - override fun getOverriddenSymbols(callableSymbol: T): List = with(analysisSession) { - val containingDeclaration = (callableSymbol as? KtSymbolWithKind)?.getContainingSymbol() as? KtClassOrObjectSymbol ?: return emptyList() - getOverriddenSymbols(callableSymbol, containingDeclaration) + private fun FirCallableSymbol<*>.collectIntersectionOverridesSymbolsTo(to: MutableCollection>) { + when (this) { + is FirIntersectionOverrideFunctionSymbol -> { + intersections.forEach { it.collectIntersectionOverridesSymbolsTo(to) } + } + is FirIntersectionOverridePropertySymbol -> { + intersections.forEach { it.collectIntersectionOverridesSymbolsTo(to) } + } + else -> { + to += this + } + } } override fun getIntersectionOverriddenSymbols(symbol: KtCallableSymbol): Collection { diff --git a/idea/idea-frontend-fir/testData/components/overridenDeclarations/inOtherFile.kt b/idea/idea-frontend-fir/testData/components/overridenDeclarations/inOtherFile.kt new file mode 100644 index 00000000000..479bfd1624d --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/overridenDeclarations/inOtherFile.kt @@ -0,0 +1,19 @@ +// FILE: main.kt +class A : B(){ + override fun foo(x: Int): Int +} + +// FILE: B.kt +abstract class B { + open fun foo(x: Int): Int + abstract fun foo(x: String): Int +} + +// RESULT + +// ALL: +// B.foo(x: Int): Int + +// DIRECT: +// B.foo(x: Int): Int + diff --git a/idea/idea-frontend-fir/testData/components/overridenDeclarations/intersectionOverride.kt b/idea/idea-frontend-fir/testData/components/overridenDeclarations/intersectionOverride.kt new file mode 100644 index 00000000000..99b7dac969a --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/overridenDeclarations/intersectionOverride.kt @@ -0,0 +1,34 @@ +// FILE: main.kt +class A : B { + override fun foo(x: Int) { + } + + override fun foo(x: String) { + } +} + +// FILE: B.kt +interface B: C, D + +// FILE: C.kt +interface C { + fun foo(x: Int) + fun foo(x: String) +} + +// FILE: D.kt +interface D { + fun foo(x: Int) + fun foo(x: String) +} + +// RESULT + +// ALL: +// C.foo(x: String): Unit +// D.foo(x: String): Unit + +// DIRECT: +// C.foo(x: String): Unit +// D.foo(x: String): Unit + diff --git a/idea/idea-frontend-fir/testData/components/overridenDeclarations/intersectionOverride2.kt b/idea/idea-frontend-fir/testData/components/overridenDeclarations/intersectionOverride2.kt new file mode 100644 index 00000000000..22fd5063883 --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/overridenDeclarations/intersectionOverride2.kt @@ -0,0 +1,34 @@ +// FILE: main.kt +class A : B, C { + override fun foo(x: Int) { + } + + override fun foo(x: String) { + } +} + +// FILE: B.kt +interface B: C, D + +// FILE: C.kt +interface C { + fun foo(x: Int) + fun foo(x: String) +} + +// FILE: D.kt +interface D { + fun foo(x: Int) + fun foo(x: String) +} + +// RESULT + +// ALL: +// C.foo(x: String): Unit +// D.foo(x: String): Unit + +// DIRECT: +// C.foo(x: String): Unit +// D.foo(x: String): Unit + diff --git a/idea/idea-frontend-fir/testData/components/overridenDeclarations/javaAccessors.kt b/idea/idea-frontend-fir/testData/components/overridenDeclarations/javaAccessors.kt new file mode 100644 index 00000000000..6c0557445e8 --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/overridenDeclarations/javaAccessors.kt @@ -0,0 +1,27 @@ +// FILE: main.kt +class A : B() { + override val x: Int get() = super.x +} + +// FILE: B.java +public class B extends C { + @Override + public int getX() { + return 0; + } +} + +// FILE: C.kt +abstract class C { + abstract val x: Int +} + +// RESULT + +// ALL: +// B.x: Int +// C.x: Int + +// DIRECT: +// B.x: Int + diff --git a/idea/idea-frontend-fir/testData/components/overridenDeclarations/multipleInterfaces.kt b/idea/idea-frontend-fir/testData/components/overridenDeclarations/multipleInterfaces.kt new file mode 100644 index 00000000000..c8a275368af --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/overridenDeclarations/multipleInterfaces.kt @@ -0,0 +1,39 @@ +// FILE: main.kt +class A : B, C, D { + override fun foo(x: Int) { + } + + override fun foo(x: String) { + } +} + +// FILE: B.kt +interface B { + fun foo(x: Int) + fun foo(x: String) +} + +// FILE: C.kt +interface C { + fun foo(x: Int) + fun foo(x: String) +} + +// FILE: D.kt +interface D { + fun foo(x: Int) + fun foo(x: String) +} + +// RESULT + +// ALL: +// B.foo(x: String): Unit +// C.foo(x: String): Unit +// D.foo(x: String): Unit + +// DIRECT: +// B.foo(x: String): Unit +// C.foo(x: String): Unit +// D.foo(x: String): Unit + diff --git a/idea/idea-frontend-fir/testData/components/overridenDeclarations/sequenceOfOverrides.kt b/idea/idea-frontend-fir/testData/components/overridenDeclarations/sequenceOfOverrides.kt new file mode 100644 index 00000000000..17f583a8244 --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/overridenDeclarations/sequenceOfOverrides.kt @@ -0,0 +1,30 @@ +// FILE: main.kt +class A : B() { + override fun foo(x: Int) {} +} + +// FILE: B.kt +open class B : C() { + override fun foo(x: Int) {} +} + +// FILE: C.kt +open class C : D() { + override fun foo(x: Int) {} +} + +// FILE: D.kt +open class D { + open fun foo(x: Int) {} +} + +// RESULT + +// ALL: +// B.foo(x: Int): Unit +// C.foo(x: Int): Unit +// D.foo(x: Int): Unit + +// DIRECT: +// B.foo(x: Int): Unit + diff --git a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/components/AbstractOverriddenDeclarationProviderTest.kt b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/components/AbstractOverriddenDeclarationProviderTest.kt new file mode 100644 index 00000000000..9a03b40707f --- /dev/null +++ b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/components/AbstractOverriddenDeclarationProviderTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2010-2021 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.idea.frontend.api.components + +import com.intellij.psi.util.parentOfType +import com.intellij.psi.util.parentsOfType +import org.jetbrains.kotlin.idea.executeOnPooledThreadInReadAction +import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.analyze +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSyntheticJavaPropertySymbol +import org.jetbrains.kotlin.idea.test.framework.AbstractKtIdeaTest +import org.jetbrains.kotlin.idea.test.framework.TestFileStructure +import org.jetbrains.kotlin.idea.test.framework.TestStructureExpectedDataBlock +import org.jetbrains.kotlin.idea.test.framework.TestStructureRenderer +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.test.KotlinTestUtils + +abstract class AbstractOverriddenDeclarationProviderTest : AbstractKtIdeaTest() { + override fun doTestByFileStructure(fileStructure: TestFileStructure) { + val signatures = executeOnPooledThreadInReadAction { + analyze(fileStructure.mainKtFile) { + val symbol = getDeclarationAtCaret().getSymbol() as KtCallableSymbol + val allOverriddenSymbols = symbol.getAllOverriddenSymbols().map { renderSignature(it) } + val directlyOverriddenSymbols = symbol.getDirectlyOverriddenSymbols().map { renderSignature(it) } + listOf( + TestStructureExpectedDataBlock("ALL:", allOverriddenSymbols), + TestStructureExpectedDataBlock("DIRECT:", directlyOverriddenSymbols), + ) + } + } + val actual = TestStructureRenderer.render(fileStructure, signatures) + KotlinTestUtils.assertEqualsToFile(fileStructure.filePath.toFile(), actual) + } + + private fun KtAnalysisSession.renderSignature(symbol: KtCallableSymbol): String = buildString { + append(getPath(symbol)) + if (symbol is KtFunctionSymbol) { + append("(") + symbol.valueParameters.forEachIndexed { index, parameter -> + append(parameter.name.identifier) + append(": ") + append(parameter.annotatedType.type.render(KtTypeRendererOptions.SHORT_NAMES)) + if (index != symbol.valueParameters.lastIndex) { + append(", ") + } + } + append(")") + } + append(": ") + append(symbol.annotatedType.type.render(KtTypeRendererOptions.SHORT_NAMES)) + } + + private fun getPath(symbol: KtCallableSymbol): String = when (symbol) { + is KtSyntheticJavaPropertySymbol -> symbol.callableIdIfNonLocal?.asString()!! + else -> { + val ktDeclaration = symbol.psi as KtDeclaration + ktDeclaration + .parentsOfType(withSelf = true) + .map { it.name ?: "" } + .toList() + .asReversed() + .joinToString(separator = ".") + } + } + + private fun getDeclarationAtCaret(): KtDeclaration = + file.findElementAt(myFixture.caretOffset) + ?.parentOfType() + ?: error("No KtDeclaration found at caret with position ${myFixture.caretOffset}") +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/components/OverriddenDeclarationProviderTestGenerated.java b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/components/OverriddenDeclarationProviderTestGenerated.java new file mode 100644 index 00000000000..9179c117b7f --- /dev/null +++ b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/components/OverriddenDeclarationProviderTestGenerated.java @@ -0,0 +1,61 @@ +/* + * Copyright 2010-2021 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.idea.frontend.api.components; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.util.KtTestUtil; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("idea/idea-frontend-fir/testData/components/overridenDeclarations") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class OverriddenDeclarationProviderTestGenerated extends AbstractOverriddenDeclarationProviderTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInOverridenDeclarations() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/testData/components/overridenDeclarations"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("inOtherFile.kt") + public void testInOtherFile() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/overridenDeclarations/inOtherFile.kt"); + } + + @TestMetadata("intersectionOverride.kt") + public void testIntersectionOverride() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/overridenDeclarations/intersectionOverride.kt"); + } + + @TestMetadata("intersectionOverride2.kt") + public void testIntersectionOverride2() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/overridenDeclarations/intersectionOverride2.kt"); + } + + @TestMetadata("javaAccessors.kt") + public void testJavaAccessors() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/overridenDeclarations/javaAccessors.kt"); + } + + @TestMetadata("multipleInterfaces.kt") + public void testMultipleInterfaces() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/overridenDeclarations/multipleInterfaces.kt"); + } + + @TestMetadata("sequenceOfOverrides.kt") + public void testSequenceOfOverrides() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/overridenDeclarations/sequenceOfOverrides.kt"); + } +}