[FIR] Make MemberWithScope a data class

Previously, there was a contract that each callable symbol in the chain
  of `processDirectOverriddenWithBaseScope` will be unique. And if some
  symbol is accessible from multiple scopes, then only last of them will
  be returned as a component of `MemberWithScope`. So there actually was
  no such thing as "pair of two different MemberWithScope with the same
  symbol and different scopes"

After the change of `processDirectOverriddenWithBaseScope` contract (see
  previous commits) each scope returns `MemberWithScope` for some symbol
  with the previous scope in the hierarchy even if it contains the same
  symbol as the current scope

So now scope is actually a part of `MemberWithScope`, which should be
  considered as part of equality. Otherwise, we can skip some part of
  the overridden hierarchy, because we will start to consider symbol as
  visited after its first occurrence (in opposite to the previous behavior,
  when only the last scope was returned)
This commit is contained in:
Dmitriy Novozhilov
2023-12-05 13:24:06 +02:00
committed by Space Team
parent 385bff3be1
commit bc95c5d142
4 changed files with 31 additions and 23 deletions
@@ -30,4 +30,4 @@ fun test_1(x: String?) {
fun test_2(x: String?) {
trickyRequireNotNull(x)
x.length
}
}
@@ -144,8 +144,13 @@ class FirTypeIntersectionScopeContext(
return members.map { ResultOfIntersection.SingleMember(it, MemberWithBaseScope(it, scope)) }
}
val uniqueSymbols = mutableSetOf<D>()
val allMembersWithScope = membersByScope.flatMapTo(linkedSetOf()) { (scope, members) ->
members.map { MemberWithBaseScope(it, scope) }
members.mapNotNull {
runIf(uniqueSymbols.add(it)) {
MemberWithBaseScope(it, scope)
}
}
}
val result = mutableListOf<ResultOfIntersection<D>>()
@@ -92,26 +92,30 @@ class MemberScopeTowerLevel(
)
if (scopeWithoutSmartcast == null) {
consumeCandidates(output, candidates)
consumeCandidates(
output,
candidatesWithoutSmartcast = candidates,
candidatesWithSmartcast = null
)
} else {
val isFromSmartCast: MutableMap<MemberWithBaseScope<T>, Boolean> = mutableMapOf()
val map: MutableMap<T, MemberFromSmartcastScope<T>> = mutableMapOf()
scopeWithoutSmartcast.collectCandidates(processScopeMembers).let { (isEmpty, originalCandidates) ->
empty = empty && isEmpty
for (originalCandidate in originalCandidates) {
isFromSmartCast[originalCandidate] = false
map[originalCandidate.member] = MemberFromSmartcastScope(originalCandidate, cameFromSmartcast = false)
}
}
for (candidateFromSmartCast in candidates) {
isFromSmartCast[candidateFromSmartCast] = true
map[candidateFromSmartCast.member] = MemberFromSmartcastScope(candidateFromSmartCast, cameFromSmartcast = true)
}
consumeCandidates(
output,
// all the candidates, both from original type and smart cast
candidates = isFromSmartCast.keys,
isFromSmartCast,
candidatesWithoutSmartcast = null,
candidatesWithSmartcast = map
)
}
@@ -162,6 +166,11 @@ class MemberScopeTowerLevel(
return if (empty) ProcessResult.SCOPE_EMPTY else ProcessResult.FOUND
}
private data class MemberFromSmartcastScope<T : FirCallableSymbol<*>>(
val memberWithBaseScope: MemberWithBaseScope<T>,
val cameFromSmartcast: Boolean
)
private fun <T : FirCallableSymbol<*>> FirTypeScope.collectCandidates(
processScopeMembers: FirScope.(processor: (T) -> Unit) -> Unit
): Pair<Boolean, List<MemberWithBaseScope<T>>> {
@@ -185,16 +194,21 @@ class MemberScopeTowerLevel(
private fun <T : FirCallableSymbol<*>> consumeCandidates(
output: TowerScopeLevelProcessor<T>,
candidates: Collection<MemberWithBaseScope<T>>,
candidatesWithoutSmartcast: Collection<MemberWithBaseScope<T>>?,
// The map is not null only if there's a smart cast type on a dispatch receiver
// and candidates are present both in smart cast and original types.
// isFromSmartCast[candidate] == true iff exactly that member is present in smart cast type
isFromSmartCast: Map<MemberWithBaseScope<T>, Boolean>? = null
candidatesWithSmartcast: Map<T, MemberFromSmartcastScope<T>>?
) {
val candidates = candidatesWithoutSmartcast
?: candidatesWithSmartcast?.values?.map { it.memberWithBaseScope }
?: error("candidatesWithoutSmartcast or candidatesWithSmartcast should be not null")
for (candidateWithScope in candidates) {
val (candidate, scope) = candidateWithScope
if (candidate.hasConsistentExtensionReceiver(givenExtensionReceiverOptions)) {
val isFromOriginalTypeInPresenceOfSmartCast = isFromSmartCast != null && !isFromSmartCast.getValue(candidateWithScope)
val isFromOriginalTypeInPresenceOfSmartCast = candidatesWithSmartcast != null &&
!candidatesWithSmartcast.getValue(candidateWithScope.member).cameFromSmartcast
val dispatchReceiverToUse = when {
isFromOriginalTypeInPresenceOfSmartCast ->
@@ -64,18 +64,7 @@ abstract class FirTypeScope : FirContainingNamesAwareScope() {
}
}
class MemberWithBaseScope<out D : FirCallableSymbol<*>>(val member: D, val baseScope: FirTypeScope) {
operator fun component1() = member
operator fun component2() = baseScope
override fun equals(other: Any?): Boolean {
return other is MemberWithBaseScope<*> && member == other.member
}
override fun hashCode(): Int {
return member.hashCode()
}
}
data class MemberWithBaseScope<out D : FirCallableSymbol<*>>(val member: D, val baseScope: FirTypeScope)
typealias ProcessOverriddenWithBaseScope<D> = FirTypeScope.(D, (D, FirTypeScope) -> ProcessorAction) -> ProcessorAction
typealias ProcessAllOverridden<D> = FirTypeScope.(D, (D) -> ProcessorAction) -> ProcessorAction