1
Fork 0

feat: implement error recovery in expected_ident_found

This commit is contained in:
Ezra Shaw 2023-03-17 22:27:17 +13:00
parent b4e17a5098
commit 05b5046633
No known key found for this signature in database
GPG key ID: 67ABF16FB0ECD870
8 changed files with 184 additions and 78 deletions

View file

@ -269,13 +269,18 @@ impl<'a> Parser<'a> {
} }
/// Emits an error with suggestions if an identifier was expected but not found. /// Emits an error with suggestions if an identifier was expected but not found.
pub(super) fn expected_ident_found(&mut self) -> DiagnosticBuilder<'a, ErrorGuaranteed> { ///
/// Returns a possibly recovered identifier.
pub(super) fn expected_ident_found(
&mut self,
recover: bool,
) -> PResult<'a, (Ident, /* is_raw */ bool)> {
if let TokenKind::DocComment(..) = self.prev_token.kind { if let TokenKind::DocComment(..) = self.prev_token.kind {
return DocCommentDoesNotDocumentAnything { return Err(DocCommentDoesNotDocumentAnything {
span: self.prev_token.span, span: self.prev_token.span,
missing_comma: None, missing_comma: None,
} }
.into_diagnostic(&self.sess.span_diagnostic); .into_diagnostic(&self.sess.span_diagnostic));
} }
let valid_follow = &[ let valid_follow = &[
@ -290,34 +295,51 @@ impl<'a> Parser<'a> {
TokenKind::CloseDelim(Delimiter::Parenthesis), TokenKind::CloseDelim(Delimiter::Parenthesis),
]; ];
let suggest_raw = match self.token.ident() { let mut recovered_ident = None;
Some((ident, false)) // we take this here so that the correct original token is retained in
if ident.is_raw_guess() // the diagnostic, regardless of eager recovery.
&& self.look_ahead(1, |t| valid_follow.contains(&t.kind)) => let bad_token = self.token.clone();
{
Some(SuggEscapeIdentifier {
span: ident.span.shrink_to_lo(),
// `Symbol::to_string()` is different from `Symbol::into_diagnostic_arg()`,
// which uses `Symbol::to_ident_string()` and "helpfully" adds an implicit `r#`
ident_name: ident.name.to_string(),
})
}
_ => None,
};
let suggest_remove_comma = (self.token == token::Comma // suggest prepending a keyword in identifier position with `r#`
&& self.look_ahead(1, |t| t.is_ident())) let suggest_raw = if let Some((ident, false)) = self.token.ident()
.then_some(SuggRemoveComma { span: self.token.span }); && ident.is_raw_guess()
&& self.look_ahead(1, |t| valid_follow.contains(&t.kind))
{
recovered_ident = Some((ident, true));
let help_cannot_start_number = self.is_lit_bad_ident().map(|(len, _valid_portion)| { // `Symbol::to_string()` is different from `Symbol::into_diagnostic_arg()`,
let (invalid, _valid) = self.token.span.split_at(len as u32); // which uses `Symbol::to_ident_string()` and "helpfully" adds an implicit `r#`
let ident_name = ident.name.to_string();
Some(SuggEscapeIdentifier {
span: ident.span.shrink_to_lo(),
ident_name
})
} else { None };
let suggest_remove_comma =
if self.token == token::Comma && self.look_ahead(1, |t| t.is_ident()) {
if recover {
self.bump();
recovered_ident = self.ident_or_err(false).ok();
};
Some(SuggRemoveComma { span: bad_token.span })
} else {
None
};
let help_cannot_start_number = self.is_lit_bad_ident().map(|(len, valid_portion)| {
let (invalid, valid) = self.token.span.split_at(len as u32);
recovered_ident = Some((Ident::new(valid_portion, valid), false));
HelpIdentifierStartsWithNumber { num_span: invalid } HelpIdentifierStartsWithNumber { num_span: invalid }
}); });
let err = ExpectedIdentifier { let err = ExpectedIdentifier {
span: self.token.span, span: bad_token.span,
token: self.token.clone(), token: bad_token,
suggest_raw, suggest_raw,
suggest_remove_comma, suggest_remove_comma,
help_cannot_start_number, help_cannot_start_number,
@ -326,6 +348,7 @@ impl<'a> Parser<'a> {
// if the token we have is a `<` // if the token we have is a `<`
// it *might* be a misplaced generic // it *might* be a misplaced generic
// FIXME: could we recover with this?
if self.token == token::Lt { if self.token == token::Lt {
// all keywords that could have generic applied // all keywords that could have generic applied
let valid_prev_keywords = let valid_prev_keywords =
@ -376,7 +399,16 @@ impl<'a> Parser<'a> {
} }
} }
err if let Some(recovered_ident) = recovered_ident && recover {
err.emit();
Ok(recovered_ident)
} else {
Err(err)
}
}
pub(super) fn expected_ident_found_err(&mut self) -> DiagnosticBuilder<'a, ErrorGuaranteed> {
self.expected_ident_found(false).unwrap_err()
} }
/// Checks if the current token is a integer or float literal and looks like /// Checks if the current token is a integer or float literal and looks like
@ -392,7 +424,7 @@ impl<'a> Parser<'a> {
kind: token::LitKind::Integer | token::LitKind::Float, kind: token::LitKind::Integer | token::LitKind::Float,
symbol, symbol,
suffix, suffix,
}) = self.token.uninterpolate().kind }) = self.token.kind
&& rustc_ast::MetaItemLit::from_token(&self.token).is_none() && rustc_ast::MetaItemLit::from_token(&self.token).is_none()
{ {
Some((symbol.as_str().len(), suffix.unwrap())) Some((symbol.as_str().len(), suffix.unwrap()))

View file

@ -1181,7 +1181,7 @@ impl<'a> Parser<'a> {
defaultness: Defaultness, defaultness: Defaultness,
) -> PResult<'a, ItemInfo> { ) -> PResult<'a, ItemInfo> {
let impl_span = self.token.span; let impl_span = self.token.span;
let mut err = self.expected_ident_found(); let mut err = self.expected_ident_found_err();
// Only try to recover if this is implementing a trait for a type // Only try to recover if this is implementing a trait for a type
let mut impl_info = match self.parse_item_impl(attrs, defaultness) { let mut impl_info = match self.parse_item_impl(attrs, defaultness) {
@ -1776,7 +1776,7 @@ impl<'a> Parser<'a> {
Err(err) => { Err(err) => {
err.cancel(); err.cancel();
self.restore_snapshot(snapshot); self.restore_snapshot(snapshot);
self.expected_ident_found() self.expected_ident_found_err()
} }
} }
} else if self.eat_keyword(kw::Struct) { } else if self.eat_keyword(kw::Struct) {
@ -1792,11 +1792,11 @@ impl<'a> Parser<'a> {
Err(err) => { Err(err) => {
err.cancel(); err.cancel();
self.restore_snapshot(snapshot); self.restore_snapshot(snapshot);
self.expected_ident_found() self.expected_ident_found_err()
} }
} }
} else { } else {
let mut err = self.expected_ident_found(); let mut err = self.expected_ident_found_err();
if self.eat_keyword_noexpect(kw::Let) if self.eat_keyword_noexpect(kw::Let)
&& let removal_span = self.prev_token.span.until(self.token.span) && let removal_span = self.prev_token.span.until(self.token.span)
&& let Ok(ident) = self.parse_ident_common(false) && let Ok(ident) = self.parse_ident_common(false)

View file

@ -553,8 +553,9 @@ impl<'a> Parser<'a> {
fn parse_ident_common(&mut self, recover: bool) -> PResult<'a, Ident> { fn parse_ident_common(&mut self, recover: bool) -> PResult<'a, Ident> {
let (ident, is_raw) = self.ident_or_err(recover)?; let (ident, is_raw) = self.ident_or_err(recover)?;
if !is_raw && ident.is_reserved() { if !is_raw && ident.is_reserved() {
let mut err = self.expected_ident_found(); let mut err = self.expected_ident_found_err();
if recover { if recover {
err.emit(); err.emit();
} else { } else {
@ -565,12 +566,16 @@ impl<'a> Parser<'a> {
Ok(ident) Ok(ident)
} }
fn ident_or_err(&mut self, _recover: bool) -> PResult<'a, (Ident, /* is_raw */ bool)> { fn ident_or_err(&mut self, recover: bool) -> PResult<'a, (Ident, /* is_raw */ bool)> {
let result = self.token.ident().ok_or_else(|| self.expected_ident_found()); let result = self.token.ident().ok_or_else(|| self.expected_ident_found(recover));
let (ident, is_raw) = match result { let (ident, is_raw) = match result {
Ok(ident) => ident, Ok(ident) => ident,
Err(err) => return Err(err), Err(err) => match err {
// we recovered!
Ok(ident) => ident,
Err(err) => return Err(err),
},
}; };
Ok((ident, is_raw)) Ok((ident, is_raw))

View file

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

View file

@ -0,0 +1,16 @@
fn ,comma() {
//~^ ERROR expected identifier, found `,`
struct Foo {
x: i32,,
//~^ ERROR expected identifier, found `,`
y: u32,
}
}
fn break() {
//~^ ERROR expected identifier, found keyword `break`
let continue = 5;
//~^ ERROR expected identifier, found keyword `continue`
}
fn main() {}

View file

@ -0,0 +1,42 @@
error: expected identifier, found `,`
--> $DIR/ident-recovery.rs:1:4
|
LL | fn ,comma() {
| ^
| |
| expected identifier
| help: remove this comma
error: expected identifier, found `,`
--> $DIR/ident-recovery.rs:4:16
|
LL | x: i32,,
| ^
| |
| expected identifier
| help: remove this comma
error: expected identifier, found keyword `break`
--> $DIR/ident-recovery.rs:10:4
|
LL | fn break() {
| ^^^^^ expected identifier, found keyword
|
help: escape `break` to use it as an identifier
|
LL | fn r#break() {
| ++
error: expected identifier, found keyword `continue`
--> $DIR/ident-recovery.rs:12:9
|
LL | let continue = 5;
| ^^^^^^^^ expected identifier, found keyword
|
help: escape `continue` to use it as an identifier
|
LL | let r#continue = 5;
| ++
error: aborting due to 4 previous errors

View file

@ -1,26 +1,19 @@
fn test() { fn 1234test() {
//~^ ERROR expected identifier, found `1234test`
if let 123 = 123 { println!("yes"); } if let 123 = 123 { println!("yes"); }
}
fn test_2() { if let 2e1 = 123 {
//~^ ERROR mismatched types
}
let 23name = 123;
//~^ ERROR expected identifier, found `23name`
let 2x: i32 = 123;
//~^ ERROR expected identifier, found `2x`
let 1x = 123; let 1x = 123;
//~^ ERROR expected identifier, found `1x` //~^ ERROR expected identifier, found `1x`
} }
fn test_3() {
let 2x: i32 = 123;
//~^ ERROR expected identifier, found `2x`
}
fn test_4() {
if let 2e1 = 123 {
//~^ ERROR mismatched types
}
}
fn test_5() {
let 23name = 123;
//~^ ERROR expected identifier, found `23name`
}
fn main() {} fn main() {}

View file

@ -1,47 +1,59 @@
error: expected identifier, found `1x` error: expected identifier, found `1234test`
--> $DIR/issue-104088.rs:6:9 --> $DIR/issue-104088.rs:1:4
| |
LL | let 1x = 123; LL | fn 1234test() {
| ^^ expected identifier | ^^^^^^^^ expected identifier
| |
help: identifiers cannot start with a number help: identifiers cannot start with a number
--> $DIR/issue-104088.rs:6:9 --> $DIR/issue-104088.rs:1:4
| |
LL | let 1x = 123; LL | fn 1234test() {
| ^ | ^^^^
error: expected identifier, found `2x`
--> $DIR/issue-104088.rs:11:9
|
LL | let 2x: i32 = 123;
| ^^ expected identifier
|
help: identifiers cannot start with a number
--> $DIR/issue-104088.rs:11:9
|
LL | let 2x: i32 = 123;
| ^
error: expected identifier, found `23name` error: expected identifier, found `23name`
--> $DIR/issue-104088.rs:22:9 --> $DIR/issue-104088.rs:9:9
| |
LL | let 23name = 123; LL | let 23name = 123;
| ^^^^^^ expected identifier | ^^^^^^ expected identifier
| |
help: identifiers cannot start with a number help: identifiers cannot start with a number
--> $DIR/issue-104088.rs:22:9 --> $DIR/issue-104088.rs:9:9
| |
LL | let 23name = 123; LL | let 23name = 123;
| ^^ | ^^
error: expected identifier, found `2x`
--> $DIR/issue-104088.rs:12:9
|
LL | let 2x: i32 = 123;
| ^^ expected identifier
|
help: identifiers cannot start with a number
--> $DIR/issue-104088.rs:12:9
|
LL | let 2x: i32 = 123;
| ^
error: expected identifier, found `1x`
--> $DIR/issue-104088.rs:15:9
|
LL | let 1x = 123;
| ^^ expected identifier
|
help: identifiers cannot start with a number
--> $DIR/issue-104088.rs:15:9
|
LL | let 1x = 123;
| ^
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/issue-104088.rs:16:12 --> $DIR/issue-104088.rs:5:12
| |
LL | if let 2e1 = 123 { LL | if let 2e1 = 123 {
| ^^^ --- this expression has type `{integer}` | ^^^ --- this expression has type `{integer}`
| | | |
| expected integer, found floating-point number | expected integer, found floating-point number
error: aborting due to 4 previous errors error: aborting due to 5 previous errors
For more information about this error, try `rustc --explain E0308`. For more information about this error, try `rustc --explain E0308`.