FIR DFA: smartcast variable to Nothing? on null assignment

In order to make resolution still work for members not available from
`Nothing`, we track the type without `Nothing?` and use that for
resolution instead.
This commit is contained in:
Tianyu Geng
2021-07-22 10:01:10 -07:00
committed by teamcityserver
parent 7e2f15f532
commit 4726dcce40
54 changed files with 334 additions and 320 deletions
@@ -702,44 +702,45 @@ digraph boundSmartcastsInBranches_kt {
293 [label="Access variable R|<local>/x|"];
294 [label="Access variable <Inapplicable(UNSAFE_CALL): kotlin/String.length>#"];
295 [label="Access variable R|<local>/y|"];
296 [label="Access variable R|kotlin/String.length|"];
297 [label="Access variable R|<local>/z|"];
298 [label="Access variable <Inapplicable(UNSAFE_CALL): kotlin/String.length>#"];
299 [label="Exit block"];
296 [label="Stub" style="filled" fillcolor=gray];
297 [label="Access variable R|kotlin/String.length|" style="filled" fillcolor=gray];
298 [label="Access variable R|<local>/z|" style="filled" fillcolor=gray];
299 [label="Access variable <Inapplicable(UNSAFE_CALL): kotlin/String.length>#" style="filled" fillcolor=gray];
300 [label="Exit block" style="filled" fillcolor=gray];
}
300 [label="Exit when branch result"];
301 [label="Exit when"];
301 [label="Exit when branch result" style="filled" fillcolor=gray];
302 [label="Exit when"];
}
subgraph cluster_67 {
color=blue
302 [label="Enter when"];
303 [label="Enter when"];
subgraph cluster_68 {
color=blue
303 [label="Enter when branch condition "];
304 [label="Access variable R|<local>/z|"];
305 [label="Const: Null(null)"];
306 [label="Equality operator !="];
307 [label="Exit when branch condition"];
304 [label="Enter when branch condition "];
305 [label="Access variable R|<local>/z|"];
306 [label="Const: Null(null)"];
307 [label="Equality operator !="];
308 [label="Exit when branch condition"];
}
308 [label="Synthetic else branch"];
309 [label="Enter when branch result"];
309 [label="Synthetic else branch"];
310 [label="Enter when branch result"];
subgraph cluster_69 {
color=blue
310 [label="Enter block"];
311 [label="Access variable R|<local>/x|"];
312 [label="Access variable R|kotlin/String.length|"];
313 [label="Access variable R|<local>/y|"];
314 [label="Access variable <Inapplicable(UNSAFE_CALL): kotlin/String.length>#"];
315 [label="Access variable R|<local>/z|"];
316 [label="Access variable R|kotlin/String.length|"];
317 [label="Exit block"];
311 [label="Enter block"];
312 [label="Access variable R|<local>/x|"];
313 [label="Access variable R|kotlin/String.length|"];
314 [label="Access variable R|<local>/y|"];
315 [label="Access variable <Inapplicable(UNSAFE_CALL): kotlin/String.length>#"];
316 [label="Access variable R|<local>/z|"];
317 [label="Access variable R|kotlin/String.length|"];
318 [label="Exit block"];
}
318 [label="Exit when branch result"];
319 [label="Exit when"];
319 [label="Exit when branch result"];
320 [label="Exit when"];
}
320 [label="Exit block"];
321 [label="Exit block"];
}
321 [label="Exit function test_7" style="filled" fillcolor=red];
322 [label="Exit function test_7" style="filled" fillcolor=red];
}
202 -> {203};
203 -> {204};
@@ -829,26 +830,27 @@ digraph boundSmartcastsInBranches_kt {
287 -> {288};
288 -> {289};
289 -> {291 290};
290 -> {301};
290 -> {302};
291 -> {292};
292 -> {293};
293 -> {294};
294 -> {295};
295 -> {296};
296 -> {297};
297 -> {298};
298 -> {299};
299 -> {300};
300 -> {301};
301 -> {302};
295 -> {322} [label=onUncaughtException];
295 -> {296} [style=dotted];
296 -> {297} [style=dotted];
297 -> {298} [style=dotted];
298 -> {299} [style=dotted];
299 -> {300} [style=dotted];
300 -> {301} [style=dotted];
301 -> {302} [style=dotted];
302 -> {303};
303 -> {304};
304 -> {305};
305 -> {306};
306 -> {307};
307 -> {309 308};
308 -> {319};
309 -> {310};
307 -> {308};
308 -> {310 309};
309 -> {320};
310 -> {311};
311 -> {312};
312 -> {313};
@@ -860,5 +862,6 @@ digraph boundSmartcastsInBranches_kt {
318 -> {319};
319 -> {320};
320 -> {321};
321 -> {322};
}
@@ -32,10 +32,10 @@ import org.jetbrains.kotlin.fir.resolve.inference.isBuiltinFunctionalType
import org.jetbrains.kotlin.fir.resolve.providers.getSymbolByTypeRef
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.resultType
import org.jetbrains.kotlin.fir.resolve.transformers.firClassLike
import org.jetbrains.kotlin.fir.symbols.ensureResolved
import org.jetbrains.kotlin.fir.scopes.impl.delegatedWrapperData
import org.jetbrains.kotlin.fir.scopes.impl.importedFromObjectData
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.ensureResolved
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.builder.buildErrorTypeRef
@@ -266,10 +266,21 @@ fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
} else {
SmartcastStability.STABLE_VALUE
}
val originalType = qualifiedAccessExpression.resultType.coneType
val allTypes = typesFromSmartCast.also {
it += originalType
}
val intersectedType = ConeTypeIntersector.intersectTypes(session.inferenceComponents.ctx, allTypes)
if (intersectedType == originalType) return qualifiedAccessExpression
val intersectedTypeRef = buildResolvedTypeRef {
source = qualifiedAccessExpression.resultType.source?.fakeElement(FirFakeSourceElementKind.SmartCastedTypeRef)
type = intersectedType
annotations += qualifiedAccessExpression.resultType.annotations
delegatedTypeRef = qualifiedAccessExpression.resultType
}
// 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?
// we need to track the type without `Nothing?` so that resolution with this as receiver can go through properly.
if (typesFromSmartCast.any { it.isNullableNothing }) {
val typesFromSmartcastWithoutNullableNothing =
typesFromSmartCast.filterTo(mutableListOf()) { !it.isNullableNothing }.also {
@@ -285,24 +296,13 @@ fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
}
return buildExpressionWithSmartcastToNull {
originalExpression = qualifiedAccessExpression
// TODO: Use Nothing? during resolution?
smartcastType = intersectedTypeRefWithoutNullableNothing
// NB: Nothing? in types from smartcast in DFA is recorded here (and the expression kind itself).
smartcastType = intersectedTypeRef
smartcastTypeWithoutNullableNothing = intersectedTypeRefWithoutNullableNothing
this.typesFromSmartCast = typesFromSmartCast
this.smartcastStability = smartcastStability
}
}
val allTypes = typesFromSmartCast.also {
it += originalType
}
val intersectedType = ConeTypeIntersector.intersectTypes(session.inferenceComponents.ctx, allTypes)
if (intersectedType == originalType) return qualifiedAccessExpression
val intersectedTypeRef = buildResolvedTypeRef {
source = qualifiedAccessExpression.resultType.source?.fakeElement(FirFakeSourceElementKind.SmartCastedTypeRef)
type = intersectedType
annotations += qualifiedAccessExpression.resultType.annotations
delegatedTypeRef = qualifiedAccessExpression.resultType
}
return buildExpressionWithSmartcast {
originalExpression = qualifiedAccessExpression
smartcastType = intersectedTypeRef
@@ -339,7 +339,13 @@ fun FirAnnotationCall.fqName(session: FirSession): FqName? {
}
fun FirCheckedSafeCallSubject.propagateTypeFromOriginalReceiver(nullableReceiverExpression: FirExpression, session: FirSession) {
val receiverType = nullableReceiverExpression.typeRef.coneTypeSafe<ConeKotlinType>() ?: return
// If the receiver expression is smartcast to `null`, it would have `Nothing?` as its type, which may not have members called by user
// code. Hence, we fallback to the type before intersecting with `Nothing?`.
val receiverType = ((nullableReceiverExpression as? FirExpressionWithSmartcastToNull)
?.takeIf { it.isStable }
?.smartcastTypeWithoutNullableNothing
?: nullableReceiverExpression.typeRef)
.coneTypeSafe<ConeKotlinType>() ?: return
val expandedReceiverType = if (receiverType is ConeClassLikeType) receiverType.fullyExpandedType(session) else receiverType
@@ -11,8 +11,8 @@ import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.expressions.FirExpressionWithSmartcast
import org.jetbrains.kotlin.fir.expressions.FirExpressionWithSmartcastToNull
import org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap
import org.jetbrains.kotlin.fir.symbols.ensureResolved
import org.jetbrains.kotlin.fir.scopes.FakeOverrideTypeCalculator
import org.jetbrains.kotlin.fir.scopes.FirTypeScope
import org.jetbrains.kotlin.fir.scopes.FirUnstableSmartcastTypeScope
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.fir.scopes.impl.FirScopeWithFakeOverrideTypeCalculat
import org.jetbrains.kotlin.fir.scopes.impl.FirStandardOverrideChecker
import org.jetbrains.kotlin.fir.scopes.impl.FirTypeIntersectionScope
import org.jetbrains.kotlin.fir.scopes.scopeForClass
import org.jetbrains.kotlin.fir.symbols.ensureResolved
import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
@@ -33,7 +34,8 @@ fun FirExpressionWithSmartcast.smartcastScope(
useSiteSession: FirSession,
scopeSession: ScopeSession
): FirTypeScope? {
val smartcastType = smartcastType.coneType
val smartcastType =
if (this is FirExpressionWithSmartcastToNull) smartcastTypeWithoutNullableNothing.coneType else smartcastType.coneType
val smartcastScope = smartcastType.scope(useSiteSession, scopeSession, FakeOverrideTypeCalculator.DoNothing)
if (isStable) {
return smartcastScope
@@ -7,9 +7,7 @@ package org.jetbrains.kotlin.fir.resolve.calls
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.diagnostics.ConeIntermediateDiagnostic
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirExpressionWithSmartcast
import org.jetbrains.kotlin.fir.expressions.FirThisReceiverExpression
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.builder.buildExpressionWithSmartcast
import org.jetbrains.kotlin.fir.expressions.builder.buildThisReceiverExpression
import org.jetbrains.kotlin.fir.references.builder.buildImplicitThisReference
@@ -60,9 +58,18 @@ abstract class AbstractExplicitReceiverValue<E : FirExpression> : AbstractExplic
class ExpressionReceiverValue(
override val explicitReceiver: FirExpression
) : AbstractExplicitReceiverValue<FirExpression>(), ReceiverValue {
override fun scope(useSiteSession: FirSession, scopeSession: ScopeSession): FirTypeScope? =
(receiverExpression as? FirExpressionWithSmartcast)?.smartcastScope(useSiteSession, scopeSession)
?: type.scope(useSiteSession, scopeSession, FakeOverrideTypeCalculator.DoNothing)
override fun scope(useSiteSession: FirSession, scopeSession: ScopeSession): FirTypeScope? {
var receiverExpr: FirExpression? = receiverExpression
// Unwrap `x!!` to `x` and use the resulted expression to derive receiver type. This is necessary so that smartcast types inside
// `!!` is handled correctly.
if (receiverExpr is FirCheckNotNullCall) {
receiverExpr = receiverExpr.arguments.firstOrNull()
}
if (receiverExpr is FirExpressionWithSmartcast) {
return receiverExpr.smartcastScope(useSiteSession, scopeSession)
}
return type.scope(useSiteSession, scopeSession, FakeOverrideTypeCalculator.DoNothing)
}
}
sealed class ImplicitReceiverValue<S : FirBasedSymbol<*>>(
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.fir.resolve.calls
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.FirVisibilityChecker
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.isInfix
@@ -23,13 +22,14 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.typeContext
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.visibilityChecker
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind.*
import org.jetbrains.kotlin.types.AbstractNullabilityChecker
import org.jetbrains.kotlin.types.SmartcastStability
abstract class ResolutionStage {
abstract suspend fun check(candidate: Candidate, callInfo: CallInfo, sink: CheckerSink, context: ResolutionContext)
@@ -116,9 +116,8 @@ object CheckDispatchReceiver : ResolutionStage() {
(candidate.originScope as? FirUnstableSmartcastTypeScope)?.isSymbolFromUnstableSmartcast(candidate.symbol) == true
if (explicitReceiverExpression is FirExpressionWithSmartcast &&
explicitReceiverExpression !is FirExpressionWithSmartcastToNull &&
explicitReceiverExpression.smartcastStability != SmartcastStability.STABLE_VALUE &&
(isCandidateFromUnstableSmartcast || isReceiverNullable)
!explicitReceiverExpression.isStable &&
(isCandidateFromUnstableSmartcast || (isReceiverNullable && !explicitReceiverExpression.smartcastType.canBeNull))
) {
sink.yieldDiagnostic(UnstableSmartCast(explicitReceiverExpression, explicitReceiverExpression.smartcastType.coneType))
} else if (isReceiverNullable) {
@@ -1124,14 +1124,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
}
if (isAssignment) {
if (initializer is FirConstExpression<*> && initializer.kind == ConstantValueKind.Null) {
flow.addTypeStatement(
propertyVariable typeEq
property.returnTypeRef.coneType.withNullability(ConeNullability.NULLABLE, components.session.typeContext)
)
} else {
flow.addTypeStatement(propertyVariable typeEq initializer.typeRef.coneType)
}
flow.addTypeStatement(propertyVariable typeEq initializer.typeRef.coneType)
}
}
@@ -36,6 +36,7 @@ abstract class FirExpressionWithSmartcastToNull : FirExpressionWithSmartcast() {
abstract override val typesFromSmartCast: Collection<ConeKotlinType>
abstract override val originalType: FirTypeRef
abstract override val smartcastType: FirTypeRef
abstract val smartcastTypeWithoutNullableNothing: FirTypeRef
abstract override val smartcastStability: SmartcastStability
override fun <R, D> accept(visitor: FirVisitor<R, D>, data: D): R = visitor.visitExpressionWithSmartcastToNull(this, data)
@@ -17,9 +17,17 @@ class FirExpressionWithSmartcastToNullBuilder {
lateinit var smartcastType: FirTypeRef
lateinit var typesFromSmartCast: Collection<ConeKotlinType>
lateinit var smartcastStability: SmartcastStability
lateinit var smartcastTypeWithoutNullableNothing: FirTypeRef
fun build(): FirExpressionWithSmartcastToNull {
return FirExpressionWithSmartcastToNullImpl(originalExpression, smartcastType, typesFromSmartCast, smartcastStability, listOf())
return FirExpressionWithSmartcastToNullImpl(
originalExpression,
smartcastType,
typesFromSmartCast,
smartcastStability,
smartcastTypeWithoutNullableNothing,
listOf()
)
}
}
@@ -27,6 +27,7 @@ class FirExpressionWithSmartcastToNullImpl(
override val smartcastType: FirTypeRef,
override val typesFromSmartCast: Collection<ConeKotlinType>,
override val smartcastStability: SmartcastStability,
override val smartcastTypeWithoutNullableNothing: FirTypeRef,
override val nonFatalDiagnostics: List<ConeDiagnostic>
) : FirExpressionWithSmartcastToNull() {
init {
@@ -466,6 +466,7 @@ object NodeConfigurator : AbstractFieldConfigurator<FirTreeBuilder>(FirTreeBuild
+field("typesFromSmartCast", "Collection<ConeKotlinType>", null, customType = coneKotlinTypeType)
+field("originalType", typeRef)
+field("smartcastType", typeRef)
+field("smartcastTypeWithoutNullableNothing", typeRef)
+smartcastStability
}