Force perform FULL analysis to avoid redundant analysis for the current open file

#KT-38687 Fixed
This commit is contained in:
Vladimir Dolzhenko
2020-07-01 08:22:58 +00:00
parent 1a82c94979
commit 853503d75f
4 changed files with 76 additions and 29 deletions
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.idea.project package org.jetbrains.kotlin.idea.project
import com.intellij.openapi.fileEditor.FileEditorManager
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.psi.util.CachedValue 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.psi.util.CachedValuesManager
import com.intellij.util.containers.ContainerUtil import com.intellij.util.containers.ContainerUtil
import com.intellij.util.containers.SLRUCache import com.intellij.util.containers.SLRUCache
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.cfg.ControlFlowInformationProvider import org.jetbrains.kotlin.cfg.ControlFlowInformationProvider
import org.jetbrains.kotlin.container.get import org.jetbrains.kotlin.container.get
import org.jetbrains.kotlin.context.SimpleGlobalContext 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.KotlinCodeBlockModificationListener
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListenerCompat import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListenerCompat
import org.jetbrains.kotlin.idea.caches.trackers.inBlockModificationCount 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.name.Name
import org.jetbrains.kotlin.platform.TargetPlatform import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
@@ -151,6 +154,22 @@ class ResolveElementCache(
assert(bodyResolveMode == BodyResolveMode.FULL) 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 // check if full additional resolve already performed and is up-to-date
val fullResolveMap = fullResolveCache.value val fullResolveMap = fullResolveCache.value
val cachedFullResolve = fullResolveMap[resolveElement] val cachedFullResolve = fullResolveMap[resolveElement]
@@ -807,5 +826,10 @@ class ResolveElementCache(
override fun getTopDownAnalysisMode() = topDownAnalysisMode override fun getTopDownAnalysisMode() = topDownAnalysisMode
} }
companion object {
@set:TestOnly
var forceFullAnalysisModeInTests: Boolean = false
}
} }
@@ -26,11 +26,11 @@ import com.intellij.util.io.exists
import org.jetbrains.kotlin.idea.configuration.getModulesWithKotlinFiles import org.jetbrains.kotlin.idea.configuration.getModulesWithKotlinFiles
import org.jetbrains.kotlin.idea.perf.Stats.Companion.runAndMeasure import org.jetbrains.kotlin.idea.perf.Stats.Companion.runAndMeasure
import org.jetbrains.kotlin.idea.perf.util.logMessage 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.project.getAndCacheLanguageLevelByDependencies
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import java.io.File import java.io.File
import java.nio.file.Paths import java.nio.file.Paths
import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
data class OpenProject(val projectPath: String, val projectName: String, val jdk: Sdk, val projectOpenAction: ProjectOpenAction) 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 projectManagerEx = ProjectManagerEx.getInstanceEx()
val project = loadProjectWithName(projectPath, projectName) val project = loadProjectWithName(projectPath, projectName)
assertNotNull(project, "project $projectName at $projectPath is not loaded") ?: error("project $projectName at $projectPath is not loaded")
runWriteAction { runWriteAction {
ProjectRootManager.getInstance(project).projectSdk = jdk ProjectRootManager.getInstance(project).projectSdk = jdk
@@ -123,6 +123,7 @@ enum class ProjectOpenAction {
open fun postOpenProject(project: Project, openProject: OpenProject) { open fun postOpenProject(project: Project, openProject: OpenProject) {
ApplicationManager.getApplication().executeOnPooledThread { ApplicationManager.getApplication().executeOnPooledThread {
ResolveElementCache.forceFullAnalysisModeInTests = true
DumbService.getInstance(project).waitForSmartMode() DumbService.getInstance(project).waitForSmartMode()
for (module in getModulesWithKotlinFiles(project)) { for (module in getModulesWithKotlinFiles(project)) {
@@ -5,8 +5,12 @@
package org.jetbrains.kotlin.idea 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.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor 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.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
@@ -43,6 +47,16 @@ class C(param1: String = "", param2: Int = 0) {
val factory: KtPsiFactory 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) { protected fun doTest(handler: Data.() -> Unit) {
val file = myFixture.configureByText("Test.kt", FILE_TEXT) as KtFile val file = myFixture.configureByText("Test.kt", FILE_TEXT) as KtFile
val data = extractData(file) val data = extractData(file)
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.idea
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
import junit.framework.TestCase import junit.framework.TestCase
import org.intellij.lang.annotations.Language
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks 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.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
import org.jetbrains.kotlin.idea.imports.importableFqName 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.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.name.SpecialNames import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
@@ -291,12 +291,12 @@ class ResolveElementCacheTest : AbstractResolveElementCacheTest() {
} }
fun testIncompleteFileAnnotationList() { fun testIncompleteFileAnnotationList() {
val file = myFixture.configureByText( val file = configureWithKotlin(
"Test.kt", """ """
@file @file
import some.hello import some.hello
""" """
) as KtFile )
val fileAnnotationList = file.fileAnnotationList!! val fileAnnotationList = file.fileAnnotationList!!
fileAnnotationList.analyze(BodyResolveMode.PARTIAL) fileAnnotationList.analyze(BodyResolveMode.PARTIAL)
@@ -336,16 +336,12 @@ class ResolveElementCacheTest : AbstractResolveElementCacheTest() {
assertEmpty(context.diagnostics.all()) assertEmpty(context.diagnostics.all())
} }
private fun configureWithKotlin(@Language("kotlin") text: String): KtFile {
return myFixture.configureByText("Test.kt", text.trimIndent()) as KtFile
}
fun testPrimaryConstructorParameterFullAnalysis() { fun testPrimaryConstructorParameterFullAnalysis() {
myFixture.configureByText( configureWithKotlin(
"Test.kt", """ """
class My(param: Int = <caret>0) class My(param: Int = <caret>0)
""" """
) as KtFile )
val defaultValue = myFixture.elementByOffset.getParentOfType<KtExpression>(true)!! val defaultValue = myFixture.elementByOffset.getParentOfType<KtExpression>(true)!!
// Kept to preserve correct behaviour of analyzeFully() on class internal elements // Kept to preserve correct behaviour of analyzeFully() on class internal elements
@@ -355,11 +351,11 @@ class ResolveElementCacheTest : AbstractResolveElementCacheTest() {
} }
fun testPrimaryConstructorAnnotationFullAnalysis() { fun testPrimaryConstructorAnnotationFullAnalysis() {
myFixture.configureByText( configureWithKotlin(
"Test.kt", """ """
class My @Deprecated("<caret>xyz") protected constructor(param: Int) class My @Deprecated("<caret>xyz") protected constructor(param: Int)
""" """
) as KtFile )
val annotationArguments = myFixture.elementByOffset.getParentOfType<KtValueArgumentList>(true)!! val annotationArguments = myFixture.elementByOffset.getParentOfType<KtValueArgumentList>(true)!!
@@ -368,14 +364,14 @@ class ResolveElementCacheTest : AbstractResolveElementCacheTest() {
} }
fun testFunctionParameterAnnotation() { fun testFunctionParameterAnnotation() {
val file = myFixture.configureByText( val file = configureWithKotlin(
"Test.kt", """ """
annotation class Ann annotation class Ann
fun foo(@<caret>Ann p: Int) { fun foo(@<caret>Ann p: Int) {
bar() bar()
} }
""" """
) as KtFile )
val function = (file.declarations[1]) as KtFunction val function = (file.declarations[1]) as KtFunction
val typeRef = myFixture.elementByOffset.getParentOfType<KtTypeReference>(true)!! val typeRef = myFixture.elementByOffset.getParentOfType<KtTypeReference>(true)!!
@@ -391,12 +387,12 @@ class ResolveElementCacheTest : AbstractResolveElementCacheTest() {
} }
fun testPrimaryConstructorParameterAnnotation() { fun testPrimaryConstructorParameterAnnotation() {
myFixture.configureByText( configureWithKotlin(
"Test.kt", """ """
annotation class Ann annotation class Ann
class X(@set:<caret>Ann var p: Int) class X(@set:<caret>Ann var p: Int)
""" """
) as KtFile )
val typeRef = myFixture.elementByOffset.getParentOfType<KtTypeReference>(true)!! val typeRef = myFixture.elementByOffset.getParentOfType<KtTypeReference>(true)!!
@@ -408,8 +404,8 @@ class ResolveElementCacheTest : AbstractResolveElementCacheTest() {
} }
fun testSecondaryConstructorParameterAnnotation() { fun testSecondaryConstructorParameterAnnotation() {
val file = myFixture.configureByText( val file = configureWithKotlin(
"Test.kt", """ """
annotation class Ann annotation class Ann
class X { class X {
constructor(@<caret>Ann p: Int) { constructor(@<caret>Ann p: Int) {
@@ -417,7 +413,7 @@ class ResolveElementCacheTest : AbstractResolveElementCacheTest() {
} }
} }
""" """
) as KtFile )
val constructor = ((file.declarations[1]) as KtClass).secondaryConstructors[0] val constructor = ((file.declarations[1]) as KtClass).secondaryConstructors[0]
val typeRef = myFixture.elementByOffset.getParentOfType<KtTypeReference>(true)!! val typeRef = myFixture.elementByOffset.getParentOfType<KtTypeReference>(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() { 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<KtNameReferenceExpression>()!! val nameRef = file.findDescendantOfType<KtNameReferenceExpression>()!!
val bindingContext = nameRef.analyze(BodyResolveMode.PARTIAL) val bindingContext = nameRef.analyze(BodyResolveMode.PARTIAL)
assert(bindingContext[BindingContext.REFERENCE_TARGET, nameRef]?.fqNameSafe?.asString() == "kotlin.Int") assert(bindingContext[BindingContext.REFERENCE_TARGET, nameRef]?.fqNameSafe?.asString() == "kotlin.Int")
} }
fun testResolveDefaultValueInPrimaryConstructor() { fun testResolveDefaultValueInPrimaryConstructor() {
myFixture.configureByText( configureWithKotlin(
"Test.kt", """ """
class ClassA<N> ( class ClassA<N> (
messenger: ClassB<N> = object : ClassB<N> { messenger: ClassB<N> = object : ClassB<N> {
override fun methodOne<caret>(param: List<N>) { override fun methodOne<caret>(param: List<N>) {
@@ -496,7 +504,7 @@ class ResolveElementCacheTest : AbstractResolveElementCacheTest() {
fun methodOne(param: List<N>) fun methodOne(param: List<N>)
} }
""" """
) as KtFile )
val methodOne = myFixture.elementByOffset.getParentOfType<KtFunction>(true)!! val methodOne = myFixture.elementByOffset.getParentOfType<KtFunction>(true)!!