[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!!
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 }
@@ -375,7 +375,11 @@ class JavaOverrideChecker internal constructor(
extractedOverrides: Collection<MemberWithBaseScope<D>>,
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
@@ -176,6 +176,16 @@ fun MemberWithBaseScope<FirCallableSymbol<*>>.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 B if D overrides B.
@@ -166,7 +166,11 @@ class FirStandardOverrideChecker(private val session: FirSession) : FirAbstractO
extractedOverrides: Collection<MemberWithBaseScope<D>>,
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
}
@@ -64,7 +64,8 @@ class FirTypeIntersectionScopeContext(
val context: FirTypeIntersectionScopeContext,
val mostSpecific: List<MemberWithBaseScope<D>>,
overriddenMembers: List<MemberWithBaseScope<D>>,
containingScope: FirTypeScope?
containingScope: FirTypeScope?,
val containsMultipleNonSubsumed: Boolean,
) : ResultOfIntersection<D>(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 <D : FirCallableSymbol<*>> createIntersectionOverride(
mostSpecific: List<MemberWithBaseScope<D>>,
extractedOverrides: List<MemberWithBaseScope<D>>,
containsMultipleNonSubsumed: Boolean,
): MemberWithBaseScope<FirCallableSymbol<*>> {
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<FirCallableSymbol<*>>,
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<FirCallableSymbol<*>>,
newModality: Modality?,
newVisibility: Visibility,
containsMultipleNonSubsumed: Boolean,
): FirPropertySymbol {
return createIntersectionOverrideVariable<FirPropertySymbol, _>(
mostSpecific,
overrides,
containsMultipleNonSubsumed,
::FirIntersectionOverridePropertySymbol,
) { symbol, fir, deferredReturnTypeCalculation, returnType ->
FirFakeOverrideGenerator.createCopyForFirProperty(
@@ -380,10 +399,12 @@ class FirTypeIntersectionScopeContext(
overrides: Collection<FirCallableSymbol<*>>,
newModality: Modality?,
newVisibility: Visibility,
containsMultipleNonSubsumed: Boolean,
): FirFieldSymbol {
return createIntersectionOverrideVariable<FirFieldSymbol, _>(
mostSpecific,
overrides,
containsMultipleNonSubsumed,
::FirIntersectionOverrideFieldSymbol
) { symbol, fir, deferredReturnTypeCalculation, returnType ->
FirFakeOverrideGenerator.createCopyForFirField(
@@ -404,13 +425,14 @@ class FirTypeIntersectionScopeContext(
private inline fun <reified S : FirVariableSymbol<F>, F : FirVariable> createIntersectionOverrideVariable(
mostSpecific: 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
): 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<ConeKotlinType, FirIntersectionOverrideCache, Nothing?> =
cachesFactory.createCache { _ ->
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 {
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(
callableId: CallableId,
override val intersections: Collection<FirCallableSymbol<*>>,
override val containsMultipleNonSubsumed: Boolean,
) : FirNamedFunctionSymbol(callableId), FirIntersectionCallableSymbol
class FirConstructorSymbol(callableId: CallableId) : FirFunctionSymbol<FirConstructor>(callableId), ConstructorSymbolMarker {
@@ -72,12 +72,14 @@ open class FirPropertySymbol(callableId: CallableId, ) : FirVariableSymbol<FirPr
class FirIntersectionOverridePropertySymbol(
callableId: CallableId,
override val intersections: Collection<FirCallableSymbol<*>>
override val intersections: Collection<FirCallableSymbol<*>>,
override val containsMultipleNonSubsumed: Boolean,
) : FirPropertySymbol(callableId), FirIntersectionCallableSymbol
class FirIntersectionOverrideFieldSymbol(
callableId: CallableId,
override val intersections: Collection<FirCallableSymbol<*>>
override val intersections: Collection<FirCallableSymbol<*>>,
override val containsMultipleNonSubsumed: Boolean,
) : FirFieldSymbol(callableId), FirIntersectionCallableSymbol
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
public interface ColoredTextContainer {
default void setToolTipText(String text) {}