Recreate module descriptors for affected modules only on a change

This commit is contained in:
Dmitry Jemerov
2017-07-27 16:54:52 +02:00
parent 07794c8188
commit bdbe134b60
13 changed files with 262 additions and 40 deletions
@@ -18,7 +18,9 @@ package org.jetbrains.kotlin.analyzer
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.ModificationTracker
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.config.LanguageVersionSettings
@@ -85,9 +87,17 @@ class ResolverForProjectImpl<M : ModuleInfo>(
private val packagePartProviderFactory: (M, ModuleContent) -> PackagePartProvider = { _, _ -> PackagePartProvider.Empty },
private val firstDependency: M? = null,
private val modulePlatforms: (M) -> MultiTargetPlatform?,
private val packageOracleFactory: PackageOracleFactory = PackageOracleFactory.OptimisticFactory
private val packageOracleFactory: PackageOracleFactory = PackageOracleFactory.OptimisticFactory,
private val invalidateOnOOCB: Boolean = true
) : ResolverForProject<M>() {
private val descriptorByModule = mutableMapOf<M, ModuleDescriptorImpl>()
private class ModuleData(
val moduleDescriptor: ModuleDescriptorImpl,
val modificationTracker: ModificationTracker?,
val modificationCount: Long?
)
private val descriptorByModule = mutableMapOf<M, ModuleData>()
private val moduleInfoByDescriptor = mutableMapOf<ModuleDescriptorImpl, M>()
val modules = modules.toSet()
@@ -156,18 +166,42 @@ class ResolverForProjectImpl<M : ModuleInfo>(
private fun doGetDescriptorForModule(module: M): ModuleDescriptorImpl {
if (module in modules) {
return projectContext.storageManager.compute {
descriptorByModule.getOrPut(module) {
ModuleDescriptorImpl(module.name,
projectContext.storageManager, builtIns, modulePlatforms(module), module.capabilities).apply {
moduleInfoByDescriptor[this] = module
setupModuleDescriptor(module, this)
}
var moduleData = descriptorByModule.getOrPut(module) {
createModuleDescriptor(module)
}
val currentModCount = moduleData.modificationTracker?.modificationCount
if (currentModCount != null && currentModCount > moduleData.modificationCount!!) {
moduleData = recreateModuleDescriptor(module)
}
moduleData.moduleDescriptor
}
}
return delegateResolver.descriptorForModule(module) as ModuleDescriptorImpl
}
private fun recreateModuleDescriptor(module: M): ModuleData {
val oldDescriptor = descriptorByModule[module]?.moduleDescriptor
if (oldDescriptor != null) {
oldDescriptor.isValid = false
moduleInfoByDescriptor.remove(oldDescriptor)
resolverByModuleDescriptor.remove(oldDescriptor)
}
val moduleData = createModuleDescriptor(module)
descriptorByModule[module] = moduleData
return moduleData
}
private fun createModuleDescriptor(module: M): ModuleData {
val moduleDescriptor = ModuleDescriptorImpl(module.name,
projectContext.storageManager, builtIns, modulePlatforms(module), module.capabilities)
moduleInfoByDescriptor[moduleDescriptor] = module
setupModuleDescriptor(module, moduleDescriptor)
val modificationTracker = (module as? TrackableModuleInfo)?.createModificationTracker() ?:
(PsiModificationTracker.SERVICE.getInstance(projectContext.project).outOfCodeBlockModificationTracker.takeIf { invalidateOnOOCB })
return ModuleData(moduleDescriptor, modificationTracker, modificationTracker?.modificationCount)
}
}
data class ModuleContent(
@@ -207,6 +241,10 @@ interface ModuleInfo {
}
}
interface TrackableModuleInfo : ModuleInfo {
fun createModificationTracker(): ModificationTracker
}
abstract class AnalyzerFacade {
abstract fun <M : ModuleInfo> createResolverForModule(
moduleInfo: M,
@@ -48,4 +48,6 @@ interface ModuleDescriptor : DeclarationDescriptor {
class Capability<T>(val name: String) {
override fun toString() = name
}
val isValid: Boolean
}
@@ -47,6 +47,14 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
private var dependencies: ModuleDependencies? = null
private var packageFragmentProviderForModuleContent: PackageFragmentProvider? = null
override var isValid: Boolean = true
private fun assertValid() {
if (!isValid) {
throw IllegalStateException("Accessing invalid module descriptor $this")
}
}
private val packages = storageManager.createMemoizedFunction<FqName, PackageViewDescriptor> {
fqName: FqName -> LazyPackageViewDescriptorImpl(this, fqName, storageManager)
}
@@ -60,9 +68,13 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
override val allImplementingModules: Set<ModuleDescriptor>
get() = this.dependencies.sure { "Dependencies of module $id were not set" }.allImplementingModules
override fun getPackage(fqName: FqName): PackageViewDescriptor = packages(fqName)
override fun getPackage(fqName: FqName): PackageViewDescriptor {
assertValid()
return packages(fqName)
}
override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection<FqName> {
assertValid()
return packageFragmentProvider.getSubPackagesOf(fqName, nameFilter)
}
@@ -117,7 +129,10 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
}
val packageFragmentProvider: PackageFragmentProvider
get() = packageFragmentProviderForWholeModuleWithDependencies
get() {
assertValid()
return packageFragmentProviderForWholeModuleWithDependencies
}
@Suppress("UNCHECKED_CAST")
override fun <T> getCapability(capability: ModuleDescriptor.Capability<T>) = capabilities[capability] as? T
@@ -122,6 +122,11 @@ public class ErrorUtils {
public KotlinBuiltIns getBuiltIns() {
return DefaultBuiltIns.getInstance();
}
@Override
public boolean isValid() {
return false;
}
};
}
@@ -23,6 +23,7 @@ import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.*
import com.intellij.openapi.roots.impl.libraries.LibraryEx
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.util.ModificationTracker
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.CachedValueProvider
@@ -30,9 +31,11 @@ import com.intellij.psi.util.CachedValuesManager
import com.intellij.util.PathUtil
import com.intellij.util.SmartList
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.analyzer.TrackableModuleInfo
import org.jetbrains.kotlin.caches.resolve.LibraryModuleInfo
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.framework.getLibraryPlatform
import org.jetbrains.kotlin.idea.project.KotlinModuleModificationTracker
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
import org.jetbrains.kotlin.idea.util.isInSourceContentWithoutInjected
@@ -97,7 +100,7 @@ private fun ideaModelDependencies(module: Module, productionOnly: Boolean): List
return result.toList()
}
interface ModuleSourceInfo : IdeaModuleInfo {
interface ModuleSourceInfo : IdeaModuleInfo, TrackableModuleInfo {
val module: Module
override val displayedName get() = module.name
@@ -107,6 +110,9 @@ interface ModuleSourceInfo : IdeaModuleInfo {
override val platform: TargetPlatform
get() = TargetPlatformDetector.getPlatform(module)
override fun createModificationTracker(): ModificationTracker =
KotlinModuleModificationTracker(module)
}
data class ModuleProductionSourceInfo(override val module: Module) : ModuleSourceInfo {
@@ -125,6 +125,7 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
LibraryModificationTracker.getInstance(project),
ProjectRootModificationTracker.getInstance(project)
),
invalidateOnOOCB = false,
reuseDataFrom = null
)
@@ -134,6 +135,7 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
project, librariesContext, settings,
reuseDataFrom = facadeForSdk,
moduleFilter = { it is LibraryInfo },
invalidateOnOOCB = false,
dependencies = listOf(
LibraryModificationTracker.getInstance(project),
ProjectRootModificationTracker.getInstance(project)
@@ -146,7 +148,10 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
project, modulesContext, settings,
reuseDataFrom = facadeForLibraries,
moduleFilter = { !it.isLibraryClasses() },
dependencies = listOf(PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT)
dependencies = listOf(
LibraryModificationTracker.getInstance(project),
ProjectRootModificationTracker.getInstance(project)
)
)
}
@@ -52,7 +52,8 @@ fun createModuleResolverProvider(
moduleFilter: (IdeaModuleInfo) -> Boolean,
allModules: Collection<IdeaModuleInfo>?,
providedBuiltIns: KotlinBuiltIns?, // null means create new builtins based on SDK
dependencies: Collection<Any>
dependencies: Collection<Any>,
invalidateOnOOCB: Boolean = true
): ModuleResolverProvider {
val builtIns = providedBuiltIns ?: createBuiltIns(analysisSettings, globalContext)
@@ -82,7 +83,8 @@ fun createModuleResolverProvider(
delegateResolver, { _, c -> IDEPackagePartProvider(c.moduleContentScope) },
analysisSettings.sdk?.let { SdkInfo(project, it) },
modulePlatforms = { module -> module.platform?.multiTargetPlatform },
packageOracleFactory = project.service<IdePackageOracleFactory>()
packageOracleFactory = project.service<IdePackageOracleFactory>(),
invalidateOnOOCB = invalidateOnOOCB
)
if (providedBuiltIns == null && builtIns is JvmBuiltIns) {
@@ -30,14 +30,15 @@ import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.CompositeBindingContext
internal class ProjectResolutionFacade(
val debugString: String,
val resolverDebugName: String,
private val debugString: String,
private val resolverDebugName: String,
val project: Project,
val globalContext: GlobalContextImpl,
val settings: PlatformAnalysisSettings,
val reuseDataFrom: ProjectResolutionFacade?,
val moduleFilter: (IdeaModuleInfo) -> Boolean,
val dependencies: List<Any>,
private val invalidateOnOOCB: Boolean = true,
val syntheticFiles: Collection<KtFile> = listOf(),
val allModules: Collection<IdeaModuleInfo>? = null // null means create resolvers for modules from idea model
) {
@@ -61,11 +62,12 @@ internal class ProjectResolutionFacade(
delegateResolver = delegateResolverForProject, moduleFilter = moduleFilter,
allModules = allModules,
providedBuiltIns = delegateResolverProvider?.builtIns,
dependencies = dependencies
dependencies = dependencies,
invalidateOnOOCB = invalidateOnOOCB
)
}
val moduleResolverProvider: ModuleResolverProvider
private val moduleResolverProvider: ModuleResolverProvider
get() = globalContext.storageManager.compute { cachedValue.value }
fun resolverForModuleInfo(moduleInfo: IdeaModuleInfo) = moduleResolverProvider.resolverForProject.resolverForModule(moduleInfo)
@@ -16,8 +16,12 @@
package org.jetbrains.kotlin.idea.project
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.ModificationTracker
import com.intellij.pom.PomManager
import com.intellij.pom.PomModelAspect
import com.intellij.pom.event.PomModelEvent
@@ -28,6 +32,7 @@ import com.intellij.psi.PsiCodeFragment
import com.intellij.psi.PsiElement
import com.intellij.psi.impl.PsiModificationTrackerImpl
import com.intellij.psi.util.PsiModificationTracker
import com.intellij.util.CommonProcessors
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentOfType
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
@@ -41,10 +46,27 @@ class KotlinCodeBlockModificationListener(
project: Project,
private val treeAspect: TreeAspect
) {
private val perModuleModCount = mutableMapOf<Module, Long>()
private val modificationTrackerImpl = modificationTracker as PsiModificationTrackerImpl
private var lastAffectedModule: Module? = null
private var lastAffectedModuleModCount = -1L
// All modifications since that count are known to be single-module modifications reflected in
// perModuleModCount map
private var perModuleChangesHighwatermark: Long? = null
fun getModificationCount(module: Module): Long {
return perModuleModCount[module] ?:
perModuleChangesHighwatermark ?:
modificationTrackerImpl.outOfCodeBlockModificationCount
}
fun hasPerModuleModificationCounts() = perModuleChangesHighwatermark != null
init {
val model = PomManager.getModel(project)
@Suppress("NAME_SHADOWING")
val modificationTracker = modificationTracker as PsiModificationTrackerImpl
val messageBusConnection = project.messageBus.connect()
model.addModelListener(object: PomModelListener {
override fun isAspectChangeInteresting(aspect: PomModelAspect): Boolean {
return aspect == treeAspect
@@ -58,13 +80,31 @@ class KotlinCodeBlockModificationListener(
// contents to be replaced, which is represented in a POM event as an empty list of changed elements
if (changedElements.any { getInsideCodeBlockModificationScope(it.psi) == null } ||
(file is PsiCodeFragment && changedElements.isEmpty())) {
messageBusConnection.deliverImmediately()
if (file.isPhysical) {
modificationTracker.incCounter()
lastAffectedModule = ModuleUtil.findModuleForPsiElement(file)
lastAffectedModuleModCount = modificationTrackerImpl.outOfCodeBlockModificationCount
modificationTrackerImpl.incCounter()
}
incOutOfBlockModificationCount(file)
}
}
})
messageBusConnection.subscribe(PsiModificationTracker.TOPIC, PsiModificationTracker.Listener {
val newModCount = modificationTrackerImpl.outOfCodeBlockModificationCount
val affectedModule = lastAffectedModule
if (affectedModule != null && newModCount == lastAffectedModuleModCount + 1) {
if (perModuleChangesHighwatermark== null) {
perModuleChangesHighwatermark = lastAffectedModuleModCount
}
perModuleModCount[affectedModule] = newModCount
}
else {
perModuleChangesHighwatermark = null
perModuleModCount.clear()
}
})
}
companion object {
@@ -117,6 +157,8 @@ class KotlinCodeBlockModificationListener(
KtProperty::class.java,
KtNamedFunction::class.java
)
fun getInstance(project: Project) = project.getComponent(KotlinCodeBlockModificationListener::class.java)
}
}
@@ -124,3 +166,31 @@ private val FILE_OUT_OF_BLOCK_MODIFICATION_COUNT = Key<Long>("FILE_OUT_OF_BLOCK_
val KtFile.outOfBlockModificationCount: Long
get() = getUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT) ?: 0
class KotlinModuleModificationTracker(val module: Module): ModificationTracker {
private val kotlinModCountListener = KotlinCodeBlockModificationListener.getInstance(module.project)
private val psiModificationTracker = PsiModificationTracker.SERVICE.getInstance(module.project)
private val dependencies by lazy {
HashSet<Module>().apply {
ModuleRootManager.getInstance(module).orderEntries().recursively().forEachModule(
CommonProcessors.CollectProcessor(this))
}
}
override fun getModificationCount(): Long {
val currentGlobalCount = psiModificationTracker.outOfCodeBlockModificationCount
if (kotlinModCountListener.hasPerModuleModificationCounts()) {
val selfCount = kotlinModCountListener.getModificationCount(module)
if (selfCount == currentGlobalCount) return selfCount
var maxCount = selfCount
for (dependency in dependencies) {
val depCount = kotlinModCountListener.getModificationCount(dependency)
if (depCount == currentGlobalCount) return currentGlobalCount
if (depCount > maxCount) maxCount = depCount
}
return maxCount
}
return currentGlobalCount
}
}
@@ -0,0 +1,4 @@
package foo.bar.used
public fun m1() {
}
@@ -0,0 +1,5 @@
import foo.bar.used.*
public fun m2() {
m1()
}
@@ -17,8 +17,16 @@
package org.jetbrains.kotlin.idea.caches.resolve
import com.intellij.facet.FacetManager
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiManager
import com.intellij.psi.impl.PsiModificationTrackerImpl
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.analyzer.ResolverForModuleComputationTracker
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
@@ -30,8 +38,12 @@ import org.jetbrains.kotlin.idea.completion.test.withServiceRegistered
import org.jetbrains.kotlin.idea.facet.KotlinFacetConfiguration
import org.jetbrains.kotlin.idea.facet.KotlinFacetType
import org.jetbrains.kotlin.idea.framework.JSLibraryKind
import org.jetbrains.kotlin.idea.project.KotlinCodeBlockModificationListener
import org.jetbrains.kotlin.idea.project.KotlinModuleModificationTracker
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
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.test.TestJdkKind.FULL_JDK
open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
@@ -66,20 +78,9 @@ open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
}
fun testLazyResolvers() {
val resolversComputed = mutableSetOf<Module>()
val tracker = ResolverTracker()
val resolversTracker = object : ResolverForModuleComputationTracker {
override fun onResolverComputed(moduleInfo: ModuleInfo) {
(moduleInfo as IdeaModuleInfo).let {
if (it is ModuleSourceInfo) {
val module = it.module
resolversComputed.add(module)
}
}
}
}
project.withServiceRegistered<ResolverForModuleComputationTracker, Unit>(resolversTracker) {
project.withServiceRegistered<ResolverForModuleComputationTracker, Unit>(tracker) {
val module1 = module("m1")
val module2 = module("m2")
val module3 = module("m3")
@@ -87,15 +88,82 @@ open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
module3.addDependency(module2)
module3.addDependency(module1)
assertTrue(module1 !in resolversComputed)
assertTrue(module2 !in resolversComputed)
assertTrue(module3 !in resolversComputed)
assertTrue(module1 !in tracker.moduleResolversComputed)
assertTrue(module2 !in tracker.moduleResolversComputed)
assertTrue(module3 !in tracker.moduleResolversComputed)
checkHighlightingInAllFiles { "m3" in file.name }
assertTrue(module1 in resolversComputed)
assertTrue(module2 !in resolversComputed)
assertTrue(module3 in resolversComputed)
assertTrue(module1 in tracker.moduleResolversComputed)
assertTrue(module2 !in tracker.moduleResolversComputed)
assertTrue(module3 in tracker.moduleResolversComputed)
}
}
class ResolverTracker : ResolverForModuleComputationTracker {
val moduleResolversComputed = mutableListOf<Module>()
val sdkResolversComputed = mutableListOf<Sdk>()
override fun onResolverComputed(moduleInfo: ModuleInfo) {
when (moduleInfo) {
is ModuleSourceInfo -> moduleResolversComputed.add(moduleInfo.module)
is SdkInfo -> sdkResolversComputed.add(moduleInfo.sdk)
}
}
}
fun testRecomputeResolversOnChange() {
val tracker = ResolverTracker()
project.withServiceRegistered<ResolverForModuleComputationTracker, Unit>(tracker) {
val module1 = module("m1")
val module2 = module("m2")
val module3 = module("m3")
module2.addDependency(module1)
module3.addDependency(module2)
// Ensure modules have the same SDK instance, and not two distinct SDKs with the same path
ModuleRootModificationUtil.setModuleSdk(module2, module1.sdk)
assertEquals(0, tracker.sdkResolversComputed.size)
checkHighlightingInAllFiles { "m2" in file.name }
assertEquals(2, tracker.moduleResolversComputed.size)
tracker.sdkResolversComputed.clear()
tracker.moduleResolversComputed.clear()
val module1ModCount = KotlinCodeBlockModificationListener.getInstance(myProject).getModificationCount(module1)
val module1ModTracker = KotlinModuleModificationTracker(module1)
val module2ModTracker = KotlinModuleModificationTracker(module2)
val module3ModTracker = KotlinModuleModificationTracker(module3)
val contentRoot = ModuleRootManager.getInstance(module2).contentRoots.single()
val m2 = contentRoot.findChild("m2.kt")!!
val m2doc = FileDocumentManager.getInstance().getDocument(m2)!!
project.executeWriteCommand("a") {
m2doc.insertString(m2doc.textLength , "fun foo() = 1")
PsiDocumentManager.getInstance(myProject).commitAllDocuments()
}
val currentModCount = PsiManager.getInstance(project).modificationTracker.outOfCodeBlockModificationCount
assertEquals(module1ModCount, KotlinCodeBlockModificationListener.getInstance(myProject).getModificationCount(module1))
assertEquals(module1ModCount, module1ModTracker.modificationCount)
assertEquals(currentModCount, module2ModTracker.modificationCount)
assertEquals(currentModCount, module3ModTracker.modificationCount)
checkHighlightingInAllFiles { "m2" in file.name }
assertEquals(0, tracker.sdkResolversComputed.size)
assertEquals(1, tracker.moduleResolversComputed.size)
tracker.moduleResolversComputed.clear()
(PsiModificationTracker.SERVICE.getInstance(myProject) as PsiModificationTrackerImpl).incOutOfCodeBlockModificationCounter()
checkHighlightingInAllFiles { "m2" in file.name }
assertEquals(0, tracker.sdkResolversComputed.size)
assertEquals(2, tracker.moduleResolversComputed.size)
}
}