RedundantLetInspection: fix false positive with nullable receiver extension call

#KT-31601 Fixed
This commit is contained in:
Toshiaki Kameyama
2020-02-03 10:46:10 +09:00
committed by Dmitry Gridin
parent a6139f3635
commit 266149b88c
7 changed files with 79 additions and 4 deletions
@@ -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>(
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<KtLambdaExpression>() ?: false
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun String.foo() {}
fun test(s: String?) {
s?.let<caret> { it.foo() }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun String.foo() {}
fun test(s: String?) {
s?.foo()
}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
// PROBLEM: none
class Optional<out T>(val value: T)
fun Any?.foo() = println("foo: $this")
fun main() {
val b: Optional<Any?>? = Optional(null)
b?.let<caret> { it.value.foo() }
}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
// PROBLEM: none
class Optional<out T>(val value: T)
val Any?.foo get() = println("foo: $this")
fun main() {
val b: Optional<Any?>? = Optional(null)
b?.let<caret> { it.value.foo }
}
@@ -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<caret> { it.b.getC().d }
}
@@ -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");