From d6cb75ca919bb4f9eea76a60ebb2f8a64f998fbc Mon Sep 17 00:00:00 2001 From: Marco Pennekamp Date: Tue, 20 Dec 2022 18:41:23 +0100 Subject: [PATCH] [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 --- ...Fe10SymbolContainingDeclarationProvider.kt | 2 +- .../structure/impl/KtLibraryModuleImpl.kt | 2 + .../impl/KtLibrarySourceModuleImpl.kt | 2 + .../impl/KtNotUnderContentRootModuleImpl.kt | 2 + .../project/structure/impl/KtSdkModuleImpl.kt | 2 + .../structure/impl/KtSourceModuleImpl.kt | 2 + .../KtSourceModuleByCompilerConfiguration.kt | 8 ++-- .../project/structure/KtSourceModuleImpl.kt | 23 ++++++---- .../api/fir/sessions/LLFirSessionFactory.kt | 44 ++++++++++++------- ...sApiFirOutOfContentRootTestConfigurator.kt | 3 ++ .../analysis/project/structure/KtModule.kt | 15 +++++-- .../project/structure/ktModuleUtils.kt | 14 +++++- .../tests/multiplatform/hmpp/simple.kt | 1 - .../noSymbolProvidersDuplicationInDiamond.kt | 1 - .../org/jetbrains/kotlin/utils/sortUtils.kt | 41 +++++++++++++++++ 15 files changed, 126 insertions(+), 36 deletions(-) create mode 100644 compiler/util/src/org/jetbrains/kotlin/utils/sortUtils.kt diff --git a/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/components/KtFe10SymbolContainingDeclarationProvider.kt b/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/components/KtFe10SymbolContainingDeclarationProvider.kt index 5e6eaf5f864..67f5d6a12a6 100644 --- a/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/components/KtFe10SymbolContainingDeclarationProvider.kt +++ b/analysis/analysis-api-fe10/src/org/jetbrains/kotlin/analysis/api/descriptors/components/KtFe10SymbolContainingDeclarationProvider.kt @@ -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 = listOf(libraryPath) override val directRegularDependencies: List = emptyList() override val directDependsOnDependencies: List = emptyList() + override val transitiveDependsOnDependencies: List = emptyList() override val directFriendDependencies: List = emptyList() override val contentScope: GlobalSearchScope = ProjectScope.getLibrariesScope(project) override val platform: TargetPlatform diff --git a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtLibraryModuleImpl.kt b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtLibraryModuleImpl.kt index a158551ee31..7dd1da851b6 100644 --- a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtLibraryModuleImpl.kt +++ b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtLibraryModuleImpl.kt @@ -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 by lazy { computeTransitiveDependsOnDependencies(directDependsOnDependencies) } override val analyzerServices: PlatformDependentAnalyzerServices = super.analyzerServices override fun getBinaryRoots(): Collection = binaryRoots diff --git a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtLibrarySourceModuleImpl.kt b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtLibrarySourceModuleImpl.kt index 2dca9c9df20..1713de9c601 100644 --- a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtLibrarySourceModuleImpl.kt +++ b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtLibrarySourceModuleImpl.kt @@ -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 by lazy { computeTransitiveDependsOnDependencies(directDependsOnDependencies) } override val analyzerServices: PlatformDependentAnalyzerServices = super.analyzerServices } \ No newline at end of file diff --git a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtNotUnderContentRootModuleImpl.kt b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtNotUnderContentRootModuleImpl.kt index d5c8b49b285..904fe5517de 100644 --- a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtNotUnderContentRootModuleImpl.kt +++ b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtNotUnderContentRootModuleImpl.kt @@ -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 by lazy { computeTransitiveDependsOnDependencies(directDependsOnDependencies) } override val analyzerServices: PlatformDependentAnalyzerServices = super.analyzerServices override val contentScope: GlobalSearchScope = diff --git a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtSdkModuleImpl.kt b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtSdkModuleImpl.kt index 45f859187d0..58fa8d48412 100644 --- a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtSdkModuleImpl.kt +++ b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtSdkModuleImpl.kt @@ -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, override val sdkName: String, ) : KtSdkModule, KtModuleWithPlatform { + override val transitiveDependsOnDependencies: List by lazy { computeTransitiveDependsOnDependencies(directDependsOnDependencies) } override val analyzerServices: PlatformDependentAnalyzerServices = super.analyzerServices override fun getBinaryRoots(): Collection = binaryRoots diff --git a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtSourceModuleImpl.kt b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtSourceModuleImpl.kt index 8bd4469e6a9..a6c8470f1f4 100644 --- a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtSourceModuleImpl.kt +++ b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtSourceModuleImpl.kt @@ -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, ) : KtSourceModule, KtModuleWithPlatform { + override val transitiveDependsOnDependencies: List by lazy { computeTransitiveDependsOnDependencies(directDependsOnDependencies) } override val analyzerServices: PlatformDependentAnalyzerServices = super.analyzerServices } \ No newline at end of file diff --git a/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/project/structure/KtSourceModuleByCompilerConfiguration.kt b/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/project/structure/KtSourceModuleByCompilerConfiguration.kt index 633e4bf549d..b97106d475e 100644 --- a/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/project/structure/KtSourceModuleByCompilerConfiguration.kt +++ b/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/project/structure/KtSourceModuleByCompilerConfiguration.kt @@ -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 by lazy { computeTransitiveDependsOnDependencies(directDependsOnDependencies) } + val directFriendDependencies: List 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 get() = emptyList() override val directDependsOnDependencies: List get() = emptyList() + override val transitiveDependsOnDependencies: List get() = emptyList() override val directFriendDependencies: List get() = emptyList() override val contentScope: GlobalSearchScope get() = ProjectScope.getLibrariesScope(project) override val platform: TargetPlatform get() = module.platform diff --git a/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/project/structure/KtSourceModuleImpl.kt b/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/project/structure/KtSourceModuleImpl.kt index 3ede9c03432..3db19ca15a5 100644 --- a/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/project/structure/KtSourceModuleImpl.kt +++ b/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/project/structure/KtSourceModuleImpl.kt @@ -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 - val directDependsOnDependencies: MutableList - val directFriendDependencies: MutableList +abstract class KtModuleWithModifiableDependencies { + abstract val directRegularDependencies: MutableList + abstract val directDependsOnDependencies: MutableList + abstract val directFriendDependencies: MutableList + + /** + * When dependencies are modifiable, transitive `dependsOn` dependencies must be recomputed each time as [directDependsOnDependencies] + * may have been mutated. + */ + val transitiveDependsOnDependencies: List + 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 = mutableListOf() @@ -40,7 +47,7 @@ class KtJdkModuleImpl( override val contentScope: GlobalSearchScope, override val project: Project, private val binaryRoots: Collection, -) : 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, override var librarySources: KtLibrarySourceModule?, -) : KtLibraryModule, KtModuleWithModifiableDependencies { +) : KtModuleWithModifiableDependencies(), KtLibraryModule { override val analyzerServices: PlatformDependentAnalyzerServices get() = platform.getAnalyzerServices() override fun getBinaryRoots(): Collection = 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 = mutableListOf() diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionFactory.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionFactory.kt index 54a2a579249..261028281a0 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionFactory.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionFactory.kt @@ -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) } diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/test/configurators/AnalysisApiFirOutOfContentRootTestConfigurator.kt b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/test/configurators/AnalysisApiFirOutOfContentRootTestConfigurator.kt index 2762df4f71d..73b86d33226 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/test/configurators/AnalysisApiFirOutOfContentRootTestConfigurator.kt +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/test/configurators/AnalysisApiFirOutOfContentRootTestConfigurator.kt @@ -79,6 +79,9 @@ private class KtNotUnderContentRootModuleForTest( override val directDependsOnDependencies: List get() = emptyList() + override val transitiveDependsOnDependencies: List + get() = emptyList() + override val directFriendDependencies: List get() = emptyList() diff --git a/analysis/project-structure/src/org/jetbrains/kotlin/analysis/project/structure/KtModule.kt b/analysis/project-structure/src/org/jetbrains/kotlin/analysis/project/structure/KtModule.kt index 54a3d979e93..e927a942c11 100644 --- a/analysis/project-structure/src/org/jetbrains/kotlin/analysis/project/structure/KtModule.kt +++ b/analysis/project-structure/src/org/jetbrains/kotlin/analysis/project/structure/KtModule.kt @@ -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 @@ -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 + /** + * 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 + /** * 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 @@ -159,6 +167,7 @@ public class KtBuiltinsModule( ) : KtBinaryModule { override val directRegularDependencies: List get() = emptyList() override val directDependsOnDependencies: List get() = emptyList() + override val transitiveDependsOnDependencies: List get() = emptyList() override val directFriendDependencies: List get() = emptyList() override val contentScope: GlobalSearchScope get() = GlobalSearchScope.EMPTY_SCOPE override fun getBinaryRoots(): Collection = emptyList() diff --git a/analysis/project-structure/src/org/jetbrains/kotlin/analysis/project/structure/ktModuleUtils.kt b/analysis/project-structure/src/org/jetbrains/kotlin/analysis/project/structure/ktModuleUtils.kt index d007a57c750..ea85e161a75 100644 --- a/analysis/project-structure/src/org/jetbrains/kotlin/analysis/project/structure/ktModuleUtils.kt +++ b/analysis/project-structure/src/org/jetbrains/kotlin/analysis/project/structure/ktModuleUtils.kt @@ -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 = * @see KtModule.directFriendDependencies */ public inline fun KtModule.allDirectDependenciesOfType(): Sequence = - allDirectDependencies().filterIsInstance() \ No newline at end of file + allDirectDependencies().filterIsInstance() + +/** + * 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): List = + topologicalSort(directDependsOnDependencies) { this.directDependsOnDependencies } diff --git a/compiler/testData/diagnostics/tests/multiplatform/hmpp/simple.kt b/compiler/testData/diagnostics/tests/multiplatform/hmpp/simple.kt index 8df3cabd382..fd79eff6cdc 100644 --- a/compiler/testData/diagnostics/tests/multiplatform/hmpp/simple.kt +++ b/compiler/testData/diagnostics/tests/multiplatform/hmpp/simple.kt @@ -1,5 +1,4 @@ // FIR_IDENTICAL -// FIR_IDE_IGNORE // MODULE: common // TARGET_PLATFORM: Common diff --git a/compiler/testData/diagnostics/tests/noSymbolProvidersDuplicationInDiamond.kt b/compiler/testData/diagnostics/tests/noSymbolProvidersDuplicationInDiamond.kt index faca9a00b4b..e2211593428 100644 --- a/compiler/testData/diagnostics/tests/noSymbolProvidersDuplicationInDiamond.kt +++ b/compiler/testData/diagnostics/tests/noSymbolProvidersDuplicationInDiamond.kt @@ -1,5 +1,4 @@ // FIR_IDENTICAL -// FIR_IDE_IGNORE // !LANGUAGE: +MultiPlatformProjects // TARGET_BACKEND: JVM diff --git a/compiler/util/src/org/jetbrains/kotlin/utils/sortUtils.kt b/compiler/util/src/org/jetbrains/kotlin/utils/sortUtils.kt new file mode 100644 index 00000000000..0784cbcceee --- /dev/null +++ b/compiler/util/src/org/jetbrains/kotlin/utils/sortUtils.kt @@ -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 topologicalSort(nodes: Iterable, dependencies: A.() -> Iterable): List { + val visiting = mutableSetOf() + val visited = mutableSetOf() + + 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() } +}