Skip to main content

rustc_attr_parsing/
interface.rs

1use std::convert::identity;
2
3use rustc_ast as ast;
4use rustc_ast::token::DocFragmentKind;
5use rustc_ast::{AttrItemKind, AttrStyle, CRATE_NODE_ID, NodeId, Safety};
6use rustc_data_structures::sync::{DynSend, DynSync};
7use rustc_errors::{Diag, DiagCtxtHandle, Diagnostic, Level, MultiSpan};
8use rustc_feature::{AttributeTemplate, Features};
9use rustc_hir::attrs::AttributeKind;
10use rustc_hir::{AttrArgs, AttrItem, AttrPath, Attribute, HashIgnoredAttrId, Target};
11use rustc_session::Session;
12use rustc_session::lint::LintId;
13use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym};
14
15use crate::attributes::AttributeSafety;
16use crate::context::{
17    ATTRIBUTE_PARSERS, AcceptContext, FinalizeContext, FinalizeFn, SharedContext,
18};
19use crate::early_parsed::{EARLY_PARSED_ATTRIBUTES, EarlyParsedState};
20use crate::parser::{AllowExprMetavar, ArgParser, PathParser, RefPathParser};
21use crate::session_diagnostics::ParsedDescription;
22use crate::{OmitDoc, ShouldEmit};
23
24pub struct EmitAttribute(
25    pub  Box<
26        dyn for<'a> FnOnce(DiagCtxtHandle<'a>, Level, &Session) -> Diag<'a, ()>
27            + DynSend
28            + DynSync
29            + 'static,
30    >,
31);
32
33/// Context created once, for example as part of the ast lowering
34/// context, through which all attributes can be lowered.
35pub struct AttributeParser<'sess> {
36    pub(crate) tools: Vec<Symbol>,
37    pub(crate) features: Option<&'sess Features>,
38    pub(crate) sess: &'sess Session,
39    pub(crate) should_emit: ShouldEmit,
40
41    /// *Only* parse attributes with this symbol.
42    ///
43    /// Used in cases where we want the lowering infrastructure for parse just a single attribute.
44    parse_only: Option<&'static [Symbol]>,
45}
46
47impl<'sess> AttributeParser<'sess> {
48    /// This method allows you to parse attributes *before* you have access to features or tools.
49    /// One example where this is necessary, is to parse `feature` attributes themselves for
50    /// example.
51    ///
52    /// Try to use this as little as possible. Attributes *should* be lowered during
53    /// `rustc_ast_lowering`. Some attributes require access to features to parse, which would
54    /// crash if you tried to do so through [`parse_limited`](Self::parse_limited).
55    ///
56    /// To make sure use is limited, supply a `Symbol` you'd like to parse. Only attributes with
57    /// that symbol are picked out of the list of instructions and parsed. Those are returned.
58    ///
59    /// No diagnostics will be emitted when parsing limited. Lints are not emitted at all, while
60    /// errors will be emitted as a delayed bugs. in other words, we *expect* attributes parsed
61    /// with `parse_limited` to be reparsed later during ast lowering where we *do* emit the errors
62    pub fn parse_limited(
63        sess: &'sess Session,
64        attrs: &[ast::Attribute],
65        sym: &'static [Symbol],
66    ) -> Option<Attribute> {
67        Self::parse_limited_should_emit(
68            sess,
69            attrs,
70            sym,
71            // Because we're not emitting warnings/errors, the target should not matter
72            DUMMY_SP,
73            CRATE_NODE_ID,
74            Target::Crate,
75            None,
76            ShouldEmit::Nothing,
77        )
78    }
79
80    /// This does the same as `parse_limited`, except it has a `should_emit` parameter which allows it to emit errors.
81    /// Usually you want `parse_limited`, which emits no errors.
82    pub fn parse_limited_should_emit(
83        sess: &'sess Session,
84        attrs: &[ast::Attribute],
85        sym: &'static [Symbol],
86        target_span: Span,
87        target_node_id: NodeId,
88        target: Target,
89        features: Option<&'sess Features>,
90        should_emit: ShouldEmit,
91    ) -> Option<Attribute> {
92        let mut parsed = Self::parse_limited_all(
93            sess,
94            attrs,
95            Some(sym),
96            target,
97            target_span,
98            target_node_id,
99            features,
100            should_emit,
101        );
102        if !(parsed.len() <= 1) {
    ::core::panicking::panic("assertion failed: parsed.len() <= 1")
};assert!(parsed.len() <= 1);
103        parsed.pop()
104    }
105
106    /// This method allows you to parse a list of attributes *before* `rustc_ast_lowering`.
107    /// This can be used for attributes that would be removed before `rustc_ast_lowering`, such as attributes on macro calls.
108    ///
109    /// Try to use this as little as possible. Attributes *should* be lowered during
110    /// `rustc_ast_lowering`. Some attributes require access to features to parse, which would
111    /// crash if you tried to do so through [`parse_limited_all`](Self::parse_limited_all).
112    /// Therefore, if `parse_only` is None, then features *must* be provided.
113    pub fn parse_limited_all(
114        sess: &'sess Session,
115        attrs: &[ast::Attribute],
116        parse_only: Option<&'static [Symbol]>,
117        target: Target,
118        target_span: Span,
119        target_node_id: NodeId,
120        features: Option<&'sess Features>,
121        should_emit: ShouldEmit,
122    ) -> Vec<Attribute> {
123        let mut p = Self { features, tools: Vec::new(), parse_only, sess, should_emit };
124        p.parse_attribute_list(
125            attrs,
126            target_span,
127            target,
128            OmitDoc::Skip,
129            std::convert::identity,
130            |lint_id, span, kind| {
131                sess.psess.dyn_buffer_lint_sess(lint_id.lint, span, target_node_id, kind.0)
132            },
133        )
134    }
135
136    /// This method parses a single attribute, using `parse_fn`.
137    /// This is useful if you already know what exact attribute this is, and want to parse it.
138    pub fn parse_single<T>(
139        sess: &'sess Session,
140        attr: &ast::Attribute,
141        target_span: Span,
142        target_node_id: NodeId,
143        target: Target,
144        features: Option<&'sess Features>,
145        emit_errors: ShouldEmit,
146        parse_fn: fn(cx: &mut AcceptContext<'_, '_>, item: &ArgParser) -> Option<T>,
147        template: &AttributeTemplate,
148        allow_expr_metavar: AllowExprMetavar,
149        expected_safety: AttributeSafety,
150    ) -> Option<T> {
151        let ast::AttrKind::Normal(normal_attr) = &attr.kind else {
152            {
    ::core::panicking::panic_fmt(format_args!("parse_single called on a doc attr"));
}panic!("parse_single called on a doc attr")
153        };
154        let parts =
155            normal_attr.item.path.segments.iter().map(|seg| seg.ident.name).collect::<Vec<_>>();
156
157        let path = AttrPath::from_ast(&normal_attr.item.path, identity);
158        let args = ArgParser::from_attr_args(
159            &normal_attr.item.args.unparsed_ref().unwrap(),
160            &parts,
161            &sess.psess,
162            emit_errors,
163            allow_expr_metavar,
164        )?;
165        Self::parse_single_args(
166            sess,
167            attr.span,
168            normal_attr.item.span(),
169            attr.style,
170            path,
171            Some(normal_attr.item.unsafety),
172            expected_safety,
173            ParsedDescription::Attribute,
174            target_span,
175            target_node_id,
176            target,
177            features,
178            emit_errors,
179            &args,
180            parse_fn,
181            template,
182        )
183    }
184
185    /// This method is equivalent to `parse_single`, but parses arguments using `parse_fn` using manually created `args`.
186    /// This is useful when you want to parse other things than attributes using attribute parsers.
187    pub fn parse_single_args<T, I>(
188        sess: &'sess Session,
189        attr_span: Span,
190        inner_span: Span,
191        attr_style: AttrStyle,
192        attr_path: AttrPath,
193        attr_safety: Option<Safety>,
194        expected_safety: AttributeSafety,
195        parsed_description: ParsedDescription,
196        target_span: Span,
197        target_node_id: NodeId,
198        target: Target,
199        features: Option<&'sess Features>,
200        should_emit: ShouldEmit,
201        args: &I,
202        parse_fn: fn(cx: &mut AcceptContext<'_, '_>, item: &I) -> T,
203        template: &AttributeTemplate,
204    ) -> T {
205        let mut parser = Self { features, tools: Vec::new(), parse_only: None, sess, should_emit };
206        let mut emit_lint = |lint_id: LintId, span: MultiSpan, kind: EmitAttribute| {
207            sess.psess.dyn_buffer_lint_sess(lint_id.lint, span, target_node_id, kind.0)
208        };
209        if let Some(safety) = attr_safety {
210            parser.check_attribute_safety(
211                &attr_path,
212                inner_span,
213                safety,
214                expected_safety,
215                &mut emit_lint,
216            );
217        }
218        let mut cx: AcceptContext<'_, 'sess> = AcceptContext {
219            shared: SharedContext {
220                cx: &mut parser,
221                target_span,
222                target,
223                emit_lint: &mut emit_lint,
224            },
225            attr_span,
226            inner_span,
227            attr_style,
228            parsed_description,
229            template,
230            attr_path,
231        };
232        parse_fn(&mut cx, args)
233    }
234}
235
236impl<'sess> AttributeParser<'sess> {
237    pub fn new(
238        sess: &'sess Session,
239        features: &'sess Features,
240        tools: Vec<Symbol>,
241        should_emit: ShouldEmit,
242    ) -> Self {
243        Self { features: Some(features), tools, parse_only: None, sess, should_emit }
244    }
245
246    pub(crate) fn sess(&self) -> &'sess Session {
247        &self.sess
248    }
249
250    pub(crate) fn features(&self) -> &'sess Features {
251        self.features.expect("features not available at this point in the compiler")
252    }
253
254    pub(crate) fn features_option(&self) -> Option<&'sess Features> {
255        self.features
256    }
257
258    pub(crate) fn dcx(&self) -> DiagCtxtHandle<'sess> {
259        self.sess().dcx()
260    }
261
262    pub(crate) fn emit_err(&self, diag: impl for<'x> Diagnostic<'x>) -> ErrorGuaranteed {
263        self.should_emit.emit_err(self.sess.dcx().create_err(diag))
264    }
265
266    /// Parse a list of attributes.
267    ///
268    /// `target_span` is the span of the thing this list of attributes is applied to,
269    /// and when `omit_doc` is set, doc attributes are filtered out.
270    pub fn parse_attribute_list(
271        &mut self,
272        attrs: &[ast::Attribute],
273        target_span: Span,
274        target: Target,
275        omit_doc: OmitDoc,
276        lower_span: impl Copy + Fn(Span) -> Span,
277        mut emit_lint: impl FnMut(LintId, MultiSpan, EmitAttribute),
278    ) -> Vec<Attribute> {
279        let mut attributes = Vec::new();
280        // We store the attributes we intend to discard at the end of this function in order to
281        // check they are applied to the right target and error out if necessary. In practice, we
282        // end up dropping only derive attributes and derive helpers, both being fully processed
283        // at macro expansion.
284        let mut dropped_attributes = Vec::new();
285        let mut attr_paths: Vec<RefPathParser<'_>> = Vec::new();
286        let mut early_parsed_state = EarlyParsedState::default();
287
288        let mut finalizers: Vec<FinalizeFn> = Vec::with_capacity(attrs.len());
289
290        for attr in attrs {
291            // If we're only looking for a single attribute, skip all the ones we don't care about.
292            if let Some(expected) = self.parse_only {
293                if !attr.path_matches(expected) {
294                    continue;
295                }
296            }
297
298            // Sometimes, for example for `#![doc = include_str!("readme.md")]`,
299            // doc still contains a non-literal. You might say, when we're lowering attributes
300            // that's expanded right? But no, sometimes, when parsing attributes on macros,
301            // we already use the lowering logic and these are still there. So, when `omit_doc`
302            // is set we *also* want to ignore these.
303            let is_doc_attribute = attr.has_name(sym::doc);
304            if omit_doc == OmitDoc::Skip && is_doc_attribute {
305                continue;
306            }
307
308            let attr_span = lower_span(attr.span);
309            match &attr.kind {
310                ast::AttrKind::DocComment(comment_kind, symbol) => {
311                    if omit_doc == OmitDoc::Skip {
312                        continue;
313                    }
314
315                    attributes.push(Attribute::Parsed(AttributeKind::DocComment {
316                        style: attr.style,
317                        kind: DocFragmentKind::Sugared(*comment_kind),
318                        span: attr_span,
319                        comment: *symbol,
320                    }));
321                }
322                ast::AttrKind::Normal(n) => {
323                    attr_paths.push(PathParser(&n.item.path));
324                    let attr_path = AttrPath::from_ast(&n.item.path, lower_span);
325
326                    let args = match &n.item.args {
327                        AttrItemKind::Unparsed(args) => args,
328                        AttrItemKind::Parsed(parsed) => {
329                            early_parsed_state
330                                .accept_early_parsed_attribute(attr_span, lower_span, parsed);
331                            continue;
332                        }
333                    };
334
335                    let parts =
336                        n.item.path.segments.iter().map(|seg| seg.ident.name).collect::<Vec<_>>();
337
338                    if let Some(accept) = ATTRIBUTE_PARSERS.accepters.get(parts.as_slice()) {
339                        self.check_attribute_safety(
340                            &attr_path,
341                            lower_span(n.item.span()),
342                            n.item.unsafety,
343                            accept.safety,
344                            &mut emit_lint,
345                        );
346
347                        let Some(args) = ArgParser::from_attr_args(
348                            args,
349                            &parts,
350                            &self.sess.psess,
351                            self.should_emit,
352                            AllowExprMetavar::No,
353                        ) else {
354                            continue;
355                        };
356
357                        // Special-case handling for `#[doc = "..."]`: if we go through with
358                        // `DocParser`, the order of doc comments will be messed up because `///`
359                        // doc comments are added into `attributes` whereas attributes parsed with
360                        // `DocParser` are added into `parsed_attributes` which are then appended
361                        // to `attributes`. So if you have:
362                        //
363                        // /// bla
364                        // #[doc = "a"]
365                        // /// blob
366                        //
367                        // You would get:
368                        //
369                        // bla
370                        // blob
371                        // a
372                        if is_doc_attribute
373                            && let ArgParser::NameValue(nv) = &args
374                            // If not a string key/value, it should emit an error, but to make
375                            // things simpler, it's handled in `DocParser` because it's simpler to
376                            // emit an error with `AcceptContext`.
377                            && let Some(comment) = nv.value_as_str()
378                        {
379                            attributes.push(Attribute::Parsed(AttributeKind::DocComment {
380                                style: attr.style,
381                                kind: DocFragmentKind::Raw(nv.value_span),
382                                span: attr_span,
383                                comment,
384                            }));
385                            continue;
386                        }
387
388                        let mut cx: AcceptContext<'_, 'sess> = AcceptContext {
389                            shared: SharedContext {
390                                cx: self,
391                                target_span,
392                                target,
393                                emit_lint: &mut emit_lint,
394                            },
395                            attr_span,
396                            inner_span: lower_span(n.item.span()),
397                            attr_style: attr.style,
398                            parsed_description: ParsedDescription::Attribute,
399                            template: &accept.template,
400                            attr_path: attr_path.clone(),
401                        };
402
403                        (accept.accept_fn)(&mut cx, &args);
404                        finalizers.push(accept.finalizer);
405
406                        if !#[allow(non_exhaustive_omitted_patterns)] match cx.should_emit {
    ShouldEmit::Nothing => true,
    _ => false,
}matches!(cx.should_emit, ShouldEmit::Nothing) {
407                            Self::check_target(&accept.allowed_targets, target, &mut cx);
408                        }
409                    } else {
410                        let attr = AttrItem {
411                            path: attr_path.clone(),
412                            args: self
413                                .lower_attr_args(n.item.args.unparsed_ref().unwrap(), lower_span),
414                            id: HashIgnoredAttrId { attr_id: attr.id },
415                            style: attr.style,
416                            span: attr_span,
417                        };
418
419                        self.check_attribute_safety(
420                            &attr_path,
421                            lower_span(n.item.span()),
422                            n.item.unsafety,
423                            AttributeSafety::Normal,
424                            &mut emit_lint,
425                        );
426
427                        if !#[allow(non_exhaustive_omitted_patterns)] match self.should_emit {
    ShouldEmit::Nothing => true,
    _ => false,
}matches!(self.should_emit, ShouldEmit::Nothing)
428                            && target == Target::Crate
429                        {
430                            self.check_invalid_crate_level_attr_item(&attr, n.item.span());
431                        }
432
433                        let attr = Attribute::Unparsed(Box::new(attr));
434
435                        if self.tools.contains(&parts[0])
436                            // FIXME: this can be removed once #152369 has been merged.
437                            // https://github.com/rust-lang/rust/pull/152369
438                            || [sym::allow, sym::deny, sym::expect, sym::forbid, sym::warn]
439                                .contains(&parts[0])
440                        {
441                            attributes.push(attr);
442                        } else {
443                            dropped_attributes.push(attr);
444                        }
445                    }
446                }
447            }
448        }
449
450        early_parsed_state.finalize_early_parsed_attributes(&mut attributes);
451        for f in &finalizers {
452            if let Some(attr) = f(&mut FinalizeContext {
453                shared: SharedContext { cx: self, target_span, target, emit_lint: &mut emit_lint },
454                all_attrs: &attr_paths,
455            }) {
456                attributes.push(Attribute::Parsed(attr));
457            }
458        }
459
460        if !#[allow(non_exhaustive_omitted_patterns)] match self.should_emit {
    ShouldEmit::Nothing => true,
    _ => false,
}matches!(self.should_emit, ShouldEmit::Nothing) && target == Target::WherePredicate {
461            self.check_invalid_where_predicate_attrs(attributes.iter().chain(&dropped_attributes));
462        }
463
464        attributes
465    }
466
467    /// Returns whether there is a parser for an attribute with this name
468    pub fn is_parsed_attribute(path: &[Symbol]) -> bool {
469        /// The list of attributes that are parsed attributes,
470        /// even though they don't have a parser in `Late::parsers()`
471        const SPECIAL_ATTRIBUTES: &[&[Symbol]] = &[
472            // Cfg attrs are removed after being early-parsed, so don't need to be in the parser list
473            &[sym::cfg],
474            &[sym::cfg_attr],
475        ];
476
477        ATTRIBUTE_PARSERS.accepters.contains_key(path)
478            || EARLY_PARSED_ATTRIBUTES.contains(&path)
479            || SPECIAL_ATTRIBUTES.contains(&path)
480    }
481
482    fn lower_attr_args(&self, args: &ast::AttrArgs, lower_span: impl Fn(Span) -> Span) -> AttrArgs {
483        match args {
484            ast::AttrArgs::Empty => AttrArgs::Empty,
485            ast::AttrArgs::Delimited(args) => AttrArgs::Delimited(args.clone()),
486            // This is an inert key-value attribute - it will never be visible to macros
487            // after it gets lowered to HIR. Therefore, we can extract literals to handle
488            // nonterminals in `#[doc]` (e.g. `#[doc = $e]`).
489            ast::AttrArgs::Eq { eq_span, expr } => {
490                // In valid code the value always ends up as a single literal. Otherwise, a dummy
491                // literal suffices because the error is handled elsewhere.
492                let lit = if let ast::ExprKind::Lit(token_lit) = expr.kind
493                    && let Ok(lit) =
494                        ast::MetaItemLit::from_token_lit(token_lit, lower_span(expr.span))
495                {
496                    lit
497                } else {
498                    let guar = self.dcx().span_delayed_bug(
499                        args.span().unwrap_or(DUMMY_SP),
500                        "expr in place where literal is expected (builtin attr parsing)",
501                    );
502                    ast::MetaItemLit {
503                        symbol: sym::dummy,
504                        suffix: None,
505                        kind: ast::LitKind::Err(guar),
506                        span: DUMMY_SP,
507                    }
508                };
509                AttrArgs::Eq { eq_span: lower_span(*eq_span), expr: lit }
510            }
511        }
512    }
513}