FIR DFA: slightly simplify {and,or}ForTypeStatements

And use them less in boolean expression analysis. This uncovers a fun
new case that doesn't work:

    interface I { val x: String }
    class C(override val x: String) : I
    class D(override val x: String) : I

    fun foo(x: Any?) {
      if (x is D || (x as C).x != "") {
        println(x.x.length) // actually OK (x is D || x is C => x is I)
      }
    }
This commit is contained in:
pyos
2022-11-08 18:45:47 +01:00
committed by teamcity
parent ac0137f45f
commit 39f6b50836
5 changed files with 50 additions and 81 deletions
@@ -138,10 +138,8 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
flow: PersistentFlow, flow: PersistentFlow,
statement: OperationStatement, statement: OperationStatement,
builtinTypes: BuiltinTypes builtinTypes: BuiltinTypes
): MutableTypeStatements { ): TypeStatements {
val newTypeStatements = flow.approvedTypeStatements.asMutableStatements() val newTypeStatements = andForTypeStatements(flow.approvedTypeStatements, approveOperationStatement(flow, statement, null))
approveStatementsTo(newTypeStatements, flow, statement, flow.logicStatements.flatMap { it.value })
val variable = statement.variable val variable = statement.variable
if (!variable.isReal()) return newTypeStatements if (!variable.isReal()) return newTypeStatements
val extraStatement = when (statement.operation) { val extraStatement = when (statement.operation) {
@@ -158,7 +156,7 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
variableStorage: VariableStorageImpl, variableStorage: VariableStorageImpl,
flow: Flow, flow: Flow,
context: CheckerContext context: CheckerContext
): MutableTypeStatements? { ): TypeStatements? {
fun buildTypeStatements(arg: ConeValueParameterReference, exactType: Boolean, type: ConeKotlinType): MutableTypeStatements? { fun buildTypeStatements(arg: ConeValueParameterReference, exactType: Boolean, type: ConeKotlinType): MutableTypeStatements? {
val parameterSymbol = function.getParameterSymbol(arg.parameterIndex, context) val parameterSymbol = function.getParameterSymbol(arg.parameterIndex, context)
@@ -1232,51 +1232,41 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
* TODO: Here we should handle case when one of arguments is dead (e.g. in cases `false && expr` or `true || expr`) * TODO: Here we should handle case when one of arguments is dead (e.g. in cases `false && expr` or `true || expr`)
* But since conditions with const are rare it can be delayed * But since conditions with const are rare it can be delayed
*/ */
val leftVariable = variableStorage.getOrCreateVariable(flow, binaryLogicExpression.leftOperand) val leftVariable = variableStorage.getOrCreateVariable(flow, binaryLogicExpression.leftOperand)
val rightVariable = variableStorage.getOrCreateVariable(flow, binaryLogicExpression.rightOperand) val rightVariable = variableStorage.getOrCreateVariable(flow, binaryLogicExpression.rightOperand)
val operatorVariable = variableStorage.getOrCreateVariable(flow, binaryLogicExpression) val operatorVariable = variableStorage.getOrCreateVariable(flow, binaryLogicExpression)
if (!node.leftOperandNode.isDead && node.rightOperandNode.isDead) { if (!node.leftOperandNode.isDead && node.rightOperandNode.isDead) {
/* // If the right operand does not terminate, then we know that the value of the entire expression
* If there was a jump from right argument then we know that we well exit from // has to be `onlyLeftEvaluated`, and it has to be produced by the left operand.
* boolean operator only if right operand was not executed
*
* a && return => a == false
* a || return => a == true
*/
logicSystem.approveStatementsInsideFlow( logicSystem.approveStatementsInsideFlow(
flow, flow,
leftVariable eq !isAnd, leftVariable eq onlyLeftEvaluated,
shouldForkFlow = false, shouldForkFlow = false,
shouldRemoveSynthetics = true shouldRemoveSynthetics = true
) )
} else { } else {
val (conditionalFromLeft, conditionalFromRight, approvedFromRight) = logicSystem.collectInfoForBooleanOperator( val (conditionalFromLeft, conditionalFromRight, approvedFromRight) =
flowFromLeft, logicSystem.collectInfoForBooleanOperator(flowFromLeft, leftVariable, flowFromRight, rightVariable)
leftVariable,
flowFromRight,
rightVariable
)
// left && right == True // If `left && right` is true, then both are true (and evaluated).
// left || right == False // If `left || right` is false, then both are false.
val approvedIfTrue: MutableTypeStatements = mutableMapOf() arrayOf(
logicSystem.approveStatementsTo(approvedIfTrue, flowFromRight, leftVariable eq bothEvaluated, conditionalFromLeft) approvedFromRight,
logicSystem.approveStatementsTo(approvedIfTrue, flowFromRight, rightVariable eq bothEvaluated, conditionalFromRight) logicSystem.approveOperationStatement(flowFromRight, leftVariable eq bothEvaluated, conditionalFromLeft),
logicSystem.andForTypeStatements(approvedIfTrue, approvedFromRight).values.forEach { logicSystem.approveOperationStatement(flowFromRight, rightVariable eq bothEvaluated, conditionalFromRight),
flow.addImplication((operatorVariable eq bothEvaluated) implies it) ).forEach { statements ->
statements.values.forEach { flow.addImplication((operatorVariable eq bothEvaluated) implies it) }
} }
// left && right == False // If `left && right` is false, then either `left` is false, or both were evaluated and `right` is false.
// left || right == True // If `left || right` is true, then either `left` is true, or both were evaluated and `right` is true.
val approvedIfFalse: MutableTypeStatements = mutableMapOf() logicSystem.orForTypeStatements(
val leftIsFalse = logicSystem.approveOperationStatement(flowFromLeft, leftVariable eq onlyLeftEvaluated, conditionalFromLeft) logicSystem.approveOperationStatement(flowFromLeft, leftVariable eq onlyLeftEvaluated, conditionalFromLeft),
val rightIsFalse = // TODO: and(approvedFromRight, ...)? FE1.0 doesn't seem to handle that correctly either.
logicSystem.approveOperationStatement(flowFromRight, rightVariable eq onlyLeftEvaluated, conditionalFromRight) // if (x is A || whatever(x as B)) { /* x is (A | B) */ }
logicSystem.andForTypeStatements(approvedIfFalse, logicSystem.orForTypeStatements(leftIsFalse, rightIsFalse)).values.forEach { logicSystem.approveOperationStatement(flowFromRight, rightVariable eq onlyLeftEvaluated, conditionalFromRight),
flow.addImplication((operatorVariable eq onlyLeftEvaluated) implies it) ).values.forEach { flow.addImplication((operatorVariable eq onlyLeftEvaluated) implies it) }
}
} }
logicSystem.updateAllReceivers(flow) logicSystem.updateAllReceivers(flow)
@@ -65,38 +65,37 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
rightVariable: DataFlowVariable, rightVariable: DataFlowVariable,
): InfoForBooleanOperator ): InfoForBooleanOperator
abstract fun approveStatementsTo( abstract fun approveOperationStatement(
destination: MutableTypeStatements,
flow: FLOW, flow: FLOW,
approvedStatement: OperationStatement, approvedStatement: OperationStatement,
statements: Collection<Implication>, statementsForVariable: Collection<Implication>?
) ): TypeStatements
fun orForTypeStatements(left: TypeStatements, right: TypeStatements): MutableTypeStatements { fun orForTypeStatements(left: TypeStatements, right: TypeStatements): TypeStatements = when {
if (left.isEmpty() || right.isEmpty()) return mutableMapOf() left.isEmpty() -> left
val map = mutableMapOf<RealVariable, MutableTypeStatement>() right.isEmpty() -> right
for (variable in left.keys.intersect(right.keys)) { else -> buildMap {
val leftStatement = left.getValue(variable) for ((variable, leftStatement) in left) {
val rightStatement = right.getValue(variable) put(variable, or(listOf(leftStatement, right[variable] ?: continue)))
map[variable] = or(listOf(leftStatement, rightStatement)) }
} }
return map
} }
fun andForTypeStatements(left: TypeStatements, right: TypeStatements): MutableTypeStatements { fun andForTypeStatements(left: TypeStatements, right: TypeStatements): TypeStatements = when {
if (left.isEmpty() && right.isEmpty()) return mutableMapOf() left.isEmpty() -> right
val map = left.asMutableStatements() right.isEmpty() -> left
for ((variable, rightStatement) in right) { else -> left.toMutableMap().apply {
map[variable] = and(listOfNotNull(map[variable], rightStatement)) for ((variable, rightStatement) in right) {
put(variable, { rightStatement }, { and(listOf(it, rightStatement)) })
}
} }
return map
} }
// ------------------------------- Util functions ------------------------------- // ------------------------------- Util functions -------------------------------
private fun foldStatements(statements: Collection<TypeStatement>, all: Boolean): MutableTypeStatement { private fun foldStatements(statements: Collection<TypeStatement>, all: Boolean): TypeStatement {
require(statements.isNotEmpty()) require(statements.isNotEmpty())
statements.singleOrNull()?.let { return it.asMutableStatement() } statements.singleOrNull()?.let { return it }
val variable = statements.first().variable val variable = statements.first().variable
assert(statements.all { it.variable == variable }) assert(statements.all { it.variable == variable })
// TypeStatement(variable, exactType, exactNotType) = // TypeStatement(variable, exactType, exactNotType) =
@@ -127,23 +126,13 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
}.takeIf { !onlyInputTypes || it in intersected } }.takeIf { !onlyInputTypes || it in intersected }
} }
protected fun and(statements: Collection<TypeStatement>): MutableTypeStatement = protected fun and(statements: Collection<TypeStatement>): TypeStatement =
foldStatements(statements, all = true) foldStatements(statements, all = true)
protected fun or(statements: Collection<TypeStatement>): MutableTypeStatement = protected fun or(statements: Collection<TypeStatement>): TypeStatement =
foldStatements(statements, all = false) foldStatements(statements, all = false)
} }
fun <FLOW : Flow> LogicSystem<FLOW>.approveOperationStatement(
flow: FLOW,
approvedStatement: OperationStatement,
statements: Collection<Implication>,
): MutableTypeStatements {
return mutableMapOf<RealVariable, MutableTypeStatement>().apply {
approveStatementsTo(this, flow, approvedStatement, statements)
}
}
/* /*
* used for: * used for:
* 1. val b = x is String * 1. val b = x is String
@@ -339,18 +339,13 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
} }
} }
override fun approveStatementsTo( override fun approveOperationStatement(
destination: MutableTypeStatements,
flow: PersistentFlow, flow: PersistentFlow,
approvedStatement: OperationStatement, approvedStatement: OperationStatement,
statements: Collection<Implication> statementsForVariable: Collection<Implication>?
) { ): TypeStatements =
val approveOperationStatements = approveOperationStatementsInternal(flow, approvedStatement, statementsForVariable, shouldRemoveSynthetics = false)
approveOperationStatementsInternal(flow, approvedStatement, statements, shouldRemoveSynthetics = false) .asMap().mapValues { and(it.value) }
approveOperationStatements.asMap().forEach { (variable, infos) ->
destination.put(variable, { and(infos) }, { and(listOf(it) + infos) })
}
}
override fun collectInfoForBooleanOperator( override fun collectInfoForBooleanOperator(
leftFlow: PersistentFlow, leftFlow: PersistentFlow,
@@ -35,9 +35,6 @@ fun Implication.invertCondition(): Implication = Implication(condition.invert(),
typealias TypeStatements = Map<RealVariable, TypeStatement> typealias TypeStatements = Map<RealVariable, TypeStatement>
typealias MutableTypeStatements = MutableMap<RealVariable, MutableTypeStatement> typealias MutableTypeStatements = MutableMap<RealVariable, MutableTypeStatement>
fun TypeStatements.asMutableStatements(): MutableTypeStatements =
mapValuesTo(mutableMapOf()) { it.value.asMutableStatement() }
// --------------------------------------- DSL --------------------------------------- // --------------------------------------- DSL ---------------------------------------
infix fun DataFlowVariable.eq(constant: Boolean?): OperationStatement { infix fun DataFlowVariable.eq(constant: Boolean?): OperationStatement {