FIR: introduce FirExpressionWithSmartcastToNull
This new kind of expression encompasses the nullability of the original expression after null check (or equivalent `is Nothing?` check). Unlike FirExpressionWithSmartcast, this expression won't be materialized during conversion to backend IR. Also, Nothing? is discarded when computing the intersection of possible types from smartcast info. In that way, Nothing? is not used during resolution, while such smartcast info is stored in it (and the expression kind itself).
This commit is contained in:
committed by
TeamCityServer
parent
8da183e4f4
commit
8e10b5fdec
@@ -247,9 +247,35 @@ private fun BodyResolveComponents.typeFromSymbol(symbol: AbstractFirBasedSymbol<
|
||||
}
|
||||
}
|
||||
|
||||
fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(qualifiedAccessExpression: FirQualifiedAccessExpression): FirQualifiedAccessExpression {
|
||||
fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
|
||||
qualifiedAccessExpression: FirQualifiedAccessExpression
|
||||
): FirQualifiedAccessExpression {
|
||||
val typesFromSmartCast = dataFlowAnalyzer.getTypeUsingSmartcastInfo(qualifiedAccessExpression) ?: return qualifiedAccessExpression
|
||||
val originalType = qualifiedAccessExpression.resultType.coneType
|
||||
// For example, if (x == null) { ... },
|
||||
// we don't want to smartcast to Nothing?, but we want to record the nullability to its own kind of node.
|
||||
// TODO: should we differentiate x == null v.s. x is Nothing?
|
||||
if (typesFromSmartCast.any { it.isNullableNothing }) {
|
||||
val typesFromSmartcastWithoutNullableNothing =
|
||||
typesFromSmartCast.filterTo(mutableListOf()) { !it.isNullableNothing }.also {
|
||||
it += originalType
|
||||
}
|
||||
val intersectedTypeWithoutNullableNothing =
|
||||
ConeTypeIntersector.intersectTypes(session.inferenceComponents.ctx, typesFromSmartcastWithoutNullableNothing)
|
||||
val intersectedTypeRefWithoutNullableNothing = buildResolvedTypeRef {
|
||||
source = qualifiedAccessExpression.resultType.source?.fakeElement(FirFakeSourceElementKind.SmartCastedTypeRef)
|
||||
type = intersectedTypeWithoutNullableNothing
|
||||
annotations += qualifiedAccessExpression.resultType.annotations
|
||||
delegatedTypeRef = qualifiedAccessExpression.resultType
|
||||
}
|
||||
return buildExpressionWithSmartcastToNull {
|
||||
originalExpression = qualifiedAccessExpression
|
||||
// TODO: Use Nothing? during resolution?
|
||||
typeRef = intersectedTypeRefWithoutNullableNothing
|
||||
// NB: Nothing? in types from smartcast in DFA is recorded here (and the expression kind itself).
|
||||
this.typesFromSmartCast = typesFromSmartCast
|
||||
}
|
||||
}
|
||||
val allTypes = typesFromSmartCast.also {
|
||||
it += originalType
|
||||
}
|
||||
@@ -259,6 +285,7 @@ fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(qualifiedAc
|
||||
source = qualifiedAccessExpression.resultType.source?.fakeElement(FirFakeSourceElementKind.SmartCastedTypeRef)
|
||||
type = intersectedType
|
||||
annotations += qualifiedAccessExpression.resultType.annotations
|
||||
delegatedTypeRef = qualifiedAccessExpression.resultType
|
||||
}
|
||||
return buildExpressionWithSmartcast {
|
||||
originalExpression = qualifiedAccessExpression
|
||||
|
||||
+9
-4
@@ -119,9 +119,11 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
}
|
||||
|
||||
override fun ConeKotlinType.isAcceptableForSmartcast(): Boolean {
|
||||
if (this.isNullableNothing) return false
|
||||
return when (this) {
|
||||
is ConeClassLikeType -> {
|
||||
val symbol = fullyExpandedType(components.session).lookupTag.toSymbol(components.session) ?: return false
|
||||
val symbol =
|
||||
fullyExpandedType(components.session).lookupTag.toSymbol(components.session) ?: return false
|
||||
val declaration = symbol.fir as? FirRegularClass ?: return true
|
||||
visibilityChecker.isVisible(
|
||||
declaration,
|
||||
@@ -545,9 +547,12 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
flow.addImplication((expressionVariable notEq isEq) implies (operandVariable typeEq any))
|
||||
}
|
||||
|
||||
// TODO: design do we need casts to Nothing?
|
||||
// flow.addImplication((expressionVariable eq !isEq) implies (operandVariable typeEq nullableNothing))
|
||||
// flow.addImplication((expressionVariable notEq !isEq) implies (operandVariable typeNotEq nullableNothing))
|
||||
if (shouldAddImplicationForStatement(expressionVariable eq !isEq)) {
|
||||
flow.addImplication((expressionVariable eq !isEq) implies (operandVariable typeNotEq nullableNothing))
|
||||
}
|
||||
if (shouldAddImplicationForStatement(expressionVariable notEq !isEq)) {
|
||||
flow.addImplication((expressionVariable notEq !isEq) implies (operandVariable typeEq nullableNothing))
|
||||
}
|
||||
}
|
||||
node.flow = flow
|
||||
}
|
||||
|
||||
@@ -99,7 +99,10 @@ internal val FirElement.symbol: AbstractFirBasedSymbol<*>?
|
||||
is FirWhenSubjectExpression -> whenRef.value.subject?.symbol
|
||||
is FirSafeCallExpression -> regularQualifiedAccess.symbol
|
||||
else -> null
|
||||
}?.takeIf { this.unwrapSmartcastExpression() is FirThisReceiverExpression || (it !is FirFunctionSymbol<*> && it !is FirAccessorSymbol) }
|
||||
}?.takeIf {
|
||||
(this as? FirExpression)?.unwrapSmartcastExpression() is FirThisReceiverExpression ||
|
||||
(it !is FirFunctionSymbol<*> && it !is FirAccessorSymbol)
|
||||
}
|
||||
|
||||
@DfaInternals
|
||||
internal val FirResolvable.symbol: AbstractFirBasedSymbol<*>?
|
||||
@@ -110,4 +113,8 @@ internal val FirResolvable.symbol: AbstractFirBasedSymbol<*>?
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun FirElement.unwrapSmartcastExpression(): FirElement = if (this is FirExpressionWithSmartcast) originalExpression else this
|
||||
internal fun FirExpression.unwrapSmartcastExpression(): FirExpression =
|
||||
when (this) {
|
||||
is FirExpressionWithSmartcast -> originalExpression
|
||||
else -> this
|
||||
}
|
||||
|
||||
+2
-1
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||
import org.jetbrains.kotlin.fir.resolve.*
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.FirNamedReferenceWithCandidate
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.FirControlFlowGraphReferenceImpl
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.unwrapSmartcastExpression
|
||||
import org.jetbrains.kotlin.fir.resolve.inference.extractLambdaInfoFromFunctionalType
|
||||
import org.jetbrains.kotlin.fir.resolve.inference.isSuspendFunctionType
|
||||
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
|
||||
@@ -793,7 +794,7 @@ open class FirDeclarationsResolveTransformer(transformer: FirBodyResolveTransfor
|
||||
if (variable.returnTypeRef is FirImplicitTypeRef) {
|
||||
val resultType = when {
|
||||
initializer != null -> {
|
||||
val unwrappedInitializer = (initializer as? FirExpressionWithSmartcast)?.originalExpression ?: initializer
|
||||
val unwrappedInitializer = initializer.unwrapSmartcastExpression()
|
||||
unwrappedInitializer.resultType
|
||||
}
|
||||
variable.getter != null && variable.getter !is FirDefaultPropertyAccessor -> variable.getter?.returnTypeRef
|
||||
|
||||
+8
-1
@@ -118,6 +118,13 @@ class ConeEffectExtractor(
|
||||
return expressionWithSmartcast.originalExpression.accept(this, data)
|
||||
}
|
||||
|
||||
override fun visitExpressionWithSmartcastToNull(
|
||||
expressionWithSmartcastToNull: FirExpressionWithSmartcastToNull,
|
||||
data: Nothing?
|
||||
): ConeContractDescriptionElement? {
|
||||
return expressionWithSmartcastToNull.originalExpression.accept(this, data)
|
||||
}
|
||||
|
||||
override fun visitQualifiedAccessExpression(
|
||||
qualifiedAccessExpression: FirQualifiedAccessExpression,
|
||||
data: Nothing?
|
||||
@@ -189,4 +196,4 @@ class ConeEffectExtractor(
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user