]> git.lizzy.rs Git - rust.git/blob - src/librustc/hir/check_attr.rs
Auto merge of #66384 - cjgillot:typefoldable, r=Zoxc
[rust.git] / src / librustc / hir / 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 crate::hir::{self, HirId, HirVec, Attribute, Item, ItemKind, TraitItem, TraitItemKind};
8 use crate::hir::DUMMY_HIR_ID;
9 use crate::hir::def_id::DefId;
10 use crate::hir::intravisit::{self, Visitor, NestedVisitorMap};
11 use crate::lint::builtin::UNUSED_ATTRIBUTES;
12 use crate::ty::TyCtxt;
13 use crate::ty::query::Providers;
14
15 use std::fmt::{self, Display};
16 use syntax::{attr, symbol::sym};
17 use syntax_pos::Span;
18
19 use rustc_error_codes::*;
20
21 #[derive(Copy, Clone, PartialEq)]
22 pub(crate) enum MethodKind {
23     Trait { body: bool },
24     Inherent,
25 }
26
27 #[derive(Copy, Clone, PartialEq)]
28 pub(crate) enum Target {
29     ExternCrate,
30     Use,
31     Static,
32     Const,
33     Fn,
34     Closure,
35     Mod,
36     ForeignMod,
37     GlobalAsm,
38     TyAlias,
39     OpaqueTy,
40     Enum,
41     Struct,
42     Union,
43     Trait,
44     TraitAlias,
45     Impl,
46     Expression,
47     Statement,
48     AssocConst,
49     Method(MethodKind),
50     AssocTy,
51     ForeignFn,
52     ForeignStatic,
53     ForeignTy,
54 }
55
56 impl Display for Target {
57     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58         write!(f, "{}", match *self {
59             Target::ExternCrate => "extern crate",
60             Target::Use => "use",
61             Target::Static => "static item",
62             Target::Const => "constant item",
63             Target::Fn => "function",
64             Target::Closure => "closure",
65             Target::Mod => "module",
66             Target::ForeignMod => "foreign module",
67             Target::GlobalAsm => "global asm",
68             Target::TyAlias => "type alias",
69             Target::OpaqueTy => "opaque type",
70             Target::Enum => "enum",
71             Target::Struct => "struct",
72             Target::Union => "union",
73             Target::Trait => "trait",
74             Target::TraitAlias => "trait alias",
75             Target::Impl => "item",
76             Target::Expression => "expression",
77             Target::Statement => "statement",
78             Target::AssocConst => "associated const",
79             Target::Method(_) => "method",
80             Target::AssocTy => "associated type",
81             Target::ForeignFn => "foreign function",
82             Target::ForeignStatic => "foreign static item",
83             Target::ForeignTy => "foreign type",
84         })
85     }
86 }
87
88 impl Target {
89     pub(crate) fn from_item(item: &Item) -> Target {
90         match item.kind {
91             ItemKind::ExternCrate(..) => Target::ExternCrate,
92             ItemKind::Use(..) => Target::Use,
93             ItemKind::Static(..) => Target::Static,
94             ItemKind::Const(..) => Target::Const,
95             ItemKind::Fn(..) => Target::Fn,
96             ItemKind::Mod(..) => Target::Mod,
97             ItemKind::ForeignMod(..) => Target::ForeignMod,
98             ItemKind::GlobalAsm(..) => Target::GlobalAsm,
99             ItemKind::TyAlias(..) => Target::TyAlias,
100             ItemKind::OpaqueTy(..) => Target::OpaqueTy,
101             ItemKind::Enum(..) => Target::Enum,
102             ItemKind::Struct(..) => Target::Struct,
103             ItemKind::Union(..) => Target::Union,
104             ItemKind::Trait(..) => Target::Trait,
105             ItemKind::TraitAlias(..) => Target::TraitAlias,
106             ItemKind::Impl(..) => Target::Impl,
107         }
108     }
109
110     fn from_trait_item(trait_item: &TraitItem) -> Target {
111         match trait_item.kind {
112             TraitItemKind::Const(..) => Target::AssocConst,
113             TraitItemKind::Method(_, hir::TraitMethod::Required(_)) => {
114                 Target::Method(MethodKind::Trait { body: false })
115             }
116             TraitItemKind::Method(_, hir::TraitMethod::Provided(_)) => {
117                 Target::Method(MethodKind::Trait { body: true })
118             }
119             TraitItemKind::Type(..) => Target::AssocTy,
120         }
121     }
122
123     fn from_foreign_item(foreign_item: &hir::ForeignItem) -> Target {
124         match foreign_item.kind {
125             hir::ForeignItemKind::Fn(..) => Target::ForeignFn,
126             hir::ForeignItemKind::Static(..) => Target::ForeignStatic,
127             hir::ForeignItemKind::Type => Target::ForeignTy,
128         }
129     }
130
131     fn from_impl_item<'tcx>(tcx: TyCtxt<'tcx>, impl_item: &hir::ImplItem) -> Target {
132         match impl_item.kind {
133             hir::ImplItemKind::Const(..) => Target::AssocConst,
134             hir::ImplItemKind::Method(..) => {
135                 let parent_hir_id = tcx.hir().get_parent_item(impl_item.hir_id);
136                 let containing_item = tcx.hir().expect_item(parent_hir_id);
137                 let containing_impl_is_for_trait = match &containing_item.kind {
138                     hir::ItemKind::Impl(_, _, _, _, tr, _, _) => tr.is_some(),
139                     _ => bug!("parent of an ImplItem must be an Impl"),
140                 };
141                 if containing_impl_is_for_trait {
142                     Target::Method(MethodKind::Trait { body: true })
143                 } else {
144                     Target::Method(MethodKind::Inherent)
145                 }
146             }
147             hir::ImplItemKind::TyAlias(..) | hir::ImplItemKind::OpaqueTy(..) => Target::AssocTy,
148         }
149     }
150 }
151
152 struct CheckAttrVisitor<'tcx> {
153     tcx: TyCtxt<'tcx>,
154 }
155
156 impl CheckAttrVisitor<'tcx> {
157     /// Checks any attribute.
158     fn check_attributes(
159         &self,
160         hir_id: HirId,
161         attrs: &HirVec<Attribute>,
162         span: &Span,
163         target: Target,
164         item: Option<&Item>,
165     ) {
166         let mut is_valid = true;
167         for attr in attrs {
168             is_valid &= if attr.check_name(sym::inline) {
169                 self.check_inline(hir_id, attr, span, target)
170             } else if attr.check_name(sym::non_exhaustive) {
171                 self.check_non_exhaustive(attr, span, target)
172             } else if attr.check_name(sym::marker) {
173                 self.check_marker(attr, span, target)
174             } else if attr.check_name(sym::target_feature) {
175                 self.check_target_feature(attr, span, target)
176             } else if attr.check_name(sym::track_caller) {
177                 self.check_track_caller(&attr.span, attrs, span, target)
178             } else {
179                 true
180             };
181         }
182
183         if !is_valid {
184             return;
185         }
186
187         if target == Target::Fn {
188             self.tcx.codegen_fn_attrs(self.tcx.hir().local_def_id(hir_id));
189         }
190
191         self.check_repr(attrs, span, target, item);
192         self.check_used(attrs, target);
193     }
194
195     /// Checks if an `#[inline]` is applied to a function or a closure. Returns `true` if valid.
196     fn check_inline(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) -> bool {
197         match target {
198             Target::Fn | Target::Closure | Target::Method(MethodKind::Trait { body: true })
199             | Target::Method(MethodKind::Inherent) => true,
200             Target::Method(MethodKind::Trait { body: false }) | Target::ForeignFn => {
201                 self.tcx.struct_span_lint_hir(
202                     UNUSED_ATTRIBUTES,
203                     hir_id,
204                     attr.span,
205                     "`#[inline]` is ignored on function prototypes",
206                 ).emit();
207                 true
208             }
209             // FIXME(#65833): We permit associated consts to have an `#[inline]` attribute with
210             // just a lint, because we previously erroneously allowed it and some crates used it
211             // accidentally, to to be compatible with crates depending on them, we can't throw an
212             // error here.
213             Target::AssocConst => {
214                 self.tcx.struct_span_lint_hir(
215                     UNUSED_ATTRIBUTES,
216                     hir_id,
217                     attr.span,
218                     "`#[inline]` is ignored on constants",
219                 ).warn("this was previously accepted by the compiler but is \
220                        being phased out; it will become a hard error in \
221                        a future release!")
222                 .note("for more information, see issue #65833 \
223                        <https://github.com/rust-lang/rust/issues/65833>")
224                 .emit();
225                 true
226             }
227             _ => {
228                 struct_span_err!(
229                     self.tcx.sess,
230                     attr.span,
231                     E0518,
232                     "attribute should be applied to function or closure",
233                 ).span_label(*span, "not a function or closure")
234                     .emit();
235                 false
236             }
237         }
238     }
239
240     /// Checks if a `#[track_caller]` is applied to a non-naked function. Returns `true` if valid.
241     fn check_track_caller(
242         &self,
243         attr_span: &Span,
244         attrs: &HirVec<Attribute>,
245         span: &Span,
246         target: Target,
247     ) -> bool {
248         match target {
249             Target::Fn if attr::contains_name(attrs, sym::naked) => {
250                 struct_span_err!(
251                     self.tcx.sess,
252                     *attr_span,
253                     E0736,
254                     "cannot use `#[track_caller]` with `#[naked]`",
255                 ).emit();
256                 false
257             }
258             Target::Fn | Target::Method(MethodKind::Inherent) => true,
259             Target::Method(_) => {
260                 struct_span_err!(
261                     self.tcx.sess,
262                     *attr_span,
263                     E0738,
264                     "`#[track_caller]` may not be used on trait methods",
265                 ).emit();
266                 false
267             }
268             _ => {
269                 struct_span_err!(
270                     self.tcx.sess,
271                     *attr_span,
272                     E0739,
273                     "attribute should be applied to function"
274                 )
275                 .span_label(*span, "not a function")
276                 .emit();
277                 false
278             }
279         }
280     }
281
282     /// Checks if the `#[non_exhaustive]` attribute on an `item` is valid. Returns `true` if valid.
283     fn check_non_exhaustive(
284         &self,
285         attr: &Attribute,
286         span: &Span,
287         target: Target,
288     ) -> bool {
289         match target {
290             Target::Struct | Target::Enum => true,
291             _ => {
292                 struct_span_err!(self.tcx.sess,
293                                  attr.span,
294                                  E0701,
295                                  "attribute can only be applied to a struct or enum")
296                     .span_label(*span, "not a struct or enum")
297                     .emit();
298                 false
299             }
300         }
301     }
302
303     /// Checks if the `#[marker]` attribute on an `item` is valid. Returns `true` if valid.
304     fn check_marker(&self, attr: &Attribute, span: &Span, target: Target) -> bool {
305         match target {
306             Target::Trait => true,
307             _ => {
308                 self.tcx.sess
309                     .struct_span_err(attr.span, "attribute can only be applied to a trait")
310                     .span_label(*span, "not a trait")
311                     .emit();
312                 false
313             }
314         }
315     }
316
317     /// Checks if the `#[target_feature]` attribute on `item` is valid. Returns `true` if valid.
318     fn check_target_feature(&self, attr: &Attribute, span: &Span, target: Target) -> bool {
319         match target {
320             Target::Fn | Target::Method(MethodKind::Trait { body: true })
321             | Target::Method(MethodKind::Inherent) => true,
322             _ => {
323                 self.tcx.sess
324                     .struct_span_err(attr.span, "attribute should be applied to a function")
325                     .span_label(*span, "not a function")
326                     .emit();
327                 false
328             },
329         }
330     }
331
332     /// Checks if the `#[repr]` attributes on `item` are valid.
333     fn check_repr(
334         &self,
335         attrs: &HirVec<Attribute>,
336         span: &Span,
337         target: Target,
338         item: Option<&Item>,
339     ) {
340         // Extract the names of all repr hints, e.g., [foo, bar, align] for:
341         // ```
342         // #[repr(foo)]
343         // #[repr(bar, align(8))]
344         // ```
345         let hints: Vec<_> = attrs
346             .iter()
347             .filter(|attr| attr.check_name(sym::repr))
348             .filter_map(|attr| attr.meta_item_list())
349             .flatten()
350             .collect();
351
352         let mut int_reprs = 0;
353         let mut is_c = false;
354         let mut is_simd = false;
355         let mut is_transparent = false;
356
357         for hint in &hints {
358             let (article, allowed_targets) = match hint.name_or_empty() {
359                 name @ sym::C | name @ sym::align => {
360                     is_c |= name == sym::C;
361                     match target {
362                         Target::Struct | Target::Union | Target::Enum => continue,
363                         _ => ("a", "struct, enum, or union"),
364                     }
365                 }
366                 sym::packed => {
367                     if target != Target::Struct &&
368                             target != Target::Union {
369                                 ("a", "struct or union")
370                     } else {
371                         continue
372                     }
373                 }
374                 sym::simd => {
375                     is_simd = true;
376                     if target != Target::Struct {
377                         ("a", "struct")
378                     } else {
379                         continue
380                     }
381                 }
382                 sym::transparent => {
383                     is_transparent = true;
384                     match target {
385                         Target::Struct | Target::Union | Target::Enum => continue,
386                         _ => ("a", "struct, enum, or union"),
387                     }
388                 }
389                 sym::i8  | sym::u8  | sym::i16 | sym::u16 |
390                 sym::i32 | sym::u32 | sym::i64 | sym::u64 |
391                 sym::isize | sym::usize => {
392                     int_reprs += 1;
393                     if target != Target::Enum {
394                         ("an", "enum")
395                     } else {
396                         continue
397                     }
398                 }
399                 _ => continue,
400             };
401             self.emit_repr_error(
402                 hint.span(),
403                 *span,
404                 &format!("attribute should be applied to {}", allowed_targets),
405                 &format!("not {} {}", article, allowed_targets),
406             )
407         }
408
409         // Just point at all repr hints if there are any incompatibilities.
410         // This is not ideal, but tracking precisely which ones are at fault is a huge hassle.
411         let hint_spans = hints.iter().map(|hint| hint.span());
412
413         // Error on repr(transparent, <anything else>).
414         if is_transparent && hints.len() > 1 {
415             let hint_spans: Vec<_> = hint_spans.clone().collect();
416             span_err!(self.tcx.sess, hint_spans, E0692,
417                       "transparent {} cannot have other repr hints", target);
418         }
419         // Warn on repr(u8, u16), repr(C, simd), and c-like-enum-repr(C, u8)
420         if (int_reprs > 1)
421            || (is_simd && is_c)
422            || (int_reprs == 1 && is_c && item.map_or(false, |item| is_c_like_enum(item))) {
423             let hint_spans: Vec<_> = hint_spans.collect();
424             span_warn!(self.tcx.sess, hint_spans, E0566,
425                        "conflicting representation hints");
426         }
427     }
428
429     fn emit_repr_error(
430         &self,
431         hint_span: Span,
432         label_span: Span,
433         hint_message: &str,
434         label_message: &str,
435     ) {
436         struct_span_err!(self.tcx.sess, hint_span, E0517, "{}", hint_message)
437             .span_label(label_span, label_message)
438             .emit();
439     }
440
441     fn check_stmt_attributes(&self, stmt: &hir::Stmt) {
442         // When checking statements ignore expressions, they will be checked later
443         if let hir::StmtKind::Local(ref l) = stmt.kind {
444             for attr in l.attrs.iter() {
445                 if attr.check_name(sym::inline) {
446                     self.check_inline(DUMMY_HIR_ID, attr, &stmt.span, Target::Statement);
447                 }
448                 if attr.check_name(sym::repr) {
449                     self.emit_repr_error(
450                         attr.span,
451                         stmt.span,
452                         "attribute should not be applied to a statement",
453                         "not a struct, enum, or union",
454                     );
455                 }
456             }
457         }
458     }
459
460     fn check_expr_attributes(&self, expr: &hir::Expr) {
461         let target = match expr.kind {
462             hir::ExprKind::Closure(..) => Target::Closure,
463             _ => Target::Expression,
464         };
465         for attr in expr.attrs.iter() {
466             if attr.check_name(sym::inline) {
467                 self.check_inline(DUMMY_HIR_ID, attr, &expr.span, target);
468             }
469             if attr.check_name(sym::repr) {
470                 self.emit_repr_error(
471                     attr.span,
472                     expr.span,
473                     "attribute should not be applied to an expression",
474                     "not defining a struct, enum, or union",
475                 );
476             }
477         }
478     }
479
480     fn check_used(&self, attrs: &HirVec<Attribute>, target: Target) {
481         for attr in attrs {
482             if attr.check_name(sym::used) && target != Target::Static {
483                 self.tcx.sess
484                     .span_err(attr.span, "attribute must be applied to a `static` variable");
485             }
486         }
487     }
488 }
489
490 impl Visitor<'tcx> for CheckAttrVisitor<'tcx> {
491     fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
492         NestedVisitorMap::OnlyBodies(&self.tcx.hir())
493     }
494
495     fn visit_item(&mut self, item: &'tcx Item) {
496         let target = Target::from_item(item);
497         self.check_attributes(item.hir_id, &item.attrs, &item.span, target, Some(item));
498         intravisit::walk_item(self, item)
499     }
500
501     fn visit_trait_item(&mut self, trait_item: &'tcx TraitItem) {
502         let target = Target::from_trait_item(trait_item);
503         self.check_attributes(trait_item.hir_id, &trait_item.attrs, &trait_item.span, target, None);
504         intravisit::walk_trait_item(self, trait_item)
505     }
506
507     fn visit_foreign_item(&mut self, f_item: &'tcx hir::ForeignItem) {
508         let target = Target::from_foreign_item(f_item);
509         self.check_attributes(f_item.hir_id, &f_item.attrs, &f_item.span, target, None);
510         intravisit::walk_foreign_item(self, f_item)
511     }
512
513     fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem) {
514         let target = Target::from_impl_item(self.tcx, impl_item);
515         self.check_attributes(impl_item.hir_id, &impl_item.attrs, &impl_item.span, target, None);
516         intravisit::walk_impl_item(self, impl_item)
517     }
518
519     fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt) {
520         self.check_stmt_attributes(stmt);
521         intravisit::walk_stmt(self, stmt)
522     }
523
524     fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
525         self.check_expr_attributes(expr);
526         intravisit::walk_expr(self, expr)
527     }
528 }
529
530 fn is_c_like_enum(item: &Item) -> bool {
531     if let ItemKind::Enum(ref def, _) = item.kind {
532         for variant in &def.variants {
533             match variant.data {
534                 hir::VariantData::Unit(..) => { /* continue */ }
535                 _ => return false,
536             }
537         }
538         true
539     } else {
540         false
541     }
542 }
543
544 fn check_mod_attrs(tcx: TyCtxt<'_>, module_def_id: DefId) {
545     tcx.hir().visit_item_likes_in_module(
546         module_def_id,
547         &mut CheckAttrVisitor { tcx }.as_deep_visitor()
548     );
549 }
550
551 pub(crate) fn provide(providers: &mut Providers<'_>) {
552     *providers = Providers {
553         check_mod_attrs,
554         ..*providers
555     };
556 }