FIR DFA: store stability in RealVariable

As part of this change, we also extend the usage of RealVariable in more
places during DFA. Now mutable properties, property with custom getters,
delegated properties, etc are also treatd as a `RealVariable`. In
general this is needed in order to carry out smartcast computation in
order to report `SMARTCAST_IMPOSSIBLE`. It seems to also have side
effects that improves behavior of some test files.
This commit is contained in:
Tianyu Geng
2021-05-07 21:20:17 -07:00
committed by Dmitriy Novozhilov
parent 96bd2c54f0
commit 62f7e8f71f
9 changed files with 147 additions and 73 deletions
@@ -156,30 +156,34 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
variableStorage: VariableStorage, variableStorage: VariableStorage,
flow: Flow, flow: Flow,
context: CheckerContext context: CheckerContext
): MutableTypeStatements? = when (this) { ): MutableTypeStatements? {
is ConeBinaryLogicExpression -> { fun buildTypeStatements(arg: ConeValueParameterReference, exactType: Boolean, type: ConeKotlinType): MutableTypeStatements? {
val left = left.buildTypeStatements(function, logicSystem, variableStorage, flow, context)
val right = right.buildTypeStatements(function, logicSystem, variableStorage, flow, context)
if (left != null && right != null) {
if (kind == LogicOperationKind.AND) {
left.apply { mergeTypeStatements(right) }
} else logicSystem.orForTypeStatements(left, right)
} else (left ?: right)
}
is ConeIsInstancePredicate -> {
val fir = function.getParameterSymbol(arg.parameterIndex, context).fir val fir = function.getParameterSymbol(arg.parameterIndex, context).fir
val realVar = variableStorage.getOrCreateRealVariable(flow, fir.symbol, fir) val realVar = variableStorage.getOrCreateRealVariable(flow, fir.symbol, fir)
realVar?.to(simpleTypeStatement(realVar, !isNegated, type))?.let { mutableMapOf(it) } ?.takeIf {
it.stability == PropertyStability.STABLE_VALUE ||
// TODO: consider removing the part below
it.stability == PropertyStability.LOCAL_VAR
}
return realVar?.to(simpleTypeStatement(realVar, exactType, type))?.let { mutableMapOf(it) }
} }
is ConeIsNullPredicate -> { return when (this) {
val fir = function.getParameterSymbol(arg.parameterIndex, context).fir is ConeBinaryLogicExpression -> {
val realVar = variableStorage.getOrCreateRealVariable(flow, fir.symbol, fir) val left = left.buildTypeStatements(function, logicSystem, variableStorage, flow, context)
realVar?.to(simpleTypeStatement(realVar, isNegated, context.session.builtinTypes.anyType.type))?.let { mutableMapOf(it) } val right = right.buildTypeStatements(function, logicSystem, variableStorage, flow, context)
} if (left != null && right != null) {
is ConeLogicalNot -> arg.buildTypeStatements(function, logicSystem, variableStorage, flow, context) if (kind == LogicOperationKind.AND) {
?.mapValuesTo(mutableMapOf()) { (_, value) -> value.invert() } left.apply { mergeTypeStatements(right) }
} else logicSystem.orForTypeStatements(left, right)
} else (left ?: right)
}
is ConeIsInstancePredicate -> buildTypeStatements(arg, !isNegated, type)
is ConeIsNullPredicate -> buildTypeStatements(arg, isNegated, context.session.builtinTypes.anyType.type)
is ConeLogicalNot -> arg.buildTypeStatements(function, logicSystem, variableStorage, flow, context)
?.mapValuesTo(mutableMapOf()) { (_, value) -> value.invert() }
else -> null else -> null
}
} }
private fun ConeKotlinType.isInapplicableWith(operation: Operation, session: FirSession): Boolean { private fun ConeKotlinType.isInapplicableWith(operation: Operation, session: FirSession): Boolean {
@@ -10,7 +10,10 @@ import org.jetbrains.kotlin.builtins.functions.FunctionClassKind
import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.fir.* import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.diagnostics.* import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic
import org.jetbrains.kotlin.fir.diagnostics.ConeSimpleDiagnostic
import org.jetbrains.kotlin.fir.diagnostics.ConeStubDiagnostic
import org.jetbrains.kotlin.fir.diagnostics.DiagnosticKind
import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.builder.* import org.jetbrains.kotlin.fir.expressions.builder.*
import org.jetbrains.kotlin.fir.references.FirErrorNamedReference import org.jetbrains.kotlin.fir.references.FirErrorNamedReference
@@ -23,12 +26,12 @@ import org.jetbrains.kotlin.fir.resolve.calls.ImplicitDispatchReceiverValue
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedNameError import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedNameError
import org.jetbrains.kotlin.fir.resolve.inference.inferenceComponents import org.jetbrains.kotlin.fir.resolve.inference.inferenceComponents
import org.jetbrains.kotlin.fir.resolve.inference.isBuiltinFunctionalType import org.jetbrains.kotlin.fir.resolve.inference.isBuiltinFunctionalType
import org.jetbrains.kotlin.fir.resolve.providers.* 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.body.resolve.resultType
import org.jetbrains.kotlin.fir.resolve.transformers.ensureResolved import org.jetbrains.kotlin.fir.resolve.transformers.ensureResolved
import org.jetbrains.kotlin.fir.scopes.impl.delegatedWrapperData import org.jetbrains.kotlin.fir.scopes.impl.delegatedWrapperData
import org.jetbrains.kotlin.fir.scopes.impl.importedFromObjectData import org.jetbrains.kotlin.fir.scopes.impl.importedFromObjectData
import org.jetbrains.kotlin.fir.symbols.* import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.* import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.builder.buildErrorTypeRef import org.jetbrains.kotlin.fir.types.builder.buildErrorTypeRef
@@ -247,7 +250,8 @@ private fun BodyResolveComponents.typeFromSymbol(symbol: AbstractFirBasedSymbol<
fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo( fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
qualifiedAccessExpression: FirQualifiedAccessExpression qualifiedAccessExpression: FirQualifiedAccessExpression
): FirQualifiedAccessExpression { ): FirQualifiedAccessExpression {
val typesFromSmartCast = dataFlowAnalyzer.getTypeUsingSmartcastInfo(qualifiedAccessExpression) ?: return qualifiedAccessExpression val (stability, typesFromSmartCast) = dataFlowAnalyzer.getTypeUsingSmartcastInfo(qualifiedAccessExpression)
?: return qualifiedAccessExpression
val originalType = qualifiedAccessExpression.resultType.coneType val originalType = qualifiedAccessExpression.resultType.coneType
// For example, if (x == null) { ... }, // 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. // we don't want to smartcast to Nothing?, but we want to record the nullability to its own kind of node.
@@ -268,9 +272,11 @@ fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
return buildExpressionWithSmartcastToNull { return buildExpressionWithSmartcastToNull {
originalExpression = qualifiedAccessExpression originalExpression = qualifiedAccessExpression
// TODO: Use Nothing? during resolution? // TODO: Use Nothing? during resolution?
typeRef = intersectedTypeRefWithoutNullableNothing smartcastType = intersectedTypeRefWithoutNullableNothing
// NB: Nothing? in types from smartcast in DFA is recorded here (and the expression kind itself). // NB: Nothing? in types from smartcast in DFA is recorded here (and the expression kind itself).
this.typesFromSmartCast = typesFromSmartCast this.typesFromSmartCast = typesFromSmartCast
// TODO: differentiate capture local variable
this.smartcastStability = stability.impliedSmartcastStability ?: SmartcastStability.STABLE_VALUE
} }
} }
val allTypes = typesFromSmartCast.also { val allTypes = typesFromSmartCast.also {
@@ -288,7 +294,8 @@ fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
originalExpression = qualifiedAccessExpression originalExpression = qualifiedAccessExpression
smartcastType = intersectedTypeRef smartcastType = intersectedTypeRef
this.typesFromSmartCast = typesFromSmartCast this.typesFromSmartCast = typesFromSmartCast
smartcastStability = SmartcastStability.STABLE_VALUE // TODO: differentiate capture local variable
this.smartcastStability = stability.impliedSmartcastStability ?: SmartcastStability.STABLE_VALUE
} }
} }
@@ -162,7 +162,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
// ----------------------------------- Requests ----------------------------------- // ----------------------------------- Requests -----------------------------------
fun getTypeUsingSmartcastInfo(qualifiedAccessExpression: FirQualifiedAccessExpression): MutableList<ConeKotlinType>? { fun getTypeUsingSmartcastInfo(qualifiedAccessExpression: FirQualifiedAccessExpression): Pair<PropertyStability, MutableList<ConeKotlinType>>? {
/* /*
* DataFlowAnalyzer holds variables only for declarations that have some smartcast (or can have) * 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 * If there is no useful information there is no data flow variable also
@@ -170,13 +170,14 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val symbol: AbstractFirBasedSymbol<*> = qualifiedAccessExpression.symbol ?: return null val symbol: AbstractFirBasedSymbol<*> = qualifiedAccessExpression.symbol ?: return null
val flow = graphBuilder.lastNode.flow val flow = graphBuilder.lastNode.flow
var variable = variableStorage.getRealVariableWithoutUnwrappingAlias(symbol, qualifiedAccessExpression, flow) ?: return null var variable = variableStorage.getRealVariableWithoutUnwrappingAlias(symbol, qualifiedAccessExpression, flow) ?: return null
val stability = variable.stability
val result = mutableListOf<ConeKotlinType>() val result = mutableListOf<ConeKotlinType>()
flow.directAliasMap[variable]?.let { flow.directAliasMap[variable]?.let {
result.addIfNotNull(it.originalType) result.addIfNotNull(it.originalType)
variable = it.variable variable = it.variable
} }
flow.getTypeStatement(variable)?.exactType?.let { result += it } flow.getTypeStatement(variable)?.exactType?.let { result += it }
return result.takeIf { it.isNotEmpty() } return result.takeIf { it.isNotEmpty() }?.let { stability to it }
} }
fun returnExpressionsOfAnonymousFunction(function: FirAnonymousFunction): Collection<FirStatement> { fun returnExpressionsOfAnonymousFunction(function: FirAnonymousFunction): Collection<FirStatement> {
@@ -1009,7 +1010,12 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
assignment: FirVariableAssignment? assignment: FirVariableAssignment?
) { ) {
val flow = node.flow val flow = node.flow
val propertyVariable = variableStorage.getOrCreateRealVariableWithoutUnwrappingAlias(flow, property.symbol, assignment ?: property) val propertyVariable = variableStorage.getOrCreateRealVariableWithoutUnwrappingAlias(
flow,
property.symbol,
assignment ?: property,
if (property.isVal) PropertyStability.STABLE_VALUE else PropertyStability.LOCAL_VAR
)
val isAssignment = assignment != null val isAssignment = assignment != null
if (isAssignment) { if (isAssignment) {
logicSystem.removeLocalVariableAlias(flow, propertyVariable) logicSystem.removeLocalVariableAlias(flow, propertyVariable)
@@ -1017,13 +1023,21 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
logicSystem.recordNewAssignment(flow, propertyVariable, context.newAssignmentIndex()) logicSystem.recordNewAssignment(flow, propertyVariable, context.newAssignmentIndex())
} }
variableStorage.getOrCreateRealVariable(flow, initializer.symbol, initializer)?.let { initializerVariable -> variableStorage.getOrCreateRealVariable(flow, initializer.symbol, initializer)
logicSystem.addLocalVariableAlias( ?.let { initializerVariable ->
flow, propertyVariable, // TODO: handle capture variable
RealVariableAndType(initializerVariable, initializer.coneType) if ((initializerVariable.stability == PropertyStability.STABLE_VALUE || initializerVariable.stability == PropertyStability.LOCAL_VAR) &&
) (propertyVariable.stability == PropertyStability.STABLE_VALUE || propertyVariable.stability == PropertyStability.LOCAL_VAR)
// node.flow.addImplication((propertyVariable notEq null) implies (initializerVariable notEq null)) ) {
} logicSystem.addLocalVariableAlias(
flow, propertyVariable,
RealVariableAndType(initializerVariable, initializer.coneType)
)
// node.flow.addImplication((propertyVariable notEq null) implies (initializerVariable notEq null))
} else {
logicSystem.replaceVariableFromConditionInStatements(flow, initializerVariable, propertyVariable)
}
}
variableStorage.getSyntheticVariable(initializer)?.let { initializerVariable -> variableStorage.getSyntheticVariable(initializer)?.let { initializerVariable ->
/* /*
@@ -8,7 +8,9 @@ package org.jetbrains.kotlin.fir.resolve.dfa
import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirAnonymousObject
import org.jetbrains.kotlin.fir.declarations.FirProperty import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
import org.jetbrains.kotlin.fir.declarations.modality import org.jetbrains.kotlin.fir.declarations.modality
import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.*
@@ -34,14 +36,24 @@ class VariableStorage(private val session: FirSession) {
fun clear(): VariableStorage = VariableStorage(session) fun clear(): VariableStorage = VariableStorage(session)
fun getOrCreateRealVariableWithoutUnwrappingAlias(flow: Flow, symbol: AbstractFirBasedSymbol<*>, fir: FirElement): RealVariable { fun getOrCreateRealVariableWithoutUnwrappingAlias(
flow: Flow,
symbol: AbstractFirBasedSymbol<*>,
fir: FirElement,
stability: PropertyStability
): RealVariable {
val realFir = fir.unwrapElement() val realFir = fir.unwrapElement()
val identifier = getIdentifierBySymbol(flow, symbol, realFir) val identifier = getIdentifierBySymbol(flow, symbol, realFir)
return _realVariables.getOrPut(identifier) { createRealVariableInternal(flow, identifier, realFir) } return _realVariables.getOrPut(identifier) { createRealVariableInternal(flow, identifier, realFir, stability) }
} }
private fun getOrCreateRealVariable(flow: Flow, symbol: AbstractFirBasedSymbol<*>, fir: FirElement): RealVariable { private fun getOrCreateRealVariable(
val variable = getOrCreateRealVariableWithoutUnwrappingAlias(flow, symbol, fir) flow: Flow,
symbol: AbstractFirBasedSymbol<*>,
fir: FirElement,
stability: PropertyStability
): RealVariable {
val variable = getOrCreateRealVariableWithoutUnwrappingAlias(flow, symbol, fir, stability)
return flow.directAliasMap[variable]?.variable ?: variable return flow.directAliasMap[variable]?.variable ?: variable
} }
@@ -69,7 +81,12 @@ class VariableStorage(private val session: FirSession) {
/** /**
* [originalFir] used for extracting expression under <when_subject> and extracting receiver * [originalFir] used for extracting expression under <when_subject> and extracting receiver
*/ */
private fun createRealVariableInternal(flow: Flow, identifier: Identifier, originalFir: FirElement): RealVariable { private fun createRealVariableInternal(
flow: Flow,
identifier: Identifier,
originalFir: FirElement,
stability: PropertyStability
): RealVariable {
val receiver: FirExpression? val receiver: FirExpression?
val isThisReference: Boolean val isThisReference: Boolean
val expression: FirQualifiedAccess? = when (originalFir) { val expression: FirQualifiedAccess? = when (originalFir) {
@@ -88,12 +105,12 @@ class VariableStorage(private val session: FirSession) {
} }
val receiverVariable = receiver?.let { getOrCreateVariable(flow, it) } val receiverVariable = receiver?.let { getOrCreateVariable(flow, it) }
return RealVariable(identifier, isThisReference, receiverVariable, counter++) return RealVariable(identifier, isThisReference, receiverVariable, counter++, stability)
} }
@JvmName("getOrCreateRealVariableOrNull") @JvmName("getOrCreateRealVariableOrNull")
fun getOrCreateRealVariable(flow: Flow, symbol: AbstractFirBasedSymbol<*>?, fir: FirElement): RealVariable? = fun getOrCreateRealVariable(flow: Flow, symbol: AbstractFirBasedSymbol<*>?, fir: FirElement): RealVariable? =
symbol.takeIf { it.isStable(fir) }?.let { getOrCreateRealVariable(flow, it, fir) } symbol.getStability(fir)?.let { getOrCreateRealVariable(flow, symbol!!, fir, it) }
fun createSyntheticVariable(fir: FirElement): SyntheticVariable = fun createSyntheticVariable(fir: FirElement): SyntheticVariable =
SyntheticVariable(fir, counter++).also { syntheticVariables[fir] = it } SyntheticVariable(fir, counter++).also { syntheticVariables[fir] = it }
@@ -101,8 +118,9 @@ class VariableStorage(private val session: FirSession) {
fun getOrCreateVariable(flow: Flow, fir: FirElement): DataFlowVariable { fun getOrCreateVariable(flow: Flow, fir: FirElement): DataFlowVariable {
val realFir = fir.unwrapElement() val realFir = fir.unwrapElement()
val symbol = realFir.symbol val symbol = realFir.symbol
return if (symbol.isStable(realFir)) { val stability = symbol.getStability(realFir)
getOrCreateRealVariable(flow, symbol!!, realFir) return if (stability != null) {
getOrCreateRealVariable(flow, symbol!!, realFir, stability)
} else { } else {
syntheticVariables[realFir] ?: createSyntheticVariable(realFir) syntheticVariables[realFir] ?: createSyntheticVariable(realFir)
} }
@@ -110,7 +128,7 @@ class VariableStorage(private val session: FirSession) {
fun getRealVariableWithoutUnwrappingAlias(symbol: AbstractFirBasedSymbol<*>?, fir: FirElement, flow: Flow): RealVariable? { fun getRealVariableWithoutUnwrappingAlias(symbol: AbstractFirBasedSymbol<*>?, fir: FirElement, flow: Flow): RealVariable? {
val realFir = fir.unwrapElement() val realFir = fir.unwrapElement()
return symbol.takeIf { it.isStable(realFir) }?.let { return symbol.takeIf { it.getStability(realFir) != null }?.let {
_realVariables[getIdentifierBySymbol(flow, it, realFir.unwrapElement())] _realVariables[getIdentifierBySymbol(flow, it, realFir.unwrapElement())]
} }
} }
@@ -126,7 +144,8 @@ class VariableStorage(private val session: FirSession) {
fun getVariable(fir: FirElement, flow: Flow): DataFlowVariable? { fun getVariable(fir: FirElement, flow: Flow): DataFlowVariable? {
val realFir = fir.unwrapElement() val realFir = fir.unwrapElement()
val symbol = realFir.symbol val symbol = realFir.symbol
return if (symbol.isStable(fir)) { val stability = symbol.getStability(fir)
return if (stability != null) {
getRealVariable(symbol, realFir, flow) getRealVariable(symbol, realFir, flow)
} else { } else {
getSyntheticVariable(fir) getSyntheticVariable(fir)
@@ -143,39 +162,40 @@ class VariableStorage(private val session: FirSession) {
} }
@OptIn(ExperimentalContracts::class) @OptIn(ExperimentalContracts::class)
fun AbstractFirBasedSymbol<*>?.isStable(originalFir: FirElement): Boolean { fun AbstractFirBasedSymbol<*>?.getStability(originalFir: FirElement): PropertyStability? {
contract { contract {
returns(true) implies(this@isStable != null) returnsNotNull() implies (this@getStability != null)
} }
when (this) { when (this) {
is FirAnonymousObjectSymbol -> return false is FirAnonymousObjectSymbol -> return null
is FirFunctionSymbol<*>, is FirFunctionSymbol<*>,
is FirClassSymbol<*>, is FirClassSymbol<*>,
is FirBackingFieldSymbol -> return true is FirBackingFieldSymbol -> return PropertyStability.STABLE_VALUE
null -> return false null -> return null
} }
if (originalFir is FirThisReceiverExpression) return true if (originalFir is FirThisReceiverExpression) return PropertyStability.STABLE_VALUE
if (this !is FirVariableSymbol<*>) return false if (this !is FirVariableSymbol<*>) return null
val property = this.fir as? FirProperty ?: return true val property = this.fir as? FirProperty ?: return PropertyStability.STABLE_VALUE
return when { return when {
property.isLocal -> true property.delegate != null -> PropertyStability.DELEGATED_PROPERTY
property.isVar -> false property.isLocal -> if (property.isVal) PropertyStability.STABLE_VALUE else PropertyStability.LOCAL_VAR
property.receiverTypeRef != null -> false property.isVar -> PropertyStability.MUTABLE_PROPERTY
property.getter.let { it != null && it !is FirDefaultPropertyAccessor } -> false property.receiverTypeRef != null -> PropertyStability.PROPERTY_WITH_GETTER
property.getter.let { it != null && it !is FirDefaultPropertyAccessor } -> PropertyStability.PROPERTY_WITH_GETTER
property.moduleData.session != session -> PropertyStability.ALIEN_PUBLIC_PROPERTY
property.modality != Modality.FINAL -> { property.modality != Modality.FINAL -> {
val dispatchReceiver = (originalFir.unwrapElement() as? FirQualifiedAccess)?.dispatchReceiver ?: return false val dispatchReceiver = (originalFir.unwrapElement() as? FirQualifiedAccess)?.dispatchReceiver ?: return null
val receiverType = dispatchReceiver.typeRef.coneTypeSafe<ConeClassLikeType>()?.fullyExpandedType(session) ?: return false val receiverType = dispatchReceiver.typeRef.coneTypeSafe<ConeClassLikeType>()?.fullyExpandedType(session) ?: return null
val receiverSymbol = receiverType.lookupTag.toSymbol(session) ?: return false val receiverSymbol = receiverType.lookupTag.toSymbol(session) ?: return null
when (val receiverFir = receiverSymbol.fir) { when (val receiverFir = receiverSymbol.fir) {
is org.jetbrains.kotlin.fir.declarations.FirAnonymousObject -> true is FirAnonymousObject -> PropertyStability.STABLE_VALUE
is org.jetbrains.kotlin.fir.declarations.FirRegularClass -> receiverFir.modality == Modality.FINAL is FirRegularClass -> if (receiverFir.modality == Modality.FINAL) PropertyStability.STABLE_VALUE else PropertyStability.PROPERTY_WITH_GETTER
else -> throw IllegalStateException("Should not be here: $receiverFir") else -> throw IllegalStateException("Should not be here: $receiverFir")
} }
} }
else -> true else -> PropertyStability.STABLE_VALUE
} }
} }
} }
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.types.ConeClassErrorType import org.jetbrains.kotlin.fir.types.ConeClassErrorType
import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.types.SmartcastStability
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract import kotlin.contracts.contract
@@ -33,11 +34,37 @@ sealed class DataFlowVariable(private val variableIndexForDebug: Int) {
} }
} }
enum class PropertyStability(val impliedSmartcastStability: SmartcastStability?) {
// Immutable and no custom getter or local.
// Smartcast is definitely safe regardless of usage.
STABLE_VALUE(SmartcastStability.STABLE_VALUE),
// Open or custom getter.
// Smartcast is always unsafe regardless of usage.
PROPERTY_WITH_GETTER(SmartcastStability.PROPERTY_WITH_GETTER),
// Protected / public member value from another module.
// Smartcast is always unsafe regardless of usage.
ALIEN_PUBLIC_PROPERTY(SmartcastStability.ALIEN_PUBLIC_PROPERTY),
// Smartcast may or may not be safe, depending on whether there are concurrent writes to this local variable.
LOCAL_VAR(null),
// Mutable member property of a class or object.
// Smartcast is always unsafe regardless of usage.
MUTABLE_PROPERTY(SmartcastStability.MUTABLE_PROPERTY),
// Delegated property of a class or object.
// Smartcast is always unsafe regardless of usage.
DELEGATED_PROPERTY(SmartcastStability.DELEGATED_PROPERTY),
}
class RealVariable( class RealVariable(
val identifier: Identifier, val identifier: Identifier,
val isThisReference: Boolean, val isThisReference: Boolean,
val explicitReceiverVariable: DataFlowVariable?, val explicitReceiverVariable: DataFlowVariable?,
variableIndexForDebug: Int variableIndexForDebug: Int,
val stability: PropertyStability,
) : DataFlowVariable(variableIndexForDebug) { ) : DataFlowVariable(variableIndexForDebug) {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
return this === other return this === other
@@ -467,6 +467,8 @@ object NodeConfigurator : AbstractFieldConfigurator<FirTreeBuilder>(FirTreeBuild
+field("originalExpression", qualifiedAccessExpression) +field("originalExpression", qualifiedAccessExpression)
+field("typesFromSmartCast", "Collection<ConeKotlinType>", null, customType = coneKotlinTypeType) +field("typesFromSmartCast", "Collection<ConeKotlinType>", null, customType = coneKotlinTypeType)
+field("originalType", typeRef) +field("originalType", typeRef)
+field("smartcastType", typeRef)
+smartcastStability
} }
safeCallExpression.configure { safeCallExpression.configure {
@@ -14,13 +14,13 @@ fun test(u: A?, x: A?, y: A?, z: A?, w: A, v: A?) {
u!!.b<!UNNECESSARY_SAFE_CALL!>?.<!>foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!> u!!.b<!UNNECESSARY_SAFE_CALL!>?.<!>foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>
x?.b!!.foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!> x?.b!!.foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>
// x?.b is not null // x?.b is not null
x!!.b<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>.foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!> x<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>.b<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>.foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>
y?.nb?.foo()!! y?.nb?.foo()!!
y!!.nb?.foo()!! y!!.nb?.foo()!!
z?.nb!!.foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!> z?.nb!!.foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>
// z?.nb is not null // z?.nb is not null
z!!.nb!!.foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!> z<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>.nb!!.foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>
w.b<!UNNECESSARY_SAFE_CALL!>?.<!>foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!> w.b<!UNNECESSARY_SAFE_CALL!>?.<!>foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>
w.b<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>.foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!> w.b<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>.foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>
@@ -17,7 +17,7 @@ import a.X
public fun X.gav(): Int { public fun X.gav(): Int {
if (x != null) if (x != null)
// Smart cast is not possible if definition is in another module // Smart cast is not possible if definition is in another module
return x.length return x<!UNSAFE_CALL!>.<!>length
else else
return 0 return 0
} }
@@ -29,7 +29,7 @@ package a
public fun X.gav(): Int { public fun X.gav(): Int {
if (x != null) if (x != null)
// Even if it's in the same package // Even if it's in the same package
return x.length return x<!UNSAFE_CALL!>.<!>length
else else
return 0 return 0
} }
@@ -33,7 +33,7 @@ fun case_3(x: Int) = ""
fun case_3(x: Int?) = 10 fun case_3(x: Int?) = 10
fun case_3() { fun case_3() {
if (case_3_prop != null) { if (case_3_prop != null) {
val z = case_3(<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>case_3_prop<!>) val z = case_3(<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int?")!>case_3_prop<!>)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int")!>z<!> <!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int")!>z<!>
} }
} }
@@ -89,7 +89,7 @@ fun case_7(x: Int) = ""
fun case_7(x: Int?) = 10 fun case_7(x: Int?) = 10
fun case_7() { fun case_7() {
if (case_7_prop != null) { if (case_7_prop != null) {
val z = case_7(<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>case_7_prop<!>) val z = case_7(<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int?")!>case_7_prop<!>)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int")!>z<!> <!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int")!>z<!>
} }
} }