From 68a58f255ac7b7487769667fdd5fe2536f952c15 Mon Sep 17 00:00:00 2001 From: Ross Smyth Date: Thu, 15 Feb 2024 19:54:35 -0500 Subject: [PATCH 1/4] Add postfix-match experimental feature Co-authored-by: Josh Stone --- compiler/rustc_ast_passes/src/feature_gate.rs | 1 + compiler/rustc_feature/src/unstable.rs | 2 + compiler/rustc_parse/src/parser/expr.rs | 19 +++++- compiler/rustc_span/src/symbol.rs | 1 + .../src/language-features/postfix-match.md | 22 +++++++ .../feature-gate-postfix_match.rs | 17 +++++ .../feature-gate-postfix_match.stderr | 23 +++++++ .../ui/match/postfix-match/pf-match-chain.rs | 16 +++++ .../postfix-match/pf-match-exhaustiveness.rs | 7 +++ .../pf-match-exhaustiveness.stderr | 21 +++++++ .../ui/match/postfix-match/pf-match-types.rs | 15 +++++ .../match/postfix-match/pf-match-types.stderr | 21 +++++++ tests/ui/match/postfix-match/postfix-match.rs | 62 +++++++++++++++++++ 13 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 src/doc/unstable-book/src/language-features/postfix-match.md create mode 100644 tests/ui/feature-gates/feature-gate-postfix_match.rs create mode 100644 tests/ui/feature-gates/feature-gate-postfix_match.stderr create mode 100644 tests/ui/match/postfix-match/pf-match-chain.rs create mode 100644 tests/ui/match/postfix-match/pf-match-exhaustiveness.rs create mode 100644 tests/ui/match/postfix-match/pf-match-exhaustiveness.stderr create mode 100644 tests/ui/match/postfix-match/pf-match-types.rs create mode 100644 tests/ui/match/postfix-match/pf-match-types.stderr create mode 100644 tests/ui/match/postfix-match/postfix-match.rs diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index a28fcb0077948..296f237763eb2 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -565,6 +565,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) { gate_all!(generic_const_items, "generic const items are experimental"); gate_all!(unnamed_fields, "unnamed fields are not yet fully implemented"); gate_all!(fn_delegation, "functions delegation is not yet fully implemented"); + gate_all!(postfix_match, "postfix match is experimental"); if !visitor.features.never_patterns { if let Some(spans) = spans.get(&sym::never_patterns) { diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 17c4d81474e27..89f471d21bb07 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -555,6 +555,8 @@ declare_features! ( (unstable, offset_of_nested, "1.77.0", Some(120140)), /// Allows using `#[optimize(X)]`. (unstable, optimize_attribute, "1.34.0", Some(54882)), + /// Allows postfix match `expr.match { ... }` + (unstable, postfix_match, "CURRENT_RUSTC_VERSION", Some(121618)), /// Allows macro attributes on expressions, statements and non-inline modules. (unstable, proc_macro_hygiene, "1.30.0", Some(54727)), /// Allows `&raw const $place_expr` and `&raw mut $place_expr` expressions. diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 6cc358db9fcae..02ff452473dd0 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1375,6 +1375,12 @@ impl<'a> Parser<'a> { return Ok(self.mk_await_expr(self_arg, lo)); } + if self.eat_keyword(kw::Match) { + let match_span = self.prev_token.span; + self.psess.gated_spans.gate(sym::postfix_match, match_span); + return self.parse_match_block(lo, match_span, self_arg); + } + let fn_span_lo = self.token.span; let mut seg = self.parse_path_segment(PathStyle::Expr, None)?; self.check_trailing_angle_brackets(&seg, &[&token::OpenDelim(Delimiter::Parenthesis)]); @@ -2889,8 +2895,19 @@ impl<'a> Parser<'a> { /// Parses a `match ... { ... }` expression (`match` token already eaten). fn parse_expr_match(&mut self) -> PResult<'a, P> { let match_span = self.prev_token.span; - let lo = self.prev_token.span; let scrutinee = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None)?; + + self.parse_match_block(match_span, match_span, scrutinee) + } + + /// Parses a `match expr { ... }` or a `expr.match { ... }` expression. + /// This is after the match token and scrutinee are eaten + fn parse_match_block( + &mut self, + lo: Span, + match_span: Span, + scrutinee: P, + ) -> PResult<'a, P> { if let Err(mut e) = self.expect(&token::OpenDelim(Delimiter::Brace)) { if self.token == token::Semi { e.span_suggestion_short( diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 3784a08b1b720..2474189fef6e1 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1319,6 +1319,7 @@ symbols! { poll, poll_next, post_dash_lto: "post-lto", + postfix_match, powerpc_target_feature, powf128, powf16, diff --git a/src/doc/unstable-book/src/language-features/postfix-match.md b/src/doc/unstable-book/src/language-features/postfix-match.md new file mode 100644 index 0000000000000..cd6b6a7442c5a --- /dev/null +++ b/src/doc/unstable-book/src/language-features/postfix-match.md @@ -0,0 +1,22 @@ +# `postfix-match` + +`postfix-match` adds the feature for matching upon values postfix +the expressions that generate the values. + +```rust,edition2021 +#![feature(postfix_match)] + +enum Foo { + Bar, + Baz +} + +fn get_foo() -> Foo { + Foo::Bar +} + +get_foo().match { + Foo::Bar => {}, + Foo::Baz => panic!(), +} +``` diff --git a/tests/ui/feature-gates/feature-gate-postfix_match.rs b/tests/ui/feature-gates/feature-gate-postfix_match.rs new file mode 100644 index 0000000000000..dce7e81a9ae44 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-postfix_match.rs @@ -0,0 +1,17 @@ +// Testing that postfix match doesn't work without enabling the feature + +fn main() { + let val = Some(42); + + val.match { //~ ERROR postfix match is experimental + Some(42) => "the answer to life, the universe, and everything", + _ => "might be the answer to something" + }; + + // Test that the gate works behind a cfg + #[cfg(FALSE)] + val.match { //~ ERROR postfix match is experimental + Some(42) => "the answer to life, the universe, and everything", + _ => "might be the answer to something" + }; +} diff --git a/tests/ui/feature-gates/feature-gate-postfix_match.stderr b/tests/ui/feature-gates/feature-gate-postfix_match.stderr new file mode 100644 index 0000000000000..136838788dd29 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-postfix_match.stderr @@ -0,0 +1,23 @@ +error[E0658]: postfix match is experimental + --> $DIR/feature-gate-postfix_match.rs:6:9 + | +LL | val.match { + | ^^^^^ + | + = note: see issue #121618 for more information + = help: add `#![feature(postfix_match)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: postfix match is experimental + --> $DIR/feature-gate-postfix_match.rs:13:9 + | +LL | val.match { + | ^^^^^ + | + = note: see issue #121618 for more information + = help: add `#![feature(postfix_match)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/match/postfix-match/pf-match-chain.rs b/tests/ui/match/postfix-match/pf-match-chain.rs new file mode 100644 index 0000000000000..80546e1963bb6 --- /dev/null +++ b/tests/ui/match/postfix-match/pf-match-chain.rs @@ -0,0 +1,16 @@ +//@ run-pass + +#![feature(postfix_match)] + +fn main() { + 1.match { + 2 => Some(0), + _ => None, + }.match { + None => Ok(true), + Some(_) => Err("nope") + }.match { + Ok(_) => (), + Err(_) => panic!() + } +} diff --git a/tests/ui/match/postfix-match/pf-match-exhaustiveness.rs b/tests/ui/match/postfix-match/pf-match-exhaustiveness.rs new file mode 100644 index 0000000000000..f4cac46f7cd64 --- /dev/null +++ b/tests/ui/match/postfix-match/pf-match-exhaustiveness.rs @@ -0,0 +1,7 @@ +#![feature(postfix_match)] + +fn main() { + Some(1).match { //~ non-exhaustive patterns + None => {}, + } +} diff --git a/tests/ui/match/postfix-match/pf-match-exhaustiveness.stderr b/tests/ui/match/postfix-match/pf-match-exhaustiveness.stderr new file mode 100644 index 0000000000000..f458218bb5d67 --- /dev/null +++ b/tests/ui/match/postfix-match/pf-match-exhaustiveness.stderr @@ -0,0 +1,21 @@ +error[E0004]: non-exhaustive patterns: `Some(_)` not covered + --> $DIR/pf-match-exhaustiveness.rs:4:5 + | +LL | Some(1).match { + | ^^^^^^^ pattern `Some(_)` not covered + | +note: `Option` defined here + --> $SRC_DIR/core/src/option.rs:LL:COL + ::: $SRC_DIR/core/src/option.rs:LL:COL + | + = note: not covered + = note: the matched value is of type `Option` +help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown + | +LL ~ None => {}, +LL ~ Some(_) => todo!(), + | + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0004`. diff --git a/tests/ui/match/postfix-match/pf-match-types.rs b/tests/ui/match/postfix-match/pf-match-types.rs new file mode 100644 index 0000000000000..af205926fb610 --- /dev/null +++ b/tests/ui/match/postfix-match/pf-match-types.rs @@ -0,0 +1,15 @@ +#![feature(postfix_match)] + +fn main() { + Some(10).match { + //~^ NOTE `match` arms have incompatible types + Some(5) => false, + //~^ NOTE this is found to be of type `bool` + Some(2) => true, + //~^ NOTE this is found to be of type `bool` + None => (), + //~^ ERROR `match` arms have incompatible types + //~| NOTE expected `bool`, found `()` + _ => true + } +} diff --git a/tests/ui/match/postfix-match/pf-match-types.stderr b/tests/ui/match/postfix-match/pf-match-types.stderr new file mode 100644 index 0000000000000..0cfc1363d5f1e --- /dev/null +++ b/tests/ui/match/postfix-match/pf-match-types.stderr @@ -0,0 +1,21 @@ +error[E0308]: `match` arms have incompatible types + --> $DIR/pf-match-types.rs:10:20 + | +LL | / Some(10).match { +LL | | +LL | | Some(5) => false, + | | ----- this is found to be of type `bool` +LL | | +LL | | Some(2) => true, + | | ---- this is found to be of type `bool` +LL | | +LL | | None => (), + | | ^^ expected `bool`, found `()` +... | +LL | | _ => true +LL | | } + | |_____- `match` arms have incompatible types + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/match/postfix-match/postfix-match.rs b/tests/ui/match/postfix-match/postfix-match.rs new file mode 100644 index 0000000000000..03c4e8ab5451f --- /dev/null +++ b/tests/ui/match/postfix-match/postfix-match.rs @@ -0,0 +1,62 @@ +//@ run-pass + +#![feature(postfix_match)] + +struct Bar { + foo: u8, + baz: u8, +} + +pub fn main() { + let thing = Some("thing"); + + thing.match { + Some("nothing") => {}, + Some(text) if text.eq_ignore_ascii_case("tapir") => {}, + Some("true") | Some("false") => {}, + Some("thing") => {}, + Some(_) => {}, + None => {} + }; + + let num = 2u8; + + num.match { + 0 => {}, + 1..=5 => {}, + _ => {}, + }; + + let slic = &[1, 2, 3, 4][..]; + + slic.match { + [1] => {}, + [2, _tail @ ..] => {}, + [1, _] => {}, + _ => {}, + }; + + slic[0].match { + 1 => 0, + i => i, + }; + + let out = (1, 2).match { + (1, 3) => 0, + (_, 1) => 0, + (1, i) => i, + _ => 3, + }; + assert!(out == 2); + + let strct = Bar { + foo: 3, + baz: 4 + }; + + strct.match { + Bar { foo: 1, .. } => {}, + Bar { baz: 2, .. } => {}, + _ => (), + }; +} From 78b3bf98c3425d139b42a6326701573ff98b1a1f Mon Sep 17 00:00:00 2001 From: Ross Smyth Date: Sat, 17 Feb 2024 12:43:54 -0500 Subject: [PATCH 2/4] Add MatchKind member to the Match expr for pretty printing & fmt --- compiler/rustc_ast/src/ast.rs | 11 +++++++++- compiler/rustc_ast/src/mut_visit.rs | 2 +- compiler/rustc_ast/src/visit.rs | 2 +- compiler/rustc_ast_lowering/src/expr.rs | 2 +- .../rustc_ast_pretty/src/pprust/state/expr.rs | 20 +++++++++++++----- .../src/assert/context.rs | 2 +- .../src/deriving/cmp/partial_ord.rs | 2 +- .../src/deriving/debug.rs | 3 +-- compiler/rustc_expand/src/build.rs | 4 ++-- compiler/rustc_lint/src/unused.rs | 4 ++-- compiler/rustc_parse/src/parser/expr.rs | 18 +++++++++------- .../clippy/clippy_lints/src/redundant_else.rs | 2 +- .../src/suspicious_operation_groupings.rs | 2 +- .../clippy/clippy_utils/src/ast_utils.rs | 2 +- src/tools/rustfmt/src/expr.rs | 8 +++---- src/tools/rustfmt/src/matches.rs | 4 +++- tests/pretty/postfix-match.rs | 21 +++++++++++++++++++ 17 files changed, 76 insertions(+), 33 deletions(-) create mode 100644 tests/pretty/postfix-match.rs diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 13b1a589d9ba9..6ffb3d65abcad 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1436,7 +1436,7 @@ pub enum ExprKind { /// `'label: loop { block }` Loop(P, Option