ResolveElementCache works correctly for non-physical files

This commit is contained in:
Valentin Kipyatkov
2015-08-12 15:21:25 +03:00
parent 4b6991603f
commit b6c94a9e68
6 changed files with 165 additions and 155 deletions
@@ -1,81 +0,0 @@
/*
* Copyright 2010-2015 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.util.Key
import com.intellij.psi.PsiElement
import com.intellij.psi.impl.PsiTreeChangeEventImpl
import com.intellij.psi.impl.PsiTreeChangePreprocessor
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
public class SubtreeModificationCountUpdater : PsiTreeChangePreprocessor {
override fun treeChanged(event: PsiTreeChangeEventImpl) {
if (event.file !is JetFile) return
when (event.code!!) {
PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILDREN_CHANGE,
PsiTreeChangeEventImpl.PsiEventType.BEFORE_PROPERTY_CHANGE,
PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILD_MOVEMENT,
PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILD_REPLACEMENT,
PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILD_ADDITION,
PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILD_REMOVAL,
PsiTreeChangeEventImpl.PsiEventType.PROPERTY_CHANGED -> {
// skip
}
PsiTreeChangeEventImpl.PsiEventType.CHILD_ADDED,
PsiTreeChangeEventImpl.PsiEventType.CHILD_REMOVED,
PsiTreeChangeEventImpl.PsiEventType.CHILD_REPLACED -> {
incCounters(event.parent)
}
PsiTreeChangeEventImpl.PsiEventType.CHILDREN_CHANGED -> {
if (!event.isGenericChange) {
incCounters(event.parent)
}
}
PsiTreeChangeEventImpl.PsiEventType.CHILD_MOVED -> {
incCounters(event.oldParent)
incCounters(event.newParent)
}
else -> error("Unknown code:${event.code}")
}
}
private fun incCounters(element: PsiElement?) {
element?.parentsWithSelf?.forEach {
val count = it.getUserData(MODIFICATION_COUNT_KEY)
if (count != null) {
it.putUserData(MODIFICATION_COUNT_KEY, count + 1)
}
}
}
companion object {
private val MODIFICATION_COUNT_KEY = Key<Int>("SubtreeModificationCountUpdater.MODIFICATION_COUNT_KEY")
public fun getModificationCount(element: PsiElement): Int {
element.getUserData(MODIFICATION_COUNT_KEY)?.let { return it }
element.putUserData(MODIFICATION_COUNT_KEY, 0)
return 0
}
}
}
@@ -23,8 +23,6 @@ import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiModificationTracker
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.asJava.KotlinCodeBlockModificationListener
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.SubtreeModificationCountUpdater
import org.jetbrains.kotlin.idea.stubindex.JetProbablyNothingFunctionShortNameIndex
import org.jetbrains.kotlin.idea.stubindex.JetProbablyNothingPropertyShortNameIndex
import org.jetbrains.kotlin.psi.*
@@ -33,34 +31,53 @@ import org.jetbrains.kotlin.resolve.BodyResolveCache
import org.jetbrains.kotlin.resolve.StatementFilter
import org.jetbrains.kotlin.resolve.TargetPlatform
import org.jetbrains.kotlin.resolve.lazy.*
import org.jetbrains.kotlin.utils.addToStdlib.check
public class ResolveElementCache(resolveSession: ResolveSession, private val project: Project) : ElementResolver(resolveSession), BodyResolveCache {
private class CachedResolve(val bindingContext: BindingContext, resolveElement: JetElement) {
private val elementModificationCount: Int? =
if (resolveElement is JetDeclaration && KotlinCodeBlockModificationListener.isBlockDeclaration(resolveElement))
SubtreeModificationCountUpdater.getModificationCount(resolveElement)
else
null
private class CachedFullResolve(val bindingContext: BindingContext, resolveElement: JetElement) {
private val modificationStamp: Long? = modificationStamp(resolveElement)
fun isUpToDate(resolveElement: JetElement)
= elementModificationCount == null || elementModificationCount == SubtreeModificationCountUpdater.getModificationCount(resolveElement)
fun isUpToDate(resolveElement: JetElement) = modificationStamp == modificationStamp(resolveElement)
private fun modificationStamp(resolveElement: JetElement): Long? {
val file = resolveElement.containingFile
return if (!file.isPhysical) // for non-physical file we don't get OUT_OF_CODE_BLOCK_MODIFICATION_COUNT increased and must reset data on any modification of the file
file.modificationStamp
else if (resolveElement is JetDeclaration && KotlinCodeBlockModificationListener.isBlockDeclaration(resolveElement))
resolveElement.getModificationStamp()
else
null
}
}
// drop whole cache after change "out of code block"
private val fullResolveCache: CachedValue<MutableMap<JetElement, CachedResolve>> = CachedValuesManager.getManager(project).createCachedValue(
object : CachedValueProvider<MutableMap<JetElement, CachedResolve>> {
override fun compute(): CachedValueProvider.Result<MutableMap<JetElement, CachedResolve>> {
return CachedValueProvider.Result.create(ContainerUtil.createConcurrentSoftValueMap<JetElement, CachedResolve>(),
private val fullResolveCache: CachedValue<MutableMap<JetElement, CachedFullResolve>> = CachedValuesManager.getManager(project).createCachedValue(
object : CachedValueProvider<MutableMap<JetElement, CachedFullResolve>> {
override fun compute(): CachedValueProvider.Result<MutableMap<JetElement, CachedFullResolve>> {
return CachedValueProvider.Result.create(ContainerUtil.createConcurrentSoftValueMap<JetElement, CachedFullResolve>(),
PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT,
resolveSession.exceptionTracker)
}
},
false)
private val partialBodyResolveCache: CachedValue<MutableMap<JetExpression, BindingContext>> = CachedValuesManager.getManager(project).createCachedValue(
object : CachedValueProvider<MutableMap<JetExpression, BindingContext>> {
override fun compute(): CachedValueProvider.Result<MutableMap<JetExpression, BindingContext>> {
return CachedValueProvider.Result.create(ContainerUtil.createConcurrentSoftValueMap<JetExpression, BindingContext>(),
private class CachedPartialResolve(val bindingContext: BindingContext, file: JetFile) {
private val modificationStamp: Long? = modificationStamp(file)
fun isUpToDate(file: JetFile) = modificationStamp == modificationStamp(file)
private fun modificationStamp(file: JetFile): Long? {
return if (!file.isPhysical) // for non-physical file we don't get MODIFICATION_COUNT increased and must reset data on any modification of the file
file.modificationStamp
else
null
}
}
private val partialBodyResolveCache: CachedValue<MutableMap<JetExpression, CachedPartialResolve>> = CachedValuesManager.getManager(project).createCachedValue(
object : CachedValueProvider<MutableMap<JetExpression, CachedPartialResolve>> {
override fun compute(): CachedValueProvider.Result<MutableMap<JetExpression, CachedPartialResolve>> {
return CachedValueProvider.Result.create(ContainerUtil.createConcurrentSoftValueMap<JetExpression, CachedPartialResolve>(),
PsiModificationTracker.MODIFICATION_COUNT,
resolveSession.exceptionTracker)
}
@@ -83,7 +100,7 @@ public class ResolveElementCache(resolveSession: ResolveSession, private val pro
when (bodyResolveMode) {
BodyResolveMode.FULL -> {
val bindingContext = performElementAdditionalResolve(resolveElement, resolveElement, BodyResolveMode.FULL).first
fullResolveMap[resolveElement] = CachedResolve(bindingContext, resolveElement)
fullResolveMap[resolveElement] = CachedFullResolve(bindingContext, resolveElement)
return bindingContext
}
@@ -92,23 +109,28 @@ public class ResolveElementCache(resolveSession: ResolveSession, private val pro
return getElementAdditionalResolve(resolveElement, contextElement, BodyResolveMode.FULL)
}
val file = resolveElement.getContainingJetFile()
val statementToResolve = PartialBodyResolveFilter.findStatementToResolve(contextElement, resolveElement)
val partialResolveMap = partialBodyResolveCache.value
partialResolveMap[statementToResolve ?: resolveElement]?.let { return it } // partial resolve is already cached for this statement
partialResolveMap[statementToResolve ?: resolveElement]
?.check { it.isUpToDate(file) }
?.let { return it.bindingContext } // partial resolve is already cached for this statement
val (bindingContext, statementFilter) = performElementAdditionalResolve(resolveElement, contextElement, BodyResolveMode.PARTIAL)
if (statementFilter == StatementFilter.NONE) { // partial resolve is not supported for the given declaration - full resolve performed instead
fullResolveMap[resolveElement] = CachedResolve(bindingContext, resolveElement)
fullResolveMap[resolveElement] = CachedFullResolve(bindingContext, resolveElement)
return bindingContext
}
val resolveToCache = CachedPartialResolve(bindingContext, file)
for (statement in (statementFilter as PartialBodyResolveFilter).allStatementsToResolve) {
if (!partialResolveMap.containsKey(statement) && bindingContext[BindingContext.PROCESSED, statement] == true) {
partialResolveMap[statement] = bindingContext
partialResolveMap[statement] = resolveToCache
}
}
partialResolveMap[resolveElement] = bindingContext // we use the whole declaration key in the map to obtain resolve not inside any block (e.g. default parameter values)
partialResolveMap[resolveElement] = resolveToCache // we use the whole declaration key in the map to obtain resolve not inside any block (e.g. default parameter values)
return bindingContext
}
@@ -136,3 +158,4 @@ public class ResolveElementCache(resolveSession: ResolveSession, private val pro
override fun resolveFunctionBody(function: JetNamedFunction)
= getElementAdditionalResolve(function, function, BodyResolveMode.FULL)
}