diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertLambdaToReferenceIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertLambdaToReferenceIntention.kt index f738c44a57f..3e281f46c3f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertLambdaToReferenceIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertLambdaToReferenceIntention.kt @@ -95,7 +95,7 @@ class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntentio dispatchReceiverParameter != null || extensionReceiverParameter != null } val callHasReceiver = explicitReceiver != null - if (descriptorHasReceiver != callHasReceiver) return false + if (!descriptorHasReceiver && callHasReceiver) return false val callableArgumentsCount = (callableExpression as? KtCallExpression)?.valueArguments?.size ?: 0 if (calleeDescriptor.valueParameters.size != callableArgumentsCount) return false if (lambdaMustReturnUnit) { @@ -105,17 +105,16 @@ class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntentio } } - val hasSpecification = lambdaExpression.functionLiteral.hasParameterSpecification() - val receiverShift = if (callHasReceiver) 1 else 0 - val parametersCount = if (hasSpecification) lambdaExpression.valueParameters.size else 1 - if (parametersCount != callableArgumentsCount + receiverShift) return false + val explicitReceiverTarget = (explicitReceiver as? KtNameReferenceExpression)?.let { context[REFERENCE_TARGET, it] } val lambdaValueParameters = context[FUNCTION, lambdaExpression.functionLiteral]?.valueParameters ?: return false - if (explicitReceiver != null) { - if (explicitReceiver !is KtNameReferenceExpression) return false - if (lambdaValueParameters.isEmpty()) return false - val explicitReceiverTarget = context[REFERENCE_TARGET, explicitReceiver] as? ParameterDescriptor ?: return false - if (explicitReceiverTarget != lambdaValueParameters[0]) return false + val lambdaParameterAsExplicitReceiver = explicitReceiverTarget is ParameterDescriptor && + explicitReceiverTarget == lambdaValueParameters.firstOrNull() + val explicitReceiverShift = if (lambdaParameterAsExplicitReceiver) 1 else 0 + val parametersCount = + if (lambdaExpression.functionLiteral.hasParameterSpecification()) lambdaExpression.valueParameters.size else 1 + if (parametersCount != callableArgumentsCount + explicitReceiverShift) return false + if (explicitReceiver != null && explicitReceiverTarget is ParameterDescriptor && lambdaParameterAsExplicitReceiver) { val receiverType = explicitReceiverTarget.type // No exotic receiver types if (receiverType.isTypeParameter() || receiverType.isError || receiverType.isDynamic() || @@ -130,16 +129,17 @@ class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntentio )) return false } } + // Same lambda / references function parameter order if (callableExpression is KtCallExpression) { - if (lambdaValueParameters.size < receiverShift + callableExpression.valueArguments.size) return false + if (lambdaValueParameters.size < explicitReceiverShift + callableExpression.valueArguments.size) return false val resolvedCall = callableExpression.getResolvedCall(context) ?: return false resolvedCall.valueArguments.entries.forEach { (valueParameter, resolvedArgument) -> val argumentExpression = resolvedArgument.arguments.singleOrNull()?.getArgumentExpression() as? KtNameReferenceExpression ?: return false val argumentTarget = context[REFERENCE_TARGET, argumentExpression] as? ValueParameterDescriptor ?: return false - if (argumentTarget != lambdaValueParameters[valueParameter.index + receiverShift]) return false + if (argumentTarget != lambdaValueParameters[valueParameter.index + explicitReceiverShift]) return false } } return true @@ -160,8 +160,7 @@ class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntentio } override fun applyTo(element: KtLambdaExpression, editor: Editor?) { - val body = element.bodyExpression ?: return - val referenceName = buildReferenceText(body.statements.singleOrNull() ?: return, shortTypes = false) ?: return + val referenceName = buildReferenceText(lambdaExpression = element, shortTypes = false) ?: return val factory = KtPsiFactory(element) val lambdaArgument = element.parent as? KtLambdaArgument if (lambdaArgument == null) { @@ -213,31 +212,47 @@ class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntentio companion object { internal fun shouldSuggestToConvert(element: KtLambdaExpression): Boolean { - val body = element.bodyExpression ?: return false - val referenceName = buildReferenceText(body.statements.singleOrNull() ?: return false, shortTypes = true) ?: return false + val referenceName = buildReferenceText(lambdaExpression = element, shortTypes = true) ?: return false return referenceName.length < element.text.length } - private fun buildReferenceText(expression: KtExpression, shortTypes: Boolean): String? { - return when (expression) { - is KtCallExpression -> "::${expression.getCallReferencedName()}" + private fun buildReferenceText(lambdaExpression: KtLambdaExpression, shortTypes: Boolean): String? { + val body = lambdaExpression.bodyExpression ?: return null + val callableExpression = body.statements.singleOrNull() + return when (callableExpression) { + is KtCallExpression -> { + val calleeReferenceExpression = callableExpression.calleeExpression as? KtNameReferenceExpression ?: return null + val context = callableExpression.analyze() + val calleeDescriptor = context[REFERENCE_TARGET, calleeReferenceExpression] as? CallableMemberDescriptor + ?: return null + if (calleeDescriptor.dispatchReceiverParameter != null || calleeDescriptor.extensionReceiverParameter != null) + "this::${callableExpression.getCallReferencedName()}" + else + "::${callableExpression.getCallReferencedName()}" + } is KtDotQualifiedExpression -> { - val selector = expression.selectorExpression + val selector = callableExpression.selectorExpression val selectorReferenceName = when (selector) { is KtCallExpression -> selector.getCallReferencedName() ?: return null is KtNameReferenceExpression -> selector.getReferencedName() else -> return null } - val receiver = expression.receiverExpression as? KtNameReferenceExpression ?: return null + val receiver = callableExpression.receiverExpression as? KtNameReferenceExpression ?: return null val context = receiver.analyze() - val receiverDescriptor = context[REFERENCE_TARGET, receiver] as? ParameterDescriptor ?: return null - val originalReceiverType = receiverDescriptor.type - val receiverType = originalReceiverType.approximateFlexibleTypes(preferNotNull = true) - if (shortTypes) { - "${IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(receiverType)}::$selectorReferenceName" + val receiverDescriptor = context[REFERENCE_TARGET, receiver] ?: return null + val lambdaValueParameters = context[FUNCTION, lambdaExpression.functionLiteral]?.valueParameters ?: return null + if (receiverDescriptor is ParameterDescriptor && receiverDescriptor == lambdaValueParameters.firstOrNull()) { + val originalReceiverType = receiverDescriptor.type + val receiverType = originalReceiverType.approximateFlexibleTypes(preferNotNull = true) + if (shortTypes) { + "${IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(receiverType)}::$selectorReferenceName" + } + else { + "${IdeDescriptorRenderers.SOURCE_CODE.renderType(receiverType)}::$selectorReferenceName" + } } else { - "${IdeDescriptorRenderers.SOURCE_CODE.renderType(receiverType)}::$selectorReferenceName" + "${receiverDescriptor.name}::$selectorReferenceName" } } else -> null diff --git a/idea/testData/intentions/convertLambdaToReference/extension.kt b/idea/testData/intentions/convertLambdaToReference/extension.kt index 8e0595daa28..599e1c40d97 100644 --- a/idea/testData/intentions/convertLambdaToReference/extension.kt +++ b/idea/testData/intentions/convertLambdaToReference/extension.kt @@ -1,7 +1,6 @@ -// IS_APPLICABLE: false +// IS_APPLICABLE: true class Owner(val z: Int) { - // Possible only in 1.1 with bound references (this::foo) val x = { arg: Int -> foo(arg) } } diff --git a/idea/testData/intentions/convertLambdaToReference/extension.kt.after b/idea/testData/intentions/convertLambdaToReference/extension.kt.after new file mode 100644 index 00000000000..9d63dd71858 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/extension.kt.after @@ -0,0 +1,8 @@ +// IS_APPLICABLE: true + +class Owner(val z: Int) { + val x = this::foo +} + +fun Owner.foo(y: Int) = y + z + diff --git a/idea/testData/intentions/convertLambdaToReference/member.kt b/idea/testData/intentions/convertLambdaToReference/member.kt index 218e7f5afcc..3458e213f9c 100644 --- a/idea/testData/intentions/convertLambdaToReference/member.kt +++ b/idea/testData/intentions/convertLambdaToReference/member.kt @@ -1,7 +1,6 @@ -// IS_APPLICABLE: false +// IS_APPLICABLE: true class Owner(val z: Int) { fun foo(y: Int) = y + z - // Possible only in 1.1 with bound references (this::foo) val x = { arg: Int -> foo(arg) } } \ No newline at end of file diff --git a/idea/testData/intentions/convertLambdaToReference/member.kt.after b/idea/testData/intentions/convertLambdaToReference/member.kt.after new file mode 100644 index 00000000000..d72043a4d65 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/member.kt.after @@ -0,0 +1,6 @@ +// IS_APPLICABLE: true + +class Owner(val z: Int) { + fun foo(y: Int) = y + z + val x = this::foo +} \ No newline at end of file diff --git a/idea/testData/intentions/convertLambdaToReference/memberExplicit.kt b/idea/testData/intentions/convertLambdaToReference/memberExplicit.kt new file mode 100644 index 00000000000..c798fc92e61 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/memberExplicit.kt @@ -0,0 +1,9 @@ +// IS_APPLICABLE: true + +class Owner(val z: Int) { + fun foo(y: Int) = y + z +} + +val owner = Owner(42) + +val x = { arg: Int -> owner.foo(arg) } diff --git a/idea/testData/intentions/convertLambdaToReference/memberExplicit.kt.after b/idea/testData/intentions/convertLambdaToReference/memberExplicit.kt.after new file mode 100644 index 00000000000..71c6a314bb8 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/memberExplicit.kt.after @@ -0,0 +1,9 @@ +// IS_APPLICABLE: true + +class Owner(val z: Int) { + fun foo(y: Int) = y + z +} + +val owner = Owner(42) + +val x = owner::foo diff --git a/idea/testData/intentions/convertLambdaToReference/memberExternalParameter.kt b/idea/testData/intentions/convertLambdaToReference/memberExternalParameter.kt new file mode 100644 index 00000000000..00994aeead4 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/memberExternalParameter.kt @@ -0,0 +1,9 @@ +// IS_APPLICABLE: true + +class Owner(val z: Int) { + fun foo(y: Int) = y + z +} + +fun bar(owner: Owner) { + val x = { arg: Int -> owner.foo(arg) } +} diff --git a/idea/testData/intentions/convertLambdaToReference/memberExternalParameter.kt.after b/idea/testData/intentions/convertLambdaToReference/memberExternalParameter.kt.after new file mode 100644 index 00000000000..2ef4f092972 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/memberExternalParameter.kt.after @@ -0,0 +1,9 @@ +// IS_APPLICABLE: true + +class Owner(val z: Int) { + fun foo(y: Int) = y + z +} + +fun bar(owner: Owner) { + val x = owner::foo +} diff --git a/idea/testData/intentions/convertLambdaToReference/memberInLambdaArgument.kt b/idea/testData/intentions/convertLambdaToReference/memberInLambdaArgument.kt new file mode 100644 index 00000000000..2fbd1e900c4 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/memberInLambdaArgument.kt @@ -0,0 +1,7 @@ +// IS_APPLICABLE: true +// WITH_RUNTIME + +class Owner(val z: Int) { + fun foo(y: Int) = y + z + val x = 7.let { foo(it) } +} \ No newline at end of file diff --git a/idea/testData/intentions/convertLambdaToReference/memberInLambdaArgument.kt.after b/idea/testData/intentions/convertLambdaToReference/memberInLambdaArgument.kt.after new file mode 100644 index 00000000000..33b4b3b2d8e --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/memberInLambdaArgument.kt.after @@ -0,0 +1,7 @@ +// IS_APPLICABLE: true +// WITH_RUNTIME + +class Owner(val z: Int) { + fun foo(y: Int) = y + z + val x = 7.let(this::foo) +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index d7b472ccfc6..4072a802439 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -4436,12 +4436,30 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("memberExplicit.kt") + public void testMemberExplicit() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/memberExplicit.kt"); + doTest(fileName); + } + @TestMetadata("memberExtension.kt") public void testMemberExtension() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/memberExtension.kt"); doTest(fileName); } + @TestMetadata("memberExternalParameter.kt") + public void testMemberExternalParameter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/memberExternalParameter.kt"); + doTest(fileName); + } + + @TestMetadata("memberInLambdaArgument.kt") + public void testMemberInLambdaArgument() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/memberInLambdaArgument.kt"); + doTest(fileName); + } + @TestMetadata("name.kt") public void testName() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/name.kt");