[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
This commit is contained in:
Nikolay Lunyak
2024-02-21 14:49:42 +02:00
committed by Space Team
parent ee20d979cb
commit 341a23d05c
9 changed files with 68 additions and 49 deletions
@@ -334,7 +334,10 @@ class FirSignatureEnhancement(
this.name = name!! this.name = name!!
status = firMethod.status status = firMethod.status
symbol = if (isIntersectionOverride) { symbol = if (isIntersectionOverride) {
FirIntersectionOverrideFunctionSymbol(methodId, overriddenMembers.map { it.symbol }) FirIntersectionOverrideFunctionSymbol(
methodId, overriddenMembers.map { it.symbol },
containsMultipleNonSubsumed = (firMethod.symbol as? FirIntersectionCallableSymbol)?.containsMultipleNonSubsumed == true,
)
} else { } else {
FirNamedFunctionSymbol(methodId) FirNamedFunctionSymbol(methodId)
}.also { functionSymbol = it } }.also { functionSymbol = it }
@@ -375,7 +375,11 @@ class JavaOverrideChecker internal constructor(
extractedOverrides: Collection<MemberWithBaseScope<D>>, extractedOverrides: Collection<MemberWithBaseScope<D>>,
dispatchClassSymbol: FirRegularClassSymbol?, dispatchClassSymbol: FirRegularClassSymbol?,
): Visibility { ): 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() val nonSubsumed = overridesWithoutIntersections.nonSubsumed().filterOutDuplicates()
// In Java it's OK to inherit multiple implementations of the same function // In Java it's OK to inherit multiple implementations of the same function
@@ -176,6 +176,16 @@ fun MemberWithBaseScope<FirCallableSymbol<*>>.flattenIntersectionsRecursively():
return baseScope.getDirectOverriddenMembersWithBaseScope(member).flatMap { it.flattenIntersectionsRecursively() } return baseScope.getDirectOverriddenMembersWithBaseScope(member).flatMap { it.flattenIntersectionsRecursively() }
} }
fun MemberWithBaseScope<FirCallableSymbol<*>>.flattenPhantomIntersectionsRecursively(): List<MemberWithBaseScope<FirCallableSymbol<*>>> {
val symbol = member.unwrapSubstitutionOverrides<FirCallableSymbol<*>>()
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 D [subsumes](https://kotlinlang.org/spec/inheritance.html#matching-and-subsumption-of-declarations)
* a callable declaration B if D overrides B. * a callable declaration B if D overrides B.
@@ -166,7 +166,11 @@ class FirStandardOverrideChecker(private val session: FirSession) : FirAbstractO
extractedOverrides: Collection<MemberWithBaseScope<D>>, extractedOverrides: Collection<MemberWithBaseScope<D>>,
dispatchClassSymbol: FirRegularClassSymbol?, dispatchClassSymbol: FirRegularClassSymbol?,
): Visibility { ): 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() val nonSubsumed = overridesWithoutIntersections.nonSubsumed().filterOutDuplicates()
return chooseIntersectionVisibilityOrNull(nonSubsumed) ?: Visibilities.Unknown return chooseIntersectionVisibilityOrNull(nonSubsumed) ?: Visibilities.Unknown
} }
@@ -64,7 +64,8 @@ class FirTypeIntersectionScopeContext(
val context: FirTypeIntersectionScopeContext, val context: FirTypeIntersectionScopeContext,
val mostSpecific: List<MemberWithBaseScope<D>>, val mostSpecific: List<MemberWithBaseScope<D>>,
overriddenMembers: List<MemberWithBaseScope<D>>, overriddenMembers: List<MemberWithBaseScope<D>>,
containingScope: FirTypeScope? containingScope: FirTypeScope?,
val containsMultipleNonSubsumed: Boolean,
) : ResultOfIntersection<D>(overriddenMembers, containingScope) { ) : ResultOfIntersection<D>(overriddenMembers, containingScope) {
override val chosenSymbol: D by lazy { override val chosenSymbol: D by lazy {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@@ -174,7 +175,15 @@ class FirTypeIntersectionScopeContext(
} }
if (nonTrivial) { if (nonTrivial) {
// Only add non-subsumed members to list of overridden in intersection override. // 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 { } else {
val (member, containingScope) = mostSpecific.first() val (member, containingScope) = mostSpecific.first()
result += ResultOfIntersection.SingleMember(member, group, containingScope) result += ResultOfIntersection.SingleMember(member, group, containingScope)
@@ -203,6 +212,7 @@ class FirTypeIntersectionScopeContext(
fun <D : FirCallableSymbol<*>> createIntersectionOverride( fun <D : FirCallableSymbol<*>> createIntersectionOverride(
mostSpecific: List<MemberWithBaseScope<D>>, mostSpecific: List<MemberWithBaseScope<D>>,
extractedOverrides: List<MemberWithBaseScope<D>>, extractedOverrides: List<MemberWithBaseScope<D>>,
containsMultipleNonSubsumed: Boolean,
): MemberWithBaseScope<FirCallableSymbol<*>> { ): MemberWithBaseScope<FirCallableSymbol<*>> {
val newModality = chooseIntersectionOverrideModality(extractedOverrides.flatMap { it.flattenIntersectionsRecursively() }.nonSubsumed()) val newModality = chooseIntersectionOverrideModality(extractedOverrides.flatMap { it.flattenIntersectionsRecursively() }.nonSubsumed())
val newVisibility = overrideChecker.chooseIntersectionVisibility(extractedOverrides, dispatchClassSymbol) val newVisibility = overrideChecker.chooseIntersectionVisibility(extractedOverrides, dispatchClassSymbol)
@@ -211,14 +221,20 @@ class FirTypeIntersectionScopeContext(
val key = mostSpecific.first() val key = mostSpecific.first()
return when (key.member) { return when (key.member) {
is FirNamedFunctionSymbol -> is FirNamedFunctionSymbol ->
createIntersectionOverrideFunction(mostSpecificSymbols, extractedOverridesSymbols, newModality, newVisibility) createIntersectionOverrideFunction(
mostSpecificSymbols, extractedOverridesSymbols, newModality, newVisibility, containsMultipleNonSubsumed
)
is FirPropertySymbol -> is FirPropertySymbol ->
createIntersectionOverrideProperty(mostSpecificSymbols, extractedOverridesSymbols, newModality, newVisibility) createIntersectionOverrideProperty(
mostSpecificSymbols, extractedOverridesSymbols, newModality, newVisibility, containsMultipleNonSubsumed
)
is FirFieldSymbol -> { is FirFieldSymbol -> {
if (forClassUseSiteScope) error("Can not create intersection override in class scope for field ${key.member}") 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}") else -> error("Unsupported symbol type for creating intersection overrides: ${key.member}")
@@ -323,6 +339,7 @@ class FirTypeIntersectionScopeContext(
overrides: Collection<FirCallableSymbol<*>>, overrides: Collection<FirCallableSymbol<*>>,
newModality: Modality?, newModality: Modality?,
newVisibility: Visibility, newVisibility: Visibility,
containsMultipleNonSubsumed: Boolean,
): FirNamedFunctionSymbol { ): FirNamedFunctionSymbol {
val key = mostSpecific.first() as FirNamedFunctionSymbol val key = mostSpecific.first() as FirNamedFunctionSymbol
val keyFir = key.fir val keyFir = key.fir
@@ -330,7 +347,7 @@ class FirTypeIntersectionScopeContext(
dispatchReceiverType.classId ?: keyFir.dispatchReceiverClassLookupTagOrNull()?.classId!!, dispatchReceiverType.classId ?: keyFir.dispatchReceiverClassLookupTagOrNull()?.classId!!,
keyFir.name keyFir.name
) )
val newSymbol = FirIntersectionOverrideFunctionSymbol(callableId, overrides) val newSymbol = FirIntersectionOverrideFunctionSymbol(callableId, overrides, containsMultipleNonSubsumed)
val deferredReturnTypeCalculation = deferredReturnTypeCalculationOrNull(mostSpecific) val deferredReturnTypeCalculation = deferredReturnTypeCalculationOrNull(mostSpecific)
FirFakeOverrideGenerator.createCopyForFirFunction( FirFakeOverrideGenerator.createCopyForFirFunction(
newSymbol, keyFir, derivedClassLookupTag = null, session, newSymbol, keyFir, derivedClassLookupTag = null, session,
@@ -353,10 +370,12 @@ class FirTypeIntersectionScopeContext(
overrides: Collection<FirCallableSymbol<*>>, overrides: Collection<FirCallableSymbol<*>>,
newModality: Modality?, newModality: Modality?,
newVisibility: Visibility, newVisibility: Visibility,
containsMultipleNonSubsumed: Boolean,
): FirPropertySymbol { ): FirPropertySymbol {
return createIntersectionOverrideVariable<FirPropertySymbol, _>( return createIntersectionOverrideVariable<FirPropertySymbol, _>(
mostSpecific, mostSpecific,
overrides, overrides,
containsMultipleNonSubsumed,
::FirIntersectionOverridePropertySymbol, ::FirIntersectionOverridePropertySymbol,
) { symbol, fir, deferredReturnTypeCalculation, returnType -> ) { symbol, fir, deferredReturnTypeCalculation, returnType ->
FirFakeOverrideGenerator.createCopyForFirProperty( FirFakeOverrideGenerator.createCopyForFirProperty(
@@ -380,10 +399,12 @@ class FirTypeIntersectionScopeContext(
overrides: Collection<FirCallableSymbol<*>>, overrides: Collection<FirCallableSymbol<*>>,
newModality: Modality?, newModality: Modality?,
newVisibility: Visibility, newVisibility: Visibility,
containsMultipleNonSubsumed: Boolean,
): FirFieldSymbol { ): FirFieldSymbol {
return createIntersectionOverrideVariable<FirFieldSymbol, _>( return createIntersectionOverrideVariable<FirFieldSymbol, _>(
mostSpecific, mostSpecific,
overrides, overrides,
containsMultipleNonSubsumed,
::FirIntersectionOverrideFieldSymbol ::FirIntersectionOverrideFieldSymbol
) { symbol, fir, deferredReturnTypeCalculation, returnType -> ) { symbol, fir, deferredReturnTypeCalculation, returnType ->
FirFakeOverrideGenerator.createCopyForFirField( FirFakeOverrideGenerator.createCopyForFirField(
@@ -404,13 +425,14 @@ class FirTypeIntersectionScopeContext(
private inline fun <reified S : FirVariableSymbol<F>, F : FirVariable> createIntersectionOverrideVariable( private inline fun <reified S : FirVariableSymbol<F>, F : FirVariable> createIntersectionOverrideVariable(
mostSpecific: Collection<FirCallableSymbol<*>>, mostSpecific: Collection<FirCallableSymbol<*>>,
overrides: Collection<FirCallableSymbol<*>>, overrides: Collection<FirCallableSymbol<*>>,
createIntersectionOverrideSymbol: (CallableId, Collection<FirCallableSymbol<*>>) -> S, containsMultipleNonSubsumed: Boolean,
createIntersectionOverrideSymbol: (CallableId, Collection<FirCallableSymbol<*>>, Boolean) -> S,
createCopy: (S, F, deferredReturnTypeCalculation: CallableCopyDeferredReturnTypeCalculation?, returnType: ConeKotlinType?) -> F createCopy: (S, F, deferredReturnTypeCalculation: CallableCopyDeferredReturnTypeCalculation?, returnType: ConeKotlinType?) -> F
): S { ): S {
val key = mostSpecific.first() as S val key = mostSpecific.first() as S
val keyFir = key.fir val keyFir = key.fir
val callableId = CallableId(dispatchReceiverType.classId ?: keyFir.dispatchReceiverClassLookupTagOrNull()?.classId!!, keyFir.name) 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 deferredReturnTypeCalculation = deferredReturnTypeCalculationOrNull(mostSpecific)
val newReturnType = val newReturnType =
runIf(!forClassUseSiteScope && mostSpecific.none { (it as FirVariableSymbol<*>).fir.isVar } && deferredReturnTypeCalculation == null) { runIf(!forClassUseSiteScope && mostSpecific.none { (it as FirVariableSymbol<*>).fir.isVar } && deferredReturnTypeCalculation == null) {
@@ -459,7 +481,7 @@ class FirIntersectionOverrideStorage(val session: FirSession) : FirSessionCompon
val cacheByScope: FirCache<ConeKotlinType, FirIntersectionOverrideCache, Nothing?> = val cacheByScope: FirCache<ConeKotlinType, FirIntersectionOverrideCache, Nothing?> =
cachesFactory.createCache { _ -> cachesFactory.createCache { _ ->
cachesFactory.createCache { _, result -> cachesFactory.createCache { _, result ->
result.context.createIntersectionOverride(result.mostSpecific, result.overriddenMembers) result.context.createIntersectionOverride(result.mostSpecific, result.overriddenMembers, result.containsMultipleNonSubsumed)
} }
} }
} }
@@ -47,11 +47,19 @@ open class FirNamedFunctionSymbol(callableId: CallableId) : FirFunctionSymbol<Fi
interface FirIntersectionCallableSymbol { interface FirIntersectionCallableSymbol {
val intersections: Collection<FirCallableSymbol<*>> val intersections: Collection<FirCallableSymbol<*>>
/**
* `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( class FirIntersectionOverrideFunctionSymbol(
callableId: CallableId, callableId: CallableId,
override val intersections: Collection<FirCallableSymbol<*>>, override val intersections: Collection<FirCallableSymbol<*>>,
override val containsMultipleNonSubsumed: Boolean,
) : FirNamedFunctionSymbol(callableId), FirIntersectionCallableSymbol ) : FirNamedFunctionSymbol(callableId), FirIntersectionCallableSymbol
class FirConstructorSymbol(callableId: CallableId) : FirFunctionSymbol<FirConstructor>(callableId), ConstructorSymbolMarker { class FirConstructorSymbol(callableId: CallableId) : FirFunctionSymbol<FirConstructor>(callableId), ConstructorSymbolMarker {
@@ -72,12 +72,14 @@ open class FirPropertySymbol(callableId: CallableId, ) : FirVariableSymbol<FirPr
class FirIntersectionOverridePropertySymbol( class FirIntersectionOverridePropertySymbol(
callableId: CallableId, callableId: CallableId,
override val intersections: Collection<FirCallableSymbol<*>> override val intersections: Collection<FirCallableSymbol<*>>,
override val containsMultipleNonSubsumed: Boolean,
) : FirPropertySymbol(callableId), FirIntersectionCallableSymbol ) : FirPropertySymbol(callableId), FirIntersectionCallableSymbol
class FirIntersectionOverrideFieldSymbol( class FirIntersectionOverrideFieldSymbol(
callableId: CallableId, callableId: CallableId,
override val intersections: Collection<FirCallableSymbol<*>> override val intersections: Collection<FirCallableSymbol<*>>,
override val containsMultipleNonSubsumed: Boolean,
) : FirFieldSymbol(callableId), FirIntersectionCallableSymbol ) : FirFieldSymbol(callableId), FirIntersectionCallableSymbol
class FirBackingFieldSymbol(callableId: CallableId) : FirVariableSymbol<FirBackingField>(callableId) { class FirBackingFieldSymbol(callableId: CallableId) : FirVariableSymbol<FirBackingField>(callableId) {
@@ -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 <!CANNOT_INFER_VISIBILITY!>class TextFragmentImpl<!> : TextFragment, SimpleColoredComponent()
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// FILE: ColoredTextContainer.java // FILE: ColoredTextContainer.java
public interface ColoredTextContainer { public interface ColoredTextContainer {
default void setToolTipText(String text) {} default void setToolTipText(String text) {}