diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java index 7e0b0003b02..506f33a618d 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/JetControlFlowProcessor.java @@ -837,8 +837,7 @@ public class JetControlFlowProcessor { } } builder.bindLabel(doneLabel); - boolean isWhenExhaust = WhenChecker.isWhenExhaustive(expression, trace); - if (!hasElseOrIrrefutableBranch && !isWhenExhaust) { + if (!hasElseOrIrrefutableBranch && WhenChecker.mustHaveElse(expression, trace)) { trace.report(NO_ELSE_IN_WHEN.on(expression)); } } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/WhenChecker.java b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/WhenChecker.java index f929ea194fb..f567a9f556b 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/cfg/WhenChecker.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/cfg/WhenChecker.java @@ -26,6 +26,7 @@ import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.resolve.BindingTrace; import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; import java.util.Collection; @@ -35,7 +36,16 @@ public final class WhenChecker { private WhenChecker() { } - public static boolean isWhenExhaustive(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) { + public static boolean mustHaveElse(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) { + JetType expectedType = trace.get(BindingContext.EXPECTED_EXPRESSION_TYPE, expression); + boolean isUnit = expectedType != null && KotlinBuiltIns.getInstance().isUnit(expectedType); + // Some "statements" are actually expressions returned from lambdas, their expected types are non-null + boolean isStatement = Boolean.TRUE.equals(trace.get(BindingContext.STATEMENT, expression)) && expectedType == null; + + return !isUnit && !isStatement && !isWhenExhaustive(expression, trace); + } + + private static boolean isWhenExhaustive(@NotNull JetWhenExpression expression, @NotNull BindingTrace trace) { JetExpression subjectExpression = expression.getSubjectExpression(); if (subjectExpression == null) return false; JetType type = trace.get(BindingContext.EXPRESSION_TYPE, subjectExpression); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/DataFlowUtils.java b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/DataFlowUtils.java index 549c293d573..e3790780899 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/DataFlowUtils.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/DataFlowUtils.java @@ -42,6 +42,7 @@ import static org.jetbrains.jet.lang.diagnostics.Errors.*; import static org.jetbrains.jet.lang.resolve.BindingContext.AUTOCAST; import static org.jetbrains.jet.lang.resolve.calls.context.ContextDependency.INDEPENDENT; import static org.jetbrains.jet.lang.types.TypeUtils.NO_EXPECTED_TYPE; +import static org.jetbrains.jet.lang.types.TypeUtils.UNIT_EXPECTED_TYPE; import static org.jetbrains.jet.lang.types.TypeUtils.noExpectedType; public class DataFlowUtils { @@ -162,9 +163,7 @@ public class DataFlowUtils { ) { // non-block 'if' branches are wrapped in a block, but here genuine expressions (not wrappers) should be checked JetExpression expression = JetPsiUtil.unwrapFromBlock(possiblyWrappedInBlockExpression); - if (!noExpectedType(expectedType)) { - trace.record(BindingContext.EXPECTED_EXPRESSION_TYPE, expression, expectedType); - } + recordExpectedType(trace, expression, expectedType); if (expressionType == null || noExpectedType(expectedType) || JetTypeChecker.INSTANCE.isSubtypeOf(expressionType, expectedType)) { @@ -196,6 +195,13 @@ public class DataFlowUtils { return expressionType; } + public static void recordExpectedType(@NotNull BindingTrace trace, @NotNull JetExpression expression, @NotNull JetType expectedType) { + if (expectedType != NO_EXPECTED_TYPE) { + JetType normalizeExpectedType = expectedType == UNIT_EXPECTED_TYPE ? KotlinBuiltIns.getInstance().getUnitType() : expectedType; + trace.record(BindingContext.EXPECTED_EXPRESSION_TYPE, expression, normalizeExpectedType); + } + } + @NotNull public static JetTypeInfo checkStatementType(@NotNull JetExpression expression, @NotNull ResolutionContext context, @NotNull DataFlowInfo dataFlowInfo) { return JetTypeInfo.create(checkStatementType(expression, context), dataFlowInfo); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/PatternMatchingTypingVisitor.java b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/PatternMatchingTypingVisitor.java index 4db11b2cda7..7b963e81d25 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/PatternMatchingTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/PatternMatchingTypingVisitor.java @@ -66,6 +66,8 @@ public class PatternMatchingTypingVisitor extends ExpressionTypingVisitor { } public JetTypeInfo visitWhenExpression(JetWhenExpression expression, ExpressionTypingContext contextWithExpectedType, boolean isStatement) { + DataFlowUtils.recordExpectedType(contextWithExpectedType.trace, expression, contextWithExpectedType.expectedType); + ExpressionTypingContext context = contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT); // TODO :change scope according to the bound value in the when header JetExpression subjectExpression = expression.getSubjectExpression(); @@ -277,7 +279,8 @@ public class PatternMatchingTypingVisitor extends ExpressionTypingVisitor { if (typeReferenceAfterIs == null) { return noChange(context); } - JetType type = context.expressionTypingServices.getTypeResolver().resolveType(context.scope, typeReferenceAfterIs, context.trace, true); + JetType type = context.expressionTypingServices.getTypeResolver().resolveType(context.scope, typeReferenceAfterIs, context.trace, + true); if (!subjectType.isNullable() && type.isNullable()) { JetTypeElement element = typeReferenceAfterIs.getTypeElement(); assert element instanceof JetNullableType : "element must be instance of " + JetNullableType.class.getName(); diff --git a/compiler/testData/codegen/box/when/noElseAssigned.kt b/compiler/testData/codegen/box/when/noElseAssigned.kt new file mode 100644 index 00000000000..40a0dc030bc --- /dev/null +++ b/compiler/testData/codegen/box/when/noElseAssigned.kt @@ -0,0 +1,9 @@ +fun box(): String { + var r = "Fail 0" + val x = 1 + val y: Unit = when (x) { + 1 -> { r = "OK" } + 2 -> { r = "Fail 1" } + } + return r +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/when/noElseInRetunedExpression.kt b/compiler/testData/codegen/box/when/noElseInRetunedExpression.kt new file mode 100644 index 00000000000..c83cd49bda5 --- /dev/null +++ b/compiler/testData/codegen/box/when/noElseInRetunedExpression.kt @@ -0,0 +1,11 @@ +var r = "Fail 0" + +fun foo(x: Int) { + return when (x) { + 1 -> { r = "OK" } + } +} +fun box(): String { + foo(1) + return r +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/when/noElseInStatement.kt b/compiler/testData/codegen/box/when/noElseInStatement.kt new file mode 100644 index 00000000000..9ab1c3fef63 --- /dev/null +++ b/compiler/testData/codegen/box/when/noElseInStatement.kt @@ -0,0 +1,7 @@ +fun box(): String { + val x = 1 + when (x) { + 1 -> return "OK" + } + return "Fail 1" +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/NoElseExpectedUnit.kt b/compiler/testData/diagnostics/tests/when/NoElseExpectedUnit.kt new file mode 100644 index 00000000000..4b240e601c9 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/NoElseExpectedUnit.kt @@ -0,0 +1,7 @@ +fun foo(x: Int) { + val y: Unit = when (x) { + 2 -> {} + 3 -> {} + } + return y +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/NoElseNoExpectedType.kt b/compiler/testData/diagnostics/tests/when/NoElseNoExpectedType.kt new file mode 100644 index 00000000000..7fa7ba2c583 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/NoElseNoExpectedType.kt @@ -0,0 +1,6 @@ +fun foo(x: Int): Any { + val v = when (x) { + 2 -> 0 + } + return v +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/NoElseReturnedCoercionToUnit.kt b/compiler/testData/diagnostics/tests/when/NoElseReturnedCoercionToUnit.kt new file mode 100644 index 00000000000..695f64ab91c --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/NoElseReturnedCoercionToUnit.kt @@ -0,0 +1,11 @@ +fun foo(x: Int) { + r { + when (x) { + 2 -> 0 + } + } +} + +fun r(f: () -> Unit) { + f() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/NoElseReturnedFromLambdaExpectedInt.kt b/compiler/testData/diagnostics/tests/when/NoElseReturnedFromLambdaExpectedInt.kt new file mode 100644 index 00000000000..44dc8fb1315 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/NoElseReturnedFromLambdaExpectedInt.kt @@ -0,0 +1,11 @@ +fun foo(x: Int) { + r { + when (x) { + 2 -> 0 + } + } +} + +fun r(f: () -> Int) { + f() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/NoElseReturnedNonUnit.kt b/compiler/testData/diagnostics/tests/when/NoElseReturnedNonUnit.kt new file mode 100644 index 00000000000..c0f1d665ae8 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/NoElseReturnedNonUnit.kt @@ -0,0 +1,5 @@ +fun foo(x: Int): Any { + return when (x) { + 2 -> 0 + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/NoElseReturnedUnit.kt b/compiler/testData/diagnostics/tests/when/NoElseReturnedUnit.kt new file mode 100644 index 00000000000..606fe43e816 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/NoElseReturnedUnit.kt @@ -0,0 +1,6 @@ +fun foo(x: Int) { + return when (x) { + 2 -> {} + 3 -> {} + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/when/NoElseWhenStatement.kt b/compiler/testData/diagnostics/tests/when/NoElseWhenStatement.kt new file mode 100644 index 00000000000..000a829fa71 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/NoElseWhenStatement.kt @@ -0,0 +1,6 @@ +fun foo(x: Int) { + when (x) { + 2 -> {} + 3 -> {} + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/When.kt b/compiler/testData/diagnostics/tests/when/When.kt similarity index 95% rename from compiler/testData/diagnostics/tests/When.kt rename to compiler/testData/diagnostics/tests/when/When.kt index e557c385a29..ec84bc61415 100644 --- a/compiler/testData/diagnostics/tests/When.kt +++ b/compiler/testData/diagnostics/tests/when/When.kt @@ -24,7 +24,7 @@ fun test() { val x = 1; val s = ""; - when (x) { + when (x) { s -> 1 "" -> 1 x -> 1 diff --git a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java index 4af452edd9b..7ebfa281de4 100644 --- a/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/checkers/JetDiagnosticsTestGenerated.java @@ -33,7 +33,7 @@ import org.jetbrains.jet.checkers.AbstractDiagnosticsTestWithEagerResolve; @InnerTestClasses({JetDiagnosticsTestGenerated.Tests.class, JetDiagnosticsTestGenerated.Script.class}) public class JetDiagnosticsTestGenerated extends AbstractDiagnosticsTestWithEagerResolve { @TestMetadata("compiler/testData/diagnostics/tests") - @InnerTestClasses({Tests.Annotations.class, Tests.BackingField.class, Tests.CallableReference.class, Tests.Cast.class, Tests.CheckArguments.class, Tests.ControlFlowAnalysis.class, Tests.ControlStructures.class, Tests.DataClasses.class, Tests.DataFlow.class, Tests.DataFlowInfoTraversal.class, Tests.DeclarationChecks.class, Tests.DelegatedProperty.class, Tests.Deparenthesize.class, Tests.Enum.class, Tests.Extensions.class, Tests.FunctionLiterals.class, Tests.Generics.class, Tests.IncompleteCode.class, Tests.Inference.class, Tests.Infos.class, Tests.Inner.class, Tests.J_k.class, Tests.Jdk_annotations.class, Tests.Library.class, Tests.NullabilityAndAutoCasts.class, Tests.NullableTypes.class, Tests.Numbers.class, Tests.Objects.class, Tests.OperatorsOverloading.class, Tests.Overload.class, Tests.Override.class, Tests.Recovery.class, Tests.Redeclarations.class, Tests.Regressions.class, Tests.Resolve.class, Tests.Scopes.class, Tests.SenselessComparison.class, Tests.Shadowing.class, Tests.SmartCasts.class, Tests.Substitutions.class, Tests.Subtyping.class, Tests.ThisAndSuper.class, Tests.Varargs.class}) + @InnerTestClasses({Tests.Annotations.class, Tests.BackingField.class, Tests.CallableReference.class, Tests.Cast.class, Tests.CheckArguments.class, Tests.ControlFlowAnalysis.class, Tests.ControlStructures.class, Tests.DataClasses.class, Tests.DataFlow.class, Tests.DataFlowInfoTraversal.class, Tests.DeclarationChecks.class, Tests.DelegatedProperty.class, Tests.Deparenthesize.class, Tests.Enum.class, Tests.Extensions.class, Tests.FunctionLiterals.class, Tests.Generics.class, Tests.IncompleteCode.class, Tests.Inference.class, Tests.Infos.class, Tests.Inner.class, Tests.J_k.class, Tests.Jdk_annotations.class, Tests.Library.class, Tests.NullabilityAndAutoCasts.class, Tests.NullableTypes.class, Tests.Numbers.class, Tests.Objects.class, Tests.OperatorsOverloading.class, Tests.Overload.class, Tests.Override.class, Tests.Recovery.class, Tests.Redeclarations.class, Tests.Regressions.class, Tests.Resolve.class, Tests.Scopes.class, Tests.SenselessComparison.class, Tests.Shadowing.class, Tests.SmartCasts.class, Tests.Substitutions.class, Tests.Subtyping.class, Tests.ThisAndSuper.class, Tests.Varargs.class, Tests.When.class}) public static class Tests extends AbstractDiagnosticsTestWithEagerResolve { @TestMetadata("Abstract.kt") public void testAbstract() throws Exception { @@ -524,11 +524,6 @@ public class JetDiagnosticsTestGenerated extends AbstractDiagnosticsTestWithEage doTest("compiler/testData/diagnostics/tests/Variance.kt"); } - @TestMetadata("When.kt") - public void testWhen() throws Exception { - doTest("compiler/testData/diagnostics/tests/When.kt"); - } - @TestMetadata("compiler/testData/diagnostics/tests/annotations") public static class Annotations extends AbstractDiagnosticsTestWithEagerResolve { public void testAllFilesPresentInAnnotations() throws Exception { @@ -5587,6 +5582,54 @@ public class JetDiagnosticsTestGenerated extends AbstractDiagnosticsTestWithEage } + @TestMetadata("compiler/testData/diagnostics/tests/when") + public static class When extends AbstractDiagnosticsTestWithEagerResolve { + public void testAllFilesPresentInWhen() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("compiler/testData/diagnostics/tests/when"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("NoElseExpectedUnit.kt") + public void testNoElseExpectedUnit() throws Exception { + doTest("compiler/testData/diagnostics/tests/when/NoElseExpectedUnit.kt"); + } + + @TestMetadata("NoElseNoExpectedType.kt") + public void testNoElseNoExpectedType() throws Exception { + doTest("compiler/testData/diagnostics/tests/when/NoElseNoExpectedType.kt"); + } + + @TestMetadata("NoElseReturnedCoercionToUnit.kt") + public void testNoElseReturnedCoercionToUnit() throws Exception { + doTest("compiler/testData/diagnostics/tests/when/NoElseReturnedCoercionToUnit.kt"); + } + + @TestMetadata("NoElseReturnedFromLambdaExpectedInt.kt") + public void testNoElseReturnedFromLambdaExpectedInt() throws Exception { + doTest("compiler/testData/diagnostics/tests/when/NoElseReturnedFromLambdaExpectedInt.kt"); + } + + @TestMetadata("NoElseReturnedNonUnit.kt") + public void testNoElseReturnedNonUnit() throws Exception { + doTest("compiler/testData/diagnostics/tests/when/NoElseReturnedNonUnit.kt"); + } + + @TestMetadata("NoElseReturnedUnit.kt") + public void testNoElseReturnedUnit() throws Exception { + doTest("compiler/testData/diagnostics/tests/when/NoElseReturnedUnit.kt"); + } + + @TestMetadata("NoElseWhenStatement.kt") + public void testNoElseWhenStatement() throws Exception { + doTest("compiler/testData/diagnostics/tests/when/NoElseWhenStatement.kt"); + } + + @TestMetadata("When.kt") + public void testWhen() throws Exception { + doTest("compiler/testData/diagnostics/tests/when/When.kt"); + } + + } + public static Test innerSuite() { TestSuite suite = new TestSuite("Tests"); suite.addTestSuite(Tests.class); @@ -5633,6 +5676,7 @@ public class JetDiagnosticsTestGenerated extends AbstractDiagnosticsTestWithEage suite.addTestSuite(Subtyping.class); suite.addTestSuite(ThisAndSuper.class); suite.addTestSuite(Varargs.class); + suite.addTestSuite(When.class); return suite; } } diff --git a/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxCodegenTestGenerated.java index 541157bb67c..30bb63fd6c1 100644 --- a/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxCodegenTestGenerated.java @@ -4282,6 +4282,21 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest("compiler/testData/codegen/box/when/matchNotNullAgainstNullable.kt"); } + @TestMetadata("noElseAssigned.kt") + public void testNoElseAssigned() throws Exception { + doTest("compiler/testData/codegen/box/when/noElseAssigned.kt"); + } + + @TestMetadata("noElseInRetunedExpression.kt") + public void testNoElseInRetunedExpression() throws Exception { + doTest("compiler/testData/codegen/box/when/noElseInRetunedExpression.kt"); + } + + @TestMetadata("noElseInStatement.kt") + public void testNoElseInStatement() throws Exception { + doTest("compiler/testData/codegen/box/when/noElseInStatement.kt"); + } + @TestMetadata("nullableWhen.kt") public void testNullableWhen() throws Exception { doTest("compiler/testData/codegen/box/when/nullableWhen.kt");