From 53e751be1f93f5db0bf1ea6e04b179aecb2043a5 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Mon, 13 Jul 2015 16:21:37 +0300 Subject: [PATCH] Caching of partial body resolve --- .../kotlin/resolve/lazy/ElementResolver.kt | 60 ++++---- .../resolve/lazy/PartialBodyResolveFilter.kt | 5 + .../idea/project/ResolveElementCache.kt | 49 +++++-- .../kotlin/idea/ResolveElementCacheTest.kt | 135 ++++++++++++++++++ 4 files changed, 203 insertions(+), 46 deletions(-) create mode 100644 idea/tests/org/jetbrains/kotlin/idea/ResolveElementCacheTest.kt diff --git a/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/ElementResolver.kt b/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/ElementResolver.kt index 0610a5ec0c3..999cdbd03f1 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/ElementResolver.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/ElementResolver.kt @@ -46,41 +46,26 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull public abstract class ElementResolver protected constructor( public val resolveSession: ResolveSession ) { - - public open fun getElementAdditionalResolve(jetElement: JetElement): BindingContext { - return performElementAdditionalResolve(jetElement, jetElement, BodyResolveMode.FULL) + public open fun getElementAdditionalResolve(resolveElement: JetElement, contextElement: JetElement, bodyResolveMode: BodyResolveMode): BindingContext { + return performElementAdditionalResolve(resolveElement, resolveElement, bodyResolveMode) } - public open fun hasElementAdditionalResolveCached(jetElement: JetElement): Boolean = false - protected open fun probablyNothingCallableNames(): ProbablyNothingCallableNames = throw UnsupportedOperationException("Cannot use partial body resolve with no Nothing-functions index"); - public fun resolveToElement(jetElement: JetElement, bodyResolveMode: BodyResolveMode = BodyResolveMode.FULL): BindingContext { - @suppress("NAME_SHADOWING") - var jetElement = jetElement + public fun resolveToElement(element: JetElement, bodyResolveMode: BodyResolveMode = BodyResolveMode.FULL): BindingContext { + var contextElement = element - val elementOfAdditionalResolve = findElementOfAdditionalResolve(jetElement) + val elementOfAdditionalResolve = findElementOfAdditionalResolve(contextElement) - if (elementOfAdditionalResolve != null) { - if (elementOfAdditionalResolve !is JetParameter) { - if (bodyResolveMode != BodyResolveMode.FULL && !hasElementAdditionalResolveCached(elementOfAdditionalResolve)) { - return performElementAdditionalResolve(elementOfAdditionalResolve, jetElement, bodyResolveMode) - } - - return getElementAdditionalResolve(elementOfAdditionalResolve) - } - - val klass = elementOfAdditionalResolve.getParentOfType(true) - if (klass != null && elementOfAdditionalResolve.getParent() == klass.getPrimaryConstructorParameterList()) { - return getElementAdditionalResolve(klass) - } - - // Parameters for function literal could be met inside other parameters. We can't make resolveToDescriptors for internal elements. - jetElement = elementOfAdditionalResolve + if (elementOfAdditionalResolve is JetParameter) { + contextElement = elementOfAdditionalResolve + } + else if (elementOfAdditionalResolve != null) { + return getElementAdditionalResolve(elementOfAdditionalResolve, contextElement, bodyResolveMode) } - val declaration = jetElement.getParentOfType(false) + val declaration = contextElement.getParentOfType(false) if (declaration != null && declaration !is JetClassInitializer) { // Activate descriptor resolution resolveSession.resolveToDescriptor(declaration) @@ -89,8 +74,8 @@ public abstract class ElementResolver protected constructor( return resolveSession.getBindingContext() } - private fun findElementOfAdditionalResolve(element: JetElement): JetElement? { - var elementOfAdditionalResolve = JetPsiUtil.getTopmostParentOfTypes( + protected fun findElementOfAdditionalResolve(element: JetElement): JetElement? { + val elementOfAdditionalResolve = JetPsiUtil.getTopmostParentOfTypes( element, javaClass(), javaClass(), @@ -104,13 +89,22 @@ public abstract class ElementResolver protected constructor( javaClass(), javaClass(), javaClass(), - javaClass()) as JetElement? + javaClass()) as JetElement? ?: return null - if (elementOfAdditionalResolve is JetPackageDirective) { - return element + when (elementOfAdditionalResolve) { + is JetPackageDirective -> return element + + is JetParameter -> { + val klass = elementOfAdditionalResolve.getParentOfType(strict = true) + if (klass != null && elementOfAdditionalResolve.getParent() == klass.getPrimaryConstructorParameterList()) { + return klass + } + + return elementOfAdditionalResolve + } + + else -> return elementOfAdditionalResolve } - - return elementOfAdditionalResolve } protected fun performElementAdditionalResolve(resolveElement: JetElement, contextElement: JetElement, bodyResolveMode: BodyResolveMode): BindingContext { diff --git a/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/PartialBodyResolveFilter.kt b/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/PartialBodyResolveFilter.kt index 5a1407fe076..1eb9319cb4a 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/PartialBodyResolveFilter.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/PartialBodyResolveFilter.kt @@ -27,6 +27,7 @@ import com.intellij.psi.PsiElement import org.jetbrains.kotlin.JetNodeTypes import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.psi.psiUtil.isAncestor +import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import org.jetbrains.kotlin.resolve.StatementFilter import org.jetbrains.kotlin.utils.addToStdlib.swap import org.jetbrains.kotlin.util.isProbablyNothing @@ -538,6 +539,10 @@ class PartialBodyResolveFilter( } companion object { + public fun findResolveElement(element: JetElement, declaration: JetDeclaration): JetExpression? { + return element.parentsWithSelf.takeWhile { it != declaration }.firstOrNull { it.isStatement() } as JetExpression? + } + private fun JetElement.blocks(): Collection { val result = ArrayList(1) this.accept(object : JetVisitorVoid() { 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 b9134fc691d..4fcfea60318 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 @@ -24,15 +24,10 @@ import com.intellij.psi.util.PsiModificationTracker import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.idea.stubindex.JetProbablyNothingFunctionShortNameIndex import org.jetbrains.kotlin.idea.stubindex.JetProbablyNothingPropertyShortNameIndex -import org.jetbrains.kotlin.psi.JetElement -import org.jetbrains.kotlin.psi.JetFile -import org.jetbrains.kotlin.psi.JetNamedFunction +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BodyResolveCache -import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode -import org.jetbrains.kotlin.resolve.lazy.ElementResolver -import org.jetbrains.kotlin.resolve.lazy.ProbablyNothingCallableNames -import org.jetbrains.kotlin.resolve.lazy.ResolveSession +import org.jetbrains.kotlin.resolve.lazy.* import org.jetbrains.kotlin.storage.MemoizedFunctionToNotNull public class ResolveElementCache(resolveSession: ResolveSession, private val project: Project) : ElementResolver(resolveSession), BodyResolveCache { @@ -41,20 +36,48 @@ public class ResolveElementCache(resolveSession: ResolveSession, private val pro object : CachedValueProvider> { override fun compute(): CachedValueProvider.Result> { val manager = resolveSession.getStorageManager() - val elementsCacheFunction = manager.createSoftlyRetainedMemoizedFunction { element -> + val cacheFunction = manager.createSoftlyRetainedMemoizedFunction { element -> performElementAdditionalResolve(element, element, BodyResolveMode.FULL) } - return CachedValueProvider.Result.create(elementsCacheFunction, + return CachedValueProvider.Result.create(cacheFunction, PsiModificationTracker.MODIFICATION_COUNT, resolveSession.getExceptionTracker()) } }, false) - override fun getElementAdditionalResolve(jetElement: JetElement) - = additionalResolveCache.getValue().invoke(jetElement) + private val partialBodyResolveCache: CachedValue> = CachedValuesManager.getManager(project).createCachedValue( + object : CachedValueProvider> { + override fun compute(): CachedValueProvider.Result> { + val manager = resolveSession.getStorageManager() + val cacheFunction = manager.createSoftlyRetainedMemoizedFunction { expression -> + val resolveElement = findElementOfAdditionalResolve(expression)!! + performElementAdditionalResolve(resolveElement, expression, BodyResolveMode.PARTIAL) + } + return CachedValueProvider.Result.create(cacheFunction, + PsiModificationTracker.MODIFICATION_COUNT, + resolveSession.getExceptionTracker()) + } + }, + false) - override fun hasElementAdditionalResolveCached(jetElement: JetElement) + override fun getElementAdditionalResolve(resolveElement: JetElement, contextElement: JetElement, bodyResolveMode: BodyResolveMode): BindingContext { + if (bodyResolveMode != BodyResolveMode.FULL && !hasElementAdditionalResolveCached(resolveElement) && resolveElement is JetDeclaration) { + if (bodyResolveMode == BodyResolveMode.PARTIAL) { + val partialResolveElement = PartialBodyResolveFilter.findResolveElement(contextElement, resolveElement) + if (partialResolveElement != null) { + return partialBodyResolveCache.getValue().invoke(partialResolveElement) + } + } + else { + return performElementAdditionalResolve(resolveElement, contextElement, bodyResolveMode) + } + } + + return additionalResolveCache.getValue().invoke(resolveElement) + } + + private fun hasElementAdditionalResolveCached(jetElement: JetElement) = additionalResolveCache.hasUpToDateValue() && additionalResolveCache.getValue().isComputed(jetElement) override fun createAdditionalCheckerProvider(jetFile: JetFile, module: ModuleDescriptor) @@ -71,5 +94,5 @@ public class ResolveElementCache(resolveSession: ResolveSession, private val pro } override fun resolveFunctionBody(function: JetNamedFunction) - = getElementAdditionalResolve(function) + = getElementAdditionalResolve(function, function, BodyResolveMode.FULL) } diff --git a/idea/tests/org/jetbrains/kotlin/idea/ResolveElementCacheTest.kt b/idea/tests/org/jetbrains/kotlin/idea/ResolveElementCacheTest.kt new file mode 100644 index 00000000000..c2fd2c652bc --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/ResolveElementCacheTest.kt @@ -0,0 +1,135 @@ +/* + * 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 + +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase +import org.jetbrains.kotlin.idea.test.JetLightProjectDescriptor +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +public class ResolveElementCacheTest : JetLightCodeInsightFixtureTestCase() { + override fun getProjectDescriptor() = JetLightProjectDescriptor.INSTANCE + + private val FILE_TEXT = +""" +class C { + fun a() { + b(1, 2) + c() + } + + fun b() { + } + + fun c() { + } +} +""" + + private data class Data( + val file: JetFile, + val members: List, + val statements: List, + val factory: JetPsiFactory + ) + + private fun doTest(handler: Data.() -> Unit) { + val file = myFixture.configureByText("Test.kt", FILE_TEXT) as JetFile + val klass = file.getDeclarations().single() as JetClass + val members = klass.getDeclarations() + val function = members.first() as JetNamedFunction + val statements = (function.getBodyExpression() as JetBlockExpression).getStatements() + myFixture.getProject().executeWriteCommand("") { + Data(file, members, statements, JetPsiFactory(getProject())).handler() + } + } + + public fun testResolveCaching() { + doTest { + val statement1 = statements[0] + val statement2 = statements[1] + val bindingContext1 = statement1.analyze(BodyResolveMode.FULL) + val bindingContext2 = statement2.analyze(BodyResolveMode.FULL) + assert(bindingContext1 === bindingContext2) + + val bindingContext3 = statement1.analyze(BodyResolveMode.FULL) + assert(bindingContext3 === bindingContext1) + + file.add(factory.createFunction("fun foo(){}")) + + val bindingContext4 = statement1.analyze(BodyResolveMode.FULL) + assert(bindingContext4 !== bindingContext1) + + statement1.getParent().addAfter(factory.createExpression("x()"), statement1) + + val bindingContext5 = statement1.analyze(BodyResolveMode.PARTIAL) + assert(bindingContext5 !== bindingContext4) + } + } + + public fun testPartialResolveUsesFullResolveCached() { + doTest { + val statement1 = statements[0] + val statement2 = statements[1] + val bindingContext1 = statement1.analyze(BodyResolveMode.FULL) + + val bindingContext2 = statement2.analyze(BodyResolveMode.PARTIAL) + assert(bindingContext2 === bindingContext1) + + val bindingContext3 = statement2.analyze(BodyResolveMode.PARTIAL_FOR_COMPLETION) + assert(bindingContext3 === bindingContext1) + } + } + + public fun testPartialResolveCaching() { + doTest { + val statement1 = statements[0] + val statement2 = statements[1] + val bindingContext1 = statement1.analyze(BodyResolveMode.PARTIAL) + val bindingContext2 = statement2.analyze(BodyResolveMode.PARTIAL) + assert(bindingContext1 !== bindingContext2) + + val bindingContext3 = statement1.analyze(BodyResolveMode.PARTIAL) + val bindingContext4 = statement2.analyze(BodyResolveMode.PARTIAL) + assert(bindingContext3 === bindingContext1) + assert(bindingContext4 === bindingContext2) + + file.add(factory.createFunction("fun foo(){}")) + + val bindingContext5 = statement1.analyze(BodyResolveMode.PARTIAL) + assert(bindingContext5 !== bindingContext1) + + statement1.getParent().addAfter(factory.createExpression("x()"), statement1) + + val bindingContext6 = statement1.analyze(BodyResolveMode.PARTIAL) + assert(bindingContext6 !== bindingContext5) + } + } + + public fun testPartialResolveCachedForWholeStatement() { + doTest { + val statement = statements[0] as JetCallExpression + val argument1 = statement.getValueArguments()[0] + val argument2 = statement.getValueArguments()[1] + val bindingContext1 = argument1.analyze(BodyResolveMode.PARTIAL) + val bindingContext2 = argument2.analyze(BodyResolveMode.PARTIAL) + assert(bindingContext1 === bindingContext2) + } + } +}