From 7fe9b99087f6c1a038bb974258a243f8a21f0a89 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Tue, 11 Apr 2017 19:25:42 +0300 Subject: [PATCH] Support different target platforms for modules in AnalyzerFacade Instead of reusing the same AnalyzerFacade that is used for resolution of a module to resolve its dependencies, analyze each dependency module/library with a facade depending on its target platform. Introduce and use CommonLibraryDetectionUtil in addition to KotlinJavaScriptLibraryDetectionUtil, to detect common libraries (with .kotlin_metadata files). Note that before multi-platform projects, this was not needed because there were only two platforms (JVM and JS), and JVM module had only JVM modules/libraries as dependencies, JS module had only JS modules/libraries as dependencies. Now, for example, a JVM module can have a common module/library as a dependency, and it would be incorrect to analyze that dependency with JvmAnalyzerFacade because that facade does not know anything about .kotlin_metadata files. The changes in Dsl.kt and KotlinCacheServiceImpl.kt are needed because PsiElement.getJavaDescriptorResolver, called from some IDE code, started to fail on a common module, because the container for a common module does not have the JavaDescriptorResolver --- .../src/org/jetbrains/kotlin/container/Dsl.kt | 7 +- .../kotlin/analyzer/AnalyzerFacade.kt | 111 +++++++++--------- .../analyzer/common/DefaultAnalyzerFacade.kt | 2 +- .../forTestCompile/ForTestCompileRuntime.java | 5 + .../resolve/lazy/lazyResolveTestUtils.kt | 5 +- .../MultiModuleJavaAnalysisCustomTest.kt | 5 +- .../kotlin/caches/resolve/JsAnalyzerFacade.kt | 2 +- .../caches/resolve/LibraryModuleInfo.kt | 4 +- .../idea/caches/resolve/IdeaModuleInfos.kt | 11 +- .../caches/resolve/KotlinCacheServiceImpl.kt | 3 +- .../caches/resolve/ModuleDependencyMapper.kt | 21 ++-- .../framework/CommonLibraryDetectionUtil.kt | 52 ++++++++ .../kotlin/idea/PluginStartupComponent.java | 2 + .../common/common.kt | 3 + .../jvm/jvm.kt | 10 ++ .../resolve/MultiModuleHighlightingTest.kt | 10 +- .../idea/stubs/AbstractMultiModuleTest.kt | 11 ++ libraries/stdlib/common/build.gradle | 11 +- 18 files changed, 202 insertions(+), 73 deletions(-) create mode 100644 idea/idea-analysis/src/org/jetbrains/kotlin/idea/framework/CommonLibraryDetectionUtil.kt create mode 100644 idea/testData/multiModuleHighlighting/multiplatform/catchHeaderExceptionInPlatformModule/common/common.kt create mode 100644 idea/testData/multiModuleHighlighting/multiplatform/catchHeaderExceptionInPlatformModule/jvm/jvm.kt diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Dsl.kt b/compiler/container/src/org/jetbrains/kotlin/container/Dsl.kt index 38e8f3efc6e..fbafb01efac 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Dsl.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Dsl.kt @@ -34,9 +34,12 @@ inline fun ComponentProvider.get(): T { } @Suppress("UNCHECKED_CAST") +fun ComponentProvider.tryGetService(request: Class): T? { + return resolve(request)?.getValue() as T? +} + fun ComponentProvider.getService(request: Class): T { - val valueDescriptor = resolve(request) ?: throw IllegalArgumentException("Unresolved service: $request") - return valueDescriptor.getValue() as T + return tryGetService(request) ?: throw IllegalArgumentException("Unresolved service: $request") } fun StorageComponentContainer.useInstance(instance: Any) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt index 753176c197c..c8fc1ae0c0b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt @@ -157,66 +157,71 @@ interface ModuleInfo { } abstract class AnalyzerFacade { - fun setupResolverForProject( - debugName: String, - projectContext: ProjectContext, - modules: Collection, - modulesContent: (M) -> ModuleContent, - platformParameters: P, - targetEnvironment: TargetEnvironment = CompilerEnvironment, - builtIns: (M) -> KotlinBuiltIns = { DefaultBuiltIns.Instance }, - delegateResolver: ResolverForProject = EmptyResolverForProject(), - packagePartProviderFactory: (M, ModuleContent) -> PackagePartProvider = { _, _ -> PackagePartProvider.Empty }, - firstDependency: M? = null, - modulePlatforms: (M) -> MultiTargetPlatform? - ): ResolverForProject { - val storageManager = projectContext.storageManager + companion object { + fun

setupResolverForProject( + debugName: String, + projectContext: ProjectContext, + modules: Collection, + analyzerFacade: (M) -> AnalyzerFacade

, + modulesContent: (M) -> ModuleContent, + platformParameters: P, + targetEnvironment: TargetEnvironment = CompilerEnvironment, + builtIns: (M) -> KotlinBuiltIns = { DefaultBuiltIns.Instance }, + delegateResolver: ResolverForProject = EmptyResolverForProject(), + packagePartProviderFactory: (M, ModuleContent) -> PackagePartProvider = { _, _ -> PackagePartProvider.Empty }, + firstDependency: M? = null, + modulePlatforms: (M) -> MultiTargetPlatform? + ): ResolverForProject { + val storageManager = projectContext.storageManager - val resolverForProject = ResolverForProjectImpl(debugName, modules.keysToMap { module -> - ModuleDescriptorImpl(module.name, storageManager, builtIns(module), modulePlatforms(module), module.capabilities) - }, delegateResolver) + val resolverForProject = ResolverForProjectImpl(debugName, modules.keysToMap { module -> + ModuleDescriptorImpl(module.name, storageManager, builtIns(module), modulePlatforms(module), module.capabilities) + }, delegateResolver) - for (module in modules) { - resolverForProject.descriptorForModule(module).setDependencies(LazyModuleDependencies( - storageManager, - computeDependencies = { - val orderedDependencies = listOfNotNull(firstDependency) + module.dependencies() - val dependenciesDescriptors = orderedDependencies.mapTo(ArrayList()) { dependencyInfo -> - resolverForProject.descriptorForModule(dependencyInfo as M) + for (module in modules) { + resolverForProject.descriptorForModule(module).setDependencies(LazyModuleDependencies( + storageManager, + computeDependencies = { + val orderedDependencies = listOfNotNull(firstDependency) + module.dependencies() + val dependenciesDescriptors = orderedDependencies.mapTo(ArrayList()) { dependencyInfo -> + resolverForProject.descriptorForModule(dependencyInfo as M) + } + module.dependencyOnBuiltIns().adjustDependencies( + resolverForProject.descriptorForModule(module).builtIns.builtInsModule, dependenciesDescriptors) + dependenciesDescriptors + }, + computeModulesWhoseInternalsAreVisible = { + module.modulesWhoseInternalsAreVisible().mapTo(LinkedHashSet()) { + resolverForProject.descriptorForModule(it as M) + } + }, + computeImplementingModules = { + if (modulePlatforms(module) != MultiTargetPlatform.Common) emptySet() + else modules + .filter { modulePlatforms(it) != MultiTargetPlatform.Common && module in it.dependencies() } + .mapTo(mutableSetOf(), resolverForProject::descriptorForModule) } - module.dependencyOnBuiltIns().adjustDependencies( - resolverForProject.descriptorForModule(module).builtIns.builtInsModule, dependenciesDescriptors) - dependenciesDescriptors - }, - computeModulesWhoseInternalsAreVisible = { - module.modulesWhoseInternalsAreVisible().mapTo(LinkedHashSet()) { resolverForProject.descriptorForModule(it as M) } - }, - computeImplementingModules = { - if (modulePlatforms(module) != MultiTargetPlatform.Common) emptySet() - else modules - .filter { modulePlatforms(it) != MultiTargetPlatform.Common && module in it.dependencies() } - .mapTo(mutableSetOf(), resolverForProject::descriptorForModule) - } - ) - ) - } - - for (module in modules) { - val descriptor = resolverForProject.descriptorForModule(module) - val computeResolverForModule = storageManager.createLazyValue { - val content = modulesContent(module) - createResolverForModule( - module, descriptor, projectContext.withModule(descriptor), modulesContent(module), - platformParameters, targetEnvironment, resolverForProject, - packagePartProviderFactory(module, content) + ) ) } - descriptor.initialize(DelegatingPackageFragmentProvider { computeResolverForModule().packageFragmentProvider }) - resolverForProject.resolverByModuleDescriptor[descriptor] = computeResolverForModule - } + for (module in modules) { + val descriptor = resolverForProject.descriptorForModule(module) + val computeResolverForModule = storageManager.createLazyValue { + val content = modulesContent(module) + analyzerFacade(module).createResolverForModule( + module, descriptor, projectContext.withModule(descriptor), modulesContent(module), + platformParameters, targetEnvironment, resolverForProject, + packagePartProviderFactory(module, content) + ) + } - return resolverForProject + descriptor.initialize(DelegatingPackageFragmentProvider { computeResolverForModule().packageFragmentProvider }) + resolverForProject.resolverByModuleDescriptor[descriptor] = computeResolverForModule + } + + return resolverForProject + } } protected abstract fun createResolverForModule( diff --git a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/common/DefaultAnalyzerFacade.kt b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/common/DefaultAnalyzerFacade.kt index 00d5997a3db..e53dbc81444 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/common/DefaultAnalyzerFacade.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/common/DefaultAnalyzerFacade.kt @@ -72,7 +72,7 @@ object DefaultAnalyzerFacade : AnalyzerFacade() { val project = files.firstOrNull()?.project ?: throw AssertionError("No files to analyze") val resolver = setupResolverForProject( "sources for metadata serializer", - ProjectContext(project), listOf(moduleInfo), + ProjectContext(project), listOf(moduleInfo), { DefaultAnalyzerFacade }, { ModuleContent(files, GlobalSearchScope.allScope(project)) }, object : PlatformAnalysisParameters {}, packagePartProviderFactory = packagePartProviderFactory, diff --git a/compiler/tests-common/org/jetbrains/kotlin/codegen/forTestCompile/ForTestCompileRuntime.java b/compiler/tests-common/org/jetbrains/kotlin/codegen/forTestCompile/ForTestCompileRuntime.java index d9f8fd87ee8..7415efdf915 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/codegen/forTestCompile/ForTestCompileRuntime.java +++ b/compiler/tests-common/org/jetbrains/kotlin/codegen/forTestCompile/ForTestCompileRuntime.java @@ -61,6 +61,11 @@ public class ForTestCompileRuntime { return assertExists(new File("dist/kotlinc/lib/kotlin-runtime-sources.jar")); } + @NotNull + public static File stdlibCommonForTests() { + return assertExists(new File("dist/common/kotlin-stdlib-common.jar")); + } + // TODO: Do not use these classes, remove them after stdlib tests are merged in the same build as the compiler @NotNull @Deprecated diff --git a/compiler/tests-common/org/jetbrains/kotlin/resolve/lazy/lazyResolveTestUtils.kt b/compiler/tests-common/org/jetbrains/kotlin/resolve/lazy/lazyResolveTestUtils.kt index 09ea201e710..ee2b00178d4 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/resolve/lazy/lazyResolveTestUtils.kt +++ b/compiler/tests-common/org/jetbrains/kotlin/resolve/lazy/lazyResolveTestUtils.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.resolve.lazy import com.intellij.openapi.project.Project import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.analyzer.AnalyzerFacade import org.jetbrains.kotlin.analyzer.ModuleContent import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.container.get @@ -35,9 +36,9 @@ fun createResolveSessionForFiles( ): ResolveSession { val projectContext = ProjectContext(project) val testModule = TestModule(addBuiltIns) - val resolverForProject = JvmAnalyzerFacade.setupResolverForProject( + val resolverForProject = AnalyzerFacade.setupResolverForProject( "test", - projectContext, listOf(testModule), + projectContext, listOf(testModule), { JvmAnalyzerFacade }, { ModuleContent(syntheticFiles, GlobalSearchScope.allScope(project)) }, JvmPlatformParameters { testModule }, modulePlatforms = { MultiTargetPlatform.Specific("JVM") } diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/MultiModuleJavaAnalysisCustomTest.kt b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/MultiModuleJavaAnalysisCustomTest.kt index 667fdeda525..44ed508cec4 100644 --- a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/MultiModuleJavaAnalysisCustomTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/MultiModuleJavaAnalysisCustomTest.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.jvm.compiler import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.search.DelegatingGlobalSearchScope import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.analyzer.AnalyzerFacade import org.jetbrains.kotlin.analyzer.ModuleContent import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.ResolverForProject @@ -65,9 +66,9 @@ class MultiModuleJavaAnalysisCustomTest : KtUsefulTestCase() { val modules = setupModules(environment, moduleDirs) val projectContext = ProjectContext(environment.project) val builtIns = JvmBuiltIns(projectContext.storageManager) - val resolverForProject = JvmAnalyzerFacade.setupResolverForProject( + val resolverForProject = AnalyzerFacade.setupResolverForProject( "test", - projectContext, modules, + projectContext, modules, { JvmAnalyzerFacade }, { module -> ModuleContent(module.kotlinFiles, module.javaFilesScope) }, JvmPlatformParameters { javaClass -> diff --git a/idea/ide-common/src/org/jetbrains/kotlin/caches/resolve/JsAnalyzerFacade.kt b/idea/ide-common/src/org/jetbrains/kotlin/caches/resolve/JsAnalyzerFacade.kt index 72ad0926fbb..24d1a1f72f3 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/caches/resolve/JsAnalyzerFacade.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/caches/resolve/JsAnalyzerFacade.kt @@ -67,7 +67,7 @@ object JsAnalyzerFacade : AnalyzerFacade() { ) var packageFragmentProvider = container.get().packageFragmentProvider - if (moduleInfo is LibraryModuleInfo && moduleInfo.isJsLibrary()) { + if (moduleInfo is LibraryModuleInfo && moduleInfo.libraryPlatform == JsPlatform) { val providers = moduleInfo.getLibraryRoots() .flatMap { KotlinJavascriptMetadataUtils.loadMetadata(it) } .filter { it.version.isCompatible() } diff --git a/idea/ide-common/src/org/jetbrains/kotlin/caches/resolve/LibraryModuleInfo.kt b/idea/ide-common/src/org/jetbrains/kotlin/caches/resolve/LibraryModuleInfo.kt index 0eb27bf6607..f0e584309cf 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/caches/resolve/LibraryModuleInfo.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/caches/resolve/LibraryModuleInfo.kt @@ -17,8 +17,10 @@ package org.jetbrains.kotlin.caches.resolve import org.jetbrains.kotlin.analyzer.ModuleInfo +import org.jetbrains.kotlin.resolve.TargetPlatform interface LibraryModuleInfo : ModuleInfo { - fun isJsLibrary(): Boolean + val libraryPlatform: TargetPlatform + fun getLibraryRoots(): Collection } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfos.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfos.kt index 8bb6d5be2fb..8e3fe25c434 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfos.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IdeaModuleInfos.kt @@ -32,11 +32,15 @@ import com.intellij.util.SmartList import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.caches.resolve.LibraryModuleInfo import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.idea.framework.CommonLibraryDetectionUtil import org.jetbrains.kotlin.idea.framework.KotlinJavaScriptLibraryDetectionUtil import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope import org.jetbrains.kotlin.idea.util.isInSourceContentWithoutInjected +import org.jetbrains.kotlin.js.resolve.JsPlatform import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.TargetPlatform import org.jetbrains.kotlin.resolve.jvm.GlobalSearchScopeWithModuleSources +import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform import java.util.* interface IdeaModuleInfo : ModuleInfo { @@ -200,7 +204,12 @@ class LibraryInfo(val project: Project, val library: Library) : IdeaModuleInfo, return result.toList() } - override fun isJsLibrary(): Boolean = KotlinJavaScriptLibraryDetectionUtil.isKotlinJavaScriptLibrary(library) + override val libraryPlatform: TargetPlatform + get() = when { + KotlinJavaScriptLibraryDetectionUtil.isKotlinJavaScriptLibrary(library) -> JsPlatform + CommonLibraryDetectionUtil.isCommonLibrary(library) -> TargetPlatform.Default + else -> JvmPlatform + } override val sourcesModuleInfo: SourceForBinaryModuleInfo get() = LibrarySourceInfo(project, library) diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/KotlinCacheServiceImpl.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/KotlinCacheServiceImpl.kt index 6bf41406943..b0a89abc085 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/KotlinCacheServiceImpl.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/KotlinCacheServiceImpl.kt @@ -32,6 +32,7 @@ import org.jetbrains.kotlin.analyzer.EmptyResolverForProject import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.caches.resolve.KotlinCacheService import org.jetbrains.kotlin.container.getService +import org.jetbrains.kotlin.container.tryGetService import org.jetbrains.kotlin.context.GlobalContext import org.jetbrains.kotlin.context.GlobalContextImpl import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor @@ -164,7 +165,7 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService { } fun tryGetProjectService(platform: TargetPlatform, ideaModuleInfo: IdeaModuleInfo, serviceClass: Class): T? { - return globalFacade(platform, ideaModuleInfo.sdk).tryGetResolverForModuleInfo(ideaModuleInfo)?.componentProvider?.getService(serviceClass) + return globalFacade(platform, ideaModuleInfo.sdk).tryGetResolverForModuleInfo(ideaModuleInfo)?.componentProvider?.tryGetService(serviceClass) } private fun globalFacade(platform: TargetPlatform, sdk: Sdk?) = diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ModuleDependencyMapper.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ModuleDependencyMapper.kt index 6f43ea8c1bb..f7503a05c46 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ModuleDependencyMapper.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ModuleDependencyMapper.kt @@ -23,10 +23,12 @@ import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.roots.JdkOrderEntry import com.intellij.openapi.roots.LibraryOrderEntry import com.intellij.openapi.roots.ModuleRootManager +import org.jetbrains.kotlin.analyzer.AnalyzerFacade import org.jetbrains.kotlin.analyzer.ModuleContent import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.ResolverForProject import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.caches.resolve.LibraryModuleInfo import org.jetbrains.kotlin.context.GlobalContextImpl import org.jetbrains.kotlin.context.withProject import org.jetbrains.kotlin.idea.project.AnalyzerFacadeProvider @@ -78,15 +80,13 @@ fun createModuleResolverProvider( psiClass.getNullableModuleInfo() } - return AnalyzerFacadeProvider.getAnalyzerFacade(platform).setupResolverForProject( - debugName, globalContext.withProject(project), modulesToCreateResolversFor, modulesContent, - jvmPlatformParameters, IdeaEnvironment, builtInsProvider, + return AnalyzerFacade.setupResolverForProject( + debugName, globalContext.withProject(project), modulesToCreateResolversFor, + { module -> AnalyzerFacadeProvider.getAnalyzerFacade(module.getTargetPlatform() ?: platform) }, + modulesContent, jvmPlatformParameters, IdeaEnvironment, builtInsProvider, delegateResolver, { _, c -> IDEPackagePartProvider(c.moduleContentScope) }, sdk?.let { SdkInfo(project, it) }, - modulePlatforms = { moduleInfo -> - val module = (moduleInfo as? ModuleSourceInfo)?.module - module?.let { TargetPlatformDetector.getPlatform(module).multiTargetPlatform } - } + modulePlatforms = { module -> module.getTargetPlatform()?.multiTargetPlatform } ) } @@ -105,6 +105,13 @@ fun createModuleResolverProvider( ) } +private fun ModuleInfo.getTargetPlatform(): TargetPlatform? = + when (this) { + is ModuleSourceInfo -> TargetPlatformDetector.getPlatform(module) + is LibraryModuleInfo -> libraryPlatform + else -> null + } + fun collectAllModuleInfosFromIdeaModel(project: Project): List { val ideaModules = ModuleManager.getInstance(project).modules.toList() val modulesSourcesInfos = ideaModules.flatMap { listOf(it.productionSourceInfo(), it.testSourceInfo()) } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/framework/CommonLibraryDetectionUtil.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/framework/CommonLibraryDetectionUtil.kt new file mode 100644 index 00000000000..3bce68ca08c --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/framework/CommonLibraryDetectionUtil.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.framework + +import com.intellij.openapi.roots.OrderRootType +import com.intellij.openapi.roots.impl.libraries.LibraryEx +import com.intellij.openapi.roots.libraries.Library +import com.intellij.openapi.vfs.VfsUtilCore +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.idea.caches.JarUserDataManager +import org.jetbrains.kotlin.serialization.deserialization.MetadataPackageFragment + +object CommonLibraryDetectionUtil { + fun isCommonLibrary(library: Library): Boolean { + if (library is LibraryEx && library.isDisposed) return false + + for (root in library.getFiles(OrderRootType.CLASSES)) { + val hasMetadata = JarUserDataManager.hasFileWithProperty(HasCommonKotlinMetadataInJar, root) + if (hasMetadata != null) { + return hasMetadata + } + + if (!VfsUtilCore.processFilesRecursively(root) { !isKotlinMetadataFile(it) }) { + return true + } + } + + return false + } + + private fun isKotlinMetadataFile(file: VirtualFile): Boolean = + !file.isDirectory && + file.extension == MetadataPackageFragment.METADATA_FILE_EXTENSION + + object HasCommonKotlinMetadataInJar : JarUserDataManager.JarBooleanPropertyCounter(HasCommonKotlinMetadataInJar::class.simpleName!!) { + override fun hasProperty(file: VirtualFile) = isKotlinMetadataFile(file) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/PluginStartupComponent.java b/idea/src/org/jetbrains/kotlin/idea/PluginStartupComponent.java index 205ef7dbe66..5b16fa73471 100644 --- a/idea/src/org/jetbrains/kotlin/idea/PluginStartupComponent.java +++ b/idea/src/org/jetbrains/kotlin/idea/PluginStartupComponent.java @@ -29,6 +29,7 @@ import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.idea.caches.JarUserDataManager; import org.jetbrains.kotlin.idea.debugger.filter.DebuggerFiltersUtilKt; +import org.jetbrains.kotlin.idea.framework.CommonLibraryDetectionUtil; import org.jetbrains.kotlin.idea.framework.KotlinJavaScriptLibraryDetectionUtil; import org.jetbrains.kotlin.utils.PathUtil; @@ -59,6 +60,7 @@ public class PluginStartupComponent implements ApplicationComponent { } JarUserDataManager.INSTANCE.register(KotlinJavaScriptLibraryDetectionUtil.HasKotlinJSMetadataInJar.INSTANCE); + JarUserDataManager.INSTANCE.register(CommonLibraryDetectionUtil.HasCommonKotlinMetadataInJar.INSTANCE); DebuggerFiltersUtilKt.addKotlinStdlibDebugFilterIfNeeded(); diff --git a/idea/testData/multiModuleHighlighting/multiplatform/catchHeaderExceptionInPlatformModule/common/common.kt b/idea/testData/multiModuleHighlighting/multiplatform/catchHeaderExceptionInPlatformModule/common/common.kt new file mode 100644 index 00000000000..1a0f1ae1a85 --- /dev/null +++ b/idea/testData/multiModuleHighlighting/multiplatform/catchHeaderExceptionInPlatformModule/common/common.kt @@ -0,0 +1,3 @@ +package test + +class MyException : Exception() diff --git a/idea/testData/multiModuleHighlighting/multiplatform/catchHeaderExceptionInPlatformModule/jvm/jvm.kt b/idea/testData/multiModuleHighlighting/multiplatform/catchHeaderExceptionInPlatformModule/jvm/jvm.kt new file mode 100644 index 00000000000..15add7ed334 --- /dev/null +++ b/idea/testData/multiModuleHighlighting/multiplatform/catchHeaderExceptionInPlatformModule/jvm/jvm.kt @@ -0,0 +1,10 @@ +package test + +fun test(f: () -> Unit) { + try { + f() + } + catch (e: MyException) { + f() + } +} 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 8ce3691e8e0..14111386d6b 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.idea.caches.resolve import com.intellij.openapi.roots.DependencyScope +import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime import org.jetbrains.kotlin.config.JvmTarget import org.jetbrains.kotlin.config.TargetPlatformKind @@ -64,9 +65,12 @@ class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() { class MultiPlatform : AbstractMultiModuleHighlightingTest() { override val testPath get() = super.testPath + "multiplatform/" - private fun doTest(vararg platforms: TargetPlatformKind<*>) { + private fun doTest(vararg platforms: TargetPlatformKind<*>, withStdlibCommon: Boolean = false) { val commonModule = module("common") commonModule.createFacet(TargetPlatformKind.Common) + if (withStdlibCommon) { + commonModule.addLibrary(ForTestCompileRuntime.stdlibCommonForTests()) + } for (platform in platforms) { val path = when (platform) { @@ -106,5 +110,9 @@ class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() { fun testSuppressHeaderWithoutImpl() { doTest(TargetPlatformKind.Jvm[JvmTarget.JVM_1_6]) } + + fun testCatchHeaderExceptionInPlatformModule() { + doTest(TargetPlatformKind.Jvm[JvmTarget.JVM_1_6], withStdlibCommon = true) + } } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt b/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt index 57a823e9157..3f6c5c627fc 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt @@ -24,8 +24,11 @@ import com.intellij.openapi.module.Module import com.intellij.openapi.module.StdModuleTypes import com.intellij.openapi.roots.DependencyScope import com.intellij.openapi.roots.ModuleRootModificationUtil +import com.intellij.openapi.roots.OrderRootType +import com.intellij.openapi.roots.ui.configuration.libraryEditor.NewLibraryEditor import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.VfsUtil import com.intellij.testFramework.PsiTestUtil import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments import org.jetbrains.kotlin.cli.common.parser.com.sampullara.cli.Argument @@ -35,6 +38,7 @@ import org.jetbrains.kotlin.config.TargetPlatformKind import org.jetbrains.kotlin.idea.facet.getOrCreateFacet import org.jetbrains.kotlin.idea.facet.initializeIfNeeded import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil +import org.jetbrains.kotlin.idea.test.KotlinJdkAndLibraryProjectDescriptor import org.jetbrains.kotlin.idea.test.PluginTestCaseBase import java.io.File @@ -75,6 +79,13 @@ abstract class AbstractMultiModuleTest : DaemonAnalyzerTestCase() { exported: Boolean = false ) = ModuleRootModificationUtil.addDependency(this, other, dependencyScope, exported) + protected fun Module.addLibrary(jar: File) { + ConfigLibraryUtil.addLibrary(NewLibraryEditor().apply { + name = KotlinJdkAndLibraryProjectDescriptor.LIBRARY_NAME + addRoot(VfsUtil.getUrlForLibraryRoot(jar), OrderRootType.CLASSES) + }, this) + } + protected fun Module.createFacet(platformKind: TargetPlatformKind<*>? = null) { val accessToken = WriteAction.start() try { diff --git a/libraries/stdlib/common/build.gradle b/libraries/stdlib/common/build.gradle index 84759eb3530..09b85a4fe02 100644 --- a/libraries/stdlib/common/build.gradle +++ b/libraries/stdlib/common/build.gradle @@ -43,4 +43,13 @@ task sourcesJar(type: Jar, dependsOn: classes) { artifacts { archives sourcesJar archives javadocJar -} \ No newline at end of file +} + +// TODO: call the "dist" task instead, once we need to publish kotlin-stdlib-common.jar with the compiler distribution +task distCommon(type: Copy) { + from(jar) + into "$distDir/common" + rename "-${java.util.regex.Pattern.quote(version)}", '' +} + +dist.dependsOn distCommon