[LL FIR] designation refactoring

Collect `FirDesignation` by default with `FirFile` instead of just
a separate path and additional repeated logic around

^KT-65344
This commit is contained in:
Dmitrii Gridin
2024-02-05 17:31:51 +01:00
committed by Space Team
parent 47e8d987b9
commit 8f987644ab
7 changed files with 118 additions and 73 deletions
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.llFirMo
import org.jetbrains.kotlin.analysis.low.level.api.fir.providers.nullableJavaSymbolProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirLibraryOrLibrarySourceResolvableModuleSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.FirElementFinder
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.containingClassIdOrNull
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.getContainingFile
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.isLocalForLazyResolutionPurposes
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.unwrapCopy
@@ -25,13 +26,13 @@ import org.jetbrains.kotlin.fir.declarations.utils.isLocal
import org.jetbrains.kotlin.fir.resolve.providers.firProvider
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirScriptSymbol
import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.utils.exceptions.ExceptionAttachmentBuilder
import org.jetbrains.kotlin.utils.exceptions.checkWithAttachment
@@ -118,7 +119,7 @@ fun FirDesignation.toSequence(includeTarget: Boolean): Sequence<FirElementWithRe
if (includeTarget) yield(target)
}
private fun collectDesignationPath(target: FirElementWithResolveState): List<FirDeclaration>? {
private fun tryCollectDesignation(providedFile: FirFile?, target: FirElementWithResolveState): FirDesignation? {
when (target) {
is FirSimpleFunction,
is FirProperty,
@@ -132,14 +133,14 @@ private fun collectDesignationPath(target: FirElementWithResolveState): List<Fir
// We shouldn't try to build a designation path for such fake declarations as they
// do not depend on outer classes during resolution
if (target.isCopyCreatedInScope) return emptyList()
if (target.isCopyCreatedInScope) return FirDesignation(target)
if (target.symbol.isLocalForLazyResolutionPurposes) {
return null
}
val containingClassId = target.containingClassLookupTag()?.classId
return collectDesignationPathWithContainingClass(target, containingClassId)
return collectDesignationPathWithContainingClass(providedFile, target, containingClassId)
}
is FirClassLikeDeclaration -> {
@@ -148,55 +149,58 @@ private fun collectDesignationPath(target: FirElementWithResolveState): List<Fir
}
val containingClassId = target.symbol.classId.outerClassId
return collectDesignationPathWithContainingClass(target, containingClassId)
return collectDesignationPathWithContainingClass(providedFile, target, containingClassId)
}
is FirDanglingModifierList -> {
val containingClassId = target.containingClass()?.classId
return collectDesignationPathWithContainingClass(target, containingClassId)
return collectDesignationPathWithContainingClass(providedFile, target, containingClassId)
}
is FirAnonymousInitializer -> {
val containingDeclarationSymbol = target.containingDeclarationSymbol
return when (containingDeclarationSymbol) {
is FirScriptSymbol -> listOf(containingDeclarationSymbol.fir)
is FirClassSymbol<*> -> collectDesignationPathWithContainingClass(target, containingDeclarationSymbol.classId)
else -> null
}
val containingClassId = target.containingClassIdOrNull()
return collectDesignationPathWithContainingClass(providedFile, target, containingClassId)
}
is FirFileAnnotationsContainer -> return FirDesignation(path = listOf(target.containingFileSymbol.fir), target = target)
is FirScript, is FirCodeFragment -> {
requireIsInstance<FirDeclaration>(target)
return collectDesignationPathWithContainingClass(providedFile, target, containingClassId = null)
}
is FirScript, is FirCodeFragment, is FirFileAnnotationsContainer -> return emptyList()
else -> return null
}
}
private fun collectDesignationPathWithContainingClassByFirFile(
firFile: FirFile,
containingClassId: ClassId?,
private fun collectDesignationPathWithContainingClass(
providedFile: FirFile?,
target: FirDeclaration,
): List<FirDeclaration>? = FirElementFinder.findClassPathToDeclaration(
firFile = firFile,
declarationContainerClassId = containingClassId,
targetMemberDeclaration = target,
)
private fun collectDesignationPathWithContainingClass(target: FirDeclaration, containingClassId: ClassId?): List<FirDeclaration>? {
containingClassId: ClassId?,
): FirDesignation? {
if (containingClassId?.isLocal == true) {
return null
}
val firFile = target.getContainingFile()
if (firFile != null && (containingClassId == null || firFile.packageFqName == containingClassId.packageFqName)) {
val designationPath = collectDesignationPathWithContainingClassByFirFile(firFile, containingClassId, target)
val file = providedFile ?: target.getContainingFile()
if (file != null && (containingClassId == null || file.packageFqName == containingClassId.packageFqName)) {
val designationPath = FirElementFinder.collectDesignationPath(
firFile = file,
declarationContainerClassId = containingClassId,
targetMemberDeclaration = target,
)
if (designationPath != null) {
return designationPath
}
}
val fallbackClassPath = collectDesignationPathWithContainingClassFallback(target, containingClassId)
val firScript = firFile?.declarations?.singleOrNull() as? FirScript
val fallbackPath = listOfNotNull(firScript) + fallbackClassPath.orEmpty()
return patchDesignationPathIfNeeded(target, fallbackPath)
val fallbackFile = providedFile ?: fallbackClassPath?.lastOrNull()?.getContainingFile() ?: file
val fallbackScript = fallbackFile?.declarations?.singleOrNull() as? FirScript
val fallbackPath = listOfNotNull(fallbackFile, fallbackScript) + fallbackClassPath.orEmpty()
val patchedPath = patchDesignationPathIfNeeded(target, fallbackPath)
return FirDesignation(patchedPath, target)
}
private fun collectDesignationPathWithContainingClassFallback(
@@ -321,26 +325,18 @@ fun FirElementWithResolveState.collectDesignationWithFile(): FirDesignation =
}
fun FirElementWithResolveState.tryCollectDesignation(firFile: FirFile): FirDesignation? =
collectDesignationPath(this)?.let {
FirDesignation(path = listOf(firFile) + it, target = this)
}
tryCollectDesignation(providedFile = firFile, target = this)
fun FirElementWithResolveState.tryCollectDesignation(): FirDesignation? =
collectDesignationPath(this)?.let {
FirDesignation(path = it, target = this)
}
tryCollectDesignation(providedFile = null, target = this)
fun FirElementWithResolveState.tryCollectDesignationWithFile(): FirDesignation? {
return when (this) {
is FirSyntheticProperty, is FirSyntheticPropertyAccessor -> unexpectedElementError<FirElementWithResolveState>(this)
is FirFileAnnotationsContainer, is FirDeclaration -> {
val path = collectDesignationPath(this) ?: return null
val firFile = path.lastOrNull()?.getContainingFile() ?: getContainingFile() ?: return null
FirDesignation(path = listOf(firFile) + path, target = this)
}
else -> unexpectedElementError<FirElementWithResolveState>(this)
fun FirElementWithResolveState.tryCollectDesignationWithFile(): FirDesignation? = when (this) {
is FirSyntheticProperty, is FirSyntheticPropertyAccessor -> unexpectedElementError<FirElementWithResolveState>(this)
is FirFileAnnotationsContainer, is FirDeclaration -> {
val designation = tryCollectDesignation(providedFile = null, target = this)
designation?.takeIf { it.fileOrNull != null }
}
else -> unexpectedElementError<FirElementWithResolveState>(this)
}
internal fun patchDesignationPathIfNeeded(target: FirElementWithResolveState, targetPath: List<FirDeclaration>): List<FirDeclaration> {
@@ -358,11 +354,14 @@ private fun patchDesignationPathForCopy(target: FirElementWithResolveState, targ
return buildList {
for (targetPathDeclaration in targetPath) {
val targetPathPsi = targetPathDeclaration.psi as? KtDeclaration ?: return null
val targetPathPsi = targetPathDeclaration.psi ?: return null
if (targetPathPsi !is KtClassOrObject && targetPathPsi !is KtScript && targetPathPsi !is KtFile) return null
val originalPathPsi = targetPathPsi.unwrapCopy(targetPsiFile) ?: return null
val originalPathDeclaration = when (originalPathPsi) {
is KtClassOrObject -> originalPathPsi.getOrBuildFirSafe<FirRegularClass>(contextResolveSession)
is KtScript -> originalPathPsi.getOrBuildFirSafe<FirScript>(contextResolveSession)
is KtFile -> originalPathPsi.getOrBuildFirFile(contextResolveSession)
else -> null
} ?: return null
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.analysis.low.level.api.fir.util
import com.intellij.openapi.progress.ProgressManager
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.FirDesignation
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.withFirDesignationEntry
import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.getNonLocalContainingOrThisDeclaration
import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.isAutonomousDeclaration
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.ContextCollector.Context
@@ -34,6 +35,7 @@ import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.BodyResolveCon
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.typeContext
import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry
import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitorVoid
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
import org.jetbrains.kotlin.psi.KtDeclaration
@@ -41,6 +43,7 @@ import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.psiUtil.isPropertyParameter
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.util.PrivateForInline
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
import java.util.ArrayList
object ContextCollector {
@@ -160,35 +163,47 @@ object ContextCollector {
shouldCollectBodyContext: Boolean,
filter: (PsiElement) -> FilterResponse,
): ContextProvider {
val interceptor = designation?.let(::DesignationInterceptor) ?: { null }
val interceptor = designation?.let(::DesignationInterceptor)
val visitor = ContextCollectorVisitor(holder, shouldCollectBodyContext, filter, interceptor)
file.accept(visitor)
visitor.collect(file)
return ContextProvider { element, kind -> visitor[element, kind] }
}
private class DesignationInterceptor(private val designation: FirDesignation) : () -> FirElement? {
private val targetIterator = iterator {
yieldAll(designation.path)
yield(designation.target)
}
override fun invoke(): FirElement? {
return if (targetIterator.hasNext()) targetIterator.next() else null
}
}
fun interface ContextProvider {
operator fun get(element: PsiElement, kind: ContextKind): Context?
}
}
private class DesignationInterceptor(val designation: FirDesignation) : () -> FirElement? {
private val targetIterator = iterator {
yieldAll(designation.path)
yield(designation.target)
}
override fun invoke(): FirElement? = if (targetIterator.hasNext()) targetIterator.next() else null
}
private class ContextCollectorVisitor(
private val holder: SessionHolder,
private val shouldCollectBodyContext: Boolean,
private val filter: (PsiElement) -> FilterResponse,
private val designationPathInterceptor: () -> FirElement?,
private val designationPathInterceptor: DesignationInterceptor?,
) : FirDefaultVisitorVoid() {
fun collect(file: FirFile) {
if (designationPathInterceptor != null) {
withInterceptor {
// This code is unreachable in the case of a not empty path
errorWithAttachment("Designation path is empty") {
withFirEntry("file", file)
withFirDesignationEntry("designation", designationPathInterceptor.designation)
}
}
} else {
file.accept(this)
}
}
private data class ContextKey(val element: PsiElement, val kind: ContextKind)
operator fun get(element: PsiElement, kind: ContextKind): Context? {
@@ -754,7 +769,7 @@ private class ContextCollectorVisitor(
* If the designation is over, then allows the [block] code to take control.
*/
private fun withInterceptor(block: () -> Unit) {
val target = designationPathInterceptor()
val target = designationPathInterceptor?.invoke()
if (target != null) {
target.accept(this)
} else {
@@ -28,15 +28,15 @@ internal object FirElementFinder {
expectedDeclarationAcceptor = { it is FirClassLikeDeclaration && it.symbol.name == classId.shortClassName },
)?.target?.let { it as FirClassLikeDeclaration }
fun findClassPathToDeclaration(
fun collectDesignationPath(
firFile: FirFile,
declarationContainerClassId: ClassId?,
targetMemberDeclaration: FirDeclaration,
): List<FirDeclaration>? = collectDesignationPath(
): FirDesignation? = collectDesignationPath(
firFile = firFile,
containerClassId = declarationContainerClassId,
expectedDeclarationAcceptor = { it == targetMemberDeclaration },
)?.path
)
fun findDeclaration(firFile: FirFile, nonLocalDeclaration: KtDeclaration): FirDeclaration? = collectDesignationPath(
firFile = firFile,
@@ -90,7 +90,7 @@ internal object FirElementFinder {
}
val classIdPathSegment = containerClassId?.relativeClassName?.pathSegments().orEmpty()
val path = ArrayList<FirDeclaration>(classIdPathSegment.size + 1)
val path = ArrayList<FirDeclaration>(classIdPathSegment.size + 2)
var result: FirDeclaration? = null
fun find(declarations: Iterable<FirDeclaration>, classIdPathIndex: Int): Boolean {
@@ -136,6 +136,7 @@ internal object FirElementFinder {
return false
}
path += firFile
find(firFile.declarations, classIdPathIndex = 0)
if (result == null) {
@@ -47,8 +47,8 @@ Tower Data Context:
Scope: FirLocalScope
FILE: [ResolvedTo(IMPORTS)] statement.kts
context(<script>@R|kotlin/script/templates/standard/ScriptTemplateWithArgs|)
SCRIPT: [ResolvedTo(TYPES)] <script-statement.kts>
context(<script>@kotlin.script.templates.standard.ScriptTemplateWithArgs)
SCRIPT: [ResolvedTo(RAW_FIR)] <script-statement.kts>
[ResolvedTo(RAW_FIR)] lval args: R|kotlin/Array<kotlin/String>|
public final? [ResolvedTo(RAW_FIR)] class ScriptClass : R|kotlin/Any| {
@@ -61,9 +61,7 @@ FILE: [ResolvedTo(IMPORTS)] statement.kts
}
public final [ResolvedTo(IMPLICIT_TYPES_BODY_RESOLVE)] fun scriptFunction(): R|kotlin/Int| {
^scriptFunction Int(42)
}
public? final? [ResolvedTo(RAW_FIR)] fun scriptFunction(): <implicit> { LAZY_BLOCK }
public? final? [ResolvedTo(RAW_FIR)] fun unusedScriptFunction([ResolvedTo(RAW_FIR)] p: String): <implicit> { LAZY_BLOCK }
@@ -53,8 +53,8 @@ Tower Data Context:
Scope: FirLocalScope
FILE: [ResolvedTo(IMPORTS)] topLevel.kts
context(<script>@R|kotlin/script/templates/standard/ScriptTemplateWithArgs|)
SCRIPT: [ResolvedTo(TYPES)] <script-topLevel.kts>
context(<script>@kotlin.script.templates.standard.ScriptTemplateWithArgs)
SCRIPT: [ResolvedTo(RAW_FIR)] <script-topLevel.kts>
[ResolvedTo(RAW_FIR)] lval args: R|kotlin/Array<kotlin/String>|
public? final? [ResolvedTo(RAW_FIR)] fun before_Fun(): R|kotlin/Unit| { LAZY_BLOCK }
@@ -0,0 +1,16 @@
Tower Data Context:
Element 0
Scope: FirDefaultStarImportingScope
Element 1
Scope: FirExplicitStarImportingScope
Element 2
Scope: FirDefaultSimpleImportingScope
Element 3
Scope: FirDefaultSimpleImportingScope
Element 4
Scope: FirPackageMemberScope
Element 5
Scope: FirExplicitSimpleImportingScope
FILE: [ResolvedTo(RAW_FIR)] typeAlias.kt
public final [ResolvedTo(RAW_FIR)] typealias Foo = String
@@ -0,0 +1,16 @@
Tower Data Context:
Element 0
Scope: FirDefaultStarImportingScope
Element 1
Scope: FirExplicitStarImportingScope
Element 2
Scope: FirDefaultSimpleImportingScope
Element 3
Scope: FirDefaultSimpleImportingScope
Element 4
Scope: FirPackageMemberScope
Element 5
Scope: FirExplicitSimpleImportingScope
FILE: [ResolvedTo(RAW_FIR)] typeAliasInitializer.kt
public final [ResolvedTo(RAW_FIR)] typealias Foo = String