1
1
//! lint when there is a large size difference between variants on an enum
2
2
3
3
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} ;
5
5
use rustc_errors:: Applicability ;
6
6
use rustc_hir:: { Item , ItemKind } ;
7
7
use rustc_lint:: { LateContext , LateLintPass } ;
8
8
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 } ;
11
10
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
12
11
use rustc_span:: source_map:: Span ;
13
12
@@ -17,7 +16,7 @@ declare_clippy_lint! {
17
16
/// `enum`s.
18
17
///
19
18
/// ### 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
21
20
/// large variant can penalize the memory layout of that enum.
22
21
///
23
22
/// ### Known problems
@@ -33,8 +32,9 @@ declare_clippy_lint! {
33
32
/// use case it may be possible to store the large data in an auxiliary
34
33
/// structure (e.g. Arena or ECS).
35
34
///
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.
38
38
///
39
39
/// ### Example
40
40
/// ```rust
@@ -83,6 +83,38 @@ struct VariantInfo {
83
83
fields_size : Vec < FieldInfo > ,
84
84
}
85
85
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
+
86
118
impl_lint_pass ! ( LargeEnumVariant => [ LARGE_ENUM_VARIANT ] ) ;
87
119
88
120
impl < ' tcx > LateLintPass < ' tcx > for LargeEnumVariant {
@@ -92,57 +124,45 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
92
124
}
93
125
if let ItemKind :: Enum ( ref def, _) = item. kind {
94
126
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
+ } ;
96
131
if adt. variants ( ) . len ( ) <= 1 {
97
132
return ;
98
133
}
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) ;
125
135
126
136
let mut difference = variants_size[ 0 ] . size - variants_size[ 1 ] . size ;
127
137
if difference > self . maximum_size_difference_allowed {
128
138
let help_text = "consider boxing the large fields to reduce the total size of the enum" ;
129
139
span_lint_and_then (
130
140
cx,
131
141
LARGE_ENUM_VARIANT ,
132
- def . variants [ variants_size [ 0 ] . ind ] . span ,
142
+ item . span ,
133
143
"large size difference between variants" ,
134
144
|diag| {
145
+ diag. span_label (
146
+ item. span ,
147
+ format ! ( "the entire enum is at least {} bytes" , approx_ty_size( cx, ty) ) ,
148
+ ) ;
135
149
diag. span_label (
136
150
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) ,
138
152
) ;
139
- diag. span_note (
153
+ diag. span_label (
140
154
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
+ } ,
142
163
) ;
143
164
144
165
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 ) ) ) ;
146
166
let mut applicability = Applicability :: MaybeIncorrect ;
147
167
if is_copy ( cx, ty) || maybe_copy ( cx, ty) {
148
168
diag. span_note (
0 commit comments