From 853503d75f00f3dd87aaa7de821dad81257bacf7 Mon Sep 17 00:00:00 2001 From: Vladimir Dolzhenko Date: Wed, 1 Jul 2020 08:22:58 +0000 Subject: [PATCH] Force perform FULL analysis to avoid redundant analysis for the current open file #KT-38687 Fixed --- .../idea/project/ResolveElementCache.kt | 24 +++++++ .../idea/testFramework/ProjectOpenAction.kt | 5 +- .../idea/AbstractResolveElementCacheTest.kt | 14 +++++ .../kotlin/idea/ResolveElementCacheTest.kt | 62 +++++++++++-------- 4 files changed, 76 insertions(+), 29 deletions(-) diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/ResolveElementCache.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/ResolveElementCache.kt index d7f221f8627..6261820bd1f 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/ResolveElementCache.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/ResolveElementCache.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.idea.project +import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ProjectRootModificationTracker import com.intellij.psi.util.CachedValue @@ -12,6 +13,7 @@ import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValuesManager import com.intellij.util.containers.ContainerUtil import com.intellij.util.containers.SLRUCache +import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.cfg.ControlFlowInformationProvider import org.jetbrains.kotlin.container.get import org.jetbrains.kotlin.context.SimpleGlobalContext @@ -25,6 +27,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.util.analyzeControlFlow import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListenerCompat import org.jetbrains.kotlin.idea.caches.trackers.inBlockModificationCount +import org.jetbrains.kotlin.idea.util.application.isUnitTestMode import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.platform.TargetPlatform import org.jetbrains.kotlin.psi.* @@ -151,6 +154,22 @@ class ResolveElementCache( assert(bodyResolveMode == BodyResolveMode.FULL) } + // KT-38687: There are lots of editor specific items like inlays, line markers, injected items etc + // those require analysis: it is called with BodyResolveMode.PARTIAL or BodyResolveMode.PARTIAL_WITH_CFA almost simultaneously. + // In the same time Kotlin Annotator (e.g. KotlinPsiChecker) requires BodyResolveMode.FULL analysis. + // + // While results of FULL could be reused by any of PARTIAL or PARTIAL_WITH_CFA analysis, + // neither result of PARTIAL nor result of PARTIAL_WITH_CFA analyses could be reused by FULL analysis. + // + // Force perform FULL analysis to avoid redundant analysis for the current selected files. + if (bodyResolveMode != BodyResolveMode.FULL && (!isUnitTestMode() || forceFullAnalysisModeInTests)) { + val virtualFile = resolveElement.containingFile.virtualFile + // applicable for real (physical) files only + if (virtualFile != null && FileEditorManager.getInstance(resolveElement.project).selectedFiles.any { it == virtualFile }) { + return getElementsAdditionalResolve(resolveElement, contextElements, BodyResolveMode.FULL) + } + } + // check if full additional resolve already performed and is up-to-date val fullResolveMap = fullResolveCache.value val cachedFullResolve = fullResolveMap[resolveElement] @@ -807,5 +826,10 @@ class ResolveElementCache( override fun getTopDownAnalysisMode() = topDownAnalysisMode } + + companion object { + @set:TestOnly + var forceFullAnalysisModeInTests: Boolean = false + } } diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/ProjectOpenAction.kt b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/ProjectOpenAction.kt index 2d2cdb69777..bf1a742c0a1 100644 --- a/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/ProjectOpenAction.kt +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/testFramework/ProjectOpenAction.kt @@ -26,11 +26,11 @@ import com.intellij.util.io.exists import org.jetbrains.kotlin.idea.configuration.getModulesWithKotlinFiles import org.jetbrains.kotlin.idea.perf.Stats.Companion.runAndMeasure import org.jetbrains.kotlin.idea.perf.util.logMessage +import org.jetbrains.kotlin.idea.project.ResolveElementCache import org.jetbrains.kotlin.idea.project.getAndCacheLanguageLevelByDependencies import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil import java.io.File import java.nio.file.Paths -import kotlin.test.assertNotNull import kotlin.test.assertTrue data class OpenProject(val projectPath: String, val projectName: String, val jdk: Sdk, val projectOpenAction: ProjectOpenAction) @@ -64,7 +64,7 @@ enum class ProjectOpenAction { val projectManagerEx = ProjectManagerEx.getInstanceEx() val project = loadProjectWithName(projectPath, projectName) - assertNotNull(project, "project $projectName at $projectPath is not loaded") + ?: error("project $projectName at $projectPath is not loaded") runWriteAction { ProjectRootManager.getInstance(project).projectSdk = jdk @@ -123,6 +123,7 @@ enum class ProjectOpenAction { open fun postOpenProject(project: Project, openProject: OpenProject) { ApplicationManager.getApplication().executeOnPooledThread { + ResolveElementCache.forceFullAnalysisModeInTests = true DumbService.getInstance(project).waitForSmartMode() for (module in getModulesWithKotlinFiles(project)) { diff --git a/idea/tests/org/jetbrains/kotlin/idea/AbstractResolveElementCacheTest.kt b/idea/tests/org/jetbrains/kotlin/idea/AbstractResolveElementCacheTest.kt index 88ee80c2589..935562184cd 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/AbstractResolveElementCacheTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/AbstractResolveElementCacheTest.kt @@ -5,8 +5,12 @@ package org.jetbrains.kotlin.idea +import com.intellij.util.ThrowableRunnable +import org.intellij.lang.annotations.Language +import org.jetbrains.kotlin.idea.project.ResolveElementCache import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor +import org.jetbrains.kotlin.idea.test.runAll import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.psi.* @@ -43,6 +47,16 @@ class C(param1: String = "", param2: Int = 0) { val factory: KtPsiFactory ) + override fun tearDown() { + runAll( + ThrowableRunnable { ResolveElementCache.forceFullAnalysisModeInTests = false }, + ThrowableRunnable { super.tearDown() }, + ) + } + + protected fun configureWithKotlin(@Language("kotlin") text: String): KtFile = + myFixture.configureByText("Test.kt", text.trimIndent()) as KtFile + protected fun doTest(handler: Data.() -> Unit) { val file = myFixture.configureByText("Test.kt", FILE_TEXT) as KtFile val data = extractData(file) diff --git a/idea/tests/org/jetbrains/kotlin/idea/ResolveElementCacheTest.kt b/idea/tests/org/jetbrains/kotlin/idea/ResolveElementCacheTest.kt index d79edb7214a..d76a6ecaf43 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/ResolveElementCacheTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/ResolveElementCacheTest.kt @@ -7,7 +7,6 @@ package org.jetbrains.kotlin.idea import com.intellij.psi.PsiDocumentManager import junit.framework.TestCase -import org.intellij.lang.annotations.Language import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks @@ -15,6 +14,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager import org.jetbrains.kotlin.idea.imports.importableFqName +import org.jetbrains.kotlin.idea.project.ResolveElementCache import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.name.SpecialNames import org.jetbrains.kotlin.psi.* @@ -291,12 +291,12 @@ class ResolveElementCacheTest : AbstractResolveElementCacheTest() { } fun testIncompleteFileAnnotationList() { - val file = myFixture.configureByText( - "Test.kt", """ + val file = configureWithKotlin( + """ @file import some.hello """ - ) as KtFile + ) val fileAnnotationList = file.fileAnnotationList!! fileAnnotationList.analyze(BodyResolveMode.PARTIAL) @@ -336,16 +336,12 @@ class ResolveElementCacheTest : AbstractResolveElementCacheTest() { assertEmpty(context.diagnostics.all()) } - private fun configureWithKotlin(@Language("kotlin") text: String): KtFile { - return myFixture.configureByText("Test.kt", text.trimIndent()) as KtFile - } - fun testPrimaryConstructorParameterFullAnalysis() { - myFixture.configureByText( - "Test.kt", """ + configureWithKotlin( + """ class My(param: Int = 0) """ - ) as KtFile + ) val defaultValue = myFixture.elementByOffset.getParentOfType(true)!! // Kept to preserve correct behaviour of analyzeFully() on class internal elements @@ -355,11 +351,11 @@ class ResolveElementCacheTest : AbstractResolveElementCacheTest() { } fun testPrimaryConstructorAnnotationFullAnalysis() { - myFixture.configureByText( - "Test.kt", """ + configureWithKotlin( + """ class My @Deprecated("xyz") protected constructor(param: Int) """ - ) as KtFile + ) val annotationArguments = myFixture.elementByOffset.getParentOfType(true)!! @@ -368,14 +364,14 @@ class ResolveElementCacheTest : AbstractResolveElementCacheTest() { } fun testFunctionParameterAnnotation() { - val file = myFixture.configureByText( - "Test.kt", """ + val file = configureWithKotlin( + """ annotation class Ann fun foo(@Ann p: Int) { bar() } """ - ) as KtFile + ) val function = (file.declarations[1]) as KtFunction val typeRef = myFixture.elementByOffset.getParentOfType(true)!! @@ -391,12 +387,12 @@ class ResolveElementCacheTest : AbstractResolveElementCacheTest() { } fun testPrimaryConstructorParameterAnnotation() { - myFixture.configureByText( - "Test.kt", """ + configureWithKotlin( + """ annotation class Ann class X(@set:Ann var p: Int) """ - ) as KtFile + ) val typeRef = myFixture.elementByOffset.getParentOfType(true)!! @@ -408,8 +404,8 @@ class ResolveElementCacheTest : AbstractResolveElementCacheTest() { } fun testSecondaryConstructorParameterAnnotation() { - val file = myFixture.configureByText( - "Test.kt", """ + val file = configureWithKotlin( + """ annotation class Ann class X { constructor(@Ann p: Int) { @@ -417,7 +413,7 @@ class ResolveElementCacheTest : AbstractResolveElementCacheTest() { } } """ - ) as KtFile + ) val constructor = ((file.declarations[1]) as KtClass).secondaryConstructors[0] val typeRef = myFixture.elementByOffset.getParentOfType(true)!! @@ -475,16 +471,28 @@ class ResolveElementCacheTest : AbstractResolveElementCacheTest() { } } + fun testPartialResolveIsAFullResolveForOpenedFile() { + ResolveElementCache.forceFullAnalysisModeInTests = true + doTest { + val function = members[0] as KtFunction + val bindingContextPartial = function.analyze(BodyResolveMode.PARTIAL) + val bindingContextFull = function.analyze(BodyResolveMode.FULL) + assert(bindingContextPartial === bindingContextFull) { + "Partial resolve is forced to FULL resolve for a file currently opened in editor" + } + } + } + fun testKT14376() { - val file = myFixture.configureByText("Test.kt", "object Obj(val x: Int)") as KtFile + val file = configureWithKotlin("object Obj(val x: Int)") val nameRef = file.findDescendantOfType()!! val bindingContext = nameRef.analyze(BodyResolveMode.PARTIAL) assert(bindingContext[BindingContext.REFERENCE_TARGET, nameRef]?.fqNameSafe?.asString() == "kotlin.Int") } fun testResolveDefaultValueInPrimaryConstructor() { - myFixture.configureByText( - "Test.kt", """ + configureWithKotlin( + """ class ClassA ( messenger: ClassB = object : ClassB { override fun methodOne(param: List) { @@ -496,7 +504,7 @@ class ResolveElementCacheTest : AbstractResolveElementCacheTest() { fun methodOne(param: List) } """ - ) as KtFile + ) val methodOne = myFixture.elementByOffset.getParentOfType(true)!!