blob: 22430d1dd2b29b727aa75808f79c3ebacc52cc38 [file] [log] [blame]
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "../../static_type_helper.dart";
test() {
{
// Guard expressions can promote (in this case, the guard `x != null`
// promotes `x` to non-nullable `int`).
int? reachability0 = 0;
int? reachability1 = 0;
int? x;
switch (expr<Object>()) {
case _ when x != null:
reachability0 = null;
x.expectStaticType<Exactly<int>>();
default:
reachability1 = null;
x.expectStaticType<Exactly<int?>>();
}
reachability0.expectStaticType<Exactly<int?>>();
reachability1.expectStaticType<Exactly<int?>>();
}
{
// When a pattern fully covers the scrutinee type, a guard can cause
// promotion in later cases (in this case, the guard `x == null` causes `x`
// to be promoted to non-nullable later in the switch).
int? x;
switch (expr<Object?>()) {
case _ when x == null:
break;
case _:
x.expectStaticType<Exactly<int>>();
}
}
{
// When a pattern doesn't fully cover the scrutinee type, a guard doesn't
// cause promotion in later cases (in this case, the guard `x == null` does
// *not* cause `x` to be promoted to non-nullable later in the switch,
// because the guard only executes when the scrutinee is a `String`).
int? x;
switch (expr<Object?>()) {
case String _ when x == null:
break;
case _:
x.expectStaticType<Exactly<int?>>();
}
}
{
// A switch statement can promote the scrutinee.
int? reachability0 = 0;
int? reachability1 = 0;
var x = expr<num>();
switch (x) {
case int y:
reachability0 = null;
x.expectStaticType<Exactly<int>>();
default:
reachability1 = null;
x.expectStaticType<Exactly<num>>();
}
reachability0.expectStaticType<Exactly<int?>>();
reachability1.expectStaticType<Exactly<int?>>();
}
{
// Every case ends in an implicit break, so in this example, the code after
// the switch statement is reachable.
int? reachability0 = 0;
var x = expr<Object>();
if (expr<bool>()) {
switch (expr<Object>()) {
case int _:
x as int;
default:
return;
}
reachability0 = null;
x.expectStaticType<Exactly<int>>();
}
reachability0.expectStaticType<Exactly<int?>>();
}
{
// A switch on an always-exhaustive type is guaranteed to be exhaustive
// (thanks to the exhaustiveness checker), so in this example, the code
// after the switch statement is not reachable.
int? reachability0 = 0;
if (expr<bool>()) {
switch (expr<SealedClass>()) {
case DerivedFromSealedClass _:
return;
}
reachability0 = null;
}
reachability0.expectStaticType<Exactly<int>>();
}
{
// A switch on a type that is not always-exhaustive is not guaranteed to be
// exhaustive.
int? reachability0 = 0;
if (expr<bool>()) {
switch (expr<int>()) {
case 0:
return;
}
reachability0 = null;
}
reachability0.expectStaticType<Exactly<int?>>();
}
{
// An empty switch statement on an always-exhaustive type acts like a
// `throw`.
int? reachability0 = 0;
if (expr<bool>()) {
switch (expr<EmptySealedClass>()) {}
reachability0 = null;
}
reachability0.expectStaticType<Exactly<int>>();
}
{
// Test nested switch statements. This test verifies that the scrutinee
// types between the inner and outer switch statements are not mixed up.
switch (expr<int>()) {
case var x when expr<bool>():
x.expectStaticType<Exactly<int>>();
switch (expr<String>()) {
case var y:
y.expectStaticType<Exactly<String>>();
}
case var z:
z.expectStaticType<Exactly<int>>();
}
}
{
// This test verifies that the scrutinee references between the inner and
// outer switch statements are not mixed up.
var x = expr<Object>();
var y = expr<Object>();
switch (x) {
case num _:
x.expectStaticType<Exactly<num>>();
y.expectStaticType<Exactly<Object>>();
switch (y) {
case int _:
x.expectStaticType<Exactly<num>>();
y.expectStaticType<Exactly<int>>();
default:
return;
}
x.expectStaticType<Exactly<num>>();
y.expectStaticType<Exactly<int>>();
case String _:
x.expectStaticType<Exactly<String>>();
y.expectStaticType<Exactly<Object>>();
}
}
{
// If the scrutinee is reassigned in a guard clause, a successful match
// won't promote it. But the matched value is still promoted.
var x = expr<Object>();
switch (x) {
case int _ && var y when expr<bool>():
x.expectStaticType<Exactly<int>>();
y.expectStaticType<Exactly<int>>();
case _ when pickSecond(x = expr<Object>(), expr<bool>()):
break;
case int _ && var z:
x.expectStaticType<Exactly<Object>>();
z.expectStaticType<Exactly<int>>();
}
}
{
// If multiple cases share a body, promotions in those cases are joined. In
// this example, the first case promotes to `num` and then to `int`, whereas
// the second case promotes only to `num`, so the promotion to `num` is
// retained.
var x = expr<Object>();
switch (x) {
case num() && int():
case num():
x.expectStaticType<Exactly<num>>();
}
}
{
// In this example, the second case promotes to `num` and then to `int`,
// whereas the second case promotes only to `num`, so the promotion to `num`
// is retained.
var x = expr<Object>();
switch (x) {
case num() when expr<bool>():
case num() && int():
x.expectStaticType<Exactly<num>>();
}
}
{
// A similar rule applies to variables declared in patterns. In this
// example, `x` is promoted to `int` in the first case but not the second
// (because the matched value type is `int` in the first case), so the
// promotion is not retained.
switch (expr<(int, int?)>()) {
case (0, int? x?):
case (1, int? x):
x.expectStaticType<Exactly<int?>>();
}
}
{
// In this example, `x` is promoted to `int` in the second case but not the
// first, so the promotion is not retained.
switch (expr<(int, int?)>()) {
case (0, int? x):
case (1, int? x?):
x.expectStaticType<Exactly<int?>>();
}
}
{
// In this example, `x` is promoted to `int` in both the first and second
// cases, so the promotion is retained.
switch (expr<int?>()) {
case int? x? when expr<bool>():
case int? x?:
x.expectStaticType<Exactly<int>>();
}
}
{
// A guard clause can also promote a variable declared in a pattern. In this
// example, `x` is promoted to `int` in both the first and second clauses;
// in the first case it's promoted at the declaration site (because the
// matched value type is `int`), and in the second case it's promoted in the
// guard clause.
switch (expr<(int, int?)>()) {
case (0, int? x?):
case (1, int? x) when x != null:
x.expectStaticType<Exactly<int>>();
}
}
{
// Here's a more complex example of promotion of matched variables, based on
// a co19 test that was failing early in the implementation.
switch (expr<Object?>()) {
case String? a? when a is Never:
case String? a when a != null:
case String? a! when a == 1:
a.expectStaticType<Exactly<String>>();
}
}
{
// If a switch statement contains a case that fully covers the matched value
// type, it is recognized to be trivially exhaustive (so in this example,
// the code after the switch statement is unreachable)..
int? reachability0 = 0;
if (expr<bool>()) {
switch (expr<Object>()) {
case _:
return;
}
reachability0 = null;
}
reachability0.expectStaticType<Exactly<int>>();
}
{
// However, if a switch statement is trivially exhaustive, but one of its
// reachable switch cases completes normally, the code after the switch
// statements is reachable.
int? reachability0 = 0;
int? reachability1 = 0;
if (expr<bool>()) {
switch (expr<Object>()) {
case int _:
reachability0 = null;
case _:
return;
}
reachability1 = null;
}
reachability0.expectStaticType<Exactly<int?>>();
reachability1.expectStaticType<Exactly<int?>>();
}
{
// Conversely, if a switch statement is trivially exhaustive, and one of its
// switch cases completes normally, but that switch case is itself trivially
// unreachable, then the code after the switch statements is unreachable.
int? reachability0 = 0;
int? reachability1 = 0;
if (expr<bool>()) {
switch (expr<Object>()) {
case _:
return;
case int _:
// ^^^^
// [analyzer] HINT.UNREACHABLE_SWITCH_CASE
reachability0 = null;
}
reachability1 = null;
}
reachability0.expectStaticType<Exactly<int>>();
reachability1.expectStaticType<Exactly<int>>();
}
{
// If a switch statement is trivially exhaustive, but one of its reachable
// switch cases contains a reachable `break` statement, the code after the
// switch statements is reachable.
int? reachability0 = 0;
int? reachability1 = 0;
if (expr<bool>()) {
switch (expr<Object>()) {
case int _:
reachability0 = null;
break;
case _:
return;
}
reachability1 = null;
}
reachability0.expectStaticType<Exactly<int?>>();
reachability1.expectStaticType<Exactly<int?>>();
}
{
// However, if a switch statement is trivially exhaustive, and one of its
// switch cases contains a `break` statement that is reachable from the top
// of that case, but the switch case is itself trivially unreachable, then
// the code after the switch statements is unreachable.
int? reachability0 = 0;
int? reachability1 = 0;
if (expr<bool>()) {
switch (expr<Object>()) {
case _:
return;
case int _:
// ^^^^
// [analyzer] HINT.UNREACHABLE_SWITCH_CASE
reachability0 = null;
break;
}
reachability1 = null;
}
reachability0.expectStaticType<Exactly<int>>();
reachability1.expectStaticType<Exactly<int>>();
}
{
// Here is an example of a switch statement that is not trivially
// exhaustive, to verify that the code after the switch statement is
// considered reachable.
int? reachability0 = 0;
if (expr<bool>()) {
switch (expr<Object>()) {
case int _:
return;
}
reachability0 = null;
}
reachability0.expectStaticType<Exactly<int?>>();
}
}
T expr<T>() => throw UnimplementedError();
T pickSecond<T>(dynamic x, T y) => y;
sealed class SealedClass {}
class DerivedFromSealedClass extends SealedClass {}
sealed class EmptySealedClass {}
main() {}