[Analysis API] Remove components of obsolete on-air analysis

On-air analysis only worked correctly inside declaration bodies, and
required special handling ('analyzeInDependedAnalysisSession()').
Dangling files, the on-air replacement, works seamlessly, and it is
available in all contexts.
This commit is contained in:
Yan Zhulanow
2024-01-11 19:03:41 +09:00
committed by Space Team
parent 9021fdaf83
commit 9227359256
15 changed files with 7 additions and 885 deletions
@@ -21,7 +21,6 @@ import org.jetbrains.kotlin.analysis.api.symbols.KtSymbolProviderByJavaPsi
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.project.structure.ProjectStructureProvider
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
@OptIn(KtAnalysisApiInternals::class, KtAnalysisNonPublicApi::class)
@Suppress("LeakingThis")
@@ -79,8 +78,4 @@ class KtFe10AnalysisSession(
@Suppress("AnalysisApiMissingLifetimeCheck")
override val substitutorProviderImpl: KtSubstitutorProvider
get() = throw NotSupportedForK1Exception()
@Suppress("OVERRIDE_DEPRECATION")
override fun createContextDependentCopy(originalKtFile: KtFile, elementToReanalyze: KtElement): KtAnalysisSession =
KtFe10AnalysisSession(originalKtFile.project, elementToReanalyze, token)
}
@@ -18,7 +18,6 @@ import org.jetbrains.kotlin.analysis.api.impl.base.components.KtAnalysisScopePro
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeToken
import org.jetbrains.kotlin.analysis.api.lifetime.withValidityAssertion
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirResolveSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LowLevelFirApiFacadeForResolveOnAir
import org.jetbrains.kotlin.analysis.low.level.api.fir.resolve.extensions.LLFirResolveExtensionTool
import org.jetbrains.kotlin.analysis.low.level.api.fir.resolve.extensions.llResolveExtensionTool
import org.jetbrains.kotlin.analysis.project.structure.KtModule
@@ -35,8 +34,6 @@ import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProvider
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.utils.addIfNotNull
@OptIn(KtAnalysisApiInternals::class, KtAnalysisNonPublicApi::class)
@@ -135,26 +132,6 @@ private constructor(
override val substitutorProviderImpl: KtSubstitutorProvider = KtFirSubstitutorProvider(this)
@Suppress("AnalysisApiMissingLifetimeCheck", "OVERRIDE_DEPRECATION")
override fun createContextDependentCopy(originalKtFile: KtFile, elementToReanalyze: KtElement): KtAnalysisSession {
check(mode == AnalysisSessionMode.REGULAR) {
"Cannot create context-dependent copy of KtAnalysis session from a context dependent one"
}
val contextFirResolveSession = LowLevelFirApiFacadeForResolveOnAir.getFirResolveSessionForDependentCopy(
originalFirResolveSession = firResolveSession,
originalKtFile = originalKtFile,
elementToAnalyze = elementToReanalyze
)
return KtFirAnalysisSession(
project,
contextFirResolveSession,
token,
AnalysisSessionMode.DEPENDENT_COPY
)
}
internal val useSiteSession: FirSession get() = firResolveSession.useSiteFirSession
internal val firSymbolProvider: FirSymbolProvider get() = useSiteSession.symbolProvider
internal val targetPlatform: TargetPlatform get() = useSiteSession.moduleData.platform
@@ -13,8 +13,6 @@ import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtSymbolPointer
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.project.structure.ProjectStructureProvider
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
/**
* [KtAnalysisSession] is the entry point to all frontend-related work. It has the following contracts:
@@ -100,15 +98,6 @@ public abstract class KtAnalysisSession(final override val token: KtLifetimeToke
override val analysisSession: KtAnalysisSession get() = this
/**
* Creates a new [KtAnalysisSession] which depends on this analysis session, but additionally provides its own symbols derived from
* analyzing [elementToReanalyze].
*
* @see analyzeInDependedAnalysisSession
*/
@Deprecated("On-air analysis is obsolete. Use 'analyzeCopy()' instead")
public abstract fun createContextDependentCopy(originalKtFile: KtFile, elementToReanalyze: KtElement): KtAnalysisSession
internal val smartCastProvider: KtSmartCastProvider get() = smartCastProviderImpl
protected abstract val smartCastProviderImpl: KtSmartCastProvider
@@ -14,7 +14,6 @@ import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.project.structure.KtModuleStructureInternals
import org.jetbrains.kotlin.analysis.project.structure.withDanglingFileResolutionMode
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
/**
* Executes the given [action] in a [KtAnalysisSession] context.
@@ -46,32 +45,6 @@ public inline fun <R> analyze(
return sessionProvider.analyze(useSiteKtModule, action)
}
/**
* Executes the given [action] in the context of a [KtAnalysisSession] that depends on an original analysis session determined by
* [originalFile]. In addition to the symbols provided by the original analysis session, the dependent analysis session provides its own
* symbols derived from analyzing [elementToReanalyze]. This allows analyzing some new or copied (and modified) element in the larger
* context of the original analysis session.
*
* @see KtAnalysisSession.createContextDependentCopy
*/
@Deprecated(
"On-air analysis is obsolete. Use 'analyzeCopy()' instead",
ReplaceWith(
"analyzeCopy(elementToReanalyze, DanglingFileResolutionMode.IGNORE.SELF)",
imports = [
"org.jetbrains.kotlin.analysis.api.analyzeCopy",
"org.jetbrains.kotlin.analysis.project.structure.DanglingFileResolutionMode"
]
)
)
public inline fun <R> analyzeInDependedAnalysisSession(
@Suppress("unused", "UNUSED_PARAMETER") originalFile: KtFile,
elementToReanalyze: KtElement,
crossinline action: KtAnalysisSession.() -> R
): R {
return analyzeCopy(elementToReanalyze, DanglingFileResolutionMode.IGNORE_SELF, action)
}
/**
* Executes the given [action] in a [KtAnalysisSession] context.
* Depending on the passed [resolutionMode], declarations inside a file copy will be treated in a specific way.
@@ -39,24 +39,6 @@ public abstract class KtAnalysisSessionProvider(public val project: Project) : D
public abstract fun getAnalysisSessionByUseSiteKtModule(useSiteKtModule: KtModule): KtAnalysisSession
@Deprecated(
"On-air analysis is obsolete. Use 'analyzeCopy()' instead",
replaceWith = ReplaceWith(
"analyzeCopy(elementToReanalyze, DanglingFileAnalysisMode.IGNORE_SELF, action)",
imports = [
"org.jetbrains.kotlin.analysis.api.analyzeCopy",
"org.jetbrains.kotlin.analysis.project.structure.DanglingFileResolutionMode"
]
)
)
public inline fun <R> analyseInDependedAnalysisSession(
@Suppress("unused", "UNUSED_PARAMETER") originalFile: KtFile,
elementToReanalyze: KtElement,
crossinline action: KtAnalysisSession.() -> R,
): R {
return analyzeCopy(elementToReanalyze, DanglingFileResolutionMode.IGNORE_SELF, action)
}
public inline fun <R> analyse(
useSiteKtElement: KtElement,
action: KtAnalysisSession.() -> R,
@@ -1,104 +0,0 @@
/*
* Copyright 2010-2020 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
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootModificationTracker
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.DiagnosticCheckerFilter
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirResolveSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.FirElementBuilder
import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.FirTowerContextProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.file.structure.KtToFirMapping
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.state.LLDiagnosticProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.state.LLFirResolvableResolveSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.state.LLScopeSessionProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.state.TowerProviderForElementForState
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.LLFirScopeSessionProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.originalDeclaration
import org.jetbrains.kotlin.analysis.utils.caches.*
import org.jetbrains.kotlin.diagnostics.KtPsiDiagnostic
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
/**
* An [LLFirResolveSession] which *depends* on an [originalFirResolveSession], but can provide its own FIR elements and symbols selectively.
* The dependent session mostly provides FIR elements and symbols from the original resolve session, but additionally provides FIR elements
* (and their associated symbols) from its own [ktToFirMapping].
*
* [LLFirResolveSessionDepended] is used by on-air resolve to provide FIR elements for a copied declaration in the larger context of the
* original resolve session.
*/
internal class LLFirResolveSessionDepended(
val originalFirResolveSession: LLFirResolvableResolveSession,
val towerProviderBuiltUponElement: FirTowerContextProvider,
private val ktToFirMapping: KtToFirMapping?,
) : LLFirResolveSession(
originalFirResolveSession.moduleProvider,
originalFirResolveSession.resolutionStrategyProvider,
originalFirResolveSession.sessionProvider,
LLDependedScopeSessionProvider(originalFirResolveSession.project),
LLDependedDiagnosticProvider
) {
override fun getOrBuildFirFor(element: KtElement): FirElement? {
val psi = FirElementBuilder.getPsiAsFirElementSource(element) ?: return null
ktToFirMapping?.getFir(psi)?.let { return it }
return originalFirResolveSession.getOrBuildFirFor(element = element)
}
override fun getOrBuildFirFile(ktFile: KtFile): FirFile =
originalFirResolveSession.getOrBuildFirFile(ktFile)
override fun resolveFirToPhase(declaration: FirDeclaration, toPhase: FirResolvePhase) {
originalFirResolveSession.resolveFirToPhase(declaration, toPhase)
}
override fun resolveToFirSymbol(ktDeclaration: KtDeclaration, phase: FirResolvePhase): FirBasedSymbol<*> {
val declarationToResolve = ktDeclaration.originalDeclaration ?: ktDeclaration
ktToFirMapping?.getElement(declarationToResolve)?.let { it as? FirDeclaration }?.symbol?.let { return it }
return originalFirResolveSession.resolveToFirSymbol(declarationToResolve, phase)
}
override fun getTowerContextProvider(ktFile: KtFile): FirTowerContextProvider =
TowerProviderForElementForState(this)
}
private object LLDependedDiagnosticProvider : LLDiagnosticProvider {
override fun collectDiagnostics(file: KtFile, filter: DiagnosticCheckerFilter): List<KtPsiDiagnostic> {
throw NotImplementedError("Diagnostics are not implemented for depended state")
}
override fun getDiagnostics(element: KtElement, filter: DiagnosticCheckerFilter): List<KtPsiDiagnostic> {
throw NotImplementedError("Diagnostics are not implemented for depended state")
}
}
private class LLDependedScopeSessionProvider(private val project: Project) : LLScopeSessionProvider {
private val scopeSessionProviderCache = SoftCachedMap.create<FirSession, LLFirScopeSessionProvider>(
project,
SoftCachedMap.Kind.SOFT_KEYS_SOFT_VALUES,
listOf(
PsiModificationTracker.MODIFICATION_COUNT,
ProjectRootModificationTracker.getInstance(project)
)
)
override fun getScopeSession(session: LLFirSession): ScopeSession {
return scopeSessionProviderCache
.getOrPut(session) { LLFirScopeSessionProvider.create(project, invalidationTrackers = emptyList()) }
.getScopeSession()
}
}
@@ -82,8 +82,6 @@ abstract class LLFirResolveSession(
abstract fun resolveToFirSymbol(ktDeclaration: KtDeclaration, phase: FirResolvePhase): FirBasedSymbol<*>
internal abstract fun resolveFirToPhase(declaration: FirDeclaration, toPhase: FirResolvePhase)
abstract fun getTowerContextProvider(ktFile: KtFile): FirTowerContextProvider
}
fun LLFirResolveSession.getModule(element: PsiElement): KtModule {
@@ -1,485 +0,0 @@
/*
* 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.api
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirResolveSessionDepended
import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.FileTowerProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.FirTowerContextProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.FirTowerDataContextAllElementsCollector
import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.getNonLocalContainingOrThisDeclaration
import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.isAutonomousDeclaration
import org.jetbrains.kotlin.analysis.low.level.api.fir.file.structure.KtToFirMapping
import org.jetbrains.kotlin.analysis.low.level.api.fir.lazy.resolve.RawFirNonLocalDeclarationBuilder
import org.jetbrains.kotlin.analysis.low.level.api.fir.lazy.resolve.RawFirReplacement
import org.jetbrains.kotlin.analysis.low.level.api.fir.lazy.resolve.buildFileFirAnnotation
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirResolvableModuleSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.llFirResolvableSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.llFirSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.state.LLFirResolvableResolveSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.FirElementFinder
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.codeFragment
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.errorWithFirSpecificEntries
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.isElementWhichShouldBeResolvedAsPartOfScript
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.originalDeclaration
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.originalKtFile
import org.jetbrains.kotlin.analysis.utils.printer.parentOfType
import org.jetbrains.kotlin.analysis.utils.printer.parentsOfType
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.builder.buildFileAnnotationsContainer
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.realPsi
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirResolveContextCollector
import org.jetbrains.kotlin.fir.scopes.createImportingScopes
import org.jetbrains.kotlin.fir.scopes.kotlinScopeProvider
import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
import org.jetbrains.kotlin.utils.exceptions.withPsiEntry
object LowLevelFirApiFacadeForResolveOnAir {
private fun KtDeclaration.isApplicableForOnAirResolve(): Boolean = when (this) {
is KtDestructuringDeclaration -> false
else -> true
}
private fun PsiElement.getNonLocalContainingOrThisDeclarationCodeFragmentAware(predicate: (KtDeclaration) -> Boolean): KtAnnotated? {
return getNonLocalContainingOrThisDeclaration(predicate) ?: containingFile as? KtCodeFragment
}
private fun PsiElement.onAirGetNonLocalContainingOrThisDeclaration(): KtElement? {
return getNonLocalContainingOrThisDeclarationCodeFragmentAware { declaration ->
declaration.isApplicableForOnAirResolve() && declaration.isAutonomousDeclaration
}
}
private fun recordOriginalDeclaration(targetDeclaration: KtDeclaration, originalDeclaration: KtDeclaration) {
require(originalDeclaration.containingKtFile !== targetDeclaration.containingKtFile)
val originalDeclarationParents = originalDeclaration.parentsOfType<KtDeclaration>().toList()
val fakeDeclarationParents = targetDeclaration.parentsOfType<KtDeclaration>().toList()
originalDeclarationParents.zip(fakeDeclarationParents) { original, fake ->
fake.originalDeclaration = original
}
/**
* We assume that [targetDeclaration] should have the same number of declarations
*
* @see restoreOriginalDeclarationsInScript
*/
if (targetDeclaration is KtScript && originalDeclaration is KtScript) {
val originalStatements = originalDeclaration.declarationSequence()
val newStatements = targetDeclaration.declarationSequence()
originalStatements.zip(newStatements) { original, fake ->
fake.originalDeclaration = original
}
}
}
private fun KtScript.declarationSequence(): Sequence<KtDeclaration> {
val sequence = blockExpression.statements.asSequence().filter { it !is KtScriptInitializer && it is KtDeclaration }
@Suppress("UNCHECKED_CAST")
return sequence as Sequence<KtDeclaration>
}
fun <T : KtElement> onAirResolveElement(
firResolveSession: LLFirResolveSession,
place: T,
elementToResolve: T,
): FirElement {
require(firResolveSession is LLFirResolvableResolveSession)
val declaration = runBodyResolveOnAir(
firResolveSession = firResolveSession,
originalPlace = place,
replacementElement = elementToResolve,
collector = null,
)
val expressionLocator = object : FirVisitorVoid() {
var result: FirElement? = null
private set
override fun visitElement(element: FirElement) {
if (element.realPsi == elementToResolve) result = element
if (result != null) return
element.acceptChildren(this)
}
}
declaration.accept(expressionLocator)
return expressionLocator.result ?: errorWithFirSpecificEntries("Resolved on-air element was not found in containing declaration") {
withPsiEntry("place", place)
withPsiEntry("elementToResolve", elementToResolve)
}
}
fun getOnAirTowerDataContextProviderForTheWholeFile(
firResolveSession: LLFirResolveSession,
ktFile: KtFile,
): FirTowerContextProvider {
val ktModule = firResolveSession.getModule(ktFile)
val session = firResolveSession.getSessionFor(ktModule) as LLFirResolvableModuleSession
val moduleComponents = session.moduleComponents
val onAirFirFile = RawFirNonLocalDeclarationBuilder.buildNewFile(session, session.kotlinScopeProvider, ktFile)
val collector = FirTowerDataContextAllElementsCollector()
moduleComponents.firModuleLazyDeclarationResolver.runLazyDesignatedOnAirResolve(
FirDesignationWithFile(emptyList(), onAirFirFile, onAirFirFile),
collector,
)
return collector
}
fun getOnAirGetTowerContextProvider(
firResolveSession: LLFirResolveSession,
place: KtElement,
): FirTowerContextProvider {
require(firResolveSession is LLFirResolvableResolveSession)
return if (place is KtFile) {
FileTowerProvider(place, onAirGetTowerContextForFile(firResolveSession, place))
} else {
val validPlace = PsiTreeUtil.findFirstParent(place, false) {
it is KtElement && RawFirReplacement.isApplicableForReplacement(it)
} as KtElement
FirTowerDataContextAllElementsCollector().also {
runBodyResolveOnAir(
firResolveSession = firResolveSession,
collector = it,
originalPlace = validPlace,
replacementElement = validPlace,
)
}
}
}
private fun onAirGetTowerContextForFile(
firResolveSession: LLFirResolvableResolveSession,
file: KtFile,
): FirTowerDataContext {
val module = firResolveSession.getModule(file)
val session = firResolveSession.getSessionFor(module) as LLFirResolvableModuleSession
val moduleComponents = session.moduleComponents
val firFile = moduleComponents.firFileBuilder.buildRawFirFileWithCaching(file)
val scopeSession = firResolveSession.getScopeSessionFor(session)
return firFile.createTowerDataContext(scopeSession)
}
private fun FirFile.createTowerDataContext(scopeSession: ScopeSession): FirTowerDataContext {
val importingScopes = createImportingScopes(this, moduleData.session, scopeSession)
val fileScopeElements = importingScopes.map { it.asTowerDataElement(isLocal = false) }
return FirTowerDataContext().addNonLocalTowerDataElements(fileScopeElements)
}
/**
* Makes a copy of the original fir declaration and resolves it to the minimum required phase
* depends on [elementToAnalyze] position inside a non-local containing declaration.
*
* If [elementToAnalyze] is non-local declaration then resolves it to [FirResolvePhase.BODY_RESOLVE].
*
* Resulted [LLFirResolveSession] have [FirTowerDataContext] for elements from the copy.
*/
fun getFirResolveSessionForDependentCopy(
originalFirResolveSession: LLFirResolveSession,
originalKtFile: KtFile,
elementToAnalyze: KtElement,
): LLFirResolveSession {
require(originalFirResolveSession is LLFirResolvableResolveSession)
require(elementToAnalyze !is KtFile) { "KtFile for dependency element not supported" }
elementToAnalyze.containingKtFile.originalKtFile = originalKtFile
val minimalCopiedDeclaration = elementToAnalyze.getNonLocalContainingOrThisDeclarationCodeFragmentAware {
it.isApplicableForOnAirResolve()
}
val copiedNonLocalDeclaration = minimalCopiedDeclaration?.onAirGetNonLocalContainingOrThisDeclaration()
if (copiedNonLocalDeclaration == null) {
val towerDataContext = onAirGetTowerContextForFile(originalFirResolveSession, originalKtFile)
val fileTowerProvider = FileTowerProvider(elementToAnalyze.containingKtFile, towerDataContext)
return LLFirResolveSessionDepended(originalFirResolveSession, fileTowerProvider, ktToFirMapping = null)
}
val originalNonLocalDeclaration = PsiTreeUtil.findSameElementInCopy(copiedNonLocalDeclaration, originalKtFile)
?: errorWithAttachment("Cannot find original function matching") {
withPsiEntry("matchingPsi", elementToAnalyze)
withPsiEntry("originalFile", originalKtFile)
}
if (copiedNonLocalDeclaration is KtNamedDeclaration && originalNonLocalDeclaration is KtNamedDeclaration) {
recordOriginalDeclaration(
targetDeclaration = copiedNonLocalDeclaration,
originalDeclaration = originalNonLocalDeclaration
)
}
val collector = FirTowerDataContextAllElementsCollector()
val copiedFirDeclaration = runBodyResolveOnAir(
originalFirResolveSession,
originalPlace = originalNonLocalDeclaration,
replacementElement = copiedNonLocalDeclaration,
collector = collector,
forcedResolvePhase = requiredResolvePhase(minimalCopiedDeclaration, elementToAnalyze),
)
val mapping = KtToFirMapping(copiedFirDeclaration)
return LLFirResolveSessionDepended(originalFirResolveSession, collector, mapping)
}
private fun tryResolveAsFileAnnotation(
annotationEntry: KtAnnotationEntry,
replacement: RawFirReplacement,
firFile: FirFile,
collector: FirResolveContextCollector? = null,
firResolveSession: LLFirResolvableResolveSession,
): FirAnnotation {
val annotationCall = buildFileFirAnnotation(
firFile = firFile,
fileAnnotation = annotationEntry,
replacement = replacement,
)
val fileAnnotationsContainer = buildFileAnnotationsContainer {
moduleData = firFile.moduleData
containingFileSymbol = firFile.symbol
annotations += annotationCall
}
val llFirResolvableSession = firFile.llFirResolvableSession
?: errorWithAttachment("FirFile session expected to be a resolvable session but was ${firFile.llFirSession::class.java}") {
withEntry("firSession", firFile.llFirSession) { it.toString() }
}
val declarationResolver = llFirResolvableSession.moduleComponents.firModuleLazyDeclarationResolver
declarationResolver.runLazyDesignatedOnAirResolve(
FirDesignationWithFile(path = emptyList(), target = fileAnnotationsContainer, firFile),
collector,
)
collector?.addFileContext(firFile, firFile.createTowerDataContext(firResolveSession.getScopeSessionFor(llFirResolvableSession)))
// We should return annotation from fileAnnotationsContainer because the original annotation can be replaced
return fileAnnotationsContainer.annotations.single()
}
/**
* Creates a new fir declaration from closer non-local declaration based on [originalPlace] position.
* The resulted [FirResolvePhase] depends on [forcedResolvePhase] or the position of [originalPlace] inside the non-local declaration.
*
* Note: the new declaration and its content won't be rebinded to the original.
*
* @see requiredResolvePhase
*/
private fun runBodyResolveOnAir(
firResolveSession: LLFirResolvableResolveSession,
originalPlace: KtElement,
replacementElement: KtElement,
collector: FirResolveContextCollector?,
forcedResolvePhase: FirResolvePhase? = null,
): FirElement {
val minimalOriginalDeclarationToReplace = originalPlace.getNonLocalContainingOrThisDeclarationCodeFragmentAware {
it.isApplicableForOnAirResolve()
}
val originalFirFile = firResolveSession.getOrBuildFirFile(originalPlace.containingKtFile)
val originalDeclaration = minimalOriginalDeclarationToReplace?.onAirGetNonLocalContainingOrThisDeclaration()
if (originalDeclaration == null) {
//It is possible that it is file annotation is going to resolve
val annotationCall = originalPlace.parentOfType<KtAnnotationEntry>(withSelf = true)
if (annotationCall != null) {
return tryResolveAsFileAnnotation(
annotationEntry = annotationCall,
replacement = RawFirReplacement(from = originalPlace, to = replacementElement),
firFile = originalFirFile,
collector = collector,
firResolveSession = firResolveSession,
)
} else {
errorWithFirSpecificEntries(
"Cannot find enclosing declaration for ${originalPlace::class.simpleName}",
psi = originalPlace,
)
}
}
val originalDesignationPath = computeDesignation(originalDeclaration, originalFirFile)
?: errorWithFirSpecificEntries(message = "Impossible to collect designation", fir = originalFirFile, psi = originalDeclaration)
val originalFirDeclaration = originalDesignationPath.target
val session = originalFirDeclaration.llFirResolvableSession
?: errorWithAttachment("Expected resolvable session, found ${originalFirDeclaration.llFirSession::class}") {
withFirEntry("sessionOwner", originalFirDeclaration)
}
/**
* Special case for [getOnAirGetTowerContextProvider]. Can be dropped after KT-59498
*/
val replacement = if (originalPlace === replacementElement) {
RawFirReplacement(from = originalDeclaration, to = originalDeclaration)
} else {
RawFirReplacement(from = originalPlace, to = replacementElement)
}
val newFirDeclaration = RawFirNonLocalDeclarationBuilder.buildWithReplacement(
session = session,
scopeProvider = session.kotlinScopeProvider,
designation = FirDesignation(originalDesignationPath.path, originalFirDeclaration),
rootNonLocalDeclaration = originalDeclaration,
replacement = replacement,
)
/**
* We shouldn't touch script declarations because they're independent of a script
*/
if (newFirDeclaration is FirScript && originalFirDeclaration is FirScript) {
restoreOriginalDeclarationsInScript(originalScript = originalFirDeclaration, newScript = newFirDeclaration)
}
session.moduleComponents.firModuleLazyDeclarationResolver.runLazyDesignatedOnAirResolve(
FirDesignationWithFile(originalDesignationPath.path, newFirDeclaration, originalFirFile),
collector,
forcedResolvePhase ?: requiredResolvePhase(minimalOriginalDeclarationToReplace, originalPlace),
)
return newFirDeclaration
}
/**
* We assume that [newScript] has the same declarations as [originalScript]
*/
private fun restoreOriginalDeclarationsInScript(originalScript: FirScript, newScript: FirScript) {
val updatedDeclarations = ArrayList<FirDeclaration>(newScript.declarations.size)
val originalDeclarations = originalScript.declarations.iterator()
for (recreatedDeclaration in newScript.declarations) {
updatedDeclarations += if (recreatedDeclaration.isElementWhichShouldBeResolvedAsPartOfScript) {
recreatedDeclaration
} else {
originalDeclarations.nextDeclaration() ?: scriptDeclarationInconsistencyError(originalScript, newScript)
}
}
val nextDeclaration = originalDeclarations.nextDeclaration()
if (nextDeclaration != null) {
scriptDeclarationInconsistencyError(originalScript, newScript)
}
newScript.replaceDeclarations(updatedDeclarations)
}
private fun scriptDeclarationInconsistencyError(originalScript: FirScript, newScript: FirScript): Nothing {
val originalDeclarations = originalScript.declarations.filterNot(FirDeclaration::isElementWhichShouldBeResolvedAsPartOfScript)
val newDeclarations = newScript.declarations.filterNot(FirDeclaration::isElementWhichShouldBeResolvedAsPartOfScript)
errorWithAttachment("New script has ${if (newDeclarations.size > originalDeclarations.size) "more" else "less"} declarations") {
withFirEntry("originalScript", originalScript)
withFirEntry("newScript", newScript)
withEntryGroup("originalDeclarations") {
for ((index, declaration) in originalDeclarations.withIndex()) {
withFirEntry(index.toString(), declaration)
}
}
withEntryGroup("newDeclarations") {
for ((index, declaration) in newDeclarations.withIndex()) {
withFirEntry(index.toString(), declaration)
}
}
}
}
private fun Iterator<FirDeclaration>.nextDeclaration(): FirDeclaration? {
while (hasNext()) {
val declaration = next()
if (declaration.isElementWhichShouldBeResolvedAsPartOfScript) continue
return declaration
}
return null
}
private fun computeDesignation(originalDeclaration: KtElement, originalFirFile: FirFile): FirElementFinder.FirDeclarationDesignation? {
if (originalDeclaration is KtCodeFragment) {
val firCodeFragment = originalFirFile.codeFragment
return FirElementFinder.FirDeclarationDesignation(emptyList(), firCodeFragment)
}
require(originalDeclaration is KtDeclaration)
return FirElementFinder.collectDesignationPath(originalFirFile, originalDeclaration)
}
private fun requiredResolvePhase(
container: KtAnnotated,
elementToReplace: PsiElement,
): FirResolvePhase {
assert(container is KtDeclaration || container is KtCodeFragment)
return when {
container !is KtDeclaration -> FirResolvePhase.BODY_RESOLVE
bodyResolveRequired(container, elementToReplace) -> FirResolvePhase.BODY_RESOLVE
annotationMappingRequired(container, elementToReplace) -> FirResolvePhase.ANNOTATION_ARGUMENTS
else -> {
/** Currently it is a minimal phase there we can collect [FirResolveContextCollector] */
FirResolvePhase.CONTRACTS
}
}
}
private fun annotationMappingRequired(
container: KtDeclaration,
elementToReplace: PsiElement,
): Boolean = elementToReplace.parentsWithSelf.takeWhile { it != container }.any {
it is KtAnnotationEntry
} || elementToReplace.anyDescendantOfType<KtAnnotationEntry>(
canGoInside = {
// We shouldn't go inside a class body, because class resolution won't process such elements
// In case of container is class, we are interested only in elements outside its body
it !is KtClassBody
},
)
private fun bodyResolveRequired(container: KtDeclaration, elementToReplace: PsiElement): Boolean = when {
container == elementToReplace -> true
container is KtDeclarationWithBody -> {
val bodyExpression = container.bodyExpression
val secondaryConstructorSuperCallArgs = (container as? KtSecondaryConstructor)?.getDelegationCallOrNull()?.valueArgumentList
bodyExpression.isAncestor(elementToReplace) || secondaryConstructorSuperCallArgs.isAncestor(elementToReplace)
}
container is KtProperty -> container.delegateExpressionOrInitializer.isAncestor(elementToReplace)
container is KtParameter -> container.defaultValue.isAncestor(elementToReplace)
container is KtEnumEntry -> {
container.initializerList.isAncestor(elementToReplace) || container.body.isAncestor(elementToReplace)
}
container is KtClassOrObject -> {
container.superTypeListEntries.any { superTypeEntry ->
when (superTypeEntry) {
is KtSuperTypeCallEntry -> superTypeEntry.valueArgumentList.isAncestor(elementToReplace)
is KtDelegatedSuperTypeEntry -> superTypeEntry.delegateExpression.isAncestor(elementToReplace)
else -> false
}
}
}
container is KtScript || container is KtAnonymousInitializer -> true
else -> false
}
}
@@ -6,14 +6,10 @@
package org.jetbrains.kotlin.analysis.low.level.api.fir.lazy.resolve
import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirModuleResolveComponents
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.FirDesignationWithFile
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLFirResolveTarget
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLFirSingleResolveTarget
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.LLFirWholeElementResolveTarget
import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.llFirModuleData
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.llFirSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.transformers.LLFirLazyResolverRunner
import org.jetbrains.kotlin.analysis.low.level.api.fir.transformers.withOnAirDesignation
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.checkCanceled
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.getContainingFile
import org.jetbrains.kotlin.fir.FirElementWithResolveState
@@ -130,38 +126,6 @@ internal class LLFirModuleLazyDeclarationResolver(val moduleComponents: LLFirMod
}
}
/**
* Resolve on-air created declaration in a context of real [FirDesignationWithFile.firFile] and [FirDesignationWithFile.path].
* If target declaration is [FirFile] then the entire file will be resolved.
* The same for [FirRegularClass] if [resolvePhase] is [FirResolvePhase.BODY_RESOLVE].
*/
fun runLazyDesignatedOnAirResolve(
designation: FirDesignationWithFile,
towerDataContextCollector: FirResolveContextCollector?,
resolvePhase: FirResolvePhase = FirResolvePhase.BODY_RESOLVE,
) {
resolveFileToImportsWithLock(designation.firFile)
val target = when {
designation.target is FirFile -> LLFirWholeElementResolveTarget(designation.firFile)
resolvePhase == FirResolvePhase.BODY_RESOLVE && designation.target is FirRegularClass -> {
LLFirWholeElementResolveTarget(designation.firFile, designation.path, designation.target)
}
else -> LLFirSingleResolveTarget(designation.firFile, designation.path, designation.target)
}
try {
withOnAirDesignation(designation) {
// New session to avoid garbage in the original session
val scopeSession = ScopeSession()
lazyResolveTargets(listOf(target), scopeSession, resolvePhase, towerDataContextCollector)
}
} catch (e: Exception) {
handleExceptionFromResolve(e, target, resolvePhase)
}
}
private fun resolveContainingFileToImports(target: FirElementWithResolveState) {
if (target.resolvePhase >= FirResolvePhase.IMPORTS) return
val firFile = target.getContainingFile() ?: return
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.analysis.low.level.api.fir.providers
import org.jetbrains.kotlin.analysis.low.level.api.fir.transformers.SyntheticFirClassProvider
import org.jetbrains.kotlin.fir.declarations.FirClassLikeDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.resolve.providers.FirProvider
@@ -20,8 +19,7 @@ internal class LLFirLibrarySessionProvider(
override val symbolProvider: FirSymbolProvider
) : FirProvider() {
override fun getFirClassifierByFqName(classId: ClassId): FirClassLikeDeclaration? {
return SyntheticFirClassProvider.getInstance(symbolProvider.session).getFirClassifierByFqName(classId)
?: symbolProvider.getClassLikeSymbolByClassId(classId)?.fir
return symbolProvider.getClassLikeSymbolByClassId(classId)?.fir
}
override fun getFirClassifierContainerFile(fqName: ClassId): FirFile = shouldNotBeCalled()
@@ -8,7 +8,6 @@ package org.jetbrains.kotlin.analysis.low.level.api.fir.providers
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirModuleResolveComponents
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.transformers.SyntheticFirClassProvider
import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProvider
import org.jetbrains.kotlin.analysis.providers.KotlinPackageProvider
import org.jetbrains.kotlin.fir.NoMutableState
@@ -62,8 +61,9 @@ internal class LLFirProvider(
private fun getFirClassifierByFqNameAndDeclaration(
classId: ClassId,
classLikeDeclaration: KtClassLikeDeclaration?,
): FirClassLikeDeclaration? = SyntheticFirClassProvider.getInstance(session).getFirClassifierByFqName(classId)
?: providerHelper.getFirClassifierByFqNameAndDeclaration(classId, classLikeDeclaration)
): FirClassLikeDeclaration? {
return providerHelper.getFirClassifierByFqNameAndDeclaration(classId, classLikeDeclaration)
}
override fun getFirClassifierContainerFile(fqName: ClassId): FirFile {
return getFirClassifierContainerFileIfAny(fqName)
@@ -73,8 +73,7 @@ internal class LLFirProvider(
}
override fun getFirClassifierContainerFileIfAny(fqName: ClassId): FirFile? {
return SyntheticFirClassProvider.getInstance(session).getFirClassifierContainerFileIfAny(fqName)
?: getFirClassifierByFqName(fqName)?.let { moduleComponents.cache.getContainerFirFile(it) }
return getFirClassifierByFqName(fqName)?.let { moduleComponents.cache.getContainerFirFile(it) }
}
override fun getFirClassifierContainerFile(symbol: FirClassLikeSymbol<*>): FirFile {
@@ -85,13 +84,11 @@ internal class LLFirProvider(
}
override fun getFirClassifierContainerFileIfAny(symbol: FirClassLikeSymbol<*>): FirFile? {
return SyntheticFirClassProvider.getInstance(session).getFirClassifierContainerFileIfAny(symbol.classId)
?: moduleComponents.cache.getContainerFirFile(symbol.fir)
return moduleComponents.cache.getContainerFirFile(symbol.fir)
}
override fun getFirCallableContainerFile(symbol: FirCallableSymbol<*>): FirFile? {
return symbol.callableId.classId?.let { SyntheticFirClassProvider.getInstance(session).getFirClassifierContainerFileIfAny(it) }
?: moduleComponents.cache.getContainerFirFile(symbol.fir)
return moduleComponents.cache.getContainerFirFile(symbol.fir)
}
override fun getFirScriptContainerFile(symbol: FirScriptSymbol): FirFile? {
@@ -8,7 +8,6 @@ package org.jetbrains.kotlin.analysis.low.level.api.fir.state
import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirModuleResolveComponents
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirResolveSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getModule
import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.FirTowerContextProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.getNonLocalContainingDeclaration
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.*
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.FirDeclarationForCompiledElementSearcher
@@ -131,8 +130,4 @@ internal class LLFirResolvableResolveSession(
override fun resolveFirToPhase(declaration: FirDeclaration, toPhase: FirResolvePhase) {
declaration.lazyResolveToPhase(toPhase)
}
override fun getTowerContextProvider(ktFile: KtFile): FirTowerContextProvider {
return TowerProviderForElementForState(this)
}
}
@@ -1,30 +0,0 @@
/*
* 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.analysis.low.level.api.fir.state
import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirResolveSessionDepended
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirResolveSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LowLevelFirApiFacadeForResolveOnAir
import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.FirTowerContextProvider
import org.jetbrains.kotlin.fir.declarations.FirTowerDataContext
import org.jetbrains.kotlin.psi.KtElement
internal class TowerProviderForElementForState(private val firResolveSession: LLFirResolveSession) : FirTowerContextProvider {
override fun getClosestAvailableParentContext(ktElement: KtElement): FirTowerDataContext? {
if (firResolveSession is LLFirResolveSessionDepended) {
firResolveSession.towerProviderBuiltUponElement
.getClosestAvailableParentContext(ktElement)
?.let { return it }
return LowLevelFirApiFacadeForResolveOnAir
.getOnAirGetTowerContextProvider(firResolveSession.originalFirResolveSession, ktElement)
.getClosestAvailableParentContext(ktElement)
}
return LowLevelFirApiFacadeForResolveOnAir
.getOnAirGetTowerContextProvider(firResolveSession, ktElement)
.getClosestAvailableParentContext(ktElement)
}
}
@@ -5,12 +5,7 @@
package org.jetbrains.kotlin.analysis.low.level.api.fir.transformers
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirElementWithResolveState
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
@DslMarker
internal annotation class StateKeeperDsl
@@ -264,15 +259,6 @@ internal inline fun <Target : FirElementWithResolveState, Context : Any, Result>
prepareTarget: (Target) -> Unit = {},
action: () -> Result,
): Result {
val onAirAnalysisTarget = target.moduleData.session.onAirAnalysisTarget
if (onAirAnalysisTarget != null && target in onAirAnalysisTarget) {
// Several arrangers reset declaration bodies to lazy ones, and then re-construct the FIR tree from the backing PSI.
// This won't work for on-air analysis, as the tree is manually modified. However, it doesn't seem that tree guards are needed
// in the first place, as results of the on-air analysis aren't going to be shared.
prepareTarget(target)
return action()
}
var preservedState: PreservedState? = null
try {
@@ -284,26 +270,4 @@ internal inline fun <Target : FirElementWithResolveState, Context : Any, Result>
preservedState?.restore()
throw e
}
}
private operator fun FirElementWithResolveState.contains(child: FirElementWithResolveState): Boolean {
if (this == child) {
return true
}
var isFound = false
accept(object : FirVisitorVoid() {
override fun visitElement(element: FirElement) {
if (!isFound) {
if (element == child) {
isFound = true
} else if (element is FirDeclaration || element !is FirStatement) {
element.acceptChildren(this)
}
}
}
})
return isFound
}
@@ -1,91 +0,0 @@
/*
* Copyright 2010-2021 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.transformers
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.FirDesignationWithFile
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirElementWithResolveState
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirClassLikeDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
import org.jetbrains.kotlin.name.ClassId
internal interface SyntheticFirClassProvider {
fun getFirClassifierContainerFileIfAny(classId: ClassId): FirFile?
fun getFirClassifierByFqName(classId: ClassId): FirClassLikeDeclaration?
companion object {
fun getInstance(session: FirSession): SyntheticFirClassProvider {
return onAirProviderForThread.get()?.takeIf { it.session == session } ?: EmptySyntheticFirClassProvider
}
}
}
internal val FirSession.onAirAnalysisTarget: FirElementWithResolveState?
get() = onAirProviderForThread.get()?.target
/**
* Injects a designation-based class file provider to 'LLFirProvider'.
* Used for on-air analysis.
*/
internal fun withOnAirDesignation(designation: FirDesignationWithFile, block: () -> Unit) {
check(onAirProviderForThread.get() == null) { "Nested on-air analysis is not allowed" }
try {
// Make additional classes available only inside a given `block`.
// The provider is stored inside a `ThreadLocal` value to avoid parallel analysis modification.
val provider = OnAirSyntheticFirClassProvider.create(designation)
onAirProviderForThread.set(provider)
block()
} finally {
onAirProviderForThread.remove()
}
}
private class OnAirSyntheticFirClassProvider private constructor(
private val firFile: FirFile,
val target: FirElementWithResolveState,
private val classes: Map<ClassId, FirClassLikeDeclaration>
) : SyntheticFirClassProvider {
val session: FirSession
get() = firFile.moduleData.session
override fun getFirClassifierContainerFileIfAny(classId: ClassId): FirFile? {
return if (classId in classes) firFile else null
}
override fun getFirClassifierByFqName(classId: ClassId): FirClassLikeDeclaration? {
return classes[classId]
}
companion object {
fun create(designation: FirDesignationWithFile): OnAirSyntheticFirClassProvider {
val firFile = designation.firFile
val firElement = designation.target
val nodeInfoCollector = object : FirVisitorVoid() {
val classes = mutableMapOf<ClassId, FirClassLikeDeclaration>()
override fun visitElement(element: FirElement) {
if (element is FirClassLikeDeclaration) {
classes[element.symbol.classId] = element
}
element.acceptChildren(this)
}
}
nodeInfoCollector.visitElement(firElement)
return OnAirSyntheticFirClassProvider(firFile, designation.target, nodeInfoCollector.classes)
}
}
}
private object EmptySyntheticFirClassProvider : SyntheticFirClassProvider {
override fun getFirClassifierContainerFileIfAny(classId: ClassId) = null
override fun getFirClassifierByFqName(classId: ClassId) = null
}
private val onAirProviderForThread: ThreadLocal<OnAirSyntheticFirClassProvider?> = ThreadLocal()