diff --git a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtStandaloneProjectStructureProvider.kt b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtStandaloneProjectStructureProvider.kt index 37356507008..5d9b6519966 100644 --- a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtStandaloneProjectStructureProvider.kt +++ b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KtStandaloneProjectStructureProvider.kt @@ -17,7 +17,6 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.LLFirBu import org.jetbrains.kotlin.analysis.project.structure.* import org.jetbrains.kotlin.platform.TargetPlatform import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.analysisContext import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerializerProtocol import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments @@ -40,38 +39,39 @@ internal class KtStandaloneProjectStructureProvider( LLFirBuiltinsSessionFactory.getInstance(project).getBuiltinsSession(platform).ktModule as KtBuiltinsModule } + override fun getNotUnderContentRootModule(project: Project, file: PsiFile?): KtNotUnderContentRootModule { + if (file == null) { + return ktNotUnderContentRootModuleWithoutPsiFile + } + + return notUnderContentRootModuleCache.getOrPut(file) { + KtNotUnderContentRootModuleImpl( + name = file.name, + moduleDescription = "Standalone-not-under-content-root-module-for-$file", + file = file, + project = project, + ) + } + } + @OptIn(KtModuleStructureInternals::class) override fun getModule(element: PsiElement, contextualModule: KtModule?): KtModule { - val containingFileAsPsiFile = element.containingFile + val containingFile = element.containingFile ?: return ktNotUnderContentRootModuleWithoutPsiFile - // If an [element] is created on the fly, e.g., via [KtPsiFactory], - // its containing [PsiFile] may not have [VirtualFile]. - // We can attempt to use an associated [analysisContext] (from [KtPsiFactory]) if any. - // If both fail, the [element] is not bound to any [KtModule], and we bail out early - // by returning a [KtNotUnderContentRootModule] (for that specific unbound file). - val containingFileAsVirtualFile = containingFileAsPsiFile.virtualFile - ?: (containingFileAsPsiFile as? KtFile)?.analysisContext?.containingFile?.virtualFile - ?: return notUnderContentRootModuleCache.getOrPut(containingFileAsPsiFile) { - KtNotUnderContentRootModuleImpl( - name = containingFileAsPsiFile.name, - moduleDescription = "Standalone-not-under-content-root-module-for-$containingFileAsPsiFile", - file = containingFileAsPsiFile, - project = project, - ) - } - if (containingFileAsVirtualFile.extension == BuiltInSerializerProtocol.BUILTINS_FILE_EXTENSION) { + val virtualFile = containingFile.virtualFile + ?: error("${containingFile.name} is not a physical file") + + if (virtualFile.extension == BuiltInSerializerProtocol.BUILTINS_FILE_EXTENSION) { return builtinsModule } - containingFileAsVirtualFile.analysisExtensionFileContextModule?.let { return it } + computeSpecialModule(containingFile)?.let { return it } - return allKtModules.firstOrNull { module -> - containingFileAsVirtualFile in module.contentScope - } + return allKtModules.firstOrNull { module -> virtualFile in module.contentScope } ?: throw KotlinExceptionWithAttachments("Cannot find KtModule; see the attachment for more details.") .withAttachment( - containingFileAsVirtualFile.path, + virtualFile.path, allKtModules.joinToString(separator = System.lineSeparator()) { it.asDebugString() } ) } diff --git a/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/services/KtTestProjectStructureProvider.kt b/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/services/KtTestProjectStructureProvider.kt index eb2ba621ecb..009e954a925 100644 --- a/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/services/KtTestProjectStructureProvider.kt +++ b/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/services/KtTestProjectStructureProvider.kt @@ -5,14 +5,15 @@ package org.jetbrains.kotlin.analysis.test.framework.services +import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile import com.intellij.psi.PsiFileSystemItem import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleProjectStructure import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtStaticProjectStructureProvider import org.jetbrains.kotlin.analysis.project.structure.* -import org.jetbrains.kotlin.analysis.project.structure.impl.KtDanglingFileModuleImpl import org.jetbrains.kotlin.config.LanguageVersionSettings -import org.jetbrains.kotlin.psi.KtCodeFragment +import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.psiUtil.contains import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerializerProtocol import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments @@ -22,37 +23,35 @@ class KtTestProjectStructureProvider( private val builtinsModule: KtBuiltinsModule, private val projectStructure: KtModuleProjectStructure, ) : KtStaticProjectStructureProvider() { + override fun getNotUnderContentRootModule(project: Project, file: PsiFile?): KtNotUnderContentRootModule { + error("Not-under content root modules most be initialized explicitly in tests") + } + @OptIn(KtModuleStructureInternals::class) override fun getModule(element: PsiElement, contextualModule: KtModule?): KtModule { // Unwrap context-dependent file copies coming from dependent sessions - val containingPsiFile = element.containingFile.originalFile + val containingFile = element.containingFile.originalFile - if (containingPsiFile is KtCodeFragment) { - val contextElement = containingPsiFile.context - ?: throw KotlinExceptionWithAttachments("Context not found for a code fragment") - .withAttachment("codeFragment.kt", containingPsiFile.text) + val virtualFile = containingFile.virtualFile - val contextModule = getModule(contextElement, contextualModule) - return KtDanglingFileModuleImpl(containingPsiFile, contextModule) + if (virtualFile != null) { + if (virtualFile.extension == BuiltInSerializerProtocol.BUILTINS_FILE_EXTENSION) { + return builtinsModule + } + + projectStructure.binaryModules + .firstOrNull { binaryModule -> virtualFile in binaryModule.contentScope } + ?.let { return it } } - val containingVirtualFile = containingPsiFile.virtualFile - if (containingVirtualFile.extension == BuiltInSerializerProtocol.BUILTINS_FILE_EXTENSION) { - return builtinsModule - } - - containingVirtualFile.analysisExtensionFileContextModule?.let { return it } - - projectStructure.binaryModules.firstOrNull { binaryModule -> - containingVirtualFile in binaryModule.contentScope - }?.let { return it } + computeSpecialModule(containingFile)?.let { return it } return projectStructure.mainModules.firstOrNull { module -> element in module.ktModule.contentScope }?.ktModule ?: throw KotlinExceptionWithAttachments("Cannot find KtModule; see the attachment for more details.") .withAttachment( - containingVirtualFile.path, + virtualFile?.path ?: containingFile.name, allKtModules.joinToString(separator = System.lineSeparator()) { it.asDebugString() } ) } diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/test/configurators/AnalysisApiFirOutOfContentRootTestConfigurator.kt b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/test/configurators/AnalysisApiFirOutOfContentRootTestConfigurator.kt index 98be027b32a..874661cd82b 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/test/configurators/AnalysisApiFirOutOfContentRootTestConfigurator.kt +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/test/configurators/AnalysisApiFirOutOfContentRootTestConfigurator.kt @@ -65,7 +65,7 @@ private object KtOutOfContentRootModuleFactory : KtModuleFactory { } } -private class KtNotUnderContentRootModuleForTest( +internal class KtNotUnderContentRootModuleForTest( override val name: String, override val file: PsiFile, override val platform: TargetPlatform diff --git a/analysis/project-structure/src/org/jetbrains/kotlin/analysis/project/structure/ProjectStructureProvider.kt b/analysis/project-structure/src/org/jetbrains/kotlin/analysis/project/structure/ProjectStructureProvider.kt index 4097815da72..f32999de62f 100644 --- a/analysis/project-structure/src/org/jetbrains/kotlin/analysis/project/structure/ProjectStructureProvider.kt +++ b/analysis/project-structure/src/org/jetbrains/kotlin/analysis/project/structure/ProjectStructureProvider.kt @@ -7,8 +7,14 @@ package org.jetbrains.kotlin.analysis.project.structure import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.analysis.project.structure.impl.KtDanglingFileModuleImpl import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl +import org.jetbrains.kotlin.psi.KtCodeFragment +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.analysisContext +import org.jetbrains.kotlin.psi.doNotAnalyze public abstract class ProjectStructureProvider { /** @@ -34,6 +40,39 @@ public abstract class ProjectStructureProvider { */ public abstract fun getModule(element: PsiElement, contextualModule: KtModule?): KtModule + protected abstract fun getNotUnderContentRootModule(project: Project, file: PsiFile?): KtNotUnderContentRootModule + + @OptIn(KtModuleStructureInternals::class) + protected fun computeSpecialModule(file: PsiFile): KtModule? { + val virtualFile = file.virtualFile + if (virtualFile != null) { + val contextModule = virtualFile.analysisExtensionFileContextModule + if (contextModule != null) { + return contextModule + } + } + + if (file is KtFile && file.isDangling) { + val contextModule = computeContextModule(file) + return KtDanglingFileModuleImpl(file, contextModule) + } + + return null + } + + @OptIn(KtModuleStructureInternals::class) + private fun computeContextModule(file: KtFile): KtModule { + val contextElement = file.context + ?: file.analysisContext + ?: file.originalFile.takeIf { it !== file } + + if (contextElement != null) { + return getModule(contextElement, contextualModule = null) + } + + return getNotUnderContentRootModule(file.project, file = null) + } + /** * Project-global [LanguageVersionSettings] for source modules lacking explicit settings (such as [KtNotUnderContentRootModule]). */ @@ -55,4 +94,11 @@ public abstract class ProjectStructureProvider { return getInstance(project).getModule(element, contextualModule) } } -} \ No newline at end of file +} + +@OptIn(KtModuleStructureInternals::class) +private val KtFile.isDangling: Boolean + get() = this is KtCodeFragment + || !isPhysical + || analysisContext != null + || doNotAnalyze != null \ No newline at end of file