diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/MoveLambdaOutsideParenthesesIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/MoveLambdaOutsideParenthesesIntention.kt index 5cf2b1feaee..3d547b2f051 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/MoveLambdaOutsideParenthesesIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/MoveLambdaOutsideParenthesesIntention.kt @@ -17,16 +17,38 @@ package org.jetbrains.kotlin.idea.intentions import com.intellij.openapi.editor.Editor +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.util.psiModificationUtil.moveLambdaOutsideParentheses import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.getValueArgumentsInParentheses +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode public class MoveLambdaOutsideParenthesesIntention : JetSelfTargetingIntention(javaClass(), "Move lambda argument out of parentheses") { override fun isApplicableTo(element: JetCallExpression, caretOffset: Int): Boolean { val argument = element.getValueArgumentsInParentheses().lastOrNull() ?: return false val expression = argument.getArgumentExpression() ?: return false val functionLiteral = getFunctionLiteral(expression) ?: return false + + val callee = element.getCalleeExpression() + if (callee is JetSimpleNameExpression) { + val bindingContext = element.analyze(BodyResolveMode.PARTIAL) + val targets = bindingContext[BindingContext.REFERENCE_TARGET, callee]?.let { listOf(it) } + ?: bindingContext[BindingContext.AMBIGUOUS_REFERENCE_TARGET, callee] + ?: listOf() + val candidates = targets.filterIsInstance() + // if there are functions among candidates but none of them have last function parameter then not show the intention + if (candidates.isNotEmpty() && candidates.none { + val lastParameter = it.getValueParameters().lastOrNull() + lastParameter != null && KotlinBuiltIns.isFunctionOrExtensionFunctionType(lastParameter.getType()) + }) { + return false + } + } + if (caretOffset < argument.asElement().startOffset) return false val bodyRange = functionLiteral.getBodyExpression()?.getTextRange() ?: return true return !bodyRange.containsInside(caretOffset) diff --git a/idea/testData/intentions/moveLambdaOutsideParentheses/ambigousOverload.kt b/idea/testData/intentions/moveLambdaOutsideParentheses/ambigousOverload.kt new file mode 100644 index 00000000000..d1980a66223 --- /dev/null +++ b/idea/testData/intentions/moveLambdaOutsideParentheses/ambigousOverload.kt @@ -0,0 +1,11 @@ +// IS_AVAILABLE: true +// ERROR: None of the following functions can be called with the arguments supplied. +// ERROR: Unresolved reference: it + +fun foo() { + bar({ it }) +} + +fun bar(a: Int = 0, f: (Int) -> Int) { } +fun bar(a: Int, b: Int, f: (Int) -> Int) { } + diff --git a/idea/testData/intentions/moveLambdaOutsideParentheses/ambigousOverload.kt.after b/idea/testData/intentions/moveLambdaOutsideParentheses/ambigousOverload.kt.after new file mode 100644 index 00000000000..553d7079b70 --- /dev/null +++ b/idea/testData/intentions/moveLambdaOutsideParentheses/ambigousOverload.kt.after @@ -0,0 +1,11 @@ +// IS_AVAILABLE: true +// ERROR: None of the following functions can be called with the arguments supplied.
  • bar(Int = ..., (Int) → Int) defined in root package
  • bar(Int, Int, (Int) → Int) defined in root package
+// ERROR: Unresolved reference: it + +fun foo() { + bar { it } +} + +fun bar(a: Int = 0, f: (Int) -> Int) { } +fun bar(a: Int, b: Int, f: (Int) -> Int) { } + diff --git a/idea/testData/intentions/moveLambdaOutsideParentheses/functionalValueCall.kt b/idea/testData/intentions/moveLambdaOutsideParentheses/functionalValueCall.kt new file mode 100644 index 00000000000..0bc4627b173 --- /dev/null +++ b/idea/testData/intentions/moveLambdaOutsideParentheses/functionalValueCall.kt @@ -0,0 +1,3 @@ +fun foo(p: (Int, () -> Int) -> Unit) { + p(1, { 2 }) +} diff --git a/idea/testData/intentions/moveLambdaOutsideParentheses/functionalValueCall.kt.after b/idea/testData/intentions/moveLambdaOutsideParentheses/functionalValueCall.kt.after new file mode 100644 index 00000000000..d2425d2c89d --- /dev/null +++ b/idea/testData/intentions/moveLambdaOutsideParentheses/functionalValueCall.kt.after @@ -0,0 +1,3 @@ +fun foo(p: (Int, () -> Int) -> Unit) { + p(1) { 2 } +} diff --git a/idea/testData/intentions/moveLambdaOutsideParentheses/inapplicableOptionalParametersAfter.kt b/idea/testData/intentions/moveLambdaOutsideParentheses/inapplicableOptionalParametersAfter.kt new file mode 100644 index 00000000000..e1d6fa8b422 --- /dev/null +++ b/idea/testData/intentions/moveLambdaOutsideParentheses/inapplicableOptionalParametersAfter.kt @@ -0,0 +1,6 @@ +// IS_APPLICABLE: false +fun foo() { + bar({ it }) +} + +fun bar(b: (Int) -> Int, option: Int = 0) { } diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 750bc24084a..48620a1fb4f 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -4921,6 +4921,18 @@ public class IntentionTestGenerated extends AbstractIntentionTest { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/moveLambdaOutsideParentheses"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("ambigousOverload.kt") + public void testAmbigousOverload() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/moveLambdaOutsideParentheses/ambigousOverload.kt"); + doTest(fileName); + } + + @TestMetadata("functionalValueCall.kt") + public void testFunctionalValueCall() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/moveLambdaOutsideParentheses/functionalValueCall.kt"); + doTest(fileName); + } + @TestMetadata("inapplicable1.kt") public void testInapplicable1() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/moveLambdaOutsideParentheses/inapplicable1.kt"); @@ -4939,6 +4951,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("inapplicableOptionalParametersAfter.kt") + public void testInapplicableOptionalParametersAfter() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/moveLambdaOutsideParentheses/inapplicableOptionalParametersAfter.kt"); + doTest(fileName); + } + @TestMetadata("labeledLambda.kt") public void testLabeledLambda() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/moveLambdaOutsideParentheses/labeledLambda.kt");