Suspicious callable reference: no quick fix with invalid reference type

#KT-23467 Fixed
This commit is contained in:
Toshiaki Kameyama
2018-08-04 14:40:23 +03:00
committed by Mikhail Glukhikh
parent 0ac969a617
commit b91f30ab15
7 changed files with 85 additions and 7 deletions
@@ -13,20 +13,23 @@ import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType
import org.jetbrains.kotlin.builtins.isBuiltinFunctionalType
import org.jetbrains.kotlin.builtins.isBuiltinFunctionalTypeOrSubtype
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
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.KtLambdaExpression
import org.jetbrains.kotlin.psi.ValueArgument
import org.jetbrains.kotlin.psi.lambdaExpressionVisitor
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getParameterForArgument
import org.jetbrains.kotlin.resolve.calls.callUtil.getParentCall
import org.jetbrains.kotlin.resolve.calls.callUtil.getParentResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
class MoveSuspiciousCallableReferenceIntoParenthesesInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
@@ -47,17 +50,41 @@ class MoveSuspiciousCallableReferenceIntoParenthesesInspection : AbstractKotlinI
}
}
}
val quickFix = if (canMove(lambdaExpression, callableReference, context))
IntentionWrapper(MoveIntoParenthesesIntention(), lambdaExpression.containingFile)
else
null
holder.registerProblem(
lambdaExpression,
"Suspicious callable reference as the only lambda element",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
IntentionWrapper(MoveIntoParenthesesIntention(), lambdaExpression.containingFile)
quickFix
)
}
})
}
private fun canMove(
lambdaExpression: KtLambdaExpression,
callableReference: KtCallableReferenceExpression,
context: BindingContext
): Boolean {
val lambdaDescriptor = context[BindingContext.FUNCTION, lambdaExpression.functionLiteral] ?: return false
val lambdaParameter = lambdaDescriptor.extensionReceiverParameter ?: lambdaDescriptor.valueParameters.singleOrNull()
val functionReceiver = callableReference.receiverExpression?.mainReference?.resolveToDescriptors(context)?.firstOrNull()
if (functionReceiver == lambdaParameter) return true
val lambdaParameterType = lambdaParameter?.type
if (functionReceiver is VariableDescriptor && functionReceiver.type == lambdaParameterType) return true
if (functionReceiver is ClassDescriptor && functionReceiver == lambdaParameterType?.constructor?.declarationDescriptor) return true
if (lambdaParameterType == null) return false
val functionDescriptor =
callableReference.callableReference.mainReference.resolveToDescriptors(context).firstOrNull() as? FunctionDescriptor
val functionParameterType = functionDescriptor?.valueParameters?.firstOrNull()?.type ?: return false
return functionParameterType == lambdaParameterType
}
class MoveIntoParenthesesIntention : ConvertLambdaToReferenceIntention(
"Move suspicious callable reference into parentheses '()'"
) {
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// FIX: none
fun test(x: Int) {
fun p(s: String): Boolean = true
x.let {<caret> ::p }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// FIX: none
fun test(x: Int) {
val p = { _: String -> true }
x.run {<caret> p::invoke }
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
// FIX: none
fun test(x: Int, y: String) {
x.run {<caret> y::length }
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
// FIX: none
fun test(x: Int) {
x.run {<caret> String::length }
}
@@ -190,7 +190,12 @@ abstract class AbstractLocalInspectionTest : KotlinLightCodeInsightFixtureTestCa
val allLocalFixActions = highlightInfos.flatMap { it.quickFixActionMarkers ?: emptyList() }.map { it.first.action }
val localFixActions = allLocalFixActions.filter { fix -> localFixTextString == null || fix.text == localFixTextString }
val localFixActions = if (localFixTextString == null || localFixTextString == "none") {
allLocalFixActions
} else {
allLocalFixActions.filter { fix -> fix.text == localFixTextString }
}
val availableDescription = allLocalFixActions.joinToString { it.text }
val fixDescription = localFixTextString?.let { "with specified text '$localFixTextString'" } ?: ""
@@ -201,6 +206,10 @@ abstract class AbstractLocalInspectionTest : KotlinLightCodeInsightFixtureTestCa
)
val localFixAction = localFixActions.singleOrNull { it !is EmptyIntentionAction }
if (localFixTextString == "none") {
Assert.assertTrue("Expected no fix action", localFixAction == null)
return false
}
TestCase.assertTrue(
"More than one fix action $fixDescription\n" +
"Available actions: $availableDescription",
@@ -3252,6 +3252,26 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
runTest("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/implicitThisReceiver.kt");
}
@TestMetadata("invalidFunctionReference.kt")
public void testInvalidFunctionReference() throws Exception {
runTest("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/invalidFunctionReference.kt");
}
@TestMetadata("invalidFunctionReference2.kt")
public void testInvalidFunctionReference2() throws Exception {
runTest("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/invalidFunctionReference2.kt");
}
@TestMetadata("invalidFunctionReference3.kt")
public void testInvalidFunctionReference3() throws Exception {
runTest("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/invalidFunctionReference3.kt");
}
@TestMetadata("invalidFunctionReference4.kt")
public void testInvalidFunctionReference4() throws Exception {
runTest("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/invalidFunctionReference4.kt");
}
@TestMetadata("it.kt")
public void testIt() throws Exception {
runTest("idea/testData/inspectionsLocal/moveSuspiciousCallableReferenceIntoParentheses/it.kt");