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