]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | //! lint when there is a large size difference between variants on an enum |
2 | ||
c295e0f8 | 3 | use clippy_utils::source::snippet_with_applicability; |
487cf647 FG |
4 | use clippy_utils::{ |
5 | diagnostics::span_lint_and_then, | |
6 | ty::{approx_ty_size, is_copy, AdtVariantInfo}, | |
7 | }; | |
f20569fa | 8 | use rustc_errors::Applicability; |
c295e0f8 | 9 | use rustc_hir::{Item, ItemKind}; |
f20569fa XL |
10 | use rustc_lint::{LateContext, LateLintPass}; |
11 | use rustc_middle::lint::in_external_macro; | |
487cf647 | 12 | use rustc_middle::ty::{Adt, Ty}; |
f20569fa | 13 | use rustc_session::{declare_tool_lint, impl_lint_pass}; |
c295e0f8 | 14 | use rustc_span::source_map::Span; |
f20569fa XL |
15 | |
16 | declare_clippy_lint! { | |
94222f64 XL |
17 | /// ### What it does |
18 | /// Checks for large size differences between variants on | |
f20569fa XL |
19 | /// `enum`s. |
20 | /// | |
94222f64 | 21 | /// ### Why is this bad? |
f2b60f7d | 22 | /// Enum size is bounded by the largest variant. Having one |
f20569fa XL |
23 | /// large variant can penalize the memory layout of that enum. |
24 | /// | |
94222f64 XL |
25 | /// ### Known problems |
26 | /// This lint obviously cannot take the distribution of | |
f20569fa XL |
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 | /// | |
923072b8 FG |
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 | |
064997fb | 35 | /// use case it may be possible to store the large data in an auxiliary |
923072b8 FG |
36 | /// structure (e.g. Arena or ECS). |
37 | /// | |
f2b60f7d FG |
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. | |
923072b8 | 41 | /// |
94222f64 | 42 | /// ### Example |
f20569fa | 43 | /// ```rust |
f20569fa XL |
44 | /// enum Test { |
45 | /// A(i32), | |
46 | /// B([i32; 8000]), | |
47 | /// } | |
923072b8 | 48 | /// ``` |
f20569fa | 49 | /// |
923072b8 FG |
50 | /// Use instead: |
51 | /// ```rust | |
f20569fa XL |
52 | /// // Possibly better |
53 | /// enum Test2 { | |
54 | /// A(i32), | |
55 | /// B(Box<[i32; 8000]>), | |
56 | /// } | |
57 | /// ``` | |
a2a8927a | 58 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
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 { | |
923072b8 | 81 | fn check_item(&mut self, cx: &LateContext<'tcx>, item: &Item<'tcx>) { |
f20569fa XL |
82 | if in_external_macro(cx.tcx.sess, item.span) { |
83 | return; | |
84 | } | |
85 | if let ItemKind::Enum(ref def, _) = item.kind { | |
2b03887a FG |
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") | |
f2b60f7d | 89 | }; |
5e7ed085 | 90 | if adt.variants().len() <= 1 { |
c295e0f8 | 91 | return; |
f20569fa | 92 | } |
487cf647 | 93 | let variants_size = AdtVariantInfo::new(cx, *adt, subst); |
f20569fa | 94 | |
c295e0f8 XL |
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, | |
f2b60f7d | 101 | item.span, |
c295e0f8 XL |
102 | "large size difference between variants", |
103 | |diag| { | |
f2b60f7d FG |
104 | diag.span_label( |
105 | item.span, | |
106 | format!("the entire enum is at least {} bytes", approx_ty_size(cx, ty)), | |
107 | ); | |
c295e0f8 XL |
108 | diag.span_label( |
109 | def.variants[variants_size[0].ind].span, | |
f2b60f7d | 110 | format!("the largest variant contains at least {} bytes", variants_size[0].size), |
c295e0f8 | 111 | ); |
f2b60f7d | 112 | diag.span_label( |
c295e0f8 | 113 | def.variants[variants_size[1].ind].span, |
f2b60f7d FG |
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 | }, | |
c295e0f8 | 122 | ); |
f20569fa | 123 | |
c295e0f8 | 124 | let fields = def.variants[variants_size[0].ind].data.fields(); |
c295e0f8 | 125 | let mut applicability = Applicability::MaybeIncorrect; |
923072b8 FG |
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() | |
487cf647 | 136 | .map_while(|&(ind, size)| { |
923072b8 | 137 | if difference > self.maximum_size_difference_allowed { |
487cf647 | 138 | difference = difference.saturating_sub(size); |
923072b8 | 139 | Some(( |
487cf647 | 140 | fields[ind].ty.span, |
923072b8 FG |
141 | format!( |
142 | "Box<{}>", | |
143 | snippet_with_applicability( | |
144 | cx, | |
487cf647 | 145 | fields[ind].ty.span, |
923072b8 FG |
146 | "..", |
147 | &mut applicability | |
148 | ) | |
149 | .into_owned() | |
150 | ), | |
151 | )) | |
152 | } else { | |
153 | None | |
154 | } | |
155 | }) | |
156 | .collect(); | |
c295e0f8 | 157 | |
923072b8 FG |
158 | if !sugg.is_empty() { |
159 | diag.multipart_suggestion(help_text, sugg, Applicability::MaybeIncorrect); | |
160 | return; | |
161 | } | |
c295e0f8 | 162 | } |
c295e0f8 XL |
163 | diag.span_help(def.variants[variants_size[0].ind].span, help_text); |
164 | }, | |
165 | ); | |
f20569fa XL |
166 | } |
167 | } | |
168 | } | |
169 | } | |
923072b8 FG |
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 | } |