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