From 31d1aa9362bc3ccdbafa27e2a7490f3e13608fec Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Sat, 20 Jul 2024 01:55:29 +0200 Subject: [PATCH] feat: match expressions (#58) Title says everything. Sadly the support is not as complete as I'd like it to be. It turns out it's incredibly hard to guess if the operand is a block expression or not. This requires many refactorings that I'm too tired to do. The result is that the `,` is required at the end of the `match` arm, even if the operand is a block expression. Part of #29. --------- Signed-off-by: Sasha Pourcelot --- README.md | 2 +- rust-grammar-dpdfa/grammar.rs | 37 +++++ rust-grammar-dpdfa/src/generated.rs | 183 ++++++++++++++++++--- rust-grammar-dpdfa/src/tests.rs | 16 +- tests/ui/fail/expr-tuple.stderr | 4 +- tests/ui/fail/invalid_fn_calls.stderr | 4 +- tests/ui/fail/js_concat.stderr | 2 +- tests/ui/fail/missing_expr.stderr | 2 +- tests/ui/fail/python_power_operator.stderr | 2 +- tests/ui/pass/match-expr.rs | 17 ++ 10 files changed, 235 insertions(+), 34 deletions(-) create mode 100644 tests/ui/pass/match-expr.rs diff --git a/README.md b/README.md index 460e0cf..9a11e9a 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ macro_rules! js_concat { This emits the following error [^error-message]: ```none -error: Potentially invalid expansion. Expected an expression, an identifier, `::`, `<`, `Self`, `break` or 12 others. +error: Potentially invalid expansion. Expected an expression, an identifier, `::`, `<`, `Self`, `break` or 13 others. --> tests/ui/fail/js_concat.rs:5:16 | 5 | $left ++ $right diff --git a/rust-grammar-dpdfa/grammar.rs b/rust-grammar-dpdfa/grammar.rs index 4595e53..960699d 100644 --- a/rust-grammar-dpdfa/grammar.rs +++ b/rust-grammar-dpdfa/grammar.rs @@ -794,6 +794,8 @@ fn expr_atom() { expr_while(); } else if peek(For) { expr_for(); + } else if peek(Match) { + expr_match(); } else { error(); } @@ -1150,6 +1152,41 @@ fn expr_for() { block(); } +fn expr_match() { + bump(Match); + // TODO(scrabsha): should be expr(no struct expression) + expr(); + + bump(LBrace); + match_arms(); + bump(RBrace); +} + +fn match_arms() { + if peek(RBrace) { + // return + } else { + match_arm(); + match_arms(); + } +} + +fn match_arm() { + pat(); + + if peek(If) { + bump(If); + expr(); + } + + bump(FatArrow); + + expr(); + // TODO: commas are not mandatory if the expression is an + // ExpressionWithBlock. + bump(Comma); +} + fn macro_call_tail() { // TODO(hypothesis): all paths are path expressions. // diff --git a/rust-grammar-dpdfa/src/generated.rs b/rust-grammar-dpdfa/src/generated.rs index 7759c59..16f2ba4 100644 --- a/rust-grammar-dpdfa/src/generated.rs +++ b/rust-grammar-dpdfa/src/generated.rs @@ -3891,13 +3891,13 @@ fn expr_after_atom( ) -> Result, Option> { input.call_now(&[expr_after_atom_31]) } -fn expr_atom_34( +fn expr_atom_37( input: &mut RustParser, ) -> Result, Option> { if input.peek_expect(Return) || input.peek_expect(Break) { input.call_now(&[expr_atom_1]) } else { - input.call_now(&[expr_atom_33]) + input.call_now(&[expr_atom_36]) } } fn expr_atom_0(input: &mut RustParser) -> Result, Option> { @@ -3906,7 +3906,7 @@ fn expr_atom_0(input: &mut RustParser) -> Result(input: &mut RustParser) -> Result, Option> { input.call_now(&[expr_atom_0]) } -fn expr_atom_33( +fn expr_atom_36( input: &mut RustParser, ) -> Result, Option> { if input.peek_expect(Ident) @@ -3920,7 +3920,7 @@ fn expr_atom_33( { input.call_now(&[expr_atom_6]) } else { - input.call_now(&[expr_atom_32]) + input.call_now(&[expr_atom_35]) } } fn expr_atom_4(input: &mut RustParser) -> Result, Option> { @@ -3942,7 +3942,7 @@ fn expr_atom_5(input: &mut RustParser) -> Result(input: &mut RustParser) -> Result, Option> { input.call_now(&[expr_atom_4, expr_atom_5]) } -fn expr_atom_32( +fn expr_atom_35( input: &mut RustParser, ) -> Result, Option> { if input.peek_expect(FragmentExpr) @@ -3951,7 +3951,7 @@ fn expr_atom_32( { input.call_now(&[expr_atom_8]) } else { - input.call_now(&[expr_atom_31]) + input.call_now(&[expr_atom_34]) } } fn expr_atom_7(input: &mut RustParser) -> Result, Option> { @@ -3960,13 +3960,13 @@ fn expr_atom_7(input: &mut RustParser) -> Result(input: &mut RustParser) -> Result, Option> { input.call_now(&[expr_atom_7]) } -fn expr_atom_31( +fn expr_atom_34( input: &mut RustParser, ) -> Result, Option> { if input.peek_expect(If) { input.call_now(&[expr_atom_10]) } else { - input.call_now(&[expr_atom_30]) + input.call_now(&[expr_atom_33]) } } fn expr_atom_9(input: &mut RustParser) -> Result, Option> { @@ -3977,13 +3977,13 @@ fn expr_atom_10( ) -> Result, Option> { input.call_now(&[expr_atom_9]) } -fn expr_atom_30( +fn expr_atom_33( input: &mut RustParser, ) -> Result, Option> { if input.peek_expect(LParen) { input.call_now(&[expr_atom_12]) } else { - input.call_now(&[expr_atom_29]) + input.call_now(&[expr_atom_32]) } } fn expr_atom_11( @@ -3996,13 +3996,13 @@ fn expr_atom_12( ) -> Result, Option> { input.call_now(&[expr_atom_11]) } -fn expr_atom_29( +fn expr_atom_32( input: &mut RustParser, ) -> Result, Option> { if input.peek_expect(LBracket) { input.call_now(&[expr_atom_14]) } else { - input.call_now(&[expr_atom_28]) + input.call_now(&[expr_atom_31]) } } fn expr_atom_13( @@ -4015,13 +4015,13 @@ fn expr_atom_14( ) -> Result, Option> { input.call_now(&[expr_atom_13]) } -fn expr_atom_28( +fn expr_atom_31( input: &mut RustParser, ) -> Result, Option> { if input.peek_expect(LBrace) { input.call_now(&[expr_atom_16]) } else { - input.call_now(&[expr_atom_27]) + input.call_now(&[expr_atom_30]) } } fn expr_atom_15( @@ -4034,13 +4034,13 @@ fn expr_atom_16( ) -> Result, Option> { input.call_now(&[expr_atom_15]) } -fn expr_atom_27( +fn expr_atom_30( input: &mut RustParser, ) -> Result, Option> { if input.peek_expect(Loop) || input.peek_expect(FragmentLifetime) { input.call_now(&[expr_atom_18]) } else { - input.call_now(&[expr_atom_26]) + input.call_now(&[expr_atom_29]) } } fn expr_atom_17( @@ -4053,13 +4053,13 @@ fn expr_atom_18( ) -> Result, Option> { input.call_now(&[expr_atom_17]) } -fn expr_atom_26( +fn expr_atom_29( input: &mut RustParser, ) -> Result, Option> { if input.peek_expect(While) { input.call_now(&[expr_atom_20]) } else { - input.call_now(&[expr_atom_25]) + input.call_now(&[expr_atom_28]) } } fn expr_atom_19( @@ -4072,13 +4072,13 @@ fn expr_atom_20( ) -> Result, Option> { input.call_now(&[expr_atom_19]) } -fn expr_atom_25( +fn expr_atom_28( input: &mut RustParser, ) -> Result, Option> { if input.peek_expect(For) { input.call_now(&[expr_atom_22]) } else { - input.call_now(&[expr_atom_24]) + input.call_now(&[expr_atom_27]) } } fn expr_atom_21( @@ -4091,23 +4091,42 @@ fn expr_atom_22( ) -> Result, Option> { input.call_now(&[expr_atom_21]) } +fn expr_atom_27( + input: &mut RustParser, +) -> Result, Option> { + if input.peek_expect(Match) { + input.call_now(&[expr_atom_24]) + } else { + input.call_now(&[expr_atom_26]) + } +} fn expr_atom_23( input: &mut RustParser, ) -> Result, Option> { - input.error() + input.call_now(&[expr_match]) } fn expr_atom_24( input: &mut RustParser, ) -> Result, Option> { input.call_now(&[expr_atom_23]) } -fn expr_atom_35( +fn expr_atom_25( + input: &mut RustParser, +) -> Result, Option> { + input.error() +} +fn expr_atom_26( input: &mut RustParser, ) -> Result, Option> { - input.call_now(&[expr_atom_34]) + input.call_now(&[expr_atom_25]) +} +fn expr_atom_38( + input: &mut RustParser, +) -> Result, Option> { + input.call_now(&[expr_atom_37]) } fn expr_atom(input: &mut RustParser) -> Result, Option> { - input.call_now(&[expr_atom_35]) + input.call_now(&[expr_atom_38]) } fn expr_return_or_break_2( input: &mut RustParser, @@ -5579,6 +5598,122 @@ fn expr_for_5(input: &mut RustParser) -> Result(input: &mut RustParser) -> Result, Option> { input.call_now(&[expr_for_5]) } +fn expr_match_0( + input: &mut RustParser, +) -> Result, Option> { + input.bump_expect(RBrace, &[]) +} +fn expr_match_1( + input: &mut RustParser, +) -> Result, Option> { + input.call_now(&[match_arms]) +} +fn expr_match_2( + input: &mut RustParser, +) -> Result, Option> { + input.bump_expect(LBrace, &[]) +} +fn expr_match_3( + input: &mut RustParser, +) -> Result, Option> { + input.call_now(&[expr]) +} +fn expr_match_4( + input: &mut RustParser, +) -> Result, Option> { + input.bump_expect(Match, &[]) +} +fn expr_match_5( + input: &mut RustParser, +) -> Result, Option> { + input.call_now(&[ + expr_match_0, + expr_match_1, + expr_match_2, + expr_match_3, + expr_match_4, + ]) +} +fn expr_match(input: &mut RustParser) -> Result, Option> { + input.call_now(&[expr_match_5]) +} +fn match_arms_4( + input: &mut RustParser, +) -> Result, Option> { + if input.peek_expect(RBrace) { + input.call_now(&[match_arms_0]) + } else { + input.call_now(&[match_arms_3]) + } +} +fn match_arms_0( + input: &mut RustParser, +) -> Result, Option> { + input.call_now(&[]) +} +fn match_arms_1( + input: &mut RustParser, +) -> Result, Option> { + input.call_now(&[match_arms]) +} +fn match_arms_2( + input: &mut RustParser, +) -> Result, Option> { + input.call_now(&[match_arm]) +} +fn match_arms_3( + input: &mut RustParser, +) -> Result, Option> { + input.call_now(&[match_arms_1, match_arms_2]) +} +fn match_arms_5( + input: &mut RustParser, +) -> Result, Option> { + input.call_now(&[match_arms_4]) +} +fn match_arms(input: &mut RustParser) -> Result, Option> { + input.call_now(&[match_arms_5]) +} +fn match_arm_0(input: &mut RustParser) -> Result, Option> { + input.bump_expect(Comma, &[]) +} +fn match_arm_1(input: &mut RustParser) -> Result, Option> { + input.call_now(&[expr]) +} +fn match_arm_2(input: &mut RustParser) -> Result, Option> { + input.bump_expect(FatArrow, &[]) +} +fn match_arm_6(input: &mut RustParser) -> Result, Option> { + if input.peek_expect(If) { + input.call_now(&[match_arm_5]) + } else { + input.call_now(&[]) + } +} +fn match_arm_3(input: &mut RustParser) -> Result, Option> { + input.call_now(&[expr]) +} +fn match_arm_4(input: &mut RustParser) -> Result, Option> { + input.bump_expect(If, &[]) +} +fn match_arm_5(input: &mut RustParser) -> Result, Option> { + input.call_now(&[match_arm_3, match_arm_4]) +} +fn match_arm_7(input: &mut RustParser) -> Result, Option> { + input.call_now(&[pat]) +} +fn match_arm_8(input: &mut RustParser) -> Result, Option> { + input.call_now(&[ + match_arm_0, + match_arm_1, + match_arm_2, + match_arm_6, + match_arm_7, + ]) +} +fn match_arm(input: &mut RustParser) -> Result, Option> { + input.call_now(&[match_arm_8]) +} fn macro_call_tail_0( input: &mut RustParser, ) -> Result, Option> { diff --git a/rust-grammar-dpdfa/src/tests.rs b/rust-grammar-dpdfa/src/tests.rs index 1b272e4..85c5255 100644 --- a/rust-grammar-dpdfa/src/tests.rs +++ b/rust-grammar-dpdfa/src/tests.rs @@ -24,8 +24,8 @@ macro_rules! check_parse { match parser.step(token, idx) { Ok(_) => {}, - Err((idx, _)) => { - panic!("Failed to parse token `{:?}` at index {}", token, idx); + Err((idx, expected)) => { + panic!("Failed to parse token `{:?}` at index {}. Expected {:?}", token, idx, expected); } } } @@ -313,3 +313,15 @@ check_parse! { } } } + +check_parse! { + fn match_arms_are_broken() { + expr, + { + match () { + () => {}, + () => {}, + } + } + } +} diff --git a/tests/ui/fail/expr-tuple.stderr b/tests/ui/fail/expr-tuple.stderr index 8604392..fa567a7 100644 --- a/tests/ui/fail/expr-tuple.stderr +++ b/tests/ui/fail/expr-tuple.stderr @@ -1,10 +1,10 @@ -error: Potentially invalid expansion. Expected an expression, an identifier, `::`, `<`, `Self`, `break` or 13 others. +error: Potentially invalid expansion. Expected an expression, an identifier, `::`, `<`, `Self`, `break` or 14 others. --> tests/ui/fail/expr-tuple.rs:6:10 | 6 | (,) | ^ -error: Potentially invalid expansion. Expected an expression, an identifier, `::`, `<`, `Self`, `break` or 13 others. +error: Potentially invalid expansion. Expected an expression, an identifier, `::`, `<`, `Self`, `break` or 14 others. --> tests/ui/fail/expr-tuple.rs:13:13 | 13 | (42,, 42) diff --git a/tests/ui/fail/invalid_fn_calls.stderr b/tests/ui/fail/invalid_fn_calls.stderr index 35db065..4d91068 100644 --- a/tests/ui/fail/invalid_fn_calls.stderr +++ b/tests/ui/fail/invalid_fn_calls.stderr @@ -1,10 +1,10 @@ -error: Potentially invalid expansion. Expected an expression, an identifier, `::`, `<`, `Self`, `break` or 13 others. +error: Potentially invalid expansion. Expected an expression, an identifier, `::`, `<`, `Self`, `break` or 14 others. --> tests/ui/fail/invalid_fn_calls.rs:6:18 | 6 | $fn_name(,) | ^ -error: Potentially invalid expansion. Expected an expression, an identifier, `::`, `<`, `Self`, `break` or 13 others. +error: Potentially invalid expansion. Expected an expression, an identifier, `::`, `<`, `Self`, `break` or 14 others. --> tests/ui/fail/invalid_fn_calls.rs:14:18 | 14 | $fn_name(,,) diff --git a/tests/ui/fail/js_concat.stderr b/tests/ui/fail/js_concat.stderr index c3952da..97c01aa 100644 --- a/tests/ui/fail/js_concat.stderr +++ b/tests/ui/fail/js_concat.stderr @@ -1,4 +1,4 @@ -error: Potentially invalid expansion. Expected an expression, an identifier, `::`, `<`, `Self`, `break` or 12 others. +error: Potentially invalid expansion. Expected an expression, an identifier, `::`, `<`, `Self`, `break` or 13 others. --> tests/ui/fail/js_concat.rs:5:16 | 5 | $left ++ $right diff --git a/tests/ui/fail/missing_expr.stderr b/tests/ui/fail/missing_expr.stderr index 3259db2..cdbf8d4 100644 --- a/tests/ui/fail/missing_expr.stderr +++ b/tests/ui/fail/missing_expr.stderr @@ -1,4 +1,4 @@ -error: Potentially invalid expansion. Expected an expression, an identifier, `::`, `<`, `Self`, `break` or 12 others. +error: Potentially invalid expansion. Expected an expression, an identifier, `::`, `<`, `Self`, `break` or 13 others. --> tests/ui/fail/missing_expr.rs:6:17 | 6 | let a = ; diff --git a/tests/ui/fail/python_power_operator.stderr b/tests/ui/fail/python_power_operator.stderr index ceee3fd..0c12eb5 100644 --- a/tests/ui/fail/python_power_operator.stderr +++ b/tests/ui/fail/python_power_operator.stderr @@ -1,4 +1,4 @@ -error: Potentially invalid expansion. Expected an expression, an identifier, `::`, `<`, `Self`, `break` or 12 others. +error: Potentially invalid expansion. Expected an expression, an identifier, `::`, `<`, `Self`, `break` or 13 others. --> tests/ui/fail/python_power_operator.rs:5:14 | 5 | $e * *$e diff --git a/tests/ui/pass/match-expr.rs b/tests/ui/pass/match-expr.rs new file mode 100644 index 0000000..999c46c --- /dev/null +++ b/tests/ui/pass/match-expr.rs @@ -0,0 +1,17 @@ +#[allow(unused)] +#[expandable::expr] +#[rustfmt::skip] +macro_rules! test { + ($e:expr, $p:pat) => { + match $e { + // TODO(scrabsha): add tests for match arms that don't end with `,`. + $p => $e, + () => {}, + () if a != $e => {}, + _ => 42, + _ | _ => 31, + } + }; +} + +fn main() {}