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:
+11
-14
@@ -139,21 +139,17 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
|
||||
statement: OperationStatement,
|
||||
builtinTypes: BuiltinTypes
|
||||
): MutableTypeStatements {
|
||||
val newTypeStatements: MutableTypeStatements = mutableMapOf()
|
||||
|
||||
val newTypeStatements = flow.approvedTypeStatements.asMutableStatements()
|
||||
approveStatementsTo(newTypeStatements, flow, statement, flow.logicStatements.flatMap { it.value })
|
||||
newTypeStatements.mergeTypeStatements(flow.approvedTypeStatements)
|
||||
|
||||
val variable = statement.variable
|
||||
if (variable.isReal()) {
|
||||
if (statement.operation == Operation.NotEqNull) {
|
||||
newTypeStatements.addStatement(variable, simpleTypeStatement(variable, true, builtinTypes.anyType.type))
|
||||
} else if (statement.operation == Operation.EqNull) {
|
||||
newTypeStatements.addStatement(variable, simpleTypeStatement(variable, false, builtinTypes.anyType.type))
|
||||
}
|
||||
if (!variable.isReal()) return newTypeStatements
|
||||
val extraStatement = when (statement.operation) {
|
||||
Operation.NotEqNull -> simpleTypeStatement(variable, true, builtinTypes.anyType.type)
|
||||
Operation.EqNull -> simpleTypeStatement(variable, false, builtinTypes.anyType.type)
|
||||
else -> return newTypeStatements
|
||||
}
|
||||
|
||||
return newTypeStatements
|
||||
return andForTypeStatements(newTypeStatements, mapOf(variable to extraStatement))
|
||||
}
|
||||
|
||||
private fun ConeBooleanExpression.buildTypeStatements(
|
||||
@@ -181,9 +177,10 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
|
||||
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)
|
||||
if (kind == LogicOperationKind.AND)
|
||||
logicSystem.andForTypeStatements(left, right)
|
||||
else
|
||||
logicSystem.orForTypeStatements(left, right)
|
||||
} else (left ?: right)
|
||||
}
|
||||
is ConeIsInstancePredicate -> buildTypeStatements(arg, !isNegated, type)
|
||||
|
||||
+19
-16
@@ -632,6 +632,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
|
||||
// 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.
|
||||
// TODO: this is incomprehensible - the comments below say what the equivalent expression is
|
||||
fun shouldAddImplicationForStatement(operationStatement: OperationStatement): Boolean {
|
||||
if (!checkAddImplicationForStatement) return true
|
||||
// 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
|
||||
}
|
||||
|
||||
logicSystem.approveOperationStatement(flow, predicate).forEach { effect ->
|
||||
if (shouldAddImplicationForStatement(expressionVariable eq true)) {
|
||||
// !checkAddImplicationForStatement || !isEq
|
||||
if (shouldAddImplicationForStatement(expressionVariable eq true)) {
|
||||
logicSystem.approveOperationStatement(flow, predicate).forEach { 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)
|
||||
val expressionVariableIsNotEq = shouldAddImplicationForStatement(expressionVariable notEq isEq)
|
||||
// !checkAddImplicationForStatement || 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) {
|
||||
flow.addImplication((expressionVariable eq isEq) implies (operandVariable eq null))
|
||||
@@ -665,10 +671,11 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
if (expressionVariableIsNotEq) {
|
||||
flow.addImplication((expressionVariable notEq isEq) implies (operandVariable typeEq any))
|
||||
}
|
||||
|
||||
// true
|
||||
if (shouldAddImplicationForStatement(expressionVariable eq !isEq)) {
|
||||
flow.addImplication((expressionVariable eq !isEq) implies (operandVariable typeNotEq nullableNothing))
|
||||
}
|
||||
// !checkAddImplicationForStatement
|
||||
if (shouldAddImplicationForStatement(expressionVariable notEq !isEq)) {
|
||||
flow.addImplication((expressionVariable notEq !isEq) implies (operandVariable typeEq nullableNothing))
|
||||
}
|
||||
@@ -1374,11 +1381,8 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
val approvedIfTrue: MutableTypeStatements = mutableMapOf()
|
||||
logicSystem.approveStatementsTo(approvedIfTrue, flowFromRight, leftVariable eq bothEvaluated, conditionalFromLeft)
|
||||
logicSystem.approveStatementsTo(approvedIfTrue, flowFromRight, rightVariable eq bothEvaluated, conditionalFromRight)
|
||||
approvedFromRight.forEach { (variable, info) ->
|
||||
approvedIfTrue.addStatement(variable, info)
|
||||
}
|
||||
approvedIfTrue.values.forEach { info ->
|
||||
flow.addImplication((operatorVariable eq bothEvaluated) implies info)
|
||||
logicSystem.andForTypeStatements(approvedIfTrue, approvedFromRight).values.forEach {
|
||||
flow.addImplication((operatorVariable eq bothEvaluated) implies it)
|
||||
}
|
||||
|
||||
// left && right == False
|
||||
@@ -1387,9 +1391,8 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
val leftIsFalse = logicSystem.approveOperationStatement(flowFromLeft, leftVariable eq onlyLeftEvaluated, conditionalFromLeft)
|
||||
val rightIsFalse =
|
||||
logicSystem.approveOperationStatement(flowFromRight, rightVariable eq onlyLeftEvaluated, conditionalFromRight)
|
||||
approvedIfFalse.mergeTypeStatements(logicSystem.orForTypeStatements(leftIsFalse, rightIsFalse))
|
||||
approvedIfFalse.values.forEach { info ->
|
||||
flow.addImplication((operatorVariable eq onlyLeftEvaluated) implies info)
|
||||
logicSystem.andForTypeStatements(approvedIfFalse, logicSystem.orForTypeStatements(leftIsFalse, rightIsFalse)).values.forEach {
|
||||
flow.addImplication((operatorVariable eq onlyLeftEvaluated) implies it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.resolve.dfa
|
||||
|
||||
import org.jetbrains.kotlin.fir.types.ConeInferenceContext
|
||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||
import org.jetbrains.kotlin.fir.types.canBeNull
|
||||
import org.jetbrains.kotlin.fir.types.commonSuperTypeOrNull
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
|
||||
abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceContext) {
|
||||
// ------------------------------- Flow operations -------------------------------
|
||||
@@ -85,11 +82,8 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
|
||||
return approveOperationStatement(flow, approvedStatement, statements).values
|
||||
}
|
||||
|
||||
fun orForTypeStatements(
|
||||
left: TypeStatements,
|
||||
right: TypeStatements,
|
||||
): MutableTypeStatements {
|
||||
if (left.isNullOrEmpty() || right.isNullOrEmpty()) return mutableMapOf()
|
||||
fun orForTypeStatements(left: TypeStatements, right: TypeStatements): MutableTypeStatements {
|
||||
if (left.isEmpty() || right.isEmpty()) return mutableMapOf()
|
||||
val map = mutableMapOf<RealVariable, MutableTypeStatement>()
|
||||
for (variable in left.keys.intersect(right.keys)) {
|
||||
val leftStatement = left.getValue(variable)
|
||||
@@ -99,63 +93,55 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
|
||||
return map
|
||||
}
|
||||
|
||||
// ------------------------------- Util functions -------------------------------
|
||||
|
||||
// TODO
|
||||
protected fun <E> Collection<Collection<E>>.intersectSets(): Set<E> {
|
||||
if (isEmpty()) return emptySet()
|
||||
val iterator = iterator()
|
||||
val result = LinkedHashSet<E>(iterator.next())
|
||||
while (iterator.hasNext()) {
|
||||
result.retainAll(iterator.next())
|
||||
fun andForTypeStatements(left: TypeStatements, right: TypeStatements): MutableTypeStatements {
|
||||
if (left.isEmpty() && right.isEmpty()) return mutableMapOf()
|
||||
val map = left.asMutableStatements()
|
||||
for ((variable, rightStatement) in right) {
|
||||
map[variable] = and(listOfNotNull(map[variable], rightStatement))
|
||||
}
|
||||
return result
|
||||
return map
|
||||
}
|
||||
|
||||
private inline fun manipulateTypeStatements(
|
||||
statements: Collection<TypeStatement>,
|
||||
op: (Collection<Set<ConeKotlinType>>) -> MutableSet<ConeKotlinType>
|
||||
): MutableTypeStatement {
|
||||
// ------------------------------- Util functions -------------------------------
|
||||
|
||||
private fun foldStatements(statements: Collection<TypeStatement>, all: Boolean): MutableTypeStatement {
|
||||
require(statements.isNotEmpty())
|
||||
statements.singleOrNull()?.let { return it.asMutableStatement() }
|
||||
val variable = statements.first().variable
|
||||
assert(statements.all { it.variable == variable })
|
||||
val exactType = op.invoke(statements.map { it.exactType })
|
||||
val exactNotType = op.invoke(statements.map { it.exactNotType })
|
||||
return MutableTypeStatement(variable, exactType, exactNotType)
|
||||
// TypeStatement(variable, exactType, exactNotType) =
|
||||
// variable is intersect(exactType) && variable !is intersect(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 =
|
||||
manipulateTypeStatements(statements, ::orForTypes)
|
||||
|
||||
private fun orForTypes(types: Collection<Set<ConeKotlinType>>): MutableSet<ConeKotlinType> {
|
||||
if (types.any { it.isEmpty() }) return mutableSetOf()
|
||||
val intersectedTypes = types.map {
|
||||
if (it.size > 1) {
|
||||
context.intersectTypes(it.toList())
|
||||
} else {
|
||||
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
|
||||
private fun unifyTypes(types: Collection<Set<ConeKotlinType>>, onlyInputTypes: Boolean): ConeKotlinType? {
|
||||
if (types.any { it.isEmpty() }) return null
|
||||
val intersected = types.map { ConeTypeIntersector.intersectTypes(context, it.toList()) }
|
||||
val unified = context.commonSuperTypeOrNull(intersected) ?: return null
|
||||
return when {
|
||||
unified.isAcceptableForSmartcast() -> unified
|
||||
unified.canBeNull -> null
|
||||
else -> context.anyType()
|
||||
}.takeIf { !onlyInputTypes || it in intersected }
|
||||
}
|
||||
|
||||
protected fun and(statements: Collection<TypeStatement>): MutableTypeStatement =
|
||||
manipulateTypeStatements(statements, ::andForTypes)
|
||||
foldStatements(statements, all = true)
|
||||
|
||||
private fun andForTypes(types: Collection<Set<ConeKotlinType>>): MutableSet<ConeKotlinType> {
|
||||
return types.flatMapTo(mutableSetOf()) { it }
|
||||
}
|
||||
protected fun or(statements: Collection<TypeStatement>): MutableTypeStatement =
|
||||
foldStatements(statements, all = false)
|
||||
}
|
||||
|
||||
fun <FLOW : Flow> LogicSystem<FLOW>.approveOperationStatement(
|
||||
|
||||
+7
-29
@@ -18,20 +18,8 @@ data class PersistentTypeStatement(
|
||||
override val exactType: PersistentSet<ConeKotlinType>,
|
||||
override val exactNotType: PersistentSet<ConeKotlinType>
|
||||
) : TypeStatement() {
|
||||
override operator fun plus(other: TypeStatement): PersistentTypeStatement {
|
||||
return PersistentTypeStatement(
|
||||
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)
|
||||
}
|
||||
override fun invert(): PersistentTypeStatement =
|
||||
PersistentTypeStatement(variable, exactNotType, exactType)
|
||||
}
|
||||
|
||||
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) {
|
||||
if ((implication.effect as? TypeStatement)?.isEmpty == true) return
|
||||
if (implication.condition == implication.effect) return
|
||||
@@ -288,14 +279,10 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
|
||||
|
||||
val updatedReceivers = mutableSetOf<RealVariable>()
|
||||
approvedFacts.asMap().forEach { (variable, infos) ->
|
||||
var resultInfo = PersistentTypeStatement(variable, persistentSetOf(), persistentSetOf())
|
||||
for (info in infos) {
|
||||
resultInfo += info
|
||||
}
|
||||
if (variable.isThisReference) {
|
||||
updatedReceivers += variable
|
||||
}
|
||||
addTypeStatement(resultFlow, resultInfo)
|
||||
addTypeStatement(resultFlow, and(infos))
|
||||
}
|
||||
|
||||
updatedReceivers.forEach {
|
||||
@@ -361,13 +348,7 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
|
||||
val approveOperationStatements =
|
||||
approveOperationStatementsInternal(flow, approvedStatement, statements, shouldRemoveSynthetics = false)
|
||||
approveOperationStatements.asMap().forEach { (variable, infos) ->
|
||||
for (info in infos) {
|
||||
val mutableInfo = info.asMutableStatement()
|
||||
destination.put(variable, mutableInfo) {
|
||||
it += mutableInfo
|
||||
it
|
||||
}
|
||||
}
|
||||
destination.put(variable, { and(infos) }, { and(listOf(it) + infos) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,9 +397,6 @@ private fun lowestCommonFlow(left: PersistentFlow, right: PersistentFlow): Persi
|
||||
return left
|
||||
}
|
||||
|
||||
private fun PersistentApprovedTypeStatements.addTypeStatement(info: TypeStatement): PersistentApprovedTypeStatements =
|
||||
put(info.variable, { info.toPersistent() }, { it + info })
|
||||
|
||||
private fun TypeStatement.toPersistent(): PersistentTypeStatement = when (this) {
|
||||
is PersistentTypeStatement -> this
|
||||
else -> PersistentTypeStatement(variable, exactType.toPersistentSet(), exactNotType.toPersistentSet())
|
||||
|
||||
@@ -12,22 +12,11 @@ import kotlin.contracts.contract
|
||||
|
||||
// --------------------------------------- Facts ---------------------------------------
|
||||
|
||||
operator fun TypeStatement.plus(other: TypeStatement?): TypeStatement = other?.let { this + other } ?: this
|
||||
|
||||
class MutableTypeStatement(
|
||||
override val variable: RealVariable,
|
||||
override val exactType: MutableSet<ConeKotlinType> = linkedSetOf(),
|
||||
override val exactNotType: MutableSet<ConeKotlinType> = linkedSetOf()
|
||||
) : 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 {
|
||||
return MutableTypeStatement(
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -51,17 +35,8 @@ fun Implication.invertCondition(): Implication = Implication(condition.invert(),
|
||||
typealias TypeStatements = Map<RealVariable, TypeStatement>
|
||||
typealias MutableTypeStatements = MutableMap<RealVariable, MutableTypeStatement>
|
||||
|
||||
typealias MutableOperationStatements = MutableMap<RealVariable, MutableTypeStatement>
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
fun TypeStatements.asMutableStatements(): MutableTypeStatements =
|
||||
mapValuesTo(mutableMapOf()) { it.value.asMutableStatement() }
|
||||
|
||||
// --------------------------------------- DSL ---------------------------------------
|
||||
|
||||
|
||||
@@ -34,9 +34,11 @@ abstract class TypeStatement : Statement<TypeStatement>() {
|
||||
abstract val exactType: Set<ConeKotlinType>
|
||||
abstract val exactNotType: Set<ConeKotlinType>
|
||||
|
||||
abstract operator fun plus(other: TypeStatement): TypeStatement
|
||||
abstract val isEmpty: Boolean
|
||||
val isNotEmpty: Boolean get() = !isEmpty
|
||||
val isEmpty: Boolean
|
||||
get() = exactType.isEmpty() && exactNotType.isEmpty()
|
||||
|
||||
val isNotEmpty: Boolean
|
||||
get() = !isEmpty
|
||||
|
||||
override fun toString(): String {
|
||||
return "$variable: $exactType, $exactNotType"
|
||||
|
||||
@@ -26,13 +26,14 @@ import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
@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 {
|
||||
callsInPlace(remappingFunction, InvocationKind.AT_MOST_ONCE)
|
||||
callsInPlace(valueProducer, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
val existing = this[key]
|
||||
if (existing == null) {
|
||||
put(key, value)
|
||||
put(key, valueProducer())
|
||||
} else {
|
||||
put(key, remappingFunction(existing))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user