Convert reference to lambda: handle special case of extension functional type #KT-14985 Fixed

This commit is contained in:
Mikhail Glukhikh
2016-12-02 18:34:06 +03:00
parent e904e56de3
commit 0994d46038
6 changed files with 64 additions and 13 deletions
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.idea.intentions package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.builtins.isExtensionFunctionType
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
@@ -28,6 +29,8 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.BindingContext.DOUBLE_COLON_LHS import org.jetbrains.kotlin.resolve.BindingContext.DOUBLE_COLON_LHS
import org.jetbrains.kotlin.resolve.BindingContext.REFERENCE_TARGET import org.jetbrains.kotlin.resolve.BindingContext.REFERENCE_TARGET
import org.jetbrains.kotlin.resolve.calls.callUtil.getParameterForArgument
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.expressions.DoubleColonLHS import org.jetbrains.kotlin.types.expressions.DoubleColonLHS
@@ -50,23 +53,30 @@ class ConvertReferenceToLambdaIntention : SelfTargetingOffsetIndependentIntentio
val receiverNameAndType = receiverType?.let { KotlinNameSuggester.suggestNamesByType(it, validator = { val receiverNameAndType = receiverType?.let { KotlinNameSuggester.suggestNamesByType(it, validator = {
name -> name !in parameterNamesAndTypes.map { it.first } name -> name !in parameterNamesAndTypes.map { it.first }
}, defaultName = "receiver").first() to it } }, defaultName = "receiver").first() to it }
val acceptsReceiverAsParameter = receiverNameAndType != null &&
val valueArgumentParent = element.parent as? KtValueArgument
val callGrandParent = valueArgumentParent?.parent?.parent as? KtCallExpression
val resolvedCall = callGrandParent?.getResolvedCall(context)
val matchingParameterType = resolvedCall?.getParameterForArgument(valueArgumentParent)?.type
val matchingParameterIsExtension = matchingParameterType?.isExtensionFunctionType ?: false
val acceptsReceiverAsParameter = receiverNameAndType != null && !matchingParameterIsExtension &&
(targetDescriptor.dispatchReceiverParameter != null || (targetDescriptor.dispatchReceiverParameter != null ||
targetDescriptor.extensionReceiverParameter != null) targetDescriptor.extensionReceiverParameter != null)
val referenceParent = element.parent
val insideCall = referenceParent is KtValueArgument
val factory = KtPsiFactory(element) val factory = KtPsiFactory(element)
val targetName = reference.text val targetName = reference.text
val lambdaParameterNamesAndTypes = val lambdaParameterNamesAndTypes =
if (acceptsReceiverAsParameter) listOf(receiverNameAndType!!) + parameterNamesAndTypes if (acceptsReceiverAsParameter) listOf(receiverNameAndType!!) + parameterNamesAndTypes
else parameterNamesAndTypes else parameterNamesAndTypes
val receiverPrefix = val receiverPrefix = when {
if (acceptsReceiverAsParameter) receiverNameAndType!!.first + "." acceptsReceiverAsParameter -> receiverNameAndType!!.first + "."
else receiverExpression?.let { it.text + "." } ?: "" matchingParameterIsExtension -> ""
val lambdaExpression = if (insideCall && lambdaParameterNamesAndTypes.size == 1) { else -> receiverExpression?.let { it.text + "." } ?: ""
}
val lambdaExpression = if (valueArgumentParent != null && lambdaParameterNamesAndTypes.size == 1) {
factory.createLambdaExpression( factory.createLambdaExpression(
parameters = "", parameters = "",
body = when { body = when {
@@ -81,7 +91,7 @@ class ConvertReferenceToLambdaIntention : SelfTargetingOffsetIndependentIntentio
else { else {
factory.createLambdaExpression( factory.createLambdaExpression(
parameters = lambdaParameterNamesAndTypes.joinToString(separator = ", ") { parameters = lambdaParameterNamesAndTypes.joinToString(separator = ", ") {
if (insideCall) it.first if (valueArgumentParent != null) it.first
else it.first + ": " + SOURCE_RENDERER.renderType(it.second) else it.first + ": " + SOURCE_RENDERER.renderType(it.second)
}, },
body = if (targetDescriptor is PropertyDescriptor) { body = if (targetDescriptor is PropertyDescriptor) {
@@ -99,11 +109,10 @@ class ConvertReferenceToLambdaIntention : SelfTargetingOffsetIndependentIntentio
val lambdaResult = element.replace(lambdaExpression) as KtLambdaExpression val lambdaResult = element.replace(lambdaExpression) as KtLambdaExpression
ShortenReferences.DEFAULT.process(lambdaResult) ShortenReferences.DEFAULT.process(lambdaResult)
if (insideCall) { if (valueArgumentParent != null && callGrandParent != null) {
val call = referenceParent?.parent?.parent as? KtCallExpression ?: return
val moveOutOfParenthesis = MoveLambdaOutsideParenthesesIntention() val moveOutOfParenthesis = MoveLambdaOutsideParenthesesIntention()
if (moveOutOfParenthesis.isApplicableTo(call, referenceParent.startOffset)) { if (moveOutOfParenthesis.isApplicableTo(callGrandParent, valueArgumentParent.startOffset)) {
moveOutOfParenthesis.applyTo(call, editor) moveOutOfParenthesis.applyTo(callGrandParent, editor)
} }
} }
} }
@@ -0,0 +1,7 @@
// WITH_RUNTIME
class My {
fun foo() {}
}
val x = My().apply(<caret>My::foo)
@@ -0,0 +1,7 @@
// WITH_RUNTIME
class My {
fun foo() {}
}
val x = My().apply { foo() }
@@ -0,0 +1,8 @@
class My {
fun foo(a: Int, b: String) = "$b$a"
}
fun baz(f: My.(Int, String) -> String) = My().f(42, "")
val x = baz(<caret>My::foo)
@@ -0,0 +1,8 @@
class My {
fun foo(a: Int, b: String) = "$b$a"
}
fun baz(f: My.(Int, String) -> String) = My().f(42, "")
val x = baz { a, b -> foo(a, b) }
@@ -4961,6 +4961,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertReferenceToLambda"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertReferenceToLambda"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
} }
@TestMetadata("apply.kt")
public void testApply() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/apply.kt");
doTest(fileName);
}
@TestMetadata("boundReference.kt") @TestMetadata("boundReference.kt")
public void testBoundReference() throws Exception { public void testBoundReference() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/boundReference.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/boundReference.kt");
@@ -4985,6 +4991,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("extensionFunctionalType.kt")
public void testExtensionFunctionalType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/extensionFunctionalType.kt");
doTest(fileName);
}
@TestMetadata("extensionProperty.kt") @TestMetadata("extensionProperty.kt")
public void testExtensionProperty() throws Exception { public void testExtensionProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/extensionProperty.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/extensionProperty.kt");