Skip to content

Commit cb72a2f

Browse files
committed
Auto merge of #7677 - surechen:edit_large_enum_variant, r=camsteffen
fix bug for large_enum_variants Fix the discussion problem in the issue of #7666 (comment) About the false positive problem of case: ```rust enum LargeEnum6 { A, B([u8;255]), C([u8;200]), } ```
2 parents ab99eec + 56f0c9a commit cb72a2f

File tree

3 files changed

+171
-71
lines changed

3 files changed

+171
-71
lines changed

clippy_lints/src/large_enum_variant.rs

+98-63
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
//! lint when there is a large size difference between variants on an enum
22
33
use clippy_utils::diagnostics::span_lint_and_then;
4-
use clippy_utils::source::snippet_opt;
4+
use clippy_utils::source::snippet_with_applicability;
55
use rustc_errors::Applicability;
6-
use rustc_hir::{Item, ItemKind, VariantData};
6+
use rustc_hir::{Item, ItemKind};
77
use rustc_lint::{LateContext, LateLintPass};
88
use rustc_middle::lint::in_external_macro;
99
use rustc_middle::ty::layout::LayoutOf;
1010
use rustc_session::{declare_tool_lint, impl_lint_pass};
11+
use rustc_span::source_map::Span;
1112

1213
declare_clippy_lint! {
1314
/// ### What it does
@@ -58,6 +59,17 @@ impl LargeEnumVariant {
5859
}
5960
}
6061

62+
struct FieldInfo {
63+
ind: usize,
64+
size: u64,
65+
}
66+
67+
struct VariantInfo {
68+
ind: usize,
69+
size: u64,
70+
fields_size: Vec<FieldInfo>,
71+
}
72+
6173
impl_lint_pass!(LargeEnumVariant => [LARGE_ENUM_VARIANT]);
6274

6375
impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
@@ -68,72 +80,95 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
6880
if let ItemKind::Enum(ref def, _) = item.kind {
6981
let ty = cx.tcx.type_of(item.def_id);
7082
let adt = ty.ty_adt_def().expect("already checked whether this is an enum");
71-
72-
let mut largest_variant: Option<(_, _)> = None;
73-
let mut second_variant: Option<(_, _)> = None;
74-
75-
for (i, variant) in adt.variants.iter().enumerate() {
76-
let size: u64 = variant
77-
.fields
78-
.iter()
79-
.filter_map(|f| {
80-
let ty = cx.tcx.type_of(f.did);
81-
// don't count generics by filtering out everything
82-
// that does not have a layout
83-
cx.layout_of(ty).ok().map(|l| l.size.bytes())
84-
})
85-
.sum();
86-
87-
let grouped = (size, (i, variant));
88-
89-
if grouped.0 >= largest_variant.map_or(0, |x| x.0) {
90-
second_variant = largest_variant;
91-
largest_variant = Some(grouped);
92-
}
83+
if adt.variants.len() <= 1 {
84+
return;
9385
}
86+
let mut variants_size: Vec<VariantInfo> = adt
87+
.variants
88+
.iter()
89+
.enumerate()
90+
.map(|(i, variant)| {
91+
let mut fields_size = Vec::new();
92+
let size: u64 = variant
93+
.fields
94+
.iter()
95+
.enumerate()
96+
.filter_map(|(i, f)| {
97+
let ty = cx.tcx.type_of(f.did);
98+
// don't count generics by filtering out everything
99+
// that does not have a layout
100+
cx.layout_of(ty).ok().map(|l| {
101+
let size = l.size.bytes();
102+
fields_size.push(FieldInfo { ind: i, size });
103+
size
104+
})
105+
})
106+
.sum();
107+
VariantInfo {
108+
ind: i,
109+
size,
110+
fields_size,
111+
}
112+
})
113+
.collect();
94114

95-
if let (Some(largest), Some(second)) = (largest_variant, second_variant) {
96-
let difference = largest.0 - second.0;
115+
variants_size.sort_by(|a, b| (b.size.cmp(&a.size)));
97116

98-
if difference > self.maximum_size_difference_allowed {
99-
let (i, variant) = largest.1;
117+
let mut difference = variants_size[0].size - variants_size[1].size;
118+
if difference > self.maximum_size_difference_allowed {
119+
let help_text = "consider boxing the large fields to reduce the total size of the enum";
120+
span_lint_and_then(
121+
cx,
122+
LARGE_ENUM_VARIANT,
123+
def.variants[variants_size[0].ind].span,
124+
"large size difference between variants",
125+
|diag| {
126+
diag.span_label(
127+
def.variants[variants_size[0].ind].span,
128+
&format!("this variant is {} bytes", variants_size[0].size),
129+
);
130+
diag.span_note(
131+
def.variants[variants_size[1].ind].span,
132+
&format!("and the second-largest variant is {} bytes:", variants_size[1].size),
133+
);
100134

101-
let help_text = "consider boxing the large fields to reduce the total size of the enum";
102-
span_lint_and_then(
103-
cx,
104-
LARGE_ENUM_VARIANT,
105-
def.variants[i].span,
106-
"large size difference between variants",
107-
|diag| {
108-
diag.span_label(
109-
def.variants[(largest.1).0].span,
110-
&format!("this variant is {} bytes", largest.0),
111-
);
112-
diag.span_note(
113-
def.variants[(second.1).0].span,
114-
&format!("and the second-largest variant is {} bytes:", second.0),
115-
);
116-
if variant.fields.len() == 1 {
117-
let span = match def.variants[i].data {
118-
VariantData::Struct(fields, ..) | VariantData::Tuple(fields, ..) => {
119-
fields[0].ty.span
120-
},
121-
VariantData::Unit(..) => unreachable!(),
122-
};
123-
if let Some(snip) = snippet_opt(cx, span) {
124-
diag.span_suggestion(
125-
span,
126-
help_text,
127-
format!("Box<{}>", snip),
128-
Applicability::MaybeIncorrect,
129-
);
130-
return;
135+
let fields = def.variants[variants_size[0].ind].data.fields();
136+
variants_size[0].fields_size.sort_by(|a, b| (a.size.cmp(&b.size)));
137+
let mut applicability = Applicability::MaybeIncorrect;
138+
let sugg: Vec<(Span, String)> = variants_size[0]
139+
.fields_size
140+
.iter()
141+
.rev()
142+
.map_while(|val| {
143+
if difference > self.maximum_size_difference_allowed {
144+
difference = difference.saturating_sub(val.size);
145+
Some((
146+
fields[val.ind].ty.span,
147+
format!(
148+
"Box<{}>",
149+
snippet_with_applicability(
150+
cx,
151+
fields[val.ind].ty.span,
152+
"..",
153+
&mut applicability
154+
)
155+
.into_owned()
156+
),
157+
))
158+
} else {
159+
None
131160
}
132-
}
133-
diag.span_help(def.variants[i].span, help_text);
134-
},
135-
);
136-
}
161+
})
162+
.collect();
163+
164+
if !sugg.is_empty() {
165+
diag.multipart_suggestion(help_text, sugg, Applicability::MaybeIncorrect);
166+
return;
167+
}
168+
169+
diag.span_help(def.variants[variants_size[0].ind].span, help_text);
170+
},
171+
);
137172
}
138173
}
139174
}

tests/ui/large_enum_variant.rs

+18
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ enum LargeEnum2 {
3535
VariantOk(i32, u32),
3636
ContainingLargeEnum(LargeEnum),
3737
}
38+
3839
enum LargeEnum3 {
3940
ContainingMoreThanOneField(i32, [i32; 8000], [i32; 9500]),
4041
VoidVariant,
@@ -56,6 +57,23 @@ enum LargeEnumOk {
5657
LargeB([i32; 8001]),
5758
}
5859

60+
enum LargeEnum6 {
61+
A,
62+
B([u8; 255]),
63+
C([u8; 200]),
64+
}
65+
66+
enum LargeEnum7 {
67+
A,
68+
B([u8; 1255]),
69+
C([u8; 200]),
70+
}
71+
72+
enum LargeEnum8 {
73+
VariantOk(i32, u32),
74+
ContainingMoreThanOneField([i32; 8000], [i32; 2], [i32; 9500], [i32; 30]),
75+
}
76+
5977
fn main() {
6078
large_enum_variant!();
6179
}

tests/ui/large_enum_variant.stderr

+55-8
Original file line numberDiff line numberDiff line change
@@ -32,30 +32,45 @@ LL | ContainingLargeEnum(Box<LargeEnum>),
3232
| ~~~~~~~~~~~~~~
3333

3434
error: large size difference between variants
35-
--> $DIR/large_enum_variant.rs:46:5
35+
--> $DIR/large_enum_variant.rs:40:5
36+
|
37+
LL | ContainingMoreThanOneField(i32, [i32; 8000], [i32; 9500]),
38+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this variant is 70004 bytes
39+
|
40+
note: and the second-largest variant is 8 bytes:
41+
--> $DIR/large_enum_variant.rs:42:5
42+
|
43+
LL | StructLikeLittle { x: i32, y: i32 },
44+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
45+
help: consider boxing the large fields to reduce the total size of the enum
46+
|
47+
LL | ContainingMoreThanOneField(i32, Box<[i32; 8000]>, Box<[i32; 9500]>),
48+
| ~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
49+
50+
error: large size difference between variants
51+
--> $DIR/large_enum_variant.rs:47:5
3652
|
3753
LL | StructLikeLarge { x: [i32; 8000], y: i32 },
3854
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this variant is 32004 bytes
3955
|
4056
note: and the second-largest variant is 8 bytes:
41-
--> $DIR/large_enum_variant.rs:45:5
57+
--> $DIR/large_enum_variant.rs:46:5
4258
|
4359
LL | VariantOk(i32, u32),
4460
| ^^^^^^^^^^^^^^^^^^^
4561
help: consider boxing the large fields to reduce the total size of the enum
46-
--> $DIR/large_enum_variant.rs:46:5
4762
|
48-
LL | StructLikeLarge { x: [i32; 8000], y: i32 },
49-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
63+
LL | StructLikeLarge { x: Box<[i32; 8000]>, y: i32 },
64+
| ~~~~~~~~~~~~~~~~
5065

5166
error: large size difference between variants
52-
--> $DIR/large_enum_variant.rs:51:5
67+
--> $DIR/large_enum_variant.rs:52:5
5368
|
5469
LL | StructLikeLarge2 { x: [i32; 8000] },
5570
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this variant is 32000 bytes
5671
|
5772
note: and the second-largest variant is 8 bytes:
58-
--> $DIR/large_enum_variant.rs:50:5
73+
--> $DIR/large_enum_variant.rs:51:5
5974
|
6075
LL | VariantOk(i32, u32),
6176
| ^^^^^^^^^^^^^^^^^^^
@@ -64,5 +79,37 @@ help: consider boxing the large fields to reduce the total size of the enum
6479
LL | StructLikeLarge2 { x: Box<[i32; 8000]> },
6580
| ~~~~~~~~~~~~~~~~
6681

67-
error: aborting due to 4 previous errors
82+
error: large size difference between variants
83+
--> $DIR/large_enum_variant.rs:68:5
84+
|
85+
LL | B([u8; 1255]),
86+
| ^^^^^^^^^^^^^ this variant is 1255 bytes
87+
|
88+
note: and the second-largest variant is 200 bytes:
89+
--> $DIR/large_enum_variant.rs:69:5
90+
|
91+
LL | C([u8; 200]),
92+
| ^^^^^^^^^^^^
93+
help: consider boxing the large fields to reduce the total size of the enum
94+
|
95+
LL | B(Box<[u8; 1255]>),
96+
| ~~~~~~~~~~~~~~~
97+
98+
error: large size difference between variants
99+
--> $DIR/large_enum_variant.rs:74:5
100+
|
101+
LL | ContainingMoreThanOneField([i32; 8000], [i32; 2], [i32; 9500], [i32; 30]),
102+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this variant is 70128 bytes
103+
|
104+
note: and the second-largest variant is 8 bytes:
105+
--> $DIR/large_enum_variant.rs:73:5
106+
|
107+
LL | VariantOk(i32, u32),
108+
| ^^^^^^^^^^^^^^^^^^^
109+
help: consider boxing the large fields to reduce the total size of the enum
110+
|
111+
LL | ContainingMoreThanOneField(Box<[i32; 8000]>, [i32; 2], Box<[i32; 9500]>, [i32; 30]),
112+
| ~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
113+
114+
error: aborting due to 7 previous errors
68115

0 commit comments

Comments
 (0)