diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertLambdaToReferenceIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertLambdaToReferenceIntention.kt index 53e5f3fea27..ee5ecf1a7ab 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertLambdaToReferenceIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertLambdaToReferenceIntention.kt @@ -32,6 +32,7 @@ import org.jetbrains.kotlin.resolve.calls.callUtil.getParameterForArgument import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.components.hasDefaultValue import org.jetbrains.kotlin.resolve.calls.components.isVararg +import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument import org.jetbrains.kotlin.resolve.descriptorUtil.isCompanionObject @@ -63,6 +64,7 @@ open class ConvertLambdaToReferenceIntention(textGetter: () -> String) : SelfTar explicitReceiver: KtExpression? = null, lambdaExpression: KtLambdaExpression ): Boolean { + val languageVersionSettings = callableExpression.languageVersionSettings val context = callableExpression.analyze() val calleeReferenceExpression = when (callableExpression) { is KtCallExpression -> callableExpression.calleeExpression as? KtNameReferenceExpression ?: return false @@ -80,7 +82,10 @@ open class ConvertLambdaToReferenceIntention(textGetter: () -> String) : SelfTar val lambdaParameterIsSuspend = lambdaParameterType?.isSuspendFunctionType == true val calleeFunctionIsSuspend = (calleeDescriptor as? FunctionDescriptor)?.isSuspend == true - if (lambdaParameterIsSuspend && !calleeFunctionIsSuspend || !lambdaParameterIsSuspend && calleeFunctionIsSuspend) return false + if (!lambdaParameterIsSuspend && calleeFunctionIsSuspend) return false + if (lambdaParameterIsSuspend && !calleeFunctionIsSuspend && + !languageVersionSettings.supportsFeature(LanguageFeature.SuspendConversion) + ) return false // No references with type parameters if (calleeDescriptor.typeParameters.isNotEmpty()) return false @@ -94,17 +99,25 @@ open class ConvertLambdaToReferenceIntention(textGetter: () -> String) : SelfTar } if (!descriptorHasReceiver && explicitReceiver != null && calleeDescriptor !is ClassConstructorDescriptor) return false - val noBoundReferences = !callableExpression.languageVersionSettings.supportsFeature(LanguageFeature.BoundCallableReferences) + val noBoundReferences = !languageVersionSettings.supportsFeature(LanguageFeature.BoundCallableReferences) if (noBoundReferences && descriptorHasReceiver && explicitReceiver == null) return false val callableArgumentsCount = (callableExpression as? KtCallExpression)?.valueArguments?.size ?: 0 - if (calleeDescriptor.valueParameters.size != callableArgumentsCount) return false - val lambdaMustReturnUnit = - if (lambdaParameterType?.isFunctionType == true) lambdaParameterType.getReturnTypeFromFunctionType().isUnit() else false - if (lambdaMustReturnUnit) { - calleeDescriptor.returnType.let { - // If Unit required, no references to non-Unit callables - if (it == null || !it.isUnit()) return false + val enableFunctionReferenceWithDefaultValueAsOtherType = + languageVersionSettings.supportsFeature(LanguageFeature.FunctionReferenceWithDefaultValueAsOtherType) + if (enableFunctionReferenceWithDefaultValueAsOtherType) { + if (calleeDescriptor.valueParameters.size != callableArgumentsCount && + (lambdaExpression.parentValueArgument() == null || calleeDescriptor.valueParameters.none { it.declaresDefaultValue() }) + ) return false + } else { + if (calleeDescriptor.valueParameters.size != callableArgumentsCount) return false + val lambdaMustReturnUnit = + if (lambdaParameterType?.isFunctionType == true) lambdaParameterType.getReturnTypeFromFunctionType().isUnit() else false + if (lambdaMustReturnUnit) { + calleeDescriptor.returnType.let { + // If Unit required, no references to non-Unit callables + if (it == null || !it.isUnit()) return false + } } } @@ -137,6 +150,7 @@ open class ConvertLambdaToReferenceIntention(textGetter: () -> String) : SelfTar if (lambdaValueParameterDescriptors.size < explicitReceiverShift + callableExpression.valueArguments.size) return false val resolvedCall = callableExpression.getResolvedCall(context) ?: return false resolvedCall.valueArguments.entries.forEach { (valueParameter, resolvedArgument) -> + if (resolvedArgument is DefaultValueArgument && enableFunctionReferenceWithDefaultValueAsOtherType) return@forEach val argument = resolvedArgument.arguments.singleOrNull() ?: return false if (resolvedArgument is VarargValueArgument && argument.getSpreadElement() == null) return false val argumentExpression = argument.getArgumentExpression() as? KtNameReferenceExpression ?: return false diff --git a/idea/testData/intentions/convertLambdaToReference/defaultArgument.kt b/idea/testData/intentions/convertLambdaToReference/defaultArgument.kt index 30171e0d752..4ec4521a7ef 100644 --- a/idea/testData/intentions/convertLambdaToReference/defaultArgument.kt +++ b/idea/testData/intentions/convertLambdaToReference/defaultArgument.kt @@ -1,4 +1,5 @@ // IS_APPLICABLE: false +// COMPILER_ARGUMENTS: -XXLanguage:-FunctionReferenceWithDefaultValueAsOtherType fun foo(z: Int, y: Int = 0) = y + z diff --git a/idea/testData/intentions/convertLambdaToReference/defaultArgument2.kt b/idea/testData/intentions/convertLambdaToReference/defaultArgument2.kt new file mode 100644 index 00000000000..30171e0d752 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/defaultArgument2.kt @@ -0,0 +1,5 @@ +// IS_APPLICABLE: false + +fun foo(z: Int, y: Int = 0) = y + z + +val x = { arg: Int -> foo(arg) } \ No newline at end of file diff --git a/idea/testData/intentions/convertLambdaToReference/defaultArgument3.kt b/idea/testData/intentions/convertLambdaToReference/defaultArgument3.kt new file mode 100644 index 00000000000..118c6c437f8 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/defaultArgument3.kt @@ -0,0 +1,7 @@ +fun foo(f: () -> Unit) {} + +fun bar(a: Int = 42) {} + +fun test() { + foo { bar() } +} diff --git a/idea/testData/intentions/convertLambdaToReference/defaultArgument3.kt.after b/idea/testData/intentions/convertLambdaToReference/defaultArgument3.kt.after new file mode 100644 index 00000000000..8f8e0712d6d --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/defaultArgument3.kt.after @@ -0,0 +1,7 @@ +fun foo(f: () -> Unit) {} + +fun bar(a: Int = 42) {} + +fun test() { + foo(::bar) +} diff --git a/idea/testData/intentions/convertLambdaToReference/defaultArgument4.kt b/idea/testData/intentions/convertLambdaToReference/defaultArgument4.kt new file mode 100644 index 00000000000..378a8c62f1f --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/defaultArgument4.kt @@ -0,0 +1,7 @@ +fun foo(f: (x: Int) -> Unit) {} + +fun bar(x: Int, y: Int = 42) {} + +fun test() { + foo { bar(it) } +} diff --git a/idea/testData/intentions/convertLambdaToReference/defaultArgument4.kt.after b/idea/testData/intentions/convertLambdaToReference/defaultArgument4.kt.after new file mode 100644 index 00000000000..f1822661618 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/defaultArgument4.kt.after @@ -0,0 +1,7 @@ +fun foo(f: (x: Int) -> Unit) {} + +fun bar(x: Int, y: Int = 42) {} + +fun test() { + foo(::bar) +} diff --git a/idea/testData/intentions/convertLambdaToReference/defaultArgument5.kt b/idea/testData/intentions/convertLambdaToReference/defaultArgument5.kt new file mode 100644 index 00000000000..7121dcda3f5 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/defaultArgument5.kt @@ -0,0 +1,7 @@ +fun Int.foo(x: Int, y: Int = 42) = x + y + +fun bar(f: (Int, Int) -> Int) {} + +fun test() { + bar { i, x -> i.foo(x) } +} diff --git a/idea/testData/intentions/convertLambdaToReference/defaultArgument5.kt.after b/idea/testData/intentions/convertLambdaToReference/defaultArgument5.kt.after new file mode 100644 index 00000000000..78021231c55 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/defaultArgument5.kt.after @@ -0,0 +1,7 @@ +fun Int.foo(x: Int, y: Int = 42) = x + y + +fun bar(f: (Int, Int) -> Int) {} + +fun test() { + bar(Int::foo) +} diff --git a/idea/testData/intentions/convertLambdaToReference/suspendFunctionParameter.kt b/idea/testData/intentions/convertLambdaToReference/suspendFunctionParameter.kt index 2595bbaa853..dbf927b8e85 100644 --- a/idea/testData/intentions/convertLambdaToReference/suspendFunctionParameter.kt +++ b/idea/testData/intentions/convertLambdaToReference/suspendFunctionParameter.kt @@ -1,4 +1,5 @@ // IS_APPLICABLE: false +// COMPILER_ARGUMENTS: -XXLanguage:-SuspendConversion fun coroutine(block: suspend () -> Unit) {} diff --git a/idea/testData/intentions/convertLambdaToReference/suspendFunctionParameter3.kt b/idea/testData/intentions/convertLambdaToReference/suspendFunctionParameter3.kt new file mode 100644 index 00000000000..7f1c294d208 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/suspendFunctionParameter3.kt @@ -0,0 +1,8 @@ +// COMPILER_ARGUMENTS: -XXLanguage:+SuspendConversion +fun foo(a: suspend () -> Unit) {} + +fun action() {} + +fun usage() { + foo { action() } +} \ No newline at end of file diff --git a/idea/testData/intentions/convertLambdaToReference/suspendFunctionParameter3.kt.after b/idea/testData/intentions/convertLambdaToReference/suspendFunctionParameter3.kt.after new file mode 100644 index 00000000000..1293d97e3c3 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/suspendFunctionParameter3.kt.after @@ -0,0 +1,8 @@ +// COMPILER_ARGUMENTS: -XXLanguage:+SuspendConversion +fun foo(a: suspend () -> Unit) {} + +fun action() {} + +fun usage() { + foo(::action) +} \ No newline at end of file diff --git a/idea/testData/intentions/convertLambdaToReference/unit.kt b/idea/testData/intentions/convertLambdaToReference/unit.kt index 2a2b3e6daec..658731be871 100644 --- a/idea/testData/intentions/convertLambdaToReference/unit.kt +++ b/idea/testData/intentions/convertLambdaToReference/unit.kt @@ -1,4 +1,5 @@ // IS_APPLICABLE: false +// COMPILER_ARGUMENTS: -XXLanguage:-FunctionReferenceWithDefaultValueAsOtherType fun Int.exec(f: (Int) -> Unit) = f(this) diff --git a/idea/testData/intentions/convertLambdaToReference/unit2.kt b/idea/testData/intentions/convertLambdaToReference/unit2.kt new file mode 100644 index 00000000000..7d7163ecb83 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/unit2.kt @@ -0,0 +1,7 @@ +fun Int.exec(f: (Int) -> Unit) = f(this) + +fun bar(x: Int) = x + +fun foo() { + 2.exec { bar(it) } +} diff --git a/idea/testData/intentions/convertLambdaToReference/unit2.kt.after b/idea/testData/intentions/convertLambdaToReference/unit2.kt.after new file mode 100644 index 00000000000..b0d340cca85 --- /dev/null +++ b/idea/testData/intentions/convertLambdaToReference/unit2.kt.after @@ -0,0 +1,7 @@ +fun Int.exec(f: (Int) -> Unit) = f(this) + +fun bar(x: Int) = x + +fun foo() { + 2.exec(::bar) +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 7c8686cd87e..535c5727b0a 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -4990,6 +4990,26 @@ public class IntentionTestGenerated extends AbstractIntentionTest { runTest("idea/testData/intentions/convertLambdaToReference/defaultArgument.kt"); } + @TestMetadata("defaultArgument2.kt") + public void testDefaultArgument2() throws Exception { + runTest("idea/testData/intentions/convertLambdaToReference/defaultArgument2.kt"); + } + + @TestMetadata("defaultArgument3.kt") + public void testDefaultArgument3() throws Exception { + runTest("idea/testData/intentions/convertLambdaToReference/defaultArgument3.kt"); + } + + @TestMetadata("defaultArgument4.kt") + public void testDefaultArgument4() throws Exception { + runTest("idea/testData/intentions/convertLambdaToReference/defaultArgument4.kt"); + } + + @TestMetadata("defaultArgument5.kt") + public void testDefaultArgument5() throws Exception { + runTest("idea/testData/intentions/convertLambdaToReference/defaultArgument5.kt"); + } + @TestMetadata("defaultBeforeLambda.kt") public void testDefaultBeforeLambda() throws Exception { runTest("idea/testData/intentions/convertLambdaToReference/defaultBeforeLambda.kt"); @@ -5225,6 +5245,11 @@ public class IntentionTestGenerated extends AbstractIntentionTest { runTest("idea/testData/intentions/convertLambdaToReference/suspendFunctionParameter2.kt"); } + @TestMetadata("suspendFunctionParameter3.kt") + public void testSuspendFunctionParameter3() throws Exception { + runTest("idea/testData/intentions/convertLambdaToReference/suspendFunctionParameter3.kt"); + } + @TestMetadata("syntheticProperty.kt") public void testSyntheticProperty() throws Exception { runTest("idea/testData/intentions/convertLambdaToReference/syntheticProperty.kt"); @@ -5295,6 +5320,11 @@ public class IntentionTestGenerated extends AbstractIntentionTest { runTest("idea/testData/intentions/convertLambdaToReference/unit.kt"); } + @TestMetadata("unit2.kt") + public void testUnit2() throws Exception { + runTest("idea/testData/intentions/convertLambdaToReference/unit2.kt"); + } + @TestMetadata("unwrap.kt") public void testUnwrap() throws Exception { runTest("idea/testData/intentions/convertLambdaToReference/unwrap.kt");