If then to elvis: now not applicable for is with nullable type #KT-14545 Fixed

This commit is contained in:
Mikhail Glukhikh
2016-10-27 14:47:18 +03:00
parent 89e908dab5
commit 87d265dc1b
4 changed files with 36 additions and 11 deletions
@@ -130,8 +130,7 @@ fun KtPostfixExpression.inlineBaseExpressionIfApplicableWithPrompt(editor: Edito
(this.baseExpression as? KtNameReferenceExpression)?.inlineIfDeclaredLocallyAndOnlyUsedOnceWithPrompt(editor)
}
fun KtExpression.isStableVariable(): Boolean {
val context = this.analyze()
fun KtExpression.isStableVariable(context: BindingContext = this.analyze()): Boolean {
val descriptor = BindingContextUtils.extractVariableDescriptorFromReference(context, this)
return descriptor is VariableDescriptor &&
DataFlowValueFactory.isStableValue(descriptor, DescriptorUtils.getContainingModule(descriptor))
@@ -27,8 +27,10 @@ import org.jetbrains.kotlin.idea.intentions.getLeftMostReceiverExpression
import org.jetbrains.kotlin.idea.intentions.replaceFirstReceiver
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode.Companion.PARTIAL
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.TypeNullability
import org.jetbrains.kotlin.types.typeUtil.nullability
@@ -39,9 +41,9 @@ class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention<KtIfExpre
"Replace 'if' expression with elvis expression"
) {
private fun KtExpression.clausesReplaceableByElvis(firstClause: KtExpression, secondClause: KtExpression) =
private fun KtExpression.clausesReplaceableByElvis(firstClause: KtExpression, secondClause: KtExpression, context: BindingContext) =
!firstClause.isNullOrBlockExpression() &&
(secondClause.evaluatesTo(this) || secondClause.hasFirstReceiverOf(this) && !secondClause.isNullableExpression()) &&
(secondClause.evaluatesTo(this) || secondClause.hasFirstReceiverOf(this) && !secondClause.isNullableExpression(context)) &&
!(firstClause is KtThrowExpression && firstClause.throwsNullPointerExceptionWithNoArguments())
private fun KtExpression.checkedExpression() = when (this) {
@@ -55,18 +57,24 @@ class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention<KtIfExpre
val thenClause = element.then ?: return false
val elseClause = element.`else` ?: return false
val context = condition.analyze(PARTIAL)
val checkedExpression = condition.checkedExpression() ?: return false
if (!checkedExpression.isStableVariable()) return false
if (!checkedExpression.isStableVariable(context)) return false
return when (condition) {
is KtBinaryExpression -> when (condition.operationToken) {
KtTokens.EQEQ -> checkedExpression.clausesReplaceableByElvis(thenClause, elseClause)
KtTokens.EXCLEQ -> checkedExpression.clausesReplaceableByElvis(elseClause, thenClause)
KtTokens.EQEQ -> checkedExpression.clausesReplaceableByElvis(thenClause, elseClause, context)
KtTokens.EXCLEQ -> checkedExpression.clausesReplaceableByElvis(elseClause, thenClause, context)
else -> false
}
is KtIsExpression -> when (condition.isNegated) {
true -> checkedExpression.clausesReplaceableByElvis(thenClause, elseClause)
false -> checkedExpression.clausesReplaceableByElvis(elseClause, thenClause)
is KtIsExpression -> {
if (!context[BindingContext.TYPE, condition.typeReference].isNotNull()) return false
when (condition.isNegated) {
true -> checkedExpression.clausesReplaceableByElvis(thenClause, elseClause, context)
false -> checkedExpression.clausesReplaceableByElvis(elseClause, thenClause, context)
}
}
else -> false
}
@@ -77,7 +85,9 @@ class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention<KtIfExpre
return innerExpression is KtBlockExpression || innerExpression.node.elementType == KtNodeTypes.NULL
}
private fun KtExpression.isNullableExpression() = getType(analyze(PARTIAL))?.nullability() != TypeNullability.NOT_NULL
private fun KtExpression.isNullableExpression(context: BindingContext) = !getType(context).isNotNull()
private fun KotlinType?.isNotNull() = this?.nullability() == TypeNullability.NOT_NULL
private fun KtExpression.hasFirstReceiverOf(receiver: KtExpression): Boolean {
val actualReceiver = (unwrapBlockOrParenthesis() as? KtDotQualifiedExpression)?.getLeftMostReceiverExpression() ?: return false
@@ -0,0 +1,10 @@
// IS_APPLICABLE: false
fun foo(x: CharSequence?) {
val y = if (x is String?) {
x
}
else {
(x as CharSequence).toString()
}<caret>
}
@@ -1302,6 +1302,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("isCheckForNullableType.kt")
public void testIsCheckForNullableType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/isCheckForNullableType.kt");
doTest(fileName);
}
@TestMetadata("isCheckWithSelector.kt")
public void testIsCheckWithSelector() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/isCheckWithSelector.kt");