From ad0ed39e488717323bc579585c7ec650b9757d95 Mon Sep 17 00:00:00 2001 From: Centri3 <114838443+Centri3@users.noreply.github.com> Date: Sun, 18 Jun 2023 13:45:08 -0500 Subject: [PATCH] new lint `trivial_default_constructed_types` --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + .../src/trivial_default_constructed_types.rs | 120 ++++++++++++++++++ .../trivial_default_constructed_types.fixed | 44 +++++++ tests/ui/trivial_default_constructed_types.rs | 44 +++++++ .../trivial_default_constructed_types.stderr | 52 ++++++++ 7 files changed, 264 insertions(+) create mode 100644 clippy_lints/src/trivial_default_constructed_types.rs create mode 100644 tests/ui/trivial_default_constructed_types.fixed create mode 100644 tests/ui/trivial_default_constructed_types.rs create mode 100644 tests/ui/trivial_default_constructed_types.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 3eaa0d199fd1..470ad8e680b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5226,6 +5226,7 @@ Released 2018-09-13 [`transmutes_expressible_as_ptr_casts`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmutes_expressible_as_ptr_casts [`transmuting_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmuting_null [`trim_split_whitespace`]: https://rust-lang.github.io/rust-clippy/master/index.html#trim_split_whitespace +[`trivial_default_constructed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_default_constructed_types [`trivial_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_regex [`trivially_copy_pass_by_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref [`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 7690e8f72470..da56fd373b74 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -620,6 +620,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::transmute::UNSOUND_COLLECTION_TRANSMUTE_INFO, crate::transmute::USELESS_TRANSMUTE_INFO, crate::transmute::WRONG_TRANSMUTE_INFO, + crate::trivial_default_constructed_types::TRIVIAL_DEFAULT_CONSTRUCTED_TYPES_INFO, crate::types::BORROWED_BOX_INFO, crate::types::BOX_COLLECTION_INFO, crate::types::LINKEDLIST_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 6ea329a9542b..57bf87cdd48b 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -309,6 +309,7 @@ mod to_digit_is_some; mod trailing_empty_array; mod trait_bounds; mod transmute; +mod trivial_default_constructed_types; mod types; mod undocumented_unsafe_blocks; mod unicode; @@ -1055,6 +1056,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(stack_size_threshold))); store.register_late_pass(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit)); store.register_late_pass(|_| Box::new(incorrect_impls::IncorrectImpls)); + store.register_late_pass(|_| Box::new(trivial_default_constructed_types::TrivialDefaultConstructedTypes)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/trivial_default_constructed_types.rs b/clippy_lints/src/trivial_default_constructed_types.rs new file mode 100644 index 000000000000..1a157c321b75 --- /dev/null +++ b/clippy_lints/src/trivial_default_constructed_types.rs @@ -0,0 +1,120 @@ +use clippy_utils::{diagnostics::span_lint_and_sugg, is_from_proc_macro, is_lang_item_or_ctor, last_path_segment}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, LangItem, QPath}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::{ + lint::in_external_macro, + ty::{self, Ty}, +}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::kw; + +declare_clippy_lint! { + /// ### What it does + /// Checks for types constructed by `default` that really don't need to be. + /// + /// ### Why is this bad? + /// It's harder for the reader to know what the value is, and it's an unnecessary function call. + /// + /// ### Example + /// ```rust + /// let a = A(Option::default()); + /// ``` + /// Use instead: + /// ```rust + /// let a = A(None); + /// ``` + #[clippy::version = "1.72.0"] + pub TRIVIAL_DEFAULT_CONSTRUCTED_TYPES, + pedantic, + "checks for usage of `Default::default` to construct trivial types" +} +declare_lint_pass!(TrivialDefaultConstructedTypes => [TRIVIAL_DEFAULT_CONSTRUCTED_TYPES]); + +impl<'tcx> LateLintPass<'tcx> for TrivialDefaultConstructedTypes { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { + if !in_external_macro(cx.sess(), expr.span) + && let ExprKind::Call(call, _) = expr.kind + && let ExprKind::Path(qpath) = call.kind + // `last_path_segment` ICEs if we give it a `LangItem`. + && !matches!(qpath, QPath::LangItem(..)) + && last_path_segment(&qpath).ident.name == kw::Default + { + let ret_ty = cx + .typeck_results() + .expr_ty(call) + .fn_sig(cx.tcx) + .output() + .skip_binder() + .peel_refs(); + if let Some(default) = default_value(cx, ret_ty) && !is_from_proc_macro(cx, expr) { + span_lint_and_sugg( + cx, + TRIVIAL_DEFAULT_CONSTRUCTED_TYPES, + expr.span, + "constructing a trivial type using `default`", + "try", + default.to_string(), + Applicability::MachineApplicable, + ); + } else if let ty::Tuple(fields) = ret_ty.kind() + && let Some(fields_default) = fields.iter() + .map(|field| default_value(cx, field)) + .collect::>>() + && !is_from_proc_macro(cx, expr) + { + let default = if fields.len() == 1 { + // Needs trailing comma to be a single-element tuple + fields_default[0].to_owned() + "," + } else { + fields_default.join(", ") + }; + + span_lint_and_sugg( + cx, + TRIVIAL_DEFAULT_CONSTRUCTED_TYPES, + expr.span, + &format!( + "constructing a {} using `default`", + if fields.is_empty() { "unit" } else { "trivial tuple" }, + ), + "try", + format!("({default})"), + Applicability::MachineApplicable, + ); + } else if let ty::Array(ty, len) = ret_ty.kind() + && let Some(default) = default_value(cx, *ty) + && !is_from_proc_macro(cx, expr) + { + span_lint_and_sugg( + cx, + TRIVIAL_DEFAULT_CONSTRUCTED_TYPES, + expr.span, + "constructing a trivial array using `default`", + "try", + format!("[{default}; {len}]"), + Applicability::MachineApplicable, + ); + } + } + } +} + +/// Gets the default value of `ty`. +fn default_value(cx: &LateContext<'_>, ty: Ty<'_>) -> Option<&'static str> { + match ty.kind() { + ty::Adt(def, _) => { + if is_lang_item_or_ctor(cx, def.did(), LangItem::Option) { + return Some("None"); + } + + None + }, + ty::Bool => Some("false"), + ty::Str => Some(r#""""#), + ty::Int(_) | ty::Uint(_) => Some("0"), + ty::Float(_) => Some("0.0"), + // Do not handle `ty::Char`, it's a lot less readable + _ => None, + } +} diff --git a/tests/ui/trivial_default_constructed_types.fixed b/tests/ui/trivial_default_constructed_types.fixed new file mode 100644 index 000000000000..9867d41c20f1 --- /dev/null +++ b/tests/ui/trivial_default_constructed_types.fixed @@ -0,0 +1,44 @@ +//@run-rustfix +//@aux-build:proc_macros.rs +#![allow(clippy::no_effect, unused)] +#![warn(clippy::trivial_default_constructed_types)] + +#[macro_use] +extern crate proc_macros; + +fn main() { + 0; + let x: Option = None; + let y: (usize,) = (0,); + (); + let x: [u32; 10] = [0; 10]; + let x: [f32; 1000] = [0.0; 1000]; + let x = ""; + let x = false; + // Do not lint + let x = char::default(); + + external! { + u32::default(); + let x: Option = Option::default(); + let y: (usize,) = Default::default(); + <()>::default(); + let x: [u32; 10] = Default::default(); + let x: [f32; 1000] = [Default::default(); 1000]; + let x = <&str>::default(); + let x = bool::default(); + let x = char::default(); + } + with_span! { + span + u32::default(); + let x: Option = Option::default(); + let y: (usize,) = Default::default(); + <()>::default(); + let x: [u32; 10] = Default::default(); + let x: [f32; 1000] = [Default::default(); 1000]; + let x = <&str>::default(); + let x = bool::default(); + let x = char::default(); + } +} diff --git a/tests/ui/trivial_default_constructed_types.rs b/tests/ui/trivial_default_constructed_types.rs new file mode 100644 index 000000000000..45c532479b5f --- /dev/null +++ b/tests/ui/trivial_default_constructed_types.rs @@ -0,0 +1,44 @@ +//@run-rustfix +//@aux-build:proc_macros.rs +#![allow(clippy::no_effect, unused)] +#![warn(clippy::trivial_default_constructed_types)] + +#[macro_use] +extern crate proc_macros; + +fn main() { + u32::default(); + let x: Option = Option::default(); + let y: (usize,) = Default::default(); + <()>::default(); + let x: [u32; 10] = Default::default(); + let x: [f32; 1000] = [Default::default(); 1000]; + let x = <&str>::default(); + let x = bool::default(); + // Do not lint + let x = char::default(); + + external! { + u32::default(); + let x: Option = Option::default(); + let y: (usize,) = Default::default(); + <()>::default(); + let x: [u32; 10] = Default::default(); + let x: [f32; 1000] = [Default::default(); 1000]; + let x = <&str>::default(); + let x = bool::default(); + let x = char::default(); + } + with_span! { + span + u32::default(); + let x: Option = Option::default(); + let y: (usize,) = Default::default(); + <()>::default(); + let x: [u32; 10] = Default::default(); + let x: [f32; 1000] = [Default::default(); 1000]; + let x = <&str>::default(); + let x = bool::default(); + let x = char::default(); + } +} diff --git a/tests/ui/trivial_default_constructed_types.stderr b/tests/ui/trivial_default_constructed_types.stderr new file mode 100644 index 000000000000..801cd74654c6 --- /dev/null +++ b/tests/ui/trivial_default_constructed_types.stderr @@ -0,0 +1,52 @@ +error: constructing a trivial type using `default` + --> $DIR/trivial_default_constructed_types.rs:10:5 + | +LL | u32::default(); + | ^^^^^^^^^^^^^^ help: try: `0` + | + = note: `-D clippy::trivial-default-constructed-types` implied by `-D warnings` + +error: constructing a trivial type using `default` + --> $DIR/trivial_default_constructed_types.rs:11:26 + | +LL | let x: Option = Option::default(); + | ^^^^^^^^^^^^^^^^^ help: try: `None` + +error: constructing a trivial tuple using `default` + --> $DIR/trivial_default_constructed_types.rs:12:23 + | +LL | let y: (usize,) = Default::default(); + | ^^^^^^^^^^^^^^^^^^ help: try: `(0,)` + +error: constructing a unit using `default` + --> $DIR/trivial_default_constructed_types.rs:13:5 + | +LL | <()>::default(); + | ^^^^^^^^^^^^^^^ help: try: `()` + +error: constructing a trivial array using `default` + --> $DIR/trivial_default_constructed_types.rs:14:24 + | +LL | let x: [u32; 10] = Default::default(); + | ^^^^^^^^^^^^^^^^^^ help: try: `[0; 10]` + +error: constructing a trivial type using `default` + --> $DIR/trivial_default_constructed_types.rs:15:27 + | +LL | let x: [f32; 1000] = [Default::default(); 1000]; + | ^^^^^^^^^^^^^^^^^^ help: try: `0.0` + +error: constructing a trivial type using `default` + --> $DIR/trivial_default_constructed_types.rs:16:13 + | +LL | let x = <&str>::default(); + | ^^^^^^^^^^^^^^^^^ help: try: `""` + +error: constructing a trivial type using `default` + --> $DIR/trivial_default_constructed_types.rs:17:13 + | +LL | let x = bool::default(); + | ^^^^^^^^^^^^^^^ help: try: `false` + +error: aborting due to 8 previous errors +