From bc95c5d1424a0e994c968abbaccb4152e6bd421d Mon Sep 17 00:00:00 2001 From: Dmitriy Novozhilov Date: Tue, 5 Dec 2023 13:24:06 +0200 Subject: [PATCH] [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) --- .../fromSource/good/returnsImplies/eqNotEq.kt | 2 +- .../impl/FirTypeIntersectionScopeContext.kt | 7 +++- .../fir/resolve/calls/tower/TowerLevels.kt | 32 +++++++++++++------ .../kotlin/fir/scopes/FirTypeScope.kt | 13 +------- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/eqNotEq.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/eqNotEq.kt index f7e7bd2d086..22c07365f15 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/eqNotEq.kt +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/contracts/fromSource/good/returnsImplies/eqNotEq.kt @@ -30,4 +30,4 @@ fun test_1(x: String?) { fun test_2(x: String?) { trickyRequireNotNull(x) x.length -} \ No newline at end of file +} 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 846432205c1..b2e3afb21ea 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 @@ -144,8 +144,13 @@ class FirTypeIntersectionScopeContext( return members.map { ResultOfIntersection.SingleMember(it, MemberWithBaseScope(it, scope)) } } + val uniqueSymbols = mutableSetOf() val allMembersWithScope = membersByScope.flatMapTo(linkedSetOf()) { (scope, members) -> - members.map { MemberWithBaseScope(it, scope) } + members.mapNotNull { + runIf(uniqueSymbols.add(it)) { + MemberWithBaseScope(it, scope) + } + } } val result = mutableListOf>() diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/TowerLevels.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/TowerLevels.kt index d7496ad4cb5..6206b57e272 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/TowerLevels.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/tower/TowerLevels.kt @@ -92,26 +92,30 @@ class MemberScopeTowerLevel( ) if (scopeWithoutSmartcast == null) { - consumeCandidates(output, candidates) + consumeCandidates( + output, + candidatesWithoutSmartcast = candidates, + candidatesWithSmartcast = null + ) } else { - val isFromSmartCast: MutableMap, Boolean> = mutableMapOf() + val map: MutableMap> = 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>( + val memberWithBaseScope: MemberWithBaseScope, + val cameFromSmartcast: Boolean + ) + private fun > FirTypeScope.collectCandidates( processScopeMembers: FirScope.(processor: (T) -> Unit) -> Unit ): Pair>> { @@ -185,16 +194,21 @@ class MemberScopeTowerLevel( private fun > consumeCandidates( output: TowerScopeLevelProcessor, - candidates: Collection>, + candidatesWithoutSmartcast: Collection>?, // 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, Boolean>? = null + candidatesWithSmartcast: Map>? ) { + 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 -> 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 956c61000e6..db458d063dc 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 @@ -64,18 +64,7 @@ abstract class FirTypeScope : FirContainingNamesAwareScope() { } } -class MemberWithBaseScope>(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>(val member: D, val baseScope: FirTypeScope) typealias ProcessOverriddenWithBaseScope = FirTypeScope.(D, (D, FirTypeScope) -> ProcessorAction) -> ProcessorAction typealias ProcessAllOverridden = FirTypeScope.(D, (D) -> ProcessorAction) -> ProcessorAction