[LL] Implement a common LLSealedInheritorsProvider

- The new `LLSealedInheritorsProvider` is based on the previous
  sealed inheritors provider implementation in the IDE. It uses the
  new direct inheritors provider and the module dependents provider to
  implement the same functionality that was previously confined to the
  IDE. With this design we avoid duplication of complex logic such as
  the KMP handling in `searchInheritors`.
- The implementation is designed to work in the production Standalone
  mode and the aforementioned services have already been implemented for
  Standalone in prior commits. Now we can get rid of the problematic
  `SealedClassInheritorsProviderForTests` and tests should more closely
  match production behavior.
- In IDE mode tests, `LLSealedInheritorsProvider` is used with
  Standalone Analysis API provider implementations. This is in line with
  the rest of the test infrastructure, where Standalone AA providers are
  generally used, as IDE providers aren't available.
- `KotlinSealedInheritorsProvider` is made obsolete by the common sealed
  inheritors provider.

^KT-66013 fixed
^KT-64505 fixed
This commit is contained in:
Marco Pennekamp
2024-02-28 21:35:59 +01:00
committed by Space Team
parent 48229f7faa
commit 1374bc8e2d
20 changed files with 123 additions and 296 deletions
@@ -1,36 +0,0 @@
/*
* 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.providers
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtClass
/**
* [KotlinSealedInheritorsProvider] provides a list of direct inheritors for sealed [KtClass]es (which may be a class or an interface). It
* may be created with [KotlinSealedInheritorsProviderFactory].
*
* Implementations of this service should consider caching the results. The [KotlinSealedInheritorsProvider] is tied to the lifetime of its
* owning session, so it will be invalidated automatically with the session.
*/
public interface KotlinSealedInheritorsProvider {
/**
* Returns the sealed inheritors of [ktClass], which may be a class or an interface. [ktClass] is guaranteed to be sealed.
*/
public fun getSealedInheritors(ktClass: KtClass): List<ClassId>
}
public interface KotlinSealedInheritorsProviderFactory {
/**
* Creates a [KotlinSealedInheritorsProvider] for a session.
*/
public fun createSealedInheritorsProvider(): KotlinSealedInheritorsProvider
public companion object {
public fun getInstance(project: Project): KotlinSealedInheritorsProviderFactory? =
project.getService(KotlinSealedInheritorsProviderFactory::class.java)
}
}
@@ -16,7 +16,6 @@ import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.FirSt
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtStaticProjectStructureProvider
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.LLFirStandaloneLibrarySymbolProviderFactory
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.StandaloneProjectFactory
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.services.LLSealedInheritorsProviderFactory
import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.LLFirLibrarySymbolProviderFactory
import org.jetbrains.kotlin.analysis.project.structure.KtSourceModule
import org.jetbrains.kotlin.analysis.project.structure.builder.KtModuleProviderBuilder
@@ -32,8 +31,6 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreProjectEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.setupIdeaStandaloneExecution
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor
import org.jetbrains.kotlin.fir.declarations.SealedClassInheritorsProvider
import org.jetbrains.kotlin.fir.declarations.SealedClassInheritorsProviderImpl
import org.jetbrains.kotlin.load.kotlin.PackagePartProvider
import org.jetbrains.kotlin.psi.KtFile
import kotlin.contracts.ExperimentalContracts
@@ -137,15 +134,6 @@ public class StandaloneAnalysisAPISessionBuilder(
)
registerService(KotlinPackageProviderMerger::class.java, KotlinStaticPackageProviderMerger(this))
registerService(
LLSealedInheritorsProviderFactory::class.java,
object : LLSealedInheritorsProviderFactory {
override fun createSealedInheritorsProvider(): SealedClassInheritorsProvider {
return SealedClassInheritorsProviderImpl
}
}
)
registerService(
PackagePartProviderFactory::class.java,
KotlinStaticPackagePartProviderFactory(packagePartProvider)
@@ -52,8 +52,8 @@ MODULE_FRAGMENT
CLASS CLASS name:Operation modality:SEALED visibility:public superTypes:[kotlin.Any]
sealedSubclasses:
CLASS CLASS name:Add modality:FINAL visibility:public superTypes:[<root>.Operation]
CLASS CLASS name:Subtract modality:FINAL visibility:public superTypes:[<root>.Operation]
CLASS CLASS name:Negate modality:FINAL visibility:public superTypes:[<root>.Operation]
CLASS CLASS name:Subtract modality:FINAL visibility:public superTypes:[<root>.Operation]
$this: VALUE_PARAMETER INSTANCE_RECEIVER name:<this> type:<root>.Operation
CLASS CLASS name:Add modality:FINAL visibility:public superTypes:[<root>.Operation]
$this: VALUE_PARAMETER INSTANCE_RECEIVER name:<this> type:<root>.Operation.Add
@@ -1,7 +1,7 @@
Diagnostics from elements:
for PSI element of type KtConstructorCalleeExpression at (34,2-12)
NO_VALUE_FOR_PARAMETER text ranges: [(115,125)]
PSI: KtConstructorCalleeExpression at (34,2-12)
for PSI element of type KtNameReferenceExpression at (32,8-10)
UNRESOLVED_REFERENCE text ranges: [(94,96)]
PSI: KtNameReferenceExpression at (32,8-10)
for PSI element of type KtConstructorCalleeExpression at (33,2-12)
NO_VALUE_FOR_PARAMETER text ranges: [(114,124)]
PSI: KtConstructorCalleeExpression at (33,2-12)
for PSI element of type KtNameReferenceExpression at (31,8-10)
UNRESOLVED_REFERENCE text ranges: [(93,95)]
PSI: KtNameReferenceExpression at (31,8-10)
@@ -1,5 +1,4 @@
// ALLOW_KOTLIN_PACKAGE
// DISABLE_SEALED_INHERITOR_CALCULATOR
// MODULE: lib1
// FILE: anno.kt
package kotlin.annotation
@@ -1,13 +1,13 @@
Diagnostics from elements:
for PSI element of type KtCallExpression at (24,20) - (27,2)
TYPE_MISMATCH text ranges: [(98,160)]
PSI: KtCallExpression at (24,20) - (27,2)
for PSI element of type KtLambdaExpression at (24,24) - (27,2)
TYPE_MISMATCH text ranges: [(102,160)]
PSI: KtLambdaExpression at (24,24) - (27,2)
for PSI element of type KtNameReferenceExpression at (25,5-17)
UNRESOLVED_REFERENCE text ranges: [(108,120)]
PSI: KtNameReferenceExpression at (25,5-17)
for PSI element of type KtNameReferenceExpression at (26,5-34)
UNRESOLVED_REFERENCE text ranges: [(127,156)]
PSI: KtNameReferenceExpression at (26,5-34)
for PSI element of type KtCallExpression at (23,20) - (26,2)
TYPE_MISMATCH text ranges: [(97,159)]
PSI: KtCallExpression at (23,20) - (26,2)
for PSI element of type KtLambdaExpression at (23,24) - (26,2)
TYPE_MISMATCH text ranges: [(101,159)]
PSI: KtLambdaExpression at (23,24) - (26,2)
for PSI element of type KtNameReferenceExpression at (24,5-17)
UNRESOLVED_REFERENCE text ranges: [(107,119)]
PSI: KtNameReferenceExpression at (24,5-17)
for PSI element of type KtNameReferenceExpression at (25,5-34)
UNRESOLVED_REFERENCE text ranges: [(126,155)]
PSI: KtNameReferenceExpression at (25,5-34)
@@ -1,4 +1,3 @@
// DISABLE_SEALED_INHERITOR_CALCULATOR
// MODULE: lib1
// FILE: dependency.kt
package usage
@@ -1,56 +0,0 @@
/*
* Copyright 2010-2021 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.api.services
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analysis.providers.KotlinSealedInheritorsProvider
import org.jetbrains.kotlin.analysis.providers.KotlinSealedInheritorsProviderFactory
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.SealedClassInheritorsProvider
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtClass
interface LLSealedInheritorsProviderFactory {
/**
* Creates a [SealedClassInheritorsProvider] for a session. A new [SealedClassInheritorsProvider] instance should be created on every
* call.
*/
fun createSealedInheritorsProvider(): SealedClassInheritorsProvider
}
/**
* Creates a [SealedClassInheritorsProvider] for a session.
*
* Requires either [LLSealedInheritorsProviderFactory] or [KotlinSealedInheritorsProviderFactory] to be registered as a project service.
*/
internal fun createSealedInheritorsProvider(project: Project): SealedClassInheritorsProvider =
createFromLLProvider(project)
?: createFromAnalysisApiProvider(project)
?: error(
"""
Expected one of the following services to be registered with $project:
- ${LLSealedInheritorsProviderFactory::class.simpleName}
- ${KotlinSealedInheritorsProviderFactory::class.simpleName}
""".trimIndent()
)
private fun createFromLLProvider(project: Project): SealedClassInheritorsProvider? =
project.getService(LLSealedInheritorsProviderFactory::class.java)?.createSealedInheritorsProvider()
private fun createFromAnalysisApiProvider(project: Project): SealedClassInheritorsProvider? =
KotlinSealedInheritorsProviderFactory.getInstance(project)
?.createSealedInheritorsProvider()
?.let(::LLSealedInheritorsProviderByAnalysisApiProvider)
private class LLSealedInheritorsProviderByAnalysisApiProvider(
private val provider: KotlinSealedInheritorsProvider,
) : SealedClassInheritorsProvider() {
override fun getSealedClassInheritors(firClass: FirRegularClass): List<ClassId> {
val ktClass = firClass.psi as? KtClass ?: return emptyList()
return provider.getSealedInheritors(ktClass)
}
}
@@ -8,11 +8,11 @@ package org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.analysis.api.resolve.extensions.KtResolveExtensionProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.services.createSealedInheritorsProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.compile.CodeFragmentScopeProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.fir.caches.FirThreadSafeCachesFactory
import org.jetbrains.kotlin.analysis.low.level.api.fir.providers.LLFirIdePredicateBasedProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.providers.LLFirIdeRegisteredPluginAnnotations
import org.jetbrains.kotlin.analysis.low.level.api.fir.providers.LLSealedInheritorsProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.resolve.extensions.LLFirNonEmptyResolveExtensionTool
import org.jetbrains.kotlin.analysis.low.level.api.fir.resolve.extensions.LLFirResolveExtensionTool
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirSession
@@ -37,7 +37,7 @@ import org.jetbrains.kotlin.fir.session.FirSessionConfigurator
@SessionConfiguration
internal fun LLFirSession.registerIdeComponents(project: Project) {
register(FirCachesFactory::class, FirThreadSafeCachesFactory)
register(SealedClassInheritorsProvider::class, createSealedInheritorsProvider(project))
register(SealedClassInheritorsProvider::class, LLSealedInheritorsProvider(project))
register(FirExceptionHandler::class, LLFirExceptionHandler)
register(CodeFragmentScopeProvider::class, CodeFragmentScopeProvider(this))
registerResolveExtensionTool()
@@ -0,0 +1,99 @@
/*
* 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.providers
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.llFirModuleData
import org.jetbrains.kotlin.analysis.project.structure.KotlinModuleDependentsProvider
import org.jetbrains.kotlin.analysis.project.structure.KtDanglingFileModule
import org.jetbrains.kotlin.analysis.providers.KotlinDirectInheritorsProvider
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.SealedClassInheritorsProvider
import org.jetbrains.kotlin.fir.declarations.SealedClassInheritorsProviderInternals
import org.jetbrains.kotlin.fir.declarations.sealedInheritorsAttr
import org.jetbrains.kotlin.fir.declarations.utils.classId
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtClass
import java.util.concurrent.ConcurrentHashMap
import kotlin.collections.filter
import kotlin.collections.map
import kotlin.collections.mapNotNull
import kotlin.collections.plus
import kotlin.collections.sortedBy
import kotlin.let
/**
* [LLSealedInheritorsProvider] is the LL FIR implementation of [SealedClassInheritorsProvider] for both the IDE and Standalone mode.
*/
@OptIn(SealedClassInheritorsProviderInternals::class)
internal class LLSealedInheritorsProvider(private val project: Project) : SealedClassInheritorsProvider() {
val cache = ConcurrentHashMap<ClassId, List<ClassId>>()
override fun getSealedClassInheritors(firClass: FirRegularClass): List<ClassId> {
// Classes from binary libraries which are deserialized from class files (but not stubs) will have their `sealedInheritorsAttr` set
// from metadata.
firClass.sealedInheritorsAttr?.let { return it.value }
val classId = firClass.classId
// Local classes cannot be sealed.
if (classId.isLocal) {
return emptyList()
}
return cache.computeIfAbsent(classId) { searchInheritors(firClass) }
}
/**
* Some notes about the search:
*
* - A Java class cannot legally extend a sealed Kotlin class (even in the same package), so we don't need to search for Java class
* inheritors.
* - Technically, we could use a package scope to narrow the search, but the search is already sufficiently narrow because it uses
* supertype indices and is confined to the current `KtModule` in most cases (except for 'expect' classes). Finding a `PsiPackage`
* for a `PackageScope` is not cheap, hence the decision to avoid it. If a `PackageScope` is needed in the future, it'd be best to
* extract a `PackageNameScope` which operates just with the qualified package name, to avoid `PsiPackage`. (At the time of writing,
* this is possible with the implementation of `PackageScope`.)
* - We ignore local classes to avoid lazy resolve contract violations.
* See KT-63795.
* - For `expect` declarations, the search scope includes all modules with a dependsOn dependency on the containing module.
* At the same time, `actual` declarations are restricted to the same module and require no special handling.
* See KT-45842.
* - KMP libraries are not yet supported.
* See KT-65591.
*/
private fun searchInheritors(firClass: FirClass): List<ClassId> {
val ktClass = firClass.psi as? KtClass ?: return emptyList()
val ktModule = when (val classKtModule = firClass.llFirModuleData.ktModule) {
is KtDanglingFileModule -> classKtModule.contextModule
else -> classKtModule
}
// `FirClass.isExpect` does not depend on the `STATUS` phase because it's already set during FIR building.
val scope = if (firClass.isExpect) {
val refinementDependents = KotlinModuleDependentsProvider.getInstance(project).getRefinementDependents(ktModule)
GlobalSearchScope.union(refinementDependents.map { it.contentScope } + ktModule.contentScope)
} else {
ktModule.contentScope
}
return searchInScope(ktClass, firClass.classId, scope)
}
private fun searchInScope(ktClass: KtClass, classId: ClassId, scope: GlobalSearchScope): List<ClassId> =
KotlinDirectInheritorsProvider.getInstance(project)
.getDirectKotlinInheritors(ktClass, scope, includeLocalInheritors = false)
.mapNotNull { it.getClassId() }
.filter { it.packageFqName == classId.packageFqName }
// Enforce a deterministic order on the result, e.g. for stable test output.
.sortedBy { it.toString() }
.ifEmpty { emptyList() }
}
@@ -21,11 +21,11 @@ internal object LLFirSealedClassInheritorsLazyResolver : LLFirLazyResolver(FirRe
/**
* This resolver is responsible for [SEALED_CLASS_INHERITORS][FirResolvePhase.SEALED_CLASS_INHERITORS] phase.
*
* LL FIR use [LLSealedInheritorsProviderByAnalysisApiProvider][org.jetbrains.kotlin.analysis.low.level.api.fir.api.services.LLSealedInheritorsProviderByAnalysisApiProvider]
* LL FIR uses [LLSealedInheritorsProvider][org.jetbrains.kotlin.analysis.low.level.api.fir.providers.LLSealedInheritorsProvider]
* instead of the compiler [SealedClassInheritorsProviderImpl][org.jetbrains.kotlin.fir.declarations.SealedClassInheritorsProviderImpl],
* so it does nothing during this phase as sealed class hierarchy will be provided later on demand.
* so it does nothing during this phase as sealed inheritors will be provided later on demand.
*
* @see org.jetbrains.kotlin.analysis.low.level.api.fir.api.services.LLSealedInheritorsProviderFactory
* @see org.jetbrains.kotlin.analysis.low.level.api.fir.providers.LLSealedInheritorsProvider
* @see org.jetbrains.kotlin.fir.declarations.SealedClassInheritorsProvider
* @see FirResolvePhase.SEALED_CLASS_INHERITORS
*/
@@ -1,5 +1,4 @@
// SKIP_WHEN_OUT_OF_CONTENT_ROOT
// DISABLE_SEALED_INHERITOR_CALCULATOR
// FILE: main.kt
fun resol<caret>veMe(i: JavaInterface) = i.id
@@ -1,5 +1,4 @@
// SKIP_WHEN_OUT_OF_CONTENT_ROOT
// DISABLE_SEALED_INHERITOR_CALCULATOR
// FILE: main.kt
fun reso<caret>lveMe(i: JavaInterface) = i.id
@@ -1,5 +1,4 @@
// SKIP_WHEN_OUT_OF_CONTENT_ROOT
// DISABLE_SEALED_INHERITOR_CALCULATOR
// MODULE: lib1
// FILE: dependency.kt
package usage
@@ -52,8 +52,6 @@ abstract class AbstractLowLevelCompilerBasedTest : AbstractCompilerBasedTest() {
configureTest(this)
defaultConfiguration(this)
registerAnalysisApiBaseTestServices(disposable, FirLowLevelCompilerBasedTestConfigurator)
useDirectives(SealedClassesInheritorsCalculatorPreAnalysisHandler.Directives)
usePreAnalysisHandlers(::SealedClassesInheritorsCalculatorPreAnalysisHandler)
useAdditionalServices(service<FirDiagnosticCollectorService>(::AnalysisApiFirDiagnosticCollectorService))
firHandlersStep {
@@ -1,105 +0,0 @@
/*
* 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.compiler.based
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.services.LLSealedInheritorsProviderFactory
import org.jetbrains.kotlin.analysis.low.level.api.fir.services.LLSealedInheritorsProviderFactoryForTests
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirSessionCache
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.project.structure.ProjectStructureProvider
import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProviderFactory
import org.jetbrains.kotlin.analysis.providers.impl.KotlinStaticDeclarationProviderFactory
import org.jetbrains.kotlin.analysis.test.framework.project.structure.ktModuleProvider
import org.jetbrains.kotlin.analysis.test.framework.project.structure.mainModules
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.resolve.transformers.FirSealedClassInheritorsProcessor
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhaseRecursively
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.test.directives.model.DirectiveApplicability
import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer
import org.jetbrains.kotlin.test.services.PreAnalysisHandler
import org.jetbrains.kotlin.test.services.TestModuleStructure
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.compilerConfigurationProvider
class SealedClassesInheritorsCalculatorPreAnalysisHandler(
testServices: TestServices,
) : PreAnalysisHandler(testServices) {
override fun preprocessModuleStructure(moduleStructure: TestModuleStructure) {
}
// Manually process all inheritors of sealed classes so that SealedClassInheritorsProviderTestImpl can work correctly for tests.
// In the actual IDE, SealedClassInheritorsProviderIdeImpl works by finding inheritors from the index instead of doing a
// preprocessing of all files. Therefore, the IDE does not rely on such a pre-analysis pass of all files in the module.
override fun prepareSealedClassInheritors(moduleStructure: TestModuleStructure) {
if (Directives.DISABLE_SEALED_INHERITOR_CALCULATOR in moduleStructure.allDirectives) {
return
}
val project = testServices.compilerConfigurationProvider.getProject(moduleStructure.modules.first())
val sealedInheritorsProviderFactory =
project.getService(LLSealedInheritorsProviderFactory::class.java) as? LLSealedInheritorsProviderFactoryForTests
?: return
val declarationProviderFactory = KotlinDeclarationProviderFactory.getInstance(project) as KotlinStaticDeclarationProviderFactory
val projectStructureProvider = project.getService(ProjectStructureProvider::class.java)
val allKtClasses = declarationProviderFactory.getAllKtClasses()
val ktClassesByKtModule = allKtClasses.groupBy { projectStructureProvider.getModule(it, contextualModule = null) }
for (ktTestModule in testServices.ktModuleProvider.mainModules) {
val ktClasses = ktClassesByKtModule[ktTestModule.ktModule] ?: continue
val sealedInheritors = collectSealedInheritorClassIds(ktTestModule.ktModule, ktClasses, project)
sealedInheritorsProviderFactory.registerInheritors(ktTestModule.ktModule, sealedInheritors)
}
// We request and cache sessions while collecting sealed inheritors. To not interfere with the test, we should invalidate all
// sessions. Note that, while we could get uncached sessions from `LLFirSessionCache`, dependency sessions will still be cached, so
// that is not really an alternative to invalidating sessions.
LLFirSessionCache.getInstance(project).removeAllSessions(includeLibraryModules = true)
}
private fun collectSealedInheritorClassIds(
ktModule: KtModule,
ktClasses: List<KtClassOrObject>,
project: Project,
): Map<ClassId, List<ClassId>> {
val sealedInheritorsByFirClass = mutableMapOf<FirRegularClass, MutableList<ClassId>>()
ktClasses.forEach { ktClass ->
val classId = ktClass.getClassId() ?: return@forEach
// Using a resolve session/source-preferred session will cause class stubs from binary libraries to be decompiled (which
// results in an exception since we don't have a decompiler for them).
val firSession = LLFirSessionCache.getInstance(project).getSession(ktModule, preferBinary = true)
val firClassSymbol = firSession.symbolProvider.getClassLikeSymbolByClassId(classId) ?: return@forEach
firClassSymbol.lazyResolveToPhaseRecursively(FirResolvePhase.TYPES)
val inheritorsCollector = FirSealedClassInheritorsProcessor.InheritorsCollector(firSession)
firClassSymbol.fir.accept(inheritorsCollector, sealedInheritorsByFirClass)
}
return buildMap {
sealedInheritorsByFirClass.forEach { firClass, classIds ->
put(firClass.symbol.classId, classIds.distinct())
}
}
}
object Directives : SimpleDirectivesContainer() {
val DISABLE_SEALED_INHERITOR_CALCULATOR by directive(
description = "Disable mock sealed class inheritor calculation",
applicability = DirectiveApplicability.Global
)
}
}
@@ -1,47 +0,0 @@
/*
* Copyright 2010-2022 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.services
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.services.LLSealedInheritorsProviderFactory
import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.llFirModuleData
import org.jetbrains.kotlin.analysis.project.structure.KtDanglingFileModule
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.SealedClassInheritorsProvider
import org.jetbrains.kotlin.fir.declarations.SealedClassInheritorsProviderInternals
import org.jetbrains.kotlin.fir.declarations.sealedInheritorsAttr
import org.jetbrains.kotlin.fir.declarations.utils.classId
import org.jetbrains.kotlin.name.ClassId
internal class LLSealedInheritorsProviderFactoryForTests : LLSealedInheritorsProviderFactory {
private val inheritorsByModule = mutableMapOf<KtModule, Map<ClassId, List<ClassId>>>()
fun registerInheritors(ktModule: KtModule, inheritors: Map<ClassId, List<ClassId>>) {
inheritorsByModule[ktModule] = inheritors
}
override fun createSealedInheritorsProvider(): SealedClassInheritorsProvider {
return SealedClassInheritorsProviderForTests(inheritorsByModule)
}
}
private class SealedClassInheritorsProviderForTests(
private val inheritorsByModule: Map<KtModule, Map<ClassId, List<ClassId>>>
) : SealedClassInheritorsProvider() {
@OptIn(SealedClassInheritorsProviderInternals::class)
override fun getSealedClassInheritors(firClass: FirRegularClass): List<ClassId> {
val ktModule = firClass.llFirModuleData.ktModule
val relevantModule = when (ktModule) {
is KtDanglingFileModule -> ktModule.contextModule
else -> ktModule
}
// If the module is absent in the map, it's either a binary library that wasn't stub-indexed (e.g. in Standalone mode) or the module
// doesn't have any `.kt` files, in which case there cannot be sealed inheritors. `sealedInheritorsAttr` covers the binary library
// case, as class-based deserialization sets this attribute.
return inheritorsByModule[relevantModule]?.get(firClass.classId) ?: firClass.sealedInheritorsAttr?.value ?: emptyList()
}
}
@@ -8,8 +8,6 @@ package org.jetbrains.kotlin.analysis.low.level.api.fir.test.base
import com.intellij.mock.MockApplication
import com.intellij.mock.MockProject
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.FirStandaloneServiceRegistrar
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.services.LLSealedInheritorsProviderFactory
import org.jetbrains.kotlin.analysis.low.level.api.fir.services.LLSealedInheritorsProviderFactoryForTests
import org.jetbrains.kotlin.analysis.low.level.api.fir.services.NoOpKtCompilerPluginsProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.services.PackagePartProviderTestImpl
import org.jetbrains.kotlin.analysis.project.structure.KtCompilerPluginsProvider
@@ -30,7 +28,6 @@ object AnalysisApiFirTestServiceRegistrar : AnalysisApiTestServiceRegistrar() {
FirStandaloneServiceRegistrar.registerProjectServices(project)
project.apply {
registerService(LLSealedInheritorsProviderFactory::class.java, LLSealedInheritorsProviderFactoryForTests())
registerService(PackagePartProviderFactory::class.java, PackagePartProviderTestImpl(testServices))
registerService(KtCompilerPluginsProvider::class.java, NoOpKtCompilerPluginsProvider)
}
@@ -10,7 +10,6 @@ import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analysis.api.impl.base.test.configurators.AnalysisApiBaseTestServiceRegistrar
import org.jetbrains.kotlin.analysis.api.impl.base.test.configurators.AnalysisApiDecompiledCodeTestServiceRegistrar
import org.jetbrains.kotlin.analysis.api.impl.base.test.configurators.AnalysisApiLibraryBaseTestServiceRegistrar
import org.jetbrains.kotlin.analysis.low.level.api.fir.compiler.based.SealedClassesInheritorsCalculatorPreAnalysisHandler
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.base.AnalysisApiFirTestServiceRegistrar
import org.jetbrains.kotlin.analysis.test.framework.project.structure.KtLibrarySourceTestModuleFactory
import org.jetbrains.kotlin.analysis.test.framework.project.structure.KtTestModuleFactory
@@ -40,8 +39,6 @@ object AnalysisApiFirLibrarySourceTestConfigurator : AnalysisApiTestConfigurator
useAdditionalService<KtTestModuleFactory> { KtLibrarySourceTestModuleFactory }
useAdditionalService<TestModuleCompiler> { DispatchingTestModuleCompiler() }
useAdditionalService { AnalysisApiIndexingConfiguration(AnalysisApiBinaryLibraryIndexingMode.INDEX_STUBS) }
useDirectives(SealedClassesInheritorsCalculatorPreAnalysisHandler.Directives)
usePreAnalysisHandlers(::SealedClassesInheritorsCalculatorPreAnalysisHandler)
useConfigurators(
::AnalysisApiJvmEnvironmentConfigurator,
::JsEnvironmentConfigurator,
@@ -9,7 +9,6 @@ import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analysis.api.impl.base.test.configurators.AnalysisApiBaseTestServiceRegistrar
import org.jetbrains.kotlin.analysis.api.impl.base.test.configurators.AnalysisApiDecompiledCodeTestServiceRegistrar
import org.jetbrains.kotlin.analysis.low.level.api.fir.compiler.based.SealedClassesInheritorsCalculatorPreAnalysisHandler
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.base.AnalysisApiFirTestServiceRegistrar
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.base.configureOptionalTestCompilerPlugin
import org.jetbrains.kotlin.analysis.test.framework.project.structure.KtTestModuleProjectStructure
@@ -34,8 +33,6 @@ abstract class AnalysisApiFirSourceLikeTestConfigurator(override val analyseInDe
override fun configureTest(builder: TestConfigurationBuilder, disposable: Disposable) {
builder.apply {
useAdditionalService { AnalysisApiIndexingConfiguration(AnalysisApiBinaryLibraryIndexingMode.INDEX_STUBS) }
useDirectives(SealedClassesInheritorsCalculatorPreAnalysisHandler.Directives)
usePreAnalysisHandlers(::SealedClassesInheritorsCalculatorPreAnalysisHandler)
configureOptionalTestCompilerPlugin()
useConfigurators(
::CommonEnvironmentConfigurator,