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:
committed by
teamcityserver
parent
7e2f15f532
commit
4726dcce40
+40
-37
@@ -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) {
|
||||
|
||||
+1
-8
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -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)
|
||||
|
||||
+9
-1
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -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 {
|
||||
|
||||
+1
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user