]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/large_enum_variant.rs
Auto merge of #8414 - MiSawa:fix/optimize-redundant-clone, r=oli-obk
[rust.git] / clippy_lints / src / large_enum_variant.rs
1 //! lint when there is a large size difference between variants on an enum
2
3 use clippy_utils::diagnostics::span_lint_and_then;
4 use clippy_utils::source::snippet_with_applicability;
5 use rustc_errors::Applicability;
6 use rustc_hir::{Item, ItemKind};
7 use rustc_lint::{LateContext, LateLintPass};
8 use rustc_middle::lint::in_external_macro;
9 use rustc_middle::ty::layout::LayoutOf;
10 use rustc_session::{declare_tool_lint, impl_lint_pass};
11 use rustc_span::source_map::Span;
12
13 declare_clippy_lint! {
14     /// ### What it does
15     /// Checks for large size differences between variants on
16     /// `enum`s.
17     ///
18     /// ### Why is this bad?
19     /// Enum size is bounded by the largest variant. Having a
20     /// large variant can penalize the memory layout of that enum.
21     ///
22     /// ### Known problems
23     /// This lint obviously cannot take the distribution of
24     /// variants in your running program into account. It is possible that the
25     /// smaller variants make up less than 1% of all instances, in which case
26     /// the overhead is negligible and the boxing is counter-productive. Always
27     /// measure the change this lint suggests.
28     ///
29     /// ### Example
30     /// ```rust
31     /// // Bad
32     /// enum Test {
33     ///     A(i32),
34     ///     B([i32; 8000]),
35     /// }
36     ///
37     /// // Possibly better
38     /// enum Test2 {
39     ///     A(i32),
40     ///     B(Box<[i32; 8000]>),
41     /// }
42     /// ```
43     #[clippy::version = "pre 1.29.0"]
44     pub LARGE_ENUM_VARIANT,
45     perf,
46     "large size difference between variants on an enum"
47 }
48
49 #[derive(Copy, Clone)]
50 pub struct LargeEnumVariant {
51     maximum_size_difference_allowed: u64,
52 }
53
54 impl LargeEnumVariant {
55     #[must_use]
56     pub fn new(maximum_size_difference_allowed: u64) -> Self {
57         Self {
58             maximum_size_difference_allowed,
59         }
60     }
61 }
62
63 struct FieldInfo {
64     ind: usize,
65     size: u64,
66 }
67
68 struct VariantInfo {
69     ind: usize,
70     size: u64,
71     fields_size: Vec<FieldInfo>,
72 }
73
74 impl_lint_pass!(LargeEnumVariant => [LARGE_ENUM_VARIANT]);
75
76 impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
77     fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
78         if in_external_macro(cx.tcx.sess, item.span) {
79             return;
80         }
81         if let ItemKind::Enum(ref def, _) = item.kind {
82             let ty = cx.tcx.type_of(item.def_id);
83             let adt = ty.ty_adt_def().expect("already checked whether this is an enum");
84             if adt.variants.len() <= 1 {
85                 return;
86             }
87             let mut variants_size: Vec<VariantInfo> = Vec::new();
88             for (i, variant) in adt.variants.iter().enumerate() {
89                 let mut fields_size = Vec::new();
90                 for (i, f) in variant.fields.iter().enumerate() {
91                     let ty = cx.tcx.type_of(f.did);
92                     // don't lint variants which have a field of generic type.
93                     match cx.layout_of(ty) {
94                         Ok(l) => {
95                             let fsize = l.size.bytes();
96                             fields_size.push(FieldInfo { ind: i, size: fsize });
97                         },
98                         Err(_) => {
99                             return;
100                         },
101                     }
102                 }
103                 let size: u64 = fields_size.iter().map(|info| info.size).sum();
104
105                 variants_size.push(VariantInfo {
106                     ind: i,
107                     size,
108                     fields_size,
109                 });
110             }
111
112             variants_size.sort_by(|a, b| (b.size.cmp(&a.size)));
113
114             let mut difference = variants_size[0].size - variants_size[1].size;
115             if difference > self.maximum_size_difference_allowed {
116                 let help_text = "consider boxing the large fields to reduce the total size of the enum";
117                 span_lint_and_then(
118                     cx,
119                     LARGE_ENUM_VARIANT,
120                     def.variants[variants_size[0].ind].span,
121                     "large size difference between variants",
122                     |diag| {
123                         diag.span_label(
124                             def.variants[variants_size[0].ind].span,
125                             &format!("this variant is {} bytes", variants_size[0].size),
126                         );
127                         diag.span_note(
128                             def.variants[variants_size[1].ind].span,
129                             &format!("and the second-largest variant is {} bytes:", variants_size[1].size),
130                         );
131
132                         let fields = def.variants[variants_size[0].ind].data.fields();
133                         variants_size[0].fields_size.sort_by(|a, b| (a.size.cmp(&b.size)));
134                         let mut applicability = Applicability::MaybeIncorrect;
135                         let sugg: Vec<(Span, String)> = variants_size[0]
136                             .fields_size
137                             .iter()
138                             .rev()
139                             .map_while(|val| {
140                                 if difference > self.maximum_size_difference_allowed {
141                                     difference = difference.saturating_sub(val.size);
142                                     Some((
143                                         fields[val.ind].ty.span,
144                                         format!(
145                                             "Box<{}>",
146                                             snippet_with_applicability(
147                                                 cx,
148                                                 fields[val.ind].ty.span,
149                                                 "..",
150                                                 &mut applicability
151                                             )
152                                             .into_owned()
153                                         ),
154                                     ))
155                                 } else {
156                                     None
157                                 }
158                             })
159                             .collect();
160
161                         if !sugg.is_empty() {
162                             diag.multipart_suggestion(help_text, sugg, Applicability::MaybeIncorrect);
163                             return;
164                         }
165
166                         diag.span_help(def.variants[variants_size[0].ind].span, help_text);
167                     },
168                 );
169             }
170         }
171     }
172 }