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:
Jinseong Jeon
2021-04-29 23:50:22 -07:00
committed by TeamCityServer
parent 8da183e4f4
commit 8e10b5fdec
36 changed files with 370 additions and 104 deletions
@@ -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
@@ -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
}
@@ -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
@@ -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
}
}
}
}