From 21b32b9de59fb029ae4aebfe1d8723d25857990c Mon Sep 17 00:00:00 2001 From: "Pavel V. Talanov" Date: Thu, 11 May 2017 21:25:07 +0300 Subject: [PATCH] IDE Performance: skip resolver construction for non-relevant modules Querying non-existent packages does not trigger module resolver computation --- .../kotlin/analyzer/AnalyzerFacade.kt | 52 ++++++++++--- .../caches/resolve/ModuleDependencyMapper.kt | 4 +- .../idea/caches/resolve/packageOracles.kt | 73 +++++++++++++++++++ idea/src/META-INF/plugin.xml | 3 + 4 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/packageOracles.kt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt index 7b526767a7e..fba5511a14f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt @@ -41,13 +41,14 @@ import org.jetbrains.kotlin.resolve.CompilerEnvironment import org.jetbrains.kotlin.resolve.MultiTargetPlatform import org.jetbrains.kotlin.resolve.TargetEnvironment import org.jetbrains.kotlin.resolve.TargetPlatform +import org.jetbrains.kotlin.storage.NotNullLazyValue import org.jetbrains.kotlin.utils.keysToMap import java.util.* import kotlin.coroutines.experimental.buildSequence class ResolverForModule( - val packageFragmentProvider: PackageFragmentProvider, - val componentProvider: ComponentProvider + val packageFragmentProvider: PackageFragmentProvider, + val componentProvider: ComponentProvider ) abstract class ResolverForProject { @@ -84,7 +85,7 @@ class ResolverForProjectImpl( return resolverForModuleDescriptor(doGetDescriptorForModule(moduleInfo)) } - internal val resolverByModuleDescriptor: MutableMap ResolverForModule> = HashMap() + internal val resolverByModuleDescriptor: MutableMap> = HashMap() override val allModules: Collection by lazy { (descriptorByModule.keys + delegateResolver.allModules).toSet() @@ -168,7 +169,8 @@ abstract class AnalyzerFacade { delegateResolver: ResolverForProject = EmptyResolverForProject(), packagePartProviderFactory: (M, ModuleContent) -> PackagePartProvider = { _, _ -> PackagePartProvider.Empty }, firstDependency: M? = null, - modulePlatforms: (M) -> MultiTargetPlatform? + modulePlatforms: (M) -> MultiTargetPlatform?, + packageOracleFactory: PackageOracleFactory = PackageOracleFactory.OptimisticFactory ): ResolverForProject { val storageManager = projectContext.storageManager @@ -212,10 +214,10 @@ abstract class AnalyzerFacade { for (module in modules) { val descriptor = resolverForProject.descriptorForModule(module) + val content = modulesContent(module) val computeResolverForModule = storageManager.createLazyValue { ResolverForModuleComputationTracker.getInstance(projectContext.project)?.onResolverComputed(module) - val content = modulesContent(module) analyzerFacade(module).createResolverForModule( module, descriptor, projectContext.withModule(descriptor), modulesContent(module), platformParameters, targetEnvironment, resolverForProject, @@ -223,7 +225,9 @@ abstract class AnalyzerFacade { ) } - descriptor.initialize(DelegatingPackageFragmentProvider { computeResolverForModule().packageFragmentProvider }) + DelegatingPackageFragmentProvider(content, packageOracleFactory.createOracle(module), computeResolverForModule) + .let { descriptor.initialize(it) } + resolverForProject.resolverByModuleDescriptor[descriptor] = computeResolverForModule } @@ -245,17 +249,45 @@ abstract class AnalyzerFacade { abstract val targetPlatform: TargetPlatform } -//NOTE: relies on delegate to be lazily computed and cached private class DelegatingPackageFragmentProvider( - private val delegate: () -> PackageFragmentProvider + moduleContent: ModuleContent, + private val packageOracle: PackageOracle, + private val resolverForModule: NotNullLazyValue ) : PackageFragmentProvider { + private val syntheticFilePackages = moduleContent.syntheticFiles.map { it.packageFqName }.toSet() override fun getPackageFragments(fqName: FqName): List { - return delegate().getPackageFragments(fqName) + if (certainlyDoesNotExist(fqName)) return emptyList() + + return resolverForModule().packageFragmentProvider.getPackageFragments(fqName) } override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection { - return delegate().getSubPackagesOf(fqName, nameFilter) + if (certainlyDoesNotExist(fqName)) return emptyList() + + return resolverForModule().packageFragmentProvider.getSubPackagesOf(fqName, nameFilter) + } + + private fun certainlyDoesNotExist(fqName: FqName): Boolean { + if (resolverForModule.isComputed()) return false // let this request get cached inside delegate + + return !packageOracle.packageExists(fqName) && fqName !in syntheticFilePackages + } +} + +interface PackageOracle { + fun packageExists(fqName: FqName): Boolean + + object Optimistic : PackageOracle { + override fun packageExists(fqName: FqName): Boolean = true + } +} + +interface PackageOracleFactory { + fun createOracle(moduleInfo: ModuleInfo): PackageOracle + + object OptimisticFactory : PackageOracleFactory { + override fun createOracle(moduleInfo: ModuleInfo) = PackageOracle.Optimistic } } 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 22d16258031..9072a26dff2 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 @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.idea.caches.resolve +import com.intellij.openapi.components.service import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.ProjectJdkTable @@ -84,7 +85,8 @@ fun createModuleResolverProvider( modulesContent, jvmPlatformParameters, IdeaEnvironment, builtInsProvider, delegateResolver, { _, c -> IDEPackagePartProvider(c.moduleContentScope) }, sdk?.let { SdkInfo(project, it) }, - modulePlatforms = { module -> module.platform?.multiTargetPlatform } + modulePlatforms = { module -> module.platform?.multiTargetPlatform }, + packageOracleFactory = project.service() ) } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/packageOracles.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/packageOracles.kt new file mode 100644 index 00000000000..982086bf102 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/packageOracles.kt @@ -0,0 +1,73 @@ +/* + * 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.caches.resolve + +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.analyzer.ModuleInfo +import org.jetbrains.kotlin.analyzer.PackageOracle +import org.jetbrains.kotlin.analyzer.PackageOracleFactory +import org.jetbrains.kotlin.idea.caches.PerModulePackageCacheService +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.isSubpackageOf +import org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade +import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform + +class IdePackageOracleFactory(val project: Project) : PackageOracleFactory { + override fun createOracle(moduleInfo: ModuleInfo): PackageOracle { + if (moduleInfo !is IdeaModuleInfo) return PackageOracle.Optimistic + + when { + moduleInfo.platform == JvmPlatform -> when (moduleInfo.moduleOrigin) { + ModuleOrigin.LIBRARY -> return JavaPackagesOracle(moduleInfo, project) + ModuleOrigin.MODULE -> return JvmSourceOracle(moduleInfo as ModuleSourceInfo, project) + ModuleOrigin.OTHER -> return PackageOracle.Optimistic + } + else -> when (moduleInfo.moduleOrigin) { + ModuleOrigin.MODULE -> return KotlinSourceFilesOracle(moduleInfo as ModuleSourceInfo) + else -> return PackageOracle.Optimistic // binaries for non-jvm platform need some oracles based on their structure + } + } + } + + private class JavaPackagesOracle(moduleInfo: IdeaModuleInfo, project: Project) : PackageOracle { + private val scope = moduleInfo.contentScope() + private val facade = project.service() + + override fun packageExists(fqName: FqName) = facade.findPackage(fqName.asString(), scope) != null + } + + private class KotlinSourceFilesOracle(private val moduleInfo: ModuleSourceInfo) : PackageOracle { + private val cacheService = moduleInfo.module.project.service() + + override fun packageExists(fqName: FqName): Boolean { + return cacheService.packageExists(fqName, moduleInfo) + } + } + + private class JvmSourceOracle(moduleInfo: ModuleSourceInfo, project: Project) : PackageOracle { + private val javaPackagesOracle = JavaPackagesOracle(moduleInfo, project) + private val kotlinSourceOracle = KotlinSourceFilesOracle(moduleInfo) + + override fun packageExists(fqName: FqName) = + javaPackagesOracle.packageExists(fqName) + || kotlinSourceOracle.packageExists(fqName) + || fqName.isSubpackageOf(ANDROID_SYNTHETIC_PACKAGE_PREFIX) + } +} + +private val ANDROID_SYNTHETIC_PACKAGE_PREFIX = FqName("kotlinx.android.synthetic") \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 65531bc12c3..3554c8a6fa8 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -322,6 +322,9 @@ + +