From f605d4e73b60c701ac464fd9712408aceec0f795 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Mon, 3 Jun 2019 13:18:09 +0300 Subject: [PATCH] Revise per module out-of-code-block (KT-32364) --- .../idea/caches/project/IdeaModuleInfos.kt | 10 +-- .../KotlinCodeBlockModificationListener.kt | 33 +------ ...ModuleOutOfCodeBlockModificationTracker.kt | 86 +++++++++++++++++-- .../resolve/MultiModuleHighlightingTest.kt | 44 +++++++--- 4 files changed, 116 insertions(+), 57 deletions(-) diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt index 669b7eed243..9f9935e5f84 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt @@ -31,11 +31,11 @@ import org.jetbrains.kotlin.config.SourceKotlinRootType import org.jetbrains.kotlin.config.TestSourceKotlinRootType import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.idea.caches.resolve.util.enlargedSearchScope +import org.jetbrains.kotlin.idea.caches.trackers.KotlinModuleOutOfCodeBlockModificationTracker import org.jetbrains.kotlin.idea.configuration.BuildSystemType import org.jetbrains.kotlin.idea.configuration.getBuildSystemType import org.jetbrains.kotlin.idea.core.isInTestSourceContentKotlinAware import org.jetbrains.kotlin.idea.framework.getLibraryPlatform -import org.jetbrains.kotlin.idea.caches.trackers.KotlinModuleModificationTracker import org.jetbrains.kotlin.idea.project.TargetPlatformDetector import org.jetbrains.kotlin.idea.project.findAnalyzerServices import org.jetbrains.kotlin.idea.project.getStableName @@ -44,15 +44,15 @@ import org.jetbrains.kotlin.idea.util.isInSourceContentWithoutInjected import org.jetbrains.kotlin.idea.util.rootManager import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.platform.DefaultIdeTargetPlatformKindProvider -import org.jetbrains.kotlin.platform.idePlatformKind -import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices -import org.jetbrains.kotlin.platform.jvm.JvmPlatforms import org.jetbrains.kotlin.platform.TargetPlatform import org.jetbrains.kotlin.platform.compat.toOldPlatform +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.JvmPlatforms import org.jetbrains.kotlin.platform.jvm.isJvm import org.jetbrains.kotlin.platform.konan.isNative +import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatformAnalyzerServices import org.jetbrains.kotlin.utils.addIfNotNull import java.util.* @@ -167,7 +167,7 @@ interface ModuleSourceInfo : IdeaModuleInfo, TrackableModuleInfo { get() = platform.findAnalyzerServices override fun createModificationTracker(): ModificationTracker = - KotlinModuleModificationTracker(module) + KotlinModuleOutOfCodeBlockModificationTracker(module) } sealed class ModuleSourceInfoWithExpectedBy(private val forProduction: Boolean) : ModuleSourceInfo { diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt index d659fadcf0f..2a722b9f95b 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt @@ -5,8 +5,6 @@ package org.jetbrains.kotlin.idea.caches.trackers -import com.intellij.openapi.module.Module -import com.intellij.openapi.module.ModuleUtil import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key import com.intellij.openapi.util.ModificationTracker @@ -44,15 +42,7 @@ class KotlinCodeBlockModificationListener( project: Project, private val treeAspect: TreeAspect ): PsiTreeChangePreprocessor { - private val perModuleModCount = mutableMapOf() - 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 @Volatile private var kotlinModificationTracker: Long = 0 @@ -65,11 +55,7 @@ class KotlinCodeBlockModificationListener( val kotlinOutOfCodeBlockTracker: ModificationTracker = kotlinOutOfCodeBlockTrackerImpl - fun getModificationCount(module: Module): Long { - return perModuleModCount[module] ?: perModuleChangesHighWatermark ?: kotlinOutOfCodeBlockTracker.modificationCount - } - - fun hasPerModuleModificationCounts() = perModuleChangesHighWatermark != null + internal val perModuleOutOfCodeBlockTrackerUpdater = KotlinModuleOutOfCodeBlockModificationTracker.Updater(project) init { val model = PomManager.getModel(project) @@ -94,10 +80,7 @@ class KotlinCodeBlockModificationListener( if (ktFile.isPhysical && !isReplLine(ktFile.virtualFile)) { kotlinOutOfCodeBlockTrackerImpl.incModificationCount() - lastAffectedModule = ModuleUtil.findModuleForPsiElement(ktFile) - lastAffectedModuleModCount = kotlinOutOfCodeBlockTracker.modificationCount - // TODO: Revise perModuleModCount -// modificationTrackerImpl.incCounter() + perModuleOutOfCodeBlockTrackerUpdater.onKotlinPhysicalFileOutOfBlockChange(ktFile) } incOutOfBlockModificationCount(ktFile) @@ -115,17 +98,7 @@ class KotlinCodeBlockModificationListener( kotlinModificationTracker = kotlinTrackerInternalIDECount } - val newModCount = kotlinOutOfCodeBlockTracker.modificationCount - val affectedModule = lastAffectedModule - if (affectedModule != null && newModCount == lastAffectedModuleModCount + 1) { - if (perModuleChangesHighWatermark == null) { - perModuleChangesHighWatermark = lastAffectedModuleModCount - } - perModuleModCount[affectedModule] = newModCount - } else { - perModuleChangesHighWatermark = null - perModuleModCount.clear() - } + perModuleOutOfCodeBlockTrackerUpdater.onPsiModificationTrackerUpdate() }) } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinModuleOutOfCodeBlockModificationTracker.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinModuleOutOfCodeBlockModificationTracker.kt index 69702201efa..8b11e264ad6 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinModuleOutOfCodeBlockModificationTracker.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinModuleOutOfCodeBlockModificationTracker.kt @@ -6,23 +6,34 @@ package org.jetbrains.kotlin.idea.caches.trackers 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.roots.ProjectRootModificationTracker import com.intellij.openapi.util.ModificationTracker import com.intellij.psi.util.CachedValueProvider import com.intellij.util.CommonProcessors +import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.idea.caches.project.cached +import org.jetbrains.kotlin.psi.KtFile -class KotlinModuleModificationTracker(val module: Module) : ModificationTracker { - private val kotlinModCountListener = - KotlinCodeBlockModificationListener.getInstance(module.project) +class KotlinModuleOutOfCodeBlockModificationTracker private constructor(private val module: Module, private val updater: Updater) : + ModificationTracker { + + constructor(module: Module) : + this(module, KotlinCodeBlockModificationListener.getInstance(module.project).perModuleOutOfCodeBlockTrackerUpdater) + + private val kotlinOutOfCodeBlockTracker = KotlinCodeBlockModificationListener.getInstance(module.project).kotlinOutOfCodeBlockTracker private val dependencies by lazy { + // Avoid implicit capturing for this to make CachedValueStabilityChecker happy + val module = module + module.cached(CachedValueProvider { CachedValueProvider.Result.create( - HashSet().apply { + HashSet().also { resultModuleSet -> ModuleRootManager.getInstance(module).orderEntries().recursively().forEachModule( - CommonProcessors.CollectProcessor(this) + CommonProcessors.CollectProcessor(resultModuleSet) ) }, ProjectRootModificationTracker.getInstance(module.project) @@ -31,15 +42,15 @@ class KotlinModuleModificationTracker(val module: Module) : ModificationTracker } override fun getModificationCount(): Long { - val currentGlobalCount = kotlinModCountListener.kotlinOutOfCodeBlockTracker.modificationCount + val currentGlobalCount = kotlinOutOfCodeBlockTracker.modificationCount - if (kotlinModCountListener.hasPerModuleModificationCounts()) { - val selfCount = kotlinModCountListener.getModificationCount(module) + if (updater.hasPerModuleModificationCounts()) { + val selfCount = updater.getModificationCount(module) if (selfCount == currentGlobalCount) return selfCount var maxCount = selfCount for (dependency in dependencies) { - val depCount = kotlinModCountListener.getModificationCount(dependency) + val depCount = updater.getModificationCount(dependency) if (depCount == currentGlobalCount) return currentGlobalCount if (depCount > maxCount) maxCount = depCount } @@ -48,4 +59,61 @@ class KotlinModuleModificationTracker(val module: Module) : ModificationTracker return currentGlobalCount } + + companion object { + @TestOnly + fun getModificationCount(module: Module): Long { + val updater = KotlinCodeBlockModificationListener.getInstance(module.project).perModuleOutOfCodeBlockTrackerUpdater + return updater.getModificationCount(module) + } + } + + internal class Updater(project: Project) { + private val kotlinOfOfCodeBlockTracker by lazy { + KotlinCodeBlockModificationListener.getInstance(project).kotlinOutOfCodeBlockTracker + } + + private val perModuleModCount = mutableMapOf() + + 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 + + internal fun getModificationCount(module: Module): Long { + return perModuleModCount[module] ?: perModuleChangesHighWatermark ?: kotlinOfOfCodeBlockTracker.modificationCount + } + + internal fun hasPerModuleModificationCounts() = perModuleChangesHighWatermark != null + + internal fun onKotlinPhysicalFileOutOfBlockChange(ktFile: KtFile) { + lastAffectedModule = ModuleUtil.findModuleForPsiElement(ktFile) + lastAffectedModuleModCount = kotlinOfOfCodeBlockTracker.modificationCount + + onPsiModificationTrackerUpdate() + } + + internal fun onPsiModificationTrackerUpdate() { + val newModCount = kotlinOfOfCodeBlockTracker.modificationCount + val affectedModule = lastAffectedModule + if (affectedModule != null && newModCount == lastAffectedModuleModCount) { + if (perModuleChangesHighWatermark == null) { + perModuleChangesHighWatermark = lastAffectedModuleModCount + } + perModuleModCount[affectedModule] = newModCount + } else { + // Some updates were not processed in our code so they probably came from other languages. Invalidate all. + clean() + } + } + + private fun clean() { + perModuleChangesHighWatermark = null + lastAffectedModule = null + perModuleModCount.clear() + } + } } \ No newline at end of file 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 9400ddd24d2..ce0287d2840 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/MultiModuleHighlightingTest.kt @@ -13,7 +13,6 @@ 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 @@ -25,14 +24,14 @@ import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.LanguageVersion import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo import org.jetbrains.kotlin.idea.caches.project.SdkInfo +import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener +import org.jetbrains.kotlin.idea.caches.trackers.KotlinModuleOutOfCodeBlockModificationTracker import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerSettings 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.caches.trackers.KotlinCodeBlockModificationListener -import org.jetbrains.kotlin.idea.caches.trackers.KotlinModuleModificationTracker import org.jetbrains.kotlin.idea.test.PluginTestCaseBase import org.jetbrains.kotlin.idea.test.allKotlinFiles import org.jetbrains.kotlin.idea.util.application.executeWriteCommand @@ -43,6 +42,7 @@ import org.jetbrains.kotlin.samWithReceiver.SamWithReceiverCommandLineProcessor. import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner import org.jetbrains.kotlin.test.MockLibraryUtil import org.jetbrains.kotlin.test.TestJdkKind.FULL_JDK +import org.junit.Assert.assertNotEquals import org.junit.runner.RunWith @RunWith(JUnit3WithIdeaConfigurationRunner::class) @@ -134,30 +134,48 @@ open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() { tracker.sdkResolversComputed.clear() tracker.moduleResolversComputed.clear() - val module1ModCount = KotlinCodeBlockModificationListener.getInstance(myProject).getModificationCount(module1) + val module1ModTracker = KotlinModuleOutOfCodeBlockModificationTracker(module1) + val module2ModTracker = KotlinModuleOutOfCodeBlockModificationTracker(module2) + val module3ModTracker = KotlinModuleOutOfCodeBlockModificationTracker(module3) - val module1ModTracker = KotlinModuleModificationTracker(module1) - val module2ModTracker = KotlinModuleModificationTracker(module2) - val module3ModTracker = KotlinModuleModificationTracker(module3) + val m2ContentRoot = ModuleRootManager.getInstance(module1).contentRoots.single() + val m1 = m2ContentRoot.findChild("m1.kt")!! + val m1doc = FileDocumentManager.getInstance().getDocument(m1)!! + project.executeWriteCommand("a") { + m1doc.insertString(m1doc.textLength , "fun foo() = 1") + PsiDocumentManager.getInstance(myProject).commitAllDocuments() + } - val contentRoot = ModuleRootManager.getInstance(module2).contentRoots.single() - val m2 = contentRoot.findChild("m2.kt")!! + // Internal counters should be ready after modifications in m1 + val afterFirstModification = KotlinModuleOutOfCodeBlockModificationTracker.getModificationCount(module1) + + assertEquals(afterFirstModification, module1ModTracker.modificationCount) + assertEquals(afterFirstModification, module2ModTracker.modificationCount) + assertEquals(afterFirstModification, module3ModTracker.modificationCount) + + val m1ContentRoot = ModuleRootManager.getInstance(module2).contentRoots.single() + val m2 = m1ContentRoot.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) + val currentModCount = KotlinCodeBlockModificationListener.getInstance(project).kotlinOutOfCodeBlockTracker.modificationCount + + // Counter for m1 module should be unaffected by modification in m2 + assertEquals(afterFirstModification, KotlinModuleOutOfCodeBlockModificationTracker.getModificationCount(module1)) + assertEquals(afterFirstModification, module1ModTracker.modificationCount) + + // Counters for m2 and m3 should be changed + assertNotEquals(afterFirstModification, currentModCount) assertEquals(currentModCount, module2ModTracker.modificationCount) assertEquals(currentModCount, module3ModTracker.modificationCount) checkHighlightingInProject { project.allKotlinFiles().filter { "m2" in it.name } } assertEquals(0, tracker.sdkResolversComputed.size) - assertEquals(1, tracker.moduleResolversComputed.size) + assertEquals(2, tracker.moduleResolversComputed.size) tracker.moduleResolversComputed.clear() (PsiModificationTracker.SERVICE.getInstance(myProject) as PsiModificationTrackerImpl).incOutOfCodeBlockModificationCounter()