From c9ddb73184290e0698060a80b0b5727d6ee11098 Mon Sep 17 00:00:00 2001 From: Ezra Shaw Date: Fri, 17 Mar 2023 21:35:43 +1300 Subject: [PATCH 1/3] refactor: refactor identifier parsing somewhat --- compiler/rustc_parse/messages.ftl | 2 +- compiler/rustc_parse/src/errors.rs | 6 ++--- .../rustc_parse/src/parser/diagnostics.rs | 17 +++++++++--- compiler/rustc_parse/src/parser/item.rs | 2 +- compiler/rustc_parse/src/parser/mod.rs | 27 +++++++++---------- 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl index c39ada95a4ec4..96765c296e79b 100644 --- a/compiler/rustc_parse/messages.ftl +++ b/compiler/rustc_parse/messages.ftl @@ -336,7 +336,7 @@ parse_expected_identifier_found_reserved_keyword = expected identifier, found re parse_expected_identifier_found_doc_comment = expected identifier, found doc comment parse_expected_identifier = expected identifier -parse_sugg_escape_to_use_as_identifier = escape `{$ident_name}` to use it as an identifier +parse_sugg_escape_identifier = escape `{$ident_name}` to use it as an identifier parse_sugg_remove_comma = remove this comma diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index af0c3026c6605..2def5c5b32f17 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -888,12 +888,12 @@ pub(crate) struct InvalidMetaItem { #[derive(Subdiagnostic)] #[suggestion( - parse_sugg_escape_to_use_as_identifier, + parse_sugg_escape_identifier, style = "verbose", applicability = "maybe-incorrect", code = "r#" )] -pub(crate) struct SuggEscapeToUseAsIdentifier { +pub(crate) struct SuggEscapeIdentifier { #[primary_span] pub span: Span, pub ident_name: String, @@ -937,7 +937,7 @@ impl ExpectedIdentifierFound { pub(crate) struct ExpectedIdentifier { pub span: Span, pub token: Token, - pub suggest_raw: Option, + pub suggest_raw: Option, pub suggest_remove_comma: Option, pub help_cannot_start_number: Option, } diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 5b12bcc182222..a9f24ab44ea84 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -6,14 +6,14 @@ use super::{ use crate::errors::{ AmbiguousPlus, AttributeOnParamType, BadQPathStage2, BadTypePlus, BadTypePlusSub, ComparisonOperatorsCannotBeChained, ComparisonOperatorsCannotBeChainedSugg, - ConstGenericWithoutBraces, ConstGenericWithoutBracesSugg, DocCommentOnParamType, - DoubleColonInBound, ExpectedIdentifier, ExpectedSemi, ExpectedSemiSugg, + ConstGenericWithoutBraces, ConstGenericWithoutBracesSugg, DocCommentDoesNotDocumentAnything, + DocCommentOnParamType, DoubleColonInBound, ExpectedIdentifier, ExpectedSemi, ExpectedSemiSugg, GenericParamsWithoutAngleBrackets, GenericParamsWithoutAngleBracketsSugg, HelpIdentifierStartsWithNumber, InInTypo, IncorrectAwait, IncorrectSemicolon, IncorrectUseOfAwait, ParenthesesInForHead, ParenthesesInForHeadSugg, PatternMethodParamWithoutBody, QuestionMarkInType, QuestionMarkInTypeSugg, SelfParamNotFirst, StructLiteralBodyWithoutPath, StructLiteralBodyWithoutPathSugg, StructLiteralNeedingParens, - StructLiteralNeedingParensSugg, SuggEscapeToUseAsIdentifier, SuggRemoveComma, + StructLiteralNeedingParensSugg, SuggEscapeIdentifier, SuggRemoveComma, UnexpectedConstInGenericParam, UnexpectedConstParamDeclaration, UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets, UseEqInstead, }; @@ -268,7 +268,16 @@ impl<'a> Parser<'a> { self.sess.source_map().span_to_snippet(span) } + /// Emits an error with suggestions if an identifier was expected but not found. pub(super) fn expected_ident_found(&mut self) -> DiagnosticBuilder<'a, ErrorGuaranteed> { + if let TokenKind::DocComment(..) = self.prev_token.kind { + return DocCommentDoesNotDocumentAnything { + span: self.prev_token.span, + missing_comma: None, + } + .into_diagnostic(&self.sess.span_diagnostic); + } + let valid_follow = &[ TokenKind::Eq, TokenKind::Colon, @@ -286,7 +295,7 @@ impl<'a> Parser<'a> { if ident.is_raw_guess() && self.look_ahead(1, |t| valid_follow.contains(&t.kind)) => { - Some(SuggEscapeToUseAsIdentifier { + 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#` diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 85cc8ca02a977..b7415dc8fb84c 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -1744,7 +1744,7 @@ impl<'a> Parser<'a> { /// Parses a field identifier. Specialized version of `parse_ident_common` /// for better diagnostics and suggestions. fn parse_field_ident(&mut self, adt_ty: &str, lo: Span) -> PResult<'a, Ident> { - let (ident, is_raw) = self.ident_or_err()?; + let (ident, is_raw) = self.ident_or_err(true)?; if !is_raw && ident.is_reserved() { let snapshot = self.create_snapshot_for_diagnostic(); let err = if self.check_fn_front_matter(false, Case::Sensitive) { diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 3251dd6d0c6fb..6991520895ddd 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -42,8 +42,7 @@ use thin_vec::ThinVec; use tracing::debug; use crate::errors::{ - DocCommentDoesNotDocumentAnything, IncorrectVisibilityRestriction, MismatchedClosingDelimiter, - NonStringAbiLiteral, + IncorrectVisibilityRestriction, MismatchedClosingDelimiter, NonStringAbiLiteral, }; bitflags::bitflags! { @@ -552,19 +551,8 @@ impl<'a> Parser<'a> { self.parse_ident_common(true) } - fn ident_or_err(&mut self) -> PResult<'a, (Ident, /* is_raw */ bool)> { - self.token.ident().ok_or_else(|| match self.prev_token.kind { - TokenKind::DocComment(..) => DocCommentDoesNotDocumentAnything { - span: self.prev_token.span, - missing_comma: None, - } - .into_diagnostic(&self.sess.span_diagnostic), - _ => self.expected_ident_found(), - }) - } - fn parse_ident_common(&mut self, recover: bool) -> PResult<'a, Ident> { - let (ident, is_raw) = self.ident_or_err()?; + let (ident, is_raw) = self.ident_or_err(recover)?; if !is_raw && ident.is_reserved() { let mut err = self.expected_ident_found(); if recover { @@ -577,6 +565,17 @@ impl<'a> Parser<'a> { Ok(ident) } + 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 (ident, is_raw) = match result { + Ok(ident) => ident, + Err(err) => return Err(err), + }; + + Ok((ident, is_raw)) + } + /// Checks if the next token is `tok`, and returns `true` if so. /// /// This method will automatically add `tok` to `expected_tokens` if `tok` is not From b4e17a5098f0413b01c90c8505e0f01e8bea50de Mon Sep 17 00:00:00 2001 From: Ezra Shaw Date: Fri, 17 Mar 2023 21:41:26 +1300 Subject: [PATCH 2/3] refactor: improve "ident starts with number" error --- compiler/rustc_parse/src/errors.rs | 5 ++- .../rustc_parse/src/parser/diagnostics.rs | 34 +++++++++++++------ compiler/rustc_parse/src/parser/pat.rs | 6 +--- compiler/rustc_span/src/lib.rs | 12 +++++++ .../parser/integer-literal-start-ident.stderr | 6 +++- tests/ui/parser/issues/issue-104088.stderr | 18 ++++++++-- 6 files changed, 61 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index 2def5c5b32f17..a9d116012ae5b 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -986,7 +986,10 @@ impl<'a, G: EmissionGuarantee> IntoDiagnostic<'a, G> for ExpectedIdentifier { #[derive(Subdiagnostic)] #[help(parse_invalid_identifier_with_leading_number)] -pub(crate) struct HelpIdentifierStartsWithNumber; +pub(crate) struct HelpIdentifierStartsWithNumber { + #[primary_span] + pub num_span: Span, +} pub(crate) struct ExpectedSemi { pub span: Span, diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index a9f24ab44ea84..47d1108491530 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -38,7 +38,7 @@ use rustc_errors::{ use rustc_session::errors::ExprParenthesesNeeded; use rustc_span::source_map::Spanned; use rustc_span::symbol::{kw, sym, Ident}; -use rustc_span::{Span, SpanSnippetError, DUMMY_SP}; +use rustc_span::{Span, SpanSnippetError, Symbol, DUMMY_SP}; use std::mem::take; use std::ops::{Deref, DerefMut}; use thin_vec::{thin_vec, ThinVec}; @@ -309,8 +309,11 @@ impl<'a> Parser<'a> { && self.look_ahead(1, |t| t.is_ident())) .then_some(SuggRemoveComma { span: self.token.span }); - let help_cannot_start_number = - self.is_lit_bad_ident().then_some(HelpIdentifierStartsWithNumber); + 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); + + HelpIdentifierStartsWithNumber { num_span: invalid } + }); let err = ExpectedIdentifier { span: self.token.span, @@ -378,13 +381,24 @@ impl<'a> Parser<'a> { /// Checks if the current token is a integer or float literal and looks like /// it could be a invalid identifier with digits at the start. - pub(super) fn is_lit_bad_ident(&mut self) -> bool { - matches!(self.token.uninterpolate().kind, token::Literal(Lit { kind: token::LitKind::Integer | token::LitKind::Float, .. }) - // ensure that the integer literal is followed by a *invalid* - // suffix: this is how we know that it is a identifier with an - // invalid beginning. - if rustc_ast::MetaItemLit::from_token(&self.token).is_none() - ) + /// + /// Returns the number of characters (bytes) composing the invalid portion + /// of the identifier and the valid portion of the identifier. + pub(super) fn is_lit_bad_ident(&mut self) -> Option<(usize, Symbol)> { + // ensure that the integer literal is followed by a *invalid* + // suffix: this is how we know that it is a identifier with an + // invalid beginning. + if let token::Literal(Lit { + kind: token::LitKind::Integer | token::LitKind::Float, + symbol, + suffix, + }) = self.token.uninterpolate().kind + && rustc_ast::MetaItemLit::from_token(&self.token).is_none() + { + Some((symbol.as_str().len(), suffix.unwrap())) + } else { + None + } } pub(super) fn expected_one_of_not_found( diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index fc9f1d1330a72..d9af241584868 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -348,10 +348,6 @@ impl<'a> Parser<'a> { lo = self.token.span; } - if self.is_lit_bad_ident() { - return Err(self.expected_ident_found()); - } - let pat = if self.check(&token::BinOp(token::And)) || self.token.kind == token::AndAnd { self.parse_pat_deref(expected)? } else if self.check(&token::OpenDelim(Delimiter::Parenthesis)) { @@ -395,7 +391,7 @@ impl<'a> Parser<'a> { } else { PatKind::Lit(const_expr) } - } else if self.can_be_ident_pat() { + } else if self.can_be_ident_pat() || self.is_lit_bad_ident().is_some() { // Parse `ident @ pat` // This can give false positives and parse nullary enums, // they are dealt with later in resolve. diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index 873cd33f6a4f2..02cffc762bed3 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -795,6 +795,18 @@ impl Span { }) } + /// Splits a span into two composite spans around a certain position. + pub fn split_at(self, pos: u32) -> (Span, Span) { + let len = self.hi().0 - self.lo().0; + debug_assert!(pos <= len); + + let split_pos = BytePos(self.lo().0 + pos); + ( + Span::new(self.lo(), split_pos, self.ctxt(), self.parent()), + Span::new(split_pos, self.hi(), self.ctxt(), self.parent()), + ) + } + /// Returns a `Span` that would enclose both `self` and `end`. /// /// Note that this can also be used to extend the span "backwards": diff --git a/tests/ui/parser/integer-literal-start-ident.stderr b/tests/ui/parser/integer-literal-start-ident.stderr index 51c37a0d24c3c..b2c6612965608 100644 --- a/tests/ui/parser/integer-literal-start-ident.stderr +++ b/tests/ui/parser/integer-literal-start-ident.stderr @@ -4,7 +4,11 @@ error: expected identifier, found `1main` LL | fn 1main() {} | ^^^^^ expected identifier | - = help: identifiers cannot start with a number +help: identifiers cannot start with a number + --> $DIR/integer-literal-start-ident.rs:1:4 + | +LL | fn 1main() {} + | ^ error: aborting due to previous error diff --git a/tests/ui/parser/issues/issue-104088.stderr b/tests/ui/parser/issues/issue-104088.stderr index 6511a313149f4..08ea1c891c172 100644 --- a/tests/ui/parser/issues/issue-104088.stderr +++ b/tests/ui/parser/issues/issue-104088.stderr @@ -4,7 +4,11 @@ error: expected identifier, found `1x` LL | let 1x = 123; | ^^ expected identifier | - = help: identifiers cannot start with a number +help: identifiers cannot start with a number + --> $DIR/issue-104088.rs:6:9 + | +LL | let 1x = 123; + | ^ error: expected identifier, found `2x` --> $DIR/issue-104088.rs:11:9 @@ -12,7 +16,11 @@ error: expected identifier, found `2x` LL | let 2x: i32 = 123; | ^^ expected identifier | - = help: identifiers cannot start with a number +help: identifiers cannot start with a number + --> $DIR/issue-104088.rs:11:9 + | +LL | let 2x: i32 = 123; + | ^ error: expected identifier, found `23name` --> $DIR/issue-104088.rs:22:9 @@ -20,7 +28,11 @@ error: expected identifier, found `23name` LL | let 23name = 123; | ^^^^^^ expected identifier | - = help: identifiers cannot start with a number +help: identifiers cannot start with a number + --> $DIR/issue-104088.rs:22:9 + | +LL | let 23name = 123; + | ^^ error[E0308]: mismatched types --> $DIR/issue-104088.rs:16:12 From 05b5046633e9f594f955e0365a1219d1a96a5b54 Mon Sep 17 00:00:00 2001 From: Ezra Shaw Date: Fri, 17 Mar 2023 22:27:17 +1300 Subject: [PATCH 3/3] feat: implement error recovery in `expected_ident_found` --- .../rustc_parse/src/parser/diagnostics.rs | 84 +++++++++++++------ compiler/rustc_parse/src/parser/item.rs | 8 +- compiler/rustc_parse/src/parser/mod.rs | 13 ++- compiler/rustc_parse/src/parser/pat.rs | 10 ++- tests/ui/parser/ident-recovery.rs | 16 ++++ tests/ui/parser/ident-recovery.stderr | 42 ++++++++++ tests/ui/parser/issues/issue-104088.rs | 23 ++--- tests/ui/parser/issues/issue-104088.stderr | 48 +++++++---- 8 files changed, 175 insertions(+), 69 deletions(-) create mode 100644 tests/ui/parser/ident-recovery.rs create mode 100644 tests/ui/parser/ident-recovery.stderr diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 47d1108491530..9544afd3d6df9 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -269,13 +269,18 @@ impl<'a> Parser<'a> { } /// 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 { - return DocCommentDoesNotDocumentAnything { + return Err(DocCommentDoesNotDocumentAnything { span: self.prev_token.span, missing_comma: None, } - .into_diagnostic(&self.sess.span_diagnostic); + .into_diagnostic(&self.sess.span_diagnostic)); } let valid_follow = &[ @@ -290,34 +295,51 @@ impl<'a> Parser<'a> { TokenKind::CloseDelim(Delimiter::Parenthesis), ]; - let suggest_raw = match self.token.ident() { - Some((ident, false)) - if ident.is_raw_guess() - && self.look_ahead(1, |t| valid_follow.contains(&t.kind)) => - { - 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 mut recovered_ident = None; + // we take this here so that the correct original token is retained in + // the diagnostic, regardless of eager recovery. + let bad_token = self.token.clone(); + + // suggest prepending a keyword in identifier position with `r#` + let suggest_raw = if let Some((ident, false)) = self.token.ident() + && ident.is_raw_guess() + && self.look_ahead(1, |t| valid_follow.contains(&t.kind)) + { + recovered_ident = Some((ident, true)); + + // `Symbol::to_string()` is different from `Symbol::into_diagnostic_arg()`, + // 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 suggest_remove_comma = (self.token == token::Comma - && self.look_ahead(1, |t| t.is_ident())) - .then_some(SuggRemoveComma { span: self.token.span }); + 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); - 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 } }); let err = ExpectedIdentifier { - span: self.token.span, - token: self.token.clone(), + span: bad_token.span, + token: bad_token, suggest_raw, suggest_remove_comma, help_cannot_start_number, @@ -326,6 +348,7 @@ impl<'a> Parser<'a> { // if the token we have is a `<` // it *might* be a misplaced generic + // FIXME: could we recover with this? if self.token == token::Lt { // all keywords that could have generic applied 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 @@ -392,7 +424,7 @@ impl<'a> Parser<'a> { kind: token::LitKind::Integer | token::LitKind::Float, symbol, suffix, - }) = self.token.uninterpolate().kind + }) = self.token.kind && rustc_ast::MetaItemLit::from_token(&self.token).is_none() { Some((symbol.as_str().len(), suffix.unwrap())) diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index b7415dc8fb84c..ae8fe90e9d611 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -1181,7 +1181,7 @@ impl<'a> Parser<'a> { defaultness: Defaultness, ) -> PResult<'a, ItemInfo> { 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 let mut impl_info = match self.parse_item_impl(attrs, defaultness) { @@ -1776,7 +1776,7 @@ impl<'a> Parser<'a> { Err(err) => { err.cancel(); self.restore_snapshot(snapshot); - self.expected_ident_found() + self.expected_ident_found_err() } } } else if self.eat_keyword(kw::Struct) { @@ -1792,11 +1792,11 @@ impl<'a> Parser<'a> { Err(err) => { err.cancel(); self.restore_snapshot(snapshot); - self.expected_ident_found() + self.expected_ident_found_err() } } } else { - let mut err = self.expected_ident_found(); + let mut err = self.expected_ident_found_err(); if self.eat_keyword_noexpect(kw::Let) && let removal_span = self.prev_token.span.until(self.token.span) && let Ok(ident) = self.parse_ident_common(false) diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 6991520895ddd..53c25a80c4bf3 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -553,8 +553,9 @@ impl<'a> Parser<'a> { fn parse_ident_common(&mut self, recover: bool) -> PResult<'a, Ident> { let (ident, is_raw) = self.ident_or_err(recover)?; + if !is_raw && ident.is_reserved() { - let mut err = self.expected_ident_found(); + let mut err = self.expected_ident_found_err(); if recover { err.emit(); } else { @@ -565,12 +566,16 @@ impl<'a> Parser<'a> { Ok(ident) } - 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()); + 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(recover)); let (ident, is_raw) = match result { 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)) diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index d9af241584868..2246002f5d32a 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -391,7 +391,13 @@ impl<'a> Parser<'a> { } else { 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` // This can give false positives and parse nullary enums, // 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`. if let token::Interpolated(nt) = &self.token.kind { if let token::NtPat(_) = **nt { - self.expected_ident_found().emit(); + self.expected_ident_found_err().emit(); } } diff --git a/tests/ui/parser/ident-recovery.rs b/tests/ui/parser/ident-recovery.rs new file mode 100644 index 0000000000000..7575372b940fd --- /dev/null +++ b/tests/ui/parser/ident-recovery.rs @@ -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() {} diff --git a/tests/ui/parser/ident-recovery.stderr b/tests/ui/parser/ident-recovery.stderr new file mode 100644 index 0000000000000..e9a55026d1245 --- /dev/null +++ b/tests/ui/parser/ident-recovery.stderr @@ -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 + diff --git a/tests/ui/parser/issues/issue-104088.rs b/tests/ui/parser/issues/issue-104088.rs index 86988c8cd21da..3dc636b6a33bd 100644 --- a/tests/ui/parser/issues/issue-104088.rs +++ b/tests/ui/parser/issues/issue-104088.rs @@ -1,26 +1,19 @@ -fn test() { +fn 1234test() { +//~^ ERROR expected identifier, found `1234test` if let 123 = 123 { println!("yes"); } -} - -fn test_2() { - let 1x = 123; - //~^ 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` + + let 2x: i32 = 123; + //~^ ERROR expected identifier, found `2x` + + let 1x = 123; + //~^ ERROR expected identifier, found `1x` } fn main() {} diff --git a/tests/ui/parser/issues/issue-104088.stderr b/tests/ui/parser/issues/issue-104088.stderr index 08ea1c891c172..8b751759d69a6 100644 --- a/tests/ui/parser/issues/issue-104088.stderr +++ b/tests/ui/parser/issues/issue-104088.stderr @@ -1,47 +1,59 @@ -error: expected identifier, found `1x` - --> $DIR/issue-104088.rs:6:9 +error: expected identifier, found `1234test` + --> $DIR/issue-104088.rs:1:4 | -LL | let 1x = 123; - | ^^ expected identifier +LL | fn 1234test() { + | ^^^^^^^^ expected identifier | 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 `23name` + --> $DIR/issue-104088.rs:9:9 + | +LL | let 23name = 123; + | ^^^^^^ expected identifier + | +help: identifiers cannot start with a number + --> $DIR/issue-104088.rs:9:9 + | +LL | let 23name = 123; + | ^^ error: expected identifier, found `2x` - --> $DIR/issue-104088.rs:11:9 + --> $DIR/issue-104088.rs:12:9 | LL | let 2x: i32 = 123; | ^^ expected identifier | help: identifiers cannot start with a number - --> $DIR/issue-104088.rs:11:9 + --> $DIR/issue-104088.rs:12:9 | LL | let 2x: i32 = 123; | ^ -error: expected identifier, found `23name` - --> $DIR/issue-104088.rs:22:9 +error: expected identifier, found `1x` + --> $DIR/issue-104088.rs:15:9 | -LL | let 23name = 123; - | ^^^^^^ expected identifier +LL | let 1x = 123; + | ^^ expected identifier | help: identifiers cannot start with a number - --> $DIR/issue-104088.rs:22:9 + --> $DIR/issue-104088.rs:15:9 | -LL | let 23name = 123; - | ^^ +LL | let 1x = 123; + | ^ error[E0308]: mismatched types - --> $DIR/issue-104088.rs:16:12 + --> $DIR/issue-104088.rs:5:12 | LL | if let 2e1 = 123 { | ^^^ --- this expression has type `{integer}` | | | 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`.