From ce841fe73b1476f6d4ac1495f88a567c501d01a8 Mon Sep 17 00:00:00 2001 From: "Samuel E. Moelius III" Date: Sat, 5 Mar 2022 18:35:58 -0500 Subject: [PATCH 01/50] Format with `rustfmt_if_chain` --- .../src/methods/unnecessary_to_owned.rs | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index 7916fb8e3b45c..cc9949b153f0e 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -114,7 +114,12 @@ fn check_addr_of_expr( parent.span, &format!("unnecessary use of `{}`", method_name), "use", - format!("{:&>width$}{}", "", receiver_snippet, width = n_target_refs - n_receiver_refs), + format!( + "{:&>width$}{}", + "", + receiver_snippet, + width = n_target_refs - n_receiver_refs + ), Applicability::MachineApplicable, ); return true; @@ -182,20 +187,10 @@ fn check_into_iter_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: if let Some(item_ty) = get_iterator_item_ty(cx, parent_ty); if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); then { - if unnecessary_iter_cloned::check_for_loop_iter( - cx, - parent, - method_name, - receiver, - true, - ) { + if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) { return true; } - let cloned_or_copied = if is_copy(cx, item_ty) { - "copied" - } else { - "cloned" - }; + let cloned_or_copied = if is_copy(cx, item_ty) { "copied" } else { "cloned" }; // The next suggestion may be incorrect because the removal of the `to_owned`-like // function could cause the iterator to hold a reference to a resource that is used // mutably. See https://github.com/rust-lang/rust-clippy/issues/8148. @@ -243,10 +238,11 @@ fn check_other_call_arg<'tcx>( if if trait_predicate.def_id() == deref_trait_id { if let [projection_predicate] = projection_predicates[..] { let normalized_ty = - cx.tcx.subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.term); + cx.tcx + .subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.term); implements_trait(cx, receiver_ty, deref_trait_id, &[]) - && get_associated_type(cx, receiver_ty, deref_trait_id, - "Target").map_or(false, |ty| ty::Term::Ty(ty) == normalized_ty) + && get_associated_type(cx, receiver_ty, deref_trait_id, "Target") + .map_or(false, |ty| ty::Term::Ty(ty) == normalized_ty) } else { false } @@ -254,7 +250,7 @@ fn check_other_call_arg<'tcx>( let composed_substs = compose_substs( cx, &trait_predicate.trait_ref.substs.iter().skip(1).collect::>()[..], - call_substs + call_substs, ); implements_trait(cx, receiver_ty, as_ref_trait_id, &composed_substs) } else { @@ -339,11 +335,7 @@ fn get_input_traits_and_projections<'tcx>( if let Some(arg) = substs.iter().next(); if let GenericArgKind::Type(arg_ty) = arg.unpack(); if arg_ty == input; - then { - true - } else { - false - } + then { true } else { false } } }; match predicate.kind().skip_binder() { From 1a95590faf2a81e7b7bae697c2209ba7607f0336 Mon Sep 17 00:00:00 2001 From: "Samuel E. Moelius III" Date: Sat, 5 Mar 2022 21:18:44 -0500 Subject: [PATCH 02/50] Fix #8507 --- .../src/methods/unnecessary_to_owned.rs | 10 +++- tests/ui/unnecessary_to_owned.fixed | 48 +++++++++++++++++++ tests/ui/unnecessary_to_owned.rs | 48 +++++++++++++++++++ tests/ui/unnecessary_to_owned.stderr | 8 +++- 4 files changed, 112 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index cc9949b153f0e..1555758fc4ad8 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -2,7 +2,9 @@ use super::implicit_clone::is_clone_like; use super::unnecessary_iter_cloned::{self, is_into_iter}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_opt; -use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs}; +use clippy_utils::ty::{ + contains_ty, get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs, +}; use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item}; use rustc_errors::Applicability; use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind}; @@ -260,6 +262,12 @@ fn check_other_call_arg<'tcx>( // `Target = T`. if n_refs > 0 || is_copy(cx, receiver_ty) || trait_predicate.def_id() != deref_trait_id; let n_refs = max(n_refs, if is_copy(cx, receiver_ty) { 0 } else { 1 }); + // If the trait is `AsRef` and the input type variable `T` occurs in the output type, then + // `T` must not be instantiated with a reference + // (https://github.com/rust-lang/rust-clippy/issues/8507). + if (n_refs == 0 && !receiver_ty.is_ref()) + || trait_predicate.def_id() != as_ref_trait_id + || !contains_ty(fn_sig.output(), input); if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); then { span_lint_and_sugg( diff --git a/tests/ui/unnecessary_to_owned.fixed b/tests/ui/unnecessary_to_owned.fixed index 720138db13772..38ba41ac54ecb 100644 --- a/tests/ui/unnecessary_to_owned.fixed +++ b/tests/ui/unnecessary_to_owned.fixed @@ -212,3 +212,51 @@ fn get_file_path(_file_type: &FileType) -> Result(P); + + pub trait Abstracted {} + + impl

Abstracted for Opaque

{} + + fn build

(p: P) -> Opaque

+ where + P: AsRef, + { + Opaque(p) + } + + // Should not lint. + fn test_str(s: &str) -> Box { + Box::new(build(s.to_string())) + } + + // Should not lint. + fn test_x(x: super::X) -> Box { + Box::new(build(x)) + } + + #[derive(Clone, Copy)] + struct Y(&'static str); + + impl AsRef for Y { + fn as_ref(&self) -> &str { + self.0 + } + } + + impl ToString for Y { + fn to_string(&self) -> String { + self.0.to_string() + } + } + + // Should lint because Y is copy. + fn test_y(y: Y) -> Box { + Box::new(build(y)) + } +} diff --git a/tests/ui/unnecessary_to_owned.rs b/tests/ui/unnecessary_to_owned.rs index 60b2e718f5d49..15fb7ee83e3d1 100644 --- a/tests/ui/unnecessary_to_owned.rs +++ b/tests/ui/unnecessary_to_owned.rs @@ -212,3 +212,51 @@ fn get_file_path(_file_type: &FileType) -> Result(P); + + pub trait Abstracted {} + + impl

Abstracted for Opaque

{} + + fn build

(p: P) -> Opaque

+ where + P: AsRef, + { + Opaque(p) + } + + // Should not lint. + fn test_str(s: &str) -> Box { + Box::new(build(s.to_string())) + } + + // Should not lint. + fn test_x(x: super::X) -> Box { + Box::new(build(x)) + } + + #[derive(Clone, Copy)] + struct Y(&'static str); + + impl AsRef for Y { + fn as_ref(&self) -> &str { + self.0 + } + } + + impl ToString for Y { + fn to_string(&self) -> String { + self.0.to_string() + } + } + + // Should lint because Y is copy. + fn test_y(y: Y) -> Box { + Box::new(build(y.to_string())) + } +} diff --git a/tests/ui/unnecessary_to_owned.stderr b/tests/ui/unnecessary_to_owned.stderr index 1dfc65e22e2bc..c53ce32be7757 100644 --- a/tests/ui/unnecessary_to_owned.stderr +++ b/tests/ui/unnecessary_to_owned.stderr @@ -491,5 +491,11 @@ LL - let path = match get_file_path(&t) { LL + let path = match get_file_path(t) { | -error: aborting due to 76 previous errors +error: unnecessary use of `to_string` + --> $DIR/unnecessary_to_owned.rs:260:24 + | +LL | Box::new(build(y.to_string())) + | ^^^^^^^^^^^^^ help: use: `y` + +error: aborting due to 77 previous errors From 840dc1047112294387a7cc16536315ab306c2de2 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Fri, 25 Feb 2022 17:13:53 -0800 Subject: [PATCH 03/50] Update clippy to new ExprUseVisitor delegate --- clippy_lints/src/escape.rs | 2 +- clippy_lints/src/loops/mut_range_bound.rs | 2 +- clippy_lints/src/needless_pass_by_value.rs | 2 +- clippy_utils/src/sugg.rs | 2 +- clippy_utils/src/usage.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/escape.rs b/clippy_lints/src/escape.rs index af591dd71aa1d..5974b67eb7285 100644 --- a/clippy_lints/src/escape.rs +++ b/clippy_lints/src/escape.rs @@ -154,7 +154,7 @@ impl<'a, 'tcx> Delegate<'tcx> for EscapeDelegate<'a, 'tcx> { } } - fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) { + fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind, _is_autoref: bool) { if cmt.place.projections.is_empty() { if let PlaceBase::Local(lid) = cmt.place.base { self.set.remove(&lid); diff --git a/clippy_lints/src/loops/mut_range_bound.rs b/clippy_lints/src/loops/mut_range_bound.rs index 9d8679d77c6d0..4b3d7c1ef247a 100644 --- a/clippy_lints/src/loops/mut_range_bound.rs +++ b/clippy_lints/src/loops/mut_range_bound.rs @@ -90,7 +90,7 @@ struct MutatePairDelegate<'a, 'tcx> { impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> { fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} - fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) { + fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind, _is_autoref: bool) { if bk == ty::BorrowKind::MutBorrow { if let PlaceBase::Local(id) = cmt.place.base { if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) { diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index ebfd908a6fb74..ccd355f480a62 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -332,7 +332,7 @@ impl<'tcx> euv::Delegate<'tcx> for MovedVariablesCtxt { self.move_common(cmt); } - fn borrow(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {} + fn borrow(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind, _is_autoref: bool) {} fn mutate(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId) {} diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 63c442e70085a..0e97d837ec59e 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -886,7 +886,7 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> { fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} #[allow(clippy::too_many_lines)] - fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) { + fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind, _is_autoref: bool) { if let PlaceBase::Local(id) = cmt.place.base { let map = self.cx.tcx.hir(); let span = map.span(cmt.hir_id); diff --git a/clippy_utils/src/usage.rs b/clippy_utils/src/usage.rs index 405e306359bc9..ae4ca77e48c37 100644 --- a/clippy_utils/src/usage.rs +++ b/clippy_utils/src/usage.rs @@ -64,7 +64,7 @@ impl<'tcx> MutVarsDelegate { impl<'tcx> Delegate<'tcx> for MutVarsDelegate { fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} - fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind) { + fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind, _is_autoref: bool) { if bk == ty::BorrowKind::MutBorrow { self.update(cmt); } From 05008fe1272942e07f819e526b1cfb85d431dcbd Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Thu, 10 Mar 2022 17:24:08 -0800 Subject: [PATCH 04/50] Remove is_autoref parameter --- clippy_lints/src/escape.rs | 2 +- clippy_lints/src/loops/mut_range_bound.rs | 2 +- clippy_lints/src/needless_pass_by_value.rs | 2 +- clippy_utils/src/sugg.rs | 2 +- clippy_utils/src/usage.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/escape.rs b/clippy_lints/src/escape.rs index 5974b67eb7285..af591dd71aa1d 100644 --- a/clippy_lints/src/escape.rs +++ b/clippy_lints/src/escape.rs @@ -154,7 +154,7 @@ impl<'a, 'tcx> Delegate<'tcx> for EscapeDelegate<'a, 'tcx> { } } - fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind, _is_autoref: bool) { + fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) { if cmt.place.projections.is_empty() { if let PlaceBase::Local(lid) = cmt.place.base { self.set.remove(&lid); diff --git a/clippy_lints/src/loops/mut_range_bound.rs b/clippy_lints/src/loops/mut_range_bound.rs index 4b3d7c1ef247a..9d8679d77c6d0 100644 --- a/clippy_lints/src/loops/mut_range_bound.rs +++ b/clippy_lints/src/loops/mut_range_bound.rs @@ -90,7 +90,7 @@ struct MutatePairDelegate<'a, 'tcx> { impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> { fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} - fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind, _is_autoref: bool) { + fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) { if bk == ty::BorrowKind::MutBorrow { if let PlaceBase::Local(id) = cmt.place.base { if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) { diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index ccd355f480a62..ebfd908a6fb74 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -332,7 +332,7 @@ impl<'tcx> euv::Delegate<'tcx> for MovedVariablesCtxt { self.move_common(cmt); } - fn borrow(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind, _is_autoref: bool) {} + fn borrow(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {} fn mutate(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId) {} diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 0e97d837ec59e..63c442e70085a 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -886,7 +886,7 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> { fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} #[allow(clippy::too_many_lines)] - fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind, _is_autoref: bool) { + fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) { if let PlaceBase::Local(id) = cmt.place.base { let map = self.cx.tcx.hir(); let span = map.span(cmt.hir_id); diff --git a/clippy_utils/src/usage.rs b/clippy_utils/src/usage.rs index ae4ca77e48c37..405e306359bc9 100644 --- a/clippy_utils/src/usage.rs +++ b/clippy_utils/src/usage.rs @@ -64,7 +64,7 @@ impl<'tcx> MutVarsDelegate { impl<'tcx> Delegate<'tcx> for MutVarsDelegate { fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} - fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind, _is_autoref: bool) { + fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind) { if bk == ty::BorrowKind::MutBorrow { self.update(cmt); } From d1b087fdee1024e11498e2d6c3e36d31e9371d3b Mon Sep 17 00:00:00 2001 From: flip1995 Date: Mon, 14 Mar 2022 12:02:53 +0100 Subject: [PATCH 05/50] Merge commit 'dc5423ad448877e33cca28db2f1445c9c4473c75' into clippyup --- .github/workflows/clippy.yml | 7 - .github/workflows/clippy_bors.yml | 7 - .github/workflows/clippy_dev.yml | 7 + CHANGELOG.md | 7 + Cargo.toml | 2 +- clippy_lints/src/attrs.rs | 60 +- .../src/casts/cast_slice_different_sizes.rs | 117 +++ clippy_lints/src/casts/mod.rs | 48 ++ clippy_lints/src/dbg_macro.rs | 5 + clippy_lints/src/lib.register_all.rs | 6 + clippy_lints/src/lib.register_complexity.rs | 3 + clippy_lints/src/lib.register_correctness.rs | 1 + clippy_lints/src/lib.register_internal.rs | 1 + clippy_lints/src/lib.register_lints.rs | 9 + clippy_lints/src/lib.register_perf.rs | 2 + clippy_lints/src/lib.register_restriction.rs | 1 + clippy_lints/src/lib.rs | 3 + clippy_lints/src/loops/missing_spin_loop.rs | 56 ++ clippy_lints/src/loops/mod.rs | 39 + clippy_lints/src/matches/mod.rs | 47 ++ clippy_lints/src/matches/needless_match.rs | 197 ++++++ clippy_lints/src/methods/iter_with_drain.rs | 72 ++ clippy_lints/src/methods/mod.rs | 68 +- .../src/methods/option_map_or_none.rs | 9 +- .../src/methods/unnecessary_filter_map.rs | 26 +- .../src/methods/unnecessary_iter_cloned.rs | 91 +-- clippy_lints/src/methods/utils.rs | 87 +++ clippy_lints/src/needless_pass_by_value.rs | 7 +- clippy_lints/src/only_used_in_recursion.rs | 668 ++++++++++++++++++ clippy_lints/src/question_mark.rs | 6 +- clippy_lints/src/redundant_clone.rs | 59 +- .../src/suspicious_operation_groupings.rs | 8 +- clippy_lints/src/transmute/mod.rs | 4 + clippy_lints/src/unnecessary_sort_by.rs | 47 +- clippy_lints/src/use_self.rs | 19 +- clippy_lints/src/utils/internal_lints.rs | 54 +- .../internal_lints/metadata_collector.rs | 6 +- clippy_lints/src/write.rs | 6 +- clippy_utils/src/ast_utils.rs | 54 +- clippy_utils/src/consts.rs | 9 +- clippy_utils/src/hir_utils.rs | 6 +- clippy_utils/src/msrvs.rs | 2 +- clippy_utils/src/paths.rs | 6 + clippy_utils/src/ty.rs | 6 +- rust-toolchain | 2 +- rustfmt.toml | 2 +- tests/compile-test.rs | 9 +- .../ui-internal/invalid_msrv_attr_impl.fixed | 40 ++ tests/ui-internal/invalid_msrv_attr_impl.rs | 38 + .../ui-internal/invalid_msrv_attr_impl.stderr | 32 + tests/ui/allow_attributes_without_reason.rs | 14 + .../ui/allow_attributes_without_reason.stderr | 23 + tests/ui/cast_slice_different_sizes.rs | 39 + tests/ui/cast_slice_different_sizes.stderr | 52 ++ .../no_std_main_recursion.rs | 4 +- tests/ui/dbg_macro.rs | 6 + tests/ui/dbg_macro.stderr | 20 +- tests/ui/extend_with_drain.fixed | 4 +- tests/ui/extend_with_drain.rs | 4 +- tests/ui/iter_with_drain.fixed | 56 ++ tests/ui/iter_with_drain.rs | 56 ++ tests/ui/iter_with_drain.stderr | 40 ++ tests/ui/manual_map_option.fixed | 1 + tests/ui/manual_map_option.rs | 1 + tests/ui/missing_spin_loop.fixed | 28 + tests/ui/missing_spin_loop.rs | 28 + tests/ui/missing_spin_loop.stderr | 40 ++ tests/ui/missing_spin_loop_no_std.fixed | 23 + tests/ui/missing_spin_loop_no_std.rs | 23 + tests/ui/missing_spin_loop_no_std.stderr | 10 + tests/ui/needless_match.fixed | 83 +++ tests/ui/needless_match.rs | 122 ++++ tests/ui/needless_match.stderr | 122 ++++ tests/ui/only_used_in_recursion.rs | 122 ++++ tests/ui/only_used_in_recursion.stderr | 82 +++ tests/ui/range_plus_minus_one.fixed | 2 +- tests/ui/range_plus_minus_one.rs | 2 +- .../transmutes_expressible_as_ptr_casts.fixed | 4 +- .../ui/transmutes_expressible_as_ptr_casts.rs | 4 +- ...transmutes_expressible_as_ptr_casts.stderr | 4 +- tests/ui/unnecessary_filter_map.rs | 129 ++++ tests/ui/unnecessary_filter_map.stderr | 8 +- tests/ui/unnecessary_find_map.rs | 23 + tests/ui/unnecessary_find_map.stderr | 38 + tests/ui/unnecessary_sort_by.fixed | 10 +- tests/ui/unnecessary_sort_by.rs | 2 - tests/ui/unnecessary_sort_by.stderr | 32 +- tests/ui/use_self.fixed | 25 +- tests/ui/use_self.rs | 25 +- tests/ui/use_self.stderr | 20 +- util/gh-pages/index.html | 282 ++++++-- 91 files changed, 3350 insertions(+), 331 deletions(-) create mode 100644 clippy_lints/src/casts/cast_slice_different_sizes.rs create mode 100644 clippy_lints/src/loops/missing_spin_loop.rs create mode 100644 clippy_lints/src/matches/needless_match.rs create mode 100644 clippy_lints/src/methods/iter_with_drain.rs create mode 100644 clippy_lints/src/only_used_in_recursion.rs create mode 100644 tests/ui-internal/invalid_msrv_attr_impl.fixed create mode 100644 tests/ui-internal/invalid_msrv_attr_impl.rs create mode 100644 tests/ui-internal/invalid_msrv_attr_impl.stderr create mode 100644 tests/ui/allow_attributes_without_reason.rs create mode 100644 tests/ui/allow_attributes_without_reason.stderr create mode 100644 tests/ui/cast_slice_different_sizes.rs create mode 100644 tests/ui/cast_slice_different_sizes.stderr create mode 100644 tests/ui/iter_with_drain.fixed create mode 100644 tests/ui/iter_with_drain.rs create mode 100644 tests/ui/iter_with_drain.stderr create mode 100644 tests/ui/missing_spin_loop.fixed create mode 100644 tests/ui/missing_spin_loop.rs create mode 100644 tests/ui/missing_spin_loop.stderr create mode 100644 tests/ui/missing_spin_loop_no_std.fixed create mode 100644 tests/ui/missing_spin_loop_no_std.rs create mode 100644 tests/ui/missing_spin_loop_no_std.stderr create mode 100644 tests/ui/needless_match.fixed create mode 100644 tests/ui/needless_match.rs create mode 100644 tests/ui/needless_match.stderr create mode 100644 tests/ui/only_used_in_recursion.rs create mode 100644 tests/ui/only_used_in_recursion.stderr create mode 100644 tests/ui/unnecessary_find_map.rs create mode 100644 tests/ui/unnecessary_find_map.stderr diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 116ae031bb719..cd83bc9642b60 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -74,10 +74,3 @@ jobs: run: bash .github/driver.sh env: OS: ${{ runner.os }} - - - name: Test cargo dev new lint - run: | - cargo dev new_lint --name new_early_pass --pass early - cargo dev new_lint --name new_late_pass --pass late - cargo check - git reset --hard HEAD diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_bors.yml index 989667037c1cb..f571485e6d389 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_bors.yml @@ -143,13 +143,6 @@ jobs: env: OS: ${{ runner.os }} - - name: Test cargo dev new lint - run: | - cargo dev new_lint --name new_early_pass --pass early - cargo dev new_lint --name new_late_pass --pass late - cargo check - git reset --hard HEAD - integration_build: needs: changelog runs-on: ubuntu-latest diff --git a/.github/workflows/clippy_dev.yml b/.github/workflows/clippy_dev.yml index fe8bce00fa82e..5dfab1d2ebc40 100644 --- a/.github/workflows/clippy_dev.yml +++ b/.github/workflows/clippy_dev.yml @@ -36,6 +36,13 @@ jobs: - name: Test fmt run: cargo dev fmt --check + - name: Test cargo dev new lint + run: | + cargo dev new_lint --name new_early_pass --pass early + cargo dev new_lint --name new_late_pass --pass late + cargo check + git reset --hard HEAD + # These jobs doesn't actually test anything, but they're only used to tell # bors the build completed, as there is no practical way to detect when a # workflow is successful listening to webhooks only. diff --git a/CHANGELOG.md b/CHANGELOG.md index 35983b7fb506f..2bc393d604256 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3042,6 +3042,7 @@ Released 2018-09-13 [`absurd_extreme_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons +[`allow_attributes_without_reason`]: https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes_without_reason [`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped [`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant [`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions @@ -3076,6 +3077,7 @@ Released 2018-09-13 [`cast_ptr_alignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ptr_alignment [`cast_ref_to_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ref_to_mut [`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss +[`cast_slice_different_sizes`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_different_sizes [`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8 [`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp [`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp @@ -3225,6 +3227,7 @@ Released 2018-09-13 [`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero [`iter_overeager_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_overeager_cloned [`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next +[`iter_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_with_drain [`iterator_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iterator_step_by_zero [`just_underscores_and_digits`]: https://rust-lang.github.io/rust-clippy/master/index.html#just_underscores_and_digits [`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays @@ -3297,6 +3300,7 @@ Released 2018-09-13 [`missing_inline_in_public_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_inline_in_public_items [`missing_panics_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_panics_doc [`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc +[`missing_spin_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_spin_loop [`mistyped_literal_suffixes`]: https://rust-lang.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes [`mixed_case_hex_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_case_hex_literals [`mod_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#mod_module_files @@ -3327,6 +3331,7 @@ Released 2018-09-13 [`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each [`needless_late_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init [`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes +[`needless_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_match [`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref [`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value [`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark @@ -3351,6 +3356,7 @@ Released 2018-09-13 [`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref [`octal_escapes`]: https://rust-lang.github.io/rust-clippy/master/index.html#octal_escapes [`ok_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#ok_expect +[`only_used_in_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#only_used_in_recursion [`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref [`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref [`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap @@ -3498,6 +3504,7 @@ Released 2018-09-13 [`unit_return_expecting_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_return_expecting_ord [`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast [`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map +[`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map [`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold [`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations [`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed diff --git a/Cargo.toml b/Cargo.toml index d4ca9480bec62..123af23881b62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ syn = { version = "1.0", features = ["full"] } futures = "0.3" parking_lot = "0.11.2" tokio = { version = "1", features = ["io-util"] } -num_cpus = "1.13" +rustc-semver = "1.1" [build-dependencies] rustc_tools_util = { version = "0.2", path = "rustc_tools_util" } diff --git a/clippy_lints/src/attrs.rs b/clippy_lints/src/attrs.rs index a58d12ddd6b43..d0b03c0a9c276 100644 --- a/clippy_lints/src/attrs.rs +++ b/clippy_lints/src/attrs.rs @@ -255,7 +255,38 @@ declare_clippy_lint! { "usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for attributes that allow lints without a reason. + /// + /// (This requires the `lint_reasons` feature) + /// + /// ### Why is this bad? + /// Allowing a lint should always have a reason. This reason should be documented to + /// ensure that others understand the reasoning + /// + /// ### Example + /// Bad: + /// ```rust + /// #![feature(lint_reasons)] + /// + /// #![allow(clippy::some_lint)] + /// ``` + /// + /// Good: + /// ```rust + /// #![feature(lint_reasons)] + /// + /// #![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")] + /// ``` + #[clippy::version = "1.61.0"] + pub ALLOW_ATTRIBUTES_WITHOUT_REASON, + restriction, + "ensures that all `allow` and `expect` attributes have a reason" +} + declare_lint_pass!(Attributes => [ + ALLOW_ATTRIBUTES_WITHOUT_REASON, INLINE_ALWAYS, DEPRECATED_SEMVER, USELESS_ATTRIBUTE, @@ -269,6 +300,9 @@ impl<'tcx> LateLintPass<'tcx> for Attributes { if is_lint_level(ident.name) { check_clippy_lint_names(cx, ident.name, items); } + if matches!(ident.name, sym::allow | sym::expect) { + check_lint_reason(cx, ident.name, items, attr); + } if items.is_empty() || !attr.has_name(sym::deprecated) { return; } @@ -404,6 +438,30 @@ fn check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMe } } +fn check_lint_reason(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem], attr: &'_ Attribute) { + // Check for the feature + if !cx.tcx.sess.features_untracked().lint_reasons { + return; + } + + // Check if the reason is present + if let Some(item) = items.last().and_then(NestedMetaItem::meta_item) + && let MetaItemKind::NameValue(_) = &item.kind + && item.path == sym::reason + { + return; + } + + span_lint_and_help( + cx, + ALLOW_ATTRIBUTES_WITHOUT_REASON, + attr.span, + &format!("`{}` attribute without specifying a reason", name.as_str()), + None, + "try adding a reason at the end with `, reason = \"..\"`", + ); +} + fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool { if let ItemKind::Fn(_, _, eid) = item.kind { is_relevant_expr(cx, cx.tcx.typeck_body(eid), &cx.tcx.hir().body(eid).value) @@ -659,5 +717,5 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) { } fn is_lint_level(symbol: Symbol) -> bool { - matches!(symbol, sym::allow | sym::warn | sym::deny | sym::forbid) + matches!(symbol, sym::allow | sym::expect | sym::warn | sym::deny | sym::forbid) } diff --git a/clippy_lints/src/casts/cast_slice_different_sizes.rs b/clippy_lints/src/casts/cast_slice_different_sizes.rs new file mode 100644 index 0000000000000..3608c1654d5c7 --- /dev/null +++ b/clippy_lints/src/casts/cast_slice_different_sizes.rs @@ -0,0 +1,117 @@ +use clippy_utils::{diagnostics::span_lint_and_then, meets_msrv, msrvs, source::snippet_opt}; +use if_chain::if_chain; +use rustc_ast::Mutability; +use rustc_hir::{Expr, ExprKind, Node}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, layout::LayoutOf, Ty, TypeAndMut}; +use rustc_semver::RustcVersion; + +use super::CAST_SLICE_DIFFERENT_SIZES; + +fn is_child_of_cast(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let map = cx.tcx.hir(); + if_chain! { + if let Some(parent_id) = map.find_parent_node(expr.hir_id); + if let Some(parent) = map.find(parent_id); + then { + let expr = match parent { + Node::Block(block) => { + if let Some(parent_expr) = block.expr { + parent_expr + } else { + return false; + } + }, + Node::Expr(expr) => expr, + _ => return false, + }; + + matches!(expr.kind, ExprKind::Cast(..)) + } else { + false + } + } +} + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: &Option) { + // suggestion is invalid if `ptr::slice_from_raw_parts` does not exist + if !meets_msrv(msrv.as_ref(), &msrvs::PTR_SLICE_RAW_PARTS) { + return; + } + + // if this cast is the child of another cast expression then don't emit something for it, the full + // chain will be analyzed + if is_child_of_cast(cx, expr) { + return; + } + + if let Some((from_slice_ty, to_slice_ty)) = expr_cast_chain_tys(cx, expr) { + if let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(from_slice_ty.ty), cx.layout_of(to_slice_ty.ty)) { + let from_size = from_layout.size.bytes(); + let to_size = to_layout.size.bytes(); + if from_size != to_size && from_size != 0 && to_size != 0 { + span_lint_and_then( + cx, + CAST_SLICE_DIFFERENT_SIZES, + expr.span, + &format!( + "casting between raw pointers to `[{}]` (element size {}) and `[{}]` (element size {}) does not adjust the count", + from_slice_ty, from_size, to_slice_ty, to_size, + ), + |diag| { + let cast_expr = match expr.kind { + ExprKind::Cast(cast_expr, ..) => cast_expr, + _ => unreachable!("expr should be a cast as checked by expr_cast_chain_tys"), + }; + let ptr_snippet = snippet_opt(cx, cast_expr.span).unwrap(); + + let (mutbl_fn_str, mutbl_ptr_str) = match to_slice_ty.mutbl { + Mutability::Mut => ("_mut", "mut"), + Mutability::Not => ("", "const"), + }; + let sugg = format!( + "core::ptr::slice_from_raw_parts{mutbl_fn_str}({ptr_snippet} as *{mutbl_ptr_str} {to_slice_ty}, ..)" + ); + + diag.span_suggestion( + expr.span, + &format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"), + sugg, + rustc_errors::Applicability::HasPlaceholders, + ); + }, + ); + } + } + } +} + +/// Returns the type T of the pointed to *const [T] or *mut [T] and the mutability of the slice if +/// the type is one of those slices +fn get_raw_slice_ty_mut(ty: Ty<'_>) -> Option> { + match ty.kind() { + ty::RawPtr(TypeAndMut { ty: slice_ty, mutbl }) => match slice_ty.kind() { + ty::Slice(ty) => Some(TypeAndMut { ty: *ty, mutbl: *mutbl }), + _ => None, + }, + _ => None, + } +} + +/// Returns the pair (original ptr T, final ptr U) if the expression is composed of casts +/// Returns None if the expr is not a Cast +fn expr_cast_chain_tys<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<(TypeAndMut<'tcx>, TypeAndMut<'tcx>)> { + if let ExprKind::Cast(cast_expr, _cast_to_hir_ty) = expr.peel_blocks().kind { + let cast_to = cx.typeck_results().expr_ty(expr); + let to_slice_ty = get_raw_slice_ty_mut(cast_to)?; + if let Some((inner_from_ty, _inner_to_ty)) = expr_cast_chain_tys(cx, cast_expr) { + Some((inner_from_ty, to_slice_ty)) + } else { + let cast_from = cx.typeck_results().expr_ty(cast_expr); + let from_slice_ty = get_raw_slice_ty_mut(cast_from)?; + Some((from_slice_ty, to_slice_ty)) + } + } else { + None + } +} diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index f2077c569c041..6a0eabd089d02 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -5,6 +5,7 @@ mod cast_precision_loss; mod cast_ptr_alignment; mod cast_ref_to_mut; mod cast_sign_loss; +mod cast_slice_different_sizes; mod char_lit_as_u8; mod fn_to_numeric_cast; mod fn_to_numeric_cast_any; @@ -409,6 +410,50 @@ declare_clippy_lint! { "casts from an enum type to an integral type which will truncate the value" } +declare_clippy_lint! { + /// Checks for `as` casts between raw pointers to slices with differently sized elements. + /// + /// ### Why is this bad? + /// The produced raw pointer to a slice does not update its length metadata. The produced + /// pointer will point to a different number of bytes than the original pointer because the + /// length metadata of a raw slice pointer is in elements rather than bytes. + /// Producing a slice reference from the raw pointer will either create a slice with + /// less data (which can be surprising) or create a slice with more data and cause Undefined Behavior. + /// + /// ### Example + /// // Missing data + /// ```rust + /// let a = [1_i32, 2, 3, 4]; + /// let p = &a as *const [i32] as *const [u8]; + /// unsafe { + /// println!("{:?}", &*p); + /// } + /// ``` + /// // Undefined Behavior (note: also potential alignment issues) + /// ```rust + /// let a = [1_u8, 2, 3, 4]; + /// let p = &a as *const [u8] as *const [u32]; + /// unsafe { + /// println!("{:?}", &*p); + /// } + /// ``` + /// Instead use `ptr::slice_from_raw_parts` to construct a slice from a data pointer and the correct length + /// ```rust + /// let a = [1_i32, 2, 3, 4]; + /// let old_ptr = &a as *const [i32]; + /// // The data pointer is cast to a pointer to the target `u8` not `[u8]` + /// // The length comes from the known length of 4 i32s times the 4 bytes per i32 + /// let new_ptr = core::ptr::slice_from_raw_parts(old_ptr as *const u8, 16); + /// unsafe { + /// println!("{:?}", &*new_ptr); + /// } + /// ``` + #[clippy::version = "1.60.0"] + pub CAST_SLICE_DIFFERENT_SIZES, + correctness, + "casting using `as` between raw pointers to slices of types with different sizes" +} + pub struct Casts { msrv: Option, } @@ -428,6 +473,7 @@ impl_lint_pass!(Casts => [ CAST_LOSSLESS, CAST_REF_TO_MUT, CAST_PTR_ALIGNMENT, + CAST_SLICE_DIFFERENT_SIZES, UNNECESSARY_CAST, FN_TO_NUMERIC_CAST_ANY, FN_TO_NUMERIC_CAST, @@ -478,6 +524,8 @@ impl<'tcx> LateLintPass<'tcx> for Casts { cast_ref_to_mut::check(cx, expr); cast_ptr_alignment::check(cx, expr); char_lit_as_u8::check(cx, expr); + ptr_as_ptr::check(cx, expr, &self.msrv); + cast_slice_different_sizes::check(cx, expr, &self.msrv); } extract_msrv_attr!(LateContext); diff --git a/clippy_lints/src/dbg_macro.rs b/clippy_lints/src/dbg_macro.rs index df1a4128af359..a0e5d30263316 100644 --- a/clippy_lints/src/dbg_macro.rs +++ b/clippy_lints/src/dbg_macro.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_in_test_function; use clippy_utils::macros::root_macro_call_first_node; use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; @@ -35,6 +36,10 @@ impl LateLintPass<'_> for DbgMacro { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return }; if cx.tcx.is_diagnostic_item(sym::dbg_macro, macro_call.def_id) { + // we make an exception for test code + if is_in_test_function(cx.tcx, expr.hir_id) { + return; + } let mut applicability = Applicability::MachineApplicable; let suggestion = match expr.peel_drop_temps().kind { // dbg!() diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index abfb46035376a..23bca5a0eabb2 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -25,6 +25,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(booleans::NONMINIMAL_BOOL), LintId::of(casts::CAST_ENUM_TRUNCATION), LintId::of(casts::CAST_REF_TO_MUT), + LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES), LintId::of(casts::CHAR_LIT_AS_U8), LintId::of(casts::FN_TO_NUMERIC_CAST), LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION), @@ -109,6 +110,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(loops::ITER_NEXT_LOOP), LintId::of(loops::MANUAL_FLATTEN), LintId::of(loops::MANUAL_MEMCPY), + LintId::of(loops::MISSING_SPIN_LOOP), LintId::of(loops::MUT_RANGE_BOUND), LintId::of(loops::NEEDLESS_COLLECT), LintId::of(loops::NEEDLESS_RANGE_LOOP), @@ -136,6 +138,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(matches::MATCH_OVERLAPPING_ARM), LintId::of(matches::MATCH_REF_PATS), LintId::of(matches::MATCH_SINGLE_BINDING), + LintId::of(matches::NEEDLESS_MATCH), LintId::of(matches::REDUNDANT_PATTERN_MATCHING), LintId::of(matches::SINGLE_MATCH), LintId::of(matches::WILDCARD_IN_OR_PATTERNS), @@ -163,6 +166,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::ITER_NTH_ZERO), LintId::of(methods::ITER_OVEREAGER_CLONED), LintId::of(methods::ITER_SKIP_NEXT), + LintId::of(methods::ITER_WITH_DRAIN), LintId::of(methods::MANUAL_FILTER_MAP), LintId::of(methods::MANUAL_FIND_MAP), LintId::of(methods::MANUAL_SATURATING_ARITHMETIC), @@ -189,6 +193,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::SUSPICIOUS_SPLITN), LintId::of(methods::UNINIT_ASSUMED_INIT), LintId::of(methods::UNNECESSARY_FILTER_MAP), + LintId::of(methods::UNNECESSARY_FIND_MAP), LintId::of(methods::UNNECESSARY_FOLD), LintId::of(methods::UNNECESSARY_LAZY_EVALUATIONS), LintId::of(methods::UNNECESSARY_TO_OWNED), @@ -231,6 +236,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS), LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS), LintId::of(octal_escapes::OCTAL_ESCAPES), + LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION), LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS), LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP), LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL), diff --git a/clippy_lints/src/lib.register_complexity.rs b/clippy_lints/src/lib.register_complexity.rs index bd5ff613447cd..68d6c6ce5f7da 100644 --- a/clippy_lints/src/lib.register_complexity.rs +++ b/clippy_lints/src/lib.register_complexity.rs @@ -30,6 +30,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN), LintId::of(matches::MATCH_AS_REF), LintId::of(matches::MATCH_SINGLE_BINDING), + LintId::of(matches::NEEDLESS_MATCH), LintId::of(matches::WILDCARD_IN_OR_PATTERNS), LintId::of(methods::BIND_INSTEAD_OF_MAP), LintId::of(methods::CLONE_ON_COPY), @@ -49,6 +50,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(methods::SEARCH_IS_SOME), LintId::of(methods::SKIP_WHILE_NEXT), LintId::of(methods::UNNECESSARY_FILTER_MAP), + LintId::of(methods::UNNECESSARY_FIND_MAP), LintId::of(methods::USELESS_ASREF), LintId::of(misc::SHORT_CIRCUIT_STATEMENT), LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN), @@ -63,6 +65,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD), LintId::of(no_effect::NO_EFFECT), LintId::of(no_effect::UNNECESSARY_OPERATION), + LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION), LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL), LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL), LintId::of(precedence::PRECEDENCE), diff --git a/clippy_lints/src/lib.register_correctness.rs b/clippy_lints/src/lib.register_correctness.rs index d7bf91eb69214..df63f84463dba 100644 --- a/clippy_lints/src/lib.register_correctness.rs +++ b/clippy_lints/src/lib.register_correctness.rs @@ -13,6 +13,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve LintId::of(bit_mask::INEFFECTIVE_BIT_MASK), LintId::of(booleans::LOGIC_BUG), LintId::of(casts::CAST_REF_TO_MUT), + LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES), LintId::of(copies::IFS_SAME_COND), LintId::of(copies::IF_SAME_THEN_ELSE), LintId::of(derive::DERIVE_HASH_XOR_EQ), diff --git a/clippy_lints/src/lib.register_internal.rs b/clippy_lints/src/lib.register_internal.rs index 7d4c7d2adb5b9..4778f4fdfa76c 100644 --- a/clippy_lints/src/lib.register_internal.rs +++ b/clippy_lints/src/lib.register_internal.rs @@ -14,6 +14,7 @@ store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![ LintId::of(utils::internal_lints::LINT_WITHOUT_LINT_PASS), LintId::of(utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM), LintId::of(utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE), + LintId::of(utils::internal_lints::MISSING_MSRV_ATTR_IMPL), LintId::of(utils::internal_lints::OUTER_EXPN_EXPN_DATA), LintId::of(utils::internal_lints::PRODUCE_ICE), LintId::of(utils::internal_lints::UNNECESSARY_SYMBOL_STR), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 686482671b482..1a45763a86965 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -26,6 +26,8 @@ store.register_lints(&[ #[cfg(feature = "internal")] utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE, #[cfg(feature = "internal")] + utils::internal_lints::MISSING_MSRV_ATTR_IMPL, + #[cfg(feature = "internal")] utils::internal_lints::OUTER_EXPN_EXPN_DATA, #[cfg(feature = "internal")] utils::internal_lints::PRODUCE_ICE, @@ -42,6 +44,7 @@ store.register_lints(&[ assign_ops::ASSIGN_OP_PATTERN, assign_ops::MISREFACTORED_ASSIGN_OP, async_yields_async::ASYNC_YIELDS_ASYNC, + attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON, attrs::BLANKET_CLIPPY_RESTRICTION_LINTS, attrs::DEPRECATED_CFG_ATTR, attrs::DEPRECATED_SEMVER, @@ -75,6 +78,7 @@ store.register_lints(&[ casts::CAST_PTR_ALIGNMENT, casts::CAST_REF_TO_MUT, casts::CAST_SIGN_LOSS, + casts::CAST_SLICE_DIFFERENT_SIZES, casts::CHAR_LIT_AS_U8, casts::FN_TO_NUMERIC_CAST, casts::FN_TO_NUMERIC_CAST_ANY, @@ -219,6 +223,7 @@ store.register_lints(&[ loops::ITER_NEXT_LOOP, loops::MANUAL_FLATTEN, loops::MANUAL_MEMCPY, + loops::MISSING_SPIN_LOOP, loops::MUT_RANGE_BOUND, loops::NEEDLESS_COLLECT, loops::NEEDLESS_RANGE_LOOP, @@ -255,6 +260,7 @@ store.register_lints(&[ matches::MATCH_SINGLE_BINDING, matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS, matches::MATCH_WILD_ERR_ARM, + matches::NEEDLESS_MATCH, matches::REDUNDANT_PATTERN_MATCHING, matches::REST_PAT_IN_FULLY_BOUND_STRUCTS, matches::SINGLE_MATCH, @@ -296,6 +302,7 @@ store.register_lints(&[ methods::ITER_NTH_ZERO, methods::ITER_OVEREAGER_CLONED, methods::ITER_SKIP_NEXT, + methods::ITER_WITH_DRAIN, methods::MANUAL_FILTER_MAP, methods::MANUAL_FIND_MAP, methods::MANUAL_SATURATING_ARITHMETIC, @@ -323,6 +330,7 @@ store.register_lints(&[ methods::SUSPICIOUS_SPLITN, methods::UNINIT_ASSUMED_INIT, methods::UNNECESSARY_FILTER_MAP, + methods::UNNECESSARY_FIND_MAP, methods::UNNECESSARY_FOLD, methods::UNNECESSARY_LAZY_EVALUATIONS, methods::UNNECESSARY_TO_OWNED, @@ -392,6 +400,7 @@ store.register_lints(&[ non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY, nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES, octal_escapes::OCTAL_ESCAPES, + only_used_in_recursion::ONLY_USED_IN_RECURSION, open_options::NONSENSICAL_OPEN_OPTIONS, option_env_unwrap::OPTION_ENV_UNWRAP, option_if_let_else::OPTION_IF_LET_ELSE, diff --git a/clippy_lints/src/lib.register_perf.rs b/clippy_lints/src/lib.register_perf.rs index c44ef124bfa0e..6e9c0ee33a12d 100644 --- a/clippy_lints/src/lib.register_perf.rs +++ b/clippy_lints/src/lib.register_perf.rs @@ -10,11 +10,13 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![ LintId::of(large_const_arrays::LARGE_CONST_ARRAYS), LintId::of(large_enum_variant::LARGE_ENUM_VARIANT), LintId::of(loops::MANUAL_MEMCPY), + LintId::of(loops::MISSING_SPIN_LOOP), LintId::of(loops::NEEDLESS_COLLECT), LintId::of(methods::EXPECT_FUN_CALL), LintId::of(methods::EXTEND_WITH_DRAIN), LintId::of(methods::ITER_NTH), LintId::of(methods::ITER_OVEREAGER_CLONED), + LintId::of(methods::ITER_WITH_DRAIN), LintId::of(methods::MANUAL_STR_REPEAT), LintId::of(methods::OR_FUN_CALL), LintId::of(methods::SINGLE_CHAR_PATTERN), diff --git a/clippy_lints/src/lib.register_restriction.rs b/clippy_lints/src/lib.register_restriction.rs index f89f35b885c15..4e30fc3819751 100644 --- a/clippy_lints/src/lib.register_restriction.rs +++ b/clippy_lints/src/lib.register_restriction.rs @@ -8,6 +8,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(as_conversions::AS_CONVERSIONS), LintId::of(asm_syntax::INLINE_ASM_X86_ATT_SYNTAX), LintId::of(asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX), + LintId::of(attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON), LintId::of(casts::FN_TO_NUMERIC_CAST_ANY), LintId::of(create_dir::CREATE_DIR), LintId::of(dbg_macro::DBG_MACRO), diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 230e2b2ccdfb5..504235d0d1ef0 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -318,6 +318,7 @@ mod non_octal_unix_permissions; mod non_send_fields_in_send_ty; mod nonstandard_macro_braces; mod octal_escapes; +mod only_used_in_recursion; mod open_options; mod option_env_unwrap; mod option_if_let_else; @@ -505,6 +506,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(utils::internal_lints::LintWithoutLintPass::default())); store.register_late_pass(|| Box::new(utils::internal_lints::MatchTypeOnDiagItem)); store.register_late_pass(|| Box::new(utils::internal_lints::OuterExpnDataPass)); + store.register_late_pass(|| Box::new(utils::internal_lints::MsrvAttrImpl)); } store.register_late_pass(|| Box::new(utils::author::Author)); @@ -856,6 +858,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || Box::new(borrow_as_ptr::BorrowAsPtr::new(msrv))); store.register_late_pass(move || Box::new(manual_bits::ManualBits::new(msrv))); store.register_late_pass(|| Box::new(default_union_representation::DefaultUnionRepresentation)); + store.register_late_pass(|| Box::new(only_used_in_recursion::OnlyUsedInRecursion)); store.register_late_pass(|| Box::new(dbg_macro::DbgMacro)); let cargo_ignore_publish = conf.cargo_ignore_publish; store.register_late_pass(move || { diff --git a/clippy_lints/src/loops/missing_spin_loop.rs b/clippy_lints/src/loops/missing_spin_loop.rs new file mode 100644 index 0000000000000..0696afa39225f --- /dev/null +++ b/clippy_lints/src/loops/missing_spin_loop.rs @@ -0,0 +1,56 @@ +use super::MISSING_SPIN_LOOP; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_no_std_crate; +use rustc_errors::Applicability; +use rustc_hir::{Block, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::sym; + +fn unpack_cond<'tcx>(cond: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { + match &cond.kind { + ExprKind::Block( + Block { + stmts: [], + expr: Some(e), + .. + }, + _, + ) + | ExprKind::Unary(_, e) => unpack_cond(e), + ExprKind::Binary(_, l, r) => { + let l = unpack_cond(l); + if let ExprKind::MethodCall(..) = l.kind { + l + } else { + unpack_cond(r) + } + }, + _ => cond, + } +} + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, body: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Block(Block { stmts: [], expr: None, ..}, _) = body.kind; + if let ExprKind::MethodCall(method, [callee, ..], _) = unpack_cond(cond).kind; + if [sym::load, sym::compare_exchange, sym::compare_exchange_weak].contains(&method.ident.name); + if let ty::Adt(def, _substs) = cx.typeck_results().expr_ty(callee).kind(); + if cx.tcx.is_diagnostic_item(sym::AtomicBool, def.did()); + then { + span_lint_and_sugg( + cx, + MISSING_SPIN_LOOP, + body.span, + "busy-waiting loop should at least have a spin loop hint", + "try this", + (if is_no_std_crate(cx) { + "{ core::hint::spin_loop() }" + } else { + "{ std::hint::spin_loop() }" + }).into(), + Applicability::MachineApplicable + ); + } + } +} diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 5bc32acf56ecc..f029067d36715 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -7,6 +7,7 @@ mod for_loops_over_fallibles; mod iter_next_loop; mod manual_flatten; mod manual_memcpy; +mod missing_spin_loop; mod mut_range_bound; mod needless_collect; mod needless_range_loop; @@ -560,6 +561,42 @@ declare_clippy_lint! { "for loops over `Option`s or `Result`s with a single expression can be simplified" } +declare_clippy_lint! { + /// ### What it does + /// Check for empty spin loops + /// + /// ### Why is this bad? + /// The loop body should have something like `thread::park()` or at least + /// `std::hint::spin_loop()` to avoid needlessly burning cycles and conserve + /// energy. Perhaps even better use an actual lock, if possible. + /// + /// ### Known problems + /// This lint doesn't currently trigger on `while let` or + /// `loop { match .. { .. } }` loops, which would be considered idiomatic in + /// combination with e.g. `AtomicBool::compare_exchange_weak`. + /// + /// ### Example + /// + /// ```ignore + /// use core::sync::atomic::{AtomicBool, Ordering}; + /// let b = AtomicBool::new(true); + /// // give a ref to `b` to another thread,wait for it to become false + /// while b.load(Ordering::Acquire) {}; + /// ``` + /// Use instead: + /// ```rust,no_run + ///# use core::sync::atomic::{AtomicBool, Ordering}; + ///# let b = AtomicBool::new(true); + /// while b.load(Ordering::Acquire) { + /// std::hint::spin_loop() + /// } + /// ``` + #[clippy::version = "1.59.0"] + pub MISSING_SPIN_LOOP, + perf, + "An empty busy waiting loop" +} + declare_lint_pass!(Loops => [ MANUAL_MEMCPY, MANUAL_FLATTEN, @@ -579,6 +616,7 @@ declare_lint_pass!(Loops => [ WHILE_IMMUTABLE_CONDITION, SAME_ITEM_PUSH, SINGLE_ELEMENT_LOOP, + MISSING_SPIN_LOOP, ]); impl<'tcx> LateLintPass<'tcx> for Loops { @@ -628,6 +666,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops { if let Some(higher::While { condition, body }) = higher::While::hir(expr) { while_immutable_condition::check(cx, condition, body); + missing_spin_loop::check(cx, condition, body); } needless_collect::check(expr, cx); diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 92179eb6f0e60..ff85623acf49b 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -16,6 +16,7 @@ mod match_same_arms; mod match_single_binding; mod match_wild_enum; mod match_wild_err_arm; +mod needless_match; mod overlapping_arms; mod redundant_pattern_match; mod rest_pat_in_fully_bound_struct; @@ -566,6 +567,49 @@ declare_clippy_lint! { "`match` with identical arm bodies" } +declare_clippy_lint! { + /// ### What it does + /// Checks for unnecessary `match` or match-like `if let` returns for `Option` and `Result` + /// when function signatures are the same. + /// + /// ### Why is this bad? + /// This `match` block does nothing and might not be what the coder intended. + /// + /// ### Example + /// ```rust,ignore + /// fn foo() -> Result<(), i32> { + /// match result { + /// Ok(val) => Ok(val), + /// Err(err) => Err(err), + /// } + /// } + /// + /// fn bar() -> Option { + /// if let Some(val) = option { + /// Some(val) + /// } else { + /// None + /// } + /// } + /// ``` + /// + /// Could be replaced as + /// + /// ```rust,ignore + /// fn foo() -> Result<(), i32> { + /// result + /// } + /// + /// fn bar() -> Option { + /// option + /// } + /// ``` + #[clippy::version = "1.61.0"] + pub NEEDLESS_MATCH, + complexity, + "`match` or match-like `if let` that are unnecessary" +} + #[derive(Default)] pub struct Matches { msrv: Option, @@ -599,6 +643,7 @@ impl_lint_pass!(Matches => [ REDUNDANT_PATTERN_MATCHING, MATCH_LIKE_MATCHES_MACRO, MATCH_SAME_ARMS, + NEEDLESS_MATCH, ]); impl<'tcx> LateLintPass<'tcx> for Matches { @@ -622,6 +667,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { overlapping_arms::check(cx, ex, arms); match_wild_enum::check(cx, ex, arms); match_as_ref::check(cx, ex, arms, expr); + needless_match::check_match(cx, ex, arms); if self.infallible_destructuring_match_linted { self.infallible_destructuring_match_linted = false; @@ -640,6 +686,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { match_like_matches::check(cx, expr); } redundant_pattern_match::check(cx, expr); + needless_match::check(cx, expr); } } diff --git a/clippy_lints/src/matches/needless_match.rs b/clippy_lints/src/matches/needless_match.rs new file mode 100644 index 0000000000000..76131d307d777 --- /dev/null +++ b/clippy_lints/src/matches/needless_match.rs @@ -0,0 +1,197 @@ +use super::NEEDLESS_MATCH; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{eq_expr_value, get_parent_expr, higher, is_else_clause, is_lang_ctor, peel_blocks_with_stmt}; +use rustc_errors::Applicability; +use rustc_hir::LangItem::OptionNone; +use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, Pat, PatKind, Path, PathSegment, QPath, UnOp}; +use rustc_lint::LateContext; +use rustc_span::sym; + +pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { + // This is for avoiding collision with `match_single_binding`. + if arms.len() < 2 { + return; + } + + for arm in arms { + if let PatKind::Wild = arm.pat.kind { + let ret_expr = strip_return(arm.body); + if !eq_expr_value(cx, ex, ret_expr) { + return; + } + } else if !pat_same_as_expr(arm.pat, arm.body) { + return; + } + } + + if let Some(match_expr) = get_parent_expr(cx, ex) { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + NEEDLESS_MATCH, + match_expr.span, + "this match expression is unnecessary", + "replace it with", + snippet_with_applicability(cx, ex.span, "..", &mut applicability).to_string(), + applicability, + ); + } +} + +/// Check for nop `if let` expression that assembled as unnecessary match +/// +/// ```rust,ignore +/// if let Some(a) = option { +/// Some(a) +/// } else { +/// None +/// } +/// ``` +/// OR +/// ```rust,ignore +/// if let SomeEnum::A = some_enum { +/// SomeEnum::A +/// } else if let SomeEnum::B = some_enum { +/// SomeEnum::B +/// } else { +/// some_enum +/// } +/// ``` +pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) { + if_chain! { + if let Some(ref if_let) = higher::IfLet::hir(cx, ex); + if !is_else_clause(cx.tcx, ex); + if check_if_let(cx, if_let); + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + NEEDLESS_MATCH, + ex.span, + "this if-let expression is unnecessary", + "replace it with", + snippet_with_applicability(cx, if_let.let_expr.span, "..", &mut applicability).to_string(), + applicability, + ); + } + } +} + +fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool { + if let Some(if_else) = if_let.if_else { + if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) { + return false; + } + + // Recurrsively check for each `else if let` phrase, + if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else) { + return check_if_let(cx, nested_if_let); + } + + if matches!(if_else.kind, ExprKind::Block(..)) { + let else_expr = peel_blocks_with_stmt(if_else); + let ret = strip_return(else_expr); + let let_expr_ty = cx.typeck_results().expr_ty(if_let.let_expr); + if is_type_diagnostic_item(cx, let_expr_ty, sym::Option) { + if let ExprKind::Path(ref qpath) = ret.kind { + return is_lang_ctor(cx, qpath, OptionNone) || eq_expr_value(cx, if_let.let_expr, ret); + } + } else { + return eq_expr_value(cx, if_let.let_expr, ret); + } + return true; + } + } + false +} + +/// Strip `return` keyword if the expression type is `ExprKind::Ret`. +fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> { + if let ExprKind::Ret(Some(ret)) = expr.kind { + ret + } else { + expr + } +} + +fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool { + let expr = strip_return(expr); + match (&pat.kind, &expr.kind) { + // Example: `Some(val) => Some(val)` + ( + PatKind::TupleStruct(QPath::Resolved(_, path), [first_pat, ..], _), + ExprKind::Call(call_expr, [first_param, ..]), + ) => { + if let ExprKind::Path(QPath::Resolved(_, call_path)) = call_expr.kind { + if has_identical_segments(path.segments, call_path.segments) + && has_same_non_ref_symbol(first_pat, first_param) + { + return true; + } + } + }, + // Example: `val => val`, or `ref val => *val` + (PatKind::Binding(annot, _, pat_ident, _), _) => { + let new_expr = if let ( + BindingAnnotation::Ref | BindingAnnotation::RefMut, + ExprKind::Unary(UnOp::Deref, operand_expr), + ) = (annot, &expr.kind) + { + operand_expr + } else { + expr + }; + + if let ExprKind::Path(QPath::Resolved( + _, + Path { + segments: [first_seg, ..], + .. + }, + )) = new_expr.kind + { + return pat_ident.name == first_seg.ident.name; + } + }, + // Example: `Custom::TypeA => Custom::TypeB`, or `None => None` + (PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => { + return has_identical_segments(p_path.segments, e_path.segments); + }, + // Example: `5 => 5` + (PatKind::Lit(pat_lit_expr), ExprKind::Lit(expr_spanned)) => { + if let ExprKind::Lit(pat_spanned) = &pat_lit_expr.kind { + return pat_spanned.node == expr_spanned.node; + } + }, + _ => {}, + } + + false +} + +fn has_identical_segments(left_segs: &[PathSegment<'_>], right_segs: &[PathSegment<'_>]) -> bool { + if left_segs.len() != right_segs.len() { + return false; + } + for i in 0..left_segs.len() { + if left_segs[i].ident.name != right_segs[i].ident.name { + return false; + } + } + true +} + +fn has_same_non_ref_symbol(pat: &Pat<'_>, expr: &Expr<'_>) -> bool { + if_chain! { + if let PatKind::Binding(annot, _, pat_ident, _) = pat.kind; + if !matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut); + if let ExprKind::Path(QPath::Resolved(_, Path {segments: [first_seg, ..], .. })) = expr.kind; + then { + return pat_ident.name == first_seg.ident.name; + } + } + + false +} diff --git a/clippy_lints/src/methods/iter_with_drain.rs b/clippy_lints/src/methods/iter_with_drain.rs new file mode 100644 index 0000000000000..958c3773087b6 --- /dev/null +++ b/clippy_lints/src/methods/iter_with_drain.rs @@ -0,0 +1,72 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_integer_const; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{ + higher::{self, Range}, + SpanlessEq, +}; +use rustc_ast::ast::RangeLimits; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, QPath}; +use rustc_lint::LateContext; +use rustc_span::symbol::{sym, Symbol}; +use rustc_span::Span; + +use super::ITER_WITH_DRAIN; + +const DRAIN_TYPES: &[Symbol] = &[sym::Vec, sym::VecDeque]; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) { + let ty = cx.typeck_results().expr_ty(recv).peel_refs(); + if let Some(drained_type) = DRAIN_TYPES.iter().find(|&&sym| is_type_diagnostic_item(cx, ty, sym)) { + // Refuse to emit `into_iter` suggestion on draining struct fields due + // to the strong possibility of processing unmovable field. + if let ExprKind::Field(..) = recv.kind { + return; + } + + if let Some(range) = higher::Range::hir(arg) { + let left_full = match range { + Range { start: Some(start), .. } if is_integer_const(cx, start, 0) => true, + Range { start: None, .. } => true, + _ => false, + }; + let full = left_full + && match range { + Range { + end: Some(end), + limits: RangeLimits::HalfOpen, + .. + } => { + // `x.drain(..x.len())` call + if_chain! { + if let ExprKind::MethodCall(len_path, len_args, _) = end.kind; + if len_path.ident.name == sym::len && len_args.len() == 1; + if let ExprKind::Path(QPath::Resolved(_, drain_path)) = recv.kind; + if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind; + if SpanlessEq::new(cx).eq_path(drain_path, len_path); + then { true } + else { false } + } + }, + Range { + end: None, + limits: RangeLimits::HalfOpen, + .. + } => true, + _ => false, + }; + if full { + span_lint_and_sugg( + cx, + ITER_WITH_DRAIN, + span.with_hi(expr.span.hi()), + &format!("`drain(..)` used on a `{}`", drained_type), + "try this", + "into_iter()".to_string(), + Applicability::MaybeIncorrect, + ); + } + } + } +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 3021a40fae142..5edd22cd14c7d 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -32,6 +32,7 @@ mod iter_nth; mod iter_nth_zero; mod iter_overeager_cloned; mod iter_skip_next; +mod iter_with_drain; mod iterator_step_by_zero; mod manual_saturating_arithmetic; mod manual_str_repeat; @@ -1118,6 +1119,31 @@ declare_clippy_lint! { "using `.skip(x).next()` on an iterator" } +declare_clippy_lint! { + /// ### What it does + /// Checks for use of `.drain(..)` on `Vec` and `VecDeque` for iteration. + /// + /// ### Why is this bad? + /// `.into_iter()` is simpler with better performance. + /// + /// ### Example + /// ```rust + /// # use std::collections::HashSet; + /// let mut foo = vec![0, 1, 2, 3]; + /// let bar: HashSet = foo.drain(..).collect(); + /// ``` + /// Use instead: + /// ```rust + /// # use std::collections::HashSet; + /// let foo = vec![0, 1, 2, 3]; + /// let bar: HashSet = foo.into_iter().collect(); + /// ``` + #[clippy::version = "1.61.0"] + pub ITER_WITH_DRAIN, + perf, + "replace `.drain(..)` with `.into_iter()`" +} + declare_clippy_lint! { /// ### What it does /// Checks for use of `.get().unwrap()` (or @@ -1309,7 +1335,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for `filter_map` calls which could be replaced by `filter` or `map`. + /// Checks for `filter_map` calls that could be replaced by `filter` or `map`. /// More specifically it checks if the closure provided is only performing one of the /// filter or map operations and suggests the appropriate option. /// @@ -1337,6 +1363,36 @@ declare_clippy_lint! { "using `filter_map` when a more succinct alternative exists" } +declare_clippy_lint! { + /// ### What it does + /// Checks for `find_map` calls that could be replaced by `find` or `map`. More + /// specifically it checks if the closure provided is only performing one of the + /// find or map operations and suggests the appropriate option. + /// + /// ### Why is this bad? + /// Complexity. The intent is also clearer if only a single + /// operation is being performed. + /// + /// ### Example + /// ```rust + /// let _ = (0..3).find_map(|x| if x > 2 { Some(x) } else { None }); + /// + /// // As there is no transformation of the argument this could be written as: + /// let _ = (0..3).find(|&x| x > 2); + /// ``` + /// + /// ```rust + /// let _ = (0..4).find_map(|x| Some(x + 1)); + /// + /// // As there is no conditional check on the argument this could be written as: + /// let _ = (0..4).map(|x| x + 1).next(); + /// ``` + #[clippy::version = "1.61.0"] + pub UNNECESSARY_FIND_MAP, + complexity, + "using `find_map` when a more succinct alternative exists" +} + declare_clippy_lint! { /// ### What it does /// Checks for `into_iter` calls on references which should be replaced by `iter` @@ -2017,9 +2073,11 @@ impl_lint_pass!(Methods => [ GET_UNWRAP, STRING_EXTEND_CHARS, ITER_CLONED_COLLECT, + ITER_WITH_DRAIN, USELESS_ASREF, UNNECESSARY_FOLD, UNNECESSARY_FILTER_MAP, + UNNECESSARY_FIND_MAP, INTO_ITER_ON_REF, SUSPICIOUS_MAP, UNINIT_ASSUMED_INIT, @@ -2296,6 +2354,9 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio Some(("map", [_, arg], _)) => suspicious_map::check(cx, expr, recv, arg), _ => {}, }, + ("drain", [arg]) => { + iter_with_drain::check(cx, expr, recv, span, arg); + }, ("expect", [_]) => match method_call(recv) { Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv), _ => expect_used::check(cx, expr, recv), @@ -2305,9 +2366,12 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio extend_with_drain::check(cx, expr, recv, arg); }, ("filter_map", [arg]) => { - unnecessary_filter_map::check(cx, expr, arg); + unnecessary_filter_map::check(cx, expr, arg, name); filter_map_identity::check(cx, expr, arg, span); }, + ("find_map", [arg]) => { + unnecessary_filter_map::check(cx, expr, arg, name); + }, ("flat_map", [arg]) => { flat_map_identity::check(cx, expr, arg, span); flat_map_option::check(cx, expr, arg, span); diff --git a/clippy_lints/src/methods/option_map_or_none.rs b/clippy_lints/src/methods/option_map_or_none.rs index bdf8cea120739..2a5ab6e625c11 100644 --- a/clippy_lints/src/methods/option_map_or_none.rs +++ b/clippy_lints/src/methods/option_map_or_none.rs @@ -14,10 +14,7 @@ use super::RESULT_MAP_OR_INTO_OPTION; // The expression inside a closure may or may not have surrounding braces // which causes problems when generating a suggestion. -fn reduce_unit_expression<'a>( - cx: &LateContext<'_>, - expr: &'a hir::Expr<'_>, -) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> { +fn reduce_unit_expression<'a>(expr: &'a hir::Expr<'_>) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> { match expr.kind { hir::ExprKind::Call(func, arg_char) => Some((func, arg_char)), hir::ExprKind::Block(block, _) => { @@ -25,7 +22,7 @@ fn reduce_unit_expression<'a>( (&[], Some(inner_expr)) => { // If block only contains an expression, // reduce `|x| { x + 1 }` to `|x| x + 1` - reduce_unit_expression(cx, inner_expr) + reduce_unit_expression(inner_expr) }, _ => None, } @@ -77,7 +74,7 @@ pub(super) fn check<'tcx>( if let hir::ExprKind::Closure(_, _, id, span, _) = map_arg.kind; let arg_snippet = snippet(cx, span, ".."); let body = cx.tcx.hir().body(id); - if let Some((func, [arg_char])) = reduce_unit_expression(cx, &body.value); + if let Some((func, [arg_char])) = reduce_unit_expression(&body.value); if let Some(id) = path_def_id(cx, func).and_then(|ctor_id| cx.tcx.parent(ctor_id)); if Some(id) == cx.tcx.lang_items().option_some_variant(); then { diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs index a307e33875ebb..2fda254ca98e9 100644 --- a/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -1,4 +1,6 @@ +use super::utils::clone_or_copy_needed; use clippy_utils::diagnostics::span_lint; +use clippy_utils::ty::is_copy; use clippy_utils::usage::mutated_variables; use clippy_utils::{is_lang_ctor, is_trait_method, path_to_local_id}; use rustc_hir as hir; @@ -9,8 +11,9 @@ use rustc_middle::ty; use rustc_span::sym; use super::UNNECESSARY_FILTER_MAP; +use super::UNNECESSARY_FIND_MAP; -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>) { +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, name: &str) { if !is_trait_method(cx, expr, sym::Iterator) { return; } @@ -20,6 +23,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr< let arg_id = body.params[0].pat.hir_id; let mutates_arg = mutated_variables(&body.value, cx).map_or(true, |used_mutably| used_mutably.contains(&arg_id)); + let (clone_or_copy_needed, _) = clone_or_copy_needed(cx, body.params[0].pat, &body.value); let (mut found_mapping, mut found_filtering) = check_expression(cx, arg_id, &body.value); @@ -28,13 +32,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr< found_mapping |= return_visitor.found_mapping; found_filtering |= return_visitor.found_filtering; + let in_ty = cx.typeck_results().node_type(body.params[0].hir_id); let sugg = if !found_filtering { - "map" - } else if !found_mapping && !mutates_arg { - let in_ty = cx.typeck_results().node_type(body.params[0].hir_id); + if name == "filter_map" { "map" } else { "map(..).next()" } + } else if !found_mapping && !mutates_arg && (!clone_or_copy_needed || is_copy(cx, in_ty)) { match cx.typeck_results().expr_ty(&body.value).kind() { - ty::Adt(adt, subst) if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && in_ty == subst.type_at(0) => { - "filter" + ty::Adt(adt, subst) + if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && in_ty == subst.type_at(0) => + { + if name == "filter_map" { "filter" } else { "find" } }, _ => return, } @@ -43,9 +49,13 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr< }; span_lint( cx, - UNNECESSARY_FILTER_MAP, + if name == "filter_map" { + UNNECESSARY_FILTER_MAP + } else { + UNNECESSARY_FIND_MAP + }, expr.span, - &format!("this `.filter_map` can be written more simply using `.{}`", sugg), + &format!("this `.{}` can be written more simply using `.{}`", name, sugg), ); } } diff --git a/clippy_lints/src/methods/unnecessary_iter_cloned.rs b/clippy_lints/src/methods/unnecessary_iter_cloned.rs index 65e94c5f44a9f..7a39557ad5757 100644 --- a/clippy_lints/src/methods/unnecessary_iter_cloned.rs +++ b/clippy_lints/src/methods/unnecessary_iter_cloned.rs @@ -1,14 +1,12 @@ +use super::utils::clone_or_copy_needed; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::ForLoop; use clippy_utils::source::snippet_opt; use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait}; -use clippy_utils::{fn_def_id, get_parent_expr, path_to_local_id, usage}; +use clippy_utils::{fn_def_id, get_parent_expr}; use rustc_errors::Applicability; -use rustc_hir::intravisit::{walk_expr, Visitor}; -use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind, HirId, LangItem, Mutability, Pat}; +use rustc_hir::{def_id::DefId, Expr, ExprKind, LangItem}; use rustc_lint::LateContext; -use rustc_middle::hir::nested_filter; -use rustc_middle::ty; use rustc_span::{sym, Symbol}; use super::UNNECESSARY_TO_OWNED; @@ -100,89 +98,6 @@ pub fn check_for_loop_iter( false } -/// The core logic of `check_for_loop_iter` above, this function wraps a use of -/// `CloneOrCopyVisitor`. -fn clone_or_copy_needed<'tcx>( - cx: &LateContext<'tcx>, - pat: &Pat<'tcx>, - body: &'tcx Expr<'tcx>, -) -> (bool, Vec<&'tcx Expr<'tcx>>) { - let mut visitor = CloneOrCopyVisitor { - cx, - binding_hir_ids: pat_bindings(pat), - clone_or_copy_needed: false, - addr_of_exprs: Vec::new(), - }; - visitor.visit_expr(body); - (visitor.clone_or_copy_needed, visitor.addr_of_exprs) -} - -/// Returns a vector of all `HirId`s bound by the pattern. -fn pat_bindings(pat: &Pat<'_>) -> Vec { - let mut collector = usage::ParamBindingIdCollector { - binding_hir_ids: Vec::new(), - }; - collector.visit_pat(pat); - collector.binding_hir_ids -} - -/// `clone_or_copy_needed` will be false when `CloneOrCopyVisitor` is done visiting if the only -/// operations performed on `binding_hir_ids` are: -/// * to take non-mutable references to them -/// * to use them as non-mutable `&self` in method calls -/// If any of `binding_hir_ids` is used in any other way, then `clone_or_copy_needed` will be true -/// when `CloneOrCopyVisitor` is done visiting. -struct CloneOrCopyVisitor<'cx, 'tcx> { - cx: &'cx LateContext<'tcx>, - binding_hir_ids: Vec, - clone_or_copy_needed: bool, - addr_of_exprs: Vec<&'tcx Expr<'tcx>>, -} - -impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> { - type NestedFilter = nested_filter::OnlyBodies; - - fn nested_visit_map(&mut self) -> Self::Map { - self.cx.tcx.hir() - } - - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - walk_expr(self, expr); - if self.is_binding(expr) { - if let Some(parent) = get_parent_expr(self.cx, expr) { - match parent.kind { - ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => { - self.addr_of_exprs.push(parent); - return; - }, - ExprKind::MethodCall(_, args, _) => { - if_chain! { - if args.iter().skip(1).all(|arg| !self.is_binding(arg)); - if let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id); - let method_ty = self.cx.tcx.type_of(method_def_id); - let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder(); - if matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not)); - then { - return; - } - } - }, - _ => {}, - } - } - self.clone_or_copy_needed = true; - } - } -} - -impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> { - fn is_binding(&self, expr: &Expr<'tcx>) -> bool { - self.binding_hir_ids - .iter() - .any(|hir_id| path_to_local_id(expr, *hir_id)) - } -} - /// Returns true if the named method is `IntoIterator::into_iter`. pub fn is_into_iter(cx: &LateContext<'_>, callee_def_id: DefId) -> bool { cx.tcx.lang_items().require(LangItem::IntoIterIntoIter) == Ok(callee_def_id) diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints/src/methods/utils.rs index 63c3273bd6816..3015531e84393 100644 --- a/clippy_lints/src/methods/utils.rs +++ b/clippy_lints/src/methods/utils.rs @@ -1,10 +1,14 @@ use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{get_parent_expr, path_to_local_id, usage}; use if_chain::if_chain; use rustc_ast::ast; use rustc_errors::Applicability; use rustc_hir as hir; +use rustc_hir::intravisit::{walk_expr, Visitor}; +use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, Mutability, Pat}; use rustc_lint::LateContext; +use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, Ty}; use rustc_span::symbol::sym; @@ -79,3 +83,86 @@ pub(super) fn get_hint_if_single_char_arg( } } } + +/// The core logic of `check_for_loop_iter` in `unnecessary_iter_cloned.rs`, this function wraps a +/// use of `CloneOrCopyVisitor`. +pub(super) fn clone_or_copy_needed<'tcx>( + cx: &LateContext<'tcx>, + pat: &Pat<'tcx>, + body: &'tcx Expr<'tcx>, +) -> (bool, Vec<&'tcx Expr<'tcx>>) { + let mut visitor = CloneOrCopyVisitor { + cx, + binding_hir_ids: pat_bindings(pat), + clone_or_copy_needed: false, + addr_of_exprs: Vec::new(), + }; + visitor.visit_expr(body); + (visitor.clone_or_copy_needed, visitor.addr_of_exprs) +} + +/// Returns a vector of all `HirId`s bound by the pattern. +fn pat_bindings(pat: &Pat<'_>) -> Vec { + let mut collector = usage::ParamBindingIdCollector { + binding_hir_ids: Vec::new(), + }; + collector.visit_pat(pat); + collector.binding_hir_ids +} + +/// `clone_or_copy_needed` will be false when `CloneOrCopyVisitor` is done visiting if the only +/// operations performed on `binding_hir_ids` are: +/// * to take non-mutable references to them +/// * to use them as non-mutable `&self` in method calls +/// If any of `binding_hir_ids` is used in any other way, then `clone_or_copy_needed` will be true +/// when `CloneOrCopyVisitor` is done visiting. +struct CloneOrCopyVisitor<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + binding_hir_ids: Vec, + clone_or_copy_needed: bool, + addr_of_exprs: Vec<&'tcx Expr<'tcx>>, +} + +impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> { + type NestedFilter = nested_filter::OnlyBodies; + + fn nested_visit_map(&mut self) -> Self::Map { + self.cx.tcx.hir() + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + walk_expr(self, expr); + if self.is_binding(expr) { + if let Some(parent) = get_parent_expr(self.cx, expr) { + match parent.kind { + ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => { + self.addr_of_exprs.push(parent); + return; + }, + ExprKind::MethodCall(_, args, _) => { + if_chain! { + if args.iter().skip(1).all(|arg| !self.is_binding(arg)); + if let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id); + let method_ty = self.cx.tcx.type_of(method_def_id); + let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder(); + if matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not)); + then { + return; + } + } + }, + _ => {}, + } + } + self.clone_or_copy_needed = true; + } + } +} + +impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> { + fn is_binding(&self, expr: &Expr<'tcx>) -> bool { + self.binding_hir_ids + .iter() + .any(|hir_id| path_to_local_id(expr, *hir_id)) + } +} diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index a57dc2b27986f..5eb7b0f0521e0 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -199,7 +199,12 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { let sugg = |diag: &mut Diagnostic| { if let ty::Adt(def, ..) = ty.kind() { if let Some(span) = cx.tcx.hir().span_if_local(def.did()) { - if can_type_implement_copy(cx.tcx, cx.param_env, ty, traits::ObligationCause::dummy_with_span(span)).is_ok() { + if can_type_implement_copy( + cx.tcx, + cx.param_env, + ty, + traits::ObligationCause::dummy_with_span(span), + ).is_ok() { diag.span_help(span, "consider marking this type as `Copy`"); } } diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs new file mode 100644 index 0000000000000..b828d9334ee0e --- /dev/null +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -0,0 +1,668 @@ +use std::collections::VecDeque; + +use clippy_utils::diagnostics::span_lint_and_sugg; +use itertools::{izip, Itertools}; +use rustc_ast::{walk_list, Label, Mutability}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::DefId; +use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData}; +use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; +use rustc_hir::{ + Arm, Block, Body, Expr, ExprKind, Guard, HirId, ImplicitSelfKind, Let, Local, Pat, PatKind, Path, PathSegment, + QPath, Stmt, StmtKind, TyKind, UnOp, +}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_middle::ty::{Ty, TyCtxt, TypeckResults}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::kw; +use rustc_span::symbol::Ident; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// Checks for arguments that are only used in recursion with no side-effects. + /// + /// ### Why is this bad? + /// It could contain a useless calculation and can make function simpler. + /// + /// The arguments can be involved in calculations and assignments but as long as + /// the calculations have no side-effects (function calls or mutating dereference) + /// and the assigned variables are also only in recursion, it is useless. + /// + /// ### Known problems + /// In some cases, this would not catch all useless arguments. + /// + /// ```rust + /// fn foo(a: usize, b: usize) -> usize { + /// let f = |x| x + 1; + /// + /// if a == 0 { + /// 1 + /// } else { + /// foo(a - 1, f(b)) + /// } + /// } + /// ``` + /// + /// For example, the argument `b` is only used in recursion, but the lint would not catch it. + /// + /// List of some examples that can not be caught: + /// - binary operation of non-primitive types + /// - closure usage + /// - some `break` relative operations + /// - struct pattern binding + /// + /// Also, when you recurse the function name with path segments, it is not possible to detect. + /// + /// ### Example + /// ```rust + /// fn f(a: usize, b: usize) -> usize { + /// if a == 0 { + /// 1 + /// } else { + /// f(a - 1, b + 1) + /// } + /// } + /// # fn main() { + /// # print!("{}", f(1, 1)); + /// # } + /// ``` + /// Use instead: + /// ```rust + /// fn f(a: usize) -> usize { + /// if a == 0 { + /// 1 + /// } else { + /// f(a - 1) + /// } + /// } + /// # fn main() { + /// # print!("{}", f(1)); + /// # } + /// ``` + #[clippy::version = "1.60.0"] + pub ONLY_USED_IN_RECURSION, + complexity, + "arguments that is only used in recursion can be removed" +} +declare_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION]); + +impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: FnKind<'tcx>, + decl: &'tcx rustc_hir::FnDecl<'tcx>, + body: &'tcx Body<'tcx>, + _: Span, + id: HirId, + ) { + if let FnKind::ItemFn(ident, ..) | FnKind::Method(ident, ..) = kind { + let def_id = id.owner.to_def_id(); + let data = cx.tcx.def_path(def_id).data; + + if data.len() > 1 { + match data.get(data.len() - 2) { + Some(DisambiguatedDefPathData { + data: DefPathData::Impl, + disambiguator, + }) if *disambiguator != 0 => return, + _ => {}, + } + } + + let has_self = !matches!(decl.implicit_self, ImplicitSelfKind::None); + + let ty_res = cx.typeck_results(); + let param_span = body + .params + .iter() + .flat_map(|param| { + let mut v = Vec::new(); + param.pat.each_binding(|_, hir_id, span, ident| { + v.push((hir_id, span, ident)); + }); + v + }) + .skip(if has_self { 1 } else { 0 }) + .filter(|(_, _, ident)| !ident.name.as_str().starts_with('_')) + .collect_vec(); + + let params = body.params.iter().map(|param| param.pat).collect(); + + let mut visitor = SideEffectVisit { + graph: FxHashMap::default(), + has_side_effect: FxHashSet::default(), + ret_vars: Vec::new(), + contains_side_effect: false, + break_vars: FxHashMap::default(), + params, + fn_ident: ident, + fn_def_id: def_id, + is_method: matches!(kind, FnKind::Method(..)), + has_self, + ty_res, + ty_ctx: cx.tcx, + }; + + visitor.visit_expr(&body.value); + let vars = std::mem::take(&mut visitor.ret_vars); + // this would set the return variables to side effect + visitor.add_side_effect(vars); + + let mut queue = visitor.has_side_effect.iter().copied().collect::>(); + + // a simple BFS to check all the variables that have side effect + while let Some(id) = queue.pop_front() { + if let Some(next) = visitor.graph.get(&id) { + for i in next { + if !visitor.has_side_effect.contains(i) { + visitor.has_side_effect.insert(*i); + queue.push_back(*i); + } + } + } + } + + for (id, span, ident) in param_span { + // if the variable is not used in recursion, it would be marked as unused + if !visitor.has_side_effect.contains(&id) { + let mut queue = VecDeque::new(); + let mut visited = FxHashSet::default(); + + queue.push_back(id); + + // a simple BFS to check the graph can reach to itself + // if it can't, it means the variable is never used in recursion + while let Some(id) = queue.pop_front() { + if let Some(next) = visitor.graph.get(&id) { + for i in next { + if !visited.contains(i) { + visited.insert(id); + queue.push_back(*i); + } + } + } + } + + if visited.contains(&id) { + span_lint_and_sugg( + cx, + ONLY_USED_IN_RECURSION, + span, + "parameter is only used in recursion", + "if this is intentional, prefix with an underscore", + format!("_{}", ident.name.as_str()), + Applicability::MaybeIncorrect, + ); + } + } + } + } + } +} + +pub fn is_primitive(ty: Ty<'_>) -> bool { + match ty.kind() { + ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => true, + ty::Ref(_, t, _) => is_primitive(*t), + _ => false, + } +} + +pub fn is_array(ty: Ty<'_>) -> bool { + match ty.kind() { + ty::Array(..) | ty::Slice(..) => true, + ty::Ref(_, t, _) => is_array(*t), + _ => false, + } +} + +/// This builds the graph of side effect. +/// The edge `a -> b` means if `a` has side effect, `b` will have side effect. +/// +/// There are some exmaple in following code: +/// ```rust, ignore +/// let b = 1; +/// let a = b; // a -> b +/// let (c, d) = (a, b); // c -> b, d -> b +/// +/// let e = if a == 0 { // e -> a +/// c // e -> c +/// } else { +/// d // e -> d +/// }; +/// ``` +pub struct SideEffectVisit<'tcx> { + graph: FxHashMap>, + has_side_effect: FxHashSet, + // bool for if the variable was dereferenced from mutable reference + ret_vars: Vec<(HirId, bool)>, + contains_side_effect: bool, + // break label + break_vars: FxHashMap>, + params: Vec<&'tcx Pat<'tcx>>, + fn_ident: Ident, + fn_def_id: DefId, + is_method: bool, + has_self: bool, + ty_res: &'tcx TypeckResults<'tcx>, + ty_ctx: TyCtxt<'tcx>, +} + +impl<'tcx> Visitor<'tcx> for SideEffectVisit<'tcx> { + fn visit_block(&mut self, b: &'tcx Block<'tcx>) { + b.stmts.iter().for_each(|stmt| { + self.visit_stmt(stmt); + self.ret_vars.clear(); + }); + walk_list!(self, visit_expr, b.expr); + } + + fn visit_stmt(&mut self, s: &'tcx Stmt<'tcx>) { + match s.kind { + StmtKind::Local(Local { + pat, init: Some(init), .. + }) => { + self.visit_pat_expr(pat, init, false); + self.ret_vars.clear(); + }, + StmtKind::Item(i) => { + let item = self.ty_ctx.hir().item(i); + self.visit_item(item); + self.ret_vars.clear(); + }, + StmtKind::Expr(e) | StmtKind::Semi(e) => { + self.visit_expr(e); + self.ret_vars.clear(); + }, + StmtKind::Local(_) => {}, + } + } + + fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { + match ex.kind { + ExprKind::Array(exprs) | ExprKind::Tup(exprs) => { + self.ret_vars = exprs + .iter() + .flat_map(|expr| { + self.visit_expr(expr); + std::mem::take(&mut self.ret_vars) + }) + .collect(); + }, + ExprKind::Call(callee, args) => self.visit_fn(callee, args), + ExprKind::MethodCall(path, args, _) => self.visit_method_call(path, args), + ExprKind::Binary(_, lhs, rhs) => { + self.visit_bin_op(lhs, rhs); + }, + ExprKind::Unary(op, expr) => self.visit_un_op(op, expr), + ExprKind::Let(Let { pat, init, .. }) => self.visit_pat_expr(pat, init, false), + ExprKind::If(bind, then_expr, else_expr) => { + self.visit_if(bind, then_expr, else_expr); + }, + ExprKind::Match(expr, arms, _) => self.visit_match(expr, arms), + // since analysing the closure is not easy, just set all variables in it to side-effect + ExprKind::Closure(_, _, body_id, _, _) => { + let body = self.ty_ctx.hir().body(body_id); + self.visit_body(body); + let vars = std::mem::take(&mut self.ret_vars); + self.add_side_effect(vars); + }, + ExprKind::Loop(block, label, _, _) | ExprKind::Block(block, label) => { + self.visit_block_label(block, label); + }, + ExprKind::Assign(bind, expr, _) => { + self.visit_assign(bind, expr); + }, + ExprKind::AssignOp(_, bind, expr) => { + self.visit_assign(bind, expr); + self.visit_bin_op(bind, expr); + }, + ExprKind::Field(expr, _) => { + self.visit_expr(expr); + if matches!(self.ty_res.expr_ty(expr).kind(), ty::Ref(_, _, Mutability::Mut)) { + self.ret_vars.iter_mut().for_each(|(_, b)| *b = true); + } + }, + ExprKind::Index(expr, index) => { + self.visit_expr(expr); + let mut vars = std::mem::take(&mut self.ret_vars); + self.visit_expr(index); + self.ret_vars.append(&mut vars); + + if !is_array(self.ty_res.expr_ty(expr)) { + self.add_side_effect(self.ret_vars.clone()); + } else if matches!(self.ty_res.expr_ty(expr).kind(), ty::Ref(_, _, Mutability::Mut)) { + self.ret_vars.iter_mut().for_each(|(_, b)| *b = true); + } + }, + ExprKind::Break(dest, Some(expr)) => { + self.visit_expr(expr); + if let Some(label) = dest.label { + self.break_vars + .entry(label.ident) + .or_insert(Vec::new()) + .append(&mut self.ret_vars); + } + self.contains_side_effect = true; + }, + ExprKind::Ret(Some(expr)) => { + self.visit_expr(expr); + let vars = std::mem::take(&mut self.ret_vars); + self.add_side_effect(vars); + self.contains_side_effect = true; + }, + ExprKind::Break(_, None) | ExprKind::Continue(_) | ExprKind::Ret(None) => { + self.contains_side_effect = true; + }, + ExprKind::Struct(_, exprs, expr) => { + let mut ret_vars = exprs + .iter() + .flat_map(|field| { + self.visit_expr(field.expr); + std::mem::take(&mut self.ret_vars) + }) + .collect(); + + walk_list!(self, visit_expr, expr); + self.ret_vars.append(&mut ret_vars); + }, + _ => walk_expr(self, ex), + } + } + + fn visit_path(&mut self, path: &'tcx Path<'tcx>, _id: HirId) { + if let Res::Local(id) = path.res { + self.ret_vars.push((id, false)); + } + } +} + +impl<'tcx> SideEffectVisit<'tcx> { + fn visit_assign(&mut self, lhs: &'tcx Expr<'tcx>, rhs: &'tcx Expr<'tcx>) { + // Just support array and tuple unwrapping for now. + // + // ex) `(a, b) = (c, d);` + // The graph would look like this: + // a -> c + // b -> d + // + // This would minimize the connection of the side-effect graph. + match (&lhs.kind, &rhs.kind) { + (ExprKind::Array(lhs), ExprKind::Array(rhs)) | (ExprKind::Tup(lhs), ExprKind::Tup(rhs)) => { + // if not, it is a compile error + debug_assert!(lhs.len() == rhs.len()); + izip!(*lhs, *rhs).for_each(|(lhs, rhs)| self.visit_assign(lhs, rhs)); + }, + // in other assigns, we have to connect all each other + // because they can be connected somehow + _ => { + self.visit_expr(lhs); + let lhs_vars = std::mem::take(&mut self.ret_vars); + self.visit_expr(rhs); + let rhs_vars = std::mem::take(&mut self.ret_vars); + self.connect_assign(&lhs_vars, &rhs_vars, false); + }, + } + } + + fn visit_block_label(&mut self, block: &'tcx Block<'tcx>, label: Option

- - -
+ +
+
🖌
+
    +
  • {{name}}
  • +
+
+ +
@@ -271,38 +337,62 @@

Clippy Lints

-
-
-
-

- Lint levels - (?) -

-
- -
+
+
+
+ +
-
-
-
-

- Lint groups - (?) -

-
- -
+
+ +
-
-
-
+
@@ -336,7 +426,7 @@

-
+
@@ -365,7 +455,7 @@

- Fork me on Github + Fork me on Github @@ -429,6 +519,46 @@

); }; }) + .directive('themeDropdown', function ($document) { + return { + restrict: 'A', + link: function ($scope, $element, $attr) { + $element.bind('click', function () { + $element.toggleClass('open'); + $element.addClass('open-recent'); + }); + + $document.bind('click', function () { + if (!$element.hasClass('open-recent')) { + $element.removeClass('open'); + } + $element.removeClass('open-recent'); + }) + } + } + }) + .directive('filterDropdown', function ($document) { + return { + restrict: 'A', + link: function ($scope, $element, $attr) { + $element.bind('click', function (event) { + if (event.target.closest('button')) { + $element.toggleClass('open'); + } else { + $element.addClass('open'); + } + $element.addClass('open-recent'); + }); + + $document.bind('click', function () { + if (!$element.hasClass('open-recent')) { + $element.removeClass('open'); + } + $element.removeClass('open-recent'); + }) + } + } + }) .directive('onFinishRender', function ($timeout) { return { restrict: 'A', @@ -462,6 +592,38 @@

suspicious: true, }; $scope.groups = GROUPS_FILTER_DEFAULT; + const THEMES_DEFAULT = { + light: "Light", + rust: "Rust", + coal: "Coal", + navy: "Navy", + ayu: "Ayu" + }; + $scope.themes = THEMES_DEFAULT; + + $scope.selectTheme = function (theme) { + setTheme(theme, true); + } + + $scope.toggleLevels = function (value) { + const levels = $scope.levels; + for (const key in levels) { + if (levels.hasOwnProperty(key)) { + levels[key] = value; + } + } + }; + $scope.toggleGroups = function (value) { + const groups = $scope.groups; + for (const key in groups) { + if (groups.hasOwnProperty(key)) { + groups[key] = value; + } + } + }; + $scope.selectedValuesCount = function (obj) { + return Object.values(obj).filter(x => x).length; + } $scope.byGroups = function (lint) { return $scope.groups[lint.group]; }; @@ -558,28 +720,6 @@

} } - function setupListeners() { - let themeIcon = document.getElementById("theme-icon"); - let themeMenu = document.getElementById("theme-menu"); - themeIcon.addEventListener("click", function(e) { - if (themeMenu.style.display == "none") { - themeMenu.style.display = "block"; - } else { - themeMenu.style.display = "none"; - } - }); - - let children = themeMenu.children; - for (let index = 0; index < children.length; index++) { - let child = children[index]; - child.addEventListener("click", function(e) { - setTheme(child.id, true); - }); - } - } - - setupListeners(); - function setTheme(theme, store) { let enableHighlight = false; let enableNight = false; From fc5540a4d3ae28e5dbf127b6fcddcae41baf218f Mon Sep 17 00:00:00 2001 From: xFrednet Date: Mon, 14 Mar 2022 22:02:10 +0100 Subject: [PATCH 06/50] Make `search_is_some`s suggestion `MachineApplicable` --- clippy_utils/src/sugg.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 63c442e70085a..1fc9979f3dd7d 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -808,7 +808,7 @@ pub fn deref_closure_args<'tcx>(cx: &LateContext<'_>, closure: &'tcx hir::Expr<' closure_arg_is_type_annotated_double_ref, next_pos: closure.span.lo(), suggestion_start: String::new(), - applicability: Applicability::MaybeIncorrect, + applicability: Applicability::MachineApplicable, }; let fn_def_id = cx.tcx.hir().local_def_id(closure.hir_id); From 2ee53723897407ea31a1803af9bbedf98a49f934 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Mon, 14 Mar 2022 22:39:36 +0100 Subject: [PATCH 07/50] Allow `single_component_path_imports` for all macros --- .../src/single_component_path_imports.rs | 18 +++++++---------- .../single_component_path_imports_macro.fixed | 20 ------------------- .../ui/single_component_path_imports_macro.rs | 4 ++-- ...single_component_path_imports_macro.stderr | 10 ---------- 4 files changed, 9 insertions(+), 43 deletions(-) delete mode 100644 tests/ui/single_component_path_imports_macro.fixed delete mode 100644 tests/ui/single_component_path_imports_macro.stderr diff --git a/clippy_lints/src/single_component_path_imports.rs b/clippy_lints/src/single_component_path_imports.rs index 961cdb317e76c..66b79513032f6 100644 --- a/clippy_lints/src/single_component_path_imports.rs +++ b/clippy_lints/src/single_component_path_imports.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; -use rustc_ast::{ptr::P, Crate, Item, ItemKind, MacroDef, ModKind, UseTreeKind, VisibilityKind}; +use rustc_ast::{ptr::P, Crate, Item, ItemKind, MacroDef, ModKind, UseTreeKind}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -76,14 +76,13 @@ fn check_mod(cx: &EarlyContext<'_>, items: &[P]) { ); } - for single_use in &single_use_usages { - if !imports_reused_with_self.contains(&single_use.0) { - let can_suggest = single_use.2; + for (name, span, can_suggest) in single_use_usages { + if !imports_reused_with_self.contains(&name) { if can_suggest { span_lint_and_sugg( cx, SINGLE_COMPONENT_PATH_IMPORTS, - single_use.1, + span, "this import is redundant", "remove it entirely", String::new(), @@ -93,7 +92,7 @@ fn check_mod(cx: &EarlyContext<'_>, items: &[P]) { span_lint_and_help( cx, SINGLE_COMPONENT_PATH_IMPORTS, - single_use.1, + span, "this import is redundant", None, "remove this import", @@ -124,14 +123,11 @@ fn track_uses( ItemKind::Use(use_tree) => { let segments = &use_tree.prefix.segments; - let should_report = - |name: &Symbol| !macros.contains(name) || matches!(item.vis.kind, VisibilityKind::Inherited); - // keep track of `use some_module;` usages if segments.len() == 1 { if let UseTreeKind::Simple(None, _, _) = use_tree.kind { let name = segments[0].ident.name; - if should_report(&name) { + if !macros.contains(&name) { single_use_usages.push((name, item.span, true)); } } @@ -146,7 +142,7 @@ fn track_uses( if segments.len() == 1 { if let UseTreeKind::Simple(None, _, _) = tree.0.kind { let name = segments[0].ident.name; - if should_report(&name) { + if !macros.contains(&name) { single_use_usages.push((name, tree.0.span, false)); } } diff --git a/tests/ui/single_component_path_imports_macro.fixed b/tests/ui/single_component_path_imports_macro.fixed deleted file mode 100644 index e43f5d381aaa1..0000000000000 --- a/tests/ui/single_component_path_imports_macro.fixed +++ /dev/null @@ -1,20 +0,0 @@ -// run-rustfix -#![warn(clippy::single_component_path_imports)] -#![allow(unused_imports)] - -// #7106: use statements exporting a macro within a crate should not trigger lint - -macro_rules! m1 { - () => {}; -} -pub(crate) use m1; // ok - -macro_rules! m2 { - () => {}; -} - // fail - -fn main() { - m1!(); - m2!(); -} diff --git a/tests/ui/single_component_path_imports_macro.rs b/tests/ui/single_component_path_imports_macro.rs index 3c65ca3054c69..fda294a61546b 100644 --- a/tests/ui/single_component_path_imports_macro.rs +++ b/tests/ui/single_component_path_imports_macro.rs @@ -1,8 +1,8 @@ -// run-rustfix #![warn(clippy::single_component_path_imports)] #![allow(unused_imports)] // #7106: use statements exporting a macro within a crate should not trigger lint +// #7923: normal `use` statements of macros should also not trigger the lint macro_rules! m1 { () => {}; @@ -12,7 +12,7 @@ pub(crate) use m1; // ok macro_rules! m2 { () => {}; } -use m2; // fail +use m2; // ok fn main() { m1!(); diff --git a/tests/ui/single_component_path_imports_macro.stderr b/tests/ui/single_component_path_imports_macro.stderr deleted file mode 100644 index 37d5176129ff3..0000000000000 --- a/tests/ui/single_component_path_imports_macro.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error: this import is redundant - --> $DIR/single_component_path_imports_macro.rs:15:1 - | -LL | use m2; // fail - | ^^^^^^^ help: remove it entirely - | - = note: `-D clippy::single-component-path-imports` implied by `-D warnings` - -error: aborting due to previous error - From 5ed40229acb46d11b6c37ac53bf85eea3af9bf1d Mon Sep 17 00:00:00 2001 From: Dylan DPC Date: Tue, 15 Mar 2022 02:00:08 +0100 Subject: [PATCH 08/50] fix typos --- .github/ISSUE_TEMPLATE/ice.yml | 2 +- clippy_lints/src/upper_case_acronyms.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/ice.yml b/.github/ISSUE_TEMPLATE/ice.yml index 2a5b8b3c89141..81bd9c5e03278 100644 --- a/.github/ISSUE_TEMPLATE/ice.yml +++ b/.github/ISSUE_TEMPLATE/ice.yml @@ -10,7 +10,7 @@ body: attributes: label: Summary description: | - If possible, try to provide a minimal verifiable example. You can read ["Rust Bug Minimization Patterns"][mve] for how to create smaller examples. Otherwise, provide the crate where the ICE occured. + If possible, try to provide a minimal verifiable example. You can read ["Rust Bug Minimization Patterns"][mve] for how to create smaller examples. Otherwise, provide the crate where the ICE occurred. [mve]: http://blog.pnkfx.org/blog/2019/11/18/rust-bug-minimization-patterns/ validations: diff --git a/clippy_lints/src/upper_case_acronyms.rs b/clippy_lints/src/upper_case_acronyms.rs index 7286d0a7bf99b..02bf09ed5068c 100644 --- a/clippy_lints/src/upper_case_acronyms.rs +++ b/clippy_lints/src/upper_case_acronyms.rs @@ -114,7 +114,7 @@ impl LateLintPass<'_> for UpperCaseAcronyms { check_ident(cx, &it.ident, self.upper_case_acronyms_aggressive); }, ItemKind::Enum(ref enumdef, _) => { - // check enum variants seperately because again we only want to lint on private enums and + // check enum variants separately because again we only want to lint on private enums and // the fn check_variant does not know about the vis of the enum of its variants enumdef .variants From b981748036e3fbf3ab13a33c7eaab05735f86a4d Mon Sep 17 00:00:00 2001 From: flip1995 Date: Tue, 15 Mar 2022 09:46:49 +0100 Subject: [PATCH 09/50] Move iter_with_drain to nursery --- clippy_lints/src/lib.register_all.rs | 1 - clippy_lints/src/lib.register_nursery.rs | 1 + clippy_lints/src/lib.register_perf.rs | 1 - clippy_lints/src/methods/mod.rs | 2 +- 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 23bca5a0eabb2..814e898b5b238 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -166,7 +166,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::ITER_NTH_ZERO), LintId::of(methods::ITER_OVEREAGER_CLONED), LintId::of(methods::ITER_SKIP_NEXT), - LintId::of(methods::ITER_WITH_DRAIN), LintId::of(methods::MANUAL_FILTER_MAP), LintId::of(methods::MANUAL_FIND_MAP), LintId::of(methods::MANUAL_SATURATING_ARITHMETIC), diff --git a/clippy_lints/src/lib.register_nursery.rs b/clippy_lints/src/lib.register_nursery.rs index 8d4dde42bbeca..c2fc67afba517 100644 --- a/clippy_lints/src/lib.register_nursery.rs +++ b/clippy_lints/src/lib.register_nursery.rs @@ -13,6 +13,7 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![ LintId::of(future_not_send::FUTURE_NOT_SEND), LintId::of(index_refutable_slice::INDEX_REFUTABLE_SLICE), LintId::of(let_if_seq::USELESS_LET_IF_SEQ), + LintId::of(methods::ITER_WITH_DRAIN), LintId::of(missing_const_for_fn::MISSING_CONST_FOR_FN), LintId::of(mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL), LintId::of(mutex_atomic::MUTEX_ATOMIC), diff --git a/clippy_lints/src/lib.register_perf.rs b/clippy_lints/src/lib.register_perf.rs index 6e9c0ee33a12d..f2f5c988d8f90 100644 --- a/clippy_lints/src/lib.register_perf.rs +++ b/clippy_lints/src/lib.register_perf.rs @@ -16,7 +16,6 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![ LintId::of(methods::EXTEND_WITH_DRAIN), LintId::of(methods::ITER_NTH), LintId::of(methods::ITER_OVEREAGER_CLONED), - LintId::of(methods::ITER_WITH_DRAIN), LintId::of(methods::MANUAL_STR_REPEAT), LintId::of(methods::OR_FUN_CALL), LintId::of(methods::SINGLE_CHAR_PATTERN), diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 5edd22cd14c7d..aa9f86f292c08 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -1140,7 +1140,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.61.0"] pub ITER_WITH_DRAIN, - perf, + nursery, "replace `.drain(..)` with `.into_iter()`" } From 6a3dbe4798e3966b80b16373fda75f5e4dd91384 Mon Sep 17 00:00:00 2001 From: dswij Date: Tue, 15 Mar 2022 21:29:17 +0800 Subject: [PATCH 10/50] `unnecessary_lazy_eval` show suggestions on multiline lint --- .../src/methods/unnecessary_lazy_eval.rs | 26 ++-- tests/ui/unnecessary_lazy_eval.fixed | 8 + tests/ui/unnecessary_lazy_eval.rs | 8 + tests/ui/unnecessary_lazy_eval.stderr | 145 ++++++++++++++---- .../ui/unnecessary_lazy_eval_unfixable.stderr | 12 +- 5 files changed, 148 insertions(+), 51 deletions(-) diff --git a/clippy_lints/src/methods/unnecessary_lazy_eval.rs b/clippy_lints/src/methods/unnecessary_lazy_eval.rs index 1e2765263c87d..0dd41a4dacfc5 100644 --- a/clippy_lints/src/methods/unnecessary_lazy_eval.rs +++ b/clippy_lints/src/methods/unnecessary_lazy_eval.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{eager_or_lazy, usage}; @@ -48,20 +48,16 @@ pub(super) fn check<'tcx>( Applicability::MaybeIncorrect }; - span_lint_and_sugg( - cx, - UNNECESSARY_LAZY_EVALUATIONS, - expr.span, - msg, - &format!("use `{}` instead", simplify_using), - format!( - "{0}.{1}({2})", - snippet(cx, recv.span, ".."), - simplify_using, - snippet(cx, body_expr.span, ".."), - ), - applicability, - ); + if let hir::ExprKind::MethodCall(_, _, span) = expr.kind { + span_lint_and_then(cx, UNNECESSARY_LAZY_EVALUATIONS, expr.span, msg, |diag| { + diag.span_suggestion( + span, + &format!("use `{}(..)` instead", simplify_using), + format!("{}({})", simplify_using, snippet(cx, body_expr.span, "..")), + applicability, + ); + }); + } } } } diff --git a/tests/ui/unnecessary_lazy_eval.fixed b/tests/ui/unnecessary_lazy_eval.fixed index 4ba2a0a5dbcc1..65fcdc43061bf 100644 --- a/tests/ui/unnecessary_lazy_eval.fixed +++ b/tests/ui/unnecessary_lazy_eval.fixed @@ -115,6 +115,14 @@ fn main() { let _: Result = res.or(Ok(2)); let _: Result = res.or(Ok(astronomers_pi)); let _: Result = res.or(Ok(ext_str.some_field)); + let _: Result = res. + // some lines + // some lines + // some lines + // some lines + // some lines + // some lines + or(Ok(ext_str.some_field)); // neither bind_instead_of_map nor unnecessary_lazy_eval applies here let _: Result = res.and_then(|x| Err(x)); diff --git a/tests/ui/unnecessary_lazy_eval.rs b/tests/ui/unnecessary_lazy_eval.rs index 466915217e42e..206080ed69ada 100644 --- a/tests/ui/unnecessary_lazy_eval.rs +++ b/tests/ui/unnecessary_lazy_eval.rs @@ -115,6 +115,14 @@ fn main() { let _: Result = res.or_else(|_| Ok(2)); let _: Result = res.or_else(|_| Ok(astronomers_pi)); let _: Result = res.or_else(|_| Ok(ext_str.some_field)); + let _: Result = res. + // some lines + // some lines + // some lines + // some lines + // some lines + // some lines + or_else(|_| Ok(ext_str.some_field)); // neither bind_instead_of_map nor unnecessary_lazy_eval applies here let _: Result = res.and_then(|x| Err(x)); diff --git a/tests/ui/unnecessary_lazy_eval.stderr b/tests/ui/unnecessary_lazy_eval.stderr index cc94bd5cd9e16..7e4dd7730e715 100644 --- a/tests/ui/unnecessary_lazy_eval.stderr +++ b/tests/ui/unnecessary_lazy_eval.stderr @@ -2,7 +2,9 @@ error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:35:13 | LL | let _ = opt.unwrap_or_else(|| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `opt.unwrap_or(2)` + | ^^^^-------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(2)` | = note: `-D clippy::unnecessary-lazy-evaluations` implied by `-D warnings` @@ -10,187 +12,264 @@ error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:36:13 | LL | let _ = opt.unwrap_or_else(|| astronomers_pi); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `opt.unwrap_or(astronomers_pi)` + | ^^^^--------------------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(astronomers_pi)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:37:13 | LL | let _ = opt.unwrap_or_else(|| ext_str.some_field); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `opt.unwrap_or(ext_str.some_field)` + | ^^^^------------------------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(ext_str.some_field)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:39:13 | LL | let _ = opt.and_then(|_| ext_opt); - | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `opt.and(ext_opt)` + | ^^^^--------------------- + | | + | help: use `and(..)` instead: `and(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:40:13 | LL | let _ = opt.or_else(|| ext_opt); - | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `opt.or(ext_opt)` + | ^^^^------------------- + | | + | help: use `or(..)` instead: `or(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:41:13 | LL | let _ = opt.or_else(|| None); - | ^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `opt.or(None)` + | ^^^^---------------- + | | + | help: use `or(..)` instead: `or(None)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:42:13 | LL | let _ = opt.get_or_insert_with(|| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `get_or_insert` instead: `opt.get_or_insert(2)` + | ^^^^------------------------ + | | + | help: use `get_or_insert(..)` instead: `get_or_insert(2)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:43:13 | LL | let _ = opt.ok_or_else(|| 2); - | ^^^^^^^^^^^^^^^^^^^^ help: use `ok_or` instead: `opt.ok_or(2)` + | ^^^^---------------- + | | + | help: use `ok_or(..)` instead: `ok_or(2)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:44:13 | LL | let _ = nested_tuple_opt.unwrap_or_else(|| Some((1, 2))); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `nested_tuple_opt.unwrap_or(Some((1, 2)))` + | ^^^^^^^^^^^^^^^^^------------------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(Some((1, 2)))` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:47:13 | LL | let _ = Some(10).unwrap_or_else(|| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Some(10).unwrap_or(2)` + | ^^^^^^^^^-------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(2)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:48:13 | LL | let _ = Some(10).and_then(|_| ext_opt); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `Some(10).and(ext_opt)` + | ^^^^^^^^^--------------------- + | | + | help: use `and(..)` instead: `and(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:49:28 | LL | let _: Option = None.or_else(|| ext_opt); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `None.or(ext_opt)` + | ^^^^^------------------- + | | + | help: use `or(..)` instead: `or(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:50:13 | LL | let _ = None.get_or_insert_with(|| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `get_or_insert` instead: `None.get_or_insert(2)` + | ^^^^^------------------------ + | | + | help: use `get_or_insert(..)` instead: `get_or_insert(2)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:51:35 | LL | let _: Result = None.ok_or_else(|| 2); - | ^^^^^^^^^^^^^^^^^^^^^ help: use `ok_or` instead: `None.ok_or(2)` + | ^^^^^---------------- + | | + | help: use `ok_or(..)` instead: `ok_or(2)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:52:28 | LL | let _: Option = None.or_else(|| None); - | ^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `None.or(None)` + | ^^^^^---------------- + | | + | help: use `or(..)` instead: `or(None)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:55:13 | LL | let _ = deep.0.unwrap_or_else(|| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `deep.0.unwrap_or(2)` + | ^^^^^^^-------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(2)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:56:13 | LL | let _ = deep.0.and_then(|_| ext_opt); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `deep.0.and(ext_opt)` + | ^^^^^^^--------------------- + | | + | help: use `and(..)` instead: `and(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:57:13 | LL | let _ = deep.0.or_else(|| None); - | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `deep.0.or(None)` + | ^^^^^^^---------------- + | | + | help: use `or(..)` instead: `or(None)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:58:13 | LL | let _ = deep.0.get_or_insert_with(|| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `get_or_insert` instead: `deep.0.get_or_insert(2)` + | ^^^^^^^------------------------ + | | + | help: use `get_or_insert(..)` instead: `get_or_insert(2)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:59:13 | LL | let _ = deep.0.ok_or_else(|| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `ok_or` instead: `deep.0.ok_or(2)` + | ^^^^^^^---------------- + | | + | help: use `ok_or(..)` instead: `ok_or(2)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:79:28 | LL | let _: Option = None.or_else(|| Some(3)); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `None.or(Some(3))` + | ^^^^^------------------- + | | + | help: use `or(..)` instead: `or(Some(3))` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:80:13 | LL | let _ = deep.0.or_else(|| Some(3)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `deep.0.or(Some(3))` + | ^^^^^^^------------------- + | | + | help: use `or(..)` instead: `or(Some(3))` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:81:13 | LL | let _ = opt.or_else(|| Some(3)); - | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `opt.or(Some(3))` + | ^^^^------------------- + | | + | help: use `or(..)` instead: `or(Some(3))` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval.rs:87:13 | LL | let _ = res2.unwrap_or_else(|_| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `res2.unwrap_or(2)` + | ^^^^^--------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(2)` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval.rs:88:13 | LL | let _ = res2.unwrap_or_else(|_| astronomers_pi); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `res2.unwrap_or(astronomers_pi)` + | ^^^^^---------------------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(astronomers_pi)` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval.rs:89:13 | LL | let _ = res2.unwrap_or_else(|_| ext_str.some_field); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `res2.unwrap_or(ext_str.some_field)` + | ^^^^^-------------------------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(ext_str.some_field)` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval.rs:111:35 | LL | let _: Result = res.and_then(|_| Err(2)); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `res.and(Err(2))` + | ^^^^-------------------- + | | + | help: use `and(..)` instead: `and(Err(2))` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval.rs:112:35 | LL | let _: Result = res.and_then(|_| Err(astronomers_pi)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `res.and(Err(astronomers_pi))` + | ^^^^--------------------------------- + | | + | help: use `and(..)` instead: `and(Err(astronomers_pi))` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval.rs:113:35 | LL | let _: Result = res.and_then(|_| Err(ext_str.some_field)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `res.and(Err(ext_str.some_field))` + | ^^^^------------------------------------- + | | + | help: use `and(..)` instead: `and(Err(ext_str.some_field))` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval.rs:115:35 | LL | let _: Result = res.or_else(|_| Ok(2)); - | ^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `res.or(Ok(2))` + | ^^^^------------------ + | | + | help: use `or(..)` instead: `or(Ok(2))` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval.rs:116:35 | LL | let _: Result = res.or_else(|_| Ok(astronomers_pi)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `res.or(Ok(astronomers_pi))` + | ^^^^------------------------------- + | | + | help: use `or(..)` instead: `or(Ok(astronomers_pi))` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval.rs:117:35 | LL | let _: Result = res.or_else(|_| Ok(ext_str.some_field)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `res.or(Ok(ext_str.some_field))` + | ^^^^----------------------------------- + | | + | help: use `or(..)` instead: `or(Ok(ext_str.some_field))` -error: aborting due to 32 previous errors +error: unnecessary closure used to substitute value for `Result::Err` + --> $DIR/unnecessary_lazy_eval.rs:118:35 + | +LL | let _: Result = res. + | ___________________________________^ +LL | | // some lines +LL | | // some lines +LL | | // some lines +... | +LL | | // some lines +LL | | or_else(|_| Ok(ext_str.some_field)); + | |_________----------------------------------^ + | | + | help: use `or(..)` instead: `or(Ok(ext_str.some_field))` + +error: aborting due to 33 previous errors diff --git a/tests/ui/unnecessary_lazy_eval_unfixable.stderr b/tests/ui/unnecessary_lazy_eval_unfixable.stderr index 75674b0a9d20a..20acab6e844f8 100644 --- a/tests/ui/unnecessary_lazy_eval_unfixable.stderr +++ b/tests/ui/unnecessary_lazy_eval_unfixable.stderr @@ -2,7 +2,9 @@ error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval_unfixable.rs:12:13 | LL | let _ = Ok(1).unwrap_or_else(|()| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Ok(1).unwrap_or(2)` + | ^^^^^^---------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(2)` | = note: `-D clippy::unnecessary-lazy-evaluations` implied by `-D warnings` @@ -10,13 +12,17 @@ error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval_unfixable.rs:16:13 | LL | let _ = Ok(1).unwrap_or_else(|e::E| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Ok(1).unwrap_or(2)` + | ^^^^^^------------------------ + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(2)` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval_unfixable.rs:17:13 | LL | let _ = Ok(1).unwrap_or_else(|SomeStruct { .. }| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Ok(1).unwrap_or(2)` + | ^^^^^^------------------------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(2)` error: aborting due to 3 previous errors From 5c1843dbce100715e574d1022651b6796fd99a30 Mon Sep 17 00:00:00 2001 From: Paolo Gentili Date: Tue, 15 Mar 2022 14:24:07 +0100 Subject: [PATCH 11/50] Moved lint to `restriction` --- clippy_lints/src/try_err.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/try_err.rs b/clippy_lints/src/try_err.rs index 80d6f3c633670..e108f7be12e6a 100644 --- a/clippy_lints/src/try_err.rs +++ b/clippy_lints/src/try_err.rs @@ -43,7 +43,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.38.0"] pub TRY_ERR, - style, + restriction, "return errors explicitly rather than hiding them behind a `?`" } From a39d64973320468a4b68fe089c9934dc0ef2cf7f Mon Sep 17 00:00:00 2001 From: Paolo Gentili Date: Tue, 15 Mar 2022 19:38:40 +0100 Subject: [PATCH 12/50] Lint list updated --- clippy_lints/src/lib.register_all.rs | 1 - clippy_lints/src/lib.register_restriction.rs | 1 + clippy_lints/src/lib.register_style.rs | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 814e898b5b238..653d4daa6b6fe 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -289,7 +289,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE), LintId::of(transmute::WRONG_TRANSMUTE), LintId::of(transmuting_null::TRANSMUTING_NULL), - LintId::of(try_err::TRY_ERR), LintId::of(types::BORROWED_BOX), LintId::of(types::BOX_COLLECTION), LintId::of(types::REDUNDANT_ALLOCATION), diff --git a/clippy_lints/src/lib.register_restriction.rs b/clippy_lints/src/lib.register_restriction.rs index 4e30fc3819751..6ab139b2fb67b 100644 --- a/clippy_lints/src/lib.register_restriction.rs +++ b/clippy_lints/src/lib.register_restriction.rs @@ -62,6 +62,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(strings::STRING_SLICE), LintId::of(strings::STRING_TO_STRING), LintId::of(strings::STR_TO_STRING), + LintId::of(try_err::TRY_ERR), LintId::of(types::RC_BUFFER), LintId::of(types::RC_MUTEX), LintId::of(undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS), diff --git a/clippy_lints/src/lib.register_style.rs b/clippy_lints/src/lib.register_style.rs index 05211476ff230..dcf399cf9562f 100644 --- a/clippy_lints/src/lib.register_style.rs +++ b/clippy_lints/src/lib.register_style.rs @@ -105,7 +105,6 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS), LintId::of(to_digit_is_some::TO_DIGIT_IS_SOME), - LintId::of(try_err::TRY_ERR), LintId::of(unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), LintId::of(unused_unit::UNUSED_UNIT), LintId::of(upper_case_acronyms::UPPER_CASE_ACRONYMS), From fc5cbba1a41fbfe165701d7e1e89bcca235079e6 Mon Sep 17 00:00:00 2001 From: Kisaragi <48310258+KisaragiEffective@users.noreply.github.com> Date: Wed, 16 Mar 2022 08:50:30 +0900 Subject: [PATCH 13/50] fix typo in code-block kind specifier --- clippy_lints/src/collapsible_if.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index f03f34e5a4b38..d99547f010685 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -42,7 +42,7 @@ declare_clippy_lint! { /// /// Should be written: /// - /// ```rust.ignore + /// ```rust,ignore /// if x && y { /// … /// } From 9f95e7622d325ca0f31dccdd42973751772b8522 Mon Sep 17 00:00:00 2001 From: Kisaragi <48310258+KisaragiEffective@users.noreply.github.com> Date: Wed, 16 Mar 2022 10:19:19 +0900 Subject: [PATCH 14/50] fix typo in code-block kind specifier --- clippy_lints/src/collapsible_if.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index d99547f010685..eae2723a7dac1 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -76,7 +76,7 @@ declare_clippy_lint! { /// /// Should be written: /// - /// ```rust.ignore + /// ```rust,ignore /// if x { /// … /// } else if y { From e71ac41d44fd2fdffcb94f6000ad2cffad5c61cd Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 15 Mar 2022 21:51:07 -0400 Subject: [PATCH 15/50] Fix `transmute_undefined_repr` for single element tuples --- clippy_lints/src/transmute/transmute_undefined_repr.rs | 5 +++-- tests/ui/transmute_undefined_repr.rs | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index 6edff2240920f..3cd9d2089435a 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -282,10 +282,11 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> }, ty::Tuple(args) if args.is_empty() => ReducedTy::TypeErasure, ty::Tuple(args) => { - let Some(sized_ty) = args.iter().find(|&ty| !is_zero_sized_ty(cx, ty)) else { + let mut iter = args.iter(); + let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else { return ReducedTy::OrderedFields(ty); }; - if args.iter().all(|ty| is_zero_sized_ty(cx, ty)) { + if iter.all(|ty| is_zero_sized_ty(cx, ty)) { ty = sized_ty; continue; } diff --git a/tests/ui/transmute_undefined_repr.rs b/tests/ui/transmute_undefined_repr.rs index b163d6056343d..e7a59dbed956a 100644 --- a/tests/ui/transmute_undefined_repr.rs +++ b/tests/ui/transmute_undefined_repr.rs @@ -87,5 +87,14 @@ fn main() { let _: *const [u8] = transmute(value::>()); // Ok let _: Box<[u8]> = transmute(value::<*mut [u8]>()); // Ok + + let _: Ty2 = transmute(value::<(Ty2,)>()); // Ok + let _: (Ty2,) = transmute(value::>()); // Ok + + let _: Ty2 = transmute(value::<(Ty2, ())>()); // Ok + let _: (Ty2, ()) = transmute(value::>()); // Ok + + let _: Ty2 = transmute(value::<((), Ty2)>()); // Ok + let _: ((), Ty2) = transmute(value::>()); // Ok } } From 442d4ce1c36011a8b12fb3ce9a4d70a7031fc4d5 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 15 Mar 2022 22:31:07 -0400 Subject: [PATCH 16/50] Don't lint fat pointer to `(usize, usize)` conversion in `transmute_undefined_repr` --- .../src/transmute/transmute_undefined_repr.rs | 15 ++++++++++++++- tests/ui/transmute_undefined_repr.rs | 7 +++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index 3cd9d2089435a..30059e5ba2bc9 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -4,7 +4,7 @@ use clippy_utils::ty::is_c_void; use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty::subst::Subst; -use rustc_middle::ty::{self, Ty, TypeAndMut}; +use rustc_middle::ty::{self, IntTy, Ty, TypeAndMut, UintTy}; use rustc_span::Span; #[allow(clippy::too_many_lines)] @@ -24,6 +24,7 @@ pub(super) fn check<'tcx>( to_ty: to_sub_ty, } => match reduce_ty(cx, to_sub_ty) { ReducedTy::IntArray | ReducedTy::TypeErasure => break, + ReducedTy::UnorderedFields(ty) if is_size_pair(ty) => break, ReducedTy::Ref(to_sub_ty) => { from_ty = unsized_ty; to_ty = to_sub_ty; @@ -49,6 +50,7 @@ pub(super) fn check<'tcx>( from_ty: from_sub_ty, } => match reduce_ty(cx, from_sub_ty) { ReducedTy::IntArray | ReducedTy::TypeErasure => break, + ReducedTy::UnorderedFields(ty) if is_size_pair(ty) => break, ReducedTy::Ref(from_sub_ty) => { from_ty = from_sub_ty; to_ty = unsized_ty; @@ -333,3 +335,14 @@ fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { } } } + +fn is_size_pair(ty: Ty<'_>) -> bool { + if let ty::Tuple(tys) = *ty.kind() + && let [ty1, ty2] = &**tys + { + matches!(ty1.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize)) + && matches!(ty2.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize)) + } else { + false + } +} diff --git a/tests/ui/transmute_undefined_repr.rs b/tests/ui/transmute_undefined_repr.rs index e7a59dbed956a..13d8cb10e9846 100644 --- a/tests/ui/transmute_undefined_repr.rs +++ b/tests/ui/transmute_undefined_repr.rs @@ -96,5 +96,12 @@ fn main() { let _: Ty2 = transmute(value::<((), Ty2)>()); // Ok let _: ((), Ty2) = transmute(value::>()); // Ok + + let _: (usize, usize) = transmute(value::<&[u8]>()); // Ok + let _: &[u8] = transmute(value::<(usize, usize)>()); // Ok + + trait Trait {} + let _: (isize, isize) = transmute(value::<&dyn Trait>()); // Ok + let _: &dyn Trait = transmute(value::<(isize, isize)>()); // Ok } } From 7fd52b5e3ce01fa856ca79693e7d5bdb7633fd03 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 15 Mar 2022 22:33:26 -0400 Subject: [PATCH 17/50] Small simplification to `transmute_undefined_repr` --- .../src/transmute/transmute_undefined_repr.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index 30059e5ba2bc9..f0aa3a2fa2868 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -23,7 +23,7 @@ pub(super) fn check<'tcx>( unsized_ty, to_ty: to_sub_ty, } => match reduce_ty(cx, to_sub_ty) { - ReducedTy::IntArray | ReducedTy::TypeErasure => break, + ReducedTy::TypeErasure => break, ReducedTy::UnorderedFields(ty) if is_size_pair(ty) => break, ReducedTy::Ref(to_sub_ty) => { from_ty = unsized_ty; @@ -49,7 +49,7 @@ pub(super) fn check<'tcx>( unsized_ty, from_ty: from_sub_ty, } => match reduce_ty(cx, from_sub_ty) { - ReducedTy::IntArray | ReducedTy::TypeErasure => break, + ReducedTy::TypeErasure => break, ReducedTy::UnorderedFields(ty) if is_size_pair(ty) => break, ReducedTy::Ref(from_sub_ty) => { from_ty = from_sub_ty; @@ -125,8 +125,7 @@ pub(super) fn check<'tcx>( from_ty: from_sub_ty, to_ty: to_sub_ty, } => match (reduce_ty(cx, from_sub_ty), reduce_ty(cx, to_sub_ty)) { - (ReducedTy::IntArray | ReducedTy::TypeErasure, _) - | (_, ReducedTy::IntArray | ReducedTy::TypeErasure) => return false, + (ReducedTy::TypeErasure, _) | (_, ReducedTy::TypeErasure) => return false, (ReducedTy::UnorderedFields(from_ty), ReducedTy::UnorderedFields(to_ty)) if from_ty != to_ty => { span_lint_and_then( cx, @@ -265,9 +264,6 @@ enum ReducedTy<'tcx> { UnorderedFields(Ty<'tcx>), /// The type is a reference to the contained type. Ref(Ty<'tcx>), - /// The type is an array of a primitive integer type. These can be used as storage for a value - /// of another type. - IntArray, /// Any other type. Other(Ty<'tcx>), } @@ -277,7 +273,7 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> loop { ty = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty).unwrap_or(ty); return match *ty.kind() { - ty::Array(sub_ty, _) if matches!(sub_ty.kind(), ty::Int(_) | ty::Uint(_)) => ReducedTy::IntArray, + ty::Array(sub_ty, _) if matches!(sub_ty.kind(), ty::Int(_) | ty::Uint(_)) => ReducedTy::TypeErasure, ty::Array(sub_ty, _) | ty::Slice(sub_ty) => { ty = sub_ty; continue; From 911e10562a0274d6baab1064a903f3aefef14070 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 15 Mar 2022 22:38:15 -0400 Subject: [PATCH 18/50] Don't lint `transmute_undefined_repr` on unions. --- clippy_lints/src/transmute/transmute_undefined_repr.rs | 2 ++ tests/ui/transmute_undefined_repr.rs | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index f0aa3a2fa2868..4922c40bcefcd 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -312,6 +312,8 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> ty::Adt(def, _) if def.is_enum() && (def.variants().is_empty() || is_c_void(cx, ty)) => { ReducedTy::TypeErasure }, + // TODO: Check if the conversion to or from at least one of a union's fields is valid. + ty::Adt(def, _) if def.is_union() => ReducedTy::TypeErasure, ty::Foreign(_) => ReducedTy::TypeErasure, ty::Ref(_, ty, _) => ReducedTy::Ref(ty), ty::RawPtr(ty) => ReducedTy::Ref(ty.ty), diff --git a/tests/ui/transmute_undefined_repr.rs b/tests/ui/transmute_undefined_repr.rs index 13d8cb10e9846..fabe0e5bc4d7f 100644 --- a/tests/ui/transmute_undefined_repr.rs +++ b/tests/ui/transmute_undefined_repr.rs @@ -2,7 +2,7 @@ #![allow(clippy::unit_arg, clippy::transmute_ptr_to_ref)] use core::ffi::c_void; -use core::mem::{size_of, transmute}; +use core::mem::{size_of, transmute, MaybeUninit}; fn value() -> T { unimplemented!() @@ -103,5 +103,8 @@ fn main() { trait Trait {} let _: (isize, isize) = transmute(value::<&dyn Trait>()); // Ok let _: &dyn Trait = transmute(value::<(isize, isize)>()); // Ok + + let _: MaybeUninit> = transmute(value::>()); // Ok + let _: Ty2 = transmute(value::>>()); // Ok } } From 47c9ed697b4041fca4488922ee5614ad98bfeec2 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 15 Mar 2022 22:47:18 -0400 Subject: [PATCH 19/50] Fix checking transmutes with adjusted types --- clippy_lints/src/transmute/mod.rs | 3 ++- tests/ui/transmute_undefined_repr.rs | 2 ++ tests/ui/transmutes_expressible_as_ptr_casts.stderr | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index 23cb9d40dfdc9..02569bd3a476e 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -415,7 +415,8 @@ impl<'tcx> LateLintPass<'tcx> for Transmute { // And see https://github.com/rust-lang/rust/issues/51911 for dereferencing raw pointers. let const_context = in_constant(cx, e.hir_id); - let from_ty = cx.typeck_results().expr_ty(arg); + let from_ty = cx.typeck_results().expr_ty_adjusted(arg); + // Adjustments for `to_ty` happen after the call to `transmute`, so don't use them. let to_ty = cx.typeck_results().expr_ty(e); // If useless_transmute is triggered, the other lints can be skipped. diff --git a/tests/ui/transmute_undefined_repr.rs b/tests/ui/transmute_undefined_repr.rs index fabe0e5bc4d7f..7cc03b89fe0ec 100644 --- a/tests/ui/transmute_undefined_repr.rs +++ b/tests/ui/transmute_undefined_repr.rs @@ -106,5 +106,7 @@ fn main() { let _: MaybeUninit> = transmute(value::>()); // Ok let _: Ty2 = transmute(value::>>()); // Ok + + let _: Ty<&[u32]> = transmute::<&[u32], _>(value::<&Vec>()); // Ok } } diff --git a/tests/ui/transmutes_expressible_as_ptr_casts.stderr b/tests/ui/transmutes_expressible_as_ptr_casts.stderr index d9b64a0ed7b02..de9418c8d1adc 100644 --- a/tests/ui/transmutes_expressible_as_ptr_casts.stderr +++ b/tests/ui/transmutes_expressible_as_ptr_casts.stderr @@ -34,13 +34,13 @@ error: transmute from a reference to a pointer LL | let _array_ptr_transmute = unsafe { transmute::<&[i32; 4], *const [i32; 4]>(array_ref) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `array_ref as *const [i32; 4]` -error: transmute from `fn(usize) -> u8 {main::foo}` to `*const usize` which could be expressed as a pointer cast instead +error: transmute from `fn(usize) -> u8` to `*const usize` which could be expressed as a pointer cast instead --> $DIR/transmutes_expressible_as_ptr_casts.rs:48:41 | LL | let _usize_ptr_transmute = unsafe { transmute:: u8, *const usize>(foo) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `foo as *const usize` -error: transmute from `fn(usize) -> u8 {main::foo}` to `usize` which could be expressed as a pointer cast instead +error: transmute from `fn(usize) -> u8` to `usize` which could be expressed as a pointer cast instead --> $DIR/transmutes_expressible_as_ptr_casts.rs:52:49 | LL | let _usize_from_fn_ptr_transmute = unsafe { transmute:: u8, usize>(foo) }; From 4beda1be708904fc2fc43e513c94b32497416db8 Mon Sep 17 00:00:00 2001 From: codehorseman Date: Wed, 16 Mar 2022 20:12:30 +0800 Subject: [PATCH 20/50] resolve the conflict in compiler/rustc_session/src/parse.rs Signed-off-by: codehorseman --- clippy_lints/src/copies.rs | 2 +- clippy_lints/src/fallible_impl_from.rs | 6 +++--- clippy_lints/src/float_equality_without_abs.rs | 6 +++--- clippy_lints/src/only_used_in_recursion.rs | 2 +- clippy_lints/src/suspicious_operation_groupings.rs | 10 +++++----- clippy_lints/src/trailing_empty_array.rs | 2 +- clippy_lints/src/trait_bounds.rs | 2 +- clippy_lints/src/unwrap_in_result.rs | 12 ++++++------ tests/lint_message_convention.rs | 2 +- tests/ui/manual_memcpy/with_loop_counters.rs | 2 +- tests/workspace.rs | 2 +- 11 files changed, 24 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index a20aa12c9ff47..e6a0162fd0272 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -126,7 +126,7 @@ declare_clippy_lint! { /// Duplicate code is less maintainable. /// /// ### Known problems - /// * The lint doesn't check if the moved expressions modify values that are beeing used in + /// * The lint doesn't check if the moved expressions modify values that are being used in /// the if condition. The suggestion can in that case modify the behavior of the program. /// See [rust-clippy#7452](https://github.com/rust-lang/rust-clippy/issues/7452) /// diff --git a/clippy_lints/src/fallible_impl_from.rs b/clippy_lints/src/fallible_impl_from.rs index 574678b554211..088d9996516e8 100644 --- a/clippy_lints/src/fallible_impl_from.rs +++ b/clippy_lints/src/fallible_impl_from.rs @@ -86,9 +86,9 @@ fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_items: &[h // check for `unwrap` if let Some(arglists) = method_chain_args(expr, &["unwrap"]) { - let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs(); - if is_type_diagnostic_item(self.lcx, reciever_ty, sym::Option) - || is_type_diagnostic_item(self.lcx, reciever_ty, sym::Result) + let receiver_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs(); + if is_type_diagnostic_item(self.lcx, receiver_ty, sym::Option) + || is_type_diagnostic_item(self.lcx, receiver_ty, sym::Result) { self.result.push(expr.span); } diff --git a/clippy_lints/src/float_equality_without_abs.rs b/clippy_lints/src/float_equality_without_abs.rs index ca8886228de26..98aee7592ae80 100644 --- a/clippy_lints/src/float_equality_without_abs.rs +++ b/clippy_lints/src/float_equality_without_abs.rs @@ -20,7 +20,7 @@ declare_clippy_lint! { /// /// ### Known problems /// If the user can ensure that b is larger than a, the `.abs()` is - /// technically unneccessary. However, it will make the code more robust and doesn't have any + /// technically unnecessary. However, it will make the code more robust and doesn't have any /// large performance implications. If the abs call was deliberately left out for performance /// reasons, it is probably better to state this explicitly in the code, which then can be done /// with an allow. @@ -69,7 +69,7 @@ impl<'tcx> LateLintPass<'tcx> for FloatEqualityWithoutAbs { if_chain! { - // left hand side is a substraction + // left hand side is a subtraction if let ExprKind::Binary( Spanned { node: BinOpKind::Sub, @@ -84,7 +84,7 @@ impl<'tcx> LateLintPass<'tcx> for FloatEqualityWithoutAbs { if let Res::Def(DefKind::AssocConst, def_id) = cx.qpath_res(epsilon_path, rhs.hir_id); if match_def_path(cx, def_id, &paths::F32_EPSILON) || match_def_path(cx, def_id, &paths::F64_EPSILON); - // values of the substractions on the left hand side are of the type float + // values of the subtractions on the left hand side are of the type float let t_val_l = cx.typeck_results().expr_ty(val_l); let t_val_r = cx.typeck_results().expr_ty(val_r); if let ty::Float(_) = t_val_l.kind(); diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs index b828d9334ee0e..8e61f2347767d 100644 --- a/clippy_lints/src/only_used_in_recursion.rs +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -224,7 +224,7 @@ pub fn is_array(ty: Ty<'_>) -> bool { /// This builds the graph of side effect. /// The edge `a -> b` means if `a` has side effect, `b` will have side effect. /// -/// There are some exmaple in following code: +/// There are some example in following code: /// ```rust, ignore /// let b = 1; /// let a = b; // a -> b diff --git a/clippy_lints/src/suspicious_operation_groupings.rs b/clippy_lints/src/suspicious_operation_groupings.rs index 940a8428f7795..b5dd27ff80de4 100644 --- a/clippy_lints/src/suspicious_operation_groupings.rs +++ b/clippy_lints/src/suspicious_operation_groupings.rs @@ -290,7 +290,7 @@ fn ident_swap_sugg( // used instead, in these cases. *applicability = Applicability::MaybeIncorrect; - // We arbitraily choose one side to suggest changing, + // We arbitrarily choose one side to suggest changing, // since we don't have a better guess. If the user // ends up duplicating a clause, the `logic_bug` lint // should catch it. @@ -374,19 +374,19 @@ fn strip_non_ident_wrappers(expr: &Expr) -> &Expr { } fn extract_related_binops(kind: &ExprKind) -> Option>> { - append_opt_vecs(chained_binops(kind), if_statment_binops(kind)) + append_opt_vecs(chained_binops(kind), if_statement_binops(kind)) } -fn if_statment_binops(kind: &ExprKind) -> Option>> { +fn if_statement_binops(kind: &ExprKind) -> Option>> { match kind { ExprKind::If(ref condition, _, _) => chained_binops(&condition.kind), - ExprKind::Paren(ref e) => if_statment_binops(&e.kind), + ExprKind::Paren(ref e) => if_statement_binops(&e.kind), ExprKind::Block(ref block, _) => { let mut output = None; for stmt in &block.stmts { match stmt.kind { StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => { - output = append_opt_vecs(output, if_statment_binops(&e.kind)); + output = append_opt_vecs(output, if_statement_binops(&e.kind)); }, _ => {}, } diff --git a/clippy_lints/src/trailing_empty_array.rs b/clippy_lints/src/trailing_empty_array.rs index c9b2ce476e89d..58cc057a39ed9 100644 --- a/clippy_lints/src/trailing_empty_array.rs +++ b/clippy_lints/src/trailing_empty_array.rs @@ -10,7 +10,7 @@ declare_clippy_lint! { /// Displays a warning when a struct with a trailing zero-sized array is declared without a `repr` attribute. /// /// ### Why is this bad? - /// Zero-sized arrays aren't very useful in Rust itself, so such a struct is likely being created to pass to C code or in some other situation where control over memory layout matters (for example, in conjuction with manual allocation to make it easy to compute the offset of the array). Either way, `#[repr(C)]` (or another `repr` attribute) is needed. + /// Zero-sized arrays aren't very useful in Rust itself, so such a struct is likely being created to pass to C code or in some other situation where control over memory layout matters (for example, in conjunction with manual allocation to make it easy to compute the offset of the array). Either way, `#[repr(C)]` (or another `repr` attribute) is needed. /// /// ### Example /// ```rust diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index be9d538c36267..43e0132a7ec7b 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -46,7 +46,7 @@ declare_clippy_lint! { /// /// ### Why is this bad? /// Duplicate bounds makes the code - /// less readable than specifing them only once. + /// less readable than specifying them only once. /// /// ### Example /// ```rust diff --git a/clippy_lints/src/unwrap_in_result.rs b/clippy_lints/src/unwrap_in_result.rs index 2c13f1049b599..b32be238cd55a 100644 --- a/clippy_lints/src/unwrap_in_result.rs +++ b/clippy_lints/src/unwrap_in_result.rs @@ -83,9 +83,9 @@ impl<'a, 'tcx> Visitor<'tcx> for FindExpectUnwrap<'a, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { // check for `expect` if let Some(arglists) = method_chain_args(expr, &["expect"]) { - let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs(); - if is_type_diagnostic_item(self.lcx, reciever_ty, sym::Option) - || is_type_diagnostic_item(self.lcx, reciever_ty, sym::Result) + let receiver_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs(); + if is_type_diagnostic_item(self.lcx, receiver_ty, sym::Option) + || is_type_diagnostic_item(self.lcx, receiver_ty, sym::Result) { self.result.push(expr.span); } @@ -93,9 +93,9 @@ impl<'a, 'tcx> Visitor<'tcx> for FindExpectUnwrap<'a, 'tcx> { // check for `unwrap` if let Some(arglists) = method_chain_args(expr, &["unwrap"]) { - let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs(); - if is_type_diagnostic_item(self.lcx, reciever_ty, sym::Option) - || is_type_diagnostic_item(self.lcx, reciever_ty, sym::Result) + let receiver_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs(); + if is_type_diagnostic_item(self.lcx, receiver_ty, sym::Option) + || is_type_diagnostic_item(self.lcx, receiver_ty, sym::Result) { self.result.push(expr.span); } diff --git a/tests/lint_message_convention.rs b/tests/lint_message_convention.rs index b4d94dc983fec..dc82ba891fb1f 100644 --- a/tests/lint_message_convention.rs +++ b/tests/lint_message_convention.rs @@ -16,7 +16,7 @@ impl Message { fn new(path: PathBuf) -> Self { let content: String = std::fs::read_to_string(&path).unwrap(); // we don't want the first letter after "error: ", "help: " ... to be capitalized - // also no puncutation (except for "?" ?) at the end of a line + // also no punctuation (except for "?" ?) at the end of a line let regex_set: RegexSet = RegexSet::new(&[ r"error: [A-Z]", r"help: [A-Z]", diff --git a/tests/ui/manual_memcpy/with_loop_counters.rs b/tests/ui/manual_memcpy/with_loop_counters.rs index ba388a05a2859..c826b082adff1 100644 --- a/tests/ui/manual_memcpy/with_loop_counters.rs +++ b/tests/ui/manual_memcpy/with_loop_counters.rs @@ -59,7 +59,7 @@ pub fn manual_copy_with_counters(src: &[i32], dst: &mut [i32], dst2: &mut [i32]) } // make sure parentheses are added properly to bitwise operators, which have lower precedence than - // arithmetric ones + // arithmetic ones let mut count = 0 << 1; for i in 0..1 << 1 { dst[count] = src[i + 2]; diff --git a/tests/workspace.rs b/tests/workspace.rs index 677b4a4d56999..e13efb3e0164b 100644 --- a/tests/workspace.rs +++ b/tests/workspace.rs @@ -93,7 +93,7 @@ fn test_no_deps_ignores_path_deps_in_workspaces() { output }; - // Trigger a sucessful build, so Cargo would like to cache the build result. + // Trigger a successful build, so Cargo would like to cache the build result. successful_build(); // Make sure there's no spurious rebuild when nothing changes. From 851e715e5a7ac21007b9491c81916e854fbab22c Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 16 Mar 2022 12:00:27 -0400 Subject: [PATCH 21/50] Don't lint `ptr_arg` on `&mut Cow<_>` --- clippy_lints/src/ptr.rs | 2 +- tests/ui/ptr_arg.rs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 9c776437d7fe5..ba1997e70e131 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -436,7 +436,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>( DerefTy::Path, None, ), - Some(sym::Cow) => { + Some(sym::Cow) if mutability == Mutability::Not => { let ty_name = name.args .and_then(|args| { args.args.iter().find_map(|a| match a { diff --git a/tests/ui/ptr_arg.rs b/tests/ui/ptr_arg.rs index 97990fedd51f3..03dd938a2339e 100644 --- a/tests/ui/ptr_arg.rs +++ b/tests/ui/ptr_arg.rs @@ -194,3 +194,10 @@ fn two_vecs(a: &mut Vec, b: &mut Vec) { a.push(0); b.push(1); } + +// Issue #8495 +fn cow_conditional_to_mut(a: &mut Cow) { + if a.is_empty() { + a.to_mut().push_str("foo"); + } +} From 8e5208cbffd5ff43f80097d74a8802aad1d6190b Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 16 Mar 2022 12:54:32 -0400 Subject: [PATCH 22/50] Don't lint `transmute_undefined_repr` when changing the type of generic params --- .../src/transmute/transmute_undefined_repr.rs | 64 +++++++++++++------ tests/ui/transmute_undefined_repr.rs | 32 ++++++++++ tests/ui/transmute_undefined_repr.stderr | 34 +++++++--- 3 files changed, 102 insertions(+), 28 deletions(-) diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index 4922c40bcefcd..f5e21267a8976 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::is_c_void; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::subst::Subst; +use rustc_middle::ty::subst::{Subst, SubstsRef}; use rustc_middle::ty::{self, IntTy, Ty, TypeAndMut, UintTy}; use rustc_span::Span; @@ -127,6 +127,17 @@ pub(super) fn check<'tcx>( } => match (reduce_ty(cx, from_sub_ty), reduce_ty(cx, to_sub_ty)) { (ReducedTy::TypeErasure, _) | (_, ReducedTy::TypeErasure) => return false, (ReducedTy::UnorderedFields(from_ty), ReducedTy::UnorderedFields(to_ty)) if from_ty != to_ty => { + let same_adt_did = if let (ty::Adt(from_def, from_subs), ty::Adt(to_def, to_subs)) + = (from_ty.kind(), to_ty.kind()) + && from_def == to_def + { + if same_except_params(from_subs, to_subs) { + return false; + } + Some(from_def.did()) + } else { + None + }; span_lint_and_then( cx, TRANSMUTE_UNDEFINED_REPR, @@ -136,21 +147,17 @@ pub(super) fn check<'tcx>( from_ty_orig, to_ty_orig ), |diag| { - if_chain! { - if let (Some(from_def), Some(to_def)) = (from_ty.ty_adt_def(), to_ty.ty_adt_def()); - if from_def == to_def; - then { - diag.note(&format!( - "two instances of the same generic type (`{}`) may have different layouts", - cx.tcx.item_name(from_def.did()) - )); - } else { - if from_ty_orig.peel_refs() != from_ty { - diag.note(&format!("the contained type `{}` has an undefined layout", from_ty)); - } - if to_ty_orig.peel_refs() != to_ty { - diag.note(&format!("the contained type `{}` has an undefined layout", to_ty)); - } + if let Some(same_adt_did) = same_adt_did { + diag.note(&format!( + "two instances of the same generic type (`{}`) may have different layouts", + cx.tcx.item_name(same_adt_did) + )); + } else { + if from_ty_orig.peel_refs() != from_ty { + diag.note(&format!("the contained type `{}` has an undefined layout", from_ty)); + } + if to_ty_orig.peel_refs() != to_ty { + diag.note(&format!("the contained type `{}` has an undefined layout", to_ty)); } } }, @@ -197,10 +204,13 @@ pub(super) fn check<'tcx>( continue; }, ( - ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_), - ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_), + ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_) | ReducedTy::Param, + ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_) | ReducedTy::Param, ) - | (ReducedTy::UnorderedFields(_), ReducedTy::UnorderedFields(_)) => break, + | ( + ReducedTy::UnorderedFields(_) | ReducedTy::Param, + ReducedTy::UnorderedFields(_) | ReducedTy::Param, + ) => break, }, } } @@ -264,6 +274,8 @@ enum ReducedTy<'tcx> { UnorderedFields(Ty<'tcx>), /// The type is a reference to the contained type. Ref(Ty<'tcx>), + /// The type is a generic parameter. + Param, /// Any other type. Other(Ty<'tcx>), } @@ -317,6 +329,7 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> ty::Foreign(_) => ReducedTy::TypeErasure, ty::Ref(_, ty, _) => ReducedTy::Ref(ty), ty::RawPtr(ty) => ReducedTy::Ref(ty.ty), + ty::Param(_) => ReducedTy::Param, _ => ReducedTy::Other(ty), }; } @@ -344,3 +357,16 @@ fn is_size_pair(ty: Ty<'_>) -> bool { false } } + +fn same_except_params(subs1: SubstsRef<'_>, subs2: SubstsRef<'_>) -> bool { + // TODO: check const parameters as well. Currently this will consider `Array<5>` the same as + // `Array<6>` + for (ty1, ty2) in subs1.types().zip(subs2.types()).filter(|(ty1, ty2)| ty1 != ty2) { + match (ty1.kind(), ty2.kind()) { + (ty::Param(_), _) | (_, ty::Param(_)) => (), + (ty::Adt(adt1, subs1), ty::Adt(adt2, subs2)) if adt1 == adt2 && same_except_params(subs1, subs2) => (), + _ => return false, + } + } + true +} diff --git a/tests/ui/transmute_undefined_repr.rs b/tests/ui/transmute_undefined_repr.rs index 7cc03b89fe0ec..b06ed4a917376 100644 --- a/tests/ui/transmute_undefined_repr.rs +++ b/tests/ui/transmute_undefined_repr.rs @@ -1,6 +1,7 @@ #![warn(clippy::transmute_undefined_repr)] #![allow(clippy::unit_arg, clippy::transmute_ptr_to_ref)] +use core::any::TypeId; use core::ffi::c_void; use core::mem::{size_of, transmute, MaybeUninit}; @@ -110,3 +111,34 @@ fn main() { let _: Ty<&[u32]> = transmute::<&[u32], _>(value::<&Vec>()); // Ok } } + +fn _with_generics() { + if TypeId::of::() != TypeId::of::() || TypeId::of::() != TypeId::of::() { + return; + } + unsafe { + let _: &u32 = transmute(value::<&T>()); // Ok + let _: &T = transmute(value::<&u32>()); // Ok + + let _: Vec = transmute(value::>()); // Ok + let _: Vec = transmute(value::>()); // Ok + + let _: Ty<&u32> = transmute(value::<&T>()); // Ok + let _: Ty<&T> = transmute(value::<&u32>()); // Ok + + let _: Vec = transmute(value::>()); // Ok + let _: Vec = transmute(value::>()); // Ok + + let _: &Ty2 = transmute(value::<&Ty2>()); // Ok + let _: &Ty2 = transmute(value::<&Ty2>()); // Ok + + let _: Vec> = transmute(value::>>()); // Ok + let _: Vec> = transmute(value::>>()); // Ok + + let _: Vec> = transmute(value::>>()); // Err + let _: Vec> = transmute(value::>>()); // Err + + let _: *const u32 = transmute(value::>()); // Ok + let _: Box = transmute(value::<*const u32>()); // Ok + } +} diff --git a/tests/ui/transmute_undefined_repr.stderr b/tests/ui/transmute_undefined_repr.stderr index 42d544fc954c5..28bfba6c7571d 100644 --- a/tests/ui/transmute_undefined_repr.stderr +++ b/tests/ui/transmute_undefined_repr.stderr @@ -1,5 +1,5 @@ error: transmute from `Ty2` which has an undefined layout - --> $DIR/transmute_undefined_repr.rs:26:33 + --> $DIR/transmute_undefined_repr.rs:27:33 | LL | let _: Ty2C = transmute(value::>()); // Lint, Ty2 is unordered | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -7,13 +7,13 @@ LL | let _: Ty2C = transmute(value::>()); // Lin = note: `-D clippy::transmute-undefined-repr` implied by `-D warnings` error: transmute into `Ty2` which has an undefined layout - --> $DIR/transmute_undefined_repr.rs:27:32 + --> $DIR/transmute_undefined_repr.rs:28:32 | LL | let _: Ty2 = transmute(value::>()); // Lint, Ty2 is unordered | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: transmute from `Ty>` to `Ty2`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:32:32 + --> $DIR/transmute_undefined_repr.rs:33:32 | LL | let _: Ty2 = transmute(value::>>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -21,7 +21,7 @@ LL | let _: Ty2 = transmute(value::>>()); // = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `Ty2` to `Ty>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:33:36 + --> $DIR/transmute_undefined_repr.rs:34:36 | LL | let _: Ty> = transmute(value::>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -29,7 +29,7 @@ LL | let _: Ty> = transmute(value::>()); // = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `Ty<&Ty2>` to `&Ty2`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:38:33 + --> $DIR/transmute_undefined_repr.rs:39:33 | LL | let _: &Ty2 = transmute(value::>>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -37,7 +37,7 @@ LL | let _: &Ty2 = transmute(value::>>()); / = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `&Ty2` to `Ty<&Ty2>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:39:37 + --> $DIR/transmute_undefined_repr.rs:40:37 | LL | let _: Ty<&Ty2> = transmute(value::<&Ty2>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -45,7 +45,7 @@ LL | let _: Ty<&Ty2> = transmute(value::<&Ty2>()); / = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `std::boxed::Box>` to `&mut Ty2`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:56:45 + --> $DIR/transmute_undefined_repr.rs:57:45 | LL | let _: &'static mut Ty2 = transmute(value::>>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -53,12 +53,28 @@ LL | let _: &'static mut Ty2 = transmute(value::` to `std::boxed::Box>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:57:37 + --> $DIR/transmute_undefined_repr.rs:58:37 | LL | let _: Box> = transmute(value::<&'static mut Ty2>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: two instances of the same generic type (`Ty2`) may have different layouts -error: aborting due to 8 previous errors +error: transmute from `std::vec::Vec>` to `std::vec::Vec>`, both of which have an undefined layout + --> $DIR/transmute_undefined_repr.rs:138:35 + | +LL | let _: Vec> = transmute(value::>>()); // Err + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: two instances of the same generic type (`Vec`) may have different layouts + +error: transmute from `std::vec::Vec>` to `std::vec::Vec>`, both of which have an undefined layout + --> $DIR/transmute_undefined_repr.rs:139:35 + | +LL | let _: Vec> = transmute(value::>>()); // Err + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: two instances of the same generic type (`Vec`) may have different layouts + +error: aborting due to 10 previous errors From b37317b028cd0d4b60126e0bcf1402e60018f891 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 6 Jan 2022 02:54:35 -0500 Subject: [PATCH 23/50] Check if there are any overlapping patterns between equal arm bodies in `match_same_arm` --- clippy_lints/src/matches/match_same_arms.rs | 253 +++++++++++++++++++- tests/ui/match_same_arms2.rs | 13 + 2 files changed, 256 insertions(+), 10 deletions(-) diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index d11dda57e6fd9..6617cf4e47f82 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -1,19 +1,53 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use clippy_utils::{path_to_local, search_same, SpanlessEq, SpanlessHash}; -use rustc_hir::{Arm, Expr, HirId, HirIdMap, HirIdSet, Pat, PatKind}; +use rustc_ast::ast::LitKind; +use rustc_hir::def_id::DefId; +use rustc_hir::{Arm, Expr, ExprKind, HirId, HirIdMap, HirIdSet, Pat, PatKind, RangeEnd}; use rustc_lint::LateContext; +use rustc_span::Symbol; use std::collections::hash_map::Entry; use super::MATCH_SAME_ARMS; -pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 { let mut h = SpanlessHash::new(cx); h.hash_expr(arm.body); h.finish() }; + let resolved_pats: Vec<_> = arms.iter().map(|a| ResolvedPat::from_pat(cx, a.pat)).collect(); + + // The furthast forwards a pattern can move without semantic changes + let forwards_blocking_idxs: Vec<_> = resolved_pats + .iter() + .enumerate() + .map(|(i, pat)| { + resolved_pats[i + 1..] + .iter() + .enumerate() + .find_map(|(j, other)| pat.can_also_match(other).then(|| i + 1 + j)) + .unwrap_or(resolved_pats.len()) + }) + .collect(); + + // The furthast backwards a pattern can move without semantic changes + let backwards_blocking_idxs: Vec<_> = resolved_pats + .iter() + .enumerate() + .map(|(i, pat)| { + resolved_pats[..i] + .iter() + .enumerate() + .rev() + .zip(forwards_blocking_idxs[..i].iter().copied().rev()) + .skip_while(|&(_, forward_block)| forward_block > i) + .find_map(|((j, other), forward_block)| (forward_block == i || pat.can_also_match(other)).then(|| j)) + .unwrap_or(0) + }) + .collect(); + let eq = |&(lindex, lhs): &(usize, &Arm<'_>), &(rindex, rhs): &(usize, &Arm<'_>)| -> bool { let min_index = usize::min(lindex, rindex); let max_index = usize::max(lindex, rindex); @@ -42,14 +76,16 @@ pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { } }; // Arms with a guard are ignored, those can’t always be merged together - // This is also the case for arms in-between each there is an arm with a guard - (min_index..=max_index).all(|index| arms[index].guard.is_none()) - && SpanlessEq::new(cx) - .expr_fallback(eq_fallback) - .eq_expr(lhs.body, rhs.body) - // these checks could be removed to allow unused bindings - && bindings_eq(lhs.pat, local_map.keys().copied().collect()) - && bindings_eq(rhs.pat, local_map.values().copied().collect()) + // If both arms overlap with an arm in between then these can't be merged either. + !(backwards_blocking_idxs[max_index] > min_index && forwards_blocking_idxs[min_index] < max_index) + && lhs.guard.is_none() + && rhs.guard.is_none() + && SpanlessEq::new(cx) + .expr_fallback(eq_fallback) + .eq_expr(lhs.body, rhs.body) + // these checks could be removed to allow unused bindings + && bindings_eq(lhs.pat, local_map.keys().copied().collect()) + && bindings_eq(rhs.pat, local_map.values().copied().collect()) }; let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect(); @@ -92,6 +128,203 @@ pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { } } +#[derive(Debug)] +enum ResolvedPat<'hir> { + Wild, + Struct(Option, Vec<(Symbol, ResolvedPat<'hir>)>), + Sequence(Option, Vec>, Option), + Or(Vec>), + Path(Option), + LitStr(Symbol), + LitBytes(&'hir [u8]), + LitInt(u128), + LitBool(bool), + Range(PatRange), +} + +#[derive(Debug)] +struct PatRange { + start: u128, + end: u128, + bounds: RangeEnd, +} +impl PatRange { + fn contains(&self, x: u128) -> bool { + x >= self.start + && match self.bounds { + RangeEnd::Included => x <= self.end, + RangeEnd::Excluded => x < self.end, + } + } + + fn overlaps(&self, other: &Self) -> bool { + !(self.is_empty() || other.is_empty()) + && match self.bounds { + RangeEnd::Included => self.end >= other.start, + RangeEnd::Excluded => self.end > other.start, + } + && match other.bounds { + RangeEnd::Included => self.start <= other.end, + RangeEnd::Excluded => self.start < other.end, + } + } + + fn is_empty(&self) -> bool { + match self.bounds { + RangeEnd::Included => false, + RangeEnd::Excluded => self.start == self.end, + } + } +} + +impl<'hir> ResolvedPat<'hir> { + fn from_pat(cx: &LateContext<'_>, pat: &'hir Pat<'_>) -> Self { + match pat.kind { + PatKind::Wild | PatKind::Binding(.., None) => Self::Wild, + PatKind::Binding(.., Some(pat)) | PatKind::Box(pat) | PatKind::Ref(pat, _) => Self::from_pat(cx, pat), + PatKind::Struct(ref path, fields, _) => { + let mut fields: Vec<_> = fields + .iter() + .map(|f| (f.ident.name, Self::from_pat(cx, f.pat))) + .collect(); + fields.sort_by_key(|&(name, _)| name); + Self::Struct(cx.qpath_res(path, pat.hir_id).opt_def_id(), fields) + }, + PatKind::TupleStruct(ref path, pats, wild_idx) => Self::Sequence( + cx.qpath_res(path, pat.hir_id).opt_def_id(), + pats.iter().map(|pat| Self::from_pat(cx, pat)).collect(), + wild_idx, + ), + PatKind::Or(pats) => Self::Or(pats.iter().map(|pat| Self::from_pat(cx, pat)).collect()), + PatKind::Path(ref path) => Self::Path(cx.qpath_res(path, pat.hir_id).opt_def_id()), + PatKind::Tuple(pats, wild_idx) => { + Self::Sequence(None, pats.iter().map(|pat| Self::from_pat(cx, pat)).collect(), wild_idx) + }, + PatKind::Lit(e) => match &e.kind { + ExprKind::Lit(lit) => match lit.node { + LitKind::Str(sym, _) => Self::LitStr(sym), + LitKind::ByteStr(ref bytes) => Self::LitBytes(&**bytes), + LitKind::Byte(val) => Self::LitInt(val.into()), + LitKind::Char(val) => Self::LitInt(val.into()), + LitKind::Int(val, _) => Self::LitInt(val), + LitKind::Bool(val) => Self::LitBool(val), + LitKind::Float(..) | LitKind::Err(_) => Self::Wild, + }, + _ => Self::Wild, + }, + PatKind::Range(start, end, bounds) => { + let start = match start { + None => 0, + Some(e) => match &e.kind { + ExprKind::Lit(lit) => match lit.node { + LitKind::Int(val, _) => val, + LitKind::Char(val) => val.into(), + LitKind::Byte(val) => val.into(), + _ => return Self::Wild, + }, + _ => return Self::Wild, + }, + }; + let (end, bounds) = match end { + None => (u128::MAX, RangeEnd::Included), + Some(e) => match &e.kind { + ExprKind::Lit(lit) => match lit.node { + LitKind::Int(val, _) => (val, bounds), + LitKind::Char(val) => (val.into(), bounds), + LitKind::Byte(val) => (val.into(), bounds), + _ => return Self::Wild, + }, + _ => return Self::Wild, + }, + }; + Self::Range(PatRange { start, end, bounds }) + }, + PatKind::Slice(pats, wild, pats2) => Self::Sequence( + None, + pats.iter() + .chain(pats2.iter()) + .map(|pat| Self::from_pat(cx, pat)) + .collect(), + wild.map(|_| pats.len()), + ), + } + } + + /// Checks if two patterns overlap in the values they can match assuming they are for the same + /// type. + fn can_also_match(&self, other: &Self) -> bool { + match (self, other) { + (Self::Wild, _) | (_, Self::Wild) => true, + (Self::Or(pats), other) | (other, Self::Or(pats)) => pats.iter().any(|pat| pat.can_also_match(other)), + (Self::Struct(lpath, lfields), Self::Struct(rpath, rfields)) => { + if lpath != rpath { + return false; + } + let mut rfields = rfields.iter(); + let mut rfield = match rfields.next() { + Some(x) => x, + None => return true, + }; + 'outer: for lfield in lfields { + loop { + if lfield.0 < rfield.0 { + continue 'outer; + } else if lfield.0 > rfield.0 { + rfield = match rfields.next() { + Some(x) => x, + None => return true, + }; + } else if !lfield.1.can_also_match(&rfield.1) { + return false; + } else { + rfield = match rfields.next() { + Some(x) => x, + None => return true, + }; + continue 'outer; + } + } + } + true + }, + (Self::Sequence(lpath, lpats, lwild_idx), Self::Sequence(rpath, rpats, rwild_idx)) => { + if lpath != rpath { + return false; + } + + let (lpats_start, lpats_end) = lwild_idx + .or(*rwild_idx) + .map_or((&**lpats, [].as_slice()), |idx| lpats.split_at(idx)); + let (rpats_start, rpats_end) = rwild_idx + .or(*lwild_idx) + .map_or((&**rpats, [].as_slice()), |idx| rpats.split_at(idx)); + + lpats_start + .iter() + .zip(rpats_start.iter()) + .all(|(lpat, rpat)| lpat.can_also_match(rpat)) + // `lpats_end` and `rpats_end` lengths may be disjointed, so start from the end and ignore any + // extras. + && lpats_end + .iter() + .rev() + .zip(rpats_end.iter().rev()) + .all(|(lpat, rpat)| lpat.can_also_match(rpat)) + }, + (Self::Path(x), Self::Path(y)) => x == y, + (Self::LitStr(x), Self::LitStr(y)) => x == y, + (Self::LitBytes(x), Self::LitBytes(y)) => x == y, + (Self::LitInt(x), Self::LitInt(y)) => x == y, + (Self::LitBool(x), Self::LitBool(y)) => x == y, + (Self::Range(x), Self::Range(y)) => x.overlaps(y), + (Self::Range(range), Self::LitInt(x)) | (Self::LitInt(x), Self::Range(range)) => range.contains(*x), + + // Todo: Lit* with Path, Range with Path, LitBytes with Sequence + _ => true, + } + } +} + fn pat_contains_local(pat: &Pat<'_>, id: HirId) -> bool { let mut result = false; pat.walk_short(|p| { diff --git a/tests/ui/match_same_arms2.rs b/tests/ui/match_same_arms2.rs index 67e1d518483c2..6dc6c4172ee09 100644 --- a/tests/ui/match_same_arms2.rs +++ b/tests/ui/match_same_arms2.rs @@ -174,4 +174,17 @@ fn main() { Some(2) => 2, _ => 1, }; + + enum Foo { + X(u32), + Y(u32), + Z(u32), + } + + let _ = match Foo::X(0) { + Foo::X(0) => 1, + Foo::X(_) | Foo::Y(_) | Foo::Z(0) => 2, + Foo::Z(_) => 1, + _ => 0, + }; } From 5508f461b853345bdfbefe61fb4b7bccb433b302 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 6 Jan 2022 13:37:37 -0500 Subject: [PATCH 24/50] Use `DroplessArena` when allocating `ResolvedPat`s Fix tuple handling in `match_same_arms` --- clippy_lints/src/doc.rs | 15 ++- clippy_lints/src/lib.rs | 1 + clippy_lints/src/matches/match_same_arms.rs | 129 ++++++++++++-------- 3 files changed, 86 insertions(+), 59 deletions(-) diff --git a/clippy_lints/src/doc.rs b/clippy_lints/src/doc.rs index 16173580fd461..703aa458f44e5 100644 --- a/clippy_lints/src/doc.rs +++ b/clippy_lints/src/doc.rs @@ -637,12 +637,6 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) { loop { match parser.parse_item(ForceCollect::No) { Ok(Some(item)) => match &item.kind { - // Tests with one of these items are ignored - ItemKind::Static(..) - | ItemKind::Const(..) - | ItemKind::ExternCrate(..) - | ItemKind::ForeignMod(..) => return false, - // We found a main function ... ItemKind::Fn(box Fn { sig, body: Some(block), .. }) if item.ident.name == sym::main => { @@ -661,8 +655,13 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) { return false; } }, - // Another function was found; this case is ignored too - ItemKind::Fn(..) => return false, + // Tests with one of these items are ignored + ItemKind::Static(..) + | ItemKind::Const(..) + | ItemKind::ExternCrate(..) + | ItemKind::ForeignMod(..) + // Another function was found; this case is ignored + | ItemKind::Fn(..) => return false, _ => {}, }, Ok(None) => break, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 504235d0d1ef0..f2a0799914448 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -23,6 +23,7 @@ // FIXME: switch to something more ergonomic here, once available. // (Currently there is no way to opt into sysroot crates without `extern crate`.) +extern crate rustc_arena; extern crate rustc_ast; extern crate rustc_ast_pretty; extern crate rustc_attr; diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index 6617cf4e47f82..0afac347d0805 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -1,10 +1,13 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use clippy_utils::{path_to_local, search_same, SpanlessEq, SpanlessHash}; +use core::iter; +use rustc_arena::DroplessArena; use rustc_ast::ast::LitKind; use rustc_hir::def_id::DefId; use rustc_hir::{Arm, Expr, ExprKind, HirId, HirIdMap, HirIdSet, Pat, PatKind, RangeEnd}; use rustc_lint::LateContext; +use rustc_middle::ty; use rustc_span::Symbol; use std::collections::hash_map::Entry; @@ -17,7 +20,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { h.finish() }; - let resolved_pats: Vec<_> = arms.iter().map(|a| ResolvedPat::from_pat(cx, a.pat)).collect(); + let arena = DroplessArena::default(); + let resolved_pats: Vec<_> = arms.iter().map(|a| ResolvedPat::from_pat(cx, &arena, a.pat)).collect(); // The furthast forwards a pattern can move without semantic changes let forwards_blocking_idxs: Vec<_> = resolved_pats @@ -128,21 +132,22 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { } } -#[derive(Debug)] -enum ResolvedPat<'hir> { +#[derive(Clone, Copy)] +enum ResolvedPat<'hir, 'arena> { Wild, - Struct(Option, Vec<(Symbol, ResolvedPat<'hir>)>), - Sequence(Option, Vec>, Option), - Or(Vec>), + Struct(Option, &'arena [(Symbol, Self)]), + Tuple(Option, &'arena [Self]), + Or(&'arena [Self]), Path(Option), LitStr(Symbol), LitBytes(&'hir [u8]), LitInt(u128), LitBool(bool), Range(PatRange), + Slice(&'arena [Self], &'arena [Self], bool), } -#[derive(Debug)] +#[derive(Clone, Copy)] struct PatRange { start: u128, end: u128, @@ -177,28 +182,66 @@ impl PatRange { } } -impl<'hir> ResolvedPat<'hir> { - fn from_pat(cx: &LateContext<'_>, pat: &'hir Pat<'_>) -> Self { +#[allow(clippy::similar_names)] +impl<'hir, 'arena> ResolvedPat<'hir, 'arena> { + #[allow(clippy::too_many_lines)] + fn from_pat(cx: &LateContext<'_>, arena: &'arena DroplessArena, pat: &'hir Pat<'_>) -> Self { match pat.kind { PatKind::Wild | PatKind::Binding(.., None) => Self::Wild, - PatKind::Binding(.., Some(pat)) | PatKind::Box(pat) | PatKind::Ref(pat, _) => Self::from_pat(cx, pat), + PatKind::Binding(.., Some(pat)) | PatKind::Box(pat) | PatKind::Ref(pat, _) => { + Self::from_pat(cx, arena, pat) + }, PatKind::Struct(ref path, fields, _) => { - let mut fields: Vec<_> = fields - .iter() - .map(|f| (f.ident.name, Self::from_pat(cx, f.pat))) - .collect(); + let fields = + arena.alloc_from_iter(fields.iter().map(|f| (f.ident.name, Self::from_pat(cx, arena, f.pat)))); fields.sort_by_key(|&(name, _)| name); Self::Struct(cx.qpath_res(path, pat.hir_id).opt_def_id(), fields) }, - PatKind::TupleStruct(ref path, pats, wild_idx) => Self::Sequence( - cx.qpath_res(path, pat.hir_id).opt_def_id(), - pats.iter().map(|pat| Self::from_pat(cx, pat)).collect(), - wild_idx, - ), - PatKind::Or(pats) => Self::Or(pats.iter().map(|pat| Self::from_pat(cx, pat)).collect()), + PatKind::TupleStruct(ref path, pats, wild_idx) => { + let adt = match cx.typeck_results().pat_ty(pat).ty_adt_def() { + Some(x) => x, + None => return Self::Wild, + }; + let (var_id, variant) = if adt.is_enum() { + match cx.qpath_res(path, pat.hir_id).opt_def_id() { + Some(x) => (Some(x), adt.variant_with_ctor_id(x)), + None => return Self::Wild, + } + } else { + (None, adt.non_enum_variant()) + }; + let (front, back) = match wild_idx { + Some(i) => pats.split_at(i), + None => (pats, [].as_slice()), + }; + let pats = arena.alloc_from_iter( + front + .iter() + .map(|pat| Self::from_pat(cx, arena, pat)) + .chain(iter::repeat_with(|| Self::Wild).take(variant.fields.len() - pats.len())) + .chain(back.iter().map(|pat| Self::from_pat(cx, arena, pat))), + ); + Self::Tuple(var_id, pats) + }, + PatKind::Or(pats) => Self::Or(arena.alloc_from_iter(pats.iter().map(|pat| Self::from_pat(cx, arena, pat)))), PatKind::Path(ref path) => Self::Path(cx.qpath_res(path, pat.hir_id).opt_def_id()), PatKind::Tuple(pats, wild_idx) => { - Self::Sequence(None, pats.iter().map(|pat| Self::from_pat(cx, pat)).collect(), wild_idx) + let field_count = match cx.typeck_results().pat_ty(pat).kind() { + ty::Tuple(subs) => subs.len(), + _ => return Self::Wild, + }; + let (front, back) = match wild_idx { + Some(i) => pats.split_at(i), + None => (pats, [].as_slice()), + }; + let pats = arena.alloc_from_iter( + front + .iter() + .map(|pat| Self::from_pat(cx, arena, pat)) + .chain(iter::repeat_with(|| Self::Wild).take(field_count - pats.len())) + .chain(back.iter().map(|pat| Self::from_pat(cx, arena, pat))), + ); + Self::Tuple(None, pats) }, PatKind::Lit(e) => match &e.kind { ExprKind::Lit(lit) => match lit.node { @@ -239,13 +282,10 @@ impl<'hir> ResolvedPat<'hir> { }; Self::Range(PatRange { start, end, bounds }) }, - PatKind::Slice(pats, wild, pats2) => Self::Sequence( - None, - pats.iter() - .chain(pats2.iter()) - .map(|pat| Self::from_pat(cx, pat)) - .collect(), - wild.map(|_| pats.len()), + PatKind::Slice(front, wild_pat, back) => Self::Slice( + arena.alloc_from_iter(front.iter().map(|pat| Self::from_pat(cx, arena, pat))), + arena.alloc_from_iter(back.iter().map(|pat| Self::from_pat(cx, arena, pat))), + wild_pat.is_some(), ), } } @@ -253,9 +293,11 @@ impl<'hir> ResolvedPat<'hir> { /// Checks if two patterns overlap in the values they can match assuming they are for the same /// type. fn can_also_match(&self, other: &Self) -> bool { - match (self, other) { + match (*self, *other) { (Self::Wild, _) | (_, Self::Wild) => true, - (Self::Or(pats), other) | (other, Self::Or(pats)) => pats.iter().any(|pat| pat.can_also_match(other)), + (Self::Or(pats), ref other) | (ref other, Self::Or(pats)) => { + pats.iter().any(|pat| pat.can_also_match(other)) + }, (Self::Struct(lpath, lfields), Self::Struct(rpath, rfields)) => { if lpath != rpath { return false; @@ -287,28 +329,13 @@ impl<'hir> ResolvedPat<'hir> { } true }, - (Self::Sequence(lpath, lpats, lwild_idx), Self::Sequence(rpath, rpats, rwild_idx)) => { + (Self::Tuple(lpath, lpats), Self::Tuple(rpath, rpats)) => { if lpath != rpath { return false; } - - let (lpats_start, lpats_end) = lwild_idx - .or(*rwild_idx) - .map_or((&**lpats, [].as_slice()), |idx| lpats.split_at(idx)); - let (rpats_start, rpats_end) = rwild_idx - .or(*lwild_idx) - .map_or((&**rpats, [].as_slice()), |idx| rpats.split_at(idx)); - - lpats_start - .iter() - .zip(rpats_start.iter()) - .all(|(lpat, rpat)| lpat.can_also_match(rpat)) - // `lpats_end` and `rpats_end` lengths may be disjointed, so start from the end and ignore any - // extras. - && lpats_end + lpats .iter() - .rev() - .zip(rpats_end.iter().rev()) + .zip(rpats.iter()) .all(|(lpat, rpat)| lpat.can_also_match(rpat)) }, (Self::Path(x), Self::Path(y)) => x == y, @@ -316,10 +343,10 @@ impl<'hir> ResolvedPat<'hir> { (Self::LitBytes(x), Self::LitBytes(y)) => x == y, (Self::LitInt(x), Self::LitInt(y)) => x == y, (Self::LitBool(x), Self::LitBool(y)) => x == y, - (Self::Range(x), Self::Range(y)) => x.overlaps(y), - (Self::Range(range), Self::LitInt(x)) | (Self::LitInt(x), Self::Range(range)) => range.contains(*x), + (Self::Range(ref x), Self::Range(ref y)) => x.overlaps(y), + (Self::Range(ref range), Self::LitInt(x)) | (Self::LitInt(x), Self::Range(ref range)) => range.contains(x), - // Todo: Lit* with Path, Range with Path, LitBytes with Sequence + // Todo: Lit* with Path, Range with Path, LitBytes with Slice, Slice with Slice _ => true, } } From 4f8f4b4c82e4647a2464e364ff96cc1dc5cb8e9a Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 6 Jan 2022 14:54:57 -0500 Subject: [PATCH 25/50] Handle slice patterns in `match_same_arms` --- clippy_lints/src/matches/match_same_arms.rs | 53 ++++++++++++++------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index 0afac347d0805..39c1f0d0d0186 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -21,27 +21,30 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { }; let arena = DroplessArena::default(); - let resolved_pats: Vec<_> = arms.iter().map(|a| ResolvedPat::from_pat(cx, &arena, a.pat)).collect(); + let normalized_pats: Vec<_> = arms + .iter() + .map(|a| NormalizedPat::from_pat(cx, &arena, a.pat)) + .collect(); // The furthast forwards a pattern can move without semantic changes - let forwards_blocking_idxs: Vec<_> = resolved_pats + let forwards_blocking_idxs: Vec<_> = normalized_pats .iter() .enumerate() .map(|(i, pat)| { - resolved_pats[i + 1..] + normalized_pats[i + 1..] .iter() .enumerate() .find_map(|(j, other)| pat.can_also_match(other).then(|| i + 1 + j)) - .unwrap_or(resolved_pats.len()) + .unwrap_or(normalized_pats.len()) }) .collect(); // The furthast backwards a pattern can move without semantic changes - let backwards_blocking_idxs: Vec<_> = resolved_pats + let backwards_blocking_idxs: Vec<_> = normalized_pats .iter() .enumerate() .map(|(i, pat)| { - resolved_pats[..i] + normalized_pats[..i] .iter() .enumerate() .rev() @@ -133,18 +136,18 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { } #[derive(Clone, Copy)] -enum ResolvedPat<'hir, 'arena> { +enum NormalizedPat<'a> { Wild, - Struct(Option, &'arena [(Symbol, Self)]), - Tuple(Option, &'arena [Self]), - Or(&'arena [Self]), + Struct(Option, &'a [(Symbol, Self)]), + Tuple(Option, &'a [Self]), + Or(&'a [Self]), Path(Option), LitStr(Symbol), - LitBytes(&'hir [u8]), + LitBytes(&'a [u8]), LitInt(u128), LitBool(bool), Range(PatRange), - Slice(&'arena [Self], &'arena [Self], bool), + Slice(&'a [Self], Option<&'a [Self]>), } #[derive(Clone, Copy)] @@ -183,9 +186,9 @@ impl PatRange { } #[allow(clippy::similar_names)] -impl<'hir, 'arena> ResolvedPat<'hir, 'arena> { +impl<'a> NormalizedPat<'a> { #[allow(clippy::too_many_lines)] - fn from_pat(cx: &LateContext<'_>, arena: &'arena DroplessArena, pat: &'hir Pat<'_>) -> Self { + fn from_pat(cx: &LateContext<'_>, arena: &'a DroplessArena, pat: &'a Pat<'_>) -> Self { match pat.kind { PatKind::Wild | PatKind::Binding(.., None) => Self::Wild, PatKind::Binding(.., Some(pat)) | PatKind::Box(pat) | PatKind::Ref(pat, _) => { @@ -284,8 +287,7 @@ impl<'hir, 'arena> ResolvedPat<'hir, 'arena> { }, PatKind::Slice(front, wild_pat, back) => Self::Slice( arena.alloc_from_iter(front.iter().map(|pat| Self::from_pat(cx, arena, pat))), - arena.alloc_from_iter(back.iter().map(|pat| Self::from_pat(cx, arena, pat))), - wild_pat.is_some(), + wild_pat.map(|_| &*arena.alloc_from_iter(back.iter().map(|pat| Self::from_pat(cx, arena, pat)))), ), } } @@ -345,6 +347,25 @@ impl<'hir, 'arena> ResolvedPat<'hir, 'arena> { (Self::LitBool(x), Self::LitBool(y)) => x == y, (Self::Range(ref x), Self::Range(ref y)) => x.overlaps(y), (Self::Range(ref range), Self::LitInt(x)) | (Self::LitInt(x), Self::Range(ref range)) => range.contains(x), + (Self::Slice(lpats, None), Self::Slice(rpats, None)) => { + lpats.len() == rpats.len() && lpats.iter().zip(rpats.iter()).all(|(x, y)| x.can_also_match(y)) + }, + (Self::Slice(pats, None), Self::Slice(front, Some(back))) + | (Self::Slice(front, Some(back)), Self::Slice(pats, None)) => { + if pats.len() < front.len() + back.len() { + return false; + } + pats[..front.len()] + .iter() + .zip(front.iter()) + .chain(pats[pats.len() - back.len()..].iter().zip(back.iter())) + .all(|(x, y)| x.can_also_match(y)) + }, + (Self::Slice(lfront, Some(lback)), Self::Slice(rfront, Some(rback))) => lfront + .iter() + .zip(rfront.iter()) + .chain(lback.iter().rev().zip(rback.iter().rev())) + .all(|(x, y)| x.can_also_match(y)), // Todo: Lit* with Path, Range with Path, LitBytes with Slice, Slice with Slice _ => true, From 08a7157a379ee7f794db386497f5cf1a4ee69fcc Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Fri, 21 Jan 2022 21:30:34 -0500 Subject: [PATCH 26/50] Improve message for `match_single_arms` --- clippy_lints/src/matches/match_same_arms.rs | 80 ++++--- tests/ui/match_same_arms.stderr | 165 +++++++-------- tests/ui/match_same_arms2.rs | 17 ++ tests/ui/match_same_arms2.stderr | 221 ++++++++++---------- 4 files changed, 249 insertions(+), 234 deletions(-) diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index 39c1f0d0d0186..5202df544f627 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -4,6 +4,7 @@ use clippy_utils::{path_to_local, search_same, SpanlessEq, SpanlessHash}; use core::iter; use rustc_arena::DroplessArena; use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::{Arm, Expr, ExprKind, HirId, HirIdMap, HirIdSet, Pat, PatKind, RangeEnd}; use rustc_lint::LateContext; @@ -13,6 +14,7 @@ use std::collections::hash_map::Entry; use super::MATCH_SAME_ARMS; +#[allow(clippy::too_many_lines)] pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 { let mut h = SpanlessHash::new(cx); @@ -96,42 +98,52 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { }; let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect(); - for (&(_, i), &(_, j)) in search_same(&indexed_arms, hash, eq) { - span_lint_and_then( - cx, - MATCH_SAME_ARMS, - j.body.span, - "this `match` has identical arm bodies", - |diag| { - diag.span_note(i.body.span, "same as this"); - - // Note: this does not use `span_suggestion` on purpose: - // there is no clean way - // to remove the other arm. Building a span and suggest to replace it to "" - // makes an even more confusing error message. Also in order not to make up a - // span for the whole pattern, the suggestion is only shown when there is only - // one pattern. The user should know about `|` if they are already using it… + for (&(i, arm1), &(j, arm2)) in search_same(&indexed_arms, hash, eq) { + if matches!(arm2.pat.kind, PatKind::Wild) { + span_lint_and_then( + cx, + MATCH_SAME_ARMS, + arm1.span, + "this match arm has an identical body to the `_` wildcard arm", + |diag| { + diag.span_suggestion( + arm1.span, + "try removing the arm", + String::new(), + Applicability::MaybeIncorrect, + ) + .help("or try changing either arm body") + .span_note(arm2.span, "`_` wildcard arm here"); + }, + ); + } else { + let back_block = backwards_blocking_idxs[j]; + let (keep_arm, move_arm) = if back_block < i || (back_block == 0 && forwards_blocking_idxs[i] <= j) { + (arm1, arm2) + } else { + (arm2, arm1) + }; - let lhs = snippet(cx, i.pat.span, ""); - let rhs = snippet(cx, j.pat.span, ""); + span_lint_and_then( + cx, + MATCH_SAME_ARMS, + keep_arm.span, + "this match arm has an identical body to another arm", + |diag| { + let move_pat_snip = snippet(cx, move_arm.pat.span, ""); + let keep_pat_snip = snippet(cx, keep_arm.pat.span, ""); - if let PatKind::Wild = j.pat.kind { - // if the last arm is _, then i could be integrated into _ - // note that i.pat cannot be _, because that would mean that we're - // hiding all the subsequent arms, and rust won't compile - diag.span_note( - i.body.span, - &format!( - "`{}` has the same arm body as the `_` wildcard, consider removing it", - lhs - ), - ); - } else { - diag.span_help(i.pat.span, &format!("consider refactoring into `{} | {}`", lhs, rhs,)) - .help("...or consider changing the match arm bodies"); - } - }, - ); + diag.span_suggestion( + keep_arm.pat.span, + "try merging the arm patterns", + format!("{} | {}", keep_pat_snip, move_pat_snip), + Applicability::MaybeIncorrect, + ) + .help("or try changing either arm body") + .span_note(move_arm.span, "other arm here"); + }, + ); + } } } diff --git a/tests/ui/match_same_arms.stderr b/tests/ui/match_same_arms.stderr index 7752a8a6ff2b4..b6d04263b37a3 100644 --- a/tests/ui/match_same_arms.stderr +++ b/tests/ui/match_same_arms.stderr @@ -1,128 +1,121 @@ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms.rs:13:14 +error: this match arm has an identical body to the `_` wildcard arm + --> $DIR/match_same_arms.rs:11:9 | -LL | _ => 0, //~ ERROR match arms have same body - | ^ +LL | Abc::A => 0, + | ^^^^^^^^^^^ help: try removing the arm | = note: `-D clippy::match-same-arms` implied by `-D warnings` -note: same as this - --> $DIR/match_same_arms.rs:11:19 + = help: or try changing either arm body +note: `_` wildcard arm here + --> $DIR/match_same_arms.rs:13:9 | -LL | Abc::A => 0, - | ^ -note: `Abc::A` has the same arm body as the `_` wildcard, consider removing it - --> $DIR/match_same_arms.rs:11:19 - | -LL | Abc::A => 0, - | ^ +LL | _ => 0, //~ ERROR match arms have same body + | ^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms.rs:18:20 - | -LL | (.., 3) => 42, //~ ERROR match arms have same body - | ^^ - | -note: same as this - --> $DIR/match_same_arms.rs:17:23 - | -LL | (1, .., 3) => 42, - | ^^ -help: consider refactoring into `(1, .., 3) | (.., 3)` +error: this match arm has an identical body to another arm --> $DIR/match_same_arms.rs:17:9 | LL | (1, .., 3) => 42, - | ^^^^^^^^^^ - = help: ...or consider changing the match arm bodies + | ----------^^^^^^ + | | + | help: try merging the arm patterns: `(1, .., 3) | (.., 3)` + | + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms.rs:18:9 + | +LL | (.., 3) => 42, //~ ERROR match arms have same body + | ^^^^^^^^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms.rs:24:15 +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms.rs:24:9 | LL | 51 => 1, //~ ERROR match arms have same body - | ^ + | --^^^^^ + | | + | help: try merging the arm patterns: `51 | 42` | -note: same as this - --> $DIR/match_same_arms.rs:23:15 - | -LL | 42 => 1, - | ^ -help: consider refactoring into `42 | 51` + = help: or try changing either arm body +note: other arm here --> $DIR/match_same_arms.rs:23:9 | LL | 42 => 1, - | ^^ - = help: ...or consider changing the match arm bodies + | ^^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms.rs:26:15 - | -LL | 52 => 2, //~ ERROR match arms have same body - | ^ - | -note: same as this - --> $DIR/match_same_arms.rs:25:15 - | -LL | 41 => 2, - | ^ -help: consider refactoring into `41 | 52` +error: this match arm has an identical body to another arm --> $DIR/match_same_arms.rs:25:9 | LL | 41 => 2, - | ^^ - = help: ...or consider changing the match arm bodies + | --^^^^^ + | | + | help: try merging the arm patterns: `41 | 52` + | + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms.rs:26:9 + | +LL | 52 => 2, //~ ERROR match arms have same body + | ^^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms.rs:32:14 +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms.rs:32:9 | LL | 2 => 2, //~ ERROR 2nd matched arms have same body - | ^ - | -note: same as this - --> $DIR/match_same_arms.rs:31:14 + | -^^^^^ + | | + | help: try merging the arm patterns: `2 | 1` | -LL | 1 => 2, - | ^ -help: consider refactoring into `1 | 2` + = help: or try changing either arm body +note: other arm here --> $DIR/match_same_arms.rs:31:9 | LL | 1 => 2, - | ^ - = help: ...or consider changing the match arm bodies + | ^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms.rs:33:14 +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms.rs:33:9 | LL | 3 => 2, //~ ERROR 3rd matched arms have same body - | ^ - | -note: same as this - --> $DIR/match_same_arms.rs:31:14 + | -^^^^^ + | | + | help: try merging the arm patterns: `3 | 1` | -LL | 1 => 2, - | ^ -help: consider refactoring into `1 | 3` + = help: or try changing either arm body +note: other arm here --> $DIR/match_same_arms.rs:31:9 | LL | 1 => 2, - | ^ - = help: ...or consider changing the match arm bodies + | ^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms.rs:50:55 +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms.rs:32:9 | -LL | CommandInfo::External { name, .. } => name.to_string(), - | ^^^^^^^^^^^^^^^^ +LL | 2 => 2, //~ ERROR 2nd matched arms have same body + | -^^^^^ + | | + | help: try merging the arm patterns: `2 | 3` | -note: same as this - --> $DIR/match_same_arms.rs:49:54 + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms.rs:33:9 | -LL | CommandInfo::BuiltIn { name, .. } => name.to_string(), - | ^^^^^^^^^^^^^^^^ -help: consider refactoring into `CommandInfo::BuiltIn { name, .. } | CommandInfo::External { name, .. }` +LL | 3 => 2, //~ ERROR 3rd matched arms have same body + | ^^^^^^ + +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms.rs:50:17 + | +LL | CommandInfo::External { name, .. } => name.to_string(), + | ----------------------------------^^^^^^^^^^^^^^^^^^^^ + | | + | help: try merging the arm patterns: `CommandInfo::External { name, .. } | CommandInfo::BuiltIn { name, .. }` + | + = help: or try changing either arm body +note: other arm here --> $DIR/match_same_arms.rs:49:17 | LL | CommandInfo::BuiltIn { name, .. } => name.to_string(), - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = help: ...or consider changing the match arm bodies + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 7 previous errors +error: aborting due to 8 previous errors diff --git a/tests/ui/match_same_arms2.rs b/tests/ui/match_same_arms2.rs index 6dc6c4172ee09..fdd88f25529e7 100644 --- a/tests/ui/match_same_arms2.rs +++ b/tests/ui/match_same_arms2.rs @@ -181,10 +181,27 @@ fn main() { Z(u32), } + // Don't lint. `Foo::X(0)` and `Foo::Z(_)` overlap with the arm in between. let _ = match Foo::X(0) { Foo::X(0) => 1, Foo::X(_) | Foo::Y(_) | Foo::Z(0) => 2, Foo::Z(_) => 1, _ => 0, }; + + // Suggest moving `Foo::Z(_)` up. + let _ = match Foo::X(0) { + Foo::X(0) => 1, + Foo::X(_) | Foo::Y(_) => 2, + Foo::Z(_) => 1, + _ => 0, + }; + + // Suggest moving `Foo::X(0)` down. + let _ = match Foo::X(0) { + Foo::X(0) => 1, + Foo::Y(_) | Foo::Z(0) => 2, + Foo::Z(_) => 1, + _ => 0, + }; } diff --git a/tests/ui/match_same_arms2.stderr b/tests/ui/match_same_arms2.stderr index e1ed12f937087..596cc8432b315 100644 --- a/tests/ui/match_same_arms2.stderr +++ b/tests/ui/match_same_arms2.stderr @@ -1,175 +1,138 @@ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms2.rs:20:14 +error: this match arm has an identical body to the `_` wildcard arm + --> $DIR/match_same_arms2.rs:11:9 | -LL | _ => { - | ______________^ -LL | | //~ ERROR match arms have same body +LL | / 42 => { LL | | foo(); LL | | let mut a = 42 + [23].len() as i32; +LL | | if true { ... | LL | | a LL | | }, - | |_________^ + | |_________^ help: try removing the arm | = note: `-D clippy::match-same-arms` implied by `-D warnings` -note: same as this - --> $DIR/match_same_arms2.rs:11:15 - | -LL | 42 => { - | _______________^ -LL | | foo(); -LL | | let mut a = 42 + [23].len() as i32; -LL | | if true { -... | -LL | | a -LL | | }, - | |_________^ -note: `42` has the same arm body as the `_` wildcard, consider removing it - --> $DIR/match_same_arms2.rs:11:15 + = help: or try changing either arm body +note: `_` wildcard arm here + --> $DIR/match_same_arms2.rs:20:9 | -LL | 42 => { - | _______________^ +LL | / _ => { +LL | | //~ ERROR match arms have same body LL | | foo(); LL | | let mut a = 42 + [23].len() as i32; -LL | | if true { ... | LL | | a LL | | }, | |_________^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms2.rs:34:15 +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms2.rs:34:9 | LL | 51 => foo(), //~ ERROR match arms have same body - | ^^^^^ - | -note: same as this - --> $DIR/match_same_arms2.rs:33:15 + | --^^^^^^^^^ + | | + | help: try merging the arm patterns: `51 | 42` | -LL | 42 => foo(), - | ^^^^^ -help: consider refactoring into `42 | 51` + = help: or try changing either arm body +note: other arm here --> $DIR/match_same_arms2.rs:33:9 | LL | 42 => foo(), - | ^^ - = help: ...or consider changing the match arm bodies + | ^^^^^^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms2.rs:40:17 - | -LL | None => 24, //~ ERROR match arms have same body - | ^^ - | -note: same as this - --> $DIR/match_same_arms2.rs:39:20 - | -LL | Some(_) => 24, - | ^^ -help: consider refactoring into `Some(_) | None` +error: this match arm has an identical body to another arm --> $DIR/match_same_arms2.rs:39:9 | LL | Some(_) => 24, - | ^^^^^^^ - = help: ...or consider changing the match arm bodies - -error: this `match` has identical arm bodies - --> $DIR/match_same_arms2.rs:62:28 + | -------^^^^^^ + | | + | help: try merging the arm patterns: `Some(_) | None` | -LL | (None, Some(a)) => bar(a), //~ ERROR match arms have same body - | ^^^^^^ - | -note: same as this - --> $DIR/match_same_arms2.rs:61:28 + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms2.rs:40:9 | -LL | (Some(a), None) => bar(a), - | ^^^^^^ -help: consider refactoring into `(Some(a), None) | (None, Some(a))` +LL | None => 24, //~ ERROR match arms have same body + | ^^^^^^^^^^ + +error: this match arm has an identical body to another arm --> $DIR/match_same_arms2.rs:61:9 | LL | (Some(a), None) => bar(a), - | ^^^^^^^^^^^^^^^ - = help: ...or consider changing the match arm bodies - -error: this `match` has identical arm bodies - --> $DIR/match_same_arms2.rs:68:26 + | ---------------^^^^^^^^^^ + | | + | help: try merging the arm patterns: `(Some(a), None) | (None, Some(a))` | -LL | (.., Some(a)) => bar(a), //~ ERROR match arms have same body - | ^^^^^^ - | -note: same as this - --> $DIR/match_same_arms2.rs:67:26 + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms2.rs:62:9 | -LL | (Some(a), ..) => bar(a), - | ^^^^^^ -help: consider refactoring into `(Some(a), ..) | (.., Some(a))` +LL | (None, Some(a)) => bar(a), //~ ERROR match arms have same body + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this match arm has an identical body to another arm --> $DIR/match_same_arms2.rs:67:9 | LL | (Some(a), ..) => bar(a), - | ^^^^^^^^^^^^^ - = help: ...or consider changing the match arm bodies - -error: this `match` has identical arm bodies - --> $DIR/match_same_arms2.rs:102:29 - | -LL | (Ok(_), Some(x)) => println!("ok {}", x), - | ^^^^^^^^^^^^^^^^^^^^ + | -------------^^^^^^^^^^ + | | + | help: try merging the arm patterns: `(Some(a), ..) | (.., Some(a))` | -note: same as this - --> $DIR/match_same_arms2.rs:101:29 + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms2.rs:68:9 | -LL | (Ok(x), Some(_)) => println!("ok {}", x), - | ^^^^^^^^^^^^^^^^^^^^ -help: consider refactoring into `(Ok(x), Some(_)) | (Ok(_), Some(x))` +LL | (.., Some(a)) => bar(a), //~ ERROR match arms have same body + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: this match arm has an identical body to another arm --> $DIR/match_same_arms2.rs:101:9 | LL | (Ok(x), Some(_)) => println!("ok {}", x), - | ^^^^^^^^^^^^^^^^ - = help: ...or consider changing the match arm bodies - = note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) + | ----------------^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: try merging the arm patterns: `(Ok(x), Some(_)) | (Ok(_), Some(x))` + | + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms2.rs:102:9 + | +LL | (Ok(_), Some(x)) => println!("ok {}", x), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms2.rs:117:18 +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms2.rs:117:9 | LL | Ok(_) => println!("ok"), - | ^^^^^^^^^^^^^^ + | -----^^^^^^^^^^^^^^^^^^ + | | + | help: try merging the arm patterns: `Ok(_) | Ok(3)` | -note: same as this - --> $DIR/match_same_arms2.rs:116:18 - | -LL | Ok(3) => println!("ok"), - | ^^^^^^^^^^^^^^ -help: consider refactoring into `Ok(3) | Ok(_)` + = help: or try changing either arm body +note: other arm here --> $DIR/match_same_arms2.rs:116:9 | LL | Ok(3) => println!("ok"), - | ^^^^^ - = help: ...or consider changing the match arm bodies - = note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) + | ^^^^^^^^^^^^^^^^^^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms2.rs:144:14 +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms2.rs:144:9 | LL | 1 => { - | ______________^ + | ^ help: try merging the arm patterns: `1 | 0` + | _________| + | | LL | | empty!(0); LL | | }, | |_________^ | -note: same as this - --> $DIR/match_same_arms2.rs:141:14 + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms2.rs:141:9 | -LL | 0 => { - | ______________^ +LL | / 0 => { LL | | empty!(0); LL | | }, | |_________^ -help: consider refactoring into `0 | 1` - --> $DIR/match_same_arms2.rs:141:9 - | -LL | 0 => { - | ^ - = help: ...or consider changing the match arm bodies error: match expression looks like `matches!` macro --> $DIR/match_same_arms2.rs:162:16 @@ -184,5 +147,35 @@ LL | | }; | = note: `-D clippy::match-like-matches-macro` implied by `-D warnings` -error: aborting due to 9 previous errors +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms2.rs:194:9 + | +LL | Foo::X(0) => 1, + | ---------^^^^^ + | | + | help: try merging the arm patterns: `Foo::X(0) | Foo::Z(_)` + | + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms2.rs:196:9 + | +LL | Foo::Z(_) => 1, + | ^^^^^^^^^^^^^^ + +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms2.rs:204:9 + | +LL | Foo::Z(_) => 1, + | ---------^^^^^ + | | + | help: try merging the arm patterns: `Foo::Z(_) | Foo::X(0)` + | + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms2.rs:202:9 + | +LL | Foo::X(0) => 1, + | ^^^^^^^^^^^^^^ + +error: aborting due to 11 previous errors From 773d20341ad8061df9acca781bab7a0fb38ed684 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 16 Mar 2022 23:55:38 -0400 Subject: [PATCH 27/50] Fix mixed enum variant kinds + code cleanup --- clippy_lints/src/matches/match_same_arms.rs | 113 +++++++++++--------- clippy_lints/src/write.rs | 9 +- tests/ui/match_same_arms2.rs | 23 ++++ tests/ui/match_same_arms2.stderr | 41 ++++--- 4 files changed, 123 insertions(+), 63 deletions(-) diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index 5202df544f627..b8591fe0db0ad 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -1,7 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use clippy_utils::{path_to_local, search_same, SpanlessEq, SpanlessHash}; +use core::cmp::Ordering; use core::iter; +use core::slice; use rustc_arena::DroplessArena; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; @@ -36,7 +38,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { normalized_pats[i + 1..] .iter() .enumerate() - .find_map(|(j, other)| pat.can_also_match(other).then(|| i + 1 + j)) + .find_map(|(j, other)| pat.has_overlapping_values(other).then(|| i + 1 + j)) .unwrap_or(normalized_pats.len()) }) .collect(); @@ -52,7 +54,9 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { .rev() .zip(forwards_blocking_idxs[..i].iter().copied().rev()) .skip_while(|&(_, forward_block)| forward_block > i) - .find_map(|((j, other), forward_block)| (forward_block == i || pat.can_also_match(other)).then(|| j)) + .find_map(|((j, other), forward_block)| { + (forward_block == i || pat.has_overlapping_values(other)).then(|| j) + }) .unwrap_or(0) }) .collect(); @@ -159,6 +163,10 @@ enum NormalizedPat<'a> { LitInt(u128), LitBool(bool), Range(PatRange), + /// A slice pattern. If the second value is `None`, then this matches an exact size. Otherwise + /// the first value contains everything before the `..` wildcard pattern, and the second value + /// contains everything afterwards. Note that either side, or both sides, may contain zero + /// patterns. Slice(&'a [Self], Option<&'a [Self]>), } @@ -178,23 +186,43 @@ impl PatRange { } fn overlaps(&self, other: &Self) -> bool { - !(self.is_empty() || other.is_empty()) - && match self.bounds { - RangeEnd::Included => self.end >= other.start, - RangeEnd::Excluded => self.end > other.start, - } - && match other.bounds { - RangeEnd::Included => self.start <= other.end, - RangeEnd::Excluded => self.start < other.end, - } + // Note: Empty ranges are impossible, so this is correct even though it would return true if an + // empty exclusive range were to reside within an inclusive range. + (match self.bounds { + RangeEnd::Included => self.end >= other.start, + RangeEnd::Excluded => self.end > other.start, + } && match other.bounds { + RangeEnd::Included => self.start <= other.end, + RangeEnd::Excluded => self.start < other.end, + }) } +} - fn is_empty(&self) -> bool { - match self.bounds { - RangeEnd::Included => false, - RangeEnd::Excluded => self.start == self.end, +/// Iterates over the pairs of fields with matching names. +fn iter_matching_struct_fields<'a>( + left: &'a [(Symbol, NormalizedPat<'a>)], + right: &'a [(Symbol, NormalizedPat<'a>)], +) -> impl Iterator, &'a NormalizedPat<'a>)> + 'a { + struct Iter<'a>( + slice::Iter<'a, (Symbol, NormalizedPat<'a>)>, + slice::Iter<'a, (Symbol, NormalizedPat<'a>)>, + ); + impl<'a> Iterator for Iter<'a> { + type Item = (&'a NormalizedPat<'a>, &'a NormalizedPat<'a>); + fn next(&mut self) -> Option { + // Note: all the fields in each slice are sorted by symbol value. + let mut left = self.0.next()?; + let mut right = self.1.next()?; + loop { + match left.0.cmp(&right.0) { + Ordering::Equal => return Some((&left.1, &right.1)), + Ordering::Less => left = self.0.next()?, + Ordering::Greater => right = self.1.next()?, + } + } } } + Iter(left.iter(), right.iter()) } #[allow(clippy::similar_names)] @@ -259,6 +287,7 @@ impl<'a> NormalizedPat<'a> { Self::Tuple(None, pats) }, PatKind::Lit(e) => match &e.kind { + // TODO: Handle negative integers. They're currently treated as a wild match. ExprKind::Lit(lit) => match lit.node { LitKind::Str(sym, _) => Self::LitStr(sym), LitKind::ByteStr(ref bytes) => Self::LitBytes(&**bytes), @@ -271,6 +300,7 @@ impl<'a> NormalizedPat<'a> { _ => Self::Wild, }, PatKind::Range(start, end, bounds) => { + // TODO: Handle negative integers. They're currently treated as a wild match. let start = match start { None => 0, Some(e) => match &e.kind { @@ -306,42 +336,17 @@ impl<'a> NormalizedPat<'a> { /// Checks if two patterns overlap in the values they can match assuming they are for the same /// type. - fn can_also_match(&self, other: &Self) -> bool { + fn has_overlapping_values(&self, other: &Self) -> bool { match (*self, *other) { (Self::Wild, _) | (_, Self::Wild) => true, (Self::Or(pats), ref other) | (ref other, Self::Or(pats)) => { - pats.iter().any(|pat| pat.can_also_match(other)) + pats.iter().any(|pat| pat.has_overlapping_values(other)) }, (Self::Struct(lpath, lfields), Self::Struct(rpath, rfields)) => { if lpath != rpath { return false; } - let mut rfields = rfields.iter(); - let mut rfield = match rfields.next() { - Some(x) => x, - None => return true, - }; - 'outer: for lfield in lfields { - loop { - if lfield.0 < rfield.0 { - continue 'outer; - } else if lfield.0 > rfield.0 { - rfield = match rfields.next() { - Some(x) => x, - None => return true, - }; - } else if !lfield.1.can_also_match(&rfield.1) { - return false; - } else { - rfield = match rfields.next() { - Some(x) => x, - None => return true, - }; - continue 'outer; - } - } - } - true + iter_matching_struct_fields(lfields, rfields).all(|(lpat, rpat)| lpat.has_overlapping_values(rpat)) }, (Self::Tuple(lpath, lpats), Self::Tuple(rpath, rpats)) => { if lpath != rpath { @@ -350,7 +355,7 @@ impl<'a> NormalizedPat<'a> { lpats .iter() .zip(rpats.iter()) - .all(|(lpat, rpat)| lpat.can_also_match(rpat)) + .all(|(lpat, rpat)| lpat.has_overlapping_values(rpat)) }, (Self::Path(x), Self::Path(y)) => x == y, (Self::LitStr(x), Self::LitStr(y)) => x == y, @@ -360,10 +365,12 @@ impl<'a> NormalizedPat<'a> { (Self::Range(ref x), Self::Range(ref y)) => x.overlaps(y), (Self::Range(ref range), Self::LitInt(x)) | (Self::LitInt(x), Self::Range(ref range)) => range.contains(x), (Self::Slice(lpats, None), Self::Slice(rpats, None)) => { - lpats.len() == rpats.len() && lpats.iter().zip(rpats.iter()).all(|(x, y)| x.can_also_match(y)) + lpats.len() == rpats.len() && lpats.iter().zip(rpats.iter()).all(|(x, y)| x.has_overlapping_values(y)) }, (Self::Slice(pats, None), Self::Slice(front, Some(back))) | (Self::Slice(front, Some(back)), Self::Slice(pats, None)) => { + // Here `pats` is an exact size match. If the combined lengths of `front` and `back` are greater + // then the minium length required will be greater than the length of `pats`. if pats.len() < front.len() + back.len() { return false; } @@ -371,15 +378,25 @@ impl<'a> NormalizedPat<'a> { .iter() .zip(front.iter()) .chain(pats[pats.len() - back.len()..].iter().zip(back.iter())) - .all(|(x, y)| x.can_also_match(y)) + .all(|(x, y)| x.has_overlapping_values(y)) }, (Self::Slice(lfront, Some(lback)), Self::Slice(rfront, Some(rback))) => lfront .iter() .zip(rfront.iter()) .chain(lback.iter().rev().zip(rback.iter().rev())) - .all(|(x, y)| x.can_also_match(y)), + .all(|(x, y)| x.has_overlapping_values(y)), + + // Enums can mix unit variants with tuple/struct variants. These can never overlap. + (Self::Path(_), Self::Tuple(..) | Self::Struct(..)) + | (Self::Tuple(..) | Self::Struct(..), Self::Path(_)) => false, + + // Tuples can be matched like a struct. + (Self::Tuple(x, _), Self::Struct(y, _)) | (Self::Struct(x, _), Self::Tuple(y, _)) => { + // TODO: check fields here. + x == y + }, - // Todo: Lit* with Path, Range with Path, LitBytes with Slice, Slice with Slice + // TODO: Lit* with Path, Range with Path, LitBytes with Slice _ => true, } } diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index 532bd810a2e30..f3d818cc3485d 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -581,14 +581,19 @@ impl Write { }; let replacement: String = match lit.token.kind { - LitKind::Integer | LitKind::Float | LitKind::Err => continue, LitKind::StrRaw(_) | LitKind::ByteStrRaw(_) if matches!(fmtstr.style, StrStyle::Raw(_)) => { lit.token.symbol.as_str().replace('{', "{{").replace('}', "}}") }, LitKind::Str | LitKind::ByteStr if matches!(fmtstr.style, StrStyle::Cooked) => { lit.token.symbol.as_str().replace('{', "{{").replace('}', "}}") }, - LitKind::StrRaw(_) | LitKind::Str | LitKind::ByteStrRaw(_) | LitKind::ByteStr => continue, + LitKind::StrRaw(_) + | LitKind::Str + | LitKind::ByteStrRaw(_) + | LitKind::ByteStr + | LitKind::Integer + | LitKind::Float + | LitKind::Err => continue, LitKind::Byte | LitKind::Char => match lit.token.symbol.as_str() { "\"" if matches!(fmtstr.style, StrStyle::Cooked) => "\\\"", "\"" if matches!(fmtstr.style, StrStyle::Raw(0)) => continue, diff --git a/tests/ui/match_same_arms2.rs b/tests/ui/match_same_arms2.rs index fdd88f25529e7..dbfeb4379d513 100644 --- a/tests/ui/match_same_arms2.rs +++ b/tests/ui/match_same_arms2.rs @@ -204,4 +204,27 @@ fn main() { Foo::Z(_) => 1, _ => 0, }; + + // Don't lint. + let _ = match 0 { + -2 => 1, + -5..=50 => 2, + -150..=88 => 1, + _ => 3, + }; + + struct Bar { + x: u32, + y: u32, + z: u32, + } + + // Lint. + let _ = match None { + Some(Bar { x: 0, y: 5, .. }) => 1, + Some(Bar { y: 10, z: 0, .. }) => 2, + None => 50, + Some(Bar { y: 0, x: 5, .. }) => 1, + _ => 200, + }; } diff --git a/tests/ui/match_same_arms2.stderr b/tests/ui/match_same_arms2.stderr index 596cc8432b315..14a672ba2fec1 100644 --- a/tests/ui/match_same_arms2.stderr +++ b/tests/ui/match_same_arms2.stderr @@ -40,33 +40,33 @@ LL | 42 => foo(), | ^^^^^^^^^^^ error: this match arm has an identical body to another arm - --> $DIR/match_same_arms2.rs:39:9 + --> $DIR/match_same_arms2.rs:40:9 | -LL | Some(_) => 24, - | -------^^^^^^ +LL | None => 24, //~ ERROR match arms have same body + | ----^^^^^^ | | - | help: try merging the arm patterns: `Some(_) | None` + | help: try merging the arm patterns: `None | Some(_)` | = help: or try changing either arm body note: other arm here - --> $DIR/match_same_arms2.rs:40:9 + --> $DIR/match_same_arms2.rs:39:9 | -LL | None => 24, //~ ERROR match arms have same body - | ^^^^^^^^^^ +LL | Some(_) => 24, + | ^^^^^^^^^^^^^ error: this match arm has an identical body to another arm - --> $DIR/match_same_arms2.rs:61:9 + --> $DIR/match_same_arms2.rs:62:9 | -LL | (Some(a), None) => bar(a), +LL | (None, Some(a)) => bar(a), //~ ERROR match arms have same body | ---------------^^^^^^^^^^ | | - | help: try merging the arm patterns: `(Some(a), None) | (None, Some(a))` + | help: try merging the arm patterns: `(None, Some(a)) | (Some(a), None)` | = help: or try changing either arm body note: other arm here - --> $DIR/match_same_arms2.rs:62:9 + --> $DIR/match_same_arms2.rs:61:9 | -LL | (None, Some(a)) => bar(a), //~ ERROR match arms have same body +LL | (Some(a), None) => bar(a), | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: this match arm has an identical body to another arm @@ -177,5 +177,20 @@ note: other arm here LL | Foo::X(0) => 1, | ^^^^^^^^^^^^^^ -error: aborting due to 11 previous errors +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms2.rs:227:9 + | +LL | Some(Bar { y: 0, x: 5, .. }) => 1, + | ----------------------------^^^^^ + | | + | help: try merging the arm patterns: `Some(Bar { y: 0, x: 5, .. }) | Some(Bar { x: 0, y: 5, .. })` + | + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms2.rs:224:9 + | +LL | Some(Bar { x: 0, y: 5, .. }) => 1, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 12 previous errors From bd888d435423a47ce3bdbaec6c61a3cb093a7e18 Mon Sep 17 00:00:00 2001 From: dswij Date: Thu, 17 Mar 2022 14:33:09 +0800 Subject: [PATCH 28/50] Add some comments --- clippy_lints/src/methods/unnecessary_lazy_eval.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clippy_lints/src/methods/unnecessary_lazy_eval.rs b/clippy_lints/src/methods/unnecessary_lazy_eval.rs index 0dd41a4dacfc5..2369be7081294 100644 --- a/clippy_lints/src/methods/unnecessary_lazy_eval.rs +++ b/clippy_lints/src/methods/unnecessary_lazy_eval.rs @@ -48,6 +48,9 @@ pub(super) fn check<'tcx>( Applicability::MaybeIncorrect }; + // This is a duplicate of what's happening in clippy_lints::methods::method_call, + // which isn't ideal, We want to get the method call span, + // but prefer to avoid changing the signature of the function itself. if let hir::ExprKind::MethodCall(_, _, span) = expr.kind { span_lint_and_then(cx, UNNECESSARY_LAZY_EVALUATIONS, expr.span, msg, |diag| { diag.span_suggestion( From 3ede557a62fd30f107ba7a6af64386e8387b2408 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Thu, 17 Mar 2022 22:53:27 +0800 Subject: [PATCH 29/50] Fix typo in bug report repoduce -> reproduce --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 68877efc9e12f..b6f70a7f18300 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -18,7 +18,7 @@ body: id: reproducer attributes: label: Reproducer - description: Please provide the code and steps to repoduce the bug + description: Please provide the code and steps to reproduce the bug value: | I tried this code: From f49a2c345780abca24d4f5a5f9f821ea961c8fb6 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Thu, 17 Mar 2022 18:57:28 +0100 Subject: [PATCH 30/50] feat: add use_unwrap_or --- CHANGELOG.md | 1 + clippy_lints/src/lib.register_all.rs | 1 + clippy_lints/src/lib.register_complexity.rs | 1 + clippy_lints/src/lib.register_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + clippy_lints/src/use_unwrap_or.rs | 105 ++++++++++++++++++++ tests/ui/use_unwrap_or.rs | 27 +++++ tests/ui/use_unwrap_or.stderr | 19 ++++ 8 files changed, 157 insertions(+) create mode 100644 clippy_lints/src/use_unwrap_or.rs create mode 100644 tests/ui/use_unwrap_or.rs create mode 100644 tests/ui/use_unwrap_or.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bc393d604256..9475c674983c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3538,6 +3538,7 @@ Released 2018-09-13 [`upper_case_acronyms`]: https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms [`use_debug`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_debug [`use_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_self +[`use_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_unwrap_or [`used_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_binding [`useless_asref`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref [`useless_attribute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_attribute diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 653d4daa6b6fe..a42f4cb6d70f3 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -310,6 +310,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(unwrap::PANICKING_UNWRAP), LintId::of(unwrap::UNNECESSARY_UNWRAP), LintId::of(upper_case_acronyms::UPPER_CASE_ACRONYMS), + LintId::of(use_unwrap_or::USE_UNWRAP_OR), LintId::of(useless_conversion::USELESS_CONVERSION), LintId::of(vec::USELESS_VEC), LintId::of(vec_init_then_push::VEC_INIT_THEN_PUSH), diff --git a/clippy_lints/src/lib.register_complexity.rs b/clippy_lints/src/lib.register_complexity.rs index 68d6c6ce5f7da..94ff53c2a60b1 100644 --- a/clippy_lints/src/lib.register_complexity.rs +++ b/clippy_lints/src/lib.register_complexity.rs @@ -94,6 +94,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(unit_types::UNIT_ARG), LintId::of(unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(unwrap::UNNECESSARY_UNWRAP), + LintId::of(use_unwrap_or::USE_UNWRAP_OR), LintId::of(useless_conversion::USELESS_CONVERSION), LintId::of(zero_div_zero::ZERO_DIVIDED_BY_ZERO), ]) diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 1a45763a86965..d1e13647e7e40 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -528,6 +528,7 @@ store.register_lints(&[ unwrap_in_result::UNWRAP_IN_RESULT, upper_case_acronyms::UPPER_CASE_ACRONYMS, use_self::USE_SELF, + use_unwrap_or::USE_UNWRAP_OR, useless_conversion::USELESS_CONVERSION, vec::USELESS_VEC, vec_init_then_push::VEC_INIT_THEN_PUSH, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 504235d0d1ef0..9c9e9643bc966 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -394,6 +394,7 @@ mod unwrap; mod unwrap_in_result; mod upper_case_acronyms; mod use_self; +mod use_unwrap_or; mod useless_conversion; mod vec; mod vec_init_then_push; @@ -866,6 +867,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: ignore_publish: cargo_ignore_publish, }) }); + store.register_late_pass(|| Box::new(use_unwrap_or::UseUnwrapOr)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/use_unwrap_or.rs b/clippy_lints/src/use_unwrap_or.rs new file mode 100644 index 0000000000000..f42e28246d423 --- /dev/null +++ b/clippy_lints/src/use_unwrap_or.rs @@ -0,0 +1,105 @@ +use if_chain::if_chain; +use rustc_hir::*; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{Span, sym}; +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::ty::is_type_diagnostic_item; + + +declare_clippy_lint! { + /// ### What it does + /// Checks for `.or(…).unwrap()` calls to Options and Results. + /// + /// ### Why is this bad? + /// You should use `.unwrap_or(…)` instead for clarity. + /// + /// ### Example + /// ```rust + /// // Result + /// let port = result.or::(Ok(fallback)).unwrap(); + /// + /// // Option + /// let value = option.or(Some(fallback)).unwrap(); + /// ``` + /// Use instead: + /// ```rust + /// // Result + /// let port = result.unwrap_or(fallback); + /// + /// // Option + /// let value = option.unwrap_or(fallback); + /// ``` + #[clippy::version = "1.61.0"] + pub USE_UNWRAP_OR, + complexity, + "checks for `.or(…).unwrap()` calls to Options and Results." +} +declare_lint_pass!(UseUnwrapOr => [USE_UNWRAP_OR]); + +impl<'tcx> LateLintPass<'tcx> for UseUnwrapOr { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + + // look for x.or().unwrap() + if_chain! { + if let ExprKind::MethodCall(path, args, unwrap_span) = expr.kind; + if path.ident.name.as_str() == "unwrap"; + if let Some(caller) = args.first(); + if let ExprKind::MethodCall(caller_path, caller_args, or_span) = caller.kind; + if caller_path.ident.name.as_str() == "or"; + then { + let ty = cx.typeck_results().expr_ty(&caller_args[0]); // get type of x (we later check if it's Option or Result) + let title; + let arg = &caller_args[1]; // the argument or(xyz) is called with + + if is_type_diagnostic_item(&cx, ty, sym::Option) { + title = ".or(Some(…)).unwrap() found"; + if !is(arg, "Some") { + return; + } + + } else if is_type_diagnostic_item(&cx, ty, sym::Result) { + title = ".or(Ok(…)).unwrap() found"; + if !is(arg, "Ok") { + return; + } + } else { + // Someone has implemented a struct with .or(...).unwrap() chaining, + // but it's not an Option or a Result, so bail + return; + } + + // span = or_span + unwrap_span + let span = Span::new(or_span.lo(), unwrap_span.hi(), or_span.ctxt(), or_span.parent()); + + span_lint_and_help( + cx, + USE_UNWRAP_OR, + span, + title, + None, + "use `unwrap_or()` instead" + ); + } + } + } +} + +/// is expr a Call to name? +/// name might be "Some", "Ok", "Err", etc. +fn is<'a>(expr: &Expr<'a>, name: &str) -> bool { + if_chain! { + if let ExprKind::Call(some_expr, _some_args) = expr.kind; + if let ExprKind::Path(path) = &some_expr.kind; + if let QPath::Resolved(_, path) = path; + if let Some(path_segment) = path.segments.first(); + if path_segment.ident.name.as_str() == name; + then { + return true; + } + else { + return false; + } + } +} + diff --git a/tests/ui/use_unwrap_or.rs b/tests/ui/use_unwrap_or.rs new file mode 100644 index 0000000000000..0bfabaae8852f --- /dev/null +++ b/tests/ui/use_unwrap_or.rs @@ -0,0 +1,27 @@ +#![warn(clippy::use_unwrap_or)] + +struct SomeStruct {} +impl SomeStruct { + fn or(self, _: Option) -> Self { self } + fn unwrap(&self){} +} + +fn main() { + let option: Option<&str> = None; + let _ = option.or(Some("fallback")).unwrap(); // should trigger lint + + let result: Result<&str, &str> = Err("Error"); + let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger lint + + // Not Option/Result + let instance = SomeStruct {}; + let _ = instance.or(Some(SomeStruct {})).unwrap(); // should not trigger lint + + // None in or + let option: Option<&str> = None; + let _ = option.or(None).unwrap(); // should not trigger lint + + // Not Err in or + let result: Result<&str, &str> = Err("Error"); + let _ = result.or::<&str>(Err("Other Error")).unwrap(); // should not trigger lint +} diff --git a/tests/ui/use_unwrap_or.stderr b/tests/ui/use_unwrap_or.stderr new file mode 100644 index 0000000000000..5bbba41f7ac4e --- /dev/null +++ b/tests/ui/use_unwrap_or.stderr @@ -0,0 +1,19 @@ +error: .or(Some(…)).unwrap() found + --> $DIR/use_unwrap_or.rs:11:20 + | +LL | let _ = option.or(Some("fallback")).unwrap(); // should trigger lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::use-unwrap-or` implied by `-D warnings` + = help: use `unwrap_or()` instead + +error: .or(Ok(…)).unwrap() found + --> $DIR/use_unwrap_or.rs:14:20 + | +LL | let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `unwrap_or()` instead + +error: aborting due to 2 previous errors + From 3f00f074de3f6debe594e11066864a0da1dfe4af Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Thu, 17 Mar 2022 19:13:44 +0100 Subject: [PATCH 31/50] fix: fix tests --- clippy_lints/src/use_unwrap_or.rs | 22 +++++++++------------- tests/ui/use_unwrap_or.rs | 6 ++++-- tests/ui/use_unwrap_or.stderr | 4 ++-- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/clippy_lints/src/use_unwrap_or.rs b/clippy_lints/src/use_unwrap_or.rs index f42e28246d423..0536900044495 100644 --- a/clippy_lints/src/use_unwrap_or.rs +++ b/clippy_lints/src/use_unwrap_or.rs @@ -1,11 +1,10 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::ty::is_type_diagnostic_item; use if_chain::if_chain; -use rustc_hir::*; +use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::{Span, sym}; -use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::ty::is_type_diagnostic_item; - +use rustc_span::{sym, Span}; declare_clippy_lint! { /// ### What it does @@ -39,7 +38,6 @@ declare_lint_pass!(UseUnwrapOr => [USE_UNWRAP_OR]); impl<'tcx> LateLintPass<'tcx> for UseUnwrapOr { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - // look for x.or().unwrap() if_chain! { if let ExprKind::MethodCall(path, args, unwrap_span) = expr.kind; @@ -52,13 +50,13 @@ impl<'tcx> LateLintPass<'tcx> for UseUnwrapOr { let title; let arg = &caller_args[1]; // the argument or(xyz) is called with - if is_type_diagnostic_item(&cx, ty, sym::Option) { + if is_type_diagnostic_item(cx, ty, sym::Option) { title = ".or(Some(…)).unwrap() found"; if !is(arg, "Some") { return; } - } else if is_type_diagnostic_item(&cx, ty, sym::Result) { + } else if is_type_diagnostic_item(cx, ty, sym::Result) { title = ".or(Ok(…)).unwrap() found"; if !is(arg, "Ok") { return; @@ -90,16 +88,14 @@ impl<'tcx> LateLintPass<'tcx> for UseUnwrapOr { fn is<'a>(expr: &Expr<'a>, name: &str) -> bool { if_chain! { if let ExprKind::Call(some_expr, _some_args) = expr.kind; - if let ExprKind::Path(path) = &some_expr.kind; - if let QPath::Resolved(_, path) = path; + if let ExprKind::Path(QPath::Resolved(_, path)) = &some_expr.kind; if let Some(path_segment) = path.segments.first(); if path_segment.ident.name.as_str() == name; then { - return true; + true } else { - return false; + false } } } - diff --git a/tests/ui/use_unwrap_or.rs b/tests/ui/use_unwrap_or.rs index 0bfabaae8852f..685fab802063f 100644 --- a/tests/ui/use_unwrap_or.rs +++ b/tests/ui/use_unwrap_or.rs @@ -2,8 +2,10 @@ struct SomeStruct {} impl SomeStruct { - fn or(self, _: Option) -> Self { self } - fn unwrap(&self){} + fn or(self, _: Option) -> Self { + self + } + fn unwrap(&self) {} } fn main() { diff --git a/tests/ui/use_unwrap_or.stderr b/tests/ui/use_unwrap_or.stderr index 5bbba41f7ac4e..2e1e920795d81 100644 --- a/tests/ui/use_unwrap_or.stderr +++ b/tests/ui/use_unwrap_or.stderr @@ -1,5 +1,5 @@ error: .or(Some(…)).unwrap() found - --> $DIR/use_unwrap_or.rs:11:20 + --> $DIR/use_unwrap_or.rs:13:20 | LL | let _ = option.or(Some("fallback")).unwrap(); // should trigger lint | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -8,7 +8,7 @@ LL | let _ = option.or(Some("fallback")).unwrap(); // should trigger lint = help: use `unwrap_or()` instead error: .or(Ok(…)).unwrap() found - --> $DIR/use_unwrap_or.rs:14:20 + --> $DIR/use_unwrap_or.rs:16:20 | LL | let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger lint | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 747bb245e7cb131917e7a3828356c14a7316ca70 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Thu, 17 Mar 2022 19:29:59 +0100 Subject: [PATCH 32/50] fix: fix broken dogfood tests --- clippy_lints/src/use_unwrap_or.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/use_unwrap_or.rs b/clippy_lints/src/use_unwrap_or.rs index 0536900044495..29bdec2cae833 100644 --- a/clippy_lints/src/use_unwrap_or.rs +++ b/clippy_lints/src/use_unwrap_or.rs @@ -41,10 +41,10 @@ impl<'tcx> LateLintPass<'tcx> for UseUnwrapOr { // look for x.or().unwrap() if_chain! { if let ExprKind::MethodCall(path, args, unwrap_span) = expr.kind; - if path.ident.name.as_str() == "unwrap"; + if path.ident.name == sym::unwrap; if let Some(caller) = args.first(); if let ExprKind::MethodCall(caller_path, caller_args, or_span) = caller.kind; - if caller_path.ident.name.as_str() == "or"; + if caller_path.ident.name == sym::or; then { let ty = cx.typeck_results().expr_ty(&caller_args[0]); // get type of x (we later check if it's Option or Result) let title; From c22ff6cd6e9dd19b31f85b9464a986023d732944 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Thu, 17 Mar 2022 19:52:07 +0100 Subject: [PATCH 33/50] fix: fix clippy_lints --- clippy_lints/src/use_unwrap_or.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/clippy_lints/src/use_unwrap_or.rs b/clippy_lints/src/use_unwrap_or.rs index 29bdec2cae833..e42765512428e 100644 --- a/clippy_lints/src/use_unwrap_or.rs +++ b/clippy_lints/src/use_unwrap_or.rs @@ -15,18 +15,25 @@ declare_clippy_lint! { /// /// ### Example /// ```rust + /// # let fallback = "fallback"; /// // Result + /// # type Error = &'static str; + /// # let result: Result<&str, Error> = Err("error"); /// let port = result.or::(Ok(fallback)).unwrap(); /// /// // Option + /// # let option: Option<&str> = None; /// let value = option.or(Some(fallback)).unwrap(); /// ``` /// Use instead: /// ```rust + /// # let fallback = "fallback"; /// // Result + /// # let result: Result<&str, &str> = Err("error"); /// let port = result.unwrap_or(fallback); /// /// // Option + /// # let option: Option<&str> = None; /// let value = option.unwrap_or(fallback); /// ``` #[clippy::version = "1.61.0"] From 4493fc7d27449cc751d890de0f3b9390f284bfbb Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 18 Mar 2022 00:23:55 +0100 Subject: [PATCH 34/50] refactor: use pattern matching for MethodCall arguments Co-authored-by: xFrednet --- clippy_lints/src/use_unwrap_or.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/use_unwrap_or.rs b/clippy_lints/src/use_unwrap_or.rs index e42765512428e..bb777b08d8bf9 100644 --- a/clippy_lints/src/use_unwrap_or.rs +++ b/clippy_lints/src/use_unwrap_or.rs @@ -4,7 +4,7 @@ use if_chain::if_chain; use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::{sym, Span}; +use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -47,25 +47,22 @@ impl<'tcx> LateLintPass<'tcx> for UseUnwrapOr { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { // look for x.or().unwrap() if_chain! { - if let ExprKind::MethodCall(path, args, unwrap_span) = expr.kind; + if let ExprKind::MethodCall(path, [unwrap_self], unwrap_span) = expr.kind; if path.ident.name == sym::unwrap; - if let Some(caller) = args.first(); - if let ExprKind::MethodCall(caller_path, caller_args, or_span) = caller.kind; + if let ExprKind::MethodCall(caller_path, [or_self, or_arg], or_span) = unwrap_self.kind; if caller_path.ident.name == sym::or; then { - let ty = cx.typeck_results().expr_ty(&caller_args[0]); // get type of x (we later check if it's Option or Result) + let ty = cx.typeck_results().expr_ty(&or_self); // get type of x (we later check if it's Option or Result) let title; - let arg = &caller_args[1]; // the argument or(xyz) is called with if is_type_diagnostic_item(cx, ty, sym::Option) { title = ".or(Some(…)).unwrap() found"; - if !is(arg, "Some") { + if !is(or_arg, "Some") { return; } - } else if is_type_diagnostic_item(cx, ty, sym::Result) { title = ".or(Ok(…)).unwrap() found"; - if !is(arg, "Ok") { + if !is(or_arg, "Ok") { return; } } else { @@ -74,13 +71,10 @@ impl<'tcx> LateLintPass<'tcx> for UseUnwrapOr { return; } - // span = or_span + unwrap_span - let span = Span::new(or_span.lo(), unwrap_span.hi(), or_span.ctxt(), or_span.parent()); - span_lint_and_help( cx, USE_UNWRAP_OR, - span, + or_span.to(unwrap_span), title, None, "use `unwrap_or()` instead" From 17ff85081d969c125b3b0abeb0366d163853ee7a Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 18 Mar 2022 00:34:24 +0100 Subject: [PATCH 35/50] feat: rename variable in example --- clippy_lints/src/use_unwrap_or.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/use_unwrap_or.rs b/clippy_lints/src/use_unwrap_or.rs index bb777b08d8bf9..8dc28231995d7 100644 --- a/clippy_lints/src/use_unwrap_or.rs +++ b/clippy_lints/src/use_unwrap_or.rs @@ -19,7 +19,7 @@ declare_clippy_lint! { /// // Result /// # type Error = &'static str; /// # let result: Result<&str, Error> = Err("error"); - /// let port = result.or::(Ok(fallback)).unwrap(); + /// let value = result.or::(Ok(fallback)).unwrap(); /// /// // Option /// # let option: Option<&str> = None; @@ -30,7 +30,7 @@ declare_clippy_lint! { /// # let fallback = "fallback"; /// // Result /// # let result: Result<&str, &str> = Err("error"); - /// let port = result.unwrap_or(fallback); + /// let value = result.unwrap_or(fallback); /// /// // Option /// # let option: Option<&str> = None; From 44c62c9aa23e0d8401d9bdc1e2df913cfd035c7e Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 18 Mar 2022 00:51:26 +0100 Subject: [PATCH 36/50] feat: add tests and fix existing ones --- clippy_lints/src/use_unwrap_or.rs | 8 ++++---- tests/ui/use_unwrap_or.rs | 16 ++++++++++++++++ tests/ui/use_unwrap_or.stderr | 4 ++-- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/use_unwrap_or.rs b/clippy_lints/src/use_unwrap_or.rs index 8dc28231995d7..3e40014f50fd3 100644 --- a/clippy_lints/src/use_unwrap_or.rs +++ b/clippy_lints/src/use_unwrap_or.rs @@ -47,12 +47,12 @@ impl<'tcx> LateLintPass<'tcx> for UseUnwrapOr { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { // look for x.or().unwrap() if_chain! { - if let ExprKind::MethodCall(path, [unwrap_self], unwrap_span) = expr.kind; + if let ExprKind::MethodCall(path, [unwrap_self], unwrap_span) = &expr.kind; if path.ident.name == sym::unwrap; - if let ExprKind::MethodCall(caller_path, [or_self, or_arg], or_span) = unwrap_self.kind; + if let ExprKind::MethodCall(caller_path, [or_self, or_arg], or_span) = &unwrap_self.kind; if caller_path.ident.name == sym::or; then { - let ty = cx.typeck_results().expr_ty(&or_self); // get type of x (we later check if it's Option or Result) + let ty = cx.typeck_results().expr_ty(or_self); // get type of x (we later check if it's Option or Result) let title; if is_type_diagnostic_item(cx, ty, sym::Option) { @@ -74,7 +74,7 @@ impl<'tcx> LateLintPass<'tcx> for UseUnwrapOr { span_lint_and_help( cx, USE_UNWRAP_OR, - or_span.to(unwrap_span), + or_span.to(*unwrap_span), title, None, "use `unwrap_or()` instead" diff --git a/tests/ui/use_unwrap_or.rs b/tests/ui/use_unwrap_or.rs index 685fab802063f..dd55b33739db6 100644 --- a/tests/ui/use_unwrap_or.rs +++ b/tests/ui/use_unwrap_or.rs @@ -1,4 +1,5 @@ #![warn(clippy::use_unwrap_or)] +#![allow(clippy::map_identity)] struct SomeStruct {} impl SomeStruct { @@ -8,6 +9,14 @@ impl SomeStruct { fn unwrap(&self) {} } +struct SomeOtherStruct {} +impl SomeOtherStruct { + fn or(self) -> Self { + self + } + fn unwrap(&self) {} +} + fn main() { let option: Option<&str> = None; let _ = option.or(Some("fallback")).unwrap(); // should trigger lint @@ -19,6 +28,9 @@ fn main() { let instance = SomeStruct {}; let _ = instance.or(Some(SomeStruct {})).unwrap(); // should not trigger lint + let instance = SomeOtherStruct {}; + let _ = instance.or().unwrap(); // should not trigger lint and should not panic + // None in or let option: Option<&str> = None; let _ = option.or(None).unwrap(); // should not trigger lint @@ -26,4 +38,8 @@ fn main() { // Not Err in or let result: Result<&str, &str> = Err("Error"); let _ = result.or::<&str>(Err("Other Error")).unwrap(); // should not trigger lint + + // other function between + let option: Option<&str> = None; + let _ = option.or(Some("fallback")).map(|v| v).unwrap(); // should not trigger lint } diff --git a/tests/ui/use_unwrap_or.stderr b/tests/ui/use_unwrap_or.stderr index 2e1e920795d81..796778a293d45 100644 --- a/tests/ui/use_unwrap_or.stderr +++ b/tests/ui/use_unwrap_or.stderr @@ -1,5 +1,5 @@ error: .or(Some(…)).unwrap() found - --> $DIR/use_unwrap_or.rs:13:20 + --> $DIR/use_unwrap_or.rs:22:20 | LL | let _ = option.or(Some("fallback")).unwrap(); // should trigger lint | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -8,7 +8,7 @@ LL | let _ = option.or(Some("fallback")).unwrap(); // should trigger lint = help: use `unwrap_or()` instead error: .or(Ok(…)).unwrap() found - --> $DIR/use_unwrap_or.rs:16:20 + --> $DIR/use_unwrap_or.rs:25:20 | LL | let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger lint | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 39329d1d404d38437270acf3cb1337a231367efc Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 17 Mar 2022 16:04:45 -0400 Subject: [PATCH 37/50] Add lint `cast_enum_constructor` --- CHANGELOG.md | 1 + .../src/casts/cast_enum_constructor.rs | 21 +++++++++++++++++++ clippy_lints/src/casts/mod.rs | 21 +++++++++++++++++++ clippy_lints/src/lib.register_all.rs | 1 + clippy_lints/src/lib.register_lints.rs | 1 + clippy_lints/src/lib.register_suspicious.rs | 1 + tests/ui/cast_enum_constructor.rs | 17 +++++++++++++++ tests/ui/cast_enum_constructor.stderr | 16 ++++++++++++++ 8 files changed, 79 insertions(+) create mode 100644 clippy_lints/src/casts/cast_enum_constructor.rs create mode 100644 tests/ui/cast_enum_constructor.rs create mode 100644 tests/ui/cast_enum_constructor.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bc393d604256..04e072d0abf61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3069,6 +3069,7 @@ Released 2018-09-13 [`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth [`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata [`case_sensitive_file_extension_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#case_sensitive_file_extension_comparisons +[`cast_enum_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_constructor [`cast_enum_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_truncation [`cast_lossless`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless [`cast_possible_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_truncation diff --git a/clippy_lints/src/casts/cast_enum_constructor.rs b/clippy_lints/src/casts/cast_enum_constructor.rs new file mode 100644 index 0000000000000..1973692e105f7 --- /dev/null +++ b/clippy_lints/src/casts/cast_enum_constructor.rs @@ -0,0 +1,21 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +use super::CAST_ENUM_CONSTRUCTOR; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>) { + if matches!(cast_from.kind(), ty::FnDef(..)) + && let ExprKind::Path(path) = &cast_expr.kind + && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), _) = cx.qpath_res(path, cast_expr.hir_id) + { + span_lint( + cx, + CAST_ENUM_CONSTRUCTOR, + expr.span, + "cast of an enum tuple constructor to an integer", + ); + } +} diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 6a0eabd089d02..be59145afa003 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -1,3 +1,4 @@ +mod cast_enum_constructor; mod cast_lossless; mod cast_possible_truncation; mod cast_possible_wrap; @@ -454,6 +455,24 @@ declare_clippy_lint! { "casting using `as` between raw pointers to slices of types with different sizes" } +declare_clippy_lint! { + /// ### What it does + /// Checks for casts from an enum tuple constructor to an integer. + /// + /// ### Why is this bad? + /// The cast is easily confused with casting a c-like enum value to an integer. + /// + /// ### Example + /// ```rust + /// enum E { X(i32) }; + /// let _ = E::X as usize; + /// ``` + #[clippy::version = "1.61.0"] + pub CAST_ENUM_CONSTRUCTOR, + suspicious, + "casts from an enum tuple constructor to an integer" +} + pub struct Casts { msrv: Option, } @@ -481,6 +500,7 @@ impl_lint_pass!(Casts => [ CHAR_LIT_AS_U8, PTR_AS_PTR, CAST_ENUM_TRUNCATION, + CAST_ENUM_CONSTRUCTOR ]); impl<'tcx> LateLintPass<'tcx> for Casts { @@ -518,6 +538,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts { cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to); } cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv); + cast_enum_constructor::check(cx, expr, cast_expr, cast_from); } } diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 653d4daa6b6fe..e360757f781fe 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -23,6 +23,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON), LintId::of(booleans::LOGIC_BUG), LintId::of(booleans::NONMINIMAL_BOOL), + LintId::of(casts::CAST_ENUM_CONSTRUCTOR), LintId::of(casts::CAST_ENUM_TRUNCATION), LintId::of(casts::CAST_REF_TO_MUT), LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 1a45763a86965..a1a9ca37c95b9 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -70,6 +70,7 @@ store.register_lints(&[ cargo::REDUNDANT_FEATURE_NAMES, cargo::WILDCARD_DEPENDENCIES, case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + casts::CAST_ENUM_CONSTRUCTOR, casts::CAST_ENUM_TRUNCATION, casts::CAST_LOSSLESS, casts::CAST_POSSIBLE_TRUNCATION, diff --git a/clippy_lints/src/lib.register_suspicious.rs b/clippy_lints/src/lib.register_suspicious.rs index 465baa8258174..fa3a88e1368ce 100644 --- a/clippy_lints/src/lib.register_suspicious.rs +++ b/clippy_lints/src/lib.register_suspicious.rs @@ -7,6 +7,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec! LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS), LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK), LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF), + LintId::of(casts::CAST_ENUM_CONSTRUCTOR), LintId::of(casts::CAST_ENUM_TRUNCATION), LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE), LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS), diff --git a/tests/ui/cast_enum_constructor.rs b/tests/ui/cast_enum_constructor.rs new file mode 100644 index 0000000000000..0193454ad144c --- /dev/null +++ b/tests/ui/cast_enum_constructor.rs @@ -0,0 +1,17 @@ +#![warn(clippy::cast_enum_constructor)] +#![allow(clippy::fn_to_numeric_cast)] + +fn main() { + enum Foo { + Y(u32), + } + + enum Bar { + X, + } + + let _ = Foo::Y as usize; + let _ = Foo::Y as isize; + let _ = Foo::Y as fn(u32) -> Foo; + let _ = Bar::X as usize; +} diff --git a/tests/ui/cast_enum_constructor.stderr b/tests/ui/cast_enum_constructor.stderr new file mode 100644 index 0000000000000..710909dd26fa8 --- /dev/null +++ b/tests/ui/cast_enum_constructor.stderr @@ -0,0 +1,16 @@ +error: cast of an enum tuple constructor to an integer + --> $DIR/cast_enum_constructor.rs:13:13 + | +LL | let _ = Foo::Y as usize; + | ^^^^^^^^^^^^^^^ + | + = note: `-D clippy::cast-enum-constructor` implied by `-D warnings` + +error: cast of an enum tuple constructor to an integer + --> $DIR/cast_enum_constructor.rs:14:13 + | +LL | let _ = Foo::Y as isize; + | ^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + From 05e05eaed7f512bf2a1f7f236fc4b484d4a52aa5 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 18 Mar 2022 01:04:33 +0100 Subject: [PATCH 38/50] refactor: rename lint to or_then_unwrap --- CHANGELOG.md | 2 +- clippy_lints/src/lib.register_all.rs | 2 +- clippy_lints/src/lib.register_complexity.rs | 2 +- clippy_lints/src/lib.register_lints.rs | 2 +- clippy_lints/src/lib.rs | 4 ++-- clippy_lints/src/{use_unwrap_or.rs => or_then_unwrap.rs} | 8 ++++---- tests/ui/{use_unwrap_or.rs => or_then_unwrap.rs} | 2 +- tests/ui/{use_unwrap_or.stderr => or_then_unwrap.stderr} | 6 +++--- 8 files changed, 14 insertions(+), 14 deletions(-) rename clippy_lints/src/{use_unwrap_or.rs => or_then_unwrap.rs} (95%) rename tests/ui/{use_unwrap_or.rs => or_then_unwrap.rs} (97%) rename tests/ui/{use_unwrap_or.stderr => or_then_unwrap.stderr} (78%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9475c674983c6..b45be38bf4dcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3538,7 +3538,7 @@ Released 2018-09-13 [`upper_case_acronyms`]: https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms [`use_debug`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_debug [`use_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_self -[`use_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_unwrap_or +[`or_then_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_then_unwrap [`used_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_binding [`useless_asref`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref [`useless_attribute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_attribute diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index a42f4cb6d70f3..5c3e352d09fd8 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -310,7 +310,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(unwrap::PANICKING_UNWRAP), LintId::of(unwrap::UNNECESSARY_UNWRAP), LintId::of(upper_case_acronyms::UPPER_CASE_ACRONYMS), - LintId::of(use_unwrap_or::USE_UNWRAP_OR), + LintId::of(or_then_unwrap::OR_THEN_UNWRAP), LintId::of(useless_conversion::USELESS_CONVERSION), LintId::of(vec::USELESS_VEC), LintId::of(vec_init_then_push::VEC_INIT_THEN_PUSH), diff --git a/clippy_lints/src/lib.register_complexity.rs b/clippy_lints/src/lib.register_complexity.rs index 94ff53c2a60b1..45ad152039654 100644 --- a/clippy_lints/src/lib.register_complexity.rs +++ b/clippy_lints/src/lib.register_complexity.rs @@ -94,7 +94,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(unit_types::UNIT_ARG), LintId::of(unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(unwrap::UNNECESSARY_UNWRAP), - LintId::of(use_unwrap_or::USE_UNWRAP_OR), + LintId::of(or_then_unwrap::OR_THEN_UNWRAP), LintId::of(useless_conversion::USELESS_CONVERSION), LintId::of(zero_div_zero::ZERO_DIVIDED_BY_ZERO), ]) diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index d1e13647e7e40..aad0c8735eeba 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -528,7 +528,7 @@ store.register_lints(&[ unwrap_in_result::UNWRAP_IN_RESULT, upper_case_acronyms::UPPER_CASE_ACRONYMS, use_self::USE_SELF, - use_unwrap_or::USE_UNWRAP_OR, + or_then_unwrap::OR_THEN_UNWRAP, useless_conversion::USELESS_CONVERSION, vec::USELESS_VEC, vec_init_then_push::VEC_INIT_THEN_PUSH, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 9c9e9643bc966..fdab58935ef95 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -322,6 +322,7 @@ mod only_used_in_recursion; mod open_options; mod option_env_unwrap; mod option_if_let_else; +mod or_then_unwrap; mod overflow_check_conditional; mod panic_in_result_fn; mod panic_unimplemented; @@ -394,7 +395,6 @@ mod unwrap; mod unwrap_in_result; mod upper_case_acronyms; mod use_self; -mod use_unwrap_or; mod useless_conversion; mod vec; mod vec_init_then_push; @@ -867,7 +867,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: ignore_publish: cargo_ignore_publish, }) }); - store.register_late_pass(|| Box::new(use_unwrap_or::UseUnwrapOr)); + store.register_late_pass(|| Box::new(or_then_unwrap::OrThenUnwrap)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/use_unwrap_or.rs b/clippy_lints/src/or_then_unwrap.rs similarity index 95% rename from clippy_lints/src/use_unwrap_or.rs rename to clippy_lints/src/or_then_unwrap.rs index 3e40014f50fd3..d467fbdfe0286 100644 --- a/clippy_lints/src/use_unwrap_or.rs +++ b/clippy_lints/src/or_then_unwrap.rs @@ -37,13 +37,13 @@ declare_clippy_lint! { /// let value = option.unwrap_or(fallback); /// ``` #[clippy::version = "1.61.0"] - pub USE_UNWRAP_OR, + pub OR_THEN_UNWRAP, complexity, "checks for `.or(…).unwrap()` calls to Options and Results." } -declare_lint_pass!(UseUnwrapOr => [USE_UNWRAP_OR]); +declare_lint_pass!(OrThenUnwrap => [OR_THEN_UNWRAP]); -impl<'tcx> LateLintPass<'tcx> for UseUnwrapOr { +impl<'tcx> LateLintPass<'tcx> for OrThenUnwrap { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { // look for x.or().unwrap() if_chain! { @@ -73,7 +73,7 @@ impl<'tcx> LateLintPass<'tcx> for UseUnwrapOr { span_lint_and_help( cx, - USE_UNWRAP_OR, + OR_THEN_UNWRAP, or_span.to(*unwrap_span), title, None, diff --git a/tests/ui/use_unwrap_or.rs b/tests/ui/or_then_unwrap.rs similarity index 97% rename from tests/ui/use_unwrap_or.rs rename to tests/ui/or_then_unwrap.rs index dd55b33739db6..cbc3c387da0f7 100644 --- a/tests/ui/use_unwrap_or.rs +++ b/tests/ui/or_then_unwrap.rs @@ -1,4 +1,4 @@ -#![warn(clippy::use_unwrap_or)] +#![warn(clippy::or_then_unwrap)] #![allow(clippy::map_identity)] struct SomeStruct {} diff --git a/tests/ui/use_unwrap_or.stderr b/tests/ui/or_then_unwrap.stderr similarity index 78% rename from tests/ui/use_unwrap_or.stderr rename to tests/ui/or_then_unwrap.stderr index 796778a293d45..fdd718b358089 100644 --- a/tests/ui/use_unwrap_or.stderr +++ b/tests/ui/or_then_unwrap.stderr @@ -1,14 +1,14 @@ error: .or(Some(…)).unwrap() found - --> $DIR/use_unwrap_or.rs:22:20 + --> $DIR/or_then_unwrap.rs:22:20 | LL | let _ = option.or(Some("fallback")).unwrap(); // should trigger lint | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: `-D clippy::use-unwrap-or` implied by `-D warnings` + = note: `-D clippy::or-then-unwrap` implied by `-D warnings` = help: use `unwrap_or()` instead error: .or(Ok(…)).unwrap() found - --> $DIR/use_unwrap_or.rs:25:20 + --> $DIR/or_then_unwrap.rs:25:20 | LL | let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger lint | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 3c4192f3e9d2983c0fa2fc30fa76f63cb62b06fa Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 18 Mar 2022 01:12:39 +0100 Subject: [PATCH 39/50] fix: ran update_lints --- CHANGELOG.md | 2 +- clippy_lints/src/lib.register_all.rs | 2 +- clippy_lints/src/lib.register_complexity.rs | 2 +- clippy_lints/src/lib.register_lints.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b45be38bf4dcf..ea30b4d297503 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3366,6 +3366,7 @@ Released 2018-09-13 [`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn [`option_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_option [`or_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call +[`or_then_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_then_unwrap [`out_of_bounds_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#out_of_bounds_indexing [`overflow_check_conditional`]: https://rust-lang.github.io/rust-clippy/master/index.html#overflow_check_conditional [`panic`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic @@ -3538,7 +3539,6 @@ Released 2018-09-13 [`upper_case_acronyms`]: https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms [`use_debug`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_debug [`use_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_self -[`or_then_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_then_unwrap [`used_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_binding [`useless_asref`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref [`useless_attribute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_attribute diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 5c3e352d09fd8..9ff5f26c1fa43 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -238,6 +238,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION), LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS), LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP), + LintId::of(or_then_unwrap::OR_THEN_UNWRAP), LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL), LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL), LintId::of(precedence::PRECEDENCE), @@ -310,7 +311,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(unwrap::PANICKING_UNWRAP), LintId::of(unwrap::UNNECESSARY_UNWRAP), LintId::of(upper_case_acronyms::UPPER_CASE_ACRONYMS), - LintId::of(or_then_unwrap::OR_THEN_UNWRAP), LintId::of(useless_conversion::USELESS_CONVERSION), LintId::of(vec::USELESS_VEC), LintId::of(vec_init_then_push::VEC_INIT_THEN_PUSH), diff --git a/clippy_lints/src/lib.register_complexity.rs b/clippy_lints/src/lib.register_complexity.rs index 45ad152039654..34d764e810d9b 100644 --- a/clippy_lints/src/lib.register_complexity.rs +++ b/clippy_lints/src/lib.register_complexity.rs @@ -66,6 +66,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(no_effect::NO_EFFECT), LintId::of(no_effect::UNNECESSARY_OPERATION), LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION), + LintId::of(or_then_unwrap::OR_THEN_UNWRAP), LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL), LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL), LintId::of(precedence::PRECEDENCE), @@ -94,7 +95,6 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(unit_types::UNIT_ARG), LintId::of(unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(unwrap::UNNECESSARY_UNWRAP), - LintId::of(or_then_unwrap::OR_THEN_UNWRAP), LintId::of(useless_conversion::USELESS_CONVERSION), LintId::of(zero_div_zero::ZERO_DIVIDED_BY_ZERO), ]) diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index aad0c8735eeba..0b6e3c7e7df33 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -404,6 +404,7 @@ store.register_lints(&[ open_options::NONSENSICAL_OPEN_OPTIONS, option_env_unwrap::OPTION_ENV_UNWRAP, option_if_let_else::OPTION_IF_LET_ELSE, + or_then_unwrap::OR_THEN_UNWRAP, overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL, panic_in_result_fn::PANIC_IN_RESULT_FN, panic_unimplemented::PANIC, @@ -528,7 +529,6 @@ store.register_lints(&[ unwrap_in_result::UNWRAP_IN_RESULT, upper_case_acronyms::UPPER_CASE_ACRONYMS, use_self::USE_SELF, - or_then_unwrap::OR_THEN_UNWRAP, useless_conversion::USELESS_CONVERSION, vec::USELESS_VEC, vec_init_then_push::VEC_INIT_THEN_PUSH, From fd2c8601711554d31f0c836d5aa96623a25e63b6 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 18 Mar 2022 14:45:48 +0100 Subject: [PATCH 40/50] feat: add comment --- tests/ui/or_then_unwrap.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ui/or_then_unwrap.rs b/tests/ui/or_then_unwrap.rs index cbc3c387da0f7..8d7e50ad405b0 100644 --- a/tests/ui/or_then_unwrap.rs +++ b/tests/ui/or_then_unwrap.rs @@ -28,6 +28,7 @@ fn main() { let instance = SomeStruct {}; let _ = instance.or(Some(SomeStruct {})).unwrap(); // should not trigger lint + // or takes no argument let instance = SomeOtherStruct {}; let _ = instance.or().unwrap(); // should not trigger lint and should not panic From 34ad33c57aa9db1b0c8473925affef08fadc7b8a Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 18 Mar 2022 21:11:54 +0100 Subject: [PATCH 41/50] refactor: move into methods module --- clippy_lints/src/lib.register_all.rs | 2 +- clippy_lints/src/lib.register_complexity.rs | 2 +- clippy_lints/src/lib.register_lints.rs | 2 +- clippy_lints/src/lib.rs | 2 - clippy_lints/src/methods/mod.rs | 41 ++++++++ clippy_lints/src/methods/or_then_unwrap.rs | 68 +++++++++++++ clippy_lints/src/or_then_unwrap.rs | 102 -------------------- 7 files changed, 112 insertions(+), 107 deletions(-) create mode 100644 clippy_lints/src/methods/or_then_unwrap.rs delete mode 100644 clippy_lints/src/or_then_unwrap.rs diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 9ff5f26c1fa43..033efcb8a89cf 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -181,6 +181,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::OPTION_FILTER_MAP), LintId::of(methods::OPTION_MAP_OR_NONE), LintId::of(methods::OR_FUN_CALL), + LintId::of(methods::OR_THEN_UNWRAP), LintId::of(methods::RESULT_MAP_OR_INTO_OPTION), LintId::of(methods::SEARCH_IS_SOME), LintId::of(methods::SHOULD_IMPLEMENT_TRAIT), @@ -238,7 +239,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION), LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS), LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP), - LintId::of(or_then_unwrap::OR_THEN_UNWRAP), LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL), LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL), LintId::of(precedence::PRECEDENCE), diff --git a/clippy_lints/src/lib.register_complexity.rs b/clippy_lints/src/lib.register_complexity.rs index 34d764e810d9b..a2ce69065f94d 100644 --- a/clippy_lints/src/lib.register_complexity.rs +++ b/clippy_lints/src/lib.register_complexity.rs @@ -47,6 +47,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(methods::NEEDLESS_SPLITN), LintId::of(methods::OPTION_AS_REF_DEREF), LintId::of(methods::OPTION_FILTER_MAP), + LintId::of(methods::OR_THEN_UNWRAP), LintId::of(methods::SEARCH_IS_SOME), LintId::of(methods::SKIP_WHILE_NEXT), LintId::of(methods::UNNECESSARY_FILTER_MAP), @@ -66,7 +67,6 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(no_effect::NO_EFFECT), LintId::of(no_effect::UNNECESSARY_OPERATION), LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION), - LintId::of(or_then_unwrap::OR_THEN_UNWRAP), LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL), LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL), LintId::of(precedence::PRECEDENCE), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 0b6e3c7e7df33..ce5a1170504fe 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -319,6 +319,7 @@ store.register_lints(&[ methods::OPTION_FILTER_MAP, methods::OPTION_MAP_OR_NONE, methods::OR_FUN_CALL, + methods::OR_THEN_UNWRAP, methods::RESULT_MAP_OR_INTO_OPTION, methods::SEARCH_IS_SOME, methods::SHOULD_IMPLEMENT_TRAIT, @@ -404,7 +405,6 @@ store.register_lints(&[ open_options::NONSENSICAL_OPEN_OPTIONS, option_env_unwrap::OPTION_ENV_UNWRAP, option_if_let_else::OPTION_IF_LET_ELSE, - or_then_unwrap::OR_THEN_UNWRAP, overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL, panic_in_result_fn::PANIC_IN_RESULT_FN, panic_unimplemented::PANIC, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index fdab58935ef95..504235d0d1ef0 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -322,7 +322,6 @@ mod only_used_in_recursion; mod open_options; mod option_env_unwrap; mod option_if_let_else; -mod or_then_unwrap; mod overflow_check_conditional; mod panic_in_result_fn; mod panic_unimplemented; @@ -867,7 +866,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: ignore_publish: cargo_ignore_publish, }) }); - store.register_late_pass(|| Box::new(or_then_unwrap::OrThenUnwrap)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index aa9f86f292c08..1e76428858bf9 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -45,6 +45,7 @@ mod option_as_ref_deref; mod option_map_or_none; mod option_map_unwrap_or; mod or_fun_call; +mod or_then_unwrap; mod search_is_some; mod single_char_add_str; mod single_char_insert_string; @@ -778,6 +779,42 @@ declare_clippy_lint! { "using any `*or` method with a function call, which suggests `*or_else`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for `.or(…).unwrap()` calls to Options and Results. + /// + /// ### Why is this bad? + /// You should use `.unwrap_or(…)` instead for clarity. + /// + /// ### Example + /// ```rust + /// # let fallback = "fallback"; + /// // Result + /// # type Error = &'static str; + /// # let result: Result<&str, Error> = Err("error"); + /// let value = result.or::(Ok(fallback)).unwrap(); + /// + /// // Option + /// # let option: Option<&str> = None; + /// let value = option.or(Some(fallback)).unwrap(); + /// ``` + /// Use instead: + /// ```rust + /// # let fallback = "fallback"; + /// // Result + /// # let result: Result<&str, &str> = Err("error"); + /// let value = result.unwrap_or(fallback); + /// + /// // Option + /// # let option: Option<&str> = None; + /// let value = option.unwrap_or(fallback); + /// ``` + #[clippy::version = "1.61.0"] + pub OR_THEN_UNWRAP, + complexity, + "checks for `.or(…).unwrap()` calls to Options and Results." +} + declare_clippy_lint! { /// ### What it does /// Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`, @@ -2039,6 +2076,7 @@ impl_lint_pass!(Methods => [ OPTION_MAP_OR_NONE, BIND_INSTEAD_OF_MAP, OR_FUN_CALL, + OR_THEN_UNWRAP, EXPECT_FUN_CALL, CHARS_NEXT_CMP, CHARS_LAST_CMP, @@ -2474,6 +2512,9 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio Some(("get_mut", [recv, get_arg], _)) => { get_unwrap::check(cx, expr, recv, get_arg, true); }, + Some(("or", [recv, or_arg], or_span)) => { + or_then_unwrap::check(cx, expr, recv, or_arg, or_span); + }, _ => {}, } unwrap_used::check(cx, expr, recv); diff --git a/clippy_lints/src/methods/or_then_unwrap.rs b/clippy_lints/src/methods/or_then_unwrap.rs new file mode 100644 index 0000000000000..02fa5887f6764 --- /dev/null +++ b/clippy_lints/src/methods/or_then_unwrap.rs @@ -0,0 +1,68 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::ty::is_type_diagnostic_item; +use if_chain::if_chain; +use rustc_hir::{Expr, ExprKind, QPath}; +use rustc_lint::LateContext; +use rustc_span::{sym, Span}; + +use super::OR_THEN_UNWRAP; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + unwrap_expr: &Expr<'_>, + recv: &'tcx Expr<'tcx>, + or_arg: &'tcx Expr<'_>, + or_span: Span, +) { + let ty = cx.typeck_results().expr_ty(recv); // get type of x (we later check if it's Option or Result) + let title; + + if is_type_diagnostic_item(cx, ty, sym::Option) { + title = ".or(Some(…)).unwrap() found"; + if !is(or_arg, "Some") { + return; + } + } else if is_type_diagnostic_item(cx, ty, sym::Result) { + title = ".or(Ok(…)).unwrap() found"; + if !is(or_arg, "Ok") { + return; + } + } else { + // Someone has implemented a struct with .or(...).unwrap() chaining, + // but it's not an Option or a Result, so bail + return; + } + + let unwrap_span = if let ExprKind::MethodCall(_, _, span) = unwrap_expr.kind { + span + } else { + // unreachable. but fallback to ident's span ("()" are missing) + unwrap_expr.span + }; + + span_lint_and_help( + cx, + OR_THEN_UNWRAP, + or_span.to(unwrap_span), + title, + None, + "use `unwrap_or()` instead", + ); +} + +/// is expr a Call to name? +/// name might be "Some", "Ok", "Err", etc. +fn is<'a>(expr: &Expr<'a>, name: &str) -> bool { + if_chain! { + if let ExprKind::Call(some_expr, _some_args) = expr.kind; + if let ExprKind::Path(QPath::Resolved(_, path)) = &some_expr.kind; + if let Some(path_segment) = path.segments.first(); + if path_segment.ident.name.as_str() == name; + then { + true + } + else { + false + } + } +} diff --git a/clippy_lints/src/or_then_unwrap.rs b/clippy_lints/src/or_then_unwrap.rs deleted file mode 100644 index d467fbdfe0286..0000000000000 --- a/clippy_lints/src/or_then_unwrap.rs +++ /dev/null @@ -1,102 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::ty::is_type_diagnostic_item; -use if_chain::if_chain; -use rustc_hir::{Expr, ExprKind, QPath}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::sym; - -declare_clippy_lint! { - /// ### What it does - /// Checks for `.or(…).unwrap()` calls to Options and Results. - /// - /// ### Why is this bad? - /// You should use `.unwrap_or(…)` instead for clarity. - /// - /// ### Example - /// ```rust - /// # let fallback = "fallback"; - /// // Result - /// # type Error = &'static str; - /// # let result: Result<&str, Error> = Err("error"); - /// let value = result.or::(Ok(fallback)).unwrap(); - /// - /// // Option - /// # let option: Option<&str> = None; - /// let value = option.or(Some(fallback)).unwrap(); - /// ``` - /// Use instead: - /// ```rust - /// # let fallback = "fallback"; - /// // Result - /// # let result: Result<&str, &str> = Err("error"); - /// let value = result.unwrap_or(fallback); - /// - /// // Option - /// # let option: Option<&str> = None; - /// let value = option.unwrap_or(fallback); - /// ``` - #[clippy::version = "1.61.0"] - pub OR_THEN_UNWRAP, - complexity, - "checks for `.or(…).unwrap()` calls to Options and Results." -} -declare_lint_pass!(OrThenUnwrap => [OR_THEN_UNWRAP]); - -impl<'tcx> LateLintPass<'tcx> for OrThenUnwrap { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - // look for x.or().unwrap() - if_chain! { - if let ExprKind::MethodCall(path, [unwrap_self], unwrap_span) = &expr.kind; - if path.ident.name == sym::unwrap; - if let ExprKind::MethodCall(caller_path, [or_self, or_arg], or_span) = &unwrap_self.kind; - if caller_path.ident.name == sym::or; - then { - let ty = cx.typeck_results().expr_ty(or_self); // get type of x (we later check if it's Option or Result) - let title; - - if is_type_diagnostic_item(cx, ty, sym::Option) { - title = ".or(Some(…)).unwrap() found"; - if !is(or_arg, "Some") { - return; - } - } else if is_type_diagnostic_item(cx, ty, sym::Result) { - title = ".or(Ok(…)).unwrap() found"; - if !is(or_arg, "Ok") { - return; - } - } else { - // Someone has implemented a struct with .or(...).unwrap() chaining, - // but it's not an Option or a Result, so bail - return; - } - - span_lint_and_help( - cx, - OR_THEN_UNWRAP, - or_span.to(*unwrap_span), - title, - None, - "use `unwrap_or()` instead" - ); - } - } - } -} - -/// is expr a Call to name? -/// name might be "Some", "Ok", "Err", etc. -fn is<'a>(expr: &Expr<'a>, name: &str) -> bool { - if_chain! { - if let ExprKind::Call(some_expr, _some_args) = expr.kind; - if let ExprKind::Path(QPath::Resolved(_, path)) = &some_expr.kind; - if let Some(path_segment) = path.segments.first(); - if path_segment.ident.name.as_str() == name; - then { - true - } - else { - false - } - } -} From f00e844a1f3b7e6d8a5d84e90baa37b0cebeac67 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 18 Mar 2022 22:44:56 +0100 Subject: [PATCH 42/50] feat: use span_lint_and_sugg --- clippy_lints/src/methods/or_then_unwrap.rs | 10 ++++++---- tests/ui/or_then_unwrap.stderr | 7 ++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/methods/or_then_unwrap.rs b/clippy_lints/src/methods/or_then_unwrap.rs index 02fa5887f6764..578f898da789f 100644 --- a/clippy_lints/src/methods/or_then_unwrap.rs +++ b/clippy_lints/src/methods/or_then_unwrap.rs @@ -1,6 +1,7 @@ -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::ty::is_type_diagnostic_item; use if_chain::if_chain; +use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_span::{sym, Span}; @@ -40,13 +41,14 @@ pub(super) fn check<'tcx>( unwrap_expr.span }; - span_lint_and_help( + span_lint_and_sugg( cx, OR_THEN_UNWRAP, or_span.to(unwrap_span), title, - None, - "use `unwrap_or()` instead", + "try this", + "unwrap_or(...)".into(), + Applicability::HasPlaceholders, ); } diff --git a/tests/ui/or_then_unwrap.stderr b/tests/ui/or_then_unwrap.stderr index fdd718b358089..7eade84bd655f 100644 --- a/tests/ui/or_then_unwrap.stderr +++ b/tests/ui/or_then_unwrap.stderr @@ -2,18 +2,15 @@ error: .or(Some(…)).unwrap() found --> $DIR/or_then_unwrap.rs:22:20 | LL | let _ = option.or(Some("fallback")).unwrap(); // should trigger lint - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or(...)` | = note: `-D clippy::or-then-unwrap` implied by `-D warnings` - = help: use `unwrap_or()` instead error: .or(Ok(…)).unwrap() found --> $DIR/or_then_unwrap.rs:25:20 | LL | let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger lint - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: use `unwrap_or()` instead + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or(...)` error: aborting due to 2 previous errors From 895de1f13e78de456d5ecbdca3c87526c4dbbef2 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 18 Mar 2022 23:18:36 +0100 Subject: [PATCH 43/50] feat: make fixable --- clippy_lints/src/methods/or_then_unwrap.rs | 30 ++++++++++---- tests/ui/or_then_unwrap.fixed | 48 ++++++++++++++++++++++ tests/ui/or_then_unwrap.rs | 2 + tests/ui/or_then_unwrap.stderr | 8 ++-- 4 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 tests/ui/or_then_unwrap.fixed diff --git a/clippy_lints/src/methods/or_then_unwrap.rs b/clippy_lints/src/methods/or_then_unwrap.rs index 578f898da789f..4e2dcf6723176 100644 --- a/clippy_lints/src/methods/or_then_unwrap.rs +++ b/clippy_lints/src/methods/or_then_unwrap.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::is_type_diagnostic_item; use if_chain::if_chain; use rustc_errors::Applicability; @@ -17,15 +18,20 @@ pub(super) fn check<'tcx>( ) { let ty = cx.typeck_results().expr_ty(recv); // get type of x (we later check if it's Option or Result) let title; + let or_arg_content: Span; if is_type_diagnostic_item(cx, ty, sym::Option) { title = ".or(Some(…)).unwrap() found"; - if !is(or_arg, "Some") { + if let Some(content) = get_content_if_is(or_arg, "Some") { + or_arg_content = content; + } else { return; } } else if is_type_diagnostic_item(cx, ty, sym::Result) { title = ".or(Ok(…)).unwrap() found"; - if !is(or_arg, "Ok") { + if let Some(content) = get_content_if_is(or_arg, "Ok") { + or_arg_content = content; + } else { return; } } else { @@ -41,30 +47,36 @@ pub(super) fn check<'tcx>( unwrap_expr.span }; + let mut applicability = Applicability::MachineApplicable; + let suggestion = format!( + "unwrap_or({})", + snippet_with_applicability(cx, or_arg_content, "..", &mut applicability) + ); + span_lint_and_sugg( cx, OR_THEN_UNWRAP, or_span.to(unwrap_span), title, "try this", - "unwrap_or(...)".into(), - Applicability::HasPlaceholders, + suggestion, + applicability, ); } -/// is expr a Call to name? +/// is expr a Call to name? if so, return what it's wrapping /// name might be "Some", "Ok", "Err", etc. -fn is<'a>(expr: &Expr<'a>, name: &str) -> bool { +fn get_content_if_is<'a>(expr: &Expr<'a>, name: &str) -> Option { if_chain! { - if let ExprKind::Call(some_expr, _some_args) = expr.kind; + if let ExprKind::Call(some_expr, [arg]) = expr.kind; if let ExprKind::Path(QPath::Resolved(_, path)) = &some_expr.kind; if let Some(path_segment) = path.segments.first(); if path_segment.ident.name.as_str() == name; then { - true + Some(arg.span) } else { - false + None } } } diff --git a/tests/ui/or_then_unwrap.fixed b/tests/ui/or_then_unwrap.fixed new file mode 100644 index 0000000000000..b1e69ce2d26ab --- /dev/null +++ b/tests/ui/or_then_unwrap.fixed @@ -0,0 +1,48 @@ +// run-rustfix + +#![warn(clippy::or_then_unwrap)] +#![allow(clippy::map_identity)] + +struct SomeStruct {} +impl SomeStruct { + fn or(self, _: Option) -> Self { + self + } + fn unwrap(&self) {} +} + +struct SomeOtherStruct {} +impl SomeOtherStruct { + fn or(self) -> Self { + self + } + fn unwrap(&self) {} +} + +fn main() { + let option: Option<&str> = None; + let _ = option.unwrap_or("fallback"); // should trigger lint + + let result: Result<&str, &str> = Err("Error"); + let _ = result.unwrap_or("fallback"); // should trigger lint + + // Not Option/Result + let instance = SomeStruct {}; + let _ = instance.or(Some(SomeStruct {})).unwrap(); // should not trigger lint + + // or takes no argument + let instance = SomeOtherStruct {}; + let _ = instance.or().unwrap(); // should not trigger lint and should not panic + + // None in or + let option: Option<&str> = None; + let _ = option.or(None).unwrap(); // should not trigger lint + + // Not Err in or + let result: Result<&str, &str> = Err("Error"); + let _ = result.or::<&str>(Err("Other Error")).unwrap(); // should not trigger lint + + // other function between + let option: Option<&str> = None; + let _ = option.or(Some("fallback")).map(|v| v).unwrap(); // should not trigger lint +} diff --git a/tests/ui/or_then_unwrap.rs b/tests/ui/or_then_unwrap.rs index 8d7e50ad405b0..dc66e86be6f3c 100644 --- a/tests/ui/or_then_unwrap.rs +++ b/tests/ui/or_then_unwrap.rs @@ -1,3 +1,5 @@ +// run-rustfix + #![warn(clippy::or_then_unwrap)] #![allow(clippy::map_identity)] diff --git a/tests/ui/or_then_unwrap.stderr b/tests/ui/or_then_unwrap.stderr index 7eade84bd655f..301a54530cfc5 100644 --- a/tests/ui/or_then_unwrap.stderr +++ b/tests/ui/or_then_unwrap.stderr @@ -1,16 +1,16 @@ error: .or(Some(…)).unwrap() found - --> $DIR/or_then_unwrap.rs:22:20 + --> $DIR/or_then_unwrap.rs:24:20 | LL | let _ = option.or(Some("fallback")).unwrap(); // should trigger lint - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or(...)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or("fallback")` | = note: `-D clippy::or-then-unwrap` implied by `-D warnings` error: .or(Ok(…)).unwrap() found - --> $DIR/or_then_unwrap.rs:25:20 + --> $DIR/or_then_unwrap.rs:27:20 | LL | let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger lint - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or(...)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or("fallback")` error: aborting due to 2 previous errors From 20c352a4f602d541920fe1745b117fc8b42ca8fc Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sat, 19 Mar 2022 18:17:43 +0100 Subject: [PATCH 44/50] test: add method chain test --- tests/ui/or_then_unwrap.fixed | 4 ++++ tests/ui/or_then_unwrap.rs | 4 ++++ tests/ui/or_then_unwrap.stderr | 8 +++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/ui/or_then_unwrap.fixed b/tests/ui/or_then_unwrap.fixed index b1e69ce2d26ab..27d4b795a5eeb 100644 --- a/tests/ui/or_then_unwrap.fixed +++ b/tests/ui/or_then_unwrap.fixed @@ -26,6 +26,10 @@ fn main() { let result: Result<&str, &str> = Err("Error"); let _ = result.unwrap_or("fallback"); // should trigger lint + // as part of a method chain + let option: Option<&str> = None; + let _ = option.map(|v| v).unwrap_or("fallback").to_string().chars(); // should trigger lint + // Not Option/Result let instance = SomeStruct {}; let _ = instance.or(Some(SomeStruct {})).unwrap(); // should not trigger lint diff --git a/tests/ui/or_then_unwrap.rs b/tests/ui/or_then_unwrap.rs index dc66e86be6f3c..0dab5ae2f1c04 100644 --- a/tests/ui/or_then_unwrap.rs +++ b/tests/ui/or_then_unwrap.rs @@ -26,6 +26,10 @@ fn main() { let result: Result<&str, &str> = Err("Error"); let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger lint + // as part of a method chain + let option: Option<&str> = None; + let _ = option.map(|v| v).or(Some("fallback")).unwrap().to_string().chars(); // should trigger lint + // Not Option/Result let instance = SomeStruct {}; let _ = instance.or(Some(SomeStruct {})).unwrap(); // should not trigger lint diff --git a/tests/ui/or_then_unwrap.stderr b/tests/ui/or_then_unwrap.stderr index 301a54530cfc5..6b32634defda1 100644 --- a/tests/ui/or_then_unwrap.stderr +++ b/tests/ui/or_then_unwrap.stderr @@ -12,5 +12,11 @@ error: .or(Ok(…)).unwrap() found LL | let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger lint | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or("fallback")` -error: aborting due to 2 previous errors +error: .or(Some(…)).unwrap() found + --> $DIR/or_then_unwrap.rs:31:31 + | +LL | let _ = option.map(|v| v).or(Some("fallback")).unwrap().to_string().chars(); // should trigger lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or("fallback")` + +error: aborting due to 3 previous errors From 0f83753934b84efb6b3263270f625f0f44c208f2 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sun, 20 Mar 2022 23:43:17 +0100 Subject: [PATCH 45/50] feat: change error message --- clippy_lints/src/methods/or_then_unwrap.rs | 4 ++-- tests/ui/or_then_unwrap.stderr | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/methods/or_then_unwrap.rs b/clippy_lints/src/methods/or_then_unwrap.rs index 4e2dcf6723176..a0f6d80c19b1d 100644 --- a/clippy_lints/src/methods/or_then_unwrap.rs +++ b/clippy_lints/src/methods/or_then_unwrap.rs @@ -21,14 +21,14 @@ pub(super) fn check<'tcx>( let or_arg_content: Span; if is_type_diagnostic_item(cx, ty, sym::Option) { - title = ".or(Some(…)).unwrap() found"; + title = "found `.or(Some(…)).unwrap()`"; if let Some(content) = get_content_if_is(or_arg, "Some") { or_arg_content = content; } else { return; } } else if is_type_diagnostic_item(cx, ty, sym::Result) { - title = ".or(Ok(…)).unwrap() found"; + title = "found `.or(Ok(…)).unwrap()`"; if let Some(content) = get_content_if_is(or_arg, "Ok") { or_arg_content = content; } else { diff --git a/tests/ui/or_then_unwrap.stderr b/tests/ui/or_then_unwrap.stderr index 6b32634defda1..da88154c59f71 100644 --- a/tests/ui/or_then_unwrap.stderr +++ b/tests/ui/or_then_unwrap.stderr @@ -1,4 +1,4 @@ -error: .or(Some(…)).unwrap() found +error: found `.or(Some(…)).unwrap()` --> $DIR/or_then_unwrap.rs:24:20 | LL | let _ = option.or(Some("fallback")).unwrap(); // should trigger lint @@ -6,13 +6,13 @@ LL | let _ = option.or(Some("fallback")).unwrap(); // should trigger lint | = note: `-D clippy::or-then-unwrap` implied by `-D warnings` -error: .or(Ok(…)).unwrap() found +error: found `.or(Ok(…)).unwrap()` --> $DIR/or_then_unwrap.rs:27:20 | LL | let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger lint | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or("fallback")` -error: .or(Some(…)).unwrap() found +error: found `.or(Some(…)).unwrap()` --> $DIR/or_then_unwrap.rs:31:31 | LL | let _ = option.map(|v| v).or(Some("fallback")).unwrap().to_string().chars(); // should trigger lint From 4580c8a9b7f61d2e9968ae1aaab08cfa2a5eb221 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sun, 20 Mar 2022 23:54:04 +0100 Subject: [PATCH 46/50] refactor: use is_lang_ctor() --- clippy_lints/src/methods/or_then_unwrap.rs | 31 +++++++++------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/clippy_lints/src/methods/or_then_unwrap.rs b/clippy_lints/src/methods/or_then_unwrap.rs index a0f6d80c19b1d..28b1bfe8fb5af 100644 --- a/clippy_lints/src/methods/or_then_unwrap.rs +++ b/clippy_lints/src/methods/or_then_unwrap.rs @@ -1,9 +1,8 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::is_type_diagnostic_item; -use if_chain::if_chain; +use clippy_utils::{diagnostics::span_lint_and_sugg, is_lang_ctor}; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, QPath}; +use rustc_hir::{lang_items::LangItem, Expr, ExprKind}; use rustc_lint::LateContext; use rustc_span::{sym, Span}; @@ -22,14 +21,14 @@ pub(super) fn check<'tcx>( if is_type_diagnostic_item(cx, ty, sym::Option) { title = "found `.or(Some(…)).unwrap()`"; - if let Some(content) = get_content_if_is(or_arg, "Some") { + if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::OptionSome) { or_arg_content = content; } else { return; } } else if is_type_diagnostic_item(cx, ty, sym::Result) { title = "found `.or(Ok(…)).unwrap()`"; - if let Some(content) = get_content_if_is(or_arg, "Ok") { + if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::ResultOk) { or_arg_content = content; } else { return; @@ -64,19 +63,13 @@ pub(super) fn check<'tcx>( ); } -/// is expr a Call to name? if so, return what it's wrapping -/// name might be "Some", "Ok", "Err", etc. -fn get_content_if_is<'a>(expr: &Expr<'a>, name: &str) -> Option { - if_chain! { - if let ExprKind::Call(some_expr, [arg]) = expr.kind; - if let ExprKind::Path(QPath::Resolved(_, path)) = &some_expr.kind; - if let Some(path_segment) = path.segments.first(); - if path_segment.ident.name.as_str() == name; - then { - Some(arg.span) - } - else { - None - } +fn get_content_if_ctor_matches(cx: &LateContext<'_>, expr: &Expr<'_>, item: LangItem) -> Option { + if let ExprKind::Call(some_expr, [arg]) = expr.kind + && let ExprKind::Path(qpath) = &some_expr.kind + && is_lang_ctor(cx, qpath, item) + { + Some(arg.span) + } else { + None } } From 765cce11b10eabe667b42df1a9a878fe38b713ce Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Mon, 21 Mar 2022 00:04:37 +0100 Subject: [PATCH 47/50] refactor: remove need for MethodCall matching --- clippy_lints/src/methods/or_then_unwrap.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/clippy_lints/src/methods/or_then_unwrap.rs b/clippy_lints/src/methods/or_then_unwrap.rs index 28b1bfe8fb5af..be5768c354504 100644 --- a/clippy_lints/src/methods/or_then_unwrap.rs +++ b/clippy_lints/src/methods/or_then_unwrap.rs @@ -39,13 +39,6 @@ pub(super) fn check<'tcx>( return; } - let unwrap_span = if let ExprKind::MethodCall(_, _, span) = unwrap_expr.kind { - span - } else { - // unreachable. but fallback to ident's span ("()" are missing) - unwrap_expr.span - }; - let mut applicability = Applicability::MachineApplicable; let suggestion = format!( "unwrap_or({})", @@ -55,7 +48,7 @@ pub(super) fn check<'tcx>( span_lint_and_sugg( cx, OR_THEN_UNWRAP, - or_span.to(unwrap_span), + unwrap_expr.span.with_lo(or_span.lo()), title, "try this", suggestion, From 5b6295d663fe5f2cc825f8a74578b0afab58c560 Mon Sep 17 00:00:00 2001 From: J-ZhengLi Date: Mon, 21 Mar 2022 14:11:22 +0800 Subject: [PATCH 48/50] allowing [`map_flatten`] to split long suggestions add new function `span_lint_and_sugg_` for edges in `clippy_utils::diagnostics` --- clippy_lints/src/methods/map_flatten.rs | 118 ++++++++--------- clippy_lints/src/methods/mod.rs | 2 +- .../internal_lints/metadata_collector.rs | 3 +- clippy_utils/src/diagnostics.rs | 86 +++++++++++- tests/ui/map_flatten.rs | 74 +++++++---- tests/ui/map_flatten.stderr | 125 +++++++++++++----- ...latten.fixed => map_flatten_fixable.fixed} | 0 tests/ui/map_flatten_fixable.rs | 31 +++++ tests/ui/map_flatten_fixable.stderr | 80 +++++++++++ 9 files changed, 395 insertions(+), 124 deletions(-) rename tests/ui/{map_flatten.fixed => map_flatten_fixable.fixed} (100%) create mode 100644 tests/ui/map_flatten_fixable.rs create mode 100644 tests/ui/map_flatten_fixable.stderr diff --git a/clippy_lints/src/methods/map_flatten.rs b/clippy_lints/src/methods/map_flatten.rs index e1212c31cfb02..f447940ea3b5a 100644 --- a/clippy_lints/src/methods/map_flatten.rs +++ b/clippy_lints/src/methods/map_flatten.rs @@ -1,83 +1,73 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_sugg_for_edges; use clippy_utils::is_trait_method; -use clippy_utils::source::snippet; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; -use rustc_hir as hir; +use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty; -use rustc_span::symbol::sym; +use rustc_span::{symbol::sym, Span}; use super::MAP_FLATTEN; /// lint use of `map().flatten()` for `Iterators` and 'Options' -pub(super) fn check<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - recv: &'tcx hir::Expr<'_>, - map_arg: &'tcx hir::Expr<'_>, -) { - // lint if caller of `.map().flatten()` is an Iterator - if is_trait_method(cx, expr, sym::Iterator) { - let map_closure_ty = cx.typeck_results().expr_ty(map_arg); - let is_map_to_option = match map_closure_ty.kind() { - ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => { - let map_closure_sig = match map_closure_ty.kind() { - ty::Closure(_, substs) => substs.as_closure().sig(), - _ => map_closure_ty.fn_sig(cx.tcx), - }; - let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output()); - is_type_diagnostic_item(cx, map_closure_return_ty, sym::Option) - }, - _ => false, - }; - - let method_to_use = if is_map_to_option { - // `(...).map(...)` has type `impl Iterator> - "filter_map" - } else { - // `(...).map(...)` has type `impl Iterator> - "flat_map" - }; - let func_snippet = snippet(cx, map_arg.span, ".."); - let hint = format!(".{0}({1})", method_to_use, func_snippet); - span_lint_and_sugg( +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, map_arg: &Expr<'_>, map_span: Span) { + if let Some((caller_ty_name, method_to_use)) = try_get_caller_ty_name_and_method_name(cx, expr, recv, map_arg) { + let mut applicability = Applicability::MachineApplicable; + let help_msgs = [ + &format!("try replacing `map` with `{}`", method_to_use), + "and remove the `.flatten()`", + ]; + let closure_snippet = snippet_with_applicability(cx, map_arg.span, "..", &mut applicability); + span_lint_and_sugg_for_edges( cx, MAP_FLATTEN, - expr.span.with_lo(recv.span.hi()), - "called `map(..).flatten()` on an `Iterator`", - &format!("try using `{}` instead", method_to_use), - hint, - Applicability::MachineApplicable, + expr.span.with_lo(map_span.lo()), + &format!("called `map(..).flatten()` on `{}`", caller_ty_name), + &help_msgs, + format!("{}({})", method_to_use, closure_snippet), + applicability, ); } +} - // lint if caller of `.map().flatten()` is an Option or Result - let caller_type = match cx.typeck_results().expr_ty(recv).kind() { - ty::Adt(adt, _) => { +fn try_get_caller_ty_name_and_method_name( + cx: &LateContext<'_>, + expr: &Expr<'_>, + caller_expr: &Expr<'_>, + map_arg: &Expr<'_>, +) -> Option<(&'static str, &'static str)> { + if is_trait_method(cx, expr, sym::Iterator) { + if is_map_to_option(cx, map_arg) { + // `(...).map(...)` has type `impl Iterator> + Some(("Iterator", "filter_map")) + } else { + // `(...).map(...)` has type `impl Iterator> + Some(("Iterator", "flat_map")) + } + } else { + if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(caller_expr).kind() { if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) { - "Option" + return Some(("Option", "and_then")); } else if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) { - "Result" - } else { - return; + return Some(("Result", "and_then")); } - }, - _ => { - return; - }, - }; + } + None + } +} - let func_snippet = snippet(cx, map_arg.span, ".."); - let hint = format!(".and_then({})", func_snippet); - let lint_info = format!("called `map(..).flatten()` on an `{}`", caller_type); - span_lint_and_sugg( - cx, - MAP_FLATTEN, - expr.span.with_lo(recv.span.hi()), - &lint_info, - "try using `and_then` instead", - hint, - Applicability::MachineApplicable, - ); +fn is_map_to_option(cx: &LateContext<'_>, map_arg: &Expr<'_>) -> bool { + let map_closure_ty = cx.typeck_results().expr_ty(map_arg); + match map_closure_ty.kind() { + ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => { + let map_closure_sig = match map_closure_ty.kind() { + ty::Closure(_, substs) => substs.as_closure().sig(), + _ => map_closure_ty.fn_sig(cx.tcx), + }; + let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output()); + is_type_diagnostic_item(cx, map_closure_return_ty, sym::Option) + }, + _ => false, + } } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index aa9f86f292c08..822f40179ee80 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -2377,7 +2377,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio flat_map_option::check(cx, expr, arg, span); }, (name @ "flatten", args @ []) => match method_call(recv) { - Some(("map", [recv, map_arg], _)) => map_flatten::check(cx, expr, recv, map_arg), + Some(("map", [recv, map_arg], map_span)) => map_flatten::check(cx, expr, recv, map_arg, map_span), Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args), _ => {}, }, diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index a617422bbeb04..b3fad6ce7b651 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -85,7 +85,7 @@ macro_rules! CONFIGURATION_VALUE_TEMPLATE { }; } -const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [ +const LINT_EMISSION_FUNCTIONS: [&[&str]; 8] = [ &["clippy_utils", "diagnostics", "span_lint"], &["clippy_utils", "diagnostics", "span_lint_and_help"], &["clippy_utils", "diagnostics", "span_lint_and_note"], @@ -93,6 +93,7 @@ const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [ &["clippy_utils", "diagnostics", "span_lint_and_sugg"], &["clippy_utils", "diagnostics", "span_lint_and_then"], &["clippy_utils", "diagnostics", "span_lint_hir_and_then"], + &["clippy_utils", "diagnostics", "span_lint_and_sugg_for_edges"], ]; const SUGGESTION_DIAGNOSTIC_BUILDER_METHODS: [(&str, bool); 9] = [ ("span_suggestion", false), diff --git a/clippy_utils/src/diagnostics.rs b/clippy_utils/src/diagnostics.rs index a927788e6a44a..625a53899df94 100644 --- a/clippy_utils/src/diagnostics.rs +++ b/clippy_utils/src/diagnostics.rs @@ -8,7 +8,7 @@ //! Thank you! //! ~The `INTERNAL_METADATA_COLLECTOR` lint -use rustc_errors::{Applicability, Diagnostic}; +use rustc_errors::{emitter::MAX_SUGGESTION_HIGHLIGHT_LINES, Applicability, Diagnostic}; use rustc_hir::HirId; use rustc_lint::{LateContext, Lint, LintContext}; use rustc_span::source_map::{MultiSpan, Span}; @@ -213,6 +213,90 @@ pub fn span_lint_and_sugg<'a, T: LintContext>( }); } +/// Like [`span_lint_and_sugg`] with a focus on the edges. The output will either +/// emit single span or multispan suggestion depending on the number of its lines. +/// +/// If the given suggestion string has more lines than the maximum display length defined by +/// [`MAX_SUGGESTION_HIGHLIGHT_LINES`][`rustc_errors::emitter::MAX_SUGGESTION_HIGHLIGHT_LINES`], +/// this function will split the suggestion and span to showcase the change for the top and +/// bottom edge of the code. For normal suggestions, in one display window, the help message +/// will be combined with a colon. +/// +/// Multipart suggestions like the one being created here currently cannot be +/// applied by rustfix (See [rustfix#141](https://github.com/rust-lang/rustfix/issues/141)). +/// Testing rustfix with this lint emission function might require a file with +/// suggestions that can be fixed and those that can't. See +/// [clippy#8520](https://github.com/rust-lang/rust-clippy/pull/8520/files) for +/// an example and of this. +/// +/// # Example for a long suggestion +/// +/// ```text +/// error: called `map(..).flatten()` on `Option` +/// --> $DIR/map_flatten.rs:8:10 +/// | +/// LL | .map(|x| { +/// | __________^ +/// LL | | if x <= 5 { +/// LL | | Some(x) +/// LL | | } else { +/// ... | +/// LL | | }) +/// LL | | .flatten(); +/// | |__________________^ +/// | +/// = note: `-D clippy::map-flatten` implied by `-D warnings` +/// help: try replacing `map` with `and_then` +/// | +/// LL ~ .and_then(|x| { +/// LL + if x <= 5 { +/// LL + Some(x) +/// | +/// help: and remove the `.flatten()` +/// | +/// LL + None +/// LL + } +/// LL ~ }); +/// | +/// ``` +pub fn span_lint_and_sugg_for_edges( + cx: &LateContext<'_>, + lint: &'static Lint, + sp: Span, + msg: &str, + helps: &[&str; 2], + sugg: String, + applicability: Applicability, +) { + span_lint_and_then(cx, lint, sp, msg, |diag| { + let sugg_lines_count = sugg.lines().count(); + if sugg_lines_count > MAX_SUGGESTION_HIGHLIGHT_LINES { + let sm = cx.sess().source_map(); + if let (Ok(line_upper), Ok(line_bottom)) = (sm.lookup_line(sp.lo()), sm.lookup_line(sp.hi())) { + let split_idx = MAX_SUGGESTION_HIGHLIGHT_LINES / 2; + let span_upper = sm.span_until_char(sp.with_hi(line_upper.sf.lines[line_upper.line + split_idx]), '\n'); + let span_bottom = sp.with_lo(line_bottom.sf.lines[line_bottom.line - split_idx]); + + let sugg_lines_vec = sugg.lines().collect::>(); + let sugg_upper = sugg_lines_vec[..split_idx].join("\n"); + let sugg_bottom = sugg_lines_vec[sugg_lines_count - split_idx..].join("\n"); + + diag.span_suggestion(span_upper, helps[0], sugg_upper, applicability); + diag.span_suggestion(span_bottom, helps[1], sugg_bottom, applicability); + + return; + } + } + diag.span_suggestion_with_style( + sp, + &helps.join(", "), + sugg, + applicability, + rustc_errors::SuggestionStyle::ShowAlways, + ); + }); +} + /// Create a suggestion made from several `span → replacement`. /// /// Note: in the JSON format (used by `compiletest_rs`), the help message will diff --git a/tests/ui/map_flatten.rs b/tests/ui/map_flatten.rs index aa1f76e335af0..7d47ee09dc1ac 100644 --- a/tests/ui/map_flatten.rs +++ b/tests/ui/map_flatten.rs @@ -1,31 +1,55 @@ -// run-rustfix - -#![warn(clippy::all, clippy::pedantic)] -#![allow(clippy::let_underscore_drop)] -#![allow(clippy::missing_docs_in_private_items)] -#![allow(clippy::map_identity)] -#![allow(clippy::redundant_closure)] -#![allow(clippy::unnecessary_wraps)] +#![warn(clippy::map_flatten)] #![feature(result_flattening)] -fn main() { - // mapping to Option on Iterator - fn option_id(x: i8) -> Option { - Some(x) - } - let option_id_ref: fn(i8) -> Option = option_id; - let option_id_closure = |x| Some(x); - let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect(); - let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect(); - let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect(); - let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect(); +// issue #8506, multi-line +#[rustfmt::skip] +fn long_span() { + let _: Option = Some(1) + .map(|x| { + if x <= 5 { + Some(x) + } else { + None + } + }) + .flatten(); - // mapping to Iterator on Iterator - let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect(); + let _: Result = Ok(1) + .map(|x| { + if x == 1 { + Ok(x) + } else { + Err(0) + } + }) + .flatten(); - // mapping to Option on Option - let _: Option<_> = (Some(Some(1))).map(|x| x).flatten(); + let result: Result = Ok(2); + fn do_something() { } + let _: Result = result + .map(|res| { + if res > 0 { + do_something(); + Ok(res) + } else { + Err(0) + } + }) + .flatten(); + + let _: Vec<_> = vec![5_i8; 6] + .into_iter() + .map(|some_value| { + if some_value > 3 { + Some(some_value) + } else { + None + } + }) + .flatten() + .collect(); +} - // mapping to Result on Result - let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten(); +fn main() { + long_span(); } diff --git a/tests/ui/map_flatten.stderr b/tests/ui/map_flatten.stderr index bcd2047e6faa3..c9c60df838f67 100644 --- a/tests/ui/map_flatten.stderr +++ b/tests/ui/map_flatten.stderr @@ -1,46 +1,107 @@ -error: called `map(..).flatten()` on an `Iterator` - --> $DIR/map_flatten.rs:18:46 +error: called `map(..).flatten()` on `Option` + --> $DIR/map_flatten.rs:8:10 | -LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id)` +LL | .map(|x| { + | __________^ +LL | | if x <= 5 { +LL | | Some(x) +LL | | } else { +... | +LL | | }) +LL | | .flatten(); + | |__________________^ | = note: `-D clippy::map-flatten` implied by `-D warnings` - -error: called `map(..).flatten()` on an `Iterator` - --> $DIR/map_flatten.rs:19:46 +help: try replacing `map` with `and_then` | -LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id_ref)` - -error: called `map(..).flatten()` on an `Iterator` - --> $DIR/map_flatten.rs:20:46 +LL ~ .and_then(|x| { +LL + if x <= 5 { +LL + Some(x) | -LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id_closure)` - -error: called `map(..).flatten()` on an `Iterator` - --> $DIR/map_flatten.rs:21:46 +help: and remove the `.flatten()` + | +LL + None +LL + } +LL ~ }); | -LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(|x| x.checked_add(1))` -error: called `map(..).flatten()` on an `Iterator` - --> $DIR/map_flatten.rs:24:46 +error: called `map(..).flatten()` on `Result` + --> $DIR/map_flatten.rs:18:10 + | +LL | .map(|x| { + | __________^ +LL | | if x == 1 { +LL | | Ok(x) +LL | | } else { +... | +LL | | }) +LL | | .flatten(); + | |__________________^ + | +help: try replacing `map` with `and_then` + | +LL ~ .and_then(|x| { +LL + if x == 1 { +LL + Ok(x) + | +help: and remove the `.flatten()` + | +LL + Err(0) +LL + } +LL ~ }); | -LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect(); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `flat_map` instead: `.flat_map(|x| 0..x)` -error: called `map(..).flatten()` on an `Option` - --> $DIR/map_flatten.rs:27:39 +error: called `map(..).flatten()` on `Result` + --> $DIR/map_flatten.rs:30:10 + | +LL | .map(|res| { + | __________^ +LL | | if res > 0 { +LL | | do_something(); +LL | | Ok(res) +... | +LL | | }) +LL | | .flatten(); + | |__________________^ + | +help: try replacing `map` with `and_then` + | +LL ~ .and_then(|res| { +LL + if res > 0 { +LL + do_something(); + | +help: and remove the `.flatten()` + | +LL + Err(0) +LL + } +LL ~ }); | -LL | let _: Option<_> = (Some(Some(1))).map(|x| x).flatten(); - | ^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `.and_then(|x| x)` -error: called `map(..).flatten()` on an `Result` - --> $DIR/map_flatten.rs:30:41 +error: called `map(..).flatten()` on `Iterator` + --> $DIR/map_flatten.rs:42:10 + | +LL | .map(|some_value| { + | __________^ +LL | | if some_value > 3 { +LL | | Some(some_value) +LL | | } else { +... | +LL | | }) +LL | | .flatten() + | |__________________^ + | +help: try replacing `map` with `filter_map` + | +LL ~ .filter_map(|some_value| { +LL + if some_value > 3 { +LL + Some(some_value) + | +help: and remove the `.flatten()` + | +LL + None +LL + } +LL + }) | -LL | let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten(); - | ^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `.and_then(|x| x)` -error: aborting due to 7 previous errors +error: aborting due to 4 previous errors diff --git a/tests/ui/map_flatten.fixed b/tests/ui/map_flatten_fixable.fixed similarity index 100% rename from tests/ui/map_flatten.fixed rename to tests/ui/map_flatten_fixable.fixed diff --git a/tests/ui/map_flatten_fixable.rs b/tests/ui/map_flatten_fixable.rs new file mode 100644 index 0000000000000..aa1f76e335af0 --- /dev/null +++ b/tests/ui/map_flatten_fixable.rs @@ -0,0 +1,31 @@ +// run-rustfix + +#![warn(clippy::all, clippy::pedantic)] +#![allow(clippy::let_underscore_drop)] +#![allow(clippy::missing_docs_in_private_items)] +#![allow(clippy::map_identity)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::unnecessary_wraps)] +#![feature(result_flattening)] + +fn main() { + // mapping to Option on Iterator + fn option_id(x: i8) -> Option { + Some(x) + } + let option_id_ref: fn(i8) -> Option = option_id; + let option_id_closure = |x| Some(x); + let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect(); + let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect(); + let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect(); + let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect(); + + // mapping to Iterator on Iterator + let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect(); + + // mapping to Option on Option + let _: Option<_> = (Some(Some(1))).map(|x| x).flatten(); + + // mapping to Result on Result + let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten(); +} diff --git a/tests/ui/map_flatten_fixable.stderr b/tests/ui/map_flatten_fixable.stderr new file mode 100644 index 0000000000000..c91c73846b69f --- /dev/null +++ b/tests/ui/map_flatten_fixable.stderr @@ -0,0 +1,80 @@ +error: called `map(..).flatten()` on `Iterator` + --> $DIR/map_flatten_fixable.rs:18:47 + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::map-flatten` implied by `-D warnings` +help: try replacing `map` with `filter_map`, and remove the `.flatten()` + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id).collect(); + | ~~~~~~~~~~~~~~~~~~~~~ + +error: called `map(..).flatten()` on `Iterator` + --> $DIR/map_flatten_fixable.rs:19:47 + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try replacing `map` with `filter_map`, and remove the `.flatten()` + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_ref).collect(); + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: called `map(..).flatten()` on `Iterator` + --> $DIR/map_flatten_fixable.rs:20:47 + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try replacing `map` with `filter_map`, and remove the `.flatten()` + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_closure).collect(); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: called `map(..).flatten()` on `Iterator` + --> $DIR/map_flatten_fixable.rs:21:47 + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try replacing `map` with `filter_map`, and remove the `.flatten()` + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(|x| x.checked_add(1)).collect(); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: called `map(..).flatten()` on `Iterator` + --> $DIR/map_flatten_fixable.rs:24:47 + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try replacing `map` with `flat_map`, and remove the `.flatten()` + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().flat_map(|x| 0..x).collect(); + | ~~~~~~~~~~~~~~~~~~ + +error: called `map(..).flatten()` on `Option` + --> $DIR/map_flatten_fixable.rs:27:40 + | +LL | let _: Option<_> = (Some(Some(1))).map(|x| x).flatten(); + | ^^^^^^^^^^^^^^^^^^^^ + | +help: try replacing `map` with `and_then`, and remove the `.flatten()` + | +LL | let _: Option<_> = (Some(Some(1))).and_then(|x| x); + | ~~~~~~~~~~~~~~~ + +error: called `map(..).flatten()` on `Result` + --> $DIR/map_flatten_fixable.rs:30:42 + | +LL | let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten(); + | ^^^^^^^^^^^^^^^^^^^^ + | +help: try replacing `map` with `and_then`, and remove the `.flatten()` + | +LL | let _: Result<_, &str> = (Ok(Ok(1))).and_then(|x| x); + | ~~~~~~~~~~~~~~~ + +error: aborting due to 7 previous errors + From b60a7fb7b67566c611cfd0de94f76c5596a4915d Mon Sep 17 00:00:00 2001 From: Yoav Lavi Date: Thu, 24 Mar 2022 13:18:18 +0100 Subject: [PATCH 49/50] `unnecessary_join` lint --- CHANGELOG.md | 1 + clippy_lints/src/lib.register_lints.rs | 1 + clippy_lints/src/lib.register_pedantic.rs | 1 + clippy_lints/src/methods/mod.rs | 36 +++++++++++++++++ clippy_lints/src/methods/unnecessary_join.rs | 41 ++++++++++++++++++++ tests/ui/unnecessary_join.fixed | 35 +++++++++++++++++ tests/ui/unnecessary_join.rs | 37 ++++++++++++++++++ tests/ui/unnecessary_join.stderr | 20 ++++++++++ 8 files changed, 172 insertions(+) create mode 100644 clippy_lints/src/methods/unnecessary_join.rs create mode 100644 tests/ui/unnecessary_join.fixed create mode 100644 tests/ui/unnecessary_join.rs create mode 100644 tests/ui/unnecessary_join.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index dc83de665548f..88f71931d92b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3508,6 +3508,7 @@ Released 2018-09-13 [`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map [`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map [`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold +[`unnecessary_join`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_join [`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations [`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed [`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 65ad64f190185..21f1ef562b5a3 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -334,6 +334,7 @@ store.register_lints(&[ methods::UNNECESSARY_FILTER_MAP, methods::UNNECESSARY_FIND_MAP, methods::UNNECESSARY_FOLD, + methods::UNNECESSARY_JOIN, methods::UNNECESSARY_LAZY_EVALUATIONS, methods::UNNECESSARY_TO_OWNED, methods::UNWRAP_OR_ELSE_DEFAULT, diff --git a/clippy_lints/src/lib.register_pedantic.rs b/clippy_lints/src/lib.register_pedantic.rs index 00d305131810d..eb6534cb8cae7 100644 --- a/clippy_lints/src/lib.register_pedantic.rs +++ b/clippy_lints/src/lib.register_pedantic.rs @@ -63,6 +63,7 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(methods::IMPLICIT_CLONE), LintId::of(methods::INEFFICIENT_TO_STRING), LintId::of(methods::MAP_UNWRAP_OR), + LintId::of(methods::UNNECESSARY_JOIN), LintId::of(misc::FLOAT_CMP), LintId::of(misc::USED_UNDERSCORE_BINDING), LintId::of(mut_mut::MUT_MUT), diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index c586a0444f003..9d4e1fa399401 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -60,6 +60,7 @@ mod uninit_assumed_init; mod unnecessary_filter_map; mod unnecessary_fold; mod unnecessary_iter_cloned; +mod unnecessary_join; mod unnecessary_lazy_eval; mod unnecessary_to_owned; mod unwrap_or_else_default; @@ -2049,6 +2050,35 @@ declare_clippy_lint! { "unnecessary calls to `to_owned`-like functions" } +declare_clippy_lint! { + /// ### What it does + /// Checks for use of `.collect::>().join("")` on iterators. + /// + /// ### Why is this bad? + /// `.collect::()` is more concise and usually more performant + /// + /// ### Example + /// ```rust + /// let vector = vec!["hello", "world"]; + /// let output = vector.iter().map(|item| item.to_uppercase()).collect::>().join(""); + /// println!("{}", output); + /// ``` + /// The correct use would be: + /// ```rust + /// let vector = vec!["hello", "world"]; + /// let output = vector.iter().map(|item| item.to_uppercase()).collect::(); + /// println!("{}", output); + /// ``` + /// ### Known problems + /// While `.collect::()` is more performant in most cases, there are cases where + /// using `.collect::()` over `.collect::>().join("")` + /// will prevent loop unrolling and will result in a negative performance impact. + #[clippy::version = "1.61.0"] + pub UNNECESSARY_JOIN, + pedantic, + "using `.collect::>().join(\"\")` on an iterator" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Option, @@ -2134,6 +2164,7 @@ impl_lint_pass!(Methods => [ MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN, UNNECESSARY_TO_OWNED, + UNNECESSARY_JOIN, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -2429,6 +2460,11 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio ("is_file", []) => filetype_is_file::check(cx, expr, recv), ("is_none", []) => check_is_some_is_none(cx, expr, recv, false), ("is_some", []) => check_is_some_is_none(cx, expr, recv, true), + ("join", [join_arg]) => { + if let Some(("collect", _, span)) = method_call(recv) { + unnecessary_join::check(cx, expr, recv, join_arg, span); + } + }, ("last", args @ []) | ("skip", args @ [_]) => { if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) { if let ("cloned", []) = (name2, args2) { diff --git a/clippy_lints/src/methods/unnecessary_join.rs b/clippy_lints/src/methods/unnecessary_join.rs new file mode 100644 index 0000000000000..973b8a7e6bf6a --- /dev/null +++ b/clippy_lints/src/methods/unnecessary_join.rs @@ -0,0 +1,41 @@ +use clippy_utils::{diagnostics::span_lint_and_sugg, ty::is_type_diagnostic_item}; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{Ref, Slice}; +use rustc_span::{sym, Span}; + +use super::UNNECESSARY_JOIN; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + join_self_arg: &'tcx Expr<'tcx>, + join_arg: &'tcx Expr<'tcx>, + span: Span, +) { + let applicability = Applicability::MachineApplicable; + let collect_output_adjusted_type = cx.typeck_results().expr_ty_adjusted(join_self_arg); + if_chain! { + // the turbofish for collect is ::> + if let Ref(_, ref_type, _) = collect_output_adjusted_type.kind(); + if let Slice(slice) = ref_type.kind(); + if is_type_diagnostic_item(cx, *slice, sym::String); + // the argument for join is "" + if let ExprKind::Lit(spanned) = &join_arg.kind; + if let LitKind::Str(symbol, _) = spanned.node; + if symbol.is_empty(); + then { + span_lint_and_sugg( + cx, + UNNECESSARY_JOIN, + span.with_hi(expr.span.hi()), + r#"called `.collect>().join("")` on an iterator"#, + "try using", + "collect::()".to_owned(), + applicability, + ); + } + } +} diff --git a/tests/ui/unnecessary_join.fixed b/tests/ui/unnecessary_join.fixed new file mode 100644 index 0000000000000..7e12c6ae4be98 --- /dev/null +++ b/tests/ui/unnecessary_join.fixed @@ -0,0 +1,35 @@ +// run-rustfix + +#![warn(clippy::unnecessary_join)] + +fn main() { + // should be linted + let vector = vec!["hello", "world"]; + let output = vector + .iter() + .map(|item| item.to_uppercase()) + .collect::(); + println!("{}", output); + + // should be linted + let vector = vec!["hello", "world"]; + let output = vector + .iter() + .map(|item| item.to_uppercase()) + .collect::(); + println!("{}", output); + + // should not be linted + let vector = vec!["hello", "world"]; + let output = vector + .iter() + .map(|item| item.to_uppercase()) + .collect::>() + .join("\n"); + println!("{}", output); + + // should not be linted + let vector = vec!["hello", "world"]; + let output = vector.iter().map(|item| item.to_uppercase()).collect::(); + println!("{}", output); +} diff --git a/tests/ui/unnecessary_join.rs b/tests/ui/unnecessary_join.rs new file mode 100644 index 0000000000000..0a21656a7558e --- /dev/null +++ b/tests/ui/unnecessary_join.rs @@ -0,0 +1,37 @@ +// run-rustfix + +#![warn(clippy::unnecessary_join)] + +fn main() { + // should be linted + let vector = vec!["hello", "world"]; + let output = vector + .iter() + .map(|item| item.to_uppercase()) + .collect::>() + .join(""); + println!("{}", output); + + // should be linted + let vector = vec!["hello", "world"]; + let output = vector + .iter() + .map(|item| item.to_uppercase()) + .collect::>() + .join(""); + println!("{}", output); + + // should not be linted + let vector = vec!["hello", "world"]; + let output = vector + .iter() + .map(|item| item.to_uppercase()) + .collect::>() + .join("\n"); + println!("{}", output); + + // should not be linted + let vector = vec!["hello", "world"]; + let output = vector.iter().map(|item| item.to_uppercase()).collect::(); + println!("{}", output); +} diff --git a/tests/ui/unnecessary_join.stderr b/tests/ui/unnecessary_join.stderr new file mode 100644 index 0000000000000..0b14b143affd6 --- /dev/null +++ b/tests/ui/unnecessary_join.stderr @@ -0,0 +1,20 @@ +error: called `.collect>().join("")` on an iterator + --> $DIR/unnecessary_join.rs:11:10 + | +LL | .collect::>() + | __________^ +LL | | .join(""); + | |_________________^ help: try using: `collect::()` + | + = note: `-D clippy::unnecessary-join` implied by `-D warnings` + +error: called `.collect>().join("")` on an iterator + --> $DIR/unnecessary_join.rs:20:10 + | +LL | .collect::>() + | __________^ +LL | | .join(""); + | |_________________^ help: try using: `collect::()` + +error: aborting due to 2 previous errors + From c995a8b5017766246ac5cfe66baa074eeee3a5a3 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Thu, 24 Mar 2022 14:22:43 +0100 Subject: [PATCH 50/50] Bump nightly version -> 2022-03-24 --- rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain b/rust-toolchain index 9d5da4ed68f8e..5befb856a0234 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-03-14" +channel = "nightly-2022-03-24" components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]