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:
committed by
Dmitriy Novozhilov
parent
96bd2c54f0
commit
62f7e8f71f
+24
-20
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+24
-10
@@ -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
|
||||||
|
|||||||
+2
@@ -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 {
|
||||||
|
|||||||
+2
-2
@@ -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!>!!<!>
|
||||||
|
|||||||
+2
-2
@@ -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<!>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user