diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertLambdaToReferenceIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertLambdaToReferenceIntention.kt index 6d2086a34f0..7e2aa493680 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertLambdaToReferenceIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertLambdaToReferenceIntention.kt @@ -25,7 +25,6 @@ import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.core.isVisible import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext.REFERENCE_TARGET import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor @@ -43,28 +42,27 @@ class ConvertLambdaToReferenceInspection( class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntention( KtLambdaExpression::class.java, "Convert lambda to reference" ) { + private fun KtLambdaArgument.outerCalleeDescriptor(): FunctionDescriptor? { + val outerCallExpression = parent as? KtCallExpression ?: return null + val context = outerCallExpression.analyze() + val outerCallee = outerCallExpression.calleeExpression as? KtReferenceExpression ?: return null + return context[REFERENCE_TARGET, outerCallee] as? FunctionDescriptor + } + override fun isApplicableTo(element: KtLambdaExpression): Boolean { val body = element.bodyExpression ?: return false val statement = body.statements.singleOrNull() ?: return false val lambdaParent = element.parent - val context: BindingContext var lambdaMustReturnUnit = false if (lambdaParent is KtLambdaArgument) { - val outerCallExpression = lambdaParent.parent as? KtCallExpression ?: return false - context = outerCallExpression.analyze() - val outerCallee = outerCallExpression.calleeExpression as? KtReferenceExpression ?: return false - val outerCalleeDescriptor = context[REFERENCE_TARGET, outerCallee] as? FunctionDescriptor ?: return false - // No function parameter predecessors with default value - if (outerCalleeDescriptor.valueParameters.any { it.hasDefaultValue() }) return false + val outerCalleeDescriptor = lambdaParent.outerCalleeDescriptor() ?: return false val lambdaParameterType = outerCalleeDescriptor.valueParameters.lastOrNull()?.type if (lambdaParameterType != null && lambdaParameterType.isFunctionType) { // Special Unit case (non-Unit returning lambda is accepted here, but non-Unit returning reference is not) lambdaMustReturnUnit = getReturnTypeFromFunctionType(lambdaParameterType).isUnit() } } - else { - context = statement.analyze() - } + val context = statement.analyze() fun isConvertableCallInLambda( callableExpression: KtExpression, @@ -187,13 +185,29 @@ class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntentio else { // Otherwise, replace the whole argument list for lambda argument-using call val outerCallExpression = lambdaArgument.parent as? KtCallExpression ?: return + val outerCalleeDescriptor = lambdaArgument.outerCalleeDescriptor() ?: return + // Parameters with default value + val valueParameters = outerCalleeDescriptor.valueParameters val arguments = outerCallExpression.valueArguments.filter { it !is KtLambdaArgument } + val useNamedArguments = valueParameters.any { it.hasDefaultValue() } || arguments.any { it.getArgumentName() != null } + + if (useNamedArguments && arguments.size > valueParameters.size) return val newArgumentList = factory.buildValueArgumentList { appendFixedText("(") - for (argument in arguments) { + arguments.forEachIndexed { i, argument -> + if (useNamedArguments) { + val argumentName = argument.getArgumentName()?.asName + val name = argumentName ?: valueParameters[i].name + appendName(name) + appendFixedText(" = ") + } appendExpression(argument.getArgumentExpression()) appendFixedText(", ") } + if (useNamedArguments) { + appendName(valueParameters.last().name) + appendFixedText(" = ") + } appendFixedText(referenceName) appendFixedText(")") } diff --git a/idea/testData/intentions/convertLambdaToReference/defaultBeforeLambda.kt b/idea/testData/intentions/convertLambdaToReference/defaultBeforeLambda.kt index 1e52ab15929..d4a8999a10d 100644 --- a/idea/testData/intentions/convertLambdaToReference/defaultBeforeLambda.kt +++ b/idea/testData/intentions/convertLambdaToReference/defaultBeforeLambda.kt @@ -1,4 +1,3 @@ -// IS_APPLICABLE: false // WITH_RUNTIME fun List.transform(x: Int = 0, f: (Int) -> Int) = map { f(it + x) } diff --git a/idea/testData/intentions/convertLambdaToReference/defaultBeforeLambda.kt.after b/idea/testData/intentions/convertLambdaToReference/defaultBeforeLambda.kt.after new file mode 100644 index 00000000000..61f8eb0dc4a --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/defaultBeforeLambda.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +fun List.transform(x: Int = 0, f: (Int) -> Int) = map { f(it + x) } + +fun bar(x: Int) = x * x + +val y = listOf(1, 2, 3).transform(f = ::bar) diff --git a/idea/testData/intentions/convertLambdaToReference/defaultNamed.kt b/idea/testData/intentions/convertLambdaToReference/defaultNamed.kt new file mode 100644 index 00000000000..0ea41f038a7 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/defaultNamed.kt @@ -0,0 +1,7 @@ +class Transformer { + fun transform(x: Int = 0, y: Int = 1, f: (Int) -> Int) = f(x + y) +} + +fun bar(x: Int) = x * x + +val y = Transformer().transform(y = 3) { bar(it) } diff --git a/idea/testData/intentions/convertLambdaToReference/defaultNamed.kt.after b/idea/testData/intentions/convertLambdaToReference/defaultNamed.kt.after new file mode 100644 index 00000000000..87ceb7a10c0 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/defaultNamed.kt.after @@ -0,0 +1,7 @@ +class Transformer { + fun transform(x: Int = 0, y: Int = 1, f: (Int) -> Int) = f(x + y) +} + +fun bar(x: Int) = x * x + +val y = Transformer().transform(y = 3, f = ::bar) diff --git a/idea/testData/intentions/convertLambdaToReference/defaultOverridden.kt b/idea/testData/intentions/convertLambdaToReference/defaultOverridden.kt index 885dd6919a9..5ecfab6fb96 100644 --- a/idea/testData/intentions/convertLambdaToReference/defaultOverridden.kt +++ b/idea/testData/intentions/convertLambdaToReference/defaultOverridden.kt @@ -1,5 +1,3 @@ -// IS_APPLICABLE: false - interface Transformer { fun transform(x: Int = 0, f: (Int) -> Int) = f(x) } diff --git a/idea/testData/intentions/convertLambdaToReference/defaultOverridden.kt.after b/idea/testData/intentions/convertLambdaToReference/defaultOverridden.kt.after new file mode 100644 index 00000000000..35287f1d2c5 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/defaultOverridden.kt.after @@ -0,0 +1,9 @@ +interface Transformer { + fun transform(x: Int = 0, f: (Int) -> Int) = f(x) +} + +class TransformerImpl : Transformer + +fun bar(x: Int) = x * x + +val y = TransformerImpl().transform(f = ::bar) \ No newline at end of file diff --git a/idea/testData/intentions/convertLambdaToReference/defaultUnnamed.kt b/idea/testData/intentions/convertLambdaToReference/defaultUnnamed.kt new file mode 100644 index 00000000000..56d43711416 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/defaultUnnamed.kt @@ -0,0 +1,7 @@ +class Transformer { + fun transform(x: Int = 0, y: Int = 1, f: (Int) -> Int) = f(x + y) +} + +fun bar(x: Int) = x * x + +val y = Transformer().transform(2) { bar(it) } diff --git a/idea/testData/intentions/convertLambdaToReference/defaultUnnamed.kt.after b/idea/testData/intentions/convertLambdaToReference/defaultUnnamed.kt.after new file mode 100644 index 00000000000..425a8787169 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/defaultUnnamed.kt.after @@ -0,0 +1,7 @@ +class Transformer { + fun transform(x: Int = 0, y: Int = 1, f: (Int) -> Int) = f(x + y) +} + +fun bar(x: Int) = x * x + +val y = Transformer().transform(x = 2, f = ::bar) diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index caf10829f9a..517f3a577a1 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -3769,12 +3769,24 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("defaultNamed.kt") + public void testDefaultNamed() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/defaultNamed.kt"); + doTest(fileName); + } + @TestMetadata("defaultOverridden.kt") public void testDefaultOverridden() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/defaultOverridden.kt"); doTest(fileName); } + @TestMetadata("defaultUnnamed.kt") + public void testDefaultUnnamed() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/defaultUnnamed.kt"); + doTest(fileName); + } + @TestMetadata("emptyBrackets.kt") public void testEmptyBrackets() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/emptyBrackets.kt");