[AA] Implement disposable KtResolveExtensions
- This commit adds the `Disposable` interface to `KtResolveExtension`. Resolve extensions are disposed after their associated session has been invalidated, or reclaimed by the GC. - Example use case: Opening a message bus connection with the lifetime of the resolve extension. ^KT-61222 fixed
This commit is contained in:
committed by
Space Team
parent
efb56ed30a
commit
7a5a1833e7
+28
-5
@@ -5,23 +5,43 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.analysis.api.resolve.extensions
|
package org.jetbrains.kotlin.analysis.api.resolve.extensions
|
||||||
|
|
||||||
|
import com.intellij.openapi.Disposable
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a list of Kotlin files which provides additional, generated declarations for resolution.
|
* Provides a list of Kotlin files which contain additional generated declarations for resolution.
|
||||||
*
|
*
|
||||||
* Provided by the [KtResolveExtensionProvider].
|
* It is created by [KtResolveExtensionProvider].
|
||||||
*
|
*
|
||||||
* All member implementations should:
|
* All member implementations should:
|
||||||
* - consider caching the results for subsequent invocations.
|
* - consider caching the results for subsequent invocations.
|
||||||
* - be lightweight and not build the whole file structure inside.
|
* - be lightweight and not build the whole file structure inside.
|
||||||
* - not use the Kotlin resolve inside, as this function is called during session initialization, so Analysis API access is forbidden.
|
* - not use Kotlin resolve inside, as these functions are called during session initialization, so Analysis API access is forbidden.
|
||||||
|
*
|
||||||
|
* #### Lifecycle Management
|
||||||
|
*
|
||||||
|
* A [KtResolveExtension] is tied to the lifetime of its module's analysis session. It is created by [KtResolveExtensionProvider] during
|
||||||
|
* creation of an analysis session, and disposed after the analysis session has been invalidated.
|
||||||
|
*
|
||||||
|
* [KtResolveExtension] implements the [Disposable] interface. The resolve extension can then act as a parent disposable, e.g. for a message
|
||||||
|
* bus connection.
|
||||||
|
*
|
||||||
|
* You *must not* implement [KtResolveExtension]s as module-level services, due to the following reasons:
|
||||||
|
*
|
||||||
|
* 1. The IntelliJ platform SDK [discourages](https://plugins.jetbrains.com/docs/intellij/plugin-services.html#types) the use of
|
||||||
|
* module-level services due to memory consumption. In particular, resolve extensions implemented as module-level services live longer
|
||||||
|
* than their corresponding analysis session, so the resolve extension would not be garbage collected after its corresponding analysis
|
||||||
|
* session has been invalidated.
|
||||||
|
* 2. The module-level service living longer than the analysis session increases the risk of caching invalidated entities in a resolve
|
||||||
|
* extension.
|
||||||
|
* 3. Because the [KtResolveExtension] is a [Disposable], if implemented as a module-level service, the service would be disposed too early
|
||||||
|
* during invalidation of the corresponding analysis session.
|
||||||
*
|
*
|
||||||
* @see KtResolveExtensionFile
|
* @see KtResolveExtensionFile
|
||||||
* @see KtResolveExtensionProvider
|
* @see KtResolveExtensionProvider
|
||||||
*/
|
*/
|
||||||
public abstract class KtResolveExtension {
|
public abstract class KtResolveExtension : Disposable {
|
||||||
/**
|
/**
|
||||||
* Get the list of files that should be generated for the module. Returned files should contain valid Kotlin code.
|
* Get the list of files that should be generated for the module. Returned files should contain valid Kotlin code.
|
||||||
*
|
*
|
||||||
@@ -64,4 +84,7 @@ public abstract class KtResolveExtension {
|
|||||||
* (potentially stale) externally generated sources.
|
* (potentially stale) externally generated sources.
|
||||||
*/
|
*/
|
||||||
public open fun getShadowedScope(): GlobalSearchScope = GlobalSearchScope.EMPTY_SCOPE
|
public open fun getShadowedScope(): GlobalSearchScope = GlobalSearchScope.EMPTY_SCOPE
|
||||||
}
|
|
||||||
|
override fun dispose() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+12
-3
@@ -6,6 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure
|
package org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure
|
||||||
|
|
||||||
import com.intellij.openapi.project.Project
|
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.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.api.services.createSealedInheritorsProvider
|
||||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.compile.CodeFragmentScopeProvider
|
import org.jetbrains.kotlin.analysis.low.level.api.fir.compile.CodeFragmentScopeProvider
|
||||||
@@ -39,9 +40,17 @@ internal fun LLFirSession.registerIdeComponents(project: Project) {
|
|||||||
register(SealedClassInheritorsProvider::class, createSealedInheritorsProvider(project))
|
register(SealedClassInheritorsProvider::class, createSealedInheritorsProvider(project))
|
||||||
register(FirExceptionHandler::class, LLFirExceptionHandler)
|
register(FirExceptionHandler::class, LLFirExceptionHandler)
|
||||||
register(CodeFragmentScopeProvider::class, CodeFragmentScopeProvider(this))
|
register(CodeFragmentScopeProvider::class, CodeFragmentScopeProvider(this))
|
||||||
createResolveExtensionTool()?.let {
|
registerResolveExtensionTool()
|
||||||
register(LLFirResolveExtensionTool::class, it)
|
}
|
||||||
}
|
|
||||||
|
@SessionConfiguration
|
||||||
|
private fun LLFirSession.registerResolveExtensionTool() {
|
||||||
|
val resolveExtensionTool = createResolveExtensionTool() ?: return
|
||||||
|
|
||||||
|
// `KtResolveExtension`s are disposables meant to be tied to the lifetime of the `LLFirSession`.
|
||||||
|
resolveExtensionTool.extensions.forEach { Disposer.register(requestDisposable(), it) }
|
||||||
|
|
||||||
|
register(LLFirResolveExtensionTool::class, resolveExtensionTool)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun LLFirSession.createResolveExtensionTool(): LLFirResolveExtensionTool? {
|
private fun LLFirSession.createResolveExtensionTool(): LLFirResolveExtensionTool? {
|
||||||
|
|||||||
+3
-1
@@ -37,6 +37,8 @@ import java.util.concurrent.ConcurrentHashMap
|
|||||||
* for the [org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProvider].
|
* for the [org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProvider].
|
||||||
*/
|
*/
|
||||||
abstract class LLFirResolveExtensionTool : FirSessionComponent {
|
abstract class LLFirResolveExtensionTool : FirSessionComponent {
|
||||||
|
internal abstract val extensions: List<KtResolveExtension>
|
||||||
|
|
||||||
abstract val declarationProvider: LLFirResolveExtensionToolDeclarationProvider
|
abstract val declarationProvider: LLFirResolveExtensionToolDeclarationProvider
|
||||||
abstract val packageProvider: KotlinPackageProvider
|
abstract val packageProvider: KotlinPackageProvider
|
||||||
abstract val packageFilter: LLFirResolveExtensionToolPackageFilter
|
abstract val packageFilter: LLFirResolveExtensionToolPackageFilter
|
||||||
@@ -48,7 +50,7 @@ val FirSession.llResolveExtensionTool: LLFirResolveExtensionTool? by FirSession.
|
|||||||
|
|
||||||
internal class LLFirNonEmptyResolveExtensionTool(
|
internal class LLFirNonEmptyResolveExtensionTool(
|
||||||
session: LLFirSession,
|
session: LLFirSession,
|
||||||
extensions: List<KtResolveExtension>,
|
override val extensions: List<KtResolveExtension>,
|
||||||
) : LLFirResolveExtensionTool() {
|
) : LLFirResolveExtensionTool() {
|
||||||
init {
|
init {
|
||||||
require(extensions.isNotEmpty())
|
require(extensions.isNotEmpty())
|
||||||
|
|||||||
Reference in New Issue
Block a user