Introduce FIR resolution API with lazy resolve to use in IDE
Now lazy resolve atomic element is a file (declaration-level resolve) or a callable declaration (expression-level resolve) #KT-24351 In Progress
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.analyzer.ModuleInfo
|
||||
import org.jetbrains.kotlin.fir.FirModuleBasedSession
|
||||
import org.jetbrains.kotlin.fir.builder.RawFirBuilder
|
||||
import org.jetbrains.kotlin.fir.java.FirProjectSessionProvider
|
||||
import org.jetbrains.kotlin.fir.java.JavaSymbolProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.FirProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.FirSymbolProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.impl.FirCompositeSymbolProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.impl.FirDependenciesSymbolProviderImpl
|
||||
import org.jetbrains.kotlin.fir.service
|
||||
import org.jetbrains.kotlin.fir.types.FirCorrespondingSupertypesCache
|
||||
|
||||
|
||||
class FirIdeJavaModuleBasedSession(
|
||||
project: Project,
|
||||
moduleInfo: ModuleInfo,
|
||||
sessionProvider: FirProjectSessionProvider,
|
||||
scope: GlobalSearchScope,
|
||||
dependenciesProvider: FirSymbolProvider? = null
|
||||
) : FirModuleBasedSession(moduleInfo, sessionProvider) {
|
||||
|
||||
|
||||
init {
|
||||
sessionProvider.sessionCache[moduleInfo] = this
|
||||
registerComponent(
|
||||
FirProvider::class,
|
||||
IdeFirProvider(project, scope, RawFirBuilder(this, stubMode = false), this)
|
||||
)
|
||||
registerComponent(
|
||||
FirSymbolProvider::class,
|
||||
FirCompositeSymbolProvider(
|
||||
listOf(
|
||||
service<FirProvider>(),
|
||||
JavaSymbolProvider(this, sessionProvider.project, scope),
|
||||
dependenciesProvider ?: FirDependenciesSymbolProviderImpl(this)
|
||||
)
|
||||
) as FirSymbolProvider
|
||||
)
|
||||
|
||||
registerComponent(
|
||||
FirCorrespondingSupertypesCache::class,
|
||||
FirCorrespondingSupertypesCache(this)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.containingDeclarationForPseudocode
|
||||
import org.jetbrains.kotlin.fir.FirElement
|
||||
import org.jetbrains.kotlin.fir.FirReference
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.render
|
||||
import org.jetbrains.kotlin.fir.resolve.FirProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.*
|
||||
import org.jetbrains.kotlin.fir.scopes.ProcessorAction
|
||||
import org.jetbrains.kotlin.fir.scopes.impl.FirTopLevelDeclaredMemberScope
|
||||
import org.jetbrains.kotlin.fir.symbols.ConeCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.types.FirErrorTypeRef
|
||||
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
|
||||
private val FirResolvePhase.stubMode: Boolean
|
||||
get() = this <= FirResolvePhase.DECLARATIONS
|
||||
|
||||
private fun KtClassOrObject.relativeFqName(): FqName {
|
||||
val className = this.nameAsSafeName
|
||||
val parentFqName = this.containingClassOrObject?.relativeFqName()
|
||||
return parentFqName?.child(className) ?: FqName.topLevel(className)
|
||||
}
|
||||
|
||||
private fun FirFile.findCallableMember(
|
||||
provider: FirProvider, callableMember: KtCallableDeclaration,
|
||||
packageFqName: FqName, klassFqName: FqName?, declName: Name
|
||||
): FirCallableMemberDeclaration<*> {
|
||||
val memberScope =
|
||||
if (klassFqName == null) FirTopLevelDeclaredMemberScope(this, session)
|
||||
else provider.getClassDeclaredMemberScope(ClassId(packageFqName, klassFqName, false))!!
|
||||
var result: FirCallableMemberDeclaration<*>? = null
|
||||
val processor = { symbol: ConeCallableSymbol ->
|
||||
val firSymbol = symbol as? FirBasedSymbol<*>
|
||||
val fir = firSymbol?.fir as? FirCallableMemberDeclaration<*>
|
||||
if (fir?.psi == callableMember) {
|
||||
result = fir
|
||||
ProcessorAction.STOP
|
||||
} else {
|
||||
ProcessorAction.NEXT
|
||||
}
|
||||
}
|
||||
if (callableMember is KtNamedFunction) {
|
||||
memberScope.processFunctionsByName(declName, processor)
|
||||
} else {
|
||||
memberScope.processPropertiesByName(declName, processor)
|
||||
}
|
||||
|
||||
return result!!
|
||||
}
|
||||
|
||||
fun KtCallableDeclaration.getOrBuildFir(
|
||||
state: FirResolveState,
|
||||
phase: FirResolvePhase = FirResolvePhase.DECLARATIONS
|
||||
): FirCallableMemberDeclaration<*> {
|
||||
val session = state.getSession(this)
|
||||
|
||||
val file = this.containingKtFile
|
||||
val packageFqName = file.packageFqName
|
||||
val klassFqName = this.containingClassOrObject?.relativeFqName()
|
||||
val declName = this.nameAsSafeName
|
||||
|
||||
val firProvider = FirProvider.getInstance(session) as IdeFirProvider
|
||||
val firFile = firProvider.getOrBuildFile(file)
|
||||
val memberSymbol = firFile.findCallableMember(firProvider, this, packageFqName, klassFqName, declName).symbol
|
||||
memberSymbol.fir.runResolve(firFile, firProvider, phase, state)
|
||||
return memberSymbol.fir
|
||||
}
|
||||
|
||||
fun KtClassOrObject.getOrBuildFir(
|
||||
state: FirResolveState,
|
||||
phase: FirResolvePhase = FirResolvePhase.DECLARATIONS
|
||||
): FirRegularClass {
|
||||
val session = state.getSession(this)
|
||||
|
||||
val file = this.containingKtFile
|
||||
val packageFqName = file.packageFqName
|
||||
val klassFqName = this.relativeFqName()
|
||||
|
||||
val firProvider = FirProvider.getInstance(session) as IdeFirProvider
|
||||
val firFile = firProvider.getOrBuildFile(file)
|
||||
val firClass = firProvider.getFirClassifierByFqName(ClassId(packageFqName, klassFqName, false)) as FirRegularClass
|
||||
firClass.runResolve(firFile, firProvider, phase, state)
|
||||
return firClass
|
||||
}
|
||||
|
||||
private fun FirDeclaration.runResolve(
|
||||
file: FirFile,
|
||||
firProvider: IdeFirProvider,
|
||||
toPhase: FirResolvePhase,
|
||||
state: FirResolveState
|
||||
) {
|
||||
val nonLazyPhase = minOf(toPhase, FirResolvePhase.DECLARATIONS)
|
||||
file.runResolve(toPhase = nonLazyPhase, fromPhase = this.resolvePhase)
|
||||
if (toPhase > nonLazyPhase) {
|
||||
val designation = mutableListOf<FirElement>()
|
||||
designation += file
|
||||
val id = when (this) {
|
||||
is FirCallableDeclaration<*> -> {
|
||||
this.symbol.callableId.classId
|
||||
}
|
||||
is FirRegularClass -> {
|
||||
this.symbol.classId
|
||||
}
|
||||
else -> error("Unsupported: ${render()}")
|
||||
}
|
||||
val outerClasses = generateSequence(id) { classId ->
|
||||
classId.outerClassId
|
||||
}.mapTo(mutableListOf()) { firProvider.getFirClassifierByFqName(it)!! }
|
||||
designation += outerClasses.asReversed()
|
||||
if (this is FirCallableDeclaration<*>) {
|
||||
designation += this
|
||||
}
|
||||
val transformer = FirDesignatedBodyResolveTransformer(
|
||||
designation.iterator(), state.getSession(psi as KtElement),
|
||||
implicitTypeOnly = toPhase == FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE
|
||||
)
|
||||
file.transform<FirFile, Nothing?>(transformer, null)
|
||||
}
|
||||
}
|
||||
|
||||
fun KtElement.getOrBuildFir(
|
||||
state: FirResolveState,
|
||||
phase: FirResolvePhase = FirResolvePhase.BODY_RESOLVE
|
||||
): FirElement {
|
||||
val containerFir: FirDeclaration =
|
||||
when (val container = this.containingDeclarationForPseudocode ?: error("No containing declaration: $text")) {
|
||||
is KtCallableDeclaration -> container.getOrBuildFir(state, phase)
|
||||
is KtClassOrObject -> container.getOrBuildFir(state, phase)
|
||||
else -> error("Unsupported: ${container.text}")
|
||||
}
|
||||
|
||||
val psi = when (this) {
|
||||
is KtPropertyDelegate -> this.expression ?: this
|
||||
else -> this
|
||||
}
|
||||
return state[this] ?: run {
|
||||
containerFir.accept(object : FirVisitorVoid() {
|
||||
override fun visitElement(element: FirElement) {
|
||||
(element.psi as? KtElement)?.let {
|
||||
state.record(it, element)
|
||||
}
|
||||
element.acceptChildren(this)
|
||||
}
|
||||
|
||||
override fun visitReference(reference: FirReference) {}
|
||||
|
||||
override fun visitErrorTypeRef(errorTypeRef: FirErrorTypeRef) {}
|
||||
})
|
||||
var current: PsiElement? = psi
|
||||
while (current is KtElement) {
|
||||
val mappedFir = state[current]
|
||||
if (mappedFir != null) {
|
||||
if (current != this) {
|
||||
state.record(current, mappedFir)
|
||||
}
|
||||
return mappedFir
|
||||
}
|
||||
current = current.parent
|
||||
}
|
||||
error("FirElement is not found for: $text")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.fir.FirElement
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.FirSessionProvider
|
||||
import org.jetbrains.kotlin.fir.dependenciesWithoutSelf
|
||||
import org.jetbrains.kotlin.fir.java.FirLibrarySession
|
||||
import org.jetbrains.kotlin.fir.java.FirProjectSessionProvider
|
||||
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
|
||||
import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo
|
||||
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
|
||||
import org.jetbrains.kotlin.idea.caches.project.isLibraryClasses
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.IDEPackagePartProvider
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
||||
|
||||
private fun createLibrarySession(moduleInfo: IdeaModuleInfo, project: Project, provider: FirProjectSessionProvider): FirLibrarySession {
|
||||
val contentScope = moduleInfo.contentScope()
|
||||
return FirLibrarySession.create(moduleInfo, provider, contentScope, project, IDEPackagePartProvider(contentScope))
|
||||
}
|
||||
|
||||
interface FirResolveState {
|
||||
val sessionProvider: FirSessionProvider
|
||||
|
||||
fun getSession(psi: KtElement): FirSession {
|
||||
val sessionProvider = sessionProvider as FirProjectSessionProvider
|
||||
val moduleInfo = psi.getModuleInfo() as ModuleSourceInfo
|
||||
return sessionProvider.getSession(moduleInfo) ?: FirIdeJavaModuleBasedSession(
|
||||
psi.project, moduleInfo, sessionProvider, moduleInfo.contentScope()
|
||||
).also {
|
||||
val ideaModuleInfo = moduleInfo.cast<IdeaModuleInfo>()
|
||||
ideaModuleInfo.dependenciesWithoutSelf().forEach {
|
||||
if (it is IdeaModuleInfo && it.isLibraryClasses()) {
|
||||
createLibrarySession(it, psi.project, sessionProvider)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operator fun get(psi: KtElement): FirElement?
|
||||
|
||||
fun record(psi: KtElement, fir: FirElement)
|
||||
}
|
||||
|
||||
class FirResolveStateImpl(override val sessionProvider: FirSessionProvider) : FirResolveState {
|
||||
private val cache = mutableMapOf<KtElement, FirElement>()
|
||||
|
||||
override fun get(psi: KtElement): FirElement? = cache[psi]
|
||||
|
||||
override fun record(psi: KtElement, fir: FirElement) {
|
||||
cache[psi] = fir
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: caching
|
||||
object FirIdeResolveFactory {
|
||||
fun initiate(psi: KtElement): FirResolveState {
|
||||
val provider = FirProjectSessionProvider(psi.project)
|
||||
return FirResolveStateImpl(provider)
|
||||
}
|
||||
}
|
||||
|
||||
fun KtElement.firResolveState(): FirResolveState = FirIdeResolveFactory.initiate(this)
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.builder.RawFirBuilder
|
||||
import org.jetbrains.kotlin.fir.declarations.FirClassLikeDeclaration
|
||||
import org.jetbrains.kotlin.fir.declarations.FirFile
|
||||
import org.jetbrains.kotlin.fir.resolve.FirProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.ScopeSession
|
||||
import org.jetbrains.kotlin.fir.resolve.impl.FirProviderImpl
|
||||
import org.jetbrains.kotlin.fir.scopes.FirScope
|
||||
import org.jetbrains.kotlin.fir.symbols.ConeCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
|
||||
import org.jetbrains.kotlin.idea.stubindex.*
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
class IdeFirProvider(
|
||||
val project: Project,
|
||||
val scope: GlobalSearchScope,
|
||||
val builder: RawFirBuilder,
|
||||
val session: FirSession
|
||||
) : FirProvider() {
|
||||
private val cacheProvider = FirProviderImpl(session)
|
||||
// TODO: invalidation?
|
||||
private val files = mutableMapOf<KtFile, FirFile>()
|
||||
|
||||
override fun getFirClassifierByFqName(fqName: ClassId): FirClassLikeDeclaration<*>? {
|
||||
return cacheProvider.getFirClassifierByFqName(fqName) ?: run {
|
||||
|
||||
val classes = KotlinFullClassNameIndex.getInstance().get(fqName.asSingleFqName().asString(), project, scope)
|
||||
val ktClass = classes.firstOrNull {
|
||||
fqName.packageFqName == it.containingKtFile.packageFqName
|
||||
} ?: return null // TODO: what if two of them?
|
||||
val ktFile = ktClass.containingKtFile
|
||||
|
||||
getOrBuildFile(ktFile)
|
||||
|
||||
cacheProvider.getFirClassifierByFqName(fqName)
|
||||
}
|
||||
}
|
||||
|
||||
fun getOrBuildFile(ktFile: KtFile): FirFile {
|
||||
return files.getOrPut(ktFile) {
|
||||
val file = builder.buildFirFile(ktFile)
|
||||
cacheProvider.recordFile(file)
|
||||
file
|
||||
}
|
||||
}
|
||||
|
||||
fun getFile(ktFile: KtFile): FirFile? = files[ktFile]
|
||||
|
||||
override fun getClassLikeSymbolByFqName(classId: ClassId): FirClassLikeSymbol<*>? {
|
||||
return getFirClassifierByFqName(classId)?.symbol
|
||||
}
|
||||
|
||||
override fun getTopLevelCallableSymbols(packageFqName: FqName, name: Name): List<FirCallableSymbol<*>> {
|
||||
val packagePrefix = if (packageFqName.isRoot) "" else "$packageFqName."
|
||||
val topLevelFunctions = KotlinTopLevelFunctionFqnNameIndex.getInstance()["$packagePrefix$name", project, scope]
|
||||
val topLevelProperties = KotlinTopLevelPropertyFqnNameIndex.getInstance()["$packagePrefix$name", project, scope]
|
||||
topLevelFunctions.forEach { getOrBuildFile(it.containingKtFile) }
|
||||
topLevelProperties.forEach { getOrBuildFile(it.containingKtFile) }
|
||||
return cacheProvider.getTopLevelCallableSymbols(packageFqName, name)
|
||||
}
|
||||
|
||||
override fun getFirClassifierContainerFile(fqName: ClassId): FirFile {
|
||||
getFirClassifierByFqName(fqName)
|
||||
return cacheProvider.getFirClassifierContainerFile(fqName)
|
||||
}
|
||||
|
||||
override fun getFirCallableContainerFile(symbol: ConeCallableSymbol): FirFile? {
|
||||
return cacheProvider.getFirCallableContainerFile(symbol)
|
||||
}
|
||||
|
||||
override fun getFirFilesByPackage(fqName: FqName): List<FirFile> {
|
||||
val files = KotlinExactPackagesIndex.getInstance()[fqName.asString(), project, scope]
|
||||
files.forEach { getOrBuildFile(it) }
|
||||
return cacheProvider.getFirFilesByPackage(fqName)
|
||||
}
|
||||
|
||||
override fun getClassDeclaredMemberScope(classId: ClassId): FirScope? {
|
||||
getFirClassifierByFqName(classId)
|
||||
return cacheProvider.getClassDeclaredMemberScope(classId)
|
||||
}
|
||||
|
||||
override fun getClassUseSiteMemberScope(classId: ClassId, useSiteSession: FirSession, scopeSession: ScopeSession): FirScope? {
|
||||
getFirClassifierByFqName(classId)
|
||||
return cacheProvider.getClassUseSiteMemberScope(classId, useSiteSession, scopeSession)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user