blob: 5e2577b5576bf8769c7f0d22c170d51e64ce0843 [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.
// This test verifies proper functionality of the "fallback exhaustiveness
// algorithm", which we are using as a stopgap measure to allow people to try
// out the "patterns" feature before the full exhaustiveness algorithm is ready.
//
// The fallback exhaustiveness algorithm works as follows: a switch is
// considered exhaustive if either (a) it is recognized as exhaustive by flow
// analysis, or (b) it is recognized as exhaustive by the old (pre-patterns)
// exhaustiveness algorithm for enums.
//
// With the enabling of the real exhaustiveness algorithm, these switches should
// in most cases no longer cause errors.
sealed class A {}
class B extends A {}
class C extends A {}
class D extends A {}
enum E {
e1,
e2
}
void ignore(Object? value) {}
void typedWildcard(A a) {
// Neither flow analysis nor the old exhaustiveness algorithm understand that
// a sealed class factors into its subclasses.
//
// The real exhaustiveness handles this.
switch (a) {
case B _:
break;
case C _:
break;
case D _:
break;
}
ignore(switch (a) {
B _ => 0,
C _ => 1,
D _ => 2
});
}
void typedVariable(A a) {
// Neither flow analysis nor the old exhaustiveness algorithm understand that
// a sealed class factors into its subclasses.
//
// The real exhaustiveness handles this.
switch (a) {
case B x:
break;
case C x:
break;
case D x:
break;
}
ignore(switch (a) {
B x => 0,
C x => 1,
D x => 2
});
}
void typedObjectPattern(A a) {
// Neither flow analysis nor the old exhaustiveness algorithm understand that
// a sealed class factors into its subclasses.
//
// The real exhaustiveness handles this.
switch (a) {
case B():
break;
case C():
break;
case D():
break;
}
ignore(switch (a) {
B() => 0,
C() => 1,
D() => 2
});
}
void logicalOrPattern(E e) {
// Neither flow analysis nor the old exhaustiveness algorithm understand that
// an enum value is exhausted by enum constants separated by `||`.
//
// The real exhaustiveness handles this.
switch (e) {
case E.e1 || E.e2:
break;
}
ignore(switch (e) {
E.e1 || E.e2 => 0
});
}
void logicalAndPattern(E e) {
// Neither flow analysis nor the old exhaustiveness algorithm understand enum
// constants appearing on either side of `&&`.
//
// The real exhaustiveness handles this.
switch (e) {
case E.e1 && E():
break;
case E.e2 && E():
break;
}
switch (e) {
case E() && E.e1:
break;
case E() && E.e2:
break;
}
ignore(switch (e) {
E.e1 && E() => 0,
E.e2 && E() => 1
});
ignore(switch (e) {
E() && E.e1 => 0,
E() && E.e2 => 1
});
}
void castPattern(E? e) {
// Neither flow analysis nor the old exhaustiveness algorithm understand enum
// constants appearing inside a cast pattern.
//
// The real exhaustiveness handles this.
switch (e) {
case E.e1 as E:
break;
case E.e2:
break;
}
ignore(switch (e) {
E.e1 as E => 0,
E.e2 => 1
});
}
void nullCheckPattern(E? e) {
// Neither flow analysis nor the old exhaustiveness algorithm understand enum
// constants appearing inside a null-check pattern.
//
// The real exhaustiveness handles this.
switch (e) {
case E.e1?:
break;
case E.e2:
break;
case null:
break;
}
ignore(switch (e) {
E.e1? => 0,
E.e2 => 1,
null => 2
});
}
void nullAssertPattern(E? e) {
// Neither flow analysis nor the old exhaustiveness algorithm understand enum
// constants appearing inside a null-assert pattern.
//
// The real exhaustiveness handles this.
switch (e) {
case E.e1!:
break;
case E.e2:
break;
}
ignore(switch (e) {
E.e1! => 0,
// ^
// [analyzer] STATIC_WARNING.UNNECESSARY_NULL_ASSERT_PATTERN
E.e2 => 1
});
}
void recordPattern((E, E) r) {
// Neither flow analysis nor the old exhaustiveness algorithm split record
// patterns up into cases.
//
// The real exhaustiveness handles this.
switch (r) {
case (E.e1, E.e1):
break;
case (E.e1, E.e2):
break;
case (E.e2, E.e1):
break;
case (E.e2, E.e2):
break;
}
ignore(switch (r) {
(E.e1, E.e1) => 0,
(E.e1, E.e2) => 1,
(E.e2, E.e1) => 2,
(E.e2, E.e2) => 3
});
}
void listPattern(List<int> l) {
// Neither flow analysis nor the old exhaustiveness algorithm understand that
// `[...]` matches all lists.
//
// The real exhaustiveness handles this.
ignore(switch (l) {
[...] => 0
});
}
void mapPattern(Map<String, int> m) {
// Neither flow analysis nor the old exhaustiveness algorithm understand that
// `{...}` matches all maps.
//
// The real exhaustiveness handles this.
ignore(switch (m) {
Map() => 0
});
}
void exhaustiveBoolean(bool b) {
// Neither flow analysis nor the old exhaustiveness algorithm understand that
// `bool` is exhausted by `true` and `false`.
//
// The real exhaustiveness handles this.
switch (b) {
case true:
break;
case false:
break;
}
ignore(switch (b) {
true => 0,
false => 1
});
}
void relationalPattern(E e) {
// Neither flow analysis nor the old exhaustiveness algorithm understand that
// `== enumValue` matches an enum value.
//
// TODO(johnniwinther): Should the real exhaustiveness handle this? The
// call is on the expression which we do not control.
switch (e) {
//^^^^^^
// [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH_STATEMENT
// ^
// [cfe] The type 'E' is not exhaustively matched by the switch cases since it doesn't match 'E.e1'.
case == E.e1:
break;
case == E.e2:
break;
}
ignore(switch (e) {
// ^^^^^^
// [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH_EXPRESSION
// ^
// [cfe] The type 'E' is not exhaustively matched by the switch cases since it doesn't match 'E.e1'.
== E.e1 => 0,
== E.e2 => 1
});
}
void withGuard(E e) {
// The old exhaustiveness algorithm doesn't understand guards, but for
// soundness it will ignore any cases that are guarded.
//
// The real exhaustiveness handles this.
switch (e) {
//^^^^^^
// [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH_STATEMENT
// ^
// [cfe] The type 'E' is not exhaustively matched by the switch cases since it doesn't match 'E.e1'.
case E.e1 when true:
break;
case E.e2:
break;
}
ignore(switch (e) {
// ^^^^^^
// [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH_EXPRESSION
// ^
// [cfe] The type 'E' is not exhaustively matched by the switch cases since it doesn't match 'E.e1'.
E.e1 when true => 0,
E.e2 => 1
});
}
main() {
typedWildcard(B());
typedVariable(B());
typedObjectPattern(B());
logicalOrPattern(E.e1);
logicalAndPattern(E.e1);
castPattern(E.e1);
nullCheckPattern(E.e1);
nullAssertPattern(E.e1);
recordPattern((E.e1, E.e1));
listPattern([]);
mapPattern({});
exhaustiveBoolean(false);
relationalPattern(E.e1);
withGuard(E.e1);
}