diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToSafeAccessInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToSafeAccessInspection.kt index a747b22f5f5..075755d81ce 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToSafeAccessInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToSafeAccessInspection.kt @@ -22,13 +22,17 @@ import com.intellij.openapi.project.Project import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement 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.intentions.branchedTransformations.* +import org.jetbrains.kotlin.idea.intentions.callExpression import org.jetbrains.kotlin.idea.refactoring.rename.KotlinVariableInplaceRenameHandler import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getParentOfType 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.calls.callUtil.getType @@ -55,18 +59,6 @@ class IfThenToSafeAccessInspection : AbstractApplicabilityBasedInspection 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 { val ifThenToSelectData = element.buildSelectTransformationData() return if (ifThenToSelectData?.baseClauseEvaluatesToReceiver() == true) { @@ -113,3 +105,33 @@ class IfThenToSafeAccessInspection : AbstractApplicabilityBasedInspection 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") \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/noStdResultInCallChain.kt b/idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/noStdResultInCallChain.kt new file mode 100644 index 00000000000..76a79ff3818 --- /dev/null +++ b/idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/noStdResultInCallChain.kt @@ -0,0 +1,11 @@ +// DISABLE-ERRORS +class Result { + fun getOrNull(): Any = TODO() +} +val someNullableString: String? = "" +fun String.bar(): Result = Result.success("") +val result = if (someNullableString == null) { + null +} else { + someNullableString.bar().getOrNull().let { } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/noStdResultInCallChain.kt.after b/idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/noStdResultInCallChain.kt.after new file mode 100644 index 00000000000..3ac63d87d17 --- /dev/null +++ b/idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/noStdResultInCallChain.kt.after @@ -0,0 +1,7 @@ +// DISABLE-ERRORS +class Result { + fun getOrNull(): Any = TODO() +} +val someNullableString: String? = "" +fun String.bar(): Result = Result.success("") +val result = someNullableString?.bar()?.getOrNull()?.let { } \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableResultInCallChain.kt b/idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableResultInCallChain.kt new file mode 100644 index 00000000000..c55b7656461 --- /dev/null +++ b/idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableResultInCallChain.kt @@ -0,0 +1,11 @@ +// PROBLEM: none +// WITH_RUNTIME +// DISABLE-ERRORS + +val someNullableString: String? = "" +fun String.bar(): Result = Result.success("") +val result = if (someNullableString == null) { + null +} else { + someNullableString.bar().getOrNull().let { } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/resultCall.kt b/idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/resultCall.kt new file mode 100644 index 00000000000..36a12d47667 --- /dev/null +++ b/idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/resultCall.kt @@ -0,0 +1,10 @@ +// DISABLE-ERRORS +// WITH_RUNTIME + +val someNullableString: String? = "" +fun String.bar(): Result = Result.success("") +val result = if (someNullableString == null) { + null +} else { + someNullableString.bar() +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/resultCall.kt.after b/idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/resultCall.kt.after new file mode 100644 index 00000000000..0a47c2dcaa2 --- /dev/null +++ b/idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/resultCall.kt.after @@ -0,0 +1,6 @@ +// DISABLE-ERRORS +// WITH_RUNTIME + +val someNullableString: String? = "" +fun String.bar(): Result = Result.success("") +val result = someNullableString?.bar() \ 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 08fa66fbbcc..84f7215efbc 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -673,6 +673,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { 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") public void testNoThenBlock() throws Exception { 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"); } + @TestMetadata("notApplicableResultInCallChain.kt") + public void testNotApplicableResultInCallChain() throws Exception { + runTest("idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableResultInCallChain.kt"); + } + @TestMetadata("nullCheckSimple.kt") public void testNullCheckSimple() throws Exception { 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"); } + @TestMetadata("resultCall.kt") + public void testResultCall() throws Exception { + runTest("idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/resultCall.kt"); + } + @TestMetadata("rhsEqualsNull.kt") public void testRhsEqualsNull() throws Exception { runTest("idea/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull.kt");