ResolutionFacade for synthetic files should be invalidated on changes in these synthetic files

Fixed EA-77017
This commit is contained in:
Valentin Kipyatkov
2016-03-01 13:56:22 +03:00
parent 28e85517eb
commit 05dc4c1c48
8 changed files with 108 additions and 151 deletions
@@ -16,15 +16,16 @@
package org.jetbrains.kotlin.asJava package org.jetbrains.kotlin.asJava
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project
import com.intellij.psi.PsiClass import com.intellij.openapi.util.Key
import com.intellij.pom.PomManager
import com.intellij.pom.PomModelAspect
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.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFileSystemItem
import com.intellij.psi.PsiInvalidElementAccessException
import com.intellij.psi.impl.PsiModificationTrackerImpl import com.intellij.psi.impl.PsiModificationTrackerImpl
import com.intellij.psi.impl.PsiTreeChangeEventImpl
import com.intellij.psi.impl.PsiTreeChangeEventImpl.PsiEventType.*
import com.intellij.psi.impl.PsiTreeChangePreprocessor
import com.intellij.psi.util.PsiModificationTracker import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isAncestor import org.jetbrains.kotlin.psi.psiUtil.isAncestor
@@ -33,84 +34,40 @@ import org.jetbrains.kotlin.psi.psiUtil.parents
/** /**
* Tested in OutOfBlockModificationTestGenerated * Tested in OutOfBlockModificationTestGenerated
*/ */
class KotlinCodeBlockModificationListener(modificationTracker: PsiModificationTracker) : PsiTreeChangePreprocessor { class KotlinCodeBlockModificationListener(
private val myModificationTracker = modificationTracker as PsiModificationTrackerImpl modificationTracker: PsiModificationTracker,
private val project: Project,
override fun treeChanged(event: PsiTreeChangeEventImpl) { private val treeAspect: TreeAspect
if (event.file !is KtFile) return ) {
init {
when (event.code) { val model = PomManager.getModel(project)
BEFORE_CHILDREN_CHANGE, @Suppress("NAME_SHADOWING")
BEFORE_PROPERTY_CHANGE, val modificationTracker = modificationTracker as PsiModificationTrackerImpl
BEFORE_CHILD_MOVEMENT, model.addModelListener(object: PomModelListener {
BEFORE_CHILD_REPLACEMENT, override fun isAspectChangeInteresting(aspect: PomModelAspect): Boolean {
BEFORE_CHILD_ADDITION, return aspect == treeAspect
BEFORE_CHILD_REMOVAL -> {
// skip
} }
CHILD_ADDED, override fun modelChanged(event: PomModelEvent) {
CHILD_REMOVED, val changeSet = event.getChangeSet(treeAspect) as TreeChangeEvent? ?: return
CHILD_REPLACED -> { val file = changeSet.rootElement.psi.containingFile as? KtFile ?: return
processChange(event.parent, event.oldChild, event.child) if (changeSet.changedElements.any { !isInsideCodeBlock(it.psi) }) {
} if (file.isPhysical) {
modificationTracker.incCounter()
CHILDREN_CHANGED -> { }
if (!event.isGenericChange) { incOutOfBlockModificationCount(file)
processChange(event.parent, event.parent, null)
} }
} }
})
CHILD_MOVED,
PROPERTY_CHANGED -> {
myModificationTracker.incCounter()
}
else -> LOG.error("Unknown code:" + event.code)
}
}
private fun processChange(parent: PsiElement?, child1: PsiElement?, child2: PsiElement?) {
try {
if (!isInsideCodeBlock(parent)) {
if (parent != null && parent.containingFile is KtFile) {
myModificationTracker.incCounter()
}
else {
myModificationTracker.incOutOfCodeBlockModificationCounter()
}
return
}
if (containsClassesInside(child1) || (child2 != child1 && containsClassesInside(child2))) {
myModificationTracker.incCounter()
}
}
catch (e: PsiInvalidElementAccessException) {
myModificationTracker.incCounter() // Shall not happen actually, just a pre-release paranoia
}
} }
companion object { companion object {
private val LOG = Logger.getInstance("#org.jetbrains.kotlin.asJava.KotlinCodeBlockModificationListener") private fun incOutOfBlockModificationCount(file: KtFile) {
val count = file.getUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT) ?: 0
private fun containsClassesInside(element: PsiElement?): Boolean { file.putUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, count + 1)
if (element == null) return false
if (element is PsiClass) return true
var child = element.firstChild
while (child != null) {
if (containsClassesInside(child)) return true
child = child.nextSibling
}
return false
} }
fun isInsideCodeBlock(element: PsiElement?): Boolean { private fun isInsideCodeBlock(element: PsiElement): Boolean {
if (element is PsiFileSystemItem) return false
if (element == null || element.parent == null) return true
//TODO: other types //TODO: other types
val blockDeclaration = KtPsiUtil.getTopmostParentOfTypes(element, *BLOCK_DECLARATION_TYPES) ?: return false val blockDeclaration = KtPsiUtil.getTopmostParentOfTypes(element, *BLOCK_DECLARATION_TYPES) ?: return false
if (blockDeclaration.parents.any { it !is KtClassBody && it !is KtClassOrObject && it !is KtFile }) return false // should not be local declaration if (blockDeclaration.parents.any { it !is KtClassBody && it !is KtClassOrObject && it !is KtFile }) return false // should not be local declaration
@@ -149,3 +106,8 @@ class KotlinCodeBlockModificationListener(modificationTracker: PsiModificationTr
) )
} }
} }
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
@@ -112,3 +112,11 @@ inline fun <T, R : Any> Iterable<T>.firstNotNullResult(transform: (T) -> R?): R?
} }
return null return null
} }
inline fun <T> Iterable<T>.sumByLong(selector: (T) -> Long): Long {
var sum: Long = 0
for (element in this) {
sum += selector(element)
}
return sum
}
@@ -19,12 +19,14 @@ package org.jetbrains.kotlin.idea.caches.resolve
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootModificationTracker import com.intellij.openapi.roots.ProjectRootModificationTracker
import com.intellij.openapi.util.ModificationTracker
import com.intellij.psi.util.CachedValue import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiModificationTracker import com.intellij.psi.util.PsiModificationTracker
import com.intellij.util.containers.SLRUCache import com.intellij.util.containers.SLRUCache
import org.jetbrains.kotlin.analyzer.EmptyResolverForProject import org.jetbrains.kotlin.analyzer.EmptyResolverForProject
import org.jetbrains.kotlin.asJava.outOfBlockModificationCount
import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
import org.jetbrains.kotlin.container.getService import org.jetbrains.kotlin.container.getService
@@ -40,6 +42,7 @@ import org.jetbrains.kotlin.resolve.TargetPlatform
import org.jetbrains.kotlin.resolve.diagnostics.KotlinSuppressCache import org.jetbrains.kotlin.resolve.diagnostics.KotlinSuppressCache
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.utils.addToStdlib.sumByLong
import org.jetbrains.kotlin.utils.keysToMap import org.jetbrains.kotlin.utils.keysToMap
internal val LOG = Logger.getInstance(KotlinCacheService::class.java) internal val LOG = Logger.getInstance(KotlinCacheService::class.java)
@@ -92,10 +95,10 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
// we assume that all files come from the same module // we assume that all files come from the same module
val targetPlatform = files.map { TargetPlatformDetector.getPlatform(it) }.toSet().single() val targetPlatform = files.map { TargetPlatformDetector.getPlatform(it) }.toSet().single()
val syntheticFileModule = files.map { it.getModuleInfo() }.toSet().single() val syntheticFileModule = files.map { it.getModuleInfo() }.toSet().single()
val dependenciesForSyntheticFileCache = listOf( val filesModificationTracker = ModificationTracker {
PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT, files.sumByLong { it.outOfBlockModificationCount }
KotlinOutOfBlockCompletionModificationTracker.getInstance(project) }
) val dependenciesForSyntheticFileCache = listOf(PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT, filesModificationTracker)
val debugName = "completion/highlighting in $syntheticFileModule for files ${files.joinToString { it.name }} for platform $targetPlatform" val debugName = "completion/highlighting in $syntheticFileModule for files ${files.joinToString { it.name }} for platform $targetPlatform"
return when { return when {
syntheticFileModule is ModuleSourceInfo -> { syntheticFileModule is ModuleSourceInfo -> {
@@ -179,19 +182,17 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
private val syntheticFileCachesLock = Any() private val syntheticFileCachesLock = Any()
private val slruCacheProvider = CachedValueProvider { private val syntheticFilesCacheProvider = CachedValueProvider {
CachedValueProvider.Result(object : SLRUCache<Set<KtFile>, ProjectResolutionFacade>(2, 3) { CachedValueProvider.Result(object : SLRUCache<Set<KtFile>, ProjectResolutionFacade>(2, 3) {
override fun createValue(files: Set<KtFile>): ProjectResolutionFacade { override fun createValue(files: Set<KtFile>) = createFacadeForSyntheticFiles(files)
return createFacadeForSyntheticFiles(files)
}
}, LibraryModificationTracker.getInstance(project), ProjectRootModificationTracker.getInstance(project)) }, LibraryModificationTracker.getInstance(project), ProjectRootModificationTracker.getInstance(project))
} }
private fun getFacadeForSyntheticFiles(files: Set<KtFile>): ProjectResolutionFacade { private fun getFacadeForSyntheticFiles(files: Set<KtFile>): ProjectResolutionFacade {
return synchronized(syntheticFileCachesLock) { synchronized(syntheticFileCachesLock) {
//NOTE: computations inside createCacheForSyntheticFiles depend on project root structure //NOTE: computations inside createCacheForSyntheticFiles depend on project root structure
// so we additionally drop the whole slru cache on change // so we additionally drop the whole slru cache on change
CachedValuesManager.getManager(project).getCachedValue(project, slruCacheProvider).get(files) return CachedValuesManager.getManager(project).getCachedValue(project, syntheticFilesCacheProvider).get(files)
} }
} }
@@ -1,48 +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.components.ServiceManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.SimpleModificationTracker
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.asJava.KotlinCodeBlockModificationListener
// NOTE: Sadly we must track out of block completion and drop sessions for synthetic files after the completion happens.
// Synthetic file for completion can be modified without sending tree changed events and sequence of completions can lead to inconsistent
// resolve session being cached for such a file otherwise.
// This code is not tested. See KT-6216 for an example.
class KotlinOutOfBlockCompletionModificationTracker() : SimpleModificationTracker() {
companion object {
fun getInstance(project: Project): KotlinOutOfBlockCompletionModificationTracker
= ServiceManager.getService(project, KotlinOutOfBlockCompletionModificationTracker::class.java)!!
}
}
fun performCompletionWithOutOfBlockTracking(completionPosition: PsiElement, body: () -> Unit) {
if (KotlinCodeBlockModificationListener.isInsideCodeBlock(completionPosition)) {
body()
return
}
try {
body()
}
finally {
KotlinOutOfBlockCompletionModificationTracker.getInstance(completionPosition.project).incModificationCount()
}
}
@@ -34,7 +34,6 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.performCompletionWithOutOfBlockTracking
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletion import org.jetbrains.kotlin.idea.completion.smart.SmartCompletion
import org.jetbrains.kotlin.idea.completion.smart.SmartCompletionSession import org.jetbrains.kotlin.idea.completion.smart.SmartCompletionSession
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
@@ -254,17 +253,13 @@ class KotlinCompletionContributor : CompletionContributor() {
val context = position.getUserData(CompletionContext.COMPLETION_CONTEXT_KEY)!! val context = position.getUserData(CompletionContext.COMPLETION_CONTEXT_KEY)!!
val correctedOffset = context.offsetMap.getOffset(STRING_TEMPLATE_AFTER_DOT_REAL_START_OFFSET) val correctedOffset = context.offsetMap.getOffset(STRING_TEMPLATE_AFTER_DOT_REAL_START_OFFSET)
val correctedParameters = parameters.withPosition(correctedPosition, correctedOffset) val correctedParameters = parameters.withPosition(correctedPosition, correctedOffset)
performCompletionWithOutOfBlockTracking(position) { doComplete(correctedParameters, toFromOriginalFileMapper, result,
doComplete(correctedParameters, toFromOriginalFileMapper, result, lookupElementPostProcessor = { wrapLookupElementForStringTemplateAfterDotCompletion(it) })
lookupElementPostProcessor = { wrapLookupElementForStringTemplateAfterDotCompletion(it) })
}
return return
} }
} }
performCompletionWithOutOfBlockTracking(position) { doComplete(parameters, toFromOriginalFileMapper, result)
doComplete(parameters, toFromOriginalFileMapper, result)
}
} }
private fun doComplete( private fun doComplete(
+3 -5
View File
@@ -31,6 +31,9 @@
<component> <component>
<implementation-class>org.jetbrains.kotlin.idea.completion.LookupCancelWatcher</implementation-class> <implementation-class>org.jetbrains.kotlin.idea.completion.LookupCancelWatcher</implementation-class>
</component> </component>
<component>
<implementation-class>org.jetbrains.kotlin.asJava.KotlinCodeBlockModificationListener</implementation-class>
</component>
</project-components> </project-components>
<application-components> <application-components>
@@ -264,9 +267,6 @@
<projectService serviceInterface="org.jetbrains.kotlin.idea.caches.resolve.LibraryModificationTracker" <projectService serviceInterface="org.jetbrains.kotlin.idea.caches.resolve.LibraryModificationTracker"
serviceImplementation="org.jetbrains.kotlin.idea.caches.resolve.LibraryModificationTracker"/> serviceImplementation="org.jetbrains.kotlin.idea.caches.resolve.LibraryModificationTracker"/>
<projectService serviceInterface="org.jetbrains.kotlin.idea.caches.resolve.KotlinOutOfBlockCompletionModificationTracker"
serviceImplementation="org.jetbrains.kotlin.idea.caches.resolve.KotlinOutOfBlockCompletionModificationTracker"/>
<projectService serviceInterface="org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade" <projectService serviceInterface="org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade"
serviceImplementation="org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade"/> serviceImplementation="org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade"/>
@@ -583,8 +583,6 @@
<editorNotificationProvider implementation="org.jetbrains.kotlin.idea.versions.UnsupportedAbiVersionNotificationPanelProvider"/> <editorNotificationProvider implementation="org.jetbrains.kotlin.idea.versions.UnsupportedAbiVersionNotificationPanelProvider"/>
<editorNotificationProvider implementation="org.jetbrains.kotlin.idea.configuration.KotlinSetupEnvironmentNotificationProvider"/> <editorNotificationProvider implementation="org.jetbrains.kotlin.idea.configuration.KotlinSetupEnvironmentNotificationProvider"/>
<psi.treeChangePreprocessor implementation="org.jetbrains.kotlin.asJava.KotlinCodeBlockModificationListener"/>
<referencesSearch implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearcher"/> <referencesSearch implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearcher"/>
<directClassInheritorsSearch implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinDirectInheritorsSearcher"/> <directClassInheritorsSearch implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinDirectInheritorsSearcher"/>
<overridingMethodsSearch implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinOverridingMethodsWithGenericsSearcher"/> <overridingMethodsSearch implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinOverridingMethodsWithGenericsSearcher"/>
@@ -22,7 +22,6 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiRecursiveElementVisitor import com.intellij.psi.PsiRecursiveElementVisitor
import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.util.SmartList import com.intellij.util.SmartList
import org.jetbrains.kotlin.idea.caches.resolve.KotlinOutOfBlockCompletionModificationTracker
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
import org.jetbrains.kotlin.idea.conversion.copy.range import org.jetbrains.kotlin.idea.conversion.copy.range
@@ -66,9 +65,6 @@ class J2kPostProcessor(private val formatCode: Boolean) : PostProcessor {
if (modificationStamp == file.modificationStamp) break if (modificationStamp == file.modificationStamp) break
//TODO: it's a hack!
KotlinOutOfBlockCompletionModificationTracker.getInstance(file.project).incModificationCount()
elementToActions = collectAvailableActions(file, rangeMarker) elementToActions = collectAvailableActions(file, rangeMarker)
} }
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2016 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
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.idea.util.getFileResolutionScope
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
class DummyFileResolveCachingTest : LightCodeInsightFixtureTestCase() {
override fun getProjectDescriptor() = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
fun test() {
myFixture.configureByText(KotlinFileType.INSTANCE, "")
val dummyFileText = "import java.util.ArrayList"
val dummyFile = KtPsiFactory(project).createAnalyzableFile("Dummy.kt", dummyFileText, file)
dummyFile.getResolutionFacade().getFileResolutionScope(dummyFile)
dummyFile.importDirectives.single().delete()
val resolutionScope = dummyFile.getResolutionFacade().getFileResolutionScope(dummyFile)
val classifier = resolutionScope.findClassifier(Name.identifier("ArrayList"), NoLookupLocation.FROM_IDE)
assertNull(classifier)
}
}