diff --git a/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/base/registerBaseTestServices.kt b/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/base/registerBaseTestServices.kt index 6b6ce295b7a..396617a8692 100644 --- a/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/base/registerBaseTestServices.kt +++ b/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/base/registerBaseTestServices.kt @@ -26,7 +26,7 @@ fun TestConfigurationBuilder.registerAnalysisApiBaseTestServices( ) { useAdditionalService(::TestDisposableProviderImpl) useAdditionalService(::AnalysisApiKtModuleProviderImpl) - useAdditionalService(::AnalysisApiEnvironmentManagerImpl.bind(testDisposable, configurator)) + useAdditionalService(::AnalysisApiEnvironmentManagerImpl.bind(testDisposable)) useAdditionalService { ExecutionListenerBasedDisposableProvider() } useAdditionalService { StandardLibrariesPathProviderForKotlinProject } diff --git a/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/services/AnalysisApiEnvironmentManager.kt b/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/services/AnalysisApiEnvironmentManager.kt index 7de356619b9..5127f1435d1 100644 --- a/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/services/AnalysisApiEnvironmentManager.kt +++ b/analysis/analysis-test-framework/tests/org/jetbrains/kotlin/analysis/test/framework/services/AnalysisApiEnvironmentManager.kt @@ -40,13 +40,12 @@ abstract class AnalysisApiEnvironmentManager : TestService { class AnalysisApiEnvironmentManagerImpl( override val testServices: TestServices, override val testRootDisposable: Disposable, - private val configurator: AnalysisApiTestConfigurator, ) : AnalysisApiEnvironmentManager() { private val _projectEnvironment: KotlinCoreProjectEnvironment by lazy { StandaloneProjectFactory.createProjectEnvironment( testRootDisposable, testServices.applicationDisposableProvider.getApplicationRootDisposable(), - KotlinCoreApplicationEnvironmentMode.UnitTest(configurator.isWriteAccessAllowed), + KotlinCoreApplicationEnvironmentMode.UnitTest, ) } 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 dc426ab25cd..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 @@ -36,15 +36,6 @@ abstract class AnalysisApiTestConfigurator { abstract val serviceRegistrars: List - /** - * Whether the associated [core application environment][org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreApplicationEnvironment] should - * permit write access. - * - * Write access is disabled by default because the Analysis API should not be used during write actions. Nevertheless, some tests may - * need write access, for example to modify PSI. - */ - open val isWriteAccessAllowed get() = false - open fun prepareFilesInModule(files: List, module: TestModule, testServices: TestServices) {} open fun doGlobalModuleStateModification(project: Project) { diff --git a/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/callExpression/contractCall.kt b/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/callExpression/contractCall.kt index 53ca1f3acdf..bdbe38a6ba2 100644 --- a/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/callExpression/contractCall.kt +++ b/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/callExpression/contractCall.kt @@ -1,6 +1,3 @@ -// IGNORE_FIR -// Reason: KT-63560 - import kotlin.contracts.contract import kotlin.contracts.InvocationKind diff --git a/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/callExpression/printlnFirstStatement.kt b/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/callExpression/printlnFirstStatement.kt index 33e033d4d16..66ac2bb0804 100644 --- a/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/callExpression/printlnFirstStatement.kt +++ b/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/callExpression/printlnFirstStatement.kt @@ -1,6 +1,3 @@ -// IGNORE_FIR -// Reason: KT-63560 - fun foo(string: String) { println("Hello world, $string!") } diff --git a/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/callExpression/printlnSecondStatement.kt b/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/callExpression/printlnSecondStatement.kt index fe46f026ac9..ad31f7e65ef 100644 --- a/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/callExpression/printlnSecondStatement.kt +++ b/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/callExpression/printlnSecondStatement.kt @@ -1,6 +1,3 @@ -// IGNORE_FIR -// Reason: KT-63560 - fun foo(string: String) { val a = 7 println("Hello world, $string!") diff --git a/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/dotQualifiedExpression/contractCall.kt b/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/dotQualifiedExpression/contractCall.kt index 392dcafaca9..79cff317223 100644 --- a/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/dotQualifiedExpression/contractCall.kt +++ b/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/dotQualifiedExpression/contractCall.kt @@ -1,6 +1,3 @@ -// IGNORE_FIR -// Reason: KT-63560 - import kotlin.contracts.InvocationKind inline fun foo(block: () -> Unit) { diff --git a/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/dotQualifiedExpression/stringLengthFirstStatement.kt b/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/dotQualifiedExpression/stringLengthFirstStatement.kt index 0350ecf483c..81c5c0c7c98 100644 --- a/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/dotQualifiedExpression/stringLengthFirstStatement.kt +++ b/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/dotQualifiedExpression/stringLengthFirstStatement.kt @@ -1,6 +1,3 @@ -// IGNORE_FIR -// Reason: KT-63560 - fun foo(string: String) { string.length } diff --git a/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/dotQualifiedExpression/stringLengthSecondStatement.kt b/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/dotQualifiedExpression/stringLengthSecondStatement.kt index 11201881886..2d9879e4b29 100644 --- a/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/dotQualifiedExpression/stringLengthSecondStatement.kt +++ b/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/dotQualifiedExpression/stringLengthSecondStatement.kt @@ -1,6 +1,3 @@ -// IGNORE_FIR -// Reason: KT-63560 - fun foo(string: String) { println() string.length diff --git a/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/propertyDeclaration/simpleProperty.kt b/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/propertyDeclaration/simpleProperty.kt index b4ac26d9053..338606be592 100644 --- a/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/propertyDeclaration/simpleProperty.kt +++ b/analysis/low-level-api-fir/testData/declarationModificationService/psiResilience/propertyDeclaration/simpleProperty.kt @@ -1,4 +1 @@ -// IGNORE_FIR -// Reason: KT-63560 - val property: Int = 5 diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/test/base/AbstractLowLevelApiModifiablePsiSingleFileTest.kt b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/test/base/AbstractLowLevelApiModifiablePsiSingleFileTest.kt index bb9e97aec23..03290724660 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/test/base/AbstractLowLevelApiModifiablePsiSingleFileTest.kt +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/test/base/AbstractLowLevelApiModifiablePsiSingleFileTest.kt @@ -40,6 +40,4 @@ object AnalysisApiFirModifiablePsiSourceTestConfigurator : AnalysisApiFirSourceT addAll(super.serviceRegistrars) add(AnalysisApiModifiablePsiTestServiceRegistrar) } - - override val isWriteAccessAllowed: Boolean get() = true } diff --git a/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreApplicationEnvironment.kt b/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreApplicationEnvironment.kt index ce2f5da4690..46d5ee0728b 100644 --- a/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreApplicationEnvironment.kt +++ b/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreApplicationEnvironment.kt @@ -13,7 +13,9 @@ import com.intellij.lang.MetaLanguage import com.intellij.mock.MockApplication import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.util.Computable import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.ThrowableComputable import com.intellij.openapi.vfs.VirtualFileSystem import com.intellij.psi.FileContextProvider import com.intellij.psi.augment.PsiAugmentProvider @@ -25,28 +27,22 @@ import org.jetbrains.kotlin.cli.jvm.compiler.jarfs.FastJarFileSystem import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem sealed interface KotlinCoreApplicationEnvironmentMode { - val isWriteAccessAllowed: Boolean + object Production : KotlinCoreApplicationEnvironmentMode - object Production : KotlinCoreApplicationEnvironmentMode { - override val isWriteAccessAllowed: Boolean get() = true - } - - class UnitTest(override val isWriteAccessAllowed: Boolean) : KotlinCoreApplicationEnvironmentMode + object UnitTest : KotlinCoreApplicationEnvironmentMode companion object { fun fromUnitTestModeFlag(isUnitTestMode: Boolean): KotlinCoreApplicationEnvironmentMode = - if (isUnitTestMode) UnitTest(isWriteAccessAllowed = false) else Production + if (isUnitTestMode) UnitTest else Production } } class KotlinCoreApplicationEnvironment private constructor( parentDisposable: Disposable, - private val environmentMode: KotlinCoreApplicationEnvironmentMode, -) : JavaCoreApplicationEnvironment(parentDisposable, environmentMode is KotlinCoreApplicationEnvironmentMode.UnitTest) { + environmentMode: KotlinCoreApplicationEnvironmentMode, +) : JavaCoreApplicationEnvironment(parentDisposable, environmentMode == KotlinCoreApplicationEnvironmentMode.UnitTest) { init { - application.initializeEnvironmentMode(environmentMode) - registerApplicationService(JavaFileCodeStyleFacadeFactory::class.java, DummyJavaFileCodeStyleFacadeFactory()) registerFileType(JavaClassFileType.INSTANCE, "sig") } @@ -63,7 +59,7 @@ class KotlinCoreApplicationEnvironment private constructor( * is not yet initialized when this function is called from the superclass constructor. */ return if (mock.isUnitTestMode) { - MockUnitTestApplication(parentDisposable) + KotlinCoreUnitTestApplication(parentDisposable) } else { mock } @@ -129,25 +125,42 @@ class KotlinCoreApplicationEnvironment private constructor( } } -private class MockUnitTestApplication(parentDisposable: Disposable) : MockApplication(parentDisposable) { +/** + * A [MockApplication] which allows write actions only in [runWriteAction] blocks. + * + * The Analysis API is usually not allowed to be used from a write action. To properly support Analysis API tests, the application should + * not return `true` for [isWriteAccessAllowed] indiscriminately like [MockApplication]. On the other hand, we have some tests which require + * write access, but don't access the Analysis API. Hence, we remember which threads have started a write action with [runWriteAction]. + */ +private class KotlinCoreUnitTestApplication(parentDisposable: Disposable) : MockApplication(parentDisposable) { /** - * We can't use [KotlinCoreApplicationEnvironment.environmentMode] in [KotlinCoreApplicationEnvironment.createApplication], because the - * corresponding property is not yet initialized when `createApplication` is called from the superclass constructor. So we have to - * initialize it later. + * We need to remember whether write access is allowed per thread because the application can be shared between multiple concurrent test + * runs. */ - lateinit var environmentMode: KotlinCoreApplicationEnvironmentMode + private val isWriteAccessAllowedInThread: ThreadLocal = ThreadLocal.withInitial { false } override fun isUnitTestMode(): Boolean = true - override fun isWriteAccessAllowed(): Boolean = environmentMode.isWriteAccessAllowed -} + override fun isWriteAccessAllowed(): Boolean = isWriteAccessAllowedInThread.get() -private fun MockApplication.initializeEnvironmentMode(environmentMode: KotlinCoreApplicationEnvironmentMode) { - if (this !is MockUnitTestApplication) return + override fun runWriteAction(action: Runnable) { + withWriteAccessAllowedInThread { + action.run() + } + } - this.environmentMode = environmentMode + override fun runWriteAction(computation: Computable): T? = + withWriteAccessAllowedInThread { computation.compute() } - require(isWriteAccessAllowed == environmentMode.isWriteAccessAllowed) { - "The mock application's `isWriteAccessAllowed` should correspond to the environment mode configuration." + override fun runWriteAction(computation: ThrowableComputable): T? = + withWriteAccessAllowedInThread { computation.compute() } + + private inline fun withWriteAccessAllowedInThread(action: () -> A): A { + isWriteAccessAllowedInThread.set(true) + try { + return action() + } finally { + isWriteAccessAllowedInThread.set(false) + } } } diff --git a/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt b/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt index a420afcb5dd..0a7c85d3cbf 100644 --- a/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt +++ b/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt @@ -471,7 +471,7 @@ class KotlinCoreEnvironment private constructor( createApplicationEnvironment( parentDisposable, configuration, - KotlinCoreApplicationEnvironmentMode.UnitTest(isWriteAccessAllowed = false), + KotlinCoreApplicationEnvironmentMode.UnitTest, ) val projectEnv = ProjectEnvironment(parentDisposable, appEnv, configuration) return KotlinCoreEnvironment(projectEnv, configuration, extensionConfigs) @@ -501,7 +501,7 @@ class KotlinCoreEnvironment private constructor( val appEnv = createApplicationEnvironment( parentDisposable, configuration, - KotlinCoreApplicationEnvironmentMode.UnitTest(isWriteAccessAllowed = false), + KotlinCoreApplicationEnvironmentMode.UnitTest, ) return ProjectEnvironment(parentDisposable, appEnv, configuration) } @@ -523,7 +523,7 @@ class KotlinCoreEnvironment private constructor( ): KotlinCoreApplicationEnvironment = getOrCreateApplicationEnvironment( parentDisposable, configuration, - KotlinCoreApplicationEnvironmentMode.UnitTest(isWriteAccessAllowed = false), + KotlinCoreApplicationEnvironmentMode.UnitTest, ) fun getOrCreateApplicationEnvironment(