Skip to content

Commit 6ea042c

Browse files
authored
Rollup merge of rust-lang#81869 - mark-i-m:leading-vert, r=petrochenkov
Simplify pattern grammar, improve or-pattern diagnostics This implements the change under FCP in rust-lang#81415. It allows nested or-patterns to contain a leading `|`, simplifying the [grammar for patterns](https://github.com/rust-lang/reference/pull/957/files?short_path=cc629f1#diff-cc629f15712821139bc706c63b3845ab59a008e2a998e08ffad42e3aebcbcbe2). Along the way, we also improve the diagnostics around a few specially-handled cases, such as using `||` instead of `|`, using or-patterns in fn params, including the leading `|` in the pattern span, etc. r? ```@petrochenkov```
2 parents 06bb4e7 + aee1e59 commit 6ea042c

18 files changed

+188
-302
lines changed

compiler/rustc_expand/src/expand.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use rustc_data_structures::stack::ensure_sufficient_stack;
2222
use rustc_data_structures::sync::Lrc;
2323
use rustc_errors::{Applicability, PResult};
2424
use rustc_feature::Features;
25-
use rustc_parse::parser::{AttemptLocalParseRecovery, ForceCollect, Parser};
25+
use rustc_parse::parser::{AttemptLocalParseRecovery, ForceCollect, GateOr, Parser, RecoverComma};
2626
use rustc_parse::validate_attr;
2727
use rustc_session::lint::builtin::UNUSED_DOC_COMMENTS;
2828
use rustc_session::lint::BuiltinLintDiagnostics;
@@ -914,7 +914,9 @@ pub fn parse_ast_fragment<'a>(
914914
}
915915
}
916916
AstFragmentKind::Ty => AstFragment::Ty(this.parse_ty()?),
917-
AstFragmentKind::Pat => AstFragment::Pat(this.parse_pat(None)?),
917+
AstFragmentKind::Pat => {
918+
AstFragment::Pat(this.parse_pat_allow_top_alt(None, GateOr::Yes, RecoverComma::No)?)
919+
}
918920
AstFragmentKind::Arms
919921
| AstFragmentKind::Fields
920922
| AstFragmentKind::FieldPats

compiler/rustc_parse/src/parser/diagnostics.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1654,7 +1654,7 @@ impl<'a> Parser<'a> {
16541654
}
16551655

16561656
pub(super) fn recover_arg_parse(&mut self) -> PResult<'a, (P<ast::Pat>, P<ast::Ty>)> {
1657-
let pat = self.parse_pat(Some("argument name"))?;
1657+
let pat = self.parse_pat_no_top_alt(Some("argument name"))?;
16581658
self.expect(&token::Colon)?;
16591659
let ty = self.parse_ty()?;
16601660

compiler/rustc_parse/src/parser/expr.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1726,7 +1726,7 @@ impl<'a> Parser<'a> {
17261726
let lo = self.token.span;
17271727
let attrs = self.parse_outer_attributes()?;
17281728
self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| {
1729-
let pat = this.parse_pat(PARAM_EXPECTED)?;
1729+
let pat = this.parse_pat_no_top_alt(PARAM_EXPECTED)?;
17301730
let ty = if this.eat(&token::Colon) {
17311731
this.parse_ty()?
17321732
} else {
@@ -1803,7 +1803,7 @@ impl<'a> Parser<'a> {
18031803
/// The `let` token has already been eaten.
18041804
fn parse_let_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
18051805
let lo = self.prev_token.span;
1806-
let pat = self.parse_top_pat(GateOr::No, RecoverComma::Yes)?;
1806+
let pat = self.parse_pat_allow_top_alt(None, GateOr::No, RecoverComma::Yes)?;
18071807
self.expect(&token::Eq)?;
18081808
let expr = self.with_res(self.restrictions | Restrictions::NO_STRUCT_LITERAL, |this| {
18091809
this.parse_assoc_expr_with(1 + prec_let_scrutinee_needs_par(), None.into())
@@ -1866,7 +1866,7 @@ impl<'a> Parser<'a> {
18661866
_ => None,
18671867
};
18681868

1869-
let pat = self.parse_top_pat(GateOr::Yes, RecoverComma::Yes)?;
1869+
let pat = self.parse_pat_allow_top_alt(None, GateOr::Yes, RecoverComma::Yes)?;
18701870
if !self.eat_keyword(kw::In) {
18711871
self.error_missing_in_for_loop();
18721872
}
@@ -1977,7 +1977,7 @@ impl<'a> Parser<'a> {
19771977
let attrs = self.parse_outer_attributes()?;
19781978
self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| {
19791979
let lo = this.token.span;
1980-
let pat = this.parse_top_pat(GateOr::No, RecoverComma::Yes)?;
1980+
let pat = this.parse_pat_allow_top_alt(None, GateOr::No, RecoverComma::Yes)?;
19811981
let guard = if this.eat_keyword(kw::If) {
19821982
let if_span = this.prev_token.span;
19831983
let cond = this.parse_expr()?;

compiler/rustc_parse/src/parser/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::lexer::UnmatchedBrace;
1414
pub use attr_wrapper::AttrWrapper;
1515
pub use diagnostics::AttemptLocalParseRecovery;
1616
use diagnostics::Error;
17+
pub use pat::{GateOr, RecoverComma};
1718
pub use path::PathStyle;
1819

1920
use rustc_ast::ptr::P;

compiler/rustc_parse/src/parser/nonterminal.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ impl<'a> Parser<'a> {
120120
},
121121
NonterminalKind::Pat2018 { .. } | NonterminalKind::Pat2021 { .. } => {
122122
token::NtPat(self.collect_tokens_no_attrs(|this| match kind {
123-
NonterminalKind::Pat2018 { .. } => this.parse_pat(None),
123+
NonterminalKind::Pat2018 { .. } => this.parse_pat_no_top_alt(None),
124124
NonterminalKind::Pat2021 { .. } => {
125-
this.parse_top_pat(GateOr::Yes, RecoverComma::No)
125+
this.parse_pat_allow_top_alt(None, GateOr::Yes, RecoverComma::No)
126126
}
127127
_ => unreachable!(),
128128
})?)

compiler/rustc_parse/src/parser/pat.rs

+102-81
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ const WHILE_PARSING_OR_MSG: &str = "while parsing this or-pattern starting here"
1919

2020
/// Whether or not an or-pattern should be gated when occurring in the current context.
2121
#[derive(PartialEq, Clone, Copy)]
22-
pub(super) enum GateOr {
22+
pub enum GateOr {
2323
Yes,
2424
No,
2525
}
2626

2727
/// Whether or not to recover a `,` when parsing or-patterns.
2828
#[derive(PartialEq, Copy, Clone)]
29-
pub(super) enum RecoverComma {
29+
pub enum RecoverComma {
3030
Yes,
3131
No,
3232
}
@@ -37,80 +37,57 @@ impl<'a> Parser<'a> {
3737
/// Corresponds to `pat<no_top_alt>` in RFC 2535 and does not admit or-patterns
3838
/// at the top level. Used when parsing the parameters of lambda expressions,
3939
/// functions, function pointers, and `pat` macro fragments.
40-
pub fn parse_pat(&mut self, expected: Expected) -> PResult<'a, P<Pat>> {
40+
pub fn parse_pat_no_top_alt(&mut self, expected: Expected) -> PResult<'a, P<Pat>> {
4141
self.parse_pat_with_range_pat(true, expected)
4242
}
4343

44-
/// Entry point to the main pattern parser.
44+
/// Parses a pattern.
45+
///
4546
/// Corresponds to `top_pat` in RFC 2535 and allows or-pattern at the top level.
46-
pub(super) fn parse_top_pat(
47+
/// Used for parsing patterns in all cases when `pat<no_top_alt>` is not used.
48+
///
49+
/// Note that after the FCP in <https://github.com/rust-lang/rust/issues/81415>,
50+
/// a leading vert is allowed in nested or-patterns, too. This allows us to
51+
/// simplify the grammar somewhat.
52+
pub fn parse_pat_allow_top_alt(
4753
&mut self,
54+
expected: Expected,
4855
gate_or: GateOr,
4956
rc: RecoverComma,
5057
) -> PResult<'a, P<Pat>> {
5158
// Allow a '|' before the pats (RFCs 1925, 2530, and 2535).
52-
let gated_leading_vert = self.eat_or_separator(None) && gate_or == GateOr::Yes;
53-
let leading_vert_span = self.prev_token.span;
54-
55-
// Parse the possibly-or-pattern.
56-
let pat = self.parse_pat_with_or(None, gate_or, rc)?;
57-
58-
// If we parsed a leading `|` which should be gated,
59-
// and no other gated or-pattern has been parsed thus far,
60-
// then we should really gate the leading `|`.
61-
// This complicated procedure is done purely for diagnostics UX.
62-
if gated_leading_vert && self.sess.gated_spans.is_ungated(sym::or_patterns) {
63-
self.sess.gated_spans.gate(sym::or_patterns, leading_vert_span);
64-
}
65-
66-
Ok(pat)
67-
}
68-
69-
/// Parse the pattern for a function or function pointer parameter.
70-
/// Special recovery is provided for or-patterns and leading `|`.
71-
pub(super) fn parse_fn_param_pat(&mut self) -> PResult<'a, P<Pat>> {
72-
self.recover_leading_vert(None, "not allowed in a parameter pattern");
73-
let pat = self.parse_pat_with_or(PARAM_EXPECTED, GateOr::No, RecoverComma::No)?;
74-
75-
if let PatKind::Or(..) = &pat.kind {
76-
self.ban_illegal_fn_param_or_pat(&pat);
77-
}
78-
79-
Ok(pat)
80-
}
59+
let leading_vert_span =
60+
if self.eat_or_separator(None) { Some(self.prev_token.span) } else { None };
8161

82-
/// Ban `A | B` immediately in a parameter pattern and suggest wrapping in parens.
83-
fn ban_illegal_fn_param_or_pat(&self, pat: &Pat) {
84-
let msg = "wrap the pattern in parenthesis";
85-
let fix = format!("({})", pprust::pat_to_string(pat));
86-
self.struct_span_err(pat.span, "an or-pattern parameter must be wrapped in parenthesis")
87-
.span_suggestion(pat.span, msg, fix, Applicability::MachineApplicable)
88-
.emit();
89-
}
90-
91-
/// Parses a pattern, that may be a or-pattern (e.g. `Foo | Bar` in `Some(Foo | Bar)`).
92-
/// Corresponds to `pat<allow_top_alt>` in RFC 2535.
93-
fn parse_pat_with_or(
94-
&mut self,
95-
expected: Expected,
96-
gate_or: GateOr,
97-
rc: RecoverComma,
98-
) -> PResult<'a, P<Pat>> {
9962
// Parse the first pattern (`p_0`).
100-
let first_pat = self.parse_pat(expected)?;
63+
let first_pat = self.parse_pat_no_top_alt(expected)?;
10164
self.maybe_recover_unexpected_comma(first_pat.span, rc, gate_or)?;
10265

10366
// If the next token is not a `|`,
10467
// this is not an or-pattern and we should exit here.
10568
if !self.check(&token::BinOp(token::Or)) && self.token != token::OrOr {
69+
// If we parsed a leading `|` which should be gated,
70+
// then we should really gate the leading `|`.
71+
// This complicated procedure is done purely for diagnostics UX.
72+
if let Some(leading_vert_span) = leading_vert_span {
73+
if gate_or == GateOr::Yes && self.sess.gated_spans.is_ungated(sym::or_patterns) {
74+
self.sess.gated_spans.gate(sym::or_patterns, leading_vert_span);
75+
}
76+
77+
// If there was a leading vert, treat this as an or-pattern. This improves
78+
// diagnostics.
79+
let span = leading_vert_span.to(self.prev_token.span);
80+
return Ok(self.mk_pat(span, PatKind::Or(vec![first_pat])));
81+
}
82+
10683
return Ok(first_pat);
10784
}
10885

10986
// Parse the patterns `p_1 | ... | p_n` where `n > 0`.
110-
let lo = first_pat.span;
87+
let lo = leading_vert_span.unwrap_or(first_pat.span);
11188
let mut pats = vec![first_pat];
11289
while self.eat_or_separator(Some(lo)) {
113-
let pat = self.parse_pat(expected).map_err(|mut err| {
90+
let pat = self.parse_pat_no_top_alt(expected).map_err(|mut err| {
11491
err.span_label(lo, WHILE_PARSING_OR_MSG);
11592
err
11693
})?;
@@ -127,6 +104,62 @@ impl<'a> Parser<'a> {
127104
Ok(self.mk_pat(or_pattern_span, PatKind::Or(pats)))
128105
}
129106

107+
/// Parse the pattern for a function or function pointer parameter.
108+
pub(super) fn parse_fn_param_pat(&mut self) -> PResult<'a, P<Pat>> {
109+
// We actually do _not_ allow top-level or-patterns in function params, but we use
110+
// `parse_pat_allow_top_alt` anyway so that we can detect when a user tries to use it. This
111+
// allows us to print a better error message.
112+
//
113+
// In order to get good UX, we first recover in the case of a leading vert for an illegal
114+
// top-level or-pat. Normally, this means recovering both `|` and `||`, but in this case,
115+
// a leading `||` probably doesn't indicate an or-pattern attempt, so we handle that
116+
// separately.
117+
if let token::OrOr = self.token.kind {
118+
let span = self.token.span;
119+
let mut err = self.struct_span_err(span, "unexpected `||` before function parameter");
120+
err.span_suggestion(
121+
span,
122+
"remove the `||`",
123+
String::new(),
124+
Applicability::MachineApplicable,
125+
);
126+
err.note("alternatives in or-patterns are separated with `|`, not `||`");
127+
err.emit();
128+
self.bump();
129+
}
130+
131+
let pat = self.parse_pat_allow_top_alt(PARAM_EXPECTED, GateOr::No, RecoverComma::No)?;
132+
133+
if let PatKind::Or(..) = &pat.kind {
134+
self.ban_illegal_fn_param_or_pat(&pat);
135+
}
136+
137+
Ok(pat)
138+
}
139+
140+
/// Ban `A | B` immediately in a parameter pattern and suggest wrapping in parens.
141+
fn ban_illegal_fn_param_or_pat(&self, pat: &Pat) {
142+
// If all we have a leading vert, then print a special message. This is the case if
143+
// `parse_pat_allow_top_alt` returns an or-pattern with one variant.
144+
let (msg, fix) = match &pat.kind {
145+
PatKind::Or(pats) if pats.len() == 1 => {
146+
let msg = "remove the leading `|`";
147+
let fix = pprust::pat_to_string(pat);
148+
(msg, fix)
149+
}
150+
151+
_ => {
152+
let msg = "wrap the pattern in parentheses";
153+
let fix = format!("({})", pprust::pat_to_string(pat));
154+
(msg, fix)
155+
}
156+
};
157+
158+
self.struct_span_err(pat.span, "an or-pattern parameter must be wrapped in parentheses")
159+
.span_suggestion(pat.span, msg, fix, Applicability::MachineApplicable)
160+
.emit();
161+
}
162+
130163
/// Eat the or-pattern `|` separator.
131164
/// If instead a `||` token is encountered, recover and pretend we parsed `|`.
132165
fn eat_or_separator(&mut self, lo: Option<Span>) -> bool {
@@ -179,7 +212,7 @@ impl<'a> Parser<'a> {
179212

180213
/// We have parsed `||` instead of `|`. Error and suggest `|` instead.
181214
fn ban_unexpected_or_or(&mut self, lo: Option<Span>) {
182-
let mut err = self.struct_span_err(self.token.span, "unexpected token `||` after pattern");
215+
let mut err = self.struct_span_err(self.token.span, "unexpected token `||` in pattern");
183216
err.span_suggestion(
184217
self.token.span,
185218
"use a single `|` to separate multiple alternative patterns",
@@ -244,30 +277,14 @@ impl<'a> Parser<'a> {
244277
/// sequence of patterns until `)` is reached.
245278
fn skip_pat_list(&mut self) -> PResult<'a, ()> {
246279
while !self.check(&token::CloseDelim(token::Paren)) {
247-
self.parse_pat(None)?;
280+
self.parse_pat_no_top_alt(None)?;
248281
if !self.eat(&token::Comma) {
249282
return Ok(());
250283
}
251284
}
252285
Ok(())
253286
}
254287

255-
/// Recursive possibly-or-pattern parser with recovery for an erroneous leading `|`.
256-
/// See `parse_pat_with_or` for details on parsing or-patterns.
257-
fn parse_pat_with_or_inner(&mut self) -> PResult<'a, P<Pat>> {
258-
self.recover_leading_vert(None, "only allowed in a top-level pattern");
259-
self.parse_pat_with_or(None, GateOr::Yes, RecoverComma::No)
260-
}
261-
262-
/// Recover if `|` or `||` is here.
263-
/// The user is thinking that a leading `|` is allowed in this position.
264-
fn recover_leading_vert(&mut self, lo: Option<Span>, ctx: &str) {
265-
if let token::BinOp(token::Or) | token::OrOr = self.token.kind {
266-
self.ban_illegal_vert(lo, "leading", ctx);
267-
self.bump();
268-
}
269-
}
270-
271288
/// A `|` or possibly `||` token shouldn't be here. Ban it.
272289
fn ban_illegal_vert(&mut self, lo: Option<Span>, pos: &str, ctx: &str) {
273290
let span = self.token.span;
@@ -305,8 +322,9 @@ impl<'a> Parser<'a> {
305322
self.parse_pat_tuple_or_parens()?
306323
} else if self.check(&token::OpenDelim(token::Bracket)) {
307324
// Parse `[pat, pat,...]` as a slice pattern.
308-
let (pats, _) =
309-
self.parse_delim_comma_seq(token::Bracket, |p| p.parse_pat_with_or_inner())?;
325+
let (pats, _) = self.parse_delim_comma_seq(token::Bracket, |p| {
326+
p.parse_pat_allow_top_alt(None, GateOr::Yes, RecoverComma::No)
327+
})?;
310328
PatKind::Slice(pats)
311329
} else if self.check(&token::DotDot) && !self.is_pat_range_end_start(1) {
312330
// A rest pattern `..`.
@@ -429,7 +447,7 @@ impl<'a> Parser<'a> {
429447

430448
// At this point we attempt to parse `@ $pat_rhs` and emit an error.
431449
self.bump(); // `@`
432-
let mut rhs = self.parse_pat(None)?;
450+
let mut rhs = self.parse_pat_no_top_alt(None)?;
433451
let sp = lhs.span.to(rhs.span);
434452

435453
if let PatKind::Ident(_, _, ref mut sub @ None) = rhs.kind {
@@ -518,8 +536,9 @@ impl<'a> Parser<'a> {
518536

519537
/// Parse a tuple or parenthesis pattern.
520538
fn parse_pat_tuple_or_parens(&mut self) -> PResult<'a, PatKind> {
521-
let (fields, trailing_comma) =
522-
self.parse_paren_comma_seq(|p| p.parse_pat_with_or_inner())?;
539+
let (fields, trailing_comma) = self.parse_paren_comma_seq(|p| {
540+
p.parse_pat_allow_top_alt(None, GateOr::Yes, RecoverComma::No)
541+
})?;
523542

524543
// Here, `(pat,)` is a tuple pattern.
525544
// For backward compatibility, `(..)` is a tuple pattern as well.
@@ -548,7 +567,7 @@ impl<'a> Parser<'a> {
548567
}
549568

550569
// Parse the pattern we hope to be an identifier.
551-
let mut pat = self.parse_pat(Some("identifier"))?;
570+
let mut pat = self.parse_pat_no_top_alt(Some("identifier"))?;
552571

553572
// If we don't have `mut $ident (@ pat)?`, error.
554573
if let PatKind::Ident(BindingMode::ByValue(m @ Mutability::Not), ..) = &mut pat.kind {
@@ -793,7 +812,7 @@ impl<'a> Parser<'a> {
793812
fn parse_pat_ident(&mut self, binding_mode: BindingMode) -> PResult<'a, PatKind> {
794813
let ident = self.parse_ident()?;
795814
let sub = if self.eat(&token::At) {
796-
Some(self.parse_pat(Some("binding pattern"))?)
815+
Some(self.parse_pat_no_top_alt(Some("binding pattern"))?)
797816
} else {
798817
None
799818
};
@@ -832,7 +851,9 @@ impl<'a> Parser<'a> {
832851
if qself.is_some() {
833852
return self.error_qpath_before_pat(&path, "(");
834853
}
835-
let (fields, _) = self.parse_paren_comma_seq(|p| p.parse_pat_with_or_inner())?;
854+
let (fields, _) = self.parse_paren_comma_seq(|p| {
855+
p.parse_pat_allow_top_alt(None, GateOr::Yes, RecoverComma::No)
856+
})?;
836857
Ok(PatKind::TupleStruct(path, fields))
837858
}
838859

@@ -998,7 +1019,7 @@ impl<'a> Parser<'a> {
9981019
// Parsing a pattern of the form `fieldname: pat`.
9991020
let fieldname = self.parse_field_name()?;
10001021
self.bump();
1001-
let pat = self.parse_pat_with_or_inner()?;
1022+
let pat = self.parse_pat_allow_top_alt(None, GateOr::Yes, RecoverComma::No)?;
10021023
hi = pat.span;
10031024
(pat, fieldname, false)
10041025
} else {

compiler/rustc_parse/src/parser/stmt.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ impl<'a> Parser<'a> {
220220
/// Parses a local variable declaration.
221221
fn parse_local(&mut self, attrs: AttrVec) -> PResult<'a, P<Local>> {
222222
let lo = self.prev_token.span;
223-
let pat = self.parse_top_pat(GateOr::Yes, RecoverComma::Yes)?;
223+
let pat = self.parse_pat_allow_top_alt(None, GateOr::Yes, RecoverComma::Yes)?;
224224

225225
let (err, ty) = if self.eat(&token::Colon) {
226226
// Save the state of the parser before parsing type normally, in case there is a `:`

0 commit comments

Comments
 (0)