diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/FirDesignation.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/FirDesignation.kt index f9a9fc28c0d..0061fd357fb 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/FirDesignation.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/api/FirDesignation.kt @@ -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? { +private fun tryCollectDesignation(providedFile: FirFile?, target: FirElementWithResolveState): FirDesignation? { when (target) { is FirSimpleFunction, is FirProperty, @@ -132,14 +133,14 @@ private fun collectDesignationPath(target: FirElementWithResolveState): List { @@ -148,55 +149,58 @@ private fun collectDesignationPath(target: FirElementWithResolveState): List { 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(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? = FirElementFinder.findClassPathToDeclaration( - firFile = firFile, - declarationContainerClassId = containingClassId, - targetMemberDeclaration = target, -) - -private fun collectDesignationPathWithContainingClass(target: FirDeclaration, containingClassId: ClassId?): List? { + 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(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(this) +fun FirElementWithResolveState.tryCollectDesignationWithFile(): FirDesignation? = when (this) { + is FirSyntheticProperty, is FirSyntheticPropertyAccessor -> unexpectedElementError(this) + is FirFileAnnotationsContainer, is FirDeclaration -> { + val designation = tryCollectDesignation(providedFile = null, target = this) + designation?.takeIf { it.fileOrNull != null } } + else -> unexpectedElementError(this) } internal fun patchDesignationPathIfNeeded(target: FirElementWithResolveState, targetPath: List): List { @@ -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(contextResolveSession) is KtScript -> originalPathPsi.getOrBuildFirSafe(contextResolveSession) + is KtFile -> originalPathPsi.getOrBuildFirFile(contextResolveSession) else -> null } ?: return null diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/ContextCollector.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/ContextCollector.kt index 514a7eaf95f..3fae8c6ae84 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/ContextCollector.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/ContextCollector.kt @@ -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 { diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/FirElementFinder.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/FirElementFinder.kt index 4c1ff6e6513..8dd87706309 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/FirElementFinder.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/FirElementFinder.kt @@ -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? = 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(classIdPathSegment.size + 1) + val path = ArrayList(classIdPathSegment.size + 2) var result: FirDeclaration? = null fun find(declarations: Iterable, classIdPathIndex: Int): Boolean { @@ -136,6 +136,7 @@ internal object FirElementFinder { return false } + path += firFile find(firFile.declarations, classIdPathIndex = 0) if (result == null) { diff --git a/analysis/low-level-api-fir/testData/contextCollector/scripts/statement.copy.txt b/analysis/low-level-api-fir/testData/contextCollector/scripts/statement.copy.txt index 3f8d1690ad2..588a2ed2406 100644 --- a/analysis/low-level-api-fir/testData/contextCollector/scripts/statement.copy.txt +++ b/analysis/low-level-api-fir/testData/contextCollector/scripts/statement.copy.txt @@ -47,8 +47,8 @@ Tower Data Context: Scope: FirLocalScope FILE: [ResolvedTo(IMPORTS)] statement.kts - context(