]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/large_enum_variant.rs
Merge remote-tracking branch 'upstream/master' into rustup
[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::source::snippet_with_applicability;
4 use clippy_utils::{
5     diagnostics::span_lint_and_then,
6     ty::{approx_ty_size, is_copy, AdtVariantInfo},
7 };
8 use rustc_errors::Applicability;
9 use rustc_hir::{Item, ItemKind};
10 use rustc_lint::{LateContext, LateLintPass};
11 use rustc_middle::lint::in_external_macro;
12 use rustc_middle::ty::{Adt, Ty};
13 use rustc_session::{declare_tool_lint, impl_lint_pass};
14 use rustc_span::source_map::Span;
15
16 declare_clippy_lint! {
17     /// ### What it does
18     /// Checks for large size differences between variants on
19     /// `enum`s.
20     ///
21     /// ### Why is this bad?
22     /// Enum size is bounded by the largest variant. Having one
23     /// large variant can penalize the memory layout of that enum.
24     ///
25     /// ### Known problems
26     /// This lint obviously cannot take the distribution of
27     /// variants in your running program into account. It is possible that the
28     /// smaller variants make up less than 1% of all instances, in which case
29     /// the overhead is negligible and the boxing is counter-productive. Always
30     /// measure the change this lint suggests.
31     ///
32     /// For types that implement `Copy`, the suggestion to `Box` a variant's
33     /// data would require removing the trait impl. The types can of course
34     /// still be `Clone`, but that is worse ergonomically. Depending on the
35     /// use case it may be possible to store the large data in an auxiliary
36     /// structure (e.g. Arena or ECS).
37     ///
38     /// The lint will ignore the impact of generic types to the type layout by
39     /// assuming every type parameter is zero-sized. Depending on your use case,
40     /// this may lead to a false positive.
41     ///
42     /// ### Example
43     /// ```rust
44     /// enum Test {
45     ///     A(i32),
46     ///     B([i32; 8000]),
47     /// }
48     /// ```
49     ///
50     /// Use instead:
51     /// ```rust
52     /// // Possibly better
53     /// enum Test2 {
54     ///     A(i32),
55     ///     B(Box<[i32; 8000]>),
56     /// }
57     /// ```
58     #[clippy::version = "pre 1.29.0"]
59     pub LARGE_ENUM_VARIANT,
60     perf,
61     "large size difference between variants on an enum"
62 }
63
64 #[derive(Copy, Clone)]
65 pub struct LargeEnumVariant {
66     maximum_size_difference_allowed: u64,
67 }
68
69 impl LargeEnumVariant {
70     #[must_use]
71     pub fn new(maximum_size_difference_allowed: u64) -> Self {
72         Self {
73             maximum_size_difference_allowed,
74         }
75     }
76 }
77
78 impl_lint_pass!(LargeEnumVariant => [LARGE_ENUM_VARIANT]);
79
80 impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
81     fn check_item(&mut self, cx: &LateContext<'tcx>, item: &Item<'tcx>) {
82         if in_external_macro(cx.tcx.sess, item.span) {
83             return;
84         }
85         if let ItemKind::Enum(ref def, _) = item.kind {
86             let ty = cx.tcx.type_of(item.owner_id);
87             let Adt(adt, subst) = ty.kind() else {
88                 panic!("already checked whether this is an enum")
89             };
90             if adt.variants().len() <= 1 {
91                 return;
92             }
93             let variants_size = AdtVariantInfo::new(cx, *adt, subst);
94
95             let mut difference = variants_size[0].size - variants_size[1].size;
96             if difference > self.maximum_size_difference_allowed {
97                 let help_text = "consider boxing the large fields to reduce the total size of the enum";
98                 span_lint_and_then(
99                     cx,
100                     LARGE_ENUM_VARIANT,
101                     item.span,
102                     "large size difference between variants",
103                     |diag| {
104                         diag.span_label(
105                             item.span,
106                             format!("the entire enum is at least {} bytes", approx_ty_size(cx, ty)),
107                         );
108                         diag.span_label(
109                             def.variants[variants_size[0].ind].span,
110                             format!("the largest variant contains at least {} bytes", variants_size[0].size),
111                         );
112                         diag.span_label(
113                             def.variants[variants_size[1].ind].span,
114                             &if variants_size[1].fields_size.is_empty() {
115                                 "the second-largest variant carries no data at all".to_owned()
116                             } else {
117                                 format!(
118                                     "the second-largest variant contains at least {} bytes",
119                                     variants_size[1].size
120                                 )
121                             },
122                         );
123
124                         let fields = def.variants[variants_size[0].ind].data.fields();
125                         let mut applicability = Applicability::MaybeIncorrect;
126                         if is_copy(cx, ty) || maybe_copy(cx, ty) {
127                             diag.span_note(
128                                 item.ident.span,
129                                 "boxing a variant would require the type no longer be `Copy`",
130                             );
131                         } else {
132                             let sugg: Vec<(Span, String)> = variants_size[0]
133                                 .fields_size
134                                 .iter()
135                                 .rev()
136                                 .map_while(|&(ind, size)| {
137                                     if difference > self.maximum_size_difference_allowed {
138                                         difference = difference.saturating_sub(size);
139                                         Some((
140                                             fields[ind].ty.span,
141                                             format!(
142                                                 "Box<{}>",
143                                                 snippet_with_applicability(
144                                                     cx,
145                                                     fields[ind].ty.span,
146                                                     "..",
147                                                     &mut applicability
148                                                 )
149                                                 .into_owned()
150                                             ),
151                                         ))
152                                     } else {
153                                         None
154                                     }
155                                 })
156                                 .collect();
157
158                             if !sugg.is_empty() {
159                                 diag.multipart_suggestion(help_text, sugg, Applicability::MaybeIncorrect);
160                                 return;
161                             }
162                         }
163                         diag.span_help(def.variants[variants_size[0].ind].span, help_text);
164                     },
165                 );
166             }
167         }
168     }
169 }
170
171 fn maybe_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
172     if let Adt(_def, substs) = ty.kind()
173         && substs.types().next().is_some()
174         && let Some(copy_trait) = cx.tcx.lang_items().copy_trait()
175     {
176         return cx.tcx.non_blanket_impls_for_ty(copy_trait, ty).next().is_some();
177     }
178     false
179 }