11//! lint when there is a large size difference between variants on an enum
22
3- use clippy_utils:: diagnostics:: span_lint_and_then;
43use clippy_utils:: source:: snippet_with_applicability;
4+ use clippy_utils:: { diagnostics:: span_lint_and_then, ty:: is_copy} ;
55use rustc_errors:: Applicability ;
66use rustc_hir:: { Item , ItemKind } ;
77use rustc_lint:: { LateContext , LateLintPass } ;
88use rustc_middle:: lint:: in_external_macro;
99use rustc_middle:: ty:: layout:: LayoutOf ;
10+ use rustc_middle:: ty:: { Adt , Ty } ;
1011use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
1112use rustc_span:: source_map:: Span ;
1213
@@ -26,6 +27,15 @@ declare_clippy_lint! {
2627 /// the overhead is negligible and the boxing is counter-productive. Always
2728 /// measure the change this lint suggests.
2829 ///
30+ /// For types that implement `Copy`, the suggestion to `Box` a variant's
31+ /// data would require removing the trait impl. The types can of course
32+ /// still be `Clone`, but that is worse ergonomically. Depending on the
33+ /// use case it may be possible to store the large data in an auxillary
34+ /// structure (e.g. Arena or ECS).
35+ ///
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+ ///
2939 /// ### Example
3040 /// ```rust
3141 /// // Bad
@@ -74,7 +84,7 @@ struct VariantInfo {
7484impl_lint_pass ! ( LargeEnumVariant => [ LARGE_ENUM_VARIANT ] ) ;
7585
7686impl < ' tcx > LateLintPass < ' tcx > for LargeEnumVariant {
77- fn check_item ( & mut self , cx : & LateContext < ' _ > , item : & Item < ' _ > ) {
87+ fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & Item < ' tcx > ) {
7888 if in_external_macro ( cx. tcx . sess , item. span ) {
7989 return ;
8090 }
@@ -132,41 +142,57 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
132142 let fields = def. variants [ variants_size[ 0 ] . ind ] . data . fields ( ) ;
133143 variants_size[ 0 ] . fields_size . sort_by ( |a, b| ( a. size . cmp ( & b. size ) ) ) ;
134144 let mut applicability = Applicability :: MaybeIncorrect ;
135- let sugg: Vec < ( Span , String ) > = variants_size[ 0 ]
136- . fields_size
137- . iter ( )
138- . rev ( )
139- . map_while ( |val| {
140- if difference > self . maximum_size_difference_allowed {
141- difference = difference. saturating_sub ( val. size ) ;
142- Some ( (
143- fields[ val. ind ] . ty . span ,
144- format ! (
145- "Box<{}>" ,
146- snippet_with_applicability(
147- cx,
148- fields[ val. ind] . ty. span,
149- ".." ,
150- & mut applicability
151- )
152- . into_owned( )
153- ) ,
154- ) )
155- } else {
156- None
157- }
158- } )
159- . collect ( ) ;
145+ if is_copy ( cx, ty) || maybe_copy ( cx, ty) {
146+ diag. span_note (
147+ item. ident . span ,
148+ "boxing a variant would require the type no longer be `Copy`" ,
149+ ) ;
150+ } else {
151+ let sugg: Vec < ( Span , String ) > = variants_size[ 0 ]
152+ . fields_size
153+ . iter ( )
154+ . rev ( )
155+ . map_while ( |val| {
156+ if difference > self . maximum_size_difference_allowed {
157+ difference = difference. saturating_sub ( val. size ) ;
158+ Some ( (
159+ fields[ val. ind ] . ty . span ,
160+ format ! (
161+ "Box<{}>" ,
162+ snippet_with_applicability(
163+ cx,
164+ fields[ val. ind] . ty. span,
165+ ".." ,
166+ & mut applicability
167+ )
168+ . into_owned( )
169+ ) ,
170+ ) )
171+ } else {
172+ None
173+ }
174+ } )
175+ . collect ( ) ;
160176
161- if !sugg. is_empty ( ) {
162- diag. multipart_suggestion ( help_text, sugg, Applicability :: MaybeIncorrect ) ;
163- return ;
177+ if !sugg. is_empty ( ) {
178+ diag. multipart_suggestion ( help_text, sugg, Applicability :: MaybeIncorrect ) ;
179+ return ;
180+ }
164181 }
165-
166182 diag. span_help ( def. variants [ variants_size[ 0 ] . ind ] . span , help_text) ;
167183 } ,
168184 ) ;
169185 }
170186 }
171187 }
172188}
189+
190+ fn maybe_copy < ' tcx > ( cx : & LateContext < ' tcx > , ty : Ty < ' tcx > ) -> bool {
191+ if let Adt ( _def, substs) = ty. kind ( )
192+ && substs. types ( ) . next ( ) . is_some ( )
193+ && let Some ( copy_trait) = cx. tcx . lang_items ( ) . copy_trait ( )
194+ {
195+ return cx. tcx . non_blanket_impls_for_ty ( copy_trait, ty) . next ( ) . is_some ( ) ;
196+ }
197+ false
198+ }
0 commit comments