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.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
-1
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user