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
|
dispatchReceiverParameter != null || extensionReceiverParameter != null
|
||||||
}
|
}
|
||||||
val callHasReceiver = explicitReceiver != null
|
val callHasReceiver = explicitReceiver != null
|
||||||
if (descriptorHasReceiver != callHasReceiver) return false
|
if (!descriptorHasReceiver && callHasReceiver) 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
|
if (calleeDescriptor.valueParameters.size != callableArgumentsCount) return false
|
||||||
if (lambdaMustReturnUnit) {
|
if (lambdaMustReturnUnit) {
|
||||||
@@ -105,17 +105,16 @@ class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntentio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val hasSpecification = lambdaExpression.functionLiteral.hasParameterSpecification()
|
val explicitReceiverTarget = (explicitReceiver as? KtNameReferenceExpression)?.let { context[REFERENCE_TARGET, it] }
|
||||||
val receiverShift = if (callHasReceiver) 1 else 0
|
|
||||||
val parametersCount = if (hasSpecification) lambdaExpression.valueParameters.size else 1
|
|
||||||
if (parametersCount != callableArgumentsCount + receiverShift) return false
|
|
||||||
val lambdaValueParameters = context[FUNCTION, lambdaExpression.functionLiteral]?.valueParameters ?: return false
|
val lambdaValueParameters = context[FUNCTION, lambdaExpression.functionLiteral]?.valueParameters ?: return false
|
||||||
if (explicitReceiver != null) {
|
val lambdaParameterAsExplicitReceiver = explicitReceiverTarget is ParameterDescriptor &&
|
||||||
if (explicitReceiver !is KtNameReferenceExpression) return false
|
explicitReceiverTarget == lambdaValueParameters.firstOrNull()
|
||||||
if (lambdaValueParameters.isEmpty()) return false
|
val explicitReceiverShift = if (lambdaParameterAsExplicitReceiver) 1 else 0
|
||||||
val explicitReceiverTarget = context[REFERENCE_TARGET, explicitReceiver] as? ParameterDescriptor ?: return false
|
val parametersCount =
|
||||||
if (explicitReceiverTarget != lambdaValueParameters[0]) return false
|
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
|
val receiverType = explicitReceiverTarget.type
|
||||||
// No exotic receiver types
|
// No exotic receiver types
|
||||||
if (receiverType.isTypeParameter() || receiverType.isError || receiverType.isDynamic() ||
|
if (receiverType.isTypeParameter() || receiverType.isError || receiverType.isDynamic() ||
|
||||||
@@ -130,16 +129,17 @@ class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntentio
|
|||||||
)) return false
|
)) return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same lambda / references function parameter order
|
// Same lambda / references function parameter order
|
||||||
if (callableExpression is KtCallExpression) {
|
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
|
val resolvedCall = callableExpression.getResolvedCall(context) ?: return false
|
||||||
resolvedCall.valueArguments.entries.forEach { (valueParameter, resolvedArgument) ->
|
resolvedCall.valueArguments.entries.forEach { (valueParameter, resolvedArgument) ->
|
||||||
val argumentExpression =
|
val argumentExpression =
|
||||||
resolvedArgument.arguments.singleOrNull()?.getArgumentExpression() as? KtNameReferenceExpression
|
resolvedArgument.arguments.singleOrNull()?.getArgumentExpression() as? KtNameReferenceExpression
|
||||||
?: return false
|
?: return false
|
||||||
val argumentTarget = context[REFERENCE_TARGET, argumentExpression] as? ValueParameterDescriptor ?: 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
|
return true
|
||||||
@@ -160,8 +160,7 @@ class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntentio
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun applyTo(element: KtLambdaExpression, editor: Editor?) {
|
override fun applyTo(element: KtLambdaExpression, editor: Editor?) {
|
||||||
val body = element.bodyExpression ?: return
|
val referenceName = buildReferenceText(lambdaExpression = element, shortTypes = false) ?: return
|
||||||
val referenceName = buildReferenceText(body.statements.singleOrNull() ?: return, shortTypes = false) ?: return
|
|
||||||
val factory = KtPsiFactory(element)
|
val factory = KtPsiFactory(element)
|
||||||
val lambdaArgument = element.parent as? KtLambdaArgument
|
val lambdaArgument = element.parent as? KtLambdaArgument
|
||||||
if (lambdaArgument == null) {
|
if (lambdaArgument == null) {
|
||||||
@@ -213,31 +212,47 @@ class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntentio
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
internal fun shouldSuggestToConvert(element: KtLambdaExpression): Boolean {
|
internal fun shouldSuggestToConvert(element: KtLambdaExpression): Boolean {
|
||||||
val body = element.bodyExpression ?: return false
|
val referenceName = buildReferenceText(lambdaExpression = element, shortTypes = true) ?: return false
|
||||||
val referenceName = buildReferenceText(body.statements.singleOrNull() ?: return false, shortTypes = true) ?: return false
|
|
||||||
return referenceName.length < element.text.length
|
return referenceName.length < element.text.length
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildReferenceText(expression: KtExpression, shortTypes: Boolean): String? {
|
private fun buildReferenceText(lambdaExpression: KtLambdaExpression, shortTypes: Boolean): String? {
|
||||||
return when (expression) {
|
val body = lambdaExpression.bodyExpression ?: return null
|
||||||
is KtCallExpression -> "::${expression.getCallReferencedName()}"
|
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 -> {
|
is KtDotQualifiedExpression -> {
|
||||||
val selector = expression.selectorExpression
|
val selector = callableExpression.selectorExpression
|
||||||
val selectorReferenceName = when (selector) {
|
val selectorReferenceName = when (selector) {
|
||||||
is KtCallExpression -> selector.getCallReferencedName() ?: return null
|
is KtCallExpression -> selector.getCallReferencedName() ?: return null
|
||||||
is KtNameReferenceExpression -> selector.getReferencedName()
|
is KtNameReferenceExpression -> selector.getReferencedName()
|
||||||
else -> return null
|
else -> return null
|
||||||
}
|
}
|
||||||
val receiver = expression.receiverExpression as? KtNameReferenceExpression ?: return null
|
val receiver = callableExpression.receiverExpression as? KtNameReferenceExpression ?: return null
|
||||||
val context = receiver.analyze()
|
val context = receiver.analyze()
|
||||||
val receiverDescriptor = context[REFERENCE_TARGET, receiver] as? ParameterDescriptor ?: return null
|
val receiverDescriptor = context[REFERENCE_TARGET, receiver] ?: return null
|
||||||
val originalReceiverType = receiverDescriptor.type
|
val lambdaValueParameters = context[FUNCTION, lambdaExpression.functionLiteral]?.valueParameters ?: return null
|
||||||
val receiverType = originalReceiverType.approximateFlexibleTypes(preferNotNull = true)
|
if (receiverDescriptor is ParameterDescriptor && receiverDescriptor == lambdaValueParameters.firstOrNull()) {
|
||||||
if (shortTypes) {
|
val originalReceiverType = receiverDescriptor.type
|
||||||
"${IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(receiverType)}::$selectorReferenceName"
|
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 {
|
else {
|
||||||
"${IdeDescriptorRenderers.SOURCE_CODE.renderType(receiverType)}::$selectorReferenceName"
|
"${receiverDescriptor.name}::$selectorReferenceName"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> null
|
else -> null
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// IS_APPLICABLE: false
|
// IS_APPLICABLE: true
|
||||||
|
|
||||||
class Owner(val z: Int) {
|
class Owner(val z: Int) {
|
||||||
// Possible only in 1.1 with bound references (this::foo)
|
|
||||||
val x = { arg: Int <caret> -> foo(arg) }
|
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) {
|
class Owner(val z: Int) {
|
||||||
fun foo(y: Int) = y + z
|
fun foo(y: Int) = y + z
|
||||||
// Possible only in 1.1 with bound references (this::foo)
|
|
||||||
val x = { arg: Int <caret> -> foo(arg) }
|
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);
|
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")
|
@TestMetadata("memberExtension.kt")
|
||||||
public void testMemberExtension() throws Exception {
|
public void testMemberExtension() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/memberExtension.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/memberExtension.kt");
|
||||||
doTest(fileName);
|
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")
|
@TestMetadata("name.kt")
|
||||||
public void testName() throws Exception {
|
public void testName() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/name.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertLambdaToReference/name.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user