Fix "move suspicious reference into parentheses" for functional types

So #KT-21743 Fixed
This commit is contained in:
Toshiaki Kameyama
2018-02-19 17:44:37 +03:00
committed by Mikhail Glukhikh
parent 3fbf85dc37
commit fbd06dc74f
8 changed files with 82 additions and 10 deletions
@@ -13,9 +13,11 @@ import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType
import org.jetbrains.kotlin.builtins.isBuiltinFunctionalType import org.jetbrains.kotlin.builtins.isBuiltinFunctionalType
import org.jetbrains.kotlin.builtins.isBuiltinFunctionalTypeOrSubtype import org.jetbrains.kotlin.builtins.isBuiltinFunctionalTypeOrSubtype
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.intentions.ConvertLambdaToReferenceIntention import org.jetbrains.kotlin.idea.intentions.ConvertLambdaToReferenceIntention
import org.jetbrains.kotlin.idea.intentions.getCallableDescriptor
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
import org.jetbrains.kotlin.psi.KtLambdaExpression import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.psi.ValueArgument import org.jetbrains.kotlin.psi.ValueArgument
@@ -33,7 +35,7 @@ class MoveSuspiciousCallableReferenceIntoParenthesesInspection : AbstractKotlinI
val parentResolvedCall = lambdaExpression.getParentResolvedCall(context) val parentResolvedCall = lambdaExpression.getParentResolvedCall(context)
if (parentResolvedCall != null) { if (parentResolvedCall != null) {
val originalParameterDescriptor = val originalParameterDescriptor =
parentResolvedCall.getParameterForArgument(lambdaExpression.parent as? ValueArgument)?.original parentResolvedCall.getParameterForArgument(lambdaExpression.parent as? ValueArgument)?.original
if (originalParameterDescriptor != null) { if (originalParameterDescriptor != null) {
val expectedType = originalParameterDescriptor.type val expectedType = originalParameterDescriptor.type
if (expectedType.isBuiltinFunctionalType) { if (expectedType.isBuiltinFunctionalType) {
@@ -43,10 +45,10 @@ class MoveSuspiciousCallableReferenceIntoParenthesesInspection : AbstractKotlinI
} }
} }
holder.registerProblem( holder.registerProblem(
lambdaExpression, lambdaExpression,
"Suspicious callable reference as the only lambda element", "Suspicious callable reference as the only lambda element",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
IntentionWrapper(MoveIntoParenthesesIntention(), lambdaExpression.containingFile) IntentionWrapper(MoveIntoParenthesesIntention(), lambdaExpression.containingFile)
) )
} }
@@ -54,15 +56,25 @@ class MoveSuspiciousCallableReferenceIntoParenthesesInspection : AbstractKotlinI
} }
class MoveIntoParenthesesIntention : ConvertLambdaToReferenceIntention( class MoveIntoParenthesesIntention : ConvertLambdaToReferenceIntention(
"Move suspicious callable reference into parentheses '()'" "Move suspicious callable reference into parentheses '()'"
) { ) {
override fun buildReferenceText(element: KtLambdaExpression): String? { override fun buildReferenceText(element: KtLambdaExpression): String? {
val callableReferenceExpression = val callableReferenceExpression =
element.bodyExpression?.statements?.singleOrNull() as? KtCallableReferenceExpression ?: return null element.bodyExpression?.statements?.singleOrNull() as? KtCallableReferenceExpression ?: return null
val callableReference = callableReferenceExpression.callableReference val callableReference = callableReferenceExpression.callableReference
val resolvedCall = callableReference.resolveToCall(BodyResolveMode.FULL) val receiverExpression = callableReferenceExpression.receiverExpression
val receiverValue = resolvedCall?.let { it.extensionReceiver ?: it.dispatchReceiver } val receiver = if (receiverExpression == null) {
return (receiverValue?.let { "${it.type}" } ?: "") + "::${callableReference.text}" ""
} else {
val descriptor = receiverExpression.getCallableDescriptor()
if (descriptor == null || descriptor is ValueParameterDescriptor)
callableReference.resolveToCall(BodyResolveMode.FULL)
?.let { it.extensionReceiver ?: it.dispatchReceiver }
?.let { "${it.type}" } ?: ""
else
receiverExpression.text
}
return "$receiver::${callableReference.text}"
} }
override fun isApplicableTo(element: KtLambdaExpression) = true override fun isApplicableTo(element: KtLambdaExpression) = true
@@ -0,0 +1,7 @@
// WITH_RUNTIME
class Test {
fun function(s: String): Boolean = true
fun test() {
"".let {<caret> this::function }
}
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
class Test {
fun function(s: String): Boolean = true
fun test() {
"".let(this::function)
}
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
class Test {
fun function(s: String): Boolean = true
fun test() {
"".let {<caret> ::function }
}
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
class Test {
fun function(s: String): Boolean = true
fun test() {
"".let(::function)
}
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
class Test {
val lambda = { s: String -> true }
fun test() {
"".let {<caret> lambda::invoke }
}
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
class Test {
val lambda = { s: String -> true }
fun test() {
"".let(lambda::invoke)
}
}
@@ -2301,6 +2301,18 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("explicitThisReceiver.kt")
public void testExplicitThisReceiver() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/explicitThisReceiver.kt");
doTest(fileName);
}
@TestMetadata("implicitThisReceiver.kt")
public void testImplicitThisReceiver() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/implicitThisReceiver.kt");
doTest(fileName);
}
@TestMetadata("it.kt") @TestMetadata("it.kt")
public void testIt() throws Exception { public void testIt() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/it.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/it.kt");
@@ -2348,6 +2360,12 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/parameter2.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/parameter2.kt");
doTest(fileName); doTest(fileName);
} }
@TestMetadata("variableReceiver.kt")
public void testVariableReceiver() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/variableReceiver.kt");
doTest(fileName);
}
} }
@TestMetadata("idea/testData/inspectionsLocal/nullChecksToSafeCall") @TestMetadata("idea/testData/inspectionsLocal/nullChecksToSafeCall")