Support smart casts after if (nullable ?: boolean) #KT-8492 Fixed

This commit is contained in:
Mikhail Glukhikh
2017-08-10 16:38:38 +03:00
parent 9be5cf89b4
commit b2d931fb1f
8 changed files with 160 additions and 2 deletions
@@ -22,6 +22,7 @@ import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.config.LanguageFeature;
import org.jetbrains.kotlin.config.LanguageVersionSettings;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.diagnostics.DiagnosticUtilsKt;
@@ -155,7 +156,7 @@ public class DataFlowAnalyzer {
}
result.set(dataFlowInfo);
}
else {
else {
DataFlowInfo expressionFlowInfo = facade.getTypeInfo(expression, context).getDataFlowInfo();
KtExpression left = expression.getLeft();
if (left == null) return;
@@ -177,6 +178,13 @@ public class DataFlowAnalyzer {
else if (operationToken == KtTokens.EXCLEQ || operationToken == KtTokens.EXCLEQEQEQ) {
equals = false;
}
else if (operationToken == KtTokens.ELVIS &&
languageVersionSettings.supportsFeature(LanguageFeature.BooleanElvisBoundSmartCasts) &&
right instanceof KtConstantExpression &&
KotlinBuiltIns.isBoolean(rhsType)) {
// ?: false is equivalent to == true, ?: true is equivalent to != false
equals = KtPsiUtil.isFalseConstant(right);
}
if (equals != null) {
if (equals == conditionValue) { // this means: equals && conditionValue || !equals && !conditionValue
boolean identityEquals = operationToken == KtTokens.EQEQEQ ||
@@ -0,0 +1,50 @@
// !LANGUAGE: -BooleanElvisBoundSmartCasts
interface Order {
val expired: Boolean?
fun notExpired(): Boolean
fun doSomething()
}
fun foo(o: Any) {
val order = o as? Order
if (order?.expired ?: false) {
order<!UNSAFE_CALL!>.<!>doSomething()
}
else {
}
if (order?.notExpired() ?: false) {
order<!UNSAFE_CALL!>.<!>doSomething()
}
}
fun bar(o: Any) {
val order = o as? Order
if (order?.expired ?: true) {
}
else {
order!!.doSomething()
}
if (order?.notExpired() ?: true) {
}
else {
order!!.doSomething()
}
}
fun baz(o: Boolean?) {
if (o ?: false) {
o<!UNSAFE_CALL!>.<!>hashCode()
}
if (o ?: true) {
}
else {
o<!UNSAFE_CALL!>.<!>hashCode()
}
}
@@ -0,0 +1,14 @@
package
public fun bar(/*0*/ o: kotlin.Any): kotlin.Unit
public fun baz(/*0*/ o: kotlin.Boolean?): kotlin.Unit
public fun foo(/*0*/ o: kotlin.Any): kotlin.Unit
public interface Order {
public abstract val expired: kotlin.Boolean?
public abstract fun doSomething(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public abstract fun notExpired(): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,50 @@
// !LANGUAGE: +BooleanElvisBoundSmartCasts
interface Order {
val expired: Boolean?
fun notExpired(): Boolean
fun doSomething()
}
fun foo(o: Any) {
val order = o as? Order
if (order?.expired ?: false) {
<!DEBUG_INFO_SMARTCAST!>order<!>.doSomething()
}
else {
}
if (order?.notExpired() ?: false) {
<!DEBUG_INFO_SMARTCAST!>order<!>.doSomething()
}
}
fun bar(o: Any) {
val order = o as? Order
if (order?.expired ?: true) {
}
else {
order<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>.doSomething()
}
if (order?.notExpired() ?: true) {
}
else {
order<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>.doSomething()
}
}
fun baz(o: Boolean?) {
if (o ?: false) {
<!DEBUG_INFO_SMARTCAST!>o<!>.hashCode()
}
if (o ?: true) {
}
else {
<!DEBUG_INFO_SMARTCAST!>o<!>.hashCode()
}
}
@@ -0,0 +1,14 @@
package
public fun bar(/*0*/ o: kotlin.Any): kotlin.Unit
public fun baz(/*0*/ o: kotlin.Boolean?): kotlin.Unit
public fun foo(/*0*/ o: kotlin.Any): kotlin.Unit
public interface Order {
public abstract val expired: kotlin.Boolean?
public abstract fun doSomething(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public abstract fun notExpired(): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -1,4 +1,4 @@
// !LANGUAGE: -SafeCastCheckBoundSmartCasts
// !LANGUAGE: -SafeCastCheckBoundSmartCasts -BooleanElvisBoundSmartCasts
// A set of examples for
// "If the result of a safe call is not null, understand that its receiver is not null"
// and some other improvements for nullability detection
@@ -20299,6 +20299,27 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
}
}
@TestMetadata("compiler/testData/diagnostics/tests/smartCasts/elvis")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Elvis extends AbstractDiagnosticsTest {
public void testAllFilesPresentInElvis() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/smartCasts/elvis"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("basicOff.kt")
public void testBasicOff() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/elvis/basicOff.kt");
doTest(fileName);
}
@TestMetadata("basicOn.kt")
public void testBasicOn() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/elvis/basicOn.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/diagnostics/tests/smartCasts/inference")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -55,6 +55,7 @@ enum class LanguageFeature(
SoundSmartCastsAfterTry(KOTLIN_1_2),
DeprecatedFieldForInvisibleCompanionObject(KOTLIN_1_2),
SafeCastCheckBoundSmartCasts(KOTLIN_1_2),
BooleanElvisBoundSmartCasts(KOTLIN_1_2),
// Experimental features