Optimization to reuse BindingContext in completion

This commit is contained in:
Valentin Kipyatkov
2016-10-31 22:49:37 +03:00
parent 1174c7bdd9
commit 09dbb07fb8
30 changed files with 548 additions and 22 deletions
@@ -1146,6 +1146,10 @@ fun main(args: Array<String>) {
testClass<AbstractJava8BasicCompletionTest> {
model("basic/java8")
}
testClass<AbstractCompletionIncrementalResolveTest>() {
model("incrementalResolve")
}
}
//TODO: move these tests into idea-completion module
@@ -28,6 +28,7 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.impl.PsiModificationTrackerImpl
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentOfType
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.psi.psiUtil.parents
@@ -51,7 +52,7 @@ class KotlinCodeBlockModificationListener(
override fun modelChanged(event: PomModelEvent) {
val changeSet = event.getChangeSet(treeAspect) as TreeChangeEvent? ?: return
val file = changeSet.rootElement.psi.containingFile as? KtFile ?: return
if (changeSet.changedElements.any { !isInsideCodeBlock(it.psi) }) {
if (changeSet.changedElements.any { getInsideCodeBlockModificationScope(it.psi) == null }) {
if (file.isPhysical) {
modificationTracker.incCounter()
}
@@ -67,39 +68,38 @@ class KotlinCodeBlockModificationListener(
file.putUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, count + 1)
}
private fun isInsideCodeBlock(element: PsiElement): Boolean {
val lambda = KtPsiUtil.getTopmostParentOfTypes(element, KtLambdaExpression::class.java)
fun getInsideCodeBlockModificationScope(element: PsiElement): KtElement? {
val lambda = element.getTopmostParentOfType<KtLambdaExpression>()
if (lambda is KtLambdaExpression) {
if (KtPsiUtil.getTopmostParentOfTypes(lambda, KtSuperTypeCallEntry::class.java) != null) {
return true
}
lambda.getTopmostParentOfType<KtSuperTypeCallEntry>()
?.let { return it }
}
val blockDeclaration = KtPsiUtil.getTopmostParentOfTypes(element, *BLOCK_DECLARATION_TYPES) ?: return false
if (blockDeclaration.parents.any { it !is KtClassBody && it !is KtClassOrObject && it !is KtFile }) return false // should not be local declaration
val blockDeclaration = KtPsiUtil.getTopmostParentOfTypes(element, *BLOCK_DECLARATION_TYPES) ?: return null
if (blockDeclaration.parents.any { it !is KtClassBody && it !is KtClassOrObject && it !is KtFile }) return null // should not be local declaration
when (blockDeclaration) {
is KtNamedFunction -> {
if (blockDeclaration.hasBlockBody()) {
return blockDeclaration.bodyExpression.isAncestor(element)
return blockDeclaration.bodyExpression?.takeIf { it.isAncestor(element) }
}
else if (blockDeclaration.hasDeclaredReturnType()) {
return blockDeclaration.initializer.isAncestor(element)
return blockDeclaration.initializer?.takeIf { it.isAncestor(element) }
}
}
is KtProperty -> {
for (accessor in blockDeclaration.accessors) {
if (accessor.initializer.isAncestor(element) || accessor.bodyExpression.isAncestor(element)) {
return true
}
(accessor.initializer ?: accessor.bodyExpression)
?.takeIf { it.isAncestor(element) }
?.let { return it }
}
}
else -> throw IllegalStateException()
}
return false
return null
}
fun isBlockDeclaration(declaration: KtDeclaration): Boolean {
@@ -0,0 +1,171 @@
/*
* Copyright 2010-2016 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.completion
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiErrorElement
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
import org.jetbrains.kotlin.idea.project.KotlinCodeBlockModificationListener
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.psi.psiUtil.siblings
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.CompositeBindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoBefore
import org.jetbrains.kotlin.resolve.calls.context.ContextDependency
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.lang.ref.SoftReference
import java.util.*
class CompletionBindingContextProvider(project: Project) {
private val LOG = Logger.getInstance(CompletionBindingContextProvider::class.java)
@TestOnly
internal var TEST_LOG: StringBuilder? = null
companion object {
fun getInstance(project: Project): CompletionBindingContextProvider
= project.getComponent(CompletionBindingContextProvider::class.java)
}
private class CompletionData(
val block: KtBlockExpression,
val prevStatement: KtExpression?,
val psiElementsBeforeAndAfter: List<PsiElementData>,
val bindingContext: BindingContext,
val statementResolutionScope: LexicalScope,
val statementDataFlowInfo: DataFlowInfo,
val debugText: String
)
private data class PsiElementData(val element: PsiElement, val level: Int)
private class DataHolder {
private var reference: SoftReference<CompletionData>? = null
var data: CompletionData?
get() = reference?.get()
set(value) { reference = value?.let { SoftReference(it) } }
}
private var prevCompletionDataCache: CachedValue<DataHolder> = CachedValuesManager.getManager(project).createCachedValue(
{ CachedValueProvider.Result.create(DataHolder(), PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT) },
false)
fun getBindingContext(position: PsiElement, resolutionFacade: ResolutionFacade): BindingContext {
assert(!position.isPhysical) // position is in synthetic file
val inStatement = position.findStatementInBlock()
val block = inStatement?.parent as KtBlockExpression?
val prevStatement = inStatement?.siblings(forward = false, withItself = false)?.firstIsInstanceOrNull<KtExpression>()
val modificationScope = inStatement?.let { KotlinCodeBlockModificationListener.getInsideCodeBlockModificationScope(it) }
val psiElementsBeforeAndAfter = modificationScope?.let { collectPsiElementsBeforeAndAfter(modificationScope, inStatement) }
val prevCompletionData = prevCompletionDataCache.value.data
if (prevCompletionData == null) {
log("No up-to-date data from previous completion\n")
}
else if (block != prevCompletionData.block) {
log("Not in the same block\n")
}
else if (prevStatement != prevCompletionData.prevStatement) {
log("Previous statement is not the same\n")
}
else if (psiElementsBeforeAndAfter != prevCompletionData.psiElementsBeforeAndAfter) {
log("PSI-tree has changed inside current scope\n")
}
else if (inStatement.isTooComplex()) {
log("Current statement is too complex to use optimization\n")
}
else {
log("Statement position is the same - analyzing only one statement:\n${inStatement.text.prependIndent(" ")}\n")
LOG.debug("Reusing data from completion of \"${prevCompletionData.debugText}\"")
//TODO: expected type?
val statementContext = inStatement.analyzeInContext(scope = prevCompletionData.statementResolutionScope,
contextExpression = block,
dataFlowInfo = prevCompletionData.statementDataFlowInfo,
isStatement = true,
contextDependency = ContextDependency.DEPENDENT)
// we do not update prevCompletionDataCache because the same data should work
return CompositeBindingContext.create(listOf(statementContext, prevCompletionData.bindingContext))
}
val bindingContext = resolutionFacade.analyze(position.parentsWithSelf.firstIsInstance<KtElement>(), BodyResolveMode.PARTIAL_FOR_COMPLETION)
prevCompletionDataCache.value.data = if (block != null && modificationScope != null) {
val resolutionScope = inStatement.getResolutionScope(bindingContext, resolutionFacade)
val dataFlowInfo = bindingContext.getDataFlowInfoBefore(inStatement)
CompletionData(block, prevStatement, psiElementsBeforeAndAfter!!, bindingContext, resolutionScope, dataFlowInfo,
debugText = position.text)
}
else {
null
}
return bindingContext
}
private fun log(message: String) {
TEST_LOG?.append(message)
LOG.debug(message)
}
private fun collectPsiElementsBeforeAndAfter(scope: PsiElement, statement: KtExpression): List<PsiElementData> {
return ArrayList<PsiElementData>().apply { addElementsInTree(scope, 0, statement) }
}
private fun MutableList<PsiElementData>.addElementsInTree(root: PsiElement, initialLevel: Int, skipSubtree: PsiElement) {
if (root == skipSubtree) return
add(PsiElementData(root, initialLevel))
var child = root.firstChild
while (child != null) {
if (child !is PsiWhiteSpace && child !is PsiComment && child !is PsiErrorElement) {
addElementsInTree(child, initialLevel + 1, skipSubtree)
}
child = child.nextSibling
}
}
private fun PsiElement.findStatementInBlock(): KtExpression? {
return parents.filterIsInstance<KtExpression>().firstOrNull { it.parent is KtBlockExpression }
}
private fun KtExpression.isTooComplex(): Boolean {
return anyDescendantOfType<KtBlockExpression> { it.statements.size > 1 }
}
}
@@ -39,7 +39,6 @@ import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.*
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
@@ -48,7 +47,6 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import java.util.*
class CompletionSessionConfiguration(
@@ -104,7 +102,7 @@ abstract class CompletionSession(
}
}
protected val bindingContext = resolutionFacade.analyze(position.parentsWithSelf.firstIsInstance<KtElement>(), BodyResolveMode.PARTIAL_FOR_COMPLETION)
protected val bindingContext = CompletionBindingContextProvider.getInstance(project).getBindingContext(position, resolutionFacade)
protected val inDescriptor = position.getResolutionScope(bindingContext, resolutionFacade).ownerDescriptor
private val kotlinIdentifierStartPattern = StandardPatterns.character().javaIdentifierStart().andNot(singleCharPattern('$'))
@@ -0,0 +1,12 @@
fun foo(p1: Int, p2: String): Int {
val v1 = p2<change>
val v2 = 123
return <before><caret>
}
// TYPE: ".hashCode()"
// COMPLETION_TYPE: SMART
// EXIST: v1
// EXIST: v2
// EXIST: p1
// ABSENT: p2
@@ -0,0 +1,2 @@
No up-to-date data from previous completion
PSI-tree has changed inside current scope
@@ -0,0 +1,12 @@
fun foo(p: Int, p1: String): Int {
val v1 = p<change>
val v2 = 123
return <before><caret>
}
// TYPE: "1"
// COMPLETION_TYPE: SMART
// ABSENT: v1
// EXIST: v2
// EXIST: p
// ABSENT: p1
@@ -0,0 +1,2 @@
No up-to-date data from previous completion
PSI-tree has changed inside current scope
@@ -0,0 +1,8 @@
fun foo(p: Any): String {
if (p !is String) return ""
r<before><change>
}
// TYPE: "eturn "
// COMPLETION_TYPE: SMART
// EXIST: p
@@ -0,0 +1,3 @@
No up-to-date data from previous completion
Statement position is the same - analyzing only one statement:
return IntellijIdeaRulezzz
@@ -0,0 +1,10 @@
fun foo(p: Any): String {
println(1)
if (<before><change>
}
fun bar(s: String): Boolean = true
// TYPE: "p is String && bar("
// COMPLETION_TYPE: SMART
// EXIST: p
@@ -0,0 +1,4 @@
No up-to-date data from previous completion
Statement position is the same - analyzing only one statement:
if (p is String && bar(IntellijIdeaRulezzz$
@@ -0,0 +1,17 @@
fun foo(p1: Int, p2: String): String {
val v1 = p1.toString()
val v2 = 123
if (p1 > 0) {
print(1)
print(2)
}
else
r<before><change>
}
// TYPE: "eturn "
// COMPLETION_TYPE: SMART
// ABSENT: p1
// EXIST: p2
// EXIST: v1
// ABSENT: v2
@@ -0,0 +1,2 @@
No up-to-date data from previous completion
Current statement is too complex to use optimization
@@ -0,0 +1,10 @@
fun foo(p: String?): String {
println()
bar(p!!, <before><change>)
}
fun bar(s: String, p: Int) { }
// BACKSPACES: 4
// TYPE: "."
// EXIST: { itemText: "substring", attributes: "grayed" }
@@ -0,0 +1,3 @@
No up-to-date data from previous completion
Statement position is the same - analyzing only one statement:
bar(p.IntellijIdeaRulezzz$
@@ -0,0 +1,8 @@
fun foo(p1: Int, p2: String): String {
r<before><change>
}
// TYPE: "eturn "
// COMPLETION_TYPE: SMART
// ABSENT: p1
// EXIST: p2
@@ -0,0 +1,3 @@
No up-to-date data from previous completion
Statement position is the same - analyzing only one statement:
return IntellijIdeaRulezzz
@@ -0,0 +1,10 @@
fun foo(): Int {
println()
return <before><caret>
}
val xxx<change>
// TYPE: " = 1"
// COMPLETION_TYPE: SMART
// EXIST: xxx
@@ -0,0 +1,2 @@
No up-to-date data from previous completion
No up-to-date data from previous completion
@@ -0,0 +1,17 @@
fun foo(p1: Any, p2: Any) {
if (p1 !is String) return
val v1 = "a"
val v2 = 123
println("a")
bar(x<before><change>)
if (p2 !is String) return
}
fun bar(s: String){}
// BACKSPACES: 1
// COMPLETION_TYPE: SMART
// EXIST: v1
// ABSENT: v2
// EXIST: p1
// ABSENT: p2
@@ -0,0 +1,3 @@
No up-to-date data from previous completion
Statement position is the same - analyzing only one statement:
bar(IntellijIdeaRulezzz$
@@ -0,0 +1,12 @@
fun foo(p1: Int, p2: String): String {
val v1 = p1.toString()
val v2 = 123
r<before><change>
}
// TYPE: "eturn "
// COMPLETION_TYPE: SMART
// ABSENT: p1
// EXIST: p2
// EXIST: v1
// ABSENT: v2
@@ -0,0 +1,3 @@
No up-to-date data from previous completion
Statement position is the same - analyzing only one statement:
return IntellijIdeaRulezzz
@@ -0,0 +1,100 @@
/*
* Copyright 2010-2016 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.completion.test
import com.intellij.codeInsight.completion.CompletionType
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
abstract class AbstractCompletionIncrementalResolveTest : KotlinLightCodeInsightFixtureTestCase() {
private val BEFORE_MARKER = "<before>" // position to invoke completion before
private val CHANGE_MARKER = "<change>" // position to insert text specified by "TYPE" directive
private val TYPE_DIRECTIVE_PREFIX = "// TYPE:"
private val BACKSPACES_DIRECTIVE_PREFIX = "// BACKSPACES:"
override fun getProjectDescriptor() = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
protected fun doTest(testPath: String) {
val file = File(testPath)
val hasCaretMarker = FileUtil.loadFile(file, true).contains("<caret>")
myFixture.configureByFile(testPath)
val document = myFixture.editor.document
val beforeMarkerOffset = document.text.indexOf(BEFORE_MARKER)
assertTrue("\"$BEFORE_MARKER\" is missing in file \"$testPath\"", beforeMarkerOffset >= 0)
val changeMarkerOffset = document.text.indexOf(CHANGE_MARKER)
assertTrue("\"$CHANGE_MARKER\" is missing in file \"$testPath\"", changeMarkerOffset >= 0)
val textToType = InTextDirectivesUtils.findArrayWithPrefixes(document.text, TYPE_DIRECTIVE_PREFIX).singleOrNull()
?.let { StringUtil.unquoteString(it) }
val backspaceCount = InTextDirectivesUtils.getPrefixedInt(document.text, BACKSPACES_DIRECTIVE_PREFIX)
assertTrue("At least one of \"$TYPE_DIRECTIVE_PREFIX\" and \"$BACKSPACES_DIRECTIVE_PREFIX\" should be defined",
textToType != null || backspaceCount != null)
val beforeMarker = document.createRangeMarker(beforeMarkerOffset, beforeMarkerOffset + BEFORE_MARKER.length)
val changeMarker = document.createRangeMarker(changeMarkerOffset, changeMarkerOffset + CHANGE_MARKER.length)
changeMarker.isGreedyToRight = true
project.executeWriteCommand("") {
document.deleteString(beforeMarker.startOffset, beforeMarker.endOffset)
document.deleteString(changeMarker.startOffset, changeMarker.endOffset)
}
val caretMarker = if (hasCaretMarker)
document.createRangeMarker(editor.caretModel.offset, editor.caretModel.offset)
else
null
editor.caretModel.moveToOffset(beforeMarker.startOffset)
val testLog = StringBuilder()
CompletionBindingContextProvider.getInstance(project).TEST_LOG = testLog
myFixture.complete(CompletionType.BASIC)
project.executeWriteCommand("") {
if (backspaceCount != null) {
document.deleteString(changeMarker.startOffset - backspaceCount, changeMarker.startOffset)
}
if (textToType != null) {
document.insertString(changeMarker.startOffset, textToType)
}
}
if (caretMarker != null) {
editor.caretModel.moveToOffset(caretMarker.startOffset)
}
else {
editor.caretModel.moveToOffset(changeMarker.endOffset)
}
testCompletion(FileUtil.loadFile(file, true),
JvmPlatform,
{ completionType, count -> myFixture.complete(completionType, count) },
additionalValidDirectives = listOf(TYPE_DIRECTIVE_PREFIX, BACKSPACES_DIRECTIVE_PREFIX))
KotlinTestUtils.assertEqualsToFile(File(file.parent, file.nameWithoutExtension + ".log"), testLog.toString())
}
}
@@ -0,0 +1,98 @@
/*
* Copyright 2010-2017 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.completion.test;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TargetBackend;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/idea-completion/testData/incrementalResolve")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class CompletionIncrementalResolveTestGenerated extends AbstractCompletionIncrementalResolveTest {
public void testAllFilesPresentInIncrementalResolve() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/incrementalResolve"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("codeAboveChanged.kt")
public void testCodeAboveChanged() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/codeAboveChanged.kt");
doTest(fileName);
}
@TestMetadata("codeAboveChanged2.kt")
public void testCodeAboveChanged2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/codeAboveChanged2.kt");
doTest(fileName);
}
@TestMetadata("dataFlowInfoFromPrevStatement.kt")
public void testDataFlowInfoFromPrevStatement() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/dataFlowInfoFromPrevStatement.kt");
doTest(fileName);
}
@TestMetadata("dataFlowInfoFromSameStatement.kt")
public void testDataFlowInfoFromSameStatement() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/dataFlowInfoFromSameStatement.kt");
doTest(fileName);
}
@TestMetadata("doNotAnalyzeComplexStatement.kt")
public void testDoNotAnalyzeComplexStatement() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/doNotAnalyzeComplexStatement.kt");
doTest(fileName);
}
@TestMetadata("noDataFlowFromOldStatement.kt")
public void testNoDataFlowFromOldStatement() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/noDataFlowFromOldStatement.kt");
doTest(fileName);
}
@TestMetadata("noPrevStatement.kt")
public void testNoPrevStatement() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/noPrevStatement.kt");
doTest(fileName);
}
@TestMetadata("outOfBlockModification.kt")
public void testOutOfBlockModification() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/outOfBlockModification.kt");
doTest(fileName);
}
@TestMetadata("prevStatementNotResolved.kt")
public void testPrevStatementNotResolved() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/prevStatementNotResolved.kt");
doTest(fileName);
}
@TestMetadata("sameStatement.kt")
public void testSameStatement() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/sameStatement.kt");
doTest(fileName);
}
}
@@ -27,13 +27,20 @@ val RELATIVE_COMPLETION_TEST_DATA_BASE_PATH = "idea/idea-completion/testData"
val COMPLETION_TEST_DATA_BASE_PATH = KotlinTestUtils.getHomeDirectory() + "/" + RELATIVE_COMPLETION_TEST_DATA_BASE_PATH
fun testCompletion(fileText: String, platform: TargetPlatform?, complete: (CompletionType, Int) -> Array<LookupElement>?, defaultCompletionType: CompletionType = CompletionType.BASIC, defaultInvocationCount: Int = 0) {
fun testCompletion(
fileText: String,
platform: TargetPlatform?,
complete: (CompletionType, Int) -> Array<LookupElement>?,
defaultCompletionType: CompletionType = CompletionType.BASIC,
defaultInvocationCount: Int = 0,
additionalValidDirectives: Collection<String> = emptyList()
) {
testWithAutoCompleteSetting(fileText) {
val completionType = ExpectedCompletionUtils.getCompletionType(fileText) ?: defaultCompletionType
val invocationCount = ExpectedCompletionUtils.getInvocationCount(fileText) ?: defaultInvocationCount
val items = complete(completionType, invocationCount) ?: emptyArray()
ExpectedCompletionUtils.assertDirectivesValid(fileText)
ExpectedCompletionUtils.assertDirectivesValid(fileText, additionalValidDirectives)
val expected = ExpectedCompletionUtils.itemsShouldExist(fileText, platform)
val unexpected = ExpectedCompletionUtils.itemsShouldAbsent(fileText, platform)
@@ -221,8 +221,8 @@ object ExpectedCompletionUtils {
return !InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, WITH_ORDER_PREFIX).isEmpty()
}
fun assertDirectivesValid(fileText: String) {
InTextDirectivesUtils.assertHasUnknownPrefixes(fileText, KNOWN_PREFIXES)
fun assertDirectivesValid(fileText: String, additionalValidDirectives: Collection<String> = emptyList()) {
InTextDirectivesUtils.assertHasUnknownPrefixes(fileText, KNOWN_PREFIXES + additionalValidDirectives)
}
fun assertContainsRenderedItems(expected: Array<CompletionProposal>, items: Array<LookupElement>, checkOrder: Boolean, nothingElse: Boolean) {
+4
View File
@@ -47,6 +47,10 @@
<component>
<implementation-class>org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener</implementation-class>
</component>
<component>
<interface-class>org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider</interface-class>
<implementation-class>org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider</implementation-class>
</component>
</project-components>
<application-components>
@@ -20,11 +20,12 @@ import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentOfType
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
class ConcatenatedStringGenerator {
fun create(element: KtBinaryExpression): String {
val binaryExpression = KtPsiUtil.getTopmostParentOfTypes(element, KtBinaryExpression::class.java) as? KtBinaryExpression ?: element
val binaryExpression = element.getTopmostParentOfType <KtBinaryExpression>() ?: element
val stringBuilder = StringBuilder()
binaryExpression.appendTo(stringBuilder)
return stringBuilder.toString()