FIR IDE: implement incremental function analysis

This commit is contained in:
Ilya Kirillov
2020-09-17 15:14:16 +03:00
parent 8bcba00bb7
commit 9a99703f24
20 changed files with 583 additions and 271 deletions
@@ -281,14 +281,19 @@ inline fun <reified T : PsiElement> PsiElement.collectDescendantsOfType(noinline
inline fun <reified T : PsiElement> PsiElement.collectDescendantsOfType( inline fun <reified T : PsiElement> PsiElement.collectDescendantsOfType(
crossinline canGoInside: (PsiElement) -> Boolean, crossinline canGoInside: (PsiElement) -> Boolean,
noinline predicate: (T) -> Boolean = { true } noinline predicate: (T) -> Boolean = { true }
): List<T> { ): List<T> = collectDescendantsOfTypeTo(ArrayList(), canGoInside, predicate)
val result = ArrayList<T>()
inline fun <reified T : PsiElement, C : MutableCollection<T>> PsiElement.collectDescendantsOfTypeTo(
to: C,
crossinline canGoInside: (PsiElement) -> Boolean,
noinline predicate: (T) -> Boolean = { true }
): C {
forEachDescendantOfType<T>(canGoInside) { forEachDescendantOfType<T>(canGoInside) {
if (predicate(it)) { if (predicate(it)) {
result.add(it) to.add(it)
} }
} }
return result return to
} }
// ----------- Working with offsets, ranges and texts ---------------------------------------------------------------------------------------------- // ----------- Working with offsets, ranges and texts ----------------------------------------------------------------------------------------------
@@ -14,6 +14,8 @@ import com.intellij.psi.util.CachedValuesManager
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
import org.jetbrains.kotlin.idea.caches.project.LibraryModificationTracker import org.jetbrains.kotlin.idea.caches.project.LibraryModificationTracker
import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
import org.jetbrains.kotlin.idea.caches.trackers.KotlinModuleOutOfCodeBlockModificationTracker
import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver
import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeDependentModulesSourcesSession import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeDependentModulesSourcesSession
import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeLibrariesSession import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeLibrariesSession
@@ -25,7 +27,7 @@ import org.jetbrains.kotlin.idea.util.psiModificationTrackerBasedCachedValue
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
internal class FirIdeResolveStateService(private val project: Project) { internal class FirIdeResolveStateService(private val project: Project) {
private val stateCache by psiModificationTrackerBasedCachedValue(project) { private val stateCache by cachedValue(project, KotlinCodeBlockModificationListener.getInstance(project).kotlinOutOfCodeBlockTracker) {
ConcurrentHashMap<IdeaModuleInfo, FirModuleResolveStateImpl>() ConcurrentHashMap<IdeaModuleInfo, FirModuleResolveStateImpl>()
} }
@@ -35,7 +37,6 @@ internal class FirIdeResolveStateService(private val project: Project) {
ConcurrentHashMap<IdeaModuleInfo, FirIdeLibrariesSession>() ConcurrentHashMap<IdeaModuleInfo, FirIdeLibrariesSession>()
} }
private fun createResolveStateFor(moduleInfo: IdeaModuleInfo): FirModuleResolveStateImpl { private fun createResolveStateFor(moduleInfo: IdeaModuleInfo): FirModuleResolveStateImpl {
require(moduleInfo is ModuleSourceInfo) require(moduleInfo is ModuleSourceInfo)
val firPhaseRunner = FirPhaseRunner() val firPhaseRunner = FirPhaseRunner()
@@ -11,17 +11,16 @@ import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFile import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.resolve.providers.FirProvider
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.DiagnosticsCollector import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.DiagnosticsCollector
import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.FirElementBuilder import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.FirElementBuilder
import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.FirTowerDataContextCollector import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.FirTowerDataContextCollector
import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.PsiToFirCache
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.FirFileBuilder import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.FirFileBuilder
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.FileStructureCache
import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver
import org.jetbrains.kotlin.idea.fir.low.level.api.providers.FirIdeProvider import org.jetbrains.kotlin.idea.fir.low.level.api.providers.FirIdeProvider
import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.*
import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeCurrentModuleSourcesSession import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeCurrentModuleSourcesSession
import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeDependentModulesSourcesSession import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeDependentModulesSourcesSession
import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeLibrariesSession import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeLibrariesSession
@@ -45,16 +44,13 @@ abstract class FirModuleResolveState {
// todo temporary, used only in completion // todo temporary, used only in completion
abstract fun recordPsiToFirMappingsForCompletionFrom(fir: FirDeclaration, firFile: FirFile, ktFile: KtFile) abstract fun recordPsiToFirMappingsForCompletionFrom(fir: FirDeclaration, firFile: FirFile, ktFile: KtFile)
// todo temporary, used only in completion
abstract fun getCachedMappingForCompletion(element: KtElement): FirElement?
abstract fun <D : FirDeclaration> resolvedFirToPhase(declaration: D, toPhase: FirResolvePhase): D abstract fun <D : FirDeclaration> resolvedFirToPhase(declaration: D, toPhase: FirResolvePhase): D
// todo temporary, used only in completion // todo temporary, used only in completion
internal abstract fun lazyResolveDeclarationForCompletion( abstract fun lazyResolveDeclarationForCompletion(
firFunction: FirDeclaration, firFunction: FirDeclaration,
containerFirFile: FirFile, containerFirFile: FirFile,
firIdeProvider: FirIdeProvider, firIdeProvider: FirProvider,
toPhase: FirResolvePhase, toPhase: FirResolvePhase,
towerDataContextCollector: FirTowerDataContextCollector towerDataContextCollector: FirTowerDataContextCollector
) )
@@ -71,22 +67,21 @@ internal class FirModuleResolveStateImpl(
val firFileBuilder: FirFileBuilder, val firFileBuilder: FirFileBuilder,
val firLazyDeclarationResolver: FirLazyDeclarationResolver, val firLazyDeclarationResolver: FirLazyDeclarationResolver,
) : FirModuleResolveState() { ) : FirModuleResolveState() {
val psiToFirCache = PsiToFirCache(currentModuleSourcesSession.cache) val fileStructureCache = FileStructureCache(firFileBuilder, firLazyDeclarationResolver)
val elementBuilder = FirElementBuilder(firFileBuilder, firLazyDeclarationResolver) val elementBuilder = FirElementBuilder()
private val diagnosticsCollector = private val diagnosticsCollector = DiagnosticsCollector(fileStructureCache, currentModuleSourcesSession.cache)
DiagnosticsCollector(firFileBuilder, elementBuilder, psiToFirCache, currentModuleSourcesSession.cache)
override fun getSessionFor(moduleInfo: IdeaModuleInfo): FirSession = override fun getSessionFor(moduleInfo: IdeaModuleInfo): FirSession =
sessionProvider.getSession(moduleInfo) sessionProvider.getSession(moduleInfo)
override fun getOrBuildFirFor(element: KtElement, toPhase: FirResolvePhase): FirElement = override fun getOrBuildFirFor(element: KtElement, toPhase: FirResolvePhase): FirElement =
elementBuilder.getOrBuildFirFor(element, currentModuleSourcesSession.cache, psiToFirCache, toPhase) elementBuilder.getOrBuildFirFor(element, currentModuleSourcesSession.cache, fileStructureCache)
override fun getDiagnostics(element: KtElement): List<Diagnostic> = override fun getDiagnostics(element: KtElement): List<Diagnostic> =
diagnosticsCollector.getDiagnosticsFor(element) diagnosticsCollector.getDiagnosticsFor(element)
override fun recordPsiToFirMappingsForCompletionFrom(fir: FirDeclaration, firFile: FirFile, ktFile: KtFile) { override fun recordPsiToFirMappingsForCompletionFrom(fir: FirDeclaration, firFile: FirFile, ktFile: KtFile) {
psiToFirCache.recordElementsForCompletionFrom(fir, firFile, ktFile) error("Should be called only from FirModuleResolveStateForCompletion")
} }
override fun <D : FirDeclaration> resolvedFirToPhase(declaration: D, toPhase: FirResolvePhase): D { override fun <D : FirDeclaration> resolvedFirToPhase(declaration: D, toPhase: FirResolvePhase): D {
@@ -98,13 +93,10 @@ internal class FirModuleResolveStateImpl(
return declaration return declaration
} }
override fun getCachedMappingForCompletion(element: KtElement): FirElement? =
psiToFirCache.getCachedMapping(element)
override fun lazyResolveDeclarationForCompletion( override fun lazyResolveDeclarationForCompletion(
firFunction: FirDeclaration, firFunction: FirDeclaration,
containerFirFile: FirFile, containerFirFile: FirFile,
firIdeProvider: FirIdeProvider, firIdeProvider: FirProvider,
toPhase: FirResolvePhase, toPhase: FirResolvePhase,
towerDataContextCollector: FirTowerDataContextCollector towerDataContextCollector: FirTowerDataContextCollector
) { ) {
@@ -113,6 +105,7 @@ internal class FirModuleResolveStateImpl(
currentModuleSourcesSession.cache, currentModuleSourcesSession.cache,
containerFirFile, containerFirFile,
firIdeProvider, firIdeProvider,
fromPhase = firFunction.resolvePhase,
toPhase, toPhase,
towerDataContextCollector, towerDataContextCollector,
checkPCE = false checkPCE = false
@@ -11,9 +11,11 @@ import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFile import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.resolve.providers.FirProvider
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.FirTowerDataContextCollector import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.FirTowerDataContextCollector
import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.PsiToFirCache import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.FileStructureCache
import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.FirElementsRecorder
import org.jetbrains.kotlin.idea.fir.low.level.api.providers.FirIdeProvider import org.jetbrains.kotlin.idea.fir.low.level.api.providers.FirIdeProvider
import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeCurrentModuleSourcesSession import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeCurrentModuleSourcesSession
import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeDependentModulesSourcesSession import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.FirIdeDependentModulesSourcesSession
@@ -29,29 +31,24 @@ internal class FirModuleResolveStateForCompletion(
override val dependentModulesSourcesSession: FirIdeDependentModulesSourcesSession get() = originalState.dependentModulesSourcesSession override val dependentModulesSourcesSession: FirIdeDependentModulesSourcesSession get() = originalState.dependentModulesSourcesSession
override val librariesSession: FirIdeLibrariesSession get() = originalState.librariesSession override val librariesSession: FirIdeLibrariesSession get() = originalState.librariesSession
private val psiToFirCache = PsiToFirCache(originalState.currentModuleSourcesSession.cache) private val fileStructureCache = originalState.fileStructureCache
private val completionMapping = mutableMapOf<KtElement, FirElement>()
override fun getSessionFor(moduleInfo: IdeaModuleInfo): FirSession = override fun getSessionFor(moduleInfo: IdeaModuleInfo): FirSession =
originalState.getSessionFor(moduleInfo) originalState.getSessionFor(moduleInfo)
override fun getOrBuildFirFor(element: KtElement, toPhase: FirResolvePhase): FirElement { override fun getOrBuildFirFor(element: KtElement, toPhase: FirResolvePhase): FirElement {
getCachedMappingForCompletion(element)?.let { return it } completionMapping[element]?.let { return it }
return originalState.elementBuilder.getOrBuildFirFor( return originalState.elementBuilder.getOrBuildFirFor(
element, element,
originalState.currentModuleSourcesSession.cache, originalState.currentModuleSourcesSession.cache,
psiToFirCache, fileStructureCache,
toPhase
) )
} }
override fun recordPsiToFirMappingsForCompletionFrom(fir: FirDeclaration, firFile: FirFile, ktFile: KtFile) { override fun recordPsiToFirMappingsForCompletionFrom(fir: FirDeclaration, firFile: FirFile, ktFile: KtFile) {
psiToFirCache.recordElementsForCompletionFrom(fir, firFile, ktFile) fir.accept(FirElementsRecorder(), completionMapping)
}
override fun getCachedMappingForCompletion(element: KtElement): FirElement? {
psiToFirCache.getCachedMapping(element)?.let { return it }
originalState.psiToFirCache.getCachedMapping(element)?.let { return it }
return null
} }
override fun <D : FirDeclaration> resolvedFirToPhase(declaration: D, toPhase: FirResolvePhase): D { override fun <D : FirDeclaration> resolvedFirToPhase(declaration: D, toPhase: FirResolvePhase): D {
@@ -61,7 +58,7 @@ internal class FirModuleResolveStateForCompletion(
override fun lazyResolveDeclarationForCompletion( override fun lazyResolveDeclarationForCompletion(
firFunction: FirDeclaration, firFunction: FirDeclaration,
containerFirFile: FirFile, containerFirFile: FirFile,
firIdeProvider: FirIdeProvider, firIdeProvider: FirProvider,
toPhase: FirResolvePhase, toPhase: FirResolvePhase,
towerDataContextCollector: FirTowerDataContextCollector towerDataContextCollector: FirTowerDataContextCollector
) { ) {
@@ -0,0 +1,35 @@
/*
* 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.idea.fir.low.level.api
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.FirSessionComponent
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.transformers.FirTransformerBasedResolveProcessor
import org.jetbrains.kotlin.fir.resolve.transformers.createTransformerBasedProcessorByPhase
import java.util.*
class FirTransformerProvider(session: FirSession) : FirSessionComponent {
private val scopeSession = ThreadLocal.withInitial { ScopeSession() }
private val transformers = ThreadLocal.withInitial {
val scopesSession = scopeSession.get()
EnumMap<FirResolvePhase, FirTransformerBasedResolveProcessor>(FirResolvePhase::class.java).apply {
FirResolvePhase.values().forEach { resolvePhase ->
if (resolvePhase != FirResolvePhase.RAW_FIR) {
put(resolvePhase, resolvePhase.createTransformerBasedProcessorByPhase(session, scopesSession))
}
}
}
}
fun getTransformerForPhase(phase: FirResolvePhase): FirTransformerBasedResolveProcessor =
transformers.get().getValue(phase)
fun getScopeSession(): ScopeSession = scopeSession.get()
}
val FirSession.firTransformerProvider: FirTransformerProvider by FirSession.sessionComponentAccessor()
@@ -13,7 +13,6 @@ import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.builder.buildPropertyAccessorCopy import org.jetbrains.kotlin.fir.declarations.builder.buildPropertyAccessorCopy
import org.jetbrains.kotlin.fir.declarations.builder.buildSimpleFunctionCopy import org.jetbrains.kotlin.fir.declarations.builder.buildSimpleFunctionCopy
import org.jetbrains.kotlin.fir.declarations.builder.buildPropertyCopy import org.jetbrains.kotlin.fir.declarations.builder.buildPropertyCopy
import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.resolve.FirTowerDataContext import org.jetbrains.kotlin.fir.resolve.FirTowerDataContext
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
import org.jetbrains.kotlin.idea.fir.low.level.api.providers.firIdeProvider import org.jetbrains.kotlin.idea.fir.low.level.api.providers.firIdeProvider
@@ -43,16 +42,11 @@ object LowLevelFirApiFacade {
class FirCompletionContext internal constructor( class FirCompletionContext internal constructor(
val session: FirSession, val session: FirSession,
private val towerDataContextCollector: FirTowerDataContextCollector, private val towerDataContextCollector: FirTowerDataContextCollector,
private val state: FirModuleResolveState,
) { ) {
fun getTowerDataContext(element: KtElement): FirTowerDataContext { fun getTowerDataContext(element: KtElement): FirTowerDataContext {
var current: PsiElement? = element var current: PsiElement? = element
while (current is KtElement) { while (current is KtElement) {
val mappedFir = state.getCachedMappingForCompletion(current) towerDataContextCollector.getContext(current)?.let { return it }
if (mappedFir is FirStatement) {
towerDataContextCollector.getContext(mappedFir)?.let { return it }
}
current = current.parent current = current.parent
} }
@@ -78,8 +72,7 @@ object LowLevelFirApiFacade {
return FirCompletionContext( return FirCompletionContext(
copyFunction.session, copyFunction.session,
contextCollector, contextCollector
state
) )
} }
@@ -101,8 +94,7 @@ object LowLevelFirApiFacade {
return FirCompletionContext( return FirCompletionContext(
copyProperty.session, copyProperty.session,
contextCollector, contextCollector
state
) )
} }
@@ -145,7 +137,7 @@ object LowLevelFirApiFacade {
symbol = builtSetter.symbol symbol = builtSetter.symbol
resolvePhase = minOf(builtSetter.resolvePhase, FirResolvePhase.DECLARATIONS) resolvePhase = minOf(builtSetter.resolvePhase, FirResolvePhase.DECLARATIONS)
source = builtSetter.source source = builtSetter.source
session = state.firIdeSourcesSession session = state.currentModuleSourcesSession
} }
} else { } else {
builtSetter builtSetter
@@ -159,7 +151,7 @@ object LowLevelFirApiFacade {
resolvePhase = minOf(originalProperty.resolvePhase, FirResolvePhase.DECLARATIONS) resolvePhase = minOf(originalProperty.resolvePhase, FirResolvePhase.DECLARATIONS)
source = builtProperty.source source = builtProperty.source
session = state.firIdeSourcesSession session = state.currentModuleSourcesSession
} }
} }
@@ -5,57 +5,68 @@
package org.jetbrains.kotlin.idea.fir.low.level.api.element.builder package org.jetbrains.kotlin.idea.fir.low.level.api.element.builder
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.resolve.firProvider
import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver
import org.jetbrains.kotlin.idea.fir.low.level.api.annotations.ThreadSafe import org.jetbrains.kotlin.idea.fir.low.level.api.annotations.ThreadSafe
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.FirFileBuilder import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.FirFileBuilder
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache
import org.jetbrains.kotlin.idea.fir.low.level.api.util.FirElementFinder import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.FileStructureCache
import org.jetbrains.kotlin.idea.fir.low.level.api.util.findNonLocalFirDeclaration
import org.jetbrains.kotlin.idea.util.getElementTextInContext import org.jetbrains.kotlin.idea.util.getElementTextInContext
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
/** /**
* Maps [KtElement] to [FirElement] * Maps [KtElement] to [FirElement]
* Stateless, caches everything into [ModuleFileCache] & [PsiToFirCache] passed into the function * Stateless, caches everything into [ModuleFileCache] & [FileStructureCache] passed into the function
*/ */
@ThreadSafe @ThreadSafe
internal class FirElementBuilder( internal class FirElementBuilder {
private val firFileBuilder: FirFileBuilder,
private val firLazyDeclarationResolver: FirLazyDeclarationResolver,
) {
fun getOrBuildFirFor( fun getOrBuildFirFor(
element: KtElement, element: KtElement,
moduleFileCache: ModuleFileCache, moduleFileCache: ModuleFileCache,
psiToFirCache: PsiToFirCache, fileStructureCache: FileStructureCache,
toPhase: FirResolvePhase,
): FirElement { ): FirElement {
val ktFile = element.containingKtFile val fileStructure = fileStructureCache.getFileStructure(element.containingKtFile, moduleFileCache)
val firFile = firFileBuilder.buildRawFirFileWithCaching(ktFile, moduleFileCache) val mappings = fileStructure.getStructureElementFor(element).mappings
val containerFir = when (val container = element.getNonLocalContainingDeclarationWithFqName()) { val psi = when {
is KtDeclaration -> container.findNonLocalFirDeclaration(firFileBuilder, firFile.session.firProvider, moduleFileCache) element is KtPropertyDelegate -> element.expression ?: element
null -> firFile element is KtQualifiedExpression && element.selectorExpression is KtCallExpression -> {
else -> error("Unsupported: ${container.text}") /*
KtQualifiedExpression with KtCallExpression in selector transformed in FIR to FirFunctionCall expression
Which will have a receiver as qualifier
*/
element.selectorExpression ?: error("Incomplete code:\n${element.getElementTextInContext()}")
}
else -> element
} }
firLazyDeclarationResolver.lazyResolveDeclaration(containerFir, moduleFileCache, toPhase, checkPCE = true) mappings[psi]?.let { return it }
return psi.getFirOfClosestParent(mappings)?.second
return psiToFirCache.getFir(element, containerFir, firFile) ?: error("FirElement is not found for:\n${element.getElementTextInContext()}")
} }
} }
private fun KtElement.getFirOfClosestParent(cache: Map<KtElement, FirElement>): Pair<KtElement, FirElement>? {
var current: PsiElement? = this
while (current is KtElement) {
val mappedFir = cache[current]
if (mappedFir != null) {
return current to mappedFir
}
current = current.parent
}
fun KtElement.getNonLocalContainingDeclarationWithFqName(): KtNamedDeclaration? { return null
var container = parent }
fun KtElement.getNonLocalContainingOrThisDeclaration(): KtNamedDeclaration? {
var container: PsiElement? = this
while (container != null && container !is KtFile) { while (container != null && container !is KtFile) {
if (container is KtNamedDeclaration if (container is KtNamedDeclaration
&& (container is KtClassOrObject || container is KtDeclarationWithBody) && (container is KtClassOrObject || container is KtDeclarationWithBody || container is KtProperty || container is KtTypeAlias)
&& !KtPsiUtil.isLocal(container) && !KtPsiUtil.isLocal(container)
&& container.name != null
&& container !is KtEnumEntry && container !is KtEnumEntry
&& container.containingClassOrObject !is KtEnumEntry && container.containingClassOrObject !is KtEnumEntry
) { ) {
@@ -5,31 +5,22 @@
package org.jetbrains.kotlin.idea.fir.low.level.api.element.builder package org.jetbrains.kotlin.idea.fir.low.level.api.element.builder
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.expressions.FirStatement import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.psi import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.resolve.FirTowerDataContext import org.jetbrains.kotlin.fir.resolve.FirTowerDataContext
import org.jetbrains.kotlin.psi.KtElement
internal class FirTowerDataContextCollector { class FirTowerDataContextCollector {
private val state: MutableMap<FirElement, FirTowerDataContext> = mutableMapOf() private val state: MutableMap<KtElement, FirTowerDataContext> = mutableMapOf()
fun addStatementContext(statement: FirStatement, context: FirTowerDataContext) { fun addStatementContext(statement: FirStatement, context: FirTowerDataContext) {
state[statement] = context (statement.psi as? KtElement)?.let { state[it] = context }
} }
fun addDeclarationContext(declaration: FirDeclaration, context: FirTowerDataContext) { fun addDeclarationContext(declaration: FirDeclaration, context: FirTowerDataContext) {
state[declaration] = context (declaration.psi as? KtElement)?.let { state[it] = context }
} }
fun getContext(statement: FirElement): FirTowerDataContext? { fun getContext(psi: KtElement): FirTowerDataContext? = state[psi]
this.state[statement]?.let { return it }
for ((key, value) in this.state) {
// TODO Rework that
if (key.psi == statement.psi) {
return value
}
}
return null
}
} }
@@ -1,160 +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.idea.fir.low.level.api.element.builder
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.realPsi
import org.jetbrains.kotlin.fir.references.*
import org.jetbrains.kotlin.fir.types.FirErrorTypeRef
import org.jetbrains.kotlin.fir.types.FirTypeRef
import org.jetbrains.kotlin.fir.types.FirUserTypeRef
import org.jetbrains.kotlin.fir.types.impl.FirResolvedTypeRefImpl
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache
import org.jetbrains.kotlin.idea.fir.low.level.api.util.isErrorElement
import java.util.concurrent.ConcurrentHashMap
import org.jetbrains.kotlin.idea.util.getElementTextInContext
import org.jetbrains.kotlin.psi.*
/**
* Belongs to a [org.jetbrains.kotlin.idea.fir.low.level.api.FirModuleResolveState]
*/
internal class PsiToFirCache(private val moduleFileCache: ModuleFileCache) {
private val caches = ConcurrentHashMap<KtFile, FileCache>()
fun getCachedMapping(element: KtElement): FirElement? {
val ktFile = element.containingKtFile
val cache = caches[ktFile]
return cache?.getCachedMapping(element)
}
fun getFir(element: KtElement, containerFir: FirDeclaration, firFile: FirFile): FirElement {
val ktFile = element.containingKtFile
val cache = caches.getOrPut(ktFile) { FileCache(ktFile, firFile, moduleFileCache) }
return cache.getFir(element, containerFir)
}
//todo for completion only
fun recordElementsForCompletionFrom(containerFir: FirDeclaration, firFile: FirFile, ktFile: KtFile) {
val cache = caches.getOrPut(ktFile) { FileCache(ktFile, firFile, moduleFileCache) }
cache.recordElementsForCompletionFrom(containerFir)
}
}
internal class FileCache(val ktFile: KtFile, firFile: FirFile, moduleFileCache: ModuleFileCache) {
private val cache: ConcurrentHashMap<KtElement, FirElement> = ConcurrentHashMap()
fun getCachedMapping(ktElement: KtElement): FirElement? {
require(ktElement.containingKtFile === ktFile)
return cache[ktElement]
}
fun getFir(element: KtElement, containerFir: FirDeclaration): FirElement {
val psi = when {
element is KtPropertyDelegate -> element.expression ?: element
element is KtQualifiedExpression && element.selectorExpression is KtCallExpression -> {
/*
KtQualifiedExpression with KtCallExpression in selector transformed in FIR to FirFunctionCall expression
Which will have a receiver as qualifier
*/
element.selectorExpression ?: error("Incomplete code:\n${element.getElementTextInContext()}")
}
else -> element
}
cache[psi]?.let { return it }
recordElementsFrom(containerFir)
val (current, mappedFir) = psi.getFirOfClosestParent()
?: error("FirElement is not found for:\n${element.getElementTextInContext()}")
if (current !== element) {
cache(current, mappedFir)
}
return mappedFir
}
fun cache(psi: KtElement, fir: FirElement) {
// todo make it thread safe
val existingFir = cache[psi]
if (existingFir != null && existingFir !== fir) {
when {
existingFir is FirTypeRef && fir is FirTypeRef && psi is KtTypeReference -> {
// FirTypeRefs are often created during resolve
// a lot of them with have the same source
// we want to take the most "resolved one" here
if (fir is FirResolvedTypeRefImpl && existingFir !is FirResolvedTypeRefImpl) {
cache[psi] = fir
}
}
existingFir.isErrorElement && !fir.isErrorElement -> {
// TODO better handle error elements
// but for now just take first non-error one if such exist
cache[psi] = fir
}
existingFir.isErrorElement || fir.isErrorElement -> {
// do nothing and maybe upgrade to a non-error element in the branch above in the future
}
else -> {
if (DuplicatedFirSourceElementsException.IS_ENABLED) {
throw DuplicatedFirSourceElementsException(existingFir, fir, psi)
}
}
}
}
if (existingFir == null) {
cache[psi] = fir
}
}
fun recordElementsForCompletionFrom(firDeclaration: FirDeclaration) {
recordElementsFrom(firDeclaration)
}
private fun recordElementsFrom(firDeclaration: FirDeclaration) {
firDeclaration.accept(elementsRecorder)
}
private val elementsRecorder = object : FirVisitorVoid() {
override fun visitElement(element: FirElement) {
(element.realPsi as? KtElement)?.let { psi ->
cache(psi, element)
}
element.acceptChildren(this)
}
override fun visitReference(reference: FirReference) {}
override fun visitControlFlowGraphReference(controlFlowGraphReference: FirControlFlowGraphReference) {}
override fun visitNamedReference(namedReference: FirNamedReference) {}
override fun visitResolvedNamedReference(resolvedNamedReference: FirResolvedNamedReference) {}
override fun visitDelegateFieldReference(delegateFieldReference: FirDelegateFieldReference) {}
override fun visitBackingFieldReference(backingFieldReference: FirBackingFieldReference) {}
override fun visitSuperReference(superReference: FirSuperReference) {}
override fun visitThisReference(thisReference: FirThisReference) {}
override fun visitErrorTypeRef(errorTypeRef: FirErrorTypeRef) {}
override fun visitUserTypeRef(userTypeRef: FirUserTypeRef) {
userTypeRef.acceptChildren(this)
}
}
private fun KtElement.getFirOfClosestParent(): Pair<KtElement, FirElement>? {
var current: PsiElement? = this
while (current is KtElement) {
val mappedFir = cache[current]
if (mappedFir != null) {
return current to mappedFir
}
current = current.parent
}
return null
}
}
@@ -0,0 +1,165 @@
/*
* 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.idea.fir.low.level.api.file.structure
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.getNonLocalContainingOrThisDeclaration
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.FirFileBuilder
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache
import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver
import org.jetbrains.kotlin.idea.fir.low.level.api.providers.firIdeProvider
import org.jetbrains.kotlin.idea.fir.low.level.api.util.findNonLocalFirDeclaration
import org.jetbrains.kotlin.idea.fir.low.level.api.util.hasExplicitTypeOrUnit
import org.jetbrains.kotlin.idea.fir.low.level.api.util.replaceFirst
import org.jetbrains.kotlin.idea.util.getElementTextInContext
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import java.util.concurrent.ConcurrentHashMap
internal class FileStructure(
private val ktFile: KtFile,
private val firFile: FirFile,
private val firLazyDeclarationResolver: FirLazyDeclarationResolver,
private val firFileBuilder: FirFileBuilder,
private val moduleFileCache: ModuleFileCache
) {
private val firIdeProvider = firFile.session.firIdeProvider
private val structureElements = ConcurrentHashMap<KtAnnotated, FileStructureElement>()
fun getStructureElementFor(element: KtElement): FileStructureElement {
val container: KtAnnotated = element.getNonLocalContainingOrThisDeclaration() ?: element.containingKtFile
return getStructureElementForDeclaration(container)
}
private fun getStructureElementForDeclaration(declaration: KtAnnotated): FileStructureElement {
val structureElement = structureElements.compute(declaration) { _, structureElement ->
when {
structureElement == null -> createStructureElement(declaration)
structureElement is WithInBlockModificationFileStructureElement && !structureElement.isUpToDate() -> {
createMappingsCopy(structureElement, declaration as KtNamedFunction)
}
else -> structureElement
}
}
return structureElement
?: error("FileStructureElement for was not defined for \n${declaration.getElementTextInContext()}")
}
@OptIn(ExperimentalStdlibApi::class)
fun getAllDiagnosticsForFile(): Collection<Diagnostic> {
val containersForStructureElement = buildList {
add(ktFile)
ktFile.forEachDescendantOfType<KtDeclaration>(
canGoInside = { psi -> psi !is KtFunction && psi !is KtValVarKeywordOwner }
) { declaration ->
if (declaration.isStructureElementContainer()) {
add(declaration)
}
}
}
val structureElements = containersForStructureElement.map(::getStructureElementFor)
return buildList {
structureElements.forEach { it.diagnostics.forEach { diagnostics -> addAll(diagnostics) } }
}
}
private fun replaceFunction(from: FirSimpleFunction, to: FirSimpleFunction) {
val declarations = if (from.symbol.callableId.className == null) {
firFile.declarations as MutableList<FirDeclaration>
} else {
val classId = from.symbol.callableId.classId
?: error("Class name should not be null for non-top-level & non-local declarations")
val containingClass = firIdeProvider.getFirClassifierByFqName(classId) as FirRegularClass
containingClass.declarations as MutableList<FirDeclaration>
}
declarations.replaceFirst(from, to)
}
private fun createMappingsCopy(
original: WithInBlockModificationFileStructureElement,
containerKtFunction: KtNamedFunction
): WithInBlockModificationFileStructureElement {
val newFunction = firIdeProvider.buildFunctionWithBody(containerKtFunction) as FirSimpleFunction
val originalFunction = original.firSymbol.fir as FirSimpleFunction
replaceFunction(originalFunction, newFunction)
try {
firLazyDeclarationResolver.lazyResolveDeclaration(
newFunction,
moduleFileCache,
FirResolvePhase.BODY_RESOLVE,
checkPCE = true,
reresolveFile = true,
)
return WithInBlockModificationFileStructureElement(
firFile,
containerKtFunction,
newFunction.symbol,
containerKtFunction.modificationStamp,
)
} catch (e: Throwable) {
replaceFunction(newFunction, originalFunction)
throw e
}
}
private fun createDeclarationStructure(declaration: KtDeclaration): FileStructureElement {
val firDeclaration = declaration.findNonLocalFirDeclaration(firFileBuilder, firIdeProvider, moduleFileCache)
firLazyDeclarationResolver.lazyResolveDeclaration(
firDeclaration,
moduleFileCache,
FirResolvePhase.BODY_RESOLVE,
checkPCE = true
)
return when {
declaration is KtNamedFunction && declaration.hasExplicitTypeOrUnit -> {
WithInBlockModificationFileStructureElement(
firFile,
declaration,
(firDeclaration as FirSimpleFunction).symbol,
declaration.modificationStamp,
)
}
else -> {
NonLocalDeclarationFileStructureElement(
firFile,
firDeclaration,
declaration,
)
}
}
}
private fun createStructureElement(container: KtAnnotated): FileStructureElement = when (container) {
is KtFile -> {
val firFile = firFileBuilder.getFirFileResolvedToPhaseWithCaching(
container,
moduleFileCache,
FirResolvePhase.BODY_RESOLVE,
checkPCE = true
)
FileWithoutDeclarationsFileStructureElement(
firFile,
container,
)
}
is KtDeclaration -> createDeclarationStructure(container)
else -> error("Invalid container $container")
}
}
private fun KtDeclaration.isStructureElementContainer(): Boolean {
if (this !is KtClassOrObject && this !is KtDeclarationWithBody && this !is KtProperty && this !is KtTypeAlias) return false
if (this is KtEnumEntry) return false
if (containingClassOrObject is KtEnumEntry) return false
return !KtPsiUtil.isLocal(this)
}
@@ -0,0 +1,27 @@
/*
* 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.idea.fir.low.level.api.file.structure
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.FirFileBuilder
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache
import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver
import org.jetbrains.kotlin.psi.KtFile
import java.util.concurrent.ConcurrentHashMap
/**
* Belongs to a [org.jetbrains.kotlin.idea.fir.low.level.api.FirModuleResolveState]
*/
internal class FileStructureCache(
private val fileBuilder: FirFileBuilder,
private val firLazyDeclarationResolver: FirLazyDeclarationResolver,
) {
private val cache = ConcurrentHashMap<KtFile, FileStructure>()
fun getFileStructure(ktFile: KtFile, moduleFileCache: ModuleFileCache): FileStructure = cache.computeIfAbsent(ktFile) {
val firFile = fileBuilder.buildRawFirFileWithCaching(ktFile, moduleFileCache)
FileStructure(ktFile, firFile, firLazyDeclarationResolver, fileBuilder, moduleFileCache)
}
}
@@ -0,0 +1,119 @@
/*
* 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.idea.fir.low.level.api.file.structure
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.FirIdeStructureElementDiagnosticsCollector
import org.jetbrains.kotlin.idea.fir.low.level.api.util.hasExplicitTypeOrUnit
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfTypeTo
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
internal class FileStructureElementDiagnostics(
private val map: Map<KtElement, List<Diagnostic>>
) {
fun diagnosticsFor(element: KtElement): List<Diagnostic> = map[element] ?: emptyList()
inline fun forEach(action: (List<Diagnostic>) -> Unit) = map.values.forEach(action)
}
internal sealed class FileStructureElement {
abstract val firFile: FirFile
abstract val psi: KtAnnotated
abstract val mappings: Map<KtElement, FirElement>
abstract val diagnostics: FileStructureElementDiagnostics
}
internal class WithInBlockModificationFileStructureElement(
override val firFile: FirFile,
override val psi: KtFunction,
val firSymbol: FirFunctionSymbol<*>,
val timestamp: Long
) : FileStructureElement() {
override val mappings: Map<KtElement, FirElement> =
FirElementsRecorder.recordElementsFrom(firSymbol.fir, recorder)
fun isUpToDate(): Boolean = psi.getModificationStamp() == timestamp
override val diagnostics: FileStructureElementDiagnostics by lazy {
val innerDeclarations = psi
.collectDescendantsOfTypeTo<KtDeclaration, HashSet<KtDeclaration>>(
hashSetOf(psi),
canGoInside = { true }
)
FirIdeStructureElementDiagnosticsCollector.collectForStructureElement(
innerDeclarations,
firFile,
substitution = null
)
}
companion object {
private val recorder = FirElementsRecorder()
}
}
internal class NonLocalDeclarationFileStructureElement(
override val firFile: FirFile,
fir: FirDeclaration,
override val psi: KtDeclaration,
) : FileStructureElement() {
override val mappings: Map<KtElement, FirElement> =
FirElementsRecorder.recordElementsFrom(fir, recorder)
override val diagnostics: FileStructureElementDiagnostics by lazy {
@Suppress("RemoveExplicitTypeArguments")
val innerDeclarations = psi
.collectDescendantsOfTypeTo<KtDeclaration, HashSet<KtDeclaration>>(
hashSetOf(psi),
canGoInside = { it.safeAs<KtNamedFunction>()?.hasExplicitTypeOrUnit != true }
)
FirIdeStructureElementDiagnosticsCollector.collectForStructureElement(innerDeclarations, firFile)
}
companion object {
private val recorder = object : FirElementsRecorder() {
override fun visitSimpleFunction(simpleFunction: FirSimpleFunction, data: MutableMap<KtElement, FirElement>) {
val psi = simpleFunction.psi as? KtNamedFunction ?: return super.visitSimpleFunction(simpleFunction, data)
if (!psi.hasExplicitTypeOrUnit || KtPsiUtil.isLocal(psi)) {
super.visitSimpleFunction(simpleFunction, data)
}
}
}
}
}
internal data class FileWithoutDeclarationsFileStructureElement(
override val firFile: FirFile,
override val psi: KtFile,
) : FileStructureElement() {
override val mappings: Map<KtElement, FirElement> =
FirElementsRecorder.recordElementsFrom(firFile, recorder)
override val diagnostics: FileStructureElementDiagnostics by lazy {
FirIdeStructureElementDiagnosticsCollector.collectForStructureElement(hashSetOf(psi), firFile)
}
companion object {
private val recorder = object : FirElementsRecorder() {
override fun visitElement(element: FirElement, data: MutableMap<KtElement, FirElement>) {
if (element !is FirDeclaration || element is FirFile) {
super.visitElement(element, data)
}
}
}
}
}
@@ -0,0 +1,83 @@
/*
* 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.idea.fir.low.level.api.file.structure
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.realPsi
import org.jetbrains.kotlin.fir.references.*
import org.jetbrains.kotlin.fir.types.FirErrorTypeRef
import org.jetbrains.kotlin.fir.types.FirTypeRef
import org.jetbrains.kotlin.fir.types.FirUserTypeRef
import org.jetbrains.kotlin.fir.types.impl.FirResolvedTypeRefImpl
import org.jetbrains.kotlin.fir.visitors.FirVisitor
import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.DuplicatedFirSourceElementsException
import org.jetbrains.kotlin.idea.fir.low.level.api.util.isErrorElement
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtTypeReference
internal open class FirElementsRecorder : FirVisitor<Unit, MutableMap<KtElement, FirElement>>() {
private fun cache(psi: KtElement, fir: FirElement, cache: MutableMap<KtElement, FirElement>) {
val existingFir = cache[psi]
if (existingFir != null && existingFir !== fir) {
when {
existingFir is FirTypeRef && fir is FirTypeRef && psi is KtTypeReference -> {
// FirTypeRefs are often created during resolve
// a lot of them with have the same source
// we want to take the most "resolved one" here
if (fir is FirResolvedTypeRefImpl && existingFir !is FirResolvedTypeRefImpl) {
cache[psi] = fir
}
}
existingFir.isErrorElement && !fir.isErrorElement -> {
// TODO better handle error elements
// but for now just take first non-error one if such exist
cache[psi] = fir
}
existingFir.isErrorElement || fir.isErrorElement -> {
// do nothing and maybe upgrade to a non-error element in the branch above in the future
}
else -> {
if (DuplicatedFirSourceElementsException.IS_ENABLED) {
throw DuplicatedFirSourceElementsException(existingFir, fir, psi)
}
}
}
}
if (existingFir == null) {
cache[psi] = fir
}
}
override fun visitElement(element: FirElement, data: MutableMap<KtElement, FirElement>) {
(element.realPsi as? KtElement)?.let { psi ->
cache(psi, element, data)
}
element.acceptChildren(this, data)
}
//@formatter:off
override fun visitReference(reference: FirReference, data: MutableMap<KtElement, FirElement>) {}
override fun visitControlFlowGraphReference(controlFlowGraphReference: FirControlFlowGraphReference, data: MutableMap<KtElement, FirElement>) {}
override fun visitNamedReference(namedReference: FirNamedReference, data: MutableMap<KtElement, FirElement>) {}
override fun visitResolvedNamedReference(resolvedNamedReference: FirResolvedNamedReference, data: MutableMap<KtElement, FirElement>) {}
override fun visitDelegateFieldReference(delegateFieldReference: FirDelegateFieldReference, data: MutableMap<KtElement, FirElement>) {}
override fun visitBackingFieldReference(backingFieldReference: FirBackingFieldReference, data: MutableMap<KtElement, FirElement>) {}
override fun visitSuperReference(superReference: FirSuperReference, data: MutableMap<KtElement, FirElement>) {}
override fun visitThisReference(thisReference: FirThisReference, data: MutableMap<KtElement, FirElement>) {}
override fun visitErrorTypeRef(errorTypeRef: FirErrorTypeRef, data: MutableMap<KtElement, FirElement>) {}
//@formatter:on
override fun visitUserTypeRef(userTypeRef: FirUserTypeRef, data: MutableMap<KtElement, FirElement>) {
userTypeRef.acceptChildren(this, data)
}
companion object {
@OptIn(ExperimentalStdlibApi::class)
fun recordElementsFrom(firDeclaration: FirDeclaration, recorder: FirElementsRecorder): Map<KtElement, FirElement> =
buildMap { firDeclaration.accept(recorder, this) }
}
}
@@ -13,7 +13,7 @@ import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.providers.FirProvider import org.jetbrains.kotlin.fir.resolve.providers.FirProvider
import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.FirTowerDataContextCollector import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.FirTowerDataContextCollector
import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.FirDesignatedBodyResolveTransformerForIDE import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.FirDesignatedBodyResolveTransformerForIDE
import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.getNonLocalContainingDeclarationWithFqName import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.getNonLocalContainingOrThisDeclaration
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.FirFileBuilder import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.FirFileBuilder
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache
import org.jetbrains.kotlin.idea.fir.low.level.api.providers.firIdeProvider import org.jetbrains.kotlin.idea.fir.low.level.api.providers.firIdeProvider
@@ -31,12 +31,14 @@ internal class FirLazyDeclarationResolver(
moduleFileCache: ModuleFileCache, moduleFileCache: ModuleFileCache,
toPhase: FirResolvePhase, toPhase: FirResolvePhase,
towerDataContextCollector: FirTowerDataContextCollector? = null, towerDataContextCollector: FirTowerDataContextCollector? = null,
checkPCE: Boolean = false checkPCE: Boolean = false,
reresolveFile: Boolean = false,
) { ) {
if (declaration.resolvePhase >= toPhase) return if (declaration.resolvePhase >= toPhase) return
val firFile = declaration.getContainingFile() val firFile = declaration.getContainingFile()
?: error("FirFile was not found for\n${declaration.render()}") ?: error("FirFile was not found for\n${declaration.render()}")
val provider = firFile.session.firIdeProvider val provider = firFile.session.firIdeProvider
val fromPhase = if (reresolveFile) declaration.resolvePhase else minOf(firFile.resolvePhase, declaration.resolvePhase)
if (checkPCE) { if (checkPCE) {
firFileBuilder.runCustomResolveWithPCECheck(firFile, moduleFileCache) { firFileBuilder.runCustomResolveWithPCECheck(firFile, moduleFileCache) {
runLazyResolveWithoutLock( runLazyResolveWithoutLock(
@@ -44,6 +46,7 @@ internal class FirLazyDeclarationResolver(
moduleFileCache, moduleFileCache,
firFile, firFile,
provider, provider,
fromPhase,
toPhase, toPhase,
towerDataContextCollector, towerDataContextCollector,
checkPCE = true checkPCE = true
@@ -57,6 +60,7 @@ internal class FirLazyDeclarationResolver(
moduleFileCache, moduleFileCache,
firFile, firFile,
provider, provider,
fromPhase,
toPhase, toPhase,
towerDataContextCollector, towerDataContextCollector,
checkPCE = false checkPCE = false
@@ -71,16 +75,17 @@ internal class FirLazyDeclarationResolver(
moduleFileCache: ModuleFileCache, moduleFileCache: ModuleFileCache,
containerFirFile: FirFile, containerFirFile: FirFile,
provider: FirProvider, provider: FirProvider,
fromPhase: FirResolvePhase,
toPhase: FirResolvePhase, toPhase: FirResolvePhase,
towerDataContextCollector: FirTowerDataContextCollector? = null, towerDataContextCollector: FirTowerDataContextCollector? = null,
checkPCE: Boolean checkPCE: Boolean,
) { ) {
if (firDeclarationToResolve.resolvePhase >= toPhase) return if (fromPhase >= toPhase) return
val nonLazyPhase = minOf(toPhase, FirResolvePhase.DECLARATIONS) val nonLazyPhase = minOf(toPhase, FirResolvePhase.DECLARATIONS)
if (firDeclarationToResolve.resolvePhase < nonLazyPhase) { if (fromPhase < nonLazyPhase) {
firFileBuilder.runResolveWithoutLock( firFileBuilder.runResolveWithoutLock(
containerFirFile, containerFirFile,
fromPhase = firDeclarationToResolve.resolvePhase, fromPhase = fromPhase,
toPhase = nonLazyPhase, toPhase = nonLazyPhase,
checkPCE = checkPCE checkPCE = checkPCE
) )
@@ -106,7 +111,7 @@ internal class FirLazyDeclarationResolver(
is FirCallableDeclaration<*> -> { is FirCallableDeclaration<*> -> {
nonLocalDeclarationToResolve.symbol.callableId.classId nonLocalDeclarationToResolve.symbol.callableId.classId
} }
is FirRegularClass -> { is FirClassLikeDeclaration<*> -> {
nonLocalDeclarationToResolve.symbol.classId nonLocalDeclarationToResolve.symbol.classId
} }
else -> error("Unsupported: ${nonLocalDeclarationToResolve.render()}") else -> error("Unsupported: ${nonLocalDeclarationToResolve.render()}")
@@ -126,7 +131,7 @@ internal class FirLazyDeclarationResolver(
val transformer = FirDesignatedBodyResolveTransformerForIDE( val transformer = FirDesignatedBodyResolveTransformerForIDE(
designation.iterator(), containerFirFile.session, designation.iterator(), containerFirFile.session,
scopeSession, scopeSession,
implicitTypeOnly = toPhase == FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE, toPhase,
towerDataContextCollector towerDataContextCollector
) )
executeWithoutPCE { executeWithoutPCE {
@@ -138,7 +143,7 @@ internal class FirLazyDeclarationResolver(
if (this is FirFile) return this if (this is FirFile) return this
val ktDeclaration = psi as? KtDeclaration ?: error("FirDeclaration should have a PSI of type KtDeclaration") val ktDeclaration = psi as? KtDeclaration ?: error("FirDeclaration should have a PSI of type KtDeclaration")
if (!KtPsiUtil.isLocal(ktDeclaration)) return this if (!KtPsiUtil.isLocal(ktDeclaration)) return this
val nonLocalPsi = ktDeclaration.getNonLocalContainingDeclarationWithFqName() val nonLocalPsi = ktDeclaration.getNonLocalContainingOrThisDeclaration()
?: error("Container for local declaration cannot be null") ?: error("Container for local declaration cannot be null")
return nonLocalPsi.findNonLocalFirDeclaration(firFileBuilder, provider, moduleFileCache) return nonLocalPsi.findNonLocalFirDeclaration(firFileBuilder, provider, moduleFileCache)
} }
@@ -0,0 +1,19 @@
/*
* 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.idea.fir.low.level.api.util
@Suppress("NOTHING_TO_INLINE")
internal inline fun <K, V> MutableMap<K, MutableList<V>>.addValueFor(element: K, value: V) {
getOrPut(element) { mutableListOf() } += value
}
internal fun <T> MutableList<T>.replaceFirst(from: T, to: T) {
val index = indexOf(from)
if (index < 0) {
error("$from was not found in $this")
}
set(index, to)
}
@@ -7,11 +7,13 @@ package org.jetbrains.kotlin.idea.fir.low.level.api.util
import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirRegularClass import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.FirTypeAlias
import org.jetbrains.kotlin.fir.psi import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.resolve.providers.FirProvider import org.jetbrains.kotlin.fir.resolve.providers.FirProvider
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.FirFileBuilder import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.FirFileBuilder
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache
import org.jetbrains.kotlin.idea.util.classIdIfNonLocal import org.jetbrains.kotlin.idea.util.classIdIfNonLocal
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
@@ -25,27 +27,45 @@ internal fun KtDeclaration.findNonLocalFirDeclaration(
this is KtClassOrObject -> findFir(provider) this is KtClassOrObject -> findFir(provider)
this is KtNamedDeclaration && (this is KtProperty || this is KtNamedFunction) -> { this is KtNamedDeclaration && (this is KtProperty || this is KtNamedFunction) -> {
val containerClass = containingClassOrObject val containerClass = containingClassOrObject
if (containerClass != null) { val declarations = if (containerClass != null) {
val containerClassFir = containerClass.findFir(provider) val containerClassFir = containerClass.findFir(provider)
containerClassFir.declarations.first { it.psi === this } containerClassFir.declarations
} else { } else {
val ktFile = containingKtFile val ktFile = containingKtFile
val firFile = firFileBuilder.buildRawFirFileWithCaching(ktFile, moduleFileCache) val firFile = firFileBuilder.buildRawFirFileWithCaching(ktFile, moduleFileCache)
firFile.declarations.first { it.psi === this } firFile.declarations
} }
val original = originalDeclaration
declarations.first { it.psi === this || it.psi == original }
} }
this is KtConstructor<*> -> { this is KtConstructor<*> -> {
val containerClassFir = containingClassOrObject?.findFir(provider) val containerClassFir = containingClassOrObject?.findFir(provider)
?: error("Container class should be not null for KtConstructor") ?: error("Container class should be not null for KtConstructor")
containerClassFir.declarations.first { it.psi === this } containerClassFir.declarations.first { it.psi === this }
} }
else -> error("Invalid container $this") this is KtTypeAlias -> findFir(provider)
else -> error("Invalid container $this::class")
} }
} }
val ORIGINAL_DECLARATION_KEY = com.intellij.openapi.util.Key<KtDeclaration>("ORIGINAL_DECLARATION_KEY")
var KtDeclaration.originalDeclaration by UserDataProperty(ORIGINAL_DECLARATION_KEY)
private fun KtClassOrObject.findFir(provider: FirProvider): FirRegularClass { private fun KtClassOrObject.findFir(provider: FirProvider): FirRegularClass {
val containerClassId = classIdIfNonLocal() val classId = classIdIfNonLocal()
?: error("Container classId should not be null for non-local declaration") ?: error("Container classId should not be null for non-local declaration")
return provider.getFirClassifierByFqName(containerClassId) as? FirRegularClass return executeWithoutPCE {
?: error("Could not find class $containerClassId") provider.getFirClassifierByFqName(classId) as? FirRegularClass
?: error("Could not find class $classId")
}
}
private fun KtTypeAlias.findFir(provider: FirProvider): FirTypeAlias {
val typeAlias = ClassId(containingKtFile.packageFqName, nameAsSafeName)
return executeWithoutPCE {
provider.getFirClassifierByFqName(typeAlias) as? FirTypeAlias
?: error("Could not find type alias $typeAlias")
}
} }
@@ -0,0 +1,11 @@
/*
* 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.idea.fir.low.level.api.util
import org.jetbrains.kotlin.psi.KtNamedFunction
internal val KtNamedFunction.hasExplicitTypeOrUnit
get() = hasBlockBody() || typeReference != null
@@ -52,10 +52,6 @@ internal inline fun checkCanceled() {
internal val FirElement.isErrorElement internal val FirElement.isErrorElement
get() = this is FirDiagnosticHolder get() = this is FirDiagnosticHolder
@Suppress("NOTHING_TO_INLINE")
internal inline fun <K, V> MutableMap<K, MutableList<V>>.addValueFor(element: K, value: V) {
getOrPut(element) { mutableListOf() } += value
}
internal fun IdeaModuleInfo.collectTransitiveDependenciesWithSelf(): List<IdeaModuleInfo> { internal fun IdeaModuleInfo.collectTransitiveDependenciesWithSelf(): List<IdeaModuleInfo> {
val result = mutableSetOf<IdeaModuleInfo>() val result = mutableSetOf<IdeaModuleInfo>()
@@ -128,7 +128,7 @@ internal class KtFirScopeProvider(
originalFile: KtFile, originalFile: KtFile,
positionInFakeFile: KtElement positionInFakeFile: KtElement
): LowLevelFirApiFacade.FirCompletionContext { ): LowLevelFirApiFacade.FirCompletionContext {
val originalFirFile = originalFile.getOrBuildFirOfType<FirFile>(firResolveState) val originalFirFile = firResolveState.getFirFile(originalFile)
val declarationContext = EnclosingDeclarationContext.detect(originalFile, positionInFakeFile) val declarationContext = EnclosingDeclarationContext.detect(originalFile, positionInFakeFile)
return declarationContext.buildCompletionContext(originalFirFile, firResolveState) return declarationContext.buildCompletionContext(originalFirFile, firResolveState)
@@ -9,6 +9,7 @@ import com.intellij.psi.util.parentsOfType
import org.jetbrains.kotlin.fir.declarations.FirFile import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.idea.fir.low.level.api.FirModuleResolveState import org.jetbrains.kotlin.idea.fir.low.level.api.FirModuleResolveState
import org.jetbrains.kotlin.idea.fir.low.level.api.LowLevelFirApiFacade import org.jetbrains.kotlin.idea.fir.low.level.api.LowLevelFirApiFacade
import org.jetbrains.kotlin.idea.fir.low.level.api.util.originalDeclaration
import org.jetbrains.kotlin.idea.util.getElementTextInContext import org.jetbrains.kotlin.idea.util.getElementTextInContext
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
@@ -20,7 +21,7 @@ internal sealed class EnclosingDeclarationContext {
if (fakeFunction != null) { if (fakeFunction != null) {
val originalFunction = originalFile.findDeclarationOfTypeAt<KtNamedFunction>(fakeFunction.textOffset) val originalFunction = originalFile.findDeclarationOfTypeAt<KtNamedFunction>(fakeFunction.textOffset)
?: error("Cannot find original function matching to ${fakeFunction.getElementTextInContext()} in $originalFile") ?: error("Cannot find original function matching to ${fakeFunction.getElementTextInContext()} in $originalFile")
fakeFunction.originalDeclaration = originalFunction
return FunctionContext(fakeFunction, originalFunction) return FunctionContext(fakeFunction, originalFunction)
} }
@@ -28,6 +29,7 @@ internal sealed class EnclosingDeclarationContext {
if (fakeProperty != null) { if (fakeProperty != null) {
val originalProperty = originalFile.findDeclarationOfTypeAt<KtProperty>(fakeProperty.textOffset) val originalProperty = originalFile.findDeclarationOfTypeAt<KtProperty>(fakeProperty.textOffset)
?: error("Cannot find original property matching to ${fakeProperty.getElementTextInContext()} in $originalFile") ?: error("Cannot find original property matching to ${fakeProperty.getElementTextInContext()} in $originalFile")
fakeProperty.originalDeclaration = originalProperty
return PropertyContext(fakeProperty, originalProperty) return PropertyContext(fakeProperty, originalProperty)
} }