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:
Mikhail Glukhikh
2016-07-18 13:00:09 +03:00
committed by Mikhail Glukhikh
parent a979c7e1d7
commit 299097d112
10 changed files with 82 additions and 15 deletions
@@ -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) }
@@ -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");