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
@@ -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(
@@ -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))
}