From 539c55c5b299553a41033e81290c962703b108b5 Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Mon, 10 Sep 2018 12:18:33 +0300 Subject: [PATCH] Call chain into sequence: fix false negative on implicit receiver Part of KT-26650 --- .../ConvertCallChainIntoSequenceInspection.kt | 36 +++++++++++++------ .../implicitReceiver.kt | 5 +++ .../implicitReceiver.kt.after | 5 +++ .../LocalInspectionTestGenerated.java | 5 +++ 4 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/implicitReceiver.kt create mode 100644 idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/implicitReceiver.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/ConvertCallChainIntoSequenceInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/ConvertCallChainIntoSequenceInspection.kt index 16e37772179..a9b3a289bb8 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/collections/ConvertCallChainIntoSequenceInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/collections/ConvertCallChainIntoSequenceInspection.kt @@ -24,15 +24,14 @@ import org.jetbrains.kotlin.idea.intentions.callExpression import org.jetbrains.kotlin.idea.util.CommentSaver import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType -import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector -import org.jetbrains.kotlin.psi.psiUtil.siblings -import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getImplicitReceiverValue import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver import org.jetbrains.kotlin.types.KotlinType import java.awt.BorderLayout import javax.swing.JPanel @@ -97,13 +96,15 @@ private class ConvertCallChainIntoSequenceFix : LocalQuickFix { val calls = expression.collectCallExpression(context).reversed() val firstCall = calls.firstOrNull() ?: return val lastCall = calls.lastOrNull() ?: return - val first = firstCall.getQualifiedExpressionForSelector() ?: return + val first = firstCall.getQualifiedExpressionForSelector() ?: firstCall val last = lastCall.getQualifiedExpressionForSelector() ?: return val endWithTermination = lastCall.isTermination(context) val psiFactory = KtPsiFactory(expression) val dot = buildString { - if (first.receiverExpression.siblings().filterIsInstance().any { it.textContains('\n') }) append("\n") + if (first is KtQualifiedExpression + && first.receiverExpression.siblings().filterIsInstance().any { it.textContains('\n') } + ) append("\n") if (first is KtSafeQualifiedExpression) append("?") append(".") } @@ -111,8 +112,10 @@ private class ConvertCallChainIntoSequenceFix : LocalQuickFix { val firstCommentSaver = CommentSaver(first) val firstReplaced = first.replaced( psiFactory.buildExpression { - appendExpression(first.receiverExpression) - appendFixedText(dot) + if (first is KtQualifiedExpression) { + appendExpression(first.receiverExpression) + appendFixedText(dot) + } appendExpression(psiFactory.createExpression("asSequence()")) appendFixedText(dot) appendExpression(firstCall) @@ -147,12 +150,15 @@ private fun KtQualifiedExpression.findCallChain(): CallChain? { val calls = collectCallExpression(context) if (calls.isEmpty()) return null + val lastCall = calls.last() val receiverType = - (calls.last().getQualifiedExpressionForSelector())?.receiverExpression?.getResolvedCall(context)?.resultingDescriptor?.returnType + (lastCall.getQualifiedExpressionForSelector())?.receiverExpression?.getResolvedCall(context)?.resultingDescriptor?.returnType + ?: lastCall.implicitReceiver(context)?.type if (receiverType?.isCollection() != true) return null - val qualified = calls.first().getQualifiedExpressionForSelector() ?: return null - return CallChain(qualified, calls.last(), calls.size) + val firstCall = calls.first() + val qualified = firstCall.getQualifiedExpressionForSelector() ?: firstCall.getQualifiedExpressionForReceiver() ?: return null + return CallChain(qualified, lastCall, calls.size) } private fun KtQualifiedExpression.collectCallExpression(context: BindingContext): List { @@ -162,6 +168,10 @@ private fun KtQualifiedExpression.collectCallExpression(context: BindingContext) val call = qualified.callExpression ?: return calls.add(call) val receiver = qualified.receiverExpression + if (receiver is KtCallExpression && receiver.implicitReceiver(context) != null) { + calls.add(receiver) + return + } if (receiver is KtQualifiedExpression) collect(receiver) } collect(this) @@ -179,6 +189,10 @@ private fun KtQualifiedExpression.collectCallExpression(context: BindingContext) return transformationCalls } +private fun KtExpression.implicitReceiver(context: BindingContext): ImplicitReceiver? { + return getResolvedCall(context)?.getImplicitReceiverValue() +} + private fun KotlinType.isCollection(): Boolean { val classDescriptor = constructor.declarationDescriptor as? ClassDescriptor ?: return false val className = classDescriptor.name.asString() diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/implicitReceiver.kt b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/implicitReceiver.kt new file mode 100644 index 00000000000..4a6f4506ba2 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/implicitReceiver.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun List.test(): List { + return filter { it > 1 }.map { it * 2 } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/implicitReceiver.kt.after b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/implicitReceiver.kt.after new file mode 100644 index 00000000000..48bde273755 --- /dev/null +++ b/idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/implicitReceiver.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun List.test(): List { + return asSequence().filter { it > 1 }.map { it * 2 }.toList() +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index ee3358bc90a..bf2e2ed1076 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -569,6 +569,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/endsWithGroupingBy.kt"); } + @TestMetadata("implicitReceiver.kt") + public void testImplicitReceiver() throws Exception { + runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/implicitReceiver.kt"); + } + @TestMetadata("mutableList.kt") public void testMutableList() throws Exception { runTest("idea/testData/inspectionsLocal/collections/convertCallChainIntoSequence/mutableList.kt");