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
@@ -15,6 +15,7 @@ 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.resolve.impl.FirLibrarySymbolProviderImpl
import org.jetbrains.kotlin.fir.resolve.impl.FirProviderImpl
import org.jetbrains.kotlin.fir.scopes.impl.FirClassDeclaredMemberScopeProvider
import org.jetbrains.kotlin.fir.types.FirCorrespondingSupertypesCache
import org.jetbrains.kotlin.load.java.JavaClassFinder
@@ -33,6 +34,8 @@ class FirJavaModuleBasedSession(
init {
sessionProvider.sessionCache[moduleInfo] = this
val firProvider = FirProviderImpl(this)
registerComponent(FirProvider::class, firProvider)
registerComponent(
FirSymbolProvider::class,
@@ -169,7 +169,8 @@ class RawFirBuilder(val session: FirSession, val stubMode: Boolean) {
}
else -> {
val result = { bodyExpression }.toFirExpression("Function has no body (but should)")
FirSingleExpressionBlock(result.toReturn())
// basePsi is null, because 'return' is synthetic & should not be bound to some PSI
FirSingleExpressionBlock(result.toReturn(basePsi = null))
}
}
@@ -18,9 +18,8 @@ import org.jetbrains.kotlin.fir.references.FirResolvedCallableReferenceImpl
import org.jetbrains.kotlin.fir.references.FirSimpleNamedReference
import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
import org.jetbrains.kotlin.fir.resolve.calls.*
import org.jetbrains.kotlin.fir.resolve.transformers.FirBodyResolveTransformer
import org.jetbrains.kotlin.fir.resolve.transformers.*
import org.jetbrains.kotlin.fir.resolve.transformers.StoreNameReference
import org.jetbrains.kotlin.fir.resolve.transformers.firUnsafe
import org.jetbrains.kotlin.fir.resolve.transformers.resultType
import org.jetbrains.kotlin.fir.resolve.typeForQualifier
import org.jetbrains.kotlin.fir.resolve.typeFromCallee
@@ -226,8 +225,10 @@ class FirCallResolver(
val coneSymbol = candidate.symbol
when {
coneSymbol is FirBackingFieldSymbol -> FirBackingFieldReferenceImpl(psi, coneSymbol)
coneSymbol is FirVariableSymbol &&
(coneSymbol !is FirPropertySymbol || coneSymbol.firUnsafe<FirMemberDeclaration>().typeParameters.isEmpty()) ->
coneSymbol is FirVariableSymbol && (
coneSymbol !is FirPropertySymbol ||
(coneSymbol.phasedFir(session) as FirMemberDeclaration).typeParameters.isEmpty()
) ->
FirResolvedCallableReferenceImpl(psi, name, coneSymbol)
else -> FirNamedReferenceWithCandidate(psi, name, candidate)
}
@@ -13,8 +13,6 @@ import org.jetbrains.kotlin.fir.scopes.impl.FirClassDeclaredMemberScopeProvider
abstract class FirModuleBasedSession(override val moduleInfo: ModuleInfo, sessionProvider: FirSessionProvider?) :
FirSessionBase(sessionProvider) {
init {
val firProvider = FirProviderImpl(this)
registerComponent(FirProvider::class, firProvider)
registerComponent(FirQualifierResolver::class, FirQualifierResolverImpl(this))
registerComponent(FirTypeResolver::class, FirTypeResolverImpl(this))
registerComponent(FirClassDeclaredMemberScopeProvider::class, FirClassDeclaredMemberScopeProvider())
@@ -178,7 +178,7 @@ fun BodyResolveComponents.typeForQualifier(resolvedQualifier: FirResolvedQualifi
val resultType = resolvedQualifier.resultType
if (classId != null) {
val classSymbol = symbolProvider.getClassLikeSymbolByFqName(classId)!!
val declaration = classSymbol.fir
val declaration = classSymbol.phasedFir
if (declaration is FirClass) {
if (declaration.classKind == ClassKind.OBJECT) {
return resultType.resolvedTypeFromPrototype(
@@ -235,7 +235,7 @@ fun <T : FirQualifiedAccess> BodyResolveComponents.typeFromCallee(access: T): Fi
private fun BodyResolveComponents.typeFromSymbol(symbol: ConeSymbol, makeNullable: Boolean): FirResolvedTypeRef {
return when (symbol) {
is FirCallableSymbol<*> -> {
val returnType = returnTypeCalculator.tryCalculateReturnType(symbol.fir)
val returnType = returnTypeCalculator.tryCalculateReturnType(symbol.phasedFir)
if (makeNullable) {
returnType.withReplacedConeType(
returnType.coneTypeUnsafe<ConeKotlinType>().withNullability(ConeNullability.NULLABLE)
@@ -245,7 +245,7 @@ private fun BodyResolveComponents.typeFromSymbol(symbol: ConeSymbol, makeNullabl
}
}
is ConeClassifierSymbol -> {
val fir = (symbol as? AbstractFirBasedSymbol<*>)?.fir
val fir = (symbol as? AbstractFirBasedSymbol<*>)?.phasedFir
// TODO: unhack
if (fir is FirEnumEntry) {
(fir.superTypeRefs.firstOrNull() as? FirResolvedTypeRef) ?: FirErrorTypeRefImpl(
@@ -7,7 +7,12 @@ package org.jetbrains.kotlin.fir.resolve
import com.google.common.collect.SetMultimap
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.resolve.transformers.ReturnTypeCalculator
import org.jetbrains.kotlin.fir.resolve.transformers.phasedFir
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.FirSymbolOwner
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.FirTypeRef
import org.jetbrains.kotlin.name.Name
@@ -21,4 +26,7 @@ interface BodyResolveComponents : SessionHolder {
val labels: SetMultimap<Name, ConeKotlinType>
val noExpectedType: FirTypeRef
val symbolProvider: FirSymbolProvider
val <D> AbstractFirBasedSymbol<D>.phasedFir: D where D : FirDeclaration, D : FirSymbolOwner<D>
get() = phasedFir(session, FirResolvePhase.DECLARATIONS)
}
@@ -30,26 +30,10 @@ abstract class FirAbstractPhaseTransformer<D>(
}
}
val <D> AbstractFirBasedSymbol<D>.phasedFir: D where D : FirDeclaration, D : FirSymbolOwner<D>
open val <D> AbstractFirBasedSymbol<D>.phasedFir: D where D : FirDeclaration, D : FirSymbolOwner<D>
get() {
val result = this.fir
val requiredPhase = transformerPhase.prev
val availablePhase = result.resolvePhase
if (availablePhase < requiredPhase) {
var resolvePhase = availablePhase.next
val provider = FirProvider.getInstance(session)
val containingFile = when (this) {
is ConeCallableSymbol -> provider.getFirCallableContainerFile(this)
is ConeClassLikeSymbol -> provider.getFirClassifierContainerFile(this)
else -> null
} ?: throw AssertionError("Cannot get container file by symbol: $this (${result.render()})")
do {
val phaseTransformer = resolvePhase.createTransformerByPhase()
containingFile.transform<FirFile, Nothing?>(phaseTransformer, null)
resolvePhase = resolvePhase.next
} while (resolvePhase <= requiredPhase)
}
return result
return phasedFir(session, requiredPhase)
}
override fun transformDeclaration(declaration: FirDeclaration, data: D): CompositeTransformResult<FirDeclaration> {
@@ -57,4 +41,13 @@ abstract class FirAbstractPhaseTransformer<D>(
return super.transformDeclaration(declaration, data)
}
}
fun FirFile.runResolve(toPhase: FirResolvePhase, fromPhase: FirResolvePhase = FirResolvePhase.RAW_FIR) {
var currentPhase = fromPhase
while (currentPhase < toPhase) {
currentPhase = currentPhase.next
val phaseTransformer = currentPhase.createTransformerByPhase()
transform<FirFile, Nothing?>(phaseTransformer, null)
}
}
@@ -21,10 +21,7 @@ import org.jetbrains.kotlin.fir.scopes.addImportingScopes
import org.jetbrains.kotlin.fir.scopes.impl.FirLocalScope
import org.jetbrains.kotlin.fir.scopes.impl.FirTopLevelDeclaredMemberScope
import org.jetbrains.kotlin.fir.scopes.impl.withReplacedConeType
import org.jetbrains.kotlin.fir.symbols.ConeSymbol
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.StandardClassIds
import org.jetbrains.kotlin.fir.symbols.invoke
import org.jetbrains.kotlin.fir.symbols.*
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.impl.*
import org.jetbrains.kotlin.fir.visitors.CompositeTransformResult
@@ -83,6 +80,12 @@ open class FirBodyResolveTransformer(
resolutionStageRunner
)
override val <D> AbstractFirBasedSymbol<D>.phasedFir: D where D : FirDeclaration, D : FirSymbolOwner<D>
get() {
val requiredPhase = transformerPhase.prev
return phasedFir(session, requiredPhase)
}
override fun transformFile(file: FirFile, data: Any?): CompositeTransformResult<FirFile> {
packageFqName = file.packageFqName
this.file = file
@@ -778,9 +781,17 @@ private fun inferenceComponents(session: FirSession, returnTypeCalculator: Retur
}, session, returnTypeCalculator, scopeSession)
class FirDesignatedBodyResolveTransformer(val designation: Iterator<FirElement>, session: FirSession, scopeSession: ScopeSession) :
FirBodyResolveTransformer(session, phase = FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE, implicitTypeOnly = true, scopeSession = scopeSession) {
class FirDesignatedBodyResolveTransformer(
private val designation: Iterator<FirElement>,
session: FirSession,
scopeSession: ScopeSession = ScopeSession(),
implicitTypeOnly: Boolean = true
) : FirBodyResolveTransformer(
session,
phase = FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE,
implicitTypeOnly = implicitTypeOnly,
scopeSession = scopeSession
) {
override fun <E : FirElement> transformElement(element: E, data: Any?): CompositeTransformResult<E> {
if (designation.hasNext()) {
designation.next().visitNoTransform(this, data)
@@ -0,0 +1,34 @@
/*
* 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.fir.resolve.transformers
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.resolve.FirProvider
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.ConeCallableSymbol
import org.jetbrains.kotlin.fir.symbols.ConeClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.FirSymbolOwner
fun <D> AbstractFirBasedSymbol<D>.phasedFir(
session: FirSession,
requiredPhase: FirResolvePhase = FirResolvePhase.DECLARATIONS
): D where D : FirDeclaration, D : FirSymbolOwner<D> {
val result = this.fir
val availablePhase = result.resolvePhase
if (availablePhase < requiredPhase) {
val provider = FirProvider.getInstance(session)
val containingFile = when (this) {
is ConeCallableSymbol -> provider.getFirCallableContainerFile(this)
is ConeClassLikeSymbol -> provider.getFirClassifierContainerFile(this)
else -> null
} ?: throw AssertionError("Cannot get container file by symbol: $this (${result.render()})")
containingFile.runResolve(toPhase = requiredPhase, fromPhase = availablePhase)
}
return result
}
@@ -72,6 +72,7 @@ import org.jetbrains.kotlin.idea.editor.AbstractMultiLineStringIndentTest
import org.jetbrains.kotlin.idea.editor.backspaceHandler.AbstractBackspaceHandlerTest
import org.jetbrains.kotlin.idea.editor.quickDoc.AbstractQuickDocProviderTest
import org.jetbrains.kotlin.idea.filters.AbstractKotlinExceptionFilterTest
import org.jetbrains.kotlin.idea.fir.AbstractFirLazyResolveTest
import org.jetbrains.kotlin.idea.fir.AbstractFirMultiModuleResolveTest
import org.jetbrains.kotlin.idea.folding.AbstractKotlinFoldingTest
import org.jetbrains.kotlin.idea.hierarchy.AbstractHierarchyTest
@@ -817,6 +818,10 @@ fun main(args: Array<String>) {
testClass<AbstractFirMultiModuleResolveTest> {
model("fir/multiModule", recursive = false, extension = null)
}
testClass<AbstractFirLazyResolveTest> {
model("fir/lazyResolve", extension = "test", singleClass = true, filenameStartsLowerCase = true)
}
}
testGroup("idea/idea-maven/test", "idea/idea-maven/testData") {
@@ -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)
}
}
+4
View File
@@ -18,6 +18,10 @@ dependencies {
compile(project(":idea:idea-jps-common"))
compile(project(":plugins:android-extensions-compiler"))
compile(project(":kotlin-scripting-compiler-impl"))
compile(project(":compiler:fir:psi2fir"))
compile(project(":compiler:fir:fir2ir"))
compile(project(":compiler:fir:resolve"))
compile(project(":compiler:fir:java"))
compile(commonDep("org.jetbrains.kotlinx", "kotlinx-coroutines-core")) { isTransitive = false }
compile(commonDep("org.jetbrains.kotlinx", "kotlinx-coroutines-jdk8")) { isTransitive = false }
+5
View File
@@ -0,0 +1,5 @@
fun callMe() = 42
fun other() {
val x = 1
}
+7
View File
@@ -0,0 +1,7 @@
FILE: callMe.kt
public final fun callMe(): R|kotlin/Int| {
^callMe Int(42)
}
public final fun other(): R|kotlin/Unit| {
lval x: <implicit> = Int(1)
}
+11
View File
@@ -0,0 +1,11 @@
fun main() {
<caret>callMe()
foo()
bar(1, 2)
}
fun foo() {
val y = 2.0
}
fun bar(x: Int, y: Int) = x + y
+12
View File
@@ -0,0 +1,12 @@
FILE: main.kt
public final fun main(): R|kotlin/Unit| {
R|/callMe|()
R|/foo|()
R|/bar|(Int(1), Int(2))
}
public final fun foo(): R|kotlin/Unit| {
lval y: <implicit> = Double(2.0)
}
public final fun bar(x: R|kotlin/Int|, y: R|kotlin/Int|): R|kotlin/Int| {
^bar R|<local>/x|.R|kotlin/Int.plus|(R|<local>/y|)
}
+1
View File
@@ -0,0 +1 @@
R|/callMe|()
+3
View File
@@ -0,0 +1,3 @@
{
"mainFile": "main.kt"
}
+3
View File
@@ -0,0 +1,3 @@
package root
fun foo() = <caret>myProperty
+4
View File
@@ -0,0 +1,4 @@
FILE: main.kt
public final fun foo(): R|kotlin/Int| {
^foo R|root/myProperty|
}
@@ -0,0 +1,3 @@
package root
val myProperty get() = 42
@@ -0,0 +1,5 @@
FILE: myProperty.kt
public final val myProperty: R|kotlin/Int|
public get(): R|kotlin/Int| {
^ Int(42)
}
@@ -0,0 +1 @@
R|root/myProperty|
@@ -0,0 +1,3 @@
{
"mainFile": "main.kt"
}
@@ -0,0 +1,94 @@
/*
* 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.google.gson.JsonObject
import com.google.gson.JsonParser
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.search.FileTypeIndex
import com.intellij.testFramework.LightProjectDescriptor
import junit.framework.TestCase
import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.resolve.FirProvider
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
import org.jetbrains.kotlin.idea.caches.project.productionSourceInfo
import org.jetbrains.kotlin.idea.jsonUtils.getString
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.idea.util.projectStructure.allModules
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
abstract class AbstractFirLazyResolveTest : KotlinLightCodeInsightFixtureTestCase() {
override fun getProjectDescriptor(): LightProjectDescriptor {
if (KotlinTestUtils.isAllFilesPresentTest(getTestName(false))) return super.getProjectDescriptor()
val testFile = File(testDataPath, fileName())
val config = JsonParser().parse(FileUtil.loadFile(testFile, true)) as JsonObject
val withRuntime = config["withRuntime"]?.asBoolean ?: false
return if (withRuntime)
KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
else
KotlinLightProjectDescriptor.INSTANCE
}
fun doTest(path: String) {
val testFile = File(path)
val config = JsonParser().parse(FileUtil.loadFile(testFile, true)) as JsonObject
val mainFilePath = config.getString("mainFile")
val mainFile = File(testFile.parent, mainFilePath)
val mainFileText = FileUtil.loadFile(mainFile, true)
TestCase.assertTrue("\"<caret>\" is missing in file \"$mainFilePath\"", mainFileText.contains("<caret>"))
val projectDir = path.removePrefix(testDataPath).substringBeforeLast('/')
myFixture.copyDirectoryToProject(projectDir, "")
PsiDocumentManager.getInstance(myFixture.project).commitAllDocuments()
myFixture.configureFromTempProjectFile(mainFilePath)
val referenceToResolve = myFixture.getReferenceAtCaretPositionWithAssertion(mainFilePath)
val elementToResolve = referenceToResolve.element
val expressionToResolve = when {
elementToResolve.parent is KtCallExpression -> elementToResolve.parent
else -> elementToResolve
} as KtExpression
val resolveState = expressionToResolve.firResolveState()
val firExpression = expressionToResolve.getOrBuildFir(resolveState)
val resultsDump = firExpression.render()
KotlinTestUtils.assertEqualsToFile(File(testFile.parent, "results.txt"), resultsDump)
fun expectedTxtPath(virtualFile: VirtualFile): String {
val virtualPath = virtualFile.path.substringAfter("/src/")
var result: String? = null
val root = File(path).parentFile
for (file in root.walkTopDown()) {
if (!file.isDirectory && virtualPath in file.absolutePath) {
result = file.absolutePath.replace(".kt", ".txt")
}
}
return result!!
}
val moduleInfo = project.allModules().single().productionSourceInfo() as IdeaModuleInfo
val contentScope = moduleInfo.contentScope()
val files = FileTypeIndex.getFiles(KotlinFileType.INSTANCE, contentScope)
for (file in files) {
val psiFile = psiManager.findFile(file) as KtFile
val session = resolveState.getSession(psiFile)
val firProvider = FirProvider.getInstance(session) as IdeFirProvider
val firFile = firProvider.getFile(psiFile) ?: continue
KotlinTestUtils.assertEqualsToFile(File(expectedTxtPath(file)), firFile.render())
}
}
}
@@ -0,0 +1,41 @@
/*
* 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.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TargetBackend;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/fir/lazyResolve")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class FirLazyResolveTestGenerated extends AbstractFirLazyResolveTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInLazyResolve() throws Exception {
KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/fir/lazyResolve"), Pattern.compile("^(.+)\\.test$"), TargetBackend.ANY);
}
@TestMetadata("simple/simple.test")
public void testSimple_Simple() throws Exception {
runTest("idea/testData/fir/lazyResolve/simple/simple.test");
}
@TestMetadata("simpleProperty/simpleProperty.test")
public void testSimpleProperty_SimpleProperty() throws Exception {
runTest("idea/testData/fir/lazyResolve/simpleProperty/simpleProperty.test");
}
}