]> git.lizzy.rs Git - rust.git/blob - src/librustc/hir/check_attr.rs
Auto merge of #55014 - ljedrz:lazyboye_unwraps, r=matthewjasper
[rust.git] / src / librustc / hir / check_attr.rs
1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 //! This module implements some validity checks for attributes.
12 //! In particular it verifies that `#[inline]` and `#[repr]` attributes are
13 //! attached to items that actually support them and if there are
14 //! conflicts between multiple such attributes attached to the same
15 //! item.
16
17 use hir;
18 use hir::intravisit::{self, Visitor, NestedVisitorMap};
19 use ty::TyCtxt;
20 use std::fmt::{self, Display};
21 use syntax_pos::Span;
22
23 #[derive(Copy, Clone, PartialEq)]
24 pub(crate) enum Target {
25     ExternCrate,
26     Use,
27     Static,
28     Const,
29     Fn,
30     Closure,
31     Mod,
32     ForeignMod,
33     GlobalAsm,
34     Ty,
35     Existential,
36     Enum,
37     Struct,
38     Union,
39     Trait,
40     TraitAlias,
41     Impl,
42     Expression,
43     Statement,
44 }
45
46 impl Display for Target {
47     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48         write!(f, "{}", match *self {
49             Target::ExternCrate => "extern crate",
50             Target::Use => "use",
51             Target::Static => "static item",
52             Target::Const => "constant item",
53             Target::Fn => "function",
54             Target::Closure => "closure",
55             Target::Mod => "module",
56             Target::ForeignMod => "foreign module",
57             Target::GlobalAsm => "global asm",
58             Target::Ty => "type alias",
59             Target::Existential => "existential type",
60             Target::Enum => "enum",
61             Target::Struct => "struct",
62             Target::Union => "union",
63             Target::Trait => "trait",
64             Target::TraitAlias => "trait alias",
65             Target::Impl => "item",
66             Target::Expression => "expression",
67             Target::Statement => "statement",
68         })
69     }
70 }
71
72 impl Target {
73     pub(crate) fn from_item(item: &hir::Item) -> Target {
74         match item.node {
75             hir::ItemKind::ExternCrate(..) => Target::ExternCrate,
76             hir::ItemKind::Use(..) => Target::Use,
77             hir::ItemKind::Static(..) => Target::Static,
78             hir::ItemKind::Const(..) => Target::Const,
79             hir::ItemKind::Fn(..) => Target::Fn,
80             hir::ItemKind::Mod(..) => Target::Mod,
81             hir::ItemKind::ForeignMod(..) => Target::ForeignMod,
82             hir::ItemKind::GlobalAsm(..) => Target::GlobalAsm,
83             hir::ItemKind::Ty(..) => Target::Ty,
84             hir::ItemKind::Existential(..) => Target::Existential,
85             hir::ItemKind::Enum(..) => Target::Enum,
86             hir::ItemKind::Struct(..) => Target::Struct,
87             hir::ItemKind::Union(..) => Target::Union,
88             hir::ItemKind::Trait(..) => Target::Trait,
89             hir::ItemKind::TraitAlias(..) => Target::TraitAlias,
90             hir::ItemKind::Impl(..) => Target::Impl,
91         }
92     }
93 }
94
95 struct CheckAttrVisitor<'a, 'tcx: 'a> {
96     tcx: TyCtxt<'a, 'tcx, 'tcx>,
97 }
98
99 impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> {
100     /// Check any attribute.
101     fn check_attributes(&self, item: &hir::Item, target: Target) {
102         if target == Target::Fn || target == Target::Const {
103             self.tcx.codegen_fn_attrs(self.tcx.hir.local_def_id(item.id));
104         } else if let Some(a) = item.attrs.iter().find(|a| a.check_name("target_feature")) {
105             self.tcx.sess.struct_span_err(a.span, "attribute should be applied to a function")
106                 .span_label(item.span, "not a function")
107                 .emit();
108         }
109
110         for attr in &item.attrs {
111             if attr.check_name("inline") {
112                 self.check_inline(attr, &item.span, target)
113             } else if attr.check_name("non_exhaustive") {
114                 self.check_non_exhaustive(attr, item, target)
115             } else if attr.check_name("marker") {
116                 self.check_marker(attr, item, target)
117             }
118         }
119
120         self.check_repr(item, target);
121         self.check_used(item, target);
122     }
123
124     /// Check if an `#[inline]` is applied to a function or a closure.
125     fn check_inline(&self, attr: &hir::Attribute, span: &Span, target: Target) {
126         if target != Target::Fn && target != Target::Closure {
127             struct_span_err!(self.tcx.sess,
128                              attr.span,
129                              E0518,
130                              "attribute should be applied to function or closure")
131                 .span_label(*span, "not a function or closure")
132                 .emit();
133         }
134     }
135
136     /// Check if the `#[non_exhaustive]` attribute on an `item` is valid.
137     fn check_non_exhaustive(&self, attr: &hir::Attribute, item: &hir::Item, target: Target) {
138         match target {
139             Target::Struct | Target::Enum => { /* Valid */ },
140             _ => {
141                 struct_span_err!(self.tcx.sess,
142                                  attr.span,
143                                  E0701,
144                                  "attribute can only be applied to a struct or enum")
145                     .span_label(item.span, "not a struct or enum")
146                     .emit();
147                 return;
148             }
149         }
150
151         if attr.meta_item_list().is_some() || attr.value_str().is_some() {
152             struct_span_err!(self.tcx.sess,
153                              attr.span,
154                              E0702,
155                              "attribute should be empty")
156                 .span_label(item.span, "not empty")
157                 .emit();
158         }
159     }
160
161     /// Check if the `#[marker]` attribute on an `item` is valid.
162     fn check_marker(&self, attr: &hir::Attribute, item: &hir::Item, target: Target) {
163         match target {
164             Target::Trait => { /* Valid */ },
165             _ => {
166                 self.tcx.sess
167                     .struct_span_err(attr.span, "attribute can only be applied to a trait")
168                     .span_label(item.span, "not a trait")
169                     .emit();
170                 return;
171             }
172         }
173
174         if !attr.is_word() {
175             self.tcx.sess
176                 .struct_span_err(attr.span, "attribute should be empty")
177                 .emit();
178         }
179     }
180
181     /// Check if the `#[repr]` attributes on `item` are valid.
182     fn check_repr(&self, item: &hir::Item, target: Target) {
183         // Extract the names of all repr hints, e.g., [foo, bar, align] for:
184         // ```
185         // #[repr(foo)]
186         // #[repr(bar, align(8))]
187         // ```
188         let hints: Vec<_> = item.attrs
189             .iter()
190             .filter(|attr| attr.name() == "repr")
191             .filter_map(|attr| attr.meta_item_list())
192             .flatten()
193             .collect();
194
195         let mut int_reprs = 0;
196         let mut is_c = false;
197         let mut is_simd = false;
198         let mut is_transparent = false;
199
200         for hint in &hints {
201             let name = if let Some(name) = hint.name() {
202                 name
203             } else {
204                 // Invalid repr hint like repr(42). We don't check for unrecognized hints here
205                 // (libsyntax does that), so just ignore it.
206                 continue;
207             };
208
209             let (article, allowed_targets) = match &*name.as_str() {
210                 "C" => {
211                     is_c = true;
212                     if target != Target::Struct &&
213                             target != Target::Union &&
214                             target != Target::Enum {
215                                 ("a", "struct, enum or union")
216                     } else {
217                         continue
218                     }
219                 }
220                 "packed" => {
221                     if target != Target::Struct &&
222                             target != Target::Union {
223                                 ("a", "struct or union")
224                     } else {
225                         continue
226                     }
227                 }
228                 "simd" => {
229                     is_simd = true;
230                     if target != Target::Struct {
231                         ("a", "struct")
232                     } else {
233                         continue
234                     }
235                 }
236                 "align" => {
237                     if target != Target::Struct &&
238                             target != Target::Union {
239                         ("a", "struct or union")
240                     } else {
241                         continue
242                     }
243                 }
244                 "transparent" => {
245                     is_transparent = true;
246                     if target != Target::Struct {
247                         ("a", "struct")
248                     } else {
249                         continue
250                     }
251                 }
252                 "i8"  | "u8"  | "i16" | "u16" |
253                 "i32" | "u32" | "i64" | "u64" |
254                 "isize" | "usize" => {
255                     int_reprs += 1;
256                     if target != Target::Enum {
257                         ("an", "enum")
258                     } else {
259                         continue
260                     }
261                 }
262                 _ => continue,
263             };
264             self.emit_repr_error(
265                 hint.span,
266                 item.span,
267                 &format!("attribute should be applied to {}", allowed_targets),
268                 &format!("not {} {}", article, allowed_targets),
269             )
270         }
271
272         // Just point at all repr hints if there are any incompatibilities.
273         // This is not ideal, but tracking precisely which ones are at fault is a huge hassle.
274         let hint_spans = hints.iter().map(|hint| hint.span);
275
276         // Error on repr(transparent, <anything else>).
277         if is_transparent && hints.len() > 1 {
278             let hint_spans: Vec<_> = hint_spans.clone().collect();
279             span_err!(self.tcx.sess, hint_spans, E0692,
280                       "transparent struct cannot have other repr hints");
281         }
282         // Warn on repr(u8, u16), repr(C, simd), and c-like-enum-repr(C, u8)
283         if (int_reprs > 1)
284            || (is_simd && is_c)
285            || (int_reprs == 1 && is_c && is_c_like_enum(item)) {
286             let hint_spans: Vec<_> = hint_spans.collect();
287             span_warn!(self.tcx.sess, hint_spans, E0566,
288                        "conflicting representation hints");
289         }
290     }
291
292     fn emit_repr_error(
293         &self,
294         hint_span: Span,
295         label_span: Span,
296         hint_message: &str,
297         label_message: &str,
298     ) {
299         struct_span_err!(self.tcx.sess, hint_span, E0517, "{}", hint_message)
300             .span_label(label_span, label_message)
301             .emit();
302     }
303
304     fn check_stmt_attributes(&self, stmt: &hir::Stmt) {
305         // When checking statements ignore expressions, they will be checked later
306         if let hir::StmtKind::Decl(_, _) = stmt.node {
307             for attr in stmt.node.attrs() {
308                 if attr.check_name("inline") {
309                     self.check_inline(attr, &stmt.span, Target::Statement);
310                 }
311                 if attr.check_name("repr") {
312                     self.emit_repr_error(
313                         attr.span,
314                         stmt.span,
315                         "attribute should not be applied to a statement",
316                         "not a struct, enum or union",
317                     );
318                 }
319             }
320         }
321     }
322
323     fn check_expr_attributes(&self, expr: &hir::Expr) {
324         let target = match expr.node {
325             hir::ExprKind::Closure(..) => Target::Closure,
326             _ => Target::Expression,
327         };
328         for attr in expr.attrs.iter() {
329             if attr.check_name("inline") {
330                 self.check_inline(attr, &expr.span, target);
331             }
332             if attr.check_name("repr") {
333                 self.emit_repr_error(
334                     attr.span,
335                     expr.span,
336                     "attribute should not be applied to an expression",
337                     "not defining a struct, enum or union",
338                 );
339             }
340         }
341     }
342
343     fn check_used(&self, item: &hir::Item, target: Target) {
344         for attr in &item.attrs {
345             if attr.name() == "used" && target != Target::Static {
346                 self.tcx.sess
347                     .span_err(attr.span, "attribute must be applied to a `static` variable");
348             }
349         }
350     }
351 }
352
353 impl<'a, 'tcx> Visitor<'tcx> for CheckAttrVisitor<'a, 'tcx> {
354     fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
355         NestedVisitorMap::OnlyBodies(&self.tcx.hir)
356     }
357
358     fn visit_item(&mut self, item: &'tcx hir::Item) {
359         let target = Target::from_item(item);
360         self.check_attributes(item, target);
361         intravisit::walk_item(self, item)
362     }
363
364
365     fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt) {
366         self.check_stmt_attributes(stmt);
367         intravisit::walk_stmt(self, stmt)
368     }
369
370     fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
371         self.check_expr_attributes(expr);
372         intravisit::walk_expr(self, expr)
373     }
374 }
375
376 pub fn check_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
377     let mut checker = CheckAttrVisitor { tcx };
378     tcx.hir.krate().visit_all_item_likes(&mut checker.as_deep_visitor());
379 }
380
381 fn is_c_like_enum(item: &hir::Item) -> bool {
382     if let hir::ItemKind::Enum(ref def, _) = item.node {
383         for variant in &def.variants {
384             match variant.node.data {
385                 hir::VariantData::Unit(_) => { /* continue */ }
386                 _ => { return false; }
387             }
388         }
389         true
390     } else {
391         false
392     }
393 }