FIR DFA: bring into compliance with De Morgan's laws

I don't even know if this affects any real code, but it did uncover
some deeper issues, like the fact that `!is` did not add any statements
when `true`.
This commit is contained in:
pyos
2022-11-08 11:47:57 +01:00
committed by teamcity
parent 3436535e7a
commit bc9b358c9f
7 changed files with 86 additions and 144 deletions
@@ -139,21 +139,17 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
statement: OperationStatement, statement: OperationStatement,
builtinTypes: BuiltinTypes builtinTypes: BuiltinTypes
): MutableTypeStatements { ): MutableTypeStatements {
val newTypeStatements: MutableTypeStatements = mutableMapOf() val newTypeStatements = flow.approvedTypeStatements.asMutableStatements()
approveStatementsTo(newTypeStatements, flow, statement, flow.logicStatements.flatMap { it.value }) approveStatementsTo(newTypeStatements, flow, statement, flow.logicStatements.flatMap { it.value })
newTypeStatements.mergeTypeStatements(flow.approvedTypeStatements)
val variable = statement.variable val variable = statement.variable
if (variable.isReal()) { if (!variable.isReal()) return newTypeStatements
if (statement.operation == Operation.NotEqNull) { val extraStatement = when (statement.operation) {
newTypeStatements.addStatement(variable, simpleTypeStatement(variable, true, builtinTypes.anyType.type)) Operation.NotEqNull -> simpleTypeStatement(variable, true, builtinTypes.anyType.type)
} else if (statement.operation == Operation.EqNull) { Operation.EqNull -> simpleTypeStatement(variable, false, builtinTypes.anyType.type)
newTypeStatements.addStatement(variable, simpleTypeStatement(variable, false, builtinTypes.anyType.type)) else -> return newTypeStatements
}
} }
return andForTypeStatements(newTypeStatements, mapOf(variable to extraStatement))
return newTypeStatements
} }
private fun ConeBooleanExpression.buildTypeStatements( private fun ConeBooleanExpression.buildTypeStatements(
@@ -181,9 +177,10 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
val left = left.buildTypeStatements(function, logicSystem, variableStorage, flow, context) val left = left.buildTypeStatements(function, logicSystem, variableStorage, flow, context)
val right = right.buildTypeStatements(function, logicSystem, variableStorage, flow, context) 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) } logicSystem.andForTypeStatements(left, right)
} else logicSystem.orForTypeStatements(left, right) else
logicSystem.orForTypeStatements(left, right)
} else (left ?: right) } else (left ?: right)
} }
is ConeIsInstancePredicate -> buildTypeStatements(arg, !isNegated, type) is ConeIsInstancePredicate -> buildTypeStatements(arg, !isNegated, type)
@@ -632,6 +632,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
// left == right && right not null -> left != null // left == right && right not null -> left != null
// [processEqNull] adds both implications: operator call could be true or false. We definitely need the matched case only. // [processEqNull] adds both implications: operator call could be true or false. We definitely need the matched case only.
// TODO: this is incomprehensible - the comments below say what the equivalent expression is
fun shouldAddImplicationForStatement(operationStatement: OperationStatement): Boolean { fun shouldAddImplicationForStatement(operationStatement: OperationStatement): Boolean {
if (!checkAddImplicationForStatement) return true if (!checkAddImplicationForStatement) return true
// Only if operation statement is == True, i.e., left == right // Only if operation statement is == True, i.e., left == right
@@ -639,17 +640,22 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
return !isEq && operationStatementOp == Operation.EqTrue || isEq && operationStatementOp == Operation.EqFalse return !isEq && operationStatementOp == Operation.EqTrue || isEq && operationStatementOp == Operation.EqFalse
} }
logicSystem.approveOperationStatement(flow, predicate).forEach { effect -> // !checkAddImplicationForStatement || !isEq
if (shouldAddImplicationForStatement(expressionVariable eq true)) { if (shouldAddImplicationForStatement(expressionVariable eq true)) {
logicSystem.approveOperationStatement(flow, predicate).forEach { effect ->
flow.addImplication((expressionVariable eq true) implies effect) flow.addImplication((expressionVariable eq true) implies effect)
} }
if (shouldAddImplicationForStatement(expressionVariable eq false)) {
flow.addImplication((expressionVariable eq false) implies effect.invert())
}
} }
val expressionVariableIsEq = shouldAddImplicationForStatement(expressionVariable eq isEq) // !checkAddImplicationForStatement || isEq
val expressionVariableIsNotEq = shouldAddImplicationForStatement(expressionVariable notEq isEq) if (shouldAddImplicationForStatement(expressionVariable eq false)) {
logicSystem.approveOperationStatement(flow, predicate.invert()).forEach { effect ->
flow.addImplication((expressionVariable eq false) implies effect)
}
}
val expressionVariableIsEq = shouldAddImplicationForStatement(expressionVariable eq isEq) // !checkAddImplicationForStatement
val expressionVariableIsNotEq = shouldAddImplicationForStatement(expressionVariable notEq isEq) // true
if (expressionVariableIsEq) { if (expressionVariableIsEq) {
flow.addImplication((expressionVariable eq isEq) implies (operandVariable eq null)) flow.addImplication((expressionVariable eq isEq) implies (operandVariable eq null))
@@ -665,10 +671,11 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
if (expressionVariableIsNotEq) { if (expressionVariableIsNotEq) {
flow.addImplication((expressionVariable notEq isEq) implies (operandVariable typeEq any)) flow.addImplication((expressionVariable notEq isEq) implies (operandVariable typeEq any))
} }
// true
if (shouldAddImplicationForStatement(expressionVariable eq !isEq)) { if (shouldAddImplicationForStatement(expressionVariable eq !isEq)) {
flow.addImplication((expressionVariable eq !isEq) implies (operandVariable typeNotEq nullableNothing)) flow.addImplication((expressionVariable eq !isEq) implies (operandVariable typeNotEq nullableNothing))
} }
// !checkAddImplicationForStatement
if (shouldAddImplicationForStatement(expressionVariable notEq !isEq)) { if (shouldAddImplicationForStatement(expressionVariable notEq !isEq)) {
flow.addImplication((expressionVariable notEq !isEq) implies (operandVariable typeEq nullableNothing)) flow.addImplication((expressionVariable notEq !isEq) implies (operandVariable typeEq nullableNothing))
} }
@@ -1374,11 +1381,8 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val approvedIfTrue: MutableTypeStatements = mutableMapOf() val approvedIfTrue: MutableTypeStatements = mutableMapOf()
logicSystem.approveStatementsTo(approvedIfTrue, flowFromRight, leftVariable eq bothEvaluated, conditionalFromLeft) logicSystem.approveStatementsTo(approvedIfTrue, flowFromRight, leftVariable eq bothEvaluated, conditionalFromLeft)
logicSystem.approveStatementsTo(approvedIfTrue, flowFromRight, rightVariable eq bothEvaluated, conditionalFromRight) logicSystem.approveStatementsTo(approvedIfTrue, flowFromRight, rightVariable eq bothEvaluated, conditionalFromRight)
approvedFromRight.forEach { (variable, info) -> logicSystem.andForTypeStatements(approvedIfTrue, approvedFromRight).values.forEach {
approvedIfTrue.addStatement(variable, info) flow.addImplication((operatorVariable eq bothEvaluated) implies it)
}
approvedIfTrue.values.forEach { info ->
flow.addImplication((operatorVariable eq bothEvaluated) implies info)
} }
// left && right == False // left && right == False
@@ -1387,9 +1391,8 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val leftIsFalse = logicSystem.approveOperationStatement(flowFromLeft, leftVariable eq onlyLeftEvaluated, conditionalFromLeft) val leftIsFalse = logicSystem.approveOperationStatement(flowFromLeft, leftVariable eq onlyLeftEvaluated, conditionalFromLeft)
val rightIsFalse = val rightIsFalse =
logicSystem.approveOperationStatement(flowFromRight, rightVariable eq onlyLeftEvaluated, conditionalFromRight) logicSystem.approveOperationStatement(flowFromRight, rightVariable eq onlyLeftEvaluated, conditionalFromRight)
approvedIfFalse.mergeTypeStatements(logicSystem.orForTypeStatements(leftIsFalse, rightIsFalse)) logicSystem.andForTypeStatements(approvedIfFalse, logicSystem.orForTypeStatements(leftIsFalse, rightIsFalse)).values.forEach {
approvedIfFalse.values.forEach { info -> flow.addImplication((operatorVariable eq onlyLeftEvaluated) implies it)
flow.addImplication((operatorVariable eq onlyLeftEvaluated) implies info)
} }
} }
@@ -5,10 +5,7 @@
package org.jetbrains.kotlin.fir.resolve.dfa package org.jetbrains.kotlin.fir.resolve.dfa
import org.jetbrains.kotlin.fir.types.ConeInferenceContext import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.canBeNull
import org.jetbrains.kotlin.fir.types.commonSuperTypeOrNull
abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceContext) { abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceContext) {
// ------------------------------- Flow operations ------------------------------- // ------------------------------- Flow operations -------------------------------
@@ -85,11 +82,8 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
return approveOperationStatement(flow, approvedStatement, statements).values return approveOperationStatement(flow, approvedStatement, statements).values
} }
fun orForTypeStatements( fun orForTypeStatements(left: TypeStatements, right: TypeStatements): MutableTypeStatements {
left: TypeStatements, if (left.isEmpty() || right.isEmpty()) return mutableMapOf()
right: TypeStatements,
): MutableTypeStatements {
if (left.isNullOrEmpty() || right.isNullOrEmpty()) return mutableMapOf()
val map = mutableMapOf<RealVariable, MutableTypeStatement>() val map = mutableMapOf<RealVariable, MutableTypeStatement>()
for (variable in left.keys.intersect(right.keys)) { for (variable in left.keys.intersect(right.keys)) {
val leftStatement = left.getValue(variable) val leftStatement = left.getValue(variable)
@@ -99,63 +93,55 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
return map return map
} }
// ------------------------------- Util functions ------------------------------- fun andForTypeStatements(left: TypeStatements, right: TypeStatements): MutableTypeStatements {
if (left.isEmpty() && right.isEmpty()) return mutableMapOf()
// TODO val map = left.asMutableStatements()
protected fun <E> Collection<Collection<E>>.intersectSets(): Set<E> { for ((variable, rightStatement) in right) {
if (isEmpty()) return emptySet() map[variable] = and(listOfNotNull(map[variable], rightStatement))
val iterator = iterator()
val result = LinkedHashSet<E>(iterator.next())
while (iterator.hasNext()) {
result.retainAll(iterator.next())
} }
return result return map
} }
private inline fun manipulateTypeStatements( // ------------------------------- Util functions -------------------------------
statements: Collection<TypeStatement>,
op: (Collection<Set<ConeKotlinType>>) -> MutableSet<ConeKotlinType> private fun foldStatements(statements: Collection<TypeStatement>, all: Boolean): MutableTypeStatement {
): MutableTypeStatement {
require(statements.isNotEmpty()) require(statements.isNotEmpty())
statements.singleOrNull()?.let { return it.asMutableStatement() } statements.singleOrNull()?.let { return it.asMutableStatement() }
val variable = statements.first().variable val variable = statements.first().variable
assert(statements.all { it.variable == variable }) assert(statements.all { it.variable == variable })
val exactType = op.invoke(statements.map { it.exactType }) // TypeStatement(variable, exactType, exactNotType) =
val exactNotType = op.invoke(statements.map { it.exactNotType }) // variable is intersect(exactType) && variable !is intersect(exactNotType)
return MutableTypeStatement(variable, exactType, exactNotType) // So `and` of two type statements computes `and` of exactType and `or` of `exactNotType`,
// while `or` is the opposite.
return if (all) {
val exactType = statements.flatMapTo(mutableSetOf()) { it.exactType }
// variable !is a && variable !is b =/=> variable !is commonSuperType(a, b)
// So in this case we can only take the union if either type is a subtype of the other.
val exactNotType = unifyTypes(statements.map { it.exactNotType }, onlyInputTypes = true)
MutableTypeStatement(variable, exactType, exactNotType?.let { mutableSetOf(it) } ?: mutableSetOf())
} else {
val exactType = unifyTypes(statements.map { it.exactType }, onlyInputTypes = false)
val exactNotType = statements.flatMapTo(mutableSetOf()) { it.exactNotType }
MutableTypeStatement(variable, exactType?.let { mutableSetOf(it) } ?: mutableSetOf(), exactNotType)
}
} }
protected fun or(statements: Collection<TypeStatement>): MutableTypeStatement = private fun unifyTypes(types: Collection<Set<ConeKotlinType>>, onlyInputTypes: Boolean): ConeKotlinType? {
manipulateTypeStatements(statements, ::orForTypes) if (types.any { it.isEmpty() }) return null
val intersected = types.map { ConeTypeIntersector.intersectTypes(context, it.toList()) }
private fun orForTypes(types: Collection<Set<ConeKotlinType>>): MutableSet<ConeKotlinType> { val unified = context.commonSuperTypeOrNull(intersected) ?: return null
if (types.any { it.isEmpty() }) return mutableSetOf() return when {
val intersectedTypes = types.map { unified.isAcceptableForSmartcast() -> unified
if (it.size > 1) { unified.canBeNull -> null
context.intersectTypes(it.toList()) else -> context.anyType()
} else { }.takeIf { !onlyInputTypes || it in intersected }
assert(it.size == 1) { "We've already checked each set of types is not empty." }
it.single()
}
}
val result = mutableSetOf<ConeKotlinType>()
context.commonSuperTypeOrNull(intersectedTypes)?.let {
if (it.isAcceptableForSmartcast()) {
result.add(it)
} else if (!it.canBeNull) {
result.add(context.anyType())
}
Unit
}
return result
} }
protected fun and(statements: Collection<TypeStatement>): MutableTypeStatement = protected fun and(statements: Collection<TypeStatement>): MutableTypeStatement =
manipulateTypeStatements(statements, ::andForTypes) foldStatements(statements, all = true)
private fun andForTypes(types: Collection<Set<ConeKotlinType>>): MutableSet<ConeKotlinType> { protected fun or(statements: Collection<TypeStatement>): MutableTypeStatement =
return types.flatMapTo(mutableSetOf()) { it } foldStatements(statements, all = false)
}
} }
fun <FLOW : Flow> LogicSystem<FLOW>.approveOperationStatement( fun <FLOW : Flow> LogicSystem<FLOW>.approveOperationStatement(
@@ -18,20 +18,8 @@ data class PersistentTypeStatement(
override val exactType: PersistentSet<ConeKotlinType>, override val exactType: PersistentSet<ConeKotlinType>,
override val exactNotType: PersistentSet<ConeKotlinType> override val exactNotType: PersistentSet<ConeKotlinType>
) : TypeStatement() { ) : TypeStatement() {
override operator fun plus(other: TypeStatement): PersistentTypeStatement { override fun invert(): PersistentTypeStatement =
return PersistentTypeStatement( PersistentTypeStatement(variable, exactNotType, exactType)
variable,
exactType + other.exactType,
exactNotType + other.exactNotType
)
}
override val isEmpty: Boolean
get() = exactType.isEmpty() && exactNotType.isEmpty()
override fun invert(): PersistentTypeStatement {
return PersistentTypeStatement(variable, exactNotType, exactType)
}
} }
typealias PersistentApprovedTypeStatements = PersistentMap<RealVariable, PersistentTypeStatement> typealias PersistentApprovedTypeStatements = PersistentMap<RealVariable, PersistentTypeStatement>
@@ -242,6 +230,9 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
} }
} }
private fun PersistentApprovedTypeStatements.addTypeStatement(info: TypeStatement): PersistentApprovedTypeStatements =
put(info.variable, { info.toPersistent() }, { and(listOf(it, info)).toPersistent() })
override fun addImplication(flow: PersistentFlow, implication: Implication) { override fun addImplication(flow: PersistentFlow, implication: Implication) {
if ((implication.effect as? TypeStatement)?.isEmpty == true) return if ((implication.effect as? TypeStatement)?.isEmpty == true) return
if (implication.condition == implication.effect) return if (implication.condition == implication.effect) return
@@ -288,14 +279,10 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
val updatedReceivers = mutableSetOf<RealVariable>() val updatedReceivers = mutableSetOf<RealVariable>()
approvedFacts.asMap().forEach { (variable, infos) -> approvedFacts.asMap().forEach { (variable, infos) ->
var resultInfo = PersistentTypeStatement(variable, persistentSetOf(), persistentSetOf())
for (info in infos) {
resultInfo += info
}
if (variable.isThisReference) { if (variable.isThisReference) {
updatedReceivers += variable updatedReceivers += variable
} }
addTypeStatement(resultFlow, resultInfo) addTypeStatement(resultFlow, and(infos))
} }
updatedReceivers.forEach { updatedReceivers.forEach {
@@ -361,13 +348,7 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
val approveOperationStatements = val approveOperationStatements =
approveOperationStatementsInternal(flow, approvedStatement, statements, shouldRemoveSynthetics = false) approveOperationStatementsInternal(flow, approvedStatement, statements, shouldRemoveSynthetics = false)
approveOperationStatements.asMap().forEach { (variable, infos) -> approveOperationStatements.asMap().forEach { (variable, infos) ->
for (info in infos) { destination.put(variable, { and(infos) }, { and(listOf(it) + infos) })
val mutableInfo = info.asMutableStatement()
destination.put(variable, mutableInfo) {
it += mutableInfo
it
}
}
} }
} }
@@ -416,9 +397,6 @@ private fun lowestCommonFlow(left: PersistentFlow, right: PersistentFlow): Persi
return left return left
} }
private fun PersistentApprovedTypeStatements.addTypeStatement(info: TypeStatement): PersistentApprovedTypeStatements =
put(info.variable, { info.toPersistent() }, { it + info })
private fun TypeStatement.toPersistent(): PersistentTypeStatement = when (this) { private fun TypeStatement.toPersistent(): PersistentTypeStatement = when (this) {
is PersistentTypeStatement -> this is PersistentTypeStatement -> this
else -> PersistentTypeStatement(variable, exactType.toPersistentSet(), exactNotType.toPersistentSet()) else -> PersistentTypeStatement(variable, exactType.toPersistentSet(), exactNotType.toPersistentSet())
@@ -12,22 +12,11 @@ import kotlin.contracts.contract
// --------------------------------------- Facts --------------------------------------- // --------------------------------------- Facts ---------------------------------------
operator fun TypeStatement.plus(other: TypeStatement?): TypeStatement = other?.let { this + other } ?: this
class MutableTypeStatement( class MutableTypeStatement(
override val variable: RealVariable, override val variable: RealVariable,
override val exactType: MutableSet<ConeKotlinType> = linkedSetOf(), override val exactType: MutableSet<ConeKotlinType> = linkedSetOf(),
override val exactNotType: MutableSet<ConeKotlinType> = linkedSetOf() override val exactNotType: MutableSet<ConeKotlinType> = linkedSetOf()
) : TypeStatement() { ) : TypeStatement() {
override fun plus(other: TypeStatement): MutableTypeStatement = MutableTypeStatement(
variable,
LinkedHashSet(exactType).apply { addAll(other.exactType) },
LinkedHashSet(exactNotType).apply { addAll(other.exactNotType) }
)
override val isEmpty: Boolean
get() = exactType.isEmpty() && exactType.isEmpty()
override fun invert(): MutableTypeStatement { override fun invert(): MutableTypeStatement {
return MutableTypeStatement( return MutableTypeStatement(
variable, variable,
@@ -36,11 +25,6 @@ class MutableTypeStatement(
) )
} }
operator fun plusAssign(info: TypeStatement) {
exactType += info.exactType
exactNotType += info.exactNotType
}
fun copy(): MutableTypeStatement = MutableTypeStatement(variable, LinkedHashSet(exactType), LinkedHashSet(exactNotType)) fun copy(): MutableTypeStatement = MutableTypeStatement(variable, LinkedHashSet(exactType), LinkedHashSet(exactNotType))
} }
@@ -51,17 +35,8 @@ 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>
typealias MutableOperationStatements = MutableMap<RealVariable, MutableTypeStatement> fun TypeStatements.asMutableStatements(): MutableTypeStatements =
mapValuesTo(mutableMapOf()) { it.value.asMutableStatement() }
fun MutableTypeStatements.addStatement(variable: RealVariable, statement: TypeStatement) {
put(variable, statement.asMutableStatement()) { it.apply { this += statement } }
}
fun MutableTypeStatements.mergeTypeStatements(other: TypeStatements) {
other.forEach { (variable, info) ->
addStatement(variable, info)
}
}
// --------------------------------------- DSL --------------------------------------- // --------------------------------------- DSL ---------------------------------------
@@ -34,9 +34,11 @@ abstract class TypeStatement : Statement<TypeStatement>() {
abstract val exactType: Set<ConeKotlinType> abstract val exactType: Set<ConeKotlinType>
abstract val exactNotType: Set<ConeKotlinType> abstract val exactNotType: Set<ConeKotlinType>
abstract operator fun plus(other: TypeStatement): TypeStatement val isEmpty: Boolean
abstract val isEmpty: Boolean get() = exactType.isEmpty() && exactNotType.isEmpty()
val isNotEmpty: Boolean get() = !isEmpty
val isNotEmpty: Boolean
get() = !isEmpty
override fun toString(): String { override fun toString(): String {
return "$variable: $exactType, $exactNotType" return "$variable: $exactType, $exactNotType"
@@ -26,13 +26,14 @@ import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
@OptIn(ExperimentalContracts::class) @OptIn(ExperimentalContracts::class)
internal inline fun <K, V> MutableMap<K, V>.put(key: K, value: V, remappingFunction: (existing: V) -> V) { internal inline fun <K, V> MutableMap<K, V>.put(key: K, valueProducer: () -> V, remappingFunction: (existing: V) -> V) {
contract { contract {
callsInPlace(remappingFunction, InvocationKind.AT_MOST_ONCE) callsInPlace(remappingFunction, InvocationKind.AT_MOST_ONCE)
callsInPlace(valueProducer, InvocationKind.AT_MOST_ONCE)
} }
val existing = this[key] val existing = this[key]
if (existing == null) { if (existing == null) {
put(key, value) put(key, valueProducer())
} else { } else {
put(key, remappingFunction(existing)) put(key, remappingFunction(existing))
} }