Skip to content

Commit 49479b4

Browse files
committed
Tweak diagnostics
* Recover from invalid `'label: ` before block. * Make suggestion to enclose statements in a block multipart. * Point at `match`, `while`, `loop` and `unsafe` keywords when failing to parse their expression. * Do not suggest `{ ; }`. * Do not suggest `|` when very unlikely to be what was wanted (in `let` statements).
1 parent 89b9f7b commit 49479b4

27 files changed

+319
-164
lines changed

compiler/rustc_expand/src/expand.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use rustc_data_structures::sync::Lrc;
2121
use rustc_errors::{Applicability, PResult};
2222
use rustc_feature::Features;
2323
use rustc_parse::parser::{
24-
AttemptLocalParseRecovery, ForceCollect, Parser, RecoverColon, RecoverComma,
24+
AttemptLocalParseRecovery, CommaRecoveryMode, ForceCollect, Parser, RecoverColon, RecoverComma,
2525
};
2626
use rustc_parse::validate_attr;
2727
use rustc_session::lint::builtin::{UNUSED_ATTRIBUTES, UNUSED_DOC_COMMENTS};
@@ -914,6 +914,7 @@ pub fn parse_ast_fragment<'a>(
914914
None,
915915
RecoverComma::No,
916916
RecoverColon::Yes,
917+
CommaRecoveryMode::LikelyTuple,
917918
)?),
918919
AstFragmentKind::Crate => AstFragment::Crate(this.parse_crate_mod()?),
919920
AstFragmentKind::Arms

compiler/rustc_parse/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#![feature(array_windows)]
44
#![feature(crate_visibility_modifier)]
55
#![feature(if_let_guard)]
6+
#![feature(let_else)]
67
#![feature(box_patterns)]
78
#![recursion_limit = "256"]
89

compiler/rustc_parse/src/parser/diagnostics.rs

+39-14
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use super::pat::Expected;
22
use super::ty::AllowPlus;
33
use super::{
4-
BlockMode, Parser, PathStyle, RecoverColon, RecoverComma, Restrictions, SemiColonMode, SeqSep,
5-
TokenExpectType, TokenType,
4+
BlockMode, CommaRecoveryMode, Parser, PathStyle, RecoverColon, RecoverComma, Restrictions,
5+
SemiColonMode, SeqSep, TokenExpectType, TokenType,
66
};
77

88
use rustc_ast as ast;
@@ -2200,12 +2200,32 @@ impl<'a> Parser<'a> {
22002200
first_pat
22012201
}
22022202

2203+
crate fn maybe_recover_unexpected_block_label(&mut self) -> bool {
2204+
let Some(label) = self.eat_label().filter(|_| {
2205+
self.eat(&token::Colon) && self.token.kind == token::OpenDelim(token::Brace)
2206+
}) else {
2207+
return false;
2208+
};
2209+
let span = label.ident.span.to(self.prev_token.span);
2210+
let mut err = self.struct_span_err(span, "block label not supported here");
2211+
err.span_label(span, "not supported here");
2212+
err.tool_only_span_suggestion(
2213+
label.ident.span.until(self.token.span),
2214+
"remove this block label",
2215+
String::new(),
2216+
Applicability::MachineApplicable,
2217+
);
2218+
err.emit();
2219+
true
2220+
}
2221+
22032222
/// Some special error handling for the "top-level" patterns in a match arm,
22042223
/// `for` loop, `let`, &c. (in contrast to subpatterns within such).
22052224
crate fn maybe_recover_unexpected_comma(
22062225
&mut self,
22072226
lo: Span,
22082227
rc: RecoverComma,
2228+
rt: CommaRecoveryMode,
22092229
) -> PResult<'a, ()> {
22102230
if rc == RecoverComma::No || self.token != token::Comma {
22112231
return Ok(());
@@ -2225,20 +2245,25 @@ impl<'a> Parser<'a> {
22252245
let seq_span = lo.to(self.prev_token.span);
22262246
let mut err = self.struct_span_err(comma_span, "unexpected `,` in pattern");
22272247
if let Ok(seq_snippet) = self.span_to_snippet(seq_span) {
2228-
const MSG: &str = "try adding parentheses to match on a tuple...";
2229-
2230-
err.span_suggestion(
2231-
seq_span,
2232-
MSG,
2233-
format!("({})", seq_snippet),
2234-
Applicability::MachineApplicable,
2235-
);
2236-
err.span_suggestion(
2237-
seq_span,
2238-
"...or a vertical bar to match on multiple alternatives",
2239-
seq_snippet.replace(',', " |"),
2248+
err.multipart_suggestion(
2249+
&format!(
2250+
"try adding parentheses to match on a tuple{}",
2251+
if let CommaRecoveryMode::LikelyTuple = rt { "" } else { "..." },
2252+
),
2253+
vec![
2254+
(seq_span.shrink_to_lo(), "(".to_string()),
2255+
(seq_span.shrink_to_hi(), ")".to_string()),
2256+
],
22402257
Applicability::MachineApplicable,
22412258
);
2259+
if let CommaRecoveryMode::EitherTupleOrPipe = rt {
2260+
err.span_suggestion(
2261+
seq_span,
2262+
"...or a vertical bar to match on multiple alternatives",
2263+
seq_snippet.replace(',', " |"),
2264+
Applicability::MachineApplicable,
2265+
);
2266+
}
22422267
}
22432268
Err(err)
22442269
}

compiler/rustc_parse/src/parser/expr.rs

+46-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::pat::{RecoverColon, RecoverComma, PARAM_EXPECTED};
1+
use super::pat::{CommaRecoveryMode, RecoverColon, RecoverComma, PARAM_EXPECTED};
22
use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
33
use super::{
44
AttrWrapper, BlockMode, ClosureSpans, ForceCollect, Parser, PathStyle, Restrictions, TokenType,
@@ -1269,18 +1269,27 @@ impl<'a> Parser<'a> {
12691269
} else if let Some(label) = self.eat_label() {
12701270
self.parse_labeled_expr(label, attrs, true)
12711271
} else if self.eat_keyword(kw::Loop) {
1272-
self.parse_loop_expr(None, self.prev_token.span, attrs)
1272+
let sp = self.prev_token.span;
1273+
self.parse_loop_expr(None, self.prev_token.span, attrs).map_err(|mut err| {
1274+
err.span_label(sp, "while parsing this `loop` expression");
1275+
err
1276+
})
12731277
} else if self.eat_keyword(kw::Continue) {
12741278
let kind = ExprKind::Continue(self.eat_label());
12751279
Ok(self.mk_expr(lo.to(self.prev_token.span), kind, attrs))
12761280
} else if self.eat_keyword(kw::Match) {
12771281
let match_sp = self.prev_token.span;
12781282
self.parse_match_expr(attrs).map_err(|mut err| {
1279-
err.span_label(match_sp, "while parsing this match expression");
1283+
err.span_label(match_sp, "while parsing this `match` expression");
12801284
err
12811285
})
12821286
} else if self.eat_keyword(kw::Unsafe) {
1287+
let sp = self.prev_token.span;
12831288
self.parse_block_expr(None, lo, BlockCheckMode::Unsafe(ast::UserProvided), attrs)
1289+
.map_err(|mut err| {
1290+
err.span_label(sp, "while parsing this `unsafe` expression");
1291+
err
1292+
})
12841293
} else if self.check_inline_const(0) {
12851294
self.parse_const_block(lo.to(self.token.span), false)
12861295
} else if self.is_do_catch_block() {
@@ -2110,7 +2119,12 @@ impl<'a> Parser<'a> {
21102119
/// The `let` token has already been eaten.
21112120
fn parse_let_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
21122121
let lo = self.prev_token.span;
2113-
let pat = self.parse_pat_allow_top_alt(None, RecoverComma::Yes, RecoverColon::Yes)?;
2122+
let pat = self.parse_pat_allow_top_alt(
2123+
None,
2124+
RecoverComma::Yes,
2125+
RecoverColon::Yes,
2126+
CommaRecoveryMode::LikelyTuple,
2127+
)?;
21142128
self.expect(&token::Eq)?;
21152129
let expr = self.with_res(self.restrictions | Restrictions::NO_STRUCT_LITERAL, |this| {
21162130
this.parse_assoc_expr_with(1 + prec_let_scrutinee_needs_par(), None.into())
@@ -2173,7 +2187,12 @@ impl<'a> Parser<'a> {
21732187
_ => None,
21742188
};
21752189

2176-
let pat = self.parse_pat_allow_top_alt(None, RecoverComma::Yes, RecoverColon::Yes)?;
2190+
let pat = self.parse_pat_allow_top_alt(
2191+
None,
2192+
RecoverComma::Yes,
2193+
RecoverColon::Yes,
2194+
CommaRecoveryMode::LikelyTuple,
2195+
)?;
21772196
if !self.eat_keyword(kw::In) {
21782197
self.error_missing_in_for_loop();
21792198
}
@@ -2216,8 +2235,15 @@ impl<'a> Parser<'a> {
22162235
lo: Span,
22172236
mut attrs: AttrVec,
22182237
) -> PResult<'a, P<Expr>> {
2219-
let cond = self.parse_cond_expr()?;
2220-
let (iattrs, body) = self.parse_inner_attrs_and_block()?;
2238+
let cond = self.parse_cond_expr().map_err(|mut err| {
2239+
err.span_label(lo, "while parsing the condition of this `while` expression");
2240+
err
2241+
})?;
2242+
let (iattrs, body) = self.parse_inner_attrs_and_block().map_err(|mut err| {
2243+
err.span_label(lo, "while parsing the body of this `while` expression");
2244+
err.span_label(cond.span, "this `while` condition successfully parsed");
2245+
err
2246+
})?;
22212247
attrs.extend(iattrs);
22222248
Ok(self.mk_expr(lo.to(self.prev_token.span), ExprKind::While(cond, body, opt_label), attrs))
22232249
}
@@ -2234,7 +2260,7 @@ impl<'a> Parser<'a> {
22342260
Ok(self.mk_expr(lo.to(self.prev_token.span), ExprKind::Loop(body, opt_label), attrs))
22352261
}
22362262

2237-
fn eat_label(&mut self) -> Option<Label> {
2263+
crate fn eat_label(&mut self) -> Option<Label> {
22382264
self.token.lifetime().map(|ident| {
22392265
self.bump();
22402266
Label { ident }
@@ -2255,7 +2281,12 @@ impl<'a> Parser<'a> {
22552281
Applicability::MaybeIncorrect, // speculative
22562282
);
22572283
}
2258-
return Err(e);
2284+
if self.maybe_recover_unexpected_block_label() {
2285+
e.cancel();
2286+
self.bump();
2287+
} else {
2288+
return Err(e);
2289+
}
22592290
}
22602291
attrs.extend(self.parse_inner_attributes()?);
22612292

@@ -2380,7 +2411,12 @@ impl<'a> Parser<'a> {
23802411
let attrs = self.parse_outer_attributes()?;
23812412
self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| {
23822413
let lo = this.token.span;
2383-
let pat = this.parse_pat_allow_top_alt(None, RecoverComma::Yes, RecoverColon::Yes)?;
2414+
let pat = this.parse_pat_allow_top_alt(
2415+
None,
2416+
RecoverComma::Yes,
2417+
RecoverColon::Yes,
2418+
CommaRecoveryMode::EitherTupleOrPipe,
2419+
)?;
23842420
let guard = if this.eat_keyword(kw::If) {
23852421
let if_span = this.prev_token.span;
23862422
let cond = this.parse_expr()?;

compiler/rustc_parse/src/parser/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub use attr_wrapper::AttrWrapper;
1515
pub use diagnostics::AttemptLocalParseRecovery;
1616
use diagnostics::Error;
1717
pub(crate) use item::FnParseMode;
18-
pub use pat::{RecoverColon, RecoverComma};
18+
pub use pat::{CommaRecoveryMode, RecoverColon, RecoverComma};
1919
pub use path::PathStyle;
2020

2121
use rustc_ast::ptr::P;

compiler/rustc_parse/src/parser/nonterminal.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use rustc_ast_pretty::pprust;
55
use rustc_errors::PResult;
66
use rustc_span::symbol::{kw, Ident};
77

8-
use crate::parser::pat::{RecoverColon, RecoverComma};
8+
use crate::parser::pat::{CommaRecoveryMode, RecoverColon, RecoverComma};
99
use crate::parser::{FollowedByType, ForceCollect, Parser, PathStyle};
1010

1111
impl<'a> Parser<'a> {
@@ -125,7 +125,7 @@ impl<'a> Parser<'a> {
125125
token::NtPat(self.collect_tokens_no_attrs(|this| match kind {
126126
NonterminalKind::PatParam { .. } => this.parse_pat_no_top_alt(None),
127127
NonterminalKind::PatWithOr { .. } => {
128-
this.parse_pat_allow_top_alt(None, RecoverComma::No, RecoverColon::No)
128+
this.parse_pat_allow_top_alt(None, RecoverComma::No, RecoverColon::No, CommaRecoveryMode::EitherTupleOrPipe)
129129
}
130130
_ => unreachable!(),
131131
})?)

compiler/rustc_parse/src/parser/pat.rs

+42-9
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ pub enum RecoverColon {
3333
No,
3434
}
3535

36+
/// Whether or not to recover a `a, b` when parsing patterns as `(a, b)` or that *and* `a | b`.
37+
#[derive(PartialEq, Copy, Clone)]
38+
pub enum CommaRecoveryMode {
39+
LikelyTuple,
40+
EitherTupleOrPipe,
41+
}
42+
3643
/// The result of `eat_or_separator`. We want to distinguish which case we are in to avoid
3744
/// emitting duplicate diagnostics.
3845
#[derive(Debug, Clone, Copy)]
@@ -68,8 +75,9 @@ impl<'a> Parser<'a> {
6875
expected: Expected,
6976
rc: RecoverComma,
7077
ra: RecoverColon,
78+
rt: CommaRecoveryMode,
7179
) -> PResult<'a, P<Pat>> {
72-
self.parse_pat_allow_top_alt_inner(expected, rc, ra).map(|(pat, _)| pat)
80+
self.parse_pat_allow_top_alt_inner(expected, rc, ra, rt).map(|(pat, _)| pat)
7381
}
7482

7583
/// Returns the pattern and a bool indicating whether we recovered from a trailing vert (true =
@@ -79,6 +87,7 @@ impl<'a> Parser<'a> {
7987
expected: Expected,
8088
rc: RecoverComma,
8189
ra: RecoverColon,
90+
rt: CommaRecoveryMode,
8291
) -> PResult<'a, (P<Pat>, bool)> {
8392
// Keep track of whether we recovered from a trailing vert so that we can avoid duplicated
8493
// suggestions (which bothers rustfix).
@@ -92,7 +101,7 @@ impl<'a> Parser<'a> {
92101

93102
// Parse the first pattern (`p_0`).
94103
let first_pat = self.parse_pat_no_top_alt(expected)?;
95-
self.maybe_recover_unexpected_comma(first_pat.span, rc)?;
104+
self.maybe_recover_unexpected_comma(first_pat.span, rc, rt)?;
96105

97106
// If the next token is not a `|`,
98107
// this is not an or-pattern and we should exit here.
@@ -130,7 +139,7 @@ impl<'a> Parser<'a> {
130139
err.span_label(lo, WHILE_PARSING_OR_MSG);
131140
err
132141
})?;
133-
self.maybe_recover_unexpected_comma(pat.span, rc)?;
142+
self.maybe_recover_unexpected_comma(pat.span, rc, rt)?;
134143
pats.push(pat);
135144
}
136145
let or_pattern_span = lo.to(self.prev_token.span);
@@ -155,8 +164,12 @@ impl<'a> Parser<'a> {
155164
// We use `parse_pat_allow_top_alt` regardless of whether we actually want top-level
156165
// or-patterns so that we can detect when a user tries to use it. This allows us to print a
157166
// better error message.
158-
let (pat, trailing_vert) =
159-
self.parse_pat_allow_top_alt_inner(expected, rc, RecoverColon::No)?;
167+
let (pat, trailing_vert) = self.parse_pat_allow_top_alt_inner(
168+
expected,
169+
rc,
170+
RecoverColon::No,
171+
CommaRecoveryMode::LikelyTuple,
172+
)?;
160173
let colon = self.eat(&token::Colon);
161174

162175
if let PatKind::Or(pats) = &pat.kind {
@@ -315,7 +328,12 @@ impl<'a> Parser<'a> {
315328
} else if self.check(&token::OpenDelim(token::Bracket)) {
316329
// Parse `[pat, pat,...]` as a slice pattern.
317330
let (pats, _) = self.parse_delim_comma_seq(token::Bracket, |p| {
318-
p.parse_pat_allow_top_alt(None, RecoverComma::No, RecoverColon::No)
331+
p.parse_pat_allow_top_alt(
332+
None,
333+
RecoverComma::No,
334+
RecoverColon::No,
335+
CommaRecoveryMode::EitherTupleOrPipe,
336+
)
319337
})?;
320338
PatKind::Slice(pats)
321339
} else if self.check(&token::DotDot) && !self.is_pat_range_end_start(1) {
@@ -529,7 +547,12 @@ impl<'a> Parser<'a> {
529547
/// Parse a tuple or parenthesis pattern.
530548
fn parse_pat_tuple_or_parens(&mut self) -> PResult<'a, PatKind> {
531549
let (fields, trailing_comma) = self.parse_paren_comma_seq(|p| {
532-
p.parse_pat_allow_top_alt(None, RecoverComma::No, RecoverColon::No)
550+
p.parse_pat_allow_top_alt(
551+
None,
552+
RecoverComma::No,
553+
RecoverColon::No,
554+
CommaRecoveryMode::LikelyTuple,
555+
)
533556
})?;
534557

535558
// Here, `(pat,)` is a tuple pattern.
@@ -873,7 +896,12 @@ impl<'a> Parser<'a> {
873896
/// Parse tuple struct or tuple variant pattern (e.g. `Foo(...)` or `Foo::Bar(...)`).
874897
fn parse_pat_tuple_struct(&mut self, qself: Option<QSelf>, path: Path) -> PResult<'a, PatKind> {
875898
let (fields, _) = self.parse_paren_comma_seq(|p| {
876-
p.parse_pat_allow_top_alt(None, RecoverComma::No, RecoverColon::No)
899+
p.parse_pat_allow_top_alt(
900+
None,
901+
RecoverComma::No,
902+
RecoverColon::No,
903+
CommaRecoveryMode::EitherTupleOrPipe,
904+
)
877905
})?;
878906
if qself.is_some() {
879907
self.sess.gated_spans.gate(sym::more_qualified_paths, path.span);
@@ -1033,7 +1061,12 @@ impl<'a> Parser<'a> {
10331061
// Parsing a pattern of the form `fieldname: pat`.
10341062
let fieldname = self.parse_field_name()?;
10351063
self.bump();
1036-
let pat = self.parse_pat_allow_top_alt(None, RecoverComma::No, RecoverColon::No)?;
1064+
let pat = self.parse_pat_allow_top_alt(
1065+
None,
1066+
RecoverComma::No,
1067+
RecoverColon::No,
1068+
CommaRecoveryMode::EitherTupleOrPipe,
1069+
)?;
10371070
hi = pat.span;
10381071
(pat, fieldname, false)
10391072
} else {

0 commit comments

Comments
 (0)