Skip to content
Draft
6 changes: 3 additions & 3 deletions compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1765,7 +1765,7 @@ pub enum ExprKind {
Lit(token::Lit),
/// A cast (e.g., `foo as f64`).
Cast(Box<Expr>, Box<Ty>),
/// A type ascription (e.g., `builtin # type_ascribe(42, usize)`).
/// A type ascription (e.g., `k#type_ascribe(42, usize)`).
///
/// Usually not written directly in user code but
/// indirectly via the macro `type_ascribe!(...)`.
Expand Down Expand Up @@ -1857,7 +1857,7 @@ pub enum ExprKind {
/// Output of the `asm!()` macro.
InlineAsm(Box<InlineAsm>),

/// An `offset_of` expression (e.g., `builtin # offset_of(Struct, field)`).
/// An `offset_of` expression (e.g., `k#offset_of(Struct, field)`).
///
/// Usually not written directly in user code but
/// indirectly via the macro `core::mem::offset_of!(...)`.
Expand Down Expand Up @@ -2565,7 +2565,7 @@ pub enum TyKind {
/// Pattern types like `pattern_type!(u32 is 1..=)`, which is the same as `NonZero<u32>`,
/// just as part of the type system.
Pat(Box<Ty>, Box<TyPat>),
/// A `field_of` expression (e.g., `builtin # field_of(Struct, field)`).
/// A `field_of` expression (e.g., `k#field_of(Struct, field)`).
///
/// Usually not written directly in user code but indirectly via the macro
/// `core::field::field_of!(...)`.
Expand Down
132 changes: 77 additions & 55 deletions compiler/rustc_ast/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,9 @@ impl Lit {
/// `Parser::eat_token_lit` (excluding unary negation).
pub fn from_token(token: &Token) -> Option<Lit> {
match token.uninterpolate().kind {
Ident(name, IdentIsRaw::No) if name.is_bool_lit() => Some(Lit::new(Bool, name, None)),
Ident(name, IdentKind::Normal | IdentKind::ForcedKeyword) if name.is_bool_lit() => {
Some(Lit::new(Bool, name, None))
}
Literal(token_lit) => Some(token_lit),
OpenInvisible(InvisibleOrigin::MetaVar(
MetaVarKind::Literal | MetaVarKind::Expr { .. },
Expand Down Expand Up @@ -307,11 +309,13 @@ impl LitKind {
}
}

pub fn ident_can_begin_expr(name: Symbol, span: Span, is_raw: IdentIsRaw) -> bool {
pub fn ident_can_begin_expr(name: Symbol, span: Span, kind: IdentKind) -> bool {
// WARNING: Take care when modifying this function! It will change the stable(!) set of
// tokens that are allowed to match an `expr` nonterminal which is user observable.

let ident_token = Token::new(Ident(name, is_raw), span);
// NOTE: We don't care about forced keywords that are gated behind `internal_syntax`.

let ident_token = Token::new(Ident(name, kind), span);

// FIXME: Remove `box` from this list given we officially no longer support box expressions
// (#108471) (needs lang FCP as it affects stable macro matching behavior).
Expand Down Expand Up @@ -344,11 +348,13 @@ pub fn ident_can_begin_expr(name: Symbol, span: Span, is_raw: IdentIsRaw) -> boo
.contains(&name)
}

fn ident_can_begin_type(name: Symbol, span: Span, is_raw: IdentIsRaw) -> bool {
fn ident_can_begin_type(name: Symbol, span: Span, kind: IdentKind) -> bool {
// WARNING: Take care when modifying this function! It will change the stable(!) set of
// tokens that are allowed to match an `ty` nonterminal which is user observable.

let ident_token = Token::new(Ident(name, is_raw), span);
// NOTE: We don't care about forced keywords that are gated behind `internal_syntax`.

let ident_token = Token::new(Ident(name, kind), span);

!ident_token.is_reserved_ident()
|| ident_token.is_path_segment_keyword()
Expand All @@ -357,32 +363,30 @@ fn ident_can_begin_type(name: Symbol, span: Span, is_raw: IdentIsRaw) -> bool {
}

#[derive(PartialEq, Eq, Encodable, Decodable, Hash, Debug, Copy, Clone, StableHash)]
pub enum IdentIsRaw {
No,
Yes,
pub enum IdentKind {
Normal,
Raw,
ForcedKeyword,
}

impl IdentIsRaw {
impl IdentKind {
pub fn to_print_mode_ident(self) -> IdentPrintMode {
match self {
IdentIsRaw::No => IdentPrintMode::Normal,
IdentIsRaw::Yes => IdentPrintMode::RawIdent,
IdentKind::Normal => IdentPrintMode::Normal,
IdentKind::Raw => IdentPrintMode::RawIdent,
IdentKind::ForcedKeyword => IdentPrintMode::ForcedKeywordIdent,
}
}

pub fn to_print_mode_lifetime(self) -> IdentPrintMode {
match self {
IdentIsRaw::No => IdentPrintMode::Normal,
IdentIsRaw::Yes => IdentPrintMode::RawLifetime,
IdentKind::Normal => IdentPrintMode::Normal,
IdentKind::Raw => IdentPrintMode::RawLifetime,
IdentKind::ForcedKeyword => unreachable!(),
}
}
}

impl From<bool> for IdentIsRaw {
fn from(b: bool) -> Self {
if b { Self::Yes } else { Self::No }
}
}

#[derive(Clone, Copy, PartialEq, Eq, Hash, Encodable, Decodable, Debug, StableHash)]
pub enum TokenKind {
/* Expression-operator symbols. */
Expand Down Expand Up @@ -507,22 +511,22 @@ pub enum TokenKind {
/// It's recommended to use `Token::{ident,uninterpolate}` and
/// `Parser::token_uninterpolated_span` to treat regular and interpolated
/// identifiers in the same way.
Ident(Symbol, IdentIsRaw),
Ident(Symbol, IdentKind),
/// This identifier (and its span) is the identifier passed to the
/// declarative macro. The span in the surrounding `Token` is the span of
/// the `ident` metavariable in the macro's RHS.
NtIdent(sp::Ident, IdentIsRaw),
NtIdent(sp::Ident, IdentKind),

/// Lifetime identifier token.
/// Do not forget about `NtLifetime` when you want to match on lifetime identifiers.
/// It's recommended to use `Token::{ident,uninterpolate}` and
/// `Parser::token_uninterpolated_span` to treat regular and interpolated
/// identifiers in the same way.
Lifetime(Symbol, IdentIsRaw),
Lifetime(Symbol, IdentKind),
/// This identifier (and its span) is the lifetime passed to the
/// declarative macro. The span in the surrounding `Token` is the span of
/// the `lifetime` metavariable in the macro's RHS.
NtLifetime(sp::Ident, IdentIsRaw),
NtLifetime(sp::Ident, IdentKind),

/// A doc comment token.
/// `Symbol` is the doc comment's data excluding its "quotes" (`///`, `/**`, etc)
Expand Down Expand Up @@ -641,9 +645,13 @@ 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 {
Token::new(Ident(ident.name, ident.is_raw_guess().into()), ident.span)
let kind = if ident.is_raw_guess() { IdentKind::Raw } else { IdentKind::Normal };
Token::new(Ident(ident.name, kind), ident.span)
}

pub fn is_range_separator(&self) -> bool {
Expand Down Expand Up @@ -674,8 +682,8 @@ impl Token {
// tokens that are allowed to match an `expr` nonterminal which is user observable.

match self.uninterpolate().kind {
Ident(name, is_raw) =>
ident_can_begin_expr(name, self.span, is_raw), // value name or keyword
Ident(name, kind) =>
ident_can_begin_expr(name, self.span, kind), // value name or keyword
OpenParen | // tuple
OpenBrace | // block
OpenBracket | // array
Expand Down Expand Up @@ -743,8 +751,8 @@ impl Token {
// object types (consider `use<>+` and `use<T> + Trait` for example).

match self.uninterpolate().kind {
Ident(name, is_raw) =>
ident_can_begin_type(name, self.span, is_raw), // type name or keyword
Ident(name, kind) =>
ident_can_begin_type(name, self.span, kind), // type name or keyword
OpenParen // tuple
| OpenBracket // array
| Bang // never
Expand All @@ -768,7 +776,7 @@ impl Token {
pub fn can_begin_const_arg(&self) -> bool {
match self.kind {
OpenBrace | Literal(..) | Minus => true,
Ident(name, IdentIsRaw::No) 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,
Expand Down Expand Up @@ -817,7 +825,7 @@ impl Token {
pub fn can_begin_literal_maybe_minus(&self) -> bool {
match self.uninterpolate().kind {
Literal(..) | Minus => true,
Ident(name, IdentIsRaw::No) 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, .. } => {
Expand Down Expand Up @@ -847,32 +855,32 @@ impl Token {
/// otherwise returns the original token.
pub fn uninterpolate(&self) -> Cow<'_, Token> {
match self.kind {
NtIdent(ident, is_raw) => Cow::Owned(Token::new(Ident(ident.name, is_raw), ident.span)),
NtLifetime(ident, is_raw) => {
Cow::Owned(Token::new(Lifetime(ident.name, is_raw), ident.span))
NtIdent(ident, kind) => Cow::Owned(Token::new(Ident(ident.name, kind), ident.span)),
NtLifetime(ident, kind) => {
Cow::Owned(Token::new(Lifetime(ident.name, kind), ident.span))
}
_ => Cow::Borrowed(self),
}
}

/// Returns an identifier if this token is an identifier.
#[inline]
pub fn ident(&self) -> Option<(sp::Ident, IdentIsRaw)> {
pub fn ident(&self) -> Option<(sp::Ident, IdentKind)> {
// We avoid using `Token::uninterpolate` here because it's slow.
match self.kind {
Ident(name, is_raw) => Some((sp::Ident::new(name, self.span), is_raw)),
NtIdent(ident, is_raw) => Some((ident, is_raw)),
Ident(name, kind) => Some((sp::Ident::new(name, self.span), kind)),
NtIdent(ident, kind) => Some((ident, kind)),
_ => None,
}
}

/// Returns a lifetime identifier if this token is a lifetime.
#[inline]
pub fn lifetime(&self) -> Option<(sp::Ident, IdentIsRaw)> {
pub fn lifetime(&self) -> Option<(sp::Ident, IdentKind)> {
// We avoid using `Token::uninterpolate` here because it's slow.
match self.kind {
Lifetime(name, is_raw) => Some((sp::Ident::new(name, self.span), is_raw)),
NtLifetime(ident, is_raw) => Some((ident, is_raw)),
Lifetime(name, kind) => Some((sp::Ident::new(name, self.span), kind)),
NtLifetime(ident, kind) => Some((ident, kind)),
_ => None,
}
}
Expand Down Expand Up @@ -931,52 +939,66 @@ impl Token {

/// Returns `true` if the token is a given keyword, `kw`.
pub fn is_keyword(&self, kw: Symbol) -> bool {
self.is_non_raw_ident_where(|id| id.name == kw)
self.non_raw_ident().is_some_and(|id| id.name == kw)
}

pub fn is_forced_keyword(&self, kw: Symbol) -> bool {
self.ident().is_some_and(|(id, kind)| id.name == kw && kind == IdentKind::ForcedKeyword)
}

/// Returns `true` if the token is a given keyword, `kw` or if `case` is `Insensitive` and this
/// token is an identifier equal to `kw` ignoring the case.
pub fn is_keyword_case(&self, kw: Symbol, case: Case) -> bool {
self.is_keyword(kw)
|| (case == Case::Insensitive
&& self.is_non_raw_ident_where(|id| {
&& self.non_raw_ident().is_some_and(|id| {
// Do an ASCII case-insensitive match, because all keywords are ASCII.
id.name.as_str().eq_ignore_ascii_case(kw.as_str())
}))
}

pub fn is_path_segment_keyword(&self) -> bool {
self.is_non_raw_ident_where(sp::Ident::is_path_segment_keyword)
self.non_raw_ident().is_some_and(sp::Ident::is_path_segment_keyword)
}

/// Returns true for reserved identifiers used internally for elided lifetimes,
/// unnamed method parameters, crate root module, error recovery etc.
pub fn is_special_ident(&self) -> bool {
self.is_non_raw_ident_where(sp::Ident::is_special)
self.non_raw_ident().is_some_and(sp::Ident::is_special)
}

/// Returns `true` if the token is a keyword used in the language.
pub fn is_used_keyword(&self) -> bool {
self.is_non_raw_ident_where(sp::Ident::is_used_keyword)
self.non_raw_ident().is_some_and(sp::Ident::is_used_keyword)
}

/// Returns `true` if the token is a keyword reserved for possible future use.
pub fn is_unused_keyword(&self) -> bool {
self.is_non_raw_ident_where(sp::Ident::is_unused_keyword)
self.non_raw_ident().is_some_and(sp::Ident::is_unused_keyword)
}

/// Returns `true` if the token is either a special identifier or a keyword.
pub fn is_reserved_ident(&self) -> bool {
self.is_non_raw_ident_where(sp::Ident::is_reserved)
self.non_raw_ident().is_some_and(sp::Ident::is_reserved)
}

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<sp::Ident> {
self.ident()
.filter(|&(id, kind)| match kind {
IdentKind::Normal => !sp::Ident::is_reserved(id),
IdentKind::Raw => true,
IdentKind::ForcedKeyword => false,
})
.map(|(id, _)| id)
}

/// Returns `true` if the token is the identifier `true` or `false`.
pub fn is_bool_lit(&self) -> bool {
self.is_non_raw_ident_where(|id| id.name.is_bool_lit())
self.non_raw_ident().is_some_and(|id| id.name.is_bool_lit())
}

pub fn is_numeric_lit(&self) -> bool {
Expand All @@ -991,11 +1013,11 @@ impl Token {
matches!(self.kind, Literal(Lit { kind: LitKind::Integer, .. }))
}

/// Returns `true` if the token is a non-raw identifier for which `pred` holds.
pub fn is_non_raw_ident_where(&self, pred: impl FnOnce(sp::Ident) -> bool) -> bool {
/// Returns an identifier if this token is a non-raw identifier.
pub fn non_raw_ident(&self) -> Option<sp::Ident> {
match self.ident() {
Some((id, IdentIsRaw::No)) => pred(id),
_ => false,
Some((id, IdentKind::Normal | IdentKind::ForcedKeyword)) => Some(id),
_ => None,
}
}

Expand Down Expand Up @@ -1072,8 +1094,8 @@ impl Token {
(Colon, Colon) => PathSep,
(Colon, _) => return None,

(SingleQuote, Ident(name, is_raw)) => {
Lifetime(Symbol::intern(&format!("'{name}")), *is_raw)
(SingleQuote, Ident(name, kind)) => {
Lifetime(Symbol::intern(&format!("'{name}")), *kind)
}
(SingleQuote, _) => return None,

Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_ast/src/tokenstream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,7 @@ impl TokenStream {
DelimSpacing::new(Spacing::JointHidden, Spacing::Alone),
Delimiter::Bracket,
[
TokenTree::token_alone(token::Ident(sym::doc, token::IdentIsRaw::No), span),
TokenTree::token_alone(token::Ident(sym::doc, token::IdentKind::Normal), span),
TokenTree::token_alone(token::Eq, span),
TokenTree::token_alone(
TokenKind::lit(token::StrRaw(num_of_hashes), data, None),
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_ast_passes/src/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,6 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {

// tidy-alphabetical-start
gate_all!(async_for_loop, "`for await` loops are experimental");
gate_all!(builtin_syntax, "`builtin #` syntax is unstable");
gate_all!(const_block_items, "const block items are experimental");
gate_all!(const_closures, "const closures are experimental");
gate_all!(const_trait_impl, "const trait impls are experimental");
Expand All @@ -435,12 +434,14 @@ 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!(gen_blocks, "gen blocks are experimental");
gate_all!(generic_const_items, "generic const items are experimental");
gate_all!(global_registration, "global registration is experimental");
gate_all!(guard_patterns, "guard patterns are experimental", "consider using match arm guards");
gate_all!(impl_restriction, "`impl` restrictions are experimental");
gate_all!(internal_syntax, "this syntax is internal");
gate_all!(min_generic_const_args, "unbraced const blocks as const args are experimental");
gate_all!(more_qualified_paths, "usage of qualified paths in this context is experimental");
gate_all!(move_expr, "`move(expr)` syntax is experimental");
Expand Down
Loading
Loading