From f9b8ab3a3a09e061814ad659962913efdfc3de9e Mon Sep 17 00:00:00 2001 From: Dmitry Savvinov Date: Mon, 17 Jun 2019 13:44:23 +0300 Subject: [PATCH] [Resolve] Introduce CompositeResolver This commit introduces CompositeResolverForModuleFactory, which should work under so-called "composite resolution mode", where sources of all all modules are analyzed in one global facade. This allows to: - avoid re-analyzation of common sources - avoid retaining memory for all platforms (which can be very bad as soon as we'll start distinguishing various flavours of platforms, especially "flavours" of common platform) - support running platform-specific checks in common modules (e.g., report JVM_PLATFORM_DECLARATION_CLASH if common sources are going to have it) - support analysis of shared platform modules, like commonNative This mode heavily depends on so-called "type refinement" support in the compiler, which is introduced in other series of commits. In this commit, CompositeResolver and related codepaths are left unused. Also, this commit misses several important pieces of logic in resolvers-setup code, which should be different for CompositeResolver - computation of 'firstDependency' - computation of built-ins - computation of modules owned by facade They will be covered in the following commits --- .../PlatformDependentAnalyzerServices.kt | 2 +- .../jetbrains/kotlin/frontend/di/injection.kt | 7 +- .../resolve/PlatformConfiguratorBase.kt | 2 +- .../CompositeResolverForModuleFactory.kt | 257 ++++++++++++++++++ .../caches/resolve/ProjectResolutionFacade.kt | 15 +- .../kotlin/idea/project/analyzerServices.kt | 26 +- 6 files changed, 295 insertions(+), 14 deletions(-) create mode 100644 idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/CompositeResolverForModuleFactory.kt diff --git a/compiler/frontend.common/src/org/jetbrains/kotlin/resolve/PlatformDependentAnalyzerServices.kt b/compiler/frontend.common/src/org/jetbrains/kotlin/resolve/PlatformDependentAnalyzerServices.kt index d35c52fb9bd..1e68bb3238d 100644 --- a/compiler/frontend.common/src/org/jetbrains/kotlin/resolve/PlatformDependentAnalyzerServices.kt +++ b/compiler/frontend.common/src/org/jetbrains/kotlin/resolve/PlatformDependentAnalyzerServices.kt @@ -53,7 +53,7 @@ abstract class PlatformDependentAnalyzerServices { ) ) - protected abstract fun computePlatformSpecificDefaultImports(storageManager: StorageManager, result: MutableList) + abstract fun computePlatformSpecificDefaultImports(storageManager: StorageManager, result: MutableList) open val excludedImports: List get() = emptyList() diff --git a/compiler/frontend/src/org/jetbrains/kotlin/frontend/di/injection.kt b/compiler/frontend/src/org/jetbrains/kotlin/frontend/di/injection.kt index e0ab82b9322..2785b5e5a70 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/frontend/di/injection.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/frontend/di/injection.kt @@ -59,7 +59,12 @@ fun StorageComponentContainer.configureModule( useInstance(platform) useInstance(analyzerServices) - useInstance(platform.componentPlatforms.singleOrNull()?.targetPlatformVersion ?: TargetPlatformVersion.NoVersion) + + val nonTrivialPlatformVersion = platform + .mapNotNull { it.targetPlatformVersion.takeIf { it != TargetPlatformVersion.NoVersion } } + .singleOrNull() + + useInstance(nonTrivialPlatformVersion ?: TargetPlatformVersion.NoVersion) analyzerServices.platformConfigurator.configureModuleComponents(this) analyzerServices.platformConfigurator.configureModuleDependentCheckers(this) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt index 48bd59e60ee..84c77cd6fc5 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/PlatformConfiguratorBase.kt @@ -100,7 +100,7 @@ abstract class PlatformConfiguratorBase( container.useImpl() } - private fun configureExtensionsAndCheckers(container: StorageComponentContainer) { + fun configureExtensionsAndCheckers(container: StorageComponentContainer) { with(container) { useInstanceIfNotNull(dynamicTypesSettings) additionalDeclarationCheckers.forEach { useInstance(it) } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/CompositeResolverForModuleFactory.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/CompositeResolverForModuleFactory.kt new file mode 100644 index 00000000000..ef08bebbd91 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/CompositeResolverForModuleFactory.kt @@ -0,0 +1,257 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +@file:Suppress("Duplicates") + +package org.jetbrains.kotlin.caches.resolve + +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.analyzer.* +import org.jetbrains.kotlin.analyzer.common.CommonAnalysisParameters +import org.jetbrains.kotlin.analyzer.common.CommonPlatformAnalyzerServices +import org.jetbrains.kotlin.analyzer.common.configureCommonSpecificComponents +import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.container.* +import org.jetbrains.kotlin.context.ModuleContext +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.PackageFragmentProvider +import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider +import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl +import org.jetbrains.kotlin.frontend.di.configureModule +import org.jetbrains.kotlin.frontend.di.configureStandardResolveComponents +import org.jetbrains.kotlin.frontend.java.di.configureJavaSpecificComponents +import org.jetbrains.kotlin.frontend.java.di.initializeJavaSpecificComponents +import org.jetbrains.kotlin.idea.project.IdeaEnvironment +import org.jetbrains.kotlin.incremental.components.LookupTracker +import org.jetbrains.kotlin.load.java.lazy.ModuleClassResolver +import org.jetbrains.kotlin.load.java.lazy.ModuleClassResolverImpl +import org.jetbrains.kotlin.load.kotlin.PackagePartProvider +import org.jetbrains.kotlin.load.kotlin.VirtualFileFinderFactory +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.platform.TargetPlatform +import org.jetbrains.kotlin.platform.has +import org.jetbrains.kotlin.platform.idePlatformKind +import org.jetbrains.kotlin.platform.isCommon +import org.jetbrains.kotlin.platform.js.isJs +import org.jetbrains.kotlin.platform.jvm.JvmPlatform +import org.jetbrains.kotlin.platform.konan.KonanPlatform +import org.jetbrains.kotlin.platform.konan.KonanPlatforms +import org.jetbrains.kotlin.resolve.* +import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.ExperimentalMarkerDeclarationAnnotationChecker +import org.jetbrains.kotlin.resolve.jvm.JavaDescriptorResolver +import org.jetbrains.kotlin.resolve.jvm.JvmPlatformParameters +import org.jetbrains.kotlin.resolve.lazy.ResolveSession +import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactory +import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactoryService +import org.jetbrains.kotlin.serialization.deserialization.MetadataPackageFragmentProvider +import org.jetbrains.kotlin.serialization.deserialization.MetadataPartProvider +import org.jetbrains.kotlin.serialization.js.KotlinJavascriptSerializationUtil +import org.jetbrains.kotlin.serialization.js.createKotlinJavascriptPackageFragmentProvider +import org.jetbrains.kotlin.storage.StorageManager +import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils + +class CompositeResolverForModuleFactory( + private val commonAnalysisParameters: CommonAnalysisParameters, + private val jvmAnalysisParameters: JvmPlatformParameters, + private val targetPlatform: TargetPlatform, + private val compilerServices: CompositeAnalyzerServices +) : ResolverForModuleFactory() { + override fun createResolverForModule( + moduleDescriptor: ModuleDescriptorImpl, + moduleContext: ModuleContext, + moduleContent: ModuleContent, + resolverForProject: ResolverForProject, + languageVersionSettings: LanguageVersionSettings + ): ResolverForModule { + val (moduleInfo, syntheticFiles, moduleContentScope) = moduleContent + val project = moduleContext.project + val declarationProviderFactory = DeclarationProviderFactoryService.createDeclarationProviderFactory( + project, moduleContext.storageManager, syntheticFiles, + moduleContentScope, + moduleInfo + ) + + val metadataPartProvider = commonAnalysisParameters.metadataPartProviderFactory(moduleContent) + val trace = CodeAnalyzerInitializer.getInstance(project).createTrace() + + + val moduleClassResolver = ModuleClassResolverImpl { javaClass -> + val referencedClassModule = jvmAnalysisParameters.moduleByJavaClass(javaClass) + // We don't have full control over idea resolve api so we allow for a situation which should not happen in Kotlin. + // For example, type in a java library can reference a class declared in a source root (is valid but rare case) + // Providing a fallback strategy in this case can hide future problems, so we should at least log to be able to diagnose those + + @Suppress("UNCHECKED_CAST") + val resolverForReferencedModule = referencedClassModule?.let { resolverForProject.tryGetResolverForModule(it as M) } + + val resolverForModule = resolverForReferencedModule?.takeIf { + referencedClassModule.platform.has() || referencedClassModule.platform == null + } ?: run { + // in case referenced class lies outside of our resolver, resolve the class as if it is inside our module + // this leads to java class being resolved several times + resolverForProject.resolverForModule(moduleInfo) + } + resolverForModule.componentProvider.get() + } + + val packagePartProvider = jvmAnalysisParameters.packagePartProviderFactory(moduleContent) + + val container = createContainerForCompositePlatform( + moduleContext, moduleContentScope, languageVersionSettings, targetPlatform, + compilerServices, trace, declarationProviderFactory, metadataPartProvider, + moduleClassResolver, packagePartProvider + ) + + val packageFragmentProviders = sequence { + yield(container.get().packageFragmentProvider) + + yieldAll(getCommonProvidersIfAny(container)) + yieldAll(getJsProvidersIfAny(moduleInfo, moduleContext, moduleDescriptor, container)) + yieldAll(getJvmProvidersIfAny(container)) + yieldAll(getKonanProvidersIfAny(moduleInfo, container)) + }.toList() + + return ResolverForModule(CompositePackageFragmentProvider(packageFragmentProviders), container) + } + + private fun getCommonProvidersIfAny(container: StorageComponentContainer): List = + if (targetPlatform.isCommon()) listOf(container.get()) else emptyList() + + private fun getJvmProvidersIfAny(container: StorageComponentContainer): List = + if (targetPlatform.has()) listOf(container.get().packageFragmentProvider) else emptyList() + + private fun getKonanProvidersIfAny(moduleInfo: ModuleInfo, container: StorageComponentContainer): List { + if (!targetPlatform.has()) return emptyList() + val resolution = KonanPlatforms.defaultKonanPlatform.idePlatformKind.resolution + + val konanProvider = resolution.createPlatformSpecificPackageFragmentProvider( + moduleInfo, + container.get(), + container.get(), + container.get() + ) ?: return emptyList() + + return listOf(konanProvider) + } + + private fun getJsProvidersIfAny( + moduleInfo: ModuleInfo, + moduleContext: ModuleContext, + moduleDescriptor: ModuleDescriptorImpl, + container: StorageComponentContainer + ): List { + if (moduleInfo !is LibraryModuleInfo || !moduleInfo.platform.isJs()) return emptyList() + + return moduleInfo.getLibraryRoots() + .flatMap { KotlinJavascriptMetadataUtils.loadMetadata(it) } + .filter { it.version.isCompatible() } + .map { metadata -> + val (header, packageFragmentProtos) = + KotlinJavascriptSerializationUtil.readModuleAsProto(metadata.body, metadata.version) + createKotlinJavascriptPackageFragmentProvider( + moduleContext.storageManager, moduleDescriptor, header, packageFragmentProtos, metadata.version, + container.get(), LookupTracker.DO_NOTHING + ) + } + } + + fun createContainerForCompositePlatform( + moduleContext: ModuleContext, + moduleContentScope: GlobalSearchScope, + languageVersionSettings: LanguageVersionSettings, + targetPlatform: TargetPlatform, + analyzerServices: CompositeAnalyzerServices, + trace: BindingTrace, + declarationProviderFactory: DeclarationProviderFactory, + metadataPartProvider: MetadataPartProvider, + // Guaranteed to be non-null for modules with JVM + moduleClassResolver: ModuleClassResolver?, + packagePartProvider: PackagePartProvider? + ): StorageComponentContainer = composeContainer("CompositePlatform") { + // Shared by all PlatformConfigurators + configureDefaultCheckers() + + // Specific for each PlatformConfigurator + for (configurator in analyzerServices.services.map { it.platformConfigurator as PlatformConfiguratorBase }) { + configurator.configureExtensionsAndCheckers(this) + } + + // Called by all normal containers set-ups + configureModule(moduleContext, targetPlatform, analyzerServices, trace, languageVersionSettings) + configureStandardResolveComponents() + useInstance(moduleContentScope) + useInstance(declarationProviderFactory) + + // Probably, should be in StandardResolveComponents, but + useInstance(VirtualFileFinderFactory.getInstance(moduleContext.project).create(moduleContentScope)) + useInstance(packagePartProvider!!) + + // JVM-specific + if (targetPlatform.has()) { + configureJavaSpecificComponents( + moduleContext, moduleClassResolver!!, languageVersionSettings, configureJavaClassFinder = null, + javaClassTracker = null, + useBuiltInsProvider = false + ) + } + + // Common-specific + if (targetPlatform.isCommon()) { + configureCommonSpecificComponents() + } + + IdeaEnvironment.configure(this) + }.apply { + if (targetPlatform.has()) { + initializeJavaSpecificComponents(trace) + } + } +} + +class CompositeAnalyzerServices(val services: List) : PlatformDependentAnalyzerServices() { + override val platformConfigurator: PlatformConfigurator = CompositePlatformConigurator(services.map { it.platformConfigurator }) + + override fun computePlatformSpecificDefaultImports(storageManager: StorageManager, result: MutableList) { + val intersectionOfDefaultImports = services.map { service -> + mutableListOf() + .apply { service.computePlatformSpecificDefaultImports(storageManager, this) } + .toSet() + }.safeIntersect() + + result.addAll(intersectionOfDefaultImports) + } + + override val defaultLowPriorityImports: List = services.map { it.defaultLowPriorityImports.toSet() }.safeIntersect() + + override val excludedImports: List = services.map { it.excludedImports.toSet() }.safeUnion() + + private fun List>.safeUnion(): List = + if (isEmpty()) emptyList() else reduce { first, second -> first.union(second) }.toList() + + private fun List>.safeIntersect(): List = + if (isEmpty()) emptyList() else reduce { first, second -> first.intersect(second) }.toList() +} + +class CompositePlatformConigurator(private val componentConfigurators: List) : PlatformConfigurator { + // TODO(dsavvinov): this is actually a hack. Review callers of that method, think about how to refactor it + // Unfortunately, clients of that container will inject additional services into them, + // without knowing about composite platform (see LocalClassifierAnalyzer), so we can't just use [createContainerForCompositePlatform] + // here. Hopefully, platformSpecific container won't at least make things worse. + override val platformSpecificContainer: StorageComponentContainer + get() = CommonPlatformAnalyzerServices.platformConfigurator.platformSpecificContainer + + override fun configureModuleComponents(container: StorageComponentContainer) { + componentConfigurators.forEach { it.configureModuleComponents(container) } + } + + override fun configureModuleDependentCheckers(container: StorageComponentContainer) { + // We (ab)use the fact that currently, platforms don't use that method, so the only injected compnent will be + // ExperimentalMarkerDeclarationAnnotationChecker. + // Unfortunately, it is declared in base class, so repeating call to 'configureModuleDependentCheckers' will lead + // to multiple registrrations. + container.useImpl() + } +} \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ProjectResolutionFacade.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ProjectResolutionFacade.kt index 3200c5ea065..177a8641f63 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ProjectResolutionFacade.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ProjectResolutionFacade.kt @@ -27,6 +27,8 @@ import org.jetbrains.kotlin.analyzer.* import org.jetbrains.kotlin.analyzer.common.CommonAnalysisParameters import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.jvm.JvmBuiltIns +import org.jetbrains.kotlin.caches.resolve.CompositeAnalyzerServices +import org.jetbrains.kotlin.caches.resolve.CompositeResolverForModuleFactory import org.jetbrains.kotlin.caches.resolve.resolution import org.jetbrains.kotlin.context.GlobalContextImpl import org.jetbrains.kotlin.context.ProjectContext @@ -37,6 +39,8 @@ import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo import org.jetbrains.kotlin.idea.caches.project.getNullableModuleInfo import org.jetbrains.kotlin.idea.compiler.IDELanguageSettingsProvider import org.jetbrains.kotlin.idea.project.IdeaEnvironment +import org.jetbrains.kotlin.idea.project.findAnalyzerServices +import org.jetbrains.kotlin.idea.project.useCompositeAnalysis import org.jetbrains.kotlin.load.java.structure.JavaClass import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl import org.jetbrains.kotlin.platform.idePlatformKind @@ -49,6 +53,7 @@ import org.jetbrains.kotlin.resolve.CompositeBindingContext import org.jetbrains.kotlin.resolve.jvm.JvmPlatformParameters import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult import org.jetbrains.kotlin.platform.DefaultIdeTargetPlatformKindProvider +import org.jetbrains.kotlin.platform.toTargetPlatform internal class ProjectResolutionFacade( private val debugString: String, @@ -149,7 +154,15 @@ internal class ProjectResolutionFacade( else -> PlatformAnalysisParameters.Empty } - platform.idePlatformKind.resolution.createResolverForModuleFactory(parameters, IdeaEnvironment, platform) + if (!project.useCompositeAnalysis) + platform.idePlatformKind.resolution.createResolverForModuleFactory(parameters, IdeaEnvironment, platform) + else + CompositeResolverForModuleFactory( + commonPlatformParameters, + jvmPlatformParameters, + modulePlatform!!, + CompositeAnalyzerServices(modulePlatform.componentPlatforms.map { it.toTargetPlatform().findAnalyzerServices }) + ) }, builtIns = builtIns, delegateResolver = delegateResolverForProject, diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/analyzerServices.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/analyzerServices.kt index 9d9b66358dc..0c996d7ca7c 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/analyzerServices.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/analyzerServices.kt @@ -5,13 +5,14 @@ package org.jetbrains.kotlin.idea.project -import org.jetbrains.kotlin.analyzer.common.CommonPlatformAnalyzerServices +import org.jetbrains.kotlin.caches.resolve.CompositeAnalyzerServices import org.jetbrains.kotlin.js.resolve.JsPlatformAnalyzerServices +import org.jetbrains.kotlin.platform.SimplePlatform import org.jetbrains.kotlin.platform.TargetPlatform import org.jetbrains.kotlin.platform.isCommon -import org.jetbrains.kotlin.platform.js.isJs -import org.jetbrains.kotlin.platform.jvm.isJvm -import org.jetbrains.kotlin.platform.konan.isNative +import org.jetbrains.kotlin.platform.js.JsPlatform +import org.jetbrains.kotlin.platform.jvm.JvmPlatform +import org.jetbrains.kotlin.platform.konan.KonanPlatform import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatformAnalyzerServices import org.jetbrains.kotlin.resolve.konan.platform.NativePlatformAnalyzerServices @@ -20,9 +21,14 @@ import java.lang.IllegalStateException val TargetPlatform.findAnalyzerServices: PlatformDependentAnalyzerServices get() = when { - isJvm() -> JvmPlatformAnalyzerServices - isJs() -> JsPlatformAnalyzerServices - isNative() -> NativePlatformAnalyzerServices - isCommon() -> CommonPlatformAnalyzerServices - else -> throw IllegalStateException("Unknown platform $this") - } \ No newline at end of file + isCommon() -> CompositeAnalyzerServices(this.componentPlatforms.map { it.findAnalyzerServices }) + else -> single().findAnalyzerServices + } + +val SimplePlatform.findAnalyzerServices: PlatformDependentAnalyzerServices + get() = when (this) { + is JvmPlatform -> JvmPlatformAnalyzerServices + is JsPlatform -> JsPlatformAnalyzerServices + is KonanPlatform -> NativePlatformAnalyzerServices + else -> throw IllegalStateException("Unknown platform $this") + } \ No newline at end of file