Skip to content

Commit 29e3212

Browse files
committed
Auto merge of #80100 - mark-i-m:pattORns-2, r=petrochenkov
or_patterns: implement :pat edition-specific behavior cc #54883 `@joshtriplett` This PR implements the edition-specific behavior of `:pat` wrt or-patterns, as determined by the crater runs and T-lang consensus in #54883 (comment). I believe this can unblock stabilization of or_patterns. r? `@petrochenkov`
2 parents 0c11b93 + 1a7d00a commit 29e3212

14 files changed

+193
-75
lines changed

compiler/rustc_expand/src/mbe/macro_parser.rs

+26-7
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ use TokenTreeOrTokenTreeSlice::*;
7777
use crate::mbe::{self, TokenTree};
7878

7979
use rustc_ast::token::{self, DocComment, Nonterminal, Token};
80-
use rustc_parse::parser::Parser;
80+
use rustc_parse::parser::{OrPatNonterminalMode, Parser};
8181
use rustc_session::parse::ParseSess;
82-
use rustc_span::symbol::MacroRulesNormalizedIdent;
82+
use rustc_span::{edition::Edition, symbol::MacroRulesNormalizedIdent};
8383

8484
use smallvec::{smallvec, SmallVec};
8585

@@ -414,6 +414,18 @@ fn token_name_eq(t1: &Token, t2: &Token) -> bool {
414414
}
415415
}
416416

417+
/// In edition 2015/18, `:pat` can only match `pat<no_top_alt>` because otherwise, we have
418+
/// breakage. As of edition 2021, `:pat` matches `top_pat`.
419+
///
420+
/// See <https://github.com/rust-lang/rust/issues/54883> for more info.
421+
fn or_pat_mode(edition: Edition) -> OrPatNonterminalMode {
422+
match edition {
423+
Edition::Edition2015 | Edition::Edition2018 => OrPatNonterminalMode::NoTopAlt,
424+
// FIXME(mark-i-m): uncomment this when edition 2021 machinery is added.
425+
// Edition::Edition2021 => OrPatNonterminalMode::TopPat,
426+
}
427+
}
428+
417429
/// Process the matcher positions of `cur_items` until it is empty. In the process, this will
418430
/// produce more items in `next_items`, `eof_items`, and `bb_items`.
419431
///
@@ -553,10 +565,14 @@ fn inner_parse_loop<'root, 'tt>(
553565

554566
// We need to match a metavar with a valid ident... call out to the black-box
555567
// parser by adding an item to `bb_items`.
556-
TokenTree::MetaVarDecl(_, _, kind) => {
557-
// Built-in nonterminals never start with these tokens,
558-
// so we can eliminate them from consideration.
559-
if Parser::nonterminal_may_begin_with(kind, token) {
568+
TokenTree::MetaVarDecl(span, _, kind) => {
569+
// Built-in nonterminals never start with these tokens, so we can eliminate
570+
// them from consideration.
571+
//
572+
// We use the span of the metavariable declaration to determine any
573+
// edition-specific matching behavior for non-terminals.
574+
if Parser::nonterminal_may_begin_with(kind, token, or_pat_mode(span.edition()))
575+
{
560576
bb_items.push(item);
561577
}
562578
}
@@ -717,7 +733,10 @@ pub(super) fn parse_tt(parser: &mut Cow<'_, Parser<'_>>, ms: &[TokenTree]) -> Na
717733
let mut item = bb_items.pop().unwrap();
718734
if let TokenTree::MetaVarDecl(span, _, kind) = item.top_elts.get_tt(item.idx) {
719735
let match_cur = item.match_cur;
720-
let nt = match parser.to_mut().parse_nonterminal(kind) {
736+
// We use the span of the metavariable declaration to determine any
737+
// edition-specific matching behavior for non-terminals.
738+
let nt = match parser.to_mut().parse_nonterminal(kind, or_pat_mode(span.edition()))
739+
{
721740
Err(mut err) => {
722741
err.span_label(
723742
span,

compiler/rustc_parse/src/parser/expr.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::pat::{GateOr, PARAM_EXPECTED};
1+
use super::pat::{GateOr, RecoverComma, PARAM_EXPECTED};
22
use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
33
use super::{BlockMode, Parser, PathStyle, Restrictions, TokenType};
44
use super::{SemiColonMode, SeqSep, TokenExpectType};
@@ -1729,7 +1729,7 @@ impl<'a> Parser<'a> {
17291729
/// The `let` token has already been eaten.
17301730
fn parse_let_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
17311731
let lo = self.prev_token.span;
1732-
let pat = self.parse_top_pat(GateOr::No)?;
1732+
let pat = self.parse_top_pat(GateOr::No, RecoverComma::Yes)?;
17331733
self.expect(&token::Eq)?;
17341734
let expr = self.with_res(self.restrictions | Restrictions::NO_STRUCT_LITERAL, |this| {
17351735
this.parse_assoc_expr_with(1 + prec_let_scrutinee_needs_par(), None.into())
@@ -1792,7 +1792,7 @@ impl<'a> Parser<'a> {
17921792
_ => None,
17931793
};
17941794

1795-
let pat = self.parse_top_pat(GateOr::Yes)?;
1795+
let pat = self.parse_top_pat(GateOr::Yes, RecoverComma::Yes)?;
17961796
if !self.eat_keyword(kw::In) {
17971797
self.error_missing_in_for_loop();
17981798
}
@@ -1902,7 +1902,7 @@ impl<'a> Parser<'a> {
19021902
pub(super) fn parse_arm(&mut self) -> PResult<'a, Arm> {
19031903
let attrs = self.parse_outer_attributes()?;
19041904
let lo = self.token.span;
1905-
let pat = self.parse_top_pat(GateOr::No)?;
1905+
let pat = self.parse_top_pat(GateOr::No, RecoverComma::Yes)?;
19061906
let guard = if self.eat_keyword(kw::If) {
19071907
let if_span = self.prev_token.span;
19081908
let cond = self.parse_expr()?;

compiler/rustc_parse/src/parser/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod ty;
1212
use crate::lexer::UnmatchedBrace;
1313
pub use diagnostics::AttemptLocalParseRecovery;
1414
use diagnostics::Error;
15+
pub use pat::OrPatNonterminalMode;
1516
pub use path::PathStyle;
1617

1718
use rustc_ast::ptr::P;

compiler/rustc_parse/src/parser/nonterminal.rs

+20-3
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,19 @@ use rustc_ast_pretty::pprust;
44
use rustc_errors::PResult;
55
use rustc_span::symbol::{kw, Ident};
66

7+
use crate::parser::pat::{GateOr, OrPatNonterminalMode, RecoverComma};
78
use crate::parser::{FollowedByType, Parser, PathStyle};
89

910
impl<'a> Parser<'a> {
1011
/// Checks whether a non-terminal may begin with a particular token.
1112
///
1213
/// Returning `false` is a *stability guarantee* that such a matcher will *never* begin with that
1314
/// token. Be conservative (return true) if not sure.
14-
pub fn nonterminal_may_begin_with(kind: NonterminalKind, token: &Token) -> bool {
15+
pub fn nonterminal_may_begin_with(
16+
kind: NonterminalKind,
17+
token: &Token,
18+
or_pat_mode: OrPatNonterminalMode,
19+
) -> bool {
1520
/// Checks whether the non-terminal may contain a single (non-keyword) identifier.
1621
fn may_be_ident(nt: &token::Nonterminal) -> bool {
1722
match *nt {
@@ -70,6 +75,8 @@ impl<'a> Parser<'a> {
7075
token::ModSep | // path
7176
token::Lt | // path (UFCS constant)
7277
token::BinOp(token::Shl) => true, // path (double UFCS)
78+
// leading vert `|` or-pattern
79+
token::BinOp(token::Or) => matches!(or_pat_mode, OrPatNonterminalMode::TopPat),
7380
token::Interpolated(ref nt) => may_be_ident(nt),
7481
_ => false,
7582
},
@@ -86,7 +93,12 @@ impl<'a> Parser<'a> {
8693
}
8794
}
8895

89-
pub fn parse_nonterminal(&mut self, kind: NonterminalKind) -> PResult<'a, Nonterminal> {
96+
/// Parse a non-terminal (e.g. MBE `:pat` or `:ident`).
97+
pub fn parse_nonterminal(
98+
&mut self,
99+
kind: NonterminalKind,
100+
or_pat_mode: OrPatNonterminalMode,
101+
) -> PResult<'a, Nonterminal> {
90102
// Any `Nonterminal` which stores its tokens (currently `NtItem` and `NtExpr`)
91103
// needs to have them force-captured here.
92104
// A `macro_rules!` invocation may pass a captured item/expr to a proc-macro,
@@ -130,7 +142,12 @@ impl<'a> Parser<'a> {
130142
}
131143
}
132144
NonterminalKind::Pat => {
133-
let (mut pat, tokens) = self.collect_tokens(|this| this.parse_pat(None))?;
145+
let (mut pat, tokens) = self.collect_tokens(|this| match or_pat_mode {
146+
OrPatNonterminalMode::TopPat => {
147+
this.parse_top_pat(GateOr::Yes, RecoverComma::No)
148+
}
149+
OrPatNonterminalMode::NoTopAlt => this.parse_pat(None),
150+
})?;
134151
// We have have eaten an NtPat, which could already have tokens
135152
if pat.tokens.is_none() {
136153
pat.tokens = tokens;

compiler/rustc_parse/src/parser/pat.rs

+14-3
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,18 @@ pub(super) enum GateOr {
2626

2727
/// Whether or not to recover a `,` when parsing or-patterns.
2828
#[derive(PartialEq, Copy, Clone)]
29-
enum RecoverComma {
29+
pub(super) enum RecoverComma {
3030
Yes,
3131
No,
3232
}
3333

34+
/// Used when parsing a non-terminal (see `parse_nonterminal`) to determine if `:pat` should match
35+
/// `top_pat` or `pat<no_top_alt>`. See issue <https://github.com/rust-lang/rust/pull/78935>.
36+
pub enum OrPatNonterminalMode {
37+
TopPat,
38+
NoTopAlt,
39+
}
40+
3441
impl<'a> Parser<'a> {
3542
/// Parses a pattern.
3643
///
@@ -43,13 +50,17 @@ impl<'a> Parser<'a> {
4350

4451
/// Entry point to the main pattern parser.
4552
/// Corresponds to `top_pat` in RFC 2535 and allows or-pattern at the top level.
46-
pub(super) fn parse_top_pat(&mut self, gate_or: GateOr) -> PResult<'a, P<Pat>> {
53+
pub(super) fn parse_top_pat(
54+
&mut self,
55+
gate_or: GateOr,
56+
rc: RecoverComma,
57+
) -> PResult<'a, P<Pat>> {
4758
// Allow a '|' before the pats (RFCs 1925, 2530, and 2535).
4859
let gated_leading_vert = self.eat_or_separator(None) && gate_or == GateOr::Yes;
4960
let leading_vert_span = self.prev_token.span;
5061

5162
// Parse the possibly-or-pattern.
52-
let pat = self.parse_pat_with_or(None, gate_or, RecoverComma::Yes)?;
63+
let pat = self.parse_pat_with_or(None, gate_or, rc)?;
5364

5465
// If we parsed a leading `|` which should be gated,
5566
// and no other gated or-pattern has been parsed thus far,

compiler/rustc_parse/src/parser/stmt.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::attr::DEFAULT_INNER_ATTR_FORBIDDEN;
22
use super::diagnostics::{AttemptLocalParseRecovery, Error};
33
use super::expr::LhsExpr;
4-
use super::pat::GateOr;
4+
use super::pat::{GateOr, RecoverComma};
55
use super::path::PathStyle;
66
use super::{BlockMode, Parser, Restrictions, SemiColonMode};
77
use crate::maybe_whole;
@@ -185,7 +185,7 @@ impl<'a> Parser<'a> {
185185
/// Parses a local variable declaration.
186186
fn parse_local(&mut self, attrs: AttrVec) -> PResult<'a, P<Local>> {
187187
let lo = self.prev_token.span;
188-
let pat = self.parse_top_pat(GateOr::Yes)?;
188+
let pat = self.parse_top_pat(GateOr::Yes, RecoverComma::Yes)?;
189189

190190
let (err, ty) = if self.eat(&token::Colon) {
191191
// Save the state of the parser before parsing type normally, in case there is a `:`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// run-pass
2+
// edition:2018
3+
4+
macro_rules! pat_bar {
5+
($p:pat | $p2:pat) => {{
6+
match Some(1u8) {
7+
$p | $p2 => {}
8+
_ => {}
9+
}
10+
}};
11+
}
12+
13+
fn main() {
14+
pat_bar!(Some(1u8) | None);
15+
}

src/test/ui/macros/macro-pat-follow.rs

+3-13
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,19 @@ macro_rules! pat_in {
33
($p:pat in $e:expr) => {{
44
let mut iter = $e.into_iter();
55
while let $p = iter.next() {}
6-
}}
6+
}};
77
}
88

99
macro_rules! pat_if {
1010
($p:pat if $e:expr) => {{
1111
match Some(1u8) {
12-
$p if $e => {},
12+
$p if $e => {}
1313
_ => {}
1414
}
15-
}}
16-
}
17-
18-
macro_rules! pat_bar {
19-
($p:pat | $p2:pat) => {{
20-
match Some(1u8) {
21-
$p | $p2 => {},
22-
_ => {}
23-
}
24-
}}
15+
}};
2516
}
2617

2718
fn main() {
2819
pat_in!(Some(_) in 0..10);
2920
pat_if!(Some(x) if x > 0);
30-
pat_bar!(Some(1u8) | None);
3121
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Test that :pat doesn't accept top-level or-patterns in edition 2018.
2+
3+
// edition:2018
4+
5+
#![feature(or_patterns)]
6+
7+
fn main() {}
8+
9+
// Test the `pat` macro fragment parser:
10+
macro_rules! accept_pat {
11+
($p:pat) => {};
12+
}
13+
14+
accept_pat!(p | q); //~ ERROR no rules expected the token `|`
15+
accept_pat!(|p| q); //~ ERROR no rules expected the token `|`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: no rules expected the token `|`
2+
--> $DIR/or-patterns-syntactic-fail-2018.rs:14:15
3+
|
4+
LL | macro_rules! accept_pat {
5+
| ----------------------- when calling this macro
6+
...
7+
LL | accept_pat!(p | q);
8+
| ^ no rules expected this token in macro call
9+
10+
error: no rules expected the token `|`
11+
--> $DIR/or-patterns-syntactic-fail-2018.rs:15:13
12+
|
13+
LL | macro_rules! accept_pat {
14+
| ----------------------- when calling this macro
15+
...
16+
LL | accept_pat!(|p| q);
17+
| ^ no rules expected this token in macro call
18+
19+
error: aborting due to 2 previous errors
20+

src/test/ui/or-patterns/or-patterns-syntactic-fail.rs

-10
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,6 @@
55

66
fn main() {}
77

8-
// Test the `pat` macro fragment parser:
9-
macro_rules! accept_pat {
10-
($p:pat) => {}
11-
}
12-
13-
accept_pat!(p | q); //~ ERROR no rules expected the token `|`
14-
accept_pat!(| p | q); //~ ERROR no rules expected the token `|`
15-
16-
// Non-macro tests:
17-
188
enum E { A, B }
199
use E::*;
2010

0 commit comments

Comments
 (0)