rustc_attr_parsing/context.rs
1use std::cell::RefCell;
2use std::collections::BTreeMap;
3use std::ops::Deref;
4use std::sync::LazyLock;
5
6use rustc_ast as ast;
7use rustc_attr_data_structures::AttributeKind;
8use rustc_errors::{DiagCtxtHandle, Diagnostic};
9use rustc_feature::Features;
10use rustc_hir::{AttrArgs, AttrItem, AttrPath, Attribute, HashIgnoredAttrId};
11use rustc_session::Session;
12use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym};
13
14use crate::attributes::allow_unstable::{AllowConstFnUnstableParser, AllowInternalUnstableParser};
15use crate::attributes::confusables::ConfusablesParser;
16use crate::attributes::deprecation::DeprecationParser;
17use crate::attributes::repr::ReprParser;
18use crate::attributes::stability::{
19 BodyStabilityParser, ConstStabilityIndirectParser, ConstStabilityParser, StabilityParser,
20};
21use crate::attributes::transparency::TransparencyParser;
22use crate::attributes::{AttributeParser as _, Combine, Single};
23use crate::parser::{ArgParser, MetaItemParser};
24
25macro_rules! attribute_groups {
26 (
27 pub(crate) static $name: ident = [$($names: ty),* $(,)?];
28 ) => {
29 pub(crate) static $name: LazyLock<(
30 BTreeMap<&'static [Symbol], Vec<Box<dyn Fn(&AcceptContext<'_>, &ArgParser<'_>) + Send + Sync>>>,
31 Vec<Box<dyn Send + Sync + Fn(&FinalizeContext<'_>) -> Option<AttributeKind>>>
32 )> = LazyLock::new(|| {
33 let mut accepts = BTreeMap::<_, Vec<Box<dyn Fn(&AcceptContext<'_>, &ArgParser<'_>) + Send + Sync>>>::new();
34 let mut finalizes = Vec::<Box<dyn Send + Sync + Fn(&FinalizeContext<'_>) -> Option<AttributeKind>>>::new();
35 $(
36 {
37 thread_local! {
38 static STATE_OBJECT: RefCell<$names> = RefCell::new(<$names>::default());
39 };
40
41 for (k, v) in <$names>::ATTRIBUTES {
42 accepts.entry(*k).or_default().push(Box::new(|cx, args| {
43 STATE_OBJECT.with_borrow_mut(|s| {
44 v(s, cx, args)
45 })
46 }));
47 }
48
49 finalizes.push(Box::new(|cx| {
50 let state = STATE_OBJECT.take();
51 state.finalize(cx)
52 }));
53 }
54 )*
55
56 (accepts, finalizes)
57 });
58 };
59}
60
61attribute_groups!(
62 pub(crate) static ATTRIBUTE_MAPPING = [
63 // tidy-alphabetical-start
64 BodyStabilityParser,
65 ConfusablesParser,
66 ConstStabilityParser,
67 StabilityParser,
68 // tidy-alphabetical-end
69
70 // tidy-alphabetical-start
71 Combine<AllowConstFnUnstableParser>,
72 Combine<AllowInternalUnstableParser>,
73 Combine<ReprParser>,
74 // tidy-alphabetical-end
75
76 // tidy-alphabetical-start
77 Single<ConstStabilityIndirectParser>,
78 Single<DeprecationParser>,
79 Single<TransparencyParser>,
80 // tidy-alphabetical-end
81 ];
82);
83
84/// Context given to every attribute parser when accepting
85///
86/// Gives [`AttributeParser`]s enough information to create errors, for example.
87pub(crate) struct AcceptContext<'a> {
88 pub(crate) group_cx: &'a FinalizeContext<'a>,
89 /// The span of the attribute currently being parsed
90 pub(crate) attr_span: Span,
91}
92
93impl<'a> AcceptContext<'a> {
94 pub(crate) fn emit_err(&self, diag: impl Diagnostic<'a>) -> ErrorGuaranteed {
95 if self.limit_diagnostics {
96 self.dcx().create_err(diag).delay_as_bug()
97 } else {
98 self.dcx().emit_err(diag)
99 }
100 }
101}
102
103impl<'a> Deref for AcceptContext<'a> {
104 type Target = FinalizeContext<'a>;
105
106 fn deref(&self) -> &Self::Target {
107 &self.group_cx
108 }
109}
110
111/// Context given to every attribute parser during finalization.
112///
113/// Gives [`AttributeParser`](crate::attributes::AttributeParser)s enough information to create errors, for example.
114pub(crate) struct FinalizeContext<'a> {
115 /// The parse context, gives access to the session and the
116 /// diagnostics context.
117 pub(crate) cx: &'a AttributeParser<'a>,
118 /// The span of the syntactical component this attribute was applied to
119 pub(crate) target_span: Span,
120}
121
122impl<'a> Deref for FinalizeContext<'a> {
123 type Target = AttributeParser<'a>;
124
125 fn deref(&self) -> &Self::Target {
126 &self.cx
127 }
128}
129
130#[derive(PartialEq, Clone, Copy, Debug)]
131pub enum OmitDoc {
132 Lower,
133 Skip,
134}
135
136/// Context created once, for example as part of the ast lowering
137/// context, through which all attributes can be lowered.
138pub struct AttributeParser<'sess> {
139 #[expect(dead_code)] // FIXME(jdonszelmann): needed later to verify we parsed all attributes
140 tools: Vec<Symbol>,
141 sess: &'sess Session,
142 features: Option<&'sess Features>,
143
144 /// *only* parse attributes with this symbol.
145 ///
146 /// Used in cases where we want the lowering infrastructure for
147 /// parse just a single attribute.
148 parse_only: Option<Symbol>,
149
150 /// Can be used to instruct parsers to reduce the number of diagnostics it emits.
151 /// Useful when using `parse_limited` and you know the attr will be reparsed later.
152 pub(crate) limit_diagnostics: bool,
153}
154
155impl<'sess> AttributeParser<'sess> {
156 /// This method allows you to parse attributes *before* you have access to features or tools.
157 /// One example where this is necessary, is to parse `feature` attributes themselves for
158 /// example.
159 ///
160 /// Try to use this as little as possible. Attributes *should* be lowered during `rustc_ast_lowering`.
161 /// Some attributes require access to features to parse, which would crash if you tried to do so
162 /// through [`parse_limited`](Self::parse_limited).
163 ///
164 /// To make sure use is limited, supply a `Symbol` you'd like to parse. Only attributes with
165 /// that symbol are picked out of the list of instructions and parsed. Those are returned.
166 pub fn parse_limited(
167 sess: &'sess Session,
168 attrs: &[ast::Attribute],
169 sym: Symbol,
170 target_span: Span,
171 limit_diagnostics: bool,
172 ) -> Option<Attribute> {
173 let mut parsed = Self {
174 sess,
175 features: None,
176 tools: Vec::new(),
177 parse_only: Some(sym),
178 limit_diagnostics,
179 }
180 .parse_attribute_list(attrs, target_span, OmitDoc::Skip, std::convert::identity);
181
182 assert!(parsed.len() <= 1);
183
184 parsed.pop()
185 }
186
187 pub fn new(sess: &'sess Session, features: &'sess Features, tools: Vec<Symbol>) -> Self {
188 Self { sess, features: Some(features), tools, parse_only: None, limit_diagnostics: false }
189 }
190
191 pub(crate) fn sess(&self) -> &'sess Session {
192 self.sess
193 }
194
195 pub(crate) fn features(&self) -> &'sess Features {
196 self.features.expect("features not available at this point in the compiler")
197 }
198
199 pub(crate) fn dcx(&self) -> DiagCtxtHandle<'sess> {
200 self.sess.dcx()
201 }
202
203 /// Parse a list of attributes.
204 ///
205 /// `target_span` is the span of the thing this list of attributes is applied to,
206 /// and when `omit_doc` is set, doc attributes are filtered out.
207 pub fn parse_attribute_list<'a>(
208 &'a self,
209 attrs: &'a [ast::Attribute],
210 target_span: Span,
211 omit_doc: OmitDoc,
212
213 lower_span: impl Copy + Fn(Span) -> Span,
214 ) -> Vec<Attribute> {
215 let mut attributes = Vec::new();
216
217 let group_cx = FinalizeContext { cx: self, target_span };
218
219 for attr in attrs {
220 // if we're only looking for a single attribute,
221 // skip all the ones we don't care about
222 if let Some(expected) = self.parse_only {
223 if !attr.has_name(expected) {
224 continue;
225 }
226 }
227
228 // sometimes, for example for `#![doc = include_str!("readme.md")]`,
229 // doc still contains a non-literal. You might say, when we're lowering attributes
230 // that's expanded right? But no, sometimes, when parsing attributes on macros,
231 // we already use the lowering logic and these are still there. So, when `omit_doc`
232 // is set we *also* want to ignore these
233 if omit_doc == OmitDoc::Skip && attr.has_name(sym::doc) {
234 continue;
235 }
236
237 match &attr.kind {
238 ast::AttrKind::DocComment(comment_kind, symbol) => {
239 if omit_doc == OmitDoc::Skip {
240 continue;
241 }
242
243 attributes.push(Attribute::Parsed(AttributeKind::DocComment {
244 style: attr.style,
245 kind: *comment_kind,
246 span: lower_span(attr.span),
247 comment: *symbol,
248 }))
249 }
250 // // FIXME: make doc attributes go through a proper attribute parser
251 // ast::AttrKind::Normal(n) if n.has_name(sym::doc) => {
252 // let p = GenericMetaItemParser::from_attr(&n, self.dcx());
253 //
254 // attributes.push(Attribute::Parsed(AttributeKind::DocComment {
255 // style: attr.style,
256 // kind: CommentKind::Line,
257 // span: attr.span,
258 // comment: p.args().name_value(),
259 // }))
260 // }
261 ast::AttrKind::Normal(n) => {
262 let parser = MetaItemParser::from_attr(n, self.dcx());
263 let (path, args) = parser.deconstruct();
264 let parts = path.segments().map(|i| i.name).collect::<Vec<_>>();
265
266 if let Some(accepts) = ATTRIBUTE_MAPPING.0.get(parts.as_slice()) {
267 for f in accepts {
268 let cx = AcceptContext {
269 group_cx: &group_cx,
270 attr_span: lower_span(attr.span),
271 };
272
273 f(&cx, &args)
274 }
275 } else {
276 // if we're here, we must be compiling a tool attribute... Or someone forgot to
277 // parse their fancy new attribute. Let's warn them in any case. If you are that
278 // person, and you really your attribute should remain unparsed, carefully read the
279 // documentation in this module and if you still think so you can add an exception
280 // to this assertion.
281
282 // FIXME(jdonszelmann): convert other attributes, and check with this that
283 // we caught em all
284 // const FIXME_TEMPORARY_ATTR_ALLOWLIST: &[Symbol] = &[sym::cfg];
285 // assert!(
286 // self.tools.contains(&parts[0]) || true,
287 // // || FIXME_TEMPORARY_ATTR_ALLOWLIST.contains(&parts[0]),
288 // "attribute {path} wasn't parsed and isn't a know tool attribute",
289 // );
290
291 attributes.push(Attribute::Unparsed(Box::new(AttrItem {
292 path: AttrPath::from_ast(&n.item.path),
293 args: self.lower_attr_args(&n.item.args, lower_span),
294 id: HashIgnoredAttrId { attr_id: attr.id },
295 style: attr.style,
296 span: lower_span(attr.span),
297 })));
298 }
299 }
300 }
301 }
302
303 let mut parsed_attributes = Vec::new();
304 for f in &ATTRIBUTE_MAPPING.1 {
305 if let Some(attr) = f(&group_cx) {
306 parsed_attributes.push(Attribute::Parsed(attr));
307 }
308 }
309
310 attributes.extend(parsed_attributes);
311
312 attributes
313 }
314
315 fn lower_attr_args(&self, args: &ast::AttrArgs, lower_span: impl Fn(Span) -> Span) -> AttrArgs {
316 match args {
317 ast::AttrArgs::Empty => AttrArgs::Empty,
318 ast::AttrArgs::Delimited(args) => AttrArgs::Delimited(args.clone()),
319 // This is an inert key-value attribute - it will never be visible to macros
320 // after it gets lowered to HIR. Therefore, we can extract literals to handle
321 // nonterminals in `#[doc]` (e.g. `#[doc = $e]`).
322 ast::AttrArgs::Eq { eq_span, expr } => {
323 // In valid code the value always ends up as a single literal. Otherwise, a dummy
324 // literal suffices because the error is handled elsewhere.
325 let lit = if let ast::ExprKind::Lit(token_lit) = expr.kind
326 && let Ok(lit) =
327 ast::MetaItemLit::from_token_lit(token_lit, lower_span(expr.span))
328 {
329 lit
330 } else {
331 let guar = self.dcx().span_delayed_bug(
332 args.span().unwrap_or(DUMMY_SP),
333 "expr in place where literal is expected (builtin attr parsing)",
334 );
335 ast::MetaItemLit {
336 symbol: sym::dummy,
337 suffix: None,
338 kind: ast::LitKind::Err(guar),
339 span: DUMMY_SP,
340 }
341 };
342 AttrArgs::Eq { eq_span: lower_span(*eq_span), expr: lit }
343 }
344 }
345 }
346}