Caching of partial body resolve

This commit is contained in:
Valentin Kipyatkov
2015-07-13 16:21:37 +03:00
parent 077c8a82c7
commit 53e751be1f
4 changed files with 203 additions and 46 deletions
@@ -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<JetClass>(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<JetDeclaration>(false)
val declaration = contextElement.getParentOfType<JetDeclaration>(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<JetNamedFunction>(),
javaClass<JetClassInitializer>(),
@@ -104,13 +89,22 @@ public abstract class ElementResolver protected constructor(
javaClass<JetTypeParameter>(),
javaClass<JetTypeConstraint>(),
javaClass<JetPackageDirective>(),
javaClass<JetCodeFragment>()) as JetElement?
javaClass<JetCodeFragment>()) as JetElement? ?: return null
if (elementOfAdditionalResolve is JetPackageDirective) {
return element
when (elementOfAdditionalResolve) {
is JetPackageDirective -> return element
is JetParameter -> {
val klass = elementOfAdditionalResolve.getParentOfType<JetClass>(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 {
@@ -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<JetBlockExpression> {
val result = ArrayList<JetBlockExpression>(1)
this.accept(object : JetVisitorVoid() {
@@ -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<MemoizedFunctionToNotNull<JetElement, BindingContext>> {
override fun compute(): CachedValueProvider.Result<MemoizedFunctionToNotNull<JetElement, BindingContext>> {
val manager = resolveSession.getStorageManager()
val elementsCacheFunction = manager.createSoftlyRetainedMemoizedFunction<JetElement, BindingContext> { element ->
val cacheFunction = manager.createSoftlyRetainedMemoizedFunction<JetElement, BindingContext> { 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<MemoizedFunctionToNotNull<JetExpression, BindingContext>> = CachedValuesManager.getManager(project).createCachedValue(
object : CachedValueProvider<MemoizedFunctionToNotNull<JetExpression, BindingContext>> {
override fun compute(): CachedValueProvider.Result<MemoizedFunctionToNotNull<JetExpression, BindingContext>> {
val manager = resolveSession.getStorageManager()
val cacheFunction = manager.createSoftlyRetainedMemoizedFunction<JetExpression, BindingContext> { 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)
}
@@ -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<JetDeclaration>,
val statements: List<JetExpression>,
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)
}
}
}