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:
Mikhail Glukhikh
2019-07-26 21:25:57 +03:00
parent 1cd559a626
commit 760fb668bb
29 changed files with 683 additions and 35 deletions
@@ -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)
}
}