]>
Commit | Line | Data |
---|---|---|
041b39d2 XL |
1 | //! This module implements some validity checks for attributes. |
2 | //! In particular it verifies that `#[inline]` and `#[repr]` attributes are | |
3 | //! attached to items that actually support them and if there are | |
4 | //! conflicts between multiple such attributes attached to the same | |
5 | //! item. | |
6 | ||
ba9703b0 XL |
7 | use rustc_middle::hir::map::Map; |
8 | use rustc_middle::ty::query::Providers; | |
9 | use rustc_middle::ty::TyCtxt; | |
e1599b0c | 10 | |
94222f64 | 11 | use rustc_ast::{ast, AttrStyle, Attribute, Lit, LitKind, NestedMetaItem}; |
3c0e092e | 12 | use rustc_data_structures::fx::FxHashMap; |
cdc7bbd5 | 13 | use rustc_errors::{pluralize, struct_span_err, Applicability}; |
3c0e092e | 14 | use rustc_feature::{AttributeDuplicates, AttributeType, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP}; |
dfeec247 | 15 | use rustc_hir as hir; |
f035d41b | 16 | use rustc_hir::def_id::LocalDefId; |
dfeec247 | 17 | use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; |
17df50a5 | 18 | use rustc_hir::{self, FnSig, ForeignItem, HirId, Item, ItemKind, TraitItem, CRATE_HIR_ID}; |
74b04a01 | 19 | use rustc_hir::{MethodKind, Target}; |
6a06907d XL |
20 | use rustc_session::lint::builtin::{ |
21 | CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, UNUSED_ATTRIBUTES, | |
22 | }; | |
74b04a01 | 23 | use rustc_session::parse::feature_err; |
29967ef6 | 24 | use rustc_span::symbol::{sym, Symbol}; |
17df50a5 | 25 | use rustc_span::{MultiSpan, Span, DUMMY_SP}; |
3c0e092e | 26 | use std::collections::hash_map::Entry; |
b039eaaf | 27 | |
f035d41b XL |
28 | pub(crate) fn target_from_impl_item<'tcx>( |
29 | tcx: TyCtxt<'tcx>, | |
30 | impl_item: &hir::ImplItem<'_>, | |
31 | ) -> Target { | |
74b04a01 XL |
32 | match impl_item.kind { |
33 | hir::ImplItemKind::Const(..) => Target::AssocConst, | |
ba9703b0 | 34 | hir::ImplItemKind::Fn(..) => { |
a2a8927a | 35 | let parent_hir_id = tcx.hir().get_parent_item(impl_item.hir_id()).expect_owner(); |
74b04a01 XL |
36 | let containing_item = tcx.hir().expect_item(parent_hir_id); |
37 | let containing_impl_is_for_trait = match &containing_item.kind { | |
5869c6ff | 38 | hir::ItemKind::Impl(impl_) => impl_.of_trait.is_some(), |
74b04a01 XL |
39 | _ => bug!("parent of an ImplItem must be an Impl"), |
40 | }; | |
41 | if containing_impl_is_for_trait { | |
e74abb32 | 42 | Target::Method(MethodKind::Trait { body: true }) |
74b04a01 XL |
43 | } else { |
44 | Target::Method(MethodKind::Inherent) | |
e74abb32 | 45 | } |
b039eaaf | 46 | } |
f035d41b | 47 | hir::ImplItemKind::TyAlias(..) => Target::AssocTy, |
b039eaaf SL |
48 | } |
49 | } | |
50 | ||
1b1a35ee XL |
51 | #[derive(Clone, Copy)] |
52 | enum ItemLike<'tcx> { | |
53 | Item(&'tcx Item<'tcx>), | |
54 | ForeignItem(&'tcx ForeignItem<'tcx>), | |
55 | } | |
56 | ||
dc9dc135 XL |
57 | struct CheckAttrVisitor<'tcx> { |
58 | tcx: TyCtxt<'tcx>, | |
b039eaaf SL |
59 | } |
60 | ||
a2a8927a | 61 | impl CheckAttrVisitor<'_> { |
9fa01778 | 62 | /// Checks any attribute. |
e74abb32 XL |
63 | fn check_attributes( |
64 | &self, | |
65 | hir_id: HirId, | |
e74abb32 XL |
66 | span: &Span, |
67 | target: Target, | |
1b1a35ee | 68 | item: Option<ItemLike<'_>>, |
e74abb32 | 69 | ) { |
c295e0f8 | 70 | let mut doc_aliases = FxHashMap::default(); |
e74abb32 | 71 | let mut is_valid = true; |
17df50a5 | 72 | let mut specified_inline = None; |
3c0e092e | 73 | let mut seen = FxHashMap::default(); |
6a06907d | 74 | let attrs = self.tcx.hir().attrs(hir_id); |
e74abb32 | 75 | for attr in attrs { |
94222f64 | 76 | let attr_is_valid = match attr.name_or_empty() { |
cdc7bbd5 XL |
77 | sym::inline => self.check_inline(hir_id, attr, span, target), |
78 | sym::non_exhaustive => self.check_non_exhaustive(hir_id, attr, span, target), | |
79 | sym::marker => self.check_marker(hir_id, attr, span, target), | |
80 | sym::target_feature => self.check_target_feature(hir_id, attr, span, target), | |
81 | sym::track_caller => { | |
82 | self.check_track_caller(hir_id, &attr.span, attrs, span, target) | |
1b1a35ee | 83 | } |
c295e0f8 XL |
84 | sym::doc => self.check_doc_attrs( |
85 | attr, | |
86 | hir_id, | |
87 | target, | |
88 | &mut specified_inline, | |
89 | &mut doc_aliases, | |
90 | ), | |
cdc7bbd5 XL |
91 | sym::no_link => self.check_no_link(hir_id, &attr, span, target), |
92 | sym::export_name => self.check_export_name(hir_id, &attr, span, target), | |
cdc7bbd5 XL |
93 | sym::rustc_layout_scalar_valid_range_start |
94 | | sym::rustc_layout_scalar_valid_range_end => { | |
95 | self.check_rustc_layout_scalar_valid_range(&attr, span, target) | |
96 | } | |
97 | sym::allow_internal_unstable => { | |
98 | self.check_allow_internal_unstable(hir_id, &attr, span, target, &attrs) | |
99 | } | |
100 | sym::rustc_allow_const_fn_unstable => { | |
101 | self.check_rustc_allow_const_fn_unstable(hir_id, &attr, span, target) | |
102 | } | |
103 | sym::naked => self.check_naked(hir_id, attr, span, target), | |
104 | sym::rustc_legacy_const_generics => { | |
105 | self.check_rustc_legacy_const_generics(&attr, span, target, item) | |
106 | } | |
107 | sym::rustc_clean | |
108 | | sym::rustc_dirty | |
109 | | sym::rustc_if_this_changed | |
110 | | sym::rustc_then_this_would_need => self.check_rustc_dirty_clean(&attr), | |
136023e0 XL |
111 | sym::cmse_nonsecure_entry => self.check_cmse_nonsecure_entry(attr, span, target), |
112 | sym::default_method_body_is_const => { | |
113 | self.check_default_method_body_is_const(attr, span, target) | |
114 | } | |
c295e0f8 | 115 | sym::must_not_suspend => self.check_must_not_suspend(&attr, span, target), |
3c0e092e | 116 | sym::must_use => self.check_must_use(hir_id, &attr, span, target), |
94222f64 XL |
117 | sym::rustc_const_unstable |
118 | | sym::rustc_const_stable | |
119 | | sym::unstable | |
120 | | sym::stable | |
121 | | sym::rustc_promotable => self.check_stability_promotable(&attr, span, target), | |
cdc7bbd5 | 122 | _ => true, |
e74abb32 | 123 | }; |
94222f64 XL |
124 | is_valid &= attr_is_valid; |
125 | ||
cdc7bbd5 XL |
126 | // lint-only checks |
127 | match attr.name_or_empty() { | |
128 | sym::cold => self.check_cold(hir_id, attr, span, target), | |
129 | sym::link_name => self.check_link_name(hir_id, attr, span, target), | |
130 | sym::link_section => self.check_link_section(hir_id, attr, span, target), | |
131 | sym::no_mangle => self.check_no_mangle(hir_id, attr, span, target), | |
94222f64 XL |
132 | sym::deprecated | sym::rustc_deprecated => { |
133 | self.check_deprecated(hir_id, attr, span, target) | |
134 | } | |
135 | sym::macro_use | sym::macro_escape => self.check_macro_use(hir_id, attr, target), | |
136 | sym::path => self.check_generic_attr(hir_id, attr, target, &[Target::Mod]), | |
137 | sym::cfg_attr => self.check_cfg_attr(hir_id, attr), | |
138 | sym::plugin_registrar => self.check_plugin_registrar(hir_id, attr, target), | |
139 | sym::macro_export => self.check_macro_export(hir_id, attr, target), | |
140 | sym::ignore | sym::should_panic | sym::proc_macro_derive => { | |
141 | self.check_generic_attr(hir_id, attr, target, &[Target::Fn]) | |
142 | } | |
143 | sym::automatically_derived => { | |
144 | self.check_generic_attr(hir_id, attr, target, &[Target::Impl]) | |
145 | } | |
146 | sym::no_implicit_prelude => { | |
147 | self.check_generic_attr(hir_id, attr, target, &[Target::Mod]) | |
148 | } | |
149 | _ => {} | |
150 | } | |
151 | ||
3c0e092e XL |
152 | let builtin = attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name)); |
153 | ||
94222f64 | 154 | if hir_id != CRATE_HIR_ID { |
3c0e092e | 155 | if let Some(BuiltinAttribute { type_: AttributeType::CrateLevel, .. }) = |
94222f64 XL |
156 | attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name)) |
157 | { | |
158 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { | |
159 | let msg = match attr.style { | |
160 | ast::AttrStyle::Outer => { | |
161 | "crate-level attribute should be an inner attribute: add an exclamation \ | |
162 | mark: `#![foo]`" | |
163 | } | |
164 | ast::AttrStyle::Inner => "crate-level attribute should be in the root module", | |
165 | }; | |
166 | lint.build(msg).emit() | |
167 | }); | |
168 | } | |
169 | } | |
170 | ||
3c0e092e XL |
171 | if let Some(BuiltinAttribute { duplicates, .. }) = builtin { |
172 | check_duplicates(self.tcx, attr, hir_id, *duplicates, &mut seen); | |
173 | } | |
174 | ||
175 | // Warn on useless empty attributes. | |
176 | if matches!( | |
177 | attr.name_or_empty(), | |
178 | sym::macro_use | |
179 | | sym::allow | |
180 | | sym::warn | |
181 | | sym::deny | |
182 | | sym::forbid | |
183 | | sym::feature | |
184 | | sym::repr | |
185 | | sym::target_feature | |
186 | ) && attr.meta_item_list().map_or(false, |list| list.is_empty()) | |
187 | { | |
188 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { | |
189 | lint.build("unused attribute") | |
190 | .span_suggestion( | |
94222f64 | 191 | attr.span, |
3c0e092e XL |
192 | "remove this attribute", |
193 | String::new(), | |
194 | Applicability::MachineApplicable, | |
195 | ) | |
196 | .note(&format!( | |
197 | "attribute `{}` with an empty list has no effect", | |
198 | attr.name_or_empty() | |
199 | )) | |
200 | .emit(); | |
201 | }); | |
cdc7bbd5 | 202 | } |
041b39d2 | 203 | } |
2c00a5a8 | 204 | |
e74abb32 XL |
205 | if !is_valid { |
206 | return; | |
207 | } | |
208 | ||
29967ef6 | 209 | if matches!(target, Target::Closure | Target::Fn | Target::Method(_) | Target::ForeignFn) { |
f9f354fc | 210 | self.tcx.ensure().codegen_fn_attrs(self.tcx.hir().local_def_id(hir_id)); |
e74abb32 XL |
211 | } |
212 | ||
dfeec247 | 213 | self.check_repr(attrs, span, target, item, hir_id); |
e74abb32 XL |
214 | self.check_used(attrs, target); |
215 | } | |
216 | ||
5869c6ff XL |
217 | fn inline_attr_str_error_with_macro_def(&self, hir_id: HirId, attr: &Attribute, sym: &str) { |
218 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { | |
219 | lint.build(&format!( | |
220 | "`#[{}]` is ignored on struct fields, match arms and macro defs", | |
221 | sym, | |
222 | )) | |
223 | .warn( | |
224 | "this was previously accepted by the compiler but is \ | |
225 | being phased out; it will become a hard error in \ | |
226 | a future release!", | |
227 | ) | |
228 | .note( | |
229 | "see issue #80564 <https://github.com/rust-lang/rust/issues/80564> \ | |
230 | for more information", | |
231 | ) | |
232 | .emit(); | |
233 | }); | |
234 | } | |
235 | ||
236 | fn inline_attr_str_error_without_macro_def(&self, hir_id: HirId, attr: &Attribute, sym: &str) { | |
237 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { | |
238 | lint.build(&format!("`#[{}]` is ignored on struct fields and match arms", sym)) | |
239 | .warn( | |
240 | "this was previously accepted by the compiler but is \ | |
241 | being phased out; it will become a hard error in \ | |
242 | a future release!", | |
243 | ) | |
244 | .note( | |
245 | "see issue #80564 <https://github.com/rust-lang/rust/issues/80564> \ | |
246 | for more information", | |
247 | ) | |
248 | .emit(); | |
249 | }); | |
250 | } | |
251 | ||
e74abb32 XL |
252 | /// Checks if an `#[inline]` is applied to a function or a closure. Returns `true` if valid. |
253 | fn check_inline(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) -> bool { | |
254 | match target { | |
dfeec247 XL |
255 | Target::Fn |
256 | | Target::Closure | |
ba9703b0 | 257 | | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true, |
e74abb32 | 258 | Target::Method(MethodKind::Trait { body: false }) | Target::ForeignFn => { |
74b04a01 XL |
259 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { |
260 | lint.build("`#[inline]` is ignored on function prototypes").emit() | |
261 | }); | |
e74abb32 XL |
262 | true |
263 | } | |
264 | // FIXME(#65833): We permit associated consts to have an `#[inline]` attribute with | |
265 | // just a lint, because we previously erroneously allowed it and some crates used it | |
266 | // accidentally, to to be compatible with crates depending on them, we can't throw an | |
267 | // error here. | |
268 | Target::AssocConst => { | |
74b04a01 XL |
269 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { |
270 | lint.build("`#[inline]` is ignored on constants") | |
271 | .warn( | |
272 | "this was previously accepted by the compiler but is \ | |
1b1a35ee XL |
273 | being phased out; it will become a hard error in \ |
274 | a future release!", | |
74b04a01 XL |
275 | ) |
276 | .note( | |
277 | "see issue #65833 <https://github.com/rust-lang/rust/issues/65833> \ | |
1b1a35ee | 278 | for more information", |
74b04a01 XL |
279 | ) |
280 | .emit(); | |
281 | }); | |
e74abb32 XL |
282 | true |
283 | } | |
5869c6ff XL |
284 | // FIXME(#80564): Same for fields, arms, and macro defs |
285 | Target::Field | Target::Arm | Target::MacroDef => { | |
286 | self.inline_attr_str_error_with_macro_def(hir_id, attr, "inline"); | |
287 | true | |
288 | } | |
e74abb32 XL |
289 | _ => { |
290 | struct_span_err!( | |
291 | self.tcx.sess, | |
292 | attr.span, | |
293 | E0518, | |
294 | "attribute should be applied to function or closure", | |
dfeec247 XL |
295 | ) |
296 | .span_label(*span, "not a function or closure") | |
297 | .emit(); | |
e74abb32 XL |
298 | false |
299 | } | |
300 | } | |
041b39d2 XL |
301 | } |
302 | ||
94222f64 XL |
303 | fn check_generic_attr( |
304 | &self, | |
305 | hir_id: HirId, | |
306 | attr: &Attribute, | |
307 | target: Target, | |
308 | allowed_targets: &[Target], | |
309 | ) { | |
310 | if !allowed_targets.iter().any(|t| t == &target) { | |
311 | let name = attr.name_or_empty(); | |
312 | let mut i = allowed_targets.iter(); | |
313 | // Pluralize | |
314 | let b = i.next().map_or_else(String::new, |t| t.to_string() + "s"); | |
315 | let supported_names = i.enumerate().fold(b, |mut b, (i, allowed_target)| { | |
316 | if allowed_targets.len() > 2 && i == allowed_targets.len() - 2 { | |
317 | b.push_str(", and "); | |
318 | } else if allowed_targets.len() == 2 && i == allowed_targets.len() - 2 { | |
319 | b.push_str(" and "); | |
320 | } else { | |
321 | b.push_str(", "); | |
322 | } | |
323 | // Pluralize | |
324 | b.push_str(&(allowed_target.to_string() + "s")); | |
325 | b | |
326 | }); | |
327 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { | |
328 | lint.build(&format!("`#[{name}]` only has an effect on {}", supported_names)) | |
329 | .emit(); | |
330 | }); | |
331 | } | |
332 | } | |
333 | ||
fc512014 | 334 | /// Checks if `#[naked]` is applied to a function definition. |
5869c6ff | 335 | fn check_naked(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) -> bool { |
fc512014 XL |
336 | match target { |
337 | Target::Fn | |
338 | | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true, | |
5869c6ff XL |
339 | // FIXME(#80564): We permit struct fields, match arms and macro defs to have an |
340 | // `#[allow_internal_unstable]` attribute with just a lint, because we previously | |
341 | // erroneously allowed it and some crates used it accidentally, to to be compatible | |
342 | // with crates depending on them, we can't throw an error here. | |
343 | Target::Field | Target::Arm | Target::MacroDef => { | |
344 | self.inline_attr_str_error_with_macro_def(hir_id, attr, "naked"); | |
345 | true | |
346 | } | |
fc512014 XL |
347 | _ => { |
348 | self.tcx | |
349 | .sess | |
350 | .struct_span_err( | |
351 | attr.span, | |
352 | "attribute should be applied to a function definition", | |
353 | ) | |
354 | .span_label(*span, "not a function definition") | |
355 | .emit(); | |
356 | false | |
357 | } | |
358 | } | |
359 | } | |
360 | ||
136023e0 XL |
361 | /// Checks if `#[cmse_nonsecure_entry]` is applied to a function definition. |
362 | fn check_cmse_nonsecure_entry(&self, attr: &Attribute, span: &Span, target: Target) -> bool { | |
363 | match target { | |
364 | Target::Fn | |
365 | | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true, | |
366 | _ => { | |
367 | self.tcx | |
368 | .sess | |
369 | .struct_span_err( | |
370 | attr.span, | |
371 | "attribute should be applied to a function definition", | |
372 | ) | |
373 | .span_label(*span, "not a function definition") | |
374 | .emit(); | |
375 | false | |
376 | } | |
377 | } | |
378 | } | |
379 | ||
e74abb32 XL |
380 | /// Checks if a `#[track_caller]` is applied to a non-naked function. Returns `true` if valid. |
381 | fn check_track_caller( | |
382 | &self, | |
5869c6ff | 383 | hir_id: HirId, |
e74abb32 | 384 | attr_span: &Span, |
a2a8927a | 385 | attrs: &[Attribute], |
e74abb32 XL |
386 | span: &Span, |
387 | target: Target, | |
388 | ) -> bool { | |
389 | match target { | |
fc512014 | 390 | _ if attrs.iter().any(|attr| attr.has_name(sym::naked)) => { |
e74abb32 XL |
391 | struct_span_err!( |
392 | self.tcx.sess, | |
393 | *attr_span, | |
394 | E0736, | |
395 | "cannot use `#[track_caller]` with `#[naked]`", | |
dfeec247 XL |
396 | ) |
397 | .emit(); | |
e74abb32 XL |
398 | false |
399 | } | |
1b1a35ee | 400 | Target::Fn | Target::Method(..) | Target::ForeignFn | Target::Closure => true, |
5869c6ff XL |
401 | // FIXME(#80564): We permit struct fields, match arms and macro defs to have an |
402 | // `#[track_caller]` attribute with just a lint, because we previously | |
403 | // erroneously allowed it and some crates used it accidentally, to to be compatible | |
404 | // with crates depending on them, we can't throw an error here. | |
405 | Target::Field | Target::Arm | Target::MacroDef => { | |
406 | for attr in attrs { | |
407 | self.inline_attr_str_error_with_macro_def(hir_id, attr, "track_caller"); | |
408 | } | |
409 | true | |
410 | } | |
e74abb32 XL |
411 | _ => { |
412 | struct_span_err!( | |
413 | self.tcx.sess, | |
414 | *attr_span, | |
415 | E0739, | |
416 | "attribute should be applied to function" | |
417 | ) | |
418 | .span_label(*span, "not a function") | |
83c7162d | 419 | .emit(); |
e74abb32 XL |
420 | false |
421 | } | |
83c7162d XL |
422 | } |
423 | } | |
424 | ||
e74abb32 | 425 | /// Checks if the `#[non_exhaustive]` attribute on an `item` is valid. Returns `true` if valid. |
5869c6ff XL |
426 | fn check_non_exhaustive( |
427 | &self, | |
428 | hir_id: HirId, | |
429 | attr: &Attribute, | |
430 | span: &Span, | |
431 | target: Target, | |
432 | ) -> bool { | |
83c7162d | 433 | match target { |
29967ef6 | 434 | Target::Struct | Target::Enum | Target::Variant => true, |
5869c6ff XL |
435 | // FIXME(#80564): We permit struct fields, match arms and macro defs to have an |
436 | // `#[non_exhaustive]` attribute with just a lint, because we previously | |
437 | // erroneously allowed it and some crates used it accidentally, to to be compatible | |
438 | // with crates depending on them, we can't throw an error here. | |
439 | Target::Field | Target::Arm | Target::MacroDef => { | |
440 | self.inline_attr_str_error_with_macro_def(hir_id, attr, "non_exhaustive"); | |
441 | true | |
442 | } | |
83c7162d | 443 | _ => { |
dfeec247 XL |
444 | struct_span_err!( |
445 | self.tcx.sess, | |
446 | attr.span, | |
447 | E0701, | |
448 | "attribute can only be applied to a struct or enum" | |
449 | ) | |
450 | .span_label(*span, "not a struct or enum") | |
451 | .emit(); | |
e74abb32 | 452 | false |
83c7162d XL |
453 | } |
454 | } | |
b039eaaf SL |
455 | } |
456 | ||
e74abb32 | 457 | /// Checks if the `#[marker]` attribute on an `item` is valid. Returns `true` if valid. |
5869c6ff | 458 | fn check_marker(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) -> bool { |
0bf4aa26 | 459 | match target { |
e74abb32 | 460 | Target::Trait => true, |
5869c6ff XL |
461 | // FIXME(#80564): We permit struct fields, match arms and macro defs to have an |
462 | // `#[marker]` attribute with just a lint, because we previously | |
463 | // erroneously allowed it and some crates used it accidentally, to to be compatible | |
464 | // with crates depending on them, we can't throw an error here. | |
465 | Target::Field | Target::Arm | Target::MacroDef => { | |
466 | self.inline_attr_str_error_with_macro_def(hir_id, attr, "marker"); | |
467 | true | |
468 | } | |
0bf4aa26 | 469 | _ => { |
dfeec247 XL |
470 | self.tcx |
471 | .sess | |
0bf4aa26 | 472 | .struct_span_err(attr.span, "attribute can only be applied to a trait") |
e74abb32 | 473 | .span_label(*span, "not a trait") |
0bf4aa26 | 474 | .emit(); |
e74abb32 | 475 | false |
0bf4aa26 XL |
476 | } |
477 | } | |
0bf4aa26 XL |
478 | } |
479 | ||
e74abb32 | 480 | /// Checks if the `#[target_feature]` attribute on `item` is valid. Returns `true` if valid. |
1b1a35ee XL |
481 | fn check_target_feature( |
482 | &self, | |
483 | hir_id: HirId, | |
484 | attr: &Attribute, | |
485 | span: &Span, | |
486 | target: Target, | |
487 | ) -> bool { | |
e74abb32 | 488 | match target { |
dfeec247 | 489 | Target::Fn |
ba9703b0 | 490 | | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true, |
1b1a35ee XL |
491 | // FIXME: #[target_feature] was previously erroneously allowed on statements and some |
492 | // crates used this, so only emit a warning. | |
493 | Target::Statement => { | |
494 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { | |
495 | lint.build("attribute should be applied to a function") | |
496 | .warn( | |
497 | "this was previously accepted by the compiler but is \ | |
498 | being phased out; it will become a hard error in \ | |
499 | a future release!", | |
500 | ) | |
501 | .span_label(*span, "not a function") | |
502 | .emit(); | |
503 | }); | |
504 | true | |
505 | } | |
5869c6ff XL |
506 | // FIXME(#80564): We permit struct fields, match arms and macro defs to have an |
507 | // `#[target_feature]` attribute with just a lint, because we previously | |
508 | // erroneously allowed it and some crates used it accidentally, to to be compatible | |
509 | // with crates depending on them, we can't throw an error here. | |
510 | Target::Field | Target::Arm | Target::MacroDef => { | |
511 | self.inline_attr_str_error_with_macro_def(hir_id, attr, "target_feature"); | |
512 | true | |
513 | } | |
e74abb32 | 514 | _ => { |
dfeec247 XL |
515 | self.tcx |
516 | .sess | |
e74abb32 XL |
517 | .struct_span_err(attr.span, "attribute should be applied to a function") |
518 | .span_label(*span, "not a function") | |
519 | .emit(); | |
520 | false | |
dfeec247 | 521 | } |
e74abb32 XL |
522 | } |
523 | } | |
524 | ||
fc512014 | 525 | fn doc_attr_str_error(&self, meta: &NestedMetaItem, attr_name: &str) { |
1b1a35ee XL |
526 | self.tcx |
527 | .sess | |
528 | .struct_span_err( | |
529 | meta.span(), | |
fc512014 | 530 | &format!("doc {0} attribute expects a string: #[doc({0} = \"a\")]", attr_name), |
1b1a35ee XL |
531 | ) |
532 | .emit(); | |
533 | } | |
534 | ||
6a06907d XL |
535 | fn check_doc_alias_value( |
536 | &self, | |
537 | meta: &NestedMetaItem, | |
538 | doc_alias: &str, | |
539 | hir_id: HirId, | |
540 | target: Target, | |
541 | is_list: bool, | |
c295e0f8 | 542 | aliases: &mut FxHashMap<String, Span>, |
6a06907d XL |
543 | ) -> bool { |
544 | let tcx = self.tcx; | |
545 | let err_fn = move |span: Span, msg: &str| { | |
546 | tcx.sess.span_err( | |
547 | span, | |
548 | &format!( | |
549 | "`#[doc(alias{})]` {}", | |
550 | if is_list { "(\"...\")" } else { " = \"...\"" }, | |
551 | msg, | |
552 | ), | |
553 | ); | |
554 | false | |
555 | }; | |
fc512014 | 556 | if doc_alias.is_empty() { |
6a06907d XL |
557 | return err_fn( |
558 | meta.name_value_literal_span().unwrap_or_else(|| meta.span()), | |
559 | "attribute cannot have empty value", | |
560 | ); | |
fc512014 XL |
561 | } |
562 | if let Some(c) = | |
563 | doc_alias.chars().find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' ')) | |
564 | { | |
6a06907d XL |
565 | self.tcx.sess.span_err( |
566 | meta.name_value_literal_span().unwrap_or_else(|| meta.span()), | |
567 | &format!( | |
568 | "{:?} character isn't allowed in `#[doc(alias{})]`", | |
569 | c, | |
570 | if is_list { "(\"...\")" } else { " = \"...\"" }, | |
571 | ), | |
572 | ); | |
fc512014 XL |
573 | return false; |
574 | } | |
575 | if doc_alias.starts_with(' ') || doc_alias.ends_with(' ') { | |
6a06907d XL |
576 | return err_fn( |
577 | meta.name_value_literal_span().unwrap_or_else(|| meta.span()), | |
578 | "cannot start or end with ' '", | |
579 | ); | |
fc512014 XL |
580 | } |
581 | if let Some(err) = match target { | |
582 | Target::Impl => Some("implementation block"), | |
583 | Target::ForeignMod => Some("extern block"), | |
584 | Target::AssocTy => { | |
a2a8927a | 585 | let parent_hir_id = self.tcx.hir().get_parent_item(hir_id).expect_owner(); |
fc512014 XL |
586 | let containing_item = self.tcx.hir().expect_item(parent_hir_id); |
587 | if Target::from_item(containing_item) == Target::Impl { | |
588 | Some("type alias in implementation block") | |
589 | } else { | |
590 | None | |
591 | } | |
592 | } | |
593 | Target::AssocConst => { | |
a2a8927a | 594 | let parent_hir_id = self.tcx.hir().get_parent_item(hir_id).expect_owner(); |
fc512014 XL |
595 | let containing_item = self.tcx.hir().expect_item(parent_hir_id); |
596 | // We can't link to trait impl's consts. | |
597 | let err = "associated constant in trait implementation block"; | |
598 | match containing_item.kind { | |
5869c6ff | 599 | ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }) => Some(err), |
fc512014 XL |
600 | _ => None, |
601 | } | |
602 | } | |
136023e0 XL |
603 | // we check the validity of params elsewhere |
604 | Target::Param => return false, | |
fc512014 XL |
605 | _ => None, |
606 | } { | |
6a06907d | 607 | return err_fn(meta.span(), &format!("isn't allowed on {}", err)); |
fc512014 | 608 | } |
5869c6ff | 609 | let item_name = self.tcx.hir().name(hir_id); |
a2a8927a | 610 | if item_name.as_str() == doc_alias { |
6a06907d XL |
611 | return err_fn(meta.span(), "is the same as the item's name"); |
612 | } | |
c295e0f8 XL |
613 | let span = meta.span(); |
614 | if let Err(entry) = aliases.try_insert(doc_alias.to_owned(), span) { | |
615 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, span, |lint| { | |
616 | lint.build("doc alias is duplicated") | |
617 | .span_label(*entry.entry.get(), "first defined here") | |
618 | .emit(); | |
619 | }); | |
620 | } | |
6a06907d XL |
621 | true |
622 | } | |
623 | ||
c295e0f8 XL |
624 | fn check_doc_alias( |
625 | &self, | |
626 | meta: &NestedMetaItem, | |
627 | hir_id: HirId, | |
628 | target: Target, | |
629 | aliases: &mut FxHashMap<String, Span>, | |
630 | ) -> bool { | |
6a06907d XL |
631 | if let Some(values) = meta.meta_item_list() { |
632 | let mut errors = 0; | |
633 | for v in values { | |
634 | match v.literal() { | |
635 | Some(l) => match l.kind { | |
636 | LitKind::Str(s, _) => { | |
c295e0f8 XL |
637 | if !self.check_doc_alias_value( |
638 | v, | |
a2a8927a | 639 | s.as_str(), |
c295e0f8 XL |
640 | hir_id, |
641 | target, | |
642 | true, | |
643 | aliases, | |
644 | ) { | |
6a06907d XL |
645 | errors += 1; |
646 | } | |
647 | } | |
648 | _ => { | |
649 | self.tcx | |
650 | .sess | |
651 | .struct_span_err( | |
652 | v.span(), | |
653 | "`#[doc(alias(\"a\"))]` expects string literals", | |
654 | ) | |
655 | .emit(); | |
656 | errors += 1; | |
657 | } | |
658 | }, | |
659 | None => { | |
660 | self.tcx | |
661 | .sess | |
662 | .struct_span_err( | |
663 | v.span(), | |
664 | "`#[doc(alias(\"a\"))]` expects string literals", | |
665 | ) | |
666 | .emit(); | |
667 | errors += 1; | |
668 | } | |
669 | } | |
670 | } | |
671 | errors == 0 | |
672 | } else if let Some(doc_alias) = meta.value_str().map(|s| s.to_string()) { | |
c295e0f8 | 673 | self.check_doc_alias_value(meta, &doc_alias, hir_id, target, false, aliases) |
6a06907d | 674 | } else { |
5869c6ff XL |
675 | self.tcx |
676 | .sess | |
677 | .struct_span_err( | |
678 | meta.span(), | |
6a06907d XL |
679 | "doc alias attribute expects a string `#[doc(alias = \"a\")]` or a list of \ |
680 | strings `#[doc(alias(\"a\", \"b\"))]`", | |
5869c6ff XL |
681 | ) |
682 | .emit(); | |
6a06907d | 683 | false |
5869c6ff | 684 | } |
fc512014 XL |
685 | } |
686 | ||
687 | fn check_doc_keyword(&self, meta: &NestedMetaItem, hir_id: HirId) -> bool { | |
688 | let doc_keyword = meta.value_str().map(|s| s.to_string()).unwrap_or_else(String::new); | |
689 | if doc_keyword.is_empty() { | |
690 | self.doc_attr_str_error(meta, "keyword"); | |
691 | return false; | |
692 | } | |
136023e0 XL |
693 | match self.tcx.hir().find(hir_id).and_then(|node| match node { |
694 | hir::Node::Item(item) => Some(&item.kind), | |
695 | _ => None, | |
696 | }) { | |
697 | Some(ItemKind::Mod(ref module)) => { | |
fc512014 XL |
698 | if !module.item_ids.is_empty() { |
699 | self.tcx | |
700 | .sess | |
701 | .struct_span_err( | |
702 | meta.span(), | |
703 | "`#[doc(keyword = \"...\")]` can only be used on empty modules", | |
704 | ) | |
705 | .emit(); | |
706 | return false; | |
707 | } | |
708 | } | |
709 | _ => { | |
710 | self.tcx | |
711 | .sess | |
712 | .struct_span_err( | |
713 | meta.span(), | |
714 | "`#[doc(keyword = \"...\")]` can only be used on modules", | |
715 | ) | |
716 | .emit(); | |
717 | return false; | |
718 | } | |
719 | } | |
720 | if !rustc_lexer::is_ident(&doc_keyword) { | |
721 | self.tcx | |
722 | .sess | |
723 | .struct_span_err( | |
724 | meta.name_value_literal_span().unwrap_or_else(|| meta.span()), | |
725 | &format!("`{}` is not a valid identifier", doc_keyword), | |
726 | ) | |
727 | .emit(); | |
728 | return false; | |
729 | } | |
730 | true | |
731 | } | |
732 | ||
17df50a5 XL |
733 | /// Checks `#[doc(inline)]`/`#[doc(no_inline)]` attributes. Returns `true` if valid. |
734 | /// | |
735 | /// A doc inlining attribute is invalid if it is applied to a non-`use` item, or | |
736 | /// if there are conflicting attributes for one item. | |
737 | /// | |
738 | /// `specified_inline` is used to keep track of whether we have | |
739 | /// already seen an inlining attribute for this item. | |
740 | /// If so, `specified_inline` holds the value and the span of | |
741 | /// the first `inline`/`no_inline` attribute. | |
742 | fn check_doc_inline( | |
743 | &self, | |
744 | attr: &Attribute, | |
745 | meta: &NestedMetaItem, | |
746 | hir_id: HirId, | |
747 | target: Target, | |
748 | specified_inline: &mut Option<(bool, Span)>, | |
749 | ) -> bool { | |
750 | if target == Target::Use || target == Target::ExternCrate { | |
751 | let do_inline = meta.name_or_empty() == sym::inline; | |
752 | if let Some((prev_inline, prev_span)) = *specified_inline { | |
753 | if do_inline != prev_inline { | |
754 | let mut spans = MultiSpan::from_spans(vec![prev_span, meta.span()]); | |
755 | spans.push_span_label(prev_span, String::from("this attribute...")); | |
756 | spans.push_span_label( | |
757 | meta.span(), | |
758 | String::from("...conflicts with this attribute"), | |
759 | ); | |
760 | self.tcx | |
761 | .sess | |
762 | .struct_span_err(spans, "conflicting doc inlining attributes") | |
763 | .help("remove one of the conflicting attributes") | |
764 | .emit(); | |
765 | return false; | |
766 | } | |
767 | true | |
768 | } else { | |
769 | *specified_inline = Some((do_inline, meta.span())); | |
770 | true | |
771 | } | |
772 | } else { | |
773 | self.tcx.struct_span_lint_hir( | |
774 | INVALID_DOC_ATTRIBUTES, | |
775 | hir_id, | |
776 | meta.span(), | |
777 | |lint| { | |
778 | let mut err = lint.build( | |
779 | "this attribute can only be applied to a `use` item", | |
780 | ); | |
781 | err.span_label(meta.span(), "only applicable on `use` items"); | |
782 | if attr.style == AttrStyle::Outer { | |
783 | err.span_label( | |
784 | self.tcx.hir().span(hir_id), | |
785 | "not a `use` item", | |
786 | ); | |
787 | } | |
3c0e092e | 788 | err.note("read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#inline-and-no_inline for more information") |
17df50a5 XL |
789 | .emit(); |
790 | }, | |
791 | ); | |
792 | false | |
793 | } | |
794 | } | |
795 | ||
796 | /// Checks that an attribute is *not* used at the crate level. Returns `true` if valid. | |
797 | fn check_attr_not_crate_level( | |
fc512014 XL |
798 | &self, |
799 | meta: &NestedMetaItem, | |
800 | hir_id: HirId, | |
801 | attr_name: &str, | |
802 | ) -> bool { | |
803 | if CRATE_HIR_ID == hir_id { | |
804 | self.tcx | |
805 | .sess | |
806 | .struct_span_err( | |
807 | meta.span(), | |
808 | &format!( | |
6a06907d | 809 | "`#![doc({} = \"...\")]` isn't allowed as a crate-level attribute", |
fc512014 XL |
810 | attr_name, |
811 | ), | |
812 | ) | |
813 | .emit(); | |
814 | return false; | |
815 | } | |
816 | true | |
817 | } | |
818 | ||
17df50a5 XL |
819 | /// Checks that an attribute is used at the crate level. Returns `true` if valid. |
820 | fn check_attr_crate_level( | |
821 | &self, | |
822 | attr: &Attribute, | |
823 | meta: &NestedMetaItem, | |
824 | hir_id: HirId, | |
825 | ) -> bool { | |
826 | if hir_id != CRATE_HIR_ID { | |
827 | self.tcx.struct_span_lint_hir( | |
828 | INVALID_DOC_ATTRIBUTES, | |
829 | hir_id, | |
830 | meta.span(), | |
831 | |lint| { | |
832 | let mut err = lint.build( | |
833 | "this attribute can only be applied at the crate level", | |
834 | ); | |
835 | if attr.style == AttrStyle::Outer && self.tcx.hir().get_parent_item(hir_id) == CRATE_HIR_ID { | |
836 | if let Ok(mut src) = | |
837 | self.tcx.sess.source_map().span_to_snippet(attr.span) | |
838 | { | |
839 | src.insert(1, '!'); | |
840 | err.span_suggestion_verbose( | |
841 | attr.span, | |
842 | "to apply to the crate, use an inner attribute", | |
843 | src, | |
844 | Applicability::MaybeIncorrect, | |
845 | ); | |
846 | } else { | |
847 | err.span_help( | |
848 | attr.span, | |
849 | "to apply to the crate, use an inner attribute", | |
850 | ); | |
851 | } | |
852 | } | |
853 | err.note("read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level for more information") | |
854 | .emit(); | |
855 | }, | |
856 | ); | |
857 | return false; | |
858 | } | |
859 | true | |
860 | } | |
861 | ||
94222f64 XL |
862 | /// Checks that `doc(test(...))` attribute contains only valid attributes. Returns `true` if |
863 | /// valid. | |
864 | fn check_test_attr(&self, meta: &NestedMetaItem, hir_id: HirId) -> bool { | |
865 | let mut is_valid = true; | |
866 | if let Some(metas) = meta.meta_item_list() { | |
867 | for i_meta in metas { | |
868 | match i_meta.name_or_empty() { | |
869 | sym::attr | sym::no_crate_inject => {} | |
870 | _ => { | |
871 | self.tcx.struct_span_lint_hir( | |
872 | INVALID_DOC_ATTRIBUTES, | |
873 | hir_id, | |
874 | i_meta.span(), | |
875 | |lint| { | |
876 | lint.build(&format!( | |
877 | "unknown `doc(test)` attribute `{}`", | |
878 | rustc_ast_pretty::pprust::path_to_string( | |
879 | &i_meta.meta_item().unwrap().path | |
880 | ), | |
881 | )) | |
882 | .emit(); | |
883 | }, | |
884 | ); | |
885 | is_valid = false; | |
886 | } | |
887 | } | |
888 | } | |
889 | } else { | |
890 | self.tcx.struct_span_lint_hir(INVALID_DOC_ATTRIBUTES, hir_id, meta.span(), |lint| { | |
891 | lint.build("`#[doc(test(...)]` takes a list of attributes").emit(); | |
892 | }); | |
893 | is_valid = false; | |
894 | } | |
895 | is_valid | |
896 | } | |
897 | ||
17df50a5 XL |
898 | /// Runs various checks on `#[doc]` attributes. Returns `true` if valid. |
899 | /// | |
900 | /// `specified_inline` should be initialized to `None` and kept for the scope | |
901 | /// of one item. Read the documentation of [`check_doc_inline`] for more information. | |
902 | /// | |
903 | /// [`check_doc_inline`]: Self::check_doc_inline | |
904 | fn check_doc_attrs( | |
905 | &self, | |
906 | attr: &Attribute, | |
907 | hir_id: HirId, | |
908 | target: Target, | |
909 | specified_inline: &mut Option<(bool, Span)>, | |
c295e0f8 | 910 | aliases: &mut FxHashMap<String, Span>, |
17df50a5 | 911 | ) -> bool { |
6a06907d XL |
912 | let mut is_valid = true; |
913 | ||
914 | if let Some(list) = attr.meta().and_then(|mi| mi.meta_item_list().map(|l| l.to_vec())) { | |
17df50a5 | 915 | for meta in &list { |
6a06907d XL |
916 | if let Some(i_meta) = meta.meta_item() { |
917 | match i_meta.name_or_empty() { | |
918 | sym::alias | |
17df50a5 | 919 | if !self.check_attr_not_crate_level(&meta, hir_id, "alias") |
c295e0f8 | 920 | || !self.check_doc_alias(&meta, hir_id, target, aliases) => |
3dfed10e | 921 | { |
6a06907d | 922 | is_valid = false |
29967ef6 | 923 | } |
6a06907d XL |
924 | |
925 | sym::keyword | |
17df50a5 | 926 | if !self.check_attr_not_crate_level(&meta, hir_id, "keyword") |
6a06907d | 927 | || !self.check_doc_keyword(&meta, hir_id) => |
fc512014 | 928 | { |
6a06907d XL |
929 | is_valid = false |
930 | } | |
931 | ||
17df50a5 XL |
932 | sym::html_favicon_url |
933 | | sym::html_logo_url | |
934 | | sym::html_playground_url | |
935 | | sym::issue_tracker_base_url | |
936 | | sym::html_root_url | |
937 | | sym::html_no_source | |
938 | | sym::test | |
939 | if !self.check_attr_crate_level(&attr, &meta, hir_id) => | |
940 | { | |
941 | is_valid = false; | |
942 | } | |
943 | ||
944 | sym::inline | sym::no_inline | |
945 | if !self.check_doc_inline( | |
946 | &attr, | |
947 | &meta, | |
6a06907d | 948 | hir_id, |
17df50a5 XL |
949 | target, |
950 | specified_inline, | |
951 | ) => | |
952 | { | |
6a06907d XL |
953 | is_valid = false; |
954 | } | |
955 | ||
956 | // no_default_passes: deprecated | |
957 | // passes: deprecated | |
958 | // plugins: removed, but rustdoc warns about it itself | |
959 | sym::alias | |
960 | | sym::cfg | |
c295e0f8 | 961 | | sym::cfg_hide |
6a06907d XL |
962 | | sym::hidden |
963 | | sym::html_favicon_url | |
964 | | sym::html_logo_url | |
965 | | sym::html_no_source | |
966 | | sym::html_playground_url | |
967 | | sym::html_root_url | |
6a06907d XL |
968 | | sym::inline |
969 | | sym::issue_tracker_base_url | |
970 | | sym::keyword | |
971 | | sym::masked | |
972 | | sym::no_default_passes | |
973 | | sym::no_inline | |
cdc7bbd5 | 974 | | sym::notable_trait |
6a06907d | 975 | | sym::passes |
94222f64 XL |
976 | | sym::plugins => {} |
977 | ||
978 | sym::test => { | |
979 | if !self.check_test_attr(&meta, hir_id) { | |
980 | is_valid = false; | |
981 | } | |
982 | } | |
983 | ||
984 | sym::primitive => { | |
3c0e092e | 985 | if !self.tcx.features().rustdoc_internals { |
94222f64 XL |
986 | self.tcx.struct_span_lint_hir( |
987 | INVALID_DOC_ATTRIBUTES, | |
988 | hir_id, | |
989 | i_meta.span, | |
990 | |lint| { | |
991 | let mut diag = lint.build( | |
992 | "`doc(primitive)` should never have been stable", | |
993 | ); | |
994 | diag.emit(); | |
995 | }, | |
996 | ); | |
997 | } | |
998 | } | |
6a06907d XL |
999 | |
1000 | _ => { | |
1001 | self.tcx.struct_span_lint_hir( | |
1002 | INVALID_DOC_ATTRIBUTES, | |
1003 | hir_id, | |
1004 | i_meta.span, | |
1005 | |lint| { | |
cdc7bbd5 | 1006 | let mut diag = lint.build(&format!( |
6a06907d XL |
1007 | "unknown `doc` attribute `{}`", |
1008 | rustc_ast_pretty::pprust::path_to_string(&i_meta.path), | |
cdc7bbd5 XL |
1009 | )); |
1010 | if i_meta.has_name(sym::spotlight) { | |
1011 | diag.note( | |
1012 | "`doc(spotlight)` was renamed to `doc(notable_trait)`", | |
1013 | ); | |
1014 | diag.span_suggestion_short( | |
1015 | i_meta.span, | |
1016 | "use `notable_trait` instead", | |
1017 | String::from("notable_trait"), | |
1018 | Applicability::MachineApplicable, | |
1019 | ); | |
1020 | diag.note("`doc(spotlight)` is now a no-op"); | |
1021 | } | |
17df50a5 XL |
1022 | if i_meta.has_name(sym::include) { |
1023 | if let Some(value) = i_meta.value_str() { | |
1024 | // if there are multiple attributes, the suggestion would suggest deleting all of them, which is incorrect | |
1025 | let applicability = if list.len() == 1 { | |
1026 | Applicability::MachineApplicable | |
1027 | } else { | |
1028 | Applicability::MaybeIncorrect | |
1029 | }; | |
1030 | let inner = if attr.style == AttrStyle::Inner { | |
1031 | "!" | |
1032 | } else { | |
1033 | "" | |
1034 | }; | |
1035 | diag.span_suggestion( | |
1036 | attr.meta().unwrap().span, | |
1037 | "use `doc = include_str!` instead", | |
1038 | format!( | |
1039 | "#{}[doc = include_str!(\"{}\")]", | |
1040 | inner, value | |
1041 | ), | |
1042 | applicability, | |
1043 | ); | |
1044 | } | |
1045 | } | |
cdc7bbd5 | 1046 | diag.emit(); |
6a06907d XL |
1047 | }, |
1048 | ); | |
1049 | is_valid = false; | |
29967ef6 | 1050 | } |
3dfed10e | 1051 | } |
6a06907d XL |
1052 | } else { |
1053 | self.tcx.struct_span_lint_hir( | |
1054 | INVALID_DOC_ATTRIBUTES, | |
1055 | hir_id, | |
1056 | meta.span(), | |
1057 | |lint| { | |
94222f64 | 1058 | lint.build(&"invalid `doc` attribute").emit(); |
6a06907d XL |
1059 | }, |
1060 | ); | |
1061 | is_valid = false; | |
3dfed10e XL |
1062 | } |
1063 | } | |
1064 | } | |
6a06907d XL |
1065 | |
1066 | is_valid | |
3dfed10e XL |
1067 | } |
1068 | ||
3c0e092e XL |
1069 | /// Warns against some misuses of `#[must_use]` |
1070 | fn check_must_use( | |
1071 | &self, | |
1072 | hir_id: HirId, | |
1073 | attr: &Attribute, | |
1074 | span: &Span, | |
1075 | _target: Target, | |
1076 | ) -> bool { | |
1077 | let node = self.tcx.hir().get(hir_id); | |
1078 | if let Some(fn_node) = node.fn_kind() { | |
1079 | if let rustc_hir::IsAsync::Async = fn_node.asyncness() { | |
1080 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { | |
1081 | lint.build( | |
1082 | "`must_use` attribute on `async` functions \ | |
1083 | applies to the anonymous `Future` returned by the \ | |
1084 | function, not the value within", | |
1085 | ) | |
1086 | .span_label( | |
1087 | *span, | |
1088 | "this attribute does nothing, the `Future`s \ | |
1089 | returned by async functions are already `must_use`", | |
1090 | ) | |
1091 | .emit(); | |
1092 | }); | |
1093 | } | |
1094 | } | |
1095 | ||
1096 | // For now, its always valid | |
1097 | true | |
1098 | } | |
1099 | ||
c295e0f8 XL |
1100 | /// Checks if `#[must_not_suspend]` is applied to a function. Returns `true` if valid. |
1101 | fn check_must_not_suspend(&self, attr: &Attribute, span: &Span, target: Target) -> bool { | |
1102 | match target { | |
1103 | Target::Struct | Target::Enum | Target::Union | Target::Trait => true, | |
1104 | _ => { | |
1105 | self.tcx | |
1106 | .sess | |
1107 | .struct_span_err(attr.span, "`must_not_suspend` attribute should be applied to a struct, enum, or trait") | |
1108 | .span_label(*span, "is not a struct, enum, or trait") | |
1109 | .emit(); | |
1110 | false | |
1111 | } | |
1112 | } | |
1113 | } | |
1114 | ||
1b1a35ee XL |
1115 | /// Checks if `#[cold]` is applied to a non-function. Returns `true` if valid. |
1116 | fn check_cold(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) { | |
1117 | match target { | |
1118 | Target::Fn | Target::Method(..) | Target::ForeignFn | Target::Closure => {} | |
5869c6ff XL |
1119 | // FIXME(#80564): We permit struct fields, match arms and macro defs to have an |
1120 | // `#[cold]` attribute with just a lint, because we previously | |
1121 | // erroneously allowed it and some crates used it accidentally, to to be compatible | |
1122 | // with crates depending on them, we can't throw an error here. | |
1123 | Target::Field | Target::Arm | Target::MacroDef => { | |
1124 | self.inline_attr_str_error_with_macro_def(hir_id, attr, "cold"); | |
1125 | } | |
1b1a35ee XL |
1126 | _ => { |
1127 | // FIXME: #[cold] was previously allowed on non-functions and some crates used | |
1128 | // this, so only emit a warning. | |
1129 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { | |
1130 | lint.build("attribute should be applied to a function") | |
1131 | .warn( | |
1132 | "this was previously accepted by the compiler but is \ | |
1133 | being phased out; it will become a hard error in \ | |
1134 | a future release!", | |
1135 | ) | |
1136 | .span_label(*span, "not a function") | |
1137 | .emit(); | |
1138 | }); | |
1139 | } | |
1140 | } | |
1141 | } | |
1142 | ||
1143 | /// Checks if `#[link_name]` is applied to an item other than a foreign function or static. | |
1144 | fn check_link_name(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) { | |
1145 | match target { | |
1146 | Target::ForeignFn | Target::ForeignStatic => {} | |
5869c6ff XL |
1147 | // FIXME(#80564): We permit struct fields, match arms and macro defs to have an |
1148 | // `#[link_name]` attribute with just a lint, because we previously | |
1149 | // erroneously allowed it and some crates used it accidentally, to to be compatible | |
1150 | // with crates depending on them, we can't throw an error here. | |
1151 | Target::Field | Target::Arm | Target::MacroDef => { | |
1152 | self.inline_attr_str_error_with_macro_def(hir_id, attr, "link_name"); | |
1153 | } | |
1b1a35ee XL |
1154 | _ => { |
1155 | // FIXME: #[cold] was previously allowed on non-functions/statics and some crates | |
1156 | // used this, so only emit a warning. | |
1157 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { | |
1158 | let mut diag = | |
1159 | lint.build("attribute should be applied to a foreign function or static"); | |
1160 | diag.warn( | |
1161 | "this was previously accepted by the compiler but is \ | |
1162 | being phased out; it will become a hard error in \ | |
1163 | a future release!", | |
1164 | ); | |
1165 | ||
1166 | // See issue #47725 | |
1167 | if let Target::ForeignMod = target { | |
1168 | if let Some(value) = attr.value_str() { | |
1169 | diag.span_help( | |
1170 | attr.span, | |
1171 | &format!(r#"try `#[link(name = "{}")]` instead"#, value), | |
1172 | ); | |
1173 | } else { | |
1174 | diag.span_help(attr.span, r#"try `#[link(name = "...")]` instead"#); | |
1175 | } | |
1176 | } | |
1177 | ||
1178 | diag.span_label(*span, "not a foreign function or static"); | |
1179 | diag.emit(); | |
1180 | }); | |
1181 | } | |
1182 | } | |
1183 | } | |
1184 | ||
1185 | /// Checks if `#[no_link]` is applied to an `extern crate`. Returns `true` if valid. | |
5869c6ff XL |
1186 | fn check_no_link(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) -> bool { |
1187 | match target { | |
1188 | Target::ExternCrate => true, | |
1189 | // FIXME(#80564): We permit struct fields, match arms and macro defs to have an | |
1190 | // `#[no_link]` attribute with just a lint, because we previously | |
1191 | // erroneously allowed it and some crates used it accidentally, to to be compatible | |
1192 | // with crates depending on them, we can't throw an error here. | |
1193 | Target::Field | Target::Arm | Target::MacroDef => { | |
1194 | self.inline_attr_str_error_with_macro_def(hir_id, attr, "no_link"); | |
1195 | true | |
1196 | } | |
1197 | _ => { | |
1198 | self.tcx | |
1199 | .sess | |
1200 | .struct_span_err( | |
1201 | attr.span, | |
1202 | "attribute should be applied to an `extern crate` item", | |
1203 | ) | |
1204 | .span_label(*span, "not an `extern crate` item") | |
1205 | .emit(); | |
1206 | false | |
1207 | } | |
1b1a35ee XL |
1208 | } |
1209 | } | |
1210 | ||
94222f64 XL |
1211 | fn is_impl_item(&self, hir_id: HirId) -> bool { |
1212 | matches!(self.tcx.hir().get(hir_id), hir::Node::ImplItem(..)) | |
1213 | } | |
1214 | ||
1b1a35ee | 1215 | /// Checks if `#[export_name]` is applied to a function or static. Returns `true` if valid. |
5869c6ff XL |
1216 | fn check_export_name( |
1217 | &self, | |
1218 | hir_id: HirId, | |
1219 | attr: &Attribute, | |
1220 | span: &Span, | |
1221 | target: Target, | |
1222 | ) -> bool { | |
1b1a35ee | 1223 | match target { |
94222f64 XL |
1224 | Target::Static | Target::Fn => true, |
1225 | Target::Method(..) if self.is_impl_item(hir_id) => true, | |
5869c6ff XL |
1226 | // FIXME(#80564): We permit struct fields, match arms and macro defs to have an |
1227 | // `#[export_name]` attribute with just a lint, because we previously | |
1228 | // erroneously allowed it and some crates used it accidentally, to to be compatible | |
1229 | // with crates depending on them, we can't throw an error here. | |
1230 | Target::Field | Target::Arm | Target::MacroDef => { | |
1231 | self.inline_attr_str_error_with_macro_def(hir_id, attr, "export_name"); | |
1232 | true | |
1233 | } | |
1b1a35ee XL |
1234 | _ => { |
1235 | self.tcx | |
1236 | .sess | |
1237 | .struct_span_err( | |
1238 | attr.span, | |
94222f64 | 1239 | "attribute should be applied to a free function, impl method or static", |
1b1a35ee | 1240 | ) |
94222f64 | 1241 | .span_label(*span, "not a free function, impl method or static") |
1b1a35ee XL |
1242 | .emit(); |
1243 | false | |
1244 | } | |
1245 | } | |
1246 | } | |
1247 | ||
6a06907d XL |
1248 | fn check_rustc_layout_scalar_valid_range( |
1249 | &self, | |
1250 | attr: &Attribute, | |
1251 | span: &Span, | |
1252 | target: Target, | |
1253 | ) -> bool { | |
1254 | if target != Target::Struct { | |
1255 | self.tcx | |
1256 | .sess | |
1257 | .struct_span_err(attr.span, "attribute should be applied to a struct") | |
1258 | .span_label(*span, "not a struct") | |
1259 | .emit(); | |
1260 | return false; | |
1261 | } | |
1262 | ||
1263 | let list = match attr.meta_item_list() { | |
1264 | None => return false, | |
1265 | Some(it) => it, | |
1266 | }; | |
1267 | ||
1268 | if matches!(&list[..], &[NestedMetaItem::Literal(Lit { kind: LitKind::Int(..), .. })]) { | |
1269 | true | |
1270 | } else { | |
1271 | self.tcx | |
1272 | .sess | |
1273 | .struct_span_err(attr.span, "expected exactly one integer literal argument") | |
1274 | .emit(); | |
1275 | false | |
1276 | } | |
1277 | } | |
1278 | ||
1279 | /// Checks if `#[rustc_legacy_const_generics]` is applied to a function and has a valid argument. | |
1280 | fn check_rustc_legacy_const_generics( | |
1281 | &self, | |
1282 | attr: &Attribute, | |
1283 | span: &Span, | |
1284 | target: Target, | |
1285 | item: Option<ItemLike<'_>>, | |
1286 | ) -> bool { | |
1287 | let is_function = matches!(target, Target::Fn | Target::Method(..)); | |
1288 | if !is_function { | |
1289 | self.tcx | |
1290 | .sess | |
1291 | .struct_span_err(attr.span, "attribute should be applied to a function") | |
1292 | .span_label(*span, "not a function") | |
1293 | .emit(); | |
1294 | return false; | |
1295 | } | |
1296 | ||
1297 | let list = match attr.meta_item_list() { | |
1298 | // The attribute form is validated on AST. | |
1299 | None => return false, | |
1300 | Some(it) => it, | |
1301 | }; | |
1302 | ||
1303 | let (decl, generics) = match item { | |
1304 | Some(ItemLike::Item(Item { | |
1305 | kind: ItemKind::Fn(FnSig { decl, .. }, generics, _), | |
1306 | .. | |
1307 | })) => (decl, generics), | |
1308 | _ => bug!("should be a function item"), | |
1309 | }; | |
1310 | ||
1311 | for param in generics.params { | |
1312 | match param.kind { | |
1313 | hir::GenericParamKind::Const { .. } => {} | |
1314 | _ => { | |
1315 | self.tcx | |
1316 | .sess | |
1317 | .struct_span_err( | |
1318 | attr.span, | |
1319 | "#[rustc_legacy_const_generics] functions must \ | |
1320 | only have const generics", | |
1321 | ) | |
1322 | .span_label(param.span, "non-const generic parameter") | |
1323 | .emit(); | |
1324 | return false; | |
1325 | } | |
1326 | } | |
1327 | } | |
1328 | ||
1329 | if list.len() != generics.params.len() { | |
1330 | self.tcx | |
1331 | .sess | |
1332 | .struct_span_err( | |
1333 | attr.span, | |
1334 | "#[rustc_legacy_const_generics] must have one index for each generic parameter", | |
1335 | ) | |
1336 | .span_label(generics.span, "generic parameters") | |
1337 | .emit(); | |
1338 | return false; | |
1339 | } | |
1340 | ||
1341 | let arg_count = decl.inputs.len() as u128 + generics.params.len() as u128; | |
1342 | let mut invalid_args = vec![]; | |
1343 | for meta in list { | |
1344 | if let Some(LitKind::Int(val, _)) = meta.literal().map(|lit| &lit.kind) { | |
1345 | if *val >= arg_count { | |
1346 | let span = meta.span(); | |
1347 | self.tcx | |
1348 | .sess | |
1349 | .struct_span_err(span, "index exceeds number of arguments") | |
1350 | .span_label( | |
1351 | span, | |
1352 | format!( | |
1353 | "there {} only {} argument{}", | |
1354 | if arg_count != 1 { "are" } else { "is" }, | |
1355 | arg_count, | |
1356 | pluralize!(arg_count) | |
1357 | ), | |
1358 | ) | |
1359 | .emit(); | |
1360 | return false; | |
1361 | } | |
1362 | } else { | |
1363 | invalid_args.push(meta.span()); | |
1364 | } | |
1365 | } | |
1366 | ||
1367 | if !invalid_args.is_empty() { | |
1368 | self.tcx | |
1369 | .sess | |
1370 | .struct_span_err(invalid_args, "arguments should be non-negative integers") | |
1371 | .emit(); | |
1372 | false | |
1373 | } else { | |
1374 | true | |
1375 | } | |
1376 | } | |
1377 | ||
cdc7bbd5 XL |
1378 | /// Checks that the dep-graph debugging attributes are only present when the query-dep-graph |
1379 | /// option is passed to the compiler. | |
1380 | fn check_rustc_dirty_clean(&self, attr: &Attribute) -> bool { | |
1381 | if self.tcx.sess.opts.debugging_opts.query_dep_graph { | |
1382 | true | |
1383 | } else { | |
1384 | self.tcx | |
1385 | .sess | |
1386 | .struct_span_err(attr.span, "attribute requires -Z query-dep-graph to be enabled") | |
1387 | .emit(); | |
1388 | false | |
1389 | } | |
1390 | } | |
1391 | ||
1b1a35ee XL |
1392 | /// Checks if `#[link_section]` is applied to a function or static. |
1393 | fn check_link_section(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) { | |
1394 | match target { | |
1395 | Target::Static | Target::Fn | Target::Method(..) => {} | |
5869c6ff XL |
1396 | // FIXME(#80564): We permit struct fields, match arms and macro defs to have an |
1397 | // `#[link_section]` attribute with just a lint, because we previously | |
1398 | // erroneously allowed it and some crates used it accidentally, to to be compatible | |
1399 | // with crates depending on them, we can't throw an error here. | |
1400 | Target::Field | Target::Arm | Target::MacroDef => { | |
1401 | self.inline_attr_str_error_with_macro_def(hir_id, attr, "link_section"); | |
1402 | } | |
1b1a35ee XL |
1403 | _ => { |
1404 | // FIXME: #[link_section] was previously allowed on non-functions/statics and some | |
1405 | // crates used this, so only emit a warning. | |
1406 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { | |
1407 | lint.build("attribute should be applied to a function or static") | |
1408 | .warn( | |
1409 | "this was previously accepted by the compiler but is \ | |
1410 | being phased out; it will become a hard error in \ | |
1411 | a future release!", | |
1412 | ) | |
1413 | .span_label(*span, "not a function or static") | |
1414 | .emit(); | |
1415 | }); | |
1416 | } | |
1417 | } | |
1418 | } | |
1419 | ||
1420 | /// Checks if `#[no_mangle]` is applied to a function or static. | |
1421 | fn check_no_mangle(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) { | |
1422 | match target { | |
94222f64 XL |
1423 | Target::Static | Target::Fn => {} |
1424 | Target::Method(..) if self.is_impl_item(hir_id) => {} | |
5869c6ff XL |
1425 | // FIXME(#80564): We permit struct fields, match arms and macro defs to have an |
1426 | // `#[no_mangle]` attribute with just a lint, because we previously | |
1427 | // erroneously allowed it and some crates used it accidentally, to to be compatible | |
1428 | // with crates depending on them, we can't throw an error here. | |
1429 | Target::Field | Target::Arm | Target::MacroDef => { | |
1430 | self.inline_attr_str_error_with_macro_def(hir_id, attr, "no_mangle"); | |
1431 | } | |
94222f64 XL |
1432 | // FIXME: #[no_mangle] was previously allowed on non-functions/statics, this should be an error |
1433 | // The error should specify that the item that is wrong is specifically a *foreign* fn/static | |
1434 | // otherwise the error seems odd | |
1435 | Target::ForeignFn | Target::ForeignStatic => { | |
1436 | let foreign_item_kind = match target { | |
1437 | Target::ForeignFn => "function", | |
1438 | Target::ForeignStatic => "static", | |
1439 | _ => unreachable!(), | |
1440 | }; | |
1441 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { | |
1442 | lint.build(&format!( | |
1443 | "`#[no_mangle]` has no effect on a foreign {}", | |
1444 | foreign_item_kind | |
1445 | )) | |
1446 | .warn( | |
1447 | "this was previously accepted by the compiler but is \ | |
1448 | being phased out; it will become a hard error in \ | |
1449 | a future release!", | |
1450 | ) | |
1451 | .span_label(*span, format!("foreign {}", foreign_item_kind)) | |
1452 | .note("symbol names in extern blocks are not mangled") | |
1453 | .span_suggestion( | |
1454 | attr.span, | |
1455 | "remove this attribute", | |
1456 | String::new(), | |
1457 | Applicability::MachineApplicable, | |
1458 | ) | |
1459 | .emit(); | |
1460 | }); | |
1461 | } | |
1b1a35ee XL |
1462 | _ => { |
1463 | // FIXME: #[no_mangle] was previously allowed on non-functions/statics and some | |
1464 | // crates used this, so only emit a warning. | |
1465 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { | |
94222f64 XL |
1466 | lint.build( |
1467 | "attribute should be applied to a free function, impl method or static", | |
1468 | ) | |
1469 | .warn( | |
1470 | "this was previously accepted by the compiler but is \ | |
1471 | being phased out; it will become a hard error in \ | |
1472 | a future release!", | |
1473 | ) | |
1474 | .span_label(*span, "not a free function, impl method or static") | |
1475 | .emit(); | |
1b1a35ee XL |
1476 | }); |
1477 | } | |
1478 | } | |
1479 | } | |
1480 | ||
9fa01778 | 1481 | /// Checks if the `#[repr]` attributes on `item` are valid. |
e74abb32 XL |
1482 | fn check_repr( |
1483 | &self, | |
a2a8927a | 1484 | attrs: &[Attribute], |
e74abb32 XL |
1485 | span: &Span, |
1486 | target: Target, | |
1b1a35ee | 1487 | item: Option<ItemLike<'_>>, |
dfeec247 | 1488 | hir_id: HirId, |
e74abb32 | 1489 | ) { |
2c00a5a8 XL |
1490 | // Extract the names of all repr hints, e.g., [foo, bar, align] for: |
1491 | // ``` | |
1492 | // #[repr(foo)] | |
1493 | // #[repr(bar, align(8))] | |
1494 | // ``` | |
e74abb32 | 1495 | let hints: Vec<_> = attrs |
2c00a5a8 | 1496 | .iter() |
cdc7bbd5 | 1497 | .filter(|attr| attr.has_name(sym::repr)) |
2c00a5a8 | 1498 | .filter_map(|attr| attr.meta_item_list()) |
b7449926 | 1499 | .flatten() |
2c00a5a8 | 1500 | .collect(); |
9e0c209e | 1501 | |
ff7c6d11 XL |
1502 | let mut int_reprs = 0; |
1503 | let mut is_c = false; | |
1504 | let mut is_simd = false; | |
2c00a5a8 XL |
1505 | let mut is_transparent = false; |
1506 | ||
1507 | for hint in &hints { | |
136023e0 XL |
1508 | if !hint.is_meta_item() { |
1509 | struct_span_err!( | |
1510 | self.tcx.sess, | |
1511 | hint.span(), | |
1512 | E0565, | |
1513 | "meta item in `repr` must be an identifier" | |
1514 | ) | |
1515 | .emit(); | |
1516 | continue; | |
1517 | } | |
1518 | ||
48663c56 | 1519 | let (article, allowed_targets) = match hint.name_or_empty() { |
136023e0 XL |
1520 | sym::C => { |
1521 | is_c = true; | |
dc9dc135 XL |
1522 | match target { |
1523 | Target::Struct | Target::Union | Target::Enum => continue, | |
1524 | _ => ("a", "struct, enum, or union"), | |
9e0c209e SL |
1525 | } |
1526 | } | |
136023e0 XL |
1527 | sym::align => { |
1528 | if let (Target::Fn, true) = (target, !self.tcx.features().fn_align) { | |
1529 | feature_err( | |
1530 | &self.tcx.sess.parse_sess, | |
1531 | sym::fn_align, | |
1532 | hint.span(), | |
1533 | "`repr(align)` attributes on functions are unstable", | |
1534 | ) | |
1535 | .emit(); | |
1536 | } | |
1537 | ||
1538 | match target { | |
1539 | Target::Struct | Target::Union | Target::Enum | Target::Fn => continue, | |
1540 | _ => ("a", "struct, enum, function, or union"), | |
1541 | } | |
1542 | } | |
48663c56 | 1543 | sym::packed => { |
dfeec247 XL |
1544 | if target != Target::Struct && target != Target::Union { |
1545 | ("a", "struct or union") | |
92a42be0 | 1546 | } else { |
dfeec247 | 1547 | continue; |
b039eaaf SL |
1548 | } |
1549 | } | |
48663c56 | 1550 | sym::simd => { |
ff7c6d11 | 1551 | is_simd = true; |
1b1a35ee XL |
1552 | if target != Target::Struct { |
1553 | ("a", "struct") | |
1554 | } else { | |
1555 | continue; | |
1556 | } | |
b039eaaf | 1557 | } |
48663c56 | 1558 | sym::transparent => { |
2c00a5a8 | 1559 | is_transparent = true; |
dc9dc135 XL |
1560 | match target { |
1561 | Target::Struct | Target::Union | Target::Enum => continue, | |
1562 | _ => ("a", "struct, enum, or union"), | |
cc61c64b XL |
1563 | } |
1564 | } | |
74b04a01 XL |
1565 | sym::no_niche => { |
1566 | if !self.tcx.features().enabled(sym::no_niche) { | |
1567 | feature_err( | |
1568 | &self.tcx.sess.parse_sess, | |
1569 | sym::no_niche, | |
1570 | hint.span(), | |
1571 | "the attribute `repr(no_niche)` is currently unstable", | |
1572 | ) | |
1573 | .emit(); | |
1574 | } | |
1575 | match target { | |
1576 | Target::Struct | Target::Enum => continue, | |
1577 | _ => ("a", "struct or enum"), | |
1578 | } | |
1579 | } | |
dfeec247 XL |
1580 | sym::i8 |
1581 | | sym::u8 | |
1582 | | sym::i16 | |
1583 | | sym::u16 | |
1584 | | sym::i32 | |
1585 | | sym::u32 | |
1586 | | sym::i64 | |
1587 | | sym::u64 | |
f035d41b XL |
1588 | | sym::i128 |
1589 | | sym::u128 | |
dfeec247 XL |
1590 | | sym::isize |
1591 | | sym::usize => { | |
ff7c6d11 | 1592 | int_reprs += 1; |
1b1a35ee XL |
1593 | if target != Target::Enum { |
1594 | ("an", "enum") | |
1595 | } else { | |
1596 | continue; | |
1597 | } | |
b039eaaf | 1598 | } |
136023e0 XL |
1599 | _ => { |
1600 | struct_span_err!( | |
1601 | self.tcx.sess, | |
1602 | hint.span(), | |
1603 | E0552, | |
1604 | "unrecognized representation hint" | |
1605 | ) | |
1606 | .emit(); | |
1607 | ||
1608 | continue; | |
1609 | } | |
92a42be0 | 1610 | }; |
29967ef6 XL |
1611 | |
1612 | struct_span_err!( | |
1613 | self.tcx.sess, | |
532ac7d7 | 1614 | hint.span(), |
29967ef6 XL |
1615 | E0517, |
1616 | "{}", | |
1617 | &format!("attribute should be applied to {} {}", article, allowed_targets) | |
0531ce1d | 1618 | ) |
29967ef6 XL |
1619 | .span_label(*span, &format!("not {} {}", article, allowed_targets)) |
1620 | .emit(); | |
9e0c209e | 1621 | } |
ff7c6d11 | 1622 | |
2c00a5a8 XL |
1623 | // Just point at all repr hints if there are any incompatibilities. |
1624 | // This is not ideal, but tracking precisely which ones are at fault is a huge hassle. | |
532ac7d7 | 1625 | let hint_spans = hints.iter().map(|hint| hint.span()); |
2c00a5a8 | 1626 | |
74b04a01 XL |
1627 | // Error on repr(transparent, <anything else apart from no_niche>). |
1628 | let non_no_niche = |hint: &&NestedMetaItem| hint.name_or_empty() != sym::no_niche; | |
1629 | let non_no_niche_count = hints.iter().filter(non_no_niche).count(); | |
1630 | if is_transparent && non_no_niche_count > 1 { | |
2c00a5a8 | 1631 | let hint_spans: Vec<_> = hint_spans.clone().collect(); |
dfeec247 XL |
1632 | struct_span_err!( |
1633 | self.tcx.sess, | |
1634 | hint_spans, | |
1635 | E0692, | |
1636 | "transparent {} cannot have other repr hints", | |
1637 | target | |
1638 | ) | |
1639 | .emit(); | |
2c00a5a8 | 1640 | } |
ff7c6d11 XL |
1641 | // Warn on repr(u8, u16), repr(C, simd), and c-like-enum-repr(C, u8) |
1642 | if (int_reprs > 1) | |
dfeec247 | 1643 | || (is_simd && is_c) |
1b1a35ee XL |
1644 | || (int_reprs == 1 |
1645 | && is_c | |
1646 | && item.map_or(false, |item| { | |
1647 | if let ItemLike::Item(item) = item { | |
1648 | return is_c_like_enum(item); | |
1649 | } | |
1650 | return false; | |
1651 | })) | |
dfeec247 | 1652 | { |
74b04a01 XL |
1653 | self.tcx.struct_span_lint_hir( |
1654 | CONFLICTING_REPR_HINTS, | |
1655 | hir_id, | |
1656 | hint_spans.collect::<Vec<Span>>(), | |
1657 | |lint| { | |
1658 | lint.build("conflicting representation hints") | |
1659 | .code(rustc_errors::error_code!(E0566)) | |
1660 | .emit(); | |
1661 | }, | |
1662 | ); | |
b039eaaf SL |
1663 | } |
1664 | } | |
0531ce1d | 1665 | |
a2a8927a | 1666 | fn check_used(&self, attrs: &[Attribute], target: Target) { |
29967ef6 | 1667 | for attr in attrs { |
cdc7bbd5 | 1668 | if attr.has_name(sym::used) && target != Target::Static { |
29967ef6 XL |
1669 | self.tcx |
1670 | .sess | |
1671 | .span_err(attr.span, "attribute must be applied to a `static` variable"); | |
0531ce1d XL |
1672 | } |
1673 | } | |
1674 | } | |
1675 | ||
29967ef6 XL |
1676 | /// Outputs an error for `#[allow_internal_unstable]` which can only be applied to macros. |
1677 | /// (Allows proc_macro functions) | |
1678 | fn check_allow_internal_unstable( | |
1679 | &self, | |
5869c6ff | 1680 | hir_id: HirId, |
29967ef6 XL |
1681 | attr: &Attribute, |
1682 | span: &Span, | |
1683 | target: Target, | |
1684 | attrs: &[Attribute], | |
1685 | ) -> bool { | |
1686 | debug!("Checking target: {:?}", target); | |
5869c6ff XL |
1687 | match target { |
1688 | Target::Fn => { | |
1689 | for attr in attrs { | |
1690 | if self.tcx.sess.is_proc_macro_attr(attr) { | |
1691 | debug!("Is proc macro attr"); | |
1692 | return true; | |
1693 | } | |
29967ef6 | 1694 | } |
5869c6ff XL |
1695 | debug!("Is not proc macro attr"); |
1696 | false | |
1697 | } | |
1698 | Target::MacroDef => true, | |
1699 | // FIXME(#80564): We permit struct fields and match arms to have an | |
1700 | // `#[allow_internal_unstable]` attribute with just a lint, because we previously | |
1701 | // erroneously allowed it and some crates used it accidentally, to to be compatible | |
1702 | // with crates depending on them, we can't throw an error here. | |
1703 | Target::Field | Target::Arm => { | |
1704 | self.inline_attr_str_error_without_macro_def( | |
1705 | hir_id, | |
1706 | attr, | |
1707 | "allow_internal_unstable", | |
1708 | ); | |
1709 | true | |
1710 | } | |
1711 | _ => { | |
1712 | self.tcx | |
1713 | .sess | |
1714 | .struct_span_err(attr.span, "attribute should be applied to a macro") | |
1715 | .span_label(*span, "not a macro") | |
1716 | .emit(); | |
1717 | false | |
0531ce1d XL |
1718 | } |
1719 | } | |
1720 | } | |
83c7162d | 1721 | |
29967ef6 XL |
1722 | /// Outputs an error for `#[allow_internal_unstable]` which can only be applied to macros. |
1723 | /// (Allows proc_macro functions) | |
1724 | fn check_rustc_allow_const_fn_unstable( | |
1725 | &self, | |
1726 | hir_id: HirId, | |
1727 | attr: &Attribute, | |
1728 | span: &Span, | |
1729 | target: Target, | |
1730 | ) -> bool { | |
5869c6ff XL |
1731 | match target { |
1732 | Target::Fn | Target::Method(_) | |
1733 | if self.tcx.is_const_fn_raw(self.tcx.hir().local_def_id(hir_id)) => | |
1734 | { | |
1735 | true | |
1736 | } | |
1737 | // FIXME(#80564): We permit struct fields and match arms to have an | |
1738 | // `#[allow_internal_unstable]` attribute with just a lint, because we previously | |
1739 | // erroneously allowed it and some crates used it accidentally, to to be compatible | |
1740 | // with crates depending on them, we can't throw an error here. | |
1741 | Target::Field | Target::Arm | Target::MacroDef => { | |
1742 | self.inline_attr_str_error_with_macro_def(hir_id, attr, "allow_internal_unstable"); | |
1743 | true | |
1744 | } | |
1745 | _ => { | |
1746 | self.tcx | |
1747 | .sess | |
1748 | .struct_span_err(attr.span, "attribute should be applied to `const fn`") | |
1749 | .span_label(*span, "not a `const fn`") | |
1750 | .emit(); | |
1751 | false | |
83c7162d XL |
1752 | } |
1753 | } | |
1754 | } | |
136023e0 XL |
1755 | |
1756 | /// default_method_body_is_const should only be applied to trait methods with default bodies. | |
1757 | fn check_default_method_body_is_const( | |
1758 | &self, | |
1759 | attr: &Attribute, | |
1760 | span: &Span, | |
1761 | target: Target, | |
1762 | ) -> bool { | |
1763 | match target { | |
1764 | Target::Method(MethodKind::Trait { body: true }) => true, | |
1765 | _ => { | |
1766 | self.tcx | |
1767 | .sess | |
1768 | .struct_span_err( | |
1769 | attr.span, | |
1770 | "attribute should be applied to a trait method with body", | |
1771 | ) | |
1772 | .span_label(*span, "not a trait method or missing a body") | |
1773 | .emit(); | |
1774 | false | |
1775 | } | |
1776 | } | |
1777 | } | |
94222f64 XL |
1778 | |
1779 | fn check_stability_promotable(&self, attr: &Attribute, _span: &Span, target: Target) -> bool { | |
1780 | match target { | |
1781 | Target::Expression => { | |
1782 | self.tcx | |
1783 | .sess | |
1784 | .struct_span_err(attr.span, "attribute cannot be applied to an expression") | |
1785 | .emit(); | |
1786 | false | |
1787 | } | |
1788 | _ => true, | |
1789 | } | |
1790 | } | |
1791 | ||
1792 | fn check_deprecated(&self, hir_id: HirId, attr: &Attribute, _span: &Span, target: Target) { | |
1793 | match target { | |
1794 | Target::Closure | Target::Expression | Target::Statement | Target::Arm => { | |
1795 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { | |
1796 | lint.build("attribute is ignored here").emit(); | |
1797 | }); | |
1798 | } | |
1799 | _ => {} | |
1800 | } | |
1801 | } | |
1802 | ||
1803 | fn check_macro_use(&self, hir_id: HirId, attr: &Attribute, target: Target) { | |
1804 | let name = attr.name_or_empty(); | |
1805 | match target { | |
1806 | Target::ExternCrate | Target::Mod => {} | |
1807 | _ => { | |
1808 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { | |
1809 | lint.build(&format!( | |
1810 | "`#[{name}]` only has an effect on `extern crate` and modules" | |
1811 | )) | |
1812 | .emit(); | |
1813 | }); | |
1814 | } | |
1815 | } | |
1816 | } | |
1817 | ||
1818 | fn check_macro_export(&self, hir_id: HirId, attr: &Attribute, target: Target) { | |
1819 | if target != Target::MacroDef { | |
1820 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { | |
c295e0f8 | 1821 | lint.build("`#[macro_export]` only has an effect on macro definitions").emit(); |
94222f64 XL |
1822 | }); |
1823 | } | |
1824 | } | |
1825 | ||
1826 | fn check_cfg_attr(&self, hir_id: HirId, attr: &Attribute) { | |
1827 | if let Some((_, attrs)) = rustc_parse::parse_cfg_attr(&attr, &self.tcx.sess.parse_sess) { | |
1828 | if attrs.is_empty() { | |
1829 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { | |
1830 | lint.build("`#[cfg_attr]` does not expand to any attributes").emit(); | |
1831 | }); | |
1832 | } | |
1833 | } | |
1834 | } | |
1835 | ||
1836 | fn check_plugin_registrar(&self, hir_id: HirId, attr: &Attribute, target: Target) { | |
1837 | if target != Target::Fn { | |
1838 | self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { | |
1839 | lint.build("`#[plugin_registrar]` only has an effect on functions").emit(); | |
1840 | }); | |
1841 | } | |
1842 | } | |
b039eaaf SL |
1843 | } |
1844 | ||
a2a8927a | 1845 | impl<'tcx> Visitor<'tcx> for CheckAttrVisitor<'tcx> { |
dfeec247 XL |
1846 | type Map = Map<'tcx>; |
1847 | ||
ba9703b0 XL |
1848 | fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { |
1849 | NestedVisitorMap::OnlyBodies(self.tcx.hir()) | |
2c00a5a8 XL |
1850 | } |
1851 | ||
dfeec247 | 1852 | fn visit_item(&mut self, item: &'tcx Item<'tcx>) { |
94222f64 XL |
1853 | // Historically we've run more checks on non-exported than exported macros, |
1854 | // so this lets us continue to run them while maintaining backwards compatibility. | |
1855 | // In the long run, the checks should be harmonized. | |
1856 | if let ItemKind::Macro(ref macro_def) = item.kind { | |
1857 | let def_id = item.def_id.to_def_id(); | |
1858 | if macro_def.macro_rules && !self.tcx.has_attr(def_id, sym::macro_export) { | |
1859 | check_non_exported_macro_for_invalid_attrs(self.tcx, item); | |
1860 | } | |
1861 | } | |
1862 | ||
b039eaaf | 1863 | let target = Target::from_item(item); |
6a06907d | 1864 | self.check_attributes(item.hir_id(), &item.span, target, Some(ItemLike::Item(item))); |
0531ce1d XL |
1865 | intravisit::walk_item(self, item) |
1866 | } | |
1867 | ||
fc512014 XL |
1868 | fn visit_generic_param(&mut self, generic_param: &'tcx hir::GenericParam<'tcx>) { |
1869 | let target = Target::from_generic_param(generic_param); | |
6a06907d | 1870 | self.check_attributes(generic_param.hir_id, &generic_param.span, target, None); |
fc512014 XL |
1871 | intravisit::walk_generic_param(self, generic_param) |
1872 | } | |
1873 | ||
dfeec247 | 1874 | fn visit_trait_item(&mut self, trait_item: &'tcx TraitItem<'tcx>) { |
e74abb32 | 1875 | let target = Target::from_trait_item(trait_item); |
6a06907d | 1876 | self.check_attributes(trait_item.hir_id(), &trait_item.span, target, None); |
e74abb32 XL |
1877 | intravisit::walk_trait_item(self, trait_item) |
1878 | } | |
1879 | ||
6a06907d XL |
1880 | fn visit_field_def(&mut self, struct_field: &'tcx hir::FieldDef<'tcx>) { |
1881 | self.check_attributes(struct_field.hir_id, &struct_field.span, Target::Field, None); | |
1882 | intravisit::walk_field_def(self, struct_field); | |
5869c6ff XL |
1883 | } |
1884 | ||
1885 | fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) { | |
6a06907d | 1886 | self.check_attributes(arm.hir_id, &arm.span, Target::Arm, None); |
5869c6ff XL |
1887 | intravisit::walk_arm(self, arm); |
1888 | } | |
1889 | ||
1b1a35ee | 1890 | fn visit_foreign_item(&mut self, f_item: &'tcx ForeignItem<'tcx>) { |
e74abb32 | 1891 | let target = Target::from_foreign_item(f_item); |
1b1a35ee | 1892 | self.check_attributes( |
6a06907d | 1893 | f_item.hir_id(), |
1b1a35ee XL |
1894 | &f_item.span, |
1895 | target, | |
1896 | Some(ItemLike::ForeignItem(f_item)), | |
1897 | ); | |
e74abb32 XL |
1898 | intravisit::walk_foreign_item(self, f_item) |
1899 | } | |
1900 | ||
dfeec247 | 1901 | fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) { |
74b04a01 | 1902 | let target = target_from_impl_item(self.tcx, impl_item); |
6a06907d | 1903 | self.check_attributes(impl_item.hir_id(), &impl_item.span, target, None); |
e74abb32 XL |
1904 | intravisit::walk_impl_item(self, impl_item) |
1905 | } | |
0531ce1d | 1906 | |
dfeec247 | 1907 | fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) { |
29967ef6 XL |
1908 | // When checking statements ignore expressions, they will be checked later. |
1909 | if let hir::StmtKind::Local(ref l) = stmt.kind { | |
6a06907d | 1910 | self.check_attributes(l.hir_id, &stmt.span, Target::Statement, None); |
29967ef6 | 1911 | } |
0531ce1d XL |
1912 | intravisit::walk_stmt(self, stmt) |
1913 | } | |
1914 | ||
dfeec247 | 1915 | fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { |
29967ef6 XL |
1916 | let target = match expr.kind { |
1917 | hir::ExprKind::Closure(..) => Target::Closure, | |
1918 | _ => Target::Expression, | |
1919 | }; | |
1920 | ||
6a06907d | 1921 | self.check_attributes(expr.hir_id, &expr.span, target, None); |
0531ce1d | 1922 | intravisit::walk_expr(self, expr) |
b039eaaf | 1923 | } |
29967ef6 XL |
1924 | |
1925 | fn visit_variant( | |
1926 | &mut self, | |
1927 | variant: &'tcx hir::Variant<'tcx>, | |
1928 | generics: &'tcx hir::Generics<'tcx>, | |
1929 | item_id: HirId, | |
1930 | ) { | |
6a06907d | 1931 | self.check_attributes(variant.id, &variant.span, Target::Variant, None); |
29967ef6 XL |
1932 | intravisit::walk_variant(self, variant, generics, item_id) |
1933 | } | |
6a06907d | 1934 | |
6a06907d XL |
1935 | fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) { |
1936 | self.check_attributes(param.hir_id, ¶m.span, Target::Param, None); | |
1937 | ||
1938 | intravisit::walk_param(self, param); | |
1939 | } | |
b039eaaf SL |
1940 | } |
1941 | ||
dfeec247 | 1942 | fn is_c_like_enum(item: &Item<'_>) -> bool { |
e74abb32 | 1943 | if let ItemKind::Enum(ref def, _) = item.kind { |
dfeec247 | 1944 | for variant in def.variants { |
e1599b0c | 1945 | match variant.data { |
9fa01778 | 1946 | hir::VariantData::Unit(..) => { /* continue */ } |
e74abb32 | 1947 | _ => return false, |
ff7c6d11 XL |
1948 | } |
1949 | } | |
1950 | true | |
1951 | } else { | |
1952 | false | |
1953 | } | |
1954 | } | |
0731742a | 1955 | |
a2a8927a XL |
1956 | // FIXME: Fix "Cannot determine resolution" error and remove built-in macros |
1957 | // from this check. | |
29967ef6 | 1958 | fn check_invalid_crate_level_attr(tcx: TyCtxt<'_>, attrs: &[Attribute]) { |
a2a8927a XL |
1959 | // Check for builtin attributes at the crate level |
1960 | // which were unsuccessfully resolved due to cannot determine | |
1961 | // resolution for the attribute macro error. | |
29967ef6 XL |
1962 | const ATTRS_TO_CHECK: &[Symbol] = &[ |
1963 | sym::macro_export, | |
1964 | sym::repr, | |
1965 | sym::path, | |
1966 | sym::automatically_derived, | |
1967 | sym::start, | |
cdc7bbd5 | 1968 | sym::rustc_main, |
a2a8927a XL |
1969 | sym::derive, |
1970 | sym::test, | |
1971 | sym::test_case, | |
1972 | sym::global_allocator, | |
1973 | sym::bench, | |
29967ef6 XL |
1974 | ]; |
1975 | ||
1976 | for attr in attrs { | |
a2a8927a XL |
1977 | // This function should only be called with crate attributes |
1978 | // which are inner attributes always but lets check to make sure | |
1979 | if attr.style == AttrStyle::Inner { | |
1980 | for attr_to_check in ATTRS_TO_CHECK { | |
1981 | if attr.has_name(*attr_to_check) { | |
1982 | let mut err = tcx.sess.struct_span_err( | |
29967ef6 XL |
1983 | attr.span, |
1984 | &format!( | |
1985 | "`{}` attribute cannot be used at crate level", | |
1986 | attr_to_check.to_ident_string() | |
1987 | ), | |
a2a8927a XL |
1988 | ); |
1989 | // Only emit an error with a suggestion if we can create a | |
1990 | // string out of the attribute span | |
1991 | if let Ok(src) = tcx.sess.source_map().span_to_snippet(attr.span) { | |
1992 | let replacement = src.replace("#!", "#"); | |
1993 | err.span_suggestion_verbose( | |
1994 | attr.span, | |
1995 | "perhaps you meant to use an outer attribute", | |
1996 | replacement, | |
1997 | rustc_errors::Applicability::MachineApplicable, | |
1998 | ); | |
1999 | } | |
2000 | err.emit() | |
2001 | } | |
29967ef6 XL |
2002 | } |
2003 | } | |
2004 | } | |
2005 | } | |
2006 | ||
94222f64 XL |
2007 | fn check_non_exported_macro_for_invalid_attrs(tcx: TyCtxt<'_>, item: &Item<'_>) { |
2008 | let attrs = tcx.hir().attrs(item.hir_id()); | |
2009 | ||
5869c6ff | 2010 | for attr in attrs { |
94222f64 | 2011 | if attr.has_name(sym::inline) { |
5869c6ff XL |
2012 | struct_span_err!( |
2013 | tcx.sess, | |
2014 | attr.span, | |
2015 | E0518, | |
2016 | "attribute should be applied to function or closure", | |
2017 | ) | |
2018 | .span_label(attr.span, "not a function or closure") | |
2019 | .emit(); | |
2020 | } | |
2021 | } | |
2022 | } | |
2023 | ||
f035d41b | 2024 | fn check_mod_attrs(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { |
5869c6ff XL |
2025 | let check_attr_visitor = &mut CheckAttrVisitor { tcx }; |
2026 | tcx.hir().visit_item_likes_in_module(module_def_id, &mut check_attr_visitor.as_deep_visitor()); | |
29967ef6 | 2027 | if module_def_id.is_top_level_module() { |
6a06907d | 2028 | check_attr_visitor.check_attributes(CRATE_HIR_ID, &DUMMY_SP, Target::Mod, None); |
29967ef6 XL |
2029 | check_invalid_crate_level_attr(tcx, tcx.hir().krate_attrs()); |
2030 | } | |
0731742a XL |
2031 | } |
2032 | ||
f035d41b | 2033 | pub(crate) fn provide(providers: &mut Providers) { |
dfeec247 | 2034 | *providers = Providers { check_mod_attrs, ..*providers }; |
0731742a | 2035 | } |
3c0e092e XL |
2036 | |
2037 | fn check_duplicates( | |
2038 | tcx: TyCtxt<'_>, | |
2039 | attr: &Attribute, | |
2040 | hir_id: HirId, | |
2041 | duplicates: AttributeDuplicates, | |
2042 | seen: &mut FxHashMap<Symbol, Span>, | |
2043 | ) { | |
2044 | use AttributeDuplicates::*; | |
2045 | if matches!(duplicates, WarnFollowingWordOnly) && !attr.is_word() { | |
2046 | return; | |
2047 | } | |
2048 | match duplicates { | |
2049 | DuplicatesOk => {} | |
2050 | WarnFollowing | FutureWarnFollowing | WarnFollowingWordOnly | FutureWarnPreceding => { | |
2051 | match seen.entry(attr.name_or_empty()) { | |
2052 | Entry::Occupied(mut entry) => { | |
2053 | let (this, other) = if matches!(duplicates, FutureWarnPreceding) { | |
2054 | let to_remove = entry.insert(attr.span); | |
2055 | (to_remove, attr.span) | |
2056 | } else { | |
2057 | (attr.span, *entry.get()) | |
2058 | }; | |
2059 | tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, this, |lint| { | |
2060 | let mut db = lint.build("unused attribute"); | |
2061 | db.span_note(other, "attribute also specified here").span_suggestion( | |
2062 | this, | |
2063 | "remove this attribute", | |
2064 | String::new(), | |
2065 | Applicability::MachineApplicable, | |
2066 | ); | |
2067 | if matches!(duplicates, FutureWarnFollowing | FutureWarnPreceding) { | |
2068 | db.warn( | |
2069 | "this was previously accepted by the compiler but is \ | |
2070 | being phased out; it will become a hard error in \ | |
2071 | a future release!", | |
2072 | ); | |
2073 | } | |
2074 | db.emit(); | |
2075 | }); | |
2076 | } | |
2077 | Entry::Vacant(entry) => { | |
2078 | entry.insert(attr.span); | |
2079 | } | |
2080 | } | |
2081 | } | |
2082 | ErrorFollowing | ErrorPreceding => match seen.entry(attr.name_or_empty()) { | |
2083 | Entry::Occupied(mut entry) => { | |
2084 | let (this, other) = if matches!(duplicates, ErrorPreceding) { | |
2085 | let to_remove = entry.insert(attr.span); | |
2086 | (to_remove, attr.span) | |
2087 | } else { | |
2088 | (attr.span, *entry.get()) | |
2089 | }; | |
2090 | tcx.sess | |
2091 | .struct_span_err( | |
2092 | this, | |
2093 | &format!("multiple `{}` attributes", attr.name_or_empty()), | |
2094 | ) | |
2095 | .span_note(other, "attribute also specified here") | |
2096 | .span_suggestion( | |
2097 | this, | |
2098 | "remove this attribute", | |
2099 | String::new(), | |
2100 | Applicability::MachineApplicable, | |
2101 | ) | |
2102 | .emit(); | |
2103 | } | |
2104 | Entry::Vacant(entry) => { | |
2105 | entry.insert(attr.span); | |
2106 | } | |
2107 | }, | |
2108 | } | |
2109 | } |