Revert "FIR DFA: store stability in RealVariable"

This reverts commit 62f7e8f7
This commit is contained in:
Dmitriy Novozhilov
2021-06-03 09:53:57 +03:00
parent 796f8e6bce
commit 0497f60af3
9 changed files with 73 additions and 147 deletions
@@ -156,34 +156,30 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
variableStorage: VariableStorage, variableStorage: VariableStorage,
flow: Flow, flow: Flow,
context: CheckerContext context: CheckerContext
): MutableTypeStatements? { ): MutableTypeStatements? = when (this) {
fun buildTypeStatements(arg: ConeValueParameterReference, exactType: Boolean, type: ConeKotlinType): MutableTypeStatements? { is ConeBinaryLogicExpression -> {
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)
?.takeIf { realVar?.to(simpleTypeStatement(realVar, !isNegated, type))?.let { mutableMapOf(it) }
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) }
} }
return when (this) { is ConeIsNullPredicate -> {
is ConeBinaryLogicExpression -> { val fir = function.getParameterSymbol(arg.parameterIndex, context).fir
val left = left.buildTypeStatements(function, logicSystem, variableStorage, flow, context) val realVar = variableStorage.getOrCreateRealVariable(flow, fir.symbol, fir)
val right = right.buildTypeStatements(function, logicSystem, variableStorage, flow, context) realVar?.to(simpleTypeStatement(realVar, isNegated, context.session.builtinTypes.anyType.type))?.let { mutableMapOf(it) }
if (left != null && right != null) { }
if (kind == LogicOperationKind.AND) { is ConeLogicalNot -> arg.buildTypeStatements(function, logicSystem, variableStorage, flow, context)
left.apply { mergeTypeStatements(right) } ?.mapValuesTo(mutableMapOf()) { (_, value) -> value.invert() }
} 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,10 +10,7 @@ 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.ConeDiagnostic import org.jetbrains.kotlin.fir.diagnostics.*
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
@@ -26,12 +23,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.getSymbolByTypeRef import org.jetbrains.kotlin.fir.resolve.providers.*
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.AbstractFirBasedSymbol import org.jetbrains.kotlin.fir.symbols.*
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
@@ -250,8 +247,7 @@ private fun BodyResolveComponents.typeFromSymbol(symbol: AbstractFirBasedSymbol<
fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo( fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
qualifiedAccessExpression: FirQualifiedAccessExpression qualifiedAccessExpression: FirQualifiedAccessExpression
): FirQualifiedAccessExpression { ): FirQualifiedAccessExpression {
val (stability, typesFromSmartCast) = dataFlowAnalyzer.getTypeUsingSmartcastInfo(qualifiedAccessExpression) val typesFromSmartCast = dataFlowAnalyzer.getTypeUsingSmartcastInfo(qualifiedAccessExpression) ?: return 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.
@@ -272,11 +268,9 @@ fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
return buildExpressionWithSmartcastToNull { return buildExpressionWithSmartcastToNull {
originalExpression = qualifiedAccessExpression originalExpression = qualifiedAccessExpression
// TODO: Use Nothing? during resolution? // TODO: Use Nothing? during resolution?
smartcastType = intersectedTypeRefWithoutNullableNothing typeRef = 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 {
@@ -294,8 +288,7 @@ fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
originalExpression = qualifiedAccessExpression originalExpression = qualifiedAccessExpression
smartcastType = intersectedTypeRef smartcastType = intersectedTypeRef
this.typesFromSmartCast = typesFromSmartCast this.typesFromSmartCast = typesFromSmartCast
// TODO: differentiate capture local variable smartcastStability = SmartcastStability.STABLE_VALUE
this.smartcastStability = stability.impliedSmartcastStability ?: SmartcastStability.STABLE_VALUE
} }
} }
@@ -162,7 +162,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
// ----------------------------------- Requests ----------------------------------- // ----------------------------------- Requests -----------------------------------
fun getTypeUsingSmartcastInfo(qualifiedAccessExpression: FirQualifiedAccessExpression): Pair<PropertyStability, MutableList<ConeKotlinType>>? { fun getTypeUsingSmartcastInfo(qualifiedAccessExpression: FirQualifiedAccessExpression): 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,14 +170,13 @@ 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() }?.let { stability to it } return result.takeIf { it.isNotEmpty() }
} }
fun returnExpressionsOfAnonymousFunction(function: FirAnonymousFunction): Collection<FirStatement> { fun returnExpressionsOfAnonymousFunction(function: FirAnonymousFunction): Collection<FirStatement> {
@@ -1010,12 +1009,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
assignment: FirVariableAssignment? assignment: FirVariableAssignment?
) { ) {
val flow = node.flow val flow = node.flow
val propertyVariable = variableStorage.getOrCreateRealVariableWithoutUnwrappingAlias( val propertyVariable = variableStorage.getOrCreateRealVariableWithoutUnwrappingAlias(flow, property.symbol, assignment ?: property)
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)
@@ -1023,21 +1017,13 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
logicSystem.recordNewAssignment(flow, propertyVariable, context.newAssignmentIndex()) logicSystem.recordNewAssignment(flow, propertyVariable, context.newAssignmentIndex())
} }
variableStorage.getOrCreateRealVariable(flow, initializer.symbol, initializer) variableStorage.getOrCreateRealVariable(flow, initializer.symbol, initializer)?.let { initializerVariable ->
?.let { initializerVariable -> logicSystem.addLocalVariableAlias(
// TODO: handle capture variable flow, propertyVariable,
if ((initializerVariable.stability == PropertyStability.STABLE_VALUE || initializerVariable.stability == PropertyStability.LOCAL_VAR) && RealVariableAndType(initializerVariable, initializer.coneType)
(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,9 +8,7 @@ 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.*
@@ -36,24 +34,14 @@ class VariableStorage(private val session: FirSession) {
fun clear(): VariableStorage = VariableStorage(session) fun clear(): VariableStorage = VariableStorage(session)
fun getOrCreateRealVariableWithoutUnwrappingAlias( fun getOrCreateRealVariableWithoutUnwrappingAlias(flow: Flow, symbol: AbstractFirBasedSymbol<*>, fir: FirElement): RealVariable {
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, stability) } return _realVariables.getOrPut(identifier) { createRealVariableInternal(flow, identifier, realFir) }
} }
private fun getOrCreateRealVariable( private fun getOrCreateRealVariable(flow: Flow, symbol: AbstractFirBasedSymbol<*>, fir: FirElement): RealVariable {
flow: Flow, val variable = getOrCreateRealVariableWithoutUnwrappingAlias(flow, symbol, fir)
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
} }
@@ -81,12 +69,7 @@ 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( private fun createRealVariableInternal(flow: Flow, identifier: Identifier, originalFir: FirElement): RealVariable {
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) {
@@ -105,12 +88,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++, stability) return RealVariable(identifier, isThisReference, receiverVariable, counter++)
} }
@JvmName("getOrCreateRealVariableOrNull") @JvmName("getOrCreateRealVariableOrNull")
fun getOrCreateRealVariable(flow: Flow, symbol: AbstractFirBasedSymbol<*>?, fir: FirElement): RealVariable? = fun getOrCreateRealVariable(flow: Flow, symbol: AbstractFirBasedSymbol<*>?, fir: FirElement): RealVariable? =
symbol.getStability(fir)?.let { getOrCreateRealVariable(flow, symbol!!, fir, it) } symbol.takeIf { it.isStable(fir) }?.let { getOrCreateRealVariable(flow, it, fir) }
fun createSyntheticVariable(fir: FirElement): SyntheticVariable = fun createSyntheticVariable(fir: FirElement): SyntheticVariable =
SyntheticVariable(fir, counter++).also { syntheticVariables[fir] = it } SyntheticVariable(fir, counter++).also { syntheticVariables[fir] = it }
@@ -118,9 +101,8 @@ 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
val stability = symbol.getStability(realFir) return if (symbol.isStable(realFir)) {
return if (stability != null) { getOrCreateRealVariable(flow, symbol!!, realFir)
getOrCreateRealVariable(flow, symbol!!, realFir, stability)
} else { } else {
syntheticVariables[realFir] ?: createSyntheticVariable(realFir) syntheticVariables[realFir] ?: createSyntheticVariable(realFir)
} }
@@ -128,7 +110,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.getStability(realFir) != null }?.let { return symbol.takeIf { it.isStable(realFir) }?.let {
_realVariables[getIdentifierBySymbol(flow, it, realFir.unwrapElement())] _realVariables[getIdentifierBySymbol(flow, it, realFir.unwrapElement())]
} }
} }
@@ -144,8 +126,7 @@ 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
val stability = symbol.getStability(fir) return if (symbol.isStable(fir)) {
return if (stability != null) {
getRealVariable(symbol, realFir, flow) getRealVariable(symbol, realFir, flow)
} else { } else {
getSyntheticVariable(fir) getSyntheticVariable(fir)
@@ -162,40 +143,39 @@ class VariableStorage(private val session: FirSession) {
} }
@OptIn(ExperimentalContracts::class) @OptIn(ExperimentalContracts::class)
fun AbstractFirBasedSymbol<*>?.getStability(originalFir: FirElement): PropertyStability? { fun AbstractFirBasedSymbol<*>?.isStable(originalFir: FirElement): Boolean {
contract { contract {
returnsNotNull() implies (this@getStability != null) returns(true) implies(this@isStable != null)
} }
when (this) { when (this) {
is FirAnonymousObjectSymbol -> return null is FirAnonymousObjectSymbol -> return false
is FirFunctionSymbol<*>, is FirFunctionSymbol<*>,
is FirClassSymbol<*>, is FirClassSymbol<*>,
is FirBackingFieldSymbol -> return PropertyStability.STABLE_VALUE is FirBackingFieldSymbol -> return true
null -> return null null -> return false
} }
if (originalFir is FirThisReceiverExpression) return PropertyStability.STABLE_VALUE if (originalFir is FirThisReceiverExpression) return true
if (this !is FirVariableSymbol<*>) return null if (this !is FirVariableSymbol<*>) return false
val property = this.fir as? FirProperty ?: return PropertyStability.STABLE_VALUE val property = this.fir as? FirProperty ?: return true
return when { return when {
property.delegate != null -> PropertyStability.DELEGATED_PROPERTY property.isLocal -> true
property.isLocal -> if (property.isVal) PropertyStability.STABLE_VALUE else PropertyStability.LOCAL_VAR property.isVar -> false
property.isVar -> PropertyStability.MUTABLE_PROPERTY property.receiverTypeRef != null -> false
property.receiverTypeRef != null -> PropertyStability.PROPERTY_WITH_GETTER property.getter.let { it != null && it !is FirDefaultPropertyAccessor } -> false
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 null val dispatchReceiver = (originalFir.unwrapElement() as? FirQualifiedAccess)?.dispatchReceiver ?: return false
val receiverType = dispatchReceiver.typeRef.coneTypeSafe<ConeClassLikeType>()?.fullyExpandedType(session) ?: return null val receiverType = dispatchReceiver.typeRef.coneTypeSafe<ConeClassLikeType>()?.fullyExpandedType(session) ?: return false
val receiverSymbol = receiverType.lookupTag.toSymbol(session) ?: return null val receiverSymbol = receiverType.lookupTag.toSymbol(session) ?: return false
when (val receiverFir = receiverSymbol.fir) { when (val receiverFir = receiverSymbol.fir) {
is FirAnonymousObject -> PropertyStability.STABLE_VALUE is org.jetbrains.kotlin.fir.declarations.FirAnonymousObject -> true
is FirRegularClass -> if (receiverFir.modality == Modality.FINAL) PropertyStability.STABLE_VALUE else PropertyStability.PROPERTY_WITH_GETTER is org.jetbrains.kotlin.fir.declarations.FirRegularClass -> receiverFir.modality == Modality.FINAL
else -> throw IllegalStateException("Should not be here: $receiverFir") else -> throw IllegalStateException("Should not be here: $receiverFir")
} }
} }
else -> PropertyStability.STABLE_VALUE else -> true
} }
} }
} }
@@ -11,7 +11,6 @@ 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
@@ -34,37 +33,11 @@ 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,8 +467,6 @@ 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<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>.b<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>.foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!> x!!.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<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>.nb!!.foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!> z!!.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<!UNSAFE_CALL!>.<!>length return x.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<!UNSAFE_CALL!>.<!>length return x.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? & kotlin.Int?")!>case_3_prop<!>) val z = case_3(<!DEBUG_INFO_EXPRESSION_TYPE("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? & kotlin.Int?")!>case_7_prop<!>) val z = case_7(<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>case_7_prop<!>)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int")!>z<!> <!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int")!>z<!>
} }
} }