Skip to content

Commit 584000a

Browse files
committed
Use approx_ty_size for large_enum_variant
1 parent 30e4532 commit 584000a

File tree

6 files changed

+267
-139
lines changed

6 files changed

+267
-139
lines changed

clippy_lints/src/large_enum_variant.rs

+58-38
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
//! lint when there is a large size difference between variants on an enum
22
33
use clippy_utils::source::snippet_with_applicability;
4-
use clippy_utils::{diagnostics::span_lint_and_then, ty::is_copy};
4+
use clippy_utils::{diagnostics::span_lint_and_then, ty::approx_ty_size, ty::is_copy};
55
use rustc_errors::Applicability;
66
use rustc_hir::{Item, ItemKind};
77
use rustc_lint::{LateContext, LateLintPass};
88
use rustc_middle::lint::in_external_macro;
9-
use rustc_middle::ty::layout::LayoutOf;
10-
use rustc_middle::ty::{Adt, Ty};
9+
use rustc_middle::ty::{Adt, AdtDef, GenericArg, List, Ty};
1110
use rustc_session::{declare_tool_lint, impl_lint_pass};
1211
use rustc_span::source_map::Span;
1312

@@ -17,7 +16,7 @@ declare_clippy_lint! {
1716
/// `enum`s.
1817
///
1918
/// ### Why is this bad?
20-
/// Enum size is bounded by the largest variant. Having a
19+
/// Enum size is bounded by the largest variant. Having one
2120
/// large variant can penalize the memory layout of that enum.
2221
///
2322
/// ### Known problems
@@ -33,8 +32,9 @@ declare_clippy_lint! {
3332
/// use case it may be possible to store the large data in an auxiliary
3433
/// structure (e.g. Arena or ECS).
3534
///
36-
/// The lint will ignore generic types if the layout depends on the
37-
/// generics, even if the size difference will be large anyway.
35+
/// The lint will ignore the impact of generic types to the type layout by
36+
/// assuming every type parameter is zero-sized. Depending on your use case,
37+
/// this may lead to a false positive.
3838
///
3939
/// ### Example
4040
/// ```rust
@@ -83,6 +83,38 @@ struct VariantInfo {
8383
fields_size: Vec<FieldInfo>,
8484
}
8585

86+
fn variants_size<'tcx>(
87+
cx: &LateContext<'tcx>,
88+
adt: AdtDef<'tcx>,
89+
subst: &'tcx List<GenericArg<'tcx>>,
90+
) -> Vec<VariantInfo> {
91+
let mut variants_size = adt
92+
.variants()
93+
.iter()
94+
.enumerate()
95+
.map(|(i, variant)| {
96+
let mut fields_size = variant
97+
.fields
98+
.iter()
99+
.enumerate()
100+
.map(|(i, f)| FieldInfo {
101+
ind: i,
102+
size: approx_ty_size(cx, f.ty(cx.tcx, subst)),
103+
})
104+
.collect::<Vec<_>>();
105+
fields_size.sort_by(|a, b| (a.size.cmp(&b.size)));
106+
107+
VariantInfo {
108+
ind: i,
109+
size: fields_size.iter().map(|info| info.size).sum(),
110+
fields_size,
111+
}
112+
})
113+
.collect::<Vec<_>>();
114+
variants_size.sort_by(|a, b| (b.size.cmp(&a.size)));
115+
variants_size
116+
}
117+
86118
impl_lint_pass!(LargeEnumVariant => [LARGE_ENUM_VARIANT]);
87119

88120
impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
@@ -92,57 +124,45 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
92124
}
93125
if let ItemKind::Enum(ref def, _) = item.kind {
94126
let ty = cx.tcx.type_of(item.def_id);
95-
let adt = ty.ty_adt_def().expect("already checked whether this is an enum");
127+
let (adt, subst) = match ty.kind() {
128+
Adt(adt, subst) => (adt, subst),
129+
_ => panic!("already checked whether this is an enum"),
130+
};
96131
if adt.variants().len() <= 1 {
97132
return;
98133
}
99-
let mut variants_size: Vec<VariantInfo> = Vec::new();
100-
for (i, variant) in adt.variants().iter().enumerate() {
101-
let mut fields_size = Vec::new();
102-
for (i, f) in variant.fields.iter().enumerate() {
103-
let ty = cx.tcx.type_of(f.did);
104-
// don't lint variants which have a field of generic type.
105-
match cx.layout_of(ty) {
106-
Ok(l) => {
107-
let fsize = l.size.bytes();
108-
fields_size.push(FieldInfo { ind: i, size: fsize });
109-
},
110-
Err(_) => {
111-
return;
112-
},
113-
}
114-
}
115-
let size: u64 = fields_size.iter().map(|info| info.size).sum();
116-
117-
variants_size.push(VariantInfo {
118-
ind: i,
119-
size,
120-
fields_size,
121-
});
122-
}
123-
124-
variants_size.sort_by(|a, b| (b.size.cmp(&a.size)));
134+
let variants_size = variants_size(cx, *adt, subst);
125135

126136
let mut difference = variants_size[0].size - variants_size[1].size;
127137
if difference > self.maximum_size_difference_allowed {
128138
let help_text = "consider boxing the large fields to reduce the total size of the enum";
129139
span_lint_and_then(
130140
cx,
131141
LARGE_ENUM_VARIANT,
132-
def.variants[variants_size[0].ind].span,
142+
item.span,
133143
"large size difference between variants",
134144
|diag| {
145+
diag.span_label(
146+
item.span,
147+
format!("the entire enum is at least {} bytes", approx_ty_size(cx, ty)),
148+
);
135149
diag.span_label(
136150
def.variants[variants_size[0].ind].span,
137-
&format!("this variant is {} bytes", variants_size[0].size),
151+
format!("the largest variant contains at least {} bytes", variants_size[0].size),
138152
);
139-
diag.span_note(
153+
diag.span_label(
140154
def.variants[variants_size[1].ind].span,
141-
&format!("and the second-largest variant is {} bytes:", variants_size[1].size),
155+
&if variants_size[1].fields_size.is_empty() {
156+
"the second-largest variant carries no data at all".to_owned()
157+
} else {
158+
format!(
159+
"the second-largest variant contains at least {} bytes",
160+
variants_size[1].size
161+
)
162+
},
142163
);
143164

144165
let fields = def.variants[variants_size[0].ind].data.fields();
145-
variants_size[0].fields_size.sort_by(|a, b| (a.size.cmp(&b.size)));
146166
let mut applicability = Applicability::MaybeIncorrect;
147167
if is_copy(cx, ty) || maybe_copy(cx, ty) {
148168
diag.span_note(

src/docs/large_enum_variant.txt

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Checks for large size differences between variants on
33
`enum`s.
44

55
### Why is this bad?
6-
Enum size is bounded by the largest variant. Having a
6+
Enum size is bounded by the largest variant. Having one
77
large variant can penalize the memory layout of that enum.
88

99
### Known problems
@@ -19,8 +19,9 @@ still be `Clone`, but that is worse ergonomically. Depending on the
1919
use case it may be possible to store the large data in an auxiliary
2020
structure (e.g. Arena or ECS).
2121

22-
The lint will ignore generic types if the layout depends on the
23-
generics, even if the size difference will be large anyway.
22+
The lint will ignore the impact of generic types to the type layout by
23+
assuming every type parameter is zero-sized. Depending on your use case,
24+
this may lead to a false positive.
2425

2526
### Example
2627
```

tests/ui/large_enum_variant.rs

+24
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,30 @@ impl<T: Copy> Clone for SomeGenericPossiblyCopyEnum<T> {
130130

131131
impl<T: Copy> Copy for SomeGenericPossiblyCopyEnum<T> {}
132132

133+
enum LargeEnumWithGenerics<T> {
134+
Small,
135+
Large((T, [u8; 512])),
136+
}
137+
138+
struct Foo<T> {
139+
foo: T,
140+
}
141+
142+
enum WithGenerics {
143+
Large([Foo<u64>; 64]),
144+
Small(u8),
145+
}
146+
147+
enum PossiblyLargeEnumWithConst<const U: usize> {
148+
SmallBuffer([u8; 4]),
149+
MightyBuffer([u16; U]),
150+
}
151+
152+
enum LargeEnumOfConst {
153+
Ok,
154+
Error(PossiblyLargeEnumWithConst<256>),
155+
}
156+
133157
fn main() {
134158
large_enum_variant!();
135159
}

0 commit comments

Comments
 (0)