Convert reference to lambda: handle special case of extension functional type #KT-14985 Fixed
This commit is contained in:
+22
-13
@@ -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)
|
||||||
+8
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user