| use clippy_config::Conf; | |
| use clippy_utils::diagnostics::span_lint_and_then; | |
| use clippy_utils::is_no_std_crate; | |
| use clippy_utils::source::snippet_with_applicability; | |
| use clippy_utils::ty::{AdtVariantInfo, approx_ty_size, is_copy}; | |
| use rustc_errors::Applicability; | |
| use rustc_hir::{Item, ItemKind}; | |
| use rustc_lint::{LateContext, LateLintPass}; | |
| use rustc_middle::ty::{self, Ty}; | |
| use rustc_session::impl_lint_pass; | |
| use rustc_span::Span; | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for large size differences between variants on | |
| /// `enum`s. | |
| /// | |
| /// ### Why is this bad? | |
| /// Enum size is bounded by the largest variant. Having one | |
| /// large variant can penalize the memory layout of that enum. | |
| /// | |
| /// ### Known problems | |
| /// This lint obviously cannot take the distribution of | |
| /// variants in your running program into account. It is possible that the | |
| /// smaller variants make up less than 1% of all instances, in which case | |
| /// the overhead is negligible and the boxing is counter-productive. Always | |
| /// measure the change this lint suggests. | |
| /// | |
| /// For types that implement `Copy`, the suggestion to `Box` a variant's | |
| /// data would require removing the trait impl. The types can of course | |
| /// still be `Clone`, but that is worse ergonomically. Depending on the | |
| /// use case it may be possible to store the large data in an auxiliary | |
| /// structure (e.g. Arena or ECS). | |
| /// | |
| /// The lint will ignore the impact of generic types to the type layout by | |
| /// assuming every type parameter is zero-sized. Depending on your use case, | |
| /// this may lead to a false positive. | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// enum Test { | |
| /// A(i32), | |
| /// B([i32; 8000]), | |
| /// } | |
| /// ``` | |
| /// | |
| /// Use instead: | |
| /// ```no_run | |
| /// // Possibly better | |
| /// enum Test2 { | |
| /// A(i32), | |
| /// B(Box<[i32; 8000]>), | |
| /// } | |
| /// ``` | |
| #[clippy::version = "pre 1.29.0"] | |
| pub LARGE_ENUM_VARIANT, | |
| perf, | |
| "large size difference between variants on an enum" | |
| } | |
| impl_lint_pass!(LargeEnumVariant => [LARGE_ENUM_VARIANT]); | |
| pub struct LargeEnumVariant { | |
| maximum_size_difference_allowed: u64, | |
| } | |
| impl LargeEnumVariant { | |
| pub fn new(conf: &'static Conf) -> Self { | |
| Self { | |
| maximum_size_difference_allowed: conf.enum_variant_size_threshold, | |
| } | |
| } | |
| } | |
| impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant { | |
| fn check_item(&mut self, cx: &LateContext<'tcx>, item: &Item<'tcx>) { | |
| if let ItemKind::Enum(ident, _, ref def) = item.kind | |
| && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() | |
| && let ty::Adt(adt, subst) = ty.kind() | |
| && adt.variants().len() > 1 | |
| && !item.span.in_external_macro(cx.tcx.sess.source_map()) | |
| { | |
| let variants_size = AdtVariantInfo::new(cx, *adt, subst); | |
| let mut difference = variants_size[0].size - variants_size[1].size; | |
| if difference > self.maximum_size_difference_allowed { | |
| let help_text = "consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum"; | |
| span_lint_and_then( | |
| cx, | |
| LARGE_ENUM_VARIANT, | |
| item.span, | |
| "large size difference between variants", | |
| |diag| { | |
| diag.span_label( | |
| item.span, | |
| format!("the entire enum is at least {} bytes", approx_ty_size(cx, ty)), | |
| ); | |
| diag.span_label( | |
| def.variants[variants_size[0].ind].span, | |
| format!("the largest variant contains at least {} bytes", variants_size[0].size), | |
| ); | |
| diag.span_label( | |
| def.variants[variants_size[1].ind].span, | |
| if variants_size[1].fields_size.is_empty() { | |
| "the second-largest variant carries no data at all".to_owned() | |
| } else { | |
| format!( | |
| "the second-largest variant contains at least {} bytes", | |
| variants_size[1].size | |
| ) | |
| }, | |
| ); | |
| let fields = def.variants[variants_size[0].ind].data.fields(); | |
| let mut applicability = Applicability::MaybeIncorrect; | |
| if is_copy(cx, ty) || maybe_copy(cx, ty) { | |
| diag.span_note( | |
| ident.span, | |
| "boxing a variant would require the type no longer be `Copy`", | |
| ); | |
| } else if !is_no_std_crate(cx) { | |
| let sugg: Vec<(Span, String)> = variants_size[0] | |
| .fields_size | |
| .iter() | |
| .rev() | |
| .map_while(|&(ind, size)| { | |
| if difference > self.maximum_size_difference_allowed { | |
| difference = difference.saturating_sub(size); | |
| Some(( | |
| fields[ind].ty.span, | |
| format!( | |
| "Box<{}>", | |
| snippet_with_applicability( | |
| cx, | |
| fields[ind].ty.span, | |
| "..", | |
| &mut applicability | |
| ) | |
| .into_owned() | |
| ), | |
| )) | |
| } else { | |
| None | |
| } | |
| }) | |
| .collect(); | |
| if !sugg.is_empty() { | |
| diag.multipart_suggestion(help_text, sugg, Applicability::MaybeIncorrect); | |
| return; | |
| } | |
| } | |
| diag.span_help(def.variants[variants_size[0].ind].span, help_text); | |
| }, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| fn maybe_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { | |
| if let ty::Adt(_def, args) = ty.kind() | |
| && args.types().next().is_some() | |
| && let Some(copy_trait) = cx.tcx.lang_items().copy_trait() | |
| { | |
| return cx.tcx.non_blanket_impls_for_ty(copy_trait, ty).next().is_some(); | |
| } | |
| false | |
| } |