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:
Toshiaki Kameyama
2020-06-26 20:21:17 +09:00
committed by GitHub
parent a87b25d10e
commit e822e871f5
16 changed files with 133 additions and 9 deletions

View File

@@ -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

View File

@@ -1,4 +1,5 @@
// IS_APPLICABLE: false
// COMPILER_ARGUMENTS: -XXLanguage:-FunctionReferenceWithDefaultValueAsOtherType
fun foo(z: Int, y: Int = 0) = y + z

View File

@@ -0,0 +1,5 @@
// IS_APPLICABLE: false
fun foo(z: Int, y: Int = 0) = y + z
val x = { arg: Int <caret>-> foo(arg) }

View File

@@ -0,0 +1,7 @@
fun foo(f: () -> Unit) {}
fun bar(a: Int = 42) {}
fun test() {
foo <caret>{ bar() }
}

View File

@@ -0,0 +1,7 @@
fun foo(f: () -> Unit) {}
fun bar(a: Int = 42) {}
fun test() {
foo(::bar)
}

View File

@@ -0,0 +1,7 @@
fun foo(f: (x: Int) -> Unit) {}
fun bar(x: Int, y: Int = 42) {}
fun test() {
foo <caret>{ bar(it) }
}

View File

@@ -0,0 +1,7 @@
fun foo(f: (x: Int) -> Unit) {}
fun bar(x: Int, y: Int = 42) {}
fun test() {
foo(::bar)
}

View File

@@ -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) }
}

View File

@@ -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)
}

View File

@@ -1,4 +1,5 @@
// IS_APPLICABLE: false
// COMPILER_ARGUMENTS: -XXLanguage:-SuspendConversion
fun coroutine(block: suspend () -> Unit) {}

View File

@@ -0,0 +1,8 @@
// COMPILER_ARGUMENTS: -XXLanguage:+SuspendConversion
fun foo(a: suspend () -> Unit) {}
fun action() {}
fun usage() {
foo { action() <caret> }
}

View File

@@ -0,0 +1,8 @@
// COMPILER_ARGUMENTS: -XXLanguage:+SuspendConversion
fun foo(a: suspend () -> Unit) {}
fun action() {}
fun usage() {
foo(::action)
}

View File

@@ -1,4 +1,5 @@
// IS_APPLICABLE: false
// COMPILER_ARGUMENTS: -XXLanguage:-FunctionReferenceWithDefaultValueAsOtherType
fun Int.exec(f: (Int) -> Unit) = f(this)

View File

@@ -0,0 +1,7 @@
fun Int.exec(f: (Int) -> Unit) = f(this)
fun bar(x: Int) = x
fun foo() {
2.exec {<caret> bar(it) }
}

View File

@@ -0,0 +1,7 @@
fun Int.exec(f: (Int) -> Unit) = f(this)
fun bar(x: Int) = x
fun foo() {
2.exec(::bar)
}

View File

@@ -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");