From b2d931fb1f34654af9ed85df9bc1cc1ba7fc7913 Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Thu, 10 Aug 2017 16:38:38 +0300 Subject: [PATCH] Support smart casts after `if (nullable ?: boolean)` #KT-8492 Fixed --- .../types/expressions/DataFlowAnalyzer.java | 10 +++- .../tests/smartCasts/elvis/basicOff.kt | 50 +++++++++++++++++++ .../tests/smartCasts/elvis/basicOff.txt | 14 ++++++ .../tests/smartCasts/elvis/basicOn.kt | 50 +++++++++++++++++++ .../tests/smartCasts/elvis/basicOn.txt | 14 ++++++ .../safecalls/safeAccessReceiverNotNull.kt | 2 +- .../checkers/DiagnosticsTestGenerated.java | 21 ++++++++ .../kotlin/config/LanguageVersionSettings.kt | 1 + 8 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/smartCasts/elvis/basicOff.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/elvis/basicOff.txt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/elvis/basicOn.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/elvis/basicOn.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java index e1862449dea..ea24297d024 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java @@ -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 || diff --git a/compiler/testData/diagnostics/tests/smartCasts/elvis/basicOff.kt b/compiler/testData/diagnostics/tests/smartCasts/elvis/basicOff.kt new file mode 100644 index 00000000000..aa8525d1d2e --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/elvis/basicOff.kt @@ -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.doSomething() + } + else { + + } + if (order?.notExpired() ?: false) { + order.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.hashCode() + } + if (o ?: true) { + + } + else { + o.hashCode() + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/elvis/basicOff.txt b/compiler/testData/diagnostics/tests/smartCasts/elvis/basicOff.txt new file mode 100644 index 00000000000..fb411029f28 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/elvis/basicOff.txt @@ -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 +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/elvis/basicOn.kt b/compiler/testData/diagnostics/tests/smartCasts/elvis/basicOn.kt new file mode 100644 index 00000000000..85fae3e713c --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/elvis/basicOn.kt @@ -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.doSomething() + } + else { + + } + if (order?.notExpired() ?: false) { + order.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.hashCode() + } + if (o ?: true) { + + } + else { + o.hashCode() + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/elvis/basicOn.txt b/compiler/testData/diagnostics/tests/smartCasts/elvis/basicOn.txt new file mode 100644 index 00000000000..fb411029f28 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/elvis/basicOn.txt @@ -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 +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/safecalls/safeAccessReceiverNotNull.kt b/compiler/testData/diagnostics/tests/smartCasts/safecalls/safeAccessReceiverNotNull.kt index 4cc8e6813a7..9186bd29de6 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/safecalls/safeAccessReceiverNotNull.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/safecalls/safeAccessReceiverNotNull.kt @@ -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 diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index afa20412936..004e724ad1d 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -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) diff --git a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt index c98ea941f03..6d130ebf21f 100644 --- a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt +++ b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt @@ -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