Skip to content

Commit b1be0d6

Browse files
authored
Merge pull request #1492 from Manishearth/largeEnumVariant
large_enum_variants lint suggests to box variants above a configurable limit
2 parents 909ef37 + 12eeffd commit b1be0d6

File tree

6 files changed

+159
-1
lines changed

6 files changed

+159
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ All notable changes to this project will be documented in this file.
337337
[`iter_next_loop`]: https://github.com/Manishearth/rust-clippy/wiki#iter_next_loop
338338
[`iter_nth`]: https://github.com/Manishearth/rust-clippy/wiki#iter_nth
339339
[`iter_skip_next`]: https://github.com/Manishearth/rust-clippy/wiki#iter_skip_next
340+
[`large_enum_variant`]: https://github.com/Manishearth/rust-clippy/wiki#large_enum_variant
340341
[`len_without_is_empty`]: https://github.com/Manishearth/rust-clippy/wiki#len_without_is_empty
341342
[`len_zero`]: https://github.com/Manishearth/rust-clippy/wiki#len_zero
342343
[`let_and_return`]: https://github.com/Manishearth/rust-clippy/wiki#let_and_return

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ transparently:
180180

181181
## Lints
182182

183-
There are 183 lints included in this crate:
183+
There are 184 lints included in this crate:
184184

185185
name | default | triggers on
186186
-----------------------------------------------------------------------------------------------------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------
@@ -255,6 +255,7 @@ name
255255
[iter_next_loop](https://github.com/Manishearth/rust-clippy/wiki#iter_next_loop) | warn | for-looping over `_.next()` which is probably not intended
256256
[iter_nth](https://github.com/Manishearth/rust-clippy/wiki#iter_nth) | warn | using `.iter().nth()` on a standard library type with O(1) element access
257257
[iter_skip_next](https://github.com/Manishearth/rust-clippy/wiki#iter_skip_next) | warn | using `.skip(x).next()` on an iterator
258+
[large_enum_variant](https://github.com/Manishearth/rust-clippy/wiki#large_enum_variant) | warn | large variants on an enum
258259
[len_without_is_empty](https://github.com/Manishearth/rust-clippy/wiki#len_without_is_empty) | warn | traits or impls with a public `len` method but no corresponding `is_empty` method
259260
[len_zero](https://github.com/Manishearth/rust-clippy/wiki#len_zero) | warn | checking `.len() == 0` or `.len() > 0` (or similar) when `.is_empty()` could be used instead
260261
[let_and_return](https://github.com/Manishearth/rust-clippy/wiki#let_and_return) | warn | creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//! lint when there are large variants on an enum
2+
3+
use rustc::lint::*;
4+
use rustc::hir::*;
5+
use utils::{span_lint_and_then, snippet_opt};
6+
use rustc::ty::layout::TargetDataLayout;
7+
use rustc::ty::TypeFoldable;
8+
use rustc::traits::Reveal;
9+
10+
/// **What it does:** Checks for large variants on `enum`s.
11+
///
12+
/// **Why is this bad?** Enum size is bounded by the largest variant. Having a 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 variants on an enum"
28+
}
29+
30+
#[derive(Copy,Clone)]
31+
pub struct LargeEnumVariant {
32+
maximum_variant_size_allowed: u64,
33+
}
34+
35+
impl LargeEnumVariant {
36+
pub fn new(maximum_variant_size_allowed: u64) -> Self {
37+
LargeEnumVariant { maximum_variant_size_allowed: maximum_variant_size_allowed }
38+
}
39+
}
40+
41+
impl LintPass for LargeEnumVariant {
42+
fn get_lints(&self) -> LintArray {
43+
lint_array!(LARGE_ENUM_VARIANT)
44+
}
45+
}
46+
47+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LargeEnumVariant {
48+
fn check_item(&mut self, cx: &LateContext, item: &Item) {
49+
let did = cx.tcx.map.local_def_id(item.id);
50+
if let ItemEnum(ref def, _) = item.node {
51+
let ty = cx.tcx.item_type(did);
52+
let adt = ty.ty_adt_def().expect("already checked whether this is an enum");
53+
for (i, variant) in adt.variants.iter().enumerate() {
54+
let data_layout = TargetDataLayout::parse(cx.sess());
55+
cx.tcx.infer_ctxt((), Reveal::All).enter(|infcx| {
56+
let size: u64 = variant.fields
57+
.iter()
58+
.map(|f| {
59+
let ty = cx.tcx.item_type(f.did);
60+
if ty.needs_subst() {
61+
0 // we can't reason about generics, so we treat them as zero sized
62+
} else {
63+
ty.layout(&infcx)
64+
.expect("layout should be computable for concrete type")
65+
.size(&data_layout)
66+
.bytes()
67+
}
68+
})
69+
.sum();
70+
if size > self.maximum_variant_size_allowed {
71+
span_lint_and_then(cx,
72+
LARGE_ENUM_VARIANT,
73+
def.variants[i].span,
74+
"large enum variant found",
75+
|db| {
76+
if variant.fields.len() == 1 {
77+
let span = match def.variants[i].node.data {
78+
VariantData::Struct(ref fields, _) |
79+
VariantData::Tuple(ref fields, _) => fields[0].ty.span,
80+
VariantData::Unit(_) => unreachable!(),
81+
};
82+
if let Some(snip) = snippet_opt(cx, span) {
83+
db.span_suggestion(span,
84+
"consider boxing the large fields to reduce the total size of \
85+
the enum",
86+
format!("Box<{}>", snip));
87+
return;
88+
}
89+
}
90+
db.span_help(def.variants[i].span,
91+
"consider boxing the large fields to reduce the total size of the enum");
92+
});
93+
}
94+
});
95+
}
96+
}
97+
}
98+
}

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ pub mod identity_op;
8888
pub mod if_let_redundant_pattern_matching;
8989
pub mod if_not_else;
9090
pub mod items_after_statements;
91+
pub mod large_enum_variant;
9192
pub mod len_zero;
9293
pub mod let_if_seq;
9394
pub mod lifetimes;
@@ -293,6 +294,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
293294
reg.register_early_lint_pass(box reference::Pass);
294295
reg.register_early_lint_pass(box double_parens::DoubleParens);
295296
reg.register_late_lint_pass(box unused_io_amount::UnusedIoAmount);
297+
reg.register_late_lint_pass(box large_enum_variant::LargeEnumVariant::new(conf.enum_variant_size_threshold));
296298

297299
reg.register_lint_group("clippy_restrictions", vec![
298300
arithmetic::FLOAT_ARITHMETIC,
@@ -384,6 +386,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
384386
functions::TOO_MANY_ARGUMENTS,
385387
identity_op::IDENTITY_OP,
386388
if_let_redundant_pattern_matching::IF_LET_REDUNDANT_PATTERN_MATCHING,
389+
large_enum_variant::LARGE_ENUM_VARIANT,
387390
len_zero::LEN_WITHOUT_IS_EMPTY,
388391
len_zero::LEN_ZERO,
389392
let_if_seq::USELESS_LET_IF_SEQ,

clippy_lints/src/utils/conf.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ define_Conf! {
186186
("too-large-for-stack", too_large_for_stack, 200 => u64),
187187
/// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
188188
("enum-variant-name-threshold", enum_variant_name_threshold, 3 => u64),
189+
/// Lint: LARGE_ENUM_VARIANT. The maximum size of a emum's variant to avoid box suggestion
190+
("enum-variant-size-threshold", enum_variant_size_threshold, 200 => u64),
189191
}
190192

191193
/// Search for the configuration file.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#![feature(plugin)]
2+
#![plugin(clippy)]
3+
4+
#![allow(dead_code)]
5+
#![allow(unused_variables)]
6+
#![deny(large_enum_variant)]
7+
8+
enum LargeEnum {
9+
A(i32),
10+
B([i32; 8000]), //~ ERROR large enum variant found
11+
//~^ HELP consider boxing the large fields to reduce the total size of the enum
12+
//~| SUGGESTION Box<[i32; 8000]>
13+
}
14+
15+
enum GenericEnum<T> {
16+
A(i32),
17+
B([i32; 8000]), //~ ERROR large enum variant found
18+
//~^ HELP consider boxing the large fields to reduce the total size of the enum
19+
//~| SUGGESTION Box<[i32; 8000]>
20+
C([T; 8000]),
21+
D(T, [i32; 8000]), //~ ERROR large enum variant found
22+
//~^ HELP consider boxing the large fields to reduce the total size of the enum
23+
}
24+
25+
trait SomeTrait {
26+
type Item;
27+
}
28+
29+
enum LargeEnumGeneric<A: SomeTrait> {
30+
Var(A::Item), // regression test, this used to ICE
31+
}
32+
33+
enum AnotherLargeEnum {
34+
VariantOk(i32, u32),
35+
ContainingLargeEnum(LargeEnum), //~ ERROR large enum variant found
36+
//~^ HELP consider boxing the large fields to reduce the total size of the enum
37+
//~| SUGGESTION Box<LargeEnum>
38+
ContainingMoreThanOneField(i32, [i32; 8000], [i32; 9500]), //~ ERROR large enum variant found
39+
//~^ HELP consider boxing the large fields to reduce the total size of the enum
40+
VoidVariant,
41+
StructLikeLittle { x: i32, y: i32 },
42+
StructLikeLarge { x: [i32; 8000], y: i32 }, //~ ERROR large enum variant found
43+
//~^ HELP consider boxing the large fields to reduce the total size of the enum
44+
StructLikeLarge2 { //~ ERROR large enum variant found
45+
x:
46+
[i32; 8000] //~ SUGGESTION Box<[i32; 8000]>
47+
//~^ HELP consider boxing the large fields to reduce the total size of the enum
48+
},
49+
}
50+
51+
fn main() {
52+
53+
}

0 commit comments

Comments
 (0)