From 341a23d05cc09effc489b7169c0f2e5abd1776c6 Mon Sep 17 00:00:00 2001 From: Nikolay Lunyak Date: Wed, 21 Feb 2024 14:49:42 +0200 Subject: [PATCH] [FIR] Introduce `containsMultipleNonSubsumed` to intersection overrides After this change the IO created for `SimpleColoredComponent::setToolTipText` will be `containsMultipleNonSubsumed == true` while IOs created for classes in `intersectionOverridesIntersection.kt` will have this property set to `false`. ^KT-65972 Fixed --- .../java/enhancement/SignatureEnhancement.kt | 5 ++- .../fir/java/scopes/JavaOverrideChecker.kt | 6 ++- .../fir/declarations/declarationUtils.kt | 10 +++++ .../scopes/impl/FirStandardOverrideChecker.kt | 6 ++- .../impl/FirTypeIntersectionScopeContext.kt | 40 ++++++++++++++----- .../fir/symbols/impl/FirFunctionSymbol.kt | 8 ++++ .../fir/symbols/impl/FirVariableSymbol.kt | 6 ++- ...avaOverriddenByIntersectionInKotlin.fir.kt | 35 ---------------- ...sInJavaOverriddenByIntersectionInKotlin.kt | 1 + 9 files changed, 68 insertions(+), 49 deletions(-) delete mode 100644 compiler/testData/diagnostics/tests/intersectionWithMultipleDefaultsInJavaOverriddenByIntersectionInKotlin.fir.kt diff --git a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/enhancement/SignatureEnhancement.kt b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/enhancement/SignatureEnhancement.kt index c001d69785d..736097d06f7 100644 --- a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/enhancement/SignatureEnhancement.kt +++ b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/enhancement/SignatureEnhancement.kt @@ -334,7 +334,10 @@ class FirSignatureEnhancement( this.name = name!! status = firMethod.status symbol = if (isIntersectionOverride) { - FirIntersectionOverrideFunctionSymbol(methodId, overriddenMembers.map { it.symbol }) + FirIntersectionOverrideFunctionSymbol( + methodId, overriddenMembers.map { it.symbol }, + containsMultipleNonSubsumed = (firMethod.symbol as? FirIntersectionCallableSymbol)?.containsMultipleNonSubsumed == true, + ) } else { FirNamedFunctionSymbol(methodId) }.also { functionSymbol = it } diff --git a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/scopes/JavaOverrideChecker.kt b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/scopes/JavaOverrideChecker.kt index 25cd697afdb..2aef507edd1 100644 --- a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/scopes/JavaOverrideChecker.kt +++ b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/scopes/JavaOverrideChecker.kt @@ -375,7 +375,11 @@ class JavaOverrideChecker internal constructor( extractedOverrides: Collection>, dispatchClassSymbol: FirRegularClassSymbol?, ): Visibility { - val overridesWithoutIntersections = extractedOverrides.flatMap { it.flattenIntersectionsRecursively() } + // It's crucial that we only unwrap phantom intersection overrides. + // See comments in the following tests for explanation: + // - intersectionWithMultipleDefaultsInJavaOverriddenByIntersectionInKotlin.kt + // - intersectionOverridesIntersection.kt + val overridesWithoutIntersections = extractedOverrides.flatMap { it.flattenPhantomIntersectionsRecursively() } val nonSubsumed = overridesWithoutIntersections.nonSubsumed().filterOutDuplicates() // In Java it's OK to inherit multiple implementations of the same function diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/declarations/declarationUtils.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/declarations/declarationUtils.kt index e8b18314305..2cfbe3d9f74 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/declarations/declarationUtils.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/declarations/declarationUtils.kt @@ -176,6 +176,16 @@ fun MemberWithBaseScope>.flattenIntersectionsRecursively(): return baseScope.getDirectOverriddenMembersWithBaseScope(member).flatMap { it.flattenIntersectionsRecursively() } } +fun MemberWithBaseScope>.flattenPhantomIntersectionsRecursively(): List>> { + val symbol = member.unwrapSubstitutionOverrides>() + + if (symbol !is FirIntersectionCallableSymbol || symbol.containsMultipleNonSubsumed) { + return listOf(this) + } + + return baseScope.getDirectOverriddenMembersWithBaseScope(member).flatMap { it.flattenPhantomIntersectionsRecursively() } +} + /** * A callable declaration D [subsumes](https://kotlinlang.org/spec/inheritance.html#matching-and-subsumption-of-declarations) * a callable declaration B if D overrides B. diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirStandardOverrideChecker.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirStandardOverrideChecker.kt index 988148047bc..1e4a375aefc 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirStandardOverrideChecker.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirStandardOverrideChecker.kt @@ -166,7 +166,11 @@ class FirStandardOverrideChecker(private val session: FirSession) : FirAbstractO extractedOverrides: Collection>, dispatchClassSymbol: FirRegularClassSymbol?, ): Visibility { - val overridesWithoutIntersections = extractedOverrides.flatMap { it.flattenIntersectionsRecursively() } + // It's crucial that we only unwrap phantom intersection overrides. + // See comments in the following tests for explanation: + // - intersectionWithMultipleDefaultsInJavaOverriddenByIntersectionInKotlin.kt + // - intersectionOverridesIntersection.kt + val overridesWithoutIntersections = extractedOverrides.flatMap { it.flattenPhantomIntersectionsRecursively() } val nonSubsumed = overridesWithoutIntersections.nonSubsumed().filterOutDuplicates() return chooseIntersectionVisibilityOrNull(nonSubsumed) ?: Visibilities.Unknown } diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirTypeIntersectionScopeContext.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirTypeIntersectionScopeContext.kt index 90645c47601..9ae7e36723a 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirTypeIntersectionScopeContext.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/scopes/impl/FirTypeIntersectionScopeContext.kt @@ -64,7 +64,8 @@ class FirTypeIntersectionScopeContext( val context: FirTypeIntersectionScopeContext, val mostSpecific: List>, overriddenMembers: List>, - containingScope: FirTypeScope? + containingScope: FirTypeScope?, + val containsMultipleNonSubsumed: Boolean, ) : ResultOfIntersection(overriddenMembers, containingScope) { override val chosenSymbol: D by lazy { @Suppress("UNCHECKED_CAST") @@ -174,7 +175,15 @@ class FirTypeIntersectionScopeContext( } if (nonTrivial) { // Only add non-subsumed members to list of overridden in intersection override. - result += ResultOfIntersection.NonTrivial(this, mostSpecific, overriddenMembers = group, containingScope = null) + result += ResultOfIntersection.NonTrivial( + this, mostSpecific, + overriddenMembers = group, + containingScope = null, + containsMultipleNonSubsumed = when { + forClassUseSiteScope -> group.flatMap { it.flattenPhantomIntersectionsRecursively() }.nonSubsumed().size > 1 + else -> mostSpecific.flatMap { it.flattenPhantomIntersectionsRecursively() }.nonSubsumed().size > 1 + }, + ) } else { val (member, containingScope) = mostSpecific.first() result += ResultOfIntersection.SingleMember(member, group, containingScope) @@ -203,6 +212,7 @@ class FirTypeIntersectionScopeContext( fun > createIntersectionOverride( mostSpecific: List>, extractedOverrides: List>, + containsMultipleNonSubsumed: Boolean, ): MemberWithBaseScope> { val newModality = chooseIntersectionOverrideModality(extractedOverrides.flatMap { it.flattenIntersectionsRecursively() }.nonSubsumed()) val newVisibility = overrideChecker.chooseIntersectionVisibility(extractedOverrides, dispatchClassSymbol) @@ -211,14 +221,20 @@ class FirTypeIntersectionScopeContext( val key = mostSpecific.first() return when (key.member) { is FirNamedFunctionSymbol -> - createIntersectionOverrideFunction(mostSpecificSymbols, extractedOverridesSymbols, newModality, newVisibility) + createIntersectionOverrideFunction( + mostSpecificSymbols, extractedOverridesSymbols, newModality, newVisibility, containsMultipleNonSubsumed + ) is FirPropertySymbol -> - createIntersectionOverrideProperty(mostSpecificSymbols, extractedOverridesSymbols, newModality, newVisibility) + createIntersectionOverrideProperty( + mostSpecificSymbols, extractedOverridesSymbols, newModality, newVisibility, containsMultipleNonSubsumed + ) is FirFieldSymbol -> { if (forClassUseSiteScope) error("Can not create intersection override in class scope for field ${key.member}") - createIntersectionOverrideField(mostSpecificSymbols, extractedOverridesSymbols, newModality, newVisibility) + createIntersectionOverrideField( + mostSpecificSymbols, extractedOverridesSymbols, newModality, newVisibility, containsMultipleNonSubsumed + ) } else -> error("Unsupported symbol type for creating intersection overrides: ${key.member}") @@ -323,6 +339,7 @@ class FirTypeIntersectionScopeContext( overrides: Collection>, newModality: Modality?, newVisibility: Visibility, + containsMultipleNonSubsumed: Boolean, ): FirNamedFunctionSymbol { val key = mostSpecific.first() as FirNamedFunctionSymbol val keyFir = key.fir @@ -330,7 +347,7 @@ class FirTypeIntersectionScopeContext( dispatchReceiverType.classId ?: keyFir.dispatchReceiverClassLookupTagOrNull()?.classId!!, keyFir.name ) - val newSymbol = FirIntersectionOverrideFunctionSymbol(callableId, overrides) + val newSymbol = FirIntersectionOverrideFunctionSymbol(callableId, overrides, containsMultipleNonSubsumed) val deferredReturnTypeCalculation = deferredReturnTypeCalculationOrNull(mostSpecific) FirFakeOverrideGenerator.createCopyForFirFunction( newSymbol, keyFir, derivedClassLookupTag = null, session, @@ -353,10 +370,12 @@ class FirTypeIntersectionScopeContext( overrides: Collection>, newModality: Modality?, newVisibility: Visibility, + containsMultipleNonSubsumed: Boolean, ): FirPropertySymbol { return createIntersectionOverrideVariable( mostSpecific, overrides, + containsMultipleNonSubsumed, ::FirIntersectionOverridePropertySymbol, ) { symbol, fir, deferredReturnTypeCalculation, returnType -> FirFakeOverrideGenerator.createCopyForFirProperty( @@ -380,10 +399,12 @@ class FirTypeIntersectionScopeContext( overrides: Collection>, newModality: Modality?, newVisibility: Visibility, + containsMultipleNonSubsumed: Boolean, ): FirFieldSymbol { return createIntersectionOverrideVariable( mostSpecific, overrides, + containsMultipleNonSubsumed, ::FirIntersectionOverrideFieldSymbol ) { symbol, fir, deferredReturnTypeCalculation, returnType -> FirFakeOverrideGenerator.createCopyForFirField( @@ -404,13 +425,14 @@ class FirTypeIntersectionScopeContext( private inline fun , F : FirVariable> createIntersectionOverrideVariable( mostSpecific: Collection>, overrides: Collection>, - createIntersectionOverrideSymbol: (CallableId, Collection>) -> S, + containsMultipleNonSubsumed: Boolean, + createIntersectionOverrideSymbol: (CallableId, Collection>, Boolean) -> S, createCopy: (S, F, deferredReturnTypeCalculation: CallableCopyDeferredReturnTypeCalculation?, returnType: ConeKotlinType?) -> F ): S { val key = mostSpecific.first() as S val keyFir = key.fir val callableId = CallableId(dispatchReceiverType.classId ?: keyFir.dispatchReceiverClassLookupTagOrNull()?.classId!!, keyFir.name) - val newSymbol = createIntersectionOverrideSymbol(callableId, overrides) + val newSymbol = createIntersectionOverrideSymbol(callableId, overrides, containsMultipleNonSubsumed) val deferredReturnTypeCalculation = deferredReturnTypeCalculationOrNull(mostSpecific) val newReturnType = runIf(!forClassUseSiteScope && mostSpecific.none { (it as FirVariableSymbol<*>).fir.isVar } && deferredReturnTypeCalculation == null) { @@ -459,7 +481,7 @@ class FirIntersectionOverrideStorage(val session: FirSession) : FirSessionCompon val cacheByScope: FirCache = cachesFactory.createCache { _ -> cachesFactory.createCache { _, result -> - result.context.createIntersectionOverride(result.mostSpecific, result.overriddenMembers) + result.context.createIntersectionOverride(result.mostSpecific, result.overriddenMembers, result.containsMultipleNonSubsumed) } } } diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/symbols/impl/FirFunctionSymbol.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/symbols/impl/FirFunctionSymbol.kt index ed3a92b0fc2..16449fc6b65 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/symbols/impl/FirFunctionSymbol.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/symbols/impl/FirFunctionSymbol.kt @@ -47,11 +47,19 @@ open class FirNamedFunctionSymbol(callableId: CallableId) : FirFunctionSymbol> + + /** + * `true` iff a call to `nonSubsumed()` for `intersections` would result into a list with more than one symbol. + * Intuitively, `false` means this intersection is, strictly speaking, redundant, but we still created it + * as an implementation detail. + */ + val containsMultipleNonSubsumed: Boolean } class FirIntersectionOverrideFunctionSymbol( callableId: CallableId, override val intersections: Collection>, + override val containsMultipleNonSubsumed: Boolean, ) : FirNamedFunctionSymbol(callableId), FirIntersectionCallableSymbol class FirConstructorSymbol(callableId: CallableId) : FirFunctionSymbol(callableId), ConstructorSymbolMarker { diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/symbols/impl/FirVariableSymbol.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/symbols/impl/FirVariableSymbol.kt index 82cb64bdb59..461dbf79936 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/symbols/impl/FirVariableSymbol.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/symbols/impl/FirVariableSymbol.kt @@ -72,12 +72,14 @@ open class FirPropertySymbol(callableId: CallableId, ) : FirVariableSymbol> + override val intersections: Collection>, + override val containsMultipleNonSubsumed: Boolean, ) : FirPropertySymbol(callableId), FirIntersectionCallableSymbol class FirIntersectionOverrideFieldSymbol( callableId: CallableId, - override val intersections: Collection> + override val intersections: Collection>, + override val containsMultipleNonSubsumed: Boolean, ) : FirFieldSymbol(callableId), FirIntersectionCallableSymbol class FirBackingFieldSymbol(callableId: CallableId) : FirVariableSymbol(callableId) { diff --git a/compiler/testData/diagnostics/tests/intersectionWithMultipleDefaultsInJavaOverriddenByIntersectionInKotlin.fir.kt b/compiler/testData/diagnostics/tests/intersectionWithMultipleDefaultsInJavaOverriddenByIntersectionInKotlin.fir.kt deleted file mode 100644 index 875986943d1..00000000000 --- a/compiler/testData/diagnostics/tests/intersectionWithMultipleDefaultsInJavaOverriddenByIntersectionInKotlin.fir.kt +++ /dev/null @@ -1,35 +0,0 @@ -// FILE: ColoredTextContainer.java -public interface ColoredTextContainer { - default void setToolTipText(String text) {} -} - -// FILE: JComponent.java -public abstract class JComponent { - public void setToolTipText(String text) {} -} - -// FILE: SimpleColoredComponent.java -// IDEALLY: -// Provides `IO SimpleColoredComponent::setToolTipText`, because inherits -// `JComponent::setToolTipText` and `ColoredTextContainer::setToolTipText`, -// which don't sumbsume one another, but Java allows picking the class-based one in this case. -// REALITY: -// Contains an IO for the above. We check if this is green Java via a -// modified check that accounts for this case. -public class SimpleColoredComponent extends JComponent implements ColoredTextContainer {} - -// FILE: Main.kt - -// Not important, left for the record -interface TextFragment : ColoredTextContainer - -// IDEALLY: -// Provides `JComponent::setToolTipText`, because inherits -// `ColoredTextContainer::setToolTipText` and `IO SimpleColoredComponent::setToolTipText`, -// and the latter subsumes the former. -// REALITY: -// Contains IO between `ColoredTextContainer::setToolTipText` and `IO SimpleColoredComponent::setToolTipText`. -// In this case `IO SimpleColoredComponent::setToolTipText` should not be unwrapped, otherwise -// we miss `nonSubsumed()` check and since this is a Kotlin class we are not allowed to implicitly choose -// between `JComponent::setToolTipText` and `ColoredTextContainer::setToolTipText`. -private class TextFragmentImpl : TextFragment, SimpleColoredComponent() diff --git a/compiler/testData/diagnostics/tests/intersectionWithMultipleDefaultsInJavaOverriddenByIntersectionInKotlin.kt b/compiler/testData/diagnostics/tests/intersectionWithMultipleDefaultsInJavaOverriddenByIntersectionInKotlin.kt index 9d0c457348b..d052d382865 100644 --- a/compiler/testData/diagnostics/tests/intersectionWithMultipleDefaultsInJavaOverriddenByIntersectionInKotlin.kt +++ b/compiler/testData/diagnostics/tests/intersectionWithMultipleDefaultsInJavaOverriddenByIntersectionInKotlin.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // FILE: ColoredTextContainer.java public interface ColoredTextContainer { default void setToolTipText(String text) {}