diff --git a/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/symbols/AbstractSymbolRestoreFromDifferentModuleTest.kt b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/symbols/AbstractSymbolRestoreFromDifferentModuleTest.kt index bbfd1572494..7831e9b9891 100644 --- a/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/symbols/AbstractSymbolRestoreFromDifferentModuleTest.kt +++ b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/symbols/AbstractSymbolRestoreFromDifferentModuleTest.kt @@ -41,7 +41,7 @@ abstract class AbstractSymbolRestoreFromDifferentModuleTest : AbstractAnalysisAp val pointer = symbol.createPointer() Triple(DebugSymbolRenderer().render(symbol), symbol.render(defaultRenderer), pointer) } - configurator.doOutOfBlockModification(declaration.containingKtFile) + configurator.doGlobalModuleStateModification(project) val (debugRenderedRestored, prettyRenderedRestored) = analyseForTest(restoreAt) { val symbol = pointer.restoreSymbol() as? KtDeclarationSymbol diff --git a/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/symbols/AbstractSymbolTest.kt b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/symbols/AbstractSymbolTest.kt index 4d81ee16a13..dfcbedfdb40 100644 --- a/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/symbols/AbstractSymbolTest.kt +++ b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/symbols/AbstractSymbolTest.kt @@ -132,7 +132,7 @@ abstract class AbstractSymbolTest : AbstractAnalysisApiSingleFileTest() { compareResults(pointersWithRendered, testServices) - configurator.doOutOfBlockModification(ktFile) + configurator.doGlobalModuleStateModification(ktFile.project) restoreSymbolsInOtherReadActionAndCompareResults( directiveToIgnore = directiveToIgnoreSymbolRestore, diff --git a/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/configurators/AnalysisApiBaseTestServiceRegistrar.kt b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/configurators/AnalysisApiBaseTestServiceRegistrar.kt index 7cc6e1b9f8c..41ada52d176 100644 --- a/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/configurators/AnalysisApiBaseTestServiceRegistrar.kt +++ b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/configurators/AnalysisApiBaseTestServiceRegistrar.kt @@ -39,6 +39,9 @@ object AnalysisApiBaseTestServiceRegistrar: AnalysisApiTestServiceRegistrar() { override fun registerProjectServices(project: MockProject, testServices: TestServices) { project.apply { registerService(KotlinModificationTrackerFactory::class.java, KotlinStaticModificationTrackerFactory::class.java) + registerService(KotlinMessageBusProvider::class.java, KotlinProjectMessageBusProvider::class.java) + registerService(KotlinGlobalModificationService::class.java, KotlinStaticGlobalModificationService::class.java) + registerService(KtLifetimeTokenProvider::class.java, KtReadActionConfinementLifetimeTokenProvider::class.java) //KotlinClassFileDecompiler is registered as application service so it's available for the tests run in parallel as well diff --git a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/KotlinGlobalModificationService.kt b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/KotlinGlobalModificationService.kt new file mode 100644 index 00000000000..1f6e4d37de6 --- /dev/null +++ b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/KotlinGlobalModificationService.kt @@ -0,0 +1,54 @@ +/* + * 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 + +import com.intellij.openapi.project.Project +import org.jetbrains.annotations.TestOnly +import org.jetbrains.kotlin.analysis.project.structure.KtModule +import org.jetbrains.kotlin.analysis.providers.topics.KotlinTopics + +/** + * [KotlinGlobalModificationService] is a central service for the invalidation of caches during/between tests. + * + * All `publish` functions must be called in a write action because the events in [KotlinTopics] guarantee that the listener is called in a + * write action. + * + * Implementations of this service should publish global modification events to at least the following components: + * - [KotlinModificationTrackerFactory] + * - [KotlinTopics] via [analysisMessageBus] + */ +public abstract class KotlinGlobalModificationService { + /** + * Publishes an event of global modification of the module state of all [KtModule]s. + */ + @TestOnly + public abstract fun publishGlobalModuleStateModification() + + /** + * Publishes an event of global out-of-block modification of all [KtModule]s, including binary modules. The event does not invalidate + * module state like [publishGlobalModuleStateModification], so some module structure-specific caches might persist. + */ + @TestOnly + public abstract fun publishGlobalOutOfBlockModification() + + /** + * Publishes an event of global modification of the module state of all source [KtModule]s. + */ + @TestOnly + public abstract fun publishGlobalSourceModuleStateModification() + + /** + * Publishes an event of global out-of-block modification of all source [KtModule]s. The event does not invalidate module state like + * [publishGlobalSourceModuleStateModification], so some module structure-specific caches might persist. + */ + @TestOnly + public abstract fun publishGlobalSourceOutOfBlockModification() + + public companion object { + public fun getInstance(project: Project): KotlinGlobalModificationService = + project.getService(KotlinGlobalModificationService::class.java) + } +} diff --git a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/KotlinMessageBusProvider.kt b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/KotlinMessageBusProvider.kt new file mode 100644 index 00000000000..51a6c3a1e61 --- /dev/null +++ b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/KotlinMessageBusProvider.kt @@ -0,0 +1,29 @@ +/* + * 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 + +import com.intellij.openapi.project.Project +import com.intellij.util.messages.MessageBus +import org.jetbrains.kotlin.analysis.providers.topics.KotlinTopics + +/** + * [KotlinMessageBusProvider] allows Analysis API implementations to provide a custom [MessageBus]. When subscribing to or publishing to + * Analysis API topics ([KotlinTopics]), the message bus provided by [getMessageBus] should be used, not the [Project]'s message bus. + */ +public interface KotlinMessageBusProvider { + public fun getMessageBus(): MessageBus + + public companion object { + public fun getInstance(project: Project): KotlinMessageBusProvider = + project.getService(KotlinMessageBusProvider::class.java) + } +} + +/** + * The [MessageBus] used to subscribe to and publish to Analysis API topics ([KotlinTopics]). + */ +public val Project.analysisMessageBus: MessageBus + get() = KotlinMessageBusProvider.getInstance(this).getMessageBus() diff --git a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/KotlinModificationTrackerFactory.kt b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/KotlinModificationTrackerFactory.kt index f592e3ccad6..05f4e3bbd58 100644 --- a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/KotlinModificationTrackerFactory.kt +++ b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/KotlinModificationTrackerFactory.kt @@ -7,126 +7,84 @@ package org.jetbrains.kotlin.analysis.providers import com.intellij.openapi.project.Project import com.intellij.openapi.util.ModificationTracker -import org.jetbrains.annotations.TestOnly -import org.jetbrains.kotlin.analysis.project.structure.KtModule -import org.jetbrains.kotlin.analysis.project.structure.KtSourceModule - +import org.jetbrains.kotlin.analysis.providers.topics.KotlinTopics +/** + * [KotlinModificationTrackerFactory] creates modification trackers for select modification events. + * + * Further modification tracking is implemented with a subscription-based mechanism via [KotlinTopics]. Modification trackers make the most + * sense when there are many, possibly short-lived objects that need to be notified of a change. In such cases, subscriber management in the + * message bus adds too much overhead. + */ public abstract class KotlinModificationTrackerFactory { /** - * Creates **OOBM** tracker which is incremented every time there is **OOB** change in some source project module. + * Creates an out-of-block modification tracker which is incremented every time there is an out-of-block change in some source project + * module. + * + * ### Out-of-block Modification (OOBM) + * + * Out-of-block modification is a source code modification which may change the resolution of other non-local declarations. + * + * #### Example 1 + * + * ``` + * val x = 10 + * val z = x + * ``` + * + * If we change the initializer of `x` to `"str"` the return type of `x` will become `String` instead of the initial `Int`. This will + * change the return type of `z` as it does not have an explicit type. So, it is an **OOBM**. + * + * #### Example 2 + * + * ``` + * val x: Int = 10 + * val z = x + * ``` + * + * If we change `10` to `"str"` as in the first example, it would not change the type of `z`, so it is not an **OOBM**. + * + * #### Examples of source code modifications which result in an **OOBM** + * + * - Modification inside non-local (i.e. accessible outside) declaration without explicit return type specified + * - Modification of a package + * - Creation of a new declaration + * - Moving a declaration to another package + * + * Generally, all modifications which happen outside the body of a callable declaration (functions, accessors, or properties) with an + * explicit type are considered **OOBM**. * - * See [createModuleWithoutDependenciesOutOfBlockModificationTracker] for the definition of **OOBM**. * @see ModificationTracker */ public abstract fun createProjectWideOutOfBlockModificationTracker(): ModificationTracker /** - * Creates **OOBM** tracker which is incremented every time there is OOB change in the [module]. - * This tracker should not consider changes in dependent modules. - * If you do not need incrementalliy in your tool (eg, it is okay to reanalyse the whole worked on every source code change) consider incrementing returned tracker on every source code change. - * If tour tool works with static code (so no codebase changes in possible) just return static modification tracker here. + * Creates a modification tracker which is incremented every time libraries in the project are changed. * - * **Out Of Block Modification (OOBM)** is a such modification in source code which may change resolve of other non-local declarations. - * - * Consider the following example #1: - * ``` - * val x = 10 - * val z = x - * ``` - * If we change initializer of `x` to `"str"` the return type of `x` will become 'String' instead of initial `Int`. - * This will change the return type of `z` as it does not have explicit type specified. So, it is an **OOBM**. - * - * - * Consider example #2: - * ``` - * val x: Int = 10 - * val z = x - * ``` - * If we change 10 to "str" as in example #1, it would not change type of z, so it is not **OOBM**. - * - * Example of modifications in source code, which results **OOBM** - * - modification inside non-local (ie, accessible outside) declaration without explicit return type specified - * - modification o a package - * - creating new declaration - * - moving declaration to another package - * - * Generally, all modifications which happens outside callable declaration (function, accessor or property) body with explicit type are considered **OOBM** - * @see ModificationTracker - */ - public abstract fun createModuleWithoutDependenciesOutOfBlockModificationTracker(module: KtSourceModule): ModificationTracker - - /** - * Creates modification tracker which is incremented every time libraries in project are changed. - * - * See [KotlinModificationTrackerFactory] for the definition of **OOBM**. * @see ModificationTracker */ public abstract fun createLibrariesWideModificationTracker(): ModificationTracker - /** - * Creates [KtModuleStateTracker] which is incremented every time [module] roots changes. - * - * @see KtModuleStateTracker - */ - public abstract fun createModuleStateTracker(module: KtModule): KtModuleStateTracker - - /** - * Increments modification trackers to invalidate caches. If [includeBinaryTrackers] is `false`, binary module-related modification - * trackers will not be incremented (such as library trackers, SDK tracker, built-ins tracker, and so on). - */ - @TestOnly - public abstract fun incrementModificationsCount(includeBinaryTrackers: Boolean = true) - public companion object { - public fun getService(project: Project): KotlinModificationTrackerFactory = + public fun getInstance(project: Project): KotlinModificationTrackerFactory = project.getService(KotlinModificationTrackerFactory::class.java) } } /** - * Represents current state of [KtModule] validity, can be created via [org.jetbrains.kotlin.analysis.providers.KotlinModificationTrackerFactory.createModuleStateTracker] - */ -public interface KtModuleStateTracker { - /** - * If the module is still valid (i.e., it was not removed) - */ - public val isValid: Boolean - - /** - * Represents modification tracker of modified roots similar to the [ModificationTracker]. - * - * If the [isValid] == `false`, when behaviour is unspecified - */ - public val rootModificationCount: Long -} - -/** - * Creates **OOBM** tracker which is incremented every time there is OOB change in some source project module. + * Creates an **OOBM** tracker which is incremented every time there is an OOB change in some source project module. * - * See [KotlinModificationTrackerFactory.createModuleWithoutDependenciesOutOfBlockModificationTracker] for the definition of **OOBM**. + * See [KotlinModificationTrackerFactory.createProjectWideOutOfBlockModificationTracker] for the definition of **OOBM**. * @see ModificationTracker */ public fun Project.createProjectWideOutOfBlockModificationTracker(): ModificationTracker = - this.getService(KotlinModificationTrackerFactory::class.java) - .createProjectWideOutOfBlockModificationTracker() + KotlinModificationTrackerFactory.getInstance(this).createProjectWideOutOfBlockModificationTracker() /** - * Creates **OOBM** tracker which is incremented every time there is OOB change in the [module]. - * - * See [KotlinModificationTrackerFactory.createModuleWithoutDependenciesOutOfBlockModificationTracker] for the definition of **OOBM**. - * @see ModificationTracker - */ -public fun KtSourceModule.createModuleWithoutDependenciesOutOfBlockModificationTracker(project: Project): ModificationTracker = - project.getService(KotlinModificationTrackerFactory::class.java) - .createModuleWithoutDependenciesOutOfBlockModificationTracker(this) - -/** - * Creates modification tracker which is incremented every time libraries in project are changed. + * Creates a modification tracker which is incremented every time libraries in the project are changed. * * See [KotlinModificationTrackerFactory] for the definition of **OOBM**. * @see ModificationTracker */ public fun Project.createAllLibrariesModificationTracker(): ModificationTracker = - this.getService(KotlinModificationTrackerFactory::class.java) - .createLibrariesWideModificationTracker() + KotlinModificationTrackerFactory.getInstance(this).createLibrariesWideModificationTracker() diff --git a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinGlobalModificationServiceBase.kt b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinGlobalModificationServiceBase.kt new file mode 100644 index 00000000000..6d862f89a28 --- /dev/null +++ b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinGlobalModificationServiceBase.kt @@ -0,0 +1,50 @@ +/* + * 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.impl + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.Project +import org.jetbrains.annotations.TestOnly +import org.jetbrains.kotlin.analysis.providers.KotlinGlobalModificationService +import org.jetbrains.kotlin.analysis.providers.analysisMessageBus +import org.jetbrains.kotlin.analysis.providers.topics.KotlinTopics + +public abstract class KotlinGlobalModificationServiceBase(private val project: Project) : KotlinGlobalModificationService() { + @TestOnly + protected abstract fun incrementModificationTrackers(includeBinaryTrackers: Boolean) + + @TestOnly + override fun publishGlobalModuleStateModification() { + ApplicationManager.getApplication().assertWriteAccessAllowed() + + incrementModificationTrackers(includeBinaryTrackers = true) + project.analysisMessageBus.syncPublisher(KotlinTopics.GLOBAL_MODULE_STATE_MODIFICATION).afterModification() + } + + @TestOnly + override fun publishGlobalOutOfBlockModification() { + ApplicationManager.getApplication().assertWriteAccessAllowed() + + incrementModificationTrackers(includeBinaryTrackers = true) + project.analysisMessageBus.syncPublisher(KotlinTopics.GLOBAL_OUT_OF_BLOCK_MODIFICATION).afterModification() + } + + @TestOnly + override fun publishGlobalSourceModuleStateModification() { + ApplicationManager.getApplication().assertWriteAccessAllowed() + + incrementModificationTrackers(includeBinaryTrackers = false) + project.analysisMessageBus.syncPublisher(KotlinTopics.GLOBAL_SOURCE_MODULE_STATE_MODIFICATION).afterModification() + } + + @TestOnly + override fun publishGlobalSourceOutOfBlockModification() { + ApplicationManager.getApplication().assertWriteAccessAllowed() + + incrementModificationTrackers(includeBinaryTrackers = false) + project.analysisMessageBus.syncPublisher(KotlinTopics.GLOBAL_SOURCE_OUT_OF_BLOCK_MODIFICATION).afterModification() + } +} diff --git a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinProjectMessageBusProvider.kt b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinProjectMessageBusProvider.kt new file mode 100644 index 00000000000..8dc809d9fae --- /dev/null +++ b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinProjectMessageBusProvider.kt @@ -0,0 +1,21 @@ +/* + * 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.impl + +import com.intellij.openapi.project.Project +import com.intellij.util.messages.MessageBus +import org.jetbrains.kotlin.analysis.providers.KotlinMessageBusProvider + +/** + * Provides the [project]'s [MessageBus] as the Analysis API message bus. This is the default implementation for both the standalone and the + * IDE Analysis API. + * + * [KotlinMessageBusProvider] exists so that this default may change in the future without breaking the API. Hence, it should not be assumed + * that the message bus provided by [KotlinMessageBusProvider] will always be equal to the project's message bus. + */ +public class KotlinProjectMessageBusProvider(private val project: Project) : KotlinMessageBusProvider { + override fun getMessageBus(): MessageBus = project.messageBus +} diff --git a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinStaticGlobalModificationService.kt b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinStaticGlobalModificationService.kt new file mode 100644 index 00000000000..c4827385ec7 --- /dev/null +++ b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinStaticGlobalModificationService.kt @@ -0,0 +1,16 @@ +/* + * 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.impl + +import com.intellij.openapi.project.Project +import org.jetbrains.annotations.TestOnly + +public class KotlinStaticGlobalModificationService(private val project: Project) : KotlinGlobalModificationServiceBase(project) { + @TestOnly + override fun incrementModificationTrackers(includeBinaryTrackers: Boolean) { + KotlinStaticModificationTrackerFactory.getInstance(project).incrementModificationsCount(includeBinaryTrackers) + } +} diff --git a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinStaticModificationTrackerFactory.kt b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinStaticModificationTrackerFactory.kt index a80571719e7..ed5b1dc82c5 100644 --- a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinStaticModificationTrackerFactory.kt +++ b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinStaticModificationTrackerFactory.kt @@ -5,61 +5,32 @@ package org.jetbrains.kotlin.analysis.providers.impl +import com.intellij.openapi.project.Project import com.intellij.openapi.util.ModificationTracker import com.intellij.openapi.util.SimpleModificationTracker -import org.jetbrains.annotations.TestOnly -import org.jetbrains.kotlin.analysis.project.structure.KtBinaryModule -import org.jetbrains.kotlin.analysis.project.structure.KtModule -import org.jetbrains.kotlin.analysis.project.structure.KtSourceModule import org.jetbrains.kotlin.analysis.providers.KotlinModificationTrackerFactory -import org.jetbrains.kotlin.analysis.providers.KtModuleStateTracker public class KotlinStaticModificationTrackerFactory : KotlinModificationTrackerFactory() { private val projectWide = SimpleModificationTracker() private val librariesWide = SimpleModificationTracker() - private val moduleOutOfBlock = mutableMapOf() - private val moduleState = mutableMapOf() override fun createProjectWideOutOfBlockModificationTracker(): ModificationTracker { return projectWide } - - override fun createModuleWithoutDependenciesOutOfBlockModificationTracker(module: KtSourceModule): ModificationTracker { - return moduleOutOfBlock.getOrPut(module) { SimpleModificationTracker() } - } - override fun createLibrariesWideModificationTracker(): ModificationTracker { return librariesWide } - override fun createModuleStateTracker(module: KtModule): KtModuleStateTracker { - return moduleState.getOrPut(module) { KtModuleStateTrackerImpl() } - } - - @TestOnly - override fun incrementModificationsCount(includeBinaryTrackers: Boolean) { + internal fun incrementModificationsCount(includeBinaryTrackers: Boolean) { projectWide.incModificationCount() if (includeBinaryTrackers) { librariesWide.incModificationCount() } - moduleOutOfBlock.values.forEach { it.incModificationCount() } - moduleState.entries.forEach { (ktModule, tracker) -> - if (ktModule is KtBinaryModule && !includeBinaryTrackers) return@forEach - tracker.incModificationCount() - } + } + + public companion object { + public fun getInstance(project: Project): KotlinStaticModificationTrackerFactory = + KotlinModificationTrackerFactory.getInstance(project) as KotlinStaticModificationTrackerFactory } } - -private class KtModuleStateTrackerImpl: KtModuleStateTracker { - override val isValid: Boolean get() = true - - private var _rootModificationCount = 0L - - override val rootModificationCount: Long get() = _rootModificationCount - - @TestOnly - fun incModificationCount() { - _rootModificationCount++ - } -} \ No newline at end of file diff --git a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinGlobalModuleStateModificationListener.kt b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinGlobalModuleStateModificationListener.kt new file mode 100644 index 00000000000..223e23df573 --- /dev/null +++ b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinGlobalModuleStateModificationListener.kt @@ -0,0 +1,18 @@ +/* + * 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.topics + +import org.jetbrains.kotlin.analysis.project.structure.KtModule + +public fun interface KotlinGlobalModuleStateModificationListener { + /** + * [afterModification] is invoked in a write action after global modification of the module state of all [KtModule]s, including binary + * modules. + * + * This event is published after SDK removal and to invalidate caches during/between tests. + */ + public fun afterModification() +} diff --git a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinGlobalOutOfBlockModificationListener.kt b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinGlobalOutOfBlockModificationListener.kt new file mode 100644 index 00000000000..88bcd791ee5 --- /dev/null +++ b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinGlobalOutOfBlockModificationListener.kt @@ -0,0 +1,17 @@ +/* + * 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.topics + +import org.jetbrains.kotlin.analysis.project.structure.KtModule + +public fun interface KotlinGlobalOutOfBlockModificationListener { + /** + * [afterModification] is invoked in a write action after global out-of-block modification of all [KtModule]s, including binary modules. + * + * This event is published to invalidate caches during/between tests. + */ + public fun afterModification() +} diff --git a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinGlobalSourceModuleStateModificationListener.kt b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinGlobalSourceModuleStateModificationListener.kt new file mode 100644 index 00000000000..d567bf8dde8 --- /dev/null +++ b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinGlobalSourceModuleStateModificationListener.kt @@ -0,0 +1,18 @@ +/* + * 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.topics + +import org.jetbrains.kotlin.analysis.project.structure.KtModule + +public fun interface KotlinGlobalSourceModuleStateModificationListener { + /** + * [afterModification] is invoked in a write action after global modification of the module state of all source [KtModule]s, excluding + * binary modules. + * + * This event is published to invalidate caches during/between tests. + */ + public fun afterModification() +} diff --git a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinGlobalSourceOutOfBlockModificationListener.kt b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinGlobalSourceOutOfBlockModificationListener.kt new file mode 100644 index 00000000000..f47fd7d9efe --- /dev/null +++ b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinGlobalSourceOutOfBlockModificationListener.kt @@ -0,0 +1,18 @@ +/* + * 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.topics + +import org.jetbrains.kotlin.analysis.project.structure.KtModule + +public fun interface KotlinGlobalSourceOutOfBlockModificationListener { + /** + * [afterModification] is invoked in a write action after global out-of-block modification of all source [KtModule]s, excluding binary + * modules. + * + * This event is published on global PSI changes. + */ + public fun afterModification() +} diff --git a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinModuleOutOfBlockModificationListener.kt b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinModuleOutOfBlockModificationListener.kt new file mode 100644 index 00000000000..d008919fdd5 --- /dev/null +++ b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinModuleOutOfBlockModificationListener.kt @@ -0,0 +1,22 @@ +/* + * 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.topics + +import org.jetbrains.kotlin.analysis.project.structure.KtModule +import org.jetbrains.kotlin.analysis.providers.KotlinModificationTrackerFactory + +public fun interface KotlinModuleOutOfBlockModificationListener { + /** + * [afterModification] is invoked in a write action after an out-of-block modification happened in [module]'s source code. + * + * See [KotlinModificationTrackerFactory.createProjectWideOutOfBlockModificationTracker] for an explanation of out-of-block + * modifications. + * + * This event may be published for any and all source code changes, not just out-of-block modifications, to simplify the implementation + * of modification detection. + */ + public fun afterModification(module: KtModule) +} diff --git a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinModuleStateModificationListener.kt b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinModuleStateModificationListener.kt new file mode 100644 index 00000000000..52af0990549 --- /dev/null +++ b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinModuleStateModificationListener.kt @@ -0,0 +1,16 @@ +/* + * 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.topics + +import org.jetbrains.kotlin.analysis.project.structure.KtModule + +public fun interface KotlinModuleStateModificationListener { + /** + * [afterModification] is invoked in a write action after the [module] was moved, removed, or its structure changed. [isRemoval] signals + * if the module was removed. + */ + public fun afterModification(module: KtModule, isRemoval: Boolean) +} diff --git a/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinTopics.kt b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinTopics.kt new file mode 100644 index 00000000000..8cdd8e99285 --- /dev/null +++ b/analysis/analysis-api-providers/src/org/jetbrains/kotlin/analysis/providers/topics/KotlinTopics.kt @@ -0,0 +1,52 @@ +/* + * 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.topics + +import com.intellij.util.messages.Topic +import org.jetbrains.kotlin.analysis.providers.analysisMessageBus + +/** + * [Topic]s for Analysis API event subscription and publication. These topics should be subscribed to and published to via the Analysis API + * message bus: [analysisMessageBus]. + * + * See the individual listener interfaces for documentation about the events described by these topics: + * - [KotlinModuleStateModificationListener] + * - [KotlinModuleOutOfBlockModificationListener] + * - [KotlinGlobalModuleStateModificationListener] + * - [KotlinGlobalOutOfBlockModificationListener] + * - [KotlinGlobalSourceModuleStateModificationListener] + * - [KotlinGlobalSourceOutOfBlockModificationListener] + * + * Care needs to be taken with the lack of interplay between different types of topics: Publishing a global modification event, for example, + * does not imply the corresponding module-level event. Similarly, publishing a module state modification event does not imply out-of-block + * modification. + * + * #### Implementation Notes + * + * Analysis API implementations need to take care of publishing to these topics via the [analysisMessageBus]. In general, if your tool works + * with static code and static module structure, you do not need to publish any events. However, keep in mind the contracts of the various + * topics. For example, if your tool can guarantee a static module structure but source code can still change, module state modification + * events do not need to be published, but out-of-block modification events do. + */ +public object KotlinTopics { + public val MODULE_STATE_MODIFICATION: Topic = + Topic(KotlinModuleStateModificationListener::class.java, Topic.BroadcastDirection.TO_CHILDREN, true) + + public val MODULE_OUT_OF_BLOCK_MODIFICATION: Topic = + Topic(KotlinModuleOutOfBlockModificationListener::class.java, Topic.BroadcastDirection.TO_CHILDREN, true) + + public val GLOBAL_MODULE_STATE_MODIFICATION: Topic = + Topic(KotlinGlobalModuleStateModificationListener::class.java, Topic.BroadcastDirection.TO_CHILDREN, true) + + public val GLOBAL_OUT_OF_BLOCK_MODIFICATION: Topic = + Topic(KotlinGlobalOutOfBlockModificationListener::class.java, Topic.BroadcastDirection.TO_CHILDREN, true) + + public val GLOBAL_SOURCE_MODULE_STATE_MODIFICATION: Topic = + Topic(KotlinGlobalSourceModuleStateModificationListener::class.java, Topic.BroadcastDirection.TO_CHILDREN, true) + + public val GLOBAL_SOURCE_OUT_OF_BLOCK_MODIFICATION: Topic = + Topic(KotlinGlobalSourceOutOfBlockModificationListener::class.java, Topic.BroadcastDirection.TO_CHILDREN, true) +} diff --git a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/api/standalone/StandaloneAnalysisAPISessionBuilder.kt b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/api/standalone/StandaloneAnalysisAPISessionBuilder.kt index 46a726e12b4..4a4c098ac18 100644 --- a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/api/standalone/StandaloneAnalysisAPISessionBuilder.kt +++ b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/api/standalone/StandaloneAnalysisAPISessionBuilder.kt @@ -119,6 +119,8 @@ public class StandaloneAnalysisAPISessionBuilder( FirStandaloneServiceRegistrar.registerProjectModelServices(project, kotlinCoreProjectEnvironment.parentDisposable) registerService(KotlinModificationTrackerFactory::class.java, KotlinStaticModificationTrackerFactory::class.java) + registerService(KotlinMessageBusProvider::class.java, KotlinProjectMessageBusProvider::class.java) + registerService(KotlinGlobalModificationService::class.java, KotlinStaticGlobalModificationService::class.java) registerService(KtModuleScopeProvider::class.java, KtModuleScopeProviderImpl()) registerService(KotlinAnnotationsResolverFactory::class.java, KotlinStaticAnnotationsResolverFactory(ktFiles)) diff --git a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/api/standalone/StandaloneUtils.kt b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/api/standalone/StandaloneUtils.kt index b658b08a830..f164ab3cd83 100644 --- a/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/api/standalone/StandaloneUtils.kt +++ b/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/api/standalone/StandaloneUtils.kt @@ -70,6 +70,8 @@ public fun configureApplicationEnvironment(app: MockApplication) { * * [SymbolKotlinAsJavaSupport] * * [ClsJavaStubByVirtualFileCache] * * [KotlinModificationTrackerFactory] + * * [KotlinMessageBusProvider] + * * [KotlinGlobalModificationService] * * [KotlinAnnotationsResolverFactory] * * [org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirResolveSessionService] * * [FirSealedClassInheritorsProcessorFactory] @@ -112,6 +114,16 @@ internal fun configureProjectEnvironment( KotlinStaticModificationTrackerFactory() ) + project.registerService( + KotlinMessageBusProvider::class.java, + KotlinProjectMessageBusProvider(project), + ) + + project.registerService( + KotlinGlobalModificationService::class.java, + KotlinStaticGlobalModificationService(project), + ) + // FIR LC project.registerService( ClsJavaStubByVirtualFileCache::class.java, diff --git a/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/test/configurators/AnalysisApiTestConfigurator.kt b/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/test/configurators/AnalysisApiTestConfigurator.kt index 0aa36a2f5de..95a8960610a 100644 --- a/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/test/configurators/AnalysisApiTestConfigurator.kt +++ b/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/test/configurators/AnalysisApiTestConfigurator.kt @@ -13,8 +13,6 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key import com.intellij.psi.PsiFile import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleProjectStructure -import org.jetbrains.kotlin.analysis.providers.KotlinModificationTrackerFactory -import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.NotNullableUserDataProperty import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder import org.jetbrains.kotlin.test.model.TestModule @@ -25,6 +23,7 @@ import java.util.concurrent.locks.ReadWriteLock import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.withLock import kotlin.reflect.KClass +import org.jetbrains.kotlin.analysis.providers.KotlinGlobalModificationService abstract class AnalysisApiTestConfigurator { open val testPrefix: String? get() = null @@ -39,9 +38,8 @@ abstract class AnalysisApiTestConfigurator { open fun prepareFilesInModule(files: List, module: TestModule, testServices: TestServices) {} - open fun doOutOfBlockModification(file: KtFile) { - file.project.getService(KotlinModificationTrackerFactory::class.java) - .incrementModificationsCount() + open fun doGlobalModuleStateModification(project: Project) { + KotlinGlobalModificationService.getInstance(project).publishGlobalModuleStateModification() } open fun preprocessTestDataPath(path: Path): Path = path diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/test/base/FirLowLevelCompilerBasedTestConfigurator.kt b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/test/base/FirLowLevelCompilerBasedTestConfigurator.kt index 5c56e2c596f..e6ca55fec34 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/test/base/FirLowLevelCompilerBasedTestConfigurator.kt +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/test/base/FirLowLevelCompilerBasedTestConfigurator.kt @@ -9,7 +9,6 @@ import com.intellij.openapi.Disposable import com.intellij.openapi.project.Project import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfigurator import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestServiceRegistrar -import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder import org.jetbrains.kotlin.test.services.TestModuleStructure import org.jetbrains.kotlin.test.services.TestServices @@ -18,7 +17,6 @@ import org.jetbrains.kotlin.analysis.api.impl.base.test.configurators.AnalysisAp import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleProjectStructure import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleWithFiles import org.jetbrains.kotlin.analysis.project.structure.KtBinaryModule -import org.jetbrains.kotlin.analysis.project.structure.KtLibraryModule import org.jetbrains.kotlin.analysis.project.structure.allDirectDependenciesOfType import org.jetbrains.kotlin.analysis.test.framework.project.structure.KtSourceModuleByCompilerConfiguration import org.jetbrains.kotlin.analysis.test.framework.project.structure.TestModuleStructureFactory @@ -57,7 +55,7 @@ object FirLowLevelCompilerBasedTestConfigurator : AnalysisApiTestConfigurator() ) } - override fun doOutOfBlockModification(file: KtFile) { + override fun doGlobalModuleStateModification(project: Project) { error("Should not be called for compiler based tests") } }