[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
This commit is contained in:
Dmitry Savvinov
2019-06-17 13:44:23 +03:00
parent adb0f5eaf9
commit f9b8ab3a3a
6 changed files with 295 additions and 14 deletions
@@ -53,7 +53,7 @@ abstract class PlatformDependentAnalyzerServices {
)
)
protected abstract fun computePlatformSpecificDefaultImports(storageManager: StorageManager, result: MutableList<ImportPath>)
abstract fun computePlatformSpecificDefaultImports(storageManager: StorageManager, result: MutableList<ImportPath>)
open val excludedImports: List<FqName> get() = emptyList()
@@ -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)
@@ -100,7 +100,7 @@ abstract class PlatformConfiguratorBase(
container.useImpl<ExperimentalMarkerDeclarationAnnotationChecker>()
}
private fun configureExtensionsAndCheckers(container: StorageComponentContainer) {
fun configureExtensionsAndCheckers(container: StorageComponentContainer) {
with(container) {
useInstanceIfNotNull(dynamicTypesSettings)
additionalDeclarationCheckers.forEach { useInstance(it) }
@@ -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 <M : ModuleInfo> createResolverForModule(
moduleDescriptor: ModuleDescriptorImpl,
moduleContext: ModuleContext,
moduleContent: ModuleContent<M>,
resolverForProject: ResolverForProject<M>,
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<JvmPlatform>() || 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<JavaDescriptorResolver>()
}
val packagePartProvider = jvmAnalysisParameters.packagePartProviderFactory(moduleContent)
val container = createContainerForCompositePlatform(
moduleContext, moduleContentScope, languageVersionSettings, targetPlatform,
compilerServices, trace, declarationProviderFactory, metadataPartProvider,
moduleClassResolver, packagePartProvider
)
val packageFragmentProviders = sequence {
yield(container.get<ResolveSession>().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<PackageFragmentProvider> =
if (targetPlatform.isCommon()) listOf(container.get<MetadataPackageFragmentProvider>()) else emptyList()
private fun getJvmProvidersIfAny(container: StorageComponentContainer): List<PackageFragmentProvider> =
if (targetPlatform.has<JvmPlatform>()) listOf(container.get<JavaDescriptorResolver>().packageFragmentProvider) else emptyList()
private fun getKonanProvidersIfAny(moduleInfo: ModuleInfo, container: StorageComponentContainer): List<PackageFragmentProvider> {
if (!targetPlatform.has<KonanPlatform>()) return emptyList()
val resolution = KonanPlatforms.defaultKonanPlatform.idePlatformKind.resolution
val konanProvider = resolution.createPlatformSpecificPackageFragmentProvider(
moduleInfo,
container.get<StorageManager>(),
container.get<LanguageVersionSettings>(),
container.get<ModuleDescriptor>()
) ?: return emptyList()
return listOf(konanProvider)
}
private fun getJsProvidersIfAny(
moduleInfo: ModuleInfo,
moduleContext: ModuleContext,
moduleDescriptor: ModuleDescriptorImpl,
container: StorageComponentContainer
): List<PackageFragmentProvider> {
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<JvmPlatform>()) {
configureJavaSpecificComponents(
moduleContext, moduleClassResolver!!, languageVersionSettings, configureJavaClassFinder = null,
javaClassTracker = null,
useBuiltInsProvider = false
)
}
// Common-specific
if (targetPlatform.isCommon()) {
configureCommonSpecificComponents()
}
IdeaEnvironment.configure(this)
}.apply {
if (targetPlatform.has<JvmPlatform>()) {
initializeJavaSpecificComponents(trace)
}
}
}
class CompositeAnalyzerServices(val services: List<PlatformDependentAnalyzerServices>) : PlatformDependentAnalyzerServices() {
override val platformConfigurator: PlatformConfigurator = CompositePlatformConigurator(services.map { it.platformConfigurator })
override fun computePlatformSpecificDefaultImports(storageManager: StorageManager, result: MutableList<ImportPath>) {
val intersectionOfDefaultImports = services.map { service ->
mutableListOf<ImportPath>()
.apply { service.computePlatformSpecificDefaultImports(storageManager, this) }
.toSet()
}.safeIntersect()
result.addAll(intersectionOfDefaultImports)
}
override val defaultLowPriorityImports: List<ImportPath> = services.map { it.defaultLowPriorityImports.toSet() }.safeIntersect()
override val excludedImports: List<FqName> = services.map { it.excludedImports.toSet() }.safeUnion()
private fun <T> List<Set<T>>.safeUnion(): List<T> =
if (isEmpty()) emptyList() else reduce { first, second -> first.union(second) }.toList()
private fun <T> List<Set<T>>.safeIntersect(): List<T> =
if (isEmpty()) emptyList() else reduce { first, second -> first.intersect(second) }.toList()
}
class CompositePlatformConigurator(private val componentConfigurators: List<PlatformConfigurator>) : 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<ExperimentalMarkerDeclarationAnnotationChecker>()
}
}
@@ -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,
@@ -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")
}
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")
}