1
1
//! lint when there is a large size difference between variants on an enum
2
2
3
3
use clippy_utils:: diagnostics:: span_lint_and_then;
4
- use clippy_utils:: source:: snippet_opt ;
4
+ use clippy_utils:: source:: snippet_with_applicability ;
5
5
use rustc_errors:: Applicability ;
6
- use rustc_hir:: { Item , ItemKind , VariantData } ;
6
+ use rustc_hir:: { Item , ItemKind } ;
7
7
use rustc_lint:: { LateContext , LateLintPass } ;
8
8
use rustc_middle:: lint:: in_external_macro;
9
9
use rustc_middle:: ty:: layout:: LayoutOf ;
10
10
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
11
+ use rustc_span:: source_map:: Span ;
11
12
12
13
declare_clippy_lint ! {
13
14
/// ### What it does
@@ -58,6 +59,17 @@ impl LargeEnumVariant {
58
59
}
59
60
}
60
61
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
+
61
73
impl_lint_pass ! ( LargeEnumVariant => [ LARGE_ENUM_VARIANT ] ) ;
62
74
63
75
impl < ' tcx > LateLintPass < ' tcx > for LargeEnumVariant {
@@ -68,72 +80,95 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
68
80
if let ItemKind :: Enum ( ref def, _) = item. kind {
69
81
let ty = cx. tcx . type_of ( item. def_id ) ;
70
82
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 ;
93
85
}
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 ( ) ;
94
114
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 ) ) ) ;
97
116
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
+ ) ;
100
134
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
131
160
}
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
+ ) ;
137
172
}
138
173
}
139
174
}
0 commit comments