diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/DelegatingLogicSystem.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/DelegatingLogicSystem.kt index 4e00fef3c4e..b2b4b5524c4 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/DelegatingLogicSystem.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/DelegatingLogicSystem.kt @@ -50,6 +50,10 @@ class DelegatingFlow( return result } + override fun removeConditionalInfos(variable: DataFlowVariable): Collection { + return conditionalInfos.removeAll(variable) + } + private inline fun collect(block: (DelegatingFlow) -> Boolean) { var flow: DelegatingFlow? = this while (flow != null) { @@ -95,6 +99,12 @@ private class ImmutableMultimap(private val original: Multimap) : Mu } abstract class DelegatingLogicSystem(context: DataFlowInferenceContext) : LogicSystem(context) { + override val Flow.approvedInfos: MutableApprovedInfos + get() = (this as DelegatingFlow).approvedInfos + + override val Flow.conditionalInfos: ConditionalInfos + get() = (this as DelegatingFlow).conditionalInfos + override fun createEmptyFlow(): Flow { return DelegatingFlow(null) } @@ -104,11 +114,16 @@ abstract class DelegatingLogicSystem(context: DataFlowInferenceContext) : LogicS return DelegatingFlow(flow) } - override fun collectInfoForBooleanOperator(leftFlow: Flow, rightFlow: Flow): InfoForBooleanOperator { + override fun collectInfoForBooleanOperator( + leftFlow: Flow, + leftVariable: DataFlowVariable, + rightFlow: Flow, + rightVariable: DataFlowVariable + ): InfoForBooleanOperator { require(leftFlow is DelegatingFlow && rightFlow is DelegatingFlow) return InfoForBooleanOperator( - leftFlow.previousFlow!!.conditionalInfosFromTopFlow(), - rightFlow.conditionalInfosFromTopFlow(), + leftFlow.previousFlow!!.conditionalInfosFromTopFlow()[leftVariable], + rightFlow.conditionalInfosFromTopFlow()[rightVariable], rightFlow.approvedInfosFromTopFlow() ) } @@ -148,83 +163,6 @@ abstract class DelegatingLogicSystem(context: DataFlowInferenceContext) : LogicS return commonFlow } - override fun changeVariableForConditionFlow( - flow: Flow, - sourceVariable: DataFlowVariable, - newVariable: DataFlowVariable, - transform: ((ConditionalFirDataFlowInfo) -> ConditionalFirDataFlowInfo)? - ) { - require(flow is DelegatingFlow) - var infos = flow.getConditionalInfos(sourceVariable) - if (transform != null) { - infos = infos.map(transform) - } - flow.conditionalInfos.putAll(newVariable, infos) - if (sourceVariable.isSynthetic()) { - flow.conditionalInfos.removeAll(sourceVariable) - } - } - - override fun approveFactsInsideFlow( - variable: DataFlowVariable, - condition: Condition, - flow: Flow, - shouldForkFlow: Boolean, - shouldRemoveSynthetics: Boolean - ): Flow { - require(flow is DelegatingFlow) - - val notApprovedFacts: Collection = if (shouldRemoveSynthetics && variable.isSynthetic()) { - flow.conditionalInfos.removeAll(variable) - } else { - flow.getConditionalInfos(variable) - } - - val resultFlow = if (shouldForkFlow) forkFlow(flow) else flow - if (notApprovedFacts.isEmpty()) { - return resultFlow - } - - val newFacts = ArrayListMultimap.create() - notApprovedFacts.forEach { - if (it.condition == condition) { - newFacts.put(it.variable, it.info) - } - } - - val updatedReceivers = mutableSetOf() - - newFacts.asMap().forEach { (variable, infos) -> - @Suppress("NAME_SHADOWING") - val info = MutableFirDataFlowInfo() - infos.forEach { - info += it - } - if (variable.isThisReference) { - updatedReceivers += variable - } - addApprovedInfo(resultFlow, variable, info) - } - updatedReceivers.forEach { - processUpdatedReceiverVariable(resultFlow, it) - } - return resultFlow - } - - override fun addApprovedInfo(flow: Flow, variable: RealDataFlowVariable, info: FirDataFlowInfo) { - assert(info is MutableFirDataFlowInfo) - require(flow is DelegatingFlow) - flow.approvedInfos.addInfo(variable, info) - if (variable.isThisReference) { - processUpdatedReceiverVariable(flow, variable) - } - } - - override fun addConditionalInfo(flow: Flow, variable: DataFlowVariable, info: ConditionalFirDataFlowInfo) { - require(flow is DelegatingFlow) - flow.conditionalInfos.put(variable, info) - } - // ------------------------------- Util functions ------------------------------- private fun lowestCommonFlow(left: DelegatingFlow, right: DelegatingFlow): DelegatingFlow { diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt index d5cd53155de..b2e5cf38b95 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt @@ -569,17 +569,16 @@ class FirDataFlowAnalyzer(transformer: FirBodyResolveTransformer) : BodyResolveC val (conditionalFromLeft, conditionalFromRight, approvedFromRight) = logicSystem.collectInfoForBooleanOperator( flowFromLeft, - flowFromRight + leftVariable, + flowFromRight, + rightVariable ) - val conditionalFromLeftArgument = conditionalFromLeft[leftVariable] - val conditionalFromRightArgument = conditionalFromRight[rightVariable] - // left && right == True // left || right == False val approvedIfTrue: MutableApprovedInfos = mutableMapOf() - logicSystem.approveFactTo(approvedIfTrue, bothEvaluated, conditionalFromLeftArgument) - logicSystem.approveFactTo(approvedIfTrue, bothEvaluated, conditionalFromRightArgument) + logicSystem.approveFactTo(approvedIfTrue, bothEvaluated, conditionalFromLeft) + logicSystem.approveFactTo(approvedIfTrue, bothEvaluated, conditionalFromRight) approvedFromRight.forEach { (variable, info) -> approvedIfTrue.addInfo(variable, info) } @@ -590,8 +589,8 @@ class FirDataFlowAnalyzer(transformer: FirBodyResolveTransformer) : BodyResolveC // left && right == False // left || right == True val approvedIfFalse: MutableApprovedInfos = mutableMapOf() - val leftIsFalse = logicSystem.approveFact(onlyLeftEvaluated, conditionalFromLeftArgument) - val rightIsFalse = logicSystem.approveFact(onlyLeftEvaluated, conditionalFromRightArgument) + val leftIsFalse = logicSystem.approveFact(onlyLeftEvaluated, conditionalFromLeft) + val rightIsFalse = logicSystem.approveFact(onlyLeftEvaluated, conditionalFromRight) approvedIfFalse.mergeInfo(logicSystem.orForVerifiedFacts(leftIsFalse, rightIsFalse)) approvedIfFalse.forEach { (variable, info) -> logicSystem.addConditionalInfo(flow, andVariable, info.toConditional(onlyLeftEvaluated, variable)) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowInfo.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowInfo.kt index 0ef032a070e..239f72fc700 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowInfo.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowInfo.kt @@ -68,6 +68,7 @@ data class MutableFirDataFlowInfo( exactNotType += info.exactNotType } + fun copy(): MutableFirDataFlowInfo = MutableFirDataFlowInfo(exactType.toMutableSet(), exactNotType.toMutableSet()) } operator fun FirDataFlowInfo.plus(other: FirDataFlowInfo?): FirDataFlowInfo = other?.let { this + other } ?: this diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt index 9d88b3303fc..6d76552c2c1 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.fir.resolve.dfa +import com.google.common.collect.ArrayListMultimap import org.jetbrains.kotlin.fir.types.ConeKotlinType interface Flow { @@ -12,17 +13,29 @@ interface Flow { fun getConditionalInfos(variable: DataFlowVariable): Collection fun getVariablesInApprovedInfos(): Collection + + fun removeConditionalInfos(variable: DataFlowVariable): Collection } abstract class LogicSystem(private val context: DataFlowInferenceContext) { + // ------------------------------- Flow operations ------------------------------- abstract fun createEmptyFlow(): Flow abstract fun forkFlow(flow: Flow): Flow abstract fun joinFlow(flows: Collection): Flow - abstract fun addApprovedInfo(flow: Flow, variable: RealDataFlowVariable, info: FirDataFlowInfo) - abstract fun addConditionalInfo(flow: Flow, variable: DataFlowVariable, info: ConditionalFirDataFlowInfo) + open fun addApprovedInfo(flow: Flow, variable: RealDataFlowVariable, info: FirDataFlowInfo) { + assert(info is MutableFirDataFlowInfo) + flow.approvedInfos.addInfo(variable, info) + if (variable.isThisReference) { + processUpdatedReceiverVariable(flow, variable) + } + } + + open fun addConditionalInfo(flow: Flow, variable: DataFlowVariable, info: ConditionalFirDataFlowInfo) { + flow.conditionalInfos.put(variable, info) + } /* * used for: @@ -30,35 +43,90 @@ abstract class LogicSystem(private val context: DataFlowInferenceContext) { * 2. b = x is String * 3. !b | b.not() for Booleans */ - abstract fun changeVariableForConditionFlow( + open fun changeVariableForConditionFlow( flow: Flow, sourceVariable: DataFlowVariable, newVariable: DataFlowVariable, transform: ((ConditionalFirDataFlowInfo) -> ConditionalFirDataFlowInfo)? = null - ) + ) { + var infos = flow.getConditionalInfos(sourceVariable) + if (transform != null) { + infos = infos.map(transform) + } + flow.conditionalInfos.putAll(newVariable, infos) + if (sourceVariable.isSynthetic()) { + flow.conditionalInfos.removeAll(sourceVariable) + } + } - abstract fun approveFactsInsideFlow( + open fun approveFactsInsideFlow( variable: DataFlowVariable, condition: Condition, flow: Flow, shouldForkFlow: Boolean, shouldRemoveSynthetics: Boolean - ): Flow + ): Flow { + val notApprovedFacts: Collection = if (shouldRemoveSynthetics && variable.isSynthetic()) { + flow.removeConditionalInfos(variable) + } else { + flow.getConditionalInfos(variable) + } + + val resultFlow = if (shouldForkFlow) forkFlow(flow) else flow + if (notApprovedFacts.isEmpty()) { + return resultFlow + } + + val newFacts = ArrayListMultimap.create() + notApprovedFacts.forEach { + if (it.condition == condition) { + newFacts.put(it.variable, it.info) + } + } + + val updatedReceivers = mutableSetOf() + + newFacts.asMap().forEach { (variable, infos) -> + @Suppress("NAME_SHADOWING") + val info = MutableFirDataFlowInfo() + infos.forEach { + info += it + } + if (variable.isThisReference) { + updatedReceivers += variable + } + addApprovedInfo(resultFlow, variable, info) + } + updatedReceivers.forEach { + processUpdatedReceiverVariable(resultFlow, it) + } + return resultFlow + } // ------------------------------- Callbacks for updating implicit receiver stack ------------------------------- abstract fun processUpdatedReceiverVariable(flow: Flow, variable: RealDataFlowVariable) abstract fun updateAllReceivers(flow: Flow) + // ------------------------------- Accessors to flow implementation ------------------------------- + + protected abstract val Flow.approvedInfos: MutableApprovedInfos + protected abstract val Flow.conditionalInfos: ConditionalInfos + // ------------------------------- Public DataFlowInfo util functions ------------------------------- data class InfoForBooleanOperator( - val conditionalFromLeft: ConditionalInfos, - val conditionalFromRight: ConditionalInfos, + val conditionalFromLeft: Collection, + val conditionalFromRight: Collection, val approvedFromRight: ApprovedInfos ) - abstract fun collectInfoForBooleanOperator(leftFlow: Flow, rightFlow: Flow): InfoForBooleanOperator + abstract fun collectInfoForBooleanOperator( + leftFlow: Flow, + leftVariable: DataFlowVariable, + rightFlow: Flow, + rightVariable: DataFlowVariable + ): InfoForBooleanOperator fun orForVerifiedFacts( left: ApprovedInfos, diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/SimpleFlow.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/SimpleFlow.kt deleted file mode 100644 index 29efe74e4dc..00000000000 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/SimpleFlow.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.fir.resolve.dfa - -import com.google.common.collect.HashMultimap - -class SimpleFlow private constructor( - val approvedInfos: MutableMap, - val conditionalInfos: HashMultimap, - private var state: State = State.Building -) { - constructor( - approvedInfos: MutableMap = mutableMapOf(), - conditionalInfos: HashMultimap = HashMultimap.create() - ) : this(approvedInfos, conditionalInfos, State.Building) - - companion object { - val EMPTY = SimpleFlow(mutableMapOf(), HashMultimap.create(), State.Frozen) - } - - private val isFrozen: Boolean get() = state == State.Frozen - - fun freeze() { - state = State.Frozen - } - - fun addApprovedFact(variable: RealDataFlowVariable, info: FirDataFlowInfo): SimpleFlow { - if (isFrozen) return copyForBuilding().addApprovedFact(variable, info) - approvedInfos.compute(variable) { _, existingInfo -> - if (existingInfo == null) info - else existingInfo + info - } - return this - } - - fun addNotApprovedFact(variable: DataFlowVariable, info: ConditionalFirDataFlowInfo): SimpleFlow { - if (isFrozen) return copyForBuilding().addNotApprovedFact(variable, info) - conditionalInfos.put(variable, info) - return this - } - - fun copyNotApprovedFacts( - from: DataFlowVariable, - to: DataFlowVariable, - transform: ((ConditionalFirDataFlowInfo) -> ConditionalFirDataFlowInfo)? = null - ): SimpleFlow { - if (isFrozen) { - return copyForBuilding().copyNotApprovedFacts(from, to, transform) - } - - var facts = if (from.isSynthetic()) { - conditionalInfos.removeAll(from) - } else { - conditionalInfos[from] - } - if (transform != null) { - facts = facts.mapTo(mutableSetOf(), transform) - } - conditionalInfos.putAll(to, facts) - return this - } - - fun approvedFacts(variable: RealDataFlowVariable): FirDataFlowInfo? { - return approvedInfos[variable] - } - - fun removeVariableFromFlow(variable: DataFlowVariable): SimpleFlow { - if (isFrozen) return copyForBuilding().removeVariableFromFlow(variable) - conditionalInfos.removeAll(variable) - approvedInfos.remove(variable) - return this - } - - private enum class State { - Building, Frozen - } - - fun copy(): SimpleFlow { - return when (state) { - State.Frozen -> this - State.Building -> copyForBuilding() - } - } - - fun copyForBuilding(): SimpleFlow { - return SimpleFlow(approvedInfos.toMutableMap(), conditionalInfos.copy(), State.Building) - } -} - -private fun HashMultimap.copy(): HashMultimap = HashMultimap.create(this) \ No newline at end of file diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/SimpleLogicSystem.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/SimpleLogicSystem.kt index 2a073c9cccb..a22cee24eb2 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/SimpleLogicSystem.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/SimpleLogicSystem.kt @@ -5,134 +5,101 @@ package org.jetbrains.kotlin.fir.resolve.dfa +import com.google.common.collect.ArrayListMultimap import com.google.common.collect.HashMultimap -import org.jetbrains.kotlin.fir.types.ConeKotlinType +import com.google.common.collect.Multimap -class SimpleLogicSystem(private val context: DataFlowInferenceContext) { - private fun List>.intersectSets(): Set = takeIf { isNotEmpty() }?.reduce { x, y -> x.intersect(y) } ?: emptySet() +private class SimpleFlow( + val approvedInfos: MutableApprovedInfos = mutableMapOf(), + val conditionalInfos: ConditionalInfos = HashMultimap.create() +) : Flow { + override fun getApprovedInfo(variable: RealDataFlowVariable): FirDataFlowInfo? { + return approvedInfos[variable] + } - fun or(storages: Collection): SimpleFlow { - storages.singleOrNull()?.let { - return it - } - val approvedFacts = mutableMapOf() - storages.map { it.approvedInfos.keys } + override fun getConditionalInfos(variable: DataFlowVariable): Collection { + return conditionalInfos[variable] + } + + override fun getVariablesInApprovedInfos(): Collection { + return approvedInfos.keys + } + + override fun removeConditionalInfos(variable: DataFlowVariable): Collection { + return conditionalInfos.removeAll(variable) + } +} + +abstract class SimpleLogicSystem(context: DataFlowInferenceContext) : LogicSystem(context) { + override val Flow.approvedInfos: MutableApprovedInfos + get() = (this as SimpleFlow).approvedInfos + + override val Flow.conditionalInfos: ConditionalInfos + get() = (this as SimpleFlow).conditionalInfos + + override fun createEmptyFlow(): Flow { + return SimpleFlow() + } + + override fun forkFlow(flow: Flow): Flow { + require(flow is SimpleFlow) + return SimpleFlow( + flow.approvedInfos.mapValuesTo(mutableMapOf()) { (_, info) -> info.copy() }, + flow.conditionalInfos.copy() + ) + } + + override fun joinFlow(flows: Collection): Flow { + @Suppress("UNCHECKED_CAST", "NAME_SHADOWING") + val flows = flows as Collection + flows.singleOrNull()?.let { return it } + val approvedFacts: MutableApprovedInfos = mutableMapOf() + flows.map { it.approvedInfos.keys } .intersectSets() .forEach { variable -> - val infos = storages.map { it.approvedInfos[variable]!! } + val infos = flows.map { it.approvedInfos[variable]!! } if (infos.isNotEmpty()) { approvedFacts[variable] = or(infos) } } - - val notApprovedFacts = HashMultimap.create() - storages.map { it.conditionalInfos.keySet() } + val notApprovedFacts: ConditionalInfos = ArrayListMultimap.create() + flows.map { it.conditionalInfos.keySet() } .intersectSets() .forEach { variable -> - val infos = storages.map { it.conditionalInfos[variable] }.intersectSets() + val infos = flows.map { it.conditionalInfos[variable] }.intersectSets() if (infos.isNotEmpty()) { notApprovedFacts.putAll(variable, infos) } } - return SimpleFlow(approvedFacts, notApprovedFacts) + val result = SimpleFlow(approvedFacts, notApprovedFacts) + updateAllReceivers(result) + return result } - fun andForVerifiedFacts( - left: Map?, - right: Map? - ): Map? { - if (left.isNullOrEmpty()) return right - if (right.isNullOrEmpty()) return left + override fun collectInfoForBooleanOperator( + leftFlow: Flow, + leftVariable: DataFlowVariable, + rightFlow: Flow, + rightVariable: DataFlowVariable + ): InfoForBooleanOperator { + require(leftFlow is SimpleFlow && rightFlow is SimpleFlow) + return InfoForBooleanOperator( + leftFlow.conditionalInfos[leftVariable], + rightFlow.conditionalInfos[rightVariable], + rightFlow.approvedInfos - leftFlow.approvedInfos + ) + } - val map = mutableMapOf() - for (variable in left.keys.union(right.keys)) { - val leftInfo = left[variable] - val rightInfo = right[variable] - map[variable] = and(listOfNotNull(leftInfo, rightInfo)) + private operator fun ApprovedInfos.minus(other: ApprovedInfos): ApprovedInfos { + val result: MutableApprovedInfos = mutableMapOf() + forEach { (variable, info) -> + require(info is MutableFirDataFlowInfo) + result[variable] = other[variable]?.let { info - it } ?: info } - return map + return result } +} - fun orForVerifiedFacts( - left: Map?, - right: Map? - ): Map? { - if (left.isNullOrEmpty() || right.isNullOrEmpty()) return null - val map = mutableMapOf() - for (variable in left.keys.intersect(right.keys)) { - val leftInfo = left[variable]!! - val rightInfo = right[variable]!! - map[variable] = or(listOf(leftInfo, rightInfo)) - } - return map - } - - fun approveFactsInsideFlow(variable: DataFlowVariable, condition: Condition, flow: SimpleFlow): Pair> { - val notApprovedFacts: Set = flow.conditionalInfos[variable] - if (notApprovedFacts.isEmpty()) { - return flow to emptyList() - } - @Suppress("NAME_SHADOWING") - val flow = flow.copyForBuilding() - val newFacts = HashMultimap.create() - notApprovedFacts.forEach { - if (it.condition == condition) { - newFacts.put(it.variable, it.info) - } - } - val updatedReceivers = mutableSetOf() - - newFacts.asMap().forEach { (variable, infos) -> - @Suppress("NAME_SHADOWING") - val infos = ArrayList(infos) - flow.approvedInfos[variable]?.let { - infos.add(it) - } - flow.approvedInfos[variable] = and(infos) - if (variable.isThisReference) { - updatedReceivers += variable - } - } - return flow to updatedReceivers - } - - fun approveFact(variable: DataFlowVariable, condition: Condition, flow: SimpleFlow): MutableMap { - val notApprovedFacts: Set = flow.conditionalInfos[variable] - if (notApprovedFacts.isEmpty()) { - return mutableMapOf() - } - val newFacts = HashMultimap.create() - notApprovedFacts.forEach { - if (it.condition == condition) { - newFacts.put(it.variable, it.info) - } - } - return newFacts.asMap().mapValuesTo(mutableMapOf()) { (_, infos) -> and(infos) } - } - - private fun or(infos: Collection): FirDataFlowInfo { - infos.singleOrNull()?.let { return it } - val exactType = orTypes(infos.map { it.exactType }) - val exactNotType = orTypes(infos.map { it.exactNotType }) - return FirDataFlowInfo(exactType, exactNotType) - } - - private fun orTypes(types: Collection>): Set { - if (types.any { it.isEmpty() }) return emptySet() - val allTypes = types.flatMapTo(mutableSetOf()) { it } - val commonTypes = allTypes.toMutableSet() - types.forEach { commonTypes.retainAll(it) } - val differentTypes = allTypes - commonTypes - context.commonSuperTypeOrNull(differentTypes.toList())?.let { commonTypes += it } - return commonTypes - } - - private fun and(infos: Collection): FirDataFlowInfo { - infos.singleOrNull()?.let { return it } - val exactType = infos.flatMapTo(mutableSetOf()) { it.exactType } - val exactNotType = infos.flatMapTo(mutableSetOf()) { it.exactNotType } - return FirDataFlowInfo(exactType, exactNotType) - } -} \ No newline at end of file +private fun Multimap.copy(): Multimap = ArrayListMultimap.create(this) \ No newline at end of file