[FIR] Properly support smartcasts on stable when subjects in when conditions
^KT-49860 Fixed
This commit is contained in:
committed by
teamcityserver
parent
20425fb458
commit
330574cab6
@@ -16,7 +16,6 @@ import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.canNarrowDownGetterType
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.expandedConeType
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.isFinal
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.isInner
|
||||
import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic
|
||||
import org.jetbrains.kotlin.fir.diagnostics.ConeSimpleDiagnostic
|
||||
import org.jetbrains.kotlin.fir.diagnostics.ConeStubDiagnostic
|
||||
@@ -31,6 +30,8 @@ import org.jetbrains.kotlin.fir.resolve.calls.Candidate
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.FirNamedReferenceWithCandidate
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.FirPropertyWithExplicitBackingFieldResolvedNamedReference
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitDispatchReceiverValue
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.FirDataFlowAnalyzer
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.PropertyStability
|
||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedNameError
|
||||
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.resultType
|
||||
@@ -291,26 +292,52 @@ private fun BodyResolveComponents.typeFromSymbol(symbol: FirBasedSymbol<*>, make
|
||||
fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
|
||||
qualifiedAccessExpression: FirQualifiedAccessExpression
|
||||
): FirQualifiedAccessExpression {
|
||||
val (stability, typesFromSmartCast) = dataFlowAnalyzer.getTypeUsingSmartcastInfo(qualifiedAccessExpression)
|
||||
?: return qualifiedAccessExpression
|
||||
val builder = transformExpressionUsingSmartcastInfo(
|
||||
qualifiedAccessExpression,
|
||||
dataFlowAnalyzer::getTypeUsingSmartcastInfo,
|
||||
::FirExpressionWithSmartcastBuilder,
|
||||
::FirExpressionWithSmartcastToNullBuilder
|
||||
) ?: return qualifiedAccessExpression
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
fun BodyResolveComponents.transformWhenSubjectExpressionUsingSmartcastInfo(
|
||||
whenSubjectExpression: FirWhenSubjectExpression
|
||||
): FirWhenSubjectExpression {
|
||||
val builder = transformExpressionUsingSmartcastInfo(
|
||||
whenSubjectExpression,
|
||||
dataFlowAnalyzer::getTypeUsingSmartcastInfo,
|
||||
::FirWhenSubjectExpressionWithSmartcastBuilder,
|
||||
::FirWhenSubjectExpressionWithSmartcastToNullBuilder
|
||||
) ?: return whenSubjectExpression
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
private inline fun <T : FirExpression> BodyResolveComponents.transformExpressionUsingSmartcastInfo(
|
||||
expression: T,
|
||||
smartcastExtractor: (T) -> Pair<PropertyStability, MutableList<ConeKotlinType>>?,
|
||||
smartcastBuilder: () -> FirWrappedExpressionWithSmartcastBuilder<T>,
|
||||
smartcastToNullBuilder: () -> FirWrappedExpressionWithSmartcastToNullBuilder<T>
|
||||
): FirWrappedExpressionWithSmartcastBuilder<T>? {
|
||||
val (stability, typesFromSmartCast) = smartcastExtractor(expression) ?: return null
|
||||
val smartcastStability = stability.impliedSmartcastStability
|
||||
?: if (dataFlowAnalyzer.isAccessToUnstableLocalVariable(qualifiedAccessExpression)) {
|
||||
?: if (dataFlowAnalyzer.isAccessToUnstableLocalVariable(expression)) {
|
||||
SmartcastStability.CAPTURED_VARIABLE
|
||||
} else {
|
||||
SmartcastStability.STABLE_VALUE
|
||||
}
|
||||
|
||||
val originalType = qualifiedAccessExpression.resultType.coneType
|
||||
val originalType = expression.resultType.coneType
|
||||
val allTypes = typesFromSmartCast.also {
|
||||
it += originalType
|
||||
}
|
||||
val intersectedType = ConeTypeIntersector.intersectTypes(session.typeContext, allTypes)
|
||||
if (intersectedType == originalType) return qualifiedAccessExpression
|
||||
if (intersectedType == originalType) return null
|
||||
val intersectedTypeRef = buildResolvedTypeRef {
|
||||
source = qualifiedAccessExpression.resultType.source?.fakeElement(KtFakeSourceElementKind.SmartCastedTypeRef)
|
||||
source = expression.resultType.source?.fakeElement(KtFakeSourceElementKind.SmartCastedTypeRef)
|
||||
type = intersectedType
|
||||
annotations += qualifiedAccessExpression.resultType.annotations
|
||||
delegatedTypeRef = qualifiedAccessExpression.resultType
|
||||
annotations += expression.resultType.annotations
|
||||
delegatedTypeRef = expression.resultType
|
||||
}
|
||||
// For example, if (x == null) { ... },
|
||||
// we need to track the type without `Nothing?` so that resolution with this as receiver can go through properly.
|
||||
@@ -322,13 +349,13 @@ fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
|
||||
val intersectedTypeWithoutNullableNothing =
|
||||
ConeTypeIntersector.intersectTypes(session.typeContext, typesFromSmartcastWithoutNullableNothing)
|
||||
val intersectedTypeRefWithoutNullableNothing = buildResolvedTypeRef {
|
||||
source = qualifiedAccessExpression.resultType.source?.fakeElement(KtFakeSourceElementKind.SmartCastedTypeRef)
|
||||
source = expression.resultType.source?.fakeElement(KtFakeSourceElementKind.SmartCastedTypeRef)
|
||||
type = intersectedTypeWithoutNullableNothing
|
||||
annotations += qualifiedAccessExpression.resultType.annotations
|
||||
delegatedTypeRef = qualifiedAccessExpression.resultType
|
||||
annotations += expression.resultType.annotations
|
||||
delegatedTypeRef = expression.resultType
|
||||
}
|
||||
return buildExpressionWithSmartcastToNull {
|
||||
originalExpression = qualifiedAccessExpression
|
||||
return smartcastToNullBuilder().apply {
|
||||
originalExpression = expression
|
||||
smartcastType = intersectedTypeRef
|
||||
smartcastTypeWithoutNullableNothing = intersectedTypeRefWithoutNullableNothing
|
||||
this.typesFromSmartCast = typesFromSmartCast
|
||||
@@ -336,8 +363,8 @@ fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
|
||||
}
|
||||
}
|
||||
|
||||
return buildExpressionWithSmartcast {
|
||||
originalExpression = qualifiedAccessExpression
|
||||
return smartcastBuilder().apply {
|
||||
originalExpression = expression
|
||||
smartcastType = intersectedTypeRef
|
||||
this.typesFromSmartCast = typesFromSmartCast
|
||||
this.smartcastStability = smartcastStability
|
||||
|
||||
+30
-6
@@ -6,8 +6,7 @@
|
||||
package org.jetbrains.kotlin.fir.resolve.dfa
|
||||
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.PrivateForInline
|
||||
import org.jetbrains.kotlin.fir.*
|
||||
import org.jetbrains.kotlin.fir.contracts.FirResolvedContractDescription
|
||||
import org.jetbrains.kotlin.fir.contracts.description.ConeBooleanConstantReference
|
||||
import org.jetbrains.kotlin.fir.contracts.description.ConeConditionalEffectDeclaration
|
||||
@@ -17,7 +16,6 @@ import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.isLocal
|
||||
import org.jetbrains.kotlin.fir.expressions.*
|
||||
import org.jetbrains.kotlin.fir.languageVersionSettings
|
||||
import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference
|
||||
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||
import org.jetbrains.kotlin.fir.resolve.PersistentImplicitReceiverStack
|
||||
@@ -35,7 +33,6 @@ import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import org.jetbrains.kotlin.fir.visibilityChecker
|
||||
import org.jetbrains.kotlin.fir.visitors.transformSingle
|
||||
import org.jetbrains.kotlin.name.StandardClassIds
|
||||
import org.jetbrains.kotlin.types.ConstantValueKind
|
||||
@@ -165,18 +162,41 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
|
||||
// ----------------------------------- Requests -----------------------------------
|
||||
|
||||
fun isAccessToUnstableLocalVariable(qualifiedAccessExpression: FirQualifiedAccessExpression): Boolean {
|
||||
fun isAccessToUnstableLocalVariable(expression: FirExpression): Boolean {
|
||||
val qualifiedAccessExpression = when (expression) {
|
||||
is FirQualifiedAccessExpression -> expression
|
||||
is FirWhenSubjectExpression -> {
|
||||
val whenExpression = expression.whenRef.value
|
||||
when {
|
||||
whenExpression.subjectVariable != null -> return true
|
||||
else -> whenExpression.subject as? FirQualifiedAccessExpression
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
} ?: return false
|
||||
return context.firLocalVariableAssignmentAnalyzer?.isAccessToUnstableLocalVariable(qualifiedAccessExpression) == true
|
||||
}
|
||||
|
||||
fun getTypeUsingSmartcastInfo(whenSubjectExpression: FirWhenSubjectExpression): Pair<PropertyStability, MutableList<ConeKotlinType>>? {
|
||||
val symbol = whenSubjectExpression.symbol ?: return null
|
||||
return getTypeUsingSmartcastInfo(symbol, whenSubjectExpression)
|
||||
}
|
||||
|
||||
fun getTypeUsingSmartcastInfo(qualifiedAccessExpression: FirQualifiedAccessExpression): Pair<PropertyStability, MutableList<ConeKotlinType>>? {
|
||||
/*
|
||||
* DataFlowAnalyzer holds variables only for declarations that have some smartcast (or can have)
|
||||
* If there is no useful information there is no data flow variable also
|
||||
*/
|
||||
val symbol: FirBasedSymbol<*> = qualifiedAccessExpression.symbol ?: return null
|
||||
return getTypeUsingSmartcastInfo(symbol, qualifiedAccessExpression)
|
||||
}
|
||||
|
||||
private fun getTypeUsingSmartcastInfo(
|
||||
symbol: FirBasedSymbol<*>,
|
||||
expression: FirExpression
|
||||
): Pair<PropertyStability, MutableList<ConeKotlinType>>? {
|
||||
val flow = graphBuilder.lastNode.flow
|
||||
var variable = variableStorage.getRealVariableWithoutUnwrappingAlias(symbol, qualifiedAccessExpression, flow) ?: return null
|
||||
var variable = variableStorage.getRealVariableWithoutUnwrappingAlias(symbol, expression, flow) ?: return null
|
||||
val stability = variable.stability
|
||||
val result = mutableListOf<ConeKotlinType>()
|
||||
flow.directAliasMap[variable]?.let {
|
||||
@@ -740,6 +760,10 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
whenExitNode.mergeIncomingFlow()
|
||||
}
|
||||
|
||||
fun exitWhenSubjectExpression(expression: FirWhenSubjectExpression) {
|
||||
graphBuilder.exitWhenSubjectExpression(expression).mergeIncomingFlow()
|
||||
}
|
||||
|
||||
// ----------------------------------- While Loop -----------------------------------
|
||||
|
||||
private fun exitCommonLoop(exitNode: LoopExitNode) {
|
||||
|
||||
+3
@@ -1178,6 +1178,9 @@ class ControlFlowGraphBuilder {
|
||||
return Pair(kind, unionNode)
|
||||
}
|
||||
|
||||
fun exitWhenSubjectExpression(expression: FirWhenSubjectExpression): WhenSubjectExpressionExitNode {
|
||||
return createWhenSubjectExpressionExitNode(expression).also { addNewSimpleNode(it) }
|
||||
}
|
||||
|
||||
// ----------------------------------- Annotations -----------------------------------
|
||||
|
||||
|
||||
+3
@@ -157,6 +157,9 @@ fun ControlFlowGraphBuilder.createElvisRhsEnterNode(fir: FirElvisExpression): El
|
||||
fun ControlFlowGraphBuilder.createElvisLhsExitNode(fir: FirElvisExpression): ElvisLhsExitNode =
|
||||
ElvisLhsExitNode(currentGraph, fir, levelCounter, createId())
|
||||
|
||||
fun ControlFlowGraphBuilder.createWhenSubjectExpressionExitNode(fir: FirWhenSubjectExpression): WhenSubjectExpressionExitNode =
|
||||
WhenSubjectExpressionExitNode(currentGraph, fir, levelCounter, createId())
|
||||
|
||||
fun ControlFlowGraphBuilder.createElvisExitNode(fir: FirElvisExpression): ElvisExitNode =
|
||||
ElvisExitNode(currentGraph, fir, levelCounter, createId())
|
||||
|
||||
|
||||
+3
-1
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.fir.expressions.impl.FirEmptyExpressionBlock
|
||||
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||
import org.jetbrains.kotlin.fir.resolve.ResolutionMode
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.isUnitOrFlexibleUnit
|
||||
import org.jetbrains.kotlin.fir.resolve.transformWhenSubjectExpressionUsingSmartcastInfo
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.FirSyntheticCallGenerator
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.FirWhenExhaustivenessTransformer
|
||||
import org.jetbrains.kotlin.fir.resolve.withExpectedType
|
||||
@@ -126,7 +127,8 @@ class FirControlFlowStatementsResolveTransformer(transformer: FirBodyResolveTran
|
||||
if (subjectType != null) {
|
||||
whenSubjectExpression.resultType = subjectType
|
||||
}
|
||||
return whenSubjectExpression
|
||||
dataFlowAnalyzer.exitWhenSubjectExpression(whenSubjectExpression)
|
||||
return components.transformWhenSubjectExpressionUsingSmartcastInfo(whenSubjectExpression)
|
||||
}
|
||||
|
||||
// ------------------------------- Try/catch expressions -------------------------------
|
||||
|
||||
Reference in New Issue
Block a user