[Analysis API] Add IGNORE_SELF dangling file resolution mode

In the 'IGNORE_SELF' mode, dangling files don't have their own
declarations in providers. As a result, all references there resolve to
declarations of the original file. It is conceptually similar to that we
had in on-air resolve, however, now it's possible to work with the whole
content of the in-memory 'FirFile'.

As it can be seen in 'ProjectStructureProvider.kt'
(KtFile.danglingFileResolutionMode), the 'IGNORE_SELF' mode is
automatically applied for non-physical files with an original file being
set. For other scenarios, now there is a new 'analyzeCopy()' function
that allows to pass the analysis mode explicitly.
This commit is contained in:
Yan Zhulanow
2023-11-22 18:21:18 +09:00
committed by Space Team
parent ae6b690d4f
commit 452d22e14f
84 changed files with 1394 additions and 50 deletions
@@ -230,6 +230,11 @@ public interface KtDanglingFileModule : KtModule {
*/
public val contextModule: KtModule
/**
* A way of resolving references to non-local declarations in the dangling file.
*/
public val resolutionMode: DanglingFileResolutionMode
/**
* True if the [file] is a code fragment.
* Useful to recognize code fragments when their PSI was collected.
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.analysis.project.structure
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.analysis.project.structure.impl.KtDanglingFileModuleImpl
@@ -13,6 +14,7 @@ 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.UserDataProperty
import org.jetbrains.kotlin.psi.analysisContext
import org.jetbrains.kotlin.psi.doNotAnalyze
@@ -54,12 +56,21 @@ public abstract class ProjectStructureProvider {
if (file is KtFile && file.isDangling) {
val contextModule = computeContextModule(file)
return KtDanglingFileModuleImpl(file, contextModule)
val resolutionMode = file.danglingFileResolutionMode ?: computeDefaultDanglingFileResolutionMode(file)
return KtDanglingFileModuleImpl(file, contextModule, resolutionMode)
}
return null
}
private fun computeDefaultDanglingFileResolutionMode(file: KtFile): DanglingFileResolutionMode {
if (!file.isPhysical && !file.viewProvider.isEventSystemEnabled && file.originalFile != file) {
return DanglingFileResolutionMode.IGNORE_SELF
}
return DanglingFileResolutionMode.PREFER_SELF
}
@OptIn(KtModuleStructureInternals::class)
private fun computeContextModule(file: KtFile): KtModule {
val contextElement = file.context
@@ -97,8 +108,63 @@ public abstract class ProjectStructureProvider {
}
@OptIn(KtModuleStructureInternals::class)
private val KtFile.isDangling: Boolean
get() = this is KtCodeFragment
|| !isPhysical
|| analysisContext != null
|| doNotAnalyze != null
public val KtFile.isDangling: Boolean
get() = when {
this is KtCodeFragment -> true
virtualFile?.analysisExtensionFileContextModule != null -> false
!isPhysical -> true
analysisContext != null -> true
doNotAnalyze != null -> true
else -> false
}
/**
* Returns the resolution mode that is explicitly set for this dangling file.
* Returns `null` for files that are not dangling, or if the mode was not set.
*
* Use the `analyzeCopy {}` method for specifying the analysis mode. Note that the effect is thread-local; this is made on purpose, as
* the file might potentially be resolved in parallel in different threads.
*
* Note that the resolution mode affects equality of [KtDanglingFileModule]. It means that for each resolution mode, a separate
* resolution session will be created.
*/
@OptIn(KtModuleStructureInternals::class)
public val KtFile.danglingFileResolutionMode: DanglingFileResolutionMode?
get() = danglingFileResolutionModeState?.get()
/**
* Runs the [action] with a resolution mode being explicitly set for the dangling [file].
*
* Avoid using this function in client-side code. Use `analyzeCopy {}` from Analysis API instead.
*/
@KtModuleStructureInternals
public fun <R> withDanglingFileResolutionMode(file: KtFile, mode: DanglingFileResolutionMode, action: () -> R): R {
require(file.isDangling) { "'withDanglingFileResolutionMode()' is only available to dangling files" }
require(file.originalFile != file) { "'withDanglingFileResolutionMode()' is only available to file copies" }
val modeState = getOrCreateDanglingFileResolutionModeState(file)
val oldValue = modeState.get()
try {
modeState.set(mode)
return action()
} finally {
modeState.set(oldValue)
}
}
private fun getOrCreateDanglingFileResolutionModeState(file: KtFile): ThreadLocal<DanglingFileResolutionMode?> {
synchronized(file) {
val existingState = file.danglingFileResolutionModeState
if (existingState != null) {
return existingState
}
val newState = ThreadLocal<DanglingFileResolutionMode?>()
file.danglingFileResolutionModeState = newState
return newState
}
}
private var KtFile.danglingFileResolutionModeState: ThreadLocal<DanglingFileResolutionMode?>?
by UserDataProperty(Key.create("DANGLING_FILE_RESOLUTION_MODE"))
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.analysis.project.structure.impl
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.project.structure.DanglingFileResolutionMode
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.project.structure.KtDanglingFileModule
import org.jetbrains.kotlin.platform.TargetPlatform
@@ -18,7 +19,8 @@ import java.util.Objects
public class KtDanglingFileModuleImpl(
file: KtFile,
override val contextModule: KtModule
override val contextModule: KtModule,
override val resolutionMode: DanglingFileResolutionMode
) : KtDanglingFileModule {
override val isCodeFragment: Boolean = file is KtCodeFragment
@@ -67,7 +69,9 @@ public class KtDanglingFileModuleImpl(
if (other is KtDanglingFileModuleImpl) {
val selfFile = this.fileRef.element
val otherFile = other.fileRef.element
return selfFile != null && selfFile == otherFile && contextModule == other.contextModule
return selfFile != null && selfFile == otherFile
&& contextModule == other.contextModule
&& resolutionMode == other.resolutionMode
}
return false
@@ -64,3 +64,14 @@ public inline fun <reified M : KtModule> KtModule.allDirectDependenciesOfType():
*/
public fun computeTransitiveDependsOnDependencies(directDependsOnDependencies: List<KtModule>): List<KtModule> =
topologicalSort(directDependsOnDependencies) { this.directDependsOnDependencies }
/**
* Specifies how references to non-local declarations in the dangling files should be resolved.
*/
public enum class DanglingFileResolutionMode {
/** Resolve first to declarations in the dangling file, and delegate to the original file or module only when needed. */
PREFER_SELF,
/** * Resolve only to declarations in the original file or module. Ignore all non-local declarations in the dangling file copy. */
IGNORE_SELF
}