Skip to content

Commit 4cf08ab

Browse files
committed
large_enum_variants lint suggests to box variants above a configurable limit
1 parent f2f6637 commit 4cf08ab

File tree

6 files changed

+127
-1
lines changed

6 files changed

+127
-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: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//! lint when there are large variants on an enum
2+
3+
use rustc::lint::*;
4+
use rustc::hir::*;
5+
use utils::span_help_and_lint;
6+
use rustc::ty::layout::TargetDataLayout;
7+
use rustc::ty::TypeFoldable;
8+
9+
/// **What it does:** Checks for large variants on enums.
10+
///
11+
/// **Why is this bad?** Enum size is bounded by the largest variant. Having a large variant
12+
/// can penalize the memory layout of that enum.
13+
///
14+
/// **Known problems:** None.
15+
///
16+
/// **Example:**
17+
/// ```rust
18+
/// enum Test {
19+
/// A(i32),
20+
/// B([i32; 8000]),
21+
/// }
22+
/// ```
23+
declare_lint! {
24+
pub LARGE_ENUM_VARIANT,
25+
Warn,
26+
"large variants on an enum"
27+
}
28+
29+
#[derive(Copy,Clone)]
30+
pub struct LargeEnumVariant {
31+
maximum_variant_size_allowed: u64,
32+
}
33+
34+
impl LargeEnumVariant {
35+
pub fn new(maximum_variant_size_allowed: u64) -> Self {
36+
LargeEnumVariant { maximum_variant_size_allowed: maximum_variant_size_allowed }
37+
}
38+
}
39+
40+
impl LintPass for LargeEnumVariant {
41+
fn get_lints(&self) -> LintArray {
42+
lint_array!(LARGE_ENUM_VARIANT)
43+
}
44+
}
45+
46+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LargeEnumVariant {
47+
fn check_item(&mut self, cx: &LateContext, item: &Item) {
48+
let did = cx.tcx.map.local_def_id(item.id);
49+
if let ItemEnum(ref def, _) = item.node {
50+
let ty = cx.tcx.item_type(did);
51+
let adt = ty.ty_adt_def().expect("already checked whether this is an enum");
52+
for (i, variant) in adt.variants.iter().enumerate() {
53+
let data_layout = TargetDataLayout::parse(cx.sess());
54+
let param_env = cx.tcx.empty_parameter_environment();
55+
let infcx = cx.tcx.borrowck_fake_infer_ctxt(param_env);
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_help_and_lint(cx,
72+
LARGE_ENUM_VARIANT,
73+
def.variants[i].span,
74+
&format!("large enum variant found on variant `{}`", variant.name),
75+
"consider boxing the large branches to reduce the total size of the enum");
76+
}
77+
}
78+
}
79+
}
80+
}

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ pub mod identity_op;
8787
pub mod if_let_redundant_pattern_matching;
8888
pub mod if_not_else;
8989
pub mod items_after_statements;
90+
pub mod large_enum_variant;
9091
pub mod len_zero;
9192
pub mod let_if_seq;
9293
pub mod lifetimes;
@@ -292,6 +293,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
292293
reg.register_early_lint_pass(box reference::Pass);
293294
reg.register_early_lint_pass(box double_parens::DoubleParens);
294295
reg.register_late_lint_pass(box unused_io_amount::UnusedIoAmount);
296+
reg.register_late_lint_pass(box large_enum_variant::LargeEnumVariant::new(conf.enum_variant_size_threshold));
295297

296298
reg.register_lint_group("clippy_restrictions", vec![
297299
arithmetic::FLOAT_ARITHMETIC,
@@ -383,6 +385,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
383385
functions::TOO_MANY_ARGUMENTS,
384386
identity_op::IDENTITY_OP,
385387
if_let_redundant_pattern_matching::IF_LET_REDUNDANT_PATTERN_MATCHING,
388+
large_enum_variant::LARGE_ENUM_VARIANT,
386389
len_zero::LEN_WITHOUT_IS_EMPTY,
387390
len_zero::LEN_ZERO,
388391
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: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 on variant `B`
11+
}
12+
13+
enum GenericEnum<T> {
14+
A(i32),
15+
B([i32; 8000]), //~ ERROR large enum variant found on variant `B`
16+
C([T; 8000]),
17+
D(T, [i32; 8000]), //~ ERROR large enum variant found on variant `D`
18+
}
19+
20+
trait SomeTrait {
21+
type Item;
22+
}
23+
24+
enum LargeEnumGeneric<A: SomeTrait> {
25+
Var(A::Item), // regression test, this used to ICE
26+
}
27+
28+
enum AnotherLargeEnum {
29+
VariantOk(i32, u32),
30+
ContainingLargeEnum(LargeEnum), //~ ERROR large enum variant found on variant `ContainingLargeEnum`
31+
ContainingMoreThanOneField(i32, [i32; 8000], [i32; 9500]), //~ ERROR large enum variant found on variant `ContainingMoreThanOneField`
32+
VoidVariant,
33+
StructLikeLittle { x: i32, y: i32 },
34+
StructLikeLarge { x: [i32; 8000], y: i32 }, //~ ERROR large enum variant found on variant `StructLikeLarge`
35+
}
36+
37+
fn main() {
38+
39+
}

0 commit comments

Comments
 (0)