[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:
+5
@@ -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.
|
||||
|
||||
+72
-6
@@ -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"))
|
||||
+6
-2
@@ -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
|
||||
|
||||
+11
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user