diff --git a/compiler/rustc_ast/src/token.rs b/compiler/rustc_ast/src/token.rs index 5ed40094c03f2..c5addc5926901 100644 --- a/compiler/rustc_ast/src/token.rs +++ b/compiler/rustc_ast/src/token.rs @@ -969,7 +969,13 @@ impl Token { } pub fn is_non_reserved_ident(&self) -> bool { - self.ident().is_some_and(|(id, raw)| raw == IdentIsRaw::Yes || !sp::Ident::is_reserved(id)) + self.non_reserved_ident().is_some() + } + + pub fn non_reserved_ident(&self) -> Option { + self.ident() + .filter(|&(id, raw)| raw == IdentIsRaw::Yes || !sp::Ident::is_reserved(id)) + .map(|(id, _)| id) } /// Returns `true` if the token is the identifier `true` or `false`. diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 6d4a0215eb7b3..53e87c2d7c220 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -337,66 +337,46 @@ impl<'a> Parser<'a> { HelpIdentifierStartsWithNumber { num_span: invalid } }); - let err = ExpectedIdentifier { + let mut err = self.dcx().create_err(ExpectedIdentifier { span: bad_token.span, token: bad_token, suggest_raw, suggest_remove_comma, help_cannot_start_number, - }; - let mut err = self.dcx().create_err(err); + }); - // if the token we have is a `<` - // it *might* be a misplaced generic - // FIXME: could we recover with this? + // If the token we have is a `<` it *might* be a misplaced generic + // parameter list as in `fn id(x: T) -> T { x }`. + // FIXME: Could we recover with this? if self.token == token::Lt { - // all keywords that could have generic applied - let valid_prev_keywords = - [kw::Fn, kw::Type, kw::Struct, kw::Enum, kw::Union, kw::Trait]; - - // If we've expected an identifier, - // and the current token is a '<' - // if the previous token is a valid keyword - // that might use a generic, then suggest a correct - // generic placement (later on) - let maybe_keyword = self.prev_token; - if valid_prev_keywords.into_iter().any(|x| maybe_keyword.is_keyword(x)) { - // if we have a valid keyword, attempt to parse generics - // also obtain the keywords symbol + // Let's check if the previous token could denote the start of an item + // whose kind can have generics. + if let Some((Ident { name, .. }, IdentIsRaw::No)) = self.prev_token.ident() + && let kw::Fn | kw::Type | kw::Struct | kw::Enum | kw::Union | kw::Trait = name + { match self.parse_generics() { - Ok(generic) => { - if let TokenKind::Ident(symbol, _) = maybe_keyword.kind { - let ident_name = symbol; - // at this point, we've found something like - // `fn id` - // and current token should be Ident with the item name (i.e. the function name) - // if there is a `<` after the fn name, then don't show a suggestion, show help - - if !self.look_ahead(1, |t| *t == token::Lt) - && let Ok(snippet) = - self.psess.source_map().span_to_snippet(generic.span) - { - err.multipart_suggestion( - format!("place the generic parameter name after the {ident_name} name"), - vec![ - (self.token.span.shrink_to_hi(), snippet), - (generic.span, String::new()) - ], - Applicability::MaybeIncorrect, - ); - } else { - err.help(format!( - "place the generic parameter name after the {ident_name} name" - )); - } + Ok(generics) => { + if !self.look_ahead(1, |t| *t == token::Lt) + && let Ok(snippet) = + self.psess.source_map().span_to_snippet(generics.span) + { + err.multipart_suggestion( + format!("place the generic parameter name after the {name} name"), + vec![ + (self.token.span.shrink_to_hi(), snippet), + (generics.span, String::new()), + ], + Applicability::MaybeIncorrect, + ); + } else { + err.help(format!( + "place the generic parameter name after the {name} name" + )); } } - Err(err) => { - // if there's an error parsing the generics, - // then don't do a misplaced generics suggestion - // and emit the expected ident error instead; - err.cancel(); - } + // It's unlikely that the user meant to write a generic parameter list. + // Let's not show them errors specific to generics. + Err(err) => err.cancel(), } } } @@ -602,15 +582,15 @@ impl<'a> Parser<'a> { ); } - if let TokenKind::Ident(symbol, _) = &self.prev_token.kind { - if ["def", "fun", "func", "function"].contains(&symbol.as_str()) { - err.span_suggestion_short( - self.prev_token.span, - format!("write `fn` instead of `{symbol}` to declare a function"), - "fn", - Applicability::MachineApplicable, - ); - } + if let Some((ident, IdentIsRaw::No)) = self.prev_token.ident() + && let "def" | "fun" | "func" | "function" = ident.name.as_str() + { + err.span_suggestion_short( + self.prev_token.span, + format!("write `fn` instead of `{}` to declare a function", ident.name), + "fn", + Applicability::MachineApplicable, + ); } if let TokenKind::Ident(prev, _) = &self.prev_token.kind diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 12de4957e99c2..8238a6518e41d 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1349,16 +1349,16 @@ impl<'a> Parser<'a> { self.bump(); // `[` let index = self.parse_expr()?; self.suggest_missing_semicolon_before_array(prev_token.span, open_delim_span)?; - self.expect(exp!(CloseBracket)).map_err(|mut e| { - if let TokenKind::Ident(_, _) = prev_token.kind { - e.span_suggestion_verbose( + self.expect(exp!(CloseBracket)).map_err(|mut err| { + if prev_token.is_non_reserved_ident() { + err.span_suggestion_verbose( prev_token.span.shrink_to_hi(), "you might have meant to call a macro", "!".to_string(), Applicability::MaybeIncorrect, ); } - e + err })?; Ok(self.mk_expr( lo.to(self.prev_token.span), @@ -3688,9 +3688,7 @@ impl<'a> Parser<'a> { fn is_try_block(&self) -> bool { self.token.is_keyword(kw::Try) && self.look_ahead(1, |t| { - *t == token::OpenBrace - || t.is_metavar_block() - || t.kind == TokenKind::Ident(sym::bikeshed, IdentIsRaw::No) + *t == token::OpenBrace || t.is_metavar_block() || t.is_keyword(sym::bikeshed) }) && self.token_uninterpolated_span().at_least_rust_2018() } @@ -3875,12 +3873,8 @@ impl<'a> Parser<'a> { // Peek the field's ident before parsing its expr in order to emit better diagnostics. let peek = self .token - .ident() - .filter(|(ident, is_raw)| { - (!ident.is_reserved() || matches!(is_raw, IdentIsRaw::Yes)) - && self.look_ahead(1, |tok| *tok == token::Colon) - }) - .map(|(ident, _)| ident); + .non_reserved_ident() + .filter(|_| self.look_ahead(1, |&tok| tok == token::Colon)); // We still want a field even if its expr didn't parse. let field_ident = |this: &Self, guar: ErrorGuaranteed| { diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 80c1eeb4ef041..b2afd1c51256a 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -714,9 +714,11 @@ impl<'a> Parser<'a> { } fn check_const_closure(&self) -> bool { + // FIXME(#146122): Parse `const async ...`, `const gen ...` & `const async gen ...` + // closures. We already parse `const static async ...` ones etc. + self.is_keyword_ahead(0, &[kw::Const]) - && self.look_ahead(1, |t| match &t.kind { - // async closures do not work with const closures, so we do not parse that here. + && self.look_ahead(1, |t| match t.uninterpolate().kind { token::Ident(kw::Move | kw::Use | kw::Static, IdentIsRaw::No) | token::OrOr | token::Or => true, diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 7c3752cfff187..ff057e61f7a04 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -1014,47 +1014,39 @@ impl<'a> Parser<'a> { break 'break_recover None; } - match &expr.kind { - ExprKind::Path(None, ast::Path { segments, .. }) - if let [segment] = segments.as_slice() => - { - if self.token == token::Colon - && self.look_ahead(1, |token| { - token.is_metavar_block() - || matches!( - token.kind, - token::Ident( - kw::For | kw::Loop | kw::While, - token::IdentIsRaw::No - ) | token::OpenBrace - ) + if self.token == token::Colon + && let ExprKind::Path(None, ast::Path { segments, .. }) = &expr.kind + && let [segment] = segments.as_slice() + && self.look_ahead(1, |t| { + t.is_metavar_block() + || t.kind == token::OpenBrace + || t.is_non_raw_ident_where(|ident| { + matches!(ident.name, kw::For | kw::Loop | kw::While) }) - { - let snapshot = self.create_snapshot_for_diagnostic(); - let label = Label { - ident: Ident::from_str_and_span( - &format!("'{}", segment.ident), - segment.ident.span, - ), - }; - match self.parse_expr_labeled(label, false) { - Ok(labeled_expr) => { - e.cancel(); - self.dcx().emit_err(MalformedLoopLabel { - span: label.ident.span, - suggestion: label.ident.span.shrink_to_lo(), - }); - *expr = labeled_expr; - break 'break_recover None; - } - Err(err) => { - err.cancel(); - self.restore_snapshot(snapshot); - } - } + }) + { + let snapshot = self.create_snapshot_for_diagnostic(); + let label = Label { + ident: Ident::from_str_and_span( + &format!("'{}", segment.ident), + segment.ident.span, + ), + }; + match self.parse_expr_labeled(label, false) { + Ok(labeled_expr) => { + e.cancel(); + self.dcx().emit_err(MalformedLoopLabel { + span: label.ident.span, + suggestion: label.ident.span.shrink_to_lo(), + }); + *expr = labeled_expr; + break 'break_recover None; + } + Err(err) => { + err.cancel(); + self.restore_snapshot(snapshot); } } - _ => {} } let res = diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index f62f8f1765652..88c07c6e2778c 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -915,18 +915,15 @@ impl<'a> Parser<'a> { /// Parses an `impl B0 + ... + Bn` type. fn parse_impl_ty(&mut self, impl_dyn_multi: &mut bool) -> PResult<'a, TyKind> { - if self.token.is_lifetime() { - self.look_ahead(1, |t| { - if let token::Ident(sym, _) = t.kind { - // parse pattern with "'a Sized" we're supposed to give suggestion like - // "'a + Sized" - self.dcx().emit_err(diagnostics::MissingPlusBounds { - span: self.token.span, - hi: self.token.span.shrink_to_hi(), - sym, - }); - } - }) + // If we encounter a type like `impl 'a Sized`, suggest `impl 'a + Sized`. + if self.token.is_lifetime() + && let Some(ident) = self.look_ahead(1, |t| t.non_reserved_ident()) + { + self.dcx().emit_err(diagnostics::MissingPlusBounds { + span: self.token.span, + hi: self.token.span.shrink_to_hi(), + sym: ident.name, + }); } // Always parse bounds greedily for better error recovery. diff --git a/tests/ui/consts/const-closures-interpolated-qualifier.rs b/tests/ui/consts/const-closures-interpolated-qualifier.rs new file mode 100644 index 0000000000000..568e592e35501 --- /dev/null +++ b/tests/ui/consts/const-closures-interpolated-qualifier.rs @@ -0,0 +1,22 @@ +// Ensure that we can still recognize const closures in the parser even if some of the other closure +// qualifiers were interpolated. + +//@ check-pass +#![feature(const_closures, const_destruct, const_trait_impl)] + +use std::marker::Destruct; + +macro_rules! make { + ($qual:ident $local:ident) => { + const $qual || { let _ = $local.len(); } + } +} + +const fn scope() { + let local = String::new(); + call(make!(move local)) +} + +const fn call(_: impl [const] FnOnce() + [const] Destruct + 'static) {} + +fn main() {}