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,
|
||||
flow: Flow,
|
||||
context: CheckerContext
|
||||
): MutableTypeStatements? = when (this) {
|
||||
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 -> {
|
||||
): MutableTypeStatements? {
|
||||
fun buildTypeStatements(arg: ConeValueParameterReference, exactType: Boolean, type: ConeKotlinType): MutableTypeStatements? {
|
||||
val fir = function.getParameterSymbol(arg.parameterIndex, context).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 -> {
|
||||
val fir = function.getParameterSymbol(arg.parameterIndex, context).fir
|
||||
val realVar = variableStorage.getOrCreateRealVariable(flow, fir.symbol, fir)
|
||||
realVar?.to(simpleTypeStatement(realVar, isNegated, context.session.builtinTypes.anyType.type))?.let { mutableMapOf(it) }
|
||||
}
|
||||
is ConeLogicalNot -> arg.buildTypeStatements(function, logicSystem, variableStorage, flow, context)
|
||||
?.mapValuesTo(mutableMapOf()) { (_, value) -> value.invert() }
|
||||
return when (this) {
|
||||
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 -> 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 {
|
||||
|
||||
@@ -10,7 +10,10 @@ import org.jetbrains.kotlin.builtins.functions.FunctionClassKind
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.fir.*
|
||||
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.builder.*
|
||||
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.inference.inferenceComponents
|
||||
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.ensureResolved
|
||||
import org.jetbrains.kotlin.fir.scopes.impl.delegatedWrapperData
|
||||
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.types.*
|
||||
import org.jetbrains.kotlin.fir.types.builder.buildErrorTypeRef
|
||||
@@ -247,7 +250,8 @@ private fun BodyResolveComponents.typeFromSymbol(symbol: AbstractFirBasedSymbol<
|
||||
fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
|
||||
qualifiedAccessExpression: FirQualifiedAccessExpression
|
||||
): FirQualifiedAccessExpression {
|
||||
val typesFromSmartCast = dataFlowAnalyzer.getTypeUsingSmartcastInfo(qualifiedAccessExpression) ?: return qualifiedAccessExpression
|
||||
val (stability, typesFromSmartCast) = dataFlowAnalyzer.getTypeUsingSmartcastInfo(qualifiedAccessExpression)
|
||||
?: return qualifiedAccessExpression
|
||||
val originalType = qualifiedAccessExpression.resultType.coneType
|
||||
// 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.
|
||||
@@ -268,9 +272,11 @@ fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
|
||||
return buildExpressionWithSmartcastToNull {
|
||||
originalExpression = qualifiedAccessExpression
|
||||
// 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).
|
||||
this.typesFromSmartCast = typesFromSmartCast
|
||||
// TODO: differentiate capture local variable
|
||||
this.smartcastStability = stability.impliedSmartcastStability ?: SmartcastStability.STABLE_VALUE
|
||||
}
|
||||
}
|
||||
val allTypes = typesFromSmartCast.also {
|
||||
@@ -288,7 +294,8 @@ fun BodyResolveComponents.transformQualifiedAccessUsingSmartcastInfo(
|
||||
originalExpression = qualifiedAccessExpression
|
||||
smartcastType = intersectedTypeRef
|
||||
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 -----------------------------------
|
||||
|
||||
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)
|
||||
* 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 flow = graphBuilder.lastNode.flow
|
||||
var variable = variableStorage.getRealVariableWithoutUnwrappingAlias(symbol, qualifiedAccessExpression, flow) ?: return null
|
||||
val stability = variable.stability
|
||||
val result = mutableListOf<ConeKotlinType>()
|
||||
flow.directAliasMap[variable]?.let {
|
||||
result.addIfNotNull(it.originalType)
|
||||
variable = it.variable
|
||||
}
|
||||
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> {
|
||||
@@ -1009,7 +1010,12 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
assignment: FirVariableAssignment?
|
||||
) {
|
||||
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
|
||||
if (isAssignment) {
|
||||
logicSystem.removeLocalVariableAlias(flow, propertyVariable)
|
||||
@@ -1017,13 +1023,21 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
logicSystem.recordNewAssignment(flow, propertyVariable, context.newAssignmentIndex())
|
||||
}
|
||||
|
||||
variableStorage.getOrCreateRealVariable(flow, initializer.symbol, initializer)?.let { initializerVariable ->
|
||||
logicSystem.addLocalVariableAlias(
|
||||
flow, propertyVariable,
|
||||
RealVariableAndType(initializerVariable, initializer.coneType)
|
||||
)
|
||||
// node.flow.addImplication((propertyVariable notEq null) implies (initializerVariable notEq null))
|
||||
}
|
||||
variableStorage.getOrCreateRealVariable(flow, initializer.symbol, initializer)
|
||||
?.let { initializerVariable ->
|
||||
// TODO: handle capture variable
|
||||
if ((initializerVariable.stability == PropertyStability.STABLE_VALUE || initializerVariable.stability == PropertyStability.LOCAL_VAR) &&
|
||||
(propertyVariable.stability == PropertyStability.STABLE_VALUE || propertyVariable.stability == PropertyStability.LOCAL_VAR)
|
||||
) {
|
||||
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 ->
|
||||
/*
|
||||
|
||||
@@ -8,7 +8,9 @@ package org.jetbrains.kotlin.fir.resolve.dfa
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.fir.FirElement
|
||||
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.FirRegularClass
|
||||
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
|
||||
import org.jetbrains.kotlin.fir.declarations.modality
|
||||
import org.jetbrains.kotlin.fir.expressions.*
|
||||
@@ -34,14 +36,24 @@ class VariableStorage(private val session: FirSession) {
|
||||
|
||||
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 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 {
|
||||
val variable = getOrCreateRealVariableWithoutUnwrappingAlias(flow, symbol, fir)
|
||||
private fun getOrCreateRealVariable(
|
||||
flow: Flow,
|
||||
symbol: AbstractFirBasedSymbol<*>,
|
||||
fir: FirElement,
|
||||
stability: PropertyStability
|
||||
): RealVariable {
|
||||
val variable = getOrCreateRealVariableWithoutUnwrappingAlias(flow, symbol, fir, stability)
|
||||
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
|
||||
*/
|
||||
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 isThisReference: Boolean
|
||||
val expression: FirQualifiedAccess? = when (originalFir) {
|
||||
@@ -88,12 +105,12 @@ class VariableStorage(private val session: FirSession) {
|
||||
}
|
||||
|
||||
val receiverVariable = receiver?.let { getOrCreateVariable(flow, it) }
|
||||
return RealVariable(identifier, isThisReference, receiverVariable, counter++)
|
||||
return RealVariable(identifier, isThisReference, receiverVariable, counter++, stability)
|
||||
}
|
||||
|
||||
@JvmName("getOrCreateRealVariableOrNull")
|
||||
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 =
|
||||
SyntheticVariable(fir, counter++).also { syntheticVariables[fir] = it }
|
||||
@@ -101,8 +118,9 @@ class VariableStorage(private val session: FirSession) {
|
||||
fun getOrCreateVariable(flow: Flow, fir: FirElement): DataFlowVariable {
|
||||
val realFir = fir.unwrapElement()
|
||||
val symbol = realFir.symbol
|
||||
return if (symbol.isStable(realFir)) {
|
||||
getOrCreateRealVariable(flow, symbol!!, realFir)
|
||||
val stability = symbol.getStability(realFir)
|
||||
return if (stability != null) {
|
||||
getOrCreateRealVariable(flow, symbol!!, realFir, stability)
|
||||
} else {
|
||||
syntheticVariables[realFir] ?: createSyntheticVariable(realFir)
|
||||
}
|
||||
@@ -110,7 +128,7 @@ class VariableStorage(private val session: FirSession) {
|
||||
|
||||
fun getRealVariableWithoutUnwrappingAlias(symbol: AbstractFirBasedSymbol<*>?, fir: FirElement, flow: Flow): RealVariable? {
|
||||
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())]
|
||||
}
|
||||
}
|
||||
@@ -126,7 +144,8 @@ class VariableStorage(private val session: FirSession) {
|
||||
fun getVariable(fir: FirElement, flow: Flow): DataFlowVariable? {
|
||||
val realFir = fir.unwrapElement()
|
||||
val symbol = realFir.symbol
|
||||
return if (symbol.isStable(fir)) {
|
||||
val stability = symbol.getStability(fir)
|
||||
return if (stability != null) {
|
||||
getRealVariable(symbol, realFir, flow)
|
||||
} else {
|
||||
getSyntheticVariable(fir)
|
||||
@@ -143,39 +162,40 @@ class VariableStorage(private val session: FirSession) {
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
fun AbstractFirBasedSymbol<*>?.isStable(originalFir: FirElement): Boolean {
|
||||
fun AbstractFirBasedSymbol<*>?.getStability(originalFir: FirElement): PropertyStability? {
|
||||
contract {
|
||||
returns(true) implies(this@isStable != null)
|
||||
returnsNotNull() implies (this@getStability != null)
|
||||
}
|
||||
when (this) {
|
||||
is FirAnonymousObjectSymbol -> return false
|
||||
is FirAnonymousObjectSymbol -> return null
|
||||
is FirFunctionSymbol<*>,
|
||||
is FirClassSymbol<*>,
|
||||
is FirBackingFieldSymbol -> return true
|
||||
null -> return false
|
||||
is FirBackingFieldSymbol -> return PropertyStability.STABLE_VALUE
|
||||
null -> return null
|
||||
}
|
||||
if (originalFir is FirThisReceiverExpression) return true
|
||||
if (this !is FirVariableSymbol<*>) return false
|
||||
if (originalFir is FirThisReceiverExpression) return PropertyStability.STABLE_VALUE
|
||||
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 {
|
||||
property.isLocal -> true
|
||||
property.isVar -> false
|
||||
property.receiverTypeRef != null -> false
|
||||
property.getter.let { it != null && it !is FirDefaultPropertyAccessor } -> false
|
||||
property.delegate != null -> PropertyStability.DELEGATED_PROPERTY
|
||||
property.isLocal -> if (property.isVal) PropertyStability.STABLE_VALUE else PropertyStability.LOCAL_VAR
|
||||
property.isVar -> PropertyStability.MUTABLE_PROPERTY
|
||||
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 -> {
|
||||
val dispatchReceiver = (originalFir.unwrapElement() as? FirQualifiedAccess)?.dispatchReceiver ?: return false
|
||||
val receiverType = dispatchReceiver.typeRef.coneTypeSafe<ConeClassLikeType>()?.fullyExpandedType(session) ?: return false
|
||||
val receiverSymbol = receiverType.lookupTag.toSymbol(session) ?: return false
|
||||
val dispatchReceiver = (originalFir.unwrapElement() as? FirQualifiedAccess)?.dispatchReceiver ?: return null
|
||||
val receiverType = dispatchReceiver.typeRef.coneTypeSafe<ConeClassLikeType>()?.fullyExpandedType(session) ?: return null
|
||||
val receiverSymbol = receiverType.lookupTag.toSymbol(session) ?: return null
|
||||
when (val receiverFir = receiverSymbol.fir) {
|
||||
is org.jetbrains.kotlin.fir.declarations.FirAnonymousObject -> true
|
||||
is org.jetbrains.kotlin.fir.declarations.FirRegularClass -> receiverFir.modality == Modality.FINAL
|
||||
is FirAnonymousObject -> PropertyStability.STABLE_VALUE
|
||||
is FirRegularClass -> if (receiverFir.modality == Modality.FINAL) PropertyStability.STABLE_VALUE else PropertyStability.PROPERTY_WITH_GETTER
|
||||
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.types.ConeClassErrorType
|
||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||
import org.jetbrains.kotlin.types.SmartcastStability
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
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(
|
||||
val identifier: Identifier,
|
||||
val isThisReference: Boolean,
|
||||
val explicitReceiverVariable: DataFlowVariable?,
|
||||
variableIndexForDebug: Int
|
||||
variableIndexForDebug: Int,
|
||||
val stability: PropertyStability,
|
||||
) : DataFlowVariable(variableIndexForDebug) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return this === other
|
||||
|
||||
+2
@@ -467,6 +467,8 @@ object NodeConfigurator : AbstractFieldConfigurator<FirTreeBuilder>(FirTreeBuild
|
||||
+field("originalExpression", qualifiedAccessExpression)
|
||||
+field("typesFromSmartCast", "Collection<ConeKotlinType>", null, customType = coneKotlinTypeType)
|
||||
+field("originalType", typeRef)
|
||||
+field("smartcastType", typeRef)
|
||||
+smartcastStability
|
||||
}
|
||||
|
||||
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!>!!<!>
|
||||
x?.b!!.foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>
|
||||
// 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()!!
|
||||
z?.nb!!.foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>
|
||||
// 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_NOT_NULL_ASSERTION!>!!<!>.foo()<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@ import a.X
|
||||
public fun X.gav(): Int {
|
||||
if (x != null)
|
||||
// Smart cast is not possible if definition is in another module
|
||||
return x.length
|
||||
return x<!UNSAFE_CALL!>.<!>length
|
||||
else
|
||||
return 0
|
||||
}
|
||||
@@ -29,7 +29,7 @@ package a
|
||||
public fun X.gav(): Int {
|
||||
if (x != null)
|
||||
// Even if it's in the same package
|
||||
return x.length
|
||||
return x<!UNSAFE_CALL!>.<!>length
|
||||
else
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ fun case_3(x: Int) = ""
|
||||
fun case_3(x: Int?) = 10
|
||||
fun case_3() {
|
||||
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<!>
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ fun case_7(x: Int) = ""
|
||||
fun case_7(x: Int?) = 10
|
||||
fun case_7() {
|
||||
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<!>
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user