dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.31k stars 1.59k forks source link

The type 'double' is not exhaustively matched by the switch cases since it doesn't match 'double()' #59648

Open filiph opened 17 hours ago

filiph commented 17 hours ago

I'm not sure if this is a bug but when I switch over a double and exhaustively match all possible values (I think), Dart still forces me to add an additional case.

      final steering = switch (bearingDifference) {
        < 0 => SteeringInstruction.right,
        > 0 => SteeringInstruction.left,
        == 0 => SteeringInstruction.straight,
      };

The code above gives this compile time error:

Error: The type 'double' is not exhaustively matched by the switch cases since it doesn't match 'double()'.
Try adding a wildcard pattern or cases that match 'double()'.

Now it might be that I'm really missing a case but the error isn't very helpful here. Am I missing double.nan? (No, because "A double can't equal 'double.nan', so the condition is always 'false'.") Is it double.infinity and double.negativeInfinity? (No, those values are already covered, and adding them doesn't make a difference.) Is it -0? (No. Adding -0 doesn't make a difference.) Is it null? (No, the number is non-nullable.) A specific suggestion would be much more helpful.

Or maybe it's that Dart doesn't know (can't prove) what's missing for continuous values such as double. If that's the case, the error should note that. Something like "Switching over real numbers is not supported. You have to include a default case, even if you know it will never be matched."

Thankfully, the workaround is easy:

      final steering = switch (bearingDifference) {
        < 0 => SteeringInstruction.right,
        > 0 => SteeringInstruction.left,
        == 0 => SteeringInstruction.straight,
        _ => throw StateError(
            'Double $bearingDifference somehow '
            "doesn't belong on the number line and Dart won't tell us why. ¯\_(ツ)_/¯"),
      };

;)

Dart info

- Dart 3.5.4 (stable) (Wed Oct 16 16:18:51 2024 +0000) on "macos_arm64"
- on macos / Version 14.7 (Build 23H124)
- locale is en-US
dart-github-bot commented 17 hours ago

Summary: User reports a confusing Dart compile-time error when using a switch statement with a double. The error message incorrectly suggests a missing case, even when all seemingly relevant cases are handled.

eernstg commented 17 hours ago

Exhaustiveness analysis doesn't reason about the exhaustiveness of a set of double or int inequalities or any other number specific properties, it only reasons about properties that are modeled at the type level. So it knows that the immediate subtypes of a sealed type is an exhaustive set, it knows the complete set of values of any given enum, it knows about null vs. non-null, etc, but it doesn't know that < 0, > 0 and == 0 covers all values of type double.

@bwilkerson, @pq, what do you think? Is there a good way to indicate for certain switches that exhaustiveness doesn't prove things about numbers?

lrhn commented 12 hours ago

it doesn't know that < 0, > 0 and == 0 covers all values of type double.

(Which it also doesn't. NaN is a thing. Even if relational operators could cover all ints, they won't be able to cover all doubles, it would have to recognize something like double(isNaN: true) for that, which is unlikely.)

filiph commented 11 hours ago

Thanks for the clarification! In that case, I would like to see a more helpful error.

You see, modern Dart switch statements give you a lot of power, e.g. the following code gives you an exhaustiveness error which goes away when you truly cover all cases.

switch((useful, efficient)) {
  case (true, true):
    return "Oh boy!";
  case (false, true):
    return "Well, at least it's efficient.";
}

So, as a developer, it's not out of the question to half-expect a similar analysis on numbers. I, of course, accept that that isn't possible, or at least practical. But the compiler should give a better indication of what's going on than it doesn't match 'double()', ideally.

In other words, the quality of Dart's exhaustiveness analysis in other cases (such as records of booleans) creates an expectation with the programmer. The ensuing surprise is not helped by the error given.