Skip to content

Commit 0d97f9b

Browse files
authored
Rollup merge of #82048 - mark-i-m:or-pat-type-ascription, r=petrochenkov
or-patterns: disallow in `let` bindings ~~Blocked on #81869 Disallows top-level or-patterns before type ascription. We want to reserve this syntactic space for possible future generalized type ascription. r? ``@petrochenkov``
2 parents 0083e6c + 402a00a commit 0d97f9b

37 files changed

+591
-344
lines changed

compiler/rustc_lint/src/unused.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -872,7 +872,7 @@ impl EarlyLintPass for UnusedParens {
872872

873873
fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
874874
if let StmtKind::Local(ref local) = s.kind {
875-
self.check_unused_parens_pat(cx, &local.pat, false, false);
875+
self.check_unused_parens_pat(cx, &local.pat, true, false);
876876
}
877877

878878
<Self as UnusedDelimLint>::check_stmt(self, cx, s)

compiler/rustc_parse/src/parser/item.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1757,8 +1757,9 @@ impl<'a> Parser<'a> {
17571757
let (pat, ty) = if is_name_required || this.is_named_param() {
17581758
debug!("parse_param_general parse_pat (is_name_required:{})", is_name_required);
17591759

1760-
let pat = this.parse_fn_param_pat()?;
1761-
if let Err(mut err) = this.expect(&token::Colon) {
1760+
let (pat, colon) = this.parse_fn_param_pat_colon()?;
1761+
if !colon {
1762+
let mut err = this.unexpected::<()>().unwrap_err();
17621763
return if let Some(ident) =
17631764
this.parameter_without_type(&mut err, pat, is_name_required, first_param)
17641765
{

compiler/rustc_parse/src/parser/pat.rs

+122-62
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ pub enum RecoverComma {
3131
No,
3232
}
3333

34+
/// The result of `eat_or_separator`. We want to distinguish which case we are in to avoid
35+
/// emitting duplicate diagnostics.
36+
#[derive(Debug, Clone, Copy)]
37+
enum EatOrResult {
38+
/// We recovered from a trailing vert.
39+
TrailingVert,
40+
/// We ate an `|` (or `||` and recovered).
41+
AteOr,
42+
/// We did not eat anything (i.e. the current token is not `|` or `||`).
43+
None,
44+
}
45+
3446
impl<'a> Parser<'a> {
3547
/// Parses a pattern.
3648
///
@@ -55,9 +67,26 @@ impl<'a> Parser<'a> {
5567
gate_or: GateOr,
5668
rc: RecoverComma,
5769
) -> PResult<'a, P<Pat>> {
70+
self.parse_pat_allow_top_alt_inner(expected, gate_or, rc).map(|(pat, _)| pat)
71+
}
72+
73+
/// Returns the pattern and a bool indicating whether we recovered from a trailing vert (true =
74+
/// recovered).
75+
fn parse_pat_allow_top_alt_inner(
76+
&mut self,
77+
expected: Expected,
78+
gate_or: GateOr,
79+
rc: RecoverComma,
80+
) -> PResult<'a, (P<Pat>, bool)> {
81+
// Keep track of whether we recovered from a trailing vert so that we can avoid duplicated
82+
// suggestions (which bothers rustfix).
83+
//
5884
// Allow a '|' before the pats (RFCs 1925, 2530, and 2535).
59-
let leading_vert_span =
60-
if self.eat_or_separator(None) { Some(self.prev_token.span) } else { None };
85+
let (leading_vert_span, mut trailing_vert) = match self.eat_or_separator(None) {
86+
EatOrResult::AteOr => (Some(self.prev_token.span), false),
87+
EatOrResult::TrailingVert => (None, true),
88+
EatOrResult::None => (None, false),
89+
};
6190

6291
// Parse the first pattern (`p_0`).
6392
let first_pat = self.parse_pat_no_top_alt(expected)?;
@@ -77,16 +106,24 @@ impl<'a> Parser<'a> {
77106
// If there was a leading vert, treat this as an or-pattern. This improves
78107
// diagnostics.
79108
let span = leading_vert_span.to(self.prev_token.span);
80-
return Ok(self.mk_pat(span, PatKind::Or(vec![first_pat])));
109+
return Ok((self.mk_pat(span, PatKind::Or(vec![first_pat])), trailing_vert));
81110
}
82111

83-
return Ok(first_pat);
112+
return Ok((first_pat, trailing_vert));
84113
}
85114

86115
// Parse the patterns `p_1 | ... | p_n` where `n > 0`.
87116
let lo = leading_vert_span.unwrap_or(first_pat.span);
88117
let mut pats = vec![first_pat];
89-
while self.eat_or_separator(Some(lo)) {
118+
loop {
119+
match self.eat_or_separator(Some(lo)) {
120+
EatOrResult::AteOr => {}
121+
EatOrResult::None => break,
122+
EatOrResult::TrailingVert => {
123+
trailing_vert = true;
124+
break;
125+
}
126+
}
90127
let pat = self.parse_pat_no_top_alt(expected).map_err(|mut err| {
91128
err.span_label(lo, WHILE_PARSING_OR_MSG);
92129
err
@@ -101,15 +138,63 @@ impl<'a> Parser<'a> {
101138
self.sess.gated_spans.gate(sym::or_patterns, or_pattern_span);
102139
}
103140

104-
Ok(self.mk_pat(or_pattern_span, PatKind::Or(pats)))
141+
Ok((self.mk_pat(or_pattern_span, PatKind::Or(pats)), trailing_vert))
105142
}
106143

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-
//
144+
/// Parse a pattern and (maybe) a `Colon` in positions where a pattern may be followed by a
145+
/// type annotation (e.g. for `let` bindings or `fn` params).
146+
///
147+
/// Generally, this corresponds to `pat_no_top_alt` followed by an optional `Colon`. It will
148+
/// eat the `Colon` token if one is present.
149+
///
150+
/// The return value represents the parsed pattern and `true` if a `Colon` was parsed (`false`
151+
/// otherwise).
152+
pub(super) fn parse_pat_before_ty(
153+
&mut self,
154+
expected: Expected,
155+
gate_or: GateOr,
156+
rc: RecoverComma,
157+
syntax_loc: &str,
158+
) -> PResult<'a, (P<Pat>, bool)> {
159+
// We use `parse_pat_allow_top_alt` regardless of whether we actually want top-level
160+
// or-patterns so that we can detect when a user tries to use it. This allows us to print a
161+
// better error message.
162+
let (pat, trailing_vert) = self.parse_pat_allow_top_alt_inner(expected, gate_or, rc)?;
163+
let colon = self.eat(&token::Colon);
164+
165+
if let PatKind::Or(pats) = &pat.kind {
166+
let msg = format!("top-level or-patterns are not allowed in {}", syntax_loc);
167+
let (help, fix) = if pats.len() == 1 {
168+
// If all we have is a leading vert, then print a special message. This is the case
169+
// if `parse_pat_allow_top_alt` returns an or-pattern with one variant.
170+
let msg = "remove the `|`";
171+
let fix = pprust::pat_to_string(&pat);
172+
(msg, fix)
173+
} else {
174+
let msg = "wrap the pattern in parentheses";
175+
let fix = format!("({})", pprust::pat_to_string(&pat));
176+
(msg, fix)
177+
};
178+
179+
if trailing_vert {
180+
// We already emitted an error and suggestion to remove the trailing vert. Don't
181+
// emit again.
182+
self.sess.span_diagnostic.delay_span_bug(pat.span, &msg);
183+
} else {
184+
self.struct_span_err(pat.span, &msg)
185+
.span_suggestion(pat.span, help, fix, Applicability::MachineApplicable)
186+
.emit();
187+
}
188+
}
189+
190+
Ok((pat, colon))
191+
}
192+
193+
/// Parse the pattern for a function or function pointer parameter, followed by a colon.
194+
///
195+
/// The return value represents the parsed pattern and `true` if a `Colon` was parsed (`false`
196+
/// otherwise).
197+
pub(super) fn parse_fn_param_pat_colon(&mut self) -> PResult<'a, (P<Pat>, bool)> {
113198
// In order to get good UX, we first recover in the case of a leading vert for an illegal
114199
// top-level or-pat. Normally, this means recovering both `|` and `||`, but in this case,
115200
// a leading `||` probably doesn't indicate an or-pattern attempt, so we handle that
@@ -128,53 +213,28 @@ impl<'a> Parser<'a> {
128213
self.bump();
129214
}
130215

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();
216+
self.parse_pat_before_ty(
217+
PARAM_EXPECTED,
218+
GateOr::No,
219+
RecoverComma::No,
220+
"function parameters",
221+
)
161222
}
162223

163224
/// Eat the or-pattern `|` separator.
164225
/// If instead a `||` token is encountered, recover and pretend we parsed `|`.
165-
fn eat_or_separator(&mut self, lo: Option<Span>) -> bool {
226+
fn eat_or_separator(&mut self, lo: Option<Span>) -> EatOrResult {
166227
if self.recover_trailing_vert(lo) {
167-
return false;
168-
}
169-
170-
match self.token.kind {
171-
token::OrOr => {
172-
// Found `||`; Recover and pretend we parsed `|`.
173-
self.ban_unexpected_or_or(lo);
174-
self.bump();
175-
true
176-
}
177-
_ => self.eat(&token::BinOp(token::Or)),
228+
EatOrResult::TrailingVert
229+
} else if matches!(self.token.kind, token::OrOr) {
230+
// Found `||`; Recover and pretend we parsed `|`.
231+
self.ban_unexpected_or_or(lo);
232+
self.bump();
233+
EatOrResult::AteOr
234+
} else if self.eat(&token::BinOp(token::Or)) {
235+
EatOrResult::AteOr
236+
} else {
237+
EatOrResult::None
178238
}
179239
}
180240

@@ -190,14 +250,14 @@ impl<'a> Parser<'a> {
190250
matches!(
191251
&token.uninterpolate().kind,
192252
token::FatArrow // e.g. `a | => 0,`.
193-
| token::Ident(kw::If, false) // e.g. `a | if expr`.
194-
| token::Eq // e.g. `let a | = 0`.
195-
| token::Semi // e.g. `let a |;`.
196-
| token::Colon // e.g. `let a | :`.
197-
| token::Comma // e.g. `let (a |,)`.
198-
| token::CloseDelim(token::Bracket) // e.g. `let [a | ]`.
199-
| token::CloseDelim(token::Paren) // e.g. `let (a | )`.
200-
| token::CloseDelim(token::Brace) // e.g. `let A { f: a | }`.
253+
| token::Ident(kw::If, false) // e.g. `a | if expr`.
254+
| token::Eq // e.g. `let a | = 0`.
255+
| token::Semi // e.g. `let a |;`.
256+
| token::Colon // e.g. `let a | :`.
257+
| token::Comma // e.g. `let (a |,)`.
258+
| token::CloseDelim(token::Bracket) // e.g. `let [a | ]`.
259+
| token::CloseDelim(token::Paren) // e.g. `let (a | )`.
260+
| token::CloseDelim(token::Brace) // e.g. `let A { f: a | }`.
201261
)
202262
});
203263
match (is_end_ahead, &self.token.kind) {

compiler/rustc_parse/src/parser/stmt.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ use rustc_ast::token::{self, TokenKind};
1313
use rustc_ast::util::classify;
1414
use rustc_ast::AstLike;
1515
use rustc_ast::{AttrStyle, AttrVec, Attribute, MacCall, MacCallStmt, MacStmtStyle};
16-
use rustc_ast::{Block, BlockCheckMode, Expr, ExprKind, Local, Stmt, StmtKind, DUMMY_NODE_ID};
16+
use rustc_ast::{Block, BlockCheckMode, Expr, ExprKind, Local, Stmt};
17+
use rustc_ast::{StmtKind, DUMMY_NODE_ID};
1718
use rustc_errors::{Applicability, PResult};
1819
use rustc_span::source_map::{BytePos, Span};
1920
use rustc_span::symbol::{kw, sym};
@@ -220,9 +221,10 @@ impl<'a> Parser<'a> {
220221
/// Parses a local variable declaration.
221222
fn parse_local(&mut self, attrs: AttrVec) -> PResult<'a, P<Local>> {
222223
let lo = self.prev_token.span;
223-
let pat = self.parse_pat_allow_top_alt(None, GateOr::Yes, RecoverComma::Yes)?;
224+
let (pat, colon) =
225+
self.parse_pat_before_ty(None, GateOr::Yes, RecoverComma::Yes, "`let` bindings")?;
224226

225-
let (err, ty) = if self.eat(&token::Colon) {
227+
let (err, ty) = if colon {
226228
// Save the state of the parser before parsing type normally, in case there is a `:`
227229
// instead of an `=` typo.
228230
let parser_snapshot_before_type = self.clone();

src/test/ui/or-patterns/already-bound-name.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,28 @@ fn main() {
1818
let (A(a, _) | B(a), a) = (A(0, 1), 2);
1919
//~^ ERROR identifier `a` is bound more than once in the same pattern
2020

21-
let A(a, a) | B(a) = A(0, 1);
21+
let (A(a, a) | B(a)) = A(0, 1);
2222
//~^ ERROR identifier `a` is bound more than once in the same pattern
2323

24-
let B(a) | A(a, a) = A(0, 1);
24+
let (B(a) | A(a, a)) = A(0, 1);
2525
//~^ ERROR identifier `a` is bound more than once in the same pattern
2626

2727
match A(0, 1) {
2828
B(a) | A(a, a) => {} // Let's ensure `match` has no funny business.
2929
//~^ ERROR identifier `a` is bound more than once in the same pattern
3030
}
3131

32-
let B(A(a, _) | B(a)) | A(a, A(a, _) | B(a)) = B(B(1));
32+
let (B(A(a, _) | B(a)) | A(a, A(a, _) | B(a))) = B(B(1));
3333
//~^ ERROR identifier `a` is bound more than once in the same pattern
3434
//~| ERROR identifier `a` is bound more than once in the same pattern
3535
//~| ERROR mismatched types
3636

37-
let B(_) | A(A(a, _) | B(a), A(a, _) | B(a)) = B(B(1));
37+
let (B(_) | A(A(a, _) | B(a), A(a, _) | B(a))) = B(B(1));
3838
//~^ ERROR identifier `a` is bound more than once in the same pattern
3939
//~| ERROR identifier `a` is bound more than once in the same pattern
4040
//~| ERROR variable `a` is not bound in all patterns
4141

42-
let B(A(a, _) | B(a)) | A(A(a, _) | B(a), A(a, _) | B(a)) = B(B(1));
42+
let (B(A(a, _) | B(a)) | A(A(a, _) | B(a), A(a, _) | B(a))) = B(B(1));
4343
//~^ ERROR identifier `a` is bound more than once in the same pattern
4444
//~| ERROR identifier `a` is bound more than once in the same pattern
4545
}

0 commit comments

Comments
 (0)