[LL API] Analyze code fragment in a separate 'FirSession'

Before, `KtCodeFragment`/`FirCodeFragment` was analyzed as a part of
its context `KtModule`. This has the following complications:

- In non-source sessions, diagnostic reporting is globally disabled.
  For code fragments, however, checking the code before passing it to
  the backend is essential.

- Special treatment for call ambiguities in libraries
  (`LLLibraryScopeAwareCallConflictResolverFactory`) becomes complicated
  as the conflict resolver has to be applied to a library module.

- `KtCodeFragment`s usually have a shorter lifetime than their own
  context. Caching may potentially be implemented differently for them.

^KT-61783 Fixed
This commit is contained in:
Yan Zhulanow
2023-09-04 22:16:33 +09:00
committed by Space Team
parent b32c083346
commit 715f7d1a35
10 changed files with 250 additions and 26 deletions
@@ -8,7 +8,9 @@ package org.jetbrains.kotlin.analysis.api.standalone.base.project.structure
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFileSystemItem
import org.jetbrains.kotlin.analysis.project.structure.*
import org.jetbrains.kotlin.analysis.project.structure.impl.KtCodeFragmentModuleImpl
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.psi.KtCodeFragment
import org.jetbrains.kotlin.psi.psiUtil.contains
import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerializerProtocol
import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments
@@ -23,6 +25,15 @@ class KtStaticModuleProvider(
// Unwrap context-dependent file copies coming from dependent sessions
val containingPsiFile = element.containingFile.originalFile
if (containingPsiFile is KtCodeFragment) {
val contextElement = containingPsiFile.context
?: throw KotlinExceptionWithAttachments("Context not found for a code fragment")
.withAttachment("codeFragment.kt", containingPsiFile.text)
val contextModule = getModule(contextElement, contextualModule)
return KtCodeFragmentModuleImpl(containingPsiFile, contextModule)
}
val containingVirtualFile = containingPsiFile.virtualFile
if (containingVirtualFile.extension == BuiltInSerializerProtocol.BUILTINS_FILE_EXTENSION) {
return builtinsModule
@@ -9,4 +9,4 @@ MODULE_FRAGMENT
EXPRESSION_BODY
BLOCK type=kotlin.Unit origin=null
CALL 'public final fun call (): kotlin.Unit declared in <root>.Foo' type=kotlin.Unit origin=null
$this: GET_OBJECT 'CLASS OBJECT name:Foo modality:FINAL visibility:public superTypes:[kotlin.Any]' type=<root>.Foo
$this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Foo modality:FINAL visibility:public superTypes:[kotlin.Any]' type=<root>.Foo
@@ -1 +1,2 @@
@Suppress("INVISIBLE_SETTER")
a = "bar"
@@ -43,17 +43,27 @@ class LLFirResolveSessionService(project: Project) {
private fun create(module: KtModule, factory: (KtModule) -> LLFirSession): LLFirResolvableResolveSession {
val moduleProvider = LLModuleProvider(module)
val sessionProvider = LLSessionProvider(module, factory)
val moduleKindProvider = createModuleKindProvider(module)
val moduleKindProvider = createModuleKindProvider(module, moduleProvider)
val diagnosticProvider = createDiagnosticProvider(moduleProvider, sessionProvider)
return LLFirResolvableResolveSession(moduleProvider, moduleKindProvider, sessionProvider, diagnosticProvider)
}
private fun createModuleKindProvider(module: KtModule): LLModuleKindProvider {
private fun createModuleKindProvider(module: KtModule, moduleProvider: LLModuleProvider): LLModuleKindProvider {
return when (module) {
is KtSourceModule -> LLSourceModuleKindProvider
is KtLibraryModule, is KtLibrarySourceModule -> LLLibraryModuleKindProvider(module)
is KtScriptModule -> LLScriptModuleKindProvider(module)
is KtCodeFragmentModule -> {
val contextElement = module.codeFragment.context
if (contextElement != null) {
val contextModule = moduleProvider.getModule(contextElement)
val contextModuleKindProvider = createModuleKindProvider(contextModule, moduleProvider)
LLCodeFragmentKindProvider(contextModuleKindProvider)
} else {
LLSimpleResolvableModuleKindProvider(module)
}
}
is KtNotUnderContentRootModule -> LLSimpleResolvableModuleKindProvider(module)
else -> {
errorWithFirSpecificEntries("Unexpected ${module::class.java}") {
@@ -65,7 +75,9 @@ class LLFirResolveSessionService(project: Project) {
private fun createDiagnosticProvider(moduleProvider: LLModuleProvider, sessionProvider: LLSessionProvider): LLDiagnosticProvider {
return when (moduleProvider.useSiteModule) {
is KtSourceModule, is KtScriptModule -> LLSourceDiagnosticProvider(moduleProvider, sessionProvider)
is KtSourceModule,
is KtScriptModule,
is KtCodeFragmentModule -> LLSourceDiagnosticProvider(moduleProvider, sessionProvider)
else -> LLEmptyDiagnosticProvider
}
}
@@ -101,4 +113,13 @@ private class LLScriptModuleKindProvider(private val useSiteModule: KtModule) :
else -> unexpectedElementError("module", module)
}
}
}
private class LLCodeFragmentKindProvider(private val delegate: LLModuleKindProvider) : LLModuleKindProvider {
override fun getKind(module: KtModule): KtModuleKind {
return when (module) {
is KtCodeFragmentModule -> KtModuleKind.RESOLVABLE_MODULE
else -> delegate.getKind(module)
}
}
}
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirLazyDeclarationResol
import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirModuleResolveComponents
import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.*
import org.jetbrains.kotlin.analysis.low.level.api.fir.providers.*
import org.jetbrains.kotlin.analysis.low.level.api.fir.resolver.LLLibraryScopeAwareCallConflictResolverFactory
import org.jetbrains.kotlin.analysis.project.structure.*
import org.jetbrains.kotlin.analysis.providers.KotlinAnchorModuleProvider
import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProvider
@@ -31,9 +32,13 @@ import org.jetbrains.kotlin.fir.analysis.extensions.additionalCheckers
import org.jetbrains.kotlin.fir.backend.jvm.FirJvmTypeMapper
import org.jetbrains.kotlin.fir.extensions.*
import org.jetbrains.kotlin.fir.java.JavaSymbolProvider
import org.jetbrains.kotlin.fir.languageVersionSettings
import org.jetbrains.kotlin.fir.resolve.calls.ConeCallConflictResolverFactory
import org.jetbrains.kotlin.fir.resolve.calls.callConflictResolverFactory
import org.jetbrains.kotlin.fir.resolve.providers.DEPENDENCIES_SYMBOL_PROVIDER_QUALIFIED_KEY
import org.jetbrains.kotlin.fir.resolve.providers.FirProvider
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProvider
import org.jetbrains.kotlin.fir.resolve.providers.dependenciesSymbolProvider
import org.jetbrains.kotlin.fir.resolve.providers.impl.FirExtensionSyntheticFunctionInterfaceProvider
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.resolve.scopes.wrapScopeWithJvmMapped
@@ -41,6 +46,8 @@ import org.jetbrains.kotlin.fir.resolve.transformers.FirDummyCompilerLazyDeclara
import org.jetbrains.kotlin.fir.scopes.FirKotlinScopeProvider
import org.jetbrains.kotlin.fir.session.*
import org.jetbrains.kotlin.fir.symbols.FirLazyDeclarationResolver
import org.jetbrains.kotlin.platform.has
import org.jetbrains.kotlin.platform.jvm.JvmPlatform
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleResolver
@@ -128,7 +135,7 @@ internal abstract class LLFirAbstractSessionFactory(protected val project: Proje
val dependencyProvider = LLFirDependenciesSymbolProvider(this) {
buildList {
addDependencySymbolProvidersTo(session, collectSourceModuleDependencies(module), this)
addMerged(collectDependencySymbolProviders(module))
add(builtinsSession.symbolProvider)
}
}
@@ -282,7 +289,7 @@ internal abstract class LLFirAbstractSessionFactory(protected val project: Proje
val dependencyProvider = LLFirDependenciesSymbolProvider(this) {
buildList {
addDependencySymbolProvidersTo(session, collectSourceModuleDependencies(module), this)
addMerged(collectDependencySymbolProviders(module))
add(builtinsSession.symbolProvider)
}
}
@@ -450,6 +457,87 @@ internal abstract class LLFirAbstractSessionFactory(protected val project: Proje
}
}
fun createCodeFragmentSession(module: KtCodeFragmentModule, contextSession: LLFirSession): LLFirSession {
val platform = module.platform
require(platform.has<JvmPlatform>())
val builtinsSession = LLFirBuiltinsSessionFactory.getInstance(project).getBuiltinsSession(platform)
val languageVersionSettings = wrapLanguageVersionSettings(contextSession.languageVersionSettings)
val scopeProvider = FirKotlinScopeProvider(::wrapScopeWithJvmMapped)
val components = LLFirModuleResolveComponents(module, globalResolveComponents, scopeProvider)
val session = LLFirCodeFragmentSession(module, components, builtinsSession.builtinTypes)
components.session = session
val moduleData = createModuleData(session)
return session.apply {
registerModuleData(moduleData)
register(FirKotlinScopeProvider::class, scopeProvider)
registerIdeComponents(project)
registerCommonComponents(languageVersionSettings)
registerResolveComponents()
val firProvider = LLFirProvider(
this,
components,
canContainKotlinPackage = true,
) { scope ->
scope.createScopedDeclarationProviderForFile(module.codeFragment)
}
register(FirProvider::class, firProvider)
register(FirLazyDeclarationResolver::class, LLFirLazyDeclarationResolver())
registerCommonComponentsAfterExtensionsAreConfigured()
val dependencyProvider = LLFirDependenciesSymbolProvider(this) {
buildList {
addMerged(computeFlattenedSymbolProviders(listOf(contextSession)))
add(contextSession.dependenciesSymbolProvider) // Add the context module dependency symbol provider as is
add(builtinsSession.symbolProvider)
}
}
register(DEPENDENCIES_SYMBOL_PROVIDER_QUALIFIED_KEY, dependencyProvider)
register(LLFirFirClassByPsiClassProvider::class, LLFirFirClassByPsiClassProvider(this))
LLFirSessionConfigurator.configure(this)
extensionService.additionalCheckers.forEach(session.checkersComponent::register)
register(FirPredicateBasedProvider::class, FirEmptyPredicateBasedProvider)
register(FirRegisteredPluginAnnotations::class, FirRegisteredPluginAnnotationsImpl(this))
registerJavaComponents(JavaModuleResolver.getInstance(project))
val javaSymbolProvider = LLFirJavaSymbolProvider(this, moduleData, project, firProvider.searchScope)
register(JavaSymbolProvider::class, javaSymbolProvider)
val syntheticFunctionInterfaceProvider = FirExtensionSyntheticFunctionInterfaceProvider
.createIfNeeded(this, moduleData, scopeProvider)
register(
FirSymbolProvider::class,
LLFirModuleWithDependenciesSymbolProvider(
this,
providers = listOfNotNull(
firProvider.symbolProvider,
syntheticFunctionInterfaceProvider,
javaSymbolProvider
),
dependencyProvider
)
)
register(FirJvmTypeMapper::class, FirJvmTypeMapper(this))
register(ConeCallConflictResolverFactory::class, LLLibraryScopeAwareCallConflictResolverFactory(callConflictResolverFactory))
}
}
private fun wrapLanguageVersionSettings(original: LanguageVersionSettings): LanguageVersionSettings {
return object : LanguageVersionSettings by original {
override fun getFeatureSupport(feature: LanguageFeature): LanguageFeature.State {
@@ -468,7 +556,7 @@ internal abstract class LLFirAbstractSessionFactory(protected val project: Proje
}
}
private fun collectSourceModuleDependencies(module: KtModule): List<LLFirSession> {
private fun collectDependencySymbolProviders(module: KtModule): List<FirSymbolProvider> {
val llFirSessionCache = LLFirSessionCache.getInstance(project)
fun getOrCreateSessionForDependency(dependency: KtModule): LLFirSession? = when (dependency) {
@@ -478,6 +566,7 @@ internal abstract class LLFirAbstractSessionFactory(protected val project: Proje
is KtScriptModule,
is KtScriptDependencyModule,
is KtCodeFragmentModule,
is KtNotUnderContentRootModule,
is KtLibrarySourceModule,
-> {
@@ -496,7 +585,17 @@ internal abstract class LLFirAbstractSessionFactory(protected val project: Proje
addAll(module.transitiveDependsOnDependencies)
}
return dependencyModules.mapNotNull(::getOrCreateSessionForDependency)
val dependencySessions = dependencyModules.mapNotNull(::getOrCreateSessionForDependency)
return computeFlattenedSymbolProviders(dependencySessions)
}
private fun computeFlattenedSymbolProviders(dependencySessions: List<LLFirSession>): List<FirSymbolProvider> {
return dependencySessions.flatMap { session ->
when (val dependencyProvider = session.symbolProvider) {
is LLFirModuleWithDependenciesSymbolProvider -> dependencyProvider.providers
else -> listOf(dependencyProvider)
}
}
}
private fun createModuleData(session: LLFirSession): LLFirModuleData {
@@ -504,24 +603,12 @@ internal abstract class LLFirAbstractSessionFactory(protected val project: Proje
}
/**
* Adds dependency symbol providers from [dependencies] to [destination]. The function might combine, reorder, or exclude specific
* symbol providers for optimization.
* Merges dependency symbol providers of the same kind, and adds the result to the receiver [MutableList].
* See [mergeDependencySymbolProvidersInto] for more information on symbol provider merging.
*/
private fun addDependencySymbolProvidersTo(
session: LLFirSession,
dependencies: List<LLFirSession>,
destination: MutableList<FirSymbolProvider>,
) {
val dependencyProviders = buildList {
dependencies.forEach { session ->
when (val dependencyProvider = session.symbolProvider) {
is LLFirModuleWithDependenciesSymbolProvider -> addAll(dependencyProvider.providers)
else -> add(dependencyProvider)
}
}
}
dependencyProviders.mergeDependencySymbolProvidersInto(session, destination)
context(LLFirSession)
private fun MutableList<FirSymbolProvider>.addMerged(dependencies: List<FirSymbolProvider>) {
dependencies.mergeDependencySymbolProvidersInto(this@LLFirSession, this)
}
/**
@@ -0,0 +1,17 @@
/*
* 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 org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirModuleResolveComponents
import org.jetbrains.kotlin.analysis.project.structure.KtCodeFragmentModule
import org.jetbrains.kotlin.fir.BuiltinTypes
import org.jetbrains.kotlin.fir.PrivateSessionConstructor
internal class LLFirCodeFragmentSession @PrivateSessionConstructor constructor(
ktModule: KtCodeFragmentModule,
override val moduleComponents: LLFirModuleResolveComponents,
builtinTypes: BuiltinTypes,
) : LLFirResolvableModuleSession(ktModule, builtinTypes)
@@ -146,6 +146,12 @@ class LLFirSessionCache(private val project: Project) {
is KtLibraryModule, is KtLibrarySourceModule -> sessionFactory.createLibrarySession(module)
is KtSdkModule -> sessionFactory.createBinaryLibrarySession(module)
is KtScriptModule -> sessionFactory.createScriptSession(module)
is KtCodeFragmentModule -> {
// 'KtCodeFragment' context must have an analyzable session, so we can properly compile code against it.
// 'KtCodeFragmentModule' is always a leaf module, there might not be a circular reference.
val contextSession = getSession(module.contextModule, preferBinary = false)
sessionFactory.createCodeFragmentSession(module, contextSession)
}
is KtNotUnderContentRootModule -> sessionFactory.createNotUnderContentRootResolvableSession(module)
else -> error("Unexpected module kind: ${module::class.simpleName}")
}
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.analysis.low.level.api.fir.transformers
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.FirDesignationWithFile
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getFirResolveSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getModule
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLFirResolveTarget
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.throwUnexpectedFirElementError
import org.jetbrains.kotlin.analysis.low.level.api.fir.compile.codeFragmentScopeProvider
@@ -38,6 +39,7 @@ import org.jetbrains.kotlin.fir.references.builder.buildExplicitSuperReference
import org.jetbrains.kotlin.fir.references.builder.buildExplicitThisReference
import org.jetbrains.kotlin.fir.resolve.FirCodeFragmentContext
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.SessionHolderImpl
import org.jetbrains.kotlin.fir.resolve.dfa.FirControlFlowGraphReferenceImpl
import org.jetbrains.kotlin.fir.resolve.codeFragmentContext
import org.jetbrains.kotlin.fir.resolve.dfa.RealVariable
@@ -219,9 +221,11 @@ private class LLFirBodyTargetResolver(
val contextKtFile = contextPsiElement?.containingFile as? KtFile
firCodeFragment.codeFragmentContext = if (contextKtFile != null) {
val contextModule = resolveSession.getModule(contextKtFile)
val contextSession = resolveSession.sessionProvider.getResolvableSession(contextModule)
val contextFirFile = resolveSession.getOrBuildFirFile(contextKtFile)
val sessionHolder = transformer.components
val sessionHolder = SessionHolderImpl(contextSession, contextSession.getScopeSession())
val elementContext = ContextCollector.process(contextFirFile, sessionHolder, contextPsiElement)
?: errorWithAttachment("Cannot find enclosing context for ${contextPsiElement::class}") {
withPsiEntry("contextPsiElement", contextPsiElement)
@@ -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.KtCodeFragment
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
import java.nio.file.Path
@@ -214,6 +215,24 @@ public interface KtScriptDependencyModule : KtModule {
public val file: KtFile?
}
/**
* A module for a Kotlin code fragment a piece of code analyzed against a specific context element.
*/
public interface KtCodeFragmentModule : KtModule {
/**
* A code fragment PSI.
*/
public val codeFragment: KtCodeFragment
/**
* Module of the context element.
*/
public val contextModule: KtModule
override val moduleDescription: String
get() = "Code fragment"
}
/**
* A set of sources which live outside the project content root. E.g, testdata files or source files of some other project.
*/
@@ -0,0 +1,58 @@
/*
* 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.KtCodeFragmentModule
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.psi.KtCodeFragment
import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
import java.util.Objects
public class KtCodeFragmentModuleImpl(
override val codeFragment: KtCodeFragment,
override val contextModule: KtModule
) : KtCodeFragmentModule {
override val project: Project
get() = contextModule.project
override val platform: TargetPlatform
get() = contextModule.platform
override val analyzerServices: PlatformDependentAnalyzerServices
get() = contextModule.analyzerServices
override val contentScope: GlobalSearchScope
get() = GlobalSearchScope.fileScope(codeFragment)
override val directRegularDependencies: List<KtModule>
get() = contextModule.directRegularDependencies
override val directDependsOnDependencies: List<KtModule>
get() = contextModule.directDependsOnDependencies
override val directFriendDependencies: List<KtModule>
get() = listOf(contextModule) + contextModule.directFriendDependencies
override val transitiveDependsOnDependencies: List<KtModule>
get() = contextModule.transitiveDependsOnDependencies
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other is KtCodeFragmentModuleImpl) {
return codeFragment == other.codeFragment && contextModule == other.contextModule
}
return false
}
override fun hashCode(): Int {
return Objects.hash(codeFragment, contextModule)
}
}