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,9 +32,6 @@ 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.
38
- ///
39
35
/// ### Example
40
36
/// ```rust
41
37
/// enum Test {
@@ -83,6 +79,38 @@ struct VariantInfo {
83
79
fields_size : Vec < FieldInfo > ,
84
80
}
85
81
82
+ fn variants_size < ' tcx > (
83
+ cx : & LateContext < ' tcx > ,
84
+ adt : AdtDef < ' tcx > ,
85
+ subst : & ' tcx List < GenericArg < ' tcx > > ,
86
+ ) -> Vec < VariantInfo > {
87
+ let mut variants_size = adt
88
+ . variants ( )
89
+ . iter ( )
90
+ . enumerate ( )
91
+ . map ( |( i, variant) | {
92
+ let mut fields_size = variant
93
+ . fields
94
+ . iter ( )
95
+ . enumerate ( )
96
+ . map ( |( i, f) | FieldInfo {
97
+ ind : i,
98
+ size : approx_ty_size ( cx, f. ty ( cx. tcx , subst) ) ,
99
+ } )
100
+ . collect :: < Vec < _ > > ( ) ;
101
+ fields_size. sort_by ( |a, b| ( a. size . cmp ( & b. size ) ) ) ;
102
+
103
+ VariantInfo {
104
+ ind : i,
105
+ size : fields_size. iter ( ) . map ( |info| info. size ) . sum ( ) ,
106
+ fields_size,
107
+ }
108
+ } )
109
+ . collect :: < Vec < _ > > ( ) ;
110
+ variants_size. sort_by ( |a, b| ( b. size . cmp ( & a. size ) ) ) ;
111
+ variants_size
112
+ }
113
+
86
114
impl_lint_pass ! ( LargeEnumVariant => [ LARGE_ENUM_VARIANT ] ) ;
87
115
88
116
impl < ' tcx > LateLintPass < ' tcx > for LargeEnumVariant {
@@ -92,57 +120,45 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
92
120
}
93
121
if let ItemKind :: Enum ( ref def, _) = item. kind {
94
122
let ty = cx. tcx . type_of ( item. def_id ) ;
95
- let adt = ty. ty_adt_def ( ) . expect ( "already checked whether this is an enum" ) ;
123
+ let ( adt, subst) = match ty. kind ( ) {
124
+ Adt ( adt, subst) => ( adt, subst) ,
125
+ _ => panic ! ( "already checked whether this is an enum" ) ,
126
+ } ;
96
127
if adt. variants ( ) . len ( ) <= 1 {
97
128
return ;
98
129
}
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 ) ) ) ;
130
+ let variants_size = variants_size ( cx, * adt, subst) ;
125
131
126
132
let mut difference = variants_size[ 0 ] . size - variants_size[ 1 ] . size ;
127
133
if difference > self . maximum_size_difference_allowed {
128
134
let help_text = "consider boxing the large fields to reduce the total size of the enum" ;
129
135
span_lint_and_then (
130
136
cx,
131
137
LARGE_ENUM_VARIANT ,
132
- def . variants [ variants_size [ 0 ] . ind ] . span ,
138
+ item . span ,
133
139
"large size difference between variants" ,
134
140
|diag| {
141
+ diag. span_label (
142
+ item. span ,
143
+ format ! ( "the entire enum is at least {} bytes" , approx_ty_size( cx, ty) ) ,
144
+ ) ;
135
145
diag. span_label (
136
146
def. variants [ variants_size[ 0 ] . ind ] . span ,
137
- & format ! ( "this variant is {} bytes" , variants_size[ 0 ] . size) ,
147
+ format ! ( "the largest variant contains at least {} bytes" , variants_size[ 0 ] . size) ,
138
148
) ;
139
- diag. span_note (
149
+ diag. span_label (
140
150
def. variants [ variants_size[ 1 ] . ind ] . span ,
141
- & format ! ( "and the second-largest variant is {} bytes:" , variants_size[ 1 ] . size) ,
151
+ & if variants_size[ 1 ] . fields_size . is_empty ( ) {
152
+ "the second-largest variant carries no data at all" . to_owned ( )
153
+ } else {
154
+ format ! (
155
+ "the second-largest variant contains at least {} bytes" ,
156
+ variants_size[ 1 ] . size
157
+ )
158
+ } ,
142
159
) ;
143
160
144
161
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
162
let mut applicability = Applicability :: MaybeIncorrect ;
147
163
if is_copy ( cx, ty) || maybe_copy ( cx, ty) {
148
164
diag. span_note (
0 commit comments