]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_passes/src/check_attr.rs
Allow #[cold], #[track_caller] on closures. Fix whitespace in error messages.
[rust.git] / compiler / rustc_passes / src / check_attr.rs
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
7 use rustc_middle::hir::map::Map;
8 use rustc_middle::ty::query::Providers;
9 use rustc_middle::ty::TyCtxt;
10
11 use rustc_ast::{Attribute, NestedMetaItem};
12 use rustc_errors::struct_span_err;
13 use rustc_hir as hir;
14 use rustc_hir::def_id::LocalDefId;
15 use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
16 use rustc_hir::{self, HirId, Item, ItemKind, TraitItem};
17 use rustc_hir::{MethodKind, Target};
18 use rustc_session::lint::builtin::{CONFLICTING_REPR_HINTS, UNUSED_ATTRIBUTES};
19 use rustc_session::parse::feature_err;
20 use rustc_span::symbol::sym;
21 use rustc_span::Span;
22
23 pub(crate) fn target_from_impl_item<'tcx>(
24     tcx: TyCtxt<'tcx>,
25     impl_item: &hir::ImplItem<'_>,
26 ) -> Target {
27     match impl_item.kind {
28         hir::ImplItemKind::Const(..) => Target::AssocConst,
29         hir::ImplItemKind::Fn(..) => {
30             let parent_hir_id = tcx.hir().get_parent_item(impl_item.hir_id);
31             let containing_item = tcx.hir().expect_item(parent_hir_id);
32             let containing_impl_is_for_trait = match &containing_item.kind {
33                 hir::ItemKind::Impl { ref of_trait, .. } => of_trait.is_some(),
34                 _ => bug!("parent of an ImplItem must be an Impl"),
35             };
36             if containing_impl_is_for_trait {
37                 Target::Method(MethodKind::Trait { body: true })
38             } else {
39                 Target::Method(MethodKind::Inherent)
40             }
41         }
42         hir::ImplItemKind::TyAlias(..) => Target::AssocTy,
43     }
44 }
45
46 struct CheckAttrVisitor<'tcx> {
47     tcx: TyCtxt<'tcx>,
48 }
49
50 impl CheckAttrVisitor<'tcx> {
51     /// Checks any attribute.
52     fn check_attributes(
53         &self,
54         hir_id: HirId,
55         attrs: &'hir [Attribute],
56         span: &Span,
57         target: Target,
58         item: Option<&Item<'_>>,
59     ) {
60         let mut is_valid = true;
61         for attr in attrs {
62             is_valid &= if self.tcx.sess.check_name(attr, sym::inline) {
63                 self.check_inline(hir_id, attr, span, target)
64             } else if self.tcx.sess.check_name(attr, sym::non_exhaustive) {
65                 self.check_non_exhaustive(attr, span, target)
66             } else if self.tcx.sess.check_name(attr, sym::marker) {
67                 self.check_marker(attr, span, target)
68             } else if self.tcx.sess.check_name(attr, sym::target_feature) {
69                 self.check_target_feature(hir_id, attr, span, target)
70             } else if self.tcx.sess.check_name(attr, sym::track_caller) {
71                 self.check_track_caller(&attr.span, attrs, span, target)
72             } else if self.tcx.sess.check_name(attr, sym::doc) {
73                 self.check_doc_alias(attr, hir_id, target)
74             } else if self.tcx.sess.check_name(attr, sym::no_link) {
75                 self.check_no_link(&attr, span, target)
76             } else if self.tcx.sess.check_name(attr, sym::export_name) {
77                 self.check_export_name(&attr, span, target)
78             } else {
79                 // lint-only checks
80                 if self.tcx.sess.check_name(attr, sym::cold) {
81                     self.check_cold(hir_id, attr, span, target);
82                 } else if self.tcx.sess.check_name(attr, sym::link_name) {
83                     self.check_link_name(hir_id, attr, span, target);
84                 } else if self.tcx.sess.check_name(attr, sym::link_section) {
85                     self.check_link_section(hir_id, attr, span, target);
86                 }
87                 true
88             };
89         }
90
91         if !is_valid {
92             return;
93         }
94
95         if matches!(target, Target::Fn | Target::Method(_) | Target::ForeignFn) {
96             self.tcx.ensure().codegen_fn_attrs(self.tcx.hir().local_def_id(hir_id));
97         }
98
99         self.check_repr(attrs, span, target, item, hir_id);
100         self.check_used(attrs, target);
101     }
102
103     /// Checks if an `#[inline]` is applied to a function or a closure. Returns `true` if valid.
104     fn check_inline(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) -> bool {
105         match target {
106             Target::Fn
107             | Target::Closure
108             | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true,
109             Target::Method(MethodKind::Trait { body: false }) | Target::ForeignFn => {
110                 self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
111                     lint.build("`#[inline]` is ignored on function prototypes").emit()
112                 });
113                 true
114             }
115             // FIXME(#65833): We permit associated consts to have an `#[inline]` attribute with
116             // just a lint, because we previously erroneously allowed it and some crates used it
117             // accidentally, to to be compatible with crates depending on them, we can't throw an
118             // error here.
119             Target::AssocConst => {
120                 self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
121                     lint.build("`#[inline]` is ignored on constants")
122                         .warn(
123                             "this was previously accepted by the compiler but is \
124                              being phased out; it will become a hard error in \
125                              a future release!",
126                         )
127                         .note(
128                             "see issue #65833 <https://github.com/rust-lang/rust/issues/65833> \
129                              for more information",
130                         )
131                         .emit();
132                 });
133                 true
134             }
135             _ => {
136                 struct_span_err!(
137                     self.tcx.sess,
138                     attr.span,
139                     E0518,
140                     "attribute should be applied to function or closure",
141                 )
142                 .span_label(*span, "not a function or closure")
143                 .emit();
144                 false
145             }
146         }
147     }
148
149     /// Checks if a `#[track_caller]` is applied to a non-naked function. Returns `true` if valid.
150     fn check_track_caller(
151         &self,
152         attr_span: &Span,
153         attrs: &'hir [Attribute],
154         span: &Span,
155         target: Target,
156     ) -> bool {
157         match target {
158             _ if self.tcx.sess.contains_name(attrs, sym::naked) => {
159                 struct_span_err!(
160                     self.tcx.sess,
161                     *attr_span,
162                     E0736,
163                     "cannot use `#[track_caller]` with `#[naked]`",
164                 )
165                 .emit();
166                 false
167             }
168             Target::Fn | Target::Method(..) | Target::ForeignFn | Target::Closure => true,
169             _ => {
170                 struct_span_err!(
171                     self.tcx.sess,
172                     *attr_span,
173                     E0739,
174                     "attribute should be applied to function"
175                 )
176                 .span_label(*span, "not a function")
177                 .emit();
178                 false
179             }
180         }
181     }
182
183     /// Checks if the `#[non_exhaustive]` attribute on an `item` is valid. Returns `true` if valid.
184     fn check_non_exhaustive(&self, attr: &Attribute, span: &Span, target: Target) -> bool {
185         match target {
186             Target::Struct | Target::Enum => true,
187             _ => {
188                 struct_span_err!(
189                     self.tcx.sess,
190                     attr.span,
191                     E0701,
192                     "attribute can only be applied to a struct or enum"
193                 )
194                 .span_label(*span, "not a struct or enum")
195                 .emit();
196                 false
197             }
198         }
199     }
200
201     /// Checks if the `#[marker]` attribute on an `item` is valid. Returns `true` if valid.
202     fn check_marker(&self, attr: &Attribute, span: &Span, target: Target) -> bool {
203         match target {
204             Target::Trait => true,
205             _ => {
206                 self.tcx
207                     .sess
208                     .struct_span_err(attr.span, "attribute can only be applied to a trait")
209                     .span_label(*span, "not a trait")
210                     .emit();
211                 false
212             }
213         }
214     }
215
216     /// Checks if the `#[target_feature]` attribute on `item` is valid. Returns `true` if valid.
217     fn check_target_feature(
218         &self,
219         hir_id: HirId,
220         attr: &Attribute,
221         span: &Span,
222         target: Target,
223     ) -> bool {
224         match target {
225             Target::Fn
226             | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true,
227             // FIXME: #[target_feature] was previously erroneously allowed on statements and some
228             // crates used this, so only emit a warning.
229             Target::Statement => {
230                 self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
231                     lint.build("attribute should be applied to a function")
232                         .warn(
233                             "this was previously accepted by the compiler but is \
234                              being phased out; it will become a hard error in \
235                              a future release!",
236                         )
237                         .span_label(*span, "not a function")
238                         .emit();
239                 });
240                 true
241             }
242             _ => {
243                 self.tcx
244                     .sess
245                     .struct_span_err(attr.span, "attribute should be applied to a function")
246                     .span_label(*span, "not a function")
247                     .emit();
248                 false
249             }
250         }
251     }
252
253     fn check_doc_alias(&self, attr: &Attribute, hir_id: HirId, target: Target) -> bool {
254         if let Some(mi) = attr.meta() {
255             if let Some(list) = mi.meta_item_list() {
256                 for meta in list {
257                     if meta.has_name(sym::alias) {
258                         if !meta.is_value_str()
259                             || meta
260                                 .value_str()
261                                 .map(|s| s.to_string())
262                                 .unwrap_or_else(String::new)
263                                 .is_empty()
264                         {
265                             self.tcx
266                                 .sess
267                                 .struct_span_err(
268                                     meta.span(),
269                                     "doc alias attribute expects a string: #[doc(alias = \"0\")]",
270                                 )
271                                 .emit();
272                             return false;
273                         }
274                         if let Some(err) = match target {
275                             Target::Impl => Some("implementation block"),
276                             Target::ForeignMod => Some("extern block"),
277                             Target::AssocTy => {
278                                 let parent_hir_id = self.tcx.hir().get_parent_item(hir_id);
279                                 let containing_item = self.tcx.hir().expect_item(parent_hir_id);
280                                 if Target::from_item(containing_item) == Target::Impl {
281                                     Some("type alias in implementation block")
282                                 } else {
283                                     None
284                                 }
285                             }
286                             Target::AssocConst => {
287                                 let parent_hir_id = self.tcx.hir().get_parent_item(hir_id);
288                                 let containing_item = self.tcx.hir().expect_item(parent_hir_id);
289                                 // We can't link to trait impl's consts.
290                                 let err = "associated constant in trait implementation block";
291                                 match containing_item.kind {
292                                     ItemKind::Impl { of_trait: Some(_), .. } => Some(err),
293                                     _ => None,
294                                 }
295                             }
296                             _ => None,
297                         } {
298                             self.tcx
299                                 .sess
300                                 .struct_span_err(
301                                     meta.span(),
302                                     &format!("`#[doc(alias = \"...\")]` isn't allowed on {}", err),
303                                 )
304                                 .emit();
305                         }
306                     }
307                 }
308             }
309         }
310         true
311     }
312
313     /// Checks if `#[cold]` is applied to a non-function. Returns `true` if valid.
314     fn check_cold(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) {
315         match target {
316             Target::Fn | Target::Method(..) | Target::ForeignFn | Target::Closure => {}
317             _ => {
318                 // FIXME: #[cold] was previously allowed on non-functions and some crates used
319                 // this, so only emit a warning.
320                 self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
321                     lint.build("attribute should be applied to a function")
322                         .warn(
323                             "this was previously accepted by the compiler but is \
324                              being phased out; it will become a hard error in \
325                              a future release!",
326                         )
327                         .span_label(*span, "not a function")
328                         .emit();
329                 });
330             }
331         }
332     }
333
334     /// Checks if `#[link_name]` is applied to an item other than a foreign function or static.
335     fn check_link_name(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) {
336         match target {
337             Target::ForeignFn | Target::ForeignStatic => {}
338             _ => {
339                 // FIXME: #[cold] was previously allowed on non-functions/statics and some crates
340                 // used this, so only emit a warning.
341                 self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
342                     let mut diag =
343                         lint.build("attribute should be applied to a foreign function or static");
344                     diag.warn(
345                         "this was previously accepted by the compiler but is \
346                          being phased out; it will become a hard error in \
347                          a future release!",
348                     );
349
350                     // See issue #47725
351                     if let Target::ForeignMod = target {
352                         if let Some(value) = attr.value_str() {
353                             diag.span_help(
354                                 attr.span,
355                                 &format!(r#"try `#[link(name = "{}")]` instead"#, value),
356                             );
357                         } else {
358                             diag.span_help(attr.span, r#"try `#[link(name = "...")]` instead"#);
359                         }
360                     }
361
362                     diag.span_label(*span, "not a foreign function or static");
363                     diag.emit();
364                 });
365             }
366         }
367     }
368
369     /// Checks if `#[no_link]` is applied to an `extern crate`. Returns `true` if valid.
370     fn check_no_link(&self, attr: &Attribute, span: &Span, target: Target) -> bool {
371         if target == Target::ExternCrate {
372             true
373         } else {
374             self.tcx
375                 .sess
376                 .struct_span_err(attr.span, "attribute should be applied to an `extern crate` item")
377                 .span_label(*span, "not an `extern crate` item")
378                 .emit();
379             false
380         }
381     }
382
383     /// Checks if `#[export_name]` is applied to a function or static. Returns `true` if valid.
384     fn check_export_name(&self, attr: &Attribute, span: &Span, target: Target) -> bool {
385         match target {
386             Target::Static | Target::Fn | Target::Method(..) => true,
387             _ => {
388                 self.tcx
389                     .sess
390                     .struct_span_err(
391                         attr.span,
392                         "attribute should be applied to a function or static",
393                     )
394                     .span_label(*span, "not a function or static")
395                     .emit();
396                 false
397             }
398         }
399     }
400
401     /// Checks if `#[link_section]` is applied to a function or static.
402     fn check_link_section(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) {
403         match target {
404             Target::Static | Target::Fn | Target::Method(..) => {}
405             _ => {
406                 // FIXME: #[link_section] was previously allowed on non-functions/statics and some
407                 // crates used this, so only emit a warning.
408                 self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
409                     lint.build("attribute should be applied to a function or static")
410                         .warn(
411                             "this was previously accepted by the compiler but is \
412                              being phased out; it will become a hard error in \
413                              a future release!",
414                         )
415                         .span_label(*span, "not a function or static")
416                         .emit();
417                 });
418             }
419         }
420     }
421
422     /// Checks if the `#[repr]` attributes on `item` are valid.
423     fn check_repr(
424         &self,
425         attrs: &'hir [Attribute],
426         span: &Span,
427         target: Target,
428         item: Option<&Item<'_>>,
429         hir_id: HirId,
430     ) {
431         // Extract the names of all repr hints, e.g., [foo, bar, align] for:
432         // ```
433         // #[repr(foo)]
434         // #[repr(bar, align(8))]
435         // ```
436         let hints: Vec<_> = attrs
437             .iter()
438             .filter(|attr| self.tcx.sess.check_name(attr, sym::repr))
439             .filter_map(|attr| attr.meta_item_list())
440             .flatten()
441             .collect();
442
443         let mut int_reprs = 0;
444         let mut is_c = false;
445         let mut is_simd = false;
446         let mut is_transparent = false;
447
448         for hint in &hints {
449             let (article, allowed_targets) = match hint.name_or_empty() {
450                 name @ sym::C | name @ sym::align => {
451                     is_c |= name == sym::C;
452                     match target {
453                         Target::Struct | Target::Union | Target::Enum => continue,
454                         _ => ("a", "struct, enum, or union"),
455                     }
456                 }
457                 sym::packed => {
458                     if target != Target::Struct && target != Target::Union {
459                         ("a", "struct or union")
460                     } else {
461                         continue;
462                     }
463                 }
464                 sym::simd => {
465                     is_simd = true;
466                     if target != Target::Struct {
467                         ("a", "struct")
468                     } else {
469                         continue;
470                     }
471                 }
472                 sym::transparent => {
473                     is_transparent = true;
474                     match target {
475                         Target::Struct | Target::Union | Target::Enum => continue,
476                         _ => ("a", "struct, enum, or union"),
477                     }
478                 }
479                 sym::no_niche => {
480                     if !self.tcx.features().enabled(sym::no_niche) {
481                         feature_err(
482                             &self.tcx.sess.parse_sess,
483                             sym::no_niche,
484                             hint.span(),
485                             "the attribute `repr(no_niche)` is currently unstable",
486                         )
487                         .emit();
488                     }
489                     match target {
490                         Target::Struct | Target::Enum => continue,
491                         _ => ("a", "struct or enum"),
492                     }
493                 }
494                 sym::i8
495                 | sym::u8
496                 | sym::i16
497                 | sym::u16
498                 | sym::i32
499                 | sym::u32
500                 | sym::i64
501                 | sym::u64
502                 | sym::i128
503                 | sym::u128
504                 | sym::isize
505                 | sym::usize => {
506                     int_reprs += 1;
507                     if target != Target::Enum {
508                         ("an", "enum")
509                     } else {
510                         continue;
511                     }
512                 }
513                 _ => continue,
514             };
515             self.emit_repr_error(
516                 hint.span(),
517                 *span,
518                 &format!("attribute should be applied to {}", allowed_targets),
519                 &format!("not {} {}", article, allowed_targets),
520             )
521         }
522
523         // Just point at all repr hints if there are any incompatibilities.
524         // This is not ideal, but tracking precisely which ones are at fault is a huge hassle.
525         let hint_spans = hints.iter().map(|hint| hint.span());
526
527         // Error on repr(transparent, <anything else apart from no_niche>).
528         let non_no_niche = |hint: &&NestedMetaItem| hint.name_or_empty() != sym::no_niche;
529         let non_no_niche_count = hints.iter().filter(non_no_niche).count();
530         if is_transparent && non_no_niche_count > 1 {
531             let hint_spans: Vec<_> = hint_spans.clone().collect();
532             struct_span_err!(
533                 self.tcx.sess,
534                 hint_spans,
535                 E0692,
536                 "transparent {} cannot have other repr hints",
537                 target
538             )
539             .emit();
540         }
541         // Warn on repr(u8, u16), repr(C, simd), and c-like-enum-repr(C, u8)
542         if (int_reprs > 1)
543             || (is_simd && is_c)
544             || (int_reprs == 1 && is_c && item.map_or(false, |item| is_c_like_enum(item)))
545         {
546             self.tcx.struct_span_lint_hir(
547                 CONFLICTING_REPR_HINTS,
548                 hir_id,
549                 hint_spans.collect::<Vec<Span>>(),
550                 |lint| {
551                     lint.build("conflicting representation hints")
552                         .code(rustc_errors::error_code!(E0566))
553                         .emit();
554                 },
555             );
556         }
557     }
558
559     fn emit_repr_error(
560         &self,
561         hint_span: Span,
562         label_span: Span,
563         hint_message: &str,
564         label_message: &str,
565     ) {
566         struct_span_err!(self.tcx.sess, hint_span, E0517, "{}", hint_message)
567             .span_label(label_span, label_message)
568             .emit();
569     }
570
571     fn check_stmt_attributes(&self, stmt: &hir::Stmt<'_>) {
572         // When checking statements ignore expressions, they will be checked later
573         if let hir::StmtKind::Local(ref l) = stmt.kind {
574             self.check_attributes(l.hir_id, &l.attrs, &stmt.span, Target::Statement, None);
575             for attr in l.attrs.iter() {
576                 if self.tcx.sess.check_name(attr, sym::repr) {
577                     self.emit_repr_error(
578                         attr.span,
579                         stmt.span,
580                         "attribute should not be applied to a statement",
581                         "not a struct, enum, or union",
582                     );
583                 }
584             }
585         }
586     }
587
588     fn check_expr_attributes(&self, expr: &hir::Expr<'_>) {
589         let target = match expr.kind {
590             hir::ExprKind::Closure(..) => Target::Closure,
591             _ => Target::Expression,
592         };
593         self.check_attributes(expr.hir_id, &expr.attrs, &expr.span, target, None);
594         for attr in expr.attrs.iter() {
595             if self.tcx.sess.check_name(attr, sym::repr) {
596                 self.emit_repr_error(
597                     attr.span,
598                     expr.span,
599                     "attribute should not be applied to an expression",
600                     "not defining a struct, enum, or union",
601                 );
602             }
603         }
604         if target == Target::Closure {
605             self.tcx.ensure().codegen_fn_attrs(self.tcx.hir().local_def_id(expr.hir_id));
606         }
607     }
608
609     fn check_used(&self, attrs: &'hir [Attribute], target: Target) {
610         for attr in attrs {
611             if self.tcx.sess.check_name(attr, sym::used) && target != Target::Static {
612                 self.tcx
613                     .sess
614                     .span_err(attr.span, "attribute must be applied to a `static` variable");
615             }
616         }
617     }
618 }
619
620 impl Visitor<'tcx> for CheckAttrVisitor<'tcx> {
621     type Map = Map<'tcx>;
622
623     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
624         NestedVisitorMap::OnlyBodies(self.tcx.hir())
625     }
626
627     fn visit_item(&mut self, item: &'tcx Item<'tcx>) {
628         let target = Target::from_item(item);
629         self.check_attributes(item.hir_id, item.attrs, &item.span, target, Some(item));
630         intravisit::walk_item(self, item)
631     }
632
633     fn visit_trait_item(&mut self, trait_item: &'tcx TraitItem<'tcx>) {
634         let target = Target::from_trait_item(trait_item);
635         self.check_attributes(trait_item.hir_id, &trait_item.attrs, &trait_item.span, target, None);
636         intravisit::walk_trait_item(self, trait_item)
637     }
638
639     fn visit_foreign_item(&mut self, f_item: &'tcx hir::ForeignItem<'tcx>) {
640         let target = Target::from_foreign_item(f_item);
641         self.check_attributes(f_item.hir_id, &f_item.attrs, &f_item.span, target, None);
642         intravisit::walk_foreign_item(self, f_item)
643     }
644
645     fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
646         let target = target_from_impl_item(self.tcx, impl_item);
647         self.check_attributes(impl_item.hir_id, &impl_item.attrs, &impl_item.span, target, None);
648         intravisit::walk_impl_item(self, impl_item)
649     }
650
651     fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) {
652         self.check_stmt_attributes(stmt);
653         intravisit::walk_stmt(self, stmt)
654     }
655
656     fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
657         self.check_expr_attributes(expr);
658         intravisit::walk_expr(self, expr)
659     }
660 }
661
662 fn is_c_like_enum(item: &Item<'_>) -> bool {
663     if let ItemKind::Enum(ref def, _) = item.kind {
664         for variant in def.variants {
665             match variant.data {
666                 hir::VariantData::Unit(..) => { /* continue */ }
667                 _ => return false,
668             }
669         }
670         true
671     } else {
672         false
673     }
674 }
675
676 fn check_mod_attrs(tcx: TyCtxt<'_>, module_def_id: LocalDefId) {
677     tcx.hir()
678         .visit_item_likes_in_module(module_def_id, &mut CheckAttrVisitor { tcx }.as_deep_visitor());
679 }
680
681 pub(crate) fn provide(providers: &mut Providers) {
682     *providers = Providers { check_mod_attrs, ..*providers };
683 }