KT-5041 Smart completion for last argument lambda outside parenthesis

#KT-5041 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-05-07 00:18:38 +03:00
parent 023c5eabf4
commit 58ac497bd7
15 changed files with 204 additions and 13 deletions
@@ -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)
@@ -116,8 +116,6 @@ class ExpectedInfos(
}
private fun calculateForArgument(callElement: JetCallElement, argumentIndex: Int, isFunctionLiteralArgument: Boolean): Collection<ExpectedInfo>? {
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<ExpectedInfo>? {
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))
}
}
@@ -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<ExpectedInfo>): Collection<LookupElement> {
val list = ArrayList<LookupElement>()
addToCollection(list, functionExpectedInfos)
return list
}
public fun addToCollection(collection: MutableCollection<LookupElement>, functionExpectedInfos: Collection<ExpectedInfo>) {
val distinctTypes = functionExpectedInfos.map { it.type }.toSet()
@@ -0,0 +1,8 @@
fun foo(p: Int, handler: (String, Char) -> Unit){}
fun bar(handler: (String, Char) -> Unit) {
foo(1)<caret>
}
// EXIST: "{ String, Char -> ... }"
// ABSENT: handler
@@ -0,0 +1,9 @@
fun foo(p: String.(Int) -> Unit){}
fun bar(p: String.(Int) -> Unit) {
foo()<caret>
}
// EXIST: "{...}"
// EXIST: "{ Int -> ... }"
// ABSENT: p
@@ -0,0 +1,8 @@
fun foo(handler: (String, Char) -> Unit){}
fun bar(handler: (String, Char) -> Unit) {
foo <caret>
}
// EXIST: "{ String, Char -> ... }"
// ABSENT: handler
@@ -0,0 +1,8 @@
fun String.foo(handler: (String, Char) -> Unit){}
fun bar(handler: (String, Char) -> Unit) {
"".foo <caret>
}
// EXIST: "{ String, Char -> ... }"
// ABSENT: handler
@@ -0,0 +1,8 @@
fun String.foo(handler: (String, Char) -> Unit){}
fun bar(handler: (String, Char) -> Unit) {
"" foo <caret>
}
// EXIST: "{ String, Char -> ... }"
// EXIST: handler
@@ -0,0 +1,9 @@
fun foo(p: Int, handler: ((String, Char) -> Unit)?){}
fun bar(handler: (String, Char) -> Unit) {
foo(1)<caret>
}
// EXIST: "{ String, Char -> ... }"
// ABSENT: null
// ABSENT: handler
@@ -0,0 +1,8 @@
fun foo(optional: Int = 0, handler: (String, Char) -> Unit){}
fun bar(handler: (String, Char) -> Unit) {
foo() <caret>
}
// EXIST: "{ String, Char -> ... }"
// ABSENT: handler
@@ -0,0 +1,8 @@
fun foo(optional: Int = 0, handler: (String, Char) -> Unit){}
fun bar(handler: (String, Char) -> Unit) {
foo <caret>
}
// EXIST: "{ String, Char -> ... }"
// ABSENT: handler
@@ -0,0 +1,7 @@
fun foo(handler: (String, Char) -> Unit, optional: Int = 0){}
fun bar(handler: (String, Char) -> Unit) {
foo() <caret>
}
// NUMBER: 0
@@ -0,0 +1,8 @@
fun foo(vararg args: Int, handler: (String, Char) -> Unit){}
fun bar(handler: (String, Char) -> Unit) {
foo() <caret>
}
// EXIST: "{ String, Char -> ... }"
// ABSENT: handler
@@ -0,0 +1,8 @@
fun foo(vararg args: Int, handler: (String, Char) -> Unit){}
fun bar(handler: (String, Char) -> Unit) {
foo(1, 2) <caret>
}
// EXIST: "{ String, Char -> ... }"
// ABSENT: handler
@@ -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")