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 7942d79e734..b5b16076040 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 @@ -32,7 +32,9 @@ 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.KtScript import org.jetbrains.kotlin.utils.exceptions.ExceptionAttachmentBuilder import org.jetbrains.kotlin.utils.exceptions.checkWithAttachment import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment @@ -104,15 +106,6 @@ class FirDesignation( * This property exists only for compatibility and should be dropped after KT-65345 */ val classPath: List get() = path.filterIsInstance() - - /** - * This constructor exists only for compatibility and should be dropped after KT-65345 - */ - constructor( - firFile: FirFile, - path: List, - target: FirElementWithResolveState, - ) : this(listOf(firFile) + path, target) } fun ExceptionAttachmentBuilder.withFirDesignationEntry(name: String, designation: FirDesignation) { @@ -130,7 +123,7 @@ fun FirDesignation.toSequence(includeTarget: Boolean): Sequence? { +private fun collectDesignationPath(target: FirElementWithResolveState): List? { when (target) { is FirSimpleFunction, is FirProperty, @@ -148,7 +141,7 @@ private fun collectDesignationPath(target: FirElementWithResolveState): List { - val containingClassId = target.containingClass()?.classId ?: return emptyList() + val containingClassId = target.containingClass()?.classId return collectDesignationPathWithContainingClass(target, containingClassId) } is FirAnonymousInitializer -> { - val containingClassId = (target.containingDeclarationSymbol as? FirClassSymbol<*>)?.classId - if (containingClassId == null || containingClassId.isLocal) return null - return collectDesignationPathWithContainingClass(target, containingClassId) + val containingDeclarationSymbol = target.containingDeclarationSymbol + return when (containingDeclarationSymbol) { + is FirScriptSymbol -> listOf(containingDeclarationSymbol.fir) + is FirClassSymbol<*> -> collectDesignationPathWithContainingClass(target, containingDeclarationSymbol.classId) + else -> null + } } is FirErrorProperty -> { return if (target.diagnostic == ConeDestructuringDeclarationsOnTopLevel) emptyList() else null } - is FirScript, is FirCodeFragment -> { + is FirScript, is FirCodeFragment, is FirFileAnnotationsContainer -> { return emptyList() } @@ -188,31 +184,37 @@ private fun collectDesignationPath(target: FirElementWithResolveState): List? = FirElementFinder.findClassPathToDeclaration( +): List? = FirElementFinder.findClassPathToDeclaration( firFile = firFile, declarationContainerClassId = containingClassId, targetMemberDeclaration = target, ) -private fun collectDesignationPathWithContainingClass(target: FirDeclaration, containingClassId: ClassId): List? { - if (containingClassId.isLocal) { +private fun collectDesignationPathWithContainingClass(target: FirDeclaration, containingClassId: ClassId?): List? { + if (containingClassId?.isLocal == true) { return null } val firFile = target.getContainingFile() - if (firFile != null && firFile.packageFqName == containingClassId.packageFqName) { + if (firFile != null && (containingClassId == null || firFile.packageFqName == containingClassId.packageFqName)) { val designationPath = collectDesignationPathWithContainingClassByFirFile(firFile, containingClassId, target) if (designationPath != null) { return designationPath } } - return collectDesignationPathWithContainingClassFallback(target, containingClassId) + val fallbackClassPath = collectDesignationPathWithContainingClassFallback(target, containingClassId) + val firScript = firFile?.declarations?.singleOrNull() as? FirScript + val fallbackPath = listOfNotNull(firScript) + fallbackClassPath.orEmpty() + return patchDesignationPathIfNeeded(target, fallbackPath) } -private fun collectDesignationPathWithContainingClassFallback(target: FirDeclaration, containingClassId: ClassId): List? { +private fun collectDesignationPathWithContainingClassFallback( + target: FirDeclaration, + containingClassId: ClassId?, +): List? { val useSiteSession = getTargetSession(target) fun resolveChunk(classId: ClassId): FirRegularClass { @@ -244,7 +246,7 @@ private fun collectDesignationPathWithContainingClassFallback(target: FirDeclara if (chunks.any { it.shortClassName.isSpecial }) { val fallbackResult = collectDesignationPathWithTreeTraversal(target) if (fallbackResult != null) { - return patchDesignationPathIfNeeded(target, fallbackResult) + return fallbackResult } } @@ -253,7 +255,7 @@ private fun collectDesignationPathWithContainingClassFallback(target: FirDeclara .map { resolveChunk(it) } .asReversed() - return patchDesignationPathIfNeeded(target, result) + return result } /* @@ -263,9 +265,7 @@ private fun collectDesignationPathWithContainingClassFallback(target: FirDeclara private fun collectDesignationPathWithTreeTraversal(target: FirDeclaration): List? { val containingFile = target.getContainingFile() ?: return null - val path = ArrayDeque() - path.addLast(containingFile) - + val path = mutableListOf() var result: List? = null val visitor = object : FirVisitorVoid() { @@ -273,13 +273,18 @@ private fun collectDesignationPathWithTreeTraversal(target: FirDeclaration): Lis if (result != null) { return } else if (element === target) { - result = path.filterIsInstance() + result = path } else { try { - path.addLast(element) + if (element is FirRegularClass) { + path += element + } + element.acceptChildren(this) } finally { - path.removeLast() + if (element is FirRegularClass) { + path.removeLast() + } } } } @@ -329,29 +334,24 @@ fun FirElementWithResolveState.collectDesignationWithFile(): FirDesignation = fun FirElementWithResolveState.tryCollectDesignation(firFile: FirFile): FirDesignation? = collectDesignationPath(this)?.let { - FirDesignation(firFile, it, this) + FirDesignation(path = listOf(firFile) + it, target = this) } fun FirElementWithResolveState.tryCollectDesignation(): FirDesignation? = collectDesignationPath(this)?.let { - FirDesignation(it, this) + FirDesignation(path = it, target = this) } fun FirElementWithResolveState.tryCollectDesignationWithFile(): FirDesignation? { return when (this) { - is FirScript, is FirCodeFragment, is FirFileAnnotationsContainer -> { - val firFile = getContainingFile() ?: return null - FirDesignation(firFile, path = emptyList(), this) - } - is FirSyntheticProperty, is FirSyntheticPropertyAccessor -> unexpectedElementError(this) - is FirDeclaration -> { - val scriptDesignation = scriptDesignation() + is FirFileAnnotationsContainer, is FirDeclaration -> { + val scriptDesignation = (this as? FirDeclaration)?.scriptDesignation() if (scriptDesignation != null) return scriptDesignation val path = collectDesignationPath(this) ?: return null val firFile = path.lastOrNull()?.getContainingFile() ?: getContainingFile() ?: return null - FirDesignation(firFile, path, this) + FirDesignation(path = listOf(firFile) + path, target = this) } else -> unexpectedElementError(this) @@ -363,22 +363,22 @@ private fun FirDeclaration.scriptDesignation(): FirDesignation? { this is FirAnonymousInitializer -> { val firScriptSymbol = (containingDeclarationSymbol as? FirScriptSymbol) ?: return null val firFile = firScriptSymbol.fir.getContainingFile() ?: return null - FirDesignation(firFile, path = emptyList(), firScriptSymbol.fir) + FirDesignation(path = listOf(firFile), target = firScriptSymbol.fir) } isScriptDependentDeclaration -> { val firFile = getContainingFile() ?: return null val firScript = firFile.declarations.singleOrNull() as? FirScript ?: return null - FirDesignation(firFile, path = emptyList(), firScript) + FirDesignation(path = listOf(firFile), target = firScript) } else -> null } } -internal fun patchDesignationPathIfNeeded(target: FirElementWithResolveState, targetPath: List): List { +internal fun patchDesignationPathIfNeeded(target: FirElementWithResolveState, targetPath: List): List { return patchDesignationPathForCopy(target, targetPath) ?: targetPath } -private fun patchDesignationPathForCopy(target: FirElementWithResolveState, targetPath: List): List? { +private fun patchDesignationPathForCopy(target: FirElementWithResolveState, targetPath: List): List? { val targetModule = target.llFirModuleData.ktModule if (targetModule is KtDanglingFileModule && targetModule.resolutionMode == DanglingFileResolutionMode.IGNORE_SELF) { @@ -388,11 +388,16 @@ private fun patchDesignationPathForCopy(target: FirElementWithResolveState, targ val contextResolveSession = contextModule.getFirResolveSession(contextModule.project) return buildList { - for (targetPathClass in targetPath) { - val targetPathPsi = targetPathClass.psi as? KtDeclaration ?: return null + for (targetPathDeclaration in targetPath) { + val targetPathPsi = targetPathDeclaration.psi as? KtDeclaration ?: return null val originalPathPsi = targetPathPsi.unwrapCopy(targetPsiFile) ?: return null - val originalPathClass = originalPathPsi.getOrBuildFirSafe(contextResolveSession) ?: return null - add(originalPathClass) + val originalPathDeclaration = when (originalPathPsi) { + is KtClassOrObject -> originalPathPsi.getOrBuildFirSafe(contextResolveSession) + is KtScript -> originalPathPsi.getOrBuildFirSafe(contextResolveSession) + else -> null + } ?: return null + + add(originalPathDeclaration) } } } 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 ee0e15e3487..2c5323152ba 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 @@ -123,12 +123,7 @@ object ContextCollector { if (contextKtDeclaration != null) { val designationPath = FirElementFinder.collectDesignationPath(file, contextKtDeclaration) if (designationPath != null) { - val script = file.declarations.singleOrNull() as? FirScript - return if (script == null || script === designationPath.target) { - FirDesignation(designationPath.path, designationPath.target) - } else { - FirDesignation(listOf(script) + designationPath.path, designationPath.target) - } + return designationPath } } @@ -163,7 +158,7 @@ object ContextCollector { holder: SessionHolder, designation: FirDesignation?, shouldCollectBodyContext: Boolean, - filter: (PsiElement) -> FilterResponse + filter: (PsiElement) -> FilterResponse, ): ContextProvider { val interceptor = designation?.let(::DesignationInterceptor) ?: { null } val visitor = ContextCollectorVisitor(holder, shouldCollectBodyContext, filter, interceptor) @@ -192,7 +187,7 @@ private class ContextCollectorVisitor( private val holder: SessionHolder, private val shouldCollectBodyContext: Boolean, private val filter: (PsiElement) -> FilterResponse, - private val designationPathInterceptor: () -> FirElement? + private val designationPathInterceptor: () -> FirElement?, ) : FirDefaultVisitorVoid() { private data class ContextKey(val element: PsiElement, val kind: ContextKind) 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 b1107a49b61..4c1ff6e6513 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 @@ -1,12 +1,12 @@ /* - * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2024 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.analysis.low.level.api.fir.util +import org.jetbrains.kotlin.analysis.low.level.api.fir.api.FirDesignation import org.jetbrains.kotlin.analysis.low.level.api.fir.api.patchDesignationPathIfNeeded -import org.jetbrains.kotlin.utils.exceptions.requireWithAttachment import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.packageFqName @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject +import org.jetbrains.kotlin.utils.exceptions.requireWithAttachment import org.jetbrains.kotlin.utils.ifEmpty internal object FirElementFinder { @@ -29,9 +30,9 @@ internal object FirElementFinder { fun findClassPathToDeclaration( firFile: FirFile, - declarationContainerClassId: ClassId, + declarationContainerClassId: ClassId?, targetMemberDeclaration: FirDeclaration, - ): List? = collectDesignationPath( + ): List? = collectDesignationPath( firFile = firFile, containerClassId = declarationContainerClassId, expectedDeclarationAcceptor = { it == targetMemberDeclaration }, @@ -40,7 +41,7 @@ internal object FirElementFinder { fun findDeclaration(firFile: FirFile, nonLocalDeclaration: KtDeclaration): FirDeclaration? = collectDesignationPath( firFile = firFile, nonLocalDeclaration = nonLocalDeclaration, - )?.target + )?.declarationTarget fun findPathToDeclarationWithTarget( firFile: FirFile, @@ -48,29 +49,32 @@ internal object FirElementFinder { ): List? = collectDesignationPath( firFile = firFile, nonLocalDeclaration = nonLocalDeclaration, - )?.pathWithTarget - - class FirDeclarationDesignation( - val path: List, - val target: FirDeclaration, - ) { - val pathWithTarget: List get() = path + target - } + )?.let { it.path + it.declarationTarget } fun collectDesignationPath( firFile: FirFile, nonLocalDeclaration: KtDeclaration, - ): FirDeclarationDesignation? = collectDesignationPath( + ): FirDesignation? = collectDesignationPath( firFile = firFile, containerClassId = nonLocalDeclaration.containingClassOrObject?.getClassId(), expectedDeclarationAcceptor = { it.psi == nonLocalDeclaration }, ) + /** + * @see collectDesignationPath + */ + private val FirDesignation.declarationTarget: FirDeclaration get() = target as FirDeclaration + + /** + * @return [FirDesignation] where [FirDesignation.target] is [FirDeclaration] + * + * @see declarationTarget + */ private fun collectDesignationPath( firFile: FirFile, containerClassId: ClassId?, expectedDeclarationAcceptor: (FirDeclaration) -> Boolean, - ): FirDeclarationDesignation? { + ): FirDesignation? { if (containerClassId != null) { requireWithAttachment(!containerClassId.isLocal, { "ClassId should not be local" }) { withEntry("classId", containerClassId) { it.asString() } @@ -86,7 +90,7 @@ internal object FirElementFinder { } val classIdPathSegment = containerClassId?.relativeClassName?.pathSegments().orEmpty() - val path = ArrayList(classIdPathSegment.size) + val path = ArrayList(classIdPathSegment.size + 1) var result: FirDeclaration? = null fun find(declarations: Iterable, classIdPathIndex: Int): Boolean { @@ -99,11 +103,13 @@ internal object FirElementFinder { } subDeclaration is FirScript -> { - val scriptDeclarations = subDeclaration.declarations.asSequence().filterNot { it is FirAnonymousInitializer } - if (find(scriptDeclarations.asIterable(), classIdPathIndex)) { + path += subDeclaration + val scriptDeclarations = subDeclaration.declarations + if (find(scriptDeclarations, classIdPathIndex)) { return true } + path.removeLast() continue } @@ -138,7 +144,7 @@ internal object FirElementFinder { // K1 doesn't perform smart-casts on 'result' @Suppress("UNNECESSARY_NOT_NULL_ASSERTION") - return FirDeclarationDesignation( + return FirDesignation( path = patchDesignationPathIfNeeded(result!!, path).ifEmpty { emptyList() }, target = result!!, ) diff --git a/analysis/low-level-api-fir/testData/contextCollector/scripts/insideClassBody.copy.txt b/analysis/low-level-api-fir/testData/contextCollector/scripts/insideClassBody.copy.txt index 3c7a01b6b70..4487be620b6 100644 --- a/analysis/low-level-api-fir/testData/contextCollector/scripts/insideClassBody.copy.txt +++ b/analysis/low-level-api-fir/testData/contextCollector/scripts/insideClassBody.copy.txt @@ -14,7 +14,7 @@ Tower Data Context: Element 6 Scope: FirScriptDeclarationsScope Classifiers: - FirRegularClassSymbol public final? class Foo : R|kotlin/Any| + FirRegularClassSymbol public final class Foo : R|kotlin/Any| Element 7 Scope: FirLocalScope Classifiers: @@ -27,7 +27,7 @@ Tower Data Context: SCRIPT: lval args: R|kotlin/Array| - public final? class Foo : R|kotlin/Any| + public final class Foo : R|kotlin/Any| Type: kotlin.script.templates.standard.ScriptTemplateWithArgs Label: