]> git.lizzy.rs Git - rust.git/blob - src/librustc/hir/check_attr.rs
2d83c158fe08f1bb8c65a1f2b695437ad6d7eed0
[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 syntax_pos::Span;
18 use ty::TyCtxt;
19
20 use hir;
21 use hir::intravisit::{self, Visitor, NestedVisitorMap};
22
23 #[derive(Copy, Clone, PartialEq)]
24 enum Target {
25     Fn,
26     Struct,
27     Union,
28     Enum,
29     Const,
30     ForeignMod,
31     Expression,
32     Statement,
33     Closure,
34     Static,
35     Other,
36 }
37
38 impl Target {
39     fn from_item(item: &hir::Item) -> Target {
40         match item.node {
41             hir::ItemFn(..) => Target::Fn,
42             hir::ItemStruct(..) => Target::Struct,
43             hir::ItemUnion(..) => Target::Union,
44             hir::ItemEnum(..) => Target::Enum,
45             hir::ItemConst(..) => Target::Const,
46             hir::ItemForeignMod(..) => Target::ForeignMod,
47             hir::ItemStatic(..) => Target::Static,
48             _ => Target::Other,
49         }
50     }
51 }
52
53 struct CheckAttrVisitor<'a, 'tcx: 'a> {
54     tcx: TyCtxt<'a, 'tcx, 'tcx>,
55 }
56
57 impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> {
58     /// Check any attribute.
59     fn check_attributes(&self, item: &hir::Item, target: Target) {
60         if target == Target::Fn || target == Target::Const {
61             self.tcx.codegen_fn_attrs(self.tcx.hir.local_def_id(item.id));
62         } else if let Some(a) = item.attrs.iter().find(|a| a.check_name("target_feature")) {
63             self.tcx.sess.struct_span_err(a.span, "attribute should be applied to a function")
64                 .span_label(item.span, "not a function")
65                 .emit();
66         }
67
68         let mut has_wasm_import_module = false;
69         for attr in &item.attrs {
70             if attr.check_name("inline") {
71                 self.check_inline(attr, &item.span, target)
72             } else if attr.check_name("non_exhaustive") {
73                 self.check_non_exhaustive(attr, item, target)
74             } else if attr.check_name("wasm_import_module") {
75                 has_wasm_import_module = true;
76                 if attr.value_str().is_none() {
77                     self.tcx.sess.span_err(attr.span, "\
78                         must be of the form #[wasm_import_module = \"...\"]");
79                 }
80                 if target != Target::ForeignMod {
81                     self.tcx.sess.span_err(attr.span, "\
82                         must only be attached to foreign modules");
83                 }
84             } else if attr.check_name("wasm_custom_section") {
85                 if target != Target::Const {
86                     self.tcx.sess.span_err(attr.span, "only allowed on consts");
87                 }
88             }
89         }
90
91         if target == Target::ForeignMod &&
92             !has_wasm_import_module &&
93             self.tcx.sess.target.target.arch == "wasm32" &&
94             false // FIXME: eventually enable this warning when stable
95         {
96             self.tcx.sess.span_warn(item.span, "\
97                 must have a #[wasm_import_module = \"...\"] attribute, this \
98                 will become a hard error before too long");
99         }
100
101         self.check_repr(item, target);
102         self.check_used(item, target);
103     }
104
105     /// Check if an `#[inline]` is applied to a function or a closure.
106     fn check_inline(&self, attr: &hir::Attribute, span: &Span, target: Target) {
107         if target != Target::Fn && target != Target::Closure {
108             struct_span_err!(self.tcx.sess,
109                              attr.span,
110                              E0518,
111                              "attribute should be applied to function or closure")
112                 .span_label(*span, "not a function or closure")
113                 .emit();
114         }
115     }
116
117     /// Check if the `#[non_exhaustive]` attribute on an `item` is valid.
118     fn check_non_exhaustive(&self, attr: &hir::Attribute, item: &hir::Item, target: Target) {
119         match target {
120             Target::Struct | Target::Enum => { /* Valid */ },
121             _ => {
122                 struct_span_err!(self.tcx.sess,
123                                  attr.span,
124                                  E0701,
125                                  "attribute can only be applied to a struct or enum")
126                     .span_label(item.span, "not a struct or enum")
127                     .emit();
128                 return;
129             }
130         }
131
132         if attr.meta_item_list().is_some() || attr.value_str().is_some() {
133             struct_span_err!(self.tcx.sess,
134                              attr.span,
135                              E0702,
136                              "attribute should be empty")
137                 .span_label(item.span, "not empty")
138                 .emit();
139         }
140     }
141
142     /// Check if the `#[repr]` attributes on `item` are valid.
143     fn check_repr(&self, item: &hir::Item, target: Target) {
144         // Extract the names of all repr hints, e.g., [foo, bar, align] for:
145         // ```
146         // #[repr(foo)]
147         // #[repr(bar, align(8))]
148         // ```
149         let hints: Vec<_> = item.attrs
150             .iter()
151             .filter(|attr| attr.name() == "repr")
152             .filter_map(|attr| attr.meta_item_list())
153             .flat_map(|hints| hints)
154             .collect();
155
156         let mut int_reprs = 0;
157         let mut is_c = false;
158         let mut is_simd = false;
159         let mut is_transparent = false;
160
161         for hint in &hints {
162             let name = if let Some(name) = hint.name() {
163                 name
164             } else {
165                 // Invalid repr hint like repr(42). We don't check for unrecognized hints here
166                 // (libsyntax does that), so just ignore it.
167                 continue;
168             };
169
170             let (article, allowed_targets) = match &*name.as_str() {
171                 "C" => {
172                     is_c = true;
173                     if target != Target::Struct &&
174                             target != Target::Union &&
175                             target != Target::Enum {
176                                 ("a", "struct, enum or union")
177                     } else {
178                         continue
179                     }
180                 }
181                 "packed" => {
182                     if target != Target::Struct &&
183                             target != Target::Union {
184                                 ("a", "struct or union")
185                     } else {
186                         continue
187                     }
188                 }
189                 "simd" => {
190                     is_simd = true;
191                     if target != Target::Struct {
192                         ("a", "struct")
193                     } else {
194                         continue
195                     }
196                 }
197                 "align" => {
198                     if target != Target::Struct &&
199                             target != Target::Union {
200                         ("a", "struct or union")
201                     } else {
202                         continue
203                     }
204                 }
205                 "transparent" => {
206                     is_transparent = true;
207                     if target != Target::Struct {
208                         ("a", "struct")
209                     } else {
210                         continue
211                     }
212                 }
213                 "i8" | "u8" | "i16" | "u16" |
214                 "i32" | "u32" | "i64" | "u64" |
215                 "isize" | "usize" => {
216                     int_reprs += 1;
217                     if target != Target::Enum {
218                         ("an", "enum")
219                     } else {
220                         continue
221                     }
222                 }
223                 _ => continue,
224             };
225             self.emit_repr_error(
226                 hint.span,
227                 item.span,
228                 &format!("attribute should be applied to {}", allowed_targets),
229                 &format!("not {} {}", article, allowed_targets),
230             )
231         }
232
233         // Just point at all repr hints if there are any incompatibilities.
234         // This is not ideal, but tracking precisely which ones are at fault is a huge hassle.
235         let hint_spans = hints.iter().map(|hint| hint.span);
236
237         // Error on repr(transparent, <anything else>).
238         if is_transparent && hints.len() > 1 {
239             let hint_spans: Vec<_> = hint_spans.clone().collect();
240             span_err!(self.tcx.sess, hint_spans, E0692,
241                       "transparent struct cannot have other repr hints");
242         }
243         // Warn on repr(u8, u16), repr(C, simd), and c-like-enum-repr(C, u8)
244         if (int_reprs > 1)
245            || (is_simd && is_c)
246            || (int_reprs == 1 && is_c && is_c_like_enum(item)) {
247             let hint_spans: Vec<_> = hint_spans.collect();
248             span_warn!(self.tcx.sess, hint_spans, E0566,
249                        "conflicting representation hints");
250         }
251     }
252
253     fn emit_repr_error(
254         &self,
255         hint_span: Span,
256         label_span: Span,
257         hint_message: &str,
258         label_message: &str,
259     ) {
260         struct_span_err!(self.tcx.sess, hint_span, E0517, "{}", hint_message)
261             .span_label(label_span, label_message)
262             .emit();
263     }
264
265     fn check_stmt_attributes(&self, stmt: &hir::Stmt) {
266         // When checking statements ignore expressions, they will be checked later
267         if let hir::Stmt_::StmtDecl(_, _) = stmt.node {
268             for attr in stmt.node.attrs() {
269                 if attr.check_name("inline") {
270                     self.check_inline(attr, &stmt.span, Target::Statement);
271                 }
272                 if attr.check_name("repr") {
273                     self.emit_repr_error(
274                         attr.span,
275                         stmt.span,
276                         &format!("attribute should not be applied to a statement"),
277                         &format!("not a struct, enum or union"),
278                     );
279                 }
280             }
281         }
282     }
283
284     fn check_expr_attributes(&self, expr: &hir::Expr) {
285         let target = match expr.node {
286             hir::ExprClosure(..) => Target::Closure,
287             _ => Target::Expression,
288         };
289         for attr in expr.attrs.iter() {
290             if attr.check_name("inline") {
291                 self.check_inline(attr, &expr.span, target);
292             }
293             if attr.check_name("repr") {
294                 self.emit_repr_error(
295                     attr.span,
296                     expr.span,
297                     &format!("attribute should not be applied to an expression"),
298                     &format!("not defining a struct, enum or union"),
299                 );
300             }
301         }
302     }
303
304     fn check_used(&self, item: &hir::Item, target: Target) {
305         for attr in &item.attrs {
306             if attr.name() == "used" && target != Target::Static {
307                 self.tcx.sess
308                     .span_err(attr.span, "attribute must be applied to a `static` variable");
309             }
310         }
311     }
312 }
313
314 impl<'a, 'tcx> Visitor<'tcx> for CheckAttrVisitor<'a, 'tcx> {
315     fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
316         NestedVisitorMap::OnlyBodies(&self.tcx.hir)
317     }
318
319     fn visit_item(&mut self, item: &'tcx hir::Item) {
320         let target = Target::from_item(item);
321         self.check_attributes(item, target);
322         intravisit::walk_item(self, item)
323     }
324
325
326     fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt) {
327         self.check_stmt_attributes(stmt);
328         intravisit::walk_stmt(self, stmt)
329     }
330
331     fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
332         self.check_expr_attributes(expr);
333         intravisit::walk_expr(self, expr)
334     }
335 }
336
337 pub fn check_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
338     let mut checker = CheckAttrVisitor { tcx };
339     tcx.hir.krate().visit_all_item_likes(&mut checker.as_deep_visitor());
340 }
341
342 fn is_c_like_enum(item: &hir::Item) -> bool {
343     if let hir::ItemEnum(ref def, _) = item.node {
344         for variant in &def.variants {
345             match variant.node.data {
346                 hir::VariantData::Unit(_) => { /* continue */ }
347                 _ => { return false; }
348             }
349         }
350         true
351     } else {
352         false
353     }
354 }