When now tries to smart cast subject to not-null if contains no null case #KT-7857 Fixed
Some new tests + some fixed tests
This commit is contained in:
@@ -22,11 +22,13 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
||||
import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.psi.*;
|
||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||
import org.jetbrains.kotlin.resolve.BindingContextUtils;
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace;
|
||||
import org.jetbrains.kotlin.resolve.CompileTimeConstantUtils;
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilPackage;
|
||||
import org.jetbrains.kotlin.types.JetType;
|
||||
import org.jetbrains.kotlin.types.TypeUtils;
|
||||
import org.jetbrains.kotlin.types.expressions.JetTypeInfo;
|
||||
|
||||
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry;
|
||||
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumClass;
|
||||
@@ -96,6 +98,27 @@ public final class WhenChecker {
|
||||
return notEmpty;
|
||||
}
|
||||
|
||||
/**
|
||||
* It's assumed that function is called for a final type. In this case the only possible smart cast is to not nullable type.
|
||||
* @return true if type is nullable, and cannot be smart casted
|
||||
*/
|
||||
private static boolean isNullableTypeWithoutPossibleSmartCast(
|
||||
@Nullable JetExpression expression,
|
||||
@NotNull JetType type,
|
||||
@NotNull BindingContext context
|
||||
) {
|
||||
if (expression == null) return false; // Normally should not happen
|
||||
if (!TypeUtils.isNullableType(type)) return false;
|
||||
// We cannot read data flow information here due to lack of inputs (module descriptor is necessary)
|
||||
if (context.get(BindingContext.SMARTCAST, expression) != null) {
|
||||
// We have smart cast from enum or boolean to something
|
||||
// Not very nice but we *can* decide it was smart cast to not-null
|
||||
// because both enum and boolean are final
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isWhenExhaustive(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) {
|
||||
JetType type = whenSubjectType(expression, trace.getBindingContext());
|
||||
if (type == null) return false;
|
||||
@@ -115,10 +138,11 @@ public final class WhenChecker {
|
||||
exhaustive = isWhenOnEnumExhaustive(expression, trace, enumClassDescriptor);
|
||||
}
|
||||
if (exhaustive) {
|
||||
if (!TypeUtils.isNullableType(type)
|
||||
if (// Flexible (nullable) enum types are also counted as exhaustive
|
||||
(enumClassDescriptor != null && isFlexible(type))
|
||||
|| containsNullCase(expression, trace)
|
||||
// Flexible (nullable) enum types are also counted as exhaustive
|
||||
|| (enumClassDescriptor != null && isFlexible(type))) {
|
||||
|| !isNullableTypeWithoutPossibleSmartCast(expression.getSubjectExpression(), type, trace.getBindingContext())) {
|
||||
|
||||
trace.record(BindingContext.EXHAUSTIVE_WHEN, expression);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -191,6 +191,20 @@ public class DataFlowUtils {
|
||||
return expressionType;
|
||||
}
|
||||
|
||||
JetType possibleType = checkPossibleCast(expressionType, expression, c);
|
||||
if (possibleType != null) return possibleType;
|
||||
|
||||
c.trace.report(TYPE_MISMATCH.on(expression, c.expectedType, expressionType));
|
||||
if (hasError != null) hasError.set(true);
|
||||
return expressionType;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static JetType checkPossibleCast(
|
||||
@NotNull JetType expressionType,
|
||||
@NotNull JetExpression expression,
|
||||
@NotNull ResolutionContext c
|
||||
) {
|
||||
DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, expressionType, c);
|
||||
|
||||
for (JetType possibleType : c.dataFlowInfo.getPossibleTypes(dataFlowValue)) {
|
||||
@@ -199,9 +213,8 @@ public class DataFlowUtils {
|
||||
return possibleType;
|
||||
}
|
||||
}
|
||||
c.trace.report(TYPE_MISMATCH.on(expression, c.expectedType, expressionType));
|
||||
if (hasError != null) hasError.set(true);
|
||||
return expressionType;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void recordExpectedType(@NotNull BindingTrace trace, @NotNull JetExpression expression, @NotNull JetType expectedType) {
|
||||
|
||||
+7
-2
@@ -89,6 +89,11 @@ public class PatternMatchingTypingVisitor extends ExpressionTypingVisitor {
|
||||
JetTypeInfo typeInfo = facade.safeGetTypeInfo(subjectExpression, context);
|
||||
loopBreakContinuePossible = typeInfo.getJumpOutPossible();
|
||||
subjectType = typeInfo.getType();
|
||||
assert subjectType != null;
|
||||
if (TypeUtils.isNullableType(subjectType) && !WhenChecker.containsNullCase(expression, context.trace)) {
|
||||
ExpressionTypingContext subjectContext = context.replaceExpectedType(TypeUtils.makeNotNullable(subjectType));
|
||||
DataFlowUtils.checkPossibleCast(subjectType, JetPsiUtil.safeDeparenthesize(subjectExpression, false), subjectContext);
|
||||
}
|
||||
context = context.replaceDataFlowInfo(typeInfo.getDataFlowInfo());
|
||||
}
|
||||
DataFlowValue subjectDataFlowValue = subjectExpression != null
|
||||
@@ -138,8 +143,8 @@ public class PatternMatchingTypingVisitor extends ExpressionTypingVisitor {
|
||||
|
||||
return TypeInfoFactoryPackage.createTypeInfo(expressionTypes.isEmpty() ? null : DataFlowUtils.checkType(
|
||||
DataFlowUtils.checkImplicitCast(
|
||||
CommonSupertypes.commonSupertype(expressionTypes), expression,
|
||||
contextWithExpectedType, isStatement),
|
||||
CommonSupertypes.commonSupertype(expressionTypes), expression,
|
||||
contextWithExpectedType, isStatement),
|
||||
expression, contextWithExpectedType),
|
||||
commonDataFlowInfo,
|
||||
loopBreakContinuePossible,
|
||||
|
||||
@@ -4,7 +4,7 @@ fun foo() {
|
||||
val x: Int? = null
|
||||
|
||||
if (x != null) {
|
||||
when (x) {
|
||||
when (<!DEBUG_INFO_SMARTCAST!>x<!>) {
|
||||
0 -> bar(<!DEBUG_INFO_SMARTCAST!>x<!>)
|
||||
else -> {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// KT-7857: when exhaustiveness does not take previous nullability checks into account
|
||||
enum class X { A, B }
|
||||
fun foo(arg: X?): Int {
|
||||
if (arg != null) {
|
||||
return when (<!DEBUG_INFO_SMARTCAST!>arg<!>) {
|
||||
X.A -> 1
|
||||
X.B -> 2
|
||||
// else or null branch should not be required here!
|
||||
}
|
||||
}
|
||||
else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package
|
||||
|
||||
internal fun foo(/*0*/ arg: X?): kotlin.Int
|
||||
|
||||
internal final enum class X : kotlin.Enum<X> {
|
||||
public enum entry A : X {
|
||||
private constructor A()
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: X): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public enum entry B : X {
|
||||
private constructor B()
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: X): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
private constructor X()
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: X): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
|
||||
// Static members
|
||||
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): X
|
||||
public final /*synthesized*/ fun values(): kotlin.Array<X>
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// KT-7857: when exhaustiveness does not take previous nullability checks into account
|
||||
enum class X { A, B }
|
||||
fun foo(arg: X?): Int {
|
||||
if (arg == null) {
|
||||
return 0
|
||||
}
|
||||
return when (<!DEBUG_INFO_SMARTCAST!>arg<!>) {
|
||||
X.A -> 1
|
||||
X.B -> 2
|
||||
// else or null branch should not be required here!
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package
|
||||
|
||||
internal fun foo(/*0*/ arg: X?): kotlin.Int
|
||||
|
||||
internal final enum class X : kotlin.Enum<X> {
|
||||
public enum entry A : X {
|
||||
private constructor A()
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: X): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public enum entry B : X {
|
||||
private constructor B()
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: X): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
private constructor X()
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: X): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
|
||||
// Static members
|
||||
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): X
|
||||
public final /*synthesized*/ fun values(): kotlin.Array<X>
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// KT-7857: when exhaustiveness does not take previous nullability checks into account
|
||||
fun foo(arg: Boolean?): Int {
|
||||
if (arg != null) {
|
||||
return when (<!DEBUG_INFO_SMARTCAST!>arg<!>) {
|
||||
true -> 1
|
||||
false -> 0
|
||||
// else or null branch should not be required here!
|
||||
}
|
||||
}
|
||||
else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package
|
||||
|
||||
internal fun foo(/*0*/ arg: kotlin.Boolean?): kotlin.Int
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// KT-7857: when exhaustiveness does not take previous nullability checks into account
|
||||
enum class X { A, B }
|
||||
fun foo(arg: X?): Int {
|
||||
if (arg == null) {
|
||||
return 0
|
||||
}
|
||||
else {
|
||||
return when (<!DEBUG_INFO_SMARTCAST!>arg<!>) {
|
||||
X.A -> 1
|
||||
X.B -> 2
|
||||
// else or null branch should not be required here!
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package
|
||||
|
||||
internal fun foo(/*0*/ arg: X?): kotlin.Int
|
||||
|
||||
internal final enum class X : kotlin.Enum<X> {
|
||||
public enum entry A : X {
|
||||
private constructor A()
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: X): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public enum entry B : X {
|
||||
private constructor B()
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: X): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
private constructor X()
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: X): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
|
||||
// Static members
|
||||
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): X
|
||||
public final /*synthesized*/ fun values(): kotlin.Array<X>
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// KT-7857: when exhaustiveness does not take previous nullability checks into account
|
||||
enum class X { A, B }
|
||||
fun foo(arg: X?): Int {
|
||||
if (arg != null) {
|
||||
return <!NO_ELSE_IN_WHEN!>when<!> (<!DEBUG_INFO_SMARTCAST!>arg<!>) {
|
||||
X.B -> 2
|
||||
}
|
||||
}
|
||||
else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package
|
||||
|
||||
internal fun foo(/*0*/ arg: X?): kotlin.Int
|
||||
|
||||
internal final enum class X : kotlin.Enum<X> {
|
||||
public enum entry A : X {
|
||||
private constructor A()
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: X): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public enum entry B : X {
|
||||
private constructor B()
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: X): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
private constructor X()
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: X): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
|
||||
// Static members
|
||||
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): X
|
||||
public final /*synthesized*/ fun values(): kotlin.Array<X>
|
||||
}
|
||||
@@ -13668,6 +13668,30 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("ExhaustiveWithNullabilityCheck.kt")
|
||||
public void testExhaustiveWithNullabilityCheck() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveWithNullabilityCheck.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("ExhaustiveWithNullabilityCheckBefore.kt")
|
||||
public void testExhaustiveWithNullabilityCheckBefore() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveWithNullabilityCheckBefore.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("ExhaustiveWithNullabilityCheckBoolean.kt")
|
||||
public void testExhaustiveWithNullabilityCheckBoolean() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveWithNullabilityCheckBoolean.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("ExhaustiveWithNullabilityCheckElse.kt")
|
||||
public void testExhaustiveWithNullabilityCheckElse() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ExhaustiveWithNullabilityCheckElse.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("kt4434.kt")
|
||||
public void testKt4434() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/kt4434.kt");
|
||||
@@ -13752,6 +13776,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("NonExhaustiveWithNullabilityCheck.kt")
|
||||
public void testNonExhaustiveWithNullabilityCheck() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/NonExhaustiveWithNullabilityCheck.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("PropertyNotInitialized.kt")
|
||||
public void testPropertyNotInitialized() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/PropertyNotInitialized.kt");
|
||||
|
||||
Reference in New Issue
Block a user