diff --git a/compiler/rustc_ast/src/token.rs b/compiler/rustc_ast/src/token.rs index f583825fbb3c5..b15171ff313cc 100644 --- a/compiler/rustc_ast/src/token.rs +++ b/compiler/rustc_ast/src/token.rs @@ -717,6 +717,13 @@ pub enum NonterminalKind { TT, } +/// Used when parsing a non-terminal (see `parse_nonterminal`) to determine if `:pat` should match +/// `top_pat` or `pat`. See issue . +pub enum OrPatNonterminalMode { + TopPat, + NoTopAlt, +} + impl NonterminalKind { pub fn from_symbol(symbol: Symbol) -> Option { Some(match symbol { diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index 92a8f23112679..202181c1e5d26 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -76,7 +76,9 @@ use TokenTreeOrTokenTreeSlice::*; use crate::mbe::{self, TokenTree}; -use rustc_ast::token::{self, DocComment, Nonterminal, Token}; +use rustc_ast::token::{ + self, BinOpToken, DocComment, Nonterminal, OrPatNonterminalMode, Token, TokenKind, +}; use rustc_parse::parser::Parser; use rustc_session::parse::ParseSess; use rustc_span::symbol::MacroRulesNormalizedIdent; @@ -414,6 +416,27 @@ fn token_name_eq(t1: &Token, t2: &Token) -> bool { } } +/// Check whether the next token in the matcher is `|` (i.e. if we are matching against `$foo:type +/// |`). The reason is that when we stabilized the `or_patterns` feature, we wanted `:pat` to match +/// top-level or-patterns too. But doing that would cause a lot of breakage, since `|` could +/// previously be a separator that follows `:pat`. Thus, we make a special case to parse `:pat` the +/// old way if it happens to be followed by `|` in the matcher. +/// +/// See for more info. +fn or_pat_mode(item: &MatcherPosHandle<'_, '_>) -> OrPatNonterminalMode { + if item.idx < item.top_elts.len() - 1 { + // Look at the token after the current one to see if it is `|`. + let tt = item.top_elts.get_tt(item.idx + 1); + if let TokenTree::Token(Token { kind: TokenKind::BinOp(BinOpToken::Or), .. }) = tt { + OrPatNonterminalMode::NoTopAlt + } else { + OrPatNonterminalMode::TopPat + } + } else { + OrPatNonterminalMode::TopPat + } +} + /// Process the matcher positions of `cur_items` until it is empty. In the process, this will /// produce more items in `next_items`, `eof_items`, and `bb_items`. /// @@ -518,6 +541,8 @@ fn inner_parse_loop<'root, 'tt>( } // We are in the middle of a matcher. else { + let or_pat_mode = or_pat_mode(&item); + // Look at what token in the matcher we are trying to match the current token (`token`) // against. Depending on that, we may generate new items. match item.top_elts.get_tt(idx) { @@ -559,7 +584,7 @@ fn inner_parse_loop<'root, 'tt>( TokenTree::MetaVarDecl(_, _, kind) => { // Built-in nonterminals never start with these tokens, // so we can eliminate them from consideration. - if Parser::nonterminal_may_begin_with(kind, token) { + if Parser::nonterminal_may_begin_with(kind, token, or_pat_mode) { bb_items.push(item); } } @@ -718,9 +743,10 @@ pub(super) fn parse_tt(parser: &mut Cow<'_, Parser<'_>>, ms: &[TokenTree]) -> Na assert_eq!(bb_items.len(), 1); let mut item = bb_items.pop().unwrap(); + let or_pat_mode = or_pat_mode(&item); if let TokenTree::MetaVarDecl(span, _, kind) = item.top_elts.get_tt(item.idx) { let match_cur = item.match_cur; - let nt = match parser.to_mut().parse_nonterminal(kind) { + let nt = match parser.to_mut().parse_nonterminal(kind, or_pat_mode) { Err(mut err) => { err.span_label( span, diff --git a/compiler/rustc_parse/src/parser/nonterminal.rs b/compiler/rustc_parse/src/parser/nonterminal.rs index c007f96a79800..4f31ab952f592 100644 --- a/compiler/rustc_parse/src/parser/nonterminal.rs +++ b/compiler/rustc_parse/src/parser/nonterminal.rs @@ -1,5 +1,5 @@ use rustc_ast::ptr::P; -use rustc_ast::token::{self, Nonterminal, NonterminalKind, Token}; +use rustc_ast::token::{self, Nonterminal, NonterminalKind, OrPatNonterminalMode, Token}; use rustc_ast_pretty::pprust; use rustc_errors::PResult; use rustc_span::symbol::{kw, Ident}; @@ -11,7 +11,11 @@ impl<'a> Parser<'a> { /// /// Returning `false` is a *stability guarantee* that such a matcher will *never* begin with that /// token. Be conservative (return true) if not sure. - pub fn nonterminal_may_begin_with(kind: NonterminalKind, token: &Token) -> bool { + pub fn nonterminal_may_begin_with( + kind: NonterminalKind, + token: &Token, + or_pat_mode: OrPatNonterminalMode, + ) -> bool { /// Checks whether the non-terminal may contain a single (non-keyword) identifier. fn may_be_ident(nt: &token::Nonterminal) -> bool { match *nt { @@ -68,6 +72,8 @@ impl<'a> Parser<'a> { token::ModSep | // path token::Lt | // path (UFCS constant) token::BinOp(token::Shl) => true, // path (double UFCS) + // leading vert `|` or-pattern + token::BinOp(token::Or) => matches!(or_pat_mode, OrPatNonterminalMode::TopPat), token::Interpolated(ref nt) => may_be_ident(nt), _ => false, }, @@ -84,7 +90,12 @@ impl<'a> Parser<'a> { } } - pub fn parse_nonterminal(&mut self, kind: NonterminalKind) -> PResult<'a, Nonterminal> { + /// Parse a non-terminal (e.g. MBE `:pat` or `:ident`). + pub fn parse_nonterminal( + &mut self, + kind: NonterminalKind, + or_pat_mode: OrPatNonterminalMode, + ) -> PResult<'a, Nonterminal> { // Any `Nonterminal` which stores its tokens (currently `NtItem` and `NtExpr`) // needs to have them force-captured here. // A `macro_rules!` invocation may pass a captured item/expr to a proc-macro, @@ -128,7 +139,14 @@ impl<'a> Parser<'a> { } } NonterminalKind::Pat => { - let (mut pat, tokens) = self.collect_tokens(|this| this.parse_pat(None))?; + let (mut pat, tokens) = match or_pat_mode { + OrPatNonterminalMode::TopPat => { + self.collect_tokens(|this| this.parse_top_pat_no_commas())? + } + OrPatNonterminalMode::NoTopAlt => { + self.collect_tokens(|this| this.parse_pat(None))? + } + }; // We have have eaten an NtPat, which could already have tokens if pat.tokens.is_none() { pat.tokens = tokens; diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index ee9a6dca5ade9..1d1732f18d4cf 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -62,6 +62,16 @@ impl<'a> Parser<'a> { Ok(pat) } + pub(super) fn parse_top_pat_no_commas(&mut self) -> PResult<'a, P> { + // Allow a '|' before the pats (RFCs 1925, 2530, and 2535). + self.eat_or_separator(None); + + // Parse the possibly-or-pattern, but don't recorver commas. + let pat = self.parse_pat_with_or(None, GateOr::No, RecoverComma::No)?; + + Ok(pat) + } + /// Parse the pattern for a function or function pointer parameter. /// Special recovery is provided for or-patterns and leading `|`. pub(super) fn parse_fn_param_pat(&mut self) -> PResult<'a, P> { diff --git a/src/test/ui/macros/macro-pat-follow.rs b/src/test/ui/macros/macro-pat-follow.rs index 8673cf7946787..98f43e24aafd3 100644 --- a/src/test/ui/macros/macro-pat-follow.rs +++ b/src/test/ui/macros/macro-pat-follow.rs @@ -15,17 +15,7 @@ macro_rules! pat_if { }} } -macro_rules! pat_bar { - ($p:pat | $p2:pat) => {{ - match Some(1u8) { - $p | $p2 => {}, - _ => {} - } - }} -} - fn main() { pat_in!(Some(_) in 0..10); pat_if!(Some(x) if x > 0); - pat_bar!(Some(1u8) | None); } diff --git a/src/test/ui/or-patterns/or-patterns-syntactic-fail.rs b/src/test/ui/or-patterns/or-patterns-syntactic-fail.rs index d23220056524b..efe90b3e3c60c 100644 --- a/src/test/ui/or-patterns/or-patterns-syntactic-fail.rs +++ b/src/test/ui/or-patterns/or-patterns-syntactic-fail.rs @@ -5,16 +5,6 @@ fn main() {} -// Test the `pat` macro fragment parser: -macro_rules! accept_pat { - ($p:pat) => {} -} - -accept_pat!(p | q); //~ ERROR no rules expected the token `|` -accept_pat!(| p | q); //~ ERROR no rules expected the token `|` - -// Non-macro tests: - enum E { A, B } use E::*; diff --git a/src/test/ui/or-patterns/or-patterns-syntactic-fail.stderr b/src/test/ui/or-patterns/or-patterns-syntactic-fail.stderr index 861d274ab5c72..989aeb5200645 100644 --- a/src/test/ui/or-patterns/or-patterns-syntactic-fail.stderr +++ b/src/test/ui/or-patterns/or-patterns-syntactic-fail.stderr @@ -1,53 +1,53 @@ error: an or-pattern parameter must be wrapped in parenthesis - --> $DIR/or-patterns-syntactic-fail.rs:27:13 + --> $DIR/or-patterns-syntactic-fail.rs:17:13 | LL | fn fun1(A | B: E) {} | ^^^^^ help: wrap the pattern in parenthesis: `(A | B)` error: a leading `|` is not allowed in a parameter pattern - --> $DIR/or-patterns-syntactic-fail.rs:29:13 + --> $DIR/or-patterns-syntactic-fail.rs:19:13 | LL | fn fun2(| A | B: E) {} | ^ help: remove the `|` error: an or-pattern parameter must be wrapped in parenthesis - --> $DIR/or-patterns-syntactic-fail.rs:29:15 + --> $DIR/or-patterns-syntactic-fail.rs:19:15 | LL | fn fun2(| A | B: E) {} | ^^^^^ help: wrap the pattern in parenthesis: `(A | B)` error: a leading `|` is only allowed in a top-level pattern - --> $DIR/or-patterns-syntactic-fail.rs:40:11 + --> $DIR/or-patterns-syntactic-fail.rs:30:11 | LL | let ( | A | B) = E::A; | ^ help: remove the `|` error: a leading `|` is only allowed in a top-level pattern - --> $DIR/or-patterns-syntactic-fail.rs:41:11 + --> $DIR/or-patterns-syntactic-fail.rs:31:11 | LL | let ( | A | B,) = (E::B,); | ^ help: remove the `|` error: a leading `|` is only allowed in a top-level pattern - --> $DIR/or-patterns-syntactic-fail.rs:42:11 + --> $DIR/or-patterns-syntactic-fail.rs:32:11 | LL | let [ | A | B ] = [E::A]; | ^ help: remove the `|` error: a leading `|` is only allowed in a top-level pattern - --> $DIR/or-patterns-syntactic-fail.rs:43:13 + --> $DIR/or-patterns-syntactic-fail.rs:33:13 | LL | let TS( | A | B ); | ^ help: remove the `|` error: a leading `|` is only allowed in a top-level pattern - --> $DIR/or-patterns-syntactic-fail.rs:44:17 + --> $DIR/or-patterns-syntactic-fail.rs:34:17 | LL | let NS { f: | A | B }; | ^ help: remove the `|` error: a leading `|` is only allowed in a top-level pattern - --> $DIR/or-patterns-syntactic-fail.rs:46:11 + --> $DIR/or-patterns-syntactic-fail.rs:36:11 | LL | let ( || A | B) = E::A; | ^^ help: remove the `||` @@ -55,7 +55,7 @@ LL | let ( || A | B) = E::A; = note: alternatives in or-patterns are separated with `|`, not `||` error: a leading `|` is only allowed in a top-level pattern - --> $DIR/or-patterns-syntactic-fail.rs:47:11 + --> $DIR/or-patterns-syntactic-fail.rs:37:11 | LL | let [ || A | B ] = [E::A]; | ^^ help: remove the `||` @@ -63,7 +63,7 @@ LL | let [ || A | B ] = [E::A]; = note: alternatives in or-patterns are separated with `|`, not `||` error: a leading `|` is only allowed in a top-level pattern - --> $DIR/or-patterns-syntactic-fail.rs:48:13 + --> $DIR/or-patterns-syntactic-fail.rs:38:13 | LL | let TS( || A | B ); | ^^ help: remove the `||` @@ -71,33 +71,15 @@ LL | let TS( || A | B ); = note: alternatives in or-patterns are separated with `|`, not `||` error: a leading `|` is only allowed in a top-level pattern - --> $DIR/or-patterns-syntactic-fail.rs:49:17 + --> $DIR/or-patterns-syntactic-fail.rs:39:17 | LL | let NS { f: || A | B }; | ^^ help: remove the `||` | = note: alternatives in or-patterns are separated with `|`, not `||` -error: no rules expected the token `|` - --> $DIR/or-patterns-syntactic-fail.rs:13:15 - | -LL | macro_rules! accept_pat { - | ----------------------- when calling this macro -... -LL | accept_pat!(p | q); - | ^ no rules expected this token in macro call - -error: no rules expected the token `|` - --> $DIR/or-patterns-syntactic-fail.rs:14:13 - | -LL | macro_rules! accept_pat { - | ----------------------- when calling this macro -... -LL | accept_pat!(| p | q); - | ^ no rules expected this token in macro call - error[E0369]: no implementation for `E | ()` - --> $DIR/or-patterns-syntactic-fail.rs:23:22 + --> $DIR/or-patterns-syntactic-fail.rs:13:22 | LL | let _ = |A | B: E| (); | ----^ -- () @@ -107,7 +89,7 @@ LL | let _ = |A | B: E| (); = note: an implementation of `std::ops::BitOr` might be missing for `E` error[E0308]: mismatched types - --> $DIR/or-patterns-syntactic-fail.rs:51:36 + --> $DIR/or-patterns-syntactic-fail.rs:41:36 | LL | let recovery_witness: String = 0; | ------ ^ @@ -116,7 +98,7 @@ LL | let recovery_witness: String = 0; | | help: try using a conversion method: `0.to_string()` | expected due to this -error: aborting due to 16 previous errors +error: aborting due to 14 previous errors Some errors have detailed explanations: E0308, E0369. For more information about an error, try `rustc --explain E0308`. diff --git a/src/test/ui/pattern/or-pattern-macro-pat-fallback.rs b/src/test/ui/pattern/or-pattern-macro-pat-fallback.rs new file mode 100644 index 0000000000000..2ba658ebb5e68 --- /dev/null +++ b/src/test/ui/pattern/or-pattern-macro-pat-fallback.rs @@ -0,0 +1,24 @@ +//#![feature(or_patterns)] + +macro_rules! bar { + ($nonor:pat |) => {}; +} + +macro_rules! baz { + ($nonor:pat) => {}; +} + +fn main() { + // Leading vert not allowed in pat + bar!(|1| 2 | 3 |); //~ERROR: no rules expected + + // Top-level or-patterns not allowed in pat + bar!(1 | 2 | 3 |); //~ERROR: no rules expected + bar!(1 | 2 | 3); //~ERROR: no rules expected + bar!((1 | 2 | 3)); //~ERROR: unexpected end + bar!((1 | 2 | 3) |); // ok + + baz!(1 | 2 | 3); // ok + baz!(|1| 2 | 3); // ok + baz!(|1| 2 | 3 |); //~ERROR: expected pattern +} diff --git a/src/test/ui/pattern/or-pattern-macro-pat-fallback.stderr b/src/test/ui/pattern/or-pattern-macro-pat-fallback.stderr new file mode 100644 index 0000000000000..b14ee122e443e --- /dev/null +++ b/src/test/ui/pattern/or-pattern-macro-pat-fallback.stderr @@ -0,0 +1,49 @@ +error: no rules expected the token `|` + --> $DIR/or-pattern-macro-pat-fallback.rs:13:10 + | +LL | macro_rules! bar { + | ---------------- when calling this macro +... +LL | bar!(|1| 2 | 3 |); + | ^ no rules expected this token in macro call + +error: no rules expected the token `2` + --> $DIR/or-pattern-macro-pat-fallback.rs:16:14 + | +LL | macro_rules! bar { + | ---------------- when calling this macro +... +LL | bar!(1 | 2 | 3 |); + | ^ no rules expected this token in macro call + +error: no rules expected the token `2` + --> $DIR/or-pattern-macro-pat-fallback.rs:17:14 + | +LL | macro_rules! bar { + | ---------------- when calling this macro +... +LL | bar!(1 | 2 | 3); + | ^ no rules expected this token in macro call + +error: unexpected end of macro invocation + --> $DIR/or-pattern-macro-pat-fallback.rs:18:21 + | +LL | macro_rules! bar { + | ---------------- when calling this macro +... +LL | bar!((1 | 2 | 3)); + | ^ missing tokens in macro arguments + +error: expected pattern, found `` + --> $DIR/or-pattern-macro-pat-fallback.rs:23:20 + | +LL | ($nonor:pat) => {}; + | ---------- while parsing argument for this `pat` macro fragment +... +LL | baz!(|1| 2 | 3 |); + | - ^ expected pattern + | | + | while parsing this or-pattern starting here + +error: aborting due to 5 previous errors + diff --git a/src/test/ui/pattern/or-pattern-macro-pat.rs b/src/test/ui/pattern/or-pattern-macro-pat.rs new file mode 100644 index 0000000000000..358ab7d518820 --- /dev/null +++ b/src/test/ui/pattern/or-pattern-macro-pat.rs @@ -0,0 +1,114 @@ +// run-pass + +//#![feature(or_patterns)] + +use Foo::*; + +#[derive(Eq, PartialEq, Debug)] +enum Foo { + A(u64), + B(u64), + C, + D, +} + +macro_rules! foo { + ($orpat:pat, $val:expr) => { + match $val { + x @ ($orpat) => x, // leading vert would not be allowed in $orpat + _ => B(0xDEADBEEFu64), + } + }; +} + +macro_rules! bar { + ($orpat:pat, $val:expr) => { + match $val { + $orpat => 42, // leading vert allowed here + _ => 0xDEADBEEFu64, + } + }; +} + +macro_rules! quux { + ($orpat1:pat | $orpat2:pat, $val:expr) => { + match $val { + $orpat1 => $val, + _ => B(0xDEADBEEFu64), + } + }; +} + +macro_rules! baz { + ($orpat:pat, $val:expr) => { + match $val { + $orpat => 42, + _ => 0xDEADBEEFu64, + } + }; + ($nonor:pat | $val:expr, C) => { + match $val { + x @ ($orpat) => x, + _ => 0xDEADBEEFu64, + } + }; +} + +macro_rules! foo2a { + ($orpat:pat, $val:expr) => { + match $val { + x @ ($orpat) => x, + _ => 0xDEADBEEFu64, + } + }; +} + +macro_rules! foo2b { + ($nonor:pat | $val:expr, 2) => { + match $val { + x @ $nonor => x, + _ => 0xDEADBEEFu64, + } + }; +} + +macro_rules! foo2 { + ($orpat:pat, $val:expr) => { + match $val { + x @ ($orpat) => x, + _ => 0xDEADBEEFu64, + } + }; + ($nonor:pat | $val:expr, 2) => { + match $val { + x @ $nonor => x, + _ => 0xDEADBEEFu64, + } + }; +} + +fn main() { + // Test or-pattern. + let y = foo!(A(_)|B(_), A(32)); + assert_eq!(y, A(32)); + + // Leading vert in or-pattern. + let y = bar!(|C| D, C); + assert_eq!(y, 42u64); + + // Leading vert in or-pattern makes baz! unambiguous. + let y = baz!(|C| D, C); + assert_eq!(y, 42u64); + + // Or-separator fallback. + let y = quux!(C | D, D); + assert_eq!(y, B(0xDEADBEEFu64)); + + // Test tricky ambiguous case. + let y = foo2a!(1 | 2, 2); + assert_eq!(y, 2); + let y = foo2b!(1 | 2, 2); + assert_eq!(y, 0xDEADBEEFu64); + let y = foo2!(1 | 2, 2); + assert_eq!(y, 2); +}