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:
Mikhail Glukhikh
2015-06-01 10:40:44 +03:00
parent de12771c0f
commit dea2ec9da6
15 changed files with 297 additions and 9 deletions
@@ -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) {
@@ -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>
}
@@ -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!
}
}
@@ -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>
}
@@ -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
}
}
@@ -0,0 +1,3 @@
package
internal fun foo(/*0*/ arg: kotlin.Boolean?): kotlin.Int
@@ -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!
}
}
}
@@ -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>
}
@@ -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
}
}
@@ -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");