Rollup merge of #108854 - Ezrashaw:improve-int-idents, r=oli-obk

feat/refactor: improve errors in case of ident with number at start

Improve parser code when we parse a integer (or float) literal but expect an identifier. We emit an error message saying that identifiers can't begin with numbers. This PR just improves that code and expands it to all identifiers. Note that I haven't implemented error recovery (this didn't exist before anyway), I might do that in a follow up PR.
This commit is contained in:
Matthias Krüger 2023-03-09 12:11:53 +01:00 committed by GitHub
commit bec7011a2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 68 additions and 45 deletions

View File

@ -412,8 +412,7 @@ parse_fn_ptr_with_generics = function pointer types may not have generic paramet
*[false] a *[false] a
} `for` parameter list } `for` parameter list
parse_invalid_identifier_with_leading_number = expected identifier, found number literal parse_invalid_identifier_with_leading_number = identifiers cannot start with a number
.label = identifiers cannot start with a number
parse_maybe_fn_typo_with_impl = you might have meant to write `impl` instead of `fn` parse_maybe_fn_typo_with_impl = you might have meant to write `impl` instead of `fn`
.suggestion = replace `fn` with `impl` here .suggestion = replace `fn` with `impl` here

View File

@ -939,6 +939,7 @@ pub(crate) struct ExpectedIdentifier {
pub token: Token, pub token: Token,
pub suggest_raw: Option<SuggEscapeToUseAsIdentifier>, pub suggest_raw: Option<SuggEscapeToUseAsIdentifier>,
pub suggest_remove_comma: Option<SuggRemoveComma>, pub suggest_remove_comma: Option<SuggRemoveComma>,
pub help_cannot_start_number: Option<HelpIdentifierStartsWithNumber>,
} }
impl<'a, G: EmissionGuarantee> IntoDiagnostic<'a, G> for ExpectedIdentifier { impl<'a, G: EmissionGuarantee> IntoDiagnostic<'a, G> for ExpectedIdentifier {
@ -975,10 +976,18 @@ impl<'a, G: EmissionGuarantee> IntoDiagnostic<'a, G> for ExpectedIdentifier {
sugg.add_to_diagnostic(&mut diag); sugg.add_to_diagnostic(&mut diag);
} }
if let Some(help) = self.help_cannot_start_number {
help.add_to_diagnostic(&mut diag);
}
diag diag
} }
} }
#[derive(Subdiagnostic)]
#[help(parse_invalid_identifier_with_leading_number)]
pub(crate) struct HelpIdentifierStartsWithNumber;
pub(crate) struct ExpectedSemi { pub(crate) struct ExpectedSemi {
pub span: Span, pub span: Span,
pub token: Token, pub token: Token,
@ -1207,14 +1216,6 @@ pub(crate) struct SelfParamNotFirst {
pub span: Span, pub span: Span,
} }
#[derive(Diagnostic)]
#[diag(parse_invalid_identifier_with_leading_number)]
pub(crate) struct InvalidIdentiferStartsWithNumber {
#[primary_span]
#[label]
pub span: Span,
}
#[derive(Diagnostic)] #[derive(Diagnostic)]
#[diag(parse_const_generic_without_braces)] #[diag(parse_const_generic_without_braces)]
pub(crate) struct ConstGenericWithoutBraces { pub(crate) struct ConstGenericWithoutBraces {

View File

@ -8,14 +8,14 @@ use crate::errors::{
ComparisonOperatorsCannotBeChained, ComparisonOperatorsCannotBeChainedSugg, ComparisonOperatorsCannotBeChained, ComparisonOperatorsCannotBeChainedSugg,
ConstGenericWithoutBraces, ConstGenericWithoutBracesSugg, DocCommentOnParamType, ConstGenericWithoutBraces, ConstGenericWithoutBracesSugg, DocCommentOnParamType,
DoubleColonInBound, ExpectedIdentifier, ExpectedSemi, ExpectedSemiSugg, DoubleColonInBound, ExpectedIdentifier, ExpectedSemi, ExpectedSemiSugg,
GenericParamsWithoutAngleBrackets, GenericParamsWithoutAngleBracketsSugg, InInTypo, GenericParamsWithoutAngleBrackets, GenericParamsWithoutAngleBracketsSugg,
IncorrectAwait, IncorrectSemicolon, IncorrectUseOfAwait, ParenthesesInForHead, HelpIdentifierStartsWithNumber, InInTypo, IncorrectAwait, IncorrectSemicolon,
ParenthesesInForHeadSugg, PatternMethodParamWithoutBody, QuestionMarkInType, IncorrectUseOfAwait, ParenthesesInForHead, ParenthesesInForHeadSugg,
QuestionMarkInTypeSugg, SelfParamNotFirst, StructLiteralBodyWithoutPath, PatternMethodParamWithoutBody, QuestionMarkInType, QuestionMarkInTypeSugg, SelfParamNotFirst,
StructLiteralBodyWithoutPathSugg, StructLiteralNeedingParens, StructLiteralNeedingParensSugg, StructLiteralBodyWithoutPath, StructLiteralBodyWithoutPathSugg, StructLiteralNeedingParens,
SuggEscapeToUseAsIdentifier, SuggRemoveComma, UnexpectedConstInGenericParam, StructLiteralNeedingParensSugg, SuggEscapeToUseAsIdentifier, SuggRemoveComma,
UnexpectedConstParamDeclaration, UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets, UnexpectedConstInGenericParam, UnexpectedConstParamDeclaration,
UseEqInstead, UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets, UseEqInstead,
}; };
use crate::fluent_generated as fluent; use crate::fluent_generated as fluent;
@ -280,6 +280,7 @@ impl<'a> Parser<'a> {
TokenKind::CloseDelim(Delimiter::Brace), TokenKind::CloseDelim(Delimiter::Brace),
TokenKind::CloseDelim(Delimiter::Parenthesis), TokenKind::CloseDelim(Delimiter::Parenthesis),
]; ];
let suggest_raw = match self.token.ident() { let suggest_raw = match self.token.ident() {
Some((ident, false)) Some((ident, false))
if ident.is_raw_guess() if ident.is_raw_guess()
@ -295,18 +296,19 @@ impl<'a> Parser<'a> {
_ => None, _ => None,
}; };
let suggest_remove_comma = let suggest_remove_comma = (self.token == token::Comma
if self.token == token::Comma && self.look_ahead(1, |t| t.is_ident()) { && self.look_ahead(1, |t| t.is_ident()))
Some(SuggRemoveComma { span: self.token.span }) .then_some(SuggRemoveComma { span: self.token.span });
} else {
None let help_cannot_start_number =
}; self.is_lit_bad_ident().then_some(HelpIdentifierStartsWithNumber);
let err = ExpectedIdentifier { let err = ExpectedIdentifier {
span: self.token.span, span: self.token.span,
token: self.token.clone(), token: self.token.clone(),
suggest_raw, suggest_raw,
suggest_remove_comma, suggest_remove_comma,
help_cannot_start_number,
}; };
let mut err = err.into_diagnostic(&self.sess.span_diagnostic); let mut err = err.into_diagnostic(&self.sess.span_diagnostic);
@ -365,6 +367,17 @@ impl<'a> Parser<'a> {
err err
} }
/// 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()
)
}
pub(super) fn expected_one_of_not_found( pub(super) fn expected_one_of_not_found(
&mut self, &mut self,
edible: &[TokenKind], edible: &[TokenKind],

View File

@ -348,6 +348,10 @@ impl<'a> Parser<'a> {
lo = self.token.span; 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 { let pat = if self.check(&token::BinOp(token::And)) || self.token.kind == token::AndAnd {
self.parse_pat_deref(expected)? self.parse_pat_deref(expected)?
} else if self.check(&token::OpenDelim(Delimiter::Parenthesis)) { } else if self.check(&token::OpenDelim(Delimiter::Parenthesis)) {

View File

@ -273,7 +273,6 @@ impl<'a> Parser<'a> {
self.bump(); self.bump();
} }
self.report_invalid_identifier_error()?;
let (pat, colon) = let (pat, colon) =
self.parse_pat_before_ty(None, RecoverComma::Yes, PatternLocation::LetBinding)?; self.parse_pat_before_ty(None, RecoverComma::Yes, PatternLocation::LetBinding)?;
@ -366,17 +365,6 @@ impl<'a> Parser<'a> {
Ok(P(ast::Local { ty, pat, kind, id: DUMMY_NODE_ID, span: lo.to(hi), attrs, tokens: None })) Ok(P(ast::Local { ty, pat, kind, id: DUMMY_NODE_ID, span: lo.to(hi), attrs, tokens: None }))
} }
/// report error for `let 1x = 123`
pub fn report_invalid_identifier_error(&mut self) -> PResult<'a, ()> {
if let token::Literal(lit) = self.token.uninterpolate().kind &&
rustc_ast::MetaItemLit::from_token(&self.token).is_none() &&
(lit.kind == token::LitKind::Integer || lit.kind == token::LitKind::Float) &&
self.look_ahead(1, |t| matches!(t.kind, token::Eq) || matches!(t.kind, token::Colon ) ) {
return Err(self.sess.create_err(errors::InvalidIdentiferStartsWithNumber { span: self.token.span }));
}
Ok(())
}
fn check_let_else_init_bool_expr(&self, init: &ast::Expr) { fn check_let_else_init_bool_expr(&self, init: &ast::Expr) {
if let ast::ExprKind::Binary(op, ..) = init.kind { if let ast::ExprKind::Binary(op, ..) = init.kind {
if op.node.lazy() { if op.node.lazy() {

View File

@ -0,0 +1,2 @@
fn 1main() {}
//~^ ERROR expected identifier, found `1main`

View File

@ -0,0 +1,10 @@
error: expected identifier, found `1main`
--> $DIR/integer-literal-start-ident.rs:1:4
|
LL | fn 1main() {}
| ^^^^^ expected identifier
|
= help: identifiers cannot start with a number
error: aborting due to previous error

View File

@ -4,12 +4,12 @@ fn test() {
fn test_2() { fn test_2() {
let 1x = 123; let 1x = 123;
//~^ ERROR expected identifier, found number literal //~^ ERROR expected identifier, found `1x`
} }
fn test_3() { fn test_3() {
let 2x: i32 = 123; let 2x: i32 = 123;
//~^ ERROR expected identifier, found number literal //~^ ERROR expected identifier, found `2x`
} }
fn test_4() { fn test_4() {
@ -20,7 +20,7 @@ fn test_4() {
fn test_5() { fn test_5() {
let 23name = 123; let 23name = 123;
//~^ ERROR expected identifier, found number literal //~^ ERROR expected identifier, found `23name`
} }
fn main() {} fn main() {}

View File

@ -1,20 +1,26 @@
error: expected identifier, found number literal error: expected identifier, found `1x`
--> $DIR/issue-104088.rs:6:9 --> $DIR/issue-104088.rs:6:9
| |
LL | let 1x = 123; LL | let 1x = 123;
| ^^ identifiers cannot start with a number | ^^ expected identifier
|
= help: identifiers cannot start with a number
error: expected identifier, found number literal error: expected identifier, found `2x`
--> $DIR/issue-104088.rs:11:9 --> $DIR/issue-104088.rs:11:9
| |
LL | let 2x: i32 = 123; LL | let 2x: i32 = 123;
| ^^ identifiers cannot start with a number | ^^ expected identifier
|
= help: identifiers cannot start with a number
error: expected identifier, found number literal error: expected identifier, found `23name`
--> $DIR/issue-104088.rs:22:9 --> $DIR/issue-104088.rs:22:9
| |
LL | let 23name = 123; LL | let 23name = 123;
| ^^^^^^ identifiers cannot start with a number | ^^^^^^ expected identifier
|
= help: identifiers cannot start with a number
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/issue-104088.rs:16:12 --> $DIR/issue-104088.rs:16:12