diff --git a/compiler/rustc_ast/src/token.rs b/compiler/rustc_ast/src/token.rs index 07da107a55c58..20079dd039f6f 100644 --- a/compiler/rustc_ast/src/token.rs +++ b/compiler/rustc_ast/src/token.rs @@ -231,7 +231,7 @@ impl Lit { /// `Parser::eat_token_lit` (excluding unary negation). pub fn from_token(token: &Token) -> Option { match token.uninterpolate().kind { - Ident(name, IdentKind::Normal) if name.is_bool_lit() => { + Ident(name, IdentKind::Normal | IdentKind::ForcedKeyword) if name.is_bool_lit() => { Some(Lit::new(Bool, name, None)) } Literal(token_lit) => Some(token_lit), @@ -362,6 +362,7 @@ fn ident_can_begin_type(name: Symbol, span: Span, kind: IdentKind) -> bool { pub enum IdentKind { Normal, Raw, + ForcedKeyword, } impl IdentKind { @@ -369,12 +370,15 @@ impl IdentKind { match self { IdentKind::Normal => IdentPrintMode::Normal, IdentKind::Raw => IdentPrintMode::RawIdent, + IdentKind::ForcedKeyword => IdentPrintMode::ForcedKeywordIdent, } } + pub fn to_print_mode_lifetime(self) -> IdentPrintMode { match self { IdentKind::Normal => IdentPrintMode::Normal, IdentKind::Raw => IdentPrintMode::RawLifetime, + IdentKind::ForcedKeyword => unreachable!(), } } } @@ -637,7 +641,10 @@ impl Token { Token::new(TokenKind::Question, DUMMY_SP) } - /// Recovers a `Token` from an `Ident`. This creates a raw identifier if necessary. + /// Recovers a `Token` from an `Ident`. + /// + /// This creates a raw identifier if necessary. + /// It will never create a forced keyword. pub fn from_ast_ident(ident: sp::Ident) -> Self { let kind = if ident.is_raw_guess() { IdentKind::Raw } else { IdentKind::Normal }; Token::new(Ident(ident.name, kind), ident.span) @@ -763,7 +770,7 @@ impl Token { pub fn can_begin_const_arg(&self) -> bool { match self.kind { OpenBrace | Literal(..) | Minus => true, - Ident(name, IdentKind::Normal) if name.is_bool_lit() => true, + Ident(name, IdentKind::Normal | IdentKind::ForcedKeyword) if name.is_bool_lit() => true, OpenInvisible(InvisibleOrigin::MetaVar( MetaVarKind::Expr { .. } | MetaVarKind::Block | MetaVarKind::Literal, )) => true, @@ -812,7 +819,7 @@ impl Token { pub fn can_begin_literal_maybe_minus(&self) -> bool { match self.uninterpolate().kind { Literal(..) | Minus => true, - Ident(name, IdentKind::Normal) if name.is_bool_lit() => true, + Ident(name, IdentKind::Normal | IdentKind::ForcedKeyword) if name.is_bool_lit() => true, OpenInvisible(InvisibleOrigin::MetaVar(mv_kind)) => match mv_kind { MetaVarKind::Literal => true, MetaVarKind::Expr { can_begin_literal_maybe_minus, .. } => { @@ -962,7 +969,7 @@ impl Token { /// Returns `true` if the token is either a special identifier or a keyword. pub fn is_reserved_ident(&self) -> bool { - self.non_raw_ident().is_some_and(sp::Ident::is_reserved) + self.ident().is_some_and(|(id, kind)| ident_of_kind_is_reserved(id, kind)) } pub fn is_non_reserved_ident(&self) -> bool { @@ -970,15 +977,13 @@ impl Token { } pub fn non_reserved_ident(&self) -> Option { - self.ident() - .filter(|&(id, kind)| kind == IdentKind::Raw || !id.is_reserved()) - .map(|(id, _)| id) + self.ident().filter(|&(id, kind)| !ident_of_kind_is_reserved(id, kind)).map(|(id, _)| id) } /// If this token is a non-raw identifier, return the identifier in question. pub fn non_raw_ident(&self) -> Option { match self.ident() { - Some((id, IdentKind::Normal)) => Some(id), + Some((id, IdentKind::Normal | IdentKind::ForcedKeyword)) => Some(id), _ => None, } } @@ -1102,6 +1107,14 @@ impl PartialEq for Token { } } +pub fn ident_of_kind_is_reserved(id: sp::Ident, kind: IdentKind) -> bool { + match kind { + IdentKind::Normal => id.is_reserved(), + IdentKind::Raw => false, + IdentKind::ForcedKeyword => true, + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Encodable, Decodable, Hash, StableHash)] pub enum NtPatKind { // Matches or-patterns. Was written using `pat` in edition 2021 or later. diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 9d0ca67ce93d5..0232e83de841d 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -441,6 +441,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) { gate_all!(explicit_tail_calls, "`become` expression is experimental"); gate_all!(final_associated_functions, "`final` on trait functions is experimental"); gate_all!(fn_delegation, "functions delegation is not yet fully implemented"); + gate_all!(forced_keywords, "forced keywords are experimental"); gate_all!(frontmatter, "frontmatters are experimental"); gate_all!(gca_min_const_items, "unbraced const blocks as const args are experimental"); gate_all!(gen_blocks, "gen blocks are experimental"); diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index 6637ed356f557..5dcc8ce69c6d1 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -370,12 +370,10 @@ fn space_between(tt1: &TokenTree, tt2: &TokenTree) -> bool { // IDENT|`fn`|`Self`|`pub` + `(`: `f(3)`, `fn(x: u8)`, `Self()`, `pub(crate)`, // but `let (a, b) = (1, 2)` needs a space after the `let` - (Tok(tk::Token { kind: tk::Ident(sym, kind), span }, _), Del(_, _, Parenthesis, _)) - if !Ident::new(*sym, *span).is_reserved() - || *sym == kw::Fn - || *sym == kw::SelfUpper - || *sym == kw::Pub - || matches!(kind, tk::IdentKind::Raw) => + (&Tok(tk::Token { kind: tk::Ident(sym, kind), span }, _), Del(_, _, Parenthesis, _)) + if kind == tk::IdentKind::Raw + || matches!(sym, kw::Fn | kw::SelfUpper | kw::Pub) + || !Ident::new(sym, span).is_reserved() => { false } diff --git a/compiler/rustc_expand/src/mbe/macro_check.rs b/compiler/rustc_expand/src/mbe/macro_check.rs index e28044d7632a9..f070009fc3d57 100644 --- a/compiler/rustc_expand/src/mbe/macro_check.rs +++ b/compiler/rustc_expand/src/mbe/macro_check.rs @@ -397,7 +397,8 @@ fn check_nested_occurrences( ( NestedMacroState::Empty, &TokenTree::Token(Token { - kind: TokenKind::Ident(name, IdentKind::Normal), .. + kind: TokenKind::Ident(name, IdentKind::Normal | IdentKind::ForcedKeyword), + .. }), ) => { if name == kw::MacroRules { diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index d762763b8dcea..12f308fc546c3 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -1750,7 +1750,7 @@ fn is_in_follow(tok: &mbe::TokenTree, kind: NonterminalKind) -> IsInFollow { match tok { TokenTree::Token(token) => match token.kind { FatArrow | Comma | Eq | Or => IsInFollow::Yes, - Ident(name, IdentKind::Normal) if name == kw::If || name == kw::In => { + Ident(kw::If | kw::In, IdentKind::Normal | IdentKind::ForcedKeyword) => { IsInFollow::Yes } _ => IsInFollow::No(TOKENS), @@ -1764,7 +1764,7 @@ fn is_in_follow(tok: &mbe::TokenTree, kind: NonterminalKind) -> IsInFollow { match tok { TokenTree::Token(token) => match token.kind { FatArrow | Comma | Eq => IsInFollow::Yes, - Ident(name, IdentKind::Normal) if name == kw::If || name == kw::In => { + Ident(kw::If | kw::In, IdentKind::Normal | IdentKind::ForcedKeyword) => { IsInFollow::Yes } _ => IsInFollow::No(TOKENS), @@ -1792,7 +1792,7 @@ fn is_in_follow(tok: &mbe::TokenTree, kind: NonterminalKind) -> IsInFollow { TokenTree::Token(token) => match token.kind { OpenBrace | OpenBracket | Comma | FatArrow | Colon | Eq | Gt | Shr | Semi | Or => IsInFollow::Yes, - Ident(name, IdentKind::Normal) if name == kw::As || name == kw::Where => { + Ident(kw::As | kw::Where, IdentKind::Normal | IdentKind::ForcedKeyword) => { IsInFollow::Yes } _ => IsInFollow::No(TOKENS), diff --git a/compiler/rustc_expand/src/mbe/metavar_expr.rs b/compiler/rustc_expand/src/mbe/metavar_expr.rs index 9774165c73400..807d4fae6de15 100644 --- a/compiler/rustc_expand/src/mbe/metavar_expr.rs +++ b/compiler/rustc_expand/src/mbe/metavar_expr.rs @@ -9,7 +9,6 @@ use rustc_span::{Ident, Span, Symbol, sym}; use crate::diagnostics; -pub(crate) const RAW_IDENT_ERR: &str = "`${concat(..)}` currently does not support raw identifiers"; pub(crate) const UNSUPPORTED_CONCAT_ELEM_ERR: &str = "expected identifier or string literal"; /// A meta-variable expression, for expansions based on properties of meta-variables. @@ -197,6 +196,10 @@ fn parse_concat<'psess>( } else { match parse_ident_from_token(psess, token) { Err(err) => { + // FIXME: Canceling this error means we emit a worse message when encountering + // raw identifiers (`r#ident`). However, we also don't want to forward + // (the current version of) this error as is since we also want to + // mentioning string literals as a valid token kind. err.cancel(); return Err(psess .dcx() @@ -281,9 +284,7 @@ fn parse_ident_from_token<'psess>( token: &Token, ) -> PResult<'psess, Ident> { if let Some((elem, kind)) = token.ident() { - if let IdentKind::Raw = kind { - return Err(psess.dcx().struct_span_err(elem.span, RAW_IDENT_ERR)); - } + validate_ident_kind(psess.dcx(), kind, elem.span)?; return Ok(elem); } let token_str = pprust::token_to_string(token); @@ -347,3 +348,16 @@ fn eat_dollar<'psess>( "meta-variables within meta-variable expressions must be referenced using a dollar sign", )) } + +pub(crate) fn validate_ident_kind<'a>( + dcx: rustc_errors::DiagCtxtHandle<'a>, + kind: IdentKind, + span: Span, +) -> PResult<'a, ()> { + let kind = match kind { + IdentKind::Normal => return Ok(()), + IdentKind::Raw => "raw identifiers", + IdentKind::ForcedKeyword => "forced keywords", + }; + Err(dcx.struct_span_err(span, format!("`${{concat(..)}}` currently does not support {kind}"))) +} diff --git a/compiler/rustc_expand/src/mbe/quoted.rs b/compiler/rustc_expand/src/mbe/quoted.rs index ff1607c9e4456..80e258e94510c 100644 --- a/compiler/rustc_expand/src/mbe/quoted.rs +++ b/compiler/rustc_expand/src/mbe/quoted.rs @@ -337,10 +337,13 @@ fn parse_tree<'a>( // `tree` is followed by an `ident`. This could be `$meta_var` or the `$crate` // special metavariable that names the crate of the invocation. - Some(tokenstream::TokenTree::Token(token, _)) if token.is_ident() => { - let (ident, kind) = token.ident().unwrap(); + Some(tokenstream::TokenTree::Token(token, _)) + if let Some((ident, kind)) = token.ident() => + { let span = ident.span.with_lo(dollar_span.lo()); - if ident.name == kw::Crate && matches!(kind, IdentKind::Normal) { + if let kw::Crate = ident.name + && let IdentKind::Normal | IdentKind::ForcedKeyword = kind + { TokenTree::token(token::Ident(kw::DollarCrate, kind), span) } else { TokenTree::MetaVar(span, ident) diff --git a/compiler/rustc_expand/src/mbe/transcribe.rs b/compiler/rustc_expand/src/mbe/transcribe.rs index 8d036972ad592..736a8d1de4c19 100644 --- a/compiler/rustc_expand/src/mbe/transcribe.rs +++ b/compiler/rustc_expand/src/mbe/transcribe.rs @@ -25,7 +25,7 @@ use crate::diagnostics::{ }; use crate::mbe::macro_parser::NamedMatch; use crate::mbe::macro_parser::NamedMatch::*; -use crate::mbe::metavar_expr::{MetaVarExprConcatElem, RAW_IDENT_ERR}; +use crate::mbe::metavar_expr::{MetaVarExprConcatElem, validate_ident_kind}; use crate::mbe::{self, KleeneOp, MetaVarExpr}; /// Context needed to perform transcription of metavariable expressions. @@ -1033,21 +1033,15 @@ fn extract_symbol_from_pnr<'a>( ) -> PResult<'a, Symbol> { match pnr { ParseNtResult::Ident(nt_ident, kind) => { - if let IdentKind::Raw = kind { - Err(dcx.struct_span_err(span_err, RAW_IDENT_ERR)) - } else { - Ok(nt_ident.name) - } + validate_ident_kind(dcx, *kind, span_err)?; + Ok(nt_ident.name) } ParseNtResult::Tt(TokenTree::Token( Token { kind: TokenKind::Ident(symbol, kind), .. }, _, )) => { - if let IdentKind::Raw = kind { - Err(dcx.struct_span_err(span_err, RAW_IDENT_ERR)) - } else { - Ok(*symbol) - } + validate_ident_kind(dcx, *kind, span_err)?; + Ok(*symbol) } ParseNtResult::Tt(TokenTree::Token( Token { diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs index 1ecf808f78839..ae985050fe71e 100644 --- a/compiler/rustc_expand/src/proc_macro_server.rs +++ b/compiler/rustc_expand/src/proc_macro_server.rs @@ -11,7 +11,8 @@ use rustc_parse::lexer::{StripTokens, nfc_normalize}; use rustc_parse::parser::Parser; use rustc_parse::{exp, new_parser_from_source_str, source_str_to_stream}; use rustc_proc_macro::bridge::{ - DelimSpan, Diagnostic, ExpnGlobals, Group, Ident, LitKind, Literal, Punct, TokenTree, server, + DelimSpan, Diagnostic, ExpnGlobals, Group, Ident, IdentKind, LitKind, Literal, Punct, + TokenTree, server, }; use rustc_proc_macro::{Delimiter, Level}; use rustc_session::Session; @@ -103,6 +104,26 @@ impl ToInternal for LitKind { } } +impl FromInternal for IdentKind { + fn from_internal(kind: tk::IdentKind) -> Self { + match kind { + tk::IdentKind::Normal => IdentKind::Normal, + tk::IdentKind::Raw => IdentKind::Raw, + tk::IdentKind::ForcedKeyword => IdentKind::ForcedKeyword, + } + } +} + +impl ToInternal for IdentKind { + fn to_internal(self) -> tk::IdentKind { + match self { + IdentKind::Normal => tk::IdentKind::Normal, + IdentKind::Raw => tk::IdentKind::Raw, + IdentKind::ForcedKeyword => tk::IdentKind::ForcedKeyword, + } + } +} + impl FromInternal for Vec> { fn from_internal(stream: TokenStream) -> Self { // Estimate the capacity as `stream.len()` rounded up to the next power @@ -127,7 +148,7 @@ impl FromInternal for Vec> { } trees.push(TokenTree::Group(Group { - delimiter: rustc_proc_macro::Delimiter::from_internal(delim), + delimiter: Delimiter::from_internal(delim), stream: Some(stream), span: DelimSpan { open: span.open, @@ -230,12 +251,12 @@ impl FromInternal for Vec> { tk::Ident(sym, kind) => trees.push(TokenTree::Ident(Ident { sym, - is_raw: matches!(kind, tk::IdentKind::Raw), + kind: IdentKind::from_internal(kind), span, })), tk::NtIdent(ident, kind) => trees.push(TokenTree::Ident(Ident { sym: ident.name, - is_raw: matches!(kind, tk::IdentKind::Raw), + kind: IdentKind::from_internal(kind), span: ident.span, })), @@ -245,7 +266,7 @@ impl FromInternal for Vec> { TokenTree::Punct(Punct { ch: b'\'', joint: true, span }), TokenTree::Ident(Ident { sym: ident.name, - is_raw: matches!(kind, tk::IdentKind::Raw), + kind: IdentKind::from_internal(kind), span, }), ]); @@ -254,7 +275,7 @@ impl FromInternal for Vec> { let stream = TokenStream::token_alone(tk::Lifetime(ident.name, kind), ident.span); trees.push(TokenTree::Group(Group { - delimiter: rustc_proc_macro::Delimiter::None, + delimiter: Delimiter::None, stream: Some(stream), span: DelimSpan::from_single(span), })) @@ -286,7 +307,7 @@ impl FromInternal for Vec> { trees.push(TokenTree::Punct(Punct { ch: b'!', joint: false, span })); } trees.push(TokenTree::Group(Group { - delimiter: rustc_proc_macro::Delimiter::Bracket, + delimiter: Delimiter::Bracket, stream: Some(stream), span: DelimSpan::from_single(span), })); @@ -364,17 +385,16 @@ impl ToInternal> stream.unwrap_or_default(), )] } - TokenTree::Ident(self::Ident { sym, is_raw, span }) => { + TokenTree::Ident(Ident { sym, kind, span }) => { rustc.psess().symbol_gallery.insert(sym, span); - let kind = if is_raw { tk::IdentKind::Raw } else { tk::IdentKind::Normal }; - smallvec![tokenstream::TokenTree::token_alone(tk::Ident(sym, kind), span)] + smallvec![tokenstream::TokenTree::token_alone( + tk::Ident(sym, kind.to_internal()), + span + )] } - TokenTree::Literal(self::Literal { - kind: self::LitKind::Integer, - symbol, - suffix, - span, - }) if let Some(symbol) = symbol.as_str().strip_prefix('-') => { + TokenTree::Literal(self::Literal { kind: LitKind::Integer, symbol, suffix, span }) + if let Some(symbol) = symbol.as_str().strip_prefix('-') => + { let symbol = Symbol::intern(symbol); let integer = tk::TokenKind::lit(tk::Integer, symbol, suffix); let a = tokenstream::TokenTree::token_joint_hidden(tk::Minus, span); diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 49489c0ed903d..35c0a28a18f8f 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -542,6 +542,8 @@ declare_features! ( (incomplete, fn_delegation, "1.76.0", Some(118212)), /// Traits for function pointers and items (unstable, fn_static, "CURRENT_RUSTC_VERSION", Some(148768)), + /// Allows using forced keywords `k#fn`. + (unstable, forced_keywords, "CURRENT_RUSTC_VERSION", Some(153839)), /// Allows impls for the Freeze trait. (internal, freeze_impls, "1.78.0", Some(121675)), /// Frontmatter `---` blocks for use by external tools. diff --git a/compiler/rustc_lexer/src/lib.rs b/compiler/rustc_lexer/src/lib.rs index dc6e3b1f358dc..043b7ad59d0a8 100644 --- a/compiler/rustc_lexer/src/lib.rs +++ b/compiler/rustc_lexer/src/lib.rs @@ -101,6 +101,9 @@ pub enum TokenKind { /// A raw identifier, e.g. "r#ident". RawIdent, + /// A forced keyword like `k#fn`. + ForcedKeywordIdent, + /// An unknown literal prefix, like `foo#`, `foo'`, `foo"`. Excludes /// literal prefixes that contain emoji, which are considered "invalid". /// @@ -574,7 +577,11 @@ impl<'a> Cursor<'a> { // Raw identifier, raw string literal or identifier. 'r' => match (self.first(), self.second()) { - ('#', c1) if is_id_start(c1) => self.raw_ident(), + ('#', c1) if is_id_start(c1) => { + self.bump(); // `#` + self.eat_identifier(); + RawIdent + } ('#', _) | ('"', _) => { let res = self.raw_double_quoted_string(1); let suffix_start = self.pos_within_token(); @@ -587,6 +594,16 @@ impl<'a> Cursor<'a> { _ => self.ident_or_unknown_prefix(), }, + // Forced keyword identifier or identifier. + 'k' => match (self.first(), self.second()) { + ('#', c1) if is_id_start(c1) => { + self.bump(); // `#` + self.eat_identifier(); + ForcedKeywordIdent + } + _ => self.ident_or_unknown_prefix(), + }, + // Byte literal, byte string literal, raw byte string literal or identifier. 'b' => self.c_or_byte_string( |terminated| ByteStr { terminated }, @@ -823,15 +840,6 @@ impl<'a> Cursor<'a> { Whitespace } - fn raw_ident(&mut self) -> TokenKind { - debug_assert!(self.prev() == 'r' && self.first() == '#' && is_id_start(self.second())); - // Eat "#" symbol. - self.bump(); - // Eat the identifier part of RawIdent. - self.eat_identifier(); - RawIdent - } - fn ident_or_unknown_prefix(&mut self) -> TokenKind { debug_assert!(is_id_start(self.prev())); // Start is already eaten, eat the rest of identifier. diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index 787747ff5daf9..03d5634fc359c 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -240,6 +240,33 @@ impl<'psess, 'src> Lexer<'psess, 'src> { self.psess.raw_identifier_spans.push(span); token::Ident(sym, IdentKind::Raw) } + rustc_lexer::TokenKind::ForcedKeywordIdent => { + let span = self.mk_sp(start, self.pos); + + if span.edition().at_least_rust_2021() { + let sym = nfc_normalize(self.str_from(start + BytePos(2))); + self.psess.symbol_gallery.insert(sym, span); + self.psess.gated_spans.gate(sym::forced_keywords, span); + token::Ident(sym, IdentKind::ForcedKeyword) + } else { + // Reset the state so that only the `k` was consumed. + self.pos = start + BytePos(1); + self.cursor = Cursor::new(&str_before[1..], FrontmatterAllowed::No); + + self.psess.buffer_lint( + RUST_2021_PREFIXES_INCOMPATIBLE_SYNTAX, + span, + ast::CRATE_NODE_ID, + crate::diagnostics::ReservedPrefixLint { + subject: "this".into(), + kind: "forced keyword", + edition: Edition::Edition2021, + sugg: self.mk_sp(start, self.pos).shrink_to_hi(), + } + ); + self.ident(start) + } + } rustc_lexer::TokenKind::UnknownPrefix => { self.report_unknown_prefix(start); self.ident(start) diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index bb8be3c9c509f..e7073a4d81978 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -243,18 +243,19 @@ impl<'a> Parser<'a> { 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 = if self.token == token::Comma + && let Some(ident) = self.look_ahead(1, Token::ident) + { + if recover { + self.bump(); + recovered_ident = Some(ident); }; + 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); diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index a5c31672a2bc7..9e74a7b1ab38c 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -745,8 +745,7 @@ impl<'a> Parser<'a> { lo: Span, ) -> PResult<'a, Box> { let mut res = loop { - let has_question = if self.prev_token == TokenKind::Ident(kw::Return, IdentKind::Normal) - { + let has_question = if self.prev_token.is_keyword(kw::Return) { // We are using noexpect here because we don't expect a `?` directly after // a `return` which could be suggested otherwise. self.eat_noexpect(&token::Question) @@ -758,7 +757,7 @@ impl<'a> Parser<'a> { e = self.mk_expr(lo.to(self.prev_token.span), ExprKind::Try(e)); continue; } - let has_dot = if self.prev_token == TokenKind::Ident(kw::Return, IdentKind::Normal) { + let has_dot = if self.prev_token.is_keyword(kw::Return) { // We are using noexpect here because we don't expect a `.` directly after // a `return` which could be suggested otherwise. self.eat_noexpect(&token::Dot) @@ -1447,6 +1446,7 @@ impl<'a> Parser<'a> { // or `async gen {}` and `async gen move {}` // FIXME: (async) gen closures aren't yet parsed. // FIXME(gen_blocks): Parse `gen async` and suggest swap + // FIXME(forced_keywords): Allow k#gen blocks prior to Rust 2024, too! if this.token_uninterpolated_span().at_least_rust_2024() && this.is_gen_block(kw::Gen, at_async as usize) { @@ -2092,7 +2092,9 @@ impl<'a> Parser<'a> { } }; match self.token.uninterpolate().kind { - token::Ident(name, IdentKind::Normal) if name.is_bool_lit() => { + token::Ident(name, IdentKind::Normal | IdentKind::ForcedKeyword) + if name.is_bool_lit() => + { self.bump(); Some(token::Lit::new(token::Bool, name, None)) } diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 3ec86dfacbd06..9fb16bc3fbf27 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -2507,8 +2507,7 @@ impl<'a> Parser<'a> { /// for better diagnostics and suggestions. fn parse_field_ident(&mut self, adt_ty: &str, lo: Span) -> PResult<'a, Ident> { let (ident, kind) = self.ident_or_err(true)?; - if kind == IdentKind::Normal - && ident.is_reserved() + if token::ident_of_kind_is_reserved(ident, kind) && !(ident.name == kw::Underscore && adt_ty == "enum") { let snapshot = self.create_snapshot_for_diagnostic(); diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 50ded81e3eeec..99d5a1fa6f257 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -459,15 +459,17 @@ impl<'a> Parser<'a> { pub(crate) fn parse_ident_common(&mut self, recover: bool) -> PResult<'a, Ident> { let (ident, kind) = self.ident_or_err(recover)?; - if kind == IdentKind::Normal && ident.is_reserved() { + if token::ident_of_kind_is_reserved(ident, kind) { let err = self.expected_ident_found_err(); - if recover { + if recover && kind != IdentKind::ForcedKeyword { err.emit(); } else { return Err(err); } } + self.bump(); + Ok(ident) } @@ -722,7 +724,10 @@ impl<'a> Parser<'a> { self.is_keyword_ahead(0, &[kw::Const]) && self.look_ahead(1, |t| match t.uninterpolate().kind { - token::Ident(kw::Move | kw::Use | kw::Static, IdentKind::Normal) + token::Ident( + kw::Move | kw::Use | kw::Static, + IdentKind::Normal | IdentKind::ForcedKeyword, + ) | token::OrOr | token::Or => true, _ => false, diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index 1ab07a626d1c0..f53cfc792bd69 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -353,7 +353,7 @@ impl<'a> Parser<'a> { matches!( &token.uninterpolate().kind, token::FatArrow // e.g. `a | => 0,`. - | token::Ident(kw::If, token::IdentKind::Normal) // e.g. `a | if expr`. + | token::Ident(kw::If, token::IdentKind::Normal | token::IdentKind::ForcedKeyword) // e.g. `a | if expr`. | token::Eq // e.g. `let a | = 0`. | token::Semi // e.g. `let a |;`. | token::Colon // e.g. `let a | :`. diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index e62ad42875248..93a83ddf213c1 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1028,6 +1028,7 @@ symbols! { forall, forbid, force_target_feature, + forced_keywords, forget, format_args, format_args_capture, @@ -2583,6 +2584,7 @@ impl fmt::Display for Ident { pub enum IdentPrintMode { Normal, RawIdent, + ForcedKeywordIdent, RawLifetime, } @@ -2643,6 +2645,10 @@ impl fmt::Display for IdentPrinter { f.write_str("r#")?; self.symbol } + IdentPrintMode::ForcedKeywordIdent => { + f.write_str("k#")?; + self.symbol + } IdentPrintMode::RawLifetime => { f.write_str("'r#")?; let s = self diff --git a/library/proc_macro/src/bridge/mod.rs b/library/proc_macro/src/bridge/mod.rs index 0761549b2e30d..4b12ed0058847 100644 --- a/library/proc_macro/src/bridge/mod.rs +++ b/library/proc_macro/src/bridge/mod.rs @@ -222,6 +222,7 @@ mark_noop! { usize, Delimiter, LitKind, + IdentKind, Level, Bound, Range, @@ -385,11 +386,36 @@ compound_traits!(struct Punct { ch, joint, span }); #[derive(Copy, Clone, Eq, PartialEq)] pub struct Ident { pub sym: Symbol, - pub is_raw: bool, + pub kind: IdentKind, pub span: Span, } -compound_traits!(struct Ident { sym, is_raw, span }); +compound_traits!(struct Ident { sym, kind, span }); + +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum IdentKind { + Normal, + Raw, + ForcedKeyword, +} + +impl IdentKind { + pub fn prefix(self) -> Option<&'static str> { + match self { + Self::Normal => None, + Self::Raw => Some("r#"), + Self::ForcedKeyword => Some("k#"), + } + } +} + +rpc_encode_decode!( + enum IdentKind { + Normal, + Raw, + ForcedKeyword, + } +); #[derive(Clone, Eq, PartialEq)] pub struct Literal { diff --git a/library/proc_macro/src/lib.rs b/library/proc_macro/src/lib.rs index b8ffcd53b25d8..e7519a8c018f2 100644 --- a/library/proc_macro/src/lib.rs +++ b/library/proc_macro/src/lib.rs @@ -1197,7 +1197,7 @@ impl Ident { pub fn new(string: &str, span: Span) -> Ident { Ident(bridge::Ident { sym: bridge::client::Symbol::new_ident(string, false), - is_raw: false, + kind: bridge::IdentKind::Normal, span: span.0, }) } @@ -1210,7 +1210,7 @@ impl Ident { pub fn new_raw(string: &str, span: Span) -> Ident { Ident(bridge::Ident { sym: bridge::client::Symbol::new_ident(string, true), - is_raw: true, + kind: bridge::IdentKind::Raw, span: span.0, }) } @@ -1234,8 +1234,8 @@ impl Ident { #[stable(feature = "proc_macro_lib2", since = "1.29.0")] impl fmt::Display for Ident { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.0.is_raw { - f.write_str("r#")?; + if let Some(prefix) = self.0.kind.prefix() { + f.write_str(prefix)?; } fmt::Display::fmt(&self.0.sym, f) } diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 9c73e3b4ad687..6ef5de0b20aba 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -1297,6 +1297,7 @@ impl<'src> Classifier<'src> { TokenKind::RawIdent | TokenKind::UnknownPrefix | TokenKind::InvalidIdent => { Class::Ident(new_span(before, text, file_span)) } + TokenKind::ForcedKeywordIdent => Class::KeyWord, TokenKind::Lifetime { .. } | TokenKind::RawLifetime | TokenKind::UnknownPrefixLifetime => Class::Lifetime, diff --git a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs index ec994b731bf95..f449809911c90 100644 --- a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs +++ b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs @@ -246,6 +246,8 @@ impl<'a> Converter<'a> { } rustc_lexer::TokenKind::RawIdent => IDENT, + #[cfg(feature = "in-rust-tree")] + rustc_lexer::TokenKind::ForcedKeywordIdent => ERROR, rustc_lexer::TokenKind::GuardedStrPrefix if self.edition.at_least_2024() => { // FIXME: rustc does something better for recovery. diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs index b9b6247b54fab..55ccc98f4f23e 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs @@ -699,14 +699,17 @@ impl<'a, T: SpanTransformer> proc_macro_srv::TokenTree::Ident(ident) => { let idx = self.ident.len() as u32; let id = self.token_id_of(ident.span); + // NOTE(forced_keywords): Let's not bother to preserve forced keywords in this + // legacy protocol. + let is_raw = matches!(ident.kind, proc_macro_srv::IdentKind::Raw); let text = if self.version >= EXTENDED_LEAF_DATA { self.intern(ident.sym.as_str()) - } else if ident.is_raw { + } else if is_raw { self.intern_owned(format!("r#{}", ident.sym.as_str(),)) } else { self.intern(ident.sym.as_str()) }; - self.ident.push(IdentRepr { id, text, is_raw: ident.is_raw }); + self.ident.push(IdentRepr { id, text, is_raw }); (idx << 2) | 0b11 } }; @@ -944,7 +947,10 @@ impl Reader<'_, T> { proc_macro_srv::TokenTree::Ident(proc_macro_srv::Ident { sym: Symbol::intern(text), span: read_span(repr.id), - is_raw: is_raw.yes(), + kind: match is_raw { + tt::IdentIsRaw::Yes => proc_macro_srv::IdentKind::Raw, + tt::IdentIsRaw::No => proc_macro_srv::IdentKind::Normal, + }, }) } other => panic!("bad tag: {other}"), diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/bridge.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/bridge.rs index fc62f9413a34e..e573e57c8b7b1 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/bridge.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/bridge.rs @@ -2,7 +2,7 @@ use rustc_proc_macro::bridge as pm_bridge; -pub use pm_bridge::{DelimSpan, Diagnostic, ExpnGlobals, LitKind}; +pub use pm_bridge::{DelimSpan, Diagnostic, ExpnGlobals, IdentKind, LitKind}; pub type TokenTree = pm_bridge::TokenTree, S, intern::Symbol>; diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs index 5201bb6aeb86b..7038152ac2651 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/token_stream.rs @@ -7,7 +7,7 @@ use intern::Symbol; use rustc_lexer::{DocStyle, LiteralKind}; use rustc_proc_macro::Delimiter; -use crate::bridge::{DelimSpan, Group, Ident, LitKind, Literal, Punct, TokenTree}; +use crate::bridge::{DelimSpan, Group, Ident, IdentKind, LitKind, Literal, Punct, TokenTree}; /// Trait for allowing tests to parse tokenstreams with dynamic span ranges pub(crate) trait SpanLike { @@ -200,7 +200,7 @@ impl TokenStream { stream: Some(TokenStream::new(vec![ TokenTree::Ident(Ident { sym: Symbol::intern("doc"), - is_raw: false, + kind: IdentKind::Normal, span, }), TokenTree::Punct(Punct { ch: b'=', joint: false, span }), @@ -226,7 +226,7 @@ impl TokenStream { stream: Some(TokenStream::new(vec![ TokenTree::Ident(Ident { sym: Symbol::intern("doc"), - is_raw: false, + kind: IdentKind::Normal, span, }), TokenTree::Punct(Punct { ch: b'=', joint: false, span }), @@ -267,7 +267,7 @@ impl TokenStream { } rustc_lexer::TokenKind::Ident => tokenstream.push(TokenTree::Ident(Ident { sym: Symbol::intern(&s[range.clone()]), - is_raw: false, + kind: IdentKind::Normal, span: span.derive_ranged(range), })), rustc_lexer::TokenKind::InvalidIdent => { @@ -277,7 +277,15 @@ impl TokenStream { let range = range.start + 2..range.end; tokenstream.push(TokenTree::Ident(Ident { sym: Symbol::intern(&s[range.clone()]), - is_raw: true, + kind: IdentKind::Raw, + span: span.derive_ranged(range), + })) + } + rustc_lexer::TokenKind::ForcedKeywordIdent => { + let range = range.start + 2..range.end; + tokenstream.push(TokenTree::Ident(Ident { + sym: Symbol::intern(&s[range.clone()]), + kind: IdentKind::ForcedKeyword, span: span.derive_ranged(range), })) } @@ -298,7 +306,7 @@ impl TokenStream { })); tokenstream.push(TokenTree::Ident(Ident { sym: Symbol::intern(&s[range.clone()]), - is_raw: true, + kind: IdentKind::Raw, span: span.derive_ranged(range), })) } @@ -314,7 +322,7 @@ impl TokenStream { })); tokenstream.push(TokenTree::Ident(Ident { sym: Symbol::intern(&s[range.clone()]), - is_raw: false, + kind: IdentKind::Normal, span: span.derive_ranged(range), })) } @@ -484,9 +492,9 @@ fn display_token_tree( *emit_whitespace = !*joint; write!(f, "{}", *ch as char)?; } - TokenTree::Ident(Ident { sym, is_raw, span: _ }) => { - if *is_raw { - write!(f, "r#")?; + TokenTree::Ident(Ident { sym, kind, span: _ }) => { + if let Some(prefix) = kind.prefix() { + write!(f, "{prefix}")?; } write!(f, "{sym}")?; *emit_whitespace = true; @@ -613,10 +621,10 @@ fn debug_token_tree( *ch as char, if *joint { "[joint]" } else { "[alone]" } )?, - TokenTree::Ident(Ident { sym, is_raw, span }) => { + TokenTree::Ident(Ident { sym, kind, span }) => { write!(f, "IDENT {span:#?} ")?; - if *is_raw { - write!(f, "r#")?; + if let Some(prefix) = kind.prefix() { + write!(f, "{prefix}")?; } write!(f, "{sym}")?; } diff --git a/tests/ui/parser/forced-keywords/basic.rs b/tests/ui/parser/forced-keywords/basic.rs new file mode 100644 index 0000000000000..7a547b8f3d667 --- /dev/null +++ b/tests/ui/parser/forced-keywords/basic.rs @@ -0,0 +1,23 @@ +//@ edition: 2021.. +//@ check-pass +#![feature(forced_keywords)] + +k#mod module { + k#pub(k#in k#super) k#static _DATA: i32 = 0i8 k#as k#_; +} + +k#use ::std::process::Termination; + +k#fn main() -> k#impl k#self::Termination { + k#let k#true = k#false k#else { k#return }; + + k#let k#ref k#mut _x: (); + + k#const k#fn perform() -> k#impl Sized { + k#loop { + k#break k#match () { () k#if k#true => {} k#_ => {} }; + } + } + + perform(); +} diff --git a/tests/ui/parser/forced-keywords/decl-macro-matching.rs b/tests/ui/parser/forced-keywords/decl-macro-matching.rs new file mode 100644 index 0000000000000..dbe0be0527d2b --- /dev/null +++ b/tests/ui/parser/forced-keywords/decl-macro-matching.rs @@ -0,0 +1,18 @@ +// Demonstrate that decl macros can tell apart forced keywords from other kinds +// of identifiers when matching. +// +//@ edition: 2021.. +#![feature(forced_keywords)] + +macro_rules! accept { + (ForcedKeyword k#type) => {}; + (Normal type) => {}; +} + +accept!(ForcedKeyword type); //~ ERROR no rules expected keyword `type` +accept!(ForcedKeyword r#type); //~ ERROR no rules expected `r#type` +accept!(ForcedKeyword k#type); // OK + +accept!(Normal k#type); //~ ERROR no rules expected keyword `k#type` + +fn main() {} diff --git a/tests/ui/parser/forced-keywords/decl-macro-matching.stderr b/tests/ui/parser/forced-keywords/decl-macro-matching.stderr new file mode 100644 index 0000000000000..36659142776d9 --- /dev/null +++ b/tests/ui/parser/forced-keywords/decl-macro-matching.stderr @@ -0,0 +1,47 @@ +error: no rules expected keyword `type` + --> $DIR/decl-macro-matching.rs:12:23 + | +LL | macro_rules! accept { + | ------------------- when calling this macro +... +LL | accept!(ForcedKeyword type); + | ^^^^ no rules expected this token in macro call + | +note: while trying to match keyword `k#type` + --> $DIR/decl-macro-matching.rs:8:20 + | +LL | (ForcedKeyword k#type) => {}; + | ^^^^^^ + +error: no rules expected `r#type` + --> $DIR/decl-macro-matching.rs:13:23 + | +LL | macro_rules! accept { + | ------------------- when calling this macro +... +LL | accept!(ForcedKeyword r#type); + | ^^^^^^ no rules expected this token in macro call + | +note: while trying to match keyword `k#type` + --> $DIR/decl-macro-matching.rs:8:20 + | +LL | (ForcedKeyword k#type) => {}; + | ^^^^^^ + +error: no rules expected keyword `k#type` + --> $DIR/decl-macro-matching.rs:16:16 + | +LL | macro_rules! accept { + | ------------------- when calling this macro +... +LL | accept!(Normal k#type); + | ^^^^^^ no rules expected this token in macro call + | +note: while trying to match keyword `type` + --> $DIR/decl-macro-matching.rs:9:13 + | +LL | (Normal type) => {}; + | ^^^^ + +error: aborting due to 3 previous errors + diff --git a/tests/ui/parser/forced-keywords/feature-gate-forced-keywords.rs b/tests/ui/parser/forced-keywords/feature-gate-forced-keywords.rs new file mode 100644 index 0000000000000..39b52d382db9b --- /dev/null +++ b/tests/ui/parser/forced-keywords/feature-gate-forced-keywords.rs @@ -0,0 +1,10 @@ +//@ edition: 2021.. + +k#fn main() {} //~ ERROR forced keywords are experimental + +#[cfg(false)] +k#fn start() {} //~ ERROR forced keywords are experimental + +macro_rules! discard { ($($tt:tt)*) => {} } + +discard!(k#static); //~ ERROR forced keywords are experimental diff --git a/tests/ui/parser/forced-keywords/feature-gate-forced-keywords.stderr b/tests/ui/parser/forced-keywords/feature-gate-forced-keywords.stderr new file mode 100644 index 0000000000000..0beb805832c83 --- /dev/null +++ b/tests/ui/parser/forced-keywords/feature-gate-forced-keywords.stderr @@ -0,0 +1,33 @@ +error[E0658]: forced keywords are experimental + --> $DIR/feature-gate-forced-keywords.rs:3:1 + | +LL | k#fn main() {} + | ^^^^ + | + = note: see issue #153839 for more information + = help: add `#![feature(forced_keywords)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: forced keywords are experimental + --> $DIR/feature-gate-forced-keywords.rs:6:1 + | +LL | k#fn start() {} + | ^^^^ + | + = note: see issue #153839 for more information + = help: add `#![feature(forced_keywords)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: forced keywords are experimental + --> $DIR/feature-gate-forced-keywords.rs:10:10 + | +LL | discard!(k#static); + | ^^^^^^^^ + | + = note: see issue #153839 for more information + = help: add `#![feature(forced_keywords)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/parser/forced-keywords/non-keyword-neg.rs b/tests/ui/parser/forced-keywords/non-keyword-neg.rs new file mode 100644 index 0000000000000..6bcba5d9e4b25 --- /dev/null +++ b/tests/ui/parser/forced-keywords/non-keyword-neg.rs @@ -0,0 +1,12 @@ +// You are intentionally allowed to force identifiers to be treated as keywords even if they aren't +// actually reserved. We must ensure that we never treat them as normal identifiers when parsing. +// +//@ edition: 2021.. +#![feature(forced_keywords)] + +fn scope() { + k#not_a_keyword() + //~^ ERROR expected expression, found `k#not_a_keyword` +} +fn k#trying_to_pass_as_a_normal_ident() {} +//~^ ERROR expected identifier, found `k#trying_to_pass_as_a_normal_ident` diff --git a/tests/ui/parser/forced-keywords/non-keyword-neg.stderr b/tests/ui/parser/forced-keywords/non-keyword-neg.stderr new file mode 100644 index 0000000000000..7d8a9f2183d93 --- /dev/null +++ b/tests/ui/parser/forced-keywords/non-keyword-neg.stderr @@ -0,0 +1,14 @@ +error: expected expression, found `k#not_a_keyword` + --> $DIR/non-keyword-neg.rs:8:5 + | +LL | k#not_a_keyword() + | ^^^^^^^^^^^^^^^ expected expression + +error: expected identifier, found `k#trying_to_pass_as_a_normal_ident` + --> $DIR/non-keyword-neg.rs:11:4 + | +LL | fn k#trying_to_pass_as_a_normal_ident() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected identifier + +error: aborting due to 2 previous errors + diff --git a/tests/ui/parser/forced-keywords/non-keyword-pos.rs b/tests/ui/parser/forced-keywords/non-keyword-pos.rs new file mode 100644 index 0000000000000..84d472bcb5d68 --- /dev/null +++ b/tests/ui/parser/forced-keywords/non-keyword-pos.rs @@ -0,0 +1,14 @@ +// Ensure that you are allowed to force *any* identifier to be treated as a keyword. +// +//@ edition: 2021.. +//@ check-pass +#![feature(forced_keywords)] + +macro_rules! discard { ($($tt:tt)*) => {} } + +discard!(k#not_a_keyword); // OK + +#[cfg(false)] +invoke!(k#arbitrary_string_of_chars); + +fn main() {} diff --git a/tests/ui/parser/forced-keywords/pre-2021-edition-fail.rs b/tests/ui/parser/forced-keywords/pre-2021-edition-fail.rs new file mode 100644 index 0000000000000..77d2712802548 --- /dev/null +++ b/tests/ui/parser/forced-keywords/pre-2021-edition-fail.rs @@ -0,0 +1,3 @@ +//@ edition: 2015..2021 + +k#fn main() {} //~ ERROR expected one of `!` or `::`, found `#` diff --git a/tests/ui/parser/forced-keywords/pre-2021-edition-fail.stderr b/tests/ui/parser/forced-keywords/pre-2021-edition-fail.stderr new file mode 100644 index 0000000000000..fefc3cafd2242 --- /dev/null +++ b/tests/ui/parser/forced-keywords/pre-2021-edition-fail.stderr @@ -0,0 +1,8 @@ +error: expected one of `!` or `::`, found `#` + --> $DIR/pre-2021-edition-fail.rs:3:2 + | +LL | k#fn main() {} + | ^ expected one of `!` or `::` + +error: aborting due to 1 previous error + diff --git a/tests/ui/parser/forced-keywords/pre-2021-edition-pass.rs b/tests/ui/parser/forced-keywords/pre-2021-edition-pass.rs new file mode 100644 index 0000000000000..63cf647a5c4c1 --- /dev/null +++ b/tests/ui/parser/forced-keywords/pre-2021-edition-pass.rs @@ -0,0 +1,14 @@ +//@ edition: 2015..2021 +//@ check-pass + +#![warn(rust_2021_prefixes_incompatible_syntax)] + +macro_rules! ensure { + ($tag:ident # $name:ident) => {}; +} + +ensure! { k#fn } +//~^ WARNING parsed as a forced keyword in Rust 2021 and onward +//~| WARNING this changes meaning in Rust 2021 + +fn main() {} diff --git a/tests/ui/parser/forced-keywords/pre-2021-edition-pass.stderr b/tests/ui/parser/forced-keywords/pre-2021-edition-pass.stderr new file mode 100644 index 0000000000000..f033a56ad49ae --- /dev/null +++ b/tests/ui/parser/forced-keywords/pre-2021-edition-pass.stderr @@ -0,0 +1,20 @@ +warning: this is parsed as a forced keyword in Rust 2021 and onward + --> $DIR/pre-2021-edition-pass.rs:10:11 + | +LL | ensure! { k#fn } + | ^^^^ + | + = warning: this changes meaning in Rust 2021 + = note: for more information, see +note: the lint level is defined here + --> $DIR/pre-2021-edition-pass.rs:4:9 + | +LL | #![warn(rust_2021_prefixes_incompatible_syntax)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: consider inserting whitespace here to avoid this + | +LL | ensure! { k #fn } + | + + +warning: 1 warning emitted +