Refactoring: convert lambda --> reference intention

This commit is contained in:
Mikhail Glukhikh
2017-03-10 16:19:42 +03:00
parent 3a14a5c461
commit b6974a88c5
@@ -55,28 +55,13 @@ class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntentio
return context[REFERENCE_TARGET, outerCallee] as? FunctionDescriptor return context[REFERENCE_TARGET, outerCallee] as? FunctionDescriptor
} }
override fun isApplicableTo(element: KtLambdaExpression): Boolean { private fun isConvertibleCallInLambda(
val body = element.bodyExpression ?: return false
val statement = body.statements.singleOrNull() ?: return false
val lambdaParent = element.parent
var lambdaMustReturnUnit = false
if (lambdaParent is KtLambdaArgument) {
val outerCalleeDescriptor = lambdaParent.outerCalleeDescriptor() ?: return false
val lambdaParameterType = outerCalleeDescriptor.valueParameters.lastOrNull()?.type
if (lambdaParameterType != null && lambdaParameterType.isFunctionType) {
// For lambda parameter with receiver, conversion is not allowed
if (lambdaParameterType.isExtensionFunctionType) return false
// Special Unit case (non-Unit returning lambda is accepted here, but non-Unit returning reference is not)
lambdaMustReturnUnit = lambdaParameterType.getReturnTypeFromFunctionType().isUnit()
}
}
val context = statement.analyze()
fun isConvertibleCallInLambda(
callableExpression: KtExpression, callableExpression: KtExpression,
explicitReceiver: KtExpression? = null, explicitReceiver: KtExpression? = null,
lambdaExpression: KtLambdaExpression lambdaExpression: KtLambdaExpression,
lambdaMustReturnUnit: Boolean
): Boolean { ): Boolean {
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
is KtNameReferenceExpression -> callableExpression is KtNameReferenceExpression -> callableExpression
@@ -89,13 +74,14 @@ class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntentio
if (calleeDescriptor is SyntheticJavaPropertyDescriptor) return false if (calleeDescriptor is SyntheticJavaPropertyDescriptor) return false
// No suspend functions // No suspend functions
if ((calleeDescriptor as? FunctionDescriptor)?.isSuspend ?: false) return false if ((calleeDescriptor as? FunctionDescriptor)?.isSuspend ?: false) return false
val descriptorHasReceiver = with(calleeDescriptor) { val descriptorHasReceiver = with(calleeDescriptor) {
// No references to both member / extension // No references to both member / extension
if (dispatchReceiverParameter != null && extensionReceiverParameter != null) return false if (dispatchReceiverParameter != null && extensionReceiverParameter != null) return false
dispatchReceiverParameter != null || extensionReceiverParameter != null dispatchReceiverParameter != null || extensionReceiverParameter != null
} }
val callHasReceiver = explicitReceiver != null
if (!descriptorHasReceiver && callHasReceiver) return false if (!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 if (calleeDescriptor.valueParameters.size != callableArgumentsCount) return false
if (lambdaMustReturnUnit) { if (lambdaMustReturnUnit) {
@@ -105,17 +91,20 @@ class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntentio
} }
} }
val explicitReceiverTarget = (explicitReceiver as? KtNameReferenceExpression)?.let { context[REFERENCE_TARGET, it] } val explicitReceiverDescriptor =
val lambdaValueParameters = context[FUNCTION, lambdaExpression.functionLiteral]?.valueParameters ?: return false (explicitReceiver as? KtNameReferenceExpression)?.let {
val lambdaParameterAsExplicitReceiver = explicitReceiverTarget is ParameterDescriptor && context[REFERENCE_TARGET, it]
explicitReceiverTarget == lambdaValueParameters.firstOrNull() } as? ValueDescriptor
val lambdaValueParameterDescriptors = context[FUNCTION, lambdaExpression.functionLiteral]?.valueParameters ?: return false
val lambdaParameterAsExplicitReceiver = explicitReceiverDescriptor != null &&
explicitReceiverDescriptor == lambdaValueParameterDescriptors.firstOrNull()
val explicitReceiverShift = if (lambdaParameterAsExplicitReceiver) 1 else 0 val explicitReceiverShift = if (lambdaParameterAsExplicitReceiver) 1 else 0
val parametersCount = val lambdaParametersCount =
if (lambdaExpression.functionLiteral.hasParameterSpecification()) lambdaExpression.valueParameters.size else 1 if (lambdaExpression.functionLiteral.hasParameterSpecification()) lambdaExpression.valueParameters.size else 1
if (parametersCount != callableArgumentsCount + explicitReceiverShift) return false if (lambdaParametersCount != callableArgumentsCount + explicitReceiverShift) return false
if (explicitReceiver != null && explicitReceiverTarget is ParameterDescriptor && lambdaParameterAsExplicitReceiver) { if (explicitReceiver != null && explicitReceiverDescriptor != null && lambdaParameterAsExplicitReceiver) {
val receiverType = explicitReceiverTarget.type val receiverType = explicitReceiverDescriptor.type
// No exotic receiver types // No exotic receiver types
if (receiverType.isTypeParameter() || receiverType.isError || receiverType.isDynamic() || if (receiverType.isTypeParameter() || receiverType.isError || receiverType.isDynamic() ||
!receiverType.constructor.isDenotable || receiverType.isFunctionType) return false !receiverType.constructor.isDenotable || receiverType.isFunctionType) return false
@@ -132,28 +121,44 @@ class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntentio
// Same lambda / references function parameter order // Same lambda / references function parameter order
if (callableExpression is KtCallExpression) { if (callableExpression is KtCallExpression) {
if (lambdaValueParameters.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) ->
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 + explicitReceiverShift]) return false if (argumentTarget != lambdaValueParameterDescriptors[valueParameter.index + explicitReceiverShift]) return false
} }
} }
return true return true
} }
return when (statement) { override fun isApplicableTo(element: KtLambdaExpression): Boolean {
val singleStatement = element.singleStatementOrNull() ?: return false
val lambdaParent = element.parent
var lambdaMustReturnUnit = false
if (lambdaParent is KtLambdaArgument) {
val outerCalleeDescriptor = lambdaParent.outerCalleeDescriptor() ?: return false
val lambdaParameterType = outerCalleeDescriptor.valueParameters.lastOrNull()?.type
if (lambdaParameterType != null && lambdaParameterType.isFunctionType) {
// For lambda parameter with receiver, conversion is not allowed
if (lambdaParameterType.isExtensionFunctionType) return false
// Special Unit case (non-Unit returning lambda is accepted here, but non-Unit returning reference is not)
lambdaMustReturnUnit = lambdaParameterType.getReturnTypeFromFunctionType().isUnit()
}
}
return when (singleStatement) {
is KtCallExpression -> { is KtCallExpression -> {
isConvertibleCallInLambda(callableExpression = statement, lambdaExpression = element) isConvertibleCallInLambda(callableExpression = singleStatement, lambdaExpression = element,
lambdaMustReturnUnit = lambdaMustReturnUnit)
} }
is KtNameReferenceExpression -> false // Global property reference is not possible (?!) is KtNameReferenceExpression -> false // Global property reference is not possible (?!)
is KtDotQualifiedExpression -> { is KtDotQualifiedExpression -> {
val selector = statement.selectorExpression ?: return false val selector = singleStatement.selectorExpression ?: return false
isConvertibleCallInLambda(callableExpression = selector, explicitReceiver = statement.receiverExpression, isConvertibleCallInLambda(callableExpression = selector, explicitReceiver = singleStatement.receiverExpression,
lambdaExpression = element) lambdaExpression = element, lambdaMustReturnUnit = lambdaMustReturnUnit)
} }
else -> false else -> false
} }
@@ -217,27 +222,25 @@ class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntentio
} }
private fun buildReferenceText(lambdaExpression: KtLambdaExpression, shortTypes: Boolean): String? { private fun buildReferenceText(lambdaExpression: KtLambdaExpression, shortTypes: Boolean): String? {
val body = lambdaExpression.bodyExpression ?: return null val singleStatement = lambdaExpression.singleStatementOrNull()
val callableExpression = body.statements.singleOrNull() return when (singleStatement) {
return when (callableExpression) {
is KtCallExpression -> { is KtCallExpression -> {
val calleeReferenceExpression = callableExpression.calleeExpression as? KtNameReferenceExpression ?: return null val calleeReferenceExpression = singleStatement.calleeExpression as? KtNameReferenceExpression ?: return null
val context = callableExpression.analyze() val context = singleStatement.analyze()
val calleeDescriptor = context[REFERENCE_TARGET, calleeReferenceExpression] as? CallableMemberDescriptor val resolvedCall = calleeReferenceExpression.getResolvedCall(context) ?: return null
?: return null if (resolvedCall.dispatchReceiver != null || resolvedCall.extensionReceiver != null)
if (calleeDescriptor.dispatchReceiverParameter != null || calleeDescriptor.extensionReceiverParameter != null) "this::${singleStatement.getCallReferencedName()}"
"this::${callableExpression.getCallReferencedName()}"
else else
"::${callableExpression.getCallReferencedName()}" "::${singleStatement.getCallReferencedName()}"
} }
is KtDotQualifiedExpression -> { is KtDotQualifiedExpression -> {
val selector = callableExpression.selectorExpression val selector = singleStatement.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 = callableExpression.receiverExpression as? KtNameReferenceExpression ?: return null val receiver = singleStatement.receiverExpression as? KtNameReferenceExpression ?: return null
val context = receiver.analyze() val context = receiver.analyze()
val receiverDescriptor = context[REFERENCE_TARGET, receiver] ?: return null val receiverDescriptor = context[REFERENCE_TARGET, receiver] ?: return null
val lambdaValueParameters = context[FUNCTION, lambdaExpression.functionLiteral]?.valueParameters ?: return null val lambdaValueParameters = context[FUNCTION, lambdaExpression.functionLiteral]?.valueParameters ?: return null
@@ -260,5 +263,7 @@ class ConvertLambdaToReferenceIntention : SelfTargetingOffsetIndependentIntentio
} }
private fun KtCallExpression.getCallReferencedName() = (calleeExpression as? KtNameReferenceExpression)?.getReferencedName() private fun KtCallExpression.getCallReferencedName() = (calleeExpression as? KtNameReferenceExpression)?.getReferencedName()
private fun KtLambdaExpression.singleStatementOrNull() = bodyExpression?.statements?.singleOrNull()
} }
} }