From 03fb7eecedf37b28a1feab1383c917d9e91dfaef Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Fri, 26 Dec 2025 20:54:15 +0100 Subject: [PATCH] Create a `rustc_ast` representation for parsed attributes --- compiler/rustc_ast/src/ast.rs | 45 ++++++++++++++++++- compiler/rustc_ast/src/attr/mod.rs | 33 +++++++++++--- compiler/rustc_ast/src/visit.rs | 2 + compiler/rustc_ast_pretty/src/pprust/state.rs | 2 +- .../rustc_attr_parsing/src/attributes/cfg.rs | 10 ++--- compiler/rustc_attr_parsing/src/parser.rs | 4 +- .../rustc_attr_parsing/src/validate_attr.rs | 4 +- compiler/rustc_builtin_macros/src/autodiff.rs | 14 +++--- .../src/deriving/generic/mod.rs | 41 +++++++++-------- compiler/rustc_expand/src/expand.rs | 4 +- compiler/rustc_parse/src/parser/attr.rs | 4 +- 11 files changed, 115 insertions(+), 48 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 7c922417ee29..571e840795b5 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -34,6 +34,7 @@ use rustc_span::source_map::{Spanned, respan}; use rustc_span::{ByteSymbol, DUMMY_SP, ErrorGuaranteed, Ident, Span, Symbol, kw, sym}; use thin_vec::{ThinVec, thin_vec}; +use crate::attr::data_structures::CfgEntry; pub use crate::format::*; use crate::token::{self, CommentKind, Delimiter}; use crate::tokenstream::{DelimSpan, LazyAttrTokenStream, TokenStream}; @@ -3390,7 +3391,7 @@ impl NormalAttr { item: AttrItem { unsafety: Safety::Default, path: Path::from_ident(ident), - args: AttrArgs::Empty, + args: AttrItemKind::Unparsed(AttrArgs::Empty), tokens: None, }, tokens: None, @@ -3402,11 +3403,51 @@ impl NormalAttr { pub struct AttrItem { pub unsafety: Safety, pub path: Path, - pub args: AttrArgs, + pub args: AttrItemKind, // Tokens for the meta item, e.g. just the `foo` within `#[foo]` or `#![foo]`. pub tokens: Option, } +/// Some attributes are stored in a parsed form, for performance reasons. +/// Their arguments don't have to be reparsed everytime they're used +#[derive(Clone, Encodable, Decodable, Debug, Walkable)] +pub enum AttrItemKind { + Parsed(EarlyParsedAttribute), + Unparsed(AttrArgs), +} + +impl AttrItemKind { + pub fn unparsed(self) -> Option { + match self { + AttrItemKind::Unparsed(args) => Some(args), + AttrItemKind::Parsed(_) => None, + } + } + + pub fn unparsed_ref(&self) -> Option<&AttrArgs> { + match self { + AttrItemKind::Unparsed(args) => Some(args), + AttrItemKind::Parsed(_) => None, + } + } + + pub fn span(&self) -> Option { + match self { + AttrItemKind::Unparsed(args) => args.span(), + AttrItemKind::Parsed(_) => None, + } + } +} + +/// Some attributes are stored in parsed form in the AST. +/// This is done for performance reasons, so the attributes don't need to be reparsed on every use. +/// +/// Currently all early parsed attributes are excluded from pretty printing at rustc_ast_pretty::pprust::state::print_attribute_inline. +/// When adding new early parsed attributes, consider whether they should be pretty printed. +#[derive(Clone, Encodable, Decodable, Debug, HashStable_Generic)] +pub enum EarlyParsedAttribute { +} + impl AttrItem { pub fn is_valid_for_outer_style(&self) -> bool { self.path == sym::cfg_attr diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index c0ed6e24e222..6ecba865c815 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -1,5 +1,8 @@ //! Functions dealing with attributes and meta items. +pub mod data_structures; +pub mod version; + use std::fmt::Debug; use std::sync::atomic::{AtomicU32, Ordering}; @@ -8,6 +11,7 @@ use rustc_span::{Ident, Span, Symbol, sym}; use smallvec::{SmallVec, smallvec}; use thin_vec::{ThinVec, thin_vec}; +use crate::AttrItemKind; use crate::ast::{ AttrArgs, AttrId, AttrItem, AttrKind, AttrStyle, AttrVec, Attribute, DUMMY_NODE_ID, DelimArgs, Expr, ExprKind, LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit, NormalAttr, Path, @@ -62,6 +66,15 @@ impl Attribute { } } + /// Replaces the arguments of this attribute with new arguments `AttrItemKind`. + /// This is useful for making this attribute into a trace attribute, and should otherwise be avoided. + pub fn replace_args(&mut self, new_args: AttrItemKind) { + match &mut self.kind { + AttrKind::Normal(normal) => normal.item.args = new_args, + AttrKind::DocComment(..) => panic!("unexpected doc comment"), + } + } + pub fn unwrap_normal_item(self) -> AttrItem { match self.kind { AttrKind::Normal(normal) => normal.item, @@ -77,7 +90,7 @@ impl AttributeExt for Attribute { fn value_span(&self) -> Option { match &self.kind { - AttrKind::Normal(normal) => match &normal.item.args { + AttrKind::Normal(normal) => match &normal.item.args.unparsed_ref()? { AttrArgs::Eq { expr, .. } => Some(expr.span), _ => None, }, @@ -147,7 +160,7 @@ impl AttributeExt for Attribute { fn is_word(&self) -> bool { if let AttrKind::Normal(normal) = &self.kind { - matches!(normal.item.args, AttrArgs::Empty) + matches!(normal.item.args, AttrItemKind::Unparsed(AttrArgs::Empty)) } else { false } @@ -303,7 +316,7 @@ impl AttrItem { } pub fn meta_item_list(&self) -> Option> { - match &self.args { + match &self.args.unparsed_ref()? { AttrArgs::Delimited(args) if args.delim == Delimiter::Parenthesis => { MetaItemKind::list_from_tokens(args.tokens.clone()) } @@ -324,7 +337,7 @@ impl AttrItem { /// #[attr("value")] /// ``` fn value_str(&self) -> Option { - match &self.args { + match &self.args.unparsed_ref()? { AttrArgs::Eq { expr, .. } => match expr.kind { ExprKind::Lit(token_lit) => { LitKind::from_token_lit(token_lit).ok().and_then(|lit| lit.str()) @@ -348,7 +361,7 @@ impl AttrItem { /// #[attr("value")] /// ``` fn value_span(&self) -> Option { - match &self.args { + match &self.args.unparsed_ref()? { AttrArgs::Eq { expr, .. } => Some(expr.span), AttrArgs::Delimited(_) | AttrArgs::Empty => None, } @@ -364,7 +377,7 @@ impl AttrItem { } pub fn meta_kind(&self) -> Option { - MetaItemKind::from_attr_args(&self.args) + MetaItemKind::from_attr_args(self.args.unparsed_ref()?) } } @@ -699,7 +712,13 @@ fn mk_attr( args: AttrArgs, span: Span, ) -> Attribute { - mk_attr_from_item(g, AttrItem { unsafety, path, args, tokens: None }, None, style, span) + mk_attr_from_item( + g, + AttrItem { unsafety, path, args: AttrItemKind::Unparsed(args), tokens: None }, + None, + style, + span, + ) } pub fn mk_attr_from_item( diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index d122adfdf966..83b751fbde90 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -366,6 +366,7 @@ macro_rules! common_visitor_and_walkers { crate::token::LitKind, crate::tokenstream::LazyAttrTokenStream, crate::tokenstream::TokenStream, + EarlyParsedAttribute, Movability, Mutability, Pinnedness, @@ -457,6 +458,7 @@ macro_rules! common_visitor_and_walkers { ModSpans, MutTy, NormalAttr, + AttrItemKind, Parens, ParenthesizedArgs, PatFieldsRest, diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index 1aa08dfd3d5e..0cf0eae821e9 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -694,7 +694,7 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere } ast::Safety::Default | ast::Safety::Safe(_) => {} } - match &item.args { + match &item.args.unparsed_ref().expect("Parsed attributes are never printed") { AttrArgs::Delimited(DelimArgs { dspan: _, delim, tokens }) => self.print_mac_common( Some(MacHeader::Path(&item.path)), false, diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index 66f0f8d391f6..ccf0a394afd0 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -294,11 +294,9 @@ pub fn parse_cfg_attr( sess: &Session, features: Option<&Features>, ) -> Option<(CfgEntry, Vec<(AttrItem, Span)>)> { - match cfg_attr.get_normal_item().args { - ast::AttrArgs::Delimited(ast::DelimArgs { dspan, delim, ref tokens }) - if !tokens.is_empty() => - { - check_cfg_attr_bad_delim(&sess.psess, dspan, delim); + match cfg_attr.get_normal_item().args.unparsed_ref().unwrap() { + ast::AttrArgs::Delimited(ast::DelimArgs { dspan, delim, tokens }) if !tokens.is_empty() => { + check_cfg_attr_bad_delim(&sess.psess, *dspan, *delim); match parse_in(&sess.psess, tokens.clone(), "`cfg_attr` input", |p| { parse_cfg_attr_internal(p, sess, features, cfg_attr) }) { @@ -322,7 +320,7 @@ pub fn parse_cfg_attr( } _ => { let (span, reason) = if let ast::AttrArgs::Delimited(ast::DelimArgs { dspan, .. }) = - cfg_attr.get_normal_item().args + cfg_attr.get_normal_item().args.unparsed_ref()? { (dspan.entire(), AttributeParseErrorReason::ExpectedAtLeastOneArgument) } else { diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs index 39419dfa4ed8..d79579fdf0b7 100644 --- a/compiler/rustc_attr_parsing/src/parser.rs +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -122,10 +122,10 @@ impl ArgParser { } if args.delim != Delimiter::Parenthesis { - psess.dcx().emit_err(MetaBadDelim { + should_emit.emit_err(psess.dcx().create_err(MetaBadDelim { span: args.dspan.entire(), sugg: MetaBadDelimSugg { open: args.dspan.open, close: args.dspan.close }, - }); + })); return None; } diff --git a/compiler/rustc_attr_parsing/src/validate_attr.rs b/compiler/rustc_attr_parsing/src/validate_attr.rs index 4879646a1107..f56e85b11061 100644 --- a/compiler/rustc_attr_parsing/src/validate_attr.rs +++ b/compiler/rustc_attr_parsing/src/validate_attr.rs @@ -48,7 +48,7 @@ pub fn check_attr(psess: &ParseSess, attr: &Attribute) { } _ => { let attr_item = attr.get_normal_item(); - if let AttrArgs::Eq { .. } = attr_item.args { + if let AttrArgs::Eq { .. } = attr_item.args.unparsed_ref().unwrap() { // All key-value attributes are restricted to meta-item syntax. match parse_meta(psess, attr) { Ok(_) => {} @@ -67,7 +67,7 @@ pub fn parse_meta<'a>(psess: &'a ParseSess, attr: &Attribute) -> PResult<'a, Met unsafety: item.unsafety, span: attr.span, path: item.path.clone(), - kind: match &item.args { + kind: match &item.args.unparsed_ref().unwrap() { AttrArgs::Empty => MetaItemKind::Word, AttrArgs::Delimited(DelimArgs { dspan, delim, tokens }) => { check_meta_bad_delim(psess, *dspan, *delim); diff --git a/compiler/rustc_builtin_macros/src/autodiff.rs b/compiler/rustc_builtin_macros/src/autodiff.rs index 39abb66df30c..95191b82ff3f 100644 --- a/compiler/rustc_builtin_macros/src/autodiff.rs +++ b/compiler/rustc_builtin_macros/src/autodiff.rs @@ -358,7 +358,7 @@ mod llvm_enzyme { let inline_item = ast::AttrItem { unsafety: ast::Safety::Default, path: ast::Path::from_ident(Ident::with_dummy_span(sym::inline)), - args: ast::AttrArgs::Delimited(never_arg), + args: rustc_ast::ast::AttrItemKind::Unparsed(ast::AttrArgs::Delimited(never_arg)), tokens: None, }; let inline_never_attr = Box::new(ast::NormalAttr { item: inline_item, tokens: None }); @@ -421,11 +421,13 @@ mod llvm_enzyme { } }; // Now update for d_fn - rustc_ad_attr.item.args = rustc_ast::AttrArgs::Delimited(rustc_ast::DelimArgs { - dspan: DelimSpan::dummy(), - delim: rustc_ast::token::Delimiter::Parenthesis, - tokens: ts, - }); + rustc_ad_attr.item.args = rustc_ast::ast::AttrItemKind::Unparsed( + rustc_ast::AttrArgs::Delimited(rustc_ast::DelimArgs { + dspan: DelimSpan::dummy(), + delim: rustc_ast::token::Delimiter::Parenthesis, + tokens: ts, + }), + ); let new_id = ecx.sess.psess.attr_id_generator.mk_attr_id(); let d_attr = outer_normal_attr(&rustc_ad_attr, new_id, span); diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs index bb6557802ece..7c84530abbee 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs @@ -807,24 +807,29 @@ impl<'a> TraitDef<'a> { rustc_ast::AttrItem { unsafety: Safety::Default, path: rustc_const_unstable, - args: AttrArgs::Delimited(DelimArgs { - dspan: DelimSpan::from_single(self.span), - delim: rustc_ast::token::Delimiter::Parenthesis, - tokens: [ - TokenKind::Ident(sym::feature, IdentIsRaw::No), - TokenKind::Eq, - TokenKind::lit(LitKind::Str, sym::derive_const, None), - TokenKind::Comma, - TokenKind::Ident(sym::issue, IdentIsRaw::No), - TokenKind::Eq, - TokenKind::lit(LitKind::Str, sym::derive_const_issue, None), - ] - .into_iter() - .map(|kind| { - TokenTree::Token(Token { kind, span: self.span }, Spacing::Alone) - }) - .collect(), - }), + args: rustc_ast::ast::AttrItemKind::Unparsed(AttrArgs::Delimited( + DelimArgs { + dspan: DelimSpan::from_single(self.span), + delim: rustc_ast::token::Delimiter::Parenthesis, + tokens: [ + TokenKind::Ident(sym::feature, IdentIsRaw::No), + TokenKind::Eq, + TokenKind::lit(LitKind::Str, sym::derive_const, None), + TokenKind::Comma, + TokenKind::Ident(sym::issue, IdentIsRaw::No), + TokenKind::Eq, + TokenKind::lit(LitKind::Str, sym::derive_const_issue, None), + ] + .into_iter() + .map(|kind| { + TokenTree::Token( + Token { kind, span: self.span }, + Spacing::Alone, + ) + }) + .collect(), + }, + )), tokens: None, }, self.span, diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 52b7339e0140..8102f8e6dac7 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -813,10 +813,10 @@ impl<'a, 'b> MacroExpander<'a, 'b> { }; let attr_item = attr.get_normal_item(); let safety = attr_item.unsafety; - if let AttrArgs::Eq { .. } = attr_item.args { + if let AttrArgs::Eq { .. } = attr_item.args.unparsed_ref().unwrap() { self.cx.dcx().emit_err(UnsupportedKeyValue { span }); } - let inner_tokens = attr_item.args.inner_tokens(); + let inner_tokens = attr_item.args.unparsed_ref().unwrap().inner_tokens(); match expander.expand_with_safety(self.cx, safety, span, inner_tokens, tokens) { Ok(tok_result) => { let fragment = self.parse_ast_fragment( diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs index 63109c7ba5cb..1cb9a1b65d65 100644 --- a/compiler/rustc_parse/src/parser/attr.rs +++ b/compiler/rustc_parse/src/parser/attr.rs @@ -1,7 +1,7 @@ use rustc_ast as ast; use rustc_ast::token::{self, MetaVarKind}; use rustc_ast::tokenstream::ParserRange; -use rustc_ast::{Attribute, attr}; +use rustc_ast::{AttrItemKind, Attribute, attr}; use rustc_errors::codes::*; use rustc_errors::{Diag, PResult}; use rustc_span::{BytePos, Span}; @@ -313,7 +313,7 @@ impl<'a> Parser<'a> { this.expect(exp!(CloseParen))?; } Ok(( - ast::AttrItem { unsafety, path, args, tokens: None }, + ast::AttrItem { unsafety, path, args: AttrItemKind::Unparsed(args), tokens: None }, Trailing::No, UsePreAttrPos::No, ))