Skip to content

Commit 243ae7d

Browse files
committed
implement or-pattern macro fallback
1 parent 1ecab12 commit 243ae7d

File tree

5 files changed

+134
-12
lines changed

5 files changed

+134
-12
lines changed

compiler/rustc_ast/src/token.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,13 @@ pub enum NonterminalKind {
717717
TT,
718718
}
719719

720+
/// Used when parsing a non-terminal (see `parse_nonterminal`) to determine if `:pat` should match
721+
/// `top_pat` or `pat<no_top_alt>`. See issue https://github.com/rust-lang/rust/pull/78935.
722+
pub enum OrPatNonterminalMode {
723+
TopPat,
724+
NoTopAlt,
725+
}
726+
720727
impl NonterminalKind {
721728
pub fn from_symbol(symbol: Symbol) -> Option<NonterminalKind> {
722729
Some(match symbol {

compiler/rustc_expand/src/mbe/macro_parser.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ use TokenTreeOrTokenTreeSlice::*;
7676

7777
use crate::mbe::{self, TokenTree};
7878

79-
use rustc_ast::token::{self, DocComment, Nonterminal, Token};
79+
use rustc_ast::token::{
80+
self, BinOpToken, DocComment, Nonterminal, OrPatNonterminalMode, Token, TokenKind,
81+
};
8082
use rustc_parse::parser::Parser;
8183
use rustc_session::parse::ParseSess;
8284
use rustc_span::symbol::MacroRulesNormalizedIdent;
@@ -414,6 +416,27 @@ fn token_name_eq(t1: &Token, t2: &Token) -> bool {
414416
}
415417
}
416418

419+
/// Check whether the next token in the matcher is `|` (i.e. if we are matching against `$foo:type
420+
/// |`). The reason is that when we stabilized the `or_patterns` feature, we wanted `:pat` to match
421+
/// top-level or-patterns too. But doing that would cause a lot of breakage, since `|` could
422+
/// previously be a separator that follows `:pat`. Thus, we make a special case to parse `:pat` the
423+
/// old way if it happens to be followed by `|` in the matcher.
424+
///
425+
/// See https://github.com/rust-lang/rust/issues/54883 for more info.
426+
fn or_pat_mode(item: &MatcherPosHandle<'_, '_>) -> OrPatNonterminalMode {
427+
if item.idx < item.top_elts.len() - 1 {
428+
// Look at the token after the current one to see if it is `|`.
429+
let tt = item.top_elts.get_tt(item.idx + 1);
430+
if let TokenTree::Token(Token { kind: TokenKind::BinOp(BinOpToken::Or), .. }) = tt {
431+
OrPatNonterminalMode::NoTopAlt
432+
} else {
433+
OrPatNonterminalMode::TopPat
434+
}
435+
} else {
436+
OrPatNonterminalMode::TopPat
437+
}
438+
}
439+
417440
/// Process the matcher positions of `cur_items` until it is empty. In the process, this will
418441
/// produce more items in `next_items`, `eof_items`, and `bb_items`.
419442
///
@@ -518,6 +541,8 @@ fn inner_parse_loop<'root, 'tt>(
518541
}
519542
// We are in the middle of a matcher.
520543
else {
544+
let or_pat_mode = or_pat_mode(&item);
545+
521546
// Look at what token in the matcher we are trying to match the current token (`token`)
522547
// against. Depending on that, we may generate new items.
523548
match item.top_elts.get_tt(idx) {
@@ -559,7 +584,7 @@ fn inner_parse_loop<'root, 'tt>(
559584
TokenTree::MetaVarDecl(_, _, kind) => {
560585
// Built-in nonterminals never start with these tokens,
561586
// so we can eliminate them from consideration.
562-
if Parser::nonterminal_may_begin_with(kind, token) {
587+
if Parser::nonterminal_may_begin_with(kind, token, or_pat_mode) {
563588
bb_items.push(item);
564589
}
565590
}
@@ -718,9 +743,10 @@ pub(super) fn parse_tt(parser: &mut Cow<'_, Parser<'_>>, ms: &[TokenTree]) -> Na
718743
assert_eq!(bb_items.len(), 1);
719744

720745
let mut item = bb_items.pop().unwrap();
746+
let or_pat_mode = or_pat_mode(&item);
721747
if let TokenTree::MetaVarDecl(span, _, kind) = item.top_elts.get_tt(item.idx) {
722748
let match_cur = item.match_cur;
723-
let nt = match parser.to_mut().parse_nonterminal(kind) {
749+
let nt = match parser.to_mut().parse_nonterminal(kind, or_pat_mode) {
724750
Err(mut err) => {
725751
err.span_label(
726752
span,

compiler/rustc_parse/src/parser/nonterminal.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use rustc_ast::ptr::P;
2-
use rustc_ast::token::{self, Nonterminal, NonterminalKind, Token};
2+
use rustc_ast::token::{self, Nonterminal, NonterminalKind, OrPatNonterminalMode, Token};
33
use rustc_ast_pretty::pprust;
44
use rustc_errors::PResult;
55
use rustc_span::symbol::{kw, Ident};
@@ -11,7 +11,11 @@ impl<'a> Parser<'a> {
1111
///
1212
/// Returning `false` is a *stability guarantee* that such a matcher will *never* begin with that
1313
/// token. Be conservative (return true) if not sure.
14-
pub fn nonterminal_may_begin_with(kind: NonterminalKind, token: &Token) -> bool {
14+
pub fn nonterminal_may_begin_with(
15+
kind: NonterminalKind,
16+
token: &Token,
17+
or_pat_mode: OrPatNonterminalMode,
18+
) -> bool {
1519
/// Checks whether the non-terminal may contain a single (non-keyword) identifier.
1620
fn may_be_ident(nt: &token::Nonterminal) -> bool {
1721
match *nt {
@@ -61,14 +65,15 @@ impl<'a> Parser<'a> {
6165
token::OpenDelim(token::Bracket) | // slice pattern
6266
token::BinOp(token::And) | // reference
6367
token::BinOp(token::Minus) | // negative literal
64-
token::BinOp(token::Or) | // leading vert `|` or-pattern
6568
token::AndAnd | // double reference
6669
token::Literal(..) | // literal
6770
token::DotDot | // range pattern (future compat)
6871
token::DotDotDot | // range pattern (future compat)
6972
token::ModSep | // path
7073
token::Lt | // path (UFCS constant)
7174
token::BinOp(token::Shl) => true, // path (double UFCS)
75+
// leading vert `|` or-pattern
76+
token::BinOp(token::Or) => matches!(or_pat_mode, OrPatNonterminalMode::TopPat),
7277
token::Interpolated(ref nt) => may_be_ident(nt),
7378
_ => false,
7479
},
@@ -85,7 +90,12 @@ impl<'a> Parser<'a> {
8590
}
8691
}
8792

88-
pub fn parse_nonterminal(&mut self, kind: NonterminalKind) -> PResult<'a, Nonterminal> {
93+
/// Parse a non-terminal (e.g. MBE `:pat` or `:ident`).
94+
pub fn parse_nonterminal(
95+
&mut self,
96+
kind: NonterminalKind,
97+
or_pat_mode: OrPatNonterminalMode,
98+
) -> PResult<'a, Nonterminal> {
8999
// Any `Nonterminal` which stores its tokens (currently `NtItem` and `NtExpr`)
90100
// needs to have them force-captured here.
91101
// A `macro_rules!` invocation may pass a captured item/expr to a proc-macro,
@@ -129,8 +139,14 @@ impl<'a> Parser<'a> {
129139
}
130140
}
131141
NonterminalKind::Pat => {
132-
let (mut pat, tokens) =
133-
self.collect_tokens(|this| this.parse_top_pat_no_commas())?;
142+
let (mut pat, tokens) = match or_pat_mode {
143+
OrPatNonterminalMode::TopPat => {
144+
self.collect_tokens(|this| this.parse_top_pat_no_commas())?
145+
}
146+
OrPatNonterminalMode::NoTopAlt => {
147+
self.collect_tokens(|this| this.parse_pat(None))?
148+
}
149+
};
134150
// We have have eaten an NtPat, which could already have tokens
135151
if pat.tokens.is_none() {
136152
pat.tokens = tokens;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// run-pass
2+
3+
//#![feature(or_patterns)]
4+
5+
macro_rules! foo {
6+
($orpat:pat, $val:expr) => {
7+
match $val {
8+
x @ ($orpat) => x,
9+
_ => 0xDEADBEEF,
10+
}
11+
};
12+
($nonor:pat | $val:expr, 3) => {
13+
match $val {
14+
x @ ($orpat) => x,
15+
_ => 0xDEADBEEF,
16+
}
17+
};
18+
}
19+
20+
macro_rules! bar {
21+
($nonor:pat |) => {};
22+
}
23+
24+
macro_rules! baz {
25+
($nonor:pat) => {};
26+
}
27+
28+
fn main() {
29+
// Test ambiguity.
30+
foo!(1 | 2, 3); //~ERROR: multiple matchers
31+
32+
// Leading vert not allowed in pat<no_top_alt>
33+
bar!(1 | 2 | 3 |); // ok
34+
bar!(|1| 2 | 3 |); //~ERROR: no rules expected
35+
bar!(1 | 2 | 3); //~ERROR: unexpected end
36+
37+
baz!(1 | 2 | 3); // ok
38+
baz!(|1| 2 | 3); // ok
39+
baz!(|1| 2 | 3 |); //~ERROR: no rules expected
40+
}

src/test/ui/pattern/or-pattern-macro-pat.rs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,50 @@ use Foo::*;
88
enum Foo {
99
A(u64),
1010
B(u64),
11-
C, D
11+
C,
12+
D,
1213
}
1314

1415
macro_rules! foo {
1516
($orpat:pat, $val:expr) => {
1617
match $val {
17-
x @ ($orpat) => x,
18+
x @ ($orpat) => x, // leading vert would not be allowed in $orpat
1819
_ => B(0xDEADBEEF),
1920
}
2021
};
2122
}
2223

2324
macro_rules! bar {
25+
($orpat:pat, $val:expr) => {
26+
match $val {
27+
$orpat => 42, // leading vert allowed here
28+
_ => 0xDEADBEEF,
29+
}
30+
};
31+
}
32+
33+
macro_rules! quux {
34+
($orpat1:pat | $orpat2:pat, $val:expr) => {
35+
match $val {
36+
x @ ($orpat1) => x,
37+
_ => B(0xDEADBEEF),
38+
}
39+
};
40+
}
41+
42+
macro_rules! baz {
2443
($orpat:pat, $val:expr) => {
2544
match $val {
2645
$orpat => 42,
2746
_ => 0xDEADBEEF,
2847
}
2948
};
49+
($nonor:pat | $val:expr, C) => {
50+
match $val {
51+
x @ ($orpat) => x,
52+
_ => 0xDEADBEEF,
53+
}
54+
};
3055
}
3156

3257
fn main() {
@@ -35,6 +60,14 @@ fn main() {
3560
assert_eq!(y, A(32));
3661

3762
// Leading vert in or-pattern.
38-
let y = bar!(| C | D, C);
63+
let y = bar!(|C| D, C);
3964
assert_eq!(y, 42u64);
65+
66+
// Leading vert in or-pattern makes baz! unambiguous.
67+
let y = baz!(|C| D, C);
68+
assert_eq!(y, 42u64);
69+
70+
// Or-separator fallback.
71+
let y = quux!(C | D, D);
72+
assert_eq!(y, B(0xDEADBEEF));
4073
}

0 commit comments

Comments
 (0)