From caa488b96e65131e4d70f219d5e89008031f9229 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 27 Nov 2023 01:08:39 +0100 Subject: [PATCH 1/7] Add tests --- tests/ui/never_patterns/check.rs | 30 +++++ tests/ui/never_patterns/check.stderr | 8 ++ tests/ui/parser/match-arm-without-body.rs | 68 ++++++++++ tests/ui/parser/match-arm-without-body.stderr | 122 ++++++++++++++++++ tests/ui/pattern/never_patterns.rs | 2 +- 5 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 tests/ui/never_patterns/check.rs create mode 100644 tests/ui/never_patterns/check.stderr create mode 100644 tests/ui/parser/match-arm-without-body.rs create mode 100644 tests/ui/parser/match-arm-without-body.stderr diff --git a/tests/ui/never_patterns/check.rs b/tests/ui/never_patterns/check.rs new file mode 100644 index 00000000000..bcc3a760c10 --- /dev/null +++ b/tests/ui/never_patterns/check.rs @@ -0,0 +1,30 @@ +#![feature(never_patterns)] +#![allow(incomplete_features)] + +enum Void {} + +fn main() {} + +macro_rules! never { + () => { ! } +} + +fn no_arms_or_guards(x: Void) { + match None:: { + Some(!) => {} + None => {} + } + match None:: { + Some(!) if true, + //~^ ERROR expected one of + None => {} + } + match None:: { + Some(!) if true => {} + None => {} + } + match None:: { + Some(never!()) => {}, + None => {} + } +} diff --git a/tests/ui/never_patterns/check.stderr b/tests/ui/never_patterns/check.stderr new file mode 100644 index 00000000000..d7cdd64840f --- /dev/null +++ b/tests/ui/never_patterns/check.stderr @@ -0,0 +1,8 @@ +error: expected one of `.`, `=>`, `?`, or an operator, found `,` + --> $DIR/check.rs:18:24 + | +LL | Some(!) if true, + | ^ expected one of `.`, `=>`, `?`, or an operator + +error: aborting due to 1 previous error + diff --git a/tests/ui/parser/match-arm-without-body.rs b/tests/ui/parser/match-arm-without-body.rs new file mode 100644 index 00000000000..5f009c7a355 --- /dev/null +++ b/tests/ui/parser/match-arm-without-body.rs @@ -0,0 +1,68 @@ +macro_rules! pat { + () => { Some(_) } +} + +fn main() { + match Some(false) { + Some(_) + } + //~^ ERROR expected one of + match Some(false) { + Some(_) + _ => {} + //~^ ERROR expected one of + } + match Some(false) { + Some(_), + //~^ ERROR unexpected `,` in pattern + //~| HELP try adding parentheses to match on a tuple + //~| HELP or a vertical bar to match on multiple alternatives + } + match Some(false) { + Some(_), + //~^ ERROR unexpected `,` in pattern + //~| HELP try adding parentheses to match on a tuple + //~| HELP or a vertical bar to match on multiple alternatives + _ => {} + } + match Some(false) { + Some(_) if true + } + //~^ ERROR expected one of + match Some(false) { + Some(_) if true + _ => {} + //~^ ERROR expected one of + } + match Some(false) { + Some(_) if true, + //~^ ERROR expected one of + } + match Some(false) { + Some(_) if true, + //~^ ERROR expected one of + _ => {} + } + match Some(false) { + pat!() + } + //~^ ERROR expected one of + match Some(false) { + pat!(), + //~^ ERROR unexpected `,` in pattern + } + match Some(false) { + pat!() if true, + //~^ ERROR expected one of + } + match Some(false) { + pat!() + _ => {} + //~^ ERROR expected one of + } + match Some(false) { + pat!(), + //~^ ERROR unexpected `,` in pattern + _ => {} + } +} diff --git a/tests/ui/parser/match-arm-without-body.stderr b/tests/ui/parser/match-arm-without-body.stderr new file mode 100644 index 00000000000..210007628db --- /dev/null +++ b/tests/ui/parser/match-arm-without-body.stderr @@ -0,0 +1,122 @@ +error: expected one of `=>`, `if`, or `|`, found `}` + --> $DIR/match-arm-without-body.rs:8:5 + | +LL | Some(_) + | - expected one of `=>`, `if`, or `|` +LL | } + | ^ unexpected token + +error: expected one of `=>`, `if`, or `|`, found reserved identifier `_` + --> $DIR/match-arm-without-body.rs:12:9 + | +LL | Some(_) + | - expected one of `=>`, `if`, or `|` +LL | _ => {} + | ^ unexpected token + +error: unexpected `,` in pattern + --> $DIR/match-arm-without-body.rs:16:16 + | +LL | Some(_), + | ^ + | +help: try adding parentheses to match on a tuple... + | +LL | (Some(_),) + | + + +help: ...or a vertical bar to match on multiple alternatives + | +LL | Some(_) | + | + +error: unexpected `,` in pattern + --> $DIR/match-arm-without-body.rs:22:16 + | +LL | Some(_), + | ^ + | +help: try adding parentheses to match on a tuple... + | +LL ~ (Some(_), +LL | +LL | +LL | +LL ~ _) => {} + | +help: ...or a vertical bar to match on multiple alternatives + | +LL ~ Some(_) | +LL + +LL + +LL + +LL ~ _ => {} + | + +error: expected one of `.`, `=>`, `?`, or an operator, found `}` + --> $DIR/match-arm-without-body.rs:30:5 + | +LL | Some(_) if true + | - expected one of `.`, `=>`, `?`, or an operator +LL | } + | ^ unexpected token + +error: expected one of `.`, `=>`, `?`, or an operator, found reserved identifier `_` + --> $DIR/match-arm-without-body.rs:34:9 + | +LL | Some(_) if true + | - expected one of `.`, `=>`, `?`, or an operator +LL | _ => {} + | ^ unexpected token + +error: expected one of `.`, `=>`, `?`, or an operator, found `,` + --> $DIR/match-arm-without-body.rs:38:24 + | +LL | Some(_) if true, + | ^ expected one of `.`, `=>`, `?`, or an operator + +error: expected one of `.`, `=>`, `?`, or an operator, found `,` + --> $DIR/match-arm-without-body.rs:42:24 + | +LL | Some(_) if true, + | ^ expected one of `.`, `=>`, `?`, or an operator + +error: expected one of `=>`, `if`, or `|`, found `}` + --> $DIR/match-arm-without-body.rs:48:5 + | +LL | pat!() + | - expected one of `=>`, `if`, or `|` +LL | } + | ^ unexpected token + +error: unexpected `,` in pattern + --> $DIR/match-arm-without-body.rs:51:15 + | +LL | pat!(), + | ^ + | + = note: macros cannot expand to match arms + +error: expected one of `.`, `=>`, `?`, or an operator, found `,` + --> $DIR/match-arm-without-body.rs:55:23 + | +LL | pat!() if true, + | ^ expected one of `.`, `=>`, `?`, or an operator + +error: expected one of `=>`, `if`, or `|`, found reserved identifier `_` + --> $DIR/match-arm-without-body.rs:60:9 + | +LL | pat!() + | - expected one of `=>`, `if`, or `|` +LL | _ => {} + | ^ unexpected token + +error: unexpected `,` in pattern + --> $DIR/match-arm-without-body.rs:64:15 + | +LL | pat!(), + | ^ + | + = note: macros cannot expand to match arms + +error: aborting due to 13 previous errors + diff --git a/tests/ui/pattern/never_patterns.rs b/tests/ui/pattern/never_patterns.rs index e2e17e0e9a7..fdba1b8e087 100644 --- a/tests/ui/pattern/never_patterns.rs +++ b/tests/ui/pattern/never_patterns.rs @@ -84,7 +84,7 @@ fn never_and_bindings() { //~^ ERROR: is not bound in all patterns } let (Ok(_x) | Err(&!)) = x; - //~^ ERROR: is not bound in all patterns + //~^ ERROR: is not bound in all patterns // FIXME(never_patterns): A never pattern mustn't have bindings. match x { From 80bdcbf50a63845dd3cfeb05751ba3dcbd1025b8 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 27 Nov 2023 03:15:56 +0100 Subject: [PATCH 2/7] Parse a pattern with no arm --- compiler/rustc_ast/src/ast.rs | 22 +- compiler/rustc_ast/src/mut_visit.rs | 2 +- compiler/rustc_ast/src/visit.rs | 2 +- compiler/rustc_ast_lowering/src/expr.rs | 29 +- .../rustc_ast_pretty/src/pprust/state/expr.rs | 39 +-- .../src/deriving/cmp/partial_ord.rs | 2 +- compiler/rustc_expand/src/build.rs | 2 +- compiler/rustc_expand/src/placeholders.rs | 2 +- compiler/rustc_lint/src/builtin.rs | 6 +- compiler/rustc_lint/src/unused.rs | 20 +- compiler/rustc_parse/src/parser/expr.rs | 250 ++++++++++-------- compiler/rustc_parse/src/parser/pat.rs | 4 +- compiler/rustc_resolve/src/late.rs | 2 +- .../clippy_lints/src/non_expressive_names.rs | 4 +- .../clippy/clippy_lints/src/redundant_else.rs | 4 +- .../clippy/clippy_utils/src/ast_utils.rs | 2 +- src/tools/rustfmt/src/matches.rs | 8 +- src/tools/rustfmt/src/spanned.rs | 7 +- .../range_pat_interactions1.rs | 2 +- .../range_pat_interactions1.stderr | 4 +- .../range_pat_interactions2.rs | 2 +- .../range_pat_interactions2.stderr | 4 +- tests/ui/never_patterns/check.rs | 3 +- tests/ui/never_patterns/check.stderr | 41 ++- tests/ui/never_patterns/parse.rs | 68 +++++ tests/ui/never_patterns/parse.stderr | 26 ++ .../attribute/attr-stmt-expr-attr-bad.rs | 6 +- .../attribute/attr-stmt-expr-attr-bad.stderr | 12 +- tests/ui/parser/issues/issue-24375.rs | 2 +- tests/ui/parser/issues/issue-24375.stderr | 4 +- .../parser/macro/macro-expand-to-match-arm.rs | 6 +- .../macro/macro-expand-to-match-arm.stderr | 11 +- tests/ui/parser/match-arm-without-body.rs | 11 +- tests/ui/parser/match-arm-without-body.stderr | 84 +----- tests/ui/parser/pat-lt-bracket-1.rs | 2 +- tests/ui/parser/pat-lt-bracket-1.stderr | 4 +- 36 files changed, 420 insertions(+), 279 deletions(-) create mode 100644 tests/ui/never_patterns/parse.rs create mode 100644 tests/ui/never_patterns/parse.stderr diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index cba9121aa5a..62bacf97f49 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -658,6 +658,24 @@ pub fn walk(&self, it: &mut impl FnMut(&Pat) -> bool) { pub fn is_rest(&self) -> bool { matches!(self.kind, PatKind::Rest) } + + /// Could this be a never pattern? I.e. is it a never pattern modulo macro invocations that + /// might return never patterns? + pub fn could_be_never_pattern(&self) -> bool { + let mut could_be_never_pattern = false; + self.walk(&mut |pat| match &pat.kind { + PatKind::Never | PatKind::MacCall(_) => { + could_be_never_pattern = true; + false + } + PatKind::Or(s) => { + could_be_never_pattern = s.iter().all(|p| p.could_be_never_pattern()); + false + } + _ => true, + }); + could_be_never_pattern + } } /// A single field in a struct pattern. @@ -1080,8 +1098,8 @@ pub struct Arm { pub pat: P, /// Match arm guard, e.g. `n > 10` in `match foo { n if n > 10 => {}, _ => {} }` pub guard: Option>, - /// Match arm body. - pub body: P, + /// Match arm body. Omitted if the pattern is a never pattern. + pub body: Option>, pub span: Span, pub id: NodeId, pub is_placeholder: bool, diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs index 8ce86bf9ecf..e909e8dc774 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -453,7 +453,7 @@ pub fn noop_visit_use_tree(use_tree: &mut UseTree, vis: &mut T) { vis.visit_id(id); vis.visit_pat(pat); visit_opt(guard, |guard| vis.visit_expr(guard)); - vis.visit_expr(body); + visit_opt(body, |body| vis.visit_expr(body)); vis.visit_span(span); smallvec![arm] } diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index 9dbadcb49d3..773a7ebc707 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -951,7 +951,7 @@ pub fn walk_param<'a, V: Visitor<'a>>(visitor: &mut V, param: &'a Param) { pub fn walk_arm<'a, V: Visitor<'a>>(visitor: &mut V, arm: &'a Arm) { visitor.visit_pat(&arm.pat); walk_list!(visitor, visit_expr, &arm.guard); - visitor.visit_expr(&arm.body); + walk_list!(visitor, visit_expr, &arm.body); walk_list!(visitor, visit_attribute, &arm.attrs); } diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index be5671f1bf7..b688950f5a3 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -566,13 +566,28 @@ fn lower_arm(&mut self, arm: &Arm) -> hir::Arm<'hir> { }); let hir_id = self.next_id(); self.lower_attrs(hir_id, &arm.attrs); - hir::Arm { - hir_id, - pat, - guard, - body: self.lower_expr(&arm.body), - span: self.lower_span(arm.span), - } + let body = if let Some(body) = &arm.body { + self.lower_expr(body) + } else { + // An arm without a body, meant for never patterns. + // We add a fake `loop {}` arm body so that it typecks to `!`. + // FIXME(never_patterns): Desugar into a call to `unreachable_unchecked`. + let span = pat.span; + let block = self.arena.alloc(hir::Block { + stmts: &[], + expr: None, + hir_id: self.next_id(), + rules: hir::BlockCheckMode::DefaultBlock, + span, + targeted_by_break: false, + }); + self.arena.alloc(hir::Expr { + hir_id: self.next_id(), + kind: hir::ExprKind::Loop(block, None, hir::LoopSource::Loop, span), + span, + }) + }; + hir::Arm { hir_id, pat, guard, body, span: self.lower_span(arm.span) } } /// Lower an `async` construct to a coroutine that implements `Future`. diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index 45a55e20ca7..d7dbc7ac035 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -631,28 +631,33 @@ fn print_arm(&mut self, arm: &ast::Arm) { self.print_expr(e); self.space(); } - self.word_space("=>"); - match &arm.body.kind { - ast::ExprKind::Block(blk, opt_label) => { - if let Some(label) = opt_label { - self.print_ident(label.ident); - self.word_space(":"); + if let Some(body) = &arm.body { + self.word_space("=>"); + + match &body.kind { + ast::ExprKind::Block(blk, opt_label) => { + if let Some(label) = opt_label { + self.print_ident(label.ident); + self.word_space(":"); + } + + // The block will close the pattern's ibox. + self.print_block_unclosed_indent(blk); + + // If it is a user-provided unsafe block, print a comma after it. + if let BlockCheckMode::Unsafe(ast::UserProvided) = blk.rules { + self.word(","); + } } - - // The block will close the pattern's ibox. - self.print_block_unclosed_indent(blk); - - // If it is a user-provided unsafe block, print a comma after it. - if let BlockCheckMode::Unsafe(ast::UserProvided) = blk.rules { + _ => { + self.end(); // Close the ibox for the pattern. + self.print_expr(body); self.word(","); } } - _ => { - self.end(); // Close the ibox for the pattern. - self.print_expr(&arm.body); - self.word(","); - } + } else { + self.word(","); } self.end(); // Close enclosing cbox. } diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs index f3164bd2c2a..7f5589210d4 100644 --- a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs @@ -136,7 +136,7 @@ fn cs_partial_cmp( && let Some(last) = arms.last_mut() && let PatKind::Wild = last.pat.kind { - last.body = expr2; + last.body = Some(expr2); expr1 } else { let eq_arm = cx.arm( diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs index 9a8d0d691f0..0996a45d900 100644 --- a/compiler/rustc_expand/src/build.rs +++ b/compiler/rustc_expand/src/build.rs @@ -505,7 +505,7 @@ pub fn arm(&self, span: Span, pat: P, expr: P) -> ast::Arm attrs: AttrVec::new(), pat, guard: None, - body: expr, + body: Some(expr), span, id: ast::DUMMY_NODE_ID, is_placeholder: false, diff --git a/compiler/rustc_expand/src/placeholders.rs b/compiler/rustc_expand/src/placeholders.rs index 1292a855230..ded0baa9563 100644 --- a/compiler/rustc_expand/src/placeholders.rs +++ b/compiler/rustc_expand/src/placeholders.rs @@ -119,7 +119,7 @@ fn mac_placeholder() -> P { }]), AstFragmentKind::Arms => AstFragment::Arms(smallvec![ast::Arm { attrs: Default::default(), - body: expr_placeholder(), + body: Some(expr_placeholder()), guard: None, id, pat: pat(), diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 8932200c5b7..64de5e92abf 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -1000,8 +1000,10 @@ fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) { } fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &ast::Arm) { - let arm_span = arm.pat.span.with_hi(arm.body.span.hi()); - warn_if_doc(cx, arm_span, "match arms", &arm.attrs); + if let Some(body) = &arm.body { + let arm_span = arm.pat.span.with_hi(body.span.hi()); + warn_if_doc(cx, arm_span, "match arms", &arm.attrs); + } } fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &ast::Pat) { diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs index 0a167b0893c..92deebb9d2c 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -1113,15 +1113,17 @@ fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { } ExprKind::Match(ref _expr, ref arm) => { for a in arm { - self.check_unused_delims_expr( - cx, - &a.body, - UnusedDelimsCtx::MatchArmExpr, - false, - None, - None, - true, - ); + if let Some(body) = &a.body { + self.check_unused_delims_expr( + cx, + body, + UnusedDelimsCtx::MatchArmExpr, + false, + None, + None, + true, + ); + } } } _ => {} diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index b1b77305e4f..09c0dd78581 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -2899,127 +2899,155 @@ pub(super) fn parse_arm(&mut self) -> PResult<'a, Arm> { self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| { let lo = this.token.span; let (pat, guard) = this.parse_match_arm_pat_and_guard()?; - let arrow_span = this.token.span; - if let Err(mut err) = this.expect(&token::FatArrow) { - // We might have a `=>` -> `=` or `->` typo (issue #89396). - if TokenKind::FatArrow - .similar_tokens() - .is_some_and(|similar_tokens| similar_tokens.contains(&this.token.kind)) - { - err.span_suggestion( - this.token.span, - "use a fat arrow to start a match arm", - "=>", - Applicability::MachineApplicable, - ); - if matches!( - (&this.prev_token.kind, &this.token.kind), - (token::DotDotEq, token::Gt) - ) { - // `error_inclusive_range_match_arrow` handles cases like `0..=> {}`, - // so we suppress the error here - err.delay_as_bug(); - } else { - err.emit(); - } - this.bump(); - } else { - return Err(err); - } - } - let arm_start_span = this.token.span; - let expr = this.parse_expr_res(Restrictions::STMT_EXPR, None).map_err(|mut err| { - err.span_label(arrow_span, "while parsing the `match` arm starting here"); - err - })?; - - let require_comma = classify::expr_requires_semi_to_be_stmt(&expr) - && this.token != token::CloseDelim(Delimiter::Brace); - - let hi = this.prev_token.span; - - if require_comma { - let sm = this.sess.source_map(); - if let Some(body) = this.parse_arm_body_missing_braces(&expr, arrow_span) { - let span = body.span; - return Ok(( - ast::Arm { - attrs, - pat, - guard, - body, - span, - id: DUMMY_NODE_ID, - is_placeholder: false, - }, - TrailingToken::None, - )); - } + let span_before_body = this.prev_token.span; + let arm_body; + let is_fat_arrow = this.check(&token::FatArrow); + let is_almost_fat_arrow = TokenKind::FatArrow + .similar_tokens() + .is_some_and(|similar_tokens| similar_tokens.contains(&this.token.kind)); + let mut result = if !is_fat_arrow && !is_almost_fat_arrow { + // A pattern without a body, allowed for never patterns. + arm_body = None; this.expect_one_of(&[token::Comma], &[token::CloseDelim(Delimiter::Brace)]) - .or_else(|mut err| { - if this.token == token::FatArrow { - if let Ok(expr_lines) = sm.span_to_lines(expr.span) - && let Ok(arm_start_lines) = sm.span_to_lines(arm_start_span) - && arm_start_lines.lines[0].end_col == expr_lines.lines[0].end_col - && expr_lines.lines.len() == 2 - { - // We check whether there's any trailing code in the parse span, - // if there isn't, we very likely have the following: - // - // X | &Y => "y" - // | -- - missing comma - // | | - // | arrow_span - // X | &X => "x" - // | - ^^ self.token.span - // | | - // | parsed until here as `"y" & X` - err.span_suggestion_short( - arm_start_span.shrink_to_hi(), - "missing a comma here to end this `match` arm", - ",", - Applicability::MachineApplicable, - ); - return Err(err); - } - } else { - // FIXME(compiler-errors): We could also recover `; PAT =>` here - - // Try to parse a following `PAT =>`, if successful - // then we should recover. - let mut snapshot = this.create_snapshot_for_diagnostic(); - let pattern_follows = snapshot - .parse_pat_allow_top_alt( - None, - RecoverComma::Yes, - RecoverColon::Yes, - CommaRecoveryMode::EitherTupleOrPipe, - ) - .map_err(|err| err.cancel()) - .is_ok(); - if pattern_follows && snapshot.check(&TokenKind::FatArrow) { - err.cancel(); - this.sess.emit_err(errors::MissingCommaAfterMatchArm { - span: hi.shrink_to_hi(), - }); - return Ok(true); - } - } - err.span_label(arrow_span, "while parsing the `match` arm starting here"); - Err(err) - })?; } else { - this.eat(&token::Comma); + if let Err(mut err) = this.expect(&token::FatArrow) { + // We might have a `=>` -> `=` or `->` typo (issue #89396). + if is_almost_fat_arrow { + err.span_suggestion( + this.token.span, + "use a fat arrow to start a match arm", + "=>", + Applicability::MachineApplicable, + ); + if matches!( + (&this.prev_token.kind, &this.token.kind), + (token::DotDotEq, token::Gt) + ) { + // `error_inclusive_range_match_arrow` handles cases like `0..=> {}`, + // so we suppress the error here + err.delay_as_bug(); + } else { + err.emit(); + } + this.bump(); + } else { + return Err(err); + } + } + let arrow_span = this.prev_token.span; + let arm_start_span = this.token.span; + + let expr = + this.parse_expr_res(Restrictions::STMT_EXPR, None).map_err(|mut err| { + err.span_label(arrow_span, "while parsing the `match` arm starting here"); + err + })?; + + let require_comma = classify::expr_requires_semi_to_be_stmt(&expr) + && this.token != token::CloseDelim(Delimiter::Brace); + + if !require_comma { + arm_body = Some(expr); + this.eat(&token::Comma); + Ok(false) + } else if let Some(body) = this.parse_arm_body_missing_braces(&expr, arrow_span) { + arm_body = Some(body); + Ok(true) + } else { + let expr_span = expr.span; + arm_body = Some(expr); + this.expect_one_of(&[token::Comma], &[token::CloseDelim(Delimiter::Brace)]) + .map_err(|mut err| { + if this.token == token::FatArrow { + let sm = this.sess.source_map(); + if let Ok(expr_lines) = sm.span_to_lines(expr_span) + && let Ok(arm_start_lines) = sm.span_to_lines(arm_start_span) + && arm_start_lines.lines[0].end_col + == expr_lines.lines[0].end_col + && expr_lines.lines.len() == 2 + { + // We check whether there's any trailing code in the parse span, + // if there isn't, we very likely have the following: + // + // X | &Y => "y" + // | -- - missing comma + // | | + // | arrow_span + // X | &X => "x" + // | - ^^ self.token.span + // | | + // | parsed until here as `"y" & X` + err.span_suggestion_short( + arm_start_span.shrink_to_hi(), + "missing a comma here to end this `match` arm", + ",", + Applicability::MachineApplicable, + ); + } + } else { + err.span_label( + arrow_span, + "while parsing the `match` arm starting here", + ); + } + err + }) + } + }; + + let hi_span = arm_body.as_ref().map_or(span_before_body, |body| body.span); + let arm_span = lo.to(hi_span); + + // We want to recover: + // X | Some(_) => foo() + // | - missing comma + // X | None => "x" + // | ^^^^ self.token.span + // as well as: + // X | Some(!) + // | - missing comma + // X | None => "x" + // | ^^^^ self.token.span + // But we musn't recover + // X | pat[0] => {} + // | ^ self.token.span + let recover_missing_comma = arm_body.is_some() || pat.could_be_never_pattern(); + if recover_missing_comma { + result = result.or_else(|err| { + // FIXME(compiler-errors): We could also recover `; PAT =>` here + + // Try to parse a following `PAT =>`, if successful + // then we should recover. + let mut snapshot = this.create_snapshot_for_diagnostic(); + let pattern_follows = snapshot + .parse_pat_allow_top_alt( + None, + RecoverComma::Yes, + RecoverColon::Yes, + CommaRecoveryMode::EitherTupleOrPipe, + ) + .map_err(|err| err.cancel()) + .is_ok(); + if pattern_follows && snapshot.check(&TokenKind::FatArrow) { + err.cancel(); + this.sess.emit_err(errors::MissingCommaAfterMatchArm { + span: arm_span.shrink_to_hi(), + }); + return Ok(true); + } + Err(err) + }); } + result?; Ok(( ast::Arm { attrs, pat, guard, - body: expr, - span: lo.to(hi), + body: arm_body, + span: arm_span, id: DUMMY_NODE_ID, is_placeholder: false, }, diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index 3d1d1ec8108..9b8a34cf0cc 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -154,7 +154,7 @@ fn parse_pat_allow_top_alt_inner( } Err(err) => return Err(err), }; - if rc == RecoverComma::Yes { + if rc == RecoverComma::Yes && !first_pat.could_be_never_pattern() { self.maybe_recover_unexpected_comma( first_pat.span, matches!(first_pat.kind, PatKind::MacCall(_)), @@ -200,7 +200,7 @@ fn parse_pat_allow_top_alt_inner( err.span_label(lo, WHILE_PARSING_OR_MSG); err })?; - if rc == RecoverComma::Yes { + if rc == RecoverComma::Yes && !pat.could_be_never_pattern() { self.maybe_recover_unexpected_comma(pat.span, false, rt)?; } pats.push(pat); diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 75a0541b89b..5a139244640 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -3294,7 +3294,7 @@ fn resolve_arm(&mut self, arm: &'ast Arm) { self.with_rib(ValueNS, RibKind::Normal, |this| { this.resolve_pattern_top(&arm.pat, PatternSource::Match); walk_list!(this, visit_expr, &arm.guard); - this.visit_expr(&arm.body); + walk_list!(this, visit_expr, &arm.body); }); } diff --git a/src/tools/clippy/clippy_lints/src/non_expressive_names.rs b/src/tools/clippy/clippy_lints/src/non_expressive_names.rs index 649a23565a9..107f488484d 100644 --- a/src/tools/clippy/clippy_lints/src/non_expressive_names.rs +++ b/src/tools/clippy/clippy_lints/src/non_expressive_names.rs @@ -341,7 +341,9 @@ fn visit_arm(&mut self, arm: &'tcx Arm) { self.apply(|this| { SimilarNamesNameVisitor(this).visit_pat(&arm.pat); - this.apply(|this| walk_expr(this, &arm.body)); + if let Some(body) = &arm.body { + this.apply(|this| walk_expr(this, body)); + } }); self.check_single_char_names(); diff --git a/src/tools/clippy/clippy_lints/src/redundant_else.rs b/src/tools/clippy/clippy_lints/src/redundant_else.rs index 221aa317e5d..530fc664657 100644 --- a/src/tools/clippy/clippy_lints/src/redundant_else.rs +++ b/src/tools/clippy/clippy_lints/src/redundant_else.rs @@ -105,7 +105,9 @@ fn visit_block(&mut self, block: &'ast Block) { fn visit_expr(&mut self, expr: &'ast Expr) { self.is_break = match expr.kind { ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) => true, - ExprKind::Match(_, ref arms) => arms.iter().all(|arm| self.check_expr(&arm.body)), + ExprKind::Match(_, ref arms) => arms.iter().all(|arm| + arm.body.is_none() || arm.body.as_deref().is_some_and(|body| self.check_expr(body)) + ), ExprKind::If(_, ref then, Some(ref els)) => self.check_block(then) && self.check_expr(els), ExprKind::If(_, _, None) // ignore loops for simplicity diff --git a/src/tools/clippy/clippy_utils/src/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs index a2c61e07b70..b2cff164661 100644 --- a/src/tools/clippy/clippy_utils/src/ast_utils.rs +++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs @@ -236,7 +236,7 @@ pub fn eq_field(l: &ExprField, r: &ExprField) -> bool { pub fn eq_arm(l: &Arm, r: &Arm) -> bool { l.is_placeholder == r.is_placeholder && eq_pat(&l.pat, &r.pat) - && eq_expr(&l.body, &r.body) + && eq_expr_opt(&l.body, &r.body) && eq_expr_opt(&l.guard, &r.guard) && over(&l.attrs, &r.attrs, eq_attr) } diff --git a/src/tools/rustfmt/src/matches.rs b/src/tools/rustfmt/src/matches.rs index 95b0ed16db8..ef509b56837 100644 --- a/src/tools/rustfmt/src/matches.rs +++ b/src/tools/rustfmt/src/matches.rs @@ -223,7 +223,7 @@ fn rewrite_match_arm( ) -> Option { let (missing_span, attrs_str) = if !arm.attrs.is_empty() { if contains_skip(&arm.attrs) { - let (_, body) = flatten_arm_body(context, &arm.body, None); + let (_, body) = flatten_arm_body(context, arm.body.as_deref()?, None); // `arm.span()` does not include trailing comma, add it manually. return Some(format!( "{}{}", @@ -246,7 +246,7 @@ fn rewrite_match_arm( }; // Patterns - let pat_shape = match &arm.body.kind { + let pat_shape = match &arm.body.as_ref()?.kind { ast::ExprKind::Block(_, Some(label)) => { // Some block with a label ` => 'label: {` // 7 = ` => : {` @@ -280,10 +280,10 @@ fn rewrite_match_arm( false, )?; - let arrow_span = mk_sp(arm.pat.span.hi(), arm.body.span().lo()); + let arrow_span = mk_sp(arm.pat.span.hi(), arm.body.as_ref()?.span().lo()); rewrite_match_body( context, - &arm.body, + arm.body.as_ref()?, &lhs_str, shape, guard_str.contains('\n'), diff --git a/src/tools/rustfmt/src/spanned.rs b/src/tools/rustfmt/src/spanned.rs index 2136cfeae1a..5960b144499 100644 --- a/src/tools/rustfmt/src/spanned.rs +++ b/src/tools/rustfmt/src/spanned.rs @@ -97,7 +97,12 @@ fn span(&self) -> Span { } else { self.attrs[0].span.lo() }; - span_with_attrs_lo_hi!(self, lo, self.body.span.hi()) + let hi = if let Some(body) = &self.body { + body.span.hi() + } else { + self.pat.span.hi() + }; + span_with_attrs_lo_hi!(self, lo, hi) } } diff --git a/tests/ui/half-open-range-patterns/range_pat_interactions1.rs b/tests/ui/half-open-range-patterns/range_pat_interactions1.rs index 55353999b67..9ffc2190d20 100644 --- a/tests/ui/half-open-range-patterns/range_pat_interactions1.rs +++ b/tests/ui/half-open-range-patterns/range_pat_interactions1.rs @@ -17,7 +17,7 @@ fn main() { } match x as i32 { 0..5+1 => errors_only.push(x), - //~^ error: expected one of `=>`, `if`, or `|`, found `+` + //~^ error: expected one of `,`, `=>`, `if`, `|`, or `}`, found `+` 1 | -3..0 => first_or.push(x), y @ (0..5 | 6) => or_two.push(y), y @ 0..const { 5 + 1 } => assert_eq!(y, 5), diff --git a/tests/ui/half-open-range-patterns/range_pat_interactions1.stderr b/tests/ui/half-open-range-patterns/range_pat_interactions1.stderr index 19ebcaf0f36..05235c9b922 100644 --- a/tests/ui/half-open-range-patterns/range_pat_interactions1.stderr +++ b/tests/ui/half-open-range-patterns/range_pat_interactions1.stderr @@ -1,8 +1,8 @@ -error: expected one of `=>`, `if`, or `|`, found `+` +error: expected one of `,`, `=>`, `if`, `|`, or `}`, found `+` --> $DIR/range_pat_interactions1.rs:19:17 | LL | 0..5+1 => errors_only.push(x), - | ^ expected one of `=>`, `if`, or `|` + | ^ expected one of `,`, `=>`, `if`, `|`, or `}` error[E0408]: variable `n` is not bound in all patterns --> $DIR/range_pat_interactions1.rs:10:25 diff --git a/tests/ui/half-open-range-patterns/range_pat_interactions2.rs b/tests/ui/half-open-range-patterns/range_pat_interactions2.rs index 4615ebd688a..b212bfbe093 100644 --- a/tests/ui/half-open-range-patterns/range_pat_interactions2.rs +++ b/tests/ui/half-open-range-patterns/range_pat_interactions2.rs @@ -9,7 +9,7 @@ fn main() { match x as i32 { 0..=(5+1) => errors_only.push(x), //~^ error: inclusive range with no end - //~| error: expected one of `=>`, `if`, or `|`, found `(` + //~| error: expected one of `,`, `=>`, `if`, `|`, or `}`, found `(` 1 | -3..0 => first_or.push(x), y @ (0..5 | 6) => or_two.push(y), y @ 0..const { 5 + 1 } => assert_eq!(y, 5), diff --git a/tests/ui/half-open-range-patterns/range_pat_interactions2.stderr b/tests/ui/half-open-range-patterns/range_pat_interactions2.stderr index 13a5542a474..0129f927e34 100644 --- a/tests/ui/half-open-range-patterns/range_pat_interactions2.stderr +++ b/tests/ui/half-open-range-patterns/range_pat_interactions2.stderr @@ -6,11 +6,11 @@ LL | 0..=(5+1) => errors_only.push(x), | = note: inclusive ranges must be bounded at the end (`..=b` or `a..=b`) -error: expected one of `=>`, `if`, or `|`, found `(` +error: expected one of `,`, `=>`, `if`, `|`, or `}`, found `(` --> $DIR/range_pat_interactions2.rs:10:17 | LL | 0..=(5+1) => errors_only.push(x), - | ^ expected one of `=>`, `if`, or `|` + | ^ expected one of `,`, `=>`, `if`, `|`, or `}` error: aborting due to 2 previous errors diff --git a/tests/ui/never_patterns/check.rs b/tests/ui/never_patterns/check.rs index bcc3a760c10..9b02fc7996d 100644 --- a/tests/ui/never_patterns/check.rs +++ b/tests/ui/never_patterns/check.rs @@ -15,11 +15,12 @@ fn no_arms_or_guards(x: Void) { None => {} } match None:: { + //~^ ERROR non-exhaustive Some(!) if true, - //~^ ERROR expected one of None => {} } match None:: { + //~^ ERROR non-exhaustive Some(!) if true => {} None => {} } diff --git a/tests/ui/never_patterns/check.stderr b/tests/ui/never_patterns/check.stderr index d7cdd64840f..945812225c4 100644 --- a/tests/ui/never_patterns/check.stderr +++ b/tests/ui/never_patterns/check.stderr @@ -1,8 +1,39 @@ -error: expected one of `.`, `=>`, `?`, or an operator, found `,` - --> $DIR/check.rs:18:24 +error[E0004]: non-exhaustive patterns: `Some(_)` not covered + --> $DIR/check.rs:17:11 + | +LL | match None:: { + | ^^^^^^^^^^^^ 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!() | -LL | Some(!) if true, - | ^ expected one of `.`, `=>`, `?`, or an operator -error: aborting due to 1 previous error +error[E0004]: non-exhaustive patterns: `Some(_)` not covered + --> $DIR/check.rs:22:11 + | +LL | match None:: { + | ^^^^^^^^^^^^ 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 2 previous errors + +For more information about this error, try `rustc --explain E0004`. diff --git a/tests/ui/never_patterns/parse.rs b/tests/ui/never_patterns/parse.rs new file mode 100644 index 00000000000..850416d723a --- /dev/null +++ b/tests/ui/never_patterns/parse.rs @@ -0,0 +1,68 @@ +#![feature(never_patterns)] +#![allow(incomplete_features)] + +enum Void {} + +fn main() {} + +macro_rules! never { + () => { ! } +} + +fn parse(x: Void) { + match None:: { + None => {} + Some(!), + } + match None:: { + Some(!), + None => {} + } + match None:: { + None => {} + Some(!) + } + match None:: { + Some(!) + //~^ ERROR expected `,` following `match` arm + None => {} + } + match None:: { + Some(!) if true + //~^ ERROR expected `,` following `match` arm + None => {} + } + match None:: { + Some(!) if true, + None => {} + } + match None:: { + Some(!) <= + //~^ ERROR expected one of + } + match x { + never!(), + } + match x { + never!() if true, + } + match x { + never!() + } + match &x { + &never!(), + } + match None:: { + Some(never!()), + None => {} + } + match x { ! } + match &x { &! } + + let res: Result = Ok(false); + let Ok(_) = res; + let Ok(_) | Err(!) = &res; // Disallowed; see #82048. + //~^ ERROR top-level or-patterns are not allowed in `let` bindings + let (Ok(_) | Err(!)) = &res; + let (Ok(_) | Err(&!)) = res.as_ref(); +} diff --git a/tests/ui/never_patterns/parse.stderr b/tests/ui/never_patterns/parse.stderr new file mode 100644 index 00000000000..7ea33540c5e --- /dev/null +++ b/tests/ui/never_patterns/parse.stderr @@ -0,0 +1,26 @@ +error: expected `,` following `match` arm + --> $DIR/parse.rs:26:16 + | +LL | Some(!) + | ^ help: missing a comma here to end this `match` arm: `,` + +error: expected `,` following `match` arm + --> $DIR/parse.rs:31:24 + | +LL | Some(!) if true + | ^ help: missing a comma here to end this `match` arm: `,` + +error: expected one of `,`, `=>`, `if`, `|`, or `}`, found `<=` + --> $DIR/parse.rs:40:17 + | +LL | Some(!) <= + | ^^ expected one of `,`, `=>`, `if`, `|`, or `}` + +error: top-level or-patterns are not allowed in `let` bindings + --> $DIR/parse.rs:64:9 + | +LL | let Ok(_) | Err(!) = &res; // Disallowed; see #82048. + | ^^^^^^^^^^^^^^ help: wrap the pattern in parentheses: `(Ok(_) | Err(!))` + +error: aborting due to 4 previous errors + diff --git a/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.rs b/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.rs index d1950087c4c..2c402e4c65e 100644 --- a/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.rs +++ b/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.rs @@ -84,15 +84,15 @@ fn main() {} #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] 10 => () } } //~^ ERROR inclusive range with no end -//~| ERROR expected one of `=>`, `if`, or `|`, found `#` +//~| ERROR expected one of `,`, `=>`, `if`, `|`, or `}`, found `#` #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] -10 => () } } //~^ ERROR inclusive range with no end -//~| ERROR expected one of `=>`, `if`, or `|`, found `#` +//~| ERROR expected one of `,`, `=>`, `if`, `|`, or `}`, found `#` #[cfg(FALSE)] fn e() { match 0 { 0..=-#[attr] 10 => () } } //~^ ERROR unexpected token: `#` #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] FOO => () } } //~^ ERROR inclusive range with no end -//~| ERROR expected one of `=>`, `if`, or `|`, found `#` +//~| ERROR expected one of `,`, `=>`, `if`, `|`, or `}`, found `#` #[cfg(FALSE)] fn e() { let _ = x.#![attr]foo(); } //~^ ERROR unexpected token: `#` diff --git a/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr b/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr index e46c591080d..a0e95c5c1ed 100644 --- a/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr +++ b/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr @@ -365,11 +365,11 @@ LL | #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] 10 => () } } | = note: inclusive ranges must be bounded at the end (`..=b` or `a..=b`) -error: expected one of `=>`, `if`, or `|`, found `#` +error: expected one of `,`, `=>`, `if`, `|`, or `}`, found `#` --> $DIR/attr-stmt-expr-attr-bad.rs:85:38 | LL | #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] 10 => () } } - | ^ expected one of `=>`, `if`, or `|` + | ^ expected one of `,`, `=>`, `if`, `|`, or `}` error[E0586]: inclusive range with no end --> $DIR/attr-stmt-expr-attr-bad.rs:88:35 @@ -379,11 +379,11 @@ LL | #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] -10 => () } } | = note: inclusive ranges must be bounded at the end (`..=b` or `a..=b`) -error: expected one of `=>`, `if`, or `|`, found `#` +error: expected one of `,`, `=>`, `if`, `|`, or `}`, found `#` --> $DIR/attr-stmt-expr-attr-bad.rs:88:38 | LL | #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] -10 => () } } - | ^ expected one of `=>`, `if`, or `|` + | ^ expected one of `,`, `=>`, `if`, `|`, or `}` error: unexpected token: `#` --> $DIR/attr-stmt-expr-attr-bad.rs:91:39 @@ -399,11 +399,11 @@ LL | #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] FOO => () } } | = note: inclusive ranges must be bounded at the end (`..=b` or `a..=b`) -error: expected one of `=>`, `if`, or `|`, found `#` +error: expected one of `,`, `=>`, `if`, `|`, or `}`, found `#` --> $DIR/attr-stmt-expr-attr-bad.rs:93:38 | LL | #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] FOO => () } } - | ^ expected one of `=>`, `if`, or `|` + | ^ expected one of `,`, `=>`, `if`, `|`, or `}` error: unexpected token: `#` --> $DIR/attr-stmt-expr-attr-bad.rs:97:34 diff --git a/tests/ui/parser/issues/issue-24375.rs b/tests/ui/parser/issues/issue-24375.rs index 1d128d33e4f..8d1bc579e7b 100644 --- a/tests/ui/parser/issues/issue-24375.rs +++ b/tests/ui/parser/issues/issue-24375.rs @@ -3,7 +3,7 @@ fn main() { let z = "hello"; match z { - tmp[0] => {} //~ ERROR expected one of `=>`, `@`, `if`, or `|`, found `[` + tmp[0] => {} //~ ERROR expected one of `,`, `=>`, `@`, `if`, `|`, or `}`, found `[` _ => {} } } diff --git a/tests/ui/parser/issues/issue-24375.stderr b/tests/ui/parser/issues/issue-24375.stderr index bb1e19e9e6d..2b980a5520f 100644 --- a/tests/ui/parser/issues/issue-24375.stderr +++ b/tests/ui/parser/issues/issue-24375.stderr @@ -1,8 +1,8 @@ -error: expected one of `=>`, `@`, `if`, or `|`, found `[` +error: expected one of `,`, `=>`, `@`, `if`, `|`, or `}`, found `[` --> $DIR/issue-24375.rs:6:12 | LL | tmp[0] => {} - | ^ expected one of `=>`, `@`, `if`, or `|` + | ^ expected one of `,`, `=>`, `@`, `if`, `|`, or `}` error: aborting due to 1 previous error diff --git a/tests/ui/parser/macro/macro-expand-to-match-arm.rs b/tests/ui/parser/macro/macro-expand-to-match-arm.rs index 39d1d065ed9..972ca61cc84 100644 --- a/tests/ui/parser/macro/macro-expand-to-match-arm.rs +++ b/tests/ui/parser/macro/macro-expand-to-match-arm.rs @@ -1,6 +1,8 @@ macro_rules! arm { ($pattern:pat => $block:block) => { $pattern => $block + //~^ ERROR macro expansion ignores token `=>` and any following + //~| NOTE the usage of `arm!` is likely invalid in pattern context }; } @@ -9,9 +11,7 @@ fn main() { match x { Some(1) => {}, arm!(None => {}), - //~^ NOTE macros cannot expand to match arms - //~| ERROR unexpected `,` in pattern - // doesn't recover + //~^ NOTE caused by the macro expansion here Some(2) => {}, _ => {}, }; diff --git a/tests/ui/parser/macro/macro-expand-to-match-arm.stderr b/tests/ui/parser/macro/macro-expand-to-match-arm.stderr index 1b34d2d12b2..a62109c5050 100644 --- a/tests/ui/parser/macro/macro-expand-to-match-arm.stderr +++ b/tests/ui/parser/macro/macro-expand-to-match-arm.stderr @@ -1,10 +1,13 @@ -error: unexpected `,` in pattern - --> $DIR/macro-expand-to-match-arm.rs:11:25 +error: macro expansion ignores token `=>` and any following + --> $DIR/macro-expand-to-match-arm.rs:3:18 | +LL | $pattern => $block + | ^^ +... LL | arm!(None => {}), - | ^ + | ---------------- caused by the macro expansion here | - = note: macros cannot expand to match arms + = note: the usage of `arm!` is likely invalid in pattern context error: aborting due to 1 previous error diff --git a/tests/ui/parser/match-arm-without-body.rs b/tests/ui/parser/match-arm-without-body.rs index 5f009c7a355..c33bd0c3031 100644 --- a/tests/ui/parser/match-arm-without-body.rs +++ b/tests/ui/parser/match-arm-without-body.rs @@ -6,7 +6,6 @@ fn main() { match Some(false) { Some(_) } - //~^ ERROR expected one of match Some(false) { Some(_) _ => {} @@ -28,7 +27,6 @@ fn main() { match Some(false) { Some(_) if true } - //~^ ERROR expected one of match Some(false) { Some(_) if true _ => {} @@ -36,33 +34,28 @@ fn main() { } match Some(false) { Some(_) if true, - //~^ ERROR expected one of } match Some(false) { Some(_) if true, - //~^ ERROR expected one of _ => {} } match Some(false) { pat!() } - //~^ ERROR expected one of match Some(false) { pat!(), - //~^ ERROR unexpected `,` in pattern } match Some(false) { pat!() if true, - //~^ ERROR expected one of } match Some(false) { pat!() + //~^ ERROR expected `,` following `match` arm + //~| HELP missing a comma here _ => {} - //~^ ERROR expected one of } match Some(false) { pat!(), - //~^ ERROR unexpected `,` in pattern _ => {} } } diff --git a/tests/ui/parser/match-arm-without-body.stderr b/tests/ui/parser/match-arm-without-body.stderr index 210007628db..77fd9176633 100644 --- a/tests/ui/parser/match-arm-without-body.stderr +++ b/tests/ui/parser/match-arm-without-body.stderr @@ -1,21 +1,13 @@ -error: expected one of `=>`, `if`, or `|`, found `}` - --> $DIR/match-arm-without-body.rs:8:5 +error: expected one of `,`, `=>`, `if`, `|`, or `}`, found reserved identifier `_` + --> $DIR/match-arm-without-body.rs:11:9 | LL | Some(_) - | - expected one of `=>`, `if`, or `|` -LL | } - | ^ unexpected token - -error: expected one of `=>`, `if`, or `|`, found reserved identifier `_` - --> $DIR/match-arm-without-body.rs:12:9 - | -LL | Some(_) - | - expected one of `=>`, `if`, or `|` + | - expected one of `,`, `=>`, `if`, `|`, or `}` LL | _ => {} | ^ unexpected token error: unexpected `,` in pattern - --> $DIR/match-arm-without-body.rs:16:16 + --> $DIR/match-arm-without-body.rs:15:16 | LL | Some(_), | ^ @@ -30,7 +22,7 @@ LL | Some(_) | | error: unexpected `,` in pattern - --> $DIR/match-arm-without-body.rs:22:16 + --> $DIR/match-arm-without-body.rs:21:16 | LL | Some(_), | ^ @@ -52,71 +44,19 @@ LL + LL ~ _ => {} | -error: expected one of `.`, `=>`, `?`, or an operator, found `}` - --> $DIR/match-arm-without-body.rs:30:5 +error: expected one of `,`, `.`, `=>`, `?`, `}`, or an operator, found reserved identifier `_` + --> $DIR/match-arm-without-body.rs:32:9 | LL | Some(_) if true - | - expected one of `.`, `=>`, `?`, or an operator -LL | } - | ^ unexpected token - -error: expected one of `.`, `=>`, `?`, or an operator, found reserved identifier `_` - --> $DIR/match-arm-without-body.rs:34:9 - | -LL | Some(_) if true - | - expected one of `.`, `=>`, `?`, or an operator + | - expected one of `,`, `.`, `=>`, `?`, `}`, or an operator LL | _ => {} | ^ unexpected token -error: expected one of `.`, `=>`, `?`, or an operator, found `,` - --> $DIR/match-arm-without-body.rs:38:24 - | -LL | Some(_) if true, - | ^ expected one of `.`, `=>`, `?`, or an operator - -error: expected one of `.`, `=>`, `?`, or an operator, found `,` - --> $DIR/match-arm-without-body.rs:42:24 - | -LL | Some(_) if true, - | ^ expected one of `.`, `=>`, `?`, or an operator - -error: expected one of `=>`, `if`, or `|`, found `}` - --> $DIR/match-arm-without-body.rs:48:5 +error: expected `,` following `match` arm + --> $DIR/match-arm-without-body.rs:52:15 | LL | pat!() - | - expected one of `=>`, `if`, or `|` -LL | } - | ^ unexpected token + | ^ help: missing a comma here to end this `match` arm: `,` -error: unexpected `,` in pattern - --> $DIR/match-arm-without-body.rs:51:15 - | -LL | pat!(), - | ^ - | - = note: macros cannot expand to match arms - -error: expected one of `.`, `=>`, `?`, or an operator, found `,` - --> $DIR/match-arm-without-body.rs:55:23 - | -LL | pat!() if true, - | ^ expected one of `.`, `=>`, `?`, or an operator - -error: expected one of `=>`, `if`, or `|`, found reserved identifier `_` - --> $DIR/match-arm-without-body.rs:60:9 - | -LL | pat!() - | - expected one of `=>`, `if`, or `|` -LL | _ => {} - | ^ unexpected token - -error: unexpected `,` in pattern - --> $DIR/match-arm-without-body.rs:64:15 - | -LL | pat!(), - | ^ - | - = note: macros cannot expand to match arms - -error: aborting due to 13 previous errors +error: aborting due to 5 previous errors diff --git a/tests/ui/parser/pat-lt-bracket-1.rs b/tests/ui/parser/pat-lt-bracket-1.rs index 2e2001434f2..33da15adb9e 100644 --- a/tests/ui/parser/pat-lt-bracket-1.rs +++ b/tests/ui/parser/pat-lt-bracket-1.rs @@ -1,7 +1,7 @@ fn main() { match 42 { x < 7 => (), - //~^ error: expected one of `=>`, `@`, `if`, or `|`, found `<` + //~^ error: expected one of `,`, `=>`, `@`, `if`, `|`, or `}`, found `<` _ => () } } diff --git a/tests/ui/parser/pat-lt-bracket-1.stderr b/tests/ui/parser/pat-lt-bracket-1.stderr index 14e679bbee0..f39487052ad 100644 --- a/tests/ui/parser/pat-lt-bracket-1.stderr +++ b/tests/ui/parser/pat-lt-bracket-1.stderr @@ -1,8 +1,8 @@ -error: expected one of `=>`, `@`, `if`, or `|`, found `<` +error: expected one of `,`, `=>`, `@`, `if`, `|`, or `}`, found `<` --> $DIR/pat-lt-bracket-1.rs:3:7 | LL | x < 7 => (), - | ^ expected one of `=>`, `@`, `if`, or `|` + | ^ expected one of `,`, `=>`, `@`, `if`, `|`, or `}` error: aborting due to 1 previous error From 0bfebc6105ea882d7048057718b2e34d09a5d17e Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 27 Nov 2023 00:50:51 +0100 Subject: [PATCH 3/7] Detect attempts to expand a macro to a match arm again Because a macro invocation can expand to a never pattern, we can't rule out a `arm!(),` arm at parse time. Instead we detect that case at expansion time, if the macro tries to output a pattern followed by `=>`. --- compiler/rustc_expand/messages.ftl | 2 + compiler/rustc_expand/src/errors.rs | 2 + compiler/rustc_expand/src/expand.rs | 3 ++ compiler/rustc_parse/messages.ftl | 2 - .../rustc_parse/src/parser/diagnostics.rs | 37 ++++++++----------- compiler/rustc_parse/src/parser/pat.rs | 8 +--- .../parser/macro/macro-expand-to-match-arm.rs | 1 + .../macro/macro-expand-to-match-arm.stderr | 1 + 8 files changed, 27 insertions(+), 29 deletions(-) diff --git a/compiler/rustc_expand/messages.ftl b/compiler/rustc_expand/messages.ftl index 8b93829623d..fc3f7b1d749 100644 --- a/compiler/rustc_expand/messages.ftl +++ b/compiler/rustc_expand/messages.ftl @@ -71,6 +71,8 @@ expand_macro_const_stability = .label = invalid const stability attribute .label2 = const stability attribute affects this macro +expand_macro_expands_to_match_arm = macros cannot expand to match arms + expand_malformed_feature_attribute = malformed `feature` attribute input .expected = expected just one word diff --git a/compiler/rustc_expand/src/errors.rs b/compiler/rustc_expand/src/errors.rs index d86632c47fc..6e919a8fa9f 100644 --- a/compiler/rustc_expand/src/errors.rs +++ b/compiler/rustc_expand/src/errors.rs @@ -304,6 +304,8 @@ pub(crate) struct IncompleteParse<'a> { pub label_span: Span, pub macro_path: &'a ast::Path, pub kind_name: &'a str, + #[note(expand_macro_expands_to_match_arm)] + pub expands_to_match_arm: Option<()>, #[suggestion( expand_suggestion_add_semi, diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 1b51d80fb38..e2d2bc03280 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -955,12 +955,15 @@ pub fn ensure_complete_parse<'a>( _ => None, }; + let expands_to_match_arm = kind_name == "pattern" && parser.token == token::FatArrow; + parser.sess.emit_err(IncompleteParse { span: def_site_span, token, label_span: span, macro_path, kind_name, + expands_to_match_arm: expands_to_match_arm.then_some(()), add_semicolon, }); } diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl index 1c3c433d8b7..363b8f4bfb9 100644 --- a/compiler/rustc_parse/messages.ftl +++ b/compiler/rustc_parse/messages.ftl @@ -456,8 +456,6 @@ parse_macro_expands_to_adt_field = macros cannot expand to {$adt_ty} fields parse_macro_expands_to_enum_variant = macros cannot expand to enum variants -parse_macro_expands_to_match_arm = macros cannot expand to match arms - parse_macro_invocation_visibility = can't qualify macro invocation with `pub` .suggestion = remove the visibility .help = try adjusting the macro to put `{$vis}` inside the invocation diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 8921c1c6a03..b11460f2766 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -2823,7 +2823,6 @@ pub(crate) fn maybe_recover_unexpected_block_label(&mut self) -> bool { pub(crate) fn maybe_recover_unexpected_comma( &mut self, lo: Span, - is_mac_invoc: bool, rt: CommaRecoveryMode, ) -> PResult<'a, ()> { if self.token != token::Comma { @@ -2844,28 +2843,24 @@ pub(crate) fn maybe_recover_unexpected_comma( let seq_span = lo.to(self.prev_token.span); let mut err = self.struct_span_err(comma_span, "unexpected `,` in pattern"); if let Ok(seq_snippet) = self.span_to_snippet(seq_span) { - if is_mac_invoc { - err.note(fluent::parse_macro_expands_to_match_arm); - } else { - err.multipart_suggestion( - format!( - "try adding parentheses to match on a tuple{}", - if let CommaRecoveryMode::LikelyTuple = rt { "" } else { "..." }, - ), - vec![ - (seq_span.shrink_to_lo(), "(".to_string()), - (seq_span.shrink_to_hi(), ")".to_string()), - ], + err.multipart_suggestion( + format!( + "try adding parentheses to match on a tuple{}", + if let CommaRecoveryMode::LikelyTuple = rt { "" } else { "..." }, + ), + vec![ + (seq_span.shrink_to_lo(), "(".to_string()), + (seq_span.shrink_to_hi(), ")".to_string()), + ], + Applicability::MachineApplicable, + ); + if let CommaRecoveryMode::EitherTupleOrPipe = rt { + err.span_suggestion( + seq_span, + "...or a vertical bar to match on multiple alternatives", + seq_snippet.replace(',', " |"), Applicability::MachineApplicable, ); - if let CommaRecoveryMode::EitherTupleOrPipe = rt { - err.span_suggestion( - seq_span, - "...or a vertical bar to match on multiple alternatives", - seq_snippet.replace(',', " |"), - Applicability::MachineApplicable, - ); - } } } Err(err) diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index 9b8a34cf0cc..3c74a3ea741 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -155,11 +155,7 @@ fn parse_pat_allow_top_alt_inner( Err(err) => return Err(err), }; if rc == RecoverComma::Yes && !first_pat.could_be_never_pattern() { - self.maybe_recover_unexpected_comma( - first_pat.span, - matches!(first_pat.kind, PatKind::MacCall(_)), - rt, - )?; + self.maybe_recover_unexpected_comma(first_pat.span, rt)?; } // If the next token is not a `|`, @@ -201,7 +197,7 @@ fn parse_pat_allow_top_alt_inner( err })?; if rc == RecoverComma::Yes && !pat.could_be_never_pattern() { - self.maybe_recover_unexpected_comma(pat.span, false, rt)?; + self.maybe_recover_unexpected_comma(pat.span, rt)?; } pats.push(pat); } diff --git a/tests/ui/parser/macro/macro-expand-to-match-arm.rs b/tests/ui/parser/macro/macro-expand-to-match-arm.rs index 972ca61cc84..98d2a27884f 100644 --- a/tests/ui/parser/macro/macro-expand-to-match-arm.rs +++ b/tests/ui/parser/macro/macro-expand-to-match-arm.rs @@ -3,6 +3,7 @@ macro_rules! arm { $pattern => $block //~^ ERROR macro expansion ignores token `=>` and any following //~| NOTE the usage of `arm!` is likely invalid in pattern context + //~| NOTE macros cannot expand to match arms }; } diff --git a/tests/ui/parser/macro/macro-expand-to-match-arm.stderr b/tests/ui/parser/macro/macro-expand-to-match-arm.stderr index a62109c5050..f162f7dd47b 100644 --- a/tests/ui/parser/macro/macro-expand-to-match-arm.stderr +++ b/tests/ui/parser/macro/macro-expand-to-match-arm.stderr @@ -8,6 +8,7 @@ LL | arm!(None => {}), | ---------------- caused by the macro expansion here | = note: the usage of `arm!` is likely invalid in pattern context + = note: macros cannot expand to match arms error: aborting due to 1 previous error From a2dcb3a6d9aead3964b3b1cdf814dc7eb9c5d8ed Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 27 Nov 2023 01:53:05 +0100 Subject: [PATCH 4/7] Disallow an arm without a body (except for never patterns) Parsing now accepts a match arm without a body, so we must make sure to only accept that if the pattern is a never pattern. --- compiler/rustc_ast/src/ast.rs | 4 +- compiler/rustc_ast_lowering/messages.ftl | 4 ++ compiler/rustc_ast_lowering/src/errors.rs | 9 +++ compiler/rustc_ast_lowering/src/expr.rs | 13 +++- compiler/rustc_hir/src/hir.rs | 17 +++++ .../parser/macro/macro-expand-to-match-arm.rs | 1 + .../macro/macro-expand-to-match-arm.stderr | 8 ++- tests/ui/parser/match-arm-without-body.rs | 18 +++++ tests/ui/parser/match-arm-without-body.stderr | 66 +++++++++++++++++-- 9 files changed, 128 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 62bacf97f49..815f5fa8368 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -659,8 +659,8 @@ pub fn is_rest(&self) -> bool { matches!(self.kind, PatKind::Rest) } - /// Could this be a never pattern? I.e. is it a never pattern modulo macro invocations that - /// might return never patterns? + /// Whether this could be a never pattern, taking into account that a macro invocation can + /// return a never pattern. Used to inform errors during parsing. pub fn could_be_never_pattern(&self) -> bool { let mut could_be_never_pattern = false; self.walk(&mut |pat| match &pat.kind { diff --git a/compiler/rustc_ast_lowering/messages.ftl b/compiler/rustc_ast_lowering/messages.ftl index 91591a71611..2a519b418e6 100644 --- a/compiler/rustc_ast_lowering/messages.ftl +++ b/compiler/rustc_ast_lowering/messages.ftl @@ -91,6 +91,10 @@ ast_lowering_invalid_register = ast_lowering_invalid_register_class = invalid register class `{$reg_class}`: {$error} +ast_lowering_match_arm_with_no_body = + `match` arm with no body + .suggestion = add a body after the pattern + ast_lowering_misplaced_assoc_ty_binding = associated type bounds are only allowed in where clauses and function signatures, not in {$position} diff --git a/compiler/rustc_ast_lowering/src/errors.rs b/compiler/rustc_ast_lowering/src/errors.rs index 6e1a9eff500..1bcf4a07eb0 100644 --- a/compiler/rustc_ast_lowering/src/errors.rs +++ b/compiler/rustc_ast_lowering/src/errors.rs @@ -340,6 +340,15 @@ pub struct NotSupportedForLifetimeBinderAsyncClosure { pub span: Span, } +#[derive(Diagnostic)] +#[diag(ast_lowering_match_arm_with_no_body)] +pub struct MatchArmWithNoBody { + #[primary_span] + pub span: Span, + #[suggestion(code = " => todo!(),", applicability = "has-placeholders")] + pub suggestion: Span, +} + #[derive(Diagnostic, Clone, Copy)] #[diag(ast_lowering_arbitrary_expression_in_pattern)] pub struct ArbitraryExpressionInPattern { diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index b688950f5a3..4a32fa7f929 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -1,7 +1,7 @@ use super::errors::{ AsyncCoroutinesNotSupported, AsyncNonMoveClosureNotSupported, AwaitOnlyInAsyncFnAndBlocks, BaseExpressionDoubleDot, ClosureCannotBeStatic, CoroutineTooManyParameters, - FunctionalRecordUpdateDestructuringAssignment, InclusiveRangeWithNoEnd, + FunctionalRecordUpdateDestructuringAssignment, InclusiveRangeWithNoEnd, MatchArmWithNoBody, NotSupportedForLifetimeBinderAsyncClosure, UnderscoreExprLhsAssign, }; use super::ResolverAstLoweringExt; @@ -565,14 +565,21 @@ fn lower_arm(&mut self, arm: &Arm) -> hir::Arm<'hir> { } }); let hir_id = self.next_id(); + let span = self.lower_span(arm.span); self.lower_attrs(hir_id, &arm.attrs); let body = if let Some(body) = &arm.body { + // FIXME(never_patterns): Disallow never pattern with a body or guard self.lower_expr(body) } else { + if !pat.is_never_pattern() { + self.tcx + .sess + .emit_err(MatchArmWithNoBody { span, suggestion: span.shrink_to_hi() }); + } + // An arm without a body, meant for never patterns. // We add a fake `loop {}` arm body so that it typecks to `!`. // FIXME(never_patterns): Desugar into a call to `unreachable_unchecked`. - let span = pat.span; let block = self.arena.alloc(hir::Block { stmts: &[], expr: None, @@ -587,7 +594,7 @@ fn lower_arm(&mut self, arm: &Arm) -> hir::Arm<'hir> { span, }) }; - hir::Arm { hir_id, pat, guard, body, span: self.lower_span(arm.span) } + hir::Arm { hir_id, pat, guard, body, span } } /// Lower an `async` construct to a coroutine that implements `Future`. diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 81733d8f64e..479a0db75b0 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1055,6 +1055,23 @@ pub fn walk_always(&self, mut it: impl FnMut(&Pat<'_>)) { true }) } + + /// Whether this a never pattern. + pub fn is_never_pattern(&self) -> bool { + let mut is_never_pattern = false; + self.walk(|pat| match &pat.kind { + PatKind::Never => { + is_never_pattern = true; + false + } + PatKind::Or(s) => { + is_never_pattern = s.iter().all(|p| p.is_never_pattern()); + false + } + _ => true, + }); + is_never_pattern + } } /// A single field in a struct pattern. diff --git a/tests/ui/parser/macro/macro-expand-to-match-arm.rs b/tests/ui/parser/macro/macro-expand-to-match-arm.rs index 98d2a27884f..db38fa0d7bc 100644 --- a/tests/ui/parser/macro/macro-expand-to-match-arm.rs +++ b/tests/ui/parser/macro/macro-expand-to-match-arm.rs @@ -13,6 +13,7 @@ fn main() { Some(1) => {}, arm!(None => {}), //~^ NOTE caused by the macro expansion here + //~| ERROR `match` arm with no body Some(2) => {}, _ => {}, }; diff --git a/tests/ui/parser/macro/macro-expand-to-match-arm.stderr b/tests/ui/parser/macro/macro-expand-to-match-arm.stderr index f162f7dd47b..e3e7ff89c81 100644 --- a/tests/ui/parser/macro/macro-expand-to-match-arm.stderr +++ b/tests/ui/parser/macro/macro-expand-to-match-arm.stderr @@ -10,5 +10,11 @@ LL | arm!(None => {}), = note: the usage of `arm!` is likely invalid in pattern context = note: macros cannot expand to match arms -error: aborting due to 1 previous error +error: `match` arm with no body + --> $DIR/macro-expand-to-match-arm.rs:14:9 + | +LL | arm!(None => {}), + | ^^^^^^^^^^^^^^^^- help: add a body after the pattern: `=> todo!(),` + +error: aborting due to 2 previous errors diff --git a/tests/ui/parser/match-arm-without-body.rs b/tests/ui/parser/match-arm-without-body.rs index c33bd0c3031..c3487c2c658 100644 --- a/tests/ui/parser/match-arm-without-body.rs +++ b/tests/ui/parser/match-arm-without-body.rs @@ -5,6 +5,8 @@ macro_rules! pat { fn main() { match Some(false) { Some(_) + //~^ ERROR `match` arm with no body + //~| HELP add a body after the pattern } match Some(false) { Some(_) @@ -26,6 +28,8 @@ fn main() { } match Some(false) { Some(_) if true + //~^ ERROR `match` arm with no body + //~| HELP add a body after the pattern } match Some(false) { Some(_) if true @@ -34,28 +38,42 @@ fn main() { } match Some(false) { Some(_) if true, + //~^ ERROR `match` arm with no body + //~| HELP add a body after the pattern } match Some(false) { Some(_) if true, + //~^ ERROR `match` arm with no body + //~| HELP add a body after the pattern _ => {} } match Some(false) { pat!() + //~^ ERROR `match` arm with no body + //~| HELP add a body after the pattern } match Some(false) { pat!(), + //~^ ERROR `match` arm with no body + //~| HELP add a body after the pattern } match Some(false) { pat!() if true, + //~^ ERROR `match` arm with no body + //~| HELP add a body after the pattern } match Some(false) { pat!() //~^ ERROR expected `,` following `match` arm //~| HELP missing a comma here + //~| ERROR `match` arm with no body + //~| HELP add a body after the pattern _ => {} } match Some(false) { pat!(), + //~^ ERROR `match` arm with no body + //~| HELP add a body after the pattern _ => {} } } diff --git a/tests/ui/parser/match-arm-without-body.stderr b/tests/ui/parser/match-arm-without-body.stderr index 77fd9176633..3a06ed050b5 100644 --- a/tests/ui/parser/match-arm-without-body.stderr +++ b/tests/ui/parser/match-arm-without-body.stderr @@ -1,5 +1,5 @@ error: expected one of `,`, `=>`, `if`, `|`, or `}`, found reserved identifier `_` - --> $DIR/match-arm-without-body.rs:11:9 + --> $DIR/match-arm-without-body.rs:13:9 | LL | Some(_) | - expected one of `,`, `=>`, `if`, `|`, or `}` @@ -7,7 +7,7 @@ LL | _ => {} | ^ unexpected token error: unexpected `,` in pattern - --> $DIR/match-arm-without-body.rs:15:16 + --> $DIR/match-arm-without-body.rs:17:16 | LL | Some(_), | ^ @@ -22,7 +22,7 @@ LL | Some(_) | | error: unexpected `,` in pattern - --> $DIR/match-arm-without-body.rs:21:16 + --> $DIR/match-arm-without-body.rs:23:16 | LL | Some(_), | ^ @@ -45,7 +45,7 @@ LL ~ _ => {} | error: expected one of `,`, `.`, `=>`, `?`, `}`, or an operator, found reserved identifier `_` - --> $DIR/match-arm-without-body.rs:32:9 + --> $DIR/match-arm-without-body.rs:36:9 | LL | Some(_) if true | - expected one of `,`, `.`, `=>`, `?`, `}`, or an operator @@ -53,10 +53,64 @@ LL | _ => {} | ^ unexpected token error: expected `,` following `match` arm - --> $DIR/match-arm-without-body.rs:52:15 + --> $DIR/match-arm-without-body.rs:66:15 | LL | pat!() | ^ help: missing a comma here to end this `match` arm: `,` -error: aborting due to 5 previous errors +error: `match` arm with no body + --> $DIR/match-arm-without-body.rs:7:9 + | +LL | Some(_) + | ^^^^^^^- help: add a body after the pattern: `=> todo!(),` + +error: `match` arm with no body + --> $DIR/match-arm-without-body.rs:30:9 + | +LL | Some(_) if true + | ^^^^^^^^^^^^^^^- help: add a body after the pattern: `=> todo!(),` + +error: `match` arm with no body + --> $DIR/match-arm-without-body.rs:40:9 + | +LL | Some(_) if true, + | ^^^^^^^^^^^^^^^- help: add a body after the pattern: `=> todo!(),` + +error: `match` arm with no body + --> $DIR/match-arm-without-body.rs:45:9 + | +LL | Some(_) if true, + | ^^^^^^^^^^^^^^^- help: add a body after the pattern: `=> todo!(),` + +error: `match` arm with no body + --> $DIR/match-arm-without-body.rs:51:9 + | +LL | pat!() + | ^^^^^^- help: add a body after the pattern: `=> todo!(),` + +error: `match` arm with no body + --> $DIR/match-arm-without-body.rs:56:9 + | +LL | pat!(), + | ^^^^^^- help: add a body after the pattern: `=> todo!(),` + +error: `match` arm with no body + --> $DIR/match-arm-without-body.rs:61:9 + | +LL | pat!() if true, + | ^^^^^^^^^^^^^^- help: add a body after the pattern: `=> todo!(),` + +error: `match` arm with no body + --> $DIR/match-arm-without-body.rs:66:9 + | +LL | pat!() + | ^^^^^^- help: add a body after the pattern: `=> todo!(),` + +error: `match` arm with no body + --> $DIR/match-arm-without-body.rs:74:9 + | +LL | pat!(), + | ^^^^^^- help: add a body after the pattern: `=> todo!(),` + +error: aborting due to 14 previous errors From 06a8ed10b627d04c9e91c41064c38745374acc71 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 27 Nov 2023 03:47:49 +0100 Subject: [PATCH 5/7] Disallow guards on never patterns --- compiler/rustc_ast_lowering/messages.ftl | 4 +++ compiler/rustc_ast_lowering/src/errors.rs | 8 +++++ compiler/rustc_ast_lowering/src/expr.rs | 7 ++-- tests/ui/never_patterns/check.rs | 3 +- tests/ui/never_patterns/check.stderr | 41 +++-------------------- tests/ui/never_patterns/parse.rs | 3 ++ tests/ui/never_patterns/parse.stderr | 24 +++++++++++-- 7 files changed, 47 insertions(+), 43 deletions(-) diff --git a/compiler/rustc_ast_lowering/messages.ftl b/compiler/rustc_ast_lowering/messages.ftl index 2a519b418e6..ecbe8cc6aec 100644 --- a/compiler/rustc_ast_lowering/messages.ftl +++ b/compiler/rustc_ast_lowering/messages.ftl @@ -108,6 +108,10 @@ ast_lowering_misplaced_impl_trait = ast_lowering_misplaced_relax_trait_bound = `?Trait` bounds are only permitted at the point where a type parameter is declared +ast_lowering_never_pattern_with_guard = + a guard on a never pattern will never be run + .suggestion = remove this guard + ast_lowering_not_supported_for_lifetime_binder_async_closure = `for<...>` binders on `async` closures are not currently supported diff --git a/compiler/rustc_ast_lowering/src/errors.rs b/compiler/rustc_ast_lowering/src/errors.rs index 1bcf4a07eb0..c6a4166f537 100644 --- a/compiler/rustc_ast_lowering/src/errors.rs +++ b/compiler/rustc_ast_lowering/src/errors.rs @@ -349,6 +349,14 @@ pub struct MatchArmWithNoBody { pub suggestion: Span, } +#[derive(Diagnostic)] +#[diag(ast_lowering_never_pattern_with_guard)] +pub struct NeverPatternWithGuard { + #[primary_span] + #[suggestion(code = "", applicability = "maybe-incorrect")] + pub span: Span, +} + #[derive(Diagnostic, Clone, Copy)] #[diag(ast_lowering_arbitrary_expression_in_pattern)] pub struct ArbitraryExpressionInPattern { diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 4a32fa7f929..4913f9cd12d 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -2,7 +2,7 @@ AsyncCoroutinesNotSupported, AsyncNonMoveClosureNotSupported, AwaitOnlyInAsyncFnAndBlocks, BaseExpressionDoubleDot, ClosureCannotBeStatic, CoroutineTooManyParameters, FunctionalRecordUpdateDestructuringAssignment, InclusiveRangeWithNoEnd, MatchArmWithNoBody, - NotSupportedForLifetimeBinderAsyncClosure, UnderscoreExprLhsAssign, + NeverPatternWithGuard, NotSupportedForLifetimeBinderAsyncClosure, UnderscoreExprLhsAssign, }; use super::ResolverAstLoweringExt; use super::{ImplTraitContext, LoweringContext, ParamMode, ParenthesizedGenericArgs}; @@ -550,7 +550,7 @@ fn wrap_in_try_constructor( fn lower_arm(&mut self, arm: &Arm) -> hir::Arm<'hir> { let pat = self.lower_pat(&arm.pat); - let guard = arm.guard.as_ref().map(|cond| { + let mut guard = arm.guard.as_ref().map(|cond| { if let ExprKind::Let(pat, scrutinee, span, is_recovered) = &cond.kind { hir::Guard::IfLet(self.arena.alloc(hir::Let { hir_id: self.next_id(), @@ -575,6 +575,9 @@ fn lower_arm(&mut self, arm: &Arm) -> hir::Arm<'hir> { self.tcx .sess .emit_err(MatchArmWithNoBody { span, suggestion: span.shrink_to_hi() }); + } else if let Some(g) = &arm.guard { + self.tcx.sess.emit_err(NeverPatternWithGuard { span: g.span }); + guard = None; } // An arm without a body, meant for never patterns. diff --git a/tests/ui/never_patterns/check.rs b/tests/ui/never_patterns/check.rs index 9b02fc7996d..1f55ef11242 100644 --- a/tests/ui/never_patterns/check.rs +++ b/tests/ui/never_patterns/check.rs @@ -15,12 +15,11 @@ fn no_arms_or_guards(x: Void) { None => {} } match None:: { - //~^ ERROR non-exhaustive Some(!) if true, + //~^ ERROR guard on a never pattern None => {} } match None:: { - //~^ ERROR non-exhaustive Some(!) if true => {} None => {} } diff --git a/tests/ui/never_patterns/check.stderr b/tests/ui/never_patterns/check.stderr index 945812225c4..a25ee1b0a2a 100644 --- a/tests/ui/never_patterns/check.stderr +++ b/tests/ui/never_patterns/check.stderr @@ -1,39 +1,8 @@ -error[E0004]: non-exhaustive patterns: `Some(_)` not covered - --> $DIR/check.rs:17:11 - | -LL | match None:: { - | ^^^^^^^^^^^^ 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: a guard on a never pattern will never be run + --> $DIR/check.rs:18:20 | +LL | Some(!) if true, + | ^^^^ help: remove this guard -error[E0004]: non-exhaustive patterns: `Some(_)` not covered - --> $DIR/check.rs:22:11 - | -LL | match None:: { - | ^^^^^^^^^^^^ 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 -error: aborting due to 2 previous errors - -For more information about this error, try `rustc --explain E0004`. diff --git a/tests/ui/never_patterns/parse.rs b/tests/ui/never_patterns/parse.rs index 850416d723a..1b23e60e0ca 100644 --- a/tests/ui/never_patterns/parse.rs +++ b/tests/ui/never_patterns/parse.rs @@ -30,10 +30,12 @@ fn parse(x: Void) { match None:: { Some(!) if true //~^ ERROR expected `,` following `match` arm + //~| ERROR guard on a never pattern None => {} } match None:: { Some(!) if true, + //~^ ERROR guard on a never pattern None => {} } match None:: { @@ -45,6 +47,7 @@ fn parse(x: Void) { } match x { never!() if true, + //~^ ERROR guard on a never pattern } match x { never!() diff --git a/tests/ui/never_patterns/parse.stderr b/tests/ui/never_patterns/parse.stderr index 7ea33540c5e..e81a13a3967 100644 --- a/tests/ui/never_patterns/parse.stderr +++ b/tests/ui/never_patterns/parse.stderr @@ -11,16 +11,34 @@ LL | Some(!) if true | ^ help: missing a comma here to end this `match` arm: `,` error: expected one of `,`, `=>`, `if`, `|`, or `}`, found `<=` - --> $DIR/parse.rs:40:17 + --> $DIR/parse.rs:42:17 | LL | Some(!) <= | ^^ expected one of `,`, `=>`, `if`, `|`, or `}` error: top-level or-patterns are not allowed in `let` bindings - --> $DIR/parse.rs:64:9 + --> $DIR/parse.rs:67:9 | LL | let Ok(_) | Err(!) = &res; // Disallowed; see #82048. | ^^^^^^^^^^^^^^ help: wrap the pattern in parentheses: `(Ok(_) | Err(!))` -error: aborting due to 4 previous errors +error: a guard on a never pattern will never be run + --> $DIR/parse.rs:31:20 + | +LL | Some(!) if true + | ^^^^ help: remove this guard + +error: a guard on a never pattern will never be run + --> $DIR/parse.rs:37:20 + | +LL | Some(!) if true, + | ^^^^ help: remove this guard + +error: a guard on a never pattern will never be run + --> $DIR/parse.rs:49:21 + | +LL | never!() if true, + | ^^^^ help: remove this guard + +error: aborting due to 7 previous errors From 70deb9a57f4c5757162f4c845ee9fe318717064f Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 27 Nov 2023 04:08:09 +0100 Subject: [PATCH 6/7] Disallow arm bodies on never patterns --- compiler/rustc_ast_lowering/messages.ftl | 5 +++ compiler/rustc_ast_lowering/src/errors.rs | 9 ++++++ compiler/rustc_ast_lowering/src/expr.rs | 21 ++++++++----- .../feature-gate-never_patterns.rs | 2 +- .../feature-gate-never_patterns.stderr | 2 +- tests/ui/never_patterns/check.rs | 3 ++ tests/ui/never_patterns/check.stderr | 31 +++++++++++++++++-- tests/ui/pattern/never_patterns.rs | 28 ++++++++--------- 8 files changed, 75 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_ast_lowering/messages.ftl b/compiler/rustc_ast_lowering/messages.ftl index ecbe8cc6aec..6bde4f2d8fa 100644 --- a/compiler/rustc_ast_lowering/messages.ftl +++ b/compiler/rustc_ast_lowering/messages.ftl @@ -108,6 +108,11 @@ ast_lowering_misplaced_impl_trait = ast_lowering_misplaced_relax_trait_bound = `?Trait` bounds are only permitted at the point where a type parameter is declared +ast_lowering_never_pattern_with_body = + a never pattern is always unreachable + .label = this will never be executed + .suggestion = remove this expression + ast_lowering_never_pattern_with_guard = a guard on a never pattern will never be run .suggestion = remove this guard diff --git a/compiler/rustc_ast_lowering/src/errors.rs b/compiler/rustc_ast_lowering/src/errors.rs index c6a4166f537..11bb559719b 100644 --- a/compiler/rustc_ast_lowering/src/errors.rs +++ b/compiler/rustc_ast_lowering/src/errors.rs @@ -349,6 +349,15 @@ pub struct MatchArmWithNoBody { pub suggestion: Span, } +#[derive(Diagnostic)] +#[diag(ast_lowering_never_pattern_with_body)] +pub struct NeverPatternWithBody { + #[primary_span] + #[label] + #[suggestion(code = "", applicability = "maybe-incorrect")] + pub span: Span, +} + #[derive(Diagnostic)] #[diag(ast_lowering_never_pattern_with_guard)] pub struct NeverPatternWithGuard { diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 4913f9cd12d..137ccb99346 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -2,7 +2,8 @@ AsyncCoroutinesNotSupported, AsyncNonMoveClosureNotSupported, AwaitOnlyInAsyncFnAndBlocks, BaseExpressionDoubleDot, ClosureCannotBeStatic, CoroutineTooManyParameters, FunctionalRecordUpdateDestructuringAssignment, InclusiveRangeWithNoEnd, MatchArmWithNoBody, - NeverPatternWithGuard, NotSupportedForLifetimeBinderAsyncClosure, UnderscoreExprLhsAssign, + NeverPatternWithBody, NeverPatternWithGuard, NotSupportedForLifetimeBinderAsyncClosure, + UnderscoreExprLhsAssign, }; use super::ResolverAstLoweringExt; use super::{ImplTraitContext, LoweringContext, ParamMode, ParenthesizedGenericArgs}; @@ -567,20 +568,24 @@ fn lower_arm(&mut self, arm: &Arm) -> hir::Arm<'hir> { let hir_id = self.next_id(); let span = self.lower_span(arm.span); self.lower_attrs(hir_id, &arm.attrs); - let body = if let Some(body) = &arm.body { - // FIXME(never_patterns): Disallow never pattern with a body or guard + let is_never_pattern = pat.is_never_pattern(); + let body = if let Some(body) = &arm.body + && !is_never_pattern + { self.lower_expr(body) } else { - if !pat.is_never_pattern() { - self.tcx - .sess - .emit_err(MatchArmWithNoBody { span, suggestion: span.shrink_to_hi() }); + // Either `body.is_none()` or `is_never_pattern` here. + if !is_never_pattern { + let suggestion = span.shrink_to_hi(); + self.tcx.sess.emit_err(MatchArmWithNoBody { span, suggestion }); + } else if let Some(body) = &arm.body { + self.tcx.sess.emit_err(NeverPatternWithBody { span: body.span }); + guard = None; } else if let Some(g) = &arm.guard { self.tcx.sess.emit_err(NeverPatternWithGuard { span: g.span }); guard = None; } - // An arm without a body, meant for never patterns. // We add a fake `loop {}` arm body so that it typecks to `!`. // FIXME(never_patterns): Desugar into a call to `unreachable_unchecked`. let block = self.arena.alloc(hir::Block { diff --git a/tests/ui/feature-gates/feature-gate-never_patterns.rs b/tests/ui/feature-gates/feature-gate-never_patterns.rs index 69e9f62abf0..ca5ce3b9489 100644 --- a/tests/ui/feature-gates/feature-gate-never_patterns.rs +++ b/tests/ui/feature-gates/feature-gate-never_patterns.rs @@ -12,7 +12,7 @@ fn main() { unsafe { let ptr: *const Void = NonNull::dangling().as_ptr(); match *ptr { - ! => {} //~ ERROR `!` patterns are experimental + ! //~ ERROR `!` patterns are experimental } } diff --git a/tests/ui/feature-gates/feature-gate-never_patterns.stderr b/tests/ui/feature-gates/feature-gate-never_patterns.stderr index b7290eeb36d..2354a3b0476 100644 --- a/tests/ui/feature-gates/feature-gate-never_patterns.stderr +++ b/tests/ui/feature-gates/feature-gate-never_patterns.stderr @@ -18,7 +18,7 @@ LL | let (Ok(_x) | Err(&!)) = res.as_ref(); error[E0658]: `!` patterns are experimental --> $DIR/feature-gate-never_patterns.rs:15:13 | -LL | ! => {} +LL | ! | ^ | = note: see issue #118155 for more information diff --git a/tests/ui/never_patterns/check.rs b/tests/ui/never_patterns/check.rs index 1f55ef11242..e298112244a 100644 --- a/tests/ui/never_patterns/check.rs +++ b/tests/ui/never_patterns/check.rs @@ -12,6 +12,7 @@ macro_rules! never { fn no_arms_or_guards(x: Void) { match None:: { Some(!) => {} + //~^ ERROR a never pattern is always unreachable None => {} } match None:: { @@ -21,10 +22,12 @@ fn no_arms_or_guards(x: Void) { } match None:: { Some(!) if true => {} + //~^ ERROR a never pattern is always unreachable None => {} } match None:: { Some(never!()) => {}, + //~^ ERROR a never pattern is always unreachable None => {} } } diff --git a/tests/ui/never_patterns/check.stderr b/tests/ui/never_patterns/check.stderr index a25ee1b0a2a..bfbc7a1b534 100644 --- a/tests/ui/never_patterns/check.stderr +++ b/tests/ui/never_patterns/check.stderr @@ -1,8 +1,35 @@ +error: a never pattern is always unreachable + --> $DIR/check.rs:14:20 + | +LL | Some(!) => {} + | ^^ + | | + | this will never be executed + | help: remove this expression + error: a guard on a never pattern will never be run - --> $DIR/check.rs:18:20 + --> $DIR/check.rs:19:20 | LL | Some(!) if true, | ^^^^ help: remove this guard -error: aborting due to 1 previous error +error: a never pattern is always unreachable + --> $DIR/check.rs:24:28 + | +LL | Some(!) if true => {} + | ^^ + | | + | this will never be executed + | help: remove this expression + +error: a never pattern is always unreachable + --> $DIR/check.rs:29:27 + | +LL | Some(never!()) => {}, + | ^^ + | | + | this will never be executed + | help: remove this expression + +error: aborting due to 4 previous errors diff --git a/tests/ui/pattern/never_patterns.rs b/tests/ui/pattern/never_patterns.rs index fdba1b8e087..915f3e75e7b 100644 --- a/tests/ui/pattern/never_patterns.rs +++ b/tests/ui/pattern/never_patterns.rs @@ -20,58 +20,58 @@ fn never_pattern_location(void: Void) { // FIXME(never_patterns): Don't accept on a non-empty type. match Some(0) { None => {} - Some(!) => {} + Some(!), } // FIXME(never_patterns): Don't accept on an arbitrary type, even if there are no more branches. match () { () => {} - ! => {} + !, } // FIXME(never_patterns): Don't accept even on an empty branch. match None:: { None => {} - ! => {} + !, } // FIXME(never_patterns): Let alone if the emptiness is behind a reference. match None::<&Void> { None => {} - ! => {} + !, } // Participate in match ergonomics. match &void { - ! => {} + ! } match &&void { - ! => {} + ! } match &&void { - &! => {} + &! } match &None:: { None => {} - Some(!) => {} + Some(!) } match None::<&Void> { None => {} - Some(!) => {} + Some(!), } // Accept on a composite empty type. match None::<&(u32, Void)> { None => {} - Some(&!) => {} + Some(&!), } // Accept on an simple empty type. match None:: { None => {} - Some(!) => {} + Some(!), } match None::<&Void> { None => {} - Some(&!) => {} + Some(&!), } match None::<&(u32, Void)> { None => {} - Some(&(_, !)) => {} + Some(&(_, !)), } } @@ -89,7 +89,7 @@ fn never_and_bindings() { // FIXME(never_patterns): A never pattern mustn't have bindings. match x { Ok(_) => {} - Err(&(_b, !)) => {} + Err(&(_b, !)), } match x { Ok(_a) | Err(&(_b, !)) => {} From 431cc4a3b0ea6e0ef2244d2e6ff2ad89ccd23c74 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sat, 2 Dec 2023 05:41:08 +0100 Subject: [PATCH 7/7] Satisfy tidy --- .../ui/{never_patterns => rfcs/rfc-0000-never_patterns}/check.rs | 0 .../{never_patterns => rfcs/rfc-0000-never_patterns}/check.stderr | 0 .../ui/{never_patterns => rfcs/rfc-0000-never_patterns}/parse.rs | 0 .../{never_patterns => rfcs/rfc-0000-never_patterns}/parse.stderr | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename tests/ui/{never_patterns => rfcs/rfc-0000-never_patterns}/check.rs (100%) rename tests/ui/{never_patterns => rfcs/rfc-0000-never_patterns}/check.stderr (100%) rename tests/ui/{never_patterns => rfcs/rfc-0000-never_patterns}/parse.rs (100%) rename tests/ui/{never_patterns => rfcs/rfc-0000-never_patterns}/parse.stderr (100%) diff --git a/tests/ui/never_patterns/check.rs b/tests/ui/rfcs/rfc-0000-never_patterns/check.rs similarity index 100% rename from tests/ui/never_patterns/check.rs rename to tests/ui/rfcs/rfc-0000-never_patterns/check.rs diff --git a/tests/ui/never_patterns/check.stderr b/tests/ui/rfcs/rfc-0000-never_patterns/check.stderr similarity index 100% rename from tests/ui/never_patterns/check.stderr rename to tests/ui/rfcs/rfc-0000-never_patterns/check.stderr diff --git a/tests/ui/never_patterns/parse.rs b/tests/ui/rfcs/rfc-0000-never_patterns/parse.rs similarity index 100% rename from tests/ui/never_patterns/parse.rs rename to tests/ui/rfcs/rfc-0000-never_patterns/parse.rs diff --git a/tests/ui/never_patterns/parse.stderr b/tests/ui/rfcs/rfc-0000-never_patterns/parse.stderr similarity index 100% rename from tests/ui/never_patterns/parse.stderr rename to tests/ui/rfcs/rfc-0000-never_patterns/parse.stderr