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::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 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 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 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
117fn 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 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 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 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}