KT-10903 Lambda to callable reference: use named arguments if needed, lift restriction about parameter default values
(cherry picked from commit 5232af5)
This commit is contained in:
committed by
Mikhail Glukhikh
parent
a979c7e1d7
commit
299097d112
+26
-12
@@ -25,7 +25,6 @@ import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
|||||||
import org.jetbrains.kotlin.idea.core.isVisible
|
import org.jetbrains.kotlin.idea.core.isVisible
|
||||||
import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection
|
import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext.REFERENCE_TARGET
|
import org.jetbrains.kotlin.resolve.BindingContext.REFERENCE_TARGET
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue
|
import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue
|
||||||
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
|
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
|
||||||
@@ -43,28 +42,27 @@ class ConvertLambdaToReferenceInspection(
|
|||||||
class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntention<KtLambdaExpression>(
|
class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntention<KtLambdaExpression>(
|
||||||
KtLambdaExpression::class.java, "Convert lambda to reference"
|
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 {
|
override fun isApplicableTo(element: KtLambdaExpression): Boolean {
|
||||||
val body = element.bodyExpression ?: return false
|
val body = element.bodyExpression ?: return false
|
||||||
val statement = body.statements.singleOrNull() ?: return false
|
val statement = body.statements.singleOrNull() ?: return false
|
||||||
val lambdaParent = element.parent
|
val lambdaParent = element.parent
|
||||||
val context: BindingContext
|
|
||||||
var lambdaMustReturnUnit = false
|
var lambdaMustReturnUnit = false
|
||||||
if (lambdaParent is KtLambdaArgument) {
|
if (lambdaParent is KtLambdaArgument) {
|
||||||
val outerCallExpression = lambdaParent.parent as? KtCallExpression ?: return false
|
val outerCalleeDescriptor = lambdaParent.outerCalleeDescriptor() ?: 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 lambdaParameterType = outerCalleeDescriptor.valueParameters.lastOrNull()?.type
|
val lambdaParameterType = outerCalleeDescriptor.valueParameters.lastOrNull()?.type
|
||||||
if (lambdaParameterType != null && lambdaParameterType.isFunctionType) {
|
if (lambdaParameterType != null && lambdaParameterType.isFunctionType) {
|
||||||
// Special Unit case (non-Unit returning lambda is accepted here, but non-Unit returning reference is not)
|
// Special Unit case (non-Unit returning lambda is accepted here, but non-Unit returning reference is not)
|
||||||
lambdaMustReturnUnit = getReturnTypeFromFunctionType(lambdaParameterType).isUnit()
|
lambdaMustReturnUnit = getReturnTypeFromFunctionType(lambdaParameterType).isUnit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
val context = statement.analyze()
|
||||||
context = statement.analyze()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isConvertableCallInLambda(
|
fun isConvertableCallInLambda(
|
||||||
callableExpression: KtExpression,
|
callableExpression: KtExpression,
|
||||||
@@ -187,13 +185,29 @@ class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntentio
|
|||||||
else {
|
else {
|
||||||
// Otherwise, replace the whole argument list for lambda argument-using call
|
// Otherwise, replace the whole argument list for lambda argument-using call
|
||||||
val outerCallExpression = lambdaArgument.parent as? KtCallExpression ?: return
|
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 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 {
|
val newArgumentList = factory.buildValueArgumentList {
|
||||||
appendFixedText("(")
|
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())
|
appendExpression(argument.getArgumentExpression())
|
||||||
appendFixedText(", ")
|
appendFixedText(", ")
|
||||||
}
|
}
|
||||||
|
if (useNamedArguments) {
|
||||||
|
appendName(valueParameters.last().name)
|
||||||
|
appendFixedText(" = ")
|
||||||
|
}
|
||||||
appendFixedText(referenceName)
|
appendFixedText(referenceName)
|
||||||
appendFixedText(")")
|
appendFixedText(")")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IS_APPLICABLE: false
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
|
|
||||||
fun List<Int>.transform(x: Int = 0, f: (Int) -> Int) = map { f(it + x) }
|
fun List<Int>.transform(x: Int = 0, f: (Int) -> Int) = map { f(it + x) }
|
||||||
|
|||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
fun List<Int>.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)
|
||||||
@@ -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) { <caret>bar(it) }
|
||||||
@@ -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)
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
// IS_APPLICABLE: false
|
|
||||||
|
|
||||||
interface Transformer {
|
interface Transformer {
|
||||||
fun transform(x: Int = 0, f: (Int) -> Int) = f(x)
|
fun transform(x: Int = 0, f: (Int) -> Int) = f(x)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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) { <caret>bar(it) }
|
||||||
@@ -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)
|
||||||
@@ -3769,12 +3769,24 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("defaultOverridden.kt")
|
||||||
public void testDefaultOverridden() throws Exception {
|
public void testDefaultOverridden() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/defaultOverridden.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/defaultOverridden.kt");
|
||||||
doTest(fileName);
|
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")
|
@TestMetadata("emptyBrackets.kt")
|
||||||
public void testEmptyBrackets() throws Exception {
|
public void testEmptyBrackets() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/emptyBrackets.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/emptyBrackets.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user