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