[FIR2IR] Properly calculate overridden functions for lazy fake overrides
For detailed explanation see the comment to `computeBaseSymbolsWithContainingClass` function in `FakeOverrideGenerator.kt` ^KT-65592
This commit is contained in:
committed by
Space Team
parent
1761c8eb25
commit
e431a96897
+6
@@ -19334,6 +19334,12 @@ public class LLFirBlackBoxCodegenBasedTestGenerated extends AbstractLLFirBlackBo
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/diamondFunction.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("diamondWithNullabilityAnnotations.kt")
|
||||
public void testDiamondWithNullabilityAnnotations() {
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/diamondWithNullabilityAnnotations.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("fakeOverrideInAnonymousObject.kt")
|
||||
public void testFakeOverrideInAnonymousObject() {
|
||||
|
||||
+6
@@ -19334,6 +19334,12 @@ public class LLFirReversedBlackBoxCodegenBasedTestGenerated extends AbstractLLFi
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/diamondFunction.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("diamondWithNullabilityAnnotations.kt")
|
||||
public void testDiamondWithNullabilityAnnotations() {
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/diamondWithNullabilityAnnotations.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("fakeOverrideInAnonymousObject.kt")
|
||||
public void testFakeOverrideInAnonymousObject() {
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.util
|
||||
|
||||
interface Multimap<K, out V, out C : Collection<V>> {
|
||||
interface Multimap<K, out V, out C : Collection<V>> : Iterable<Map.Entry<K, C>> {
|
||||
operator fun get(key: K): C
|
||||
operator fun contains(key: K): Boolean
|
||||
val keys: Set<K>
|
||||
val values: Collection<V>
|
||||
|
||||
operator fun iterator(): Iterator<Map.Entry<K, C>>
|
||||
override operator fun iterator(): Iterator<Map.Entry<K, C>>
|
||||
}
|
||||
|
||||
interface MutableMultimap<K, V, C : Collection<V>> : Multimap<K, V, C> {
|
||||
|
||||
+102
-13
@@ -9,10 +9,7 @@ import org.jetbrains.kotlin.fir.*
|
||||
import org.jetbrains.kotlin.fir.backend.*
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.*
|
||||
import org.jetbrains.kotlin.fir.resolve.defaultType
|
||||
import org.jetbrains.kotlin.fir.resolve.isRealOwnerOf
|
||||
import org.jetbrains.kotlin.fir.resolve.toFirRegularClass
|
||||
import org.jetbrains.kotlin.fir.resolve.toSymbol
|
||||
import org.jetbrains.kotlin.fir.resolve.*
|
||||
import org.jetbrains.kotlin.fir.scopes.*
|
||||
import org.jetbrains.kotlin.fir.scopes.impl.FirFakeOverrideGenerator
|
||||
import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
|
||||
@@ -23,6 +20,7 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.types.coneType
|
||||
import org.jetbrains.kotlin.fir.types.typeContext
|
||||
import org.jetbrains.kotlin.fir.util.setMultimapOf
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.symbols.*
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
@@ -30,6 +28,7 @@ import org.jetbrains.kotlin.ir.util.IdSignature
|
||||
import org.jetbrains.kotlin.ir.util.parentAsClass
|
||||
import org.jetbrains.kotlin.name.CallableId
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.shouldNotBeCalled
|
||||
|
||||
@RequiresOptIn(
|
||||
level = RequiresOptIn.Level.ERROR,
|
||||
@@ -225,40 +224,130 @@ class FakeOverrideGenerator(
|
||||
klass: FirClass,
|
||||
originalFunction: FirNamedFunctionSymbol,
|
||||
): List<Pair<FirNamedFunctionSymbol, ConeClassLikeLookupTag>> {
|
||||
return computeBaseSymbolsWithContainingClass(klass, originalFunction, FirTypeScope::getDirectOverriddenFunctions)
|
||||
return computeBaseSymbolsWithContainingClass(
|
||||
klass,
|
||||
originalFunction,
|
||||
FirTypeScope::getDirectOverriddenFunctions,
|
||||
FirTypeScope::processOverriddenFunctions
|
||||
)
|
||||
}
|
||||
|
||||
internal fun computeBaseSymbolsWithContainingClass(
|
||||
klass: FirClass,
|
||||
originalFunction: FirPropertySymbol,
|
||||
): List<Pair<FirPropertySymbol, ConeClassLikeLookupTag>> {
|
||||
return computeBaseSymbolsWithContainingClass(klass, originalFunction, FirTypeScope::getDirectOverriddenProperties)
|
||||
return computeBaseSymbolsWithContainingClass(
|
||||
klass,
|
||||
originalFunction,
|
||||
FirTypeScope::getDirectOverriddenProperties,
|
||||
FirTypeScope::processOverriddenProperties
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This functions takes a list of overridden symbols and associate each one with one of the supertypes, from which this symbol came
|
||||
* from. This mapping will be used later to generate proper fake-overrides in IR
|
||||
*
|
||||
* ```
|
||||
* open class A {
|
||||
* fun foo() {}
|
||||
* }
|
||||
*
|
||||
* interface B
|
||||
*
|
||||
* open class C : A()
|
||||
*
|
||||
* class D : C(), B {
|
||||
* override fun foo() {}
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* In this example FIR returns that `C.foo` overrides `A.foo`
|
||||
* But in IR there are fake-overrides on each level of the hierarchy, so we need to associate `A.foo` with class `C`,
|
||||
* so later it will be converted to IR f/o `B.foo`
|
||||
* To understand that we should choose `C` here instead of `B`, we check if the dispatch receiver of the overridden is supertype
|
||||
* of each of supertypes (here `A` from `A.foo` is not supertype of `B`, so supertype `B` is discarded)
|
||||
*
|
||||
* There might be some cases when the same overridden function is accessible from multiple supertypes.
|
||||
* Note that this example is simplified, and actually requires java classes on the way (see KT-65592)
|
||||
*
|
||||
* ```
|
||||
* interface A {
|
||||
* fun foo()
|
||||
* }
|
||||
*
|
||||
* open class AImpl : A {
|
||||
* override fun foo() {}
|
||||
* }
|
||||
*
|
||||
* interface B : A
|
||||
* open class BImpl : AImpl(), B
|
||||
*
|
||||
* interface C : B
|
||||
* class CImpl : BImpl(), C
|
||||
* ```
|
||||
*
|
||||
* Here we have f/o `CImpl.foo` function, which overrides `A.foo` and `AImpl.foo` from FIR point of view
|
||||
* `AImpl.foo` matches only with supertype `BImpl`
|
||||
* But receiver of `A.foo` is a supertype both of `BImpl` and `C`, which leads to the situation, when there are two different base
|
||||
* overridden functions matches `BImpl` supertype
|
||||
* To resolve this ambiguity (which one choose, `A.foo` or `AImpl.foo`), we need to understand, which of those functions override
|
||||
* all other candidates (see [chooseMostSpecificOverridden] function) and leave only `AImpl.foo`
|
||||
*/
|
||||
private inline fun <reified S : FirCallableSymbol<*>> computeBaseSymbolsWithContainingClass(
|
||||
klass: FirClass,
|
||||
originalSymbol: S,
|
||||
directOverridden: FirTypeScope.(S) -> List<S>,
|
||||
processOverridden: FirTypeScope.(S, (S) -> ProcessorAction) -> ProcessorAction
|
||||
): List<Pair<S, ConeClassLikeLookupTag>> {
|
||||
val scope = klass.unsubstitutedScope()
|
||||
val classLookupTag = klass.symbol.toLookupTag()
|
||||
val overriddenFirSymbols = computeBaseSymbols(originalSymbol, directOverridden, scope, classLookupTag)
|
||||
val superTypes = klass.superConeTypes
|
||||
val superTypes = klass.superConeTypes.toMutableList()
|
||||
val typeContext = session.typeContext
|
||||
return overriddenFirSymbols.flatMap { symbol ->
|
||||
with(typeContext) {
|
||||
val symbolDispatchReceiver = symbol.containingClassLookupTag() ?: return@flatMap emptyList()
|
||||
superTypes.mapNotNull { superType ->
|
||||
val overriddenPerSupertype = setMultimapOf<ConeClassLikeLookupTag, S>()
|
||||
with(typeContext) {
|
||||
for (symbol in overriddenFirSymbols) {
|
||||
val symbolDispatchReceiver = symbol.containingClassLookupTag() ?: continue
|
||||
for (superType in superTypes) {
|
||||
val compatibleType = superType.anySuperTypeConstructor {
|
||||
it.typeConstructor() == symbolDispatchReceiver
|
||||
}
|
||||
if (!compatibleType) {
|
||||
return@mapNotNull null
|
||||
continue
|
||||
}
|
||||
symbol to superType.lookupTag
|
||||
overriddenPerSupertype.put(superType.lookupTag, symbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val result = overriddenPerSupertype.map { (superType, overridden) ->
|
||||
val chosenOverridden = when (overridden.size) {
|
||||
0 -> shouldNotBeCalled()
|
||||
1 -> overridden.first()
|
||||
else -> chooseMostSpecificOverridden(superType, overridden, processOverridden)
|
||||
}
|
||||
chosenOverridden to superType
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private inline fun <reified S : FirCallableSymbol<*>> chooseMostSpecificOverridden(
|
||||
containingClassLookupTag: ConeClassLikeLookupTag,
|
||||
overridden: Collection<S>,
|
||||
processOverridden: FirTypeScope.(S, (S) -> ProcessorAction) -> ProcessorAction
|
||||
): S {
|
||||
val scope = containingClassLookupTag.toFirRegularClassSymbol(session)?.unsubstitutedScope() ?: return overridden.first()
|
||||
|
||||
val result = overridden.firstOrNull { s1 ->
|
||||
overridden.all { s2 ->
|
||||
if (s1 == s2) return@all true
|
||||
scope.anyOverriddenOf(s1, processOverridden) { it == s2 }
|
||||
}
|
||||
}
|
||||
|
||||
return result ?: overridden.first()
|
||||
}
|
||||
|
||||
internal fun calcBaseSymbolsForFakeOverrideProperty(
|
||||
|
||||
+6
@@ -19263,6 +19263,12 @@ public class FirLightTreeBlackBoxCodegenTestGenerated extends AbstractFirLightTr
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/diamondFunction.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("diamondWithNullabilityAnnotations.kt")
|
||||
public void testDiamondWithNullabilityAnnotations() {
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/diamondWithNullabilityAnnotations.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("fakeOverrideInAnonymousObject.kt")
|
||||
public void testFakeOverrideInAnonymousObject() {
|
||||
|
||||
+6
@@ -19263,6 +19263,12 @@ public class FirLightTreeBlackBoxCodegenWithIrFakeOverrideGeneratorTestGenerated
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/diamondFunction.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("diamondWithNullabilityAnnotations.kt")
|
||||
public void testDiamondWithNullabilityAnnotations() {
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/diamondWithNullabilityAnnotations.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("fakeOverrideInAnonymousObject.kt")
|
||||
public void testFakeOverrideInAnonymousObject() {
|
||||
|
||||
+6
@@ -19263,6 +19263,12 @@ public class FirPsiBlackBoxCodegenTestGenerated extends AbstractFirPsiBlackBoxCo
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/diamondFunction.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("diamondWithNullabilityAnnotations.kt")
|
||||
public void testDiamondWithNullabilityAnnotations() {
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/diamondWithNullabilityAnnotations.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("fakeOverrideInAnonymousObject.kt")
|
||||
public void testFakeOverrideInAnonymousObject() {
|
||||
|
||||
@@ -80,21 +80,29 @@ fun FirTypeScope.processOverriddenFunctions(
|
||||
mutableSetOf()
|
||||
)
|
||||
|
||||
inline fun <reified S : FirCallableSymbol<*>> FirTypeScope.anyOverriddenOf(
|
||||
symbol: S,
|
||||
processOverridden: FirTypeScope.(S, (S) -> ProcessorAction) -> ProcessorAction,
|
||||
noinline predicate: (S) -> Boolean,
|
||||
): Boolean {
|
||||
var result = false
|
||||
processOverridden(symbol) {
|
||||
if (predicate(it)) {
|
||||
result = true
|
||||
return@processOverridden ProcessorAction.STOP
|
||||
}
|
||||
|
||||
return@processOverridden ProcessorAction.NEXT
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun FirTypeScope.anyOverriddenOf(
|
||||
functionSymbol: FirNamedFunctionSymbol,
|
||||
predicate: (FirNamedFunctionSymbol) -> Boolean
|
||||
): Boolean {
|
||||
var result = false
|
||||
processOverriddenFunctions(functionSymbol) {
|
||||
if (predicate(it)) {
|
||||
result = true
|
||||
return@processOverriddenFunctions ProcessorAction.STOP
|
||||
}
|
||||
|
||||
return@processOverriddenFunctions ProcessorAction.NEXT
|
||||
}
|
||||
|
||||
return result
|
||||
return anyOverriddenOf(functionSymbol, FirTypeScope::processOverriddenFunctions, predicate)
|
||||
}
|
||||
|
||||
private fun FirTypeScope.processOverriddenFunctionsWithVisited(
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
// ISSUE: KT-65592
|
||||
|
||||
// FILE: A.java
|
||||
public interface A {
|
||||
String f();
|
||||
}
|
||||
|
||||
// FILE: AImpl.java
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class AImpl implements A {
|
||||
@Override
|
||||
@NotNull
|
||||
public String f() {
|
||||
return "OK";
|
||||
}
|
||||
}
|
||||
|
||||
// FILE: B.kt
|
||||
interface B : A
|
||||
|
||||
// FILE: BImpl.kt
|
||||
open class BImpl : AImpl(), B
|
||||
|
||||
// FILE: C.java
|
||||
public interface C extends B { }
|
||||
|
||||
// FILE: CImpl.java
|
||||
public class CImpl extends BImpl implements C { }
|
||||
|
||||
// FILE: D.java
|
||||
public interface D extends C { }
|
||||
|
||||
// FILE: DImpl.java
|
||||
public class DImpl extends CImpl implements D { }
|
||||
|
||||
// FILE: box.kt
|
||||
class Z : DImpl(), D
|
||||
|
||||
fun box(): String {
|
||||
return Z().f()
|
||||
}
|
||||
+6
@@ -19263,6 +19263,12 @@ public class JvmAbiConsistencyTestBoxGenerated extends AbstractJvmAbiConsistency
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/diamondFunction.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("diamondWithNullabilityAnnotations.kt")
|
||||
public void testDiamondWithNullabilityAnnotations() {
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/diamondWithNullabilityAnnotations.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("fakeOverrideInAnonymousObject.kt")
|
||||
public void testFakeOverrideInAnonymousObject() {
|
||||
|
||||
+6
@@ -19263,6 +19263,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/diamondFunction.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("diamondWithNullabilityAnnotations.kt")
|
||||
public void testDiamondWithNullabilityAnnotations() {
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/diamondWithNullabilityAnnotations.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("fakeOverrideInAnonymousObject.kt")
|
||||
public void testFakeOverrideInAnonymousObject() {
|
||||
|
||||
+6
@@ -19263,6 +19263,12 @@ public class IrBlackBoxCodegenWithIrInlinerTestGenerated extends AbstractIrBlack
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/diamondFunction.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("diamondWithNullabilityAnnotations.kt")
|
||||
public void testDiamondWithNullabilityAnnotations() {
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/diamondWithNullabilityAnnotations.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("fakeOverrideInAnonymousObject.kt")
|
||||
public void testFakeOverrideInAnonymousObject() {
|
||||
|
||||
+5
@@ -16021,6 +16021,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/diamondFunction.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("diamondWithNullabilityAnnotations.kt")
|
||||
public void testDiamondWithNullabilityAnnotations() {
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/diamondWithNullabilityAnnotations.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("fakeOverrideInAnonymousObject.kt")
|
||||
public void testFakeOverrideInAnonymousObject() {
|
||||
runTest("compiler/testData/codegen/box/fakeOverride/fakeOverrideInAnonymousObject.kt");
|
||||
|
||||
Reference in New Issue
Block a user