FIR DFA: when joining flows, clear reassigned vars with no statements
For example:
val c = C("...")
val x = c.x // alias to variable for c.x, which depends on c
if (x == null) return
// now c.x has type Any
c = C(null)
// c has been reassigned => info for c.x is no longer valid;
// c itself has never had any statements made about it, but
// we must still call removeAllAboutVariable to clear the
// dependents
This commit is contained in:
@@ -22,8 +22,7 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
|
|||||||
|
|
||||||
abstract fun addImplication(flow: FLOW, implication: Implication)
|
abstract fun addImplication(flow: FLOW, implication: Implication)
|
||||||
|
|
||||||
fun removeAllAboutVariable(flow: FLOW, variable: RealVariable?) {
|
fun removeAllAboutVariable(flow: FLOW, variable: RealVariable) {
|
||||||
if (variable == null) return
|
|
||||||
removeAliasInformationAboutVariable(flow, variable)
|
removeAliasInformationAboutVariable(flow, variable)
|
||||||
removeTypeStatementsAboutVariable(flow, variable)
|
removeTypeStatementsAboutVariable(flow, variable)
|
||||||
removeLogicStatementsAboutVariable(flow, variable)
|
removeLogicStatementsAboutVariable(flow, variable)
|
||||||
|
|||||||
+33
-58
@@ -108,10 +108,15 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun joinFlow(flows: Collection<PersistentFlow>): PersistentFlow {
|
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.getApprovedTypeStatements(variable) }).takeIf { it.isNotEmpty } }
|
return foldFlow(flows) { variable -> or(flows.map { it.getApprovedTypeStatements(variable) }).takeIf { it.isNotEmpty } }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun unionFlow(flows: Collection<PersistentFlow>): PersistentFlow {
|
override fun unionFlow(flows: Collection<PersistentFlow>): PersistentFlow {
|
||||||
|
// All input flows execute in some order. If none of them reassign the variable, then
|
||||||
|
// *all* sets of statements are true; otherwise the assignments might have happened in
|
||||||
|
// an arbitrary order, so only one set of statements is true depending on which assignment
|
||||||
|
// happened last (and the flows that don't reassign may or may not have executed after that).
|
||||||
return foldFlow(flows) { variable ->
|
return foldFlow(flows) { variable ->
|
||||||
or(flows.groupBy { it.assignmentIndex[variable] ?: -1 }.values.map { flowSubset ->
|
or(flows.groupBy { it.assignmentIndex[variable] ?: -1 }.values.map { flowSubset ->
|
||||||
and(flowSubset.map { it.getApprovedTypeStatements(variable) })
|
and(flowSubset.map { it.getApprovedTypeStatements(variable) })
|
||||||
@@ -119,45 +124,39 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun foldFlow(
|
private inline fun foldFlow(flows: Collection<PersistentFlow>, mergeOperation: (RealVariable) -> TypeStatement?): PersistentFlow =
|
||||||
flows: Collection<PersistentFlow>,
|
when (flows.size) {
|
||||||
mergeOperation: (RealVariable) -> MutableTypeStatement?,
|
0 -> createEmptyFlow()
|
||||||
): PersistentFlow {
|
1 -> flows.first()
|
||||||
if (flows.isEmpty()) return createEmptyFlow()
|
else -> foldFlow(flows, flows.flatMapTo(mutableSetOf()) { it.approvedTypeStatements.keys }.mapNotNull(mergeOperation))
|
||||||
flows.singleOrNull()?.let { return it }
|
}
|
||||||
|
|
||||||
|
private fun foldFlow(flows: Collection<PersistentFlow>, statements: Collection<TypeStatement>): PersistentFlow {
|
||||||
val aliasedVariablesThatDontChangeAlias = computeAliasesThatDontChange(flows)
|
val aliasedVariablesThatDontChangeAlias = computeAliasesThatDontChange(flows)
|
||||||
|
|
||||||
val commonFlow = flows.reduce(::lowestCommonFlow)
|
val commonFlow = flows.reduce(::lowestCommonFlow)
|
||||||
|
|
||||||
val variables = flows.flatMap { it.approvedTypeStatements.keys }.toSet()
|
for (flow in flows) {
|
||||||
for (variable in variables) {
|
for ((variable, index) in flow.assignmentIndex) {
|
||||||
val info = mergeOperation(variable) ?: continue
|
if (commonFlow.assignmentIndex[variable] != index) {
|
||||||
commonFlow.approvedTypeStatements -= variable
|
removeAllAboutVariable(commonFlow, variable)
|
||||||
commonFlow.approvedTypeStatementsDiff -= variable
|
}
|
||||||
val thereWereReassignments = variable.hasDifferentReassignments(flows)
|
|
||||||
if (thereWereReassignments) {
|
|
||||||
removeAllAboutVariable(commonFlow, variable)
|
|
||||||
}
|
}
|
||||||
commonFlow.addApprovedStatements(info)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
commonFlow.addVariableAliases(aliasedVariablesThatDontChangeAlias)
|
val toReplace = statements.map { it.variable to it.toPersistent() }
|
||||||
|
commonFlow.approvedTypeStatements += toReplace
|
||||||
|
if (commonFlow.previousFlow != null) {
|
||||||
|
commonFlow.approvedTypeStatementsDiff += toReplace
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((alias, underlyingVariable) in aliasedVariablesThatDontChangeAlias) {
|
||||||
|
addLocalVariableAlias(commonFlow, alias, underlyingVariable)
|
||||||
|
}
|
||||||
return commonFlow
|
return commonFlow
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RealVariable.hasDifferentReassignments(flows: Collection<PersistentFlow>): Boolean {
|
private fun computeAliasesThatDontChange(flows: Collection<PersistentFlow>): MutableMap<RealVariable, RealVariableAndType> {
|
||||||
val firstIndex = flows.first().assignmentIndex[this] ?: -1
|
|
||||||
for (flow in flows) {
|
|
||||||
val index = flow.assignmentIndex[this] ?: -1
|
|
||||||
if (index != firstIndex) return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun computeAliasesThatDontChange(
|
|
||||||
flows: Collection<PersistentFlow>
|
|
||||||
): MutableMap<RealVariable, RealVariableAndType> {
|
|
||||||
val flowsSize = flows.size
|
val flowsSize = flows.size
|
||||||
val aliasedVariablesThatDontChangeAlias = mutableMapOf<RealVariable, RealVariableAndType>()
|
val aliasedVariablesThatDontChangeAlias = mutableMapOf<RealVariable, RealVariableAndType>()
|
||||||
|
|
||||||
@@ -173,23 +172,6 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
|
|||||||
return aliasedVariablesThatDontChangeAlias
|
return aliasedVariablesThatDontChangeAlias
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun PersistentFlow.addVariableAliases(
|
|
||||||
aliasedVariablesThatDontChangeAlias: MutableMap<RealVariable, RealVariableAndType>
|
|
||||||
) {
|
|
||||||
for ((alias, underlyingVariable) in aliasedVariablesThatDontChangeAlias) {
|
|
||||||
addLocalVariableAlias(this, alias, underlyingVariable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun PersistentFlow.addApprovedStatements(
|
|
||||||
info: MutableTypeStatement
|
|
||||||
) {
|
|
||||||
approvedTypeStatements = approvedTypeStatements.addTypeStatement(info)
|
|
||||||
if (previousFlow != null) {
|
|
||||||
approvedTypeStatementsDiff = approvedTypeStatementsDiff.addTypeStatement(info)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addLocalVariableAlias(flow: PersistentFlow, alias: RealVariable, underlyingVariable: RealVariableAndType) {
|
override fun addLocalVariableAlias(flow: PersistentFlow, alias: RealVariable, underlyingVariable: RealVariableAndType) {
|
||||||
removeLocalVariableAlias(flow, alias)
|
removeLocalVariableAlias(flow, alias)
|
||||||
flow.directAliasMap = flow.directAliasMap.put(alias, underlyingVariable)
|
flow.directAliasMap = flow.directAliasMap.put(alias, underlyingVariable)
|
||||||
@@ -527,22 +509,15 @@ private fun lowestCommonFlow(left: PersistentFlow, right: PersistentFlow): Persi
|
|||||||
private fun PersistentApprovedTypeStatements.addTypeStatement(info: TypeStatement): PersistentApprovedTypeStatements {
|
private fun PersistentApprovedTypeStatements.addTypeStatement(info: TypeStatement): PersistentApprovedTypeStatements {
|
||||||
val variable = info.variable
|
val variable = info.variable
|
||||||
val existingInfo = this[variable]
|
val existingInfo = this[variable]
|
||||||
return if (existingInfo == null) {
|
return put(variable, if (existingInfo != null) existingInfo + info else info.toPersistent())
|
||||||
val persistentInfo = if (info is PersistentTypeStatement) info else info.toPersistent()
|
|
||||||
put(variable, persistentInfo)
|
|
||||||
} else {
|
|
||||||
put(variable, existingInfo + info)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TypeStatement.toPersistent(): PersistentTypeStatement = PersistentTypeStatement(
|
private fun TypeStatement.toPersistent(): PersistentTypeStatement = when (this) {
|
||||||
variable,
|
is PersistentTypeStatement -> this
|
||||||
exactType.toPersistentSet(),
|
else -> PersistentTypeStatement(variable, exactType.toPersistentSet(), exactNotType.toPersistentSet())
|
||||||
exactNotType.toPersistentSet()
|
}
|
||||||
)
|
|
||||||
|
|
||||||
fun TypeStatement.asMutableStatement(): MutableTypeStatement = when (this) {
|
fun TypeStatement.asMutableStatement(): MutableTypeStatement = when (this) {
|
||||||
is MutableTypeStatement -> this
|
is MutableTypeStatement -> this
|
||||||
is PersistentTypeStatement -> MutableTypeStatement(variable, exactType.toMutableSet(), exactNotType.toMutableSet())
|
else -> MutableTypeStatement(variable, exactType.toMutableSet(), exactNotType.toMutableSet())
|
||||||
else -> throw IllegalArgumentException("Unknown TypeStatement type: ${this::class}")
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -36,5 +36,5 @@ fun test3(p: Boolean) {
|
|||||||
c = C(null)
|
c = C(null)
|
||||||
}
|
}
|
||||||
x.length // ok
|
x.length // ok
|
||||||
c.x.length // bad
|
c.x<!UNSAFE_CALL!>.<!>length // bad
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user