diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirBlackBoxCodegenBasedTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirBlackBoxCodegenBasedTestGenerated.java index 61714626497..84a92e314fd 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirBlackBoxCodegenBasedTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirBlackBoxCodegenBasedTestGenerated.java @@ -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() { diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirReversedBlackBoxCodegenBasedTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirReversedBlackBoxCodegenBasedTestGenerated.java index 2514a3ce129..eb07095190d 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirReversedBlackBoxCodegenBasedTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/LLFirReversedBlackBoxCodegenBasedTestGenerated.java @@ -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() { diff --git a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/util/Multimap.kt b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/util/Multimap.kt index 8a10368375e..5e6b38d298a 100644 --- a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/util/Multimap.kt +++ b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/util/Multimap.kt @@ -5,13 +5,13 @@ package org.jetbrains.kotlin.fir.util -interface Multimap> { +interface Multimap> : Iterable> { operator fun get(key: K): C operator fun contains(key: K): Boolean val keys: Set val values: Collection - operator fun iterator(): Iterator> + override operator fun iterator(): Iterator> } interface MutableMultimap> : Multimap { diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/FakeOverrideGenerator.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/FakeOverrideGenerator.kt index 8bcfc88c3fd..014565363a2 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/FakeOverrideGenerator.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/FakeOverrideGenerator.kt @@ -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> { - return computeBaseSymbolsWithContainingClass(klass, originalFunction, FirTypeScope::getDirectOverriddenFunctions) + return computeBaseSymbolsWithContainingClass( + klass, + originalFunction, + FirTypeScope::getDirectOverriddenFunctions, + FirTypeScope::processOverriddenFunctions + ) } internal fun computeBaseSymbolsWithContainingClass( klass: FirClass, originalFunction: FirPropertySymbol, ): List> { - 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 > computeBaseSymbolsWithContainingClass( klass: FirClass, originalSymbol: S, directOverridden: FirTypeScope.(S) -> List, + processOverridden: FirTypeScope.(S, (S) -> ProcessorAction) -> ProcessorAction ): List> { 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() + 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 > chooseMostSpecificOverridden( + containingClassLookupTag: ConeClassLikeLookupTag, + overridden: Collection, + 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( diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java index 8c32e7a7fea..2ae41237f06 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java @@ -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() { diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenWithIrFakeOverrideGeneratorTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenWithIrFakeOverrideGeneratorTestGenerated.java index 79fa24d43d4..780fef85412 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenWithIrFakeOverrideGeneratorTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenWithIrFakeOverrideGeneratorTestGenerated.java @@ -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() { diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java index 6e6cb9cd269..197a309c6a8 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java @@ -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() { diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/scopes/FirTypeScope.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/scopes/FirTypeScope.kt index 8c48df88ca2..c31b3abad4e 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/scopes/FirTypeScope.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/scopes/FirTypeScope.kt @@ -80,21 +80,29 @@ fun FirTypeScope.processOverriddenFunctions( mutableSetOf() ) +inline fun > 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( diff --git a/compiler/testData/codegen/box/fakeOverride/diamondWithNullabilityAnnotations.kt b/compiler/testData/codegen/box/fakeOverride/diamondWithNullabilityAnnotations.kt new file mode 100644 index 00000000000..373784ea3d1 --- /dev/null +++ b/compiler/testData/codegen/box/fakeOverride/diamondWithNullabilityAnnotations.kt @@ -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() +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/JvmAbiConsistencyTestBoxGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/JvmAbiConsistencyTestBoxGenerated.java index 0c3da8b5e21..88f7ce395c0 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/JvmAbiConsistencyTestBoxGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/JvmAbiConsistencyTestBoxGenerated.java @@ -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() { diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java index 65ac99a0a33..b8a1b9f5f88 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java @@ -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() { diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenWithIrInlinerTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenWithIrInlinerTestGenerated.java index fdab3be2066..ee308a8ddd8 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenWithIrInlinerTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenWithIrInlinerTestGenerated.java @@ -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() { diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index a345fb21a9c..4c41948d83d 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -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");