Support smart casts after if (nullable ?: boolean) #KT-8492 Fixed
This commit is contained in:
@@ -22,6 +22,7 @@ import com.intellij.psi.tree.IElementType;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
||||||
|
import org.jetbrains.kotlin.config.LanguageFeature;
|
||||||
import org.jetbrains.kotlin.config.LanguageVersionSettings;
|
import org.jetbrains.kotlin.config.LanguageVersionSettings;
|
||||||
import org.jetbrains.kotlin.descriptors.*;
|
import org.jetbrains.kotlin.descriptors.*;
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticUtilsKt;
|
import org.jetbrains.kotlin.diagnostics.DiagnosticUtilsKt;
|
||||||
@@ -155,7 +156,7 @@ public class DataFlowAnalyzer {
|
|||||||
}
|
}
|
||||||
result.set(dataFlowInfo);
|
result.set(dataFlowInfo);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
DataFlowInfo expressionFlowInfo = facade.getTypeInfo(expression, context).getDataFlowInfo();
|
DataFlowInfo expressionFlowInfo = facade.getTypeInfo(expression, context).getDataFlowInfo();
|
||||||
KtExpression left = expression.getLeft();
|
KtExpression left = expression.getLeft();
|
||||||
if (left == null) return;
|
if (left == null) return;
|
||||||
@@ -177,6 +178,13 @@ public class DataFlowAnalyzer {
|
|||||||
else if (operationToken == KtTokens.EXCLEQ || operationToken == KtTokens.EXCLEQEQEQ) {
|
else if (operationToken == KtTokens.EXCLEQ || operationToken == KtTokens.EXCLEQEQEQ) {
|
||||||
equals = false;
|
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 != null) {
|
||||||
if (equals == conditionValue) { // this means: equals && conditionValue || !equals && !conditionValue
|
if (equals == conditionValue) { // this means: equals && conditionValue || !equals && !conditionValue
|
||||||
boolean identityEquals = operationToken == KtTokens.EQEQEQ ||
|
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
-1
@@ -1,4 +1,4 @@
|
|||||||
// !LANGUAGE: -SafeCastCheckBoundSmartCasts
|
// !LANGUAGE: -SafeCastCheckBoundSmartCasts -BooleanElvisBoundSmartCasts
|
||||||
// A set of examples for
|
// A set of examples for
|
||||||
// "If the result of a safe call is not null, understand that its receiver is not null"
|
// "If the result of a safe call is not null, understand that its receiver is not null"
|
||||||
// and some other improvements for nullability detection
|
// 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")
|
@TestMetadata("compiler/testData/diagnostics/tests/smartCasts/inference")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ enum class LanguageFeature(
|
|||||||
SoundSmartCastsAfterTry(KOTLIN_1_2),
|
SoundSmartCastsAfterTry(KOTLIN_1_2),
|
||||||
DeprecatedFieldForInvisibleCompanionObject(KOTLIN_1_2),
|
DeprecatedFieldForInvisibleCompanionObject(KOTLIN_1_2),
|
||||||
SafeCastCheckBoundSmartCasts(KOTLIN_1_2),
|
SafeCastCheckBoundSmartCasts(KOTLIN_1_2),
|
||||||
|
BooleanElvisBoundSmartCasts(KOTLIN_1_2),
|
||||||
|
|
||||||
// Experimental features
|
// Experimental features
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user