[LL FIR] KT-55329 Support transitive dependsOn dependencies in LL FIR

- In contrast to other kinds of dependencies, `dependsOn` dependencies
  must be followed transitively.
- Add `transitiveDependsOnDependencies` to `KtModule`. These
  dependencies are calculated lazily with a topological sort. They are
  added to the dependency provider when it's built in
  `LLFirSessionFactory`.

^KT-55329 fixed
This commit is contained in:
Marco Pennekamp
2022-12-20 18:41:23 +01:00
committed by teamcity
parent 1803bd36cc
commit d6cb75ca91
15 changed files with 126 additions and 36 deletions
@@ -0,0 +1,41 @@
/*
* Copyright 2010-2022 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.utils
/**
* Sorts [nodes] topologically collecting direct edges via [dependencies]. [nodes] and [dependencies] must form a directed, acyclic graph.
* [topologicalSort] will throw an [IllegalStateException] if it encounters a cycle.
*
* The algorithm is based on depth-first search, starting in order from each node in [nodes]. Kahn's algorithm is harder to apply to the
* ad-hoc dependency structure because it's not easily apparent whether a node has no other incoming edges.
*
* For example, consider the following structure: `C -> A, C -> B, B -> A`. The resulting order should be `[C, B, A]`. However, `A` is
* first in the list of dependencies of `C`. Without a way to find the incoming edge from `B` to `A` while processing `C -> A`, a naive
* implementation of Kahn's algorithm might order `A` before `B`.
*/
fun <A> topologicalSort(nodes: Iterable<A>, dependencies: A.() -> Iterable<A>): List<A> {
val visiting = mutableSetOf<A>()
val visited = mutableSetOf<A>()
fun visit(node: A) {
if (node in visited) return
if (node in visiting) throw IllegalStateException("Cannot compute a topological sort: The node $node is in a cycle.")
// Keeping track of the nodes that are being visited allows the algorithm to throw an exception in case of a cycle. The input should
// never be cyclic, but this approach gives some additional safety in case of bugs.
visiting.add(node)
node.dependencies().forEach(::visit)
visiting.remove(node)
visited.add(node)
}
nodes.forEach(::visit)
// The paper algorithm inserts nodes at the head of the result list. Because our `visited` set remembers elements in their order of
// insertion, the result needs to be reversed.
return visited.toMutableList().apply { reverse() }
}