diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/SafeAccessToIfThenIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/SafeAccessToIfThenIntention.kt index 160e1e1ff40..39055b09851 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/SafeAccessToIfThenIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/SafeAccessToIfThenIntention.kt @@ -21,16 +21,24 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.util.TextRange import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.core.replaced +import org.jetbrains.kotlin.idea.intentions.ReplaceSingleLineLetIntention import org.jetbrains.kotlin.idea.intentions.SelfTargetingRangeIntention import org.jetbrains.kotlin.idea.intentions.branchedTransformations.convertToIfNotNullExpression import org.jetbrains.kotlin.idea.intentions.branchedTransformations.introduceValueForCondition import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isStableSimpleExpression +import org.jetbrains.kotlin.idea.intentions.callExpression import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsStatement +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode -class SafeAccessToIfThenIntention : SelfTargetingRangeIntention(KtSafeQualifiedExpression::class.java, "Replace safe access expression with 'if' expression"), LowPriorityAction { +class SafeAccessToIfThenIntention : SelfTargetingRangeIntention( + KtSafeQualifiedExpression::class.java, + "Replace safe access expression with 'if' expression" +), LowPriorityAction { override fun applicabilityRange(element: KtSafeQualifiedExpression): TextRange? { if (element.selectorExpression == null) return null return element.operationTokenNode.textRange @@ -57,14 +65,33 @@ class SafeAccessToIfThenIntention : SelfTargetingRangeIntention + ((ifExpression.then as? KtBinaryExpression)?.left as? KtDotQualifiedExpression)?.receiverExpression + isRedundantLetCallRemoved -> { + val context = ifExpression.analyze(BodyResolveMode.PARTIAL) + val descriptor = (ifExpression.condition as? KtBinaryExpression)?.left?.getResolvedCall(context)?.resultingDescriptor + ifExpression.then?.findDescendantOfType { + it.getReferencedNameAsName() == descriptor?.name && it.getResolvedCall(context)?.resultingDescriptor == descriptor + } + } + else -> + (ifExpression.then as? KtDotQualifiedExpression)?.receiverExpression + } if (valueToExtract != null) ifExpression.introduceValueForCondition(valueToExtract, editor) } - } + + private fun KtIfExpression.removeRedundantLetCallIfPossible(editor: Editor?): Boolean { + val callExpression = (then as? KtQualifiedExpression)?.callExpression ?: return false + if (callExpression.calleeExpression?.text != "let") return false + val replaceSingleLineLetIntention = ReplaceSingleLineLetIntention() + if (!replaceSingleLineLetIntention.isApplicableTo(callExpression)) return false + replaceSingleLineLetIntention.applyTo(callExpression, editor) + return true + } + } diff --git a/idea/testData/intentions/branched/safeAccessToIfThen/let.kt b/idea/testData/intentions/branched/safeAccessToIfThen/let.kt new file mode 100644 index 00000000000..e5fa34ef90c --- /dev/null +++ b/idea/testData/intentions/branched/safeAccessToIfThen/let.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(value: Int?): Int? { + return value?.let { it + 1 } +} \ No newline at end of file diff --git a/idea/testData/intentions/branched/safeAccessToIfThen/let.kt.after b/idea/testData/intentions/branched/safeAccessToIfThen/let.kt.after new file mode 100644 index 00000000000..5d6e2034087 --- /dev/null +++ b/idea/testData/intentions/branched/safeAccessToIfThen/let.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(value: Int?): Int? { + return if (value != null) value + 1 else null +} \ No newline at end of file diff --git a/idea/testData/intentions/branched/safeAccessToIfThen/let2.kt b/idea/testData/intentions/branched/safeAccessToIfThen/let2.kt new file mode 100644 index 00000000000..45ee399b291 --- /dev/null +++ b/idea/testData/intentions/branched/safeAccessToIfThen/let2.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +fun foo(value: Int?): Int? { + return value?.let { baz(it) } +} + +fun baz(i: Int) = i + i \ No newline at end of file diff --git a/idea/testData/intentions/branched/safeAccessToIfThen/let2.kt.after b/idea/testData/intentions/branched/safeAccessToIfThen/let2.kt.after new file mode 100644 index 00000000000..d9ab36ad815 --- /dev/null +++ b/idea/testData/intentions/branched/safeAccessToIfThen/let2.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +fun foo(value: Int?): Int? { + return if (value != null) baz(value) else null +} + +fun baz(i: Int) = i + i \ No newline at end of file diff --git a/idea/testData/intentions/branched/safeAccessToIfThen/let3.kt b/idea/testData/intentions/branched/safeAccessToIfThen/let3.kt new file mode 100644 index 00000000000..a73d728c2d0 --- /dev/null +++ b/idea/testData/intentions/branched/safeAccessToIfThen/let3.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(a: Int, b: Int?): Int? { + return b?.let { a + b } +} \ No newline at end of file diff --git a/idea/testData/intentions/branched/safeAccessToIfThen/let3.kt.after b/idea/testData/intentions/branched/safeAccessToIfThen/let3.kt.after new file mode 100644 index 00000000000..5f519973a2e --- /dev/null +++ b/idea/testData/intentions/branched/safeAccessToIfThen/let3.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(a: Int, b: Int?): Int? { + return if (b != null) b.let { a + b } else null +} \ No newline at end of file diff --git a/idea/testData/intentions/branched/safeAccessToIfThen/letNotRedundant.kt b/idea/testData/intentions/branched/safeAccessToIfThen/letNotRedundant.kt new file mode 100644 index 00000000000..02fd950f230 --- /dev/null +++ b/idea/testData/intentions/branched/safeAccessToIfThen/letNotRedundant.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +fun foo(value: Int?): Int? { + return value?.let { + println() + it + 1 + } +} \ No newline at end of file diff --git a/idea/testData/intentions/branched/safeAccessToIfThen/letNotRedundant.kt.after b/idea/testData/intentions/branched/safeAccessToIfThen/letNotRedundant.kt.after new file mode 100644 index 00000000000..c99ae35d448 --- /dev/null +++ b/idea/testData/intentions/branched/safeAccessToIfThen/letNotRedundant.kt.after @@ -0,0 +1,8 @@ +// WITH_RUNTIME + +fun foo(value: Int?): Int? { + return if (value != null) value.let { + println() + it + 1 + } else null +} \ No newline at end of file diff --git a/idea/testData/intentions/branched/safeAccessToIfThen/letTopLevelVar.kt b/idea/testData/intentions/branched/safeAccessToIfThen/letTopLevelVar.kt new file mode 100644 index 00000000000..14280ec39d1 --- /dev/null +++ b/idea/testData/intentions/branched/safeAccessToIfThen/letTopLevelVar.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +var a: String? = "A" +fun main(args: Array) { + a?.let { it.length + 1 } +} \ No newline at end of file diff --git a/idea/testData/intentions/branched/safeAccessToIfThen/letTopLevelVar.kt.after b/idea/testData/intentions/branched/safeAccessToIfThen/letTopLevelVar.kt.after new file mode 100644 index 00000000000..e9bc7ce2775 --- /dev/null +++ b/idea/testData/intentions/branched/safeAccessToIfThen/letTopLevelVar.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +var a: String? = "A" +fun main(args: Array) { + val a1 = a + if (a1 != null) a1.length + 1 +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index bb95550fe36..530f5234011 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -2796,6 +2796,31 @@ public class IntentionTestGenerated extends AbstractIntentionTest { runTest("idea/testData/intentions/branched/safeAccessToIfThen/equality.kt"); } + @TestMetadata("let.kt") + public void testLet() throws Exception { + runTest("idea/testData/intentions/branched/safeAccessToIfThen/let.kt"); + } + + @TestMetadata("let2.kt") + public void testLet2() throws Exception { + runTest("idea/testData/intentions/branched/safeAccessToIfThen/let2.kt"); + } + + @TestMetadata("let3.kt") + public void testLet3() throws Exception { + runTest("idea/testData/intentions/branched/safeAccessToIfThen/let3.kt"); + } + + @TestMetadata("letNotRedundant.kt") + public void testLetNotRedundant() throws Exception { + runTest("idea/testData/intentions/branched/safeAccessToIfThen/letNotRedundant.kt"); + } + + @TestMetadata("letTopLevelVar.kt") + public void testLetTopLevelVar() throws Exception { + runTest("idea/testData/intentions/branched/safeAccessToIfThen/letTopLevelVar.kt"); + } + @TestMetadata("localValAsReceiver.kt") public void testLocalValAsReceiver() throws Exception { runTest("idea/testData/intentions/branched/safeAccessToIfThen/localValAsReceiver.kt");