Provide incremental analysis of a file when it is applicable

#KT32868 Fixed
This commit is contained in:
Vladimir Dolzhenko
2019-07-27 16:18:53 +02:00
parent c7bd6d8ede
commit 4b2834c4a8
68 changed files with 730 additions and 318 deletions
@@ -246,24 +246,15 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
val specialModuleInfo = files.map(KtFile::getModuleInfo).toSet().single()
val settings = specialModuleInfo.platformSettings(specialModuleInfo.platform ?: targetPlatform)
// File copies are created during completion and receive correct modification events through POM.
// Dummy files created e.g. by J2K do not receive events.
val filesModificationTracker = if (files.all { it.originalFile != it }) {
ModificationTracker {
files.sumByLong { it.outOfBlockModificationCount }
}
val dependenciesForSyntheticFileCache = if (files.all { it.originalFile != it }) {
emptyList()
} else {
ModificationTracker {
files.sumByLong { it.outOfBlockModificationCount + it.modificationStamp }
}
listOf(ModificationTracker {
files.sumByLong { it.modificationStamp }
})
}
val dependenciesForSyntheticFileCache =
listOf(
KotlinCodeBlockModificationListener.getInstance(project).kotlinOutOfCodeBlockTracker,
filesModificationTracker
)
val resolverDebugName =
"$resolverForSpecialInfoName $specialModuleInfo for files ${files.joinToString { it.name }} for platform $targetPlatform"
@@ -16,12 +16,15 @@
package org.jetbrains.kotlin.idea.caches.resolve
import com.google.common.collect.ImmutableMap
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressIndicatorProvider
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.IndexNotReadyException
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.ModificationTracker
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.*
import org.jetbrains.kotlin.analyzer.AnalysisResult
import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.container.get
@@ -29,18 +32,25 @@ import org.jetbrains.kotlin.context.GlobalContext
import org.jetbrains.kotlin.context.withModule
import org.jetbrains.kotlin.context.withProject
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.frontend.di.createContainerForLazyBodyResolve
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
import org.jetbrains.kotlin.idea.caches.trackers.clearInBlockModifications
import org.jetbrains.kotlin.idea.caches.trackers.inBlockModifications
import org.jetbrains.kotlin.idea.project.IdeaModuleStructureOracle
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.project.findAnalyzerServices
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
import org.jetbrains.kotlin.resolve.diagnostics.DiagnosticsElementsCache
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice
import org.jetbrains.kotlin.util.slicedMap.WritableSlice
import java.util.*
internal class PerFileAnalysisCache(val file: KtFile, componentProvider: ComponentProvider) {
@@ -51,6 +61,89 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
private val bodyResolveCache = componentProvider.get<BodyResolveCache>()
private val cache = HashMap<PsiElement, AnalysisResult>()
private var fileResult: AnalysisResult? = null
fun getAnalysisResults(element: KtElement): AnalysisResult {
assert(element.containingKtFile == file) { "Wrong file. Expected $file, but was ${element.containingKtFile}" }
val analyzableParent = KotlinResolveDataProvider.findAnalyzableParent(element)
return synchronized(this) {
ProgressIndicatorProvider.checkCanceled()
// step 1: perform incremental analysis IF it is applicable
getIncrementalAnalysisResult()?.let { return it }
// cache does not contain AnalysisResult per each kt/psi element
// instead it looks up analysis for its parents - see lookUp(analyzableElement)
// step 2: return result if it is cached
lookUp(analyzableParent)?.let {
return@synchronized it
}
// step 3: perform analyze of analyzableParent as nothing has been cached yet
val result = analyze(analyzableParent)
cache[analyzableParent] = result
return@synchronized result
}
}
private fun getIncrementalAnalysisResult(): AnalysisResult? {
// move fileResult from cache if it is stored there
if (fileResult == null && cache.containsKey(file)) {
fileResult = cache[file]
// drop existed results for entire cache:
// if incremental analysis is applicable it will produce a single value for file
// otherwise those results are potentially stale
cache.clear()
}
val inBlockModifications = file.inBlockModifications
if (inBlockModifications.isNotEmpty()) {
try {
// IF there is a cached result for ktFile and there are inBlockModifications
fileResult = fileResult?.let { result ->
var analysisResult = result
for (inBlockModification in inBlockModifications) {
val resultCtx = analysisResult.bindingContext
val stackedCtx =
if (resultCtx is StackedCompositeBindingContextTrace.StackedCompositeBindingContext) resultCtx else null
// no incremental analysis IF it is not applicable
if (stackedCtx?.isIncrementalAnalysisApplicable() == false) return@let null
val trace: StackedCompositeBindingContextTrace =
if (stackedCtx != null && stackedCtx.element() == inBlockModification) {
val trace = stackedCtx.bindingTrace()
trace.clear()
trace
} else {
// to reflect a depth of stacked binding context
val depth = (stackedCtx?.depth() ?: 0) + 1
StackedCompositeBindingContextTrace(
depth,
element = inBlockModification,
resolveContext = resolveSession.bindingContext,
parentContext = resultCtx
)
}
val newResult = analyze(inBlockModification, trace)
analysisResult = wrapResult(result, newResult, trace)
}
analysisResult
}
} finally {
file.clearInBlockModifications()
}
}
return fileResult
}
private fun lookUp(analyzableElement: KtElement): AnalysisResult? {
// Looking for parent elements that are already analyzed
@@ -75,25 +168,26 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
return result
}
fun getAnalysisResults(element: KtElement): AnalysisResult {
assert(element.containingKtFile == file) { "Wrong file. Expected $file, but was ${element.containingKtFile}" }
val analyzableParent = KotlinResolveDataProvider.findAnalyzableParent(element)
return synchronized<AnalysisResult>(this) {
val cached = lookUp(analyzableParent)
if (cached != null) return@synchronized cached
val result = analyze(analyzableParent)
cache[analyzableParent] = result
return@synchronized result
private fun wrapResult(
oldResult: AnalysisResult,
newResult: AnalysisResult,
elementBindingTrace: StackedCompositeBindingContextTrace
): AnalysisResult {
val newBindingCtx = elementBindingTrace.stackedContext
return when {
oldResult.isError() -> AnalysisResult.internalError(newBindingCtx, oldResult.error)
newResult.isError() -> AnalysisResult.internalError(newBindingCtx, newResult.error)
else -> AnalysisResult.success(
newBindingCtx,
oldResult.moduleDescriptor,
oldResult.shouldGenerateCode
)
}
}
private fun analyze(analyzableElement: KtElement): AnalysisResult {
private fun analyze(analyzableElement: KtElement, bindingTrace: BindingTrace? = null): AnalysisResult {
ProgressIndicatorProvider.checkCanceled()
val project = analyzableElement.project
if (DumbService.isDumb(project)) {
return AnalysisResult.EMPTY
@@ -107,7 +201,8 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
resolveSession,
codeFragmentAnalyzer,
bodyResolveCache,
analyzableElement
analyzableElement,
bindingTrace
)
} catch (e: ProcessCanceledException) {
throw e
@@ -122,6 +217,99 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
}
}
private class MergedDiagnostics(val diagnostics: Collection<Diagnostic>, override val modificationTracker: ModificationTracker) : Diagnostics {
@Suppress("UNCHECKED_CAST")
private val elementsCache = DiagnosticsElementsCache(this) { true }
override fun all() = diagnostics
override fun forElement(psiElement: PsiElement): MutableCollection<Diagnostic> = elementsCache.getDiagnostics(psiElement)
override fun noSuppression() = this
}
private class StackedCompositeBindingContextTrace(
val depth: Int, // depth of stack over original ktFile bindingContext
val element: KtElement,
val resolveContext: BindingContext,
val parentContext: BindingContext
) : DelegatingBindingTrace(
resolveContext,
"Stacked trace for resolution of $element",
allowSliceRewrite = true
) {
/**
* Effectively StackedCompositeBindingContext holds up-to-date and partially outdated contexts (parentContext)
*
* The most up-to-date results for element are stored here (in a DelegatingBindingTrace#map)
*
* Note: It does not delete outdated results rather hide it therefore there is some extra memory footprint.
*
* Note: stackedContext differs from DelegatingBindingTrace#bindingContext:
* if result is not present in this context it goes to parentContext rather to resolveContext
* diagnostics are aggregated from this context and parentContext
*/
val stackedContext = StackedCompositeBindingContext()
/**
* All diagnostics from parentContext apart those diagnostics those belongs to the element or its descendants
*/
val parentDiagnosticsApartElement: List<Diagnostic> = parentContext.diagnostics.all().filter { d ->
d.psiElement.parentsWithSelf.none { it == element }
}.toList()
inner class StackedCompositeBindingContext : BindingContext {
var cachedDiagnostics: Diagnostics? = null
fun bindingTrace(): StackedCompositeBindingContextTrace = this@StackedCompositeBindingContextTrace
fun element(): KtElement = this@StackedCompositeBindingContextTrace.element
fun depth(): Int = this@StackedCompositeBindingContextTrace.depth
// to prevent too deep stacked binding context
fun isIncrementalAnalysisApplicable(): Boolean = this@StackedCompositeBindingContextTrace.depth < 16
override fun getDiagnostics(): Diagnostics {
if (cachedDiagnostics == null) {
val diagnosticList =
parentDiagnosticsApartElement + (this@StackedCompositeBindingContextTrace.mutableDiagnostics?.all() ?: emptyList())
cachedDiagnostics = MergedDiagnostics(diagnosticList, parentContext.diagnostics.modificationTracker)
}
return cachedDiagnostics!!
}
override fun <K : Any?, V : Any?> get(slice: ReadOnlySlice<K, V>, key: K): V? {
return selfGet(slice, key) ?: parentContext.get(slice, key)
}
override fun getType(expression: KtExpression): KotlinType? {
val typeInfo = get(BindingContext.EXPRESSION_TYPE_INFO, expression)
return typeInfo?.type
}
override fun <K, V> getKeys(slice: WritableSlice<K, V>): Collection<K> {
val keys = map.getKeys(slice)
val fromParent = parentContext.getKeys(slice)
if (keys.isEmpty()) return fromParent
if (fromParent.isEmpty()) return keys
return keys + fromParent
}
override fun <K : Any?, V : Any?> getSliceContents(slice: ReadOnlySlice<K, V>): ImmutableMap<K, V> {
return ImmutableMap.copyOf(parentContext.getSliceContents(slice) + map.getSliceContents(slice))
}
override fun addOwnDataTo(trace: BindingTrace, commitDiagnostics: Boolean) = throw UnsupportedOperationException()
}
override fun clear() {
super.clear()
stackedContext.cachedDiagnostics = null
}
}
private object KotlinResolveDataProvider {
private val topmostElementTypes = arrayOf<Class<out PsiElement?>?>(
KtNamedFunction::class.java,
@@ -159,9 +347,9 @@ private object KotlinResolveDataProvider {
if (analyzableElement is KtClassInitializer) return analyzableElement.containingDeclaration
return analyzableElement
// if none of the above worked, take the outermost declaration
?: PsiTreeUtil.getTopmostParentOfType(element, KtDeclaration::class.java)
// if even that didn't work, take the whole file
?: element.containingKtFile
?: PsiTreeUtil.getTopmostParentOfType(element, KtDeclaration::class.java)
// if even that didn't work, take the whole file
?: element.containingKtFile
}
fun analyze(
@@ -171,7 +359,8 @@ private object KotlinResolveDataProvider {
resolveSession: ResolveSession,
codeFragmentAnalyzer: CodeFragmentAnalyzer,
bodyResolveCache: BodyResolveCache,
analyzableElement: KtElement
analyzableElement: KtElement,
bindingTrace: BindingTrace?
): AnalysisResult {
try {
if (analyzableElement is KtCodeFragment) {
@@ -180,6 +369,16 @@ private object KotlinResolveDataProvider {
return AnalysisResult.success(bindingContext, moduleDescriptor)
}
val trace = bindingTrace ?: DelegatingBindingTrace(
resolveSession.bindingContext,
"Trace for resolution of $analyzableElement",
allowSliceRewrite = true
)
val moduleInfo = analyzableElement.containingKtFile.getModuleInfo()
val targetPlatform = moduleInfo.platform
/*
Note that currently we *have* to re-create LazyTopDownAnalyzer with custom trace in order to disallow resolution of
bodies in top-level trace (trace from DI-container).
@@ -190,17 +389,6 @@ private object KotlinResolveDataProvider {
(see 'functionAdditionalResolve'). However, this trace is still needed, because we have other
codepaths for other KtDeclarationWithBodies (like property accessors/secondary constructors/class initializers)
*/
val trace = DelegatingBindingTrace(
resolveSession.bindingContext,
"Trace for resolution of " + analyzableElement,
allowSliceRewrite = true
)
val moduleInfo = analyzableElement.containingKtFile.getModuleInfo()
// TODO: should return proper platform!
val targetPlatform = moduleInfo.platform ?: TargetPlatformDetector.getPlatform(analyzableElement.containingKtFile)
val lazyTopDownAnalyzer = createContainerForLazyBodyResolve(
//TODO: should get ModuleContext
globalContext.withProject(project).withModule(moduleDescriptor),
@@ -20,7 +20,6 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiModificationTracker
import com.intellij.util.containers.SLRUCache
import org.jetbrains.kotlin.analyzer.*
import org.jetbrains.kotlin.context.GlobalContextImpl
@@ -28,6 +27,7 @@ import org.jetbrains.kotlin.context.withProject
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.caches.project.*
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.CompositeBindingContext
@@ -69,7 +69,8 @@ internal class ProjectResolutionFacade(
}
}
val allDependencies = resolverForProjectDependencies + listOf(PsiModificationTracker.MODIFICATION_COUNT)
val allDependencies =
resolverForProjectDependencies + listOf(KotlinCodeBlockModificationListener.getInstance(project).kotlinOutOfCodeBlockTracker)
CachedValueProvider.Result.create(results, allDependencies)
}, false
)
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.idea.caches.trackers
import com.intellij.lang.ASTNode
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.ModificationTracker
@@ -16,10 +17,7 @@ import com.intellij.pom.event.PomModelEvent
import com.intellij.pom.event.PomModelListener
import com.intellij.pom.tree.TreeAspect
import com.intellij.pom.tree.events.TreeChangeEvent
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import com.intellij.psi.PsiTreeChangeEvent
import com.intellij.psi.*
import com.intellij.psi.impl.PsiManagerImpl
import com.intellij.psi.impl.PsiModificationTrackerImpl
import com.intellij.psi.impl.PsiTreeChangeEventImpl
@@ -89,9 +87,17 @@ class KotlinCodeBlockModificationListener(
incFileModificationCount(ktFile)
val changedElements = changeSet.changedElements
// When a code fragment is reparsed, Intellij doesn't do an AST diff and considers the entire
// 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 } || changedElements.isEmpty()) {
// skip change if it contains only virtual/fake change
if (changedElements.isNotEmpty() &&
// ignore formatting (whitespaces etc)
(isFormattingChange(changeSet) ||
changedElements.all { !it.psi.isPhysical })
) return
val inBlockChange = inBlockModifications(changedElements)
if (!inBlockChange) {
messageBusConnection.deliverImmediately()
if (ktFile.isPhysical && !isReplLine(ktFile.virtualFile)) {
@@ -155,6 +161,8 @@ class KotlinCodeBlockModificationListener(
}
private fun incOutOfBlockModificationCount(file: KtFile) {
file.clearInBlockModifications()
val count = file.getUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT) ?: 0
file.putUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, count + 1)
}
@@ -165,32 +173,72 @@ class KotlinCodeBlockModificationListener(
tracker.incModificationCount()
}
fun getInsideCodeBlockModificationScope(element: PsiElement): KtElement? {
private fun inBlockModifications(elements: Array<ASTNode>): Boolean {
// When a code fragment is reparsed, Intellij doesn't do an AST diff and considers the entire
// contents to be replaced, which is represented in a POM event as an empty list of changed elements
if (elements.isEmpty()) return false
val inBlockElements = mutableSetOf<KtElement>()
for (element in elements) {
// skip fake PSI elements like `IntellijIdeaRulezzz$`
val psi = element.psi
if (!psi.isPhysical) continue
val modificationScope = getInsideCodeBlockModificationScope(psi) ?: return false
inBlockElements.add(modificationScope.blockDeclaration)
}
inBlockElements.forEach { it.containingKtFile.addInBlockModifiedItem(it) }
return inBlockElements.isNotEmpty()
}
fun isFormattingChange(changeSet: TreeChangeEvent): Boolean =
changeSet.changedElements.all {
changeSet.getChangesByElement(it).affectedChildren.all { c -> (c is PsiWhiteSpace || c is PsiComment) }
}
fun getInsideCodeBlockModificationScope(element: PsiElement): BlockModificationScopeElement? {
val lambda = element.getTopmostParentOfType<KtLambdaExpression>()
if (lambda is KtLambdaExpression) {
lambda.getTopmostParentOfType<KtSuperTypeCallEntry>()?.let {
return it
return BlockModificationScopeElement(it, it)
}
}
val blockDeclaration = KtPsiUtil.getTopmostParentOfTypes(element, *BLOCK_DECLARATION_TYPES) as? KtDeclaration ?: return null
if (KtPsiUtil.isLocal(blockDeclaration)) return null // should not be local declaration
val blockDeclaration =
KtPsiUtil.getTopmostParentOfTypes(element, *BLOCK_DECLARATION_TYPES) as? KtDeclaration ?: return null
// should not be local declaration
if (KtPsiUtil.isLocal(blockDeclaration))
return null
when (blockDeclaration) {
is KtNamedFunction -> {
if (blockDeclaration.hasBlockBody()) {
return blockDeclaration.bodyExpression?.takeIf { it.isAncestor(element) }
// case like `fun foo(): String {...<caret>...}`
return blockDeclaration.bodyExpression
?.takeIf { it.isAncestor(element) }
?.let { BlockModificationScopeElement(blockDeclaration, it) }
} else if (blockDeclaration.hasDeclaredReturnType()) {
return blockDeclaration.initializer?.takeIf { it.isAncestor(element) }
// case like `fun foo(): String = b<caret>labla`
return blockDeclaration.initializer
?.takeIf { it.isAncestor(element) }
?.let { BlockModificationScopeElement(blockDeclaration, it) }
}
}
is KtProperty -> {
if (blockDeclaration.typeReference != null) {
for (accessor in blockDeclaration.accessors) {
(accessor.initializer ?: accessor.bodyExpression)
?.takeIf { it.isAncestor(element) }
?.let { return it }
val accessors =
blockDeclaration.accessors.map { it.initializer ?: it.bodyExpression } + blockDeclaration.initializer
for (accessor in accessors) {
accessor?.takeIf {
it.isAncestor(element) &&
// adding annotations to accessor is the same as change contract of property
(element !is KtAnnotated || element.annotationEntries.isEmpty())
}
?.let { return BlockModificationScopeElement(blockDeclaration, it) }
}
}
}
@@ -201,14 +249,37 @@ class KotlinCodeBlockModificationListener(
?.lastOrNull()
?.getLambdaExpression()
?.takeIf { it.isAncestor(element) }
?.let { BlockModificationScopeElement(blockDeclaration, it) }
}
is KtClassInitializer -> {
blockDeclaration
.takeIf { it.isAncestor(element) }
?.let { ktClassInitializer ->
(KtPsiUtil.getTopmostParentOfTypes(blockDeclaration, KtClass::class.java) as? KtElement)?.let {
return BlockModificationScopeElement(it, ktClassInitializer)
}
}
}
// TODO: still under consideration - is it worth to track changes of private properties / methods
// problem could be in diagnostics - it is worth to manage it with modTracker
// is KtClass -> {
// return when (element) {
// is KtProperty -> if (element.visibilityModifierType()?.toVisibility() == Visibilities.PRIVATE) blockDeclaration else null
// is KtNamedFunction -> if (element.visibilityModifierType()?.toVisibility() == Visibilities.PRIVATE) blockDeclaration else null
// else -> null
// }
// }
else -> throw IllegalStateException()
}
return null
}
data class BlockModificationScopeElement(val blockDeclaration: KtElement, val element: KtElement)
fun isBlockDeclaration(declaration: KtDeclaration): Boolean {
return BLOCK_DECLARATION_TYPES.any { it.isInstance(declaration) }
}
@@ -216,6 +287,7 @@ class KotlinCodeBlockModificationListener(
private val BLOCK_DECLARATION_TYPES = arrayOf<Class<out KtDeclaration>>(
KtProperty::class.java,
KtNamedFunction::class.java,
KtClassInitializer::class.java,
KtScriptInitializer::class.java
)
@@ -231,6 +303,32 @@ val KtFile.perFileModificationTracker: ModificationTracker
private val FILE_OUT_OF_BLOCK_MODIFICATION_COUNT = Key<Long>("FILE_OUT_OF_BLOCK_MODIFICATION_COUNT")
val KtFile.outOfBlockModificationCount: Long
get() = getUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT) ?: 0
val KtFile.outOfBlockModificationCount: Long by NotNullableUserDataProperty(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, 0)
/**
* inBlockModifications is a collection of block elements those have in-block modifications
*/
private val IN_BLOCK_MODIFICATIONS = Key<MutableCollection<KtElement>>("IN_BLOCK_MODIFICATIONS")
val KtFile.inBlockModifications: Collection<KtElement>
get() {
val collection = getUserData(IN_BLOCK_MODIFICATIONS)
return collection ?: emptySet()
}
private fun KtFile.addInBlockModifiedItem(element: KtElement) {
val collection = putUserDataIfAbsent(IN_BLOCK_MODIFICATIONS, mutableSetOf())
synchronized(collection) {
collection.add(element)
}
}
fun KtFile.clearInBlockModifications() {
val collection = getUserData(IN_BLOCK_MODIFICATIONS)
collection?.let {
synchronized(it) {
it.clear()
}
}
}
@@ -18,18 +18,13 @@ package org.jetbrains.kotlin.idea.highlighter
import com.intellij.codeInsight.daemon.ChangeLocalityDetector
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener.Companion.getInsideCodeBlockModificationScope
class KotlinChangeLocalityDetector : ChangeLocalityDetector {
override fun getChangeHighlightingDirtyScopeFor(element: PsiElement): PsiElement? {
val parent = element.parent
if (element is KtBlockExpression && parent is KtNamedFunction && parent.name != null) {
if (parent.parents.all { it is KtClassBody || it is KtClassOrObject || it is KtFile || it is KtScript }) {
return parent
}
}
val modificationScope =
getInsideCodeBlockModificationScope(element) ?: return null
return null
return modificationScope.blockDeclaration
}
}