Skip to content

Case expressions #4343

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

Open
lrhn opened this issue Apr 30, 2025 · 2 comments
Open

Case expressions #4343

lrhn opened this issue Apr 30, 2025 · 2 comments
Labels
feature Proposed language feature that solves one or more problems patterns Issues related to pattern matching.

Comments

@lrhn
Copy link
Member

lrhn commented Apr 30, 2025

(This is basically reopening #2181 as a new feature.)

Allow <expression> 'case' <pattern> ('when' <expression>)? as an expression which:

  • Evaluates to a bool result.
  • Introduces variables that are available on the true path out of that bool, if it occurs in a condition position.

Since patterns look like expressions, and can end in ?, and the start and end of case...when are expressions, we must know where a case expression ends.
That means it must likely be a production of <expression>, which means it cannot be combined with any operators without being parenthesized, and it will have to be delimited in some way in all uses.

Proposed:

  <expression>  ::= ...
                               | <caseExpression>
  <caseExpression> ::=
        <expressionWithoutCascade> 'case' <pattern> ('when' <expressionWithoutCascade>)?

(Not absolutely sure about the placement in the grammar, but likely close to this. At least you definitely don't want an unparenthesized case-expression as the value or when clause of another case-expression.)

If the pattern introduces variables, then those variables are in scope in the when clause as usual.
Further, if the case-expression (or parenthesized expression, or !-negated expression) is use in a condition position, which means any position where its value directly affects control flow (has a "true" branch and a "false" branch), then the variables are available on the true path, until the end of the current constructor/block.
(This is not intended to be something new, it's exactly the same continuation where a promoting test would promote, except that the variables are also limited to the scope they are declared in.)

Example:

var x = input case Box(:var value) ? value : input;

and

{
   input case Box(:var value) || throw "nope";
   print(value);
}

(I know this is bad style. It does show that a variable is available in the rest of the block, as long as it's dominated by the true test.)

If a case expression is used as the condition of an if statement/element, a for statement/element or a while statement, then the variables are available in the body (if that's the true-branch), but not outside of the satement. It is as if the if/loop structure introduces a scope block containing the condition itself, and then the body is another nested scope.

This is consistent with how if/case works today, but this allows doing multiple independent cases:

if ((ex case Foo(:var x) when x > 0) && (ex2 case Bar(:var y) when y > 0)) { 
  both `x` and `y` in scope.
}
@lrhn lrhn added feature Proposed language feature that solves one or more problems patterns Issues related to pattern matching. labels Apr 30, 2025
@munificent
Copy link
Member

This looks really nice. In particular, I have sometimes wanted to have an if condition that is a chain of case and/or other conditional tests like:

if (thing case Foo(:var bar) && someCondition && bar case Blah() && otherCondition) { ... }

It looks like this would address that.

I'm not as sold on allowing variable declarations outside of if conditions. I think this is probably fine:

input case Box(:var value) ? value : input;

But:

{
   input case Box(:var value) || throw "nope";
   print(value);
}

Feels pretty subtle and unintuitive to me. I do grant that it effectively offers a solution for #2537. I just wonder if it's too subtle and we should have a more explicit guard-like statement.

But aside from that, this looks great.

@FMorschel
Copy link

I'd love this! It would also allow us to do:

if (input != other && (input case MyType(:var value))) {
  print(value);
}

Which reads the != test first and could short-circuit before the pattern without the need of a full outer if or weird workarounds like:

if ((input == other ? null : input) case MyType(:var value)) {
  print(value);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems patterns Issues related to pattern matching.
Projects
None yet
Development

No branches or pull requests

3 participants