Convert lambda to reference: suggest on lambda argument for extension function parameter
#KT-22014 Fixed #KT-22015 Fixed
This commit is contained in:
committed by
Yan Zhulanow
parent
b8ae13b3d9
commit
9b6b25f069
+40
-28
@@ -6,10 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.idea.intentions
|
package org.jetbrains.kotlin.idea.intentions
|
||||||
|
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType
|
import org.jetbrains.kotlin.builtins.*
|
||||||
import org.jetbrains.kotlin.builtins.isExtensionFunctionType
|
|
||||||
import org.jetbrains.kotlin.builtins.isFunctionType
|
|
||||||
import org.jetbrains.kotlin.builtins.isSuspendFunctionType
|
|
||||||
import org.jetbrains.kotlin.config.LanguageFeature
|
import org.jetbrains.kotlin.config.LanguageFeature
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||||
@@ -19,20 +16,26 @@ import org.jetbrains.kotlin.idea.core.ShortenReferences
|
|||||||
import org.jetbrains.kotlin.idea.imports.importableFqName
|
import org.jetbrains.kotlin.idea.imports.importableFqName
|
||||||
import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection
|
import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection
|
||||||
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.fqName.fqName
|
||||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||||
import org.jetbrains.kotlin.idea.util.approximateFlexibleTypes
|
import org.jetbrains.kotlin.idea.util.approximateFlexibleTypes
|
||||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||||
import org.jetbrains.kotlin.renderer.render
|
import org.jetbrains.kotlin.renderer.render
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext.FUNCTION
|
import org.jetbrains.kotlin.resolve.BindingContext.FUNCTION
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext.REFERENCE_TARGET
|
import org.jetbrains.kotlin.resolve.BindingContext.REFERENCE_TARGET
|
||||||
|
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.model.VarargValueArgument
|
import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isCompanionObject
|
import org.jetbrains.kotlin.resolve.descriptorUtil.isCompanionObject
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver
|
||||||
import org.jetbrains.kotlin.resolve.scopes.utils.getImplicitReceiversHierarchy
|
import org.jetbrains.kotlin.resolve.scopes.utils.getImplicitReceiversHierarchy
|
||||||
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
|
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.kotlin.types.isDynamic
|
import org.jetbrains.kotlin.types.isDynamic
|
||||||
import org.jetbrains.kotlin.types.isError
|
import org.jetbrains.kotlin.types.isError
|
||||||
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
|
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
|
||||||
@@ -58,8 +61,7 @@ open class ConvertLambdaToReferenceIntention(textGetter: () -> String) : SelfTar
|
|||||||
private fun isConvertibleCallInLambda(
|
private 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 context = callableExpression.analyze()
|
||||||
val calleeReferenceExpression = when (callableExpression) {
|
val calleeReferenceExpression = when (callableExpression) {
|
||||||
@@ -68,6 +70,15 @@ open class ConvertLambdaToReferenceIntention(textGetter: () -> String) : SelfTar
|
|||||||
else -> return false
|
else -> return false
|
||||||
}
|
}
|
||||||
val calleeDescriptor = context[REFERENCE_TARGET, calleeReferenceExpression] as? CallableMemberDescriptor ?: return false
|
val calleeDescriptor = context[REFERENCE_TARGET, calleeReferenceExpression] as? CallableMemberDescriptor ?: return false
|
||||||
|
|
||||||
|
val lambdaParameterType = lambdaExpression.lambdaParameterType(context)
|
||||||
|
if (lambdaParameterType?.isSuspendFunctionType == true) return false
|
||||||
|
if (lambdaParameterType?.isExtensionFunctionType == true) {
|
||||||
|
if (explicitReceiver != null && explicitReceiver !is KtThisExpression) return false
|
||||||
|
val receiver = calleeDescriptor.dispatchReceiverParameter ?: calleeDescriptor.extensionReceiverParameter
|
||||||
|
if (lambdaParameterType.getReceiverTypeFromFunctionType() != receiver?.type) return false
|
||||||
|
}
|
||||||
|
|
||||||
// No references with type parameters
|
// No references with type parameters
|
||||||
if (calleeDescriptor.typeParameters.isNotEmpty()) return false
|
if (calleeDescriptor.typeParameters.isNotEmpty()) return false
|
||||||
// No references to Java synthetic properties
|
// No references to Java synthetic properties
|
||||||
@@ -87,6 +98,8 @@ open class ConvertLambdaToReferenceIntention(textGetter: () -> String) : SelfTar
|
|||||||
|
|
||||||
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
|
||||||
|
val lambdaMustReturnUnit =
|
||||||
|
if (lambdaParameterType?.isFunctionType == true) lambdaParameterType.getReturnTypeFromFunctionType().isUnit() else false
|
||||||
if (lambdaMustReturnUnit) {
|
if (lambdaMustReturnUnit) {
|
||||||
calleeDescriptor.returnType.let {
|
calleeDescriptor.returnType.let {
|
||||||
// If Unit required, no references to non-Unit callables
|
// If Unit required, no references to non-Unit callables
|
||||||
@@ -135,26 +148,11 @@ open class ConvertLambdaToReferenceIntention(textGetter: () -> String) : SelfTar
|
|||||||
|
|
||||||
override fun isApplicableTo(element: KtLambdaExpression): Boolean {
|
override fun isApplicableTo(element: KtLambdaExpression): Boolean {
|
||||||
val singleStatement = element.singleStatementOrNull() ?: return false
|
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?.isSuspendFunctionType == true) return false
|
|
||||||
if (lambdaParameterType?.isFunctionType == true) {
|
|
||||||
// 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) {
|
return when (singleStatement) {
|
||||||
is KtCallExpression -> {
|
is KtCallExpression -> {
|
||||||
isConvertibleCallInLambda(
|
isConvertibleCallInLambda(
|
||||||
callableExpression = singleStatement,
|
callableExpression = singleStatement,
|
||||||
lambdaExpression = element,
|
lambdaExpression = element
|
||||||
lambdaMustReturnUnit = lambdaMustReturnUnit
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is KtNameReferenceExpression -> false // Global property reference is not possible (?!)
|
is KtNameReferenceExpression -> false // Global property reference is not possible (?!)
|
||||||
@@ -163,8 +161,7 @@ open class ConvertLambdaToReferenceIntention(textGetter: () -> String) : SelfTar
|
|||||||
isConvertibleCallInLambda(
|
isConvertibleCallInLambda(
|
||||||
callableExpression = selector,
|
callableExpression = selector,
|
||||||
explicitReceiver = singleStatement.receiverExpression,
|
explicitReceiver = singleStatement.receiverExpression,
|
||||||
lambdaExpression = element,
|
lambdaExpression = element
|
||||||
lambdaMustReturnUnit = lambdaMustReturnUnit
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> false
|
else -> false
|
||||||
@@ -221,7 +218,16 @@ open class ConvertLambdaToReferenceIntention(textGetter: () -> String) : SelfTar
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private fun KtLambdaExpression.lambdaParameterType(context: BindingContext? = null): KotlinType? {
|
||||||
|
val argument = parent as? KtValueArgument ?: return null
|
||||||
|
val callExpression = argument.getStrictParentOfType<KtCallExpression>() ?: return null
|
||||||
|
return callExpression
|
||||||
|
.getResolvedCall(context ?: analyze(BodyResolveMode.PARTIAL))
|
||||||
|
?.getParameterForArgument(argument)?.type
|
||||||
|
}
|
||||||
|
|
||||||
private fun buildReferenceText(lambdaExpression: KtLambdaExpression, shortTypes: Boolean): String? {
|
private fun buildReferenceText(lambdaExpression: KtLambdaExpression, shortTypes: Boolean): String? {
|
||||||
|
val lambdaParameterType = lambdaExpression.lambdaParameterType()
|
||||||
return when (val singleStatement = lambdaExpression.singleStatementOrNull()) {
|
return when (val singleStatement = lambdaExpression.singleStatementOrNull()) {
|
||||||
is KtCallExpression -> {
|
is KtCallExpression -> {
|
||||||
val calleeReferenceExpression = singleStatement.calleeExpression as? KtNameReferenceExpression ?: return null
|
val calleeReferenceExpression = singleStatement.calleeExpression as? KtNameReferenceExpression ?: return null
|
||||||
@@ -229,12 +235,13 @@ open class ConvertLambdaToReferenceIntention(textGetter: () -> String) : SelfTar
|
|||||||
val receiver = resolvedCall.dispatchReceiver ?: resolvedCall.extensionReceiver
|
val receiver = resolvedCall.dispatchReceiver ?: resolvedCall.extensionReceiver
|
||||||
val descriptor by lazy { receiver?.type?.constructor?.declarationDescriptor }
|
val descriptor by lazy { receiver?.type?.constructor?.declarationDescriptor }
|
||||||
val receiverText = when {
|
val receiverText = when {
|
||||||
|
lambdaParameterType?.isExtensionFunctionType == true ->
|
||||||
|
lambdaParameterType.getReceiverTypeFromFunctionType()?.fqName?.asString()
|
||||||
receiver == null || descriptor?.isCompanionObject() == true -> ""
|
receiver == null || descriptor?.isCompanionObject() == true -> ""
|
||||||
receiver is ExtensionReceiver ||
|
receiver is ExtensionReceiver ||
|
||||||
lambdaExpression.getResolutionScope().getImplicitReceiversHierarchy().size == 1 -> "this"
|
lambdaExpression.getResolutionScope().getImplicitReceiversHierarchy().size == 1 -> "this"
|
||||||
else -> descriptor?.name?.let { "this@$it" } ?: return null
|
else -> descriptor?.name?.let { "this@$it" }
|
||||||
}
|
} ?: return null
|
||||||
|
|
||||||
"$receiverText::${singleStatement.getCallReferencedName()}"
|
"$receiverText::${singleStatement.getCallReferencedName()}"
|
||||||
}
|
}
|
||||||
is KtDotQualifiedExpression -> {
|
is KtDotQualifiedExpression -> {
|
||||||
@@ -264,7 +271,12 @@ open class ConvertLambdaToReferenceIntention(textGetter: () -> String) : SelfTar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
"${receiver.text}::$selectorReferenceName"
|
val receiverText = if (lambdaParameterType?.isExtensionFunctionType == true) {
|
||||||
|
lambdaParameterType.getReceiverTypeFromFunctionType()?.fqName?.asString()
|
||||||
|
} else {
|
||||||
|
receiver.text
|
||||||
|
} ?: return null
|
||||||
|
"$receiverText::$selectorReferenceName"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package p
|
||||||
|
|
||||||
|
class A {
|
||||||
|
fun f1() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun myA(func: A.() -> Unit) {
|
||||||
|
A().func()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
myA {<caret>
|
||||||
|
f1()
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package p
|
||||||
|
|
||||||
|
class A {
|
||||||
|
fun f1() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun myA(func: A.() -> Unit) {
|
||||||
|
A().func()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
myA(A::f1)
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package p
|
||||||
|
|
||||||
|
class A {
|
||||||
|
fun f1() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun myA(func: A.() -> Unit) {
|
||||||
|
A().func()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
myA {<caret>
|
||||||
|
this.f1()
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package p
|
||||||
|
|
||||||
|
class A {
|
||||||
|
fun f1() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun myA(func: A.() -> Unit) {
|
||||||
|
A().func()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
myA(A::f1)
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package p
|
||||||
|
|
||||||
|
class A {
|
||||||
|
fun f1() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun myA(func: A.() -> Unit) {
|
||||||
|
A().func()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
myA({<caret> f1() })
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package p
|
||||||
|
|
||||||
|
class A {
|
||||||
|
fun f1() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun myA(func: A.() -> Unit) {
|
||||||
|
A().func()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
myA(A::f1)
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package p
|
||||||
|
|
||||||
|
class A {
|
||||||
|
fun f1() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun myA(func: A.() -> Unit) {
|
||||||
|
A().func()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
myA({<caret> this.f1() })
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package p
|
||||||
|
|
||||||
|
class A {
|
||||||
|
fun f1() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun myA(func: A.() -> Unit) {
|
||||||
|
A().func()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
myA(A::f1)
|
||||||
|
}
|
||||||
@@ -4875,6 +4875,26 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
|||||||
runTest("idea/testData/intentions/convertLambdaToReference/argumentWithReceiver.kt");
|
runTest("idea/testData/intentions/convertLambdaToReference/argumentWithReceiver.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("argumentWithReceiver2.kt")
|
||||||
|
public void testArgumentWithReceiver2() throws Exception {
|
||||||
|
runTest("idea/testData/intentions/convertLambdaToReference/argumentWithReceiver2.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("argumentWithReceiver3.kt")
|
||||||
|
public void testArgumentWithReceiver3() throws Exception {
|
||||||
|
runTest("idea/testData/intentions/convertLambdaToReference/argumentWithReceiver3.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("argumentWithReceiver4.kt")
|
||||||
|
public void testArgumentWithReceiver4() throws Exception {
|
||||||
|
runTest("idea/testData/intentions/convertLambdaToReference/argumentWithReceiver4.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("argumentWithReceiver5.kt")
|
||||||
|
public void testArgumentWithReceiver5() throws Exception {
|
||||||
|
runTest("idea/testData/intentions/convertLambdaToReference/argumentWithReceiver5.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("backtickEscaped.kt")
|
@TestMetadata("backtickEscaped.kt")
|
||||||
public void testBacktickEscaped() throws Exception {
|
public void testBacktickEscaped() throws Exception {
|
||||||
runTest("idea/testData/intentions/convertLambdaToReference/backtickEscaped.kt");
|
runTest("idea/testData/intentions/convertLambdaToReference/backtickEscaped.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user