FIR DFA: assert that type statements are not attached to aliases
This commit is contained in:
+4
-2
@@ -104,7 +104,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
|
||||
val index = receiverStack.getReceiverIndex(symbol) ?: return
|
||||
val info = flow.getType(variable)
|
||||
val type = if (info.isNotEmpty()) {
|
||||
val type = if (info?.isNotEmpty() == true) {
|
||||
val types = info.toMutableList()
|
||||
types += receiverStack.getOriginalType(index)
|
||||
context.intersectTypesOrNull(types)!!
|
||||
@@ -1261,7 +1261,9 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
}
|
||||
|
||||
if (isAssignment) {
|
||||
flow.addTypeStatement(propertyVariable typeEq initializer.typeRef.coneType)
|
||||
// `propertyVariable` can be an alias to `initializerVariable`, in which case this will add
|
||||
// a redundant type statement which is fine...probably. TODO: store initial type within the variable?
|
||||
flow.addTypeStatement(flow.unwrapVariable(propertyVariable) typeEq initializer.typeRef.coneType)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,15 +22,7 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
|
||||
|
||||
abstract fun addImplication(flow: FLOW, implication: Implication)
|
||||
|
||||
fun removeAllAboutVariable(flow: FLOW, variable: RealVariable) {
|
||||
removeAliasInformationAboutVariable(flow, variable)
|
||||
removeTypeStatementsAboutVariable(flow, variable)
|
||||
removeLogicStatementsAboutVariable(flow, variable)
|
||||
}
|
||||
|
||||
abstract fun removeTypeStatementsAboutVariable(flow: FLOW, variable: RealVariable)
|
||||
abstract fun removeLogicStatementsAboutVariable(flow: FLOW, variable: RealVariable)
|
||||
abstract fun removeAliasInformationAboutVariable(flow: FLOW, variable: RealVariable)
|
||||
abstract fun removeAllAboutVariable(flow: FLOW, variable: RealVariable)
|
||||
|
||||
abstract fun translateVariableFromConditionInStatements(
|
||||
flow: FLOW,
|
||||
@@ -49,7 +41,6 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
|
||||
): FLOW
|
||||
|
||||
abstract fun addLocalVariableAlias(flow: FLOW, alias: RealVariable, underlyingVariable: RealVariable)
|
||||
abstract fun removeLocalVariableAlias(flow: FLOW, alias: RealVariable)
|
||||
|
||||
abstract fun recordNewAssignment(flow: FLOW, variable: RealVariable, index: Int)
|
||||
|
||||
|
||||
+98
-115
@@ -9,6 +9,7 @@ import com.google.common.collect.ArrayListMultimap
|
||||
import kotlinx.collections.immutable.*
|
||||
import org.jetbrains.kotlin.fir.types.ConeInferenceContext
|
||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
|
||||
@@ -83,16 +84,12 @@ class PersistentFlow : Flow {
|
||||
|
||||
fun getTypeStatement(variable: RealVariable): TypeStatement {
|
||||
val result = MutableTypeStatement(variable)
|
||||
approvedTypeStatements[variable]?.let { result += it }
|
||||
val variableUnderAlias = directAliasMap[variable]
|
||||
if (variableUnderAlias != null) {
|
||||
approvedTypeStatements[variableUnderAlias]?.let { result += it }
|
||||
}
|
||||
approvedTypeStatements[unwrapVariable(variable)]?.let { result += it }
|
||||
return result
|
||||
}
|
||||
|
||||
override fun getType(variable: RealVariable): Set<ConeKotlinType> {
|
||||
return getTypeStatement(variable).exactType
|
||||
override fun getType(variable: RealVariable): Set<ConeKotlinType>? {
|
||||
return approvedTypeStatements[unwrapVariable(variable)]?.exactType
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +104,7 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
|
||||
|
||||
override fun joinFlow(flows: Collection<PersistentFlow>): PersistentFlow {
|
||||
// One input flow executes - one set of statements is true, others might be false.
|
||||
return foldFlow(flows) { variable -> or(flows.map { it.getTypeStatement(variable) }).takeIf { it.isNotEmpty } }
|
||||
return foldFlow(flows) { variable -> or(flows.map { it.getTypeStatement(variable) }) }
|
||||
}
|
||||
|
||||
override fun unionFlow(flows: Collection<PersistentFlow>): PersistentFlow {
|
||||
@@ -122,11 +119,15 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun foldFlow(flows: Collection<PersistentFlow>, mergeOperation: (RealVariable) -> TypeStatement?): PersistentFlow =
|
||||
private inline fun foldFlow(flows: Collection<PersistentFlow>, mergeOperation: (RealVariable) -> TypeStatement): PersistentFlow =
|
||||
when (flows.size) {
|
||||
0 -> createEmptyFlow()
|
||||
1 -> flows.first()
|
||||
else -> foldFlow(flows, flows.flatMapTo(mutableSetOf()) { it.approvedTypeStatements.keys }.mapNotNull(mergeOperation))
|
||||
else -> foldFlow(
|
||||
flows,
|
||||
// TODO: filter out variables which will stay aliased
|
||||
flows.flatMapTo(mutableSetOf()) { it.approvedTypeStatements.keys + it.directAliasMap.keys }.map(mergeOperation)
|
||||
)
|
||||
}
|
||||
|
||||
private fun foldFlow(flows: Collection<PersistentFlow>, statements: Collection<TypeStatement>): PersistentFlow {
|
||||
@@ -151,7 +152,7 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
|
||||
recordNewAssignment(commonFlow, variable, index)
|
||||
}
|
||||
|
||||
val toReplace = statements.map { it.variable to it.toPersistent() }
|
||||
val toReplace = statements.filter { it.isNotEmpty && it.variable !in commonAliases }.map { it.variable to it.toPersistent() }
|
||||
commonFlow.approvedTypeStatements += toReplace
|
||||
if (commonFlow.previousFlow != null) {
|
||||
commonFlow.approvedTypeStatementsDiff += toReplace
|
||||
@@ -172,43 +173,47 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
|
||||
flow.addAliases(persistentSetOf(alias), flow.unwrapVariable(underlyingVariable))
|
||||
}
|
||||
|
||||
override fun removeLocalVariableAlias(flow: PersistentFlow, alias: RealVariable) {
|
||||
val original = flow.directAliasMap[alias]
|
||||
val backAliases = flow.backwardsAliasMap[alias]
|
||||
flow.directAliasMap -= alias
|
||||
flow.backwardsAliasMap -= alias
|
||||
|
||||
// Move all statements to the next representative of the equivalence set
|
||||
val replacement = original ?: backAliases?.first()
|
||||
if (replacement != null) {
|
||||
flow.logicStatements[alias]?.let { statements ->
|
||||
val newStatements = statements.map { it.replaceVariable(alias, replacement) }
|
||||
flow.logicStatements = flow.logicStatements.put(replacement, { newStatements.toPersistentList() }, { it + newStatements })
|
||||
}
|
||||
flow.approvedTypeStatements[alias]?.let {
|
||||
flow.approvedTypeStatements = flow.approvedTypeStatements.addTypeStatement(it.replaceVariable(alias, replacement))
|
||||
}
|
||||
flow.approvedTypeStatementsDiff[alias]?.let {
|
||||
flow.approvedTypeStatementsDiff = flow.approvedTypeStatementsDiff.addTypeStatement(it.replaceVariable(alias, replacement))
|
||||
}
|
||||
}
|
||||
|
||||
// Point all other members of the equivalence set to the new representative
|
||||
override fun removeAllAboutVariable(flow: PersistentFlow, variable: RealVariable) {
|
||||
val original = flow.directAliasMap[variable]
|
||||
if (original != null) {
|
||||
val siblings = flow.backwardsAliasMap.getValue(original).remove(alias)
|
||||
// Although this code is written defensively, in practice `backAliases` should be null;
|
||||
// any aliases to this variable should have been treated as aliases to `original` instead.
|
||||
flow.backwardsAliasMap = if (backAliases != null) {
|
||||
flow.directAliasMap += backAliases.map { it to original }
|
||||
flow.backwardsAliasMap.put(original, siblings + backAliases)
|
||||
} else if (siblings.isNotEmpty()) {
|
||||
// All statements should've been made about whatever variable this is an alias to. There is nothing to remove.
|
||||
if (AbstractTypeChecker.RUN_SLOW_ASSERTIONS) {
|
||||
assert(variable !in flow.backwardsAliasMap)
|
||||
assert(variable !in flow.logicStatements)
|
||||
assert(variable !in flow.approvedTypeStatements)
|
||||
assert(variable !in flow.approvedTypeStatementsDiff)
|
||||
}
|
||||
// `variable.dependentVariables` is not separated by flow, so it may be non-empty if aliasing of this variable
|
||||
// was broken in another flow. However, in *this* flow dependent variables should have no information attached to them.
|
||||
|
||||
val siblings = flow.backwardsAliasMap.getValue(original).remove(variable)
|
||||
flow.directAliasMap -= variable
|
||||
flow.backwardsAliasMap = if (siblings.isNotEmpty()) {
|
||||
flow.backwardsAliasMap.put(original, siblings)
|
||||
} else {
|
||||
flow.backwardsAliasMap.remove(original)
|
||||
}
|
||||
} else if (replacement != null) {
|
||||
flow.directAliasMap -= replacement
|
||||
flow.addAliases(backAliases!!, replacement)
|
||||
} else {
|
||||
val aliases = flow.backwardsAliasMap[variable]
|
||||
val replacement = aliases?.first()
|
||||
for (dependent in variable.dependentVariables) {
|
||||
// TODO: replace the identifier in dependent variables instead (need to somehow interface with VariableStorage for that)
|
||||
// var x = something
|
||||
// val y = x
|
||||
// if (x.stableProperty !is A) return
|
||||
// y.stableProperty // A
|
||||
// x = somethingElse
|
||||
// y.stableProperty // still A
|
||||
removeAllAboutVariable(flow, dependent)
|
||||
}
|
||||
flow.logicStatements = flow.logicStatements.replaceVariable(variable, replacement)
|
||||
flow.approvedTypeStatements = flow.approvedTypeStatements.replaceVariable(variable, replacement)
|
||||
flow.approvedTypeStatementsDiff = flow.approvedTypeStatementsDiff.replaceVariable(variable, replacement)
|
||||
if (replacement != null) {
|
||||
flow.directAliasMap -= replacement
|
||||
flow.backwardsAliasMap -= variable
|
||||
flow.addAliases(aliases, replacement)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,21 +225,6 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
|
||||
}
|
||||
}
|
||||
|
||||
private fun Implication.replaceVariable(from: RealVariable, to: RealVariable): Implication {
|
||||
return Implication(condition.replaceVariable(from, to), effect.replaceVariable(from, to))
|
||||
}
|
||||
|
||||
private fun <T : Statement<T>> Statement<T>.replaceVariable(from: RealVariable, to: RealVariable): T {
|
||||
val statement = when (this) {
|
||||
is OperationStatement -> if (variable == from) copy(variable = to) else this
|
||||
is PersistentTypeStatement -> if (variable == from) copy(variable = to) else this
|
||||
is MutableTypeStatement -> if (variable == from) MutableTypeStatement(to, exactType, exactNotType) else this
|
||||
else -> throw IllegalArgumentException("unknown type of statement $this")
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return statement as T
|
||||
}
|
||||
|
||||
override fun addTypeStatement(flow: PersistentFlow, statement: TypeStatement) {
|
||||
if (statement.isEmpty) return
|
||||
with(flow) {
|
||||
@@ -251,58 +241,8 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
|
||||
override fun addImplication(flow: PersistentFlow, implication: Implication) {
|
||||
if ((implication.effect as? TypeStatement)?.isEmpty == true) return
|
||||
if (implication.condition == implication.effect) return
|
||||
with(flow) {
|
||||
val variable = implication.condition.variable
|
||||
val existingImplications = logicStatements[variable]
|
||||
logicStatements = if (existingImplications == null) {
|
||||
logicStatements.put(variable, persistentListOf(implication))
|
||||
} else {
|
||||
logicStatements.put(variable, existingImplications + implication)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeTypeStatementsAboutVariable(flow: PersistentFlow, variable: RealVariable) {
|
||||
variable.forEachTransitiveDependentVariable {
|
||||
flow.approvedTypeStatements -= it
|
||||
flow.approvedTypeStatementsDiff -= it
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeLogicStatementsAboutVariable(flow: PersistentFlow, variable: RealVariable) {
|
||||
variable.forEachTransitiveDependentVariable {
|
||||
flow.logicStatements -= it
|
||||
}
|
||||
var newLogicStatements = flow.logicStatements
|
||||
for ((key, implications) in flow.logicStatements) {
|
||||
val implicationsToDelete = mutableListOf<Implication>()
|
||||
variable.forEachTransitiveDependentVariable {
|
||||
implications.filterTo(implicationsToDelete) { implication ->
|
||||
implication.effect.variable == it
|
||||
}
|
||||
}
|
||||
if (implicationsToDelete.isEmpty()) continue
|
||||
val newImplications = implications.removeAll(implicationsToDelete)
|
||||
newLogicStatements = if (newImplications.isNotEmpty()) {
|
||||
newLogicStatements.put(key, newImplications)
|
||||
} else {
|
||||
newLogicStatements.remove(key)
|
||||
}
|
||||
}
|
||||
flow.logicStatements = newLogicStatements
|
||||
}
|
||||
|
||||
override fun removeAliasInformationAboutVariable(flow: PersistentFlow, variable: RealVariable) {
|
||||
variable.forEachTransitiveDependentVariable {
|
||||
removeLocalVariableAlias(flow, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun RealVariable.forEachTransitiveDependentVariable(action: (RealVariable) -> Unit) {
|
||||
action(this)
|
||||
dependentVariables.forEach {
|
||||
it.forEachTransitiveDependentVariable(action)
|
||||
}
|
||||
val variable = implication.condition.variable
|
||||
flow.logicStatements = flow.logicStatements.put(variable, { persistentListOf(implication) }, { it + implication })
|
||||
}
|
||||
|
||||
override fun translateVariableFromConditionInStatements(
|
||||
@@ -472,11 +412,8 @@ private fun lowestCommonFlow(left: PersistentFlow, right: PersistentFlow): Persi
|
||||
return left
|
||||
}
|
||||
|
||||
private fun PersistentApprovedTypeStatements.addTypeStatement(info: TypeStatement): PersistentApprovedTypeStatements {
|
||||
val variable = info.variable
|
||||
val existingInfo = this[variable]
|
||||
return put(variable, if (existingInfo != null) existingInfo + info else info.toPersistent())
|
||||
}
|
||||
private fun PersistentApprovedTypeStatements.addTypeStatement(info: TypeStatement): PersistentApprovedTypeStatements =
|
||||
put(info.variable, { info.toPersistent() }, { it + info })
|
||||
|
||||
private fun TypeStatement.toPersistent(): PersistentTypeStatement = when (this) {
|
||||
is PersistentTypeStatement -> this
|
||||
@@ -487,3 +424,49 @@ fun TypeStatement.asMutableStatement(): MutableTypeStatement = when (this) {
|
||||
is MutableTypeStatement -> this
|
||||
else -> MutableTypeStatement(variable, exactType.toMutableSet(), exactNotType.toMutableSet())
|
||||
}
|
||||
|
||||
@JvmName("replaceVariableInStatements")
|
||||
private fun PersistentApprovedTypeStatements.replaceVariable(from: RealVariable, to: RealVariable?): PersistentApprovedTypeStatements {
|
||||
val existing = this[from] ?: return this
|
||||
return if (to != null) remove(from).put(to, existing.copy(variable = to)) else remove(from)
|
||||
}
|
||||
|
||||
@JvmName("replaceVariableInImplications")
|
||||
private fun PersistentImplications.replaceVariable(from: RealVariable, to: RealVariable?): PersistentImplications =
|
||||
mutate { result ->
|
||||
for ((variable, implications) in this) {
|
||||
val newImplications = if (to != null) {
|
||||
implications.replaceAll { it.replaceVariable(from, to) }
|
||||
} else {
|
||||
implications.removeAll { it.effect.variable == from }
|
||||
}
|
||||
if (newImplications.isNotEmpty()) {
|
||||
result[implications.first().condition.variable] = newImplications
|
||||
} else {
|
||||
result.remove(variable)
|
||||
}
|
||||
}
|
||||
result.remove(from)
|
||||
}
|
||||
|
||||
private inline fun <T> PersistentList<T>.replaceAll(block: (T) -> T): PersistentList<T> =
|
||||
mutate { result ->
|
||||
val it = result.listIterator()
|
||||
while (it.hasNext()) {
|
||||
it.set(block(it.next()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun Implication.replaceVariable(from: RealVariable, to: RealVariable): Implication = when {
|
||||
condition.variable == from -> Implication(condition.copy(variable = to), effect.replaceVariable(from, to))
|
||||
effect.variable == from -> Implication(condition, effect.replaceVariable(from, to))
|
||||
else -> this
|
||||
}
|
||||
|
||||
private fun Statement<*>.replaceVariable(from: RealVariable, to: RealVariable): Statement<*> =
|
||||
if (variable != from) this else when (this) {
|
||||
is OperationStatement -> copy(variable = to)
|
||||
is PersistentTypeStatement -> copy(variable = to)
|
||||
is MutableTypeStatement -> MutableTypeStatement(to, exactType, exactNotType)
|
||||
else -> throw IllegalArgumentException("unknown type of statement $this")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user