]>
Commit | Line | Data |
---|---|---|
ea8adc8c XL |
1 | //! lint when there is a large size difference between variants on an enum |
2 | ||
3 | use rustc::lint::*; | |
4 | use rustc::hir::*; | |
abe05a73 | 5 | use utils::{snippet_opt, span_lint_and_then, type_size}; |
ea8adc8c XL |
6 | use rustc::ty::TypeFoldable; |
7 | ||
8 | /// **What it does:** Checks for large size differences between variants on | |
9 | /// `enum`s. | |
10 | /// | |
11 | /// **Why is this bad?** Enum size is bounded by the largest variant. Having a | |
12 | /// large variant | |
13 | /// can penalize the memory layout of that enum. | |
14 | /// | |
15 | /// **Known problems:** None. | |
16 | /// | |
17 | /// **Example:** | |
18 | /// ```rust | |
19 | /// enum Test { | |
20 | /// A(i32), | |
21 | /// B([i32; 8000]), | |
22 | /// } | |
23 | /// ``` | |
24 | declare_lint! { | |
25 | pub LARGE_ENUM_VARIANT, | |
26 | Warn, | |
27 | "large size difference between variants on an enum" | |
28 | } | |
29 | ||
30 | #[derive(Copy, Clone)] | |
31 | pub struct LargeEnumVariant { | |
32 | maximum_size_difference_allowed: u64, | |
33 | } | |
34 | ||
35 | impl LargeEnumVariant { | |
36 | pub fn new(maximum_size_difference_allowed: u64) -> Self { | |
abe05a73 XL |
37 | Self { |
38 | maximum_size_difference_allowed: maximum_size_difference_allowed, | |
39 | } | |
ea8adc8c XL |
40 | } |
41 | } | |
42 | ||
43 | impl LintPass for LargeEnumVariant { | |
44 | fn get_lints(&self) -> LintArray { | |
45 | lint_array!(LARGE_ENUM_VARIANT) | |
46 | } | |
47 | } | |
48 | ||
49 | impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LargeEnumVariant { | |
50 | fn check_item(&mut self, cx: &LateContext, item: &Item) { | |
51 | let did = cx.tcx.hir.local_def_id(item.id); | |
52 | if let ItemEnum(ref def, _) = item.node { | |
53 | let ty = cx.tcx.type_of(did); | |
abe05a73 XL |
54 | let adt = ty.ty_adt_def() |
55 | .expect("already checked whether this is an enum"); | |
ea8adc8c XL |
56 | |
57 | let mut smallest_variant: Option<(_, _)> = None; | |
58 | let mut largest_variant: Option<(_, _)> = None; | |
59 | ||
60 | for (i, variant) in adt.variants.iter().enumerate() { | |
61 | let size: u64 = variant | |
62 | .fields | |
63 | .iter() | |
64 | .map(|f| { | |
65 | let ty = cx.tcx.type_of(f.did); | |
66 | if ty.needs_subst() { | |
67 | 0 // we can't reason about generics, so we treat them as zero sized | |
68 | } else { | |
69 | type_size(cx, ty).expect("size should be computable for concrete type") | |
70 | } | |
71 | }) | |
72 | .sum(); | |
73 | ||
74 | let grouped = (size, (i, variant)); | |
75 | ||
76 | update_if(&mut smallest_variant, grouped, |a, b| b.0 <= a.0); | |
77 | update_if(&mut largest_variant, grouped, |a, b| b.0 >= a.0); | |
78 | } | |
79 | ||
80 | if let (Some(smallest), Some(largest)) = (smallest_variant, largest_variant) { | |
81 | let difference = largest.0 - smallest.0; | |
82 | ||
83 | if difference > self.maximum_size_difference_allowed { | |
84 | let (i, variant) = largest.1; | |
85 | ||
86 | span_lint_and_then( | |
87 | cx, | |
88 | LARGE_ENUM_VARIANT, | |
89 | def.variants[i].span, | |
90 | "large size difference between variants", | |
91 | |db| { | |
92 | if variant.fields.len() == 1 { | |
93 | let span = match def.variants[i].node.data { | |
abe05a73 XL |
94 | VariantData::Struct(ref fields, _) | VariantData::Tuple(ref fields, _) => { |
95 | fields[0].ty.span | |
96 | }, | |
ea8adc8c XL |
97 | VariantData::Unit(_) => unreachable!(), |
98 | }; | |
99 | if let Some(snip) = snippet_opt(cx, span) { | |
100 | db.span_suggestion( | |
101 | span, | |
102 | "consider boxing the large fields to reduce the total size of the \ | |
abe05a73 | 103 | enum", |
ea8adc8c XL |
104 | format!("Box<{}>", snip), |
105 | ); | |
106 | return; | |
107 | } | |
108 | } | |
109 | db.span_help( | |
110 | def.variants[i].span, | |
111 | "consider boxing the large fields to reduce the total size of the enum", | |
112 | ); | |
113 | }, | |
114 | ); | |
115 | } | |
116 | } | |
ea8adc8c XL |
117 | } |
118 | } | |
119 | } | |
120 | ||
121 | fn update_if<T, F>(old: &mut Option<T>, new: T, f: F) | |
122 | where | |
123 | F: Fn(&T, &T) -> bool, | |
124 | { | |
125 | if let Some(ref mut val) = *old { | |
126 | if f(val, &new) { | |
127 | *val = new; | |
128 | } | |
129 | } else { | |
130 | *old = Some(new); | |
131 | } | |
132 | } |