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