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
This commit is contained in:
Ilya Kirillov
2021-02-18 20:27:39 +01:00
parent 804df1aec2
commit b08eb6cf4c
16 changed files with 435 additions and 43 deletions
@@ -29,5 +29,3 @@ private class Private {
override val overridesNothing: Boolean
get() = false
}
// FIR_COMPARISON
@@ -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<String>) {
testClass<AbstractExpectedExpressionTypeTest> {
model("components/expectedExpressionType")
}
testClass<AbstractOverriddenDeclarationProviderTest> {
model("components/overridenDeclarations")
}
}
testGroup("idea/idea-frontend-fir/idea-fir-low-level-api/tests", "idea/testData") {
@@ -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 ->
@@ -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 -> {
@@ -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<KtCallableSymbol> =
symbolDeclarationOverridesProvider.getOverriddenSymbols(this, containingDeclaration)
fun KtCallableSymbol.getOverriddenSymbols(): List<KtCallableSymbol> =
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<KtCallableSymbol> =
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<KtCallableSymbol> =
symbolDeclarationOverridesProvider.getDirectlyOverriddenSymbols(this)
fun KtCallableSymbol.getIntersectionOverriddenSymbols(): Collection<KtCallableSymbol> =
symbolDeclarationOverridesProvider.getIntersectionOverriddenSymbols(this)
@@ -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 <T : KtSymbol> getOverriddenSymbols(
abstract fun <T : KtSymbol> getAllOverriddenSymbols(
callableSymbol: T,
containingDeclaration: KtClassOrObjectSymbol
): List<KtCallableSymbol>
/**
* Returns symbols that overridden by requested
* Returns symbols that are overridden by requested
*/
abstract fun <T : KtSymbol> getOverriddenSymbols(
abstract fun <T : KtSymbol> getDirectlyOverriddenSymbols(
callableSymbol: T,
): List<KtCallableSymbol>
/**
* If [symbol] origin is [org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbolOrigin.INTERSECTION_OVERRIDE]
* Then returns the symbols which [symbol] overrides, otherwise empty collection
@@ -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
@@ -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 <T : KtSymbol> getAllOverriddenSymbols(
callableSymbol: T,
): List<KtCallableSymbol> {
val overriddenElement = mutableSetOf<FirCallableSymbol<*>>()
processOverrides(callableSymbol) { firTypeScope, firCallableDeclaration ->
firTypeScope.processAllOverriddenDeclarations(firCallableDeclaration) { overriddenDeclaration ->
overriddenDeclaration.symbol.collectIntersectionOverridesSymbolsTo(overriddenElement)
}
}
return overriddenElement.map { analysisSession.firSymbolBuilder.buildCallableSymbol(it.fir) }
}
override fun <T : KtSymbol> getDirectlyOverriddenSymbols(callableSymbol: T): List<KtCallableSymbol> {
val overriddenElement = mutableSetOf<FirCallableSymbol<*>>()
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 <T : KtSymbol> getOverriddenSymbols(
callableSymbol: T,
containingDeclaration: KtClassOrObjectSymbol
): List<KtCallableSymbol> {
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 <T : KtSymbol> 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<KtCallableSymbol>()
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 <T : KtSymbol> getOverriddenSymbols(callableSymbol: T): List<KtCallableSymbol> = with(analysisSession) {
val containingDeclaration = (callableSymbol as? KtSymbolWithKind)?.getContainingSymbol() as? KtClassOrObjectSymbol ?: return emptyList()
getOverriddenSymbols(callableSymbol, containingDeclaration)
private fun FirCallableSymbol<*>.collectIntersectionOverridesSymbolsTo(to: MutableCollection<FirCallableSymbol<*>>) {
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<KtCallableSymbol> {
@@ -0,0 +1,19 @@
// FILE: main.kt
class A : B(){
override fun fo<caret>o(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
@@ -0,0 +1,34 @@
// FILE: main.kt
class A : B {
override fun foo(x: Int) {
}
override fun foo<caret>(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
@@ -0,0 +1,34 @@
// FILE: main.kt
class A : B, C {
override fun foo(x: Int) {
}
override fun foo<caret>(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
@@ -0,0 +1,27 @@
// FILE: main.kt
class A : B() {
override val x<caret>: 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
@@ -0,0 +1,39 @@
// FILE: main.kt
class A : B, C, D {
override fun foo(x: Int) {
}
override fun foo<caret>(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
@@ -0,0 +1,30 @@
// FILE: main.kt
class A : B() {
override fun foo<caret>(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
@@ -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<KtDeclaration>(withSelf = true)
.map { it.name ?: "<no name>" }
.toList()
.asReversed()
.joinToString(separator = ".")
}
}
private fun getDeclarationAtCaret(): KtDeclaration =
file.findElementAt(myFixture.caretOffset)
?.parentOfType()
?: error("No KtDeclaration found at caret with position ${myFixture.caretOffset}")
}
@@ -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");
}
}