Skip to main content

wowlab_tidy/languages/rust/rules/complexity/
large_enum_variant.rs

1use ra_ap_syntax::ast::{self, HasName};
2
3use super::support;
4use crate::{AstCtx, Example, Violation};
5
6const MIN_VARIANT_COUNT: usize = 2;
7
8#[rustfmt::skip]
9const EXAMPLES: &[Example] = &[
10    Example {
11        label: "enum with large variant",
12        code: "enum Msg { Small(u8), Large([u8; 1024]) }",
13        pass: false,
14    },
15    Example {
16        label: "enum with similar-sized variants",
17        code: "enum Msg { A(u64), B(u64) }",
18        pass: true,
19    },
20    Example {
21        label: "large variant boxed is fine",
22        code: "enum Msg { Small(u8), Large(Box<[u8; 1024]>) }",
23        pass: true,
24    },
25    Example {
26        label: "large enum in test",
27        code: "#[cfg(test)]\nmod tests {\n    enum Msg { Small(u8), Large([u8; 1024]) }\n}",
28        pass: true,
29    },
30    Example {
31        label: "single variant enum",
32        code: "enum Single { Only([u8; 1024]) }",
33        pass: true,
34    },
35    Example {
36        label: "named-field large variant",
37        code: "enum E { A { x: u8 }, B { data: [u64; 64] } }",
38        pass: false,
39    },
40    Example {
41        label: "unit variant vs large variant",
42        code: "enum E { Empty, Big([u8; 512]) }",
43        pass: false,
44    },
45];
46
47crate::ast_rule!(
48    large_enum_variant,
49    "Flag enum variants that are much larger than others (should Box the large variant).",
50    "All enum variants share the size of the largest. One huge variant wastes memory for every instance of the enum.",
51    Medium,
52    params {
53        threshold: i64 = 256
54    },
55);
56
57fn check_large_enum_variant(ctx: &AstCtx<'_>) -> Vec<Violation> {
58    let min_size_diff = ctx
59        .file
60        .config
61        .get_u64("rust_large_enum_variant", &PARAMS[0]);
62    let mut violations = Vec::new();
63
64    for item in ctx
65        .nodes::<ast::Enum>()
66        .filter(|item| !ctx.is_in_test(item))
67    {
68        let Some(variants) = item.variant_list() else {
69            continue;
70        };
71        let variants: Vec<_> = variants.variants().collect();
72
73        if variants.len() < MIN_VARIANT_COUNT {
74            continue;
75        }
76
77        let sizes: Vec<_> = variants
78            .into_iter()
79            .filter_map(|variant| {
80                support::estimate_fields_size(variant.field_list()).map(|size| (variant, size))
81            })
82            .collect();
83
84        if sizes.len() < MIN_VARIANT_COUNT {
85            continue;
86        }
87
88        let min_size = sizes.iter().map(|(_, size)| *size).min().unwrap_or(0);
89
90        for (variant, size) in sizes {
91            if size > min_size + min_size_diff {
92                let Some(name) = variant.name() else {
93                    continue;
94                };
95
96                violations.push(ctx.violation(
97                    &name,
98                    format!(
99                        "enum variant `{name}` is {size} bytes vs smallest {min_size} bytes — consider Boxing the large fields"
100                    ),
101                ));
102            }
103        }
104    }
105
106    violations
107}
108
109crate::tidy_ast_test!(check_large_enum_variant, {
110    crate::example_tests!(EXAMPLES, check_large_enum_variant);
111});