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] Allow :name syntax in switch statements #3092

Open
subzero911 opened this issue May 21, 2023 · 10 comments
Open

[Pattern-matching] Allow :name syntax in switch statements #3092

subzero911 opened this issue May 21, 2023 · 10 comments
Labels
patterns Issues related to pattern matching. request Requests to resolve a particular developer problem

Comments

@subzero911
Copy link

subzero911 commented May 21, 2023

There's a shorthand syntax in object matching
image

I tried to use it in switch statements and expected it will work.
My expectation: Data() will be destructured and its name parameter will be binded to a corresponding new name variable automatically.

but it fails with following errors:

It works only if I use an explicit syntax

But it looks excessive.

It also ain't working in switch expressions:

Proposal:

Allow using the :name syntax in object matching in switch statements and switch expressions

@subzero911 subzero911 added the request Requests to resolve a particular developer problem label May 21, 2023
@subzero911 subzero911 changed the title [ Allow :name syntax in switch statements (WIP)[ Allow :name syntax in switch statements May 21, 2023
@subzero911 subzero911 changed the title (WIP)[ Allow :name syntax in switch statements [Pattern-matching] Allow :name syntax in switch statements May 21, 2023
@lrhn
Copy link
Member

lrhn commented May 21, 2023

The reason that syntax doesn't work is that:

const foo = 42;
switch (o) {
  case Bar(x: foo): ...
}

means to check the (o as Bar).x value against the constant value 42.

We can't use the existing binding of the variable to determine that it should be a constant match, because you are allowed to introduce a new variable shadowing it.

So you have to write var (or final, or a type) to declare a variable in a case match pattern, because the non-var syntax is not free.

@lrhn lrhn closed this as completed May 21, 2023
@rrousselGit
Copy link

But that's not x: foo but instead :x that's discussed.

I would expect:
case Class(:foo) to always define a "final foo" variable independently of whether a "this.foo" variable exists already

@subzero911
Copy link
Author

I found that I can omit parameter name and use var/final and it will do:

@rrousselGit if I used :x it's not clear whether I meant :var x or :final x
We can agree on final as a default option, but it should be approved and documented.
@lrhn could you reopen please? This worth reviewing more carefully.

@subzero911
Copy link
Author

At least an analyser error about "constant pattern" is confusing here.
You could have replaced it with a more meaningful error, like
":x is ambiguous. Please use :var x or :final x to define a new variable in a case match pattern".

@lrhn
Copy link
Member

lrhn commented May 21, 2023

ACK, so the request is only about : pattern syntax for field matches in object patterns in refutable (case) patterns. It already requires them to define a variable, and uses the name of that variable for the getter it reads.

Currently the : pattern expects pattern to be a refutable patterns, which means that an identifier by itself is interpreted as a constant pattern, which is not valid in that position because it doesn't introduce a variable, and therefore there is no name.

So, the request is to allow an plain identifier in this pattern position to be treated as if it was prefixed by var or final.
I expect nested positions too (like : x?, : x as Foo and : x!).

The : x? means we can't just say that the pattern after an unnamed : must be a declaration pattern, because x? is not a valid declaration pattern

But it's not impossible. How a raw identifier is treated depends on the context and the kind of pattern.
In a declaration context, where the pattern itself is prefixed by var or final, a raw identifier introduces a new pattern.
In all other patterns, a raw identifier resolves to the variable it denotes. There are then rules about which variables you can refer to (only assignable variables in assignment patterns, only constant variables in case patterns).

This proposal would put the pattern of an unnamed : pattern in a context where raw identifiers introduce new variables, but which also allow other case-patterns as well. That's new, and it's breaking. It changes the meaning of:

class C {
  final int a;
  C(this.a);
}
const a = 42;
void main() {
  var c = C(42);
  switch (c) {
    case C(a: a && var b): print(b);
  }
}

I find it unlikely that we'll make this change, but it's not technically impossible.

@lrhn lrhn reopened this May 21, 2023
@rrousselGit
Copy link

rrousselGit commented May 21, 2023

I personally think that this would be a significant user experience improvement.

The cost of having to type that var/final keyword is pretty high in the context of pattern matching. Most cases will use those keywords, sometimes multiple times. Combined with the issue described here #2563, this makes pattern matching quite verbose.

For comparison, here's how Freezed dealt with pattern matching before:

AsyncValue<int> obj;
obj.when(
  data: (value) => print('Hello $value'),
  error: (error, stackTrace) => print('Error $error'),
  loading: () => print('loading'),
)

Here's the same thing implemented with Dart 3's pattern matching:

AsyncValue<int> obj;
switch (obj) {
  case AsyncValue(:final value?): print('Hello $value');
  case AsyncValue(:final error?): print('Error $error');
  case _: print('loading');
)

There are a bunch of unnecessary types/keywords which get in the way.

@munificent munificent added the patterns Issues related to pattern matching. label May 22, 2023
@lrhn
Copy link
Member

lrhn commented May 23, 2023

Even with #2563, I don't think the AsyncValue example will get much better, because the different cases have different types, so #2563 won't help. It still needs to be, at least:

  switch (obj) {
    case AsyncData(:value): print('Hello $value');
    case AsyncError(:error): print('Error $error');
    case _: print('loading');
  }

and then the extra var/final really doesn't irk me much:

  switch (obj) {
    case AsyncData(:var value): print('Hello $value');
    case AsyncError(:var error): print('Error $error');
    case _: print('loading');
  }

I personally think it helps readability by showing that a variable is introduced at that point.
(But that might just be, very quickly acquired, habit.)

I'm sure it can work either way.

I'd probably require that if you can omit the var, all you can write is ':' <identifier>, no other and more complicated patterns.

@rrousselGit
Copy link

rrousselGit commented May 23, 2023

Even with #2563, I don't think the AsyncValue example will get much better, because the different cases have different types, so #2563 won't help. It still needs to be, at least:

My example doesn't use different types. Instead, it relies on null checks (although I forgot to type the ? in the pattern).
So that shouldn't apply.

The behavior isn't quite identical, but that's the most common case

I'd probably require that if you can omit the var, all you can write is ':' , no other and more complicated patterns.

I'm fine with that. I think that's the logical thing to do.

Meaning my previous sample could become:

AsyncValue<int> obj;
switch (obj) {
  case .(:value?): print('Hello $value');
  case .(:error?): print('Error $error');
  case _: print('loading');
)

@lrhn
Copy link
Member

lrhn commented May 23, 2023

Ack, the AsyncValue has nullable value and error getters, but not, e.g., stackTrace.

Then my requirement of not allowing more complicated patterns than ':' <identifier> won't work for it, because :value? is a more complicated pattern because of the ?.

@rrousselGit
Copy link

getters, but not, e.g., stackTrace.

It has one for stackTrace too, I just didn't use it here. They are rarely rendered.


I think supporting ? is quite important here.

I'd expect :final name and :final name? to be the two primary use cases of pattern matching by miles.

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. request Requests to resolve a particular developer problem
Projects
None yet
Development

No branches or pull requests

4 participants