Fix false positive "Foldable if-then" with Result type
#KT-27074 Fixed
This commit is contained in:
+34
-12
@@ -22,13 +22,17 @@ import com.intellij.openapi.project.Project
|
|||||||
import com.intellij.psi.PsiDocumentManager
|
import com.intellij.psi.PsiDocumentManager
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import org.jetbrains.kotlin.idea.core.replaced
|
import org.jetbrains.kotlin.idea.core.replaced
|
||||||
|
import org.jetbrains.kotlin.idea.imports.importableFqName
|
||||||
import org.jetbrains.kotlin.idea.inspections.AbstractApplicabilityBasedInspection
|
import org.jetbrains.kotlin.idea.inspections.AbstractApplicabilityBasedInspection
|
||||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.*
|
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.*
|
||||||
|
import org.jetbrains.kotlin.idea.intentions.callExpression
|
||||||
import org.jetbrains.kotlin.idea.refactoring.rename.KotlinVariableInplaceRenameHandler
|
import org.jetbrains.kotlin.idea.refactoring.rename.KotlinVariableInplaceRenameHandler
|
||||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
|
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
|
||||||
|
|
||||||
@@ -55,18 +59,6 @@ class IfThenToSafeAccessInspection : AbstractApplicabilityBasedInspection<KtIfEx
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private fun IfThenToSelectData.clausesReplaceableBySafeCall(): Boolean = when {
|
|
||||||
baseClause == null -> false
|
|
||||||
negatedClause == null && baseClause.isUsedAsExpression(context) -> false
|
|
||||||
negatedClause != null && !negatedClause.isNullExpression() -> false
|
|
||||||
baseClause.evaluatesTo(receiverExpression) -> true
|
|
||||||
baseClause.hasFirstReceiverOf(receiverExpression) -> true
|
|
||||||
baseClause.anyArgumentEvaluatesTo(receiverExpression) -> true
|
|
||||||
receiverExpression is KtThisExpression -> getImplicitReceiver()?.let { it.type == receiverExpression.getType(context) } == true
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fixTextFor(element: KtIfExpression): String {
|
fun fixTextFor(element: KtIfExpression): String {
|
||||||
val ifThenToSelectData = element.buildSelectTransformationData()
|
val ifThenToSelectData = element.buildSelectTransformationData()
|
||||||
return if (ifThenToSelectData?.baseClauseEvaluatesToReceiver() == true) {
|
return if (ifThenToSelectData?.baseClauseEvaluatesToReceiver() == true) {
|
||||||
@@ -113,3 +105,33 @@ class IfThenToSafeAccessInspection : AbstractApplicabilityBasedInspection<KtIfEx
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun IfThenToSelectData.clausesReplaceableBySafeCall(): Boolean = when {
|
||||||
|
baseClause == null -> false
|
||||||
|
negatedClause == null && baseClause.isUsedAsExpression(context) -> false
|
||||||
|
negatedClause != null && !negatedClause.isNullExpression() -> false
|
||||||
|
baseClause.evaluatesTo(receiverExpression) -> true
|
||||||
|
baseClause.hasFirstReceiverOf(receiverExpression) -> withoutResultInCallChain(baseClause, context)
|
||||||
|
baseClause.anyArgumentEvaluatesTo(receiverExpression) -> true
|
||||||
|
receiverExpression is KtThisExpression -> getImplicitReceiver()?.let { it.type == receiverExpression.getType(context) } == true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun withoutResultInCallChain(expression: KtExpression, context: BindingContext): Boolean {
|
||||||
|
if (expression !is KtDotQualifiedExpression || expression.receiverExpression !is KtDotQualifiedExpression) return true
|
||||||
|
return !hasResultInCallExpression(expression, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hasResultInCallExpression(expression: KtExpression, context: BindingContext): Boolean =
|
||||||
|
if (expression is KtDotQualifiedExpression)
|
||||||
|
returnTypeIsResult(expression.callExpression, context) || hasResultInCallExpression(expression.receiverExpression, context)
|
||||||
|
else
|
||||||
|
false
|
||||||
|
|
||||||
|
private fun returnTypeIsResult(call: KtCallExpression?, context: BindingContext) = call
|
||||||
|
?.getType(context)
|
||||||
|
?.constructor
|
||||||
|
?.declarationDescriptor
|
||||||
|
?.importableFqName == RESULT_FQNAME
|
||||||
|
|
||||||
|
private val RESULT_FQNAME = FqName("kotlin.Result")
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
// DISABLE-ERRORS
|
||||||
|
class Result<T> {
|
||||||
|
fun getOrNull(): Any = TODO()
|
||||||
|
}
|
||||||
|
val someNullableString: String? = ""
|
||||||
|
fun String.bar(): Result<String> = Result.success("")
|
||||||
|
val result = if<caret> (someNullableString == null) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
someNullableString.bar().getOrNull().let { }
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// DISABLE-ERRORS
|
||||||
|
class Result<T> {
|
||||||
|
fun getOrNull(): Any = TODO()
|
||||||
|
}
|
||||||
|
val someNullableString: String? = ""
|
||||||
|
fun String.bar(): Result<String> = Result.success("")
|
||||||
|
val result = someNullableString?.bar()?.getOrNull()?.let { }
|
||||||
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
// WITH_RUNTIME
|
||||||
|
// DISABLE-ERRORS
|
||||||
|
|
||||||
|
val someNullableString: String? = ""
|
||||||
|
fun String.bar(): Result<String> = Result.success("")
|
||||||
|
val result = if<caret> (someNullableString == null) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
someNullableString.bar().getOrNull().let { }
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// DISABLE-ERRORS
|
||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
val someNullableString: String? = ""
|
||||||
|
fun String.bar(): Result<String> = Result.success("")
|
||||||
|
val result = if<caret> (someNullableString == null) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
someNullableString.bar()
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
// DISABLE-ERRORS
|
||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
val someNullableString: String? = ""
|
||||||
|
fun String.bar(): Result<String> = Result.success("")
|
||||||
|
val result = someNullableString?.bar()
|
||||||
+15
@@ -673,6 +673,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
|||||||
runTest("idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/noNullInCondition2.kt");
|
runTest("idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/noNullInCondition2.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("noStdResultInCallChain.kt")
|
||||||
|
public void testNoStdResultInCallChain() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/noStdResultInCallChain.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("noThenBlock.kt")
|
@TestMetadata("noThenBlock.kt")
|
||||||
public void testNoThenBlock() throws Exception {
|
public void testNoThenBlock() throws Exception {
|
||||||
runTest("idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock.kt");
|
runTest("idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock.kt");
|
||||||
@@ -688,6 +693,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
|||||||
runTest("idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableForLocalVar.kt");
|
runTest("idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableForLocalVar.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("notApplicableResultInCallChain.kt")
|
||||||
|
public void testNotApplicableResultInCallChain() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableResultInCallChain.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("nullCheckSimple.kt")
|
@TestMetadata("nullCheckSimple.kt")
|
||||||
public void testNullCheckSimple() throws Exception {
|
public void testNullCheckSimple() throws Exception {
|
||||||
runTest("idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/nullCheckSimple.kt");
|
runTest("idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/nullCheckSimple.kt");
|
||||||
@@ -728,6 +738,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
|||||||
runTest("idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/replaceWithLetInMemberWithIs.kt");
|
runTest("idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/replaceWithLetInMemberWithIs.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("resultCall.kt")
|
||||||
|
public void testResultCall() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/resultCall.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("rhsEqualsNull.kt")
|
@TestMetadata("rhsEqualsNull.kt")
|
||||||
public void testRhsEqualsNull() throws Exception {
|
public void testRhsEqualsNull() throws Exception {
|
||||||
runTest("idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull.kt");
|
runTest("idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user