1use std::convert::identity;
4use std::slice;
5
6use rustc_ast::token::Delimiter;
7use rustc_ast::tokenstream::DelimSpan;
8use rustc_ast::{
9 self as ast, AttrArgs, Attribute, DelimArgs, MetaItem, MetaItemInner, MetaItemKind, Safety,
10};
11use rustc_errors::{Applicability, PResult};
12use rustc_feature::{AttributeTemplate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute, template};
13use rustc_hir::AttrPath;
14use rustc_hir::lints::AttributeLintKind;
15use rustc_parse::parse_in;
16use rustc_session::errors::report_lit_error;
17use rustc_session::lint::builtin::ILL_FORMED_ATTRIBUTE_INPUT;
18use rustc_session::parse::ParseSess;
19use rustc_span::{Span, Symbol, sym};
20
21use crate::{AttributeParser, Late, session_diagnostics as errors};
22
23pub fn check_attr(psess: &ParseSess, attr: &Attribute) {
24 if attr.is_doc_comment() || attr.has_name(sym::cfg_trace) || attr.has_name(sym::cfg_attr_trace)
25 {
26 return;
27 }
28
29 let builtin_attr_info = attr.name().and_then(|name| BUILTIN_ATTRIBUTE_MAP.get(&name));
30
31 match builtin_attr_info {
33 Some(BuiltinAttribute { name, .. }) => {
34 if AttributeParser::<Late>::is_parsed_attribute(slice::from_ref(&name)) {
35 return;
36 }
37 match parse_meta(psess, attr) {
38 Ok(meta) => {
40 let lint_attrs = [sym::forbid, sym::allow, sym::warn, sym::deny, sym::expect];
42 if !lint_attrs.contains(name) {
::core::panicking::panic("assertion failed: lint_attrs.contains(name)")
};assert!(lint_attrs.contains(name));
43
44 let template = ::rustc_feature::AttributeTemplate {
word: false,
list: Some(&["lint1", "lint1, lint2, ...",
r#"lint1, lint2, lint3, reason = "...""#]),
one_of: &[],
name_value_str: None,
docs: Some("https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes"),
}template!(
45 List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#],
46 "https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes"
47 );
48 check_builtin_meta_item(psess, &meta, attr.style, *name, template, false)
49 }
50 Err(err) => {
51 err.emit();
52 }
53 }
54 }
55 _ => {
56 let attr_item = attr.get_normal_item();
57 if let AttrArgs::Eq { .. } = attr_item.args.unparsed_ref().unwrap() {
58 match parse_meta(psess, attr) {
60 Ok(_) => {}
61 Err(err) => {
62 err.emit();
63 }
64 }
65 }
66 }
67 }
68}
69
70pub fn parse_meta<'a>(psess: &'a ParseSess, attr: &Attribute) -> PResult<'a, MetaItem> {
71 let item = attr.get_normal_item();
72 Ok(MetaItem {
73 unsafety: item.unsafety,
74 span: attr.span,
75 path: item.path.clone(),
76 kind: match &item.args.unparsed_ref().unwrap() {
77 AttrArgs::Empty => MetaItemKind::Word,
78 AttrArgs::Delimited(DelimArgs { dspan, delim, tokens }) => {
79 check_meta_bad_delim(psess, *dspan, *delim);
80 let nmis =
81 parse_in(psess, tokens.clone(), "meta list", |p| p.parse_meta_seq_top())?;
82 MetaItemKind::List(nmis)
83 }
84 AttrArgs::Eq { expr, .. } => {
85 if let ast::ExprKind::Lit(token_lit) = expr.kind {
86 let res = ast::MetaItemLit::from_token_lit(token_lit, expr.span);
87 let res = match res {
88 Ok(lit) => {
89 if token_lit.suffix.is_some() {
90 let mut err = psess.dcx().struct_span_err(
91 expr.span,
92 "suffixed literals are not allowed in attributes",
93 );
94 err.help(
95 "instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), \
96 use an unsuffixed version (`1`, `1.0`, etc.)",
97 );
98 return Err(err);
99 } else {
100 MetaItemKind::NameValue(lit)
101 }
102 }
103 Err(err) => {
104 let guar = report_lit_error(psess, err, token_lit, expr.span);
105 let lit = ast::MetaItemLit {
106 symbol: token_lit.symbol,
107 suffix: token_lit.suffix,
108 kind: ast::LitKind::Err(guar),
109 span: expr.span,
110 };
111 MetaItemKind::NameValue(lit)
112 }
113 };
114 res
115 } else {
116 let msg = "attribute value must be a literal";
123 let mut err = psess.dcx().struct_span_err(expr.span, msg);
124 if let ast::ExprKind::Err(_) = expr.kind {
125 err.downgrade_to_delayed_bug();
126 }
127 return Err(err);
128 }
129 }
130 },
131 })
132}
133
134fn check_meta_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) {
135 if let Delimiter::Parenthesis = delim {
136 return;
137 }
138 psess.dcx().emit_err(errors::MetaBadDelim {
139 span: span.entire(),
140 sugg: errors::MetaBadDelimSugg { open: span.open, close: span.close },
141 });
142}
143
144fn is_attr_template_compatible(template: &AttributeTemplate, meta: &ast::MetaItemKind) -> bool {
146 let is_one_allowed_subword = |items: &[MetaItemInner]| match items {
147 [item] => item.is_word() && template.one_of.iter().any(|&word| item.has_name(word)),
148 _ => false,
149 };
150 match meta {
151 MetaItemKind::Word => template.word,
152 MetaItemKind::List(items) => template.list.is_some() || is_one_allowed_subword(items),
153 MetaItemKind::NameValue(lit) if lit.kind.is_str() => template.name_value_str.is_some(),
154 MetaItemKind::NameValue(..) => false,
155 }
156}
157
158pub fn check_builtin_meta_item(
159 psess: &ParseSess,
160 meta: &MetaItem,
161 style: ast::AttrStyle,
162 name: Symbol,
163 template: AttributeTemplate,
164 deny_unsafety: bool,
165) {
166 if !is_attr_template_compatible(&template, &meta.kind) {
167 emit_malformed_attribute(psess, style, meta.span, name, template);
169 }
170
171 if deny_unsafety && let Safety::Unsafe(unsafe_span) = meta.unsafety {
172 psess.dcx().emit_err(errors::InvalidAttrUnsafe {
173 span: unsafe_span,
174 name: AttrPath::from_ast(&meta.path, identity),
175 });
176 }
177}
178
179pub fn emit_malformed_attribute(
180 psess: &ParseSess,
181 style: ast::AttrStyle,
182 span: Span,
183 name: Symbol,
184 template: AttributeTemplate,
185) {
186 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);
189
190 let error_msg = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("malformed `{0}` attribute input",
name))
})format!("malformed `{name}` attribute input");
191 let mut suggestions = ::alloc::vec::Vec::new()vec![];
192 let inner = if style == ast::AttrStyle::Inner { "!" } else { "" };
193 if template.word {
194 suggestions.push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("#{0}[{1}]", inner, name))
})format!("#{inner}[{name}]"));
195 }
196 if let Some(descr) = template.list {
197 for descr in descr {
198 suggestions.push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("#{0}[{1}({2})]", inner, name,
descr))
})format!("#{inner}[{name}({descr})]"));
199 }
200 }
201 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})]")));
202 if let Some(descr) = template.name_value_str {
203 for descr in descr {
204 suggestions.push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("#{0}[{1} = \"{2}\"]", inner, name,
descr))
})format!("#{inner}[{name} = \"{descr}\"]"));
205 }
206 }
207 if suggestions.len() > 3 {
210 suggestions.clear();
211 }
212 if should_warn(name) {
213 psess.buffer_lint(
214 ILL_FORMED_ATTRIBUTE_INPUT,
215 span,
216 ast::CRATE_NODE_ID,
217 AttributeLintKind::IllFormedAttributeInput {
218 suggestions: suggestions.clone(),
219 docs: template.docs,
220 help: None,
221 },
222 );
223 } else {
224 suggestions.sort();
225 let mut err = psess.dcx().struct_span_err(span, error_msg).with_span_suggestions(
226 span,
227 if suggestions.len() == 1 {
228 "must be of the form"
229 } else {
230 "the following are the possible correct uses"
231 },
232 suggestions,
233 Applicability::HasPlaceholders,
234 );
235 if let Some(link) = template.docs {
236 err.note(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("for more information, visit <{0}>",
link))
})format!("for more information, visit <{link}>"));
237 }
238 err.emit();
239 }
240}