Elvis -> if-then: handle case with safe cast separately #KT-17816 Fixed

This commit is contained in:
Mikhail Glukhikh
2017-12-28 16:23:46 +03:00
parent ec60c92fe9
commit 5475f99cec
13 changed files with 133 additions and 6 deletions
@@ -106,7 +106,12 @@ fun KtExpression.convertToIfStatement(condition: KtExpression, thenClause: KtExp
fun KtIfExpression.introduceValueForCondition(occurrenceInThenClause: KtExpression, editor: Editor?) {
val project = this.project
val occurrenceInConditional = (this.condition as KtBinaryExpression).left!!
val condition = condition
val occurrenceInConditional = when (condition) {
is KtBinaryExpression -> condition.left
is KtIsExpression -> condition.leftHandSide
else -> throw AssertionError("Only binary / is expressions are supported here: ${condition?.text}")
}!!
KotlinIntroduceVariableHandler.doRefactoring(project,
editor,
occurrenceInConditional,
@@ -19,13 +19,18 @@ package org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions
import com.intellij.codeInsight.intention.LowPriorityAction
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.intentions.SelfTargetingRangeIntention
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.convertToIfNotNullExpression
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.convertToIfStatement
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.introduceValueForCondition
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isStable
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtPsiUtil
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.TypeUtils
class ElvisToIfThenIntention : SelfTargetingRangeIntention<KtBinaryExpression>(KtBinaryExpression::class.java, "Replace elvis expression with 'if' expression"), LowPriorityAction {
override fun applicabilityRange(element: KtBinaryExpression): TextRange? {
@@ -35,13 +40,57 @@ class ElvisToIfThenIntention : SelfTargetingRangeIntention<KtBinaryExpression>(K
null
}
private fun KtExpression.findSafeCastReceiver(context: BindingContext): KtBinaryExpressionWithTypeRHS? {
var current = this
while (current is KtQualifiedExpression) {
val resolvedCall = current.selectorExpression.getResolvedCall(context) ?: return null
val type = resolvedCall.resultingDescriptor.returnType
if (type != null && TypeUtils.isNullableType(type)) {
return null
}
current = current.receiverExpression
}
current = KtPsiUtil.safeDeparenthesize(current)
return (current as? KtBinaryExpressionWithTypeRHS)?.takeIf {
it.operationReference.getReferencedNameElementType() === KtTokens.AS_SAFE &&
it.right != null
}
}
private fun KtExpression.buildExpressionWithReplacedReceiver(
factory: KtPsiFactory,
newReceiver: KtExpression,
topLevel: Boolean = true
): KtExpression {
if (this !is KtQualifiedExpression) {
return newReceiver
}
return factory.buildExpression(reformat = topLevel) {
appendExpression(receiverExpression.buildExpressionWithReplacedReceiver(factory, newReceiver, topLevel = false))
appendFixedText(".")
appendExpression(selectorExpression)
}
}
override fun applyTo(element: KtBinaryExpression, editor: Editor?) {
val context = element.analyze(BodyResolveMode.PARTIAL)
val left = KtPsiUtil.safeDeparenthesize(element.left!!)
val right = KtPsiUtil.safeDeparenthesize(element.right!!)
val leftIsStable = left.isStable()
val ifStatement = element.convertToIfNotNullExpression(left, left, right)
val leftSafeCastReceiver = left.findSafeCastReceiver(context)
val (leftIsStable, ifStatement) = if (leftSafeCastReceiver != null) {
val newReceiver = leftSafeCastReceiver.left
val typeReference = leftSafeCastReceiver.right!!
val factory = KtPsiFactory(element)
newReceiver.isStable(context) to element.convertToIfStatement(
factory.createExpressionByPattern("$0 is $1", newReceiver, typeReference),
left.buildExpressionWithReplacedReceiver(factory, newReceiver),
right
)
}
else {
left.isStable(context) to element.convertToIfNotNullExpression(left, left, right)
}
if (!leftIsStable) {
ifStatement.introduceValueForCondition(ifStatement.then!!, editor)
@@ -0,0 +1,4 @@
class My(val x: Int?)
fun foo(arg: Any) {
val y = (arg as? My)?.x <caret>?: 42
}
@@ -0,0 +1,4 @@
class My(val x: Int?)
fun foo(arg: Any) {
val y = if ((arg as? My)?.x != null) (arg as? My)?.x else 42
}
@@ -0,0 +1,4 @@
class My(val x: Int)
fun foo(arg: Any) {
val y = (arg as? My)?.x?.hashCode() <caret>?: 42
}
@@ -0,0 +1,4 @@
class My(val x: Int)
fun foo(arg: Any) {
val y = if (arg is My) arg.x.hashCode() else 42
}
@@ -0,0 +1,4 @@
class My(val x: Int)
fun foo(arg: Any) {
val y = (arg as? My)?.x <caret>?: 42
}
@@ -0,0 +1,4 @@
class My(val x: Int)
fun foo(arg: Any) {
val y = if (arg is My) arg.x else 42
}
@@ -0,0 +1,5 @@
class My(var z: Any, val x: Int) {
fun foo() {
val y = (z as? My)?.x <caret>?: 42
}
}
@@ -0,0 +1,6 @@
class My(var z: Any, val x: Int) {
fun foo() {
val z1 = z
val y = if (z1 is My) z1 else 42
}
}
@@ -0,0 +1,4 @@
class My(val x: Int)
fun foo(arg: Any) {
val y = arg as? My <caret>?: My(42)
}
@@ -0,0 +1,4 @@
class My(val x: Int)
fun foo(arg: Any) {
val y = if (arg is My) arg else My(42)
}
@@ -1225,6 +1225,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("fakeSafeCast.kt")
public void testFakeSafeCast() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/elvisToIfThen/fakeSafeCast.kt");
doTest(fileName);
}
@TestMetadata("localValLhs.kt")
public void testLocalValLhs() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/elvisToIfThen/localValLhs.kt");
@@ -1237,6 +1243,24 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("longSafeCast.kt")
public void testLongSafeCast() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/elvisToIfThen/longSafeCast.kt");
doTest(fileName);
}
@TestMetadata("safeCast.kt")
public void testSafeCast() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/elvisToIfThen/safeCast.kt");
doTest(fileName);
}
@TestMetadata("safeCastUnstable.kt")
public void testSafeCastUnstable() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/elvisToIfThen/safeCastUnstable.kt");
doTest(fileName);
}
@TestMetadata("simpleNameExpression.kt")
public void testSimpleNameExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/elvisToIfThen/simpleNameExpression.kt");
@@ -1249,6 +1273,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("simpleSafeCast.kt")
public void testSimpleSafeCast() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/elvisToIfThen/simpleSafeCast.kt");
doTest(fileName);
}
@TestMetadata("topLevelVal.kt")
public void testTopLevelVal() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/elvisToIfThen/topLevelVal.kt");