org.jetbrains.kotlin.cli.common.FirSessionConstructionUtils.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-compiler-embeddable Show documentation
Show all versions of kotlin-compiler-embeddable Show documentation
the Kotlin compiler embeddable
/*
* 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.cli.common
import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.analysis.checkers.CliOnlyLanguageVersionSettingsCheckers
import org.jetbrains.kotlin.fir.checkers.registerExtendedCommonCheckers
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar
import org.jetbrains.kotlin.fir.java.FirProjectSessionProvider
import org.jetbrains.kotlin.fir.session.*
import org.jetbrains.kotlin.fir.session.environment.AbstractProjectEnvironment
import org.jetbrains.kotlin.fir.session.environment.AbstractProjectFileSearchScope
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.metadata.resolver.KotlinResolvedLibrary
import org.jetbrains.kotlin.load.kotlin.PackageAndMetadataPartProvider
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.CommonPlatforms
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.js.JsPlatforms
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
import org.jetbrains.kotlin.platform.konan.NativePlatforms
import org.jetbrains.kotlin.platform.wasm.WasmPlatforms
import org.jetbrains.kotlin.platform.wasm.WasmTarget
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.multiplatform.hmppModuleName
import org.jetbrains.kotlin.resolve.multiplatform.isCommonSource
import org.jetbrains.kotlin.wasm.config.WasmConfigurationKeys
import org.jetbrains.kotlin.wasm.config.wasmTarget
val isCommonSourceForPsi: (KtFile) -> Boolean = { it.isCommonSource == true }
val fileBelongsToModuleForPsi: (KtFile, String) -> Boolean = { file, moduleName -> file.hmppModuleName == moduleName }
val GroupedKtSources.isCommonSourceForLt: (KtSourceFile) -> Boolean
get() = { it in commonSources }
val GroupedKtSources.fileBelongsToModuleForLt: (KtSourceFile, String) -> Boolean
get() = { file, moduleName -> sourcesByModuleName[moduleName].orEmpty().contains(file) }
/**
* Creates library session and sources session for JVM platform
* Number of created session depends on mode of MPP:
* - disabled
* - legacy (one platform and one common module)
* - HMPP (multiple number of modules)
*/
fun prepareJvmSessions(
files: List,
configuration: CompilerConfiguration,
projectEnvironment: AbstractProjectEnvironment,
rootModuleName: Name,
extensionRegistrars: List,
librariesScope: AbstractProjectFileSearchScope,
libraryList: DependencyListForCliModule,
isCommonSource: (F) -> Boolean,
isScript: (F) -> Boolean,
fileBelongsToModule: (F, String) -> Boolean,
createProviderAndScopeForIncrementalCompilation: (List) -> IncrementalCompilationContext?,
): List> {
val javaSourcesScope = projectEnvironment.getSearchScopeForProjectJavaSources()
val predefinedJavaComponents = FirSharableJavaComponents(firCachesFactoryForCliMode)
var firJvmIncrementalCompilationSymbolProviders: FirJvmIncrementalCompilationSymbolProviders? = null
var firJvmIncrementalCompilationSymbolProvidersIsInitialized = false
return prepareSessions(
files, configuration, rootModuleName, JvmPlatforms.unspecifiedJvmPlatform,
metadataCompilationMode = false, libraryList, isCommonSource, isScript, fileBelongsToModule,
createLibrarySession = { sessionProvider ->
FirJvmSessionFactory.createLibrarySession(
rootModuleName,
sessionProvider,
libraryList.moduleDataProvider,
projectEnvironment,
extensionRegistrars,
librariesScope,
projectEnvironment.getPackagePartProvider(librariesScope),
configuration.languageVersionSettings,
predefinedJavaComponents = predefinedJavaComponents,
)
},
) { moduleFiles, moduleData, sessionProvider, sessionConfigurator ->
FirJvmSessionFactory.createModuleBasedSession(
moduleData,
sessionProvider,
javaSourcesScope,
projectEnvironment,
createIncrementalCompilationSymbolProviders = { session ->
// Temporary solution for KT-61942 - we need to share the provider built on top of previously compiled files,
// because we do not distinguish classes generated from common and platform sources, so may end up with the
// same type loaded from both. And if providers are not shared, the types will not match on the actualizing.
// The proper solution would be to build IC providers only on class files generated for the currently compiled module.
// But to solve it we need to have a mapping from module to its class files.
// TODO: reimplement with splitted providers after fixing KT-62686
if (firJvmIncrementalCompilationSymbolProvidersIsInitialized) firJvmIncrementalCompilationSymbolProviders
else {
firJvmIncrementalCompilationSymbolProvidersIsInitialized = true
createProviderAndScopeForIncrementalCompilation(moduleFiles)?.createSymbolProviders(session, moduleData, projectEnvironment)?.also {
firJvmIncrementalCompilationSymbolProviders = it
}
}
},
extensionRegistrars,
configuration.languageVersionSettings,
configuration.get(JVMConfigurationKeys.JVM_TARGET, JvmTarget.DEFAULT),
configuration.get(CommonConfigurationKeys.LOOKUP_TRACKER),
configuration.get(CommonConfigurationKeys.ENUM_WHEN_TRACKER),
configuration.get(CommonConfigurationKeys.IMPORT_TRACKER),
predefinedJavaComponents = predefinedJavaComponents,
needRegisterJavaElementFinder = true,
sessionConfigurator,
)
}
}
/**
* Creates library session and sources session for JS platform
* Number of created session depends on mode of MPP:
* - disabled
* - legacy (one platform and one common module)
* - HMPP (multiple number of modules)
*/
fun prepareJsSessions(
files: List,
configuration: CompilerConfiguration,
rootModuleName: Name,
resolvedLibraries: List,
libraryList: DependencyListForCliModule,
extensionRegistrars: List,
isCommonSource: (F) -> Boolean,
fileBelongsToModule: (F, String) -> Boolean,
lookupTracker: LookupTracker?,
icData: KlibIcData?,
): List> {
return prepareSessions(
files, configuration, rootModuleName, JsPlatforms.defaultJsPlatform,
metadataCompilationMode = false, libraryList, isCommonSource, isScript = { false },
fileBelongsToModule,
createLibrarySession = { sessionProvider ->
FirJsSessionFactory.createLibrarySession(
rootModuleName,
resolvedLibraries,
sessionProvider,
libraryList.moduleDataProvider,
extensionRegistrars,
configuration,
)
}
) { _, moduleData, sessionProvider, sessionConfigurator ->
FirJsSessionFactory.createModuleBasedSession(
moduleData,
sessionProvider,
extensionRegistrars,
configuration,
lookupTracker,
icData = icData,
init = sessionConfigurator,
)
}
}
/**
* Creates library session and sources session for Native platform
* Number of created session depends on mode of MPP:
* - disabled
* - legacy (one platform and one common module)
* - HMPP (multiple number of modules)
*/
fun prepareNativeSessions(
files: List,
configuration: CompilerConfiguration,
rootModuleName: Name,
resolvedLibraries: List,
libraryList: DependencyListForCliModule,
extensionRegistrars: List,
metadataCompilationMode: Boolean,
isCommonSource: (F) -> Boolean,
fileBelongsToModule: (F, String) -> Boolean,
): List> {
return prepareSessions(
files, configuration, rootModuleName, NativePlatforms.unspecifiedNativePlatform,
metadataCompilationMode, libraryList, isCommonSource, isScript = { false },
fileBelongsToModule, createLibrarySession = { sessionProvider ->
FirNativeSessionFactory.createLibrarySession(
rootModuleName,
resolvedLibraries,
sessionProvider,
libraryList.moduleDataProvider,
extensionRegistrars,
configuration.languageVersionSettings,
)
}
) { _, moduleData, sessionProvider, sessionConfigurator ->
FirNativeSessionFactory.createModuleBasedSession(
moduleData,
sessionProvider,
extensionRegistrars,
configuration.languageVersionSettings,
sessionConfigurator,
)
}
}
/**
* Creates library session and sources session for Wasm platform
* Number of created session depends on mode of MPP:
* - disabled
* - legacy (one platform and one common module)
* - HMPP (multiple number of modules)
*/
fun prepareWasmSessions(
files: List,
configuration: CompilerConfiguration,
rootModuleName: Name,
resolvedLibraries: List,
libraryList: DependencyListForCliModule,
extensionRegistrars: List,
isCommonSource: (F) -> Boolean,
fileBelongsToModule: (F, String) -> Boolean,
lookupTracker: LookupTracker?,
icData: KlibIcData?,
): List> {
val platform = when (configuration.get(WasmConfigurationKeys.WASM_TARGET, WasmTarget.JS)) {
WasmTarget.JS -> WasmPlatforms.wasmJs
WasmTarget.WASI -> WasmPlatforms.wasmWasi
}
return prepareSessions(
files, configuration, rootModuleName, platform,
metadataCompilationMode = false, libraryList, isCommonSource, isScript = { false },
fileBelongsToModule,
createLibrarySession = { sessionProvider ->
FirWasmSessionFactory.createLibrarySession(
rootModuleName,
resolvedLibraries,
sessionProvider,
libraryList.moduleDataProvider,
extensionRegistrars,
configuration.languageVersionSettings,
)
}
) { _, moduleData, sessionProvider, sessionConfigurator ->
FirWasmSessionFactory.createModuleBasedSession(
moduleData,
sessionProvider,
extensionRegistrars,
configuration.languageVersionSettings,
configuration.wasmTarget,
lookupTracker,
icData = icData,
init = sessionConfigurator,
)
}
}
/**
* Creates library session and sources session for Common platform (for metadata compilation)
* Number of created sessions is always one, in this mode modules are compiled against compiled
* metadata of dependent modules
*/
fun prepareCommonSessions(
files: List,
configuration: CompilerConfiguration,
projectEnvironment: AbstractProjectEnvironment,
rootModuleName: Name,
extensionRegistrars: List,
librariesScope: AbstractProjectFileSearchScope,
libraryList: DependencyListForCliModule,
resolvedLibraries: List,
isCommonSource: (F) -> Boolean,
fileBelongsToModule: (F, String) -> Boolean,
createProviderAndScopeForIncrementalCompilation: (List) -> IncrementalCompilationContext?,
): List> {
return prepareSessions(
files, configuration, rootModuleName, CommonPlatforms.defaultCommonPlatform,
metadataCompilationMode = true, libraryList, isCommonSource, isScript = { false }, fileBelongsToModule,
createLibrarySession = { sessionProvider ->
FirCommonSessionFactory.createLibrarySession(
rootModuleName,
sessionProvider,
libraryList.moduleDataProvider,
projectEnvironment,
extensionRegistrars,
librariesScope,
resolvedLibraries,
projectEnvironment.getPackagePartProvider(librariesScope) as PackageAndMetadataPartProvider,
configuration.languageVersionSettings,
)
}
) { moduleFiles, moduleData, sessionProvider, sessionConfigurator ->
FirCommonSessionFactory.createModuleBasedSession(
moduleData,
sessionProvider,
projectEnvironment,
incrementalCompilationContext = createProviderAndScopeForIncrementalCompilation(moduleFiles),
extensionRegistrars,
configuration.languageVersionSettings,
lookupTracker = configuration.get(CommonConfigurationKeys.LOOKUP_TRACKER),
enumWhenTracker = configuration.get(CommonConfigurationKeys.ENUM_WHEN_TRACKER),
importTracker = configuration.get(CommonConfigurationKeys.IMPORT_TRACKER),
init = sessionConfigurator
)
}
}
// ---------------------------------------------------- Implementation ----------------------------------------------------
private typealias FirSessionProducer = (List, FirModuleData, FirProjectSessionProvider, FirSessionConfigurator.() -> Unit) -> FirSession
private inline fun prepareSessions(
files: List,
configuration: CompilerConfiguration,
rootModuleName: Name,
targetPlatform: TargetPlatform,
metadataCompilationMode: Boolean,
libraryList: DependencyListForCliModule,
isCommonSource: (F) -> Boolean,
isScript: (F) -> Boolean,
fileBelongsToModule: (F, String) -> Boolean,
createLibrarySession: (FirProjectSessionProvider) -> FirSession,
createSourceSession: FirSessionProducer,
): List> {
val languageVersionSettings = configuration.languageVersionSettings
val (scripts, nonScriptFiles) = files.partition(isScript)
val isMppEnabled = languageVersionSettings.supportsFeature(LanguageFeature.MultiPlatformProjects)
val hmppModuleStructure = configuration.get(CommonConfigurationKeys.HMPP_MODULE_STRUCTURE)
val sessionProvider = FirProjectSessionProvider()
createLibrarySession(sessionProvider)
val extendedAnalysisMode = configuration.getBoolean(CommonConfigurationKeys.USE_FIR_EXTENDED_CHECKERS)
val sessionConfigurator: FirSessionConfigurator.() -> Unit = {
if (extendedAnalysisMode) {
registerExtendedCommonCheckers()
}
}
val nonScriptSessions = when {
metadataCompilationMode || !isMppEnabled -> {
listOf(
createSingleSession(
nonScriptFiles, rootModuleName, libraryList, targetPlatform,
sessionProvider, sessionConfigurator, createSourceSession
)
)
}
hmppModuleStructure == null -> createSessionsForLegacyMppProject(
nonScriptFiles, rootModuleName, libraryList, targetPlatform,
sessionProvider, sessionConfigurator, isCommonSource, createSourceSession
)
else -> createSessionsForHmppProject(
nonScriptFiles, rootModuleName, hmppModuleStructure, libraryList, targetPlatform,
sessionProvider, sessionConfigurator, fileBelongsToModule, createSourceSession
)
}
return if (scripts.isEmpty()) nonScriptSessions
else nonScriptSessions +
createScriptsSession(
scripts, rootModuleName, libraryList, nonScriptSessions.last().session.moduleData,
targetPlatform, sessionProvider, sessionConfigurator, createSourceSession
)
}
private inline fun createScriptsSession(
scripts: List,
rootModuleName: Name,
libraryList: DependencyListForCliModule,
lastModuleData: FirModuleData,
targetPlatform: TargetPlatform,
sessionProvider: FirProjectSessionProvider,
noinline sessionConfigurator: FirSessionConfigurator.() -> Unit,
createSourceSession: FirSessionProducer,
): SessionWithSources =
createSingleSession(
scripts, Name.identifier("${rootModuleName.asString()}-scripts"),
DependencyListForCliModule(
libraryList.regularDependencies,
listOf(lastModuleData),
libraryList.friendsDependencies,
libraryList.moduleDataProvider
),
targetPlatform,
sessionProvider, sessionConfigurator, createSourceSession
)
private inline fun createSingleSession(
files: List,
rootModuleName: Name,
libraryList: DependencyListForCliModule,
targetPlatform: TargetPlatform,
sessionProvider: FirProjectSessionProvider,
noinline sessionConfigurator: FirSessionConfigurator.() -> Unit,
createFirSession: FirSessionProducer,
): SessionWithSources {
val platformModuleData = FirModuleDataImpl(
rootModuleName,
libraryList.regularDependencies,
libraryList.dependsOnDependencies,
libraryList.friendsDependencies,
targetPlatform,
)
val session = createFirSession(files, platformModuleData, sessionProvider) {
sessionConfigurator()
useCheckers(CliOnlyLanguageVersionSettingsCheckers)
}
return SessionWithSources(session, files)
}
private inline fun createSessionsForLegacyMppProject(
files: List,
rootModuleName: Name,
libraryList: DependencyListForCliModule,
targetPlatform: TargetPlatform,
sessionProvider: FirProjectSessionProvider,
noinline sessionConfigurator: FirSessionConfigurator.() -> Unit,
isCommonSource: (F) -> Boolean,
createFirSession: FirSessionProducer,
): List> {
val commonModuleData = FirModuleDataImpl(
Name.identifier("${rootModuleName.asString()}-common"),
libraryList.regularDependencies,
listOf(),
libraryList.friendsDependencies,
targetPlatform,
isCommon = true
)
val platformModuleData = FirModuleDataImpl(
rootModuleName,
libraryList.regularDependencies,
listOf(commonModuleData),
libraryList.friendsDependencies,
targetPlatform,
isCommon = false
)
val commonFiles = mutableListOf()
val platformFiles = mutableListOf()
for (file in files) {
(if (isCommonSource(file)) commonFiles else platformFiles).add(file)
}
val commonSession = createFirSession(commonFiles, commonModuleData, sessionProvider, sessionConfigurator)
val platformSession = createFirSession(platformFiles, platformModuleData, sessionProvider) {
sessionConfigurator()
// The CLI session might contain an opt-in for an annotation that's defined in the platform module.
// Therefore, only run the opt-in LV checker on the platform module.
useCheckers(CliOnlyLanguageVersionSettingsCheckers)
}
return listOf(
SessionWithSources(commonSession, commonFiles),
SessionWithSources(platformSession, platformFiles)
)
}
private inline fun createSessionsForHmppProject(
files: List,
rootModuleName: Name,
hmppModuleStructure: HmppCliModuleStructure,
libraryList: DependencyListForCliModule,
targetPlatform: TargetPlatform,
sessionProvider: FirProjectSessionProvider,
noinline sessionConfigurator: FirSessionConfigurator.() -> Unit,
fileBelongsToModule: (F, String) -> Boolean,
createFirSession: FirSessionProducer,
): List> {
val moduleDataForHmppModule = LinkedHashMap()
for ((index, module) in hmppModuleStructure.modules.withIndex()) {
val dependencies = hmppModuleStructure.dependenciesMap[module]
?.map { moduleDataForHmppModule.getValue(it) }
.orEmpty()
val moduleData = FirModuleDataImpl(
rootModuleName,
libraryList.regularDependencies,
dependsOnDependencies = dependencies,
libraryList.friendsDependencies,
targetPlatform,
isCommon = index < hmppModuleStructure.modules.size - 1
)
moduleDataForHmppModule[module] = moduleData
}
return hmppModuleStructure.modules.mapIndexed { i, module ->
val moduleData = moduleDataForHmppModule.getValue(module)
val sources = files.filter { fileBelongsToModule(it, module.name) }
val session = createFirSession(sources, moduleData, sessionProvider) {
sessionConfigurator()
// The CLI session might contain an opt-in for an annotation that's defined in one of the modules.
// The only module that's guaranteed to have a dependency on this module is the last one.
if (i == hmppModuleStructure.modules.lastIndex) {
useCheckers(CliOnlyLanguageVersionSettingsCheckers)
}
}
SessionWithSources(session, sources)
}
}
data class SessionWithSources(val session: FirSession, val files: List)