From ae40ddcf03ea858af49f54b667f1b5c4a3d6857b Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Mon, 27 Feb 2023 20:18:05 +0900 Subject: [PATCH] [LL API] Provide basic support for scripts in K2 (KTIJ-21108) --- .../builder/KtScriptModuleBuilder.kt | 45 ++++ .../project/structure/impl/KtModuleUtils.kt | 94 ++++---- .../structure/impl/KtScriptModuleImpl.kt | 37 +++ .../level/api/fir/LLFirResolveStateService.kt | 10 + .../low/level/api/fir/api/FirDesignation.kt | 12 +- ...extCollectingDiagnosticCollectorVisitor.kt | 1 + ...FirModuleWithDependenciesSymbolProvider.kt | 5 + .../api/fir/sessions/LLFirScriptSession.kt | 19 ++ .../api/fir/sessions/LLFirSessionFactory.kt | 223 ++++++++++++++---- .../sessions/LLFirSessionProviderStorage.kt | 25 ++ .../state/LLFirResolvableResolveSession.kt | 10 +- .../fir/state/LLFirScriptResolveSession.kt | 74 ++++++ .../analysis/project/structure/KtModule.kt | 29 +++ 13 files changed, 490 insertions(+), 94 deletions(-) create mode 100644 analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/builder/KtScriptModuleBuilder.kt create mode 100644 analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtScriptModuleImpl.kt create mode 100644 analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirScriptSession.kt create mode 100644 analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/state/LLFirScriptResolveSession.kt diff --git a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/builder/KtScriptModuleBuilder.kt b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/builder/KtScriptModuleBuilder.kt new file mode 100644 index 00000000000..d9061f5d356 --- /dev/null +++ b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/builder/KtScriptModuleBuilder.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2010-2023 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.analysis.project.structure.builder + +import org.jetbrains.kotlin.analysis.project.structure.KtScriptModule +import org.jetbrains.kotlin.analysis.project.structure.impl.KtScriptModuleImpl +import org.jetbrains.kotlin.config.ApiVersion +import org.jetbrains.kotlin.config.LanguageVersion +import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl +import org.jetbrains.kotlin.psi.KtFile +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +@KtModuleBuilderDsl +public class KtScriptModuleBuilder : KtModuleBuilder() { + public lateinit var file: KtFile + + public var languageVersionSettings: LanguageVersionSettings = + LanguageVersionSettingsImpl(LanguageVersion.LATEST_STABLE, ApiVersion.LATEST) + + override fun build(): KtScriptModule { + return KtScriptModuleImpl( + directRegularDependencies, + directDependsOnDependencies, + directFriendDependencies, + platform, + project, + file, + languageVersionSettings + ) + } +} + +@OptIn(ExperimentalContracts::class) +public inline fun buildKtScriptModule(init: KtScriptModuleBuilder.() -> Unit): KtScriptModule { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + return KtScriptModuleBuilder().apply(init).build() +} \ No newline at end of file diff --git a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtModuleUtils.kt b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtModuleUtils.kt index 41938e25f1e..8dfc85826c5 100644 --- a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtModuleUtils.kt +++ b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtModuleUtils.kt @@ -17,10 +17,7 @@ import com.intellij.psi.search.ProjectScope import com.intellij.util.io.URLUtil import org.jetbrains.kotlin.analysis.api.impl.base.util.LibraryUtils import org.jetbrains.kotlin.analysis.project.structure.ProjectStructureProvider -import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtLibraryModule -import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtSdkModule -import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtSourceModule -import org.jetbrains.kotlin.analysis.project.structure.builder.buildProjectStructureProvider +import org.jetbrains.kotlin.analysis.project.structure.builder.* import org.jetbrains.kotlin.analyzer.common.CommonPlatformAnalyzerServices import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM import org.jetbrains.kotlin.cli.jvm.config.javaSourceRoots @@ -154,51 +151,66 @@ internal fun buildKtModuleProviderByCompilerConfiguration( project: Project, ktFiles: List, ): ProjectStructureProvider = buildProjectStructureProvider { + val (scriptFiles, ordinaryFiles) = ktFiles.partition { it.isScript() } val platform = JvmPlatforms.defaultJvmPlatform - addModule( - buildKtSourceModule { - val moduleName = compilerConfig.get(CommonConfigurationKeys.MODULE_NAME) ?: "" - val libraryRoots = compilerConfig.jvmModularRoots + compilerConfig.jvmClasspathRoots + fun KtModuleBuilder.addModuleDependencies(moduleName: String) { + val libraryRoots = compilerConfig.jvmModularRoots + compilerConfig.jvmClasspathRoots + addRegularDependency( + buildKtLibraryModule { + contentScope = ProjectScope.getLibrariesScope(project) + this.platform = platform + this.project = project + binaryRoots = libraryRoots.map { it.toPath() } + libraryName = "Library for $moduleName" + } + ) + compilerConfig.get(JVMConfigurationKeys.JDK_HOME)?.let { jdkHome -> + val vfm = VirtualFileManager.getInstance() + val jdkHomePath = jdkHome.toPath() + val jdkHomeVirtualFile = vfm.findFileByNioPath(jdkHomePath) + val binaryRoots = LibraryUtils.findClassesFromJdkHome(jdkHomePath).map { + Paths.get(URLUtil.extractPath(it)) + } addRegularDependency( - buildKtLibraryModule { - contentScope = ProjectScope.getLibrariesScope(project) + buildKtSdkModule { + contentScope = GlobalSearchScope.fileScope(project, jdkHomeVirtualFile) this.platform = platform this.project = project - binaryRoots = libraryRoots.map { it.toPath() } - libraryName = "Library for $moduleName" + this.binaryRoots = binaryRoots + sdkName = "JDK for $moduleName" } ) - compilerConfig.get(JVMConfigurationKeys.JDK_HOME)?.let { jdkHome -> - val vfm = VirtualFileManager.getInstance() - val jdkHomePath = jdkHome.toPath() - val jdkHomeVirtualFile = vfm.findFileByNioPath(jdkHomePath) - val binaryRoots = LibraryUtils.findClassesFromJdkHome(jdkHomePath).map { - Paths.get(URLUtil.extractPath(it)) - } - addRegularDependency( - buildKtSdkModule { - contentScope = GlobalSearchScope.fileScope(project, jdkHomeVirtualFile) - this.platform = platform - this.project = project - this.binaryRoots = binaryRoots - sdkName = "JDK for $moduleName" - } - ) - } - - contentScope = TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, ktFiles) - this.platform = platform - this.project = project - this.moduleName = moduleName - addSourceRoots( - getPsiFilesFromPaths( - project, - getSourceFilePaths(compilerConfig, includeDirectoryRoot = true) - ) - ) } - ) + } + + for (scriptFile in scriptFiles) { + buildKtScriptModule { + this.project = project + this.platform = platform + this.file = scriptFile + + addModuleDependencies("Script " + scriptFile.name) + }.apply(::addModule) + } + + buildKtSourceModule { + this.project = project + this.platform = platform + this.moduleName = compilerConfig.get(CommonConfigurationKeys.MODULE_NAME) ?: "" + + addModuleDependencies(moduleName) + + contentScope = TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, ordinaryFiles) + addSourceRoots( + getPsiFilesFromPaths( + project, + getSourceFilePaths(compilerConfig, includeDirectoryRoot = true) + ) + ) + }.apply(::addModule) + + this.platform = platform this.project = project } diff --git a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtScriptModuleImpl.kt b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtScriptModuleImpl.kt new file mode 100644 index 00000000000..40d36a95b99 --- /dev/null +++ b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtScriptModuleImpl.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2023 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.analysis.project.structure.impl + +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.KtScriptModule +import org.jetbrains.kotlin.analysis.project.structure.computeTransitiveDependsOnDependencies +import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.platform.TargetPlatform +import org.jetbrains.kotlin.platform.jvm.JvmPlatforms +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices + +internal class KtScriptModuleImpl( + override val directRegularDependencies: List = emptyList(), + override val directDependsOnDependencies: List = emptyList(), + override val directFriendDependencies: List = emptyList(), + override val platform: TargetPlatform = JvmPlatforms.defaultJvmPlatform, + override val project: Project, + override val file: KtFile, + override val languageVersionSettings: LanguageVersionSettings +) : KtScriptModule, KtModuleWithPlatform { + override val transitiveDependsOnDependencies: List by lazy { + computeTransitiveDependsOnDependencies(directDependsOnDependencies) + } + + override val analyzerServices: PlatformDependentAnalyzerServices + get() = super.analyzerServices + + override val contentScope: GlobalSearchScope + get() = GlobalSearchScope.fileScope(file) +} \ No newline at end of file diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/LLFirResolveStateService.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/LLFirResolveStateService.kt index f02c531b48e..1955d5edb7b 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/LLFirResolveStateService.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/LLFirResolveStateService.kt @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirSessionProv import org.jetbrains.kotlin.analysis.low.level.api.fir.state.LLFirLibraryOrLibrarySourceResolvableResolveSession import org.jetbrains.kotlin.analysis.low.level.api.fir.state.LLFirNotUnderContentRootResolveSession import org.jetbrains.kotlin.analysis.low.level.api.fir.state.LLFirResolvableResolveSession +import org.jetbrains.kotlin.analysis.low.level.api.fir.state.LLFirScriptResolveSession import org.jetbrains.kotlin.analysis.low.level.api.fir.state.LLFirSourceResolveSession import org.jetbrains.kotlin.analysis.low.level.api.fir.util.errorWithFirSpecificEntries import org.jetbrains.kotlin.analysis.project.structure.* @@ -69,6 +70,15 @@ internal class LLFirResolveSessionService(project: Project) { ) } + is KtScriptModule -> { + LLFirScriptResolveSession( + useSiteSession.moduleComponents.globalResolveComponents, + sessionProviderStorage.project, + useSiteKtModule, + sessionProvider + ) + } + is KtNotUnderContentRootModule -> { LLFirNotUnderContentRootResolveSession( useSiteSession.moduleComponents.globalResolveComponents, diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/FirDesignation.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/FirDesignation.kt index 120c0118106..73f478f1225 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/FirDesignation.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/FirDesignation.kt @@ -100,6 +100,10 @@ private fun collectDesignationPath(target: FirElementWithResolvePhase): List { + return emptyList() + } + else -> { return null } @@ -239,15 +243,15 @@ fun FirElementWithResolvePhase.tryCollectDesignation(): FirDesignation? = fun FirElementWithResolvePhase.tryCollectDesignationWithFile(): FirDesignationWithFile? { return when (this) { + is FirScript, is FirFileAnnotationsContainer -> { + val firFile = getContainingFile() ?: return null + FirDesignationWithFile(path = emptyList(), this, firFile) + } is FirDeclaration -> { val path = collectDesignationPath(this) ?: return null val firFile = getContainingFile() ?: return null FirDesignationWithFile(path, this, firFile) } - is FirFileAnnotationsContainer -> { - val firFile = getContainingFile() ?: return null - FirDesignationWithFile(path = emptyList(), this, firFile) - } else -> unexpectedElementError(this) } } diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostics/fir/ContextCollectingDiagnosticCollectorVisitor.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostics/fir/ContextCollectingDiagnosticCollectorVisitor.kt index 490400c25cf..440d53cda88 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostics/fir/ContextCollectingDiagnosticCollectorVisitor.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostics/fir/ContextCollectingDiagnosticCollectorVisitor.kt @@ -60,6 +60,7 @@ internal object PersistenceContextCollector { is FirClassLikeDeclaration -> declaration.symbol.classId.isLocal is FirCallableDeclaration -> declaration.symbol.callableId.isLocal is FirDanglingModifierList -> declaration.containingClass()?.classId?.isLocal == true + is FirScript -> false else -> error("Unsupported declaration ${declaration.renderWithType()}") } require(!isLocal) { diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirModuleWithDependenciesSymbolProvider.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirModuleWithDependenciesSymbolProvider.kt index 0a3826275d3..3f15f214b11 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirModuleWithDependenciesSymbolProvider.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/providers/LLFirModuleWithDependenciesSymbolProvider.kt @@ -42,6 +42,11 @@ internal class LLFirModuleWithDependenciesSymbolProvider( dependencyProvider.getTopLevelCallableSymbolsTo(destination, packageFqName, name) } + @FirSymbolProviderInternals + fun getTopLevelCallableSymbolsWithoutDependencies(packageFqName: FqName, name: Name): List> { + return buildList { getTopLevelCallableSymbolsToWithoutDependencies(this, packageFqName, name) } + } + @FirSymbolProviderInternals fun getTopLevelCallableSymbolsToWithoutDependencies(destination: MutableList>, packageFqName: FqName, name: Name) { providers.forEach { it.getTopLevelCallableSymbolsTo(destination, packageFqName, name) } diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirScriptSession.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirScriptSession.kt new file mode 100644 index 00000000000..d63f3782495 --- /dev/null +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirScriptSession.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2010-2023 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.analysis.low.level.api.fir.sessions + +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirModuleResolveComponents +import org.jetbrains.kotlin.analysis.project.structure.KtScriptModule +import org.jetbrains.kotlin.fir.BuiltinTypes +import org.jetbrains.kotlin.fir.PrivateSessionConstructor + +internal class LLFirScriptSession @PrivateSessionConstructor constructor( + override val ktModule: KtScriptModule, + override val project: Project, + override val moduleComponents: LLFirModuleResolveComponents, + builtinTypes: BuiltinTypes +) : LLFirResolvableModuleSession(builtinTypes) \ No newline at end of file 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 375a42ef791..7b9e7807b70 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 @@ -58,17 +58,7 @@ internal object LLFirSessionFactory { val platform = module.platform val builtinsSession = LLFirBuiltinsSessionFactory.getInstance(project).getBuiltinsSession(platform) - - val languageVersionSettings = object : LanguageVersionSettings by module.languageVersionSettings { - override fun getFeatureSupport(feature: LanguageFeature): LanguageFeature.State = - if (feature == LanguageFeature.EnableDfaWarningsInK2) LanguageFeature.State.ENABLED - else module.languageVersionSettings.getFeatureSupport(feature) - - override fun supportsFeature(feature: LanguageFeature): Boolean = - getFeatureSupport(feature).let { - it == LanguageFeature.State.ENABLED || it == LanguageFeature.State.ENABLED_WITH_WARNING - } - } + val languageVersionSettings = wrapLanguageVersionSettings(module.languageVersionSettings) val scopeProvider = FirKotlinScopeProvider(::wrapScopeWithJvmMapped) @@ -115,35 +105,15 @@ 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, ::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) - } - } + processSourceDependencies( + module, + sessionsCache, + globalResolveComponents, + sessionInvalidator, + librariesSessionFactory, + configureSession + ) add(builtinsSession) } @@ -233,15 +203,21 @@ internal object LLFirSessionFactory { register(FirPredicateBasedProvider::class, FirEmptyPredicateBasedProvider) val dependencyProvider = LLFirDependentModuleProvidersByProviders(this) { - // - - val librariesSearchScope = - ProjectScope.getLibrariesScope(project).intersectWith(GlobalSearchScope.notScope(libraryModule.contentScope)) add(builtinSession.symbolProvider) - addAll( - LLFirLibraryProviderFactory.createLibraryProvidersForAllProjectLibraries( - session, moduleData, scopeProvider, project, builtinTypes, librariesSearchScope + + // Script dependencies are self-contained and should not depend on other libraries + if (module !is KtScriptDependencyModule) { + // Add all libraries excluding the current one + val librariesSearchScope = ProjectScope.getLibrariesScope(project) + .intersectWith(GlobalSearchScope.notScope(libraryModule.contentScope)) + + val restLibrariesProvider = LLFirLibraryProviderFactory.createLibraryProvidersForAllProjectLibraries( + session, moduleData, scopeProvider, + project, builtinTypes, librariesSearchScope ) - ) + + addAll(restLibrariesProvider) + } } val javaSymbolProvider = createJavaSymbolProvider(this, moduleData, project, contentScope) @@ -260,6 +236,91 @@ internal object LLFirSessionFactory { register(DEPENDENCIES_SYMBOL_PROVIDER_QUALIFIED_KEY, dependencyProvider) register(FirJvmTypeMapper::class, FirJvmTypeMapper(this)) + register(LLFirFirClassByPsiClassProvider::class, LLFirFirClassByPsiClassProvider(this)) + + configureSession?.invoke(this) + } + } + + fun createScriptSession( + project: Project, + module: KtScriptModule, + sessionInvalidator: LLFirSessionInvalidator, + sessionsCache: MutableMap, + librariesSessionFactory: LLFirLibrarySessionFactory, + configureSession: (LLFirSession.() -> Unit)? = null + ): LLFirScriptSession { + sessionsCache[module]?.let { return it as LLFirScriptSession } + checkCanceled() + + val platform = module.platform + val builtinsSession = LLFirBuiltinsSessionFactory.getInstance(project).getBuiltinsSession(platform) + val languageVersionSettings = wrapLanguageVersionSettings(module.languageVersionSettings) + val scopeProvider = FirKotlinScopeProvider(::wrapScopeWithJvmMapped) + val globalResolveComponents = LLFirGlobalResolveComponents(project) + + val components = LLFirModuleResolveComponents(module, globalResolveComponents, scopeProvider, sessionInvalidator) + val contentScope = module.contentScope + + val session = LLFirScriptSession(module, project, components, builtinsSession.builtinTypes) + sessionsCache[module] = session + components.session = session + + return session.apply session@{ + val moduleData = LLFirModuleData(module).apply { bindSession(this@session) } + registerModuleData(moduleData) + register(FirKotlinScopeProvider::class, scopeProvider) + + registerIdeComponents(project) + registerCommonComponents(languageVersionSettings) + registerCommonComponentsAfterExtensionsAreConfigured() + registerCommonJavaComponents(JavaModuleResolver.getInstance(project)) + registerResolveComponents() + registerJavaSpecificResolveComponents() + + val provider = LLFirProvider( + this, + components, + FileBasedKotlinDeclarationProvider(module.file), + project.createPackageProvider(contentScope), + canContainKotlinPackage = true, + ) + + register(FirProvider::class, provider) + register(FirLazyDeclarationResolver::class, LLFirLazyDeclarationResolver()) + + val dependencyProvider = LLFirDependentModuleProvidersBySessions(this) { + processSourceDependencies( + module, + sessionsCache, + globalResolveComponents, + sessionInvalidator, + librariesSessionFactory, + configureSession + ) + + add(builtinsSession) + } + + val javaSymbolProvider = createJavaSymbolProvider(this, moduleData, project, contentScope) + register(JavaSymbolProvider::class, javaSymbolProvider) + + register( + FirSymbolProvider::class, + LLFirModuleWithDependenciesSymbolProvider( + this, + dependencyProvider, + providers = listOfNotNull( + javaSymbolProvider, + provider.symbolProvider, + ) + ) + ) + + register(FirPredicateBasedProvider::class, FirEmptyPredicateBasedProvider) + register(DEPENDENCIES_SYMBOL_PROVIDER_QUALIFIED_KEY, dependencyProvider) + register(FirJvmTypeMapper::class, FirJvmTypeMapper(this)) + register(FirRegisteredPluginAnnotations::class, FirRegisteredPluginAnnotations.Empty) configureSession?.invoke(this) } @@ -334,6 +395,76 @@ internal object LLFirSessionFactory { configureSession?.invoke(this) } } + + private fun wrapLanguageVersionSettings(original: LanguageVersionSettings): LanguageVersionSettings { + return object : LanguageVersionSettings by original { + override fun getFeatureSupport(feature: LanguageFeature): LanguageFeature.State { + return when (feature) { + LanguageFeature.EnableDfaWarningsInK2 -> LanguageFeature.State.ENABLED + else -> original.getFeatureSupport(feature) + } + } + + override fun supportsFeature(feature: LanguageFeature): Boolean { + return when (getFeatureSupport(feature)) { + LanguageFeature.State.ENABLED, LanguageFeature.State.ENABLED_WITH_WARNING -> true + else -> false + } + } + } + } + + private fun MutableList.processSourceDependencies( + module: KtModule, + sessionsCache: MutableMap, + globalResolveComponents: LLFirGlobalResolveComponents, + sessionInvalidator: LLFirSessionInvalidator, + librariesSessionFactory: LLFirLibrarySessionFactory, + configureSession: (LLFirSession.() -> Unit)? + ) { + val project = module.project + + fun getOrCreateSessionForDependency(dependency: KtModule): LLFirSession? = when (dependency) { + is KtBuiltinsModule -> { + // Built-ins are already added + null + } + + is KtBinaryModule -> { + LLFirLibrarySessionFactory.getInstance(project).getLibrarySession(dependency, sessionsCache) + } + + is KtSourceModule -> { + createSourcesSession( + project, + dependency, + globalResolveComponents, + sessionInvalidator, + sessionsCache, + librariesSessionFactory = librariesSessionFactory, + configureSession = configureSession, + ) + } + + is KtScriptModule, + is KtScriptDependencyModule, + is KtNotUnderContentRootModule, + is KtLibrarySourceModule -> { + error("Module $module cannot depend on ${dependency::class}: $dependency") + } + } + + module.directRegularDependencies.mapNotNullTo(this@processSourceDependencies, ::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(this::add) + } + } + } } @Deprecated( diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionProviderStorage.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionProviderStorage.kt index 8d94c8d680c..769918486c2 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionProviderStorage.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionProviderStorage.kt @@ -40,6 +40,10 @@ class LLFirSessionProviderStorage(val project: Project) { createSessionProviderForLibraryOrLibrarySource(useSiteKtModule, configureSession) } + is KtScriptModule -> { + createSessionProviderForScriptSession(useSiteKtModule, configureSession) + } + is KtNotUnderContentRootModule -> { createSessionProviderForNotUnderContentRootSession(useSiteKtModule, configureSession) } @@ -89,6 +93,25 @@ class LLFirSessionProviderStorage(val project: Project) { return LLFirSessionProvider(project, session, KtModuleToSessionMappingByWeakValueMapImpl(sessions)) } + private fun createSessionProviderForScriptSession( + useSiteKtModule: KtScriptModule, + configureSession: (LLFirSession.() -> Unit)? + ): LLFirSessionProvider { + val (sessions, session) = sourceAsUseSiteSessionCache.withMappings(project) { mappings -> + val sessions = mutableMapOf().apply { putAll(mappings) } + val session = LLFirSessionFactory.createScriptSession( + project, + useSiteKtModule, + sourceAsUseSiteSessionCache.sessionInvalidator, + sessions, + librariesSessionFactory, + configureSession = configureSession, + ) + sessions to session + } + return LLFirSessionProvider(project, session, KtModuleToSessionMappingByWeakValueMapImpl(sessions)) + } + private fun createSessionProviderForNotUnderContentRootSession( useSiteKtModule: KtNotUnderContentRootModule, configureSession: (LLFirSession.() -> Unit)? @@ -189,6 +212,8 @@ private class FirSessionWithModificationTracker( val outOfBlockTracker = when (ktModule) { is KtSourceModule -> trackerFactory.createModuleWithoutDependenciesOutOfBlockModificationTracker(ktModule) is KtNotUnderContentRootModule -> ModificationTracker { ktModule.file?.modificationStamp ?: 0 } + is KtScriptModule -> ModificationTracker { ktModule.file.modificationStamp } + is KtScriptDependencyModule -> ModificationTracker { ktModule.file?.modificationStamp ?: 0 } else -> null } modificationTracker = CompositeModificationTracker.create( diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/state/LLFirResolvableResolveSession.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/state/LLFirResolvableResolveSession.kt index 35bd29ec373..568f8a9403d 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/state/LLFirResolvableResolveSession.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/state/LLFirResolvableResolveSession.kt @@ -10,6 +10,7 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirModuleResolveCompone import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirResolveSession import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.FirTowerContextProvider import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.getNonLocalContainingOrThisDeclaration +import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirResolvableModuleSession import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirSession import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirSessionProvider import org.jetbrains.kotlin.analysis.low.level.api.fir.util.FirDeclarationForCompiledElementSearcher @@ -49,6 +50,9 @@ internal abstract class LLFirResolvableResolveSession( override fun getSessionFor(module: KtModule): LLFirSession = sessionProvider.getSession(module) + protected open fun getResolvableSessionFor(module: KtModule): LLFirResolvableModuleSession = + sessionProvider.getResolvableSession(module) + override fun getScopeSessionFor(firSession: FirSession): ScopeSession { requireIsInstance(firSession) return firSession.getScopeSession() @@ -66,7 +70,7 @@ internal abstract class LLFirResolvableResolveSession( protected fun getModuleComponentsForElement(element: KtElement): LLFirModuleResolveComponents { val ktModule = element.getKtModule() - return sessionProvider.getResolvableSession(ktModule).moduleComponents + return getResolvableSessionFor(ktModule).moduleComponents } override fun resolveToFirSymbol( @@ -86,7 +90,7 @@ internal abstract class LLFirResolvableResolveSession( } val ktModule = ktDeclaration.getKtModule(project) - val firSession = sessionProvider.getSession(ktModule) + val firSession = getSessionFor(ktModule) val searcher = FirDeclarationForCompiledElementSearcher(firSession.symbolProvider) val firDeclaration = searcher.findNonLocalDeclaration(ktDeclaration) return firDeclaration.symbol @@ -107,7 +111,7 @@ internal abstract class LLFirResolvableResolveSession( } if (ktDeclaration == nonLocalNamedDeclaration) { - val session = sessionProvider.getResolvableSession(module) + val session = getResolvableSessionFor(module) return nonLocalNamedDeclaration.findSourceNonLocalFirDeclaration( firFileBuilder = session.moduleComponents.firFileBuilder, provider = session.firProvider, diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/state/LLFirScriptResolveSession.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/state/LLFirScriptResolveSession.kt new file mode 100644 index 00000000000..c746009ea0e --- /dev/null +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/state/LLFirScriptResolveSession.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2010-2023 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.analysis.low.level.api.fir.state + +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.project.Project +import com.intellij.util.containers.CollectionFactory +import com.intellij.util.containers.FactoryMap +import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirGlobalResolveComponents +import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirResolveSessionService +import org.jetbrains.kotlin.analysis.low.level.api.fir.api.DiagnosticCheckerFilter +import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.* +import org.jetbrains.kotlin.analysis.project.structure.* +import org.jetbrains.kotlin.analysis.utils.errors.unexpectedElementError +import org.jetbrains.kotlin.diagnostics.KtPsiDiagnostic +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtFile + +internal class LLFirScriptResolveSession( + override val globalComponents: LLFirGlobalResolveComponents, + override val project: Project, + override val useSiteKtModule: KtModule, + sessionProvider: LLFirSessionProvider +) : LLFirResolvableResolveSession(sessionProvider) { + private val dependencyCache: Map = + FactoryMap.createMap(::createDependencySession, CollectionFactory::createConcurrentSoftValueMap) + + private fun createDependencySession(module: KtModule): LLFirSession { + when (module) { + is KtScriptDependencyModule -> { + val resolveSessionService = ServiceManager.getService(project, LLFirResolveSessionService::class.java) + return resolveSessionService.getFirResolveSession(module).useSiteFirSession + } + else -> error("Unsupported script dependency session type: ${module.javaClass.name}") + } + } + + override fun getSessionFor(module: KtModule): LLFirSession { + if (module is KtScriptDependencyModule) { + return dependencyCache.getValue(module) + } + + return super.getSessionFor(module) + } + + override fun getResolvableSessionFor(module: KtModule): LLFirResolvableModuleSession { + if (module is KtScriptDependencyModule && module is KtLibrarySourceModule) { + return dependencyCache[module] as LLFirResolvableModuleSession + } + + return super.getResolvableSessionFor(module) + } + + override fun getDiagnostics(element: KtElement, filter: DiagnosticCheckerFilter): List { + val moduleComponents = getModuleComponentsForElement(element) + return moduleComponents.diagnosticsCollector.getDiagnosticsFor(element, filter) + } + + override fun collectDiagnosticsForFile(ktFile: KtFile, filter: DiagnosticCheckerFilter): Collection { + val moduleComponents = getModuleComponentsForElement(ktFile) + return moduleComponents.diagnosticsCollector.collectDiagnosticsForFile(ktFile, filter) + } + + override fun getModuleKind(module: KtModule): ModuleKind { + return when (module) { + useSiteKtModule, is KtSourceModule -> ModuleKind.RESOLVABLE_MODULE + is KtBuiltinsModule, is KtLibraryModule -> ModuleKind.BINARY_MODULE + else -> unexpectedElementError("module", module) + } + } +} \ No newline at end of file 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 e927a942c11..89a4304e73f 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 @@ -10,6 +10,7 @@ import com.intellij.psi.PsiFile import com.intellij.psi.search.GlobalSearchScope import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.platform.TargetPlatform +import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices import java.nio.file.Path @@ -177,6 +178,34 @@ public class KtBuiltinsModule( override fun hashCode(): Int = platform.hashCode() } +/** + * A module for a Kotlin script file. + */ +public interface KtScriptModule : KtModule { + /** + * A script PSI. + */ + public val file: KtFile + + /** + * A set of Kotlin settings, like API version, supported features and flags. + */ + public val languageVersionSettings: LanguageVersionSettings + + override val moduleDescription: String + get() = "Script " + file.name +} + +/** + * A module for Kotlin script dependencies. + * Must be either a [KtLibraryModule] or [KtLibrarySourceModule]. + */ +public interface KtScriptDependencyModule : KtModule { + /** + * A `VirtualFile` that backs the dependent script PSI, or `null` if the module is for project-level dependencies. + */ + public val file: KtFile? +} /** * A set of sources which live outside the project content root. E.g, testdata files or source files of some other project.