[LL API] Recognize scripts in Analysis API

Although the proper support for script resolution is yet to arrive
in K2, some initial steps are required to make LL API understand
such a concept.
This commit is contained in:
Yan Zhulanow
2023-01-26 02:15:29 +09:00
committed by Space Team
parent 603b46e531
commit f352f7d5ba
9 changed files with 60 additions and 28 deletions
@@ -12,15 +12,13 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirModuleResolveCompone
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirResolveSession import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirResolveSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.file.structure.FileStructureElement import org.jetbrains.kotlin.analysis.low.level.api.fir.file.structure.FileStructureElement
import org.jetbrains.kotlin.analysis.low.level.api.fir.lazy.resolve.declarationCanBeLazilyResolved import org.jetbrains.kotlin.analysis.low.level.api.fir.lazy.resolve.declarationCanBeLazilyResolved
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.isNonAnonymousClassOrObject
import org.jetbrains.kotlin.analysis.utils.printer.getElementTextInContext import org.jetbrains.kotlin.analysis.utils.printer.getElementTextInContext
import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirElement
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.symbols.lazyResolveToPhase import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.psi2ir.deparenthesize import org.jetbrains.kotlin.psi2ir.deparenthesize
@@ -98,28 +96,44 @@ internal class FirElementBuilder(
} }
} }
// TODO: simplify internal fun PsiElement.getNonLocalContainingOrThisDeclaration(predicate: (KtDeclaration) -> Boolean = { true }): KtDeclaration? {
internal inline fun PsiElement.getNonLocalContainingOrThisDeclaration(predicate: (KtDeclaration) -> Boolean = { true }): KtDeclaration? { var candidate: KtDeclaration? = null
var container: PsiElement? = this
while (container != null && container !is KtFile) { fun propose(declaration: KtDeclaration) {
if (container is KtNamedDeclaration if (candidate == null) {
&& (container.isNonAnonymousClassOrObject() || container is KtDeclarationWithBody || container is KtProperty || container is KtTypeAlias) candidate = declaration
&& container !is KtPrimaryConstructor
&& declarationCanBeLazilyResolved(container)
&& container !is KtFunctionLiteral
&& container.containingClassOrObject !is KtEnumEntry
&& predicate(container)
) {
return container
} }
if (container is KtDestructuringDeclaration && container.parent is KtFile) {
return container
}
container = container.parent
} }
return null
for (parent in parentsWithSelf) {
if (candidate != null) {
if (parent is KtEnumEntry || parent is KtCallableDeclaration) {
// Candidate turned to be local. Let's find another one
candidate = null
}
}
when (parent) {
is KtScript -> propose(parent)
is KtDestructuringDeclaration -> propose(parent)
is KtNamedDeclaration -> {
val isKindApplicable = when (parent) {
is KtClassOrObject -> !parent.isObjectLiteral()
is KtDeclarationWithBody, is KtProperty, is KtTypeAlias -> true
else -> false
}
if (isKindApplicable && declarationCanBeLazilyResolved(parent) && predicate(parent)) {
propose(parent)
}
}
}
}
return candidate
} }
@Suppress("unused") // Used in the IDE plugin
fun PsiElement.getNonLocalContainingInBodyDeclarationWith(): KtDeclaration? = fun PsiElement.getNonLocalContainingInBodyDeclarationWith(): KtDeclaration? =
getNonLocalContainingOrThisDeclaration { declaration -> getNonLocalContainingOrThisDeclaration { declaration ->
when (declaration) { when (declaration) {
@@ -10,8 +10,6 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirModuleResolveCompone
import org.jetbrains.kotlin.fir.builder.RawFirBuilder import org.jetbrains.kotlin.fir.builder.RawFirBuilder
import org.jetbrains.kotlin.fir.builder.BodyBuildingMode import org.jetbrains.kotlin.fir.builder.BodyBuildingMode
import org.jetbrains.kotlin.fir.declarations.FirFile import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.scopes.FirScopeProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirPhaseRunner
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
/** /**
@@ -22,10 +20,20 @@ internal class LLFirFileBuilder(
val moduleComponents: LLFirModuleResolveComponents, val moduleComponents: LLFirModuleResolveComponents,
) { ) {
fun buildRawFirFileWithCaching(ktFile: KtFile): FirFile = moduleComponents.cache.fileCached(ktFile) { fun buildRawFirFileWithCaching(ktFile: KtFile): FirFile = moduleComponents.cache.fileCached(ktFile) {
val bodyBuildingMode = when {
ktFile.isScript() -> {
// As 'FirScript' content is never transformed, lazy bodies are not replaced with calculated ones even on BODY_RESOLVE.
// Such behavior breaks file structure mapping computation.
// TODO: remove this clause when proper support for scripts is implemented in K2.
BodyBuildingMode.NORMAL
}
else -> BodyBuildingMode.LAZY_BODIES
}
RawFirBuilder( RawFirBuilder(
moduleComponents.session, moduleComponents.session,
moduleComponents.scopeProvider, moduleComponents.scopeProvider,
bodyBuildingMode = BodyBuildingMode.LAZY_BODIES bodyBuildingMode = bodyBuildingMode
).buildFirFile(ktFile) ).buildFirFile(ktFile)
} }
} }
@@ -17,6 +17,7 @@ fun FirElementWithResolvePhase.getContainingFile(): FirFile? {
val provider = moduleData.session.firProvider val provider = moduleData.session.firProvider
return when (this) { return when (this) {
is FirFile -> this is FirFile -> this
is FirScript -> containingFileSymbol.fir
is FirFileAnnotationsContainer -> containingFileSymbol.fir is FirFileAnnotationsContainer -> containingFileSymbol.fir
is FirTypeParameter -> containingDeclarationSymbol.fir.getContainingFile() is FirTypeParameter -> containingDeclarationSymbol.fir.getContainingFile()
is FirPropertyAccessor -> propertySymbol.fir.getContainingFile() is FirPropertyAccessor -> propertySymbol.fir.getContainingFile()
@@ -9,10 +9,7 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.api.throwUnexpectedFirEle
import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.getNonLocalContainingOrThisDeclaration import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.getNonLocalContainingOrThisDeclaration
import org.jetbrains.kotlin.analysis.low.level.api.fir.file.builder.LLFirFileBuilder import org.jetbrains.kotlin.analysis.low.level.api.fir.file.builder.LLFirFileBuilder
import org.jetbrains.kotlin.analysis.low.level.api.fir.providers.LLFirProvider import org.jetbrains.kotlin.analysis.low.level.api.fir.providers.LLFirProvider
import org.jetbrains.kotlin.fir.declarations.FirClassLikeDeclaration import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.psi import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.realPsi import org.jetbrains.kotlin.fir.realPsi
import org.jetbrains.kotlin.fir.resolve.providers.FirProvider import org.jetbrains.kotlin.fir.resolve.providers.FirProvider
@@ -105,6 +102,7 @@ private fun KtDeclaration.findSourceNonLocalFirDeclarationByProvider(
val firFile = containerFirFile ?: firFileBuilder.buildRawFirFileWithCaching(containingKtFile) val firFile = containerFirFile ?: firFileBuilder.buildRawFirFileWithCaching(containingKtFile)
firFile.declarations.firstOrNull { it.psi == this } firFile.declarations.firstOrNull { it.psi == this }
} }
this is KtScript -> containerFirFile?.declarations?.singleOrNull { it is FirScript }
else -> errorWithFirSpecificEntries("Invalid container", psi = this) else -> errorWithFirSpecificEntries("Invalid container", psi = this)
} }
return candidate?.takeIf { it.realPsi == this } return candidate?.takeIf { it.realPsi == this }
@@ -1079,6 +1079,7 @@ open class RawFirBuilder(
origin = FirDeclarationOrigin.Source origin = FirDeclarationOrigin.Source
name = Name.special("<script-${containingFile.name}>") name = Name.special("<script-${containingFile.name}>")
symbol = FirScriptSymbol(context.packageFqName.child(name)) symbol = FirScriptSymbol(context.packageFqName.child(name))
containingFileSymbol = containingFile.symbol
for (declaration in script.declarations) { for (declaration in script.declarations) {
when (declaration) { when (declaration) {
is KtScriptInitializer -> { is KtScriptInitializer -> {
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirModuleData import org.jetbrains.kotlin.fir.FirModuleData
import org.jetbrains.kotlin.fir.expressions.FirAnnotation import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.FirStatement import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.symbols.impl.FirFileSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirScriptSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirScriptSymbol
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.fir.visitors.* import org.jetbrains.kotlin.fir.visitors.*
@@ -29,6 +30,7 @@ abstract class FirScript : FirDeclaration() {
abstract val name: Name abstract val name: Name
abstract val statements: List<FirStatement> abstract val statements: List<FirStatement>
abstract override val symbol: FirScriptSymbol abstract override val symbol: FirScriptSymbol
abstract val containingFileSymbol: FirFileSymbol
abstract val parameters: List<FirVariable> abstract val parameters: List<FirVariable>
abstract val contextReceivers: List<FirContextReceiver> abstract val contextReceivers: List<FirContextReceiver>
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.fir.declarations.FirVariable
import org.jetbrains.kotlin.fir.declarations.impl.FirScriptImpl import org.jetbrains.kotlin.fir.declarations.impl.FirScriptImpl
import org.jetbrains.kotlin.fir.expressions.FirAnnotation import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.FirStatement import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.symbols.impl.FirFileSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirScriptSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirScriptSymbol
import org.jetbrains.kotlin.fir.visitors.* import org.jetbrains.kotlin.fir.visitors.*
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
@@ -42,6 +43,7 @@ class FirScriptBuilder : FirAnnotationContainerBuilder {
lateinit var name: Name lateinit var name: Name
val statements: MutableList<FirStatement> = mutableListOf() val statements: MutableList<FirStatement> = mutableListOf()
lateinit var symbol: FirScriptSymbol lateinit var symbol: FirScriptSymbol
lateinit var containingFileSymbol: FirFileSymbol
val parameters: MutableList<FirVariable> = mutableListOf() val parameters: MutableList<FirVariable> = mutableListOf()
val contextReceivers: MutableList<FirContextReceiver> = mutableListOf() val contextReceivers: MutableList<FirContextReceiver> = mutableListOf()
@@ -56,6 +58,7 @@ class FirScriptBuilder : FirAnnotationContainerBuilder {
name, name,
statements, statements,
symbol, symbol,
containingFileSymbol,
parameters, parameters,
contextReceivers.toMutableOrEmpty(), contextReceivers.toMutableOrEmpty(),
) )
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.fir.declarations.FirScript
import org.jetbrains.kotlin.fir.declarations.FirVariable import org.jetbrains.kotlin.fir.declarations.FirVariable
import org.jetbrains.kotlin.fir.expressions.FirAnnotation import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.FirStatement import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.symbols.impl.FirFileSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirScriptSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirScriptSymbol
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.fir.visitors.* import org.jetbrains.kotlin.fir.visitors.*
@@ -39,6 +40,7 @@ internal class FirScriptImpl(
override val name: Name, override val name: Name,
override val statements: MutableList<FirStatement>, override val statements: MutableList<FirStatement>,
override val symbol: FirScriptSymbol, override val symbol: FirScriptSymbol,
override val containingFileSymbol: FirFileSymbol,
override val parameters: MutableList<FirVariable>, override val parameters: MutableList<FirVariable>,
override var contextReceivers: MutableOrEmptyList<FirContextReceiver>, override var contextReceivers: MutableOrEmptyList<FirContextReceiver>,
) : FirScript() { ) : FirScript() {
@@ -475,6 +475,9 @@ object NodeConfigurator : AbstractFieldConfigurator<FirTreeBuilder>(FirTreeBuild
+name +name
+fieldList(statement).withTransform() +fieldList(statement).withTransform()
+symbol("FirScriptSymbol") +symbol("FirScriptSymbol")
+field("containingFileSymbol", type("fir.symbols.impl", "FirFileSymbol"), argument = null).apply {
withBindThis = false
}
+FieldList("parameters", variable, withReplace = false) +FieldList("parameters", variable, withReplace = false)
+fieldList(contextReceiver, useMutableOrEmpty = true) +fieldList(contextReceiver, useMutableOrEmpty = true)
} }