dart-lang / language

Design of the Dart language
Other
2.68k stars 205 forks source link

Exhaustiveness checking tries to enforce cases which should not be possible #4152

Open vnmiso opened 3 weeks ago

vnmiso commented 3 weeks ago

Consider the following example:

enum MyEnum {
  a, b, c, d;
}

void doSomething(MyEnum value) {
  if (value == MyEnum.b) {
    fooB();
    return;
  }

  final text = switch (value) {
    MyEnum.a => 'a',
    MyEnum.c => 'c',
    MyEnum.d => 'd',
  };
  fooOthers(text);
}

This is a compile error (The type 'MyEnum' is not exhaustively matched by the switch cases since it doesn't match 'MyEnum.b'), even though after the if statement value cannot be MyEnum.b. Is there a deeper underlying reason that exhaustiveness checking can't handle cases like this?

Sometimes code like this can be refactored to avoid this issue (putting everything into one switch statement/expression, for example), but the code could become a lot clunkier.

mateusfccp commented 3 weeks ago

This is expected. Dart doesn't keep a track of the possible values/types a variable may assume to do static analysis with them.

It's not impossible, but it's not just an adjustment to what we have now, it's a much more complex request. We would have (1) to adjust the flow analysis to consider values (currently it only considers types, except for null, which can promote to a non-nullable type) and (2) to have a concept of "not this value"/"not this type", so when the flow analysis gets to your switch, the value is promoted to "valueMyEnum, value ≠ MyEnum.b".

For your specific case, I would use a simple switch, and I think it is even clearer.

enum MyEnum {
  a, b, c, d;
}

void doSomething(MyEnum value) {
  final String text;

  switch (value) {
    case MyEnum.a:
      text = 'a';
    case MyEnum.b:
      fooB();
      return;
    case MyEnum.c:
      text = 'c';
    case MyEnum.d:
      text = 'd';
  }

  fooOthers(text);
}