Lambda --> reference supports bound references now #KT-13111 Fixed
This commit is contained in:
+42
-27
@@ -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
|
||||
|
||||
@@ -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 <caret> -> foo(arg) }
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// IS_APPLICABLE: true
|
||||
|
||||
class Owner(val z: Int) {
|
||||
val x = this::foo
|
||||
}
|
||||
|
||||
fun Owner.foo(y: Int) = y + z
|
||||
|
||||
@@ -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 <caret> -> foo(arg) }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// IS_APPLICABLE: true
|
||||
|
||||
class Owner(val z: Int) {
|
||||
fun foo(y: Int) = y + z
|
||||
val x = this::foo
|
||||
}
|
||||
@@ -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 <caret> -> owner.foo(arg) }
|
||||
@@ -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
|
||||
@@ -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 <caret> -> owner.foo(arg) }
|
||||
}
|
||||
+9
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// IS_APPLICABLE: true
|
||||
// WITH_RUNTIME
|
||||
|
||||
class Owner(val z: Int) {
|
||||
fun foo(y: Int) = y + z
|
||||
val x = 7.let {<caret> foo(it) }
|
||||
}
|
||||
+7
@@ -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)
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user