diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt index 09001b9c426..88c418090bd 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt @@ -35,7 +35,6 @@ import org.jetbrains.kotlin.idea.KotlinIdeaAnalysisBundle import org.jetbrains.kotlin.idea.caches.resolve.util.enlargedSearchScope import org.jetbrains.kotlin.idea.caches.trackers.KotlinModuleOutOfCodeBlockModificationTracker import org.jetbrains.kotlin.idea.configuration.BuildSystemType -import org.jetbrains.kotlin.idea.configuration.IdeBuiltInsLoadingState import org.jetbrains.kotlin.idea.configuration.getBuildSystemType import org.jetbrains.kotlin.idea.core.isInTestSourceContentKotlinAware import org.jetbrains.kotlin.idea.framework.KotlinSdkType @@ -353,25 +352,7 @@ abstract class LibraryInfo(override val project: Project, val library: Library) val (libraries, sdks) = LibraryDependenciesCache.getInstance(project).getLibrariesAndSdksUsedWith(this) result.addAll(sdks) - - /* - * When built-ins are created from module dependencies (as opposed to loading them from classloader) - * we must resolve Kotlin standard library containing some of the built-ins declarations in the same - * resolver for project as JDK. This comes from the following requirements: - * - JvmBuiltins need JDK and standard library descriptors -> resolver for project should be able to - * resolve them - * - Builtins are created in BuiltinsCache -> module descriptors should be resolved under lock of the - * SDK resolver to prevent deadlocks - * This means we have to maintain dependencies of the standard library manually or effectively drop - * resolver for SDK otherwise. Libraries depend on superset of their actual dependencies because of - * the inability to get real dependencies from IDEA model. So moving stdlib with all dependencies - * down is a questionable option. - */ - if (!IdeBuiltInsLoadingState.isFromClassLoader && this.isCoreKotlinLibrary(project)) { - libraries.filterTo(result) { it.isCoreKotlinLibrary(project) } - } else { - result.addAll(libraries) - } + result.addAll(libraries) return result.toList() } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/LibraryDependenciesCache.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/LibraryDependenciesCache.kt index 8ce53fdfddf..70bbbd0ed60 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/LibraryDependenciesCache.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/LibraryDependenciesCache.kt @@ -17,6 +17,7 @@ import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValuesManager import com.intellij.util.containers.ContainerUtil import com.intellij.util.containers.MultiMap +import org.jetbrains.kotlin.idea.configuration.IdeBuiltInsLoadingState import org.jetbrains.kotlin.idea.core.util.CachedValue import org.jetbrains.kotlin.idea.core.util.getValue import org.jetbrains.kotlin.idea.project.isHMPPEnabled @@ -93,7 +94,9 @@ class LibraryDependenciesCacheImpl(private val project: Project) : LibraryDepend }, Unit) } - return Pair(libraries.toList(), sdks.toList()) + val filteredLibraries = filterForBuiltins(libraryInfo, libraries) + + return Pair(filteredLibraries.toList(), sdks.toList()) } /** @@ -109,6 +112,27 @@ class LibraryDependenciesCacheImpl(private val project: Project) : LibraryDepend }!! } + /* + * When built-ins are created from module dependencies (as opposed to loading them from classloader) + * we must resolve Kotlin standard library containing some of the built-ins declarations in the same + * resolver for project as JDK. This comes from the following requirements: + * - JvmBuiltins need JDK and standard library descriptors -> resolver for project should be able to + * resolve them + * - Builtins are created in BuiltinsCache -> module descriptors should be resolved under lock of the + * SDK resolver to prevent deadlocks + * This means we have to maintain dependencies of the standard library manually or effectively drop + * resolver for SDK otherwise. Libraries depend on superset of their actual dependencies because of + * the inability to get real dependencies from IDEA model. So moving stdlib with all dependencies + * down is a questionable option. + */ + private fun filterForBuiltins(libraryInfo: LibraryInfo, dependencyLibraries: Set): Set { + return if (!IdeBuiltInsLoadingState.isFromClassLoader && libraryInfo.isCoreKotlinLibrary(project)) { + dependencyLibraries.filterTo(mutableSetOf()) { it.isCoreKotlinLibrary(project) } + } else { + dependencyLibraries + } + } + private inner class LibraryUsageIndex { private val modulesLibraryIsUsedIn: MultiMap = MultiMap.createSet() diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ResolutionAnchorCacheService.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ResolutionAnchorCacheService.kt index 269c9dcbcea..ee1242d64d4 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ResolutionAnchorCacheService.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ResolutionAnchorCacheService.kt @@ -13,6 +13,7 @@ import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.util.containers.ContainerUtil import com.intellij.util.xmlb.XmlSerializerUtil +import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.caches.project.cacheByClassInvalidatingOnRootModifications import org.jetbrains.kotlin.idea.caches.project.LibraryDependenciesCache @@ -60,6 +61,11 @@ class ResolutionAnchorCacheServiceImpl(val project: Project) : XmlSerializerUtil.copyBean(state, myState) } + @TestOnly + fun setAnchors(mapping: Map) { + myState = State(mapping) + } + object ResolutionAnchorMappingCacheKey object ResolutionAnchorDependenciesCacheKey diff --git a/idea/testData/multiModuleHighlighting/resolutionAnchorsAndBuiltins/anchor/.keep b/idea/testData/multiModuleHighlighting/resolutionAnchorsAndBuiltins/anchor/.keep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/multiModuleHighlighting/resolutionAnchorsAndBuiltins/compositeLibraryPart/LibraryClass.kt b/idea/testData/multiModuleHighlighting/resolutionAnchorsAndBuiltins/compositeLibraryPart/LibraryClass.kt new file mode 100644 index 00000000000..a3cab8db5a0 --- /dev/null +++ b/idea/testData/multiModuleHighlighting/resolutionAnchorsAndBuiltins/compositeLibraryPart/LibraryClass.kt @@ -0,0 +1 @@ +class LibraryClass diff --git a/idea/testData/multiModuleHighlighting/resolutionAnchorsAndBuiltins/dependencyModule/DependencyClass.kt b/idea/testData/multiModuleHighlighting/resolutionAnchorsAndBuiltins/dependencyModule/DependencyClass.kt new file mode 100644 index 00000000000..e8ad157aaab --- /dev/null +++ b/idea/testData/multiModuleHighlighting/resolutionAnchorsAndBuiltins/dependencyModule/DependencyClass.kt @@ -0,0 +1 @@ +class DependencyClass diff --git a/idea/testData/multiModuleHighlighting/resolutionAnchorsAndBuiltins/sourceDependentLibrary/SourceDependentClass.kt b/idea/testData/multiModuleHighlighting/resolutionAnchorsAndBuiltins/sourceDependentLibrary/SourceDependentClass.kt new file mode 100644 index 00000000000..7ae1623fe02 --- /dev/null +++ b/idea/testData/multiModuleHighlighting/resolutionAnchorsAndBuiltins/sourceDependentLibrary/SourceDependentClass.kt @@ -0,0 +1 @@ +class SourceDependentClass diff --git a/idea/testData/multiModuleHighlighting/resolutionAnchorsAndBuiltins/sourceModule/changed.kt b/idea/testData/multiModuleHighlighting/resolutionAnchorsAndBuiltins/sourceModule/changed.kt new file mode 100644 index 00000000000..ea3e012e433 --- /dev/null +++ b/idea/testData/multiModuleHighlighting/resolutionAnchorsAndBuiltins/sourceModule/changed.kt @@ -0,0 +1 @@ +class Test1 diff --git a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt index 163cbc21169..35ef1c7b3da 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.idea.caches.resolve import com.intellij.facet.FacetManager +import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.module.Module import com.intellij.openapi.projectRoots.Sdk @@ -20,15 +21,13 @@ import org.jetbrains.kotlin.analyzer.ResolverForModuleComputationTracker import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime -import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.LanguageVersion import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo import org.jetbrains.kotlin.idea.caches.project.SdkInfo import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener import org.jetbrains.kotlin.idea.caches.trackers.KotlinModuleOutOfCodeBlockModificationTracker -import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder -import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerSettings import org.jetbrains.kotlin.idea.completion.test.withServiceRegistered +import org.jetbrains.kotlin.idea.core.util.toPsiFile import org.jetbrains.kotlin.idea.facet.KotlinFacetConfiguration import org.jetbrains.kotlin.idea.facet.KotlinFacetType import org.jetbrains.kotlin.idea.framework.JSLibraryKind @@ -37,12 +36,17 @@ import org.jetbrains.kotlin.idea.test.allKotlinFiles import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.projectStructure.sdk +import org.jetbrains.kotlin.idea.util.sourceRoots +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.samWithReceiver.SamWithReceiverCommandLineProcessor.Companion.ANNOTATION_OPTION import org.jetbrains.kotlin.samWithReceiver.SamWithReceiverCommandLineProcessor.Companion.PLUGIN_ID import org.jetbrains.kotlin.test.* import org.jetbrains.kotlin.test.TestJdkKind.FULL_JDK import org.junit.Assert.assertNotEquals import org.junit.runner.RunWith +import java.util.concurrent.ThreadLocalRandom +import kotlin.math.absoluteValue @WithMutedInDatabaseRunTest @RunWith(JUnit3WithIdeaConfigurationRunner::class) @@ -258,6 +262,49 @@ open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() { checkHighlightingInProject() } + fun testResolutionAnchorsAndBuiltins() { + val jarForCompositeLibrary = MockLibraryUtilExt.compileJvmLibraryToJar( + sourcesPath = "$testDataPath${getTestName(true)}/compositeLibraryPart", + jarName = "compositeLibraryPart" + ) + val stdlibJarForCompositeLibrary = ForTestCompileRuntime.runtimeJarForTests() + val jarForSourceDependentLibrary = MockLibraryUtilExt.compileJvmLibraryToJar( + sourcesPath = "$testDataPath${getTestName(true)}/sourceDependentLibrary", + jarName = "sourceDependentLibrary" + ) + + val dependencyModule = module("dependencyModule") + val anchorModule = module("anchor") + val sourceModule = module("sourceModule") + + val sourceDependentLibraryName = "sourceDependentLibrary" + sourceModule.addMultiJarLibrary(listOf(stdlibJarForCompositeLibrary, jarForCompositeLibrary), "compositeLibrary") + sourceModule.addLibrary(jarForSourceDependentLibrary, sourceDependentLibraryName) + anchorModule.addDependency(dependencyModule) + + val anchorMapping = mapOf(sourceDependentLibraryName to anchorModule.name) + + withResolutionAnchors(anchorMapping) { + checkHighlightingInProject() + dependencyModule.modifyTheOnlySourceFile() + checkHighlightingInProject() + } + } + + private fun Module.modifyTheOnlySourceFile() { + val sourceRoot = sourceRoots.singleOrNull() ?: error("Expected single source root in a test module") + assert(sourceRoot.isDirectory) { "Source root of a test module is not a directory" } + val ktFile = sourceRoot.children.singleOrNull()?.toPsiFile(project) as? KtFile + ?: error("Expected single .kt file in a test source module") + + val stubFunctionName = "fn${System.currentTimeMillis()}_${ThreadLocalRandom.current().nextInt().toLong().absoluteValue}" + WriteCommandAction.runWriteCommandAction(project) { + ktFile.add( + KtPsiFactory(project).createFunction("fun $stubFunctionName() {}") + ) + } + } + private fun Module.setupKotlinFacet(configure: KotlinFacetConfiguration.() -> Unit) = apply { runWriteAction { val facet = FacetManager.getInstance(this).addFacet(KotlinFacetType.INSTANCE, KotlinFacetType.NAME, null) diff --git a/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt b/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt index 0a46a4ef891..674c0590006 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt @@ -27,15 +27,18 @@ import com.intellij.util.ThrowableRunnable import org.jetbrains.kotlin.config.CompilerSettings import org.jetbrains.kotlin.config.KotlinFacetSettings import org.jetbrains.kotlin.config.KotlinFacetSettingsProvider -import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.idea.caches.resolve.ResolutionAnchorCacheService +import org.jetbrains.kotlin.idea.caches.resolve.ResolutionAnchorCacheServiceImpl import org.jetbrains.kotlin.idea.facet.getOrCreateFacet import org.jetbrains.kotlin.idea.facet.initializeIfNeeded +import org.jetbrains.kotlin.idea.project.KotlinLibraryToSourceAnalysisComponent import org.jetbrains.kotlin.idea.test.* import org.jetbrains.kotlin.platform.TargetPlatform import org.jetbrains.kotlin.test.TestJdkKind import org.jetbrains.kotlin.test.WithMutedInDatabaseRunTest import org.jetbrains.kotlin.test.runTest import org.jetbrains.kotlin.test.util.KtTestUtil +import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.junit.Assert import java.io.File @@ -111,10 +114,19 @@ abstract class AbstractMultiModuleTest : DaemonAnalyzerTestCase() { jar: File, name: String = KotlinJdkAndLibraryProjectDescriptor.LIBRARY_NAME, kind: PersistentLibraryKind<*>? = null + ) = addMultiJarLibrary(listOf(jar), name, kind) + + fun Module.addMultiJarLibrary( + jars: Collection, + name: String = KotlinJdkAndLibraryProjectDescriptor.LIBRARY_NAME, + kind: PersistentLibraryKind<*>? = null, ) { + assert(jars.isNotEmpty()) { "No JARs passed for a library" } ConfigLibraryUtil.addLibrary(NewLibraryEditor().apply { this.name = name - addRoot(VfsUtil.getUrlForLibraryRoot(jar), OrderRootType.CLASSES) + for (jar in jars) { + addRoot(VfsUtil.getUrlForLibraryRoot(jar), OrderRootType.CLASSES) + } }, this, kind) } @@ -149,6 +161,26 @@ abstract class AbstractMultiModuleTest : DaemonAnalyzerTestCase() { } Assert.assertTrue(atLeastOneFile) } + + protected fun withResolutionAnchors( + anchors: Map, + block: () -> Unit + ) { + val resolutionAnchorService = ResolutionAnchorCacheService.getInstance(project).safeAs() + ?: error("Anchor service missing") + val oldResolutionAnchorMappingState = resolutionAnchorService.state + val oldLibraryToSourceAnalysisState = KotlinLibraryToSourceAnalysisComponent.isEnabled(project) + + resolutionAnchorService.setAnchors(anchors) + KotlinLibraryToSourceAnalysisComponent.setState(project, isEnabled = true) + + try { + block() + } finally { + KotlinLibraryToSourceAnalysisComponent.setState(project, isEnabled = oldLibraryToSourceAnalysisState) + resolutionAnchorService.loadState(oldResolutionAnchorMappingState) + } + } } fun Module.createFacet(