KT-5041 Smart completion for last argument lambda outside parenthesis
#KT-5041 Fixed
This commit is contained in:
@@ -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
|
||||
+8
@@ -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
|
||||
+8
@@ -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
|
||||
+66
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user