X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=clippy_lints%2Fsrc%2Flarge_enum_variant.rs;h=f166748d86b81a021822759440e66456206309b5;hb=dc4ea800b7126e0751ba75eae095cc2a805dc8da;hp=a6d34f2c7a234290b500d5098826d18bb985e8b3;hpb=e176324fc593644a3ce00aa2ec7c1887d571003e;p=rust.git diff --git a/clippy_lints/src/large_enum_variant.rs b/clippy_lints/src/large_enum_variant.rs index a6d34f2c7a2..f166748d86b 100644 --- a/clippy_lints/src/large_enum_variant.rs +++ b/clippy_lints/src/large_enum_variant.rs @@ -1,29 +1,42 @@ //! lint when there is a large size difference between variants on an enum -use crate::utils::{snippet_opt, span_lint_and_then}; -use rustc::hir::*; -use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass}; -use rustc::ty::layout::LayoutOf; -use rustc::{declare_tool_lint, lint_array}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet_opt; use rustc_errors::Applicability; +use rustc_hir::{Item, ItemKind, VariantData}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_target::abi::LayoutOf; -/// **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 a -/// large variant -/// can penalize the memory layout of that enum. -/// -/// **Known problems:** None. -/// -/// **Example:** -/// ```rust -/// enum Test { -/// A(i32), -/// B([i32; 8000]), -/// } -/// ``` 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 a + /// 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. + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// enum Test { + /// A(i32), + /// B([i32; 8000]), + /// } + /// + /// // Possibly better + /// enum Test2 { + /// A(i32), + /// B(Box<[i32; 8000]>), + /// } + /// ``` pub LARGE_ENUM_VARIANT, perf, "large size difference between variants on an enum" @@ -35,6 +48,7 @@ pub struct LargeEnumVariant { } impl LargeEnumVariant { + #[must_use] pub fn new(maximum_size_difference_allowed: u64) -> Self { Self { maximum_size_difference_allowed, @@ -42,25 +56,19 @@ pub fn new(maximum_size_difference_allowed: u64) -> Self { } } -impl LintPass for LargeEnumVariant { - fn get_lints(&self) -> LintArray { - lint_array!(LARGE_ENUM_VARIANT) - } - - fn name(&self) -> &'static str { - "LargeEnumVariant" - } -} +impl_lint_pass!(LargeEnumVariant => [LARGE_ENUM_VARIANT]); -impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LargeEnumVariant { - fn check_item(&mut self, cx: &LateContext<'_, '_>, item: &Item) { - let did = cx.tcx.hir().local_def_id(item.id); - if let ItemKind::Enum(ref def, _) = item.node { - let ty = cx.tcx.type_of(did); +impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant { + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + if in_external_macro(cx.tcx.sess, item.span) { + return; + } + if let ItemKind::Enum(ref def, _) = item.kind { + let ty = cx.tcx.type_of(item.def_id); let adt = ty.ty_adt_def().expect("already checked whether this is an enum"); - let mut smallest_variant: Option<(_, _)> = None; let mut largest_variant: Option<(_, _)> = None; + let mut second_variant: Option<(_, _)> = None; for (i, variant) in adt.variants.iter().enumerate() { let size: u64 = variant @@ -76,44 +84,51 @@ fn check_item(&mut self, cx: &LateContext<'_, '_>, item: &Item) { let grouped = (size, (i, variant)); - update_if(&mut smallest_variant, grouped, |a, b| b.0 <= a.0); - update_if(&mut largest_variant, grouped, |a, b| b.0 >= a.0); + if grouped.0 >= largest_variant.map_or(0, |x| x.0) { + second_variant = largest_variant; + largest_variant = Some(grouped); + } } - if let (Some(smallest), Some(largest)) = (smallest_variant, largest_variant) { - let difference = largest.0 - smallest.0; + if let (Some(largest), Some(second)) = (largest_variant, second_variant) { + let difference = largest.0 - second.0; if difference > self.maximum_size_difference_allowed { let (i, variant) = largest.1; + let help_text = "consider boxing the large fields to reduce the total size of the enum"; span_lint_and_then( cx, LARGE_ENUM_VARIANT, def.variants[i].span, "large size difference between variants", - |db| { + |diag| { + diag.span_label( + def.variants[(largest.1).0].span, + &format!("this variant is {} bytes", largest.0), + ); + diag.span_note( + def.variants[(second.1).0].span, + &format!("and the second-largest variant is {} bytes:", second.0), + ); if variant.fields.len() == 1 { - let span = match def.variants[i].node.data { - VariantData::Struct(ref fields, ..) | VariantData::Tuple(ref fields, ..) => { + let span = match def.variants[i].data { + VariantData::Struct(fields, ..) | VariantData::Tuple(fields, ..) => { fields[0].ty.span }, VariantData::Unit(..) => unreachable!(), }; if let Some(snip) = snippet_opt(cx, span) { - db.span_suggestion( + diag.span_suggestion( span, - "consider boxing the large fields to reduce the total size of the \ - enum", + help_text, format!("Box<{}>", snip), Applicability::MaybeIncorrect, ); return; } } - db.span_help( - def.variants[i].span, - "consider boxing the large fields to reduce the total size of the enum", - ); + diag.span_help(def.variants[i].span, help_text); }, ); } @@ -121,16 +136,3 @@ fn check_item(&mut self, cx: &LateContext<'_, '_>, item: &Item) { } } } - -fn update_if(old: &mut Option, new: T, f: F) -where - F: Fn(&T, &T) -> bool, -{ - if let Some(ref mut val) = *old { - if f(val, &new) { - *val = new; - } - } else { - *old = Some(new); - } -}