From 0994d4603844dcc846e7ae0dfd8a55223b15418a Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Fri, 2 Dec 2016 18:34:06 +0300 Subject: [PATCH] Convert reference to lambda: handle special case of extension functional type #KT-14985 Fixed --- .../ConvertReferenceToLambdaIntention.kt | 35 ++++++++++++------- .../convertReferenceToLambda/apply.kt | 7 ++++ .../convertReferenceToLambda/apply.kt.after | 7 ++++ .../extensionFunctionalType.kt | 8 +++++ .../extensionFunctionalType.kt.after | 8 +++++ .../intentions/IntentionTestGenerated.java | 12 +++++++ 6 files changed, 64 insertions(+), 13 deletions(-) create mode 100644 idea/testData/intentions/convertReferenceToLambda/apply.kt create mode 100644 idea/testData/intentions/convertReferenceToLambda/apply.kt.after create mode 100644 idea/testData/intentions/convertReferenceToLambda/extensionFunctionalType.kt create mode 100644 idea/testData/intentions/convertReferenceToLambda/extensionFunctionalType.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertReferenceToLambdaIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertReferenceToLambdaIntention.kt index 08d97e1e2ae..7c104638429 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertReferenceToLambdaIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertReferenceToLambdaIntention.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.idea.intentions import com.intellij.openapi.editor.Editor +import org.jetbrains.kotlin.builtins.isExtensionFunctionType import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor 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.resolve.BindingContext.DOUBLE_COLON_LHS 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.types.expressions.DoubleColonLHS @@ -50,23 +53,30 @@ class ConvertReferenceToLambdaIntention : SelfTargetingOffsetIndependentIntentio val receiverNameAndType = receiverType?.let { KotlinNameSuggester.suggestNamesByType(it, validator = { name -> name !in parameterNamesAndTypes.map { it.first } }, 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.extensionReceiverParameter != null) - val referenceParent = element.parent - val insideCall = referenceParent is KtValueArgument - val factory = KtPsiFactory(element) val targetName = reference.text val lambdaParameterNamesAndTypes = if (acceptsReceiverAsParameter) listOf(receiverNameAndType!!) + parameterNamesAndTypes else parameterNamesAndTypes - val receiverPrefix = - if (acceptsReceiverAsParameter) receiverNameAndType!!.first + "." - else receiverExpression?.let { it.text + "." } ?: "" - val lambdaExpression = if (insideCall && lambdaParameterNamesAndTypes.size == 1) { + val receiverPrefix = when { + acceptsReceiverAsParameter -> receiverNameAndType!!.first + "." + matchingParameterIsExtension -> "" + else -> receiverExpression?.let { it.text + "." } ?: "" + } + + val lambdaExpression = if (valueArgumentParent != null && lambdaParameterNamesAndTypes.size == 1) { factory.createLambdaExpression( parameters = "", body = when { @@ -81,7 +91,7 @@ class ConvertReferenceToLambdaIntention : SelfTargetingOffsetIndependentIntentio else { factory.createLambdaExpression( parameters = lambdaParameterNamesAndTypes.joinToString(separator = ", ") { - if (insideCall) it.first + if (valueArgumentParent != null) it.first else it.first + ": " + SOURCE_RENDERER.renderType(it.second) }, body = if (targetDescriptor is PropertyDescriptor) { @@ -99,11 +109,10 @@ class ConvertReferenceToLambdaIntention : SelfTargetingOffsetIndependentIntentio val lambdaResult = element.replace(lambdaExpression) as KtLambdaExpression ShortenReferences.DEFAULT.process(lambdaResult) - if (insideCall) { - val call = referenceParent?.parent?.parent as? KtCallExpression ?: return + if (valueArgumentParent != null && callGrandParent != null) { val moveOutOfParenthesis = MoveLambdaOutsideParenthesesIntention() - if (moveOutOfParenthesis.isApplicableTo(call, referenceParent.startOffset)) { - moveOutOfParenthesis.applyTo(call, editor) + if (moveOutOfParenthesis.isApplicableTo(callGrandParent, valueArgumentParent.startOffset)) { + moveOutOfParenthesis.applyTo(callGrandParent, editor) } } } diff --git a/idea/testData/intentions/convertReferenceToLambda/apply.kt b/idea/testData/intentions/convertReferenceToLambda/apply.kt new file mode 100644 index 00000000000..1b1dd7cb4e3 --- /dev/null +++ b/idea/testData/intentions/convertReferenceToLambda/apply.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +class My { + fun foo() {} +} + +val x = My().apply(My::foo) \ No newline at end of file diff --git a/idea/testData/intentions/convertReferenceToLambda/apply.kt.after b/idea/testData/intentions/convertReferenceToLambda/apply.kt.after new file mode 100644 index 00000000000..99d35b9dfc4 --- /dev/null +++ b/idea/testData/intentions/convertReferenceToLambda/apply.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +class My { + fun foo() {} +} + +val x = My().apply { foo() } \ No newline at end of file diff --git a/idea/testData/intentions/convertReferenceToLambda/extensionFunctionalType.kt b/idea/testData/intentions/convertReferenceToLambda/extensionFunctionalType.kt new file mode 100644 index 00000000000..920f7be11f1 --- /dev/null +++ b/idea/testData/intentions/convertReferenceToLambda/extensionFunctionalType.kt @@ -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(My::foo) diff --git a/idea/testData/intentions/convertReferenceToLambda/extensionFunctionalType.kt.after b/idea/testData/intentions/convertReferenceToLambda/extensionFunctionalType.kt.after new file mode 100644 index 00000000000..158c133c45e --- /dev/null +++ b/idea/testData/intentions/convertReferenceToLambda/extensionFunctionalType.kt.after @@ -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) } diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 69025beeb08..40f7ba96f56 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -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); } + @TestMetadata("apply.kt") + public void testApply() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/apply.kt"); + doTest(fileName); + } + @TestMetadata("boundReference.kt") public void testBoundReference() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/boundReference.kt"); @@ -4985,6 +4991,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest { 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") public void testExtensionProperty() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/extensionProperty.kt");