Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pattern matching not properly considering "!= null" in the exhaustive check #4190

Open
gabrielgarciagava opened this issue Dec 2, 2024 · 3 comments
Labels
patterns Issues related to pattern matching. type-inference Type inference, issues or improvements

Comments

@gabrielgarciagava
Copy link

Check the following code:

void main() {
  int? x = foo();

  print(switch (x) {
    != null => "not null",
    null => "null",
  });
  
  print(switch (x) {
    _? => "not null",
    null => "null",
  });
}

int? foo() => 3;

The first switch gives the error "The type 'int?' is not exhaustively matched by the switch cases since it doesn't match 'int()'.".
The second switch works fine.
I would expect it to behave exactly like the second switch, which is "match anything that is not null". Does it make sense?

I tested it with dart 3.5.4 (current stable) and 3.7.0-183.0.dev (current master)

@lrhn
Copy link
Member

lrhn commented Dec 2, 2024

This is working as specified. It could probably work better.

The exhustiveness specification says that:

Relational pattern: The empty space union. Relational patterns don't reliably match any values, so don't help with exhaustiveness.

That means that a relational pattern does nothing towards exhaustion.

The argument that they don't reliably match any values is not true for == null and != null which are very reliable, and we could choose to make == null and != null match spaces like:

... except:

  • for the pattern == null: The space for the type Null.
  • for the pattern != null: The space for the type Object.

@munificent @stereotype441

@lrhn lrhn transferred this issue from dart-lang/sdk Dec 2, 2024
@lrhn lrhn added patterns Issues related to pattern matching. type-inference Type inference, issues or improvements labels Dec 2, 2024
@rrousselGit
Copy link

It's quite interesting that case null: and case == null: behave differently
This case doesn't matter too much though. I assume folks use case null: usually

But case != null: feels quite important.

I guess the equivalent is case _?:, but that pattern feels very unnatural.

And the linter doesn't like:

final x = switch (<int?>) {
  null => 0,
  _? => 42,
};

It complains that _? is redundant and we could use _ directly ... which is true but not really matching the intent here.

@lrhn
Copy link
Member

lrhn commented Dec 2, 2024

The reason case null: and case == null: behave differently is that the former is equivalent to null == matchedValue and the latter to matchedValue == null.
That doesn't matter for null, which is why this issue can be fixed, but in the general case the compiler knows the constant value of a constant pattern that == is called on at compile time, and whether it has primitive equality, and in the relational pattern case it doesn't. The pattern compiler can see whether the former exhausts an enum value, and it can't assume anything about the latter implemention of operator==.
(But for a null value, that implementation isn't called.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
patterns Issues related to pattern matching. type-inference Type inference, issues or improvements
Projects
None yet
Development

No branches or pull requests

3 participants