From 58ac497bd730e0e6607a8758e1be2d4d0da01231 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Thu, 7 May 2015 00:18:38 +0300 Subject: [PATCH] KT-5041 Smart completion for last argument lambda outside parenthesis #KT-5041 Fixed --- .../idea/completion/CompletionSession.kt | 22 ++++++- .../kotlin/idea/completion/ExpectedInfos.kt | 29 +++++--- .../idea/completion/smart/LambdaItems.kt | 11 +++- .../OutsideCallParenthesis1.kt | 8 +++ .../OutsideCallParenthesis2.kt | 9 +++ .../OutsideCallParenthesis3.kt | 8 +++ .../OutsideCallParenthesis4.kt | 8 +++ .../OutsideCallParenthesis5.kt | 8 +++ .../OutsideCallParenthesis6.kt | 9 +++ .../OutsideCallParenthesis7.kt | 8 +++ .../OutsideCallParenthesis8.kt | 8 +++ .../OutsideCallParenthesis9.kt | 7 ++ .../OutsideCallParenthesisAndVararg1.kt | 8 +++ .../OutsideCallParenthesisAndVararg2.kt | 8 +++ .../test/JvmSmartCompletionTestGenerated.java | 66 +++++++++++++++++++ 15 files changed, 204 insertions(+), 13 deletions(-) create mode 100644 idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis1.kt create mode 100644 idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis2.kt create mode 100644 idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis3.kt create mode 100644 idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis4.kt create mode 100644 idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis5.kt create mode 100644 idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis6.kt create mode 100644 idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis7.kt create mode 100644 idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis8.kt create mode 100644 idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis9.kt create mode 100644 idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesisAndVararg1.kt create mode 100644 idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesisAndVararg2.kt diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt index b59638dcaea..f013a2ad90e 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt @@ -19,21 +19,19 @@ package org.jetbrains.kotlin.idea.completion import com.intellij.codeInsight.completion.* import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile -import com.intellij.patterns.CharPattern import com.intellij.patterns.ElementPattern -import com.intellij.patterns.PatternCondition import com.intellij.patterns.StandardPatterns import com.intellij.psi.PsiCompiledElement import com.intellij.psi.PsiElement import com.intellij.psi.search.DelegatingGlobalSearchScope import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.util.PsiTreeUtil -import com.intellij.util.ProcessingContext import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper +import org.jetbrains.kotlin.idea.completion.smart.LambdaItems import org.jetbrains.kotlin.idea.completion.smart.SmartCompletion import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper import org.jetbrains.kotlin.idea.core.comparePossiblyOverridingDescriptors @@ -50,6 +48,7 @@ import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.psi.psiUtil.prevLeaf import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo +import org.jetbrains.kotlin.resolve.calls.callUtil.getCall import org.jetbrains.kotlin.resolve.calls.smartcasts.SmartCastUtils import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.scopes.DescriptorKindExclude @@ -400,6 +399,23 @@ class SmartCompletionSession(configuration: CompletionSessionConfiguration, para override fun doComplete() { if (expression != null) { val mapper = ToFromOriginalFileMapper(parameters.getOriginalFile() as JetFile, position.getContainingFile() as JetFile, parameters.getOffset()) + + // special completion for outside parenthesis lambda argument + if (reference != null) { + val receiverData = ReferenceVariantsHelper.getExplicitReceiverData(reference.expression) + if (receiverData != null && receiverData.second == CallType.INFIX) { + val call = receiverData.first.getCall(bindingContext) + if (call != null && call.getFunctionLiteralArguments().isEmpty()) { + val argumentIndex = call.getValueArguments().size() + val expectedInfos = ExpectedInfos(bindingContext, resolutionFacade, moduleDescriptor, true) + .calculateForArgument(call, argumentIndex, true) + if (expectedInfos != null) { + collector.addElements(LambdaItems.collect(expectedInfos)) + } + } + } + } + val completion = SmartCompletion(expression, resolutionFacade, moduleDescriptor, bindingContext, { isVisibleDescriptor(it) }, inDescriptor, prefixMatcher, originalSearchScope, mapper, lookupElementFactory) diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ExpectedInfos.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ExpectedInfos.kt index 9613e0b5017..41577b956b2 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ExpectedInfos.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ExpectedInfos.kt @@ -116,8 +116,6 @@ class ExpectedInfos( } private fun calculateForArgument(callElement: JetCallElement, argumentIndex: Int, isFunctionLiteralArgument: Boolean): Collection? { - val calleeExpression = callElement.getCalleeExpression() - val parent = callElement.getParent() val receiver: ReceiverValue val callOperationNode: ASTNode? @@ -141,21 +139,31 @@ class ExpectedInfos( receiver = ReceiverValue.NO_RECEIVER callOperationNode = null } - var call = CallMaker.makeCall(receiver, callOperationNode, callElement) - if (!isFunctionLiteralArgument) { // leave only arguments before the current one - call = object : DelegatingCall(call) { + val call = CallMaker.makeCall(receiver, callOperationNode, callElement) + return calculateForArgument(call, argumentIndex, isFunctionLiteralArgument) + } + + public fun calculateForArgument(call: Call, argumentIndex: Int, isFunctionLiteralArgument: Boolean): Collection? { + val callElement = call.getCallElement() + val calleeExpression = call.getCalleeExpression() + + val truncatedCall = if (!isFunctionLiteralArgument) { // leave only arguments before the current one + object : DelegatingCall(call) { override fun getValueArguments() = super.getValueArguments().subList(0, argumentIndex) override fun getValueArgumentList() = null } } + else { + call + } val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, calleeExpression] ?: return null //TODO: discuss it val expectedType = (callElement as? JetExpression)?.let { bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, it] } ?: TypeUtils.NO_EXPECTED_TYPE val dataFlowInfo = bindingContext.getDataFlowInfo(calleeExpression) val bindingTrace = DelegatingBindingTrace(bindingContext, "Temporary trace for completion") - val context = BasicCallResolutionContext.create(bindingTrace, resolutionScope, call, expectedType, dataFlowInfo, + val context = BasicCallResolutionContext.create(bindingTrace, resolutionScope, truncatedCall, expectedType, dataFlowInfo, ContextDependency.INDEPENDENT, CheckValueArgumentsMode.ENABLED, CompositeChecker(listOf()), SymbolUsageValidator.Empty, AdditionalTypeChecker.Composite(listOf()), false) val callResolutionContext = context.replaceCollectAllCandidates(true) @@ -175,10 +183,10 @@ class ExpectedInfos( val descriptor = candidate.getResultingDescriptor() val parameters = descriptor.getValueParameters() + if (parameters.isEmpty()) continue val parameterIndex = if (isFunctionLiteralArgument) { - if (argumentIndex != parameters.lastIndex) continue //TODO: varargs and optional parameters - argumentIndex + parameters.lastIndex } else { val varArgIndex = parameters.indexOfFirst { it.getVarargElementType() != null } @@ -200,6 +208,8 @@ class ExpectedInfos( val expectedName = if (descriptor.hasSynthesizedParameterNames()) null else parameter.getName().asString() if (varargElementType != null) { + if (isFunctionLiteralArgument) continue + expectedInfos.add(PositionalArgumentExpectedInfo(varargElementType, expectedName?.fromPlural(), null, descriptor, parameterIndex)) if (argumentIndex == parameterIndex) { @@ -231,6 +241,9 @@ class ExpectedInfos( HeuristicSignatures.correctedParameterType(descriptor, argumentIndex, moduleDescriptor, callElement.getProject()) ?: parameter.getType() else parameter.getType() + + if (isFunctionLiteralArgument && !KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(parameterType)) continue + expectedInfos.add(PositionalArgumentExpectedInfo(parameterType, expectedName, tail, descriptor, parameterIndex)) } } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/LambdaItems.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/LambdaItems.kt index 9c829f5981c..9a72d5b7689 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/LambdaItems.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/LambdaItems.kt @@ -18,14 +18,21 @@ package org.jetbrains.kotlin.idea.completion.smart import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder -import org.jetbrains.kotlin.builtins.KotlinBuiltIns -import org.jetbrains.kotlin.idea.completion.handlers.insertLambdaTemplate import com.intellij.openapi.util.TextRange +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.idea.completion.ExpectedInfo import org.jetbrains.kotlin.idea.completion.handlers.buildLambdaPresentation +import org.jetbrains.kotlin.idea.completion.handlers.insertLambdaTemplate import org.jetbrains.kotlin.idea.completion.suppressAutoInsertion +import java.util.* object LambdaItems { + public fun collect(functionExpectedInfos: Collection): Collection { + val list = ArrayList() + addToCollection(list, functionExpectedInfos) + return list + } + public fun addToCollection(collection: MutableCollection, functionExpectedInfos: Collection) { val distinctTypes = functionExpectedInfos.map { it.type }.toSet() diff --git a/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis1.kt b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis1.kt new file mode 100644 index 00000000000..cba2b28c971 --- /dev/null +++ b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis1.kt @@ -0,0 +1,8 @@ +fun foo(p: Int, handler: (String, Char) -> Unit){} + +fun bar(handler: (String, Char) -> Unit) { + foo(1) +} + +// EXIST: "{ String, Char -> ... }" +// ABSENT: handler \ No newline at end of file diff --git a/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis2.kt b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis2.kt new file mode 100644 index 00000000000..5642badc861 --- /dev/null +++ b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis2.kt @@ -0,0 +1,9 @@ +fun foo(p: String.(Int) -> Unit){} + +fun bar(p: String.(Int) -> Unit) { + foo() +} + +// EXIST: "{...}" +// EXIST: "{ Int -> ... }" +// ABSENT: p diff --git a/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis3.kt b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis3.kt new file mode 100644 index 00000000000..95dc7958baf --- /dev/null +++ b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis3.kt @@ -0,0 +1,8 @@ +fun foo(handler: (String, Char) -> Unit){} + +fun bar(handler: (String, Char) -> Unit) { + foo +} + +// EXIST: "{ String, Char -> ... }" +// ABSENT: handler \ No newline at end of file diff --git a/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis4.kt b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis4.kt new file mode 100644 index 00000000000..2623157d746 --- /dev/null +++ b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis4.kt @@ -0,0 +1,8 @@ +fun String.foo(handler: (String, Char) -> Unit){} + +fun bar(handler: (String, Char) -> Unit) { + "".foo +} + +// EXIST: "{ String, Char -> ... }" +// ABSENT: handler \ No newline at end of file diff --git a/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis5.kt b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis5.kt new file mode 100644 index 00000000000..5d134950ed7 --- /dev/null +++ b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis5.kt @@ -0,0 +1,8 @@ +fun String.foo(handler: (String, Char) -> Unit){} + +fun bar(handler: (String, Char) -> Unit) { + "" foo +} + +// EXIST: "{ String, Char -> ... }" +// EXIST: handler \ No newline at end of file diff --git a/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis6.kt b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis6.kt new file mode 100644 index 00000000000..b4433d56cf7 --- /dev/null +++ b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis6.kt @@ -0,0 +1,9 @@ +fun foo(p: Int, handler: ((String, Char) -> Unit)?){} + +fun bar(handler: (String, Char) -> Unit) { + foo(1) +} + +// EXIST: "{ String, Char -> ... }" +// ABSENT: null +// ABSENT: handler \ No newline at end of file diff --git a/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis7.kt b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis7.kt new file mode 100644 index 00000000000..36dcae63674 --- /dev/null +++ b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis7.kt @@ -0,0 +1,8 @@ +fun foo(optional: Int = 0, handler: (String, Char) -> Unit){} + +fun bar(handler: (String, Char) -> Unit) { + foo() +} + +// EXIST: "{ String, Char -> ... }" +// ABSENT: handler \ No newline at end of file diff --git a/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis8.kt b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis8.kt new file mode 100644 index 00000000000..eb955b4ca42 --- /dev/null +++ b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis8.kt @@ -0,0 +1,8 @@ +fun foo(optional: Int = 0, handler: (String, Char) -> Unit){} + +fun bar(handler: (String, Char) -> Unit) { + foo +} + +// EXIST: "{ String, Char -> ... }" +// ABSENT: handler \ No newline at end of file diff --git a/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis9.kt b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis9.kt new file mode 100644 index 00000000000..e7c48f5839f --- /dev/null +++ b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis9.kt @@ -0,0 +1,7 @@ +fun foo(handler: (String, Char) -> Unit, optional: Int = 0){} + +fun bar(handler: (String, Char) -> Unit) { + foo() +} + +// NUMBER: 0 \ No newline at end of file diff --git a/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesisAndVararg1.kt b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesisAndVararg1.kt new file mode 100644 index 00000000000..4cab0af568d --- /dev/null +++ b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesisAndVararg1.kt @@ -0,0 +1,8 @@ +fun foo(vararg args: Int, handler: (String, Char) -> Unit){} + +fun bar(handler: (String, Char) -> Unit) { + foo() +} + +// EXIST: "{ String, Char -> ... }" +// ABSENT: handler \ No newline at end of file diff --git a/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesisAndVararg2.kt b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesisAndVararg2.kt new file mode 100644 index 00000000000..b0601235a8e --- /dev/null +++ b/idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesisAndVararg2.kt @@ -0,0 +1,8 @@ +fun foo(vararg args: Int, handler: (String, Char) -> Unit){} + +fun bar(handler: (String, Char) -> Unit) { + foo(1, 2) +} + +// EXIST: "{ String, Char -> ... }" +// ABSENT: handler \ No newline at end of file diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmSmartCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmSmartCompletionTestGenerated.java index 877a74d31c1..9ca5a350189 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmSmartCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmSmartCompletionTestGenerated.java @@ -660,6 +660,72 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT public void testAllFilesPresentInFunctionLiterals() throws Exception { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/smart/functionLiterals"), Pattern.compile("^(.+)\\.kt$"), true); } + + @TestMetadata("OutsideCallParenthesis1.kt") + public void testOutsideCallParenthesis1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis1.kt"); + doTest(fileName); + } + + @TestMetadata("OutsideCallParenthesis2.kt") + public void testOutsideCallParenthesis2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis2.kt"); + doTest(fileName); + } + + @TestMetadata("OutsideCallParenthesis3.kt") + public void testOutsideCallParenthesis3() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis3.kt"); + doTest(fileName); + } + + @TestMetadata("OutsideCallParenthesis4.kt") + public void testOutsideCallParenthesis4() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis4.kt"); + doTest(fileName); + } + + @TestMetadata("OutsideCallParenthesis5.kt") + public void testOutsideCallParenthesis5() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis5.kt"); + doTest(fileName); + } + + @TestMetadata("OutsideCallParenthesis6.kt") + public void testOutsideCallParenthesis6() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis6.kt"); + doTest(fileName); + } + + @TestMetadata("OutsideCallParenthesis7.kt") + public void testOutsideCallParenthesis7() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis7.kt"); + doTest(fileName); + } + + @TestMetadata("OutsideCallParenthesis8.kt") + public void testOutsideCallParenthesis8() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis8.kt"); + doTest(fileName); + } + + @TestMetadata("OutsideCallParenthesis9.kt") + public void testOutsideCallParenthesis9() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesis9.kt"); + doTest(fileName); + } + + @TestMetadata("OutsideCallParenthesisAndVararg1.kt") + public void testOutsideCallParenthesisAndVararg1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesisAndVararg1.kt"); + doTest(fileName); + } + + @TestMetadata("OutsideCallParenthesisAndVararg2.kt") + public void testOutsideCallParenthesisAndVararg2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/functionLiterals/OutsideCallParenthesisAndVararg2.kt"); + doTest(fileName); + } } @TestMetadata("idea/idea-completion/testData/smart/functionReference")