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}