[Analysis API] Add source shadowing for resolve extensions.

KtResolveExtensions are designed to handle IDE analysis use cases where
source might not be available at analysis time, because that source is
generated by an external source generator, such as an annotation
processor or resource compiler. The sources generated by those external
generators can appear in the analysis scope, and cause issues with
source clash - resolution may find the virtual source from the
KtResolveExtension, the on-disk generated source from the external
generator, or both. This can cause issues, because that on-disk
generated source may be stale, and may not have symbols that will exist
the next time the generator is run (or, conversely, may have symbols
that will disappear on the next build).

To solve this, add a `getShadowedScope(): GlobalSearchScope` to
`KtResolveExtension`. Any files in the module that are included in that
scope will be hidden from resolution, allowing the resolve extension to
cleanly replace those files.

^KT-58834 fixed
This commit is contained in:
Justin Paupore
2023-05-22 17:59:47 -07:00
committed by Ilya Kirillov
parent 7c87eb098d
commit f590e88bdd
30 changed files with 357 additions and 82 deletions
@@ -23,7 +23,8 @@ import org.jetbrains.kotlin.psi.psiUtil.contains
class KtAnalysisScopeProviderImpl(
override val analysisSession: KtAnalysisSession,
override val token: KtLifetimeToken
override val token: KtLifetimeToken,
private val shadowedScope: GlobalSearchScope
) : KtAnalysisScopeProvider() {
private val baseResolveScope by lazy(LazyThreadSafetyMode.PUBLICATION) {
@@ -31,13 +32,13 @@ class KtAnalysisScopeProviderImpl(
}
private val resolveScope by lazy(LazyThreadSafetyMode.PUBLICATION) {
KtAnalysisScopeProviderResolveScope(baseResolveScope, analysisSession.useSiteModule)
KtAnalysisScopeProviderResolveScope(baseResolveScope, analysisSession.useSiteModule, shadowedScope)
}
override fun getAnalysisScope(): GlobalSearchScope = resolveScope
override fun canBeAnalysed(psi: PsiElement): Boolean {
return baseResolveScope.contains(psi)
return (baseResolveScope.contains(psi) && !shadowedScope.contains(psi))
|| psi.isFromGeneratedModule()
}
@@ -49,12 +50,17 @@ class KtAnalysisScopeProviderImpl(
private class KtAnalysisScopeProviderResolveScope(
private val base: GlobalSearchScope,
private val useSiteModule: KtModule
private val useSiteModule: KtModule,
private val shadowed: GlobalSearchScope,
) : GlobalSearchScope() {
override fun getProject(): Project? = base.project
override fun isSearchInModuleContent(aModule: Module): Boolean = base.isSearchInModuleContent(aModule)
override fun isSearchInLibraries(): Boolean = base.isSearchInLibraries
override fun contains(file: VirtualFile): Boolean = base.contains(file) || file.isFromGeneratedModule(useSiteModule)
override fun contains(file: VirtualFile): Boolean =
(base.contains(file) && !shadowed.contains(file)) || file.isFromGeneratedModule(useSiteModule)
override fun toString() =
"Analysis scope for $useSiteModule (base: $base, shadowed: $shadowed)"
}
@OptIn(KtModuleStructureInternals::class)
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.analysis.api.impl.base.test.cases.references
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.api.impl.base.test.util.KtMultiModuleResolveExtensionProviderForTest
import org.jetbrains.kotlin.analysis.api.resolve.extensions.KtResolveExtensionFile
import org.jetbrains.kotlin.analysis.api.resolve.extensions.KtResolveExtensionProvider
@@ -12,6 +13,12 @@ import org.jetbrains.kotlin.analysis.project.structure.KtSourceModule
import org.jetbrains.kotlin.name.FqName
abstract class AbstractMultiModuleReferenceResolveWithResolveExtensionTest : AbstractReferenceResolveWithResolveExtensionTest() {
override fun createResolveExtensionProvider(files: List<KtResolveExtensionFile>, packages: Set<FqName>): KtResolveExtensionProvider =
KtMultiModuleResolveExtensionProviderForTest(files, packages) { it is KtSourceModule && it.moduleName == "extendedModule" }
override fun createResolveExtensionProvider(
files: List<KtResolveExtensionFile>,
packages: Set<FqName>,
shadowedScope: GlobalSearchScope,
): KtResolveExtensionProvider =
KtMultiModuleResolveExtensionProviderForTest(files, packages, shadowedScope) { module ->
module is KtSourceModule && module.moduleName == "extendedModule"
}
}
@@ -5,6 +5,9 @@
package org.jetbrains.kotlin.analysis.api.impl.base.test.cases.references
import com.intellij.openapi.module.Module
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.api.impl.base.test.util.KtResolveExtensionFileForTests
import org.jetbrains.kotlin.analysis.api.impl.base.test.util.KtResolveExtensionProviderForTestPreAnalysisHandler
import org.jetbrains.kotlin.analysis.api.resolve.extensions.KtResolveExtensionFile
@@ -17,17 +20,22 @@ abstract class AbstractReferenceResolveWithResolveExtensionTest : AbstractRefere
abstract fun createResolveExtensionProvider(
files: List<KtResolveExtensionFile>,
packages: Set<FqName>,
shadowedScope: GlobalSearchScope,
): KtResolveExtensionProvider
override fun configureTest(builder: TestConfigurationBuilder) {
super.configureTest(builder)
val provider = createResolveExtensionProvider(
listOf(
files = listOf(
KtResolveExtensionFileForTests(
"extension1.kt",
packageName = FqName("generated"),
topLevelClassifiersNames = setOf("GeneratedClass1"),
topLevelCallableNames = setOf("generatedTopLevelFunction1", "generatedTopLevelExtensionFunction1"),
topLevelCallableNames = setOf(
"generatedTopLevelFunction1",
"generatedTopLevelExtensionFunction1",
"generatedOverloadedExtensionFunction",
),
fileText = """|package generated
|
|class GeneratedClass1 {
@@ -37,6 +45,8 @@ abstract class AbstractReferenceResolveWithResolveExtensionTest : AbstractRefere
|fun generatedTopLevelFunction1(): GeneratedClass2
|
|fun String.generatedTopLevelExtensionFunction1(boolean: Boolean): Int
|
|fun Any.generatedOverloadedExtensionFunction(): Int
""".trimMargin()
),
KtResolveExtensionFileForTests(
@@ -52,7 +62,14 @@ abstract class AbstractReferenceResolveWithResolveExtensionTest : AbstractRefere
""".trimMargin(),
)
),
setOf(FqName("generated"))
packages = setOf(FqName("generated")),
shadowedScope = object : GlobalSearchScope() {
override fun contains(file: VirtualFile): Boolean = ".hidden." in file.name
override fun isSearchInModuleContent(aModule: Module): Boolean = false
override fun isSearchInLibraries(): Boolean = false
}
)
with(builder) {
usePreAnalysisHandlers(::KtResolveExtensionProviderForTestPreAnalysisHandler.bind(listOf(provider)))
@@ -5,12 +5,17 @@
package org.jetbrains.kotlin.analysis.api.impl.base.test.cases.references
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.api.impl.base.test.util.KtSingleModuleResolveExtensionProviderForTest
import org.jetbrains.kotlin.analysis.api.resolve.extensions.KtResolveExtensionFile
import org.jetbrains.kotlin.analysis.api.resolve.extensions.KtResolveExtensionProvider
import org.jetbrains.kotlin.name.FqName
abstract class AbstractSingleModuleReferenceResolveWithResolveExtensionTest : AbstractReferenceResolveWithResolveExtensionTest() {
override fun createResolveExtensionProvider(files: List<KtResolveExtensionFile>, packages: Set<FqName>): KtResolveExtensionProvider =
KtSingleModuleResolveExtensionProviderForTest(files, packages)
override fun createResolveExtensionProvider(
files: List<KtResolveExtensionFile>,
packages: Set<FqName>,
shadowedScope: GlobalSearchScope,
): KtResolveExtensionProvider =
KtSingleModuleResolveExtensionProviderForTest(files, packages, shadowedScope)
}
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.analysis.api.impl.base.test.util
import com.intellij.mock.MockProject
import com.intellij.openapi.util.ModificationTracker
import com.intellij.psi.PsiElement
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.resolve.extensions.KtResolveExtension
import org.jetbrains.kotlin.analysis.api.resolve.extensions.KtResolveExtensionFile
@@ -25,20 +26,22 @@ import org.jetbrains.kotlin.test.services.TestServices
class KtSingleModuleResolveExtensionProviderForTest(
private val files: List<KtResolveExtensionFile>,
private val packages: Set<FqName>,
private val shadowedScope: GlobalSearchScope,
) : KtResolveExtensionProvider() {
override fun provideExtensionsFor(module: KtModule): List<KtResolveExtension> {
return listOf(KtResolveExtensionForTest(files, packages))
return listOf(KtResolveExtensionForTest(files, packages, shadowedScope))
}
}
class KtMultiModuleResolveExtensionProviderForTest(
private val files: List<KtResolveExtensionFile>,
private val packages: Set<FqName>,
private val shadowedScope: GlobalSearchScope,
private val hasResolveExtension: (KtModule) -> Boolean,
) : KtResolveExtensionProvider() {
override fun provideExtensionsFor(module: KtModule): List<KtResolveExtension> {
if (!hasResolveExtension(module)) return emptyList()
return listOf(KtResolveExtensionForTest(files, packages))
return listOf(KtResolveExtensionForTest(files, packages, shadowedScope))
}
}
@@ -59,10 +62,12 @@ class KtResolveExtensionProviderForTestPreAnalysisHandler(
class KtResolveExtensionForTest(
private val files: List<KtResolveExtensionFile>,
private val packages: Set<FqName>,
private val shadowedScope: GlobalSearchScope,
) : KtResolveExtension() {
override fun getKtFiles(): List<KtResolveExtensionFile> = files
override fun getModificationTracker(): ModificationTracker = ModificationTracker.NEVER_CHANGED
override fun getContainedPackages(): Set<FqName> = packages
override fun getShadowedScope(): GlobalSearchScope = shadowedScope
}
class KtResolveExtensionFileForTests(