Skip to main content

rustc_attr_parsing/
validate_attr.rs

1//! Meta-syntax validation logic of attributes for post-expansion.
2
3use std::convert::identity;
4
5use rustc_ast::token::Delimiter;
6use rustc_ast::tokenstream::DelimSpan;
7use rustc_ast::{
8    self as ast, AttrArgs, Attribute, DelimArgs, MetaItem, MetaItemInner, MetaItemKind, Safety,
9};
10use rustc_errors::{Applicability, PResult};
11use rustc_feature::{AttributeTemplate, BUILTIN_ATTRIBUTE_MAP};
12use rustc_hir::AttrPath;
13use rustc_hir::lints::AttributeLintKind;
14use rustc_parse::parse_in;
15use rustc_session::errors::report_lit_error;
16use rustc_session::lint::builtin::ILL_FORMED_ATTRIBUTE_INPUT;
17use rustc_session::parse::ParseSess;
18use rustc_span::{Span, Symbol, sym};
19
20use crate::session_diagnostics as errors;
21
22pub fn check_attr(psess: &ParseSess, attr: &Attribute) {
23    // Built-in attributes are parsed in their respective attribute parsers, so can be ignored here
24    if attr.is_doc_comment()
25        || attr.name().is_some_and(|name| BUILTIN_ATTRIBUTE_MAP.contains_key(&name))
26    {
27        return;
28    }
29
30    let attr_item = attr.get_normal_item();
31    if let AttrArgs::Eq { .. } = attr_item.args.unparsed_ref().unwrap() {
32        // All key-value attributes are restricted to meta-item syntax.
33        match parse_meta(psess, attr) {
34            Ok(_) => {}
35            Err(err) => {
36                err.emit();
37            }
38        }
39    }
40}
41
42pub fn parse_meta<'a>(psess: &'a ParseSess, attr: &Attribute) -> PResult<'a, MetaItem> {
43    let item = attr.get_normal_item();
44    Ok(MetaItem {
45        unsafety: item.unsafety,
46        span: attr.span,
47        path: item.path.clone(),
48        kind: match &item.args.unparsed_ref().unwrap() {
49            AttrArgs::Empty => MetaItemKind::Word,
50            AttrArgs::Delimited(DelimArgs { dspan, delim, tokens }) => {
51                check_meta_bad_delim(psess, *dspan, *delim);
52                let nmis =
53                    parse_in(psess, tokens.clone(), "meta list", |p| p.parse_meta_seq_top())?;
54                MetaItemKind::List(nmis)
55            }
56            AttrArgs::Eq { expr, .. } => {
57                if let ast::ExprKind::Lit(token_lit) = expr.kind {
58                    let res = ast::MetaItemLit::from_token_lit(token_lit, expr.span);
59                    let res = match res {
60                        Ok(lit) => {
61                            if token_lit.suffix.is_some() {
62                                let mut err = psess.dcx().struct_span_err(
63                                    expr.span,
64                                    "suffixed literals are not allowed in attributes",
65                                );
66                                err.help(
67                                    "instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), \
68                                    use an unsuffixed version (`1`, `1.0`, etc.)",
69                                );
70                                return Err(err);
71                            } else {
72                                MetaItemKind::NameValue(lit)
73                            }
74                        }
75                        Err(err) => {
76                            let guar = report_lit_error(psess, err, token_lit, expr.span);
77                            let lit = ast::MetaItemLit {
78                                symbol: token_lit.symbol,
79                                suffix: token_lit.suffix,
80                                kind: ast::LitKind::Err(guar),
81                                span: expr.span,
82                            };
83                            MetaItemKind::NameValue(lit)
84                        }
85                    };
86                    res
87                } else {
88                    // Example cases:
89                    // - `#[foo = 1+1]`: results in `ast::ExprKind::Binary`.
90                    // - `#[foo = include_str!("nonexistent-file.rs")]`:
91                    //   results in `ast::ExprKind::Err`. In that case we delay
92                    //   the error because an earlier error will have already
93                    //   been reported.
94                    let msg = "attribute value must be a literal";
95                    let mut err = psess.dcx().struct_span_err(expr.span, msg);
96                    if let ast::ExprKind::Err(_) = expr.kind {
97                        err.downgrade_to_delayed_bug();
98                    }
99                    return Err(err);
100                }
101            }
102        },
103    })
104}
105
106fn check_meta_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) {
107    if let Delimiter::Parenthesis = delim {
108        return;
109    }
110    psess.dcx().emit_err(errors::MetaBadDelim {
111        span: span.entire(),
112        sugg: errors::MetaBadDelimSugg { open: span.open, close: span.close },
113    });
114}
115
116/// Checks that the given meta-item is compatible with this `AttributeTemplate`.
117fn is_attr_template_compatible(template: &AttributeTemplate, meta: &ast::MetaItemKind) -> bool {
118    let is_one_allowed_subword = |items: &[MetaItemInner]| match items {
119        [item] => item.is_word() && template.one_of.iter().any(|&word| item.has_name(word)),
120        _ => false,
121    };
122    match meta {
123        MetaItemKind::Word => template.word,
124        MetaItemKind::List(items) => template.list.is_some() || is_one_allowed_subword(items),
125        MetaItemKind::NameValue(lit) if lit.kind.is_str() => template.name_value_str.is_some(),
126        MetaItemKind::NameValue(..) => false,
127    }
128}
129
130pub fn check_builtin_meta_item(
131    psess: &ParseSess,
132    meta: &MetaItem,
133    style: ast::AttrStyle,
134    name: Symbol,
135    template: AttributeTemplate,
136    deny_unsafety: bool,
137) {
138    if !is_attr_template_compatible(&template, &meta.kind) {
139        // attrs with new parsers are locally validated so excluded here
140        emit_malformed_attribute(psess, style, meta.span, name, template);
141    }
142
143    if deny_unsafety && let Safety::Unsafe(unsafe_span) = meta.unsafety {
144        psess.dcx().emit_err(errors::InvalidAttrUnsafe {
145            span: unsafe_span,
146            name: AttrPath::from_ast(&meta.path, identity),
147        });
148    }
149}
150
151pub fn emit_malformed_attribute(
152    psess: &ParseSess,
153    style: ast::AttrStyle,
154    span: Span,
155    name: Symbol,
156    template: AttributeTemplate,
157) {
158    // Some of previously accepted forms were used in practice,
159    // report them as warnings for now.
160    let should_warn = |name| #[allow(non_exhaustive_omitted_patterns)] match name {
    sym::doc | sym::link | sym::test | sym::bench => true,
    _ => false,
}matches!(name, sym::doc | sym::link | sym::test | sym::bench);
161
162    let error_msg = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("malformed `{0}` attribute input",
                name))
    })format!("malformed `{name}` attribute input");
163    let mut suggestions = ::alloc::vec::Vec::new()vec![];
164    let inner = if style == ast::AttrStyle::Inner { "!" } else { "" };
165    if template.word {
166        suggestions.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#{0}[{1}]", inner, name))
    })format!("#{inner}[{name}]"));
167    }
168    if let Some(descr) = template.list {
169        for descr in descr {
170            suggestions.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#{0}[{1}({2})]", inner, name,
                descr))
    })format!("#{inner}[{name}({descr})]"));
171        }
172    }
173    suggestions.extend(template.one_of.iter().map(|&word| ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#{0}[{1}({2})]", inner, name,
                word))
    })format!("#{inner}[{name}({word})]")));
174    if let Some(descr) = template.name_value_str {
175        for descr in descr {
176            suggestions.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#{0}[{1} = \"{2}\"]", inner, name,
                descr))
    })format!("#{inner}[{name} = \"{descr}\"]"));
177        }
178    }
179    // If there are too many suggestions, better remove all of them as it's just noise at this
180    // point.
181    if suggestions.len() > 3 {
182        suggestions.clear();
183    }
184    if should_warn(name) {
185        psess.buffer_lint(
186            ILL_FORMED_ATTRIBUTE_INPUT,
187            span,
188            ast::CRATE_NODE_ID,
189            AttributeLintKind::IllFormedAttributeInput {
190                suggestions: suggestions.clone(),
191                docs: template.docs,
192                help: None,
193            },
194        );
195    } else {
196        suggestions.sort();
197        let mut err = psess.dcx().struct_span_err(span, error_msg).with_span_suggestions(
198            span,
199            if suggestions.len() == 1 {
200                "must be of the form"
201            } else {
202                "the following are the possible correct uses"
203            },
204            suggestions,
205            Applicability::HasPlaceholders,
206        );
207        if let Some(link) = template.docs {
208            err.note(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("for more information, visit <{0}>",
                link))
    })format!("for more information, visit <{link}>"));
209        }
210        err.emit();
211    }
212}