[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:
committed by
teamcity
parent
1803bd36cc
commit
d6cb75ca91
+1
-1
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeToken
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtBackingFieldSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtDeclarationSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.KtValueParameterSymbol
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolKind
|
||||
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithKind
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtLibraryModule
|
||||
@@ -72,6 +71,7 @@ internal class KtFe10SymbolContainingDeclarationProvider(
|
||||
override fun getBinaryRoots(): Collection<Path> = listOf(libraryPath)
|
||||
override val directRegularDependencies: List<KtModule> = emptyList()
|
||||
override val directDependsOnDependencies: List<KtModule> = emptyList()
|
||||
override val transitiveDependsOnDependencies: List<KtModule> = emptyList()
|
||||
override val directFriendDependencies: List<KtModule> = emptyList()
|
||||
override val contentScope: GlobalSearchScope = ProjectScope.getLibrariesScope(project)
|
||||
override val platform: TargetPlatform
|
||||
|
||||
+2
@@ -10,6 +10,7 @@ import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtLibraryModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtLibrarySourceModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.computeTransitiveDependsOnDependencies
|
||||
import org.jetbrains.kotlin.platform.TargetPlatform
|
||||
import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
|
||||
import java.nio.file.Path
|
||||
@@ -25,6 +26,7 @@ internal class KtLibraryModuleImpl(
|
||||
override val libraryName: String,
|
||||
override val librarySources: KtLibrarySourceModule?,
|
||||
) : KtLibraryModule, KtModuleWithPlatform {
|
||||
override val transitiveDependsOnDependencies: List<KtModule> by lazy { computeTransitiveDependsOnDependencies(directDependsOnDependencies) }
|
||||
override val analyzerServices: PlatformDependentAnalyzerServices = super.analyzerServices
|
||||
|
||||
override fun getBinaryRoots(): Collection<Path> = binaryRoots
|
||||
|
||||
+2
@@ -10,6 +10,7 @@ import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtLibraryModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtLibrarySourceModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.computeTransitiveDependsOnDependencies
|
||||
import org.jetbrains.kotlin.platform.TargetPlatform
|
||||
import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
|
||||
|
||||
@@ -23,5 +24,6 @@ internal class KtLibrarySourceModuleImpl(
|
||||
override val libraryName: String,
|
||||
override val binaryLibrary: KtLibraryModule,
|
||||
) : KtLibrarySourceModule, KtModuleWithPlatform {
|
||||
override val transitiveDependsOnDependencies: List<KtModule> by lazy { computeTransitiveDependsOnDependencies(directDependsOnDependencies) }
|
||||
override val analyzerServices: PlatformDependentAnalyzerServices = super.analyzerServices
|
||||
}
|
||||
+2
@@ -10,6 +10,7 @@ import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtNotUnderContentRootModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.computeTransitiveDependsOnDependencies
|
||||
import org.jetbrains.kotlin.platform.TargetPlatform
|
||||
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
|
||||
import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
|
||||
@@ -24,6 +25,7 @@ internal class KtNotUnderContentRootModuleImpl(
|
||||
override val moduleDescription: String,
|
||||
override val project: Project,
|
||||
) : KtNotUnderContentRootModule, KtModuleWithPlatform {
|
||||
override val transitiveDependsOnDependencies: List<KtModule> by lazy { computeTransitiveDependsOnDependencies(directDependsOnDependencies) }
|
||||
override val analyzerServices: PlatformDependentAnalyzerServices = super.analyzerServices
|
||||
|
||||
override val contentScope: GlobalSearchScope =
|
||||
|
||||
+2
@@ -9,6 +9,7 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtSdkModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.computeTransitiveDependsOnDependencies
|
||||
import org.jetbrains.kotlin.platform.TargetPlatform
|
||||
import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
|
||||
import java.nio.file.Path
|
||||
@@ -23,6 +24,7 @@ internal class KtSdkModuleImpl(
|
||||
private val binaryRoots: Collection<Path>,
|
||||
override val sdkName: String,
|
||||
) : KtSdkModule, KtModuleWithPlatform {
|
||||
override val transitiveDependsOnDependencies: List<KtModule> by lazy { computeTransitiveDependsOnDependencies(directDependsOnDependencies) }
|
||||
override val analyzerServices: PlatformDependentAnalyzerServices = super.analyzerServices
|
||||
|
||||
override fun getBinaryRoots(): Collection<Path> = binaryRoots
|
||||
|
||||
+2
@@ -10,6 +10,7 @@ import com.intellij.psi.PsiFileSystemItem
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtSourceModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.computeTransitiveDependsOnDependencies
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.platform.TargetPlatform
|
||||
import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
|
||||
@@ -25,5 +26,6 @@ internal class KtSourceModuleImpl(
|
||||
override val languageVersionSettings: LanguageVersionSettings,
|
||||
internal val sourceRoots: List<PsiFileSystemItem>,
|
||||
) : KtSourceModule, KtModuleWithPlatform {
|
||||
override val transitiveDependsOnDependencies: List<KtModule> by lazy { computeTransitiveDependsOnDependencies(directDependsOnDependencies) }
|
||||
override val analyzerServices: PlatformDependentAnalyzerServices = super.analyzerServices
|
||||
}
|
||||
+4
-4
@@ -9,10 +9,7 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.ProjectScope
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtLibraryModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtLibrarySourceModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.KtSourceModule
|
||||
import org.jetbrains.kotlin.analysis.project.structure.*
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
|
||||
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
|
||||
import org.jetbrains.kotlin.cli.jvm.config.jvmModularRoots
|
||||
@@ -60,6 +57,8 @@ abstract class KtModuleByCompilerConfiguration(
|
||||
.map { moduleProvider.getModule(it.moduleName) }
|
||||
}
|
||||
|
||||
val transitiveDependsOnDependencies: List<KtModule> by lazy { computeTransitiveDependsOnDependencies(directDependsOnDependencies) }
|
||||
|
||||
val directFriendDependencies: List<KtModule> by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||
buildList {
|
||||
testModule.friendDependencies.mapTo(this) { moduleProvider.getModule(it.moduleName) }
|
||||
@@ -140,6 +139,7 @@ private class LibraryByRoots(
|
||||
override val libraryName: String get() = "Test Library"
|
||||
override val directRegularDependencies: List<KtModule> get() = emptyList()
|
||||
override val directDependsOnDependencies: List<KtModule> get() = emptyList()
|
||||
override val transitiveDependsOnDependencies: List<KtModule> get() = emptyList()
|
||||
override val directFriendDependencies: List<KtModule> get() = emptyList()
|
||||
override val contentScope: GlobalSearchScope get() = ProjectScope.getLibrariesScope(project)
|
||||
override val platform: TargetPlatform get() = module.platform
|
||||
|
||||
+15
-8
@@ -14,10 +14,17 @@ import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
|
||||
import org.jetbrains.kotlin.test.getAnalyzerServices
|
||||
import java.nio.file.Path
|
||||
|
||||
interface KtModuleWithModifiableDependencies {
|
||||
val directRegularDependencies: MutableList<KtModule>
|
||||
val directDependsOnDependencies: MutableList<KtModule>
|
||||
val directFriendDependencies: MutableList<KtModule>
|
||||
abstract class KtModuleWithModifiableDependencies {
|
||||
abstract val directRegularDependencies: MutableList<KtModule>
|
||||
abstract val directDependsOnDependencies: MutableList<KtModule>
|
||||
abstract val directFriendDependencies: MutableList<KtModule>
|
||||
|
||||
/**
|
||||
* When dependencies are modifiable, transitive `dependsOn` dependencies must be recomputed each time as [directDependsOnDependencies]
|
||||
* may have been mutated.
|
||||
*/
|
||||
val transitiveDependsOnDependencies: List<KtModule>
|
||||
get() = computeTransitiveDependsOnDependencies(directDependsOnDependencies)
|
||||
}
|
||||
|
||||
class KtSourceModuleImpl(
|
||||
@@ -26,7 +33,7 @@ class KtSourceModuleImpl(
|
||||
override val languageVersionSettings: LanguageVersionSettings,
|
||||
override val project: Project,
|
||||
override val contentScope: GlobalSearchScope,
|
||||
) : KtSourceModule, KtModuleWithModifiableDependencies {
|
||||
) : KtModuleWithModifiableDependencies(), KtSourceModule {
|
||||
override val analyzerServices: PlatformDependentAnalyzerServices get() = platform.getAnalyzerServices()
|
||||
|
||||
override val directRegularDependencies: MutableList<KtModule> = mutableListOf()
|
||||
@@ -40,7 +47,7 @@ class KtJdkModuleImpl(
|
||||
override val contentScope: GlobalSearchScope,
|
||||
override val project: Project,
|
||||
private val binaryRoots: Collection<Path>,
|
||||
) : KtSdkModule, KtModuleWithModifiableDependencies {
|
||||
) : KtModuleWithModifiableDependencies(), KtSdkModule {
|
||||
override val analyzerServices: PlatformDependentAnalyzerServices
|
||||
get() = platform.getAnalyzerServices()
|
||||
|
||||
@@ -58,7 +65,7 @@ class KtLibraryModuleImpl(
|
||||
override val project: Project,
|
||||
private val binaryRoots: Collection<Path>,
|
||||
override var librarySources: KtLibrarySourceModule?,
|
||||
) : KtLibraryModule, KtModuleWithModifiableDependencies {
|
||||
) : KtModuleWithModifiableDependencies(), KtLibraryModule {
|
||||
override val analyzerServices: PlatformDependentAnalyzerServices get() = platform.getAnalyzerServices()
|
||||
override fun getBinaryRoots(): Collection<Path> = binaryRoots
|
||||
|
||||
@@ -73,7 +80,7 @@ class KtLibrarySourceModuleImpl(
|
||||
override val contentScope: GlobalSearchScope,
|
||||
override val project: Project,
|
||||
override val binaryLibrary: KtLibraryModule,
|
||||
) : KtLibrarySourceModule, KtModuleWithModifiableDependencies {
|
||||
) : KtModuleWithModifiableDependencies(), KtLibrarySourceModule {
|
||||
override val analyzerServices: PlatformDependentAnalyzerServices get() = platform.getAnalyzerServices()
|
||||
|
||||
override val directRegularDependencies: MutableList<KtModule> = mutableListOf()
|
||||
|
||||
+27
-17
@@ -113,26 +113,36 @@ internal object LLFirSessionFactory {
|
||||
register(FirSwitchableExtensionDeclarationsSymbolProvider::class, it)
|
||||
}
|
||||
|
||||
fun getOrCreateSessionForDependency(dependency: KtModule): LLFirSession? = when (dependency) {
|
||||
is KtBuiltinsModule -> null // build in is already added
|
||||
is KtBinaryModule -> LLFirLibrarySessionFactory.getInstance(project).getLibrarySession(dependency, sessionsCache)
|
||||
is KtSourceModule -> {
|
||||
createSourcesSession(
|
||||
project,
|
||||
dependency,
|
||||
globalResolveComponents,
|
||||
sessionInvalidator,
|
||||
sessionsCache,
|
||||
librariesSessionFactory = librariesSessionFactory,
|
||||
configureSession = configureSession,
|
||||
)
|
||||
}
|
||||
is KtNotUnderContentRootModule -> error("Module $module cannot depend on ${dependency::class}: $dependency")
|
||||
is KtLibrarySourceModule -> error("Module $module cannot depend on ${dependency::class}: $dependency")
|
||||
}
|
||||
|
||||
val dependencyProvider = LLFirDependentModuleProvidersBySessions(this) {
|
||||
module.directRegularDependencies.mapNotNullTo(this) { dependency ->
|
||||
when (dependency) {
|
||||
is KtBuiltinsModule -> null // build in is already added
|
||||
is KtBinaryModule -> LLFirLibrarySessionFactory.getInstance(project).getLibrarySession(dependency, sessionsCache)
|
||||
is KtSourceModule -> {
|
||||
createSourcesSession(
|
||||
project,
|
||||
dependency,
|
||||
globalResolveComponents,
|
||||
sessionInvalidator,
|
||||
sessionsCache,
|
||||
librariesSessionFactory = librariesSessionFactory,
|
||||
configureSession = configureSession,
|
||||
)
|
||||
}
|
||||
is KtNotUnderContentRootModule -> error("Module $module cannot depend on ${dependency::class}: $dependency")
|
||||
is KtLibrarySourceModule -> error("Module $module cannot depend on ${dependency::class}: $dependency")
|
||||
module.directRegularDependencies.mapNotNullTo(this, ::getOrCreateSessionForDependency)
|
||||
|
||||
// The dependency provider needs to have access to all direct and indirect `dependsOn` dependencies, as `dependsOn`
|
||||
// dependencies are transitive.
|
||||
val directRegularDependenciesSet = module.directRegularDependencies.toSet()
|
||||
module.transitiveDependsOnDependencies.forEach { dependency ->
|
||||
if (dependency !in directRegularDependenciesSet) {
|
||||
getOrCreateSessionForDependency(dependency)?.let(::add)
|
||||
}
|
||||
}
|
||||
|
||||
add(builtinsSession)
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -79,6 +79,9 @@ private class KtNotUnderContentRootModuleForTest(
|
||||
override val directDependsOnDependencies: List<KtModule>
|
||||
get() = emptyList()
|
||||
|
||||
override val transitiveDependsOnDependencies: List<KtModule>
|
||||
get() = emptyList()
|
||||
|
||||
override val directFriendDependencies: List<KtModule>
|
||||
get() = emptyList()
|
||||
|
||||
|
||||
+12
-3
@@ -25,7 +25,7 @@ public sealed interface KtModule {
|
||||
* A list of Regular dependencies. Regular dependency allows the current module to see symbols from the dependent module. In the case
|
||||
* of a source set, it can be either the source set it depends on, a library, or an SDK.
|
||||
*
|
||||
* The dependencies list is non-transitive and should not include the current module.
|
||||
* The dependencies list is non-transitive and does not include the current module.
|
||||
*/
|
||||
public val directRegularDependencies: List<KtModule>
|
||||
|
||||
@@ -35,14 +35,22 @@ public sealed interface KtModule {
|
||||
* A `dependsOn` dependency expresses that the current module can provide `actual` declarations for `expect` declarations from the
|
||||
* dependent module, as well as see internal symbols of the dependent module.
|
||||
*
|
||||
* `dependsOn` dependencies are transitive, but the list is not a transitive closure. The list should not include the current module.
|
||||
* `dependsOn` dependencies are transitive, but the list is not a transitive closure. The list does not include the current module.
|
||||
*/
|
||||
public val directDependsOnDependencies: List<KtModule>
|
||||
|
||||
/**
|
||||
* A list of [directDependsOnDependencies] and all of their parents (directly and indirectly), sorted topologically with the nearest
|
||||
* dependencies first in the list. The list does not include the current module.
|
||||
*
|
||||
* @see computeTransitiveDependsOnDependencies
|
||||
*/
|
||||
public val transitiveDependsOnDependencies: List<KtModule>
|
||||
|
||||
/**
|
||||
* A list of Friend dependencies. Friend dependencies express that the current module may see internal symbols of the dependent module.
|
||||
*
|
||||
* The dependencies list is non-transitive and should not include the current module.
|
||||
* The dependencies list is non-transitive and does not include the current module.
|
||||
*/
|
||||
public val directFriendDependencies: List<KtModule>
|
||||
|
||||
@@ -159,6 +167,7 @@ public class KtBuiltinsModule(
|
||||
) : KtBinaryModule {
|
||||
override val directRegularDependencies: List<KtModule> get() = emptyList()
|
||||
override val directDependsOnDependencies: List<KtModule> get() = emptyList()
|
||||
override val transitiveDependsOnDependencies: List<KtModule> get() = emptyList()
|
||||
override val directFriendDependencies: List<KtModule> get() = emptyList()
|
||||
override val contentScope: GlobalSearchScope get() = GlobalSearchScope.EMPTY_SCOPE
|
||||
override fun getBinaryRoots(): Collection<Path> = emptyList()
|
||||
|
||||
+13
-1
@@ -5,6 +5,8 @@
|
||||
|
||||
package org.jetbrains.kotlin.analysis.project.structure
|
||||
|
||||
import org.jetbrains.kotlin.utils.topologicalSort
|
||||
|
||||
/**
|
||||
* A list of all modules that the current module can depend on with regular dependency.
|
||||
*
|
||||
@@ -51,4 +53,14 @@ public fun KtModule.allDirectDependencies(): Sequence<KtModule> =
|
||||
* @see KtModule.directFriendDependencies
|
||||
*/
|
||||
public inline fun <reified M : KtModule> KtModule.allDirectDependenciesOfType(): Sequence<M> =
|
||||
allDirectDependencies().filterIsInstance<M>()
|
||||
allDirectDependencies().filterIsInstance<M>()
|
||||
|
||||
/**
|
||||
* Computes the transitive `dependsOn` dependencies of [directDependsOnDependencies]. [computeTransitiveDependsOnDependencies] is the
|
||||
* default computation strategy to provide [KtModule.transitiveDependsOnDependencies].
|
||||
*
|
||||
* The algorithm is a depth-first search-based topological sort. `dependsOn` dependencies cannot be cyclical and thus form a DAG, which
|
||||
* allows the application of a topological sort.
|
||||
*/
|
||||
public fun computeTransitiveDependsOnDependencies(directDependsOnDependencies: List<KtModule>): List<KtModule> =
|
||||
topologicalSort(directDependsOnDependencies) { this.directDependsOnDependencies }
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// FIR_IDE_IGNORE
|
||||
|
||||
// MODULE: common
|
||||
// TARGET_PLATFORM: Common
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// FIR_IDE_IGNORE
|
||||
// !LANGUAGE: +MultiPlatformProjects
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
Reference in New Issue
Block a user