[AA] Add abstraction for composable Kotlin providers

This further improves the `KotlinCompositeProvider` abstraction:

- Pulling the abstraction's interfaces outside the `impl` package allows
  us to write consolidated documentation on composable Kotlin providers.
- The addition of `KotlinComposableProvider` allows more specific bounds
  for the type parameters of `KotlinCompositeProvider` and
  `KotlinCompositeProviderFactory`. It also clarifies to Analysis API
  implementors which providers can be composed at all, as providers like
  `KotlinDeclarationProvider` extend this interface.
- `KotlinComposableProviderMerger` provides a unified interface for
  provider mergers.

^KT-61791
This commit is contained in:
Marco Pennekamp
2023-10-11 19:02:00 +02:00
committed by Space Team
parent c289da6cf3
commit a62ac940c4
8 changed files with 60 additions and 41 deletions
@@ -0,0 +1,38 @@
/*
* Copyright 2010-2023 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
/**
* A marker interface for a provider that can be composed, i.e. multiple instances of the same provider can be composed into a single
* provider.
*
* Composable providers share certain traits: There is a notion of a sequentially composed [KotlinCompositeProvider] of that kind, and there
* is usually a merge function which allows to create a single provider from a list of providers. Mergers of composable providers may
* produce a merged provider which is more efficient than the naive sequential composite provider.
*
* @see KotlinDeclarationProvider
* @see KotlinPackageProvider
*/
public interface KotlinComposableProvider
/**
* A [KotlinCompositeProvider] is the sequential composition of a specific kind of composable provider [P].
*
* A composite provider should only contain providers of the same base type as the composite provider itself, so implementations of
* [KotlinCompositeProvider] should always be a subtype of their type argument [P]. (This is not enforceable in the Kotlin type system.)
*/
public interface KotlinCompositeProvider<P : KotlinComposableProvider> : KotlinComposableProvider {
public val providers: List<P>
}
public interface KotlinComposableProviderMerger<P : KotlinComposableProvider> {
/**
* Merges the given [providers] into a single provider. When possible, mergers will try to create a provider that is more efficient
* compared to the naive sequential composite provider. Not all providers might be mergeable, or there might be multiple separate sets
* of providers that can be merged individually, so the resulting provider may be a composite provider.
*/
public fun merge(providers: List<P>): P
}
@@ -15,10 +15,11 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
/**
* A declaration provider for a given scope. Can be created via [KotlinDeclarationProviderFactory].
* May be called frequently, so for implementations it is better to cache results.
* A declaration provider for a given scope. It can be created via [KotlinDeclarationProviderFactory].
*
* It may be called frequently, so implementations should cache the results.
*/
public abstract class KotlinDeclarationProvider {
public abstract class KotlinDeclarationProvider : KotlinComposableProvider {
public abstract fun getClassLikeDeclarationByClassId(classId: ClassId): KtClassLikeDeclaration?
public abstract fun getAllClassesByClassId(classId: ClassId): Collection<KtClassOrObject>
@@ -66,15 +67,7 @@ public abstract class KotlinDeclarationProviderFactory {
* [createDeclarationProvider]. [KotlinDeclarationProviderMerger] should implement proper merging logic that takes these concerns into
* account.
*/
public abstract class KotlinDeclarationProviderMerger {
/**
* Merges [declarationProviders] if possible, creating a combined declaration provider that should be more efficient compared to calling
* separate declaration providers. Not all given declaration providers might be mergeable, or there might be multiple separate sets of
* declaration providers which can be merged individually, so the resulting declaration provider may be a composite Kotlin declaration
* provider.
*/
public abstract fun mergeDeclarationProviders(declarationProviders: List<KotlinDeclarationProvider>): KotlinDeclarationProvider
public abstract class KotlinDeclarationProviderMerger : KotlinComposableProviderMerger<KotlinDeclarationProvider> {
public companion object {
public fun getInstance(project: Project): KotlinDeclarationProviderMerger =
project.getService(KotlinDeclarationProviderMerger::class.java)
@@ -85,4 +78,4 @@ public fun Project.createDeclarationProvider(scope: GlobalSearchScope, module: K
KotlinDeclarationProviderFactory.getInstance(this).createDeclarationProvider(scope, module)
public fun Project.mergeDeclarationProviders(declarationProviders: List<KotlinDeclarationProvider>): KotlinDeclarationProvider =
KotlinDeclarationProviderMerger.getInstance(this).mergeDeclarationProviders(declarationProviders)
KotlinDeclarationProviderMerger.getInstance(this).merge(declarationProviders)
@@ -15,7 +15,7 @@ import org.jetbrains.kotlin.platform.TargetPlatform
* Provides information about packages that are visible to Kotlin in the given scope. Can be constructed via [KotlinPackageProviderFactory].
* The FIR compiler calls [doesKotlinOnlyPackageExist] very often, so the implementations should consider caching the results.
*/
public abstract class KotlinPackageProvider {
public abstract class KotlinPackageProvider : KotlinComposableProvider {
/**
* Checks if a package with given [FqName] exists in current [GlobalSearchScope] with a view from a given [platform].
*
@@ -82,13 +82,7 @@ public abstract class KotlinPackageProviderFactory {
* Package providers should not be naively merged by combining scopes and calling [createPackageProvider], because there may be additional
* package providers which do not operate based on scopes (e.g. resolve extension package providers).
*/
public abstract class KotlinPackageProviderMerger {
/**
* Merges [packageProviders] if possible, creating a combined package provider that should be more efficient compared to invoking
* separate package providers.
*/
public abstract fun mergePackageProviders(packageProviders: List<KotlinPackageProvider>): KotlinPackageProvider
public abstract class KotlinPackageProviderMerger : KotlinComposableProviderMerger<KotlinPackageProvider> {
public companion object {
public fun getInstance(project: Project): KotlinPackageProviderMerger = project.getService(KotlinPackageProviderMerger::class.java)
}
@@ -99,4 +93,4 @@ public fun Project.createPackageProvider(searchScope: GlobalSearchScope): Kotlin
.createPackageProvider(searchScope)
public fun Project.mergePackageProviders(packageProviders: List<KotlinPackageProvider>): KotlinPackageProvider =
KotlinPackageProviderMerger.getInstance(this).mergePackageProviders(packageProviders)
KotlinPackageProviderMerger.getInstance(this).merge(packageProviders)
@@ -5,20 +5,14 @@
package org.jetbrains.kotlin.analysis.providers.impl
import org.jetbrains.kotlin.analysis.providers.KotlinComposableProvider
import org.jetbrains.kotlin.analysis.providers.KotlinCompositeProvider
import org.jetbrains.kotlin.analysis.providers.impl.util.mergeOnly
/**
* A composite provider should only contain providers of the same base type as the composite provider itself, so implementations of
* [KotlinCompositeProvider] should always be a subtype of their type argument [P].
* [KotlinCompositeProviderFactory] is used by various [KotlinCompositeProvider]s to share code related to provider creation and flattening.
*/
public interface KotlinCompositeProvider<P> {
public val providers: List<P>
}
/**
* [KotlinCompositeProviderFactory] is used by various composite providers to share code related to provider creation and flattening.
*/
public class KotlinCompositeProviderFactory<P>(
public class KotlinCompositeProviderFactory<P : KotlinComposableProvider>(
private val emptyProvider: P,
private val composeProviders: (List<P>) -> P,
) {
@@ -48,7 +42,7 @@ public class KotlinCompositeProviderFactory<P>(
* Uses the given [factory] to merge all providers of type [T] with the given [mergeTargets] strategy. Other providers (not of type [T]) are
* added to the resulting composite provider unmerged.
*/
public inline fun <P : Any, reified T : P> List<P>.mergeSpecificProviders(
public inline fun <P : KotlinComposableProvider, reified T : P> List<P>.mergeSpecificProviders(
factory: KotlinCompositeProviderFactory<P>,
crossinline mergeTargets: (List<T>) -> P,
): P {
@@ -375,9 +375,9 @@ public class KotlinFakeClsStubsCache {
}
public class KotlinStaticDeclarationProviderMerger(private val project: Project) : KotlinDeclarationProviderMerger() {
override fun mergeDeclarationProviders(declarationProviders: List<KotlinDeclarationProvider>): KotlinDeclarationProvider =
declarationProviders.mergeSpecificProviders<_, KotlinStaticDeclarationProvider>(CompositeKotlinDeclarationProvider.factory) { providers ->
val combinedScope = GlobalSearchScope.union(providers.map { it.scope })
override fun merge(providers: List<KotlinDeclarationProvider>): KotlinDeclarationProvider =
providers.mergeSpecificProviders<_, KotlinStaticDeclarationProvider>(CompositeKotlinDeclarationProvider.factory) { targetProviders ->
val combinedScope = GlobalSearchScope.union(targetProviders.map { it.scope })
project.createDeclarationProvider(combinedScope, module = null).apply {
check(this is KotlinStaticDeclarationProvider) {
"`KotlinStaticDeclarationProvider` can only be merged into a combined declaration provider of the same type."
@@ -54,9 +54,9 @@ public class KotlinStaticPackageProviderFactory(
}
public class KotlinStaticPackageProviderMerger(private val project: Project) : KotlinPackageProviderMerger() {
override fun mergePackageProviders(packageProviders: List<KotlinPackageProvider>): KotlinPackageProvider =
packageProviders.mergeSpecificProviders<_, KotlinStaticPackageProvider>(CompositeKotlinPackageProvider.factory) { providers ->
val combinedScope = GlobalSearchScope.union(providers.map { it.scope })
override fun merge(providers: List<KotlinPackageProvider>): KotlinPackageProvider =
providers.mergeSpecificProviders<_, KotlinStaticPackageProvider>(CompositeKotlinPackageProvider.factory) { targetProviders ->
val combinedScope = GlobalSearchScope.union(targetProviders.map { it.scope })
project.createPackageProvider(combinedScope).apply {
check(this is KotlinStaticPackageProvider) {
"`${KotlinStaticPackageProvider::class.simpleName}` can only be merged into a combined package provider of the same type."
@@ -5,8 +5,8 @@
package org.jetbrains.kotlin.analysis.providers.impl.declarationProviders
import org.jetbrains.kotlin.analysis.providers.KotlinCompositeProvider
import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProvider
import org.jetbrains.kotlin.analysis.providers.impl.KotlinCompositeProvider
import org.jetbrains.kotlin.analysis.providers.impl.KotlinCompositeProviderFactory
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
@@ -5,8 +5,8 @@
package org.jetbrains.kotlin.analysis.providers.impl.packageProviders
import org.jetbrains.kotlin.analysis.providers.KotlinCompositeProvider
import org.jetbrains.kotlin.analysis.providers.KotlinPackageProvider
import org.jetbrains.kotlin.analysis.providers.impl.KotlinCompositeProvider
import org.jetbrains.kotlin.analysis.providers.impl.KotlinCompositeProviderFactory
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name