11//! lint when there is a large size difference between variants on an enum
22
33use clippy_utils:: diagnostics:: span_lint_and_then;
4- use clippy_utils:: source:: snippet_opt ;
4+ use clippy_utils:: source:: snippet_with_applicability ;
55use rustc_errors:: Applicability ;
6- use rustc_hir:: { Item , ItemKind , VariantData } ;
6+ use rustc_hir:: { Item , ItemKind } ;
77use rustc_lint:: { LateContext , LateLintPass } ;
88use rustc_middle:: lint:: in_external_macro;
99use rustc_middle:: ty:: layout:: LayoutOf ;
1010use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
11+ use rustc_span:: source_map:: Span ;
1112
1213declare_clippy_lint ! {
1314 /// ### What it does
@@ -58,6 +59,17 @@ impl LargeEnumVariant {
5859 }
5960}
6061
62+ struct FieldInfo {
63+ ind : usize ,
64+ size : u64 ,
65+ }
66+
67+ struct VariantInfo {
68+ ind : usize ,
69+ size : u64 ,
70+ fields_size : Vec < FieldInfo > ,
71+ }
72+
6173impl_lint_pass ! ( LargeEnumVariant => [ LARGE_ENUM_VARIANT ] ) ;
6274
6375impl < ' tcx > LateLintPass < ' tcx > for LargeEnumVariant {
@@ -68,72 +80,95 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
6880 if let ItemKind :: Enum ( ref def, _) = item. kind {
6981 let ty = cx. tcx . type_of ( item. def_id ) ;
7082 let adt = ty. ty_adt_def ( ) . expect ( "already checked whether this is an enum" ) ;
71-
72- let mut largest_variant: Option < ( _ , _ ) > = None ;
73- let mut second_variant: Option < ( _ , _ ) > = None ;
74-
75- for ( i, variant) in adt. variants . iter ( ) . enumerate ( ) {
76- let size: u64 = variant
77- . fields
78- . iter ( )
79- . filter_map ( |f| {
80- let ty = cx. tcx . type_of ( f. did ) ;
81- // don't count generics by filtering out everything
82- // that does not have a layout
83- cx. layout_of ( ty) . ok ( ) . map ( |l| l. size . bytes ( ) )
84- } )
85- . sum ( ) ;
86-
87- let grouped = ( size, ( i, variant) ) ;
88-
89- if grouped. 0 >= largest_variant. map_or ( 0 , |x| x. 0 ) {
90- second_variant = largest_variant;
91- largest_variant = Some ( grouped) ;
92- }
83+ if adt. variants . len ( ) <= 1 {
84+ return ;
9385 }
86+ let mut variants_size: Vec < VariantInfo > = adt
87+ . variants
88+ . iter ( )
89+ . enumerate ( )
90+ . map ( |( i, variant) | {
91+ let mut fields_size = Vec :: new ( ) ;
92+ let size: u64 = variant
93+ . fields
94+ . iter ( )
95+ . enumerate ( )
96+ . filter_map ( |( i, f) | {
97+ let ty = cx. tcx . type_of ( f. did ) ;
98+ // don't count generics by filtering out everything
99+ // that does not have a layout
100+ cx. layout_of ( ty) . ok ( ) . map ( |l| {
101+ let size = l. size . bytes ( ) ;
102+ fields_size. push ( FieldInfo { ind : i, size } ) ;
103+ size
104+ } )
105+ } )
106+ . sum ( ) ;
107+ VariantInfo {
108+ ind : i,
109+ size,
110+ fields_size,
111+ }
112+ } )
113+ . collect ( ) ;
94114
95- if let ( Some ( largest) , Some ( second) ) = ( largest_variant, second_variant) {
96- let difference = largest. 0 - second. 0 ;
115+ variants_size. sort_by ( |a, b| ( b. size . cmp ( & a. size ) ) ) ;
97116
98- if difference > self . maximum_size_difference_allowed {
99- let ( i, variant) = largest. 1 ;
117+ let mut difference = variants_size[ 0 ] . size - variants_size[ 1 ] . size ;
118+ if difference > self . maximum_size_difference_allowed {
119+ let help_text = "consider boxing the large fields to reduce the total size of the enum" ;
120+ span_lint_and_then (
121+ cx,
122+ LARGE_ENUM_VARIANT ,
123+ def. variants [ variants_size[ 0 ] . ind ] . span ,
124+ "large size difference between variants" ,
125+ |diag| {
126+ diag. span_label (
127+ def. variants [ variants_size[ 0 ] . ind ] . span ,
128+ & format ! ( "this variant is {} bytes" , variants_size[ 0 ] . size) ,
129+ ) ;
130+ diag. span_note (
131+ def. variants [ variants_size[ 1 ] . ind ] . span ,
132+ & format ! ( "and the second-largest variant is {} bytes:" , variants_size[ 1 ] . size) ,
133+ ) ;
100134
101- let help_text = "consider boxing the large fields to reduce the total size of the enum" ;
102- span_lint_and_then (
103- cx,
104- LARGE_ENUM_VARIANT ,
105- def. variants [ i] . span ,
106- "large size difference between variants" ,
107- |diag| {
108- diag. span_label (
109- def. variants [ ( largest. 1 ) . 0 ] . span ,
110- & format ! ( "this variant is {} bytes" , largest. 0 ) ,
111- ) ;
112- diag. span_note (
113- def. variants [ ( second. 1 ) . 0 ] . span ,
114- & format ! ( "and the second-largest variant is {} bytes:" , second. 0 ) ,
115- ) ;
116- if variant. fields . len ( ) == 1 {
117- let span = match def. variants [ i] . data {
118- VariantData :: Struct ( fields, ..) | VariantData :: Tuple ( fields, ..) => {
119- fields[ 0 ] . ty . span
120- } ,
121- VariantData :: Unit ( ..) => unreachable ! ( ) ,
122- } ;
123- if let Some ( snip) = snippet_opt ( cx, span) {
124- diag. span_suggestion (
125- span,
126- help_text,
127- format ! ( "Box<{}>" , snip) ,
128- Applicability :: MaybeIncorrect ,
129- ) ;
130- return ;
135+ let fields = def. variants [ variants_size[ 0 ] . ind ] . data . fields ( ) ;
136+ variants_size[ 0 ] . fields_size . sort_by ( |a, b| ( a. size . cmp ( & b. size ) ) ) ;
137+ let mut applicability = Applicability :: MaybeIncorrect ;
138+ let sugg: Vec < ( Span , String ) > = variants_size[ 0 ]
139+ . fields_size
140+ . iter ( )
141+ . rev ( )
142+ . map_while ( |val| {
143+ if difference > self . maximum_size_difference_allowed {
144+ difference = difference. saturating_sub ( val. size ) ;
145+ Some ( (
146+ fields[ val. ind ] . ty . span ,
147+ format ! (
148+ "Box<{}>" ,
149+ snippet_with_applicability(
150+ cx,
151+ fields[ val. ind] . ty. span,
152+ ".." ,
153+ & mut applicability
154+ )
155+ . into_owned( )
156+ ) ,
157+ ) )
158+ } else {
159+ None
131160 }
132- }
133- diag. span_help ( def. variants [ i] . span , help_text) ;
134- } ,
135- ) ;
136- }
161+ } )
162+ . collect ( ) ;
163+
164+ if !sugg. is_empty ( ) {
165+ diag. multipart_suggestion ( help_text, sugg, Applicability :: MaybeIncorrect ) ;
166+ return ;
167+ }
168+
169+ diag. span_help ( def. variants [ variants_size[ 0 ] . ind ] . span , help_text) ;
170+ } ,
171+ ) ;
137172 }
138173 }
139174 }
0 commit comments