Skip to content

Commit 3eba6c1

Browse files
committed
syntax: recover trailing | in or-patterns.
1 parent 488381c commit 3eba6c1

8 files changed

+323
-37
lines changed

src/libsyntax/parse/parser/pat.rs

+79-26
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ type Expected = Option<&'static str>;
1818
/// `Expected` for function and lambda parameter patterns.
1919
pub(super) const PARAM_EXPECTED: Expected = Some("parameter name");
2020

21+
const WHILE_PARSING_OR_MSG: &str = "while parsing this or-pattern starting here";
22+
2123
/// Whether or not an or-pattern should be gated when occurring in the current context.
2224
#[derive(PartialEq)]
2325
pub enum GateOr { Yes, No }
@@ -40,7 +42,7 @@ impl<'a> Parser<'a> {
4042
/// Corresponds to `top_pat` in RFC 2535 and allows or-pattern at the top level.
4143
pub(super) fn parse_top_pat(&mut self, gate_or: GateOr) -> PResult<'a, P<Pat>> {
4244
// Allow a '|' before the pats (RFCs 1925, 2530, and 2535).
43-
let gated_leading_vert = self.eat_or_separator() && gate_or == GateOr::Yes;
45+
let gated_leading_vert = self.eat_or_separator(None) && gate_or == GateOr::Yes;
4446
let leading_vert_span = self.prev_span;
4547

4648
// Parse the possibly-or-pattern.
@@ -63,7 +65,7 @@ impl<'a> Parser<'a> {
6365
/// Parse the pattern for a function or function pointer parameter.
6466
/// Special recovery is provided for or-patterns and leading `|`.
6567
pub(super) fn parse_fn_param_pat(&mut self) -> PResult<'a, P<Pat>> {
66-
self.recover_leading_vert("not allowed in a parameter pattern");
68+
self.recover_leading_vert(None, "not allowed in a parameter pattern");
6769
let pat = self.parse_pat_with_or(PARAM_EXPECTED, GateOr::No, RecoverComma::No)?;
6870

6971
if let PatKind::Or(..) = &pat.kind {
@@ -90,7 +92,7 @@ impl<'a> Parser<'a> {
9092
gate_or: GateOr,
9193
rc: RecoverComma,
9294
) -> PResult<'a, P<Pat>> {
93-
// Parse the first pattern.
95+
// Parse the first pattern (`p_0`).
9496
let first_pat = self.parse_pat(expected)?;
9597
self.maybe_recover_unexpected_comma(first_pat.span, rc)?;
9698

@@ -100,11 +102,12 @@ impl<'a> Parser<'a> {
100102
return Ok(first_pat)
101103
}
102104

105+
// Parse the patterns `p_1 | ... | p_n` where `n > 0`.
103106
let lo = first_pat.span;
104107
let mut pats = vec![first_pat];
105-
while self.eat_or_separator() {
108+
while self.eat_or_separator(Some(lo)) {
106109
let pat = self.parse_pat(expected).map_err(|mut err| {
107-
err.span_label(lo, "while parsing this or-pattern starting here");
110+
err.span_label(lo, WHILE_PARSING_OR_MSG);
108111
err
109112
})?;
110113
self.maybe_recover_unexpected_comma(pat.span, rc)?;
@@ -122,28 +125,65 @@ impl<'a> Parser<'a> {
122125

123126
/// Eat the or-pattern `|` separator.
124127
/// If instead a `||` token is encountered, recover and pretend we parsed `|`.
125-
fn eat_or_separator(&mut self) -> bool {
128+
fn eat_or_separator(&mut self, lo: Option<Span>) -> bool {
129+
if self.recover_trailing_vert(lo) {
130+
return false;
131+
}
132+
126133
match self.token.kind {
127134
token::OrOr => {
128135
// Found `||`; Recover and pretend we parsed `|`.
129-
self.ban_unexpected_or_or();
136+
self.ban_unexpected_or_or(lo);
130137
self.bump();
131138
true
132139
}
133140
_ => self.eat(&token::BinOp(token::Or)),
134141
}
135142
}
136143

144+
/// Recover if `|` or `||` is the current token and we have one of the
145+
/// tokens `=>`, `if`, `=`, `:`, `;`, `,`, `]`, `)`, or `}` ahead of us.
146+
///
147+
/// These tokens all indicate that we reached the end of the or-pattern
148+
/// list and can now reliably say that the `|` was an illegal trailing vert.
149+
/// Note that there are more tokens such as `@` for which we know that the `|`
150+
/// is an illegal parse. However, the user's intent is less clear in that case.
151+
fn recover_trailing_vert(&mut self, lo: Option<Span>) -> bool {
152+
let is_end_ahead = self.look_ahead(1, |token| match &token.kind {
153+
token::FatArrow // e.g. `a | => 0,`.
154+
| token::Ident(kw::If, false) // e.g. `a | if expr`.
155+
| token::Eq // e.g. `let a | = 0`.
156+
| token::Semi // e.g. `let a |;`.
157+
| token::Colon // e.g. `let a | :`.
158+
| token::Comma // e.g. `let (a |,)`.
159+
| token::CloseDelim(token::Bracket) // e.g. `let [a | ]`.
160+
| token::CloseDelim(token::Paren) // e.g. `let (a | )`.
161+
| token::CloseDelim(token::Brace) => true, // e.g. `let A { f: a | }`.
162+
_ => false,
163+
});
164+
match (is_end_ahead, &self.token.kind) {
165+
(true, token::BinOp(token::Or)) | (true, token::OrOr) => {
166+
self.ban_illegal_vert(lo, "trailing", "not allowed in an or-pattern");
167+
self.bump();
168+
true
169+
}
170+
_ => false,
171+
}
172+
}
173+
137174
/// We have parsed `||` instead of `|`. Error and suggest `|` instead.
138-
fn ban_unexpected_or_or(&mut self) {
139-
self.struct_span_err(self.token.span, "unexpected token `||` after pattern")
140-
.span_suggestion(
141-
self.token.span,
142-
"use a single `|` to separate multiple alternative patterns",
143-
"|".to_owned(),
144-
Applicability::MachineApplicable
145-
)
146-
.emit();
175+
fn ban_unexpected_or_or(&mut self, lo: Option<Span>) {
176+
let mut err = self.struct_span_err(self.token.span, "unexpected token `||` after pattern");
177+
err.span_suggestion(
178+
self.token.span,
179+
"use a single `|` to separate multiple alternative patterns",
180+
"|".to_owned(),
181+
Applicability::MachineApplicable
182+
);
183+
if let Some(lo) = lo {
184+
err.span_label(lo, WHILE_PARSING_OR_MSG);
185+
}
186+
err.emit();
147187
}
148188

149189
/// Some special error handling for the "top-level" patterns in a match arm,
@@ -198,25 +238,38 @@ impl<'a> Parser<'a> {
198238
/// Recursive possibly-or-pattern parser with recovery for an erroneous leading `|`.
199239
/// See `parse_pat_with_or` for details on parsing or-patterns.
200240
fn parse_pat_with_or_inner(&mut self) -> PResult<'a, P<Pat>> {
201-
self.recover_leading_vert("only allowed in a top-level pattern");
241+
self.recover_leading_vert(None, "only allowed in a top-level pattern");
202242
self.parse_pat_with_or(None, GateOr::Yes, RecoverComma::No)
203243
}
204244

205245
/// Recover if `|` or `||` is here.
206246
/// The user is thinking that a leading `|` is allowed in this position.
207-
fn recover_leading_vert(&mut self, ctx: &str) {
247+
fn recover_leading_vert(&mut self, lo: Option<Span>, ctx: &str) {
208248
if let token::BinOp(token::Or) | token::OrOr = self.token.kind {
209-
let span = self.token.span;
210-
let rm_msg = format!("remove the `{}`", pprust::token_to_string(&self.token));
211-
212-
self.struct_span_err(span, &format!("a leading `|` is {}", ctx))
213-
.span_suggestion(span, &rm_msg, String::new(), Applicability::MachineApplicable)
214-
.emit();
215-
249+
self.ban_illegal_vert(lo, "leading", ctx);
216250
self.bump();
217251
}
218252
}
219253

254+
/// A `|` or possibly `||` token shouldn't be here. Ban it.
255+
fn ban_illegal_vert(&mut self, lo: Option<Span>, pos: &str, ctx: &str) {
256+
let span = self.token.span;
257+
let mut err = self.struct_span_err(span, &format!("a {} `|` is {}", pos, ctx));
258+
err.span_suggestion(
259+
span,
260+
&format!("remove the `{}`", pprust::token_to_string(&self.token)),
261+
String::new(),
262+
Applicability::MachineApplicable,
263+
);
264+
if let Some(lo) = lo {
265+
err.span_label(lo, WHILE_PARSING_OR_MSG);
266+
}
267+
if let token::OrOr = self.token.kind {
268+
err.note("alternatives in or-patterns are separated with `|`, not `||`");
269+
}
270+
err.emit();
271+
}
272+
220273
/// Parses a pattern, with a setting whether modern range patterns (e.g., `a..=b`, `a..b` are
221274
/// allowed).
222275
fn parse_pat_with_range_pat(
@@ -259,7 +312,7 @@ impl<'a> Parser<'a> {
259312
self.bump();
260313
self.parse_pat_range_to(RangeEnd::Included(RangeSyntax::DotDotDot), "...")?
261314
}
262-
// At this point, token != &, &&, (, [
315+
// At this point, token != `&`, `&&`, `(`, `[`, `..`, `..=`, or `...`.
263316
_ => if self.eat_keyword(kw::Underscore) {
264317
// Parse _
265318
PatKind::Wild
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// In this regression test we check that a trailing `|` in an or-pattern just
2+
// before the `if` token of a `match` guard will receive parser recovery with
3+
// an appropriate error message.
4+
5+
enum E { A, B }
6+
7+
fn main() {
8+
match E::A {
9+
E::A |
10+
E::B | //~ ERROR a trailing `|` is not allowed in an or-pattern
11+
if true => {
12+
let recovery_witness: bool = 0; //~ ERROR mismatched types
13+
}
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: a trailing `|` is not allowed in an or-pattern
2+
--> $DIR/issue-64879-trailing-before-guard.rs:10:14
3+
|
4+
LL | E::A |
5+
| ---- while parsing this or-pattern starting here
6+
LL | E::B |
7+
| ^ help: remove the `|`
8+
9+
error[E0308]: mismatched types
10+
--> $DIR/issue-64879-trailing-before-guard.rs:12:42
11+
|
12+
LL | let recovery_witness: bool = 0;
13+
| ^ expected bool, found integer
14+
|
15+
= note: expected type `bool`
16+
found type `{integer}`
17+
18+
error: aborting due to 2 previous errors
19+
20+
For more information about this error, try `rustc --explain E0308`.

src/test/ui/or-patterns/multiple-pattern-typo.stderr

+18-6
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,49 @@ error: unexpected token `||` after pattern
22
--> $DIR/multiple-pattern-typo.rs:8:15
33
|
44
LL | 1 | 2 || 3 => (),
5-
| ^^ help: use a single `|` to separate multiple alternative patterns: `|`
5+
| - ^^ help: use a single `|` to separate multiple alternative patterns: `|`
6+
| |
7+
| while parsing this or-pattern starting here
68

79
error: unexpected token `||` after pattern
810
--> $DIR/multiple-pattern-typo.rs:13:16
911
|
1012
LL | (1 | 2 || 3) => (),
11-
| ^^ help: use a single `|` to separate multiple alternative patterns: `|`
13+
| - ^^ help: use a single `|` to separate multiple alternative patterns: `|`
14+
| |
15+
| while parsing this or-pattern starting here
1216

1317
error: unexpected token `||` after pattern
1418
--> $DIR/multiple-pattern-typo.rs:18:16
1519
|
1620
LL | (1 | 2 || 3,) => (),
17-
| ^^ help: use a single `|` to separate multiple alternative patterns: `|`
21+
| - ^^ help: use a single `|` to separate multiple alternative patterns: `|`
22+
| |
23+
| while parsing this or-pattern starting here
1824

1925
error: unexpected token `||` after pattern
2026
--> $DIR/multiple-pattern-typo.rs:25:18
2127
|
2228
LL | TS(1 | 2 || 3) => (),
23-
| ^^ help: use a single `|` to separate multiple alternative patterns: `|`
29+
| - ^^ help: use a single `|` to separate multiple alternative patterns: `|`
30+
| |
31+
| while parsing this or-pattern starting here
2432

2533
error: unexpected token `||` after pattern
2634
--> $DIR/multiple-pattern-typo.rs:32:23
2735
|
2836
LL | NS { f: 1 | 2 || 3 } => (),
29-
| ^^ help: use a single `|` to separate multiple alternative patterns: `|`
37+
| - ^^ help: use a single `|` to separate multiple alternative patterns: `|`
38+
| |
39+
| while parsing this or-pattern starting here
3040

3141
error: unexpected token `||` after pattern
3242
--> $DIR/multiple-pattern-typo.rs:37:16
3343
|
3444
LL | [1 | 2 || 3] => (),
35-
| ^^ help: use a single `|` to separate multiple alternative patterns: `|`
45+
| - ^^ help: use a single `|` to separate multiple alternative patterns: `|`
46+
| |
47+
| while parsing this or-pattern starting here
3648

3749
error: unexpected token `||` after pattern
3850
--> $DIR/multiple-pattern-typo.rs:42:9

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

+8
Original file line numberDiff line numberDiff line change
@@ -51,24 +51,32 @@ error: a leading `|` is only allowed in a top-level pattern
5151
|
5252
LL | let ( || A | B) = E::A;
5353
| ^^ help: remove the `||`
54+
|
55+
= note: alternatives in or-patterns are separated with `|`, not `||`
5456

5557
error: a leading `|` is only allowed in a top-level pattern
5658
--> $DIR/or-patterns-syntactic-fail.rs:48:11
5759
|
5860
LL | let [ || A | B ] = [E::A];
5961
| ^^ help: remove the `||`
62+
|
63+
= note: alternatives in or-patterns are separated with `|`, not `||`
6064

6165
error: a leading `|` is only allowed in a top-level pattern
6266
--> $DIR/or-patterns-syntactic-fail.rs:49:13
6367
|
6468
LL | let TS( || A | B );
6569
| ^^ help: remove the `||`
70+
|
71+
= note: alternatives in or-patterns are separated with `|`, not `||`
6672

6773
error: a leading `|` is only allowed in a top-level pattern
6874
--> $DIR/or-patterns-syntactic-fail.rs:50:17
6975
|
7076
LL | let NS { f: || A | B };
7177
| ^^ help: remove the `||`
78+
|
79+
= note: alternatives in or-patterns are separated with `|`, not `||`
7280

7381
error: no rules expected the token `|`
7482
--> $DIR/or-patterns-syntactic-fail.rs:14:15

src/test/ui/or-patterns/remove-leading-vert.fixed

+25-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Test the suggestion to remove a leading `|`.
1+
// Test the suggestion to remove a leading, or trailing `|`.
22

33
// run-rustfix
44

@@ -8,7 +8,7 @@
88
fn main() {}
99

1010
#[cfg(FALSE)]
11-
fn leading_vert() {
11+
fn leading() {
1212
fn fun1( A: E) {} //~ ERROR a leading `|` is not allowed in a parameter pattern
1313
fn fun2( A: E) {} //~ ERROR a leading `|` is not allowed in a parameter pattern
1414
let ( A): E; //~ ERROR a leading `|` is only allowed in a top-level pattern
@@ -21,3 +21,26 @@ fn leading_vert() {
2121
let NS { f: A }: NS; //~ ERROR a leading `|` is only allowed in a top-level pattern
2222
let NS { f: A }: NS; //~ ERROR a leading `|` is only allowed in a top-level pattern
2323
}
24+
25+
#[cfg(FALSE)]
26+
fn trailing() {
27+
let ( A ): E; //~ ERROR a trailing `|` is not allowed in an or-pattern
28+
let (a ,): (E,); //~ ERROR a trailing `|` is not allowed in an or-pattern
29+
let ( A | B ): E; //~ ERROR a trailing `|` is not allowed in an or-pattern
30+
let [ A | B ]: [E; 1]; //~ ERROR a trailing `|` is not allowed in an or-pattern
31+
let S { f: B }; //~ ERROR a trailing `|` is not allowed in an or-pattern
32+
let ( A | B ): E; //~ ERROR unexpected token `||` after pattern
33+
//~^ ERROR a trailing `|` is not allowed in an or-pattern
34+
match A {
35+
A => {} //~ ERROR a trailing `|` is not allowed in an or-pattern
36+
A => {} //~ ERROR a trailing `|` is not allowed in an or-pattern
37+
A | B => {} //~ ERROR unexpected token `||` after pattern
38+
//~^ ERROR a trailing `|` is not allowed in an or-pattern
39+
| A | B => {}
40+
//~^ ERROR a trailing `|` is not allowed in an or-pattern
41+
}
42+
43+
let a : u8 = 0; //~ ERROR a trailing `|` is not allowed in an or-pattern
44+
let a = 0; //~ ERROR a trailing `|` is not allowed in an or-pattern
45+
let a ; //~ ERROR a trailing `|` is not allowed in an or-pattern
46+
}

src/test/ui/or-patterns/remove-leading-vert.rs

+25-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Test the suggestion to remove a leading `|`.
1+
// Test the suggestion to remove a leading, or trailing `|`.
22

33
// run-rustfix
44

@@ -8,7 +8,7 @@
88
fn main() {}
99

1010
#[cfg(FALSE)]
11-
fn leading_vert() {
11+
fn leading() {
1212
fn fun1( | A: E) {} //~ ERROR a leading `|` is not allowed in a parameter pattern
1313
fn fun2( || A: E) {} //~ ERROR a leading `|` is not allowed in a parameter pattern
1414
let ( | A): E; //~ ERROR a leading `|` is only allowed in a top-level pattern
@@ -21,3 +21,26 @@ fn leading_vert() {
2121
let NS { f: | A }: NS; //~ ERROR a leading `|` is only allowed in a top-level pattern
2222
let NS { f: || A }: NS; //~ ERROR a leading `|` is only allowed in a top-level pattern
2323
}
24+
25+
#[cfg(FALSE)]
26+
fn trailing() {
27+
let ( A | ): E; //~ ERROR a trailing `|` is not allowed in an or-pattern
28+
let (a |,): (E,); //~ ERROR a trailing `|` is not allowed in an or-pattern
29+
let ( A | B | ): E; //~ ERROR a trailing `|` is not allowed in an or-pattern
30+
let [ A | B | ]: [E; 1]; //~ ERROR a trailing `|` is not allowed in an or-pattern
31+
let S { f: B | }; //~ ERROR a trailing `|` is not allowed in an or-pattern
32+
let ( A || B | ): E; //~ ERROR unexpected token `||` after pattern
33+
//~^ ERROR a trailing `|` is not allowed in an or-pattern
34+
match A {
35+
A | => {} //~ ERROR a trailing `|` is not allowed in an or-pattern
36+
A || => {} //~ ERROR a trailing `|` is not allowed in an or-pattern
37+
A || B | => {} //~ ERROR unexpected token `||` after pattern
38+
//~^ ERROR a trailing `|` is not allowed in an or-pattern
39+
| A | B | => {}
40+
//~^ ERROR a trailing `|` is not allowed in an or-pattern
41+
}
42+
43+
let a | : u8 = 0; //~ ERROR a trailing `|` is not allowed in an or-pattern
44+
let a | = 0; //~ ERROR a trailing `|` is not allowed in an or-pattern
45+
let a | ; //~ ERROR a trailing `|` is not allowed in an or-pattern
46+
}

0 commit comments

Comments
 (0)