1use 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 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 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 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
116fn 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 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 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 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}