FirReturnsImpliesAnalyzer: find receiver/variable of accessor properly

This commit is contained in:
Mikhail Glukhikh
2021-03-10 13:12:11 +03:00
parent a345b81f2a
commit 798d848a91
3 changed files with 47 additions and 22 deletions
@@ -11,8 +11,8 @@ fun Any?.isNotNull(): Boolean {
@OptIn(ExperimentalContracts::class) @OptIn(ExperimentalContracts::class)
val Any?.isNotNull: Boolean val Any?.isNotNull: Boolean
get() { get() {
<!WRONG_IMPLIES_CONDITION!>contract { contract {
returns(true) implies (this@isNotNull != null) returns(true) implies (this@isNotNull != null)
}<!> }
return this@isNotNull != null return this@isNotNull != null
} }
@@ -6,16 +6,16 @@ interface A {
var Any?.isNotNull: Boolean var Any?.isNotNull: Boolean
get() { get() {
<!WRONG_IMPLIES_CONDITION!>contract { contract {
returns(true) implies (this@isNotNull != null) returns(true) implies (this@isNotNull != null)
}<!> }
return this != null return this != null
} }
set(value) { set(value) {
<!WRONG_IMPLIES_CONDITION!>contract { contract {
returns() implies (this@isNotNull != null) returns() implies (this@isNotNull != null)
require(this != null) require(this != null)
}<!> }
} }
fun test_1(a: A?) { fun test_1(a: A?) {
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.fir.contracts.coneEffects
import org.jetbrains.kotlin.fir.contracts.description.* import org.jetbrains.kotlin.fir.contracts.description.*
import org.jetbrains.kotlin.fir.declarations.FirContractDescriptionOwner import org.jetbrains.kotlin.fir.declarations.FirContractDescriptionOwner
import org.jetbrains.kotlin.fir.declarations.FirFunction import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.resolve.dfa.* import org.jetbrains.kotlin.fir.resolve.dfa.*
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.BlockExitNode import org.jetbrains.kotlin.fir.resolve.dfa.cfg.BlockExitNode
@@ -23,6 +24,7 @@ import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.JumpNode import org.jetbrains.kotlin.fir.resolve.dfa.cfg.JumpNode
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertyAccessorSymbol
import org.jetbrains.kotlin.fir.typeContext import org.jetbrains.kotlin.fir.typeContext
import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.fir.types.coneType
@@ -61,7 +63,7 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
effects.forEach { effect -> effects.forEach { effect ->
val wrongCondition = graph.exitNode.previousCfgNodes.any { val wrongCondition = graph.exitNode.previousCfgNodes.any {
isWrongConditionOnNode(it, effect as ConeConditionalEffectDeclaration, function, logicSystem, dataFlowInfo) isWrongConditionOnNode(it, effect as ConeConditionalEffectDeclaration, function, logicSystem, dataFlowInfo, context)
} }
if (wrongCondition) { if (wrongCondition) {
@@ -77,7 +79,8 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
effectDeclaration: ConeConditionalEffectDeclaration, effectDeclaration: ConeConditionalEffectDeclaration,
function: FirFunction<*>, function: FirFunction<*>,
logicSystem: LogicSystem<PersistentFlow>, logicSystem: LogicSystem<PersistentFlow>,
dataFlowInfo: DataFlowInfo dataFlowInfo: DataFlowInfo,
context: CheckerContext
): Boolean { ): Boolean {
val effect = effectDeclaration.effect as ConeReturnsEffectDeclaration val effect = effectDeclaration.effect as ConeReturnsEffectDeclaration
val builtinTypes = function.session.builtinTypes val builtinTypes = function.session.builtinTypes
@@ -92,7 +95,7 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
if (isReturn && resultExpression is FirWhenExpression) { if (isReturn && resultExpression is FirWhenExpression) {
return node.collectBranchExits().any { return node.collectBranchExits().any {
isWrongConditionOnNode(it, effectDeclaration, function, logicSystem, dataFlowInfo) isWrongConditionOnNode(it, effectDeclaration, function, logicSystem, dataFlowInfo, context)
} }
} }
@@ -110,15 +113,16 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
} }
} }
val conditionStatements = val conditionStatements = effectDeclaration.condition.buildTypeStatements(
effectDeclaration.condition.buildTypeStatements(function, logicSystem, dataFlowInfo.variableStorage, flow) ?: return false function, logicSystem, dataFlowInfo.variableStorage, flow, context
) ?: return false
for ((realVar, requiredTypeStatement) in conditionStatements) { for ((realVar, requiredTypeStatement) in conditionStatements) {
val fixedRealVar = typeStatements.keys.find { it.identifier == realVar.identifier } ?: realVar val fixedRealVar = typeStatements.keys.find { it.identifier == realVar.identifier } ?: realVar
val resultTypeStatement = typeStatements[fixedRealVar] val resultTypeStatement = typeStatements[fixedRealVar]
val resultType = mutableListOf<ConeKotlinType>().apply { val resultType = mutableListOf<ConeKotlinType>().apply {
addIfNotNull(function.getParameterType(fixedRealVar.identifier.symbol)) addIfNotNull(function.getParameterType(fixedRealVar.identifier.symbol, context))
if (resultTypeStatement != null) addAll(resultTypeStatement.exactType) if (resultTypeStatement != null) addAll(resultTypeStatement.exactType)
}.let { typeContext.intersectTypesOrNull(it) } }.let { typeContext.intersectTypesOrNull(it) }
@@ -154,11 +158,12 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
function: FirFunction<*>, function: FirFunction<*>,
logicSystem: LogicSystem<*>, logicSystem: LogicSystem<*>,
variableStorage: VariableStorage, variableStorage: VariableStorage,
flow: Flow flow: Flow,
context: CheckerContext
): MutableTypeStatements? = when (this) { ): MutableTypeStatements? = when (this) {
is ConeBinaryLogicExpression -> { is ConeBinaryLogicExpression -> {
val left = left.buildTypeStatements(function, logicSystem, variableStorage, flow) val left = left.buildTypeStatements(function, logicSystem, variableStorage, flow, context)
val right = right.buildTypeStatements(function, logicSystem, variableStorage, flow) val right = right.buildTypeStatements(function, logicSystem, variableStorage, flow, context)
if (left != null && right != null) { if (left != null && right != null) {
if (kind == LogicOperationKind.AND) { if (kind == LogicOperationKind.AND) {
left.apply { mergeTypeStatements(right) } left.apply { mergeTypeStatements(right) }
@@ -166,16 +171,16 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
} else (left ?: right) } else (left ?: right)
} }
is ConeIsInstancePredicate -> { is ConeIsInstancePredicate -> {
val fir = function.getParameterSymbol(arg.parameterIndex).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) } realVar?.to(simpleTypeStatement(realVar, !isNegated, type))?.let { mutableMapOf(it) }
} }
is ConeIsNullPredicate -> { is ConeIsNullPredicate -> {
val fir = function.getParameterSymbol(arg.parameterIndex).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, function.session.builtinTypes.anyType.type))?.let { mutableMapOf(it) } realVar?.to(simpleTypeStatement(realVar, isNegated, function.session.builtinTypes.anyType.type))?.let { mutableMapOf(it) }
} }
is ConeLogicalNot -> arg.buildTypeStatements(function, logicSystem, variableStorage, flow) is ConeLogicalNot -> arg.buildTypeStatements(function, logicSystem, variableStorage, flow, context)
?.mapValuesTo(mutableMapOf()) { (_, value) -> value.invert() } ?.mapValuesTo(mutableMapOf()) { (_, value) -> value.invert() }
else -> null else -> null
@@ -212,11 +217,31 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
return nodes return nodes
} }
private fun FirFunction<*>.getParameterType(symbol: AbstractFirBasedSymbol<*>): ConeKotlinType? { private val CheckerContext.containingProperty: FirProperty?
return (if (this.symbol == symbol) receiverTypeRef else valueParameters.find { it.symbol == symbol }?.returnTypeRef)?.coneType get() = (containingDeclarations.asReversed().firstOrNull { it is FirProperty } as? FirProperty)
private fun FirFunction<*>.getParameterType(symbol: AbstractFirBasedSymbol<*>, context: CheckerContext): ConeKotlinType? {
val typeRef = if (this.symbol == symbol) {
if (symbol is FirPropertyAccessorSymbol) {
context.containingProperty?.receiverTypeRef
} else {
receiverTypeRef
}
} else {
valueParameters.find { it.symbol == symbol }?.returnTypeRef
}
return typeRef?.coneType
} }
private fun FirFunction<*>.getParameterSymbol(index: Int): AbstractFirBasedSymbol<*> { private fun FirFunction<*>.getParameterSymbol(index: Int, context: CheckerContext): AbstractFirBasedSymbol<*> {
return if (index == -1) this.symbol else this.valueParameters[index].symbol return if (index == -1) {
if (symbol !is FirPropertyAccessorSymbol) {
symbol
} else {
context.containingProperty?.symbol ?: symbol
}
} else {
this.valueParameters[index].symbol
}
} }
} }