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,9 +32,6 @@ 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.
38- ///
3935 /// ### Example
4036 /// ```rust
4137 /// enum Test {
@@ -83,6 +79,38 @@ struct VariantInfo {
8379 fields_size : Vec < FieldInfo > ,
8480}
8581
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+
86114impl_lint_pass ! ( LargeEnumVariant => [ LARGE_ENUM_VARIANT ] ) ;
87115
88116impl < ' tcx > LateLintPass < ' tcx > for LargeEnumVariant {
@@ -92,57 +120,45 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
92120 }
93121 if let ItemKind :: Enum ( ref def, _) = item. kind {
94122 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+ } ;
96127 if adt. variants ( ) . len ( ) <= 1 {
97128 return ;
98129 }
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) ;
125131
126132 let mut difference = variants_size[ 0 ] . size - variants_size[ 1 ] . size ;
127133 if difference > self . maximum_size_difference_allowed {
128134 let help_text = "consider boxing the large fields to reduce the total size of the enum" ;
129135 span_lint_and_then (
130136 cx,
131137 LARGE_ENUM_VARIANT ,
132- def . variants [ variants_size [ 0 ] . ind ] . span ,
138+ item . span ,
133139 "large size difference between variants" ,
134140 |diag| {
141+ diag. span_label (
142+ item. span ,
143+ format ! ( "the entire enum is at least {} bytes" , approx_ty_size( cx, ty) ) ,
144+ ) ;
135145 diag. span_label (
136146 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) ,
138148 ) ;
139- diag. span_note (
149+ diag. span_label (
140150 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+ } ,
142159 ) ;
143160
144161 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 ) ) ) ;
146162 let mut applicability = Applicability :: MaybeIncorrect ;
147163 if is_copy ( cx, ty) || maybe_copy ( cx, ty) {
148164 diag. span_note (
0 commit comments