FIR IDE: collect snapshot of FirTowerDataContext for statements

ImplicitReceiverValue is mutable and FIR body resolve could alter it
while analysing code with smartcast. Hence, previously the IDE may see
inconsistent receiver values for a local scope. For example

```
open class A
interface Foo {
    fun foo()
}
fun A.bar() {
    if (this is Foo) {
        // scope here has implicit receiver type to be `A` rather than `it<A, Foo>`
    }
}
```

This change creates snapshots for local statements so later changes
during body resolve won't affect the collected context.
This commit is contained in:
Tianyu Geng
2021-08-19 11:56:46 -07:00
committed by Ilya Kirillov
parent aad02c1259
commit e5b9d667c0
4 changed files with 49 additions and 12 deletions
@@ -37,7 +37,8 @@ internal class FirTowerDataContextAllElementsCollector : FirTowerDataContextColl
override fun addStatementContext(statement: FirStatement, context: FirTowerDataContext) { override fun addStatementContext(statement: FirStatement, context: FirTowerDataContext) {
val closestStatementInBlock = statement.psi?.closestBlockLevelOrInitializerExpression() ?: return val closestStatementInBlock = statement.psi?.closestBlockLevelOrInitializerExpression() ?: return
elementsToContext[closestStatementInBlock] = context // FIR body transform may alter the context if there are implicit receivers with smartcast
elementsToContext[closestStatementInBlock] = context.createSnapshot()
} }
override fun addDeclarationContext(declaration: FirDeclaration, context: FirTowerDataContext) { override fun addDeclarationContext(declaration: FirDeclaration, context: FirTowerDataContext) {
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.resolve
import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import org.jetbrains.kotlin.fir.FirCallResolver import org.jetbrains.kotlin.fir.FirCallResolver
import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.*
@@ -137,6 +138,16 @@ class FirTowerDataContext private constructor(
nonLocalTowerDataElements.add(element) nonLocalTowerDataElements.add(element)
) )
} }
fun createSnapshot(): FirTowerDataContext {
return FirTowerDataContext(
towerDataElements.map { FirTowerDataElement(it.scope, it.implicitReceiver?.createSnapshot(), it.isLocal) }.toPersistentList(),
implicitReceiverStack.createSnapshot(),
localScopes.toPersistentList(),
nonLocalTowerDataElements.map { FirTowerDataElement(it.scope, it.implicitReceiver?.createSnapshot(), it.isLocal) }
.toPersistentList()
)
}
} }
class FirTowerDataElement(val scope: FirScope?, val implicitReceiver: ImplicitReceiverValue<*>?, val isLocal: Boolean) class FirTowerDataElement(val scope: FirScope?, val implicitReceiver: ImplicitReceiverValue<*>?, val isLocal: Boolean)
@@ -5,10 +5,7 @@
package org.jetbrains.kotlin.fir.resolve package org.jetbrains.kotlin.fir.resolve
import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.*
import kotlinx.collections.immutable.PersistentMap
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitDispatchReceiverValue import org.jetbrains.kotlin.fir.resolve.calls.ImplicitDispatchReceiverValue
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitReceiverValue import org.jetbrains.kotlin.fir.resolve.calls.ImplicitReceiverValue
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
@@ -84,4 +81,13 @@ class PersistentImplicitReceiverStack private constructor(
assert(index >= 0 && index < stack.size) assert(index >= 0 && index < stack.size)
stack[index].replaceType(type) stack[index].replaceType(type)
} }
fun createSnapshot(): PersistentImplicitReceiverStack {
return PersistentImplicitReceiverStack(
stack.map { it.createSnapshot() }.toPersistentList(),
indexesPerLabel,
indexesPerSymbol,
originalTypes
)
}
} }
@@ -73,7 +73,8 @@ sealed class ImplicitReceiverValue<S : FirBasedSymbol<*>>(
val boundSymbol: S, val boundSymbol: S,
type: ConeKotlinType, type: ConeKotlinType,
protected val useSiteSession: FirSession, protected val useSiteSession: FirSession,
protected val scopeSession: ScopeSession protected val scopeSession: ScopeSession,
private val mutable: Boolean,
) : ReceiverValue { ) : ReceiverValue {
final override var type: ConeKotlinType = type final override var type: ConeKotlinType = type
private set private set
@@ -93,6 +94,7 @@ sealed class ImplicitReceiverValue<S : FirBasedSymbol<*>>(
* Should be called only in ImplicitReceiverStack * Should be called only in ImplicitReceiverStack
*/ */
internal fun replaceType(type: ConeKotlinType) { internal fun replaceType(type: ConeKotlinType) {
if (!mutable) throw IllegalStateException("Cannot mutate an immutable ImplicitReceiverValue")
if (type == this.type) return if (type == this.type) return
this.type = type this.type = type
receiverExpression = if (type == originalReceiverExpression.typeRef.coneType) { receiverExpression = if (type == originalReceiverExpression.typeRef.coneType) {
@@ -107,6 +109,8 @@ sealed class ImplicitReceiverValue<S : FirBasedSymbol<*>>(
} }
implicitScope = type.scope(useSiteSession, scopeSession, FakeOverrideTypeCalculator.DoNothing) implicitScope = type.scope(useSiteSession, scopeSession, FakeOverrideTypeCalculator.DoNothing)
} }
abstract fun createSnapshot(): ImplicitReceiverValue<S>
} }
private fun receiverExpression(symbol: FirBasedSymbol<*>, type: ConeKotlinType): FirThisReceiverExpression = private fun receiverExpression(symbol: FirBasedSymbol<*>, type: ConeKotlinType): FirThisReceiverExpression =
@@ -126,27 +130,42 @@ class ImplicitDispatchReceiverValue internal constructor(
boundSymbol: FirClassSymbol<*>, boundSymbol: FirClassSymbol<*>,
type: ConeKotlinType, type: ConeKotlinType,
useSiteSession: FirSession, useSiteSession: FirSession,
scopeSession: ScopeSession scopeSession: ScopeSession,
) : ImplicitReceiverValue<FirClassSymbol<*>>(boundSymbol, type, useSiteSession, scopeSession) { mutable: Boolean = true,
) : ImplicitReceiverValue<FirClassSymbol<*>>(boundSymbol, type, useSiteSession, scopeSession, mutable) {
internal constructor( internal constructor(
boundSymbol: FirClassSymbol<*>, useSiteSession: FirSession, scopeSession: ScopeSession boundSymbol: FirClassSymbol<*>, useSiteSession: FirSession, scopeSession: ScopeSession
) : this( ) : this(
boundSymbol, boundSymbol.constructType(typeArguments = emptyArray(), isNullable = false), boundSymbol, boundSymbol.constructType(typeArguments = emptyArray(), isNullable = false),
useSiteSession, scopeSession useSiteSession, scopeSession
) )
override fun createSnapshot(): ImplicitReceiverValue<FirClassSymbol<*>> {
return ImplicitDispatchReceiverValue(boundSymbol, type, useSiteSession, scopeSession, false)
}
} }
class ImplicitExtensionReceiverValue( class ImplicitExtensionReceiverValue(
boundSymbol: FirCallableSymbol<*>, boundSymbol: FirCallableSymbol<*>,
type: ConeKotlinType, type: ConeKotlinType,
useSiteSession: FirSession, useSiteSession: FirSession,
scopeSession: ScopeSession scopeSession: ScopeSession,
) : ImplicitReceiverValue<FirCallableSymbol<*>>(boundSymbol, type, useSiteSession, scopeSession) mutable: Boolean = true,
) : ImplicitReceiverValue<FirCallableSymbol<*>>(boundSymbol, type, useSiteSession, scopeSession, mutable) {
override fun createSnapshot(): ImplicitReceiverValue<FirCallableSymbol<*>> {
return ImplicitExtensionReceiverValue(boundSymbol, type, useSiteSession, scopeSession, false)
}
}
class InaccessibleImplicitReceiverValue( class InaccessibleImplicitReceiverValue(
boundSymbol: FirClassSymbol<*>, boundSymbol: FirClassSymbol<*>,
type: ConeKotlinType, type: ConeKotlinType,
useSiteSession: FirSession, useSiteSession: FirSession,
scopeSession: ScopeSession scopeSession: ScopeSession,
) : ImplicitReceiverValue<FirClassSymbol<*>>(boundSymbol, type, useSiteSession, scopeSession) mutable: Boolean = true,
) : ImplicitReceiverValue<FirClassSymbol<*>>(boundSymbol, type, useSiteSession, scopeSession, mutable) {
override fun createSnapshot(): ImplicitReceiverValue<FirClassSymbol<*>> {
return InaccessibleImplicitReceiverValue(boundSymbol, type, useSiteSession, scopeSession, false)
}
}