From f335e9f6b20ce83e241128ac6175519e7fde09c9 Mon Sep 17 00:00:00 2001 From: "Pavel V. Talanov" Date: Wed, 3 Jun 2015 14:19:58 +0300 Subject: [PATCH] Fix a problem where a sequence of out-of-block completions could lead to an inconsistent resolve session provided for a synthetic copy of a file --- .../KotlinCodeBlockModificationListener.kt | 2 +- .../idea/caches/resolve/KotlinCacheService.kt | 10 ++-- ...OutOfBlockCompletionModificationTracker.kt | 48 +++++++++++++++++++ .../completion/KotlinCompletionContributor.kt | 8 +++- idea/src/META-INF/plugin.xml | 3 ++ 5 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/KotlinOutOfBlockCompletionModificationTracker.kt diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KotlinCodeBlockModificationListener.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KotlinCodeBlockModificationListener.kt index ca53d6aace6..86f9b55d97f 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KotlinCodeBlockModificationListener.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KotlinCodeBlockModificationListener.kt @@ -103,7 +103,7 @@ public class KotlinCodeBlockModificationListener(modificationTracker: PsiModific return false } - private fun isInsideCodeBlock(element: PsiElement?): Boolean { + public fun isInsideCodeBlock(element: PsiElement?): Boolean { if (element is PsiFileSystemItem) return false if (element == null || element.getParent() == null) return true diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/KotlinCacheService.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/KotlinCacheService.kt index a3c851286a9..80b5235b6f9 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/KotlinCacheService.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/KotlinCacheService.kt @@ -104,6 +104,10 @@ public class KotlinCacheService(val project: Project) { // we assume that all files come from the same module val targetPlatform = files.map { TargetPlatformDetector.getPlatform(it) }.toSet().single() val syntheticFileModule = files.map { it.getModuleInfo() }.toSet().single() + val dependenciesForSyntheticFileCache = listOf( + PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT, + KotlinOutOfBlockCompletionModificationTracker.getInstance(project) + ) return when { syntheticFileModule is ModuleSourceInfo -> { val dependentModules = syntheticFileModule.getDependentModules() @@ -114,7 +118,7 @@ public class KotlinCacheService(val project: Project) { syntheticFiles = files, reuseDataFromCache = getGlobalCache(targetPlatform), moduleFilter = { it in dependentModules }, - dependencies = listOf(PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT) + dependencies = dependenciesForSyntheticFileCache ) ) } @@ -127,7 +131,7 @@ public class KotlinCacheService(val project: Project) { syntheticFiles = files, reuseDataFromCache = getGlobalLibrariesCache(targetPlatform), moduleFilter = { it == syntheticFileModule }, - dependencies = listOf(PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT) + dependencies = dependenciesForSyntheticFileCache ) ) } @@ -143,7 +147,7 @@ public class KotlinCacheService(val project: Project) { targetPlatform, syntheticFiles = files, moduleFilter = { true }, - dependencies = listOf(PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT) + dependencies = dependenciesForSyntheticFileCache ) ) } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/KotlinOutOfBlockCompletionModificationTracker.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/KotlinOutOfBlockCompletionModificationTracker.kt new file mode 100644 index 00000000000..6339f9388b2 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/KotlinOutOfBlockCompletionModificationTracker.kt @@ -0,0 +1,48 @@ +/* + * 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. +public class KotlinOutOfBlockCompletionModificationTracker() : SimpleModificationTracker() { + companion object { + public fun getInstance(project: Project): KotlinOutOfBlockCompletionModificationTracker + = ServiceManager.getService(project, javaClass())!! + } +} + + +public fun performCompletionWithOutOfBlockTracking(completionPosition: PsiElement, body: () -> Unit) { + if (KotlinCodeBlockModificationListener.isInsideCodeBlock(completionPosition)) { + body() + return + } + try { + body() + } + finally { + KotlinOutOfBlockCompletionModificationTracker.getInstance(completionPosition.getProject()).incModificationCount() + } +} \ No newline at end of file diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt index 289ff2e29f9..110e1b57854 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/KotlinCompletionContributor.kt @@ -34,11 +34,11 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.FunctionDescriptor 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.lexer.JetTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* -import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull @@ -197,6 +197,12 @@ public class KotlinCompletionContributor : CompletionContributor() { val position = parameters.getPosition() if (position.getContainingFile() !is JetFile) return + performCompletionWithOutOfBlockTracking(position) { + doComplete(parameters, position, result) + } + } + + private fun doComplete(parameters: CompletionParameters, position: PsiElement, result: CompletionResultSet) { if (position.getNonStrictParentOfType() != null) { // don't stop here, allow other contributors to run return diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index e44d891983b..90ba1acc7b0 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -219,6 +219,9 @@ + +