From 266149b88c6b33f9e75d9d8c4bfd774a2731b93d Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Mon, 3 Feb 2020 10:46:10 +0900 Subject: [PATCH] RedundantLetInspection: fix false positive with nullable receiver extension call #KT-31601 Fixed --- .../inspections/RedundantLetInspection.kt | 17 ++++++++++++---- .../simpleRedundantLet/extensionCall.kt | 6 ++++++ .../simpleRedundantLet/extensionCall.kt.after | 6 ++++++ .../extensionWithNullableReceiverCall.kt | 10 ++++++++++ .../extensionWithNullableReceiverCall2.kt | 10 ++++++++++ .../extensionWithNullableReceiverCall3.kt | 14 +++++++++++++ .../LocalInspectionTestGenerated.java | 20 +++++++++++++++++++ 7 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 idea/testData/inspectionsLocal/simpleRedundantLet/extensionCall.kt create mode 100644 idea/testData/inspectionsLocal/simpleRedundantLet/extensionCall.kt.after create mode 100644 idea/testData/inspectionsLocal/simpleRedundantLet/extensionWithNullableReceiverCall.kt create mode 100644 idea/testData/inspectionsLocal/simpleRedundantLet/extensionWithNullableReceiverCall2.kt create mode 100644 idea/testData/inspectionsLocal/simpleRedundantLet/extensionWithNullableReceiverCall3.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantLetInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantLetInspection.kt index 62912b6d880..03f4ea042b0 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantLetInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantLetInspection.kt @@ -9,9 +9,9 @@ import com.intellij.codeInspection.ProblemHighlightType import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl import org.jetbrains.kotlin.idea.caches.resolve.analyze -import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.intentions.* import org.jetbrains.kotlin.idea.intentions.branchedTransformations.lineCount @@ -23,6 +23,7 @@ import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.isNullable abstract class RedundantLetInspection : AbstractApplicabilityBasedInspection( KtCallExpression::class.java @@ -196,12 +197,20 @@ private fun KtCallExpression.isApplicable(parameterName: String): Boolean = valu argumentExpression.isApplicable(parameterName) } -private fun KtDotQualifiedExpression.isApplicable(parameterName: String) = - !hasLambdaExpression() && getLeftMostReceiverExpression().let { receiver -> +private fun KtDotQualifiedExpression.isApplicable(parameterName: String): Boolean { + val context by lazy { analyze(BodyResolveMode.PARTIAL) } + return !hasLambdaExpression() && getLeftMostReceiverExpression().let { receiver -> receiver is KtNameReferenceExpression && receiver.getReferencedName() == parameterName && !nameUsed(parameterName, except = receiver) - } && callExpression?.resolveToCall() !is VariableAsFunctionResolvedCall + } && callExpression?.getResolvedCall(context) !is VariableAsFunctionResolvedCall && !hasNullableReceiverExtensionCall(context) +} + +private fun KtDotQualifiedExpression.hasNullableReceiverExtensionCall(context: BindingContext): Boolean { + val descriptor = selectorExpression?.getResolvedCall(context)?.resultingDescriptor as? CallableMemberDescriptor ?: return false + if (descriptor.extensionReceiverParameter?.type?.isNullable() == true) return true + return (KtPsiUtil.deparenthesize(receiverExpression) as? KtDotQualifiedExpression)?.hasNullableReceiverExtensionCall(context) == true +} private fun KtDotQualifiedExpression.hasLambdaExpression() = selectorExpression?.anyDescendantOfType() ?: false diff --git a/idea/testData/inspectionsLocal/simpleRedundantLet/extensionCall.kt b/idea/testData/inspectionsLocal/simpleRedundantLet/extensionCall.kt new file mode 100644 index 00000000000..a7314e54811 --- /dev/null +++ b/idea/testData/inspectionsLocal/simpleRedundantLet/extensionCall.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME +fun String.foo() {} + +fun test(s: String?) { + s?.let { it.foo() } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/simpleRedundantLet/extensionCall.kt.after b/idea/testData/inspectionsLocal/simpleRedundantLet/extensionCall.kt.after new file mode 100644 index 00000000000..42ce19fd26a --- /dev/null +++ b/idea/testData/inspectionsLocal/simpleRedundantLet/extensionCall.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME +fun String.foo() {} + +fun test(s: String?) { + s?.foo() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/simpleRedundantLet/extensionWithNullableReceiverCall.kt b/idea/testData/inspectionsLocal/simpleRedundantLet/extensionWithNullableReceiverCall.kt new file mode 100644 index 00000000000..75a6eaa8018 --- /dev/null +++ b/idea/testData/inspectionsLocal/simpleRedundantLet/extensionWithNullableReceiverCall.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// PROBLEM: none +class Optional(val value: T) + +fun Any?.foo() = println("foo: $this") + +fun main() { + val b: Optional? = Optional(null) + b?.let { it.value.foo() } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/simpleRedundantLet/extensionWithNullableReceiverCall2.kt b/idea/testData/inspectionsLocal/simpleRedundantLet/extensionWithNullableReceiverCall2.kt new file mode 100644 index 00000000000..7408a7ee85c --- /dev/null +++ b/idea/testData/inspectionsLocal/simpleRedundantLet/extensionWithNullableReceiverCall2.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// PROBLEM: none +class Optional(val value: T) + +val Any?.foo get() = println("foo: $this") + +fun main() { + val b: Optional? = Optional(null) + b?.let { it.value.foo } +} diff --git a/idea/testData/inspectionsLocal/simpleRedundantLet/extensionWithNullableReceiverCall3.kt b/idea/testData/inspectionsLocal/simpleRedundantLet/extensionWithNullableReceiverCall3.kt new file mode 100644 index 00000000000..10ee4bd3dcf --- /dev/null +++ b/idea/testData/inspectionsLocal/simpleRedundantLet/extensionWithNullableReceiverCall3.kt @@ -0,0 +1,14 @@ +// WITH_RUNTIME +// PROBLEM: none +class A(val b: B?) +class B +class C(val d: D) +class D + +fun B?.getC(): C { + return C(D()) +} + +fun test(a: A?) { + a?.let { it.b.getC().d } +} \ 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 94b35ec342d..fd92e1ae7c1 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -11879,6 +11879,26 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { runTest("idea/testData/inspectionsLocal/simpleRedundantLet/dotWithComparison.kt"); } + @TestMetadata("extensionCall.kt") + public void testExtensionCall() throws Exception { + runTest("idea/testData/inspectionsLocal/simpleRedundantLet/extensionCall.kt"); + } + + @TestMetadata("extensionWithNullableReceiverCall.kt") + public void testExtensionWithNullableReceiverCall() throws Exception { + runTest("idea/testData/inspectionsLocal/simpleRedundantLet/extensionWithNullableReceiverCall.kt"); + } + + @TestMetadata("extensionWithNullableReceiverCall2.kt") + public void testExtensionWithNullableReceiverCall2() throws Exception { + runTest("idea/testData/inspectionsLocal/simpleRedundantLet/extensionWithNullableReceiverCall2.kt"); + } + + @TestMetadata("extensionWithNullableReceiverCall3.kt") + public void testExtensionWithNullableReceiverCall3() throws Exception { + runTest("idea/testData/inspectionsLocal/simpleRedundantLet/extensionWithNullableReceiverCall3.kt"); + } + @TestMetadata("functionCall1.kt") public void testFunctionCall1() throws Exception { runTest("idea/testData/inspectionsLocal/simpleRedundantLet/functionCall1.kt");