Revise per module out-of-code-block (KT-32364)

This commit is contained in:
Nikolay Krasko
2019-06-03 13:18:09 +03:00
parent 73eea6f4b6
commit f605d4e73b
4 changed files with 116 additions and 57 deletions
@@ -31,11 +31,11 @@ import org.jetbrains.kotlin.config.SourceKotlinRootType
import org.jetbrains.kotlin.config.TestSourceKotlinRootType import org.jetbrains.kotlin.config.TestSourceKotlinRootType
import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.util.enlargedSearchScope 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.BuildSystemType
import org.jetbrains.kotlin.idea.configuration.getBuildSystemType import org.jetbrains.kotlin.idea.configuration.getBuildSystemType
import org.jetbrains.kotlin.idea.core.isInTestSourceContentKotlinAware import org.jetbrains.kotlin.idea.core.isInTestSourceContentKotlinAware
import org.jetbrains.kotlin.idea.framework.getLibraryPlatform 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.TargetPlatformDetector
import org.jetbrains.kotlin.idea.project.findAnalyzerServices import org.jetbrains.kotlin.idea.project.findAnalyzerServices
import org.jetbrains.kotlin.idea.project.getStableName 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.idea.util.rootManager
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.DefaultIdeTargetPlatformKindProvider 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.TargetPlatform
import org.jetbrains.kotlin.platform.compat.toOldPlatform import org.jetbrains.kotlin.platform.compat.toOldPlatform
import org.jetbrains.kotlin.platform.idePlatformKind
import org.jetbrains.kotlin.platform.isCommon import org.jetbrains.kotlin.platform.isCommon
import org.jetbrains.kotlin.platform.js.isJs 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.jvm.isJvm
import org.jetbrains.kotlin.platform.konan.isNative 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.resolve.jvm.platform.JvmPlatformAnalyzerServices
import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addIfNotNull
import java.util.* import java.util.*
@@ -167,7 +167,7 @@ interface ModuleSourceInfo : IdeaModuleInfo, TrackableModuleInfo {
get() = platform.findAnalyzerServices get() = platform.findAnalyzerServices
override fun createModificationTracker(): ModificationTracker = override fun createModificationTracker(): ModificationTracker =
KotlinModuleModificationTracker(module) KotlinModuleOutOfCodeBlockModificationTracker(module)
} }
sealed class ModuleSourceInfoWithExpectedBy(private val forProduction: Boolean) : ModuleSourceInfo { sealed class ModuleSourceInfoWithExpectedBy(private val forProduction: Boolean) : ModuleSourceInfo {
@@ -5,8 +5,6 @@
package org.jetbrains.kotlin.idea.caches.trackers 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.project.Project
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.util.ModificationTracker import com.intellij.openapi.util.ModificationTracker
@@ -44,15 +42,7 @@ class KotlinCodeBlockModificationListener(
project: Project, project: Project,
private val treeAspect: TreeAspect private val treeAspect: TreeAspect
): PsiTreeChangePreprocessor { ): PsiTreeChangePreprocessor {
private val perModuleModCount = mutableMapOf<Module, Long>()
private val modificationTrackerImpl = modificationTracker as PsiModificationTrackerImpl 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 @Volatile
private var kotlinModificationTracker: Long = 0 private var kotlinModificationTracker: Long = 0
@@ -65,11 +55,7 @@ class KotlinCodeBlockModificationListener(
val kotlinOutOfCodeBlockTracker: ModificationTracker = kotlinOutOfCodeBlockTrackerImpl val kotlinOutOfCodeBlockTracker: ModificationTracker = kotlinOutOfCodeBlockTrackerImpl
fun getModificationCount(module: Module): Long { internal val perModuleOutOfCodeBlockTrackerUpdater = KotlinModuleOutOfCodeBlockModificationTracker.Updater(project)
return perModuleModCount[module] ?: perModuleChangesHighWatermark ?: kotlinOutOfCodeBlockTracker.modificationCount
}
fun hasPerModuleModificationCounts() = perModuleChangesHighWatermark != null
init { init {
val model = PomManager.getModel(project) val model = PomManager.getModel(project)
@@ -94,10 +80,7 @@ class KotlinCodeBlockModificationListener(
if (ktFile.isPhysical && !isReplLine(ktFile.virtualFile)) { if (ktFile.isPhysical && !isReplLine(ktFile.virtualFile)) {
kotlinOutOfCodeBlockTrackerImpl.incModificationCount() kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
lastAffectedModule = ModuleUtil.findModuleForPsiElement(ktFile) perModuleOutOfCodeBlockTrackerUpdater.onKotlinPhysicalFileOutOfBlockChange(ktFile)
lastAffectedModuleModCount = kotlinOutOfCodeBlockTracker.modificationCount
// TODO: Revise perModuleModCount
// modificationTrackerImpl.incCounter()
} }
incOutOfBlockModificationCount(ktFile) incOutOfBlockModificationCount(ktFile)
@@ -115,17 +98,7 @@ class KotlinCodeBlockModificationListener(
kotlinModificationTracker = kotlinTrackerInternalIDECount kotlinModificationTracker = kotlinTrackerInternalIDECount
} }
val newModCount = kotlinOutOfCodeBlockTracker.modificationCount perModuleOutOfCodeBlockTrackerUpdater.onPsiModificationTrackerUpdate()
val affectedModule = lastAffectedModule
if (affectedModule != null && newModCount == lastAffectedModuleModCount + 1) {
if (perModuleChangesHighWatermark == null) {
perModuleChangesHighWatermark = lastAffectedModuleModCount
}
perModuleModCount[affectedModule] = newModCount
} else {
perModuleChangesHighWatermark = null
perModuleModCount.clear()
}
}) })
} }
@@ -6,23 +6,34 @@
package org.jetbrains.kotlin.idea.caches.trackers package org.jetbrains.kotlin.idea.caches.trackers
import com.intellij.openapi.module.Module 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.ModuleRootManager
import com.intellij.openapi.roots.ProjectRootModificationTracker import com.intellij.openapi.roots.ProjectRootModificationTracker
import com.intellij.openapi.util.ModificationTracker import com.intellij.openapi.util.ModificationTracker
import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValueProvider
import com.intellij.util.CommonProcessors import com.intellij.util.CommonProcessors
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.idea.caches.project.cached import org.jetbrains.kotlin.idea.caches.project.cached
import org.jetbrains.kotlin.psi.KtFile
class KotlinModuleModificationTracker(val module: Module) : ModificationTracker { class KotlinModuleOutOfCodeBlockModificationTracker private constructor(private val module: Module, private val updater: Updater) :
private val kotlinModCountListener = ModificationTracker {
KotlinCodeBlockModificationListener.getInstance(module.project)
constructor(module: Module) :
this(module, KotlinCodeBlockModificationListener.getInstance(module.project).perModuleOutOfCodeBlockTrackerUpdater)
private val kotlinOutOfCodeBlockTracker = KotlinCodeBlockModificationListener.getInstance(module.project).kotlinOutOfCodeBlockTracker
private val dependencies by lazy { private val dependencies by lazy {
// Avoid implicit capturing for this to make CachedValueStabilityChecker happy
val module = module
module.cached(CachedValueProvider { module.cached(CachedValueProvider {
CachedValueProvider.Result.create( CachedValueProvider.Result.create(
HashSet<Module>().apply { HashSet<Module>().also { resultModuleSet ->
ModuleRootManager.getInstance(module).orderEntries().recursively().forEachModule( ModuleRootManager.getInstance(module).orderEntries().recursively().forEachModule(
CommonProcessors.CollectProcessor(this) CommonProcessors.CollectProcessor(resultModuleSet)
) )
}, },
ProjectRootModificationTracker.getInstance(module.project) ProjectRootModificationTracker.getInstance(module.project)
@@ -31,15 +42,15 @@ class KotlinModuleModificationTracker(val module: Module) : ModificationTracker
} }
override fun getModificationCount(): Long { override fun getModificationCount(): Long {
val currentGlobalCount = kotlinModCountListener.kotlinOutOfCodeBlockTracker.modificationCount val currentGlobalCount = kotlinOutOfCodeBlockTracker.modificationCount
if (kotlinModCountListener.hasPerModuleModificationCounts()) { if (updater.hasPerModuleModificationCounts()) {
val selfCount = kotlinModCountListener.getModificationCount(module) val selfCount = updater.getModificationCount(module)
if (selfCount == currentGlobalCount) return selfCount if (selfCount == currentGlobalCount) return selfCount
var maxCount = selfCount var maxCount = selfCount
for (dependency in dependencies) { for (dependency in dependencies) {
val depCount = kotlinModCountListener.getModificationCount(dependency) val depCount = updater.getModificationCount(dependency)
if (depCount == currentGlobalCount) return currentGlobalCount if (depCount == currentGlobalCount) return currentGlobalCount
if (depCount > maxCount) maxCount = depCount if (depCount > maxCount) maxCount = depCount
} }
@@ -48,4 +59,61 @@ class KotlinModuleModificationTracker(val module: Module) : ModificationTracker
return currentGlobalCount 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<Module, Long>()
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()
}
}
} }
@@ -13,7 +13,6 @@ import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ModuleRootModificationUtil import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiManager
import com.intellij.psi.impl.PsiModificationTrackerImpl import com.intellij.psi.impl.PsiModificationTrackerImpl
import com.intellij.psi.util.PsiModificationTracker import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.kotlin.analyzer.ModuleInfo 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.config.LanguageVersion
import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo
import org.jetbrains.kotlin.idea.caches.project.SdkInfo 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.KotlinCommonCompilerArgumentsHolder
import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerSettings import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerSettings
import org.jetbrains.kotlin.idea.completion.test.withServiceRegistered import org.jetbrains.kotlin.idea.completion.test.withServiceRegistered
import org.jetbrains.kotlin.idea.facet.KotlinFacetConfiguration import org.jetbrains.kotlin.idea.facet.KotlinFacetConfiguration
import org.jetbrains.kotlin.idea.facet.KotlinFacetType import org.jetbrains.kotlin.idea.facet.KotlinFacetType
import org.jetbrains.kotlin.idea.framework.JSLibraryKind 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.PluginTestCaseBase
import org.jetbrains.kotlin.idea.test.allKotlinFiles import org.jetbrains.kotlin.idea.test.allKotlinFiles
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand 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.JUnit3WithIdeaConfigurationRunner
import org.jetbrains.kotlin.test.MockLibraryUtil import org.jetbrains.kotlin.test.MockLibraryUtil
import org.jetbrains.kotlin.test.TestJdkKind.FULL_JDK import org.jetbrains.kotlin.test.TestJdkKind.FULL_JDK
import org.junit.Assert.assertNotEquals
import org.junit.runner.RunWith import org.junit.runner.RunWith
@RunWith(JUnit3WithIdeaConfigurationRunner::class) @RunWith(JUnit3WithIdeaConfigurationRunner::class)
@@ -134,30 +134,48 @@ open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
tracker.sdkResolversComputed.clear() tracker.sdkResolversComputed.clear()
tracker.moduleResolversComputed.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 m2ContentRoot = ModuleRootManager.getInstance(module1).contentRoots.single()
val module2ModTracker = KotlinModuleModificationTracker(module2) val m1 = m2ContentRoot.findChild("m1.kt")!!
val module3ModTracker = KotlinModuleModificationTracker(module3) 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() // Internal counters should be ready after modifications in m1
val m2 = contentRoot.findChild("m2.kt")!! 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)!! val m2doc = FileDocumentManager.getInstance().getDocument(m2)!!
project.executeWriteCommand("a") { project.executeWriteCommand("a") {
m2doc.insertString(m2doc.textLength , "fun foo() = 1") m2doc.insertString(m2doc.textLength , "fun foo() = 1")
PsiDocumentManager.getInstance(myProject).commitAllDocuments() PsiDocumentManager.getInstance(myProject).commitAllDocuments()
} }
val currentModCount = PsiManager.getInstance(project).modificationTracker.outOfCodeBlockModificationCount
assertEquals(module1ModCount, KotlinCodeBlockModificationListener.getInstance(myProject).getModificationCount(module1)) val currentModCount = KotlinCodeBlockModificationListener.getInstance(project).kotlinOutOfCodeBlockTracker.modificationCount
assertEquals(module1ModCount, module1ModTracker.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, module2ModTracker.modificationCount)
assertEquals(currentModCount, module3ModTracker.modificationCount) assertEquals(currentModCount, module3ModTracker.modificationCount)
checkHighlightingInProject { project.allKotlinFiles().filter { "m2" in it.name } } checkHighlightingInProject { project.allKotlinFiles().filter { "m2" in it.name } }
assertEquals(0, tracker.sdkResolversComputed.size) assertEquals(0, tracker.sdkResolversComputed.size)
assertEquals(1, tracker.moduleResolversComputed.size) assertEquals(2, tracker.moduleResolversComputed.size)
tracker.moduleResolversComputed.clear() tracker.moduleResolversComputed.clear()
(PsiModificationTracker.SERVICE.getInstance(myProject) as PsiModificationTrackerImpl).incOutOfCodeBlockModificationCounter() (PsiModificationTracker.SERVICE.getInstance(myProject) as PsiModificationTrackerImpl).incOutOfCodeBlockModificationCounter()