11//! lint when there is a large size difference between variants on an enum
22
33use 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} ;
55use rustc_errors:: Applicability ;
66use rustc_hir:: { Item , ItemKind } ;
77use rustc_lint:: { LateContext , LateLintPass } ;
88use 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 } ;
1110use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
1211use 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+
86118impl_lint_pass ! ( LargeEnumVariant => [ LARGE_ENUM_VARIANT ] ) ;
87119
88120impl < ' 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 (
0 commit comments