Skip to content

Commit 05b5046

Browse files
committed
feat: implement error recovery in expected_ident_found
1 parent b4e17a5 commit 05b5046

File tree

8 files changed

+175
-69
lines changed

8 files changed

+175
-69
lines changed

compiler/rustc_parse/src/parser/diagnostics.rs

+58-26
Original file line numberDiff line numberDiff line change
@@ -269,13 +269,18 @@ impl<'a> Parser<'a> {
269269
}
270270

271271
/// Emits an error with suggestions if an identifier was expected but not found.
272-
pub(super) fn expected_ident_found(&mut self) -> DiagnosticBuilder<'a, ErrorGuaranteed> {
272+
///
273+
/// Returns a possibly recovered identifier.
274+
pub(super) fn expected_ident_found(
275+
&mut self,
276+
recover: bool,
277+
) -> PResult<'a, (Ident, /* is_raw */ bool)> {
273278
if let TokenKind::DocComment(..) = self.prev_token.kind {
274-
return DocCommentDoesNotDocumentAnything {
279+
return Err(DocCommentDoesNotDocumentAnything {
275280
span: self.prev_token.span,
276281
missing_comma: None,
277282
}
278-
.into_diagnostic(&self.sess.span_diagnostic);
283+
.into_diagnostic(&self.sess.span_diagnostic));
279284
}
280285

281286
let valid_follow = &[
@@ -290,34 +295,51 @@ impl<'a> Parser<'a> {
290295
TokenKind::CloseDelim(Delimiter::Parenthesis),
291296
];
292297

293-
let suggest_raw = match self.token.ident() {
294-
Some((ident, false))
295-
if ident.is_raw_guess()
296-
&& self.look_ahead(1, |t| valid_follow.contains(&t.kind)) =>
297-
{
298-
Some(SuggEscapeIdentifier {
299-
span: ident.span.shrink_to_lo(),
300-
// `Symbol::to_string()` is different from `Symbol::into_diagnostic_arg()`,
301-
// which uses `Symbol::to_ident_string()` and "helpfully" adds an implicit `r#`
302-
ident_name: ident.name.to_string(),
303-
})
304-
}
305-
_ => None,
306-
};
298+
let mut recovered_ident = None;
299+
// we take this here so that the correct original token is retained in
300+
// the diagnostic, regardless of eager recovery.
301+
let bad_token = self.token.clone();
302+
303+
// suggest prepending a keyword in identifier position with `r#`
304+
let suggest_raw = if let Some((ident, false)) = self.token.ident()
305+
&& ident.is_raw_guess()
306+
&& self.look_ahead(1, |t| valid_follow.contains(&t.kind))
307+
{
308+
recovered_ident = Some((ident, true));
309+
310+
// `Symbol::to_string()` is different from `Symbol::into_diagnostic_arg()`,
311+
// which uses `Symbol::to_ident_string()` and "helpfully" adds an implicit `r#`
312+
let ident_name = ident.name.to_string();
313+
314+
Some(SuggEscapeIdentifier {
315+
span: ident.span.shrink_to_lo(),
316+
ident_name
317+
})
318+
} else { None };
319+
320+
let suggest_remove_comma =
321+
if self.token == token::Comma && self.look_ahead(1, |t| t.is_ident()) {
322+
if recover {
323+
self.bump();
324+
recovered_ident = self.ident_or_err(false).ok();
325+
};
326+
327+
Some(SuggRemoveComma { span: bad_token.span })
328+
} else {
329+
None
330+
};
307331

308-
let suggest_remove_comma = (self.token == token::Comma
309-
&& self.look_ahead(1, |t| t.is_ident()))
310-
.then_some(SuggRemoveComma { span: self.token.span });
332+
let help_cannot_start_number = self.is_lit_bad_ident().map(|(len, valid_portion)| {
333+
let (invalid, valid) = self.token.span.split_at(len as u32);
311334

312-
let help_cannot_start_number = self.is_lit_bad_ident().map(|(len, _valid_portion)| {
313-
let (invalid, _valid) = self.token.span.split_at(len as u32);
335+
recovered_ident = Some((Ident::new(valid_portion, valid), false));
314336

315337
HelpIdentifierStartsWithNumber { num_span: invalid }
316338
});
317339

318340
let err = ExpectedIdentifier {
319-
span: self.token.span,
320-
token: self.token.clone(),
341+
span: bad_token.span,
342+
token: bad_token,
321343
suggest_raw,
322344
suggest_remove_comma,
323345
help_cannot_start_number,
@@ -326,6 +348,7 @@ impl<'a> Parser<'a> {
326348

327349
// if the token we have is a `<`
328350
// it *might* be a misplaced generic
351+
// FIXME: could we recover with this?
329352
if self.token == token::Lt {
330353
// all keywords that could have generic applied
331354
let valid_prev_keywords =
@@ -376,7 +399,16 @@ impl<'a> Parser<'a> {
376399
}
377400
}
378401

379-
err
402+
if let Some(recovered_ident) = recovered_ident && recover {
403+
err.emit();
404+
Ok(recovered_ident)
405+
} else {
406+
Err(err)
407+
}
408+
}
409+
410+
pub(super) fn expected_ident_found_err(&mut self) -> DiagnosticBuilder<'a, ErrorGuaranteed> {
411+
self.expected_ident_found(false).unwrap_err()
380412
}
381413

382414
/// Checks if the current token is a integer or float literal and looks like
@@ -392,7 +424,7 @@ impl<'a> Parser<'a> {
392424
kind: token::LitKind::Integer | token::LitKind::Float,
393425
symbol,
394426
suffix,
395-
}) = self.token.uninterpolate().kind
427+
}) = self.token.kind
396428
&& rustc_ast::MetaItemLit::from_token(&self.token).is_none()
397429
{
398430
Some((symbol.as_str().len(), suffix.unwrap()))

compiler/rustc_parse/src/parser/item.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1181,7 +1181,7 @@ impl<'a> Parser<'a> {
11811181
defaultness: Defaultness,
11821182
) -> PResult<'a, ItemInfo> {
11831183
let impl_span = self.token.span;
1184-
let mut err = self.expected_ident_found();
1184+
let mut err = self.expected_ident_found_err();
11851185

11861186
// Only try to recover if this is implementing a trait for a type
11871187
let mut impl_info = match self.parse_item_impl(attrs, defaultness) {
@@ -1776,7 +1776,7 @@ impl<'a> Parser<'a> {
17761776
Err(err) => {
17771777
err.cancel();
17781778
self.restore_snapshot(snapshot);
1779-
self.expected_ident_found()
1779+
self.expected_ident_found_err()
17801780
}
17811781
}
17821782
} else if self.eat_keyword(kw::Struct) {
@@ -1792,11 +1792,11 @@ impl<'a> Parser<'a> {
17921792
Err(err) => {
17931793
err.cancel();
17941794
self.restore_snapshot(snapshot);
1795-
self.expected_ident_found()
1795+
self.expected_ident_found_err()
17961796
}
17971797
}
17981798
} else {
1799-
let mut err = self.expected_ident_found();
1799+
let mut err = self.expected_ident_found_err();
18001800
if self.eat_keyword_noexpect(kw::Let)
18011801
&& let removal_span = self.prev_token.span.until(self.token.span)
18021802
&& let Ok(ident) = self.parse_ident_common(false)

compiler/rustc_parse/src/parser/mod.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -553,8 +553,9 @@ impl<'a> Parser<'a> {
553553

554554
fn parse_ident_common(&mut self, recover: bool) -> PResult<'a, Ident> {
555555
let (ident, is_raw) = self.ident_or_err(recover)?;
556+
556557
if !is_raw && ident.is_reserved() {
557-
let mut err = self.expected_ident_found();
558+
let mut err = self.expected_ident_found_err();
558559
if recover {
559560
err.emit();
560561
} else {
@@ -565,12 +566,16 @@ impl<'a> Parser<'a> {
565566
Ok(ident)
566567
}
567568

568-
fn ident_or_err(&mut self, _recover: bool) -> PResult<'a, (Ident, /* is_raw */ bool)> {
569-
let result = self.token.ident().ok_or_else(|| self.expected_ident_found());
569+
fn ident_or_err(&mut self, recover: bool) -> PResult<'a, (Ident, /* is_raw */ bool)> {
570+
let result = self.token.ident().ok_or_else(|| self.expected_ident_found(recover));
570571

571572
let (ident, is_raw) = match result {
572573
Ok(ident) => ident,
573-
Err(err) => return Err(err),
574+
Err(err) => match err {
575+
// we recovered!
576+
Ok(ident) => ident,
577+
Err(err) => return Err(err),
578+
},
574579
};
575580

576581
Ok((ident, is_raw))

compiler/rustc_parse/src/parser/pat.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,13 @@ impl<'a> Parser<'a> {
391391
} else {
392392
PatKind::Lit(const_expr)
393393
}
394-
} else if self.can_be_ident_pat() || self.is_lit_bad_ident().is_some() {
394+
// Don't eagerly error on semantically invalid tokens when matching
395+
// declarative macros, as the input to those doesn't have to be
396+
// semantically valid. For attribute/derive proc macros this is not the
397+
// case, so doing the recovery for them is fine.
398+
} else if self.can_be_ident_pat()
399+
|| (self.is_lit_bad_ident().is_some() && self.may_recover())
400+
{
395401
// Parse `ident @ pat`
396402
// This can give false positives and parse nullary enums,
397403
// they are dealt with later in resolve.
@@ -590,7 +596,7 @@ impl<'a> Parser<'a> {
590596
// Make sure we don't allow e.g. `let mut $p;` where `$p:pat`.
591597
if let token::Interpolated(nt) = &self.token.kind {
592598
if let token::NtPat(_) = **nt {
593-
self.expected_ident_found().emit();
599+
self.expected_ident_found_err().emit();
594600
}
595601
}
596602

tests/ui/parser/ident-recovery.rs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
fn ,comma() {
2+
//~^ ERROR expected identifier, found `,`
3+
struct Foo {
4+
x: i32,,
5+
//~^ ERROR expected identifier, found `,`
6+
y: u32,
7+
}
8+
}
9+
10+
fn break() {
11+
//~^ ERROR expected identifier, found keyword `break`
12+
let continue = 5;
13+
//~^ ERROR expected identifier, found keyword `continue`
14+
}
15+
16+
fn main() {}

tests/ui/parser/ident-recovery.stderr

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
error: expected identifier, found `,`
2+
--> $DIR/ident-recovery.rs:1:4
3+
|
4+
LL | fn ,comma() {
5+
| ^
6+
| |
7+
| expected identifier
8+
| help: remove this comma
9+
10+
error: expected identifier, found `,`
11+
--> $DIR/ident-recovery.rs:4:16
12+
|
13+
LL | x: i32,,
14+
| ^
15+
| |
16+
| expected identifier
17+
| help: remove this comma
18+
19+
error: expected identifier, found keyword `break`
20+
--> $DIR/ident-recovery.rs:10:4
21+
|
22+
LL | fn break() {
23+
| ^^^^^ expected identifier, found keyword
24+
|
25+
help: escape `break` to use it as an identifier
26+
|
27+
LL | fn r#break() {
28+
| ++
29+
30+
error: expected identifier, found keyword `continue`
31+
--> $DIR/ident-recovery.rs:12:9
32+
|
33+
LL | let continue = 5;
34+
| ^^^^^^^^ expected identifier, found keyword
35+
|
36+
help: escape `continue` to use it as an identifier
37+
|
38+
LL | let r#continue = 5;
39+
| ++
40+
41+
error: aborting due to 4 previous errors
42+
+8-15
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,19 @@
1-
fn test() {
1+
fn 1234test() {
2+
//~^ ERROR expected identifier, found `1234test`
23
if let 123 = 123 { println!("yes"); }
3-
}
4-
5-
fn test_2() {
6-
let 1x = 123;
7-
//~^ ERROR expected identifier, found `1x`
8-
}
9-
10-
fn test_3() {
11-
let 2x: i32 = 123;
12-
//~^ ERROR expected identifier, found `2x`
13-
}
144

15-
fn test_4() {
165
if let 2e1 = 123 {
176
//~^ ERROR mismatched types
187
}
19-
}
208

21-
fn test_5() {
229
let 23name = 123;
2310
//~^ ERROR expected identifier, found `23name`
11+
12+
let 2x: i32 = 123;
13+
//~^ ERROR expected identifier, found `2x`
14+
15+
let 1x = 123;
16+
//~^ ERROR expected identifier, found `1x`
2417
}
2518

2619
fn main() {}
+30-18
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,59 @@
1-
error: expected identifier, found `1x`
2-
--> $DIR/issue-104088.rs:6:9
1+
error: expected identifier, found `1234test`
2+
--> $DIR/issue-104088.rs:1:4
33
|
4-
LL | let 1x = 123;
5-
| ^^ expected identifier
4+
LL | fn 1234test() {
5+
| ^^^^^^^^ expected identifier
66
|
77
help: identifiers cannot start with a number
8-
--> $DIR/issue-104088.rs:6:9
8+
--> $DIR/issue-104088.rs:1:4
99
|
10-
LL | let 1x = 123;
11-
| ^
10+
LL | fn 1234test() {
11+
| ^^^^
12+
13+
error: expected identifier, found `23name`
14+
--> $DIR/issue-104088.rs:9:9
15+
|
16+
LL | let 23name = 123;
17+
| ^^^^^^ expected identifier
18+
|
19+
help: identifiers cannot start with a number
20+
--> $DIR/issue-104088.rs:9:9
21+
|
22+
LL | let 23name = 123;
23+
| ^^
1224

1325
error: expected identifier, found `2x`
14-
--> $DIR/issue-104088.rs:11:9
26+
--> $DIR/issue-104088.rs:12:9
1527
|
1628
LL | let 2x: i32 = 123;
1729
| ^^ expected identifier
1830
|
1931
help: identifiers cannot start with a number
20-
--> $DIR/issue-104088.rs:11:9
32+
--> $DIR/issue-104088.rs:12:9
2133
|
2234
LL | let 2x: i32 = 123;
2335
| ^
2436

25-
error: expected identifier, found `23name`
26-
--> $DIR/issue-104088.rs:22:9
37+
error: expected identifier, found `1x`
38+
--> $DIR/issue-104088.rs:15:9
2739
|
28-
LL | let 23name = 123;
29-
| ^^^^^^ expected identifier
40+
LL | let 1x = 123;
41+
| ^^ expected identifier
3042
|
3143
help: identifiers cannot start with a number
32-
--> $DIR/issue-104088.rs:22:9
44+
--> $DIR/issue-104088.rs:15:9
3345
|
34-
LL | let 23name = 123;
35-
| ^^
46+
LL | let 1x = 123;
47+
| ^
3648

3749
error[E0308]: mismatched types
38-
--> $DIR/issue-104088.rs:16:12
50+
--> $DIR/issue-104088.rs:5:12
3951
|
4052
LL | if let 2e1 = 123 {
4153
| ^^^ --- this expression has type `{integer}`
4254
| |
4355
| expected integer, found floating-point number
4456

45-
error: aborting due to 4 previous errors
57+
error: aborting due to 5 previous errors
4658

4759
For more information about this error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)