KT-39532 Support intention to convert reference to lambda and vice versa for adapted references (#3495)
* Convert lambda to reference: support a function which has default parameters/unit return type/suspendability #KT-39532 Fixed
This commit is contained in:
committed by
GitHub
parent
a87b25d10e
commit
e822e871f5
@@ -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.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.components.hasDefaultValue
|
import org.jetbrains.kotlin.resolve.calls.components.hasDefaultValue
|
||||||
import org.jetbrains.kotlin.resolve.calls.components.isVararg
|
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.ResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument
|
import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isCompanionObject
|
import org.jetbrains.kotlin.resolve.descriptorUtil.isCompanionObject
|
||||||
@@ -63,6 +64,7 @@ open class ConvertLambdaToReferenceIntention(textGetter: () -> String) : SelfTar
|
|||||||
explicitReceiver: KtExpression? = null,
|
explicitReceiver: KtExpression? = null,
|
||||||
lambdaExpression: KtLambdaExpression
|
lambdaExpression: KtLambdaExpression
|
||||||
): Boolean {
|
): Boolean {
|
||||||
|
val languageVersionSettings = callableExpression.languageVersionSettings
|
||||||
val context = callableExpression.analyze()
|
val context = callableExpression.analyze()
|
||||||
val calleeReferenceExpression = when (callableExpression) {
|
val calleeReferenceExpression = when (callableExpression) {
|
||||||
is KtCallExpression -> callableExpression.calleeExpression as? KtNameReferenceExpression ?: return false
|
is KtCallExpression -> callableExpression.calleeExpression as? KtNameReferenceExpression ?: return false
|
||||||
@@ -80,7 +82,10 @@ open class ConvertLambdaToReferenceIntention(textGetter: () -> String) : SelfTar
|
|||||||
|
|
||||||
val lambdaParameterIsSuspend = lambdaParameterType?.isSuspendFunctionType == true
|
val lambdaParameterIsSuspend = lambdaParameterType?.isSuspendFunctionType == true
|
||||||
val calleeFunctionIsSuspend = (calleeDescriptor as? FunctionDescriptor)?.isSuspend == 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
|
// No references with type parameters
|
||||||
if (calleeDescriptor.typeParameters.isNotEmpty()) return false
|
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
|
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
|
if (noBoundReferences && descriptorHasReceiver && explicitReceiver == null) return false
|
||||||
|
|
||||||
val callableArgumentsCount = (callableExpression as? KtCallExpression)?.valueArguments?.size ?: 0
|
val callableArgumentsCount = (callableExpression as? KtCallExpression)?.valueArguments?.size ?: 0
|
||||||
if (calleeDescriptor.valueParameters.size != callableArgumentsCount) return false
|
val enableFunctionReferenceWithDefaultValueAsOtherType =
|
||||||
val lambdaMustReturnUnit =
|
languageVersionSettings.supportsFeature(LanguageFeature.FunctionReferenceWithDefaultValueAsOtherType)
|
||||||
if (lambdaParameterType?.isFunctionType == true) lambdaParameterType.getReturnTypeFromFunctionType().isUnit() else false
|
if (enableFunctionReferenceWithDefaultValueAsOtherType) {
|
||||||
if (lambdaMustReturnUnit) {
|
if (calleeDescriptor.valueParameters.size != callableArgumentsCount &&
|
||||||
calleeDescriptor.returnType.let {
|
(lambdaExpression.parentValueArgument() == null || calleeDescriptor.valueParameters.none { it.declaresDefaultValue() })
|
||||||
// If Unit required, no references to non-Unit callables
|
) return false
|
||||||
if (it == null || !it.isUnit()) 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
|
if (lambdaValueParameterDescriptors.size < explicitReceiverShift + callableExpression.valueArguments.size) return false
|
||||||
val resolvedCall = callableExpression.getResolvedCall(context) ?: return false
|
val resolvedCall = callableExpression.getResolvedCall(context) ?: return false
|
||||||
resolvedCall.valueArguments.entries.forEach { (valueParameter, resolvedArgument) ->
|
resolvedCall.valueArguments.entries.forEach { (valueParameter, resolvedArgument) ->
|
||||||
|
if (resolvedArgument is DefaultValueArgument && enableFunctionReferenceWithDefaultValueAsOtherType) return@forEach
|
||||||
val argument = resolvedArgument.arguments.singleOrNull() ?: return false
|
val argument = resolvedArgument.arguments.singleOrNull() ?: return false
|
||||||
if (resolvedArgument is VarargValueArgument && argument.getSpreadElement() == null) return false
|
if (resolvedArgument is VarargValueArgument && argument.getSpreadElement() == null) return false
|
||||||
val argumentExpression = argument.getArgumentExpression() as? KtNameReferenceExpression ?: return false
|
val argumentExpression = argument.getArgumentExpression() as? KtNameReferenceExpression ?: return false
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// IS_APPLICABLE: false
|
// IS_APPLICABLE: false
|
||||||
|
// COMPILER_ARGUMENTS: -XXLanguage:-FunctionReferenceWithDefaultValueAsOtherType
|
||||||
|
|
||||||
fun foo(z: Int, y: Int = 0) = y + z
|
fun foo(z: Int, y: Int = 0) = y + z
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// IS_APPLICABLE: false
|
||||||
|
|
||||||
|
fun foo(z: Int, y: Int = 0) = y + z
|
||||||
|
|
||||||
|
val x = { arg: Int <caret>-> foo(arg) }
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fun foo(f: () -> Unit) {}
|
||||||
|
|
||||||
|
fun bar(a: Int = 42) {}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
foo <caret>{ bar() }
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fun foo(f: () -> Unit) {}
|
||||||
|
|
||||||
|
fun bar(a: Int = 42) {}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
foo(::bar)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fun foo(f: (x: Int) -> Unit) {}
|
||||||
|
|
||||||
|
fun bar(x: Int, y: Int = 42) {}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
foo <caret>{ bar(it) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fun foo(f: (x: Int) -> Unit) {}
|
||||||
|
|
||||||
|
fun bar(x: Int, y: Int = 42) {}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
foo(::bar)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fun Int.foo(x: Int, y: Int = 42) = x + y
|
||||||
|
|
||||||
|
fun bar(f: (Int, Int) -> Int) {}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
bar <caret>{ i, x -> i.foo(x) }
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
// IS_APPLICABLE: false
|
// IS_APPLICABLE: false
|
||||||
|
// COMPILER_ARGUMENTS: -XXLanguage:-SuspendConversion
|
||||||
|
|
||||||
fun coroutine(block: suspend () -> Unit) {}
|
fun coroutine(block: suspend () -> Unit) {}
|
||||||
|
|
||||||
|
|||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
// COMPILER_ARGUMENTS: -XXLanguage:+SuspendConversion
|
||||||
|
fun foo(a: suspend () -> Unit) {}
|
||||||
|
|
||||||
|
fun action() {}
|
||||||
|
|
||||||
|
fun usage() {
|
||||||
|
foo { action() <caret> }
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
// COMPILER_ARGUMENTS: -XXLanguage:+SuspendConversion
|
||||||
|
fun foo(a: suspend () -> Unit) {}
|
||||||
|
|
||||||
|
fun action() {}
|
||||||
|
|
||||||
|
fun usage() {
|
||||||
|
foo(::action)
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
// IS_APPLICABLE: false
|
// IS_APPLICABLE: false
|
||||||
|
// COMPILER_ARGUMENTS: -XXLanguage:-FunctionReferenceWithDefaultValueAsOtherType
|
||||||
|
|
||||||
fun Int.exec(f: (Int) -> Unit) = f(this)
|
fun Int.exec(f: (Int) -> Unit) = f(this)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fun Int.exec(f: (Int) -> Unit) = f(this)
|
||||||
|
|
||||||
|
fun bar(x: Int) = x
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
2.exec {<caret> bar(it) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fun Int.exec(f: (Int) -> Unit) = f(this)
|
||||||
|
|
||||||
|
fun bar(x: Int) = x
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
2.exec(::bar)
|
||||||
|
}
|
||||||
@@ -4990,6 +4990,26 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
|||||||
runTest("idea/testData/intentions/convertLambdaToReference/defaultArgument.kt");
|
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")
|
@TestMetadata("defaultBeforeLambda.kt")
|
||||||
public void testDefaultBeforeLambda() throws Exception {
|
public void testDefaultBeforeLambda() throws Exception {
|
||||||
runTest("idea/testData/intentions/convertLambdaToReference/defaultBeforeLambda.kt");
|
runTest("idea/testData/intentions/convertLambdaToReference/defaultBeforeLambda.kt");
|
||||||
@@ -5225,6 +5245,11 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
|||||||
runTest("idea/testData/intentions/convertLambdaToReference/suspendFunctionParameter2.kt");
|
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")
|
@TestMetadata("syntheticProperty.kt")
|
||||||
public void testSyntheticProperty() throws Exception {
|
public void testSyntheticProperty() throws Exception {
|
||||||
runTest("idea/testData/intentions/convertLambdaToReference/syntheticProperty.kt");
|
runTest("idea/testData/intentions/convertLambdaToReference/syntheticProperty.kt");
|
||||||
@@ -5295,6 +5320,11 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
|||||||
runTest("idea/testData/intentions/convertLambdaToReference/unit.kt");
|
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")
|
@TestMetadata("unwrap.kt")
|
||||||
public void testUnwrap() throws Exception {
|
public void testUnwrap() throws Exception {
|
||||||
runTest("idea/testData/intentions/convertLambdaToReference/unwrap.kt");
|
runTest("idea/testData/intentions/convertLambdaToReference/unwrap.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user