From 50da77521e922f17735b9d97461925ca0f3dec1c Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 8 Jun 2023 18:47:44 +0200 Subject: [PATCH 01/49] Rename cast_ref_to_mut to invalid_reference_casting (clippy side) --- clippy_lints/src/renamed_lints.rs | 2 +- tests/ui/rename.fixed | 4 ++-- tests/ui/rename.rs | 2 +- tests/ui/rename.stderr | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/renamed_lints.rs b/clippy_lints/src/renamed_lints.rs index d24215c229245..e532dd61a829b 100644 --- a/clippy_lints/src/renamed_lints.rs +++ b/clippy_lints/src/renamed_lints.rs @@ -31,7 +31,7 @@ pub static RENAMED_LINTS: &[(&str, &str)] = &[ ("clippy::stutter", "clippy::module_name_repetitions"), ("clippy::to_string_in_display", "clippy::recursive_format_impl"), ("clippy::zero_width_space", "clippy::invisible_characters"), - ("clippy::cast_ref_to_mut", "cast_ref_to_mut"), + ("clippy::cast_ref_to_mut", "invalid_reference_casting"), ("clippy::clone_double_ref", "suspicious_double_ref_op"), ("clippy::cmp_nan", "invalid_nan_comparisons"), ("clippy::drop_bounds", "drop_bounds"), diff --git a/tests/ui/rename.fixed b/tests/ui/rename.fixed index cab02bb93c9ce..03d9ea41a0b1b 100644 --- a/tests/ui/rename.fixed +++ b/tests/ui/rename.fixed @@ -28,9 +28,9 @@ #![allow(clippy::module_name_repetitions)] #![allow(clippy::recursive_format_impl)] #![allow(clippy::invisible_characters)] -#![allow(cast_ref_to_mut)] #![allow(suspicious_double_ref_op)] #![allow(invalid_nan_comparisons)] +#![allow(invalid_reference_casting)] #![allow(drop_bounds)] #![allow(dropping_copy_types)] #![allow(dropping_references)] @@ -79,7 +79,7 @@ #![warn(clippy::module_name_repetitions)] #![warn(clippy::recursive_format_impl)] #![warn(clippy::invisible_characters)] -#![warn(cast_ref_to_mut)] +#![warn(invalid_reference_casting)] #![warn(suspicious_double_ref_op)] #![warn(invalid_nan_comparisons)] #![warn(drop_bounds)] diff --git a/tests/ui/rename.rs b/tests/ui/rename.rs index e5e31452149dd..c028fcb5a3769 100644 --- a/tests/ui/rename.rs +++ b/tests/ui/rename.rs @@ -28,9 +28,9 @@ #![allow(clippy::module_name_repetitions)] #![allow(clippy::recursive_format_impl)] #![allow(clippy::invisible_characters)] -#![allow(cast_ref_to_mut)] #![allow(suspicious_double_ref_op)] #![allow(invalid_nan_comparisons)] +#![allow(invalid_reference_casting)] #![allow(drop_bounds)] #![allow(dropping_copy_types)] #![allow(dropping_references)] diff --git a/tests/ui/rename.stderr b/tests/ui/rename.stderr index 783608a084102..4d8a7fd70f454 100644 --- a/tests/ui/rename.stderr +++ b/tests/ui/rename.stderr @@ -174,11 +174,11 @@ error: lint `clippy::zero_width_space` has been renamed to `clippy::invisible_ch LL | #![warn(clippy::zero_width_space)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::invisible_characters` -error: lint `clippy::cast_ref_to_mut` has been renamed to `cast_ref_to_mut` +error: lint `clippy::cast_ref_to_mut` has been renamed to `invalid_reference_casting` --> $DIR/rename.rs:82:9 | LL | #![warn(clippy::cast_ref_to_mut)] - | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `cast_ref_to_mut` + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_reference_casting` error: lint `clippy::clone_double_ref` has been renamed to `suspicious_double_ref_op` --> $DIR/rename.rs:83:9 From d4f735c3f5386c6b27ca6e5b1d8d0171593dd90d Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Wed, 19 Jul 2023 17:22:06 +0000 Subject: [PATCH 02/49] redundant_type_annotations: only pass certain def kinds to type_of --- .../src/redundant_type_annotations.rs | 11 +++++--- tests/ui/redundant_type_annotations.rs | 20 +++++++++++-- tests/ui/redundant_type_annotations.stderr | 28 +++++++++---------- 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/clippy_lints/src/redundant_type_annotations.rs b/clippy_lints/src/redundant_type_annotations.rs index 8e9234bba3c86..3e963d7989280 100644 --- a/clippy_lints/src/redundant_type_annotations.rs +++ b/clippy_lints/src/redundant_type_annotations.rs @@ -1,6 +1,8 @@ use clippy_utils::diagnostics::span_lint; +use clippy_utils::is_lint_allowed; use rustc_ast::LitKind; use rustc_hir as hir; +use rustc_hir::def::DefKind; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::Ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -45,8 +47,8 @@ fn is_same_type<'tcx>(cx: &LateContext<'tcx>, ty_resolved_path: hir::def::Res, f return primty.name() == func_return_type_sym; } - // type annotation is any other non generic type - if let hir::def::Res::Def(_, defid) = ty_resolved_path + // type annotation is a non generic type + if let hir::def::Res::Def(DefKind::Struct | DefKind::Union | DefKind::Enum, defid) = ty_resolved_path && let Some(annotation_ty) = cx.tcx.type_of(defid).no_bound_vars() { return annotation_ty == func_return_type; @@ -130,8 +132,9 @@ fn extract_primty(ty_kind: &hir::TyKind<'_>) -> Option { impl LateLintPass<'_> for RedundantTypeAnnotations { fn check_local<'tcx>(&mut self, cx: &LateContext<'tcx>, local: &'tcx rustc_hir::Local<'tcx>) { - // type annotation part - if !local.span.from_expansion() + if !is_lint_allowed(cx, REDUNDANT_TYPE_ANNOTATIONS, local.hir_id) + // type annotation part + && !local.span.from_expansion() && let Some(ty) = &local.ty // initialization part diff --git a/tests/ui/redundant_type_annotations.rs b/tests/ui/redundant_type_annotations.rs index cc507b8d658ce..09dbd3c9b39e1 100644 --- a/tests/ui/redundant_type_annotations.rs +++ b/tests/ui/redundant_type_annotations.rs @@ -6,8 +6,8 @@ struct Cake { _data: T, } -fn make_something() -> T { - T::default() +fn make_something() -> T { + unimplemented!() } fn make_cake() -> Cake { @@ -117,7 +117,15 @@ fn test_non_locals() { let _closure_arg = |x: u32| x; } -fn test_complex_types() { +trait Trait { + type AssocTy; +} + +impl Trait for () { + type AssocTy = String; +} + +fn test_complex_types() { // Shouldn't be lint, since the literal will be i32 otherwise let _u8: u8 = 128; @@ -135,6 +143,10 @@ fn test_complex_types() { // Shouldn't be lint let _array: [u32; 2] = [8, 9]; + + let ty_param: T = make_something(); + + let assoc_ty: <() as Trait>::AssocTy = String::new(); } fn test_functions() { @@ -173,4 +185,6 @@ fn test_simple_types() { let _var: bool = false; } +fn issue11190() {} + fn main() {} diff --git a/tests/ui/redundant_type_annotations.stderr b/tests/ui/redundant_type_annotations.stderr index e8b2fe5c3847a..988ebe6372268 100644 --- a/tests/ui/redundant_type_annotations.stderr +++ b/tests/ui/redundant_type_annotations.stderr @@ -19,85 +19,85 @@ LL | let v: &Slice = self.return_a_ref_to_struct(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant type annotation - --> $DIR/redundant_type_annotations.rs:143:5 + --> $DIR/redundant_type_annotations.rs:155:5 | LL | let _return: String = return_a_string(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant type annotation - --> $DIR/redundant_type_annotations.rs:145:5 + --> $DIR/redundant_type_annotations.rs:157:5 | LL | let _return: Pie = return_a_struct(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant type annotation - --> $DIR/redundant_type_annotations.rs:147:5 + --> $DIR/redundant_type_annotations.rs:159:5 | LL | let _return: Pizza = return_an_enum(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant type annotation - --> $DIR/redundant_type_annotations.rs:149:5 + --> $DIR/redundant_type_annotations.rs:161:5 | LL | let _return: u32 = return_an_int(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant type annotation - --> $DIR/redundant_type_annotations.rs:151:5 + --> $DIR/redundant_type_annotations.rs:163:5 | LL | let _return: String = String::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant type annotation - --> $DIR/redundant_type_annotations.rs:153:5 + --> $DIR/redundant_type_annotations.rs:165:5 | LL | let new_pie: Pie = Pie::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant type annotation - --> $DIR/redundant_type_annotations.rs:155:5 + --> $DIR/redundant_type_annotations.rs:167:5 | LL | let _return: u32 = new_pie.return_an_int(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant type annotation - --> $DIR/redundant_type_annotations.rs:157:5 + --> $DIR/redundant_type_annotations.rs:169:5 | LL | let _return: u32 = Pie::associated_return_an_int(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant type annotation - --> $DIR/redundant_type_annotations.rs:159:5 + --> $DIR/redundant_type_annotations.rs:171:5 | LL | let _return: String = Pie::associated_return_a_string(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant type annotation - --> $DIR/redundant_type_annotations.rs:165:5 + --> $DIR/redundant_type_annotations.rs:177:5 | LL | let _var: u32 = u32::MAX; | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant type annotation - --> $DIR/redundant_type_annotations.rs:167:5 + --> $DIR/redundant_type_annotations.rs:179:5 | LL | let _var: u32 = 5_u32; | ^^^^^^^^^^^^^^^^^^^^^^ error: redundant type annotation - --> $DIR/redundant_type_annotations.rs:169:5 + --> $DIR/redundant_type_annotations.rs:181:5 | LL | let _var: &str = "test"; | ^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant type annotation - --> $DIR/redundant_type_annotations.rs:171:5 + --> $DIR/redundant_type_annotations.rs:183:5 | LL | let _var: &[u8] = b"test"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant type annotation - --> $DIR/redundant_type_annotations.rs:173:5 + --> $DIR/redundant_type_annotations.rs:185:5 | LL | let _var: bool = false; | ^^^^^^^^^^^^^^^^^^^^^^^ From 1662fdb2d0c64bb5d11111cc993231d33c13a45d Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Thu, 25 May 2023 05:21:44 +0000 Subject: [PATCH 03/49] fix --- tests/ui/explicit_deref_methods.fixed | 1 + tests/ui/explicit_deref_methods.rs | 1 + tests/ui/explicit_deref_methods.stderr | 24 ++++++++++++------------ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/tests/ui/explicit_deref_methods.fixed b/tests/ui/explicit_deref_methods.fixed index 4d72b58cdf867..4c0b0d8f275e0 100644 --- a/tests/ui/explicit_deref_methods.fixed +++ b/tests/ui/explicit_deref_methods.fixed @@ -4,6 +4,7 @@ #![allow( clippy::borrow_deref_ref, suspicious_double_ref_op, + noop_method_call, clippy::explicit_auto_deref, clippy::needless_borrow, clippy::no_effect, diff --git a/tests/ui/explicit_deref_methods.rs b/tests/ui/explicit_deref_methods.rs index fcd945de33860..bc5da35e52e3f 100644 --- a/tests/ui/explicit_deref_methods.rs +++ b/tests/ui/explicit_deref_methods.rs @@ -4,6 +4,7 @@ #![allow( clippy::borrow_deref_ref, suspicious_double_ref_op, + noop_method_call, clippy::explicit_auto_deref, clippy::needless_borrow, clippy::no_effect, diff --git a/tests/ui/explicit_deref_methods.stderr b/tests/ui/explicit_deref_methods.stderr index 362e559b21a57..e4d2fe3a1c3d5 100644 --- a/tests/ui/explicit_deref_methods.stderr +++ b/tests/ui/explicit_deref_methods.stderr @@ -1,5 +1,5 @@ error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:54:19 + --> $DIR/explicit_deref_methods.rs:55:19 | LL | let b: &str = a.deref(); | ^^^^^^^^^ help: try: `&*a` @@ -7,67 +7,67 @@ LL | let b: &str = a.deref(); = note: `-D clippy::explicit-deref-methods` implied by `-D warnings` error: explicit `deref_mut` method call - --> $DIR/explicit_deref_methods.rs:56:23 + --> $DIR/explicit_deref_methods.rs:57:23 | LL | let b: &mut str = a.deref_mut(); | ^^^^^^^^^^^^^ help: try: `&mut **a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:59:39 + --> $DIR/explicit_deref_methods.rs:60:39 | LL | let b: String = format!("{}, {}", a.deref(), a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:59:50 + --> $DIR/explicit_deref_methods.rs:60:50 | LL | let b: String = format!("{}, {}", a.deref(), a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:61:20 + --> $DIR/explicit_deref_methods.rs:62:20 | LL | println!("{}", a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:64:11 + --> $DIR/explicit_deref_methods.rs:65:11 | LL | match a.deref() { | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:68:28 + --> $DIR/explicit_deref_methods.rs:69:28 | LL | let b: String = concat(a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:70:13 + --> $DIR/explicit_deref_methods.rs:71:13 | LL | let b = just_return(a).deref(); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `just_return(a)` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:72:28 + --> $DIR/explicit_deref_methods.rs:73:28 | LL | let b: String = concat(just_return(a).deref()); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `just_return(a)` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:74:19 + --> $DIR/explicit_deref_methods.rs:75:19 | LL | let b: &str = a.deref().deref(); | ^^^^^^^^^^^^^^^^^ help: try: `&**a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:77:13 + --> $DIR/explicit_deref_methods.rs:78:13 | LL | let b = opt_a.unwrap().deref(); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*opt_a.unwrap()` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:114:31 + --> $DIR/explicit_deref_methods.rs:115:31 | LL | let b: &str = expr_deref!(a.deref()); | ^^^^^^^^^ help: try: `&*a` From d94d85f3d8c64a216cee1c07f8c1c5384959984d Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Mon, 24 Jul 2023 14:47:32 +0000 Subject: [PATCH 04/49] Perform OpaqueCast field projection on HIR, too. This is necessary for closure captures in 2021 edition, as they capture individual fields, not the full mentioned variables. So it may try to capture a field of an opaque (because the hidden type is known to be something with a field). --- clippy_utils/src/sugg.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 5d7e1494fcfde..a43a81bc63a13 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -1011,6 +1011,8 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> { }, // note: unable to trigger `Subslice` kind in tests ProjectionKind::Subslice => (), + // Doesn't have surface syntax. Only occurs in patterns. + ProjectionKind::OpaqueCast => (), ProjectionKind::Deref => { // Explicit derefs are typically handled later on, but // some items do not need explicit deref, such as array accesses, From be0b4d5a9bd6943220026345dd72a3013d434dce Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Thu, 27 Jul 2023 15:50:42 +0000 Subject: [PATCH 05/49] Remove `constness` from `ParamEnv` --- clippy_lints/src/derive.rs | 3 +-- clippy_lints/src/manual_float_methods.rs | 2 +- clippy_utils/src/qualify_min_const_fn.rs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index c343f248d06d9..91bc30ab57744 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -6,7 +6,7 @@ use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor}; use rustc_hir::{ - self as hir, BlockCheckMode, BodyId, Constness, Expr, ExprKind, FnDecl, Impl, Item, ItemKind, UnsafeSource, + self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Impl, Item, ItemKind, UnsafeSource, Unsafety, }; use rustc_lint::{LateContext, LateLintPass}; @@ -526,6 +526,5 @@ fn param_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> }), )), Reveal::UserFacing, - Constness::NotConst, ) } diff --git a/clippy_lints/src/manual_float_methods.rs b/clippy_lints/src/manual_float_methods.rs index 085c73a5f9fbf..ccd2f06aad27d 100644 --- a/clippy_lints/src/manual_float_methods.rs +++ b/clippy_lints/src/manual_float_methods.rs @@ -83,7 +83,7 @@ impl Variant { impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if !in_external_macro(cx.sess(), expr.span) - && (!cx.param_env.is_const() || cx.tcx.features().active(sym!(const_float_classify))) + && cx.tcx.features().active(sym!(const_float_classify)) && let ExprKind::Binary(kind, lhs, rhs) = expr.kind && let ExprKind::Binary(lhs_kind, lhs_lhs, lhs_rhs) = lhs.kind && let ExprKind::Binary(rhs_kind, rhs_lhs, rhs_rhs) = rhs.kind diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index e563e41ab2aa4..aed3d601bcac4 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -399,7 +399,7 @@ fn is_ty_const_destruct<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, body: &Body<'tcx> let obligation = Obligation::new( tcx, ObligationCause::dummy_with_span(body.span), - ConstCx::new(tcx, body).param_env.with_const(), + ConstCx::new(tcx, body).param_env, TraitRef::from_lang_item(tcx, LangItem::Destruct, body.span, [ty]).with_constness(BoundConstness::ConstIfConst), ); From 39fb315396a70bf08ea6f030d1e97c4ef91128da Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Thu, 27 Jul 2023 17:56:25 +0000 Subject: [PATCH 06/49] bless clippy --- clippy_lints/src/manual_float_methods.rs | 8 +-- clippy_utils/src/qualify_min_const_fn.rs | 52 +++++++++++-------- .../ui/missing_const_for_fn/could_be_const.rs | 1 + .../could_be_const.stderr | 8 +-- 4 files changed, 36 insertions(+), 33 deletions(-) diff --git a/clippy_lints/src/manual_float_methods.rs b/clippy_lints/src/manual_float_methods.rs index ccd2f06aad27d..f48a5d9d24564 100644 --- a/clippy_lints/src/manual_float_methods.rs +++ b/clippy_lints/src/manual_float_methods.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_opt; use clippy_utils::{is_from_proc_macro, path_to_local}; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_hir::{BinOpKind, Constness, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass, Lint, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -83,8 +83,10 @@ impl Variant { impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if !in_external_macro(cx.sess(), expr.span) - && cx.tcx.features().active(sym!(const_float_classify)) - && let ExprKind::Binary(kind, lhs, rhs) = expr.kind + && ( + matches!(cx.tcx.constness(cx.tcx.hir().enclosing_body_owner(expr.hir_id)), Constness::NotConst) + || cx.tcx.features().active(sym!(const_float_classify)) + ) && let ExprKind::Binary(kind, lhs, rhs) = expr.kind && let ExprKind::Binary(lhs_kind, lhs_lhs, lhs_rhs) = lhs.kind && let ExprKind::Binary(rhs_kind, rhs_lhs, rhs_rhs) = rhs.kind // Checking all possible scenarios using a function would be a hopeless task, as we have diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index aed3d601bcac4..a98e2038d5f04 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -391,32 +391,38 @@ fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: &Msrv) -> bool { #[expect(clippy::similar_names)] // bit too pedantic fn is_ty_const_destruct<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, body: &Body<'tcx>) -> bool { - // Avoid selecting for simple cases, such as builtin types. - if ty::util::is_trivially_const_drop(ty) { - return true; - } + // FIXME(effects, fee1-dead) revert to const destruct once it works again + #[expect(unused)] + fn is_ty_const_destruct_unused<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, body: &Body<'tcx>) -> bool { + // Avoid selecting for simple cases, such as builtin types. + if ty::util::is_trivially_const_drop(ty) { + return true; + } - let obligation = Obligation::new( - tcx, - ObligationCause::dummy_with_span(body.span), - ConstCx::new(tcx, body).param_env, - TraitRef::from_lang_item(tcx, LangItem::Destruct, body.span, [ty]).with_constness(BoundConstness::ConstIfConst), - ); + let obligation = Obligation::new( + tcx, + ObligationCause::dummy_with_span(body.span), + ConstCx::new(tcx, body).param_env, + TraitRef::from_lang_item(tcx, LangItem::Destruct, body.span, [ty]).with_constness(BoundConstness::ConstIfConst), + ); - let infcx = tcx.infer_ctxt().build(); - let mut selcx = SelectionContext::new(&infcx); - let Some(impl_src) = selcx.select(&obligation).ok().flatten() else { - return false; - }; + let infcx = tcx.infer_ctxt().build(); + let mut selcx = SelectionContext::new(&infcx); + let Some(impl_src) = selcx.select(&obligation).ok().flatten() else { + return false; + }; + + if !matches!( + impl_src, + ImplSource::Builtin(BuiltinImplSource::Misc, _) | ImplSource::Param(ty::BoundConstness::ConstIfConst, _) + ) { + return false; + } - if !matches!( - impl_src, - ImplSource::Builtin(BuiltinImplSource::Misc, _) | ImplSource::Param(ty::BoundConstness::ConstIfConst, _) - ) { - return false; + let ocx = ObligationCtxt::new(&infcx); + ocx.register_obligations(impl_src.nested_obligations()); + ocx.select_all_or_error().is_empty() } - let ocx = ObligationCtxt::new(&infcx); - ocx.register_obligations(impl_src.nested_obligations()); - ocx.select_all_or_error().is_empty() + !ty.needs_drop(tcx, ConstCx::new(tcx, body).param_env) } diff --git a/tests/ui/missing_const_for_fn/could_be_const.rs b/tests/ui/missing_const_for_fn/could_be_const.rs index b1980b1b523fd..3aaee67e1d971 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.rs +++ b/tests/ui/missing_const_for_fn/could_be_const.rs @@ -99,4 +99,5 @@ impl const Drop for D { } // Lint this, since it can be dropped in const contexts +// FIXME(effects) fn d(this: D) {} diff --git a/tests/ui/missing_const_for_fn/could_be_const.stderr b/tests/ui/missing_const_for_fn/could_be_const.stderr index 7be2cc0ca9306..66cf4e3152934 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.stderr +++ b/tests/ui/missing_const_for_fn/could_be_const.stderr @@ -89,11 +89,5 @@ LL | | 46 LL | | } | |_^ -error: this could be a `const fn` - --> $DIR/could_be_const.rs:102:1 - | -LL | fn d(this: D) {} - | ^^^^^^^^^^^^^^^^ - -error: aborting due to 12 previous errors +error: aborting due to 11 previous errors From 4c9d68e9b84c52a2b942f53d0def103a82af9340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 2 Jul 2023 23:21:36 +0200 Subject: [PATCH 07/49] Make Clippy understand generic const items --- clippy_lints/src/large_const_arrays.rs | 6 +++++- clippy_lints/src/non_copy_const.rs | 2 +- clippy_lints/src/types/mod.rs | 2 +- clippy_utils/src/ast_utils.rs | 8 ++++++-- clippy_utils/src/consts.rs | 2 +- clippy_utils/src/lib.rs | 2 +- 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/large_const_arrays.rs b/clippy_lints/src/large_const_arrays.rs index 4dc750c03b488..9b26c3573e18f 100644 --- a/clippy_lints/src/large_const_arrays.rs +++ b/clippy_lints/src/large_const_arrays.rs @@ -50,7 +50,11 @@ impl<'tcx> LateLintPass<'tcx> for LargeConstArrays { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if_chain! { if !item.span.from_expansion(); - if let ItemKind::Const(hir_ty, _) = &item.kind; + if let ItemKind::Const(hir_ty, generics, _) = &item.kind; + // Since static items may not have generics, skip generic const items. + // FIXME(generic_const_items): I don't think checking `generics.hwcp` suffices as it + // doesn't account for empty where-clauses that only consist of keyword `where` IINM. + if generics.params.is_empty() && !generics.has_where_clause_predicates; let ty = hir_ty_to_ty(cx.tcx, hir_ty); if let ty::Array(element_type, cst) = ty.kind(); if let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind(); diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index 87699fd0ca6e4..8bb2fa9258562 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -302,7 +302,7 @@ declare_lint_pass!(NonCopyConst => [DECLARE_INTERIOR_MUTABLE_CONST, BORROW_INTER impl<'tcx> LateLintPass<'tcx> for NonCopyConst { fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'_>) { - if let ItemKind::Const(hir_ty, body_id) = it.kind { + if let ItemKind::Const(hir_ty, _generics, body_id) = it.kind { let ty = hir_ty_to_ty(cx.tcx, hir_ty); if !ignored_macro(cx, it) && is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, body_id, ty) { lint(cx, Source::Item { item: it.span }); diff --git a/clippy_lints/src/types/mod.rs b/clippy_lints/src/types/mod.rs index 3c873a5901d42..79f9d45d597ef 100644 --- a/clippy_lints/src/types/mod.rs +++ b/clippy_lints/src/types/mod.rs @@ -349,7 +349,7 @@ impl<'tcx> LateLintPass<'tcx> for Types { let is_exported = cx.effective_visibilities.is_exported(item.owner_id.def_id); match item.kind { - ItemKind::Static(ty, _, _) | ItemKind::Const(ty, _) => self.check_ty( + ItemKind::Static(ty, _, _) | ItemKind::Const(ty, _, _) => self.check_ty( cx, ty, CheckTyContext { diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index 8cc01f1ef9740..7e42924603a41 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -301,15 +301,17 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { ( Const(box ast::ConstItem { defaultness: ld, + generics: lg, ty: lt, expr: le, }), Const(box ast::ConstItem { defaultness: rd, + generics: rg, ty: rt, expr: re, }), - ) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), + ) => eq_defaultness(*ld, *rd) && eq_generics(lg, rg) && eq_ty(lt, rt) && eq_expr_opt(le, re), ( Fn(box ast::Fn { defaultness: ld, @@ -476,15 +478,17 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { ( Const(box ast::ConstItem { defaultness: ld, + generics: lg, ty: lt, expr: le, }), Const(box ast::ConstItem { defaultness: rd, + generics: rg, ty: rt, expr: re, }), - ) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), + ) => eq_defaultness(*ld, *rd) && eq_generics(lg, rg) && eq_ty(lt, rt) && eq_expr_opt(le, re), ( Fn(box ast::Fn { defaultness: ld, diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 061086c4fc208..f19e09a18ec56 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -461,7 +461,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { // Check if this constant is based on `cfg!(..)`, // which is NOT constant for our purposes. if let Some(node) = self.lcx.tcx.hir().get_if_local(def_id) - && let Node::Item(Item { kind: ItemKind::Const(_, body_id), .. }) = node + && let Node::Item(Item { kind: ItemKind::Const(.., body_id), .. }) = node && let Node::Expr(Expr { kind: ExprKind::Lit(_), span, .. }) = self.lcx .tcx .hir() diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 035511e891258..45b99df46b010 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -2380,7 +2380,7 @@ fn with_test_item_names(tcx: TyCtxt<'_>, module: LocalDefId, f: impl Fn(&[Symbol for id in tcx.hir().module_items(module) { if matches!(tcx.def_kind(id.owner_id), DefKind::Const) && let item = tcx.hir().item(id) - && let ItemKind::Const(ty, _body) = item.kind { + && let ItemKind::Const(ty, _generics, _body) = item.kind { if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind { // We could also check for the type name `test::TestDescAndFn` if let Res::Def(DefKind::Struct, _) = path.res { From 0c93e309561ec9a93bbef464b2201606d9094877 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 15 Jun 2023 13:15:52 +0000 Subject: [PATCH 08/49] Mark `map_or` as `#[must_use]` --- clippy_lints/src/operators/bit_mask.rs | 6 +++--- tests/ui/result_map_or_into_option.fixed | 2 +- tests/ui/result_map_or_into_option.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/operators/bit_mask.rs b/clippy_lints/src/operators/bit_mask.rs index 1fddf0f50e322..c146f3ae95b36 100644 --- a/clippy_lints/src/operators/bit_mask.rs +++ b/clippy_lints/src/operators/bit_mask.rs @@ -40,9 +40,9 @@ fn check_compare(cx: &LateContext<'_>, bit_op: &Expr<'_>, cmp_op: BinOpKind, cmp if op.node != BinOpKind::BitAnd && op.node != BinOpKind::BitOr { return; } - fetch_int_literal(cx, right) - .or_else(|| fetch_int_literal(cx, left)) - .map_or((), |mask| check_bit_mask(cx, op.node, cmp_op, mask, cmp_value, span)); + if let Some(mask) = fetch_int_literal(cx, right).or_else(|| fetch_int_literal(cx, left)) { + check_bit_mask(cx, op.node, cmp_op, mask, cmp_value, span); + } } } diff --git a/tests/ui/result_map_or_into_option.fixed b/tests/ui/result_map_or_into_option.fixed index 119ff25918a76..6850eeb7a4cdb 100644 --- a/tests/ui/result_map_or_into_option.fixed +++ b/tests/ui/result_map_or_into_option.fixed @@ -15,5 +15,5 @@ fn main() { // A non-Some `f` closure where the argument is not used as the // return should not emit the lint let opt: Result = Ok(1); - opt.map_or(None, |_x| Some(1)); + _ = opt.map_or(None, |_x| Some(1)); } diff --git a/tests/ui/result_map_or_into_option.rs b/tests/ui/result_map_or_into_option.rs index eeeef830af0a5..8e15181440782 100644 --- a/tests/ui/result_map_or_into_option.rs +++ b/tests/ui/result_map_or_into_option.rs @@ -15,5 +15,5 @@ fn main() { // A non-Some `f` closure where the argument is not used as the // return should not emit the lint let opt: Result = Ok(1); - opt.map_or(None, |_x| Some(1)); + _ = opt.map_or(None, |_x| Some(1)); } From 9a0af827811b3af1b925131eaf07c6e92df8b026 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 25 Jul 2023 13:25:38 +0000 Subject: [PATCH 09/49] Use builder pattern instead of lots of arguments for `EmitterWriter::new` --- clippy_lints/src/doc.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/clippy_lints/src/doc.rs b/clippy_lints/src/doc.rs index 00f70e586f428..573838ce63ef9 100644 --- a/clippy_lints/src/doc.rs +++ b/clippy_lints/src/doc.rs @@ -16,7 +16,7 @@ use rustc_ast::token::CommentKind; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::sync::Lrc; use rustc_errors::emitter::EmitterWriter; -use rustc_errors::{Applicability, Handler, SuggestionStyle, TerminalUrl}; +use rustc_errors::{Applicability, Handler, SuggestionStyle}; use rustc_hir as hir; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{AnonConst, Expr}; @@ -718,16 +718,8 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) { rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false); let emitter = EmitterWriter::new( Box::new(io::sink()), - None, - None, fallback_bundle, false, - false, - false, - None, - false, - false, - TerminalUrl::No, ); let handler = Handler::with_emitter(Box::new(emitter)).disable_warnings(); let sess = ParseSess::with_span_handler(handler, sm); From 084c90a305ae8c6ac18c5db50a1f86f6e62afb0c Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 26 Jul 2023 13:58:50 +0000 Subject: [PATCH 10/49] Remove a `bool` for color in favor of the `WriteColor` trait wrapping colored and uncolored printing --- clippy_lints/src/doc.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/clippy_lints/src/doc.rs b/clippy_lints/src/doc.rs index 573838ce63ef9..2c4d93e33ba76 100644 --- a/clippy_lints/src/doc.rs +++ b/clippy_lints/src/doc.rs @@ -719,7 +719,6 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) { let emitter = EmitterWriter::new( Box::new(io::sink()), fallback_bundle, - false, ); let handler = Handler::with_emitter(Box::new(emitter)).disable_warnings(); let sess = ParseSess::with_span_handler(handler, sm); From dc1e8b0dd9f63beeb3a858051d183a80c46fac32 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Mon, 31 Jul 2023 21:09:52 +0200 Subject: [PATCH 11/49] [`unnecessary_mut_passed`]: don't lint in macro expansions --- clippy_lints/src/mut_reference.rs | 5 +++++ tests/ui/mut_reference.rs | 15 ++++++++++++++- tests/ui/mut_reference.stderr | 8 ++++---- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/mut_reference.rs b/clippy_lints/src/mut_reference.rs index 4b20aecad4a19..e53e146ec5dbe 100644 --- a/clippy_lints/src/mut_reference.rs +++ b/clippy_lints/src/mut_reference.rs @@ -37,6 +37,11 @@ declare_lint_pass!(UnnecessaryMutPassed => [UNNECESSARY_MUT_PASSED]); impl<'tcx> LateLintPass<'tcx> for UnnecessaryMutPassed { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if e.span.from_expansion() { + // Issue #11268 + return; + } + match e.kind { ExprKind::Call(fn_expr, arguments) => { if let ExprKind::Path(ref path) = fn_expr.kind { diff --git a/tests/ui/mut_reference.rs b/tests/ui/mut_reference.rs index 73906121c402e..00661c51a78f9 100644 --- a/tests/ui/mut_reference.rs +++ b/tests/ui/mut_reference.rs @@ -1,8 +1,21 @@ -#![allow(unused_variables)] +#![allow(unused_variables, dead_code)] fn takes_an_immutable_reference(a: &i32) {} fn takes_a_mutable_reference(a: &mut i32) {} +mod issue11268 { + macro_rules! x { + ($f:expr) => { + $f(&mut 1); + }; + } + + fn f() { + x!(super::takes_an_immutable_reference); + x!(super::takes_a_mutable_reference); + } +} + struct MyStruct; impl MyStruct { diff --git a/tests/ui/mut_reference.stderr b/tests/ui/mut_reference.stderr index 1fdfbf9227e31..d8a71d264610a 100644 --- a/tests/ui/mut_reference.stderr +++ b/tests/ui/mut_reference.stderr @@ -1,5 +1,5 @@ error: the function `takes_an_immutable_reference` doesn't need a mutable reference - --> $DIR/mut_reference.rs:17:34 + --> $DIR/mut_reference.rs:30:34 | LL | takes_an_immutable_reference(&mut 42); | ^^^^^^^ @@ -7,19 +7,19 @@ LL | takes_an_immutable_reference(&mut 42); = note: `-D clippy::unnecessary-mut-passed` implied by `-D warnings` error: the function `as_ptr` doesn't need a mutable reference - --> $DIR/mut_reference.rs:19:12 + --> $DIR/mut_reference.rs:32:12 | LL | as_ptr(&mut 42); | ^^^^^^^ error: the method `takes_an_immutable_reference` doesn't need a mutable reference - --> $DIR/mut_reference.rs:23:44 + --> $DIR/mut_reference.rs:36:44 | LL | my_struct.takes_an_immutable_reference(&mut 42); | ^^^^^^^ error: this argument is a mutable reference, but not used mutably - --> $DIR/mut_reference.rs:11:44 + --> $DIR/mut_reference.rs:24:44 | LL | fn takes_a_mutable_reference(&self, a: &mut i32) {} | ^^^^^^^^ help: consider changing to: `&i32` From f9a6dfa60d4f55ec9a98bc517dea4e4fb9c0f61a Mon Sep 17 00:00:00 2001 From: "Samuel \"Sam\" Tardieu" Date: Thu, 27 Jul 2023 19:04:01 +0200 Subject: [PATCH 12/49] New lint `ignored_unit_patterns` --- CHANGELOG.md | 1 + clippy_dev/src/main.rs | 2 +- clippy_dev/src/setup/vscode.rs | 2 +- clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/ignored_unit_patterns.rs | 52 +++++++++++++++++++++++ clippy_lints/src/lib.rs | 2 + clippy_lints/src/option_if_let_else.rs | 2 +- tests/ui/ignored_unit_patterns.fixed | 17 ++++++++ tests/ui/ignored_unit_patterns.rs | 17 ++++++++ tests/ui/ignored_unit_patterns.stderr | 28 ++++++++++++ 10 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 clippy_lints/src/ignored_unit_patterns.rs create mode 100644 tests/ui/ignored_unit_patterns.fixed create mode 100644 tests/ui/ignored_unit_patterns.rs create mode 100644 tests/ui/ignored_unit_patterns.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 251f32d2e6e5e..370772498e4e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4890,6 +4890,7 @@ Released 2018-09-13 [`if_same_then_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_same_then_else [`if_then_some_else_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_then_some_else_none [`ifs_same_cond`]: https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond +[`ignored_unit_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#ignored_unit_patterns [`impl_trait_in_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#impl_trait_in_params [`implicit_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_clone [`implicit_hasher`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_hasher diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 43eaccdf5a31a..fca750fafc792 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -41,7 +41,7 @@ fn main() { matches.get_one::("type").map(String::as_str), matches.get_flag("msrv"), ) { - Ok(_) => update_lints::update(update_lints::UpdateMode::Change), + Ok(()) => update_lints::update(update_lints::UpdateMode::Change), Err(e) => eprintln!("Unable to create lint: {e}"), } }, diff --git a/clippy_dev/src/setup/vscode.rs b/clippy_dev/src/setup/vscode.rs index dbcdc9b59e529..204f4af2cf1be 100644 --- a/clippy_dev/src/setup/vscode.rs +++ b/clippy_dev/src/setup/vscode.rs @@ -47,7 +47,7 @@ fn check_install_precondition(force_override: bool) -> bool { } } else { match fs::create_dir(vs_dir_path) { - Ok(_) => { + Ok(()) => { println!("info: created `{VSCODE_DIR}` directory for clippy"); }, Err(err) => { diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index e084471433d45..413025dafcbfd 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -203,6 +203,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::if_let_mutex::IF_LET_MUTEX_INFO, crate::if_not_else::IF_NOT_ELSE_INFO, crate::if_then_some_else_none::IF_THEN_SOME_ELSE_NONE_INFO, + crate::ignored_unit_patterns::IGNORED_UNIT_PATTERNS_INFO, crate::implicit_hasher::IMPLICIT_HASHER_INFO, crate::implicit_return::IMPLICIT_RETURN_INFO, crate::implicit_saturating_add::IMPLICIT_SATURATING_ADD_INFO, diff --git a/clippy_lints/src/ignored_unit_patterns.rs b/clippy_lints/src/ignored_unit_patterns.rs new file mode 100644 index 0000000000000..c635120b88245 --- /dev/null +++ b/clippy_lints/src/ignored_unit_patterns.rs @@ -0,0 +1,52 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use hir::PatKind; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_` in patterns of type `()`. + /// + /// ### Why is this bad? + /// Matching with `()` explicitly instead of `_` outlines + /// the fact that the pattern contains no data. Also it + /// would detect a type change that `_` would ignore. + /// + /// ### Example + /// ```rust + /// match std::fs::create_dir("tmp-work-dir") { + /// Ok(_) => println!("Working directory created"), + /// Err(s) => eprintln!("Could not create directory: {s}"), + /// } + /// ``` + /// Use instead: + /// ```rust + /// match std::fs::create_dir("tmp-work-dir") { + /// Ok(()) => println!("Working directory created"), + /// Err(s) => eprintln!("Could not create directory: {s}"), + /// } + /// ``` + #[clippy::version = "1.73.0"] + pub IGNORED_UNIT_PATTERNS, + pedantic, + "suggest replacing `_` by `()` in patterns where appropriate" +} +declare_lint_pass!(IgnoredUnitPatterns => [IGNORED_UNIT_PATTERNS]); + +impl<'tcx> LateLintPass<'tcx> for IgnoredUnitPatterns { + fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx hir::Pat<'tcx>) { + if matches!(pat.kind, PatKind::Wild) && cx.typeck_results().pat_ty(pat).is_unit() { + span_lint_and_sugg( + cx, + IGNORED_UNIT_PATTERNS, + pat.span, + "matching over `()` is more explicit", + "use `()` instead of `_`", + String::from("()"), + Applicability::MachineApplicable, + ); + } + } +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 9d6096ccb2ae8..358004cf460b1 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -147,6 +147,7 @@ mod future_not_send; mod if_let_mutex; mod if_not_else; mod if_then_some_else_none; +mod ignored_unit_patterns; mod implicit_hasher; mod implicit_return; mod implicit_saturating_add; @@ -1093,6 +1094,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: }) }); store.register_late_pass(|_| Box::new(redundant_locals::RedundantLocals)); + store.register_late_pass(|_| Box::new(ignored_unit_patterns::IgnoredUnitPatterns)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index 40da002f4ff0d..a7a7f4fd8fa2c 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -155,7 +155,7 @@ fn try_get_option_occurrence<'tcx>( }); if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(local_id), .. })) = e.kind { match some_captures.get(local_id) - .or_else(|| (method_sugg == "map_or_else").then_some(()).and_then(|_| none_captures.get(local_id))) + .or_else(|| (method_sugg == "map_or_else").then_some(()).and_then(|()| none_captures.get(local_id))) { Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return None, Some(CaptureKind::Ref(Mutability::Not)) if as_mut => return None, diff --git a/tests/ui/ignored_unit_patterns.fixed b/tests/ui/ignored_unit_patterns.fixed new file mode 100644 index 0000000000000..492219fe44711 --- /dev/null +++ b/tests/ui/ignored_unit_patterns.fixed @@ -0,0 +1,17 @@ +//@run-rustfix + +#![warn(clippy::ignored_unit_patterns)] +#![allow(clippy::redundant_pattern_matching, clippy::single_match)] + +fn foo() -> Result<(), ()> { + unimplemented!() +} + +fn main() { + match foo() { + Ok(()) => {}, + Err(()) => {}, + } + if let Ok(()) = foo() {} + let _ = foo().map_err(|()| todo!()); +} diff --git a/tests/ui/ignored_unit_patterns.rs b/tests/ui/ignored_unit_patterns.rs new file mode 100644 index 0000000000000..90af36f8e5ed8 --- /dev/null +++ b/tests/ui/ignored_unit_patterns.rs @@ -0,0 +1,17 @@ +//@run-rustfix + +#![warn(clippy::ignored_unit_patterns)] +#![allow(clippy::redundant_pattern_matching, clippy::single_match)] + +fn foo() -> Result<(), ()> { + unimplemented!() +} + +fn main() { + match foo() { + Ok(_) => {}, + Err(_) => {}, + } + if let Ok(_) = foo() {} + let _ = foo().map_err(|_| todo!()); +} diff --git a/tests/ui/ignored_unit_patterns.stderr b/tests/ui/ignored_unit_patterns.stderr new file mode 100644 index 0000000000000..8feea3cc2a835 --- /dev/null +++ b/tests/ui/ignored_unit_patterns.stderr @@ -0,0 +1,28 @@ +error: matching over `()` is more explicit + --> $DIR/ignored_unit_patterns.rs:12:12 + | +LL | Ok(_) => {}, + | ^ help: use `()` instead of `_`: `()` + | + = note: `-D clippy::ignored-unit-patterns` implied by `-D warnings` + +error: matching over `()` is more explicit + --> $DIR/ignored_unit_patterns.rs:13:13 + | +LL | Err(_) => {}, + | ^ help: use `()` instead of `_`: `()` + +error: matching over `()` is more explicit + --> $DIR/ignored_unit_patterns.rs:15:15 + | +LL | if let Ok(_) = foo() {} + | ^ help: use `()` instead of `_`: `()` + +error: matching over `()` is more explicit + --> $DIR/ignored_unit_patterns.rs:16:28 + | +LL | let _ = foo().map_err(|_| todo!()); + | ^ help: use `()` instead of `_`: `()` + +error: aborting due to 4 previous errors + From b0e64a9c09773e5ac908d89bff3db95d35491308 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Mon, 31 Jul 2023 23:53:53 +0200 Subject: [PATCH 13/49] Merge commit '5436dba826191964ac1d0dab534b7eb6d4c878f6' into clippyup --- .github/workflows/clippy_bors.yml | 29 +- CHANGELOG.md | 14 + book/src/lint_configuration.md | 21 + clippy_dev/src/lib.rs | 2 +- clippy_dev/src/new_lint.rs | 18 +- clippy_lints/src/absolute_paths.rs | 100 ++ clippy_lints/src/arc_with_non_send_sync.rs | 12 +- clippy_lints/src/casts/mod.rs | 8 + clippy_lints/src/casts/unnecessary_cast.rs | 24 +- clippy_lints/src/declared_lints.rs | 13 +- clippy_lints/src/dereference.rs | 1018 +++++++---------- clippy_lints/src/derive.rs | 48 +- clippy_lints/src/error_impl_error.rs | 87 ++ clippy_lints/src/eta_reduction.rs | 334 +++--- clippy_lints/src/four_forward_slashes.rs | 99 ++ clippy_lints/src/incorrect_impls.rs | 72 +- clippy_lints/src/inherent_to_string.rs | 35 +- clippy_lints/src/len_zero.rs | 30 +- clippy_lints/src/lib.rs | 17 +- clippy_lints/src/lifetimes.rs | 3 +- clippy_lints/src/loops/explicit_iter_loop.rs | 2 +- clippy_lints/src/loops/single_element_loop.rs | 3 +- clippy_lints/src/loops/utils.rs | 4 +- clippy_lints/src/matches/mod.rs | 33 + clippy_lints/src/matches/redundant_guards.rs | 190 +++ .../src/matches/redundant_pattern_match.rs | 70 +- clippy_lints/src/methods/filter_map.rs | 320 +++++- .../src/methods/filter_map_bool_then.rs | 45 + clippy_lints/src/methods/format_collect.rs | 33 + clippy_lints/src/methods/iter_skip_zero.rs | 34 + clippy_lints/src/methods/mod.rs | 220 +++- clippy_lints/src/methods/or_fun_call.rs | 122 +- .../src/methods/read_line_without_trim.rs | 4 +- .../src/methods/readonly_write_lock.rs | 52 + .../src/methods/string_lit_chars_any.rs | 58 + clippy_lints/src/methods/type_id_on_box.rs | 4 +- .../src/methods/unnecessary_filter_map.rs | 10 + .../src/methods/unnecessary_literal_unwrap.rs | 31 + .../src/methods/unwrap_or_else_default.rs | 66 -- clippy_lints/src/min_ident_chars.rs | 8 + clippy_lints/src/needless_pass_by_ref_mut.rs | 205 +++- clippy_lints/src/needless_pass_by_value.rs | 10 +- clippy_lints/src/non_copy_const.rs | 13 +- .../src/operators/arithmetic_side_effects.rs | 8 +- clippy_lints/src/option_env_unwrap.rs | 39 +- clippy_lints/src/ptr.rs | 13 + clippy_lints/src/redundant_locals.rs | 103 ++ .../src/redundant_static_lifetimes.rs | 3 +- clippy_lints/src/renamed_lints.rs | 1 + clippy_lints/src/returns.rs | 80 +- .../src/semicolon_if_nothing_returned.rs | 2 +- .../src/significant_drop_tightening.rs | 41 +- .../src/slow_vector_initialization.rs | 113 +- clippy_lints/src/tuple_array_conversions.rs | 316 +++-- clippy_lints/src/unused_async.rs | 108 +- clippy_lints/src/utils/conf.rs | 10 + clippy_lints/src/utils/mod.rs | 2 +- clippy_utils/src/higher.rs | 1 + clippy_utils/src/hir_utils.rs | 25 +- clippy_utils/src/lib.rs | 282 ++++- clippy_utils/src/mir/possible_origin.rs | 2 +- clippy_utils/src/paths.rs | 4 + clippy_utils/src/ty.rs | 198 ++-- .../src/ty/type_certainty/certainty.rs | 122 ++ clippy_utils/src/ty/type_certainty/mod.rs | 320 ++++++ clippy_utils/src/usage.rs | 49 +- clippy_utils/src/visitors.rs | 10 + rust-toolchain | 2 +- src/driver.rs | 2 +- src/main.rs | 3 +- tests/compile-test.rs | 40 +- tests/integration.rs | 24 + .../cargo_common_metadata/fail/src/main.rs | 1 - .../fail_publish/src/main.rs | 1 - .../fail_publish_true/src/main.rs | 1 - .../cargo_common_metadata/pass/src/main.rs | 1 - .../pass_publish_empty/src/main.rs | 1 - .../pass_publish_false/src/main.rs | 1 - tests/ui-cargo/feature_name/fail/src/main.rs | 1 - tests/ui-cargo/feature_name/pass/src/main.rs | 1 - .../module_style/fail_mod_remap/src/main.rs | 1 + .../5041_allow_dev_build/src/main.rs | 1 - .../multiple_crate_versions/fail/src/main.rs | 1 - .../multiple_crate_versions/pass/src/main.rs | 1 - .../wildcard_dependencies/fail/src/main.rs | 1 - .../wildcard_dependencies/pass/src/main.rs | 1 - tests/ui-internal/custom_ice_message.stderr | 2 +- .../absolute_paths.allow_crates.stderr | 28 + .../absolute_paths.disallow_crates.stderr | 70 ++ .../ui-toml/absolute_paths/absolute_paths.rs | 97 ++ .../absolute_paths/allow_crates/clippy.toml | 2 + .../absolute_paths/auxiliary/helper.rs | 11 + .../disallow_crates/clippy.toml | 1 + .../toml_unknown_key/conf_unknown_key.stderr | 4 + tests/ui/arc_with_non_send_sync.rs | 30 +- tests/ui/arc_with_non_send_sync.stderr | 6 +- tests/ui/arithmetic_side_effects.rs | 7 + tests/ui/comparison_to_empty.fixed | 12 +- tests/ui/comparison_to_empty.rs | 12 +- tests/ui/comparison_to_empty.stderr | 40 +- tests/ui/error_impl_error.rs | 90 ++ tests/ui/error_impl_error.stderr | 45 + tests/ui/eta.fixed | 55 + tests/ui/eta.rs | 55 + tests/ui/eta.stderr | 8 +- tests/ui/filter_map_bool_then.fixed | 44 + tests/ui/filter_map_bool_then.rs | 44 + tests/ui/filter_map_bool_then.stderr | 34 + tests/ui/format_collect.rs | 31 + tests/ui/format_collect.stderr | 62 + tests/ui/four_forward_slashes.fixed | 48 + tests/ui/four_forward_slashes.rs | 48 + tests/ui/four_forward_slashes.stderr | 68 ++ .../ui/four_forward_slashes_first_line.fixed | 7 + tests/ui/four_forward_slashes_first_line.rs | 7 + .../ui/four_forward_slashes_first_line.stderr | 15 + tests/ui/if_same_then_else.rs | 41 + tests/ui/if_same_then_else.stderr | 20 +- tests/ui/ifs_same_cond.rs | 4 + tests/ui/ifs_same_cond.stderr | 4 +- ...correct_partial_ord_impl_on_ord_type.fixed | 33 +- .../incorrect_partial_ord_impl_on_ord_type.rs | 33 +- ...orrect_partial_ord_impl_on_ord_type.stderr | 4 +- ...partial_ord_impl_on_ord_type_fully_qual.rs | 51 + ...ial_ord_impl_on_ord_type_fully_qual.stderr | 31 + tests/ui/infinite_loop.stderr | 16 +- tests/ui/inherent_to_string.rs | 26 +- tests/ui/inherent_to_string.stderr | 10 +- tests/ui/iter_skip_zero.fixed | 25 + tests/ui/iter_skip_zero.rs | 25 + tests/ui/iter_skip_zero.stderr | 43 + tests/ui/let_and_return.rs | 10 + tests/ui/let_underscore_future.stderr | 16 +- tests/ui/manual_filter_map.fixed | 24 + tests/ui/manual_filter_map.rs | 28 + tests/ui/manual_filter_map.stderr | 73 +- tests/ui/manual_find_map.stderr | 53 + tests/ui/match_expr_like_matches_macro.fixed | 3 +- tests/ui/match_expr_like_matches_macro.rs | 3 +- tests/ui/match_expr_like_matches_macro.stderr | 28 +- tests/ui/min_ident_chars.rs | 4 + tests/ui/mut_key.stderr | 16 +- tests/ui/mut_reference.stderr | 24 +- tests/ui/needless_pass_by_ref_mut.rs | 120 +- tests/ui/needless_pass_by_ref_mut.stderr | 72 +- .../needless_return_with_question_mark.fixed | 40 + .../ui/needless_return_with_question_mark.rs | 40 + .../needless_return_with_question_mark.stderr | 10 + tests/ui/option_env_unwrap.rs | 1 + tests/ui/option_env_unwrap.stderr | 18 +- tests/ui/option_if_let_else.fixed | 3 +- tests/ui/option_if_let_else.rs | 3 +- tests/ui/option_if_let_else.stderr | 46 +- tests/ui/or_fun_call.fixed | 63 +- tests/ui/or_fun_call.rs | 55 + tests/ui/or_fun_call.stderr | 90 +- tests/ui/ptr_arg.rs | 13 + tests/ui/read_zero_byte_vec.rs | 6 +- tests/ui/read_zero_byte_vec.stderr | 20 +- tests/ui/readonly_write_lock.rs | 42 + tests/ui/readonly_write_lock.stderr | 16 + tests/ui/redundant_guards.fixed | 133 +++ tests/ui/redundant_guards.rs | 133 +++ tests/ui/redundant_guards.stderr | 98 ++ tests/ui/redundant_locals.rs | 111 ++ tests/ui/redundant_locals.stderr | 136 +++ .../redundant_pattern_matching_option.fixed | 14 + tests/ui/redundant_pattern_matching_option.rs | 14 + .../redundant_pattern_matching_option.stderr | 74 +- tests/ui/rename.fixed | 2 + tests/ui/rename.rs | 2 + tests/ui/rename.stderr | 114 +- tests/ui/semicolon_if_nothing_returned.fixed | 123 ++ tests/ui/semicolon_if_nothing_returned.rs | 3 +- tests/ui/semicolon_if_nothing_returned.stderr | 10 +- tests/ui/shadow.rs | 7 +- tests/ui/shadow.stderr | 92 +- .../ui/should_impl_trait/method_list_2.stderr | 18 +- tests/ui/significant_drop_tightening.fixed | 27 + tests/ui/significant_drop_tightening.rs | 27 + tests/ui/significant_drop_tightening.stderr | 6 +- tests/ui/single_match.fixed | 1 + tests/ui/single_match.rs | 1 + tests/ui/single_match.stderr | 36 +- tests/ui/slow_vector_initialization.rs | 16 + tests/ui/slow_vector_initialization.stderr | 64 +- tests/ui/string_lit_chars_any.fixed | 50 + tests/ui/string_lit_chars_any.rs | 50 + tests/ui/string_lit_chars_any.stderr | 58 + tests/ui/swap.fixed | 3 +- tests/ui/swap.rs | 3 +- tests/ui/swap.stderr | 34 +- tests/ui/try_err.fixed | 6 +- tests/ui/try_err.rs | 6 +- tests/ui/try_err.stderr | 22 +- tests/ui/tuple_array_conversions.rs | 30 + tests/ui/tuple_array_conversions.stderr | 36 +- tests/ui/unnecessary_cast.fixed | 17 + tests/ui/unnecessary_cast.rs | 17 + tests/ui/unnecessary_cast.stderr | 88 +- tests/ui/unnecessary_filter_map.rs | 6 + tests/ui/unnecessary_filter_map.stderr | 8 +- tests/ui/unnecessary_find_map.rs | 6 + tests/ui/unnecessary_find_map.stderr | 8 +- tests/ui/unnecessary_literal_unwrap.fixed | 11 + tests/ui/unnecessary_literal_unwrap.rs | 11 + tests/ui/unnecessary_literal_unwrap.stderr | 188 ++- .../unnecessary_literal_unwrap_unfixable.rs | 2 + ...nnecessary_literal_unwrap_unfixable.stderr | 182 +-- tests/ui/unused_async.rs | 17 + tests/ui/unused_async.stderr | 14 +- tests/ui/unwrap_or_else_default.fixed | 62 +- tests/ui/unwrap_or_else_default.rs | 62 +- tests/ui/unwrap_or_else_default.stderr | 100 +- triagebot.toml | 5 + 215 files changed, 7968 insertions(+), 2143 deletions(-) create mode 100644 clippy_lints/src/absolute_paths.rs create mode 100644 clippy_lints/src/error_impl_error.rs create mode 100644 clippy_lints/src/four_forward_slashes.rs create mode 100644 clippy_lints/src/matches/redundant_guards.rs create mode 100644 clippy_lints/src/methods/filter_map_bool_then.rs create mode 100644 clippy_lints/src/methods/format_collect.rs create mode 100644 clippy_lints/src/methods/iter_skip_zero.rs create mode 100644 clippy_lints/src/methods/readonly_write_lock.rs create mode 100644 clippy_lints/src/methods/string_lit_chars_any.rs delete mode 100644 clippy_lints/src/methods/unwrap_or_else_default.rs create mode 100644 clippy_lints/src/redundant_locals.rs create mode 100644 clippy_utils/src/ty/type_certainty/certainty.rs create mode 100644 clippy_utils/src/ty/type_certainty/mod.rs create mode 100644 tests/ui-toml/absolute_paths/absolute_paths.allow_crates.stderr create mode 100644 tests/ui-toml/absolute_paths/absolute_paths.disallow_crates.stderr create mode 100644 tests/ui-toml/absolute_paths/absolute_paths.rs create mode 100644 tests/ui-toml/absolute_paths/allow_crates/clippy.toml create mode 100644 tests/ui-toml/absolute_paths/auxiliary/helper.rs create mode 100644 tests/ui-toml/absolute_paths/disallow_crates/clippy.toml create mode 100644 tests/ui/error_impl_error.rs create mode 100644 tests/ui/error_impl_error.stderr create mode 100644 tests/ui/filter_map_bool_then.fixed create mode 100644 tests/ui/filter_map_bool_then.rs create mode 100644 tests/ui/filter_map_bool_then.stderr create mode 100644 tests/ui/format_collect.rs create mode 100644 tests/ui/format_collect.stderr create mode 100644 tests/ui/four_forward_slashes.fixed create mode 100644 tests/ui/four_forward_slashes.rs create mode 100644 tests/ui/four_forward_slashes.stderr create mode 100644 tests/ui/four_forward_slashes_first_line.fixed create mode 100644 tests/ui/four_forward_slashes_first_line.rs create mode 100644 tests/ui/four_forward_slashes_first_line.stderr create mode 100644 tests/ui/incorrect_partial_ord_impl_on_ord_type_fully_qual.rs create mode 100644 tests/ui/incorrect_partial_ord_impl_on_ord_type_fully_qual.stderr create mode 100644 tests/ui/iter_skip_zero.fixed create mode 100644 tests/ui/iter_skip_zero.rs create mode 100644 tests/ui/iter_skip_zero.stderr create mode 100644 tests/ui/needless_return_with_question_mark.fixed create mode 100644 tests/ui/needless_return_with_question_mark.rs create mode 100644 tests/ui/needless_return_with_question_mark.stderr create mode 100644 tests/ui/readonly_write_lock.rs create mode 100644 tests/ui/readonly_write_lock.stderr create mode 100644 tests/ui/redundant_guards.fixed create mode 100644 tests/ui/redundant_guards.rs create mode 100644 tests/ui/redundant_guards.stderr create mode 100644 tests/ui/redundant_locals.rs create mode 100644 tests/ui/redundant_locals.stderr create mode 100644 tests/ui/semicolon_if_nothing_returned.fixed create mode 100644 tests/ui/string_lit_chars_any.fixed create mode 100644 tests/ui/string_lit_chars_any.rs create mode 100644 tests/ui/string_lit_chars_any.stderr diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_bors.yml index 4eb11a3ac8572..5c69714bc1e99 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_bors.yml @@ -187,16 +187,14 @@ jobs: - name: Extract Binaries run: | DIR=$CARGO_TARGET_DIR/debug - rm $DIR/deps/integration-*.d - mv $DIR/deps/integration-* $DIR/integration + find $DIR/deps/integration-* -executable ! -type d | xargs -I {} mv {} $DIR/integration find $DIR ! -executable -o -type d ! -path $DIR | xargs rm -rf - rm -rf $CARGO_TARGET_DIR/release - name: Upload Binaries - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: - name: target - path: target + name: binaries + path: target/debug integration: needs: integration_build @@ -206,22 +204,20 @@ jobs: matrix: integration: - 'rust-lang/cargo' - # FIXME: re-enable once fmt_macros is renamed in RLS - # - 'rust-lang/rls' - 'rust-lang/chalk' - 'rust-lang/rustfmt' - 'Marwes/combine' - 'Geal/nom' - 'rust-lang/stdarch' - 'serde-rs/serde' - # FIXME: chrono currently cannot be compiled with `--all-targets` - # - 'chronotope/chrono' + - 'chronotope/chrono' - 'hyperium/hyper' - 'rust-random/rand' - 'rust-lang/futures-rs' - 'rust-itertools/itertools' - 'rust-lang-nursery/failure' - 'rust-lang/log' + - 'matthiaskrgr/clippy_ci_panic_test' runs-on: ubuntu-latest @@ -237,12 +233,17 @@ jobs: - name: Install toolchain run: rustup show active-toolchain + - name: Set LD_LIBRARY_PATH + run: | + SYSROOT=$(rustc --print sysroot) + echo "LD_LIBRARY_PATH=${SYSROOT}/lib${LD_LIBRARY_PATH+:${LD_LIBRARY_PATH}}" >> $GITHUB_ENV + # Download - name: Download target dir - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v3 with: - name: target - path: target + name: binaries + path: target/debug - name: Make Binaries Executable run: chmod +x $CARGO_TARGET_DIR/debug/* @@ -251,7 +252,7 @@ jobs: - name: Test ${{ matrix.integration }} run: | RUSTUP_TOOLCHAIN="$(rustup show active-toolchain | grep -o -E "nightly-[0-9]{4}-[0-9]{2}-[0-9]{2}")" \ - $CARGO_TARGET_DIR/debug/integration + $CARGO_TARGET_DIR/debug/integration --show-output env: INTEGRATION: ${{ matrix.integration }} diff --git a/CHANGELOG.md b/CHANGELOG.md index b3b6e3b865fce..2655d93599e7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4680,6 +4680,7 @@ Released 2018-09-13 +[`absolute_paths`]: https://rust-lang.github.io/rust-clippy/master/index.html#absolute_paths [`absurd_extreme_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons [`alloc_instead_of_core`]: https://rust-lang.github.io/rust-clippy/master/index.html#alloc_instead_of_core [`allow_attributes`]: https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes @@ -4819,6 +4820,7 @@ Released 2018-09-13 [`equatable_if_let`]: https://rust-lang.github.io/rust-clippy/master/index.html#equatable_if_let [`erasing_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#erasing_op [`err_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#err_expect +[`error_impl_error`]: https://rust-lang.github.io/rust-clippy/master/index.html#error_impl_error [`eval_order_dependence`]: https://rust-lang.github.io/rust-clippy/master/index.html#eval_order_dependence [`excessive_nesting`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_nesting [`excessive_precision`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision @@ -4842,6 +4844,7 @@ Released 2018-09-13 [`field_reassign_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#field_reassign_with_default [`filetype_is_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#filetype_is_file [`filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map +[`filter_map_bool_then`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_bool_then [`filter_map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_identity [`filter_map_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_next [`filter_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_next @@ -4865,8 +4868,10 @@ Released 2018-09-13 [`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy [`forget_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_non_drop [`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref +[`format_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_collect [`format_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_in_format_args [`format_push_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_push_string +[`four_forward_slashes`]: https://rust-lang.github.io/rust-clippy/master/index.html#four_forward_slashes [`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect [`from_over_into`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into [`from_raw_with_void_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_raw_with_void_ptr @@ -4937,6 +4942,7 @@ Released 2018-09-13 [`iter_on_single_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_on_single_items [`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_skip_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_zero [`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 @@ -5092,6 +5098,7 @@ Released 2018-09-13 [`needless_raw_string_hashes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_raw_string_hashes [`needless_raw_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_raw_strings [`needless_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return +[`needless_return_with_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return_with_question_mark [`needless_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_splitn [`needless_update`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_update [`neg_cmp_op_on_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_cmp_op_on_partial_ord @@ -5174,6 +5181,7 @@ Released 2018-09-13 [`rc_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex [`read_line_without_trim`]: https://rust-lang.github.io/rust-clippy/master/index.html#read_line_without_trim [`read_zero_byte_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#read_zero_byte_vec +[`readonly_write_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#readonly_write_lock [`recursive_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#recursive_format_impl [`redundant_allocation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation [`redundant_async_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_async_block @@ -5185,6 +5193,8 @@ Released 2018-09-13 [`redundant_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_else [`redundant_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_feature_names [`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names +[`redundant_guards`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_guards +[`redundant_locals`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_locals [`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern [`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching [`redundant_pub_crate`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pub_crate @@ -5254,6 +5264,7 @@ Released 2018-09-13 [`string_extend_chars`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_extend_chars [`string_from_utf8_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_from_utf8_as_bytes [`string_lit_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_lit_as_bytes +[`string_lit_chars_any`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_lit_chars_any [`string_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_slice [`string_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_to_string [`strlen_on_c_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#strlen_on_c_strings @@ -5362,6 +5373,7 @@ Released 2018-09-13 [`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit [`unusual_byte_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unusual_byte_groupings [`unwrap_in_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_in_result +[`unwrap_or_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_or_default [`unwrap_or_else_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_or_else_default [`unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used [`upper_case_acronyms`]: https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms @@ -5462,4 +5474,6 @@ Released 2018-09-13 [`accept-comment-above-statement`]: https://doc.rust-lang.org/clippy/lint_configuration.html#accept-comment-above-statement [`accept-comment-above-attributes`]: https://doc.rust-lang.org/clippy/lint_configuration.html#accept-comment-above-attributes [`allow-one-hash-in-raw-strings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-one-hash-in-raw-strings +[`absolute-paths-max-segments`]: https://doc.rust-lang.org/clippy/lint_configuration.html#absolute-paths-max-segments +[`absolute-paths-allowed-crates`]: https://doc.rust-lang.org/clippy/lint_configuration.html#absolute-paths-allowed-crates diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index f8073dac3301a..caaad6d11736f 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -730,3 +730,24 @@ Whether to allow `r#""#` when `r""` can be used * [`unnecessary_raw_string_hashes`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_raw_string_hashes) +## `absolute-paths-max-segments` +The maximum number of segments a path can have before being linted, anything above this will +be linted. + +**Default Value:** `2` (`u64`) + +--- +**Affected lints:** +* [`absolute_paths`](https://rust-lang.github.io/rust-clippy/master/index.html#absolute_paths) + + +## `absolute-paths-allowed-crates` +Which crates to allow absolute paths from + +**Default Value:** `{}` (`rustc_data_structures::fx::FxHashSet`) + +--- +**Affected lints:** +* [`absolute_paths`](https://rust-lang.github.io/rust-clippy/master/index.html#absolute_paths) + + diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 4624451cff4fc..c4ae4f0e2bdd6 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -51,7 +51,7 @@ pub fn clippy_project_root() -> PathBuf { for path in current_dir.ancestors() { let result = std::fs::read_to_string(path.join("Cargo.toml")); if let Err(err) = &result { - if err.kind() == std::io::ErrorKind::NotFound { + if err.kind() == io::ErrorKind::NotFound { continue; } } diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index f39bc06e6d732..e64cf2c874968 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -96,8 +96,7 @@ fn create_test(lint: &LintData<'_>) -> io::Result<()> { path.push("src"); fs::create_dir(&path)?; - let header = format!("//@compile-flags: --crate-name={lint_name}"); - write_file(path.join("main.rs"), get_test_file_contents(lint_name, Some(&header)))?; + write_file(path.join("main.rs"), get_test_file_contents(lint_name))?; Ok(()) } @@ -113,7 +112,7 @@ fn create_test(lint: &LintData<'_>) -> io::Result<()> { println!("Generated test directories: `{relative_test_dir}/pass`, `{relative_test_dir}/fail`"); } else { let test_path = format!("tests/ui/{}.rs", lint.name); - let test_contents = get_test_file_contents(lint.name, None); + let test_contents = get_test_file_contents(lint.name); write_file(lint.project_root.join(&test_path), test_contents)?; println!("Generated test file: `{test_path}`"); @@ -195,23 +194,16 @@ pub(crate) fn get_stabilization_version() -> String { parse_manifest(&contents).expect("Unable to find package version in `Cargo.toml`") } -fn get_test_file_contents(lint_name: &str, header_commands: Option<&str>) -> String { - let mut contents = formatdoc!( +fn get_test_file_contents(lint_name: &str) -> String { + formatdoc!( r#" - #![allow(unused)] #![warn(clippy::{lint_name})] fn main() {{ // test code goes here }} "# - ); - - if let Some(header) = header_commands { - contents = format!("{header}\n{contents}"); - } - - contents + ) } fn get_manifest_contents(lint_name: &str, hint: &str) -> String { diff --git a/clippy_lints/src/absolute_paths.rs b/clippy_lints/src/absolute_paths.rs new file mode 100644 index 0000000000000..04417c4c46007 --- /dev/null +++ b/clippy_lints/src/absolute_paths.rs @@ -0,0 +1,100 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::source::snippet_opt; +use rustc_data_structures::fx::FxHashSet; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX}; +use rustc_hir::{HirId, ItemKind, Node, Path}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::symbol::kw; + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of items through absolute paths, like `std::env::current_dir`. + /// + /// ### Why is this bad? + /// Many codebases have their own style when it comes to importing, but one that is seldom used + /// is using absolute paths *everywhere*. This is generally considered unidiomatic, and you + /// should add a `use` statement. + /// + /// The default maximum segments (2) is pretty strict, you may want to increase this in + /// `clippy.toml`. + /// + /// Note: One exception to this is code from macro expansion - this does not lint such cases, as + /// using absolute paths is the proper way of referencing items in one. + /// + /// ### Example + /// ```rust + /// let x = std::f64::consts::PI; + /// ``` + /// Use any of the below instead, or anything else: + /// ```rust + /// use std::f64; + /// use std::f64::consts; + /// use std::f64::consts::PI; + /// let x = f64::consts::PI; + /// let x = consts::PI; + /// let x = PI; + /// use std::f64::consts as f64_consts; + /// let x = f64_consts::PI; + /// ``` + #[clippy::version = "1.73.0"] + pub ABSOLUTE_PATHS, + restriction, + "checks for usage of an item without a `use` statement" +} +impl_lint_pass!(AbsolutePaths => [ABSOLUTE_PATHS]); + +pub struct AbsolutePaths { + pub absolute_paths_max_segments: u64, + pub absolute_paths_allowed_crates: FxHashSet, +} + +impl LateLintPass<'_> for AbsolutePaths { + // We should only lint `QPath::Resolved`s, but since `Path` is only used in `Resolved` and `UsePath` + // we don't need to use a visitor or anything as we can just check if the `Node` for `hir_id` isn't + // a `Use` + #[expect(clippy::cast_possible_truncation)] + fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, hir_id: HirId) { + let Self { + absolute_paths_max_segments, + absolute_paths_allowed_crates, + } = self; + + if !path.span.from_expansion() + && let Some(node) = cx.tcx.hir().find(hir_id) + && !matches!(node, Node::Item(item) if matches!(item.kind, ItemKind::Use(_, _))) + && let [first, rest @ ..] = path.segments + // Handle `::std` + && let (segment, len) = if first.ident.name == kw::PathRoot { + // Indexing is fine as `PathRoot` must be followed by another segment. `len() - 1` + // is fine here for the same reason + (&rest[0], path.segments.len() - 1) + } else { + (first, path.segments.len()) + } + && len > *absolute_paths_max_segments as usize + && let Some(segment_snippet) = snippet_opt(cx, segment.ident.span) + && segment_snippet == segment.ident.as_str() + { + let is_abs_external = + matches!(segment.res, Res::Def(DefKind::Mod, DefId { index, .. }) if index == CRATE_DEF_INDEX); + let is_abs_crate = segment.ident.name == kw::Crate; + + if is_abs_external && absolute_paths_allowed_crates.contains(segment.ident.name.as_str()) + || is_abs_crate && absolute_paths_allowed_crates.contains("crate") + { + return; + } + + if is_abs_external || is_abs_crate { + span_lint( + cx, + ABSOLUTE_PATHS, + path.span, + "consider bringing this path into scope with the `use` keyword", + ); + } + } + } +} diff --git a/clippy_lints/src/arc_with_non_send_sync.rs b/clippy_lints/src/arc_with_non_send_sync.rs index 7adcd9ad05559..35a04b5e44a30 100644 --- a/clippy_lints/src/arc_with_non_send_sync.rs +++ b/clippy_lints/src/arc_with_non_send_sync.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::last_path_segment; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; +use clippy_utils::{is_from_proc_macro, last_path_segment}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; @@ -38,10 +38,11 @@ declare_clippy_lint! { } declare_lint_pass!(ArcWithNonSendSync => [ARC_WITH_NON_SEND_SYNC]); -impl LateLintPass<'_> for ArcWithNonSendSync { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - let ty = cx.typeck_results().expr_ty(expr); - if is_type_diagnostic_item(cx, ty, sym::Arc) +impl<'tcx> LateLintPass<'tcx> for ArcWithNonSendSync { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if !expr.span.from_expansion() + && let ty = cx.typeck_results().expr_ty(expr) + && is_type_diagnostic_item(cx, ty, sym::Arc) && let ExprKind::Call(func, [arg]) = expr.kind && let ExprKind::Path(func_path) = func.kind && last_path_segment(&func_path).ident.name == sym::new @@ -54,6 +55,7 @@ impl LateLintPass<'_> for ArcWithNonSendSync { && let Some(sync) = cx.tcx.lang_items().sync_trait() && let [is_send, is_sync] = [send, sync].map(|id| implements_trait(cx, arg_ty, id, &[])) && !(is_send && is_sync) + && !is_from_proc_macro(cx, expr) { span_lint_and_then( cx, diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 0ac6ef6496a8a..d34de305f5dce 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -181,6 +181,14 @@ declare_clippy_lint! { /// ### Why is this bad? /// It's just unnecessary. /// + /// ### Known problems + /// When the expression on the left is a function call, the lint considers the return type to be + /// a type alias if it's aliased through a `use` statement + /// (like `use std::io::Result as IoResult`). It will not lint such cases. + /// + /// This check is also rather primitive. It will only work on primitive types without any + /// intermediate references, raw pointers and trait objects may or may not work. + /// /// ### Example /// ```rust /// let _ = 2i32 as i32; diff --git a/clippy_lints/src/casts/unnecessary_cast.rs b/clippy_lints/src/casts/unnecessary_cast.rs index ae56f38d9ad57..86057bb74ee9d 100644 --- a/clippy_lints/src/casts/unnecessary_cast.rs +++ b/clippy_lints/src/casts/unnecessary_cast.rs @@ -85,11 +85,6 @@ pub(super) fn check<'tcx>( } } - // skip cast of fn call that returns type alias - if let ExprKind::Cast(inner, ..) = expr.kind && is_cast_from_ty_alias(cx, inner, cast_from) { - return false; - } - // skip cast to non-primitive type if_chain! { if let ExprKind::Cast(_, cast_to) = expr.kind; @@ -101,6 +96,11 @@ pub(super) fn check<'tcx>( } } + // skip cast of fn call that returns type alias + if let ExprKind::Cast(inner, ..) = expr.kind && is_cast_from_ty_alias(cx, inner, cast_from) { + return false; + } + if let Some(lit) = get_numeric_literal(cast_expr) { let literal_str = &cast_str; @@ -254,14 +254,12 @@ fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx // function's declaration snippet is exactly equal to the `Ty`. That way, we can // see whether it's a type alias. // - // Will this work for more complex types? Probably not! + // FIXME: This won't work if the type is given an alias through `use`, should we + // consider this a type alias as well? if !snippet .split("->") - .skip(0) - .map(|s| { - s.trim() == cast_from.to_string() - || s.split("where").any(|ty| ty.trim() == cast_from.to_string()) - }) + .skip(1) + .map(|s| snippet_eq_ty(s, cast_from) || s.split("where").any(|ty| snippet_eq_ty(ty, cast_from))) .any(|a| a) { return ControlFlow::Break(()); @@ -288,3 +286,7 @@ fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx }) .is_some() } + +fn snippet_eq_ty(snippet: &str, ty: Ty<'_>) -> bool { + snippet.trim() == ty.to_string() || snippet.trim().contains(&format!("::{ty}")) +} diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 498d657b31f9b..d4e0d2863348b 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -37,6 +37,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::utils::internal_lints::produce_ice::PRODUCE_ICE_INFO, #[cfg(feature = "internal")] crate::utils::internal_lints::unnecessary_def_path::UNNECESSARY_DEF_PATH_INFO, + crate::absolute_paths::ABSOLUTE_PATHS_INFO, crate::allow_attributes::ALLOW_ATTRIBUTES_INFO, crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO, crate::approx_const::APPROX_CONSTANT_INFO, @@ -155,6 +156,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::enum_variants::MODULE_INCEPTION_INFO, crate::enum_variants::MODULE_NAME_REPETITIONS_INFO, crate::equatable_if_let::EQUATABLE_IF_LET_INFO, + crate::error_impl_error::ERROR_IMPL_ERROR_INFO, crate::escape::BOXED_LOCAL_INFO, crate::eta_reduction::REDUNDANT_CLOSURE_INFO, crate::eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS_INFO, @@ -183,6 +185,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING_INFO, crate::formatting::SUSPICIOUS_ELSE_FORMATTING_INFO, crate::formatting::SUSPICIOUS_UNARY_OP_FORMATTING_INFO, + crate::four_forward_slashes::FOUR_FORWARD_SLASHES_INFO, crate::from_over_into::FROM_OVER_INTO_INFO, crate::from_raw_with_void_ptr::FROM_RAW_WITH_VOID_PTR_INFO, crate::from_str_radix_10::FROM_STR_RADIX_10_INFO, @@ -305,6 +308,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS_INFO, crate::matches::MATCH_WILD_ERR_ARM_INFO, crate::matches::NEEDLESS_MATCH_INFO, + crate::matches::REDUNDANT_GUARDS_INFO, crate::matches::REDUNDANT_PATTERN_MATCHING_INFO, crate::matches::REST_PAT_IN_FULLY_BOUND_STRUCTS_INFO, crate::matches::SIGNIFICANT_DROP_IN_SCRUTINEE_INFO, @@ -333,11 +337,13 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::EXPECT_USED_INFO, crate::methods::EXTEND_WITH_DRAIN_INFO, crate::methods::FILETYPE_IS_FILE_INFO, + crate::methods::FILTER_MAP_BOOL_THEN_INFO, crate::methods::FILTER_MAP_IDENTITY_INFO, crate::methods::FILTER_MAP_NEXT_INFO, crate::methods::FILTER_NEXT_INFO, crate::methods::FLAT_MAP_IDENTITY_INFO, crate::methods::FLAT_MAP_OPTION_INFO, + crate::methods::FORMAT_COLLECT_INFO, crate::methods::FROM_ITER_INSTEAD_OF_COLLECT_INFO, crate::methods::GET_FIRST_INFO, crate::methods::GET_LAST_WITH_LEN_INFO, @@ -358,6 +364,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::ITER_ON_SINGLE_ITEMS_INFO, crate::methods::ITER_OVEREAGER_CLONED_INFO, crate::methods::ITER_SKIP_NEXT_INFO, + crate::methods::ITER_SKIP_ZERO_INFO, crate::methods::ITER_WITH_DRAIN_INFO, crate::methods::MANUAL_FILTER_MAP_INFO, crate::methods::MANUAL_FIND_MAP_INFO, @@ -391,6 +398,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::OR_THEN_UNWRAP_INFO, crate::methods::PATH_BUF_PUSH_OVERWRITE_INFO, crate::methods::RANGE_ZIP_WITH_LEN_INFO, + crate::methods::READONLY_WRITE_LOCK_INFO, crate::methods::READ_LINE_WITHOUT_TRIM_INFO, crate::methods::REPEAT_ONCE_INFO, crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO, @@ -403,6 +411,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::SKIP_WHILE_NEXT_INFO, crate::methods::STABLE_SORT_PRIMITIVE_INFO, crate::methods::STRING_EXTEND_CHARS_INFO, + crate::methods::STRING_LIT_CHARS_ANY_INFO, crate::methods::SUSPICIOUS_COMMAND_ARG_SPACE_INFO, crate::methods::SUSPICIOUS_MAP_INFO, crate::methods::SUSPICIOUS_SPLITN_INFO, @@ -418,7 +427,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO, crate::methods::UNNECESSARY_SORT_BY_INFO, crate::methods::UNNECESSARY_TO_OWNED_INFO, - crate::methods::UNWRAP_OR_ELSE_DEFAULT_INFO, + crate::methods::UNWRAP_OR_DEFAULT_INFO, crate::methods::UNWRAP_USED_INFO, crate::methods::USELESS_ASREF_INFO, crate::methods::VEC_RESIZE_TO_ZERO_INFO, @@ -555,6 +564,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::redundant_closure_call::REDUNDANT_CLOSURE_CALL_INFO, crate::redundant_else::REDUNDANT_ELSE_INFO, crate::redundant_field_names::REDUNDANT_FIELD_NAMES_INFO, + crate::redundant_locals::REDUNDANT_LOCALS_INFO, crate::redundant_pub_crate::REDUNDANT_PUB_CRATE_INFO, crate::redundant_slicing::DEREF_BY_SLICING_INFO, crate::redundant_slicing::REDUNDANT_SLICING_INFO, @@ -568,6 +578,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::return_self_not_must_use::RETURN_SELF_NOT_MUST_USE_INFO, crate::returns::LET_AND_RETURN_INFO, crate::returns::NEEDLESS_RETURN_INFO, + crate::returns::NEEDLESS_RETURN_WITH_QUESTION_MARK_INFO, crate::same_name_method::SAME_NAME_METHOD_INFO, crate::self_named_constructors::SELF_NAMED_CONSTRUCTORS_INFO, crate::semicolon_block::SEMICOLON_INSIDE_BLOCK_INFO, diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 8a9d978a1063f..a5ec979ccd978 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -3,21 +3,23 @@ use clippy_utils::mir::{enclosing_mir, expr_local, local_assignments, used_exact use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; -use clippy_utils::ty::{adt_and_variant_of_res, expr_sig, is_copy, peel_mid_ty_refs, ty_sig}; +use clippy_utils::ty::{is_copy, peel_mid_ty_refs}; use clippy_utils::{ - fn_def_id, get_parent_expr, get_parent_expr_for_hir, is_lint_allowed, path_to_local, walk_to_expr_usage, + expr_use_ctxt, get_parent_expr, get_parent_node, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode, }; +use hir::def::DefKind; +use hir::MatchSource; use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX}; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::graph::iterate::{CycleDetector, TriColorDepthFirstSearch}; use rustc_errors::Applicability; +use rustc_hir::def::Res; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::intravisit::{walk_ty, Visitor}; use rustc_hir::{ - self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Closure, Expr, ExprKind, FnRetTy, GenericArg, HirId, - ImplItem, ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem, - TraitItemKind, TyKind, UnOp, + self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Expr, ExprKind, HirId, Mutability, Node, Pat, PatKind, + Path, QPath, TyKind, UnOp, }; use rustc_index::bit_set::BitSet; use rustc_infer::infer::TyCtxtInferExt; @@ -25,8 +27,8 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::{Rvalue, StatementKind}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::{ - self, Binder, BoundVariableKind, ClauseKind, EarlyBinder, FnSig, GenericArgKind, List, ParamEnv, ParamTy, - ProjectionPredicate, Ty, TyCtxt, TypeVisitableExt, TypeckResults, + self, ClauseKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, List, ParamEnv, ParamTy, ProjectionPredicate, Ty, + TyCtxt, TypeVisitableExt, TypeckResults, }; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::symbol::sym; @@ -163,7 +165,7 @@ impl_lint_pass!(Dereferencing<'_> => [ #[derive(Default)] pub struct Dereferencing<'tcx> { - state: Option<(State, StateData)>, + state: Option<(State, StateData<'tcx>)>, // While parsing a `deref` method call in ufcs form, the path to the function is itself an // expression. This is to store the id of that expression so it can be skipped when @@ -203,29 +205,28 @@ impl<'tcx> Dereferencing<'tcx> { } #[derive(Debug)] -struct StateData { +struct StateData<'tcx> { /// Span of the top level expression span: Span, hir_id: HirId, - position: Position, + adjusted_ty: Ty<'tcx>, } -#[derive(Debug)] struct DerefedBorrow { count: usize, msg: &'static str, - snip_expr: Option, + stability: TyCoercionStability, + for_field_access: Option, } -#[derive(Debug)] enum State { // Any number of deref method calls. DerefMethod { // The number of calls in a sequence which changed the referenced type ty_changed_count: usize, - is_final_ufcs: bool, + is_ufcs: bool, /// The required mutability - target_mut: Mutability, + mutbl: Mutability, }, DerefedBorrow(DerefedBorrow), ExplicitDeref { @@ -244,7 +245,7 @@ enum State { // A reference operation considered by this lint pass enum RefOp { - Method(Mutability), + Method { mutbl: Mutability, is_ufcs: bool }, Deref, AddrOf(Mutability), } @@ -294,48 +295,115 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { match (self.state.take(), kind) { (None, kind) => { let expr_ty = typeck.expr_ty(expr); - let (position, adjustments) = walk_parents(cx, &mut self.possible_borrowers, expr, &self.msrv); - match kind { - RefOp::Deref => { + let use_cx = expr_use_ctxt(cx, expr); + let adjusted_ty = match &use_cx { + Some(use_cx) => match use_cx.adjustments { + [.., a] => a.target, + _ => expr_ty, + }, + _ => typeck.expr_ty_adjusted(expr), + }; + + match (use_cx, kind) { + (Some(use_cx), RefOp::Deref) => { let sub_ty = typeck.expr_ty(sub_expr); - if let Position::FieldAccess { - name, - of_union: false, - } = position - && !ty_contains_field(sub_ty, name) + if let ExprUseNode::FieldAccess(name) = use_cx.node + && adjusted_ty.ty_adt_def().map_or(true, |adt| !adt.is_union()) + && !ty_contains_field(sub_ty, name.name) { self.state = Some(( - State::ExplicitDerefField { name }, - StateData { span: expr.span, hir_id: expr.hir_id, position }, + State::ExplicitDerefField { name: name.name }, + StateData { + span: expr.span, + hir_id: expr.hir_id, + adjusted_ty, + }, )); - } else if position.is_deref_stable() && sub_ty.is_ref() { + } else if sub_ty.is_ref() + // Linting method receivers would require verifying that name lookup + // would resolve the same way. This is complicated by trait methods. + && !use_cx.node.is_recv() + && let Some(ty) = use_cx.node.defined_ty(cx) + && TyCoercionStability::for_defined_ty(cx, ty, use_cx.node.is_return()).is_deref_stable() + { self.state = Some(( State::ExplicitDeref { mutability: None }, - StateData { span: expr.span, hir_id: expr.hir_id, position }, + StateData { + span: expr.span, + hir_id: expr.hir_id, + adjusted_ty, + }, )); } }, - RefOp::Method(target_mut) + (_, RefOp::Method { mutbl, is_ufcs }) if !is_lint_allowed(cx, EXPLICIT_DEREF_METHODS, expr.hir_id) - && position.lint_explicit_deref() => + // Allow explicit deref in method chains. e.g. `foo.deref().bar()` + && (is_ufcs || !in_postfix_position(cx, expr)) => { let ty_changed_count = usize::from(!deref_method_same_type(expr_ty, typeck.expr_ty(sub_expr))); self.state = Some(( State::DerefMethod { ty_changed_count, - is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)), - target_mut, + is_ufcs, + mutbl, }, StateData { span: expr.span, hir_id: expr.hir_id, - position, + adjusted_ty, }, )); }, - RefOp::AddrOf(mutability) => { + (Some(use_cx), RefOp::AddrOf(mutability)) => { + let defined_ty = use_cx.node.defined_ty(cx); + + // Check needless_borrow for generic arguments. + if !use_cx.is_ty_unified + && let Some(DefinedTy::Mir(ty)) = defined_ty + && let ty::Param(ty) = *ty.value.skip_binder().kind() + && let Some((hir_id, fn_id, i)) = match use_cx.node { + ExprUseNode::MethodArg(_, _, 0) => None, + ExprUseNode::MethodArg(hir_id, None, i) => { + typeck.type_dependent_def_id(hir_id).map(|id| (hir_id, id, i)) + }, + ExprUseNode::FnArg(&Expr { kind: ExprKind::Path(ref p), hir_id, .. }, i) + if !path_has_args(p) => match typeck.qpath_res(p, hir_id) { + Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, id) => { + Some((hir_id, id, i)) + }, + _ => None, + }, + _ => None, + } && let count = needless_borrow_generic_arg_count( + cx, + &mut self.possible_borrowers, + fn_id, + typeck.node_args(hir_id), + i, + ty, + expr, + &self.msrv, + ) && count != 0 + { + self.state = Some(( + State::DerefedBorrow(DerefedBorrow { + count: count - 1, + msg: "the borrowed expression implements the required traits", + stability: TyCoercionStability::None, + for_field_access: None, + }), + StateData { + span: expr.span, + hir_id: expr.hir_id, + adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target), + }, + )); + return; + } + // Find the number of times the borrow is auto-derefed. - let mut iter = adjustments.iter(); + let mut iter = use_cx.adjustments.iter(); let mut deref_count = 0usize; let next_adjust = loop { match iter.next() { @@ -352,6 +420,58 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { }; }; + let stability = defined_ty.map_or(TyCoercionStability::None, |ty| { + TyCoercionStability::for_defined_ty(cx, ty, use_cx.node.is_return()) + }); + let can_auto_borrow = match use_cx.node { + ExprUseNode::Callee => true, + ExprUseNode::FieldAccess(_) => adjusted_ty.ty_adt_def().map_or(true, |adt| !adt.is_union()), + ExprUseNode::MethodArg(hir_id, _, 0) if !use_cx.moved_before_use => { + // Check for calls to trait methods where the trait is implemented + // on a reference. + // Two cases need to be handled: + // * `self` methods on `&T` will never have auto-borrow + // * `&self` methods on `&T` can have auto-borrow, but `&self` methods on `T` will take + // priority. + if let Some(fn_id) = typeck.type_dependent_def_id(hir_id) + && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) + && let arg_ty + = cx.tcx.erase_regions(use_cx.adjustments.last().map_or(expr_ty, |a| a.target)) + && let ty::Ref(_, sub_ty, _) = *arg_ty.kind() + && let args = cx + .typeck_results() + .node_args_opt(hir_id).map(|args| &args[1..]).unwrap_or_default() + && let impl_ty = if cx.tcx.fn_sig(fn_id) + .instantiate_identity() + .skip_binder() + .inputs()[0].is_ref() + { + // Trait methods taking `&self` + sub_ty + } else { + // Trait methods taking `self` + arg_ty + } && impl_ty.is_ref() + && cx.tcx.infer_ctxt().build() + .type_implements_trait( + trait_id, + [impl_ty.into()].into_iter().chain(args.iter().copied()), + cx.param_env, + ) + .must_apply_modulo_regions() + { + false + } else { + true + } + }, + _ => false, + }; + + let deref_msg = + "this expression creates a reference which is immediately dereferenced by the compiler"; + let borrow_msg = "this expression borrows a value the compiler would automatically borrow"; + // Determine the required number of references before any can be removed. In all cases the // reference made by the current expression will be removed. After that there are four cases to // handle. @@ -374,26 +494,18 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { // }; // } // ``` - let deref_msg = - "this expression creates a reference which is immediately dereferenced by the compiler"; - let borrow_msg = "this expression borrows a value the compiler would automatically borrow"; - let impl_msg = "the borrowed expression implements the required traits"; - - let (required_refs, msg, snip_expr) = if position.can_auto_borrow() { - (1, if deref_count == 1 { borrow_msg } else { deref_msg }, None) - } else if let Position::ImplArg(hir_id) = position { - (0, impl_msg, Some(hir_id)) - } else if let Some(&Adjust::Borrow(AutoBorrow::Ref(_, mutability))) = - next_adjust.map(|a| &a.kind) + let (required_refs, msg) = if can_auto_borrow { + (1, if deref_count == 1 { borrow_msg } else { deref_msg }) + } else if let Some(&Adjustment { + kind: Adjust::Borrow(AutoBorrow::Ref(_, mutability)), + .. + }) = next_adjust + && matches!(mutability, AutoBorrowMutability::Mut { .. }) + && !stability.is_reborrow_stable() { - if matches!(mutability, AutoBorrowMutability::Mut { .. }) && !position.is_reborrow_stable() - { - (3, deref_msg, None) - } else { - (2, deref_msg, None) - } + (3, deref_msg) } else { - (2, deref_msg, None) + (2, deref_msg) }; if deref_count >= required_refs { @@ -403,15 +515,19 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { // can't be removed without breaking the code. See earlier comment. count: deref_count - required_refs, msg, - snip_expr, + stability, + for_field_access: match use_cx.node { + ExprUseNode::FieldAccess(name) => Some(name.name), + _ => None, + }, }), StateData { span: expr.span, hir_id: expr.hir_id, - position, + adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target), }, )); - } else if position.is_deref_stable() + } else if stability.is_deref_stable() // Auto-deref doesn't combine with other adjustments && next_adjust.map_or(true, |a| matches!(a.kind, Adjust::Deref(_) | Adjust::Borrow(_))) && iter.all(|a| matches!(a.kind, Adjust::Deref(_) | Adjust::Borrow(_))) @@ -421,24 +537,24 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { StateData { span: expr.span, hir_id: expr.hir_id, - position, + adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target), }, )); } }, - RefOp::Method(..) => (), + (None, _) | (_, RefOp::Method { .. }) => (), } }, ( Some(( State::DerefMethod { - target_mut, + mutbl, ty_changed_count, .. }, data, )), - RefOp::Method(_), + RefOp::Method { is_ufcs, .. }, ) => { self.state = Some(( State::DerefMethod { @@ -447,8 +563,8 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { } else { ty_changed_count + 1 }, - is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)), - target_mut, + is_ufcs, + mutbl, }, data, )); @@ -463,33 +579,44 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { )); }, (Some((State::DerefedBorrow(state), data)), RefOp::AddrOf(mutability)) => { - let position = data.position; + let adjusted_ty = data.adjusted_ty; + let stability = state.stability; report(cx, expr, State::DerefedBorrow(state), data); - if position.is_deref_stable() { + if stability.is_deref_stable() { self.state = Some(( State::Borrow { mutability }, StateData { span: expr.span, hir_id: expr.hir_id, - position, + adjusted_ty, }, )); } }, (Some((State::DerefedBorrow(state), data)), RefOp::Deref) => { - let position = data.position; + let adjusted_ty = data.adjusted_ty; + let stability = state.stability; + let for_field_access = state.for_field_access; report(cx, expr, State::DerefedBorrow(state), data); - if let Position::FieldAccess{name, ..} = position + if let Some(name) = for_field_access && !ty_contains_field(typeck.expr_ty(sub_expr), name) { self.state = Some(( State::ExplicitDerefField { name }, - StateData { span: expr.span, hir_id: expr.hir_id, position }, + StateData { + span: expr.span, + hir_id: expr.hir_id, + adjusted_ty, + }, )); - } else if position.is_deref_stable() { + } else if stability.is_deref_stable() { self.state = Some(( State::ExplicitDeref { mutability: None }, - StateData { span: expr.span, hir_id: expr.hir_id, position }, + StateData { + span: expr.span, + hir_id: expr.hir_id, + adjusted_ty, + }, )); } }, @@ -611,8 +738,8 @@ fn try_parse_ref_op<'tcx>( typeck: &'tcx TypeckResults<'_>, expr: &'tcx Expr<'_>, ) -> Option<(RefOp, &'tcx Expr<'tcx>)> { - let (def_id, arg) = match expr.kind { - ExprKind::MethodCall(_, arg, [], _) => (typeck.type_dependent_def_id(expr.hir_id)?, arg), + let (is_ufcs, def_id, arg) = match expr.kind { + ExprKind::MethodCall(_, arg, [], _) => (false, typeck.type_dependent_def_id(expr.hir_id)?, arg), ExprKind::Call( Expr { kind: ExprKind::Path(path), @@ -620,7 +747,7 @@ fn try_parse_ref_op<'tcx>( .. }, [arg], - ) => (typeck.qpath_res(path, *hir_id).opt_def_id()?, arg), + ) => (true, typeck.qpath_res(path, *hir_id).opt_def_id()?, arg), ExprKind::Unary(UnOp::Deref, sub_expr) if !typeck.expr_ty(sub_expr).is_unsafe_ptr() => { return Some((RefOp::Deref, sub_expr)); }, @@ -628,9 +755,21 @@ fn try_parse_ref_op<'tcx>( _ => return None, }; if tcx.is_diagnostic_item(sym::deref_method, def_id) { - Some((RefOp::Method(Mutability::Not), arg)) + Some(( + RefOp::Method { + mutbl: Mutability::Not, + is_ufcs, + }, + arg, + )) } else if tcx.trait_of_item(def_id)? == tcx.lang_items().deref_mut_trait()? { - Some((RefOp::Method(Mutability::Mut), arg)) + Some(( + RefOp::Method { + mutbl: Mutability::Mut, + is_ufcs, + }, + arg, + )) } else { None } @@ -649,420 +788,164 @@ fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool { } } -/// The position of an expression relative to it's parent. -#[derive(Clone, Copy, Debug)] -enum Position { - MethodReceiver, - /// The method is defined on a reference type. e.g. `impl Foo for &T` - MethodReceiverRefImpl, - Callee, - ImplArg(HirId), - FieldAccess { - name: Symbol, - of_union: bool, - }, // union fields cannot be auto borrowed - Postfix, - Deref, - /// Any other location which will trigger auto-deref to a specific time. - /// Contains the precedence of the parent expression and whether the target type is sized. - DerefStable(i8, bool), - /// Any other location which will trigger auto-reborrowing. - /// Contains the precedence of the parent expression. - ReborrowStable(i8), - /// Contains the precedence of the parent expression. - Other(i8), -} -impl Position { - fn is_deref_stable(self) -> bool { - matches!(self, Self::DerefStable(..)) +fn path_has_args(p: &QPath<'_>) -> bool { + match *p { + QPath::Resolved(_, Path { segments: [.., s], .. }) | QPath::TypeRelative(_, s) => s.args.is_some(), + _ => false, } +} - fn is_reborrow_stable(self) -> bool { - matches!(self, Self::DerefStable(..) | Self::ReborrowStable(_)) +fn in_postfix_position<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool { + if let Some(parent) = get_parent_expr(cx, e) + && parent.span.ctxt() == e.span.ctxt() + { + match parent.kind { + ExprKind::Call(child, _) | ExprKind::MethodCall(_, child, _, _) | ExprKind::Index(child, _) + if child.hir_id == e.hir_id => true, + ExprKind::Field(_, _) | ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar) => true, + _ => false, + } + } else { + false } +} - fn can_auto_borrow(self) -> bool { - matches!( - self, - Self::MethodReceiver | Self::FieldAccess { of_union: false, .. } | Self::Callee - ) +#[derive(Clone, Copy)] +enum TyCoercionStability { + Deref, + Reborrow, + None, +} +impl TyCoercionStability { + fn is_deref_stable(self) -> bool { + matches!(self, Self::Deref) } - fn lint_explicit_deref(self) -> bool { - matches!(self, Self::Other(_) | Self::DerefStable(..) | Self::ReborrowStable(_)) + fn is_reborrow_stable(self) -> bool { + matches!(self, Self::Deref | Self::Reborrow) } - fn precedence(self) -> i8 { - match self { - Self::MethodReceiver - | Self::MethodReceiverRefImpl - | Self::Callee - | Self::FieldAccess { .. } - | Self::Postfix => PREC_POSTFIX, - Self::ImplArg(_) | Self::Deref => PREC_PREFIX, - Self::DerefStable(p, _) | Self::ReborrowStable(p) | Self::Other(p) => p, + fn for_defined_ty<'tcx>(cx: &LateContext<'tcx>, ty: DefinedTy<'tcx>, for_return: bool) -> Self { + match ty { + DefinedTy::Hir(ty) => Self::for_hir_ty(ty), + DefinedTy::Mir(ty) => Self::for_mir_ty( + cx.tcx, + ty.param_env, + cx.tcx.erase_late_bound_regions(ty.value), + for_return, + ), } } -} - -/// Walks up the parent expressions attempting to determine both how stable the auto-deref result -/// is, and which adjustments will be applied to it. Note this will not consider auto-borrow -/// locations as those follow different rules. -#[expect(clippy::too_many_lines)] -fn walk_parents<'tcx>( - cx: &LateContext<'tcx>, - possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>, - e: &'tcx Expr<'_>, - msrv: &Msrv, -) -> (Position, &'tcx [Adjustment<'tcx>]) { - let mut adjustments = [].as_slice(); - let mut precedence = 0i8; - let ctxt = e.span.ctxt(); - let position = walk_to_expr_usage(cx, e, &mut |parent, child_id| { - // LocalTableInContext returns the wrong lifetime, so go use `expr_adjustments` instead. - if adjustments.is_empty() && let Node::Expr(e) = cx.tcx.hir().get(child_id) { - adjustments = cx.typeck_results().expr_adjustments(e); - } - match parent { - Node::Local(Local { ty: Some(ty), span, .. }) if span.ctxt() == ctxt => { - Some(binding_ty_auto_deref_stability(cx, ty, precedence, List::empty())) - }, - Node::Item(&Item { - kind: ItemKind::Static(..) | ItemKind::Const(..), - owner_id, - span, - .. - }) - | Node::TraitItem(&TraitItem { - kind: TraitItemKind::Const(..), - owner_id, - span, - .. - }) - | Node::ImplItem(&ImplItem { - kind: ImplItemKind::Const(..), - owner_id, - span, - .. - }) if span.ctxt() == ctxt => { - let ty = cx.tcx.type_of(owner_id.def_id).instantiate_identity(); - Some(ty_auto_deref_stability(cx.tcx, cx.param_env, ty, precedence).position_for_result(cx)) - }, - Node::Item(&Item { - kind: ItemKind::Fn(..), - owner_id, - span, - .. - }) - | Node::TraitItem(&TraitItem { - kind: TraitItemKind::Fn(..), - owner_id, - span, - .. - }) - | Node::ImplItem(&ImplItem { - kind: ImplItemKind::Fn(..), - owner_id, - span, - .. - }) if span.ctxt() == ctxt => { - let output = cx - .tcx - .erase_late_bound_regions(cx.tcx.fn_sig(owner_id).instantiate_identity().output()); - Some(ty_auto_deref_stability(cx.tcx, cx.param_env, output, precedence).position_for_result(cx)) - }, - - Node::ExprField(field) if field.span.ctxt() == ctxt => match get_parent_expr_for_hir(cx, field.hir_id) { - Some(Expr { - hir_id, - kind: ExprKind::Struct(path, ..), - .. - }) => adt_and_variant_of_res(cx, cx.qpath_res(path, *hir_id)) - .and_then(|(adt, variant)| { - variant - .fields - .iter() - .find(|f| f.name == field.ident.name) - .map(|f| (adt, f)) - }) - .map(|(adt, field_def)| { - ty_auto_deref_stability( - cx.tcx, - // Use the param_env of the target type. - cx.tcx.param_env(adt.did()), - cx.tcx.type_of(field_def.did).instantiate_identity(), - precedence, - ) - .position_for_arg() - }), - _ => None, - }, + // Checks the stability of type coercions when assigned to a binding with the given explicit type. + // + // e.g. + // let x = Box::new(Box::new(0u32)); + // let y1: &Box<_> = x.deref(); + // let y2: &Box<_> = &x; + // + // Here `y1` and `y2` would resolve to different types, so the type `&Box<_>` is not stable when + // switching to auto-dereferencing. + fn for_hir_ty<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Self { + let TyKind::Ref(_, ty) = &ty.kind else { + return Self::None; + }; + let mut ty = ty; - Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind { - ExprKind::Ret(_) => { - let owner_id = cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()); - Some( - if let Node::Expr( - closure_expr @ Expr { - kind: ExprKind::Closure(closure), - .. - }, - ) = cx.tcx.hir().get_by_def_id(owner_id) - { - closure_result_position(cx, closure, cx.typeck_results().expr_ty(closure_expr), precedence) - } else { - let output = cx - .tcx - .erase_late_bound_regions(cx.tcx.fn_sig(owner_id).instantiate_identity().output()); - ty_auto_deref_stability(cx.tcx, cx.param_env, output, precedence).position_for_result(cx) - }, - ) - }, - ExprKind::Closure(closure) => Some(closure_result_position( - cx, - closure, - cx.typeck_results().expr_ty(parent), - precedence, - )), - ExprKind::Call(func, _) if func.hir_id == child_id => { - (child_id == e.hir_id).then_some(Position::Callee) + loop { + break match ty.ty.kind { + TyKind::Ref(_, ref ref_ty) => { + ty = ref_ty; + continue; }, - ExprKind::Call(func, args) => args - .iter() - .position(|arg| arg.hir_id == child_id) - .zip(expr_sig(cx, func)) - .and_then(|(i, sig)| { - sig.input_with_hir(i).map(|(hir_ty, ty)| { - match hir_ty { - // Type inference for closures can depend on how they're called. Only go by the explicit - // types here. - Some(hir_ty) => { - binding_ty_auto_deref_stability(cx, hir_ty, precedence, ty.bound_vars()) - }, - None => { - // `e.hir_id == child_id` for https://github.com/rust-lang/rust-clippy/issues/9739 - // `!call_is_qualified(func)` for https://github.com/rust-lang/rust-clippy/issues/9782 - if e.hir_id == child_id - && !call_is_qualified(func) - && let ty::Param(param_ty) = ty.skip_binder().kind() - { - needless_borrow_impl_arg_position( - cx, - possible_borrowers, - parent, - i, - *param_ty, - e, - precedence, - msrv, - ) - } else { - ty_auto_deref_stability( - cx.tcx, - // Use the param_env of the target function. - sig.predicates_id().map_or(ParamEnv::empty(), |id| cx.tcx.param_env(id)), - cx.tcx.erase_late_bound_regions(ty), - precedence - ).position_for_arg() - } - }, - } + TyKind::Path( + QPath::TypeRelative(_, path) + | QPath::Resolved( + _, + Path { + segments: [.., path], .. + }, + ), + ) => { + if let Some(args) = path.args + && args.args.iter().any(|arg| match arg { + hir::GenericArg::Infer(_) => true, + hir::GenericArg::Type(ty) => ty_contains_infer(ty), + _ => false, }) - }), - ExprKind::MethodCall(method, receiver, args, _) => { - let fn_id = cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(); - if receiver.hir_id == child_id { - // Check for calls to trait methods where the trait is implemented on a reference. - // Two cases need to be handled: - // * `self` methods on `&T` will never have auto-borrow - // * `&self` methods on `&T` can have auto-borrow, but `&self` methods on `T` will take - // priority. - if e.hir_id != child_id { - return Some(Position::ReborrowStable(precedence)) - } else if let Some(trait_id) = cx.tcx.trait_of_item(fn_id) - && let arg_ty = cx.tcx.erase_regions(cx.typeck_results().expr_ty_adjusted(e)) - && let ty::Ref(_, sub_ty, _) = *arg_ty.kind() - && let subs = cx - .typeck_results() - .node_args_opt(parent.hir_id).map(|subs| &subs[1..]).unwrap_or_default() - && let impl_ty = if cx.tcx.fn_sig(fn_id) - .instantiate_identity() - .skip_binder() - .inputs()[0].is_ref() - { - // Trait methods taking `&self` - sub_ty - } else { - // Trait methods taking `self` - arg_ty - } && impl_ty.is_ref() - && let infcx = cx.tcx.infer_ctxt().build() - && infcx - .type_implements_trait( - trait_id, - [impl_ty.into()].into_iter().chain(subs.iter().copied()), - cx.param_env, - ) - .must_apply_modulo_regions() - { - return Some(Position::MethodReceiverRefImpl) - } - return Some(Position::MethodReceiver); + { + Self::Reborrow + } else { + Self::Deref } - args.iter().position(|arg| arg.hir_id == child_id).map(|i| { - let ty = cx.tcx.fn_sig(fn_id).instantiate_identity().input(i + 1); - // `e.hir_id == child_id` for https://github.com/rust-lang/rust-clippy/issues/9739 - // `method.args.is_none()` for https://github.com/rust-lang/rust-clippy/issues/9782 - if e.hir_id == child_id - && method.args.is_none() - && let ty::Param(param_ty) = ty.skip_binder().kind() - { - needless_borrow_impl_arg_position( - cx, - possible_borrowers, - parent, - i + 1, - *param_ty, - e, - precedence, - msrv, - ) - } else { - ty_auto_deref_stability( - cx.tcx, - // Use the param_env of the target function. - cx.tcx.param_env(fn_id), - cx.tcx.erase_late_bound_regions(ty), - precedence, - ) - .position_for_arg() - } - }) - }, - ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(Position::FieldAccess { - name: name.name, - of_union: is_union(cx.typeck_results(), child), - }), - ExprKind::Unary(UnOp::Deref, child) if child.hir_id == e.hir_id => Some(Position::Deref), - ExprKind::Match(child, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar) - | ExprKind::Index(child, _) - if child.hir_id == e.hir_id => - { - Some(Position::Postfix) - }, - _ if child_id == e.hir_id => { - precedence = parent.precedence().order(); - None }, - _ => None, - }, - _ => None, + TyKind::Slice(_) + | TyKind::Array(..) + | TyKind::Ptr(_) + | TyKind::BareFn(_) + | TyKind::Never + | TyKind::Tup(_) + | TyKind::Path(_) => Self::Deref, + TyKind::OpaqueDef(..) + | TyKind::Infer + | TyKind::Typeof(..) + | TyKind::TraitObject(..) + | TyKind::Err(_) => Self::Reborrow, + }; } - }) - .unwrap_or(Position::Other(precedence)); - (position, adjustments) -} - -fn is_union<'tcx>(typeck: &'tcx TypeckResults<'_>, path_expr: &'tcx Expr<'_>) -> bool { - typeck - .expr_ty_adjusted(path_expr) - .ty_adt_def() - .map_or(false, rustc_middle::ty::AdtDef::is_union) -} - -fn closure_result_position<'tcx>( - cx: &LateContext<'tcx>, - closure: &'tcx Closure<'_>, - ty: Ty<'tcx>, - precedence: i8, -) -> Position { - match closure.fn_decl.output { - FnRetTy::Return(hir_ty) => { - if let Some(sig) = ty_sig(cx, ty) - && let Some(output) = sig.output() - { - binding_ty_auto_deref_stability(cx, hir_ty, precedence, output.bound_vars()) - } else { - Position::Other(precedence) - } - }, - FnRetTy::DefaultReturn(_) => Position::Other(precedence), } -} -// Checks the stability of auto-deref when assigned to a binding with the given explicit type. -// -// e.g. -// let x = Box::new(Box::new(0u32)); -// let y1: &Box<_> = x.deref(); -// let y2: &Box<_> = &x; -// -// Here `y1` and `y2` would resolve to different types, so the type `&Box<_>` is not stable when -// switching to auto-dereferencing. -fn binding_ty_auto_deref_stability<'tcx>( - cx: &LateContext<'tcx>, - ty: &'tcx hir::Ty<'_>, - precedence: i8, - binder_args: &'tcx List, -) -> Position { - let TyKind::Ref(_, ty) = &ty.kind else { - return Position::Other(precedence); - }; - let mut ty = ty; + fn for_mir_ty<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: Ty<'tcx>, for_return: bool) -> Self { + let ty::Ref(_, mut ty, _) = *ty.kind() else { + return Self::None; + }; - loop { - break match ty.ty.kind { - TyKind::Ref(_, ref ref_ty) => { - ty = ref_ty; - continue; - }, - TyKind::Path( - QPath::TypeRelative(_, path) - | QPath::Resolved( - _, - Path { - segments: [.., path], .. - }, - ), - ) => { - if let Some(args) = path.args - && args.args.iter().any(|arg| match arg { - GenericArg::Infer(_) => true, - GenericArg::Type(ty) => ty_contains_infer(ty), - _ => false, - }) + ty = tcx.try_normalize_erasing_regions(param_env, ty).unwrap_or(ty); + loop { + break match *ty.kind() { + ty::Ref(_, ref_ty, _) => { + ty = ref_ty; + continue; + }, + ty::Param(_) if for_return => Self::Deref, + ty::Alias(ty::Weak | ty::Inherent, _) => unreachable!("should have been normalized away above"), + ty::Alias(ty::Projection, _) if !for_return && ty.has_non_region_param() => Self::Reborrow, + ty::Infer(_) + | ty::Error(_) + | ty::Bound(..) + | ty::Alias(ty::Opaque, ..) + | ty::Placeholder(_) + | ty::Dynamic(..) + | ty::Param(_) => Self::Reborrow, + ty::Adt(_, args) + if ty.has_placeholders() + || ty.has_opaque_types() + || (!for_return && args.has_non_region_param()) => { - Position::ReborrowStable(precedence) - } else { - Position::DerefStable( - precedence, - cx.tcx - .erase_late_bound_regions(Binder::bind_with_vars( - cx.typeck_results().node_type(ty.ty.hir_id), - binder_args, - )) - .is_sized(cx.tcx, cx.param_env.without_caller_bounds()), - ) - } - }, - TyKind::Slice(_) => Position::DerefStable(precedence, false), - TyKind::Array(..) | TyKind::Ptr(_) | TyKind::BareFn(_) => Position::DerefStable(precedence, true), - TyKind::Never - | TyKind::Tup(_) - | TyKind::Path(_) => Position::DerefStable( - precedence, - cx.tcx - .erase_late_bound_regions(Binder::bind_with_vars( - cx.typeck_results().node_type(ty.ty.hir_id), - binder_args, - )) - .is_sized(cx.tcx, cx.param_env.without_caller_bounds()), - ), - TyKind::OpaqueDef(..) | TyKind::Infer | TyKind::Typeof(..) | TyKind::TraitObject(..) | TyKind::Err(_) => { - Position::ReborrowStable(precedence) - }, - }; + Self::Reborrow + }, + ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Array(..) + | ty::Float(_) + | ty::RawPtr(..) + | ty::FnPtr(_) + | ty::Str + | ty::Slice(..) + | ty::Adt(..) + | ty::Foreign(_) + | ty::FnDef(..) + | ty::Generator(..) + | ty::GeneratorWitness(..) + | ty::GeneratorWitnessMIR(..) + | ty::Closure(..) + | ty::Never + | ty::Tuple(_) + | ty::Alias(ty::Projection, _) => Self::Deref, + }; + } } } @@ -1084,10 +967,10 @@ fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool { } } - fn visit_generic_arg(&mut self, arg: &GenericArg<'_>) { - if self.0 || matches!(arg, GenericArg::Infer(_)) { + fn visit_generic_arg(&mut self, arg: &hir::GenericArg<'_>) { + if self.0 || matches!(arg, hir::GenericArg::Infer(_)) { self.0 = true; - } else if let GenericArg::Type(ty) = arg { + } else if let hir::GenericArg::Type(ty) = arg { self.visit_ty(ty); } } @@ -1097,51 +980,29 @@ fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool { v.0 } -fn call_is_qualified(expr: &Expr<'_>) -> bool { - if let ExprKind::Path(path) = &expr.kind { - match path { - QPath::Resolved(_, path) => path.segments.last().map_or(false, |segment| segment.args.is_some()), - QPath::TypeRelative(_, segment) => segment.args.is_some(), - QPath::LangItem(..) => false, - } - } else { - false - } -} - -// Checks whether: -// * child is an expression of the form `&e` in an argument position requiring an `impl Trait` -// * `e`'s type implements `Trait` and is copyable -// If the conditions are met, returns `Some(Position::ImplArg(..))`; otherwise, returns `None`. -// The "is copyable" condition is to avoid the case where removing the `&` means `e` would have to -// be moved, but it cannot be. -#[expect(clippy::too_many_arguments, clippy::too_many_lines)] -fn needless_borrow_impl_arg_position<'tcx>( +/// Checks for the number of borrow expressions which can be removed from the given expression +/// where the expression is used as an argument to a function expecting a generic type. +/// +/// The following constraints will be checked: +/// * The borrowed expression meets all the generic type's constraints. +/// * The generic type appears only once in the functions signature. +/// * The borrowed value will not be moved if it is used later in the function. +#[expect(clippy::too_many_arguments)] +fn needless_borrow_generic_arg_count<'tcx>( cx: &LateContext<'tcx>, possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>, - parent: &Expr<'tcx>, + fn_id: DefId, + callee_args: &'tcx List>, arg_index: usize, param_ty: ParamTy, mut expr: &Expr<'tcx>, - precedence: i8, msrv: &Msrv, -) -> Position { +) -> usize { let destruct_trait_def_id = cx.tcx.lang_items().destruct_trait(); let sized_trait_def_id = cx.tcx.lang_items().sized_trait(); - let Some(callee_def_id) = fn_def_id(cx, parent) else { - return Position::Other(precedence); - }; - let fn_sig = cx.tcx.fn_sig(callee_def_id).instantiate_identity().skip_binder(); - let args_with_expr_ty = cx - .typeck_results() - .node_args(if let ExprKind::Call(callee, _) = parent.kind { - callee.hir_id - } else { - parent.hir_id - }); - - let predicates = cx.tcx.param_env(callee_def_id).caller_bounds(); + let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity().skip_binder(); + let predicates = cx.tcx.param_env(fn_id).caller_bounds(); let projection_predicates = predicates .iter() .filter_map(|predicate| { @@ -1176,7 +1037,7 @@ fn needless_borrow_impl_arg_position<'tcx>( || cx.tcx.is_diagnostic_item(sym::Any, trait_def_id) }) { - return Position::Other(precedence); + return 0; } // See: @@ -1184,14 +1045,14 @@ fn needless_borrow_impl_arg_position<'tcx>( // - https://github.com/rust-lang/rust-clippy/pull/9674#issuecomment-1292225232 if projection_predicates .iter() - .any(|projection_predicate| is_mixed_projection_predicate(cx, callee_def_id, projection_predicate)) + .any(|projection_predicate| is_mixed_projection_predicate(cx, fn_id, projection_predicate)) { - return Position::Other(precedence); + return 0; } // `args_with_referent_ty` can be constructed outside of `check_referent` because the same // elements are modified each time `check_referent` is called. - let mut args_with_referent_ty = args_with_expr_ty.to_vec(); + let mut args_with_referent_ty = callee_args.to_vec(); let mut check_reference_and_referent = |reference, referent| { let referent_ty = cx.typeck_results().expr_ty(referent); @@ -1238,20 +1099,15 @@ fn needless_borrow_impl_arg_position<'tcx>( }) }; - let mut needless_borrow = false; + let mut count = 0; while let ExprKind::AddrOf(_, _, referent) = expr.kind { if !check_reference_and_referent(expr, referent) { break; } expr = referent; - needless_borrow = true; - } - - if needless_borrow { - Position::ImplArg(expr.hir_id) - } else { - Position::Other(precedence) + count += 1; } + count } fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool { @@ -1392,93 +1248,6 @@ fn replace_types<'tcx>( true } -struct TyPosition<'tcx> { - position: Position, - ty: Option>, -} -impl From for TyPosition<'_> { - fn from(position: Position) -> Self { - Self { position, ty: None } - } -} -impl<'tcx> TyPosition<'tcx> { - fn new_deref_stable_for_result(precedence: i8, ty: Ty<'tcx>) -> Self { - Self { - position: Position::ReborrowStable(precedence), - ty: Some(ty), - } - } - fn position_for_result(self, cx: &LateContext<'tcx>) -> Position { - match (self.position, self.ty) { - (Position::ReborrowStable(precedence), Some(ty)) => { - Position::DerefStable(precedence, ty.is_sized(cx.tcx, cx.param_env)) - }, - (position, _) => position, - } - } - fn position_for_arg(self) -> Position { - self.position - } -} - -// Checks whether a type is stable when switching to auto dereferencing, -fn ty_auto_deref_stability<'tcx>( - tcx: TyCtxt<'tcx>, - param_env: ParamEnv<'tcx>, - ty: Ty<'tcx>, - precedence: i8, -) -> TyPosition<'tcx> { - let ty::Ref(_, mut ty, _) = *ty.kind() else { - return Position::Other(precedence).into(); - }; - - ty = tcx.try_normalize_erasing_regions(param_env, ty).unwrap_or(ty); - - loop { - break match *ty.kind() { - ty::Ref(_, ref_ty, _) => { - ty = ref_ty; - continue; - }, - ty::Param(_) => TyPosition::new_deref_stable_for_result(precedence, ty), - ty::Alias(ty::Weak, _) => unreachable!("should have been normalized away above"), - ty::Alias(ty::Inherent, _) => unreachable!("inherent projection should have been normalized away above"), - ty::Alias(ty::Projection, _) if ty.has_non_region_param() => { - TyPosition::new_deref_stable_for_result(precedence, ty) - }, - ty::Infer(_) - | ty::Error(_) - | ty::Bound(..) - | ty::Alias(ty::Opaque, ..) - | ty::Placeholder(_) - | ty::Dynamic(..) => Position::ReborrowStable(precedence).into(), - ty::Adt(..) if ty.has_placeholders() || ty.has_opaque_types() => { - Position::ReborrowStable(precedence).into() - }, - ty::Adt(_, args) if args.has_non_region_param() => TyPosition::new_deref_stable_for_result(precedence, ty), - ty::Bool - | ty::Char - | ty::Int(_) - | ty::Uint(_) - | ty::Array(..) - | ty::Float(_) - | ty::RawPtr(..) - | ty::FnPtr(_) => Position::DerefStable(precedence, true).into(), - ty::Str | ty::Slice(..) => Position::DerefStable(precedence, false).into(), - ty::Adt(..) - | ty::Foreign(_) - | ty::FnDef(..) - | ty::Generator(..) - | ty::GeneratorWitness(..) - | ty::GeneratorWitnessMIR(..) - | ty::Closure(..) - | ty::Never - | ty::Tuple(_) - | ty::Alias(ty::Projection, _) => Position::DerefStable(precedence, ty.is_sized(tcx, param_env)).into(), - }; - } -} - fn ty_contains_field(ty: Ty<'_>, name: Symbol) -> bool { if let ty::Adt(adt, _) = *ty.kind() { adt.is_struct() && adt.all_fields().any(|f| f.name == name) @@ -1488,12 +1257,12 @@ fn ty_contains_field(ty: Ty<'_>, name: Symbol) -> bool { } #[expect(clippy::needless_pass_by_value, clippy::too_many_lines)] -fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data: StateData) { +fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data: StateData<'tcx>) { match state { State::DerefMethod { ty_changed_count, - is_final_ufcs, - target_mut, + is_ufcs, + mutbl, } => { let mut app = Applicability::MachineApplicable; let (expr_str, _expr_is_macro_call) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app); @@ -1508,12 +1277,12 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data }; let addr_of_str = if ty_changed_count < ref_count { // Check if a reborrow from &mut T -> &T is required. - if target_mut == Mutability::Not && matches!(ty.kind(), ty::Ref(_, _, Mutability::Mut)) { + if mutbl == Mutability::Not && matches!(ty.kind(), ty::Ref(_, _, Mutability::Mut)) { "&*" } else { "" } - } else if target_mut == Mutability::Mut { + } else if mutbl == Mutability::Mut { "&mut " } else { "&" @@ -1530,7 +1299,7 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data */ // Fix #10850, do not lint if it's `Foo::deref` instead of `foo.deref()`. - if is_final_ufcs { + if is_ufcs { return; } @@ -1538,7 +1307,7 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data cx, EXPLICIT_DEREF_METHODS, data.span, - match target_mut { + match mutbl { Mutability::Not => "explicit `deref` method call", Mutability::Mut => "explicit `deref_mut` method call", }, @@ -1549,13 +1318,19 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data }, State::DerefedBorrow(state) => { let mut app = Applicability::MachineApplicable; - let snip_expr = state.snip_expr.map_or(expr, |hir_id| cx.tcx.hir().expect_expr(hir_id)); - let (snip, snip_is_macro) = snippet_with_context(cx, snip_expr.span, data.span.ctxt(), "..", &mut app); + let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app); span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, state.msg, |diag| { - let calls_field = matches!(expr.kind, ExprKind::Field(..)) && matches!(data.position, Position::Callee); + let (precedence, calls_field) = match get_parent_node(cx.tcx, data.hir_id) { + Some(Node::Expr(e)) => match e.kind { + ExprKind::Call(callee, _) if callee.hir_id != data.hir_id => (0, false), + ExprKind::Call(..) => (PREC_POSTFIX, matches!(expr.kind, ExprKind::Field(..))), + _ => (e.precedence().order(), false), + }, + _ => (0, false), + }; let sugg = if !snip_is_macro + && (calls_field || expr.precedence().order() < precedence) && !has_enclosing_paren(&snip) - && (expr.precedence().order() < data.position.precedence() || calls_field) { format!("({snip})") } else { @@ -1572,7 +1347,8 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data | ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..) - ) && matches!(data.position, Position::DerefStable(_, true)) + ) && let ty::Ref(_, ty, _) = data.adjusted_ty.kind() + && ty.is_sized(cx.tcx, cx.param_env) { // Rustc bug: auto deref doesn't work on block expression when targeting sized types. return; @@ -1585,9 +1361,9 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data Mutability::Not => "&", Mutability::Mut => "&mut ", }; - (prefix, 0) + (prefix, PREC_PREFIX) } else { - ("", data.position.precedence()) + ("", 0) }; span_lint_hir_and_then( cx, @@ -1616,7 +1392,7 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data | ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..) - ) && matches!(data.position, Position::DerefStable(_, true)) + ) && data.adjusted_ty.is_sized(cx.tcx, cx.param_env) { // Rustc bug: auto deref doesn't work on block expression when targeting sized types. return; diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 91bc30ab57744..e3f2026cfe9e1 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -1,4 +1,6 @@ -use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::{ + span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then, +}; use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy}; use clippy_utils::{is_lint_allowed, match_def_path, paths}; use if_chain::if_chain; @@ -6,15 +8,15 @@ use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor}; use rustc_hir::{ - self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Impl, Item, ItemKind, UnsafeSource, - Unsafety, + self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Impl, Item, ItemKind, + UnsafeSource, Unsafety, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_middle::traits::Reveal; use rustc_middle::ty::{ - self, BoundConstness, ClauseKind, GenericArgKind, GenericParamDefKind, ImplPolarity, ParamEnv, ToPredicate, - TraitPredicate, Ty, TyCtxt, + self, BoundConstness, ClauseKind, GenericArgKind, GenericParamDefKind, ImplPolarity, ParamEnv, + ToPredicate, TraitPredicate, Ty, TyCtxt, }; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::def_id::LocalDefId; @@ -205,13 +207,10 @@ declare_lint_pass!(Derive => [ impl<'tcx> LateLintPass<'tcx> for Derive { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), - .. - }) = item.kind - { + if let ItemKind::Impl(Impl { of_trait: Some(ref trait_ref), .. }) = item.kind { let ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); - let is_automatically_derived = cx.tcx.has_attr(item.owner_id, sym::automatically_derived); + let is_automatically_derived = + cx.tcx.has_attr(item.owner_id, sym::automatically_derived); check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived); check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived); @@ -328,7 +327,12 @@ fn check_ord_partial_ord<'tcx>( } /// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint. -fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { +fn check_copy_clone<'tcx>( + cx: &LateContext<'tcx>, + item: &Item<'_>, + trait_ref: &hir::TraitRef<'_>, + ty: Ty<'tcx>, +) { let clone_id = match cx.tcx.lang_items().clone_trait() { Some(id) if trait_ref.trait_def_id() == Some(id) => id, _ => return, @@ -427,7 +431,14 @@ struct UnsafeVisitor<'a, 'tcx> { impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { type NestedFilter = nested_filter::All; - fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, _: Span, id: LocalDefId) { + fn visit_fn( + &mut self, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body_id: BodyId, + _: Span, + id: LocalDefId, + ) { if self.has_unsafe { return; } @@ -463,7 +474,12 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { } /// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint. -fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { +fn check_partial_eq_without_eq<'tcx>( + cx: &LateContext<'tcx>, + span: Span, + trait_ref: &hir::TraitRef<'_>, + ty: Ty<'tcx>, +) { if_chain! { if let ty::Adt(adt, args) = ty.kind(); if cx.tcx.visibility(adt.did()).is_public(); @@ -471,12 +487,12 @@ fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_r if let Some(def_id) = trait_ref.trait_def_id(); if cx.tcx.is_diagnostic_item(sym::PartialEq, def_id); let param_env = param_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id); - if !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, []); + if !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[]); // If all of our fields implement `Eq`, we can implement `Eq` too if adt .all_fields() .map(|f| f.ty(cx.tcx, args)) - .all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, [])); + .all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[])); then { span_lint_and_sugg( cx, diff --git a/clippy_lints/src/error_impl_error.rs b/clippy_lints/src/error_impl_error.rs new file mode 100644 index 0000000000000..379af9b2234c2 --- /dev/null +++ b/clippy_lints/src/error_impl_error.rs @@ -0,0 +1,87 @@ +use clippy_utils::diagnostics::{span_lint, span_lint_hir_and_then}; +use clippy_utils::path_res; +use clippy_utils::ty::implements_trait; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::{Item, ItemKind}; +use rustc_hir_analysis::hir_ty_to_ty; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::Visibility; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; + +declare_clippy_lint! { + /// ### What it does + /// Checks for types named `Error` that implement `Error`. + /// + /// ### Why is this bad? + /// It can become confusing when a codebase has 20 types all named `Error`, requiring either + /// aliasing them in the `use` statement or qualifying them like `my_module::Error`. This + /// hinders comprehension, as it requires you to memorize every variation of importing `Error` + /// used across a codebase. + /// + /// ### Example + /// ```rust,ignore + /// #[derive(Debug)] + /// pub enum Error { ... } + /// + /// impl std::fmt::Display for Error { ... } + /// + /// impl std::error::Error for Error { ... } + /// ``` + #[clippy::version = "1.72.0"] + pub ERROR_IMPL_ERROR, + restriction, + "exported types named `Error` that implement `Error`" +} +declare_lint_pass!(ErrorImplError => [ERROR_IMPL_ERROR]); + +impl<'tcx> LateLintPass<'tcx> for ErrorImplError { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + let Some(error_def_id) = cx.tcx.get_diagnostic_item(sym::Error) else { + return; + }; + + match item.kind { + ItemKind::TyAlias(ty, _) if implements_trait(cx, hir_ty_to_ty(cx.tcx, ty), error_def_id, &[]) + && item.ident.name == sym::Error + && is_visible_outside_module(cx, item.owner_id.def_id) => + { + span_lint( + cx, + ERROR_IMPL_ERROR, + item.ident.span, + "exported type alias named `Error` that implements `Error`", + ); + }, + ItemKind::Impl(imp) if let Some(trait_def_id) = imp.of_trait.and_then(|t| t.trait_def_id()) + && error_def_id == trait_def_id + && let Some(def_id) = path_res(cx, imp.self_ty).opt_def_id().and_then(DefId::as_local) + && let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id) + && let Some(ident) = cx.tcx.opt_item_ident(def_id.to_def_id()) + && ident.name == sym::Error + && is_visible_outside_module(cx, def_id) => + { + span_lint_hir_and_then( + cx, + ERROR_IMPL_ERROR, + hir_id, + ident.span, + "exported type named `Error` that implements `Error`", + |diag| { + diag.span_note(item.span, "`Error` was implemented here"); + } + ); + } + _ => {}, + } + } +} + +/// Do not lint private `Error`s, i.e., ones without any `pub` (minus `pub(self)` of course) and +/// which aren't reexported +fn is_visible_outside_module(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { + !matches!( + cx.tcx.visibility(def_id), + Visibility::Restricted(mod_def_id) if cx.tcx.parent_module_from_def_id(def_id).to_def_id() == mod_def_id + ) +} diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 22e10accd357f..8d6fb8438b658 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -1,19 +1,22 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::higher::VecArgs; use clippy_utils::source::snippet_opt; -use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; -use clippy_utils::usage::local_used_after_expr; +use clippy_utils::ty::type_diagnostic_name; +use clippy_utils::usage::{local_used_after_expr, local_used_in}; use clippy_utils::{higher, is_adjusted, path_to_local, path_to_local_id}; -use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; -use rustc_hir::{Closure, Expr, ExprKind, Param, PatKind, Unsafety}; +use rustc_hir::{BindingAnnotation, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, TyKind, Unsafety}; +use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; -use rustc_middle::ty::binding::BindingMode; -use rustc_middle::ty::{self, EarlyBinder, GenericArgsRef, Ty, TypeVisitableExt}; +use rustc_middle::ty::{ + self, Binder, BoundConstness, ClosureArgs, ClosureKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, + GenericArgsRef, ImplPolarity, List, Region, RegionKind, Ty, TypeVisitableExt, TypeckResults, +}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::sym; +use rustc_target::spec::abi::Abi; +use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _; declare_clippy_lint! { /// ### What it does @@ -72,14 +75,18 @@ declare_clippy_lint! { declare_lint_pass!(EtaReduction => [REDUNDANT_CLOSURE, REDUNDANT_CLOSURE_FOR_METHOD_CALLS]); impl<'tcx> LateLintPass<'tcx> for EtaReduction { + #[allow(clippy::too_many_lines)] fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if expr.span.from_expansion() { + let body = if let ExprKind::Closure(c) = expr.kind + && c.fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer)) + && matches!(c.fn_decl.output, FnRetTy::DefaultReturn(_)) + && !expr.span.from_expansion() + { + cx.tcx.hir().body(c.body) + } else { return; - } - let body = match expr.kind { - ExprKind::Closure(&Closure { body, .. }) => cx.tcx.hir().body(body), - _ => return, }; + if body.value.span.from_expansion() { if body.params.is_empty() { if let Some(VecArgs::Vec(&[])) = higher::VecArgs::hir(cx, body.value) { @@ -99,140 +106,209 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction { return; } - let closure_ty = cx.typeck_results().expr_ty(expr); + let typeck = cx.typeck_results(); + let closure = if let ty::Closure(_, closure_subs) = typeck.expr_ty(expr).kind() { + closure_subs.as_closure() + } else { + return; + }; - if_chain!( - if !is_adjusted(cx, body.value); - if let ExprKind::Call(callee, args) = body.value.kind; - if let ExprKind::Path(_) = callee.kind; - if check_inputs(cx, body.params, None, args); - let callee_ty = cx.typeck_results().expr_ty_adjusted(callee); - let call_ty = cx.typeck_results().type_dependent_def_id(body.value.hir_id) - .map_or(callee_ty, |id| cx.tcx.type_of(id).instantiate_identity()); - if check_sig(cx, closure_ty, call_ty); - let args = cx.typeck_results().node_args(callee.hir_id); - // This fixes some false positives that I don't entirely understand - if args.is_empty() || !cx.typeck_results().expr_ty(expr).has_late_bound_regions(); - // A type param function ref like `T::f` is not 'static, however - // it is if cast like `T::f as fn()`. This seems like a rustc bug. - if !args.types().any(|t| matches!(t.kind(), ty::Param(_))); - let callee_ty_unadjusted = cx.typeck_results().expr_ty(callee).peel_refs(); - if !is_type_diagnostic_item(cx, callee_ty_unadjusted, sym::Arc); - if !is_type_diagnostic_item(cx, callee_ty_unadjusted, sym::Rc); - if let ty::Closure(_, args) = *closure_ty.kind(); - // Don't lint if this is an inclusive range expression. - // They desugar to a call to `RangeInclusiveNew` which would have odd suggestions. (#10684) - if !matches!(higher::Range::hir(body.value), Some(higher::Range { - start: Some(_), - end: Some(_), - limits: rustc_ast::RangeLimits::Closed - })); - then { - span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| { - if let Some(mut snippet) = snippet_opt(cx, callee.span) { - if let Some(fn_mut_id) = cx.tcx.lang_items().fn_mut_trait() - && let args = cx.tcx.erase_late_bound_regions(args.as_closure().sig()).inputs() - && implements_trait( - cx, - callee_ty.peel_refs(), - fn_mut_id, - &args.iter().copied().map(Into::into).collect::>(), - ) - && path_to_local(callee).map_or(false, |l| local_used_after_expr(cx, l, expr)) - { - // Mutable closure is used after current expr; we cannot consume it. - snippet = format!("&mut {snippet}"); - } + if is_adjusted(cx, body.value) { + return; + } - diag.span_suggestion( - expr.span, - "replace the closure with the function itself", - snippet, - Applicability::MachineApplicable, - ); - } - }); - } - ); + match body.value.kind { + ExprKind::Call(callee, args) + if matches!(callee.kind, ExprKind::Path(QPath::Resolved(..) | QPath::TypeRelative(..))) => + { + let callee_ty = typeck.expr_ty(callee).peel_refs(); + if matches!( + type_diagnostic_name(cx, callee_ty), + Some(sym::Arc | sym::Rc) + ) || !check_inputs(typeck, body.params, None, args) { + return; + } + let callee_ty_adjusted = typeck.expr_adjustments(callee).last().map_or( + callee_ty, + |a| a.target.peel_refs(), + ); - if_chain!( - if !is_adjusted(cx, body.value); - if let ExprKind::MethodCall(path, receiver, args, _) = body.value.kind; - if check_inputs(cx, body.params, Some(receiver), args); - let method_def_id = cx.typeck_results().type_dependent_def_id(body.value.hir_id).unwrap(); - let args = cx.typeck_results().node_args(body.value.hir_id); - let call_ty = cx.tcx.type_of(method_def_id).instantiate(cx.tcx, args); - if check_sig(cx, closure_ty, call_ty); - then { - span_lint_and_then(cx, REDUNDANT_CLOSURE_FOR_METHOD_CALLS, expr.span, "redundant closure", |diag| { - let name = get_ufcs_type_name(cx, method_def_id, args); - diag.span_suggestion( + let sig = match callee_ty_adjusted.kind() { + ty::FnDef(def, _) => cx.tcx.fn_sig(def).skip_binder().skip_binder(), + ty::FnPtr(sig) => sig.skip_binder(), + ty::Closure(_, subs) => cx + .tcx + .signature_unclosure(subs.as_closure().sig(), Unsafety::Normal) + .skip_binder(), + _ => { + if typeck.type_dependent_def_id(body.value.hir_id).is_some() + && let subs = typeck.node_args(body.value.hir_id) + && let output = typeck.expr_ty(body.value) + && let ty::Tuple(tys) = *subs.type_at(1).kind() + { + cx.tcx.mk_fn_sig(tys, output, false, Unsafety::Normal, Abi::Rust) + } else { + return; + } + }, + }; + if check_sig(cx, closure, sig) + && let generic_args = typeck.node_args(callee.hir_id) + // Given some trait fn `fn f() -> ()` and some type `T: Trait`, `T::f` is not + // `'static` unless `T: 'static`. The cast `T::f as fn()` will, however, result + // in a type which is `'static`. + // For now ignore all callee types which reference a type parameter. + && !generic_args.types().any(|t| matches!(t.kind(), ty::Param(_))) + { + span_lint_and_then( + cx, + REDUNDANT_CLOSURE, expr.span, - "replace the closure with the method itself", - format!("{name}::{}", path.ident.name), - Applicability::MachineApplicable, + "redundant closure", + |diag| { + if let Some(mut snippet) = snippet_opt(cx, callee.span) { + if let Ok((ClosureKind::FnMut, _)) + = cx.tcx.infer_ctxt().build().type_implements_fn_trait( + cx.param_env, + Binder::bind_with_vars(callee_ty_adjusted, List::empty()), + BoundConstness::NotConst, + ImplPolarity::Positive, + ) && path_to_local(callee) + .map_or( + false, + |l| local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr), + ) + { + // Mutable closure is used after current expr; we cannot consume it. + snippet = format!("&mut {snippet}"); + } + diag.span_suggestion( + expr.span, + "replace the closure with the function itself", + snippet, + Applicability::MachineApplicable, + ); + } + } ); - }) - } - ); + } + }, + ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => { + if let Some(method_def_id) = typeck.type_dependent_def_id(body.value.hir_id) + && check_sig(cx, closure, cx.tcx.fn_sig(method_def_id).skip_binder().skip_binder()) + { + span_lint_and_then( + cx, + REDUNDANT_CLOSURE_FOR_METHOD_CALLS, + expr.span, + "redundant closure", + |diag| { + let args = typeck.node_args(body.value.hir_id); + let name = get_ufcs_type_name(cx, method_def_id, args); + diag.span_suggestion( + expr.span, + "replace the closure with the method itself", + format!("{}::{}", name, path.ident.name), + Applicability::MachineApplicable, + ); + }, + ); + } + }, + _ => (), + } } } fn check_inputs( - cx: &LateContext<'_>, + typeck: &TypeckResults<'_>, params: &[Param<'_>], - receiver: Option<&Expr<'_>>, - call_args: &[Expr<'_>], + self_arg: Option<&Expr<'_>>, + args: &[Expr<'_>], ) -> bool { - if receiver.map_or(params.len() != call_args.len(), |_| params.len() != call_args.len() + 1) { - return false; + params.len() == self_arg.map_or(0, |_| 1) + args.len() + && params.iter().zip(self_arg.into_iter().chain(args)).all(|(p, arg)| { + matches!( + p.pat.kind,PatKind::Binding(BindingAnnotation::NONE, id, _, None) + if path_to_local_id(arg, id) + ) + // Only allow adjustments which change regions (i.e. re-borrowing). + && typeck + .expr_adjustments(arg) + .last() + .map_or(true, |a| a.target == typeck.expr_ty(arg)) + }) +} + +fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure: ClosureArgs<'tcx>, call_sig: FnSig<'_>) -> bool { + call_sig.unsafety == Unsafety::Normal + && !has_late_bound_to_non_late_bound_regions( + cx.tcx + .signature_unclosure(closure.sig(), Unsafety::Normal) + .skip_binder(), + call_sig, + ) +} + +/// This walks through both signatures and checks for any time a late-bound region is expected by an +/// `impl Fn` type, but the target signature does not have a late-bound region in the same position. +/// +/// This is needed because rustc is unable to late bind early-bound regions in a function signature. +fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'_>) -> bool { + fn check_region(from_region: Region<'_>, to_region: Region<'_>) -> bool { + matches!(from_region.kind(), RegionKind::ReLateBound(..)) + && !matches!(to_region.kind(), RegionKind::ReLateBound(..)) } - let binding_modes = cx.typeck_results().pat_binding_modes(); - let check_inputs = |param: &Param<'_>, arg| { - match param.pat.kind { - PatKind::Binding(_, id, ..) if path_to_local_id(arg, id) => {}, - _ => return false, - } - // checks that parameters are not bound as `ref` or `ref mut` - if let Some(BindingMode::BindByReference(_)) = binding_modes.get(param.pat.hir_id) { - return false; - } - match *cx.typeck_results().expr_adjustments(arg) { - [] => true, - [ - Adjustment { - kind: Adjust::Deref(None), - .. + fn check_subs(from_subs: &[GenericArg<'_>], to_subs: &[GenericArg<'_>]) -> bool { + if from_subs.len() != to_subs.len() { + return true; + } + for (from_arg, to_arg) in to_subs.iter().zip(from_subs) { + match (from_arg.unpack(), to_arg.unpack()) { + (GenericArgKind::Lifetime(from_region), GenericArgKind::Lifetime(to_region)) => { + if check_region(from_region, to_region) { + return true; + } }, - Adjustment { - kind: Adjust::Borrow(AutoBorrow::Ref(_, mu2)), - .. + (GenericArgKind::Type(from_ty), GenericArgKind::Type(to_ty)) => { + if check_ty(from_ty, to_ty) { + return true; + } }, - ] => { - // re-borrow with the same mutability is allowed - let ty = cx.typeck_results().expr_ty(arg); - matches!(*ty.kind(), ty::Ref(.., mu1) if mu1 == mu2.into()) - }, - _ => false, + (GenericArgKind::Const(_), GenericArgKind::Const(_)) => (), + _ => return true, + } } - }; - std::iter::zip(params, receiver.into_iter().chain(call_args.iter())).all(|(param, arg)| check_inputs(param, arg)) -} - -fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure_ty: Ty<'tcx>, call_ty: Ty<'tcx>) -> bool { - let call_sig = call_ty.fn_sig(cx.tcx); - if call_sig.unsafety() == Unsafety::Unsafe { - return false; + false } - if !closure_ty.has_late_bound_regions() { - return true; + + fn check_ty(from_ty: Ty<'_>, to_ty: Ty<'_>) -> bool { + match (from_ty.kind(), to_ty.kind()) { + (&ty::Adt(_, from_subs), &ty::Adt(_, to_subs)) => check_subs(from_subs, to_subs), + (&ty::Array(from_ty, _), &ty::Array(to_ty, _)) | (&ty::Slice(from_ty), &ty::Slice(to_ty)) => { + check_ty(from_ty, to_ty) + }, + (&ty::Ref(from_region, from_ty, _), &ty::Ref(to_region, to_ty, _)) => { + check_region(from_region, to_region) || check_ty(from_ty, to_ty) + }, + (&ty::Tuple(from_tys), &ty::Tuple(to_tys)) => { + from_tys.len() != to_tys.len() + || from_tys + .iter() + .zip(to_tys) + .any(|(from_ty, to_ty)| check_ty(from_ty, to_ty)) + }, + _ => from_ty.has_late_bound_regions(), + } } - let ty::Closure(_, args) = closure_ty.kind() else { - return false; - }; - let closure_sig = cx.tcx.signature_unclosure(args.as_closure().sig(), Unsafety::Normal); - cx.tcx.erase_late_bound_regions(closure_sig) == cx.tcx.erase_late_bound_regions(call_sig) + + assert!(from_sig.inputs_and_output.len() == to_sig.inputs_and_output.len()); + from_sig + .inputs_and_output + .iter() + .zip(to_sig.inputs_and_output) + .any(|(from_ty, to_ty)| check_ty(from_ty, to_ty)) } fn get_ufcs_type_name<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId, args: GenericArgsRef<'tcx>) -> String { @@ -241,7 +317,7 @@ fn get_ufcs_type_name<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId, args: match assoc_item.container { ty::TraitContainer => cx.tcx.def_path_str(def_id), ty::ImplContainer => { - let ty = cx.tcx.type_of(def_id).skip_binder(); + let ty = cx.tcx.type_of(def_id).instantiate_identity(); match ty.kind() { ty::Adt(adt, _) => cx.tcx.def_path_str(adt.did()), ty::Array(..) diff --git a/clippy_lints/src/four_forward_slashes.rs b/clippy_lints/src/four_forward_slashes.rs new file mode 100644 index 0000000000000..419c77343441c --- /dev/null +++ b/clippy_lints/src/four_forward_slashes.rs @@ -0,0 +1,99 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_errors::Applicability; +use rustc_hir::Item; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// Checks for outer doc comments written with 4 forward slashes (`////`). + /// + /// ### Why is this bad? + /// This is (probably) a typo, and results in it not being a doc comment; just a regular + /// comment. + /// + /// ### Example + /// ```rust + /// //// My amazing data structure + /// pub struct Foo { + /// // ... + /// } + /// ``` + /// + /// Use instead: + /// ```rust + /// /// My amazing data structure + /// pub struct Foo { + /// // ... + /// } + /// ``` + #[clippy::version = "1.72.0"] + pub FOUR_FORWARD_SLASHES, + suspicious, + "comments with 4 forward slashes (`////`) likely intended to be doc comments (`///`)" +} +declare_lint_pass!(FourForwardSlashes => [FOUR_FORWARD_SLASHES]); + +impl<'tcx> LateLintPass<'tcx> for FourForwardSlashes { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if item.span.from_expansion() { + return; + } + let sm = cx.sess().source_map(); + let mut span = cx + .tcx + .hir() + .attrs(item.hir_id()) + .iter() + .fold(item.span.shrink_to_lo(), |span, attr| span.to(attr.span)); + let (Some(file), _, _, end_line, _) = sm.span_to_location_info(span) else { + return; + }; + let mut bad_comments = vec![]; + for line in (0..end_line.saturating_sub(1)).rev() { + let Some(contents) = file.get_line(line).map(|c| c.trim().to_owned()) else { + return; + }; + // Keep searching until we find the next item + if !contents.is_empty() && !contents.starts_with("//") && !contents.starts_with("#[") { + break; + } + + if contents.starts_with("////") && !matches!(contents.chars().nth(4), Some('/' | '!')) { + let bounds = file.line_bounds(line); + let line_span = Span::with_root_ctxt(bounds.start, bounds.end); + span = line_span.to(span); + bad_comments.push((line_span, contents)); + } + } + + if !bad_comments.is_empty() { + span_lint_and_then( + cx, + FOUR_FORWARD_SLASHES, + span, + "this item has comments with 4 forward slashes (`////`). These look like doc comments, but they aren't", + |diag| { + let msg = if bad_comments.len() == 1 { + "make this a doc comment by removing one `/`" + } else { + "turn these into doc comments by removing one `/`" + }; + + diag.multipart_suggestion( + msg, + bad_comments + .into_iter() + // It's a little unfortunate but the span includes the `\n` yet the contents + // do not, so we must add it back. If some codebase uses `\r\n` instead they + // will need normalization but it should be fine + .map(|(span, c)| (span, c.replacen("////", "///", 1) + "\n")) + .collect(), + Applicability::MachineApplicable, + ); + }, + ); + } + } +} diff --git a/clippy_lints/src/incorrect_impls.rs b/clippy_lints/src/incorrect_impls.rs index e6c42ebb8329b..3c59b839a39c6 100644 --- a/clippy_lints/src/incorrect_impls.rs +++ b/clippy_lints/src/incorrect_impls.rs @@ -1,8 +1,9 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::paths::ORD_CMP; use clippy_utils::ty::implements_trait; -use clippy_utils::{get_parent_node, is_res_lang_ctor, last_path_segment, path_res}; +use clippy_utils::{get_parent_node, is_res_lang_ctor, last_path_segment, match_def_path, path_res, std_or_core}; use rustc_errors::Applicability; -use rustc_hir::def::Res; +use rustc_hir::def_id::LocalDefId; use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, ItemKind, LangItem, Node, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::EarlyBinder; @@ -59,6 +60,10 @@ declare_clippy_lint! { /// wrapping the result of `cmp` in `Some` for `partial_cmp`. Not doing this may silently /// introduce an error upon refactoring. /// + /// ### Known issues + /// Code that calls the `.into()` method instead will be flagged as incorrect, despite `.into()` + /// wrapping it in `Some`. + /// /// ### Limitations /// Will not lint if `Self` and `Rhs` do not have the same type. /// @@ -190,6 +195,11 @@ impl LateLintPass<'_> for IncorrectImpls { &[], ) { + // If the `cmp` call likely needs to be fully qualified in the suggestion + // (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't + // access `cmp_expr` in the suggestion without major changes, as we lint in `else`. + let mut needs_fully_qualified = false; + if block.stmts.is_empty() && let Some(expr) = block.expr && let ExprKind::Call( @@ -201,9 +211,8 @@ impl LateLintPass<'_> for IncorrectImpls { [cmp_expr], ) = expr.kind && is_res_lang_ctor(cx, cx.qpath_res(some_path, *some_hir_id), LangItem::OptionSome) - && let ExprKind::MethodCall(cmp_path, _, [other_expr], ..) = cmp_expr.kind - && cmp_path.ident.name == sym::cmp - && let Res::Local(..) = path_res(cx, other_expr) + // Fix #11178, allow `Self::cmp(self, ..)` too + && self_cmp_call(cx, cmp_expr, impl_item.owner_id.def_id, &mut needs_fully_qualified) {} else { // If `Self` and `Rhs` are not the same type, bail. This makes creating a valid // suggestion tons more complex. @@ -220,14 +229,29 @@ impl LateLintPass<'_> for IncorrectImpls { let [_, other] = body.params else { return; }; + let Some(std_or_core) = std_or_core(cx) else { + return; + }; - let suggs = if let Some(other_ident) = other.pat.simple_ident() { - vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))] - } else { - vec![ + let suggs = match (other.pat.simple_ident(), needs_fully_qualified) { + (Some(other_ident), true) => vec![( + block.span, + format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}", other_ident.name), + )], + (Some(other_ident), false) => { + vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))] + }, + (None, true) => vec![ + ( + block.span, + format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}"), + ), + (other.pat.span, "other".to_owned()), + ], + (None, false) => vec![ (block.span, "{ Some(self.cmp(other)) }".to_owned()), (other.pat.span, "other".to_owned()), - ] + ], }; diag.multipart_suggestion( @@ -241,3 +265,31 @@ impl LateLintPass<'_> for IncorrectImpls { } } } + +/// Returns whether this is any of `self.cmp(..)`, `Self::cmp(self, ..)` or `Ord::cmp(self, ..)`. +fn self_cmp_call<'tcx>( + cx: &LateContext<'tcx>, + cmp_expr: &'tcx Expr<'tcx>, + def_id: LocalDefId, + needs_fully_qualified: &mut bool, +) -> bool { + match cmp_expr.kind { + ExprKind::Call(path, [_self, _other]) => path_res(cx, path) + .opt_def_id() + .is_some_and(|def_id| match_def_path(cx, def_id, &ORD_CMP)), + ExprKind::MethodCall(_, _, [_other], ..) => { + // We can set this to true here no matter what as if it's a `MethodCall` and goes to the + // `else` branch, it must be a method named `cmp` that isn't `Ord::cmp` + *needs_fully_qualified = true; + + // It's a bit annoying but `typeck_results` only gives us the CURRENT body, which we + // have none, not of any `LocalDefId` we want, so we must call the query itself to avoid + // an immediate ICE + cx.tcx + .typeck(def_id) + .type_dependent_def_id(cmp_expr.hir_id) + .is_some_and(|def_id| match_def_path(cx, def_id, &ORD_CMP)) + }, + _ => false, + } +} diff --git a/clippy_lints/src/inherent_to_string.rs b/clippy_lints/src/inherent_to_string.rs index d43e5cc9b2c3d..bc4ec33b7334e 100644 --- a/clippy_lints/src/inherent_to_string.rs +++ b/clippy_lints/src/inherent_to_string.rs @@ -1,11 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::ty::{implements_trait, is_type_lang_item}; use clippy_utils::{return_ty, trait_ref_of_method}; -use if_chain::if_chain; -use rustc_hir::{GenericParamKind, ImplItem, ImplItemKind, LangItem}; +use rustc_hir::{GenericParamKind, ImplItem, ImplItemKind, LangItem, Unsafety}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::sym; +use rustc_target::spec::abi::Abi; declare_clippy_lint! { /// ### What it does @@ -95,24 +95,23 @@ impl<'tcx> LateLintPass<'tcx> for InherentToString { return; } - if_chain! { - // Check if item is a method, called to_string and has a parameter 'self' - if let ImplItemKind::Fn(ref signature, _) = impl_item.kind; - if impl_item.ident.name == sym::to_string; - let decl = &signature.decl; - if decl.implicit_self.has_implicit_self(); - if decl.inputs.len() == 1; - if impl_item.generics.params.iter().all(|p| matches!(p.kind, GenericParamKind::Lifetime { .. })); - + // Check if item is a method called `to_string` and has a parameter 'self' + if let ImplItemKind::Fn(ref signature, _) = impl_item.kind + // #11201 + && let header = signature.header + && header.unsafety == Unsafety::Normal + && header.abi == Abi::Rust + && impl_item.ident.name == sym::to_string + && let decl = signature.decl + && decl.implicit_self.has_implicit_self() + && decl.inputs.len() == 1 + && impl_item.generics.params.iter().all(|p| matches!(p.kind, GenericParamKind::Lifetime { .. })) // Check if return type is String - if is_type_lang_item(cx, return_ty(cx, impl_item.owner_id), LangItem::String); - + && is_type_lang_item(cx, return_ty(cx, impl_item.owner_id), LangItem::String) // Filters instances of to_string which are required by a trait - if trait_ref_of_method(cx, impl_item.owner_id.def_id).is_none(); - - then { - show_lint(cx, impl_item); - } + && trait_ref_of_method(cx, impl_item.owner_id.def_id).is_none() + { + show_lint(cx, impl_item); } } } diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 50c433d31613e..deba232bdd23e 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -7,11 +7,10 @@ use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::def_id::{DefId, DefIdSet}; -use rustc_hir::lang_items::LangItem; use rustc_hir::{ AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind, - ImplicitSelfKind, Item, ItemKind, Mutability, Node, PathSegment, PrimTy, QPath, TraitItemRef, TyKind, - TypeBindingKind, + ImplicitSelfKind, Item, ItemKind, LangItem, Mutability, Node, PatKind, PathSegment, PrimTy, QPath, TraitItemRef, + TyKind, TypeBindingKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, AssocKind, FnSig, Ty}; @@ -171,6 +170,31 @@ impl<'tcx> LateLintPass<'tcx> for LenZero { return; } + if let ExprKind::Let(lt) = expr.kind + && has_is_empty(cx, lt.init) + && match lt.pat.kind { + PatKind::Slice([], None, []) => true, + PatKind::Lit(lit) if is_empty_string(lit) => true, + _ => false, + } + { + let mut applicability = Applicability::MachineApplicable; + + let lit1 = peel_ref_operators(cx, lt.init); + let lit_str = + Sugg::hir_with_context(cx, lit1, lt.span.ctxt(), "_", &mut applicability).maybe_par(); + + span_lint_and_sugg( + cx, + COMPARISON_TO_EMPTY, + lt.span, + "comparison to empty slice using `if let`", + "using `is_empty` is clearer and more explicit", + format!("{lit_str}.is_empty()"), + applicability, + ); + } + if let ExprKind::Binary(Spanned { node: cmp, .. }, left, right) = expr.kind { // expr.span might contains parenthesis, see issue #10529 let actual_span = left.span.with_hi(right.span.hi()); diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 5e62cfd70ec0f..9d6096ccb2ae8 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -65,6 +65,7 @@ mod declared_lints; mod renamed_lints; // begin lints modules, do not remove this comment, it’s used in `update_lints` +mod absolute_paths; mod allow_attributes; mod almost_complete_range; mod approx_const; @@ -120,6 +121,7 @@ mod entry; mod enum_clike; mod enum_variants; mod equatable_if_let; +mod error_impl_error; mod escape; mod eta_reduction; mod excessive_bools; @@ -136,6 +138,7 @@ mod format_args; mod format_impl; mod format_push_string; mod formatting; +mod four_forward_slashes; mod from_over_into; mod from_raw_with_void_ptr; mod from_str_radix_10; @@ -272,6 +275,7 @@ mod redundant_clone; mod redundant_closure_call; mod redundant_else; mod redundant_field_names; +mod redundant_locals; mod redundant_pub_crate; mod redundant_slicing; mod redundant_static_lifetimes; @@ -909,7 +913,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move |_| Box::new(if_then_some_else_none::IfThenSomeElseNone::new(msrv()))); store.register_late_pass(|_| Box::new(bool_assert_comparison::BoolAssertComparison)); store.register_early_pass(move || Box::new(module_style::ModStyle)); - store.register_late_pass(|_| Box::new(unused_async::UnusedAsync)); + store.register_late_pass(|_| Box::::default()); let disallowed_types = conf.disallowed_types.clone(); store.register_late_pass(move |_| Box::new(disallowed_types::DisallowedTypes::new(disallowed_types.clone()))); let import_renames = conf.enforced_import_renames.clone(); @@ -1078,6 +1082,17 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| Box::new(visibility::Visibility)); store.register_late_pass(move |_| Box::new(tuple_array_conversions::TupleArrayConversions { msrv: msrv() })); store.register_late_pass(|_| Box::new(manual_float_methods::ManualFloatMethods)); + store.register_late_pass(|_| Box::new(four_forward_slashes::FourForwardSlashes)); + store.register_late_pass(|_| Box::new(error_impl_error::ErrorImplError)); + let absolute_paths_max_segments = conf.absolute_paths_max_segments; + let absolute_paths_allowed_crates = conf.absolute_paths_allowed_crates.clone(); + store.register_late_pass(move |_| { + Box::new(absolute_paths::AbsolutePaths { + absolute_paths_max_segments, + absolute_paths_allowed_crates: absolute_paths_allowed_crates.clone(), + }) + }); + store.register_late_pass(|_| Box::new(redundant_locals::RedundantLocals)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 852f6736585bc..0004a150d51b5 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -15,6 +15,7 @@ use rustc_hir::{ PredicateOrigin, TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WherePredicate, }; use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; use rustc_middle::hir::nested_filter as middle_nested_filter; use rustc_middle::lint::in_external_macro; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -620,7 +621,7 @@ impl<'cx, 'tcx, F> Visitor<'tcx> for LifetimeChecker<'cx, 'tcx, F> where F: NestedFilter<'tcx>, { - type Map = rustc_middle::hir::map::Map<'tcx>; + type Map = Map<'tcx>; type NestedFilter = F; // for lifetimes as parameters of generics diff --git a/clippy_lints/src/loops/explicit_iter_loop.rs b/clippy_lints/src/loops/explicit_iter_loop.rs index a84a0a6eeb82a..7b8c88235a97d 100644 --- a/clippy_lints/src/loops/explicit_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_iter_loop.rs @@ -109,7 +109,7 @@ fn is_ref_iterable<'tcx>( && let sig = cx.tcx.liberate_late_bound_regions(fn_id, cx.tcx.fn_sig(fn_id).skip_binder()) && let &[req_self_ty, req_res_ty] = &**sig.inputs_and_output && let param_env = cx.tcx.param_env(fn_id) - && implements_trait_with_env(cx.tcx, param_env, req_self_ty, trait_id, []) + && implements_trait_with_env(cx.tcx, param_env, req_self_ty, trait_id, &[]) && let Some(into_iter_ty) = make_normalized_projection_with_regions(cx.tcx, param_env, trait_id, sym!(IntoIter), [req_self_ty]) && let req_res_ty = normalize_with_regions(cx.tcx, param_env, req_res_ty) diff --git a/clippy_lints/src/loops/single_element_loop.rs b/clippy_lints/src/loops/single_element_loop.rs index 744fd61bd135c..dfb800ccf714d 100644 --- a/clippy_lints/src/loops/single_element_loop.rs +++ b/clippy_lints/src/loops/single_element_loop.rs @@ -9,6 +9,7 @@ use rustc_errors::Applicability; use rustc_hir::{is_range_literal, BorrowKind, Expr, ExprKind, Pat}; use rustc_lint::LateContext; use rustc_span::edition::Edition; +use rustc_span::sym; pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, @@ -51,7 +52,7 @@ pub(super) fn check<'tcx>( }, [], _, - ) if method.ident.name.as_str() == "iter_mut" => (arg, "&mut "), + ) if method.ident.name == sym::iter_mut => (arg, "&mut "), ExprKind::MethodCall( method, Expr { diff --git a/clippy_lints/src/loops/utils.rs b/clippy_lints/src/loops/utils.rs index 28ee24309cc46..6edca2d55f649 100644 --- a/clippy_lints/src/loops/utils.rs +++ b/clippy_lints/src/loops/utils.rs @@ -76,7 +76,7 @@ impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> { ExprKind::Assign(lhs, _, _) if lhs.hir_id == expr.hir_id => { *state = IncrementVisitorVarState::DontWarn; }, - ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => { + ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) => { *state = IncrementVisitorVarState::DontWarn; }, _ => (), @@ -226,7 +226,7 @@ impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> { InitializeVisitorState::DontWarn } }, - ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => { + ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) => { self.state = InitializeVisitorState::DontWarn; }, _ => (), diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index d1061171e4d69..6d16d18875408 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -16,6 +16,7 @@ mod match_wild_enum; mod match_wild_err_arm; mod needless_match; mod overlapping_arms; +mod redundant_guards; mod redundant_pattern_match; mod rest_pat_in_fully_bound_struct; mod significant_drop_in_scrutinee; @@ -936,6 +937,36 @@ declare_clippy_lint! { "reimplementation of `filter`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for unnecessary guards in match expressions. + /// + /// ### Why is this bad? + /// It's more complex and much less readable. Making it part of the pattern can improve + /// exhaustiveness checking as well. + /// + /// ### Example + /// ```rust,ignore + /// match x { + /// Some(x) if matches!(x, Some(1)) => .., + /// Some(x) if x == Some(2) => .., + /// _ => todo!(), + /// } + /// ``` + /// Use instead: + /// ```rust,ignore + /// match x { + /// Some(Some(1)) => .., + /// Some(Some(2)) => .., + /// _ => todo!(), + /// } + /// ``` + #[clippy::version = "1.72.0"] + pub REDUNDANT_GUARDS, + complexity, + "checks for unnecessary guards in match expressions" +} + #[derive(Default)] pub struct Matches { msrv: Msrv, @@ -978,6 +1009,7 @@ impl_lint_pass!(Matches => [ TRY_ERR, MANUAL_MAP, MANUAL_FILTER, + REDUNDANT_GUARDS, ]); impl<'tcx> LateLintPass<'tcx> for Matches { @@ -1025,6 +1057,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { needless_match::check_match(cx, ex, arms, expr); match_on_vec_items::check(cx, ex); match_str_case_mismatch::check(cx, ex, arms); + redundant_guards::check(cx, arms); if !in_constant(cx, expr.hir_id) { manual_unwrap_or::check(cx, expr, ex, arms); diff --git a/clippy_lints/src/matches/redundant_guards.rs b/clippy_lints/src/matches/redundant_guards.rs new file mode 100644 index 0000000000000..6383326aa38df --- /dev/null +++ b/clippy_lints/src/matches/redundant_guards.rs @@ -0,0 +1,190 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::path_to_local; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::visitors::{for_each_expr, is_local_used}; +use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, Guard, MatchSource, Node, Pat, PatKind}; +use rustc_lint::LateContext; +use rustc_span::Span; +use std::ops::ControlFlow; + +use super::REDUNDANT_GUARDS; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>]) { + for outer_arm in arms { + let Some(guard) = outer_arm.guard else { + continue; + }; + + // `Some(x) if matches!(x, y)` + if let Guard::If(if_expr) = guard + && let ExprKind::Match( + scrutinee, + [ + arm, + Arm { + pat: Pat { + kind: PatKind::Wild, + .. + }, + .. + }, + ], + MatchSource::Normal, + ) = if_expr.kind + { + emit_redundant_guards( + cx, + outer_arm, + if_expr.span, + scrutinee, + arm.pat.span, + arm.guard, + ); + } + // `Some(x) if let Some(2) = x` + else if let Guard::IfLet(let_expr) = guard { + emit_redundant_guards( + cx, + outer_arm, + let_expr.span, + let_expr.init, + let_expr.pat.span, + None, + ); + } + // `Some(x) if x == Some(2)` + else if let Guard::If(if_expr) = guard + && let ExprKind::Binary(bin_op, local, pat) = if_expr.kind + && matches!(bin_op.node, BinOpKind::Eq) + && expr_can_be_pat(cx, pat) + // Ensure they have the same type. If they don't, we'd need deref coercion which isn't + // possible (currently) in a pattern. In some cases, you can use something like + // `as_deref` or similar but in general, we shouldn't lint this as it'd create an + // extraordinary amount of FPs. + // + // This isn't necessary in the other two checks, as they must be a pattern already. + && cx.typeck_results().expr_ty(local) == cx.typeck_results().expr_ty(pat) + { + emit_redundant_guards( + cx, + outer_arm, + if_expr.span, + local, + pat.span, + None, + ); + } + } +} + +fn get_pat_binding<'tcx>(cx: &LateContext<'tcx>, guard_expr: &Expr<'_>, outer_arm: &Arm<'tcx>) -> Option<(Span, bool)> { + if let Some(local) = path_to_local(guard_expr) && !is_local_used(cx, outer_arm.body, local) { + let mut span = None; + let mut multiple_bindings = false; + // `each_binding` gives the `HirId` of the `Pat` itself, not the binding + outer_arm.pat.walk(|pat| { + if let PatKind::Binding(_, hir_id, _, _) = pat.kind + && hir_id == local + && span.replace(pat.span).is_some() + { + multiple_bindings = true; + return false; + } + + true + }); + + // Ignore bindings from or patterns, like `First(x) | Second(x, _) | Third(x, _, _)` + if !multiple_bindings { + return span.map(|span| { + ( + span, + !matches!(cx.tcx.hir().get_parent(local), Node::PatField(_)), + ) + }); + } + } + + None +} + +fn emit_redundant_guards<'tcx>( + cx: &LateContext<'tcx>, + outer_arm: &Arm<'tcx>, + guard_span: Span, + local: &Expr<'_>, + pat_span: Span, + inner_guard: Option>, +) { + let mut app = Applicability::MaybeIncorrect; + let Some((pat_binding, can_use_shorthand)) = get_pat_binding(cx, local, outer_arm) else { + return; + }; + + span_lint_and_then( + cx, + REDUNDANT_GUARDS, + guard_span.source_callsite(), + "redundant guard", + |diag| { + let binding_replacement = snippet_with_applicability(cx, pat_span, "", &mut app); + diag.multipart_suggestion_verbose( + "try", + vec![ + if can_use_shorthand { + (pat_binding, binding_replacement.into_owned()) + } else { + (pat_binding.shrink_to_hi(), format!(": {binding_replacement}")) + }, + ( + guard_span.source_callsite().with_lo(outer_arm.pat.span.hi()), + inner_guard.map_or_else(String::new, |guard| { + let (prefix, span) = match guard { + Guard::If(e) => ("if", e.span), + Guard::IfLet(l) => ("if let", l.span), + }; + + format!( + " {prefix} {}", + snippet_with_applicability(cx, span, "", &mut app), + ) + }), + ), + ], + app, + ); + }, + ); +} + +/// Checks if the given `Expr` can also be represented as a `Pat`. +fn expr_can_be_pat(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + for_each_expr(expr, |expr| { + if match expr.kind { + ExprKind::ConstBlock(..) => cx.tcx.features().inline_const_pat, + ExprKind::Call(c, ..) if let ExprKind::Path(qpath) = c.kind => { + // Allow ctors + matches!(cx.qpath_res(&qpath, c.hir_id), Res::Def(DefKind::Ctor(..), ..)) + }, + ExprKind::Path(qpath) => { + matches!( + cx.qpath_res(&qpath, expr.hir_id), + Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Ctor(..), ..), + ) + }, + ExprKind::AddrOf(..) + | ExprKind::Array(..) + | ExprKind::Tup(..) + | ExprKind::Struct(..) + | ExprKind::Lit(..) => true, + _ => false, + } { + return ControlFlow::Continue(()); + } + + ControlFlow::Break(()) + }) + .is_none() +} diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index ad38f1394b463..9a7c00823b670 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -3,17 +3,19 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::{snippet, walk_span_to_context}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{is_type_diagnostic_item, needs_ordered_drop}; -use clippy_utils::visitors::any_temporaries_need_ordered_drop; +use clippy_utils::visitors::{any_temporaries_need_ordered_drop, for_each_expr}; use clippy_utils::{higher, is_expn_of, is_trait_method}; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::LangItem::{self, OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk}; -use rustc_hir::{Arm, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp}; +use rustc_hir::{Arm, Expr, ExprKind, Guard, Node, Pat, PatKind, QPath, UnOp}; use rustc_lint::LateContext; use rustc_middle::ty::{self, GenericArgKind, Ty}; use rustc_span::{sym, Symbol}; +use std::fmt::Write; +use std::ops::ControlFlow; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) { @@ -201,30 +203,58 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op if arms.len() == 2 { let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind); - if let Some(good_method) = found_good_method(cx, arms, node_pair) { + if let Some((good_method, maybe_guard)) = found_good_method(cx, arms, node_pair) { let span = is_expn_of(expr.span, "matches").unwrap_or(expr.span.to(op.span)); let result_expr = match &op.kind { ExprKind::AddrOf(_, _, borrowed) => borrowed, _ => op, }; + let mut sugg = format!("{}.{good_method}", snippet(cx, result_expr.span, "_")); + + if let Some(guard) = maybe_guard { + let Guard::If(guard) = *guard else { return }; // `...is_none() && let ...` is a syntax error + + // wow, the HIR for match guards in `PAT if let PAT = expr && expr => ...` is annoying! + // `guard` here is `Guard::If` with the let expression somewhere deep in the tree of exprs, + // counter to the intuition that it should be `Guard::IfLet`, so we need another check + // to see that there aren't any let chains anywhere in the guard, as that would break + // if we suggest `t.is_none() && (let X = y && z)` for: + // `match t { None if let X = y && z => true, _ => false }` + let has_nested_let_chain = for_each_expr(guard, |expr| { + if matches!(expr.kind, ExprKind::Let(..)) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_some(); + + if has_nested_let_chain { + return; + } + + let guard = Sugg::hir(cx, guard, ".."); + let _ = write!(sugg, " && {}", guard.maybe_par()); + } + span_lint_and_sugg( cx, REDUNDANT_PATTERN_MATCHING, span, &format!("redundant pattern matching, consider using `{good_method}`"), "try", - format!("{}.{good_method}", snippet(cx, result_expr.span, "_")), + sugg, Applicability::MachineApplicable, ); } } } -fn found_good_method<'a>( +fn found_good_method<'tcx>( cx: &LateContext<'_>, - arms: &[Arm<'_>], + arms: &'tcx [Arm<'tcx>], node: (&PatKind<'_>, &PatKind<'_>), -) -> Option<&'a str> { +) -> Option<(&'static str, Option<&'tcx Guard<'tcx>>)> { match node { ( PatKind::TupleStruct(ref path_left, patterns_left, _), @@ -310,7 +340,11 @@ fn get_ident(path: &QPath<'_>) -> Option { } } -fn get_good_method<'a>(cx: &LateContext<'_>, arms: &[Arm<'_>], path_left: &QPath<'_>) -> Option<&'a str> { +fn get_good_method<'tcx>( + cx: &LateContext<'_>, + arms: &'tcx [Arm<'tcx>], + path_left: &QPath<'_>, +) -> Option<(&'static str, Option<&'tcx Guard<'tcx>>)> { if let Some(name) = get_ident(path_left) { return match name.as_str() { "Ok" => { @@ -376,16 +410,16 @@ fn is_pat_variant(cx: &LateContext<'_>, pat: &Pat<'_>, path: &QPath<'_>, expecte } #[expect(clippy::too_many_arguments)] -fn find_good_method_for_match<'a>( +fn find_good_method_for_match<'a, 'tcx>( cx: &LateContext<'_>, - arms: &[Arm<'_>], + arms: &'tcx [Arm<'tcx>], path_left: &QPath<'_>, path_right: &QPath<'_>, expected_item_left: Item, expected_item_right: Item, should_be_left: &'a str, should_be_right: &'a str, -) -> Option<&'a str> { +) -> Option<(&'a str, Option<&'tcx Guard<'tcx>>)> { let first_pat = arms[0].pat; let second_pat = arms[1].pat; @@ -403,22 +437,22 @@ fn find_good_method_for_match<'a>( match body_node_pair { (ExprKind::Lit(lit_left), ExprKind::Lit(lit_right)) => match (&lit_left.node, &lit_right.node) { - (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left), - (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right), + (LitKind::Bool(true), LitKind::Bool(false)) => Some((should_be_left, arms[0].guard.as_ref())), + (LitKind::Bool(false), LitKind::Bool(true)) => Some((should_be_right, arms[1].guard.as_ref())), _ => None, }, _ => None, } } -fn find_good_method_for_matches_macro<'a>( +fn find_good_method_for_matches_macro<'a, 'tcx>( cx: &LateContext<'_>, - arms: &[Arm<'_>], + arms: &'tcx [Arm<'tcx>], path_left: &QPath<'_>, expected_item_left: Item, should_be_left: &'a str, should_be_right: &'a str, -) -> Option<&'a str> { +) -> Option<(&'a str, Option<&'tcx Guard<'tcx>>)> { let first_pat = arms[0].pat; let body_node_pair = if is_pat_variant(cx, first_pat, path_left, expected_item_left) { @@ -429,8 +463,8 @@ fn find_good_method_for_matches_macro<'a>( match body_node_pair { (ExprKind::Lit(lit_left), ExprKind::Lit(lit_right)) => match (&lit_left.node, &lit_right.node) { - (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left), - (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right), + (LitKind::Bool(true), LitKind::Bool(false)) => Some((should_be_left, arms[0].guard.as_ref())), + (LitKind::Bool(false), LitKind::Bool(true)) => Some((should_be_right, arms[1].guard.as_ref())), _ => None, }, _ => None, diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index 597a423b53779..c9eaa185acce8 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -1,7 +1,9 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::macros::{is_panic, root_macro_call}; use clippy_utils::source::{indent_of, reindent_multiline, snippet}; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_trait_method, path_to_local_id, peel_blocks, SpanlessEq}; +use clippy_utils::{higher, is_trait_method, path_to_local_id, peel_blocks, SpanlessEq}; +use hir::{Body, HirId, MatchSource, Pat}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; @@ -10,7 +12,7 @@ use rustc_hir::{Closure, Expr, ExprKind, PatKind, PathSegment, QPath, UnOp}; use rustc_lint::LateContext; use rustc_middle::ty::adjustment::Adjust; use rustc_span::source_map::Span; -use rustc_span::symbol::{sym, Symbol}; +use rustc_span::symbol::{sym, Ident, Symbol}; use std::borrow::Cow; use super::{MANUAL_FILTER_MAP, MANUAL_FIND_MAP, OPTION_FILTER_MAP}; @@ -48,6 +50,214 @@ fn is_option_filter_map(cx: &LateContext<'_>, filter_arg: &hir::Expr<'_>, map_ar is_method(cx, map_arg, sym::unwrap) && is_method(cx, filter_arg, sym!(is_some)) } +#[derive(Debug, Copy, Clone)] +enum OffendingFilterExpr<'tcx> { + /// `.filter(|opt| opt.is_some())` + IsSome { + /// The receiver expression + receiver: &'tcx Expr<'tcx>, + /// If `Some`, then this contains the span of an expression that possibly contains side + /// effects: `.filter(|opt| side_effect(opt).is_some())` + /// ^^^^^^^^^^^^^^^^ + /// + /// We will use this later for warning the user that the suggested fix may change + /// the behavior. + side_effect_expr_span: Option, + }, + /// `.filter(|res| res.is_ok())` + IsOk { + /// The receiver expression + receiver: &'tcx Expr<'tcx>, + /// See `IsSome` + side_effect_expr_span: Option, + }, + /// `.filter(|enum| matches!(enum, Enum::A(_)))` + Matches { + /// The DefId of the variant being matched + variant_def_id: hir::def_id::DefId, + }, +} + +#[derive(Debug)] +enum CalledMethod { + OptionIsSome, + ResultIsOk, +} + +/// The result of checking a `map` call, returned by `OffendingFilterExpr::check_map_call` +#[derive(Debug)] +enum CheckResult<'tcx> { + Method { + map_arg: &'tcx Expr<'tcx>, + /// The method that was called inside of `filter` + method: CalledMethod, + /// See `OffendingFilterExpr::IsSome` + side_effect_expr_span: Option, + }, + PatternMatching { + /// The span of the variant being matched + /// if let Some(s) = enum + /// ^^^^^^^ + variant_span: Span, + /// if let Some(s) = enum + /// ^ + variant_ident: Ident, + }, +} + +impl<'tcx> OffendingFilterExpr<'tcx> { + pub fn check_map_call( + &mut self, + cx: &LateContext<'tcx>, + map_body: &'tcx Body<'tcx>, + map_param_id: HirId, + filter_param_id: HirId, + is_filter_param_ref: bool, + ) -> Option> { + match *self { + OffendingFilterExpr::IsSome { + receiver, + side_effect_expr_span, + } + | OffendingFilterExpr::IsOk { + receiver, + side_effect_expr_span, + } => { + // check if closure ends with expect() or unwrap() + if let ExprKind::MethodCall(seg, map_arg, ..) = map_body.value.kind + && matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or) + // .map(|y| f(y).copied().unwrap()) + // ~~~~ + && let map_arg_peeled = match map_arg.kind { + ExprKind::MethodCall(method, original_arg, [], _) if acceptable_methods(method) => { + original_arg + }, + _ => map_arg, + } + // .map(|y| y[.acceptable_method()].unwrap()) + && let simple_equal = (path_to_local_id(receiver, filter_param_id) + && path_to_local_id(map_arg_peeled, map_param_id)) + && let eq_fallback = (|a: &Expr<'_>, b: &Expr<'_>| { + // in `filter(|x| ..)`, replace `*x` with `x` + let a_path = if_chain! { + if !is_filter_param_ref; + if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind; + then { expr_path } else { a } + }; + // let the filter closure arg and the map closure arg be equal + path_to_local_id(a_path, filter_param_id) + && path_to_local_id(b, map_param_id) + && cx.typeck_results().expr_ty_adjusted(a) == cx.typeck_results().expr_ty_adjusted(b) + }) + && (simple_equal + || SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(receiver, map_arg_peeled)) + { + Some(CheckResult::Method { + map_arg, + side_effect_expr_span, + method: match self { + OffendingFilterExpr::IsSome { .. } => CalledMethod::OptionIsSome, + OffendingFilterExpr::IsOk { .. } => CalledMethod::ResultIsOk, + OffendingFilterExpr::Matches { .. } => unreachable!("only IsSome and IsOk can get here"), + } + }) + } else { + None + } + }, + OffendingFilterExpr::Matches { variant_def_id } => { + let expr_uses_local = |pat: &Pat<'_>, expr: &Expr<'_>| { + if let PatKind::TupleStruct(QPath::Resolved(_, path), [subpat], _) = pat.kind + && let PatKind::Binding(_, local_id, ident, _) = subpat.kind + && path_to_local_id(expr.peel_blocks(), local_id) + && let Some(local_variant_def_id) = path.res.opt_def_id() + && local_variant_def_id == variant_def_id + { + Some((ident, pat.span)) + } else { + None + } + }; + + // look for: + // `if let Variant (v) = enum { v } else { unreachable!() }` + // ^^^^^^^ ^ ^^^^ ^^^^^^^^^^^^^^^^^^ + // variant_span variant_ident scrutinee else_ (blocks peeled later) + // OR + // `match enum { Variant (v) => v, _ => unreachable!() }` + // ^^^^ ^^^^^^^ ^ ^^^^^^^^^^^^^^ + // scrutinee variant_span variant_ident else_ + let (scrutinee, else_, variant_ident, variant_span) = + match higher::IfLetOrMatch::parse(cx, map_body.value) { + // For `if let` we want to check that the variant matching arm references the local created by its pattern + Some(higher::IfLetOrMatch::IfLet(sc, pat, then, Some(else_))) + if let Some((ident, span)) = expr_uses_local(pat, then) => + { + (sc, else_, ident, span) + }, + // For `match` we want to check that the "else" arm is the wildcard (`_`) pattern + // and that the variant matching arm references the local created by its pattern + Some(higher::IfLetOrMatch::Match(sc, [arm, wild_arm], MatchSource::Normal)) + if let PatKind::Wild = wild_arm.pat.kind + && let Some((ident, span)) = expr_uses_local(arm.pat, arm.body.peel_blocks()) => + { + (sc, wild_arm.body, ident, span) + }, + _ => return None, + }; + + if path_to_local_id(scrutinee, map_param_id) + // else branch should be a `panic!` or `unreachable!` macro call + && let Some(mac) = root_macro_call(else_.peel_blocks().span) + && (is_panic(cx, mac.def_id) || cx.tcx.opt_item_name(mac.def_id) == Some(sym::unreachable)) + { + Some(CheckResult::PatternMatching { variant_span, variant_ident }) + } else { + None + } + }, + } + } + + fn hir(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, filter_param_id: HirId) -> Option { + if let ExprKind::MethodCall(path, receiver, [], _) = expr.kind + && let Some(recv_ty) = cx.typeck_results().expr_ty(receiver).peel_refs().ty_adt_def() + { + // we still want to lint if the expression possibly contains side effects, + // *but* it can't be machine-applicable then, because that can change the behavior of the program: + // .filter(|x| effect(x).is_some()).map(|x| effect(x).unwrap()) + // vs. + // .filter_map(|x| effect(x)) + // + // the latter only calls `effect` once + let side_effect_expr_span = receiver.can_have_side_effects().then_some(receiver.span); + + if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did()) + && path.ident.name == sym!(is_some) + { + Some(Self::IsSome { receiver, side_effect_expr_span }) + } else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did()) + && path.ident.name == sym!(is_ok) + { + Some(Self::IsOk { receiver, side_effect_expr_span }) + } else { + None + } + } else if let Some(macro_call) = root_macro_call(expr.span) + && cx.tcx.get_diagnostic_name(macro_call.def_id) == Some(sym::matches_macro) + // we know for a fact that the wildcard pattern is the second arm + && let ExprKind::Match(scrutinee, [arm, _], _) = expr.kind + && path_to_local_id(scrutinee, filter_param_id) + && let PatKind::TupleStruct(QPath::Resolved(_, path), ..) = arm.pat.kind + && let Some(variant_def_id) = path.res.opt_def_id() + { + Some(OffendingFilterExpr::Matches { variant_def_id }) + } else { + None + } + } +} + /// is `filter(|x| x.is_some()).map(|x| x.unwrap())` fn is_filter_some_map_unwrap( cx: &LateContext<'_>, @@ -102,55 +312,18 @@ pub(super) fn check( } else { (filter_param.pat, false) }; - // closure ends with is_some() or is_ok() + if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind; - if let ExprKind::MethodCall(path, filter_arg, [], _) = filter_body.value.kind; - if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).peel_refs().ty_adt_def(); - if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::Option, opt_ty.did()) { - Some(false) - } else if cx.tcx.is_diagnostic_item(sym::Result, opt_ty.did()) { - Some(true) - } else { - None - }; - if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" }; + if let Some(mut offending_expr) = OffendingFilterExpr::hir(cx, filter_body.value, filter_param_id); - // ...map(|x| ...unwrap()) if let ExprKind::Closure(&Closure { body: map_body_id, .. }) = map_arg.kind; let map_body = cx.tcx.hir().body(map_body_id); if let [map_param] = map_body.params; if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind; - // closure ends with expect() or unwrap() - if let ExprKind::MethodCall(seg, map_arg, ..) = map_body.value.kind; - if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or); - - // .filter(..).map(|y| f(y).copied().unwrap()) - // ~~~~ - let map_arg_peeled = match map_arg.kind { - ExprKind::MethodCall(method, original_arg, [], _) if acceptable_methods(method) => { - original_arg - }, - _ => map_arg, - }; - // .filter(|x| x.is_some()).map(|y| y[.acceptable_method()].unwrap()) - let simple_equal = path_to_local_id(filter_arg, filter_param_id) - && path_to_local_id(map_arg_peeled, map_param_id); + if let Some(check_result) = + offending_expr.check_map_call(cx, map_body, map_param_id, filter_param_id, is_filter_param_ref); - let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { - // in `filter(|x| ..)`, replace `*x` with `x` - let a_path = if_chain! { - if !is_filter_param_ref; - if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind; - then { expr_path } else { a } - }; - // let the filter closure arg and the map closure arg be equal - path_to_local_id(a_path, filter_param_id) - && path_to_local_id(b, map_param_id) - && cx.typeck_results().expr_ty_adjusted(a) == cx.typeck_results().expr_ty_adjusted(b) - }; - - if simple_equal || SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg_peeled); then { let span = filter_span.with_hi(expr.span.hi()); let (filter_name, lint) = if is_find { @@ -159,22 +332,53 @@ pub(super) fn check( ("filter", MANUAL_FILTER_MAP) }; let msg = format!("`{filter_name}(..).map(..)` can be simplified as `{filter_name}_map(..)`"); - let (to_opt, deref) = if is_result { - (".ok()", String::new()) - } else { - let derefs = cx.typeck_results() - .expr_adjustments(map_arg) - .iter() - .filter(|adj| matches!(adj.kind, Adjust::Deref(_))) - .count(); - ("", "*".repeat(derefs)) + let (sugg, note_and_span, applicability) = match check_result { + CheckResult::Method { map_arg, method, side_effect_expr_span } => { + let (to_opt, deref) = match method { + CalledMethod::ResultIsOk => (".ok()", String::new()), + CalledMethod::OptionIsSome => { + let derefs = cx.typeck_results() + .expr_adjustments(map_arg) + .iter() + .filter(|adj| matches!(adj.kind, Adjust::Deref(_))) + .count(); + + ("", "*".repeat(derefs)) + } + }; + + let sugg = format!( + "{filter_name}_map(|{map_param_ident}| {deref}{}{to_opt})", + snippet(cx, map_arg.span, ".."), + ); + let (note_and_span, applicability) = if let Some(span) = side_effect_expr_span { + let note = "the suggestion might change the behavior of the program when merging `filter` and `map`, \ + because this expression potentially contains side effects and will only execute once"; + + (Some((note, span)), Applicability::MaybeIncorrect) + } else { + (None, Applicability::MachineApplicable) + }; + + (sugg, note_and_span, applicability) + } + CheckResult::PatternMatching { variant_span, variant_ident } => { + let pat = snippet(cx, variant_span, ""); + + (format!("{filter_name}_map(|{map_param_ident}| match {map_param_ident} {{ \ + {pat} => Some({variant_ident}), \ + _ => None \ + }})"), None, Applicability::MachineApplicable) + } }; - let sugg = format!( - "{filter_name}_map(|{map_param_ident}| {deref}{}{to_opt})", - snippet(cx, map_arg.span, ".."), - ); - span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable); + span_lint_and_then(cx, lint, span, &msg, |diag| { + diag.span_suggestion(span, "try", sugg, applicability); + + if let Some((note, span)) = note_and_span { + diag.span_note(span, note); + } + }); } } } diff --git a/clippy_lints/src/methods/filter_map_bool_then.rs b/clippy_lints/src/methods/filter_map_bool_then.rs new file mode 100644 index 0000000000000..4aee22a4afc3b --- /dev/null +++ b/clippy_lints/src/methods/filter_map_bool_then.rs @@ -0,0 +1,45 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::paths::BOOL_THEN; +use clippy_utils::source::snippet_opt; +use clippy_utils::ty::is_copy; +use clippy_utils::{is_from_proc_macro, is_trait_method, match_def_path, peel_blocks}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_span::{sym, Span}; + +use super::FILTER_MAP_BOOL_THEN; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &Expr<'_>, call_span: Span) { + if !in_external_macro(cx.sess(), expr.span) + && is_trait_method(cx, expr, sym::Iterator) + && let ExprKind::Closure(closure) = arg.kind + && let body = cx.tcx.hir().body(closure.body) + && let value = peel_blocks(body.value) + // Indexing should be fine as `filter_map` always has 1 input, we unfortunately need both + // `inputs` and `params` here as we need both the type and the span + && let param_ty = closure.fn_decl.inputs[0] + && let param = body.params[0] + && is_copy(cx, cx.typeck_results().node_type(param_ty.hir_id).peel_refs()) + && let ExprKind::MethodCall(_, recv, [then_arg], _) = value.kind + && let ExprKind::Closure(then_closure) = then_arg.kind + && let then_body = peel_blocks(cx.tcx.hir().body(then_closure.body).value) + && let Some(def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id) + && match_def_path(cx, def_id, &BOOL_THEN) + && !is_from_proc_macro(cx, expr) + && let Some(param_snippet) = snippet_opt(cx, param.span) + && let Some(filter) = snippet_opt(cx, recv.span) + && let Some(map) = snippet_opt(cx, then_body.span) + { + span_lint_and_sugg( + cx, + FILTER_MAP_BOOL_THEN, + call_span, + "usage of `bool::then` in `filter_map`", + "use `filter` then `map` instead", + format!("filter(|&{param_snippet}| {filter}).map(|{param_snippet}| {map})"), + Applicability::MachineApplicable, + ); + } +} diff --git a/clippy_lints/src/methods/format_collect.rs b/clippy_lints/src/methods/format_collect.rs new file mode 100644 index 0000000000000..1f8863f852186 --- /dev/null +++ b/clippy_lints/src/methods/format_collect.rs @@ -0,0 +1,33 @@ +use super::FORMAT_COLLECT; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::{is_format_macro, root_macro_call_first_node}; +use clippy_utils::ty::is_type_lang_item; +use rustc_hir::{Expr, ExprKind, LangItem}; +use rustc_lint::LateContext; +use rustc_span::Span; + +/// Same as `peel_blocks` but only actually considers blocks that are not from an expansion. +/// This is needed because always calling `peel_blocks` would otherwise remove parts of the +/// `format!` macro, which would cause `root_macro_call_first_node` to return `None`. +fn peel_non_expn_blocks<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + match expr.kind { + ExprKind::Block(block, _) if !expr.span.from_expansion() => peel_non_expn_blocks(block.expr?), + _ => Some(expr), + } +} + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, map_arg: &Expr<'_>, map_span: Span) { + if is_type_lang_item(cx, cx.typeck_results().expr_ty(expr), LangItem::String) + && let ExprKind::Closure(closure) = map_arg.kind + && let body = cx.tcx.hir().body(closure.body) + && let Some(value) = peel_non_expn_blocks(body.value) + && let Some(mac) = root_macro_call_first_node(cx, value) + && is_format_macro(cx, mac.def_id) + { + span_lint_and_then(cx, FORMAT_COLLECT, expr.span, "use of `format!` to build up a string from an iterator", |diag| { + diag.span_help(map_span, "call `fold` instead") + .span_help(value.span.source_callsite(), "... and use the `write!` macro here") + .note("this can be written more efficiently by appending to a `String` directly"); + }); + } +} diff --git a/clippy_lints/src/methods/iter_skip_zero.rs b/clippy_lints/src/methods/iter_skip_zero.rs new file mode 100644 index 0000000000000..6b696b42a6931 --- /dev/null +++ b/clippy_lints/src/methods/iter_skip_zero.rs @@ -0,0 +1,34 @@ +use clippy_utils::consts::{constant, Constant}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::{is_from_proc_macro, is_trait_method}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::ITER_SKIP_ZERO; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg_expr: &Expr<'_>) { + if !expr.span.from_expansion() + && is_trait_method(cx, expr, sym::Iterator) + && let Some(arg) = constant(cx, cx.typeck_results(), arg_expr).and_then(|constant| { + if let Constant::Int(arg) = constant { + Some(arg) + } else { + None + } + }) + && arg == 0 + && !is_from_proc_macro(cx, expr) + { + span_lint_and_then(cx, ITER_SKIP_ZERO, arg_expr.span, "usage of `.skip(0)`", |diag| { + diag.span_suggestion( + arg_expr.span, + "if you meant to skip the first element, use", + "1", + Applicability::MaybeIncorrect, + ) + .note("this call to `skip` does nothing and is useless; remove it"); + }); + } +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 123ab520e4859..dd694ce7393e2 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -21,11 +21,13 @@ mod expect_used; mod extend_with_drain; mod filetype_is_file; mod filter_map; +mod filter_map_bool_then; mod filter_map_identity; mod filter_map_next; mod filter_next; mod flat_map_identity; mod flat_map_option; +mod format_collect; mod from_iter_instead_of_collect; mod get_first; mod get_last_with_len; @@ -44,6 +46,7 @@ mod iter_nth_zero; mod iter_on_single_or_empty_collections; mod iter_overeager_cloned; mod iter_skip_next; +mod iter_skip_zero; mod iter_with_drain; mod iterator_step_by_zero; mod manual_next_back; @@ -73,6 +76,7 @@ mod or_then_unwrap; mod path_buf_push_overwrite; mod range_zip_with_len; mod read_line_without_trim; +mod readonly_write_lock; mod repeat_once; mod search_is_some; mod seek_from_current; @@ -85,6 +89,7 @@ mod skip_while_next; mod stable_sort_primitive; mod str_splitn; mod string_extend_chars; +mod string_lit_chars_any; mod suspicious_command_arg_space; mod suspicious_map; mod suspicious_splitn; @@ -100,7 +105,6 @@ mod unnecessary_lazy_eval; mod unnecessary_literal_unwrap; mod unnecessary_sort_by; mod unnecessary_to_owned; -mod unwrap_or_else_default; mod unwrap_used; mod useless_asref; mod utils; @@ -114,7 +118,7 @@ use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy, is_type_diagnostic_item}; -use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, return_ty}; +use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty}; use if_chain::if_chain; use rustc_hir as hir; use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind}; @@ -473,29 +477,40 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for usage of `_.unwrap_or_else(Default::default)` on `Option` and - /// `Result` values. + /// Checks for usages of the following functions with an argument that constructs a default value + /// (e.g., `Default::default` or `String::new`): + /// - `unwrap_or` + /// - `unwrap_or_else` + /// - `or_insert` + /// - `or_insert_with` /// /// ### Why is this bad? - /// Readability, these can be written as `_.unwrap_or_default`, which is - /// simpler and more concise. + /// Readability. Using `unwrap_or_default` in place of `unwrap_or`/`unwrap_or_else`, or `or_default` + /// in place of `or_insert`/`or_insert_with`, is simpler and more concise. + /// + /// ### Known problems + /// In some cases, the argument of `unwrap_or`, etc. is needed for type inference. The lint uses a + /// heuristic to try to identify such cases. However, the heuristic can produce false negatives. /// /// ### Examples /// ```rust /// # let x = Some(1); - /// x.unwrap_or_else(Default::default); - /// x.unwrap_or_else(u32::default); + /// # let mut map = std::collections::HashMap::::new(); + /// x.unwrap_or(Default::default()); + /// map.entry(42).or_insert_with(String::new); /// ``` /// /// Use instead: /// ```rust /// # let x = Some(1); + /// # let mut map = std::collections::HashMap::::new(); /// x.unwrap_or_default(); + /// map.entry(42).or_default(); /// ``` #[clippy::version = "1.56.0"] - pub UNWRAP_OR_ELSE_DEFAULT, + pub UNWRAP_OR_DEFAULT, style, - "using `.unwrap_or_else(Default::default)`, which is more succinctly expressed as `.unwrap_or_default()`" + "using `.unwrap_or`, etc. with an argument that constructs a default value" } declare_clippy_lint! { @@ -3378,6 +3393,152 @@ declare_clippy_lint! { "calling `Stdin::read_line`, then trying to parse it without first trimming" } +declare_clippy_lint! { + /// ### What it does + /// Checks for `.chars().any(|i| i == c)`. + /// + /// ### Why is this bad? + /// It's significantly slower than using a pattern instead, like + /// `matches!(c, '\\' | '.' | '+')`. + /// + /// Despite this being faster, this is not `perf` as this is pretty common, and is a rather nice + /// way to check if a `char` is any in a set. In any case, this `restriction` lint is available + /// for situations where that additional performance is absolutely necessary. + /// + /// ### Example + /// ```rust + /// # let c = 'c'; + /// "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == c); + /// ``` + /// Use instead: + /// ```rust + /// # let c = 'c'; + /// matches!(c, '\\' | '.' | '+' | '*' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + /// ``` + #[clippy::version = "1.72.0"] + pub STRING_LIT_CHARS_ANY, + restriction, + "checks for `.chars().any(|i| i == c)`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.map(|_| format!(..)).collect::()`. + /// + /// ### Why is this bad? + /// This allocates a new string for every element in the iterator. + /// This can be done more efficiently by creating the `String` once and appending to it in `Iterator::fold`, + /// using either the `write!` macro which supports exactly the same syntax as the `format!` macro, + /// or concatenating with `+` in case the iterator yields `&str`/`String`. + /// + /// Note also that `write!`-ing into a `String` can never fail, despite the return type of `write!` being `std::fmt::Result`, + /// so it can be safely ignored or unwrapped. + /// + /// ### Example + /// ```rust + /// fn hex_encode(bytes: &[u8]) -> String { + /// bytes.iter().map(|b| format!("{b:02X}")).collect() + /// } + /// ``` + /// Use instead: + /// ```rust + /// use std::fmt::Write; + /// fn hex_encode(bytes: &[u8]) -> String { + /// bytes.iter().fold(String::new(), |mut output, b| { + /// let _ = write!(output, "{b:02X}"); + /// output + /// }) + /// } + /// ``` + #[clippy::version = "1.72.0"] + pub FORMAT_COLLECT, + perf, + "`format!`ing every element in a collection, then collecting the strings into a new `String`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.skip(0)` on iterators. + /// + /// ### Why is this bad? + /// This was likely intended to be `.skip(1)` to skip the first element, as `.skip(0)` does + /// nothing. If not, the call should be removed. + /// + /// ### Example + /// ```rust + /// let v = vec![1, 2, 3]; + /// let x = v.iter().skip(0).collect::>(); + /// let y = v.iter().collect::>(); + /// assert_eq!(x, y); + /// ``` + #[clippy::version = "1.72.0"] + pub ITER_SKIP_ZERO, + correctness, + "disallows `.skip(0)`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `bool::then` in `Iterator::filter_map`. + /// + /// ### Why is this bad? + /// This can be written with `filter` then `map` instead, which would reduce nesting and + /// separates the filtering from the transformation phase. This comes with no cost to + /// performance and is just cleaner. + /// + /// ### Limitations + /// Does not lint `bool::then_some`, as it eagerly evaluates its arguments rather than lazily. + /// This can create differing behavior, so better safe than sorry. + /// + /// ### Example + /// ```rust + /// # fn really_expensive_fn(i: i32) -> i32 { i } + /// # let v = vec![]; + /// _ = v.into_iter().filter_map(|i| (i % 2 == 0).then(|| really_expensive_fn(i))); + /// ``` + /// Use instead: + /// ```rust + /// # fn really_expensive_fn(i: i32) -> i32 { i } + /// # let v = vec![]; + /// _ = v.into_iter().filter(|i| i % 2 == 0).map(|i| really_expensive_fn(i)); + /// ``` + #[clippy::version = "1.72.0"] + pub FILTER_MAP_BOOL_THEN, + style, + "checks for usage of `bool::then` in `Iterator::filter_map`" +} + +declare_clippy_lint! { + /// ### What it does + /// Looks for calls to `RwLock::write` where the lock is only used for reading. + /// + /// ### Why is this bad? + /// The write portion of `RwLock` is exclusive, meaning that no other thread + /// can access the lock while this writer is active. + /// + /// ### Example + /// ```rust + /// use std::sync::RwLock; + /// fn assert_is_zero(lock: &RwLock) { + /// let num = lock.write().unwrap(); + /// assert_eq!(*num, 0); + /// } + /// ``` + /// + /// Use instead: + /// ```rust + /// use std::sync::RwLock; + /// fn assert_is_zero(lock: &RwLock) { + /// let num = lock.read().unwrap(); + /// assert_eq!(*num, 0); + /// } + /// ``` + #[clippy::version = "1.73.0"] + pub READONLY_WRITE_LOCK, + nursery, + "acquiring a write lock when a read lock would work" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -3408,7 +3569,7 @@ impl_lint_pass!(Methods => [ SHOULD_IMPLEMENT_TRAIT, WRONG_SELF_CONVENTION, OK_EXPECT, - UNWRAP_OR_ELSE_DEFAULT, + UNWRAP_OR_DEFAULT, MAP_UNWRAP_OR, RESULT_MAP_OR_INTO_OPTION, OPTION_MAP_OR_NONE, @@ -3512,6 +3673,11 @@ impl_lint_pass!(Methods => [ UNNECESSARY_LITERAL_UNWRAP, DRAIN_COLLECT, MANUAL_TRY_FOLD, + FORMAT_COLLECT, + STRING_LIT_CHARS_ANY, + ITER_SKIP_ZERO, + FILTER_MAP_BOOL_THEN, + READONLY_WRITE_LOCK ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -3666,8 +3832,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { then { let first_arg_span = first_arg_ty.span; let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty); - let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()) - .self_ty(); + let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty(); wrong_self_convention::check( cx, item.ident.name.as_str(), @@ -3684,8 +3849,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { if item.ident.name == sym::new; if let TraitItemKind::Fn(_, _) = item.kind; let ret_ty = return_ty(cx, item.owner_id); - let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()) - .self_ty(); + let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty(); if !ret_ty.contains(self_ty); then { @@ -3733,8 +3897,9 @@ impl Methods { Some((name @ ("cloned" | "copied"), recv2, [], _, _)) => { iter_cloned_collect::check(cx, name, expr, recv2); }, - Some(("map", m_recv, [m_arg], _, _)) => { + Some(("map", m_recv, [m_arg], m_ident_span, _)) => { map_collect_result_unit::check(cx, expr, m_recv, m_arg); + format_collect::check(cx, expr, m_arg, m_ident_span); }, Some(("take", take_self_arg, [take_arg], _, _)) => { if self.msrv.meets(msrvs::STR_REPEAT) { @@ -3790,6 +3955,7 @@ impl Methods { }, ("filter_map", [arg]) => { unnecessary_filter_map::check(cx, expr, arg, name); + filter_map_bool_then::check(cx, expr, arg, call_span); filter_map_identity::check(cx, expr, arg, span); }, ("find_map", [arg]) => { @@ -3833,7 +3999,16 @@ impl Methods { unnecessary_join::check(cx, expr, recv, join_arg, span); } }, - ("last", []) | ("skip", [_]) => { + ("skip", [arg]) => { + iter_skip_zero::check(cx, expr, arg); + + if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) { + if let ("cloned", []) = (name2, args2) { + iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); + } + } + } + ("last", []) => { if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) { if let ("cloned", []) = (name2, args2) { iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); @@ -3885,6 +4060,13 @@ impl Methods { } } }, + ("any", [arg]) if let ExprKind::Closure(arg) = arg.kind + && let body = cx.tcx.hir().body(arg.body) + && let [param] = body.params + && let Some(("chars", recv, _, _, _)) = method_call(recv) => + { + string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv); + } ("nth", [n_arg]) => match method_call(recv) { Some(("bytes", recv2, [], _, _)) => bytes_nth::check(cx, expr, recv2, n_arg), Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false), @@ -4027,7 +4209,6 @@ impl Methods { Some(("map", recv, [map_arg], _, _)) if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, &self.msrv) => {}, _ => { - unwrap_or_else_default::check(cx, expr, recv, u_arg); unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or"); }, } @@ -4040,6 +4221,9 @@ impl Methods { range_zip_with_len::check(cx, expr, iter_recv, arg); } }, + ("write", []) => { + readonly_write_lock::check(cx, expr, recv); + } _ => {}, } } diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index 9165c1248f0aa..8b2f57160af48 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -1,16 +1,17 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::eager_or_lazy::switch_to_lazy_eval; use clippy_utils::source::snippet_with_context; -use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; -use clippy_utils::{contains_return, is_trait_item, last_path_segment}; +use clippy_utils::ty::{expr_type_is_certain, implements_trait, is_type_diagnostic_item}; +use clippy_utils::{contains_return, is_default_equivalent, is_default_equivalent_call, last_path_segment}; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir as hir; use rustc_lint::LateContext; +use rustc_middle::ty; use rustc_span::source_map::Span; -use rustc_span::symbol::{kw, sym, Symbol}; +use rustc_span::symbol::{self, sym, Symbol}; +use {rustc_ast as ast, rustc_hir as hir}; -use super::OR_FUN_CALL; +use super::{OR_FUN_CALL, UNWRAP_OR_DEFAULT}; /// Checks for the `OR_FUN_CALL` lint. #[allow(clippy::too_many_lines)] @@ -24,53 +25,72 @@ pub(super) fn check<'tcx>( ) { /// Checks for `unwrap_or(T::new())`, `unwrap_or(T::default())`, /// `or_insert(T::new())` or `or_insert(T::default())`. + /// Similarly checks for `unwrap_or_else(T::new)`, `unwrap_or_else(T::default)`, + /// `or_insert_with(T::new)` or `or_insert_with(T::default)`. #[allow(clippy::too_many_arguments)] fn check_unwrap_or_default( cx: &LateContext<'_>, name: &str, + receiver: &hir::Expr<'_>, fun: &hir::Expr<'_>, - arg: &hir::Expr<'_>, - or_has_args: bool, + call_expr: Option<&hir::Expr<'_>>, span: Span, method_span: Span, ) -> bool { - let is_default_default = || is_trait_item(cx, fun, sym::Default); + if !expr_type_is_certain(cx, receiver) { + return false; + } - let implements_default = |arg, default_trait_id| { - let arg_ty = cx.typeck_results().expr_ty(arg); - implements_trait(cx, arg_ty, default_trait_id, &[]) + let is_new = |fun: &hir::Expr<'_>| { + if let hir::ExprKind::Path(ref qpath) = fun.kind { + let path = last_path_segment(qpath).ident.name; + matches!(path, sym::new) + } else { + false + } }; - if_chain! { - if !or_has_args; - if let Some(sugg) = match name { - "unwrap_or" => Some("unwrap_or_default"), - "or_insert" => Some("or_default"), - _ => None, - }; - if let hir::ExprKind::Path(ref qpath) = fun.kind; - if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default); - let path = last_path_segment(qpath).ident.name; - // needs to target Default::default in particular or be *::new and have a Default impl - // available - if (matches!(path, kw::Default) && is_default_default()) - || (matches!(path, sym::new) && implements_default(arg, default_trait_id)); - - then { - span_lint_and_sugg( - cx, - OR_FUN_CALL, - method_span.with_hi(span.hi()), - &format!("use of `{name}` followed by a call to `{path}`"), - "try", - format!("{sugg}()"), - Applicability::MachineApplicable, - ); - - true + let output_type_implements_default = |fun| { + let fun_ty = cx.typeck_results().expr_ty(fun); + if let ty::FnDef(def_id, args) = fun_ty.kind() { + let output_ty = cx.tcx.fn_sig(def_id).instantiate(cx.tcx, args).skip_binder().output(); + cx.tcx + .get_diagnostic_item(sym::Default) + .map_or(false, |default_trait_id| { + implements_trait(cx, output_ty, default_trait_id, &[]) + }) } else { false } + }; + + let sugg = match (name, call_expr.is_some()) { + ("unwrap_or", true) | ("unwrap_or_else", false) => "unwrap_or_default", + ("or_insert", true) | ("or_insert_with", false) => "or_default", + _ => return false, + }; + + // needs to target Default::default in particular or be *::new and have a Default impl + // available + if (is_new(fun) && output_type_implements_default(fun)) + || match call_expr { + Some(call_expr) => is_default_equivalent(cx, call_expr), + None => is_default_equivalent_call(cx, fun) || closure_body_returns_empty_to_string(cx, fun), + } + { + span_lint_and_sugg( + cx, + UNWRAP_OR_DEFAULT, + method_span.with_hi(span.hi()), + &format!("use of `{name}` to construct default value"), + "try", + format!("{sugg}()"), + Applicability::MachineApplicable, + ); + + true + } else { + false } } @@ -168,11 +188,16 @@ pub(super) fn check<'tcx>( match inner_arg.kind { hir::ExprKind::Call(fun, or_args) => { let or_has_args = !or_args.is_empty(); - if !check_unwrap_or_default(cx, name, fun, arg, or_has_args, expr.span, method_span) { + if or_has_args + || !check_unwrap_or_default(cx, name, receiver, fun, Some(inner_arg), expr.span, method_span) + { let fun_span = if or_has_args { None } else { Some(fun.span) }; check_general_case(cx, name, method_span, receiver, arg, None, expr.span, fun_span); } }, + hir::ExprKind::Path(..) | hir::ExprKind::Closure(..) => { + check_unwrap_or_default(cx, name, receiver, inner_arg, None, expr.span, method_span); + }, hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => { check_general_case(cx, name, method_span, receiver, arg, None, expr.span, None); }, @@ -189,3 +214,22 @@ pub(super) fn check<'tcx>( } } } + +fn closure_body_returns_empty_to_string(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> bool { + if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = e.kind { + let body = cx.tcx.hir().body(body); + + if body.params.is_empty() + && let hir::Expr{ kind, .. } = &body.value + && let hir::ExprKind::MethodCall(hir::PathSegment {ident, ..}, self_arg, _, _) = kind + && ident.name == sym::to_string + && let hir::Expr{ kind, .. } = self_arg + && let hir::ExprKind::Lit(lit) = kind + && let ast::LitKind::Str(symbol::kw::Empty, _) = lit.node + { + return true; + } + } + + false +} diff --git a/clippy_lints/src/methods/read_line_without_trim.rs b/clippy_lints/src/methods/read_line_without_trim.rs index 8add0656101e5..81f9e2a77fce7 100644 --- a/clippy_lints/src/methods/read_line_without_trim.rs +++ b/clippy_lints/src/methods/read_line_without_trim.rs @@ -35,8 +35,8 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr< && segment.ident.name == sym!(parse) && let parse_result_ty = cx.typeck_results().expr_ty(parent) && is_type_diagnostic_item(cx, parse_result_ty, sym::Result) - && let ty::Adt(_, substs) = parse_result_ty.kind() - && let Some(ok_ty) = substs[0].as_type() + && let ty::Adt(_, args) = parse_result_ty.kind() + && let Some(ok_ty) = args[0].as_type() && parse_fails_on_trailing_newline(ok_ty) { let local_snippet = snippet(cx, expr.span, ""); diff --git a/clippy_lints/src/methods/readonly_write_lock.rs b/clippy_lints/src/methods/readonly_write_lock.rs new file mode 100644 index 0000000000000..e3ec921da0ce4 --- /dev/null +++ b/clippy_lints/src/methods/readonly_write_lock.rs @@ -0,0 +1,52 @@ +use super::READONLY_WRITE_LOCK; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::mir::{enclosing_mir, visit_local_usage}; +use clippy_utils::source::snippet; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, Node}; +use rustc_lint::LateContext; +use rustc_middle::mir::{Location, START_BLOCK}; +use rustc_span::sym; + +fn is_unwrap_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let ExprKind::MethodCall(path, receiver, ..) = expr.kind + && path.ident.name == sym::unwrap + { + is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(receiver).peel_refs(), sym::Result) + } else { + false + } +} + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, receiver: &Expr<'_>) { + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(receiver).peel_refs(), sym::RwLock) + && let Node::Expr(unwrap_call_expr) = cx.tcx.hir().get_parent(expr.hir_id) + && is_unwrap_call(cx, unwrap_call_expr) + && let parent = cx.tcx.hir().get_parent(unwrap_call_expr.hir_id) + && let Node::Local(local) = parent + && let Some(mir) = enclosing_mir(cx.tcx, expr.hir_id) + && let Some((local, _)) = mir.local_decls.iter_enumerated().find(|(_, decl)| { + local.span.contains(decl.source_info.span) + }) + && let Some(usages) = visit_local_usage(&[local], mir, Location { + block: START_BLOCK, + statement_index: 0, + }) + && let [usage] = usages.as_slice() + { + let writer_never_mutated = usage.local_consume_or_mutate_locs.is_empty(); + + if writer_never_mutated { + span_lint_and_sugg( + cx, + READONLY_WRITE_LOCK, + expr.span, + "this write lock is used only for reading", + "consider using a read lock instead", + format!("{}.read()", snippet(cx, receiver.span, "")), + Applicability::MaybeIncorrect // write lock might be intentional for enforcing exclusiveness + ); + } + } +} diff --git a/clippy_lints/src/methods/string_lit_chars_any.rs b/clippy_lints/src/methods/string_lit_chars_any.rs new file mode 100644 index 0000000000000..70da6ad58bdcf --- /dev/null +++ b/clippy_lints/src/methods/string_lit_chars_any.rs @@ -0,0 +1,58 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::msrvs::{Msrv, MATCHES_MACRO}; +use clippy_utils::source::snippet_opt; +use clippy_utils::{is_from_proc_macro, is_trait_method, path_to_local}; +use itertools::Itertools; +use rustc_ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, Param, PatKind}; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::STRING_LIT_CHARS_ANY; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + recv: &Expr<'_>, + param: &'tcx Param<'tcx>, + body: &Expr<'_>, + msrv: &Msrv, +) { + if msrv.meets(MATCHES_MACRO) + && is_trait_method(cx, expr, sym::Iterator) + && let PatKind::Binding(_, arg, _, _) = param.pat.kind + && let ExprKind::Lit(lit_kind) = recv.kind + && let LitKind::Str(val, _) = lit_kind.node + && let ExprKind::Binary(kind, lhs, rhs) = body.kind + && let BinOpKind::Eq = kind.node + && let Some(lhs_path) = path_to_local(lhs) + && let Some(rhs_path) = path_to_local(rhs) + && let scrutinee = match (lhs_path == arg, rhs_path == arg) { + (true, false) => rhs, + (false, true) => lhs, + _ => return, + } + && !is_from_proc_macro(cx, expr) + && let Some(scrutinee_snip) = snippet_opt(cx, scrutinee.span) + { + // Normalize the char using `map` so `join` doesn't use `Display`, if we don't then + // something like `r"\"` will become `'\'`, which is of course invalid + let pat_snip = val.as_str().chars().map(|c| format!("{c:?}")).join(" | "); + + span_lint_and_then( + cx, + STRING_LIT_CHARS_ANY, + expr.span, + "usage of `.chars().any(...)` to check if a char matches any from a string literal", + |diag| { + diag.span_suggestion_verbose( + expr.span, + "use `matches!(...)` instead", + format!("matches!({scrutinee_snip}, {pat_snip})"), + Applicability::MachineApplicable, + ); + } + ); + } +} diff --git a/clippy_lints/src/methods/type_id_on_box.rs b/clippy_lints/src/methods/type_id_on_box.rs index 35137c97101e6..3404bdfe79beb 100644 --- a/clippy_lints/src/methods/type_id_on_box.rs +++ b/clippy_lints/src/methods/type_id_on_box.rs @@ -24,9 +24,9 @@ pub(super) fn check(cx: &LateContext<'_>, receiver: &Expr<'_>, call_span: Span) if let Some(Adjustment { target: recv_ty, .. }) = recv_adjusts.last() && let ty::Ref(_, ty, _) = recv_ty.kind() - && let ty::Adt(adt, substs) = ty.kind() + && let ty::Adt(adt, args) = ty.kind() && adt.is_box() - && is_dyn_any(cx, substs.type_at(0)) + && is_dyn_any(cx, args.type_at(0)) { span_lint_and_then( cx, diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs index cc64a2e794875..fabf3fa0c0c8b 100644 --- a/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -77,6 +77,16 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc } (true, true) }, + hir::ExprKind::MethodCall(segment, recv, [arg], _) => { + if segment.ident.name == sym!(then_some) + && cx.typeck_results().expr_ty(recv).is_bool() + && path_to_local_id(arg, arg_id) + { + (false, true) + } else { + (true, true) + } + }, hir::ExprKind::Block(block, _) => block .expr .as_ref() diff --git a/clippy_lints/src/methods/unnecessary_literal_unwrap.rs b/clippy_lints/src/methods/unnecessary_literal_unwrap.rs index 111bcaaecec37..937aac8d25ef0 100644 --- a/clippy_lints/src/methods/unnecessary_literal_unwrap.rs +++ b/clippy_lints/src/methods/unnecessary_literal_unwrap.rs @@ -3,6 +3,8 @@ use clippy_utils::{is_res_lang_ctor, last_path_segment, path_res, MaybePath}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_middle::ty::print::with_forced_trimmed_paths; use super::UNNECESSARY_LITERAL_UNWRAP; @@ -22,6 +24,7 @@ fn get_ty_from_args<'a>(args: Option<&'a [hir::GenericArg<'a>]>, index: usize) - } } +#[expect(clippy::too_many_lines)] pub(super) fn check( cx: &LateContext<'_>, expr: &hir::Expr<'_>, @@ -84,6 +87,34 @@ pub(super) fn check( } Some(suggs) }, + ("None", "unwrap_or_default", _) => { + let ty = cx.typeck_results().expr_ty(expr); + let default_ty_string = if let ty::Adt(def, ..) = ty.kind() { + with_forced_trimmed_paths!(format!("{}", cx.tcx.def_path_str(def.did()))) + } else { + "Default".to_string() + }; + Some(vec![(expr.span, format!("{default_ty_string}::default()"))]) + }, + ("None", "unwrap_or", _) => Some(vec![ + (expr.span.with_hi(args[0].span.lo()), String::new()), + (expr.span.with_lo(args[0].span.hi()), String::new()), + ]), + ("None", "unwrap_or_else", _) => match args[0].kind { + hir::ExprKind::Closure(hir::Closure { + fn_decl: + hir::FnDecl { + output: hir::FnRetTy::DefaultReturn(span) | hir::FnRetTy::Return(hir::Ty { span, .. }), + .. + }, + .. + }) => Some(vec![ + (expr.span.with_hi(span.hi()), String::new()), + (expr.span.with_lo(args[0].span.hi()), String::new()), + ]), + _ => None, + }, + _ if call_args.is_empty() => None, (_, _, Some(_)) => None, ("Ok", "unwrap_err", None) | ("Err", "unwrap", None) => Some(vec![ ( diff --git a/clippy_lints/src/methods/unwrap_or_else_default.rs b/clippy_lints/src/methods/unwrap_or_else_default.rs deleted file mode 100644 index 474a33b67e1c6..0000000000000 --- a/clippy_lints/src/methods/unwrap_or_else_default.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! Lint for `some_result_or_option.unwrap_or_else(Default::default)` - -use super::UNWRAP_OR_ELSE_DEFAULT; -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_default_equivalent_call; -use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_diagnostic_item; -use rustc_ast::ast::LitKind; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_lint::LateContext; -use rustc_span::{sym, symbol}; - -pub(super) fn check<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - recv: &'tcx hir::Expr<'_>, - u_arg: &'tcx hir::Expr<'_>, -) { - // something.unwrap_or_else(Default::default) - // ^^^^^^^^^- recv ^^^^^^^^^^^^^^^^- u_arg - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- expr - let recv_ty = cx.typeck_results().expr_ty(recv); - let is_option = is_type_diagnostic_item(cx, recv_ty, sym::Option); - let is_result = is_type_diagnostic_item(cx, recv_ty, sym::Result); - - if_chain! { - if is_option || is_result; - if closure_body_returns_empty_to_string(cx, u_arg) || is_default_equivalent_call(cx, u_arg); - then { - let mut applicability = Applicability::MachineApplicable; - - span_lint_and_sugg( - cx, - UNWRAP_OR_ELSE_DEFAULT, - expr.span, - "use of `.unwrap_or_else(..)` to construct default value", - "try", - format!( - "{}.unwrap_or_default()", - snippet_with_applicability(cx, recv.span, "..", &mut applicability) - ), - applicability, - ); - } - } -} - -fn closure_body_returns_empty_to_string(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> bool { - if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = e.kind { - let body = cx.tcx.hir().body(body); - - if body.params.is_empty() - && let hir::Expr{ kind, .. } = &body.value - && let hir::ExprKind::MethodCall(hir::PathSegment {ident, ..}, self_arg, _, _) = kind - && ident == &symbol::Ident::from_str("to_string") - && let hir::Expr{ kind, .. } = self_arg - && let hir::ExprKind::Lit(lit) = kind - && let LitKind::Str(symbol::kw::Empty, _) = lit.node - { - return true; - } - } - - false -} diff --git a/clippy_lints/src/min_ident_chars.rs b/clippy_lints/src/min_ident_chars.rs index 2a60f2faca0ae..c79a1a7b9d428 100644 --- a/clippy_lints/src/min_ident_chars.rs +++ b/clippy_lints/src/min_ident_chars.rs @@ -129,6 +129,14 @@ impl Visitor<'_> for IdentVisitor<'_, '_> { return; } + // `struct Array([T; N])` + // ^ + if let Node::GenericParam(generic_param) = node + && let GenericParamKind::Const { .. } = generic_param.kind + { + return; + } + if is_from_proc_macro(cx, &ident) { return; } diff --git a/clippy_lints/src/needless_pass_by_ref_mut.rs b/clippy_lints/src/needless_pass_by_ref_mut.rs index d323a16c2a736..c634de960d137 100644 --- a/clippy_lints/src/needless_pass_by_ref_mut.rs +++ b/clippy_lints/src/needless_pass_by_ref_mut.rs @@ -1,16 +1,18 @@ use super::needless_pass_by_value::requires_exact_signature; -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::snippet; -use clippy_utils::{is_from_proc_macro, is_self}; -use if_chain::if_chain; +use clippy_utils::{get_parent_node, inherits_cfg, is_from_proc_macro, is_self}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_errors::Applicability; -use rustc_hir::intravisit::FnKind; -use rustc_hir::{Body, FnDecl, HirId, HirIdMap, HirIdSet, Impl, ItemKind, Mutability, Node, PatKind}; +use rustc_hir::intravisit::{walk_qpath, FnKind, Visitor}; +use rustc_hir::{Body, ExprKind, FnDecl, HirId, HirIdMap, HirIdSet, Impl, ItemKind, Mutability, Node, PatKind, QPath}; use rustc_hir_typeck::expr_use_visitor as euv; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::associated_body; +use rustc_middle::hir::nested_filter::OnlyBodies; use rustc_middle::mir::FakeReadCause; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::{self, Ty, UpvarId, UpvarPath}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::def_id::LocalDefId; use rustc_span::symbol::kw; @@ -46,20 +48,24 @@ declare_clippy_lint! { "using a `&mut` argument when it's not mutated" } -#[derive(Copy, Clone)] -pub struct NeedlessPassByRefMut { +#[derive(Clone)] +pub struct NeedlessPassByRefMut<'tcx> { avoid_breaking_exported_api: bool, + used_fn_def_ids: FxHashSet, + fn_def_ids_to_maybe_unused_mut: FxIndexMap>>, } -impl NeedlessPassByRefMut { +impl NeedlessPassByRefMut<'_> { pub fn new(avoid_breaking_exported_api: bool) -> Self { Self { avoid_breaking_exported_api, + used_fn_def_ids: FxHashSet::default(), + fn_def_ids_to_maybe_unused_mut: FxIndexMap::default(), } } } -impl_lint_pass!(NeedlessPassByRefMut => [NEEDLESS_PASS_BY_REF_MUT]); +impl_lint_pass!(NeedlessPassByRefMut<'_> => [NEEDLESS_PASS_BY_REF_MUT]); fn should_skip<'tcx>( cx: &LateContext<'tcx>, @@ -87,12 +93,12 @@ fn should_skip<'tcx>( is_from_proc_macro(cx, &input) } -impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut { +impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { fn check_fn( &mut self, cx: &LateContext<'tcx>, kind: FnKind<'tcx>, - decl: &'tcx FnDecl<'_>, + decl: &'tcx FnDecl<'tcx>, body: &'tcx Body<'_>, span: Span, fn_def_id: LocalDefId, @@ -102,17 +108,17 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut { } let hir_id = cx.tcx.hir().local_def_id_to_hir_id(fn_def_id); - - match kind { + let is_async = match kind { FnKind::ItemFn(.., header) => { let attrs = cx.tcx.hir().attrs(hir_id); if header.abi != Abi::Rust || requires_exact_signature(attrs) { return; } + header.is_async() }, - FnKind::Method(..) => (), + FnKind::Method(.., sig) => sig.header.is_async(), FnKind::Closure => return, - } + }; // Exclude non-inherent impls if let Some(Node::Item(item)) = cx.tcx.hir().find_parent(hir_id) { @@ -128,63 +134,89 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut { let fn_sig = cx.tcx.liberate_late_bound_regions(fn_def_id.to_def_id(), fn_sig); // If there are no `&mut` argument, no need to go any further. - if !decl + let mut it = decl .inputs .iter() .zip(fn_sig.inputs()) .zip(body.params) - .any(|((&input, &ty), arg)| !should_skip(cx, input, ty, arg)) - { + .filter(|((&input, &ty), arg)| !should_skip(cx, input, ty, arg)) + .peekable(); + if it.peek().is_none() { return; } - // Collect variables mutably used and spans which will need dereferencings from the // function body. let MutablyUsedVariablesCtxt { mutably_used_vars, .. } = { let mut ctx = MutablyUsedVariablesCtxt::default(); let infcx = cx.tcx.infer_ctxt().build(); euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body); + if is_async { + let closures = ctx.async_closures.clone(); + let hir = cx.tcx.hir(); + for closure in closures { + ctx.prev_bind = None; + ctx.prev_move_to_closure.clear(); + if let Some(body) = hir + .find_by_def_id(closure) + .and_then(associated_body) + .map(|(_, body_id)| hir.body(body_id)) + { + euv::ExprUseVisitor::new(&mut ctx, &infcx, closure, cx.param_env, cx.typeck_results()) + .consume_body(body); + } + } + } ctx }; - - let mut it = decl - .inputs - .iter() - .zip(fn_sig.inputs()) - .zip(body.params) - .filter(|((&input, &ty), arg)| !should_skip(cx, input, ty, arg)) - .peekable(); - if it.peek().is_none() { - return; - } - let show_semver_warning = self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(fn_def_id); for ((&input, &_), arg) in it { // Only take `&mut` arguments. - if_chain! { - if let PatKind::Binding(_, canonical_id, ..) = arg.pat.kind; - if !mutably_used_vars.contains(&canonical_id); - if let rustc_hir::TyKind::Ref(_, inner_ty) = input.kind; - then { - // If the argument is never used mutably, we emit the warning. - let sp = input.span; - span_lint_and_then( + if let PatKind::Binding(_, canonical_id, ..) = arg.pat.kind + && !mutably_used_vars.contains(&canonical_id) + { + self.fn_def_ids_to_maybe_unused_mut.entry(fn_def_id).or_default().push(input); + } + } + } + + fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { + cx.tcx.hir().visit_all_item_likes_in_crate(&mut FnNeedsMutVisitor { + cx, + used_fn_def_ids: &mut self.used_fn_def_ids, + }); + + for (fn_def_id, unused) in self + .fn_def_ids_to_maybe_unused_mut + .iter() + .filter(|(def_id, _)| !self.used_fn_def_ids.contains(def_id)) + { + let show_semver_warning = + self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(*fn_def_id); + + let mut is_cfged = None; + for input in unused { + // If the argument is never used mutably, we emit the warning. + let sp = input.span; + if let rustc_hir::TyKind::Ref(_, inner_ty) = input.kind { + let is_cfged = is_cfged.get_or_insert_with(|| inherits_cfg(cx.tcx, *fn_def_id)); + span_lint_hir_and_then( cx, NEEDLESS_PASS_BY_REF_MUT, + cx.tcx.hir().local_def_id_to_hir_id(*fn_def_id), sp, "this argument is a mutable reference, but not used mutably", |diag| { diag.span_suggestion( sp, "consider changing to".to_string(), - format!( - "&{}", - snippet(cx, cx.tcx.hir().span(inner_ty.ty.hir_id), "_"), - ), + format!("&{}", snippet(cx, cx.tcx.hir().span(inner_ty.ty.hir_id), "_"),), Applicability::Unspecified, ); if show_semver_warning { diag.warn("changing this function will impact semver compatibility"); } + if *is_cfged { + diag.note("this is cfg-gated and may require further changes"); + } }, ); } @@ -197,7 +229,9 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut { struct MutablyUsedVariablesCtxt { mutably_used_vars: HirIdSet, prev_bind: Option, + prev_move_to_closure: HirIdSet, aliases: HirIdMap, + async_closures: FxHashSet, } impl MutablyUsedVariablesCtxt { @@ -213,16 +247,27 @@ impl MutablyUsedVariablesCtxt { impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt { fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId) { if let euv::Place { - base: euv::PlaceBase::Local(vid), + base: + euv::PlaceBase::Local(vid) + | euv::PlaceBase::Upvar(UpvarId { + var_path: UpvarPath { hir_id: vid }, + .. + }), base_ty, .. } = &cmt.place { if let Some(bind_id) = self.prev_bind.take() { - self.aliases.insert(bind_id, *vid); - } else if matches!(base_ty.ref_mutability(), Some(Mutability::Mut)) { + if bind_id != *vid { + self.aliases.insert(bind_id, *vid); + } + } else if !self.prev_move_to_closure.contains(vid) + && matches!(base_ty.ref_mutability(), Some(Mutability::Mut)) + { self.add_mutably_used_var(*vid); } + self.prev_bind = None; + self.prev_move_to_closure.remove(vid); } } @@ -265,9 +310,73 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt { self.prev_bind = None; } - fn fake_read(&mut self, _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {} + fn fake_read( + &mut self, + cmt: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, + cause: FakeReadCause, + _id: HirId, + ) { + if let euv::Place { + base: + euv::PlaceBase::Upvar(UpvarId { + var_path: UpvarPath { hir_id: vid }, + .. + }), + .. + } = &cmt.place + { + if let FakeReadCause::ForLet(Some(inner)) = cause { + // Seems like we are inside an async function. We need to store the closure `DefId` + // to go through it afterwards. + self.async_closures.insert(inner); + self.aliases.insert(cmt.hir_id, *vid); + self.prev_move_to_closure.insert(*vid); + } + } + } fn bind(&mut self, _cmt: &euv::PlaceWithHirId<'tcx>, id: HirId) { self.prev_bind = Some(id); } } + +/// A final pass to check for paths referencing this function that require the argument to be +/// `&mut`, basically if the function is ever used as a `fn`-like argument. +struct FnNeedsMutVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + used_fn_def_ids: &'a mut FxHashSet, +} + +impl<'tcx> Visitor<'tcx> for FnNeedsMutVisitor<'_, 'tcx> { + type NestedFilter = OnlyBodies; + + fn nested_visit_map(&mut self) -> Self::Map { + self.cx.tcx.hir() + } + + fn visit_qpath(&mut self, qpath: &'tcx QPath<'tcx>, hir_id: HirId, _: Span) { + walk_qpath(self, qpath, hir_id); + + let Self { cx, used_fn_def_ids } = self; + + // #11182; do not lint if mutability is required elsewhere + if let Node::Expr(expr) = cx.tcx.hir().get(hir_id) + && let Some(parent) = get_parent_node(cx.tcx, expr.hir_id) + && let ty::FnDef(def_id, _) = cx.tcx.typeck(cx.tcx.hir().enclosing_body_owner(hir_id)).expr_ty(expr).kind() + && let Some(def_id) = def_id.as_local() + { + if let Node::Expr(e) = parent + && let ExprKind::Call(call, _) = e.kind + && call.hir_id == expr.hir_id + { + return; + } + + // We don't need to check each argument individually as you cannot coerce a function + // taking `&mut` -> `&`, for some reason, so if we've gotten this far we know it's + // passed as a `fn`-like argument (or is unified) and should ignore every "unused" + // argument entirely + used_fn_def_ids.insert(def_id); + } + } +} diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index 5e26601537f31..5ee26966fa716 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; use clippy_utils::ptr::get_spans; use clippy_utils::source::{snippet, snippet_opt}; use clippy_utils::ty::{ - implements_trait, implements_trait_with_env, is_copy, is_type_diagnostic_item, is_type_lang_item, + implements_trait, implements_trait_with_env_from_iter, is_copy, is_type_diagnostic_item, is_type_lang_item, }; use clippy_utils::{get_trait_def_id, is_self, paths}; use if_chain::if_chain; @@ -182,7 +182,13 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { if !ty.is_mutable_ptr(); if !is_copy(cx, ty); if ty.is_sized(cx.tcx, cx.param_env); - if !allowed_traits.iter().any(|&t| implements_trait_with_env(cx.tcx, cx.param_env, ty, t, [None])); + if !allowed_traits.iter().any(|&t| implements_trait_with_env_from_iter( + cx.tcx, + cx.param_env, + ty, + t, + [Option::>::None], + )); if !implements_borrow_trait; if !all_borrowable_trait; diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index 8bb2fa9258562..a70692d8ff864 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -154,7 +154,7 @@ fn is_value_unfrozen_raw<'tcx>( ty::Adt(def, ..) if def.is_union() => false, ty::Array(ty, _) => val.unwrap_branch().iter().any(|field| inner(cx, *field, ty)), ty::Adt(def, _) if def.is_union() => false, - ty::Adt(def, substs) if def.is_enum() => { + ty::Adt(def, args) if def.is_enum() => { let (&variant_index, fields) = val.unwrap_branch().split_first().unwrap(); let variant_index = VariantIdx::from_u32(variant_index.unwrap_leaf().try_to_u32().ok().unwrap()); fields @@ -164,19 +164,14 @@ fn is_value_unfrozen_raw<'tcx>( def.variants()[variant_index] .fields .iter() - .map(|field| field.ty(cx.tcx, substs)), + .map(|field| field.ty(cx.tcx, args)), ) .any(|(field, ty)| inner(cx, field, ty)) }, - ty::Adt(def, substs) => val + ty::Adt(def, args) => val .unwrap_branch() .iter() - .zip( - def.non_enum_variant() - .fields - .iter() - .map(|field| field.ty(cx.tcx, substs)), - ) + .zip(def.non_enum_variant().fields.iter().map(|field| field.ty(cx.tcx, args))) .any(|(field, ty)| inner(cx, *field, ty)), ty::Tuple(tys) => val .unwrap_branch() diff --git a/clippy_lints/src/operators/arithmetic_side_effects.rs b/clippy_lints/src/operators/arithmetic_side_effects.rs index 35dd8fabe6e60..f9108145cdb6d 100644 --- a/clippy_lints/src/operators/arithmetic_side_effects.rs +++ b/clippy_lints/src/operators/arithmetic_side_effects.rs @@ -1,7 +1,7 @@ use super::ARITHMETIC_SIDE_EFFECTS; use clippy_utils::consts::{constant, constant_simple, Constant}; use clippy_utils::diagnostics::span_lint; -use clippy_utils::{is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary}; +use clippy_utils::{expr_or_init, is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::Ty; @@ -138,8 +138,10 @@ impl ArithmeticSideEffects { ) { return; }; - let (actual_lhs, lhs_ref_counter) = peel_hir_expr_refs(lhs); - let (actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs); + let (mut actual_lhs, lhs_ref_counter) = peel_hir_expr_refs(lhs); + let (mut actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs); + actual_lhs = expr_or_init(cx, actual_lhs); + actual_rhs = expr_or_init(cx, actual_rhs); let lhs_ty = cx.typeck_results().expr_ty(actual_lhs).peel_refs(); let rhs_ty = cx.typeck_results().expr_ty(actual_rhs).peel_refs(); if self.has_allowed_binary(lhs_ty, rhs_ty) { diff --git a/clippy_lints/src/option_env_unwrap.rs b/clippy_lints/src/option_env_unwrap.rs index 377bddeaa5fea..9c7f7e1cd7f8e 100644 --- a/clippy_lints/src/option_env_unwrap.rs +++ b/clippy_lints/src/option_env_unwrap.rs @@ -1,10 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::is_direct_expn_of; -use if_chain::if_chain; use rustc_ast::ast::{Expr, ExprKind, MethodCall}; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::sym; +use rustc_span::{sym, Span}; declare_clippy_lint! { /// ### What it does @@ -36,21 +35,27 @@ declare_lint_pass!(OptionEnvUnwrap => [OPTION_ENV_UNWRAP]); impl EarlyLintPass for OptionEnvUnwrap { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { - if_chain! { - if let ExprKind::MethodCall(box MethodCall { seg, receiver, .. }) = &expr.kind; - if matches!(seg.ident.name, sym::expect | sym::unwrap); - if let ExprKind::Call(caller, _) = &receiver.kind; - if is_direct_expn_of(caller.span, "option_env").is_some(); - then { - span_lint_and_help( - cx, - OPTION_ENV_UNWRAP, - expr.span, - "this will panic at run-time if the environment variable doesn't exist at compile-time", - None, - "consider using the `env!` macro instead" - ); - } + fn lint(cx: &EarlyContext<'_>, span: Span) { + span_lint_and_help( + cx, + OPTION_ENV_UNWRAP, + span, + "this will panic at run-time if the environment variable doesn't exist at compile-time", + None, + "consider using the `env!` macro instead", + ); } + + if let ExprKind::MethodCall(box MethodCall { seg, receiver, .. }) = &expr.kind && + matches!(seg.ident.name, sym::expect | sym::unwrap) { + if let ExprKind::Call(caller, _) = &receiver.kind && + // If it exists, it will be ::core::option::Option::Some("").unwrap() (A method call in the HIR) + is_direct_expn_of(caller.span, "option_env").is_some() { + lint(cx, expr.span); + } else if let ExprKind::Path(_, caller) = &receiver.kind && // If it doesn't exist, it will be ::core::option::Option::None::<&'static str>.unwrap() (A path in the HIR) + is_direct_expn_of(caller.span, "option_env").is_some() { + lint(cx, expr.span); + } + } } } diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 264c38df11f51..42299d8d42f54 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -26,6 +26,7 @@ use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use rustc_span::sym; use rustc_span::symbol::Symbol; +use rustc_target::spec::abi::Abi; use rustc_trait_selection::infer::InferCtxtExt as _; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use std::{fmt, iter}; @@ -163,6 +164,12 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { } check_mut_from_ref(cx, sig, None); + + if !matches!(sig.header.abi, Abi::Rust) { + // Ignore `extern` functions with non-Rust calling conventions + return; + } + for arg in check_fn_args( cx, cx.tcx @@ -222,6 +229,12 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { }; check_mut_from_ref(cx, sig, Some(body)); + + if !matches!(sig.header.abi, Abi::Rust) { + // Ignore `extern` functions with non-Rust calling conventions + return; + } + let decl = sig.decl; let sig = cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder(); let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, &decl.output, body.params) diff --git a/clippy_lints/src/redundant_locals.rs b/clippy_lints/src/redundant_locals.rs new file mode 100644 index 0000000000000..140ae837a1723 --- /dev/null +++ b/clippy_lints/src/redundant_locals.rs @@ -0,0 +1,103 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::is_from_proc_macro; +use clippy_utils::ty::needs_ordered_drop; +use rustc_hir::def::Res; +use rustc_hir::{BindingAnnotation, ByRef, Expr, ExprKind, HirId, Local, Node, Pat, PatKind, QPath}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::Ident; + +declare_clippy_lint! { + /// ### What it does + /// Checks for redundant redefinitions of local bindings. + /// + /// ### Why is this bad? + /// Redundant redefinitions of local bindings do not change behavior and are likely to be unintended. + /// + /// Note that although these bindings do not affect your code's meaning, they _may_ affect `rustc`'s stack allocation. + /// + /// ### Example + /// ```rust + /// let a = 0; + /// let a = a; + /// + /// fn foo(b: i32) { + /// let b = b; + /// } + /// ``` + /// Use instead: + /// ```rust + /// let a = 0; + /// // no redefinition with the same name + /// + /// fn foo(b: i32) { + /// // no redefinition with the same name + /// } + /// ``` + #[clippy::version = "1.72.0"] + pub REDUNDANT_LOCALS, + correctness, + "redundant redefinition of a local binding" +} +declare_lint_pass!(RedundantLocals => [REDUNDANT_LOCALS]); + +impl<'tcx> LateLintPass<'tcx> for RedundantLocals { + fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { + if_chain! { + // the pattern is a single by-value binding + if let PatKind::Binding(BindingAnnotation(ByRef::No, mutability), _, ident, None) = local.pat.kind; + // the binding is not type-ascribed + if local.ty.is_none(); + // the expression is a resolved path + if let Some(expr) = local.init; + if let ExprKind::Path(qpath @ QPath::Resolved(None, path)) = expr.kind; + // the path is a single segment equal to the local's name + if let [last_segment] = path.segments; + if last_segment.ident == ident; + // resolve the path to its defining binding pattern + if let Res::Local(binding_id) = cx.qpath_res(&qpath, expr.hir_id); + if let Node::Pat(binding_pat) = cx.tcx.hir().get(binding_id); + // the previous binding has the same mutability + if find_binding(binding_pat, ident).unwrap().1 == mutability; + // the local does not affect the code's drop behavior + if !affects_drop_behavior(cx, binding_id, local.hir_id, expr); + // the local is user-controlled + if !in_external_macro(cx.sess(), local.span); + if !is_from_proc_macro(cx, expr); + then { + span_lint_and_help( + cx, + REDUNDANT_LOCALS, + vec![binding_pat.span, local.span], + "redundant redefinition of a binding", + None, + &format!("remove the redefinition of `{ident}`"), + ); + } + } + } +} + +/// Find the annotation of a binding introduced by a pattern, or `None` if it's not introduced. +fn find_binding(pat: &Pat<'_>, name: Ident) -> Option { + let mut ret = None; + + pat.each_binding_or_first(&mut |annotation, _, _, ident| { + if ident == name { + ret = Some(annotation); + } + }); + + ret +} + +/// Check if a rebinding of a local affects the code's drop behavior. +fn affects_drop_behavior<'tcx>(cx: &LateContext<'tcx>, bind: HirId, rebind: HirId, rebind_expr: &Expr<'tcx>) -> bool { + let hir = cx.tcx.hir(); + + // the rebinding is in a different scope than the original binding + // and the type of the binding cares about drop order + hir.get_enclosing_scope(bind) != hir.get_enclosing_scope(rebind) + && needs_ordered_drop(cx, cx.typeck_results().expr_ty(rebind_expr)) +} diff --git a/clippy_lints/src/redundant_static_lifetimes.rs b/clippy_lints/src/redundant_static_lifetimes.rs index 038dfe8e4803c..ed42a422b4b5a 100644 --- a/clippy_lints/src/redundant_static_lifetimes.rs +++ b/clippy_lints/src/redundant_static_lifetimes.rs @@ -5,6 +5,7 @@ use rustc_ast::ast::{ConstItem, Item, ItemKind, StaticItem, Ty, TyKind}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::symbol::kw; declare_clippy_lint! { /// ### What it does @@ -64,7 +65,7 @@ impl RedundantStaticLifetimes { if let Some(lifetime) = *optional_lifetime { match borrow_type.ty.kind { TyKind::Path(..) | TyKind::Slice(..) | TyKind::Array(..) | TyKind::Tup(..) => { - if lifetime.ident.name == rustc_span::symbol::kw::StaticLifetime { + if lifetime.ident.name == kw::StaticLifetime { let snip = snippet(cx, borrow_type.ty.span, ""); let sugg = format!("&{}{snip}", borrow_type.mutbl.prefix_str()); span_lint_and_then( diff --git a/clippy_lints/src/renamed_lints.rs b/clippy_lints/src/renamed_lints.rs index e532dd61a829b..49bdc6796049a 100644 --- a/clippy_lints/src/renamed_lints.rs +++ b/clippy_lints/src/renamed_lints.rs @@ -30,6 +30,7 @@ pub static RENAMED_LINTS: &[(&str, &str)] = &[ ("clippy::single_char_push_str", "clippy::single_char_add_str"), ("clippy::stutter", "clippy::module_name_repetitions"), ("clippy::to_string_in_display", "clippy::recursive_format_impl"), + ("clippy::unwrap_or_else_default", "clippy::unwrap_or_default"), ("clippy::zero_width_space", "clippy::invisible_characters"), ("clippy::cast_ref_to_mut", "invalid_reference_casting"), ("clippy::clone_double_ref", "suspicious_double_ref_op"), diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index 2a494ff1fa674..351bacf5691f3 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -1,12 +1,14 @@ -use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::source::{snippet_opt, snippet_with_context}; use clippy_utils::visitors::{for_each_expr_with_closures, Descend}; -use clippy_utils::{fn_def_id, path_to_local_id, span_find_starting_semi}; +use clippy_utils::{fn_def_id, is_from_proc_macro, path_to_local_id, span_find_starting_semi}; use core::ops::ControlFlow; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; -use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, LangItem, MatchSource, PatKind, QPath, StmtKind}; +use rustc_hir::{ + Block, Body, Expr, ExprKind, FnDecl, ItemKind, LangItem, MatchSource, OwnerNode, PatKind, QPath, Stmt, StmtKind, +}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::{self, GenericArgKind, Ty}; @@ -76,6 +78,46 @@ declare_clippy_lint! { "using a return statement like `return expr;` where an expression would suffice" } +declare_clippy_lint! { + /// ### What it does + /// Checks for return statements on `Err` paired with the `?` operator. + /// + /// ### Why is this bad? + /// The `return` is unnecessary. + /// + /// ### Example + /// ```rust,ignore + /// fn foo(x: usize) -> Result<(), Box> { + /// if x == 0 { + /// return Err(...)?; + /// } + /// Ok(()) + /// } + /// ``` + /// simplify to + /// ```rust,ignore + /// fn foo(x: usize) -> Result<(), Box> { + /// if x == 0 { + /// Err(...)?; + /// } + /// Ok(()) + /// } + /// ``` + /// if paired with `try_err`, use instead: + /// ```rust,ignore + /// fn foo(x: usize) -> Result<(), Box> { + /// if x == 0 { + /// return Err(...); + /// } + /// Ok(()) + /// } + /// ``` + #[clippy::version = "1.73.0"] + pub NEEDLESS_RETURN_WITH_QUESTION_MARK, + style, + "using a return statement like `return Err(expr)?;` where removing it would suffice" +} + #[derive(PartialEq, Eq)] enum RetReplacement<'tcx> { Empty, @@ -115,9 +157,35 @@ impl<'tcx> ToString for RetReplacement<'tcx> { } } -declare_lint_pass!(Return => [LET_AND_RETURN, NEEDLESS_RETURN]); +declare_lint_pass!(Return => [LET_AND_RETURN, NEEDLESS_RETURN, NEEDLESS_RETURN_WITH_QUESTION_MARK]); impl<'tcx> LateLintPass<'tcx> for Return { + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { + if !in_external_macro(cx.sess(), stmt.span) + && let StmtKind::Semi(expr) = stmt.kind + && let ExprKind::Ret(Some(ret)) = expr.kind + && let ExprKind::Match(.., MatchSource::TryDesugar) = ret.kind + // Ensure this is not the final stmt, otherwise removing it would cause a compile error + && let OwnerNode::Item(item) = cx.tcx.hir().owner(cx.tcx.hir().get_parent_item(expr.hir_id)) + && let ItemKind::Fn(_, _, body) = item.kind + && let block = cx.tcx.hir().body(body).value + && let ExprKind::Block(block, _) = block.kind + && let [.., final_stmt] = block.stmts + && final_stmt.hir_id != stmt.hir_id + && !is_from_proc_macro(cx, expr) + { + span_lint_and_sugg( + cx, + NEEDLESS_RETURN_WITH_QUESTION_MARK, + expr.span.until(ret.span), + "unneeded `return` statement with `?` operator", + "remove it", + String::new(), + Applicability::MachineApplicable, + ); + } + } + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { // we need both a let-binding stmt and an expr if_chain! { @@ -173,6 +241,10 @@ impl<'tcx> LateLintPass<'tcx> for Return { sp: Span, _: LocalDefId, ) { + if sp.from_expansion() { + return; + } + match kind { FnKind::Closure => { // when returning without value in closure, replace this `return` diff --git a/clippy_lints/src/semicolon_if_nothing_returned.rs b/clippy_lints/src/semicolon_if_nothing_returned.rs index 355f907e2577b..c9547cd95dca1 100644 --- a/clippy_lints/src/semicolon_if_nothing_returned.rs +++ b/clippy_lints/src/semicolon_if_nothing_returned.rs @@ -43,7 +43,7 @@ impl<'tcx> LateLintPass<'tcx> for SemicolonIfNothingReturned { if let Some(expr) = block.expr; let t_expr = cx.typeck_results().expr_ty(expr); if t_expr.is_unit(); - let mut app = Applicability::MaybeIncorrect; + let mut app = Applicability::MachineApplicable; if let snippet = snippet_with_context(cx, expr.span, block.span.ctxt(), "}", &mut app).0; if !snippet.ends_with('}') && !snippet.ends_with(';'); if cx.sess().source_map().is_multiline(block.span); diff --git a/clippy_lints/src/significant_drop_tightening.rs b/clippy_lints/src/significant_drop_tightening.rs index 3b282dbfa0480..4b248c9c7900b 100644 --- a/clippy_lints/src/significant_drop_tightening.rs +++ b/clippy_lints/src/significant_drop_tightening.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{indent_of, snippet}; -use clippy_utils::{expr_or_init, get_attr, path_to_local}; +use clippy_utils::{expr_or_init, get_attr, path_to_local, peel_hir_expr_unary}; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; @@ -235,7 +235,7 @@ impl<'ap, 'lc, 'others, 'stmt, 'tcx> StmtsChecker<'ap, 'lc, 'others, 'stmt, 'tcx fn manage_has_expensive_expr_after_last_attr(&mut self) { let has_expensive_stmt = match self.ap.curr_stmt.kind { - hir::StmtKind::Expr(expr) if !is_expensive_expr(expr) => false, + hir::StmtKind::Expr(expr) if is_inexpensive_expr(expr) => false, hir::StmtKind::Local(local) if let Some(expr) = local.init && let hir::ExprKind::Path(_) = expr.kind => false, _ => true @@ -330,13 +330,13 @@ impl<'ap, 'lc, 'others, 'stmt, 'tcx> Visitor<'tcx> for StmtsChecker<'ap, 'lc, 'o apa.last_method_span = span; } }, - hir::StmtKind::Semi(expr) => { - if has_drop(expr, &apa.first_bind_ident, self.cx) { + hir::StmtKind::Semi(semi_expr) => { + if has_drop(semi_expr, &apa.first_bind_ident, self.cx) { apa.has_expensive_expr_after_last_attr = false; apa.last_stmt_span = DUMMY_SP; return; } - if let hir::ExprKind::MethodCall(_, _, _, span) = expr.kind { + if let hir::ExprKind::MethodCall(_, _, _, span) = semi_expr.kind { apa.last_method_span = span; } }, @@ -434,16 +434,31 @@ fn has_drop(expr: &hir::Expr<'_>, first_bind_ident: &Ident, lcx: &LateContext<'_ && let Res::Def(DefKind::Fn, did) = fun_path.res && lcx.tcx.is_diagnostic_item(sym::mem_drop, did) && let [first_arg, ..] = args - && let hir::ExprKind::Path(hir::QPath::Resolved(_, arg_path)) = &first_arg.kind - && let [first_arg_ps, .. ] = arg_path.segments { - &first_arg_ps.ident == first_bind_ident - } - else { - false + let has_ident = |local_expr: &hir::Expr<'_>| { + if let hir::ExprKind::Path(hir::QPath::Resolved(_, arg_path)) = &local_expr.kind + && let [first_arg_ps, .. ] = arg_path.segments + && &first_arg_ps.ident == first_bind_ident + { + true + } + else { + false + } + }; + if has_ident(first_arg) { + return true; + } + if let hir::ExprKind::Tup(value) = &first_arg.kind && value.iter().any(has_ident) { + return true; + } } + false } -fn is_expensive_expr(expr: &hir::Expr<'_>) -> bool { - !matches!(expr.kind, hir::ExprKind::Path(_)) +fn is_inexpensive_expr(expr: &hir::Expr<'_>) -> bool { + let actual = peel_hir_expr_unary(expr).0; + let is_path = matches!(actual.kind, hir::ExprKind::Path(_)); + let is_lit = matches!(actual.kind, hir::ExprKind::Lit(_)); + is_path || is_lit } diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index 858135c8d4647..54a33eb2986d3 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -1,13 +1,13 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{ - get_enclosing_block, is_integer_literal, is_path_diagnostic_item, path_to_local, path_to_local_id, SpanlessEq, + get_enclosing_block, is_expr_path_def_path, is_integer_literal, is_path_diagnostic_item, path_to_local, + path_to_local_id, paths, SpanlessEq, }; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_block, walk_expr, walk_stmt, Visitor}; -use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, PatKind, QPath, Stmt, StmtKind}; +use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, PatKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::sym; @@ -60,7 +60,24 @@ struct VecAllocation<'tcx> { /// Reference to the expression used as argument on `with_capacity` call. This is used /// to only match slow zero-filling idioms of the same length than vector initialization. - len_expr: &'tcx Expr<'tcx>, + size_expr: InitializedSize<'tcx>, +} + +/// Initializer for the creation of the vector. +/// +/// When `Vec::with_capacity(size)` is found, the `size` expression will be in +/// `InitializedSize::Initialized`. +/// +/// Otherwise, for `Vec::new()` calls, there is no allocation initializer yet, so +/// `InitializedSize::Uninitialized` is used. +/// Later, when a call to `.resize(size, 0)` or similar is found, it's set +/// to `InitializedSize::Initialized(size)`. +/// +/// Since it will be set to `InitializedSize::Initialized(size)` when a slow initialization is +/// found, it is always safe to "unwrap" it at lint time. +enum InitializedSize<'tcx> { + Initialized(&'tcx Expr<'tcx>), + Uninitialized, } /// Type of slow initialization @@ -77,18 +94,14 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit { // Matches initialization on reassignments. For example: `vec = Vec::with_capacity(100)` if_chain! { if let ExprKind::Assign(left, right, _) = expr.kind; - - // Extract variable if let Some(local_id) = path_to_local(left); - - // Extract len argument - if let Some(len_arg) = Self::is_vec_with_capacity(cx, right); + if let Some(size_expr) = Self::as_vec_initializer(cx, right); then { let vi = VecAllocation { local_id, allocation_expr: right, - len_expr: len_arg, + size_expr, }; Self::search_initialization(cx, vi, expr.hir_id); @@ -98,17 +111,18 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit { fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { // Matches statements which initializes vectors. For example: `let mut vec = Vec::with_capacity(10)` + // or `Vec::new()` if_chain! { if let StmtKind::Local(local) = stmt.kind; if let PatKind::Binding(BindingAnnotation::MUT, local_id, _, None) = local.pat.kind; if let Some(init) = local.init; - if let Some(len_arg) = Self::is_vec_with_capacity(cx, init); + if let Some(size_expr) = Self::as_vec_initializer(cx, init); then { let vi = VecAllocation { local_id, allocation_expr: init, - len_expr: len_arg, + size_expr, }; Self::search_initialization(cx, vi, stmt.hir_id); @@ -118,19 +132,20 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit { } impl SlowVectorInit { - /// Checks if the given expression is `Vec::with_capacity(..)`. It will return the expression - /// of the first argument of `with_capacity` call if it matches or `None` if it does not. - fn is_vec_with_capacity<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { - if_chain! { - if let ExprKind::Call(func, [arg]) = expr.kind; - if let ExprKind::Path(QPath::TypeRelative(ty, name)) = func.kind; - if name.ident.as_str() == "with_capacity"; - if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec); - then { - Some(arg) - } else { - None - } + /// Looks for `Vec::with_capacity(size)` or `Vec::new()` calls and returns the initialized size, + /// if any. More specifically, it returns: + /// - `Some(InitializedSize::Initialized(size))` for `Vec::with_capacity(size)` + /// - `Some(InitializedSize::Uninitialized)` for `Vec::new()` + /// - `None` for other, unrelated kinds of expressions + fn as_vec_initializer<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option> { + if let ExprKind::Call(func, [len_expr]) = expr.kind + && is_expr_path_def_path(cx, func, &paths::VEC_WITH_CAPACITY) + { + Some(InitializedSize::Initialized(len_expr)) + } else if matches!(expr.kind, ExprKind::Call(func, _) if is_expr_path_def_path(cx, func, &paths::VEC_NEW)) { + Some(InitializedSize::Uninitialized) + } else { + None } } @@ -169,12 +184,19 @@ impl SlowVectorInit { } fn emit_lint(cx: &LateContext<'_>, slow_fill: &Expr<'_>, vec_alloc: &VecAllocation<'_>, msg: &str) { - let len_expr = Sugg::hir(cx, vec_alloc.len_expr, "len"); + let len_expr = Sugg::hir( + cx, + match vec_alloc.size_expr { + InitializedSize::Initialized(expr) => expr, + InitializedSize::Uninitialized => unreachable!("size expression must be set by this point"), + }, + "len", + ); span_lint_and_then(cx, SLOW_VECTOR_INITIALIZATION, slow_fill.span, msg, |diag| { diag.span_suggestion( vec_alloc.allocation_expr.span, - "consider replace allocation with", + "consider replacing this with", format!("vec![0; {len_expr}]"), Applicability::Unspecified, ); @@ -214,36 +236,45 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> { } /// Checks if the given expression is resizing a vector with 0 - fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'_>) { + fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'tcx>) { if self.initialization_found && let ExprKind::MethodCall(path, self_arg, [len_arg, fill_arg], _) = expr.kind && path_to_local_id(self_arg, self.vec_alloc.local_id) && path.ident.name == sym!(resize) // Check that is filled with 0 - && is_integer_literal(fill_arg, 0) { - // Check that len expression is equals to `with_capacity` expression - if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) { - self.slow_expression = Some(InitializationType::Resize(expr)); - } else if let ExprKind::MethodCall(path, ..) = len_arg.kind && path.ident.as_str() == "capacity" { - self.slow_expression = Some(InitializationType::Resize(expr)); - } + && is_integer_literal(fill_arg, 0) + { + let is_matching_resize = if let InitializedSize::Initialized(size_expr) = self.vec_alloc.size_expr { + // If we have a size expression, check that it is equal to what's passed to `resize` + SpanlessEq::new(self.cx).eq_expr(len_arg, size_expr) + || matches!(len_arg.kind, ExprKind::MethodCall(path, ..) if path.ident.as_str() == "capacity") + } else { + self.vec_alloc.size_expr = InitializedSize::Initialized(len_arg); + true + }; + + if is_matching_resize { + self.slow_expression = Some(InitializationType::Resize(expr)); } + } } /// Returns `true` if give expression is `repeat(0).take(...)` - fn is_repeat_take(&self, expr: &Expr<'_>) -> bool { + fn is_repeat_take(&mut self, expr: &'tcx Expr<'tcx>) -> bool { if_chain! { if let ExprKind::MethodCall(take_path, recv, [len_arg, ..], _) = expr.kind; if take_path.ident.name == sym!(take); // Check that take is applied to `repeat(0)` if self.is_repeat_zero(recv); then { - // Check that len expression is equals to `with_capacity` expression - if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) { - return true; - } else if let ExprKind::MethodCall(path, ..) = len_arg.kind && path.ident.as_str() == "capacity" { - return true; + if let InitializedSize::Initialized(size_expr) = self.vec_alloc.size_expr { + // Check that len expression is equals to `with_capacity` expression + return SpanlessEq::new(self.cx).eq_expr(len_arg, size_expr) + || matches!(len_arg.kind, ExprKind::MethodCall(path, ..) if path.ident.as_str() == "capacity") } + + self.vec_alloc.size_expr = InitializedSize::Initialized(len_arg); + return true; } } diff --git a/clippy_lints/src/tuple_array_conversions.rs b/clippy_lints/src/tuple_array_conversions.rs index 2e00ed042a8b6..7eec69820924d 100644 --- a/clippy_lints/src/tuple_array_conversions.rs +++ b/clippy_lints/src/tuple_array_conversions.rs @@ -1,21 +1,29 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::visitors::for_each_local_use_after_expr; use clippy_utils::{is_from_proc_macro, path_to_local}; +use itertools::Itertools; use rustc_ast::LitKind; -use rustc_hir::{Expr, ExprKind, HirId, Node, Pat}; +use rustc_hir::{Expr, ExprKind, Node, PatKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty; +use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use std::iter::once; +use std::ops::ControlFlow; declare_clippy_lint! { /// ### What it does /// Checks for tuple<=>array conversions that are not done with `.into()`. /// /// ### Why is this bad? - /// It may be unnecessary complexity. `.into()` works for converting tuples - /// <=> arrays of up to 12 elements and may convey intent more clearly. + /// It may be unnecessary complexity. `.into()` works for converting tuples<=> arrays of up to + /// 12 elements and conveys the intent more clearly, while also leaving less room for hard to + /// spot bugs! + /// + /// ### Known issues + /// The suggested code may hide potential asymmetry in some cases. See + /// [#11085](https://github.com/rust-lang/rust-clippy/issues/11085) for more info. /// /// ### Example /// ```rust,ignore @@ -29,7 +37,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.72.0"] pub TUPLE_ARRAY_CONVERSIONS, - pedantic, + nursery, "checks for tuple<=>array conversions that are not done with `.into()`" } impl_lint_pass!(TupleArrayConversions => [TUPLE_ARRAY_CONVERSIONS]); @@ -41,130 +49,152 @@ pub struct TupleArrayConversions { impl LateLintPass<'_> for TupleArrayConversions { fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - if !in_external_macro(cx.sess(), expr.span) && self.msrv.meets(msrvs::TUPLE_ARRAY_CONVERSIONS) { - match expr.kind { - ExprKind::Array(elements) if (1..=12).contains(&elements.len()) => check_array(cx, expr, elements), - ExprKind::Tup(elements) if (1..=12).contains(&elements.len()) => check_tuple(cx, expr, elements), - _ => {}, - } + if in_external_macro(cx.sess(), expr.span) || !self.msrv.meets(msrvs::TUPLE_ARRAY_CONVERSIONS) { + return; + } + + match expr.kind { + ExprKind::Array(elements) if (1..=12).contains(&elements.len()) => check_array(cx, expr, elements), + ExprKind::Tup(elements) if (1..=12).contains(&elements.len()) => check_tuple(cx, expr, elements), + _ => {}, } } extract_msrv_attr!(LateContext); } -#[expect( - clippy::blocks_in_if_conditions, - reason = "not a FP, but this is much easier to understand" -)] fn check_array<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, elements: &'tcx [Expr<'tcx>]) { - if should_lint( - cx, - elements, - // This is cursed. - Some, - |(first_id, local)| { - if let Node::Pat(pat) = local - && let parent = parent_pat(cx, pat) - && parent.hir_id == first_id - { - return matches!( - cx.typeck_results().pat_ty(parent).peel_refs().kind(), - ty::Tuple(len) if len.len() == elements.len() - ); - } - - false - }, - ) || should_lint( - cx, - elements, - |(i, expr)| { - if let ExprKind::Field(path, field) = expr.kind && field.as_str() == i.to_string() { - return Some((i, path)); - }; - - None - }, - |(first_id, local)| { - if let Node::Pat(pat) = local - && let parent = parent_pat(cx, pat) - && parent.hir_id == first_id - { - return matches!( - cx.typeck_results().pat_ty(parent).peel_refs().kind(), - ty::Tuple(len) if len.len() == elements.len() - ); - } + let (ty::Array(ty, _) | ty::Slice(ty)) = cx.typeck_results().expr_ty(expr).kind() else { + unreachable!("`expr` must be an array or slice due to `ExprKind::Array`"); + }; + + if let [first, ..] = elements + && let Some(locals) = (match first.kind { + ExprKind::Field(_, _) => elements + .iter() + .enumerate() + .map(|(i, f)| -> Option<&'tcx Expr<'tcx>> { + let ExprKind::Field(lhs, ident) = f.kind else { + return None; + }; + (ident.name.as_str() == i.to_string()).then_some(lhs) + }) + .collect::>>(), + ExprKind::Path(_) => Some(elements.iter().collect()), + _ => None, + }) + && all_bindings_are_for_conv(cx, &[*ty], expr, elements, &locals, ToType::Array) + && !is_from_proc_macro(cx, expr) + { + span_lint_and_help( + cx, + TUPLE_ARRAY_CONVERSIONS, + expr.span, + "it looks like you're trying to convert a tuple to an array", + None, + "use `.into()` instead, or `<[T; N]>::from` if type annotations are needed", + ); + } +} - false - }, - ) { - emit_lint(cx, expr, ToType::Array); +fn check_tuple<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, elements: &'tcx [Expr<'tcx>]) { + if let ty::Tuple(tys) = cx.typeck_results().expr_ty(expr).kind() + && let [first, ..] = elements + // Fix #11100 + && tys.iter().all_equal() + && let Some(locals) = (match first.kind { + ExprKind::Index(_, _) => elements + .iter() + .enumerate() + .map(|(i, i_expr)| -> Option<&'tcx Expr<'tcx>> { + if let ExprKind::Index(lhs, index) = i_expr.kind + && let ExprKind::Lit(lit) = index.kind + && let LitKind::Int(val, _) = lit.node + { + return (val == i as u128).then_some(lhs); + }; + + None + }) + .collect::>>(), + ExprKind::Path(_) => Some(elements.iter().collect()), + _ => None, + }) + && all_bindings_are_for_conv(cx, tys, expr, elements, &locals, ToType::Tuple) + && !is_from_proc_macro(cx, expr) + { + span_lint_and_help( + cx, + TUPLE_ARRAY_CONVERSIONS, + expr.span, + "it looks like you're trying to convert an array to a tuple", + None, + "use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed", + ); } } -#[expect( - clippy::blocks_in_if_conditions, - reason = "not a FP, but this is much easier to understand" -)] +/// Checks that every binding in `elements` comes from the same parent `Pat` with the kind if there +/// is a parent `Pat`. Returns false in any of the following cases: +/// * `kind` does not match `pat.kind` +/// * one or more elements in `elements` is not a binding +/// * one or more bindings does not have the same parent `Pat` +/// * one or more bindings are used after `expr` +/// * the bindings do not all have the same type #[expect(clippy::cast_possible_truncation)] -fn check_tuple<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, elements: &'tcx [Expr<'tcx>]) { - if should_lint(cx, elements, Some, |(first_id, local)| { - if let Node::Pat(pat) = local - && let parent = parent_pat(cx, pat) - && parent.hir_id == first_id - { - return matches!( - cx.typeck_results().pat_ty(parent).peel_refs().kind(), - ty::Array(_, len) if len.eval_target_usize(cx.tcx, cx.param_env) as usize == elements.len() - ); +fn all_bindings_are_for_conv<'tcx>( + cx: &LateContext<'tcx>, + final_tys: &[Ty<'tcx>], + expr: &Expr<'_>, + elements: &[Expr<'_>], + locals: &[&Expr<'_>], + kind: ToType, +) -> bool { + let Some(locals) = locals.iter().map(|e| path_to_local(e)).collect::>>() else { + return false; + }; + let Some(local_parents) = locals + .iter() + .map(|&l| cx.tcx.hir().find_parent(l)) + .collect::>>() + else { + return false; + }; + + local_parents + .iter() + .map(|node| match node { + Node::Pat(pat) => kind.eq(&pat.kind).then_some(pat.hir_id), + Node::Local(l) => Some(l.hir_id), + _ => None, + }) + .all_equal() + // Fix #11124, very convenient utils function! ❤️ + && locals + .iter() + .all(|&l| for_each_local_use_after_expr(cx, l, expr.hir_id, |_| ControlFlow::Break::<()>(())).is_continue()) + && local_parents.first().is_some_and(|node| { + let Some(ty) = match node { + Node::Pat(pat) => Some(pat.hir_id), + Node::Local(l) => Some(l.hir_id), + _ => None, } - - false - }) || should_lint( - cx, - elements, - |(i, expr)| { - if let ExprKind::Index(path, index) = expr.kind - && let ExprKind::Lit(lit) = index.kind - && let LitKind::Int(val, _) = lit.node - && val as usize == i - { - return Some((i, path)); + .map(|hir_id| cx.typeck_results().node_type(hir_id)) else { + return false; }; - - None - }, - |(first_id, local)| { - if let Node::Pat(pat) = local - && let parent = parent_pat(cx, pat) - && parent.hir_id == first_id - { - return matches!( - cx.typeck_results().pat_ty(parent).peel_refs().kind(), - ty::Array(_, len) if len.eval_target_usize(cx.tcx, cx.param_env) as usize == elements.len() - ); + match (kind, ty.kind()) { + // Ensure the final type and the original type have the same length, and that there + // is no implicit `&mut`<=>`&` anywhere (#11100). Bit ugly, I know, but it works. + (ToType::Array, ty::Tuple(tys)) => { + tys.len() == elements.len() && tys.iter().chain(final_tys.iter().copied()).all_equal() + }, + (ToType::Tuple, ty::Array(ty, len)) => { + len.eval_target_usize(cx.tcx, cx.param_env) as usize == elements.len() + && final_tys.iter().chain(once(ty)).all_equal() + }, + _ => false, } - - false - }, - ) { - emit_lint(cx, expr, ToType::Tuple); - } -} - -/// Walks up the `Pat` until it's reached the final containing `Pat`. -fn parent_pat<'tcx>(cx: &LateContext<'tcx>, start: &'tcx Pat<'tcx>) -> &'tcx Pat<'tcx> { - let mut end = start; - for (_, node) in cx.tcx.hir().parent_iter(start.hir_id) { - if let Node::Pat(pat) = node { - end = pat; - } else { - break; - } - } - end + }) } #[derive(Clone, Copy)] @@ -173,61 +203,11 @@ enum ToType { Tuple, } -impl ToType { - fn msg(self) -> &'static str { - match self { - ToType::Array => "it looks like you're trying to convert a tuple to an array", - ToType::Tuple => "it looks like you're trying to convert an array to a tuple", - } - } - - fn help(self) -> &'static str { +impl PartialEq> for ToType { + fn eq(&self, other: &PatKind<'_>) -> bool { match self { - ToType::Array => "use `.into()` instead, or `<[T; N]>::from` if type annotations are needed", - ToType::Tuple => "use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed", + ToType::Array => matches!(other, PatKind::Tuple(_, _)), + ToType::Tuple => matches!(other, PatKind::Slice(_, _, _)), } } } - -fn emit_lint<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, to_type: ToType) -> bool { - if !is_from_proc_macro(cx, expr) { - span_lint_and_help( - cx, - TUPLE_ARRAY_CONVERSIONS, - expr.span, - to_type.msg(), - None, - to_type.help(), - ); - - return true; - } - - false -} - -fn should_lint<'tcx>( - cx: &LateContext<'tcx>, - elements: &'tcx [Expr<'tcx>], - map: impl FnMut((usize, &'tcx Expr<'tcx>)) -> Option<(usize, &Expr<'_>)>, - predicate: impl FnMut((HirId, &Node<'tcx>)) -> bool, -) -> bool { - if let Some(elements) = elements - .iter() - .enumerate() - .map(map) - .collect::>>() - && let Some(locals) = elements - .iter() - .map(|(_, element)| path_to_local(element).and_then(|local| cx.tcx.hir().find(local))) - .collect::>>() - && let [first, rest @ ..] = &*locals - && let Node::Pat(first_pat) = first - && let parent = parent_pat(cx, first_pat).hir_id - && rest.iter().chain(once(first)).map(|i| (parent, i)).all(predicate) - { - return true; - } - - false -} diff --git a/clippy_lints/src/unused_async.rs b/clippy_lints/src/unused_async.rs index 5e42cf7e4f3f6..bc7c3897a6e8d 100644 --- a/clippy_lints/src/unused_async.rs +++ b/clippy_lints/src/unused_async.rs @@ -1,11 +1,12 @@ -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::is_def_id_trait_method; +use rustc_hir::def::DefKind; use rustc_hir::intravisit::{walk_body, walk_expr, walk_fn, FnKind, Visitor}; -use rustc_hir::{Body, Expr, ExprKind, FnDecl, YieldSource}; +use rustc_hir::{Body, Expr, ExprKind, FnDecl, Node, YieldSource}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::def_id::LocalDefId; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::def_id::{LocalDefId, LocalDefIdSet}; use rustc_span::Span; declare_clippy_lint! { @@ -38,7 +39,24 @@ declare_clippy_lint! { "finds async functions with no await statements" } -declare_lint_pass!(UnusedAsync => [UNUSED_ASYNC]); +#[derive(Default)] +pub struct UnusedAsync { + /// Keeps track of async functions used as values (i.e. path expressions to async functions that + /// are not immediately called) + async_fns_as_value: LocalDefIdSet, + /// Functions with unused `async`, linted post-crate after we've found all uses of local async + /// functions + unused_async_fns: Vec, +} + +#[derive(Copy, Clone)] +struct UnusedAsyncFn { + def_id: LocalDefId, + fn_span: Span, + await_in_async_block: Option, +} + +impl_lint_pass!(UnusedAsync => [UNUSED_ASYNC]); struct AsyncFnVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, @@ -101,24 +119,70 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync { }; walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), def_id); if !visitor.found_await { - span_lint_and_then( - cx, - UNUSED_ASYNC, - span, - "unused `async` for function with no await statements", - |diag| { - diag.help("consider removing the `async` from this function"); - - if let Some(span) = visitor.await_in_async_block { - diag.span_note( - span, - "`await` used in an async block, which does not require \ - the enclosing function to be `async`", - ); - } - }, - ); + // Don't lint just yet, but store the necessary information for later. + // The actual linting happens in `check_crate_post`, once we've found all + // uses of local async functions that do require asyncness to pass typeck + self.unused_async_fns.push(UnusedAsyncFn { + await_in_async_block: visitor.await_in_async_block, + fn_span: span, + def_id, + }); } } } + + fn check_path(&mut self, cx: &LateContext<'tcx>, path: &rustc_hir::Path<'tcx>, hir_id: rustc_hir::HirId) { + fn is_node_func_call(node: Node<'_>, expected_receiver: Span) -> bool { + matches!( + node, + Node::Expr(Expr { + kind: ExprKind::Call(Expr { span, .. }, _) | ExprKind::MethodCall(_, Expr { span, .. }, ..), + .. + }) if *span == expected_receiver + ) + } + + // Find paths to local async functions that aren't immediately called. + // E.g. `async fn f() {}; let x = f;` + // Depending on how `x` is used, f's asyncness might be required despite not having any `await` + // statements, so don't lint at all if there are any such paths. + if let Some(def_id) = path.res.opt_def_id() + && let Some(local_def_id) = def_id.as_local() + && let Some(DefKind::Fn) = cx.tcx.opt_def_kind(def_id) + && cx.tcx.asyncness(def_id).is_async() + && !is_node_func_call(cx.tcx.hir().get_parent(hir_id), path.span) + { + self.async_fns_as_value.insert(local_def_id); + } + } + + // After collecting all unused `async` and problematic paths to such functions, + // lint those unused ones that didn't have any path expressions to them. + fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { + let iter = self + .unused_async_fns + .iter() + .filter(|UnusedAsyncFn { def_id, .. }| (!self.async_fns_as_value.contains(def_id))); + + for fun in iter { + span_lint_hir_and_then( + cx, + UNUSED_ASYNC, + cx.tcx.local_def_id_to_hir_id(fun.def_id), + fun.fn_span, + "unused `async` for function with no await statements", + |diag| { + diag.help("consider removing the `async` from this function"); + + if let Some(span) = fun.await_in_async_block { + diag.span_note( + span, + "`await` used in an async block, which does not require \ + the enclosing function to be `async`", + ); + } + }, + ); + } + } } diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 76654bfe53602..58ae0656db73e 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -551,6 +551,16 @@ define_Conf! { /// /// Whether to allow `r#""#` when `r""` can be used (allow_one_hash_in_raw_strings: bool = false), + /// Lint: ABSOLUTE_PATHS. + /// + /// The maximum number of segments a path can have before being linted, anything above this will + /// be linted. + (absolute_paths_max_segments: u64 = 2), + /// Lint: ABSOLUTE_PATHS. + /// + /// Which crates to allow absolute paths from + (absolute_paths_allowed_crates: rustc_data_structures::fx::FxHashSet = + rustc_data_structures::fx::FxHashSet::default()), } /// Search for the configuration file. diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs index fb08256931f66..4fef8c0717d8a 100644 --- a/clippy_lints/src/utils/mod.rs +++ b/clippy_lints/src/utils/mod.rs @@ -18,7 +18,7 @@ const BOOK_CONFIGS_PATH: &str = "https://doc.rust-lang.org/clippy/lint_configura // ================================================================== // Configuration // ================================================================== -#[derive(Debug, Clone, Default)] //~ ERROR no such field +#[derive(Debug, Clone, Default)] pub struct ClippyConfiguration { pub name: String, #[allow(dead_code)] diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 357ac40b45594..802adbd4d2d56 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -138,6 +138,7 @@ impl<'hir> IfLet<'hir> { } /// An `if let` or `match` expression. Useful for lints that trigger on one or the other. +#[derive(Debug)] pub enum IfLetOrMatch<'hir> { /// Any `match` expression Match(&'hir Expr<'hir>, &'hir [Arm<'hir>], MatchSource), diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index fb359ee3bbe01..85b3b005f93df 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -252,15 +252,15 @@ impl HirEqInterExpr<'_, '_, '_> { return false; } - if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results { - if let (Some(l), Some(r)) = ( + if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results + && typeck_lhs.expr_ty(left) == typeck_rhs.expr_ty(right) + && let (Some(l), Some(r)) = ( constant_simple(self.inner.cx, typeck_lhs, left), constant_simple(self.inner.cx, typeck_rhs, right), - ) { - if l == r { - return true; - } - } + ) + && l == r + { + return true; } let is_eq = match ( @@ -494,10 +494,13 @@ impl HirEqInterExpr<'_, '_, '_> { loop { use TokenKind::{BlockComment, LineComment, Whitespace}; if left_data.macro_def_id != right_data.macro_def_id - || (matches!(left_data.kind, ExpnKind::Macro(MacroKind::Bang, name) if name == sym::cfg) - && !eq_span_tokens(self.inner.cx, left_data.call_site, right_data.call_site, |t| { - !matches!(t, Whitespace | LineComment { .. } | BlockComment { .. }) - })) + || (matches!( + left_data.kind, + ExpnKind::Macro(MacroKind::Bang, name) + if name == sym::cfg || name == sym::option_env + ) && !eq_span_tokens(self.inner.cx, left_data.call_site, right_data.call_site, |t| { + !matches!(t, Whitespace | LineComment { .. } | BlockComment { .. }) + })) { // Either a different chain of macro calls, or different arguments to the `cfg` macro. return false; diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 45b99df46b010..cf30930b76eac 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -89,21 +89,21 @@ use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; use rustc_hir::{ self as hir, def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Closure, Destination, Expr, - ExprKind, FnDecl, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, IsAsync, Item, ItemKind, LangItem, Local, - MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, - TraitItem, TraitItemRef, TraitRef, TyKind, UnOp, + ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, IsAsync, Item, + ItemKind, LangItem, Local, MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, PathSegment, PrimTy, + QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp, }; use rustc_lexer::{tokenize, TokenKind}; use rustc_lint::{LateContext, Level, Lint, LintContext}; use rustc_middle::hir::place::PlaceBase; use rustc_middle::mir::ConstantKind; -use rustc_middle::ty as rustc_ty; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; use rustc_middle::ty::binding::BindingMode; use rustc_middle::ty::fast_reject::SimplifiedType; use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::{ - BorrowKind, ClosureKind, FloatTy, IntTy, Ty, TyCtxt, TypeAndMut, TypeVisitableExt, UintTy, UpvarCapture, + self as rustc_ty, Binder, BorrowKind, ClosureKind, FloatTy, IntTy, ParamEnv, ParamEnvAnd, Ty, TyCtxt, TypeAndMut, + TypeVisitableExt, UintTy, UpvarCapture, }; use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::source_map::SourceMap; @@ -113,7 +113,10 @@ use rustc_target::abi::Integer; use crate::consts::{constant, miri_to_const, Constant}; use crate::higher::Range; -use crate::ty::{can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, ty_is_fn_once_param}; +use crate::ty::{ + adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, + ty_is_fn_once_param, +}; use crate::visitors::for_each_expr; use rustc_middle::hir::nested_filter; @@ -2445,6 +2448,17 @@ pub fn is_in_cfg_test(tcx: TyCtxt<'_>, id: hir::HirId) -> bool { .any(is_cfg_test) } +/// Checks if the item of any of its parents has `#[cfg(...)]` attribute applied. +pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { + let hir = tcx.hir(); + + tcx.has_attr(def_id, sym::cfg) + || hir + .parent_iter(hir.local_def_id_to_hir_id(def_id)) + .flat_map(|(parent_id, _)| hir.attrs(parent_id)) + .any(|attr| attr.has_name(sym::cfg)) +} + /// Checks whether item either has `test` attribute applied, or /// is a module with `test` in its name. /// @@ -2499,6 +2513,262 @@ pub fn walk_to_expr_usage<'tcx, T>( None } +/// A type definition as it would be viewed from within a function. +#[derive(Clone, Copy)] +pub enum DefinedTy<'tcx> { + // Used for locals and closures defined within the function. + Hir(&'tcx hir::Ty<'tcx>), + /// Used for function signatures, and constant and static values. This includes the `ParamEnv` + /// from the definition site. + Mir(ParamEnvAnd<'tcx, Binder<'tcx, Ty<'tcx>>>), +} + +/// The context an expressions value is used in. +pub struct ExprUseCtxt<'tcx> { + /// The parent node which consumes the value. + pub node: ExprUseNode<'tcx>, + /// Any adjustments applied to the type. + pub adjustments: &'tcx [Adjustment<'tcx>], + /// Whether or not the type must unify with another code path. + pub is_ty_unified: bool, + /// Whether or not the value will be moved before it's used. + pub moved_before_use: bool, +} + +/// The node which consumes a value. +pub enum ExprUseNode<'tcx> { + /// Assignment to, or initializer for, a local + Local(&'tcx Local<'tcx>), + /// Initializer for a const or static item. + ConstStatic(OwnerId), + /// Implicit or explicit return from a function. + Return(OwnerId), + /// Initialization of a struct field. + Field(&'tcx ExprField<'tcx>), + /// An argument to a function. + FnArg(&'tcx Expr<'tcx>, usize), + /// An argument to a method. + MethodArg(HirId, Option<&'tcx GenericArgs<'tcx>>, usize), + /// The callee of a function call. + Callee, + /// Access of a field. + FieldAccess(Ident), +} +impl<'tcx> ExprUseNode<'tcx> { + /// Checks if the value is returned from the function. + pub fn is_return(&self) -> bool { + matches!(self, Self::Return(_)) + } + + /// Checks if the value is used as a method call receiver. + pub fn is_recv(&self) -> bool { + matches!(self, Self::MethodArg(_, _, 0)) + } + + /// Gets the needed type as it's defined without any type inference. + pub fn defined_ty(&self, cx: &LateContext<'tcx>) -> Option> { + match *self { + Self::Local(Local { ty: Some(ty), .. }) => Some(DefinedTy::Hir(ty)), + Self::ConstStatic(id) => Some(DefinedTy::Mir( + cx.param_env + .and(Binder::dummy(cx.tcx.type_of(id).instantiate_identity())), + )), + Self::Return(id) => { + let hir_id = cx.tcx.hir().local_def_id_to_hir_id(id.def_id); + if let Some(Node::Expr(Expr { + kind: ExprKind::Closure(c), + .. + })) = cx.tcx.hir().find(hir_id) + { + match c.fn_decl.output { + FnRetTy::DefaultReturn(_) => None, + FnRetTy::Return(ty) => Some(DefinedTy::Hir(ty)), + } + } else { + Some(DefinedTy::Mir( + cx.param_env.and(cx.tcx.fn_sig(id).instantiate_identity().output()), + )) + } + }, + Self::Field(field) => match get_parent_expr_for_hir(cx, field.hir_id) { + Some(Expr { + hir_id, + kind: ExprKind::Struct(path, ..), + .. + }) => adt_and_variant_of_res(cx, cx.qpath_res(path, *hir_id)) + .and_then(|(adt, variant)| { + variant + .fields + .iter() + .find(|f| f.name == field.ident.name) + .map(|f| (adt, f)) + }) + .map(|(adt, field_def)| { + DefinedTy::Mir( + cx.tcx + .param_env(adt.did()) + .and(Binder::dummy(cx.tcx.type_of(field_def.did).instantiate_identity())), + ) + }), + _ => None, + }, + Self::FnArg(callee, i) => { + let sig = expr_sig(cx, callee)?; + let (hir_ty, ty) = sig.input_with_hir(i)?; + Some(match hir_ty { + Some(hir_ty) => DefinedTy::Hir(hir_ty), + None => DefinedTy::Mir( + sig.predicates_id() + .map_or(ParamEnv::empty(), |id| cx.tcx.param_env(id)) + .and(ty), + ), + }) + }, + Self::MethodArg(id, _, i) => { + let id = cx.typeck_results().type_dependent_def_id(id)?; + let sig = cx.tcx.fn_sig(id).skip_binder(); + Some(DefinedTy::Mir(cx.tcx.param_env(id).and(sig.input(i)))) + }, + Self::Local(_) | Self::FieldAccess(..) | Self::Callee => None, + } + } +} + +/// Gets the context an expression's value is used in. +#[expect(clippy::too_many_lines)] +pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Option> { + let mut adjustments = [].as_slice(); + let mut is_ty_unified = false; + let mut moved_before_use = false; + let ctxt = e.span.ctxt(); + walk_to_expr_usage(cx, e, &mut |parent, child_id| { + // LocalTableInContext returns the wrong lifetime, so go use `expr_adjustments` instead. + if adjustments.is_empty() && let Node::Expr(e) = cx.tcx.hir().get(child_id) { + adjustments = cx.typeck_results().expr_adjustments(e); + } + match parent { + Node::Local(l) if l.span.ctxt() == ctxt => Some(ExprUseCtxt { + node: ExprUseNode::Local(l), + adjustments, + is_ty_unified, + moved_before_use, + }), + Node::Item(&Item { + kind: ItemKind::Static(..) | ItemKind::Const(..), + owner_id, + span, + .. + }) + | Node::TraitItem(&TraitItem { + kind: TraitItemKind::Const(..), + owner_id, + span, + .. + }) + | Node::ImplItem(&ImplItem { + kind: ImplItemKind::Const(..), + owner_id, + span, + .. + }) if span.ctxt() == ctxt => Some(ExprUseCtxt { + node: ExprUseNode::ConstStatic(owner_id), + adjustments, + is_ty_unified, + moved_before_use, + }), + + Node::Item(&Item { + kind: ItemKind::Fn(..), + owner_id, + span, + .. + }) + | Node::TraitItem(&TraitItem { + kind: TraitItemKind::Fn(..), + owner_id, + span, + .. + }) + | Node::ImplItem(&ImplItem { + kind: ImplItemKind::Fn(..), + owner_id, + span, + .. + }) if span.ctxt() == ctxt => Some(ExprUseCtxt { + node: ExprUseNode::Return(owner_id), + adjustments, + is_ty_unified, + moved_before_use, + }), + + Node::ExprField(field) if field.span.ctxt() == ctxt => Some(ExprUseCtxt { + node: ExprUseNode::Field(field), + adjustments, + is_ty_unified, + moved_before_use, + }), + + Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind { + ExprKind::Ret(_) => Some(ExprUseCtxt { + node: ExprUseNode::Return(OwnerId { + def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()), + }), + adjustments, + is_ty_unified, + moved_before_use, + }), + ExprKind::Closure(closure) => Some(ExprUseCtxt { + node: ExprUseNode::Return(OwnerId { def_id: closure.def_id }), + adjustments, + is_ty_unified, + moved_before_use, + }), + ExprKind::Call(func, args) => Some(ExprUseCtxt { + node: match args.iter().position(|arg| arg.hir_id == child_id) { + Some(i) => ExprUseNode::FnArg(func, i), + None => ExprUseNode::Callee, + }, + adjustments, + is_ty_unified, + moved_before_use, + }), + ExprKind::MethodCall(name, _, args, _) => Some(ExprUseCtxt { + node: ExprUseNode::MethodArg( + parent.hir_id, + name.args, + args.iter().position(|arg| arg.hir_id == child_id).map_or(0, |i| i + 1), + ), + adjustments, + is_ty_unified, + moved_before_use, + }), + ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(ExprUseCtxt { + node: ExprUseNode::FieldAccess(name), + adjustments, + is_ty_unified, + moved_before_use, + }), + ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => { + is_ty_unified = true; + moved_before_use = true; + None + }, + ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => { + is_ty_unified = true; + moved_before_use = true; + None + }, + ExprKind::Block(..) => { + moved_before_use = true; + None + }, + _ => None, + }, + _ => None, + } + }) +} + /// Tokenizes the input while keeping the text associated with each token. pub fn tokenize_with_text(s: &str) -> impl Iterator { let mut pos = 0; diff --git a/clippy_utils/src/mir/possible_origin.rs b/clippy_utils/src/mir/possible_origin.rs index 8e7513d740ab3..da04266863f94 100644 --- a/clippy_utils/src/mir/possible_origin.rs +++ b/clippy_utils/src/mir/possible_origin.rs @@ -44,7 +44,7 @@ impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleOriginVisitor<'a, 'tcx> { let lhs = place.local; match rvalue { // Only consider `&mut`, which can modify origin place - mir::Rvalue::Ref(_, rustc_middle::mir::BorrowKind::Mut { .. }, borrowed) | + mir::Rvalue::Ref(_, mir::BorrowKind::Mut { .. }, borrowed) | // _2: &mut _; // _3 = move _2 mir::Rvalue::Use(mir::Operand::Move(borrowed)) | diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index f3677e6f61417..914ea85ac2809 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -149,6 +149,7 @@ pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"]; pub const VEC_DEQUE_ITER: [&str; 5] = ["alloc", "collections", "vec_deque", "VecDeque", "iter"]; pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"]; pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"]; +pub const VEC_WITH_CAPACITY: [&str; 4] = ["alloc", "vec", "Vec", "with_capacity"]; pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"]; pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"]; pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"]; @@ -161,3 +162,6 @@ pub const OPTION_UNWRAP: [&str; 4] = ["core", "option", "Option", "unwrap"]; pub const OPTION_EXPECT: [&str; 4] = ["core", "option", "Option", "expect"]; pub const FORMATTER: [&str; 3] = ["core", "fmt", "Formatter"]; pub const DEBUG_STRUCT: [&str; 4] = ["core", "fmt", "builders", "DebugStruct"]; +pub const ORD_CMP: [&str; 4] = ["core", "cmp", "Ord", "cmp"]; +#[expect(clippy::invalid_paths)] // not sure why it thinks this, it works so +pub const BOOL_THEN: [&str; 4] = ["core", "bool", "", "then"]; diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index fd39a246f48de..d0e15aa8bb328 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -3,6 +3,7 @@ #![allow(clippy::module_name_repetitions)] use core::ops::ControlFlow; +use itertools::Itertools; use rustc_ast::ast::Mutability; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; @@ -13,21 +14,26 @@ use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKi use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; use rustc_middle::mir::interpret::{ConstValue, Scalar}; +use rustc_middle::traits::EvaluationResult; use rustc_middle::ty::layout::ValidityRequirement; use rustc_middle::ty::{ - self, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef, IntTy, - List, ParamEnv, Region, RegionKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, - UintTy, VariantDef, VariantDiscr, + self, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef, + GenericParamDefKind, IntTy, List, ParamEnv, Region, RegionKind, ToPredicate, TraitRef, Ty, TyCtxt, + TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, VariantDef, VariantDiscr, }; use rustc_span::symbol::Ident; use rustc_span::{sym, Span, Symbol, DUMMY_SP}; use rustc_target::abi::{Size, VariantIdx}; -use rustc_trait_selection::infer::InferCtxtExt; +use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt; +use rustc_trait_selection::traits::{Obligation, ObligationCause}; use std::iter; use crate::{match_def_path, path_res, paths}; +mod type_certainty; +pub use type_certainty::expr_type_is_certain; + /// Checks if the given type implements copy. pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { ty.is_copy_modulo_regions(cx.tcx, cx.param_env) @@ -204,15 +210,9 @@ pub fn implements_trait<'tcx>( cx: &LateContext<'tcx>, ty: Ty<'tcx>, trait_id: DefId, - ty_params: &[GenericArg<'tcx>], + args: &[GenericArg<'tcx>], ) -> bool { - implements_trait_with_env( - cx.tcx, - cx.param_env, - ty, - trait_id, - ty_params.iter().map(|&arg| Some(arg)), - ) + implements_trait_with_env_from_iter(cx.tcx, cx.param_env, ty, trait_id, args.iter().map(|&x| Some(x))) } /// Same as `implements_trait` but allows using a `ParamEnv` different from the lint context. @@ -221,7 +221,18 @@ pub fn implements_trait_with_env<'tcx>( param_env: ParamEnv<'tcx>, ty: Ty<'tcx>, trait_id: DefId, - ty_params: impl IntoIterator>>, + args: &[GenericArg<'tcx>], +) -> bool { + implements_trait_with_env_from_iter(tcx, param_env, ty, trait_id, args.iter().map(|&x| Some(x))) +} + +/// Same as `implements_trait_from_env` but takes the arguments as an iterator. +pub fn implements_trait_with_env_from_iter<'tcx>( + tcx: TyCtxt<'tcx>, + param_env: ParamEnv<'tcx>, + ty: Ty<'tcx>, + trait_id: DefId, + args: impl IntoIterator>>>, ) -> bool { // Clippy shouldn't have infer types assert!(!ty.has_infer()); @@ -230,19 +241,37 @@ pub fn implements_trait_with_env<'tcx>( if ty.has_escaping_bound_vars() { return false; } + let infcx = tcx.infer_ctxt().build(); - let orig = TypeVariableOrigin { - kind: TypeVariableOriginKind::MiscVariable, - span: DUMMY_SP, - }; - let ty_params = tcx.mk_args_from_iter( - ty_params + let trait_ref = TraitRef::new( + tcx, + trait_id, + Some(GenericArg::from(ty)) .into_iter() - .map(|arg| arg.unwrap_or_else(|| infcx.next_ty_var(orig).into())), + .chain(args.into_iter().map(|arg| { + arg.into().unwrap_or_else(|| { + let orig = TypeVariableOrigin { + kind: TypeVariableOriginKind::MiscVariable, + span: DUMMY_SP, + }; + infcx.next_ty_var(orig).into() + }) + })), ); + + debug_assert_eq!(tcx.def_kind(trait_id), DefKind::Trait); + #[cfg(debug_assertions)] + assert_generic_args_match(tcx, trait_id, trait_ref.args); + + let obligation = Obligation { + cause: ObligationCause::dummy(), + param_env, + recursion_depth: 0, + predicate: ty::Binder::dummy(trait_ref).without_const().to_predicate(tcx), + }; infcx - .type_implements_trait(trait_id, [ty.into()].into_iter().chain(ty_params), param_env) - .must_apply_modulo_regions() + .evaluate_obligation(&obligation) + .is_ok_and(EvaluationResult::must_apply_modulo_regions) } /// Checks whether this type implements `Drop`. @@ -390,6 +419,11 @@ pub fn is_type_lang_item(cx: &LateContext<'_>, ty: Ty<'_>, lang_item: hir::LangI } } +/// Gets the diagnostic name of the type, if it has one +pub fn type_diagnostic_name(cx: &LateContext<'_>, ty: Ty<'_>) -> Option { + ty.ty_adt_def().and_then(|adt| cx.tcx.get_diagnostic_name(adt.did())) +} + /// Return `true` if the passed `typ` is `isize` or `usize`. pub fn is_isize_or_usize(typ: Ty<'_>) -> bool { matches!(typ.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize)) @@ -739,7 +773,11 @@ fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: AliasTy<'tcx>) -> Option let mut output = None; let lang_items = cx.tcx.lang_items(); - for (pred, _) in cx.tcx.explicit_item_bounds(ty.def_id).iter_instantiated_copied(cx.tcx, ty.args) { + for (pred, _) in cx + .tcx + .explicit_item_bounds(ty.def_id) + .iter_instantiated_copied(cx.tcx, ty.args) + { match pred.kind().skip_binder() { ty::ClauseKind::Trait(p) if (lang_items.fn_trait() == Some(p.def_id()) @@ -1007,12 +1045,60 @@ pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 { } } +/// Asserts that the given arguments match the generic parameters of the given item. +#[allow(dead_code)] +fn assert_generic_args_match<'tcx>(tcx: TyCtxt<'tcx>, did: DefId, args: &[GenericArg<'tcx>]) { + let g = tcx.generics_of(did); + let parent = g.parent.map(|did| tcx.generics_of(did)); + let count = g.parent_count + g.params.len(); + let params = parent + .map_or([].as_slice(), |p| p.params.as_slice()) + .iter() + .chain(&g.params) + .map(|x| &x.kind); + + assert!( + count == args.len(), + "wrong number of arguments for `{did:?}`: expected `{count}`, found {}\n\ + note: the expected arguments are: `[{}]`\n\ + the given arguments are: `{args:#?}`", + args.len(), + params.clone().map(GenericParamDefKind::descr).format(", "), + ); + + if let Some((idx, (param, arg))) = + params + .clone() + .zip(args.iter().map(|&x| x.unpack())) + .enumerate() + .find(|(_, (param, arg))| match (param, arg) { + (GenericParamDefKind::Lifetime, GenericArgKind::Lifetime(_)) + | (GenericParamDefKind::Type { .. }, GenericArgKind::Type(_)) + | (GenericParamDefKind::Const { .. }, GenericArgKind::Const(_)) => false, + ( + GenericParamDefKind::Lifetime + | GenericParamDefKind::Type { .. } + | GenericParamDefKind::Const { .. }, + _, + ) => true, + }) + { + panic!( + "incorrect argument for `{did:?}` at index `{idx}`: expected a {}, found `{arg:?}`\n\ + note: the expected arguments are `[{}]`\n\ + the given arguments are `{args:#?}`", + param.descr(), + params.clone().map(GenericParamDefKind::descr).format(", "), + ); + } +} + /// Makes the projection type for the named associated type in the given impl or trait impl. /// /// This function is for associated types which are "known" to exist, and as such, will only return /// `None` when debug assertions are disabled in order to prevent ICE's. With debug assertions /// enabled this will check that the named associated type exists, the correct number of -/// substitutions are given, and that the correct kinds of substitutions are given (lifetime, +/// arguments are given, and that the correct kinds of arguments are given (lifetime, /// constant or type). This will not check if type normalization would succeed. pub fn make_projection<'tcx>( tcx: TyCtxt<'tcx>, @@ -1036,49 +1122,7 @@ pub fn make_projection<'tcx>( return None; }; #[cfg(debug_assertions)] - { - let generics = tcx.generics_of(assoc_item.def_id); - let generic_count = generics.parent_count + generics.params.len(); - let params = generics - .parent - .map_or([].as_slice(), |id| &*tcx.generics_of(id).params) - .iter() - .chain(&generics.params) - .map(|x| &x.kind); - - debug_assert!( - generic_count == args.len(), - "wrong number of args for `{:?}`: found `{}` expected `{generic_count}`.\n\ - note: the expected parameters are: {:#?}\n\ - the given arguments are: `{args:#?}`", - assoc_item.def_id, - args.len(), - params.map(ty::GenericParamDefKind::descr).collect::>(), - ); - - if let Some((idx, (param, arg))) = params - .clone() - .zip(args.iter().map(GenericArg::unpack)) - .enumerate() - .find(|(_, (param, arg))| { - !matches!( - (param, arg), - (ty::GenericParamDefKind::Lifetime, GenericArgKind::Lifetime(_)) - | (ty::GenericParamDefKind::Type { .. }, GenericArgKind::Type(_)) - | (ty::GenericParamDefKind::Const { .. }, GenericArgKind::Const(_)) - ) - }) - { - debug_assert!( - false, - "mismatched subst type at index {idx}: expected a {}, found `{arg:?}`\n\ - note: the expected parameters are {:#?}\n\ - the given arguments are {args:#?}", - param.descr(), - params.map(ty::GenericParamDefKind::descr).collect::>() - ); - } - } + assert_generic_args_match(tcx, assoc_item.def_id, args); Some(tcx.mk_alias_ty(assoc_item.def_id, args)) } @@ -1093,7 +1137,7 @@ pub fn make_projection<'tcx>( /// Normalizes the named associated type in the given impl or trait impl. /// /// This function is for associated types which are "known" to be valid with the given -/// substitutions, and as such, will only return `None` when debug assertions are disabled in order +/// arguments, and as such, will only return `None` when debug assertions are disabled in order /// to prevent ICE's. With debug assertions enabled this will check that type normalization /// succeeds as well as everything checked by `make_projection`. pub fn make_normalized_projection<'tcx>( @@ -1105,17 +1149,12 @@ pub fn make_normalized_projection<'tcx>( ) -> Option> { fn helper<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: AliasTy<'tcx>) -> Option> { #[cfg(debug_assertions)] - if let Some((i, subst)) = ty - .args - .iter() - .enumerate() - .find(|(_, subst)| subst.has_late_bound_regions()) - { + if let Some((i, arg)) = ty.args.iter().enumerate().find(|(_, arg)| arg.has_late_bound_regions()) { debug_assert!( false, "args contain late-bound region at index `{i}` which can't be normalized.\n\ use `TyCtxt::erase_late_bound_regions`\n\ - note: subst is `{subst:#?}`", + note: arg is `{arg:#?}`", ); return None; } @@ -1183,17 +1222,12 @@ pub fn make_normalized_projection_with_regions<'tcx>( ) -> Option> { fn helper<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: AliasTy<'tcx>) -> Option> { #[cfg(debug_assertions)] - if let Some((i, subst)) = ty - .args - .iter() - .enumerate() - .find(|(_, subst)| subst.has_late_bound_regions()) - { + if let Some((i, arg)) = ty.args.iter().enumerate().find(|(_, arg)| arg.has_late_bound_regions()) { debug_assert!( false, "args contain late-bound region at index `{i}` which can't be normalized.\n\ use `TyCtxt::erase_late_bound_regions`\n\ - note: subst is `{subst:#?}`", + note: arg is `{arg:#?}`", ); return None; } diff --git a/clippy_utils/src/ty/type_certainty/certainty.rs b/clippy_utils/src/ty/type_certainty/certainty.rs new file mode 100644 index 0000000000000..0e69ffa2212d8 --- /dev/null +++ b/clippy_utils/src/ty/type_certainty/certainty.rs @@ -0,0 +1,122 @@ +use rustc_hir::def_id::DefId; +use std::fmt::Debug; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Certainty { + /// Determining the type requires contextual information. + Uncertain, + + /// The type can be determined purely from subexpressions. If the argument is `Some(..)`, the + /// specific `DefId` is known. Such arguments are needed to handle path segments whose `res` is + /// `Res::Err`. + Certain(Option), + + /// The heuristic believes that more than one `DefId` applies to a type---this is a bug. + Contradiction, +} + +pub trait Meet { + fn meet(self, other: Self) -> Self; +} + +pub trait TryJoin: Sized { + fn try_join(self, other: Self) -> Option; +} + +impl Meet for Option { + fn meet(self, other: Self) -> Self { + match (self, other) { + (None, _) | (_, None) => None, + (Some(lhs), Some(rhs)) => (lhs == rhs).then_some(lhs), + } + } +} + +impl TryJoin for Option { + fn try_join(self, other: Self) -> Option { + match (self, other) { + (Some(lhs), Some(rhs)) => (lhs == rhs).then_some(Some(lhs)), + (Some(def_id), _) | (_, Some(def_id)) => Some(Some(def_id)), + (None, None) => Some(None), + } + } +} + +impl Meet for Certainty { + fn meet(self, other: Self) -> Self { + match (self, other) { + (Certainty::Uncertain, _) | (_, Certainty::Uncertain) => Certainty::Uncertain, + (Certainty::Certain(lhs), Certainty::Certain(rhs)) => Certainty::Certain(lhs.meet(rhs)), + (Certainty::Certain(inner), _) | (_, Certainty::Certain(inner)) => Certainty::Certain(inner), + (Certainty::Contradiction, Certainty::Contradiction) => Certainty::Contradiction, + } + } +} + +impl Certainty { + /// Join two `Certainty`s preserving their `DefId`s (if any). Generally speaking, this method + /// should be used only when `self` and `other` refer directly to types. Otherwise, + /// `join_clearing_def_ids` should be used. + pub fn join(self, other: Self) -> Self { + match (self, other) { + (Certainty::Contradiction, _) | (_, Certainty::Contradiction) => Certainty::Contradiction, + + (Certainty::Certain(lhs), Certainty::Certain(rhs)) => { + if let Some(inner) = lhs.try_join(rhs) { + Certainty::Certain(inner) + } else { + debug_assert!(false, "Contradiction with {lhs:?} and {rhs:?}"); + Certainty::Contradiction + } + }, + + (Certainty::Certain(inner), _) | (_, Certainty::Certain(inner)) => Certainty::Certain(inner), + + (Certainty::Uncertain, Certainty::Uncertain) => Certainty::Uncertain, + } + } + + /// Join two `Certainty`s after clearing their `DefId`s. This method should be used when `self` + /// or `other` do not necessarily refer to types, e.g., when they are aggregations of other + /// `Certainty`s. + pub fn join_clearing_def_ids(self, other: Self) -> Self { + self.clear_def_id().join(other.clear_def_id()) + } + + pub fn clear_def_id(self) -> Certainty { + if matches!(self, Certainty::Certain(_)) { + Certainty::Certain(None) + } else { + self + } + } + + pub fn with_def_id(self, def_id: DefId) -> Certainty { + if matches!(self, Certainty::Certain(_)) { + Certainty::Certain(Some(def_id)) + } else { + self + } + } + + pub fn to_def_id(self) -> Option { + match self { + Certainty::Certain(inner) => inner, + _ => None, + } + } + + pub fn is_certain(self) -> bool { + matches!(self, Self::Certain(_)) + } +} + +/// Think: `iter.all(/* is certain */)` +pub fn meet(iter: impl Iterator) -> Certainty { + iter.fold(Certainty::Certain(None), Certainty::meet) +} + +/// Think: `iter.any(/* is certain */)` +pub fn join(iter: impl Iterator) -> Certainty { + iter.fold(Certainty::Uncertain, Certainty::join) +} diff --git a/clippy_utils/src/ty/type_certainty/mod.rs b/clippy_utils/src/ty/type_certainty/mod.rs new file mode 100644 index 0000000000000..45b5483e323d5 --- /dev/null +++ b/clippy_utils/src/ty/type_certainty/mod.rs @@ -0,0 +1,320 @@ +//! A heuristic to tell whether an expression's type can be determined purely from its +//! subexpressions, and the arguments and locals they use. Put another way, `expr_type_is_certain` +//! tries to tell whether an expression's type can be determined without appeal to the surrounding +//! context. +//! +//! This is, in some sense, a counterpart to `let_unit_value`'s `expr_needs_inferred_result`. +//! Intuitively, that function determines whether an expression's type is needed for type inference, +//! whereas `expr_type_is_certain` determines whether type inference is needed for an expression's +//! type. +//! +//! As a heuristic, `expr_type_is_certain` may produce false negatives, but a false positive should +//! be considered a bug. + +use crate::def_path_res; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::DefId; +use rustc_hir::intravisit::{walk_qpath, walk_ty, Visitor}; +use rustc_hir::{self as hir, Expr, ExprKind, GenericArgs, HirId, Node, PathSegment, QPath, TyKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, AdtDef, GenericArgKind, Ty}; +use rustc_span::{Span, Symbol}; + +mod certainty; +use certainty::{join, meet, Certainty, Meet}; + +pub fn expr_type_is_certain(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + expr_type_certainty(cx, expr).is_certain() +} + +fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty { + let certainty = match &expr.kind { + ExprKind::Unary(_, expr) + | ExprKind::Field(expr, _) + | ExprKind::Index(expr, _) + | ExprKind::AddrOf(_, _, expr) => expr_type_certainty(cx, expr), + + ExprKind::Array(exprs) => join(exprs.iter().map(|expr| expr_type_certainty(cx, expr))), + + ExprKind::Call(callee, args) => { + let lhs = expr_type_certainty(cx, callee); + let rhs = if type_is_inferrable_from_arguments(cx, expr) { + meet(args.iter().map(|arg| expr_type_certainty(cx, arg))) + } else { + Certainty::Uncertain + }; + lhs.join_clearing_def_ids(rhs) + }, + + ExprKind::MethodCall(method, receiver, args, _) => { + let mut receiver_type_certainty = expr_type_certainty(cx, receiver); + // Even if `receiver_type_certainty` is `Certain(Some(..))`, the `Self` type in the method + // identified by `type_dependent_def_id(..)` can differ. This can happen as a result of a `deref`, + // for example. So update the `DefId` in `receiver_type_certainty` (if any). + if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) + && let Some(self_ty_def_id) = adt_def_id(self_ty(cx, method_def_id)) + { + receiver_type_certainty = receiver_type_certainty.with_def_id(self_ty_def_id); + }; + let lhs = path_segment_certainty(cx, receiver_type_certainty, method, false); + let rhs = if type_is_inferrable_from_arguments(cx, expr) { + meet( + std::iter::once(receiver_type_certainty).chain(args.iter().map(|arg| expr_type_certainty(cx, arg))), + ) + } else { + Certainty::Uncertain + }; + lhs.join(rhs) + }, + + ExprKind::Tup(exprs) => meet(exprs.iter().map(|expr| expr_type_certainty(cx, expr))), + + ExprKind::Binary(_, lhs, rhs) => expr_type_certainty(cx, lhs).meet(expr_type_certainty(cx, rhs)), + + ExprKind::Lit(_) => Certainty::Certain(None), + + ExprKind::Cast(_, ty) => type_certainty(cx, ty), + + ExprKind::If(_, if_expr, Some(else_expr)) => { + expr_type_certainty(cx, if_expr).join(expr_type_certainty(cx, else_expr)) + }, + + ExprKind::Path(qpath) => qpath_certainty(cx, qpath, false), + + ExprKind::Struct(qpath, _, _) => qpath_certainty(cx, qpath, true), + + _ => Certainty::Uncertain, + }; + + let expr_ty = cx.typeck_results().expr_ty(expr); + if let Some(def_id) = adt_def_id(expr_ty) { + certainty.with_def_id(def_id) + } else { + certainty + } +} + +struct CertaintyVisitor<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + certainty: Certainty, +} + +impl<'cx, 'tcx> CertaintyVisitor<'cx, 'tcx> { + fn new(cx: &'cx LateContext<'tcx>) -> Self { + Self { + cx, + certainty: Certainty::Certain(None), + } + } +} + +impl<'cx, 'tcx> Visitor<'cx> for CertaintyVisitor<'cx, 'tcx> { + fn visit_qpath(&mut self, qpath: &'cx QPath<'_>, hir_id: HirId, _: Span) { + self.certainty = self.certainty.meet(qpath_certainty(self.cx, qpath, true)); + if self.certainty != Certainty::Uncertain { + walk_qpath(self, qpath, hir_id); + } + } + + fn visit_ty(&mut self, ty: &'cx hir::Ty<'_>) { + if matches!(ty.kind, TyKind::Infer) { + self.certainty = Certainty::Uncertain; + } + if self.certainty != Certainty::Uncertain { + walk_ty(self, ty); + } + } +} + +fn type_certainty(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> Certainty { + // Handle `TyKind::Path` specially so that its `DefId` can be preserved. + // + // Note that `CertaintyVisitor::new` initializes the visitor's internal certainty to + // `Certainty::Certain(None)`. Furthermore, if a `TyKind::Path` is encountered while traversing + // `ty`, the result of the call to `qpath_certainty` is combined with the visitor's internal + // certainty using `Certainty::meet`. Thus, if the `TyKind::Path` were not treated specially here, + // the resulting certainty would be `Certainty::Certain(None)`. + if let TyKind::Path(qpath) = &ty.kind { + return qpath_certainty(cx, qpath, true); + } + + let mut visitor = CertaintyVisitor::new(cx); + visitor.visit_ty(ty); + visitor.certainty +} + +fn generic_args_certainty(cx: &LateContext<'_>, args: &GenericArgs<'_>) -> Certainty { + let mut visitor = CertaintyVisitor::new(cx); + visitor.visit_generic_args(args); + visitor.certainty +} + +/// Tries to tell whether a `QPath` resolves to something certain, e.g., whether all of its path +/// segments generic arguments are are instantiated. +/// +/// `qpath` could refer to either a type or a value. The heuristic never needs the `DefId` of a +/// value. So `DefId`s are retained only when `resolves_to_type` is true. +fn qpath_certainty(cx: &LateContext<'_>, qpath: &QPath<'_>, resolves_to_type: bool) -> Certainty { + let certainty = match qpath { + QPath::Resolved(ty, path) => { + let len = path.segments.len(); + path.segments.iter().enumerate().fold( + ty.map_or(Certainty::Uncertain, |ty| type_certainty(cx, ty)), + |parent_certainty, (i, path_segment)| { + path_segment_certainty(cx, parent_certainty, path_segment, i != len - 1 || resolves_to_type) + }, + ) + }, + + QPath::TypeRelative(ty, path_segment) => { + path_segment_certainty(cx, type_certainty(cx, ty), path_segment, resolves_to_type) + }, + + QPath::LangItem(lang_item, _, _) => { + cx.tcx + .lang_items() + .get(*lang_item) + .map_or(Certainty::Uncertain, |def_id| { + let generics = cx.tcx.generics_of(def_id); + if generics.parent_count == 0 && generics.params.is_empty() { + Certainty::Certain(if resolves_to_type { Some(def_id) } else { None }) + } else { + Certainty::Uncertain + } + }) + }, + }; + debug_assert!(resolves_to_type || certainty.to_def_id().is_none()); + certainty +} + +fn path_segment_certainty( + cx: &LateContext<'_>, + parent_certainty: Certainty, + path_segment: &PathSegment<'_>, + resolves_to_type: bool, +) -> Certainty { + let certainty = match update_res(cx, parent_certainty, path_segment).unwrap_or(path_segment.res) { + // A definition's type is certain if it refers to something without generics (e.g., a crate or module, or + // an unparameterized type), or the generics are instantiated with arguments that are certain. + // + // If the parent is uncertain, then the current path segment must account for the parent's generic arguments. + // Consider the following examples, where the current path segment is `None`: + // - `Option::None` // uncertain; parent (i.e., `Option`) is uncertain + // - `Option::>::None` // certain; parent (i.e., `Option::<..>`) is certain + // - `Option::None::>` // certain; parent (i.e., `Option`) is uncertain + Res::Def(_, def_id) => { + // Checking `res_generics_def_id(..)` before calling `generics_of` avoids an ICE. + if cx.tcx.res_generics_def_id(path_segment.res).is_some() { + let generics = cx.tcx.generics_of(def_id); + let lhs = if (parent_certainty.is_certain() || generics.parent_count == 0) && generics.params.is_empty() + { + Certainty::Certain(None) + } else { + Certainty::Uncertain + }; + let rhs = path_segment + .args + .map_or(Certainty::Uncertain, |args| generic_args_certainty(cx, args)); + // See the comment preceding `qpath_certainty`. `def_id` could refer to a type or a value. + let certainty = lhs.join_clearing_def_ids(rhs); + if resolves_to_type { + if cx.tcx.def_kind(def_id) == DefKind::TyAlias { + adt_def_id(cx.tcx.type_of(def_id).instantiate_identity()) + .map_or(certainty, |def_id| certainty.with_def_id(def_id)) + } else { + certainty.with_def_id(def_id) + } + } else { + certainty + } + } else { + Certainty::Certain(None) + } + }, + + Res::PrimTy(_) | Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } | Res::SelfCtor(_) => { + Certainty::Certain(None) + }, + + // `get_parent` because `hir_id` refers to a `Pat`, and we're interested in the node containing the `Pat`. + Res::Local(hir_id) => match cx.tcx.hir().get_parent(hir_id) { + // An argument's type is always certain. + Node::Param(..) => Certainty::Certain(None), + // A local's type is certain if its type annotation is certain or it has an initializer whose + // type is certain. + Node::Local(local) => { + let lhs = local.ty.map_or(Certainty::Uncertain, |ty| type_certainty(cx, ty)); + let rhs = local + .init + .map_or(Certainty::Uncertain, |init| expr_type_certainty(cx, init)); + let certainty = lhs.join(rhs); + if resolves_to_type { + certainty + } else { + certainty.clear_def_id() + } + }, + _ => Certainty::Uncertain, + }, + + _ => Certainty::Uncertain, + }; + debug_assert!(resolves_to_type || certainty.to_def_id().is_none()); + certainty +} + +/// For at least some `QPath::TypeRelative`, the path segment's `res` can be `Res::Err`. +/// `update_res` tries to fix the resolution when `parent_certainty` is `Certain(Some(..))`. +fn update_res(cx: &LateContext<'_>, parent_certainty: Certainty, path_segment: &PathSegment<'_>) -> Option { + if path_segment.res == Res::Err && let Some(def_id) = parent_certainty.to_def_id() { + let mut def_path = cx.get_def_path(def_id); + def_path.push(path_segment.ident.name); + let reses = def_path_res(cx, &def_path.iter().map(Symbol::as_str).collect::>()); + if let [res] = reses.as_slice() { Some(*res) } else { None } + } else { + None + } +} + +#[allow(clippy::cast_possible_truncation)] +fn type_is_inferrable_from_arguments(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let Some(callee_def_id) = (match expr.kind { + ExprKind::Call(callee, _) => { + let callee_ty = cx.typeck_results().expr_ty(callee); + if let ty::FnDef(callee_def_id, _) = callee_ty.kind() { + Some(*callee_def_id) + } else { + None + } + }, + ExprKind::MethodCall(_, _, _, _) => cx.typeck_results().type_dependent_def_id(expr.hir_id), + _ => None, + }) else { + return false; + }; + + let generics = cx.tcx.generics_of(callee_def_id); + let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder(); + + // Check that all type parameters appear in the functions input types. + (0..(generics.parent_count + generics.params.len()) as u32).all(|index| { + fn_sig + .inputs() + .iter() + .any(|input_ty| contains_param(*input_ty.skip_binder(), index)) + }) +} + +fn self_ty<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId) -> Ty<'tcx> { + cx.tcx.fn_sig(method_def_id).skip_binder().inputs().skip_binder()[0] +} + +fn adt_def_id(ty: Ty<'_>) -> Option { + ty.peel_refs().ty_adt_def().map(AdtDef::did) +} + +fn contains_param(ty: Ty<'_>, index: u32) -> bool { + ty.walk() + .any(|arg| matches!(arg.unpack(), GenericArgKind::Type(ty) if ty.is_param(index))) +} diff --git a/clippy_utils/src/usage.rs b/clippy_utils/src/usage.rs index 20993398d1b78..39ef76348d751 100644 --- a/clippy_utils/src/usage.rs +++ b/clippy_utils/src/usage.rs @@ -1,14 +1,15 @@ -use crate::visitors::{for_each_expr, for_each_expr_with_closures, Descend}; +use crate::visitors::{for_each_expr, for_each_expr_with_closures, Descend, Visitable}; +use crate::{self as utils, get_enclosing_loop_or_multi_call_closure}; use core::ops::ControlFlow; +use hir::def::Res; use rustc_hir::intravisit::{self, Visitor}; -use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Node}; +use rustc_hir::{self as hir, Expr, ExprKind, HirId, HirIdSet}; use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::mir::FakeReadCause; use rustc_middle::ty; -use {crate as utils, rustc_hir as hir}; /// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined. pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Option { @@ -127,7 +128,7 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> { } fn visit_path(&mut self, path: &hir::Path<'tcx>, _: hir::HirId) { - if let hir::def::Res::Local(id) = path.res { + if let Res::Local(id) = path.res { if self.binding_ids.contains(&id) { self.usage_found = true; } @@ -153,6 +154,17 @@ pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool { .is_some() } +pub fn local_used_in<'tcx>(cx: &LateContext<'tcx>, local_id: HirId, v: impl Visitable<'tcx>) -> bool { + for_each_expr_with_closures(cx, v, |e| { + if utils::path_to_local_id(e, local_id) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_some() +} + pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr<'_>) -> bool { let Some(block) = utils::get_enclosing_block(cx, local_id) else { return false; @@ -165,32 +177,21 @@ pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr // let closure = || local; // closure(); // closure(); - let in_loop_or_closure = cx - .tcx - .hir() - .parent_iter(after.hir_id) - .take_while(|&(id, _)| id != block.hir_id) - .any(|(_, node)| { - matches!( - node, - Node::Expr(Expr { - kind: ExprKind::Loop(..) | ExprKind::Closure { .. }, - .. - }) - ) - }); - if in_loop_or_closure { - return true; - } + let loop_start = get_enclosing_loop_or_multi_call_closure(cx, after).map(|e| e.hir_id); let mut past_expr = false; for_each_expr_with_closures(cx, block, |e| { - if e.hir_id == after.hir_id { + if past_expr { + if utils::path_to_local_id(e, local_id) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(Descend::Yes) + } + } else if e.hir_id == after.hir_id { past_expr = true; ControlFlow::Continue(Descend::No) - } else if past_expr && utils::path_to_local_id(e, local_id) { - ControlFlow::Break(()) } else { + past_expr = Some(e.hir_id) == loop_start; ControlFlow::Continue(Descend::Yes) } }) diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index 8dafa723afa00..09f447b27eb4f 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -52,6 +52,16 @@ pub trait Visitable<'tcx> { /// Calls the corresponding `visit_*` function on the visitor. fn visit>(self, visitor: &mut V); } +impl<'tcx, T> Visitable<'tcx> for &'tcx [T] +where + &'tcx T: Visitable<'tcx>, +{ + fn visit>(self, visitor: &mut V) { + for x in self { + x.visit(visitor); + } + } +} macro_rules! visitable_ref { ($t:ident, $f:ident) => { impl<'tcx> Visitable<'tcx> for &'tcx $t<'tcx> { diff --git a/rust-toolchain b/rust-toolchain index b658101a10d83..833608faff06f 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2023-07-14" +channel = "nightly-2023-07-28" components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/src/driver.rs b/src/driver.rs index ee17feed77a00..1d89477dcc16b 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -192,7 +192,7 @@ You can use tool lints to allow or deny lints from your code, eg.: ); } -const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/new"; +const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/new?template=ice.yml"; #[allow(clippy::too_many_lines)] pub fn main() { diff --git a/src/main.rs b/src/main.rs index cdc85cb33ca2b..26b655076cf65 100644 --- a/src/main.rs +++ b/src/main.rs @@ -132,8 +132,7 @@ impl ClippyCmd { let clippy_args: String = self .clippy_args .iter() - .map(|arg| format!("{arg}__CLIPPY_HACKERY__")) - .collect(); + .fold(String::new(), |s, arg| s + arg + "__CLIPPY_HACKERY__"); // Currently, `CLIPPY_TERMINAL_WIDTH` is used only to format "unknown field" error messages. let terminal_width = termize::dimensions().map_or(0, |(w, _)| w); diff --git a/tests/compile-test.rs b/tests/compile-test.rs index f714b2962331d..385e25ce6b8f0 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -5,7 +5,7 @@ #![warn(rust_2018_idioms, unused_lifetimes)] #![allow(unused_extern_crates)] -use compiletest::{status_emitter, CommandBuilder}; +use compiletest::{status_emitter, CommandBuilder, OutputConflictHandling}; use ui_test as compiletest; use ui_test::Mode as TestMode; @@ -115,12 +115,12 @@ fn base_config(test_dir: &str) -> compiletest::Config { mode: TestMode::Yolo, stderr_filters: vec![], stdout_filters: vec![], - // FIXME(tgross35): deduplicate bless env once clippy can update output_conflict_handling: if var_os("RUSTC_BLESS").is_some_and(|v| v != "0") - || env::args().any(|arg| arg == "--bless") { - compiletest::OutputConflictHandling::Bless + || env::args().any(|arg| arg == "--bless") + { + OutputConflictHandling::Bless } else { - compiletest::OutputConflictHandling::Error("cargo test -- -- --bless".into()) + OutputConflictHandling::Error("cargo uibless".into()) }, target: None, out_dir: PathBuf::from(std::env::var_os("CARGO_TARGET_DIR").unwrap_or("target".into())).join("ui_test"), @@ -189,9 +189,6 @@ fn run_ui() { .to_string() }), ); - eprintln!(" Compiler: {}", config.program.display()); - - let name = config.root_dir.display().to_string(); let test_filter = test_filter(); @@ -199,7 +196,7 @@ fn run_ui() { config, move |path| compiletest::default_file_filter(path) && test_filter(path), compiletest::default_per_file_config, - (status_emitter::Text, status_emitter::Gha:: { name }), + status_emitter::Text, ) .unwrap(); check_rustfix_coverage(); @@ -210,8 +207,19 @@ fn run_internal_tests() { if !RUN_INTERNAL_TESTS { return; } - let config = base_config("ui-internal"); - compiletest::run_tests(config).unwrap(); + let mut config = base_config("ui-internal"); + if let OutputConflictHandling::Error(err) = &mut config.output_conflict_handling { + *err = "cargo uitest --features internal -- -- --bless".into(); + } + let test_filter = test_filter(); + + compiletest::run_tests_generic( + config, + move |path| compiletest::default_file_filter(path) && test_filter(path), + compiletest::default_per_file_config, + status_emitter::Text, + ) + .unwrap(); } fn run_ui_toml() { @@ -230,13 +238,11 @@ fn run_ui_toml() { "$$DIR", ); - let name = config.root_dir.display().to_string(); - let test_filter = test_filter(); ui_test::run_tests_generic( config, - |path| test_filter(path) && path.extension() == Some("rs".as_ref()), + |path| compiletest::default_file_filter(path) && test_filter(path), |config, path| { let mut config = config.clone(); config @@ -245,7 +251,7 @@ fn run_ui_toml() { .push(("CLIPPY_CONF_DIR".into(), Some(path.parent().unwrap().into()))); Some(config) }, - (status_emitter::Text, status_emitter::Gha:: { name }), + status_emitter::Text, ) .unwrap(); } @@ -286,8 +292,6 @@ fn run_ui_cargo() { "$$DIR", ); - let name = config.root_dir.display().to_string(); - let test_filter = test_filter(); ui_test::run_tests_generic( @@ -298,7 +302,7 @@ fn run_ui_cargo() { config.out_dir = PathBuf::from("target/ui_test_cargo/").join(path.parent().unwrap()); Some(config) }, - (status_emitter::Text, status_emitter::Gha:: { name }), + status_emitter::Text, ) .unwrap(); } diff --git a/tests/integration.rs b/tests/integration.rs index a771d8b87c81a..031982edbe9e1 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -65,6 +65,30 @@ fn integration_test() { .expect("unable to run clippy"); let stderr = String::from_utf8_lossy(&output.stderr); + + // debug: + eprintln!("{stderr}"); + + // this is an internal test to make sure we would correctly panic on a delay_span_bug + if repo_name == "matthiaskrgr/clippy_ci_panic_test" { + // we need to kind of switch around our logic here: + // if we find a panic, everything is fine, if we don't panic, SOMETHING is broken about our testing + + // the repo basically just contains a delay_span_bug that forces rustc/clippy to panic: + /* + #![feature(rustc_attrs)] + #[rustc_error(delay_span_bug_from_inside_query)] + fn main() {} + */ + + if stderr.find("error: internal compiler error").is_some() { + eprintln!("we saw that we intentionally panicked, yay"); + return; + } + + panic!("panic caused by delay_span_bug was NOT detected! Something is broken!"); + } + if let Some(backtrace_start) = stderr.find("error: internal compiler error") { static BACKTRACE_END_MSG: &str = "end of query stack"; let backtrace_end = stderr[backtrace_start..] diff --git a/tests/ui-cargo/cargo_common_metadata/fail/src/main.rs b/tests/ui-cargo/cargo_common_metadata/fail/src/main.rs index 1a69bb24101ea..c67166fc4b007 100644 --- a/tests/ui-cargo/cargo_common_metadata/fail/src/main.rs +++ b/tests/ui-cargo/cargo_common_metadata/fail/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=cargo_common_metadata #![warn(clippy::cargo_common_metadata)] fn main() {} diff --git a/tests/ui-cargo/cargo_common_metadata/fail_publish/src/main.rs b/tests/ui-cargo/cargo_common_metadata/fail_publish/src/main.rs index 1a69bb24101ea..c67166fc4b007 100644 --- a/tests/ui-cargo/cargo_common_metadata/fail_publish/src/main.rs +++ b/tests/ui-cargo/cargo_common_metadata/fail_publish/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=cargo_common_metadata #![warn(clippy::cargo_common_metadata)] fn main() {} diff --git a/tests/ui-cargo/cargo_common_metadata/fail_publish_true/src/main.rs b/tests/ui-cargo/cargo_common_metadata/fail_publish_true/src/main.rs index 1a69bb24101ea..c67166fc4b007 100644 --- a/tests/ui-cargo/cargo_common_metadata/fail_publish_true/src/main.rs +++ b/tests/ui-cargo/cargo_common_metadata/fail_publish_true/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=cargo_common_metadata #![warn(clippy::cargo_common_metadata)] fn main() {} diff --git a/tests/ui-cargo/cargo_common_metadata/pass/src/main.rs b/tests/ui-cargo/cargo_common_metadata/pass/src/main.rs index 1a69bb24101ea..c67166fc4b007 100644 --- a/tests/ui-cargo/cargo_common_metadata/pass/src/main.rs +++ b/tests/ui-cargo/cargo_common_metadata/pass/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=cargo_common_metadata #![warn(clippy::cargo_common_metadata)] fn main() {} diff --git a/tests/ui-cargo/cargo_common_metadata/pass_publish_empty/src/main.rs b/tests/ui-cargo/cargo_common_metadata/pass_publish_empty/src/main.rs index 1a69bb24101ea..c67166fc4b007 100644 --- a/tests/ui-cargo/cargo_common_metadata/pass_publish_empty/src/main.rs +++ b/tests/ui-cargo/cargo_common_metadata/pass_publish_empty/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=cargo_common_metadata #![warn(clippy::cargo_common_metadata)] fn main() {} diff --git a/tests/ui-cargo/cargo_common_metadata/pass_publish_false/src/main.rs b/tests/ui-cargo/cargo_common_metadata/pass_publish_false/src/main.rs index 1a69bb24101ea..c67166fc4b007 100644 --- a/tests/ui-cargo/cargo_common_metadata/pass_publish_false/src/main.rs +++ b/tests/ui-cargo/cargo_common_metadata/pass_publish_false/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=cargo_common_metadata #![warn(clippy::cargo_common_metadata)] fn main() {} diff --git a/tests/ui-cargo/feature_name/fail/src/main.rs b/tests/ui-cargo/feature_name/fail/src/main.rs index 4dd9582aff89c..74e40c09ebc83 100644 --- a/tests/ui-cargo/feature_name/fail/src/main.rs +++ b/tests/ui-cargo/feature_name/fail/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=feature_name #![warn(clippy::redundant_feature_names)] #![warn(clippy::negative_feature_names)] diff --git a/tests/ui-cargo/feature_name/pass/src/main.rs b/tests/ui-cargo/feature_name/pass/src/main.rs index 4dd9582aff89c..74e40c09ebc83 100644 --- a/tests/ui-cargo/feature_name/pass/src/main.rs +++ b/tests/ui-cargo/feature_name/pass/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=feature_name #![warn(clippy::redundant_feature_names)] #![warn(clippy::negative_feature_names)] diff --git a/tests/ui-cargo/module_style/fail_mod_remap/src/main.rs b/tests/ui-cargo/module_style/fail_mod_remap/src/main.rs index c70d92e359e44..ac21b3a442298 100644 --- a/tests/ui-cargo/module_style/fail_mod_remap/src/main.rs +++ b/tests/ui-cargo/module_style/fail_mod_remap/src/main.rs @@ -1,3 +1,4 @@ +// FIXME: find a way to add rustflags to ui-cargo tests //@compile-flags: --remap-path-prefix {{src-base}}=/remapped #![warn(clippy::self_named_module_files)] diff --git a/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/src/main.rs b/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/src/main.rs index ece260b743df8..4bc61dd62992f 100644 --- a/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/src/main.rs +++ b/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=multiple_crate_versions #![warn(clippy::multiple_crate_versions)] fn main() {} diff --git a/tests/ui-cargo/multiple_crate_versions/fail/src/main.rs b/tests/ui-cargo/multiple_crate_versions/fail/src/main.rs index ece260b743df8..4bc61dd62992f 100644 --- a/tests/ui-cargo/multiple_crate_versions/fail/src/main.rs +++ b/tests/ui-cargo/multiple_crate_versions/fail/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=multiple_crate_versions #![warn(clippy::multiple_crate_versions)] fn main() {} diff --git a/tests/ui-cargo/multiple_crate_versions/pass/src/main.rs b/tests/ui-cargo/multiple_crate_versions/pass/src/main.rs index ece260b743df8..4bc61dd62992f 100644 --- a/tests/ui-cargo/multiple_crate_versions/pass/src/main.rs +++ b/tests/ui-cargo/multiple_crate_versions/pass/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=multiple_crate_versions #![warn(clippy::multiple_crate_versions)] fn main() {} diff --git a/tests/ui-cargo/wildcard_dependencies/fail/src/main.rs b/tests/ui-cargo/wildcard_dependencies/fail/src/main.rs index bb3a39d0720a9..3491ccb0d47d5 100644 --- a/tests/ui-cargo/wildcard_dependencies/fail/src/main.rs +++ b/tests/ui-cargo/wildcard_dependencies/fail/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=wildcard_dependencies #![warn(clippy::wildcard_dependencies)] fn main() {} diff --git a/tests/ui-cargo/wildcard_dependencies/pass/src/main.rs b/tests/ui-cargo/wildcard_dependencies/pass/src/main.rs index bb3a39d0720a9..3491ccb0d47d5 100644 --- a/tests/ui-cargo/wildcard_dependencies/pass/src/main.rs +++ b/tests/ui-cargo/wildcard_dependencies/pass/src/main.rs @@ -1,4 +1,3 @@ -//@compile-flags: --crate-name=wildcard_dependencies #![warn(clippy::wildcard_dependencies)] fn main() {} diff --git a/tests/ui-internal/custom_ice_message.stderr b/tests/ui-internal/custom_ice_message.stderr index b88aeae2a9b8e..31df0ebd9fd6b 100644 --- a/tests/ui-internal/custom_ice_message.stderr +++ b/tests/ui-internal/custom_ice_message.stderr @@ -3,7 +3,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: the compiler unexpectedly panicked. this is a bug. -note: we would appreciate a bug report: https://github.com/rust-lang/rust-clippy/issues/new +note: we would appreciate a bug report: https://github.com/rust-lang/rust-clippy/issues/new?template=ice.yml note: rustc running on diff --git a/tests/ui-toml/absolute_paths/absolute_paths.allow_crates.stderr b/tests/ui-toml/absolute_paths/absolute_paths.allow_crates.stderr new file mode 100644 index 0000000000000..a8900da4e6eb4 --- /dev/null +++ b/tests/ui-toml/absolute_paths/absolute_paths.allow_crates.stderr @@ -0,0 +1,28 @@ +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:40:5 + | +LL | std::f32::MAX; + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::absolute-paths` implied by `-D warnings` + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:41:5 + | +LL | core::f32::MAX; + | ^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:42:5 + | +LL | ::core::f32::MAX; + | ^^^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:58:5 + | +LL | ::std::f32::MAX; + | ^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/tests/ui-toml/absolute_paths/absolute_paths.disallow_crates.stderr b/tests/ui-toml/absolute_paths/absolute_paths.disallow_crates.stderr new file mode 100644 index 0000000000000..41b70644be8c3 --- /dev/null +++ b/tests/ui-toml/absolute_paths/absolute_paths.disallow_crates.stderr @@ -0,0 +1,70 @@ +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:40:5 + | +LL | std::f32::MAX; + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::absolute-paths` implied by `-D warnings` + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:41:5 + | +LL | core::f32::MAX; + | ^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:42:5 + | +LL | ::core::f32::MAX; + | ^^^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:43:5 + | +LL | crate::a::b::c::C; + | ^^^^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:44:5 + | +LL | crate::a::b::c::d::e::f::F; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:45:5 + | +LL | crate::a::A; + | ^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:46:5 + | +LL | crate::a::b::B; + | ^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:47:5 + | +LL | crate::a::b::c::C::ZERO; + | ^^^^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:48:5 + | +LL | helper::b::c::d::e::f(); + | ^^^^^^^^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:49:5 + | +LL | ::helper::b::c::d::e::f(); + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: consider bringing this path into scope with the `use` keyword + --> $DIR/absolute_paths.rs:58:5 + | +LL | ::std::f32::MAX; + | ^^^^^^^^^^^^^^^ + +error: aborting due to 11 previous errors + diff --git a/tests/ui-toml/absolute_paths/absolute_paths.rs b/tests/ui-toml/absolute_paths/absolute_paths.rs new file mode 100644 index 0000000000000..d4c250a8ff231 --- /dev/null +++ b/tests/ui-toml/absolute_paths/absolute_paths.rs @@ -0,0 +1,97 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs:proc-macro +//@aux-build:helper.rs +//@revisions: allow_crates disallow_crates +//@[allow_crates] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/absolute_paths/allow_crates +//@[disallow_crates] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/absolute_paths/disallow_crates +#![allow(clippy::no_effect, unused)] +#![warn(clippy::absolute_paths)] +#![feature(decl_macro)] + +extern crate helper; +#[macro_use] +extern crate proc_macros; + +pub mod a { + pub mod b { + pub mod c { + pub struct C; + + impl C { + pub const ZERO: u32 = 0; + } + + pub mod d { + pub mod e { + pub mod f { + pub struct F; + } + } + } + } + + pub struct B; + } + + pub struct A; +} + +fn main() { + f32::max(1.0, 2.0); + std::f32::MAX; + core::f32::MAX; + ::core::f32::MAX; + crate::a::b::c::C; + crate::a::b::c::d::e::f::F; + crate::a::A; + crate::a::b::B; + crate::a::b::c::C::ZERO; + helper::b::c::d::e::f(); + ::helper::b::c::d::e::f(); + fn b() -> a::b::B { + todo!() + } + std::println!("a"); + let x = 1; + std::ptr::addr_of!(x); + // Test we handle max segments with `PathRoot` properly; this has 4 segments but we should say it + // has 3 + ::std::f32::MAX; + // Do not lint due to the above + ::helper::a(); + // Do not lint + helper::a(); + use crate::a::b::c::C; + use a::b; + use std::f32::MAX; + a::b::c::d::e::f::F; + b::c::C; + fn a() -> a::A { + todo!() + } + use a::b::c; + + fn c() -> c::C { + todo!() + } + fn d() -> Result<(), ()> { + todo!() + } + external! { + crate::a::b::c::C::ZERO; + } + // For some reason, `path.span.from_expansion()` takes care of this for us + with_span! { + span + crate::a::b::c::C::ZERO; + } + macro_rules! local_crate { + () => { + crate::a::b::c::C::ZERO; + }; + } + macro local_crate_2_0() { + crate::a::b::c::C::ZERO; + } + local_crate!(); + local_crate_2_0!(); +} diff --git a/tests/ui-toml/absolute_paths/allow_crates/clippy.toml b/tests/ui-toml/absolute_paths/allow_crates/clippy.toml new file mode 100644 index 0000000000000..59a621e9d1dba --- /dev/null +++ b/tests/ui-toml/absolute_paths/allow_crates/clippy.toml @@ -0,0 +1,2 @@ +absolute-paths-max-segments = 2 +absolute-paths-allowed-crates = ["crate", "helper"] diff --git a/tests/ui-toml/absolute_paths/auxiliary/helper.rs b/tests/ui-toml/absolute_paths/auxiliary/helper.rs new file mode 100644 index 0000000000000..8e2678f5fe697 --- /dev/null +++ b/tests/ui-toml/absolute_paths/auxiliary/helper.rs @@ -0,0 +1,11 @@ +pub fn a() {} + +pub mod b { + pub mod c { + pub mod d { + pub mod e { + pub fn f() {} + } + } + } +} diff --git a/tests/ui-toml/absolute_paths/disallow_crates/clippy.toml b/tests/ui-toml/absolute_paths/disallow_crates/clippy.toml new file mode 100644 index 0000000000000..d44d648c64118 --- /dev/null +++ b/tests/ui-toml/absolute_paths/disallow_crates/clippy.toml @@ -0,0 +1 @@ +absolute-paths-max-segments = 2 diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 6ba26e9773023..cdabe6460cdca 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -1,4 +1,6 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expected one of + absolute-paths-allowed-crates + absolute-paths-max-segments accept-comment-above-attributes accept-comment-above-statement allow-dbg-in-tests @@ -68,6 +70,8 @@ LL | foobar = 42 | ^^^^^^ error: error reading Clippy's configuration file: unknown field `barfoo`, expected one of + absolute-paths-allowed-crates + absolute-paths-max-segments accept-comment-above-attributes accept-comment-above-statement allow-dbg-in-tests diff --git a/tests/ui/arc_with_non_send_sync.rs b/tests/ui/arc_with_non_send_sync.rs index b6fcca0a7919e..2940c27325526 100644 --- a/tests/ui/arc_with_non_send_sync.rs +++ b/tests/ui/arc_with_non_send_sync.rs @@ -1,6 +1,12 @@ +//@aux-build:proc_macros.rs:proc-macro #![warn(clippy::arc_with_non_send_sync)] #![allow(unused_variables)] + +#[macro_use] +extern crate proc_macros; + use std::cell::RefCell; +use std::ptr::{null, null_mut}; use std::sync::{Arc, Mutex}; fn foo(x: T) { @@ -11,14 +17,32 @@ fn issue11076() { let a: Arc> = Arc::new(Vec::new()); } +fn issue11232() { + external! { + let a: Arc<*const u8> = Arc::new(null()); + let a: Arc<*mut u8> = Arc::new(null_mut()); + } + with_span! { + span + let a: Arc<*const u8> = Arc::new(null()); + let a: Arc<*mut u8> = Arc::new(null_mut()); + } +} + fn main() { let _ = Arc::new(42); - // !Sync let _ = Arc::new(RefCell::new(42)); + //~^ ERROR: usage of an `Arc` that is not `Send` or `Sync` + //~| NOTE: the trait `Sync` is not implemented for `RefCell` + let mutex = Mutex::new(1); - // !Send let _ = Arc::new(mutex.lock().unwrap()); - // !Send + !Sync + //~^ ERROR: usage of an `Arc` that is not `Send` or `Sync` + //~| NOTE: the trait `Send` is not implemented for `MutexGuard<'_, i32>` + let _ = Arc::new(&42 as *const i32); + //~^ ERROR: usage of an `Arc` that is not `Send` or `Sync` + //~| NOTE: the trait `Send` is not implemented for `*const i32` + //~| NOTE: the trait `Sync` is not implemented for `*const i32` } diff --git a/tests/ui/arc_with_non_send_sync.stderr b/tests/ui/arc_with_non_send_sync.stderr index 7633b38dfb597..de3f2fb9e16e6 100644 --- a/tests/ui/arc_with_non_send_sync.stderr +++ b/tests/ui/arc_with_non_send_sync.stderr @@ -1,5 +1,5 @@ error: usage of an `Arc` that is not `Send` or `Sync` - --> $DIR/arc_with_non_send_sync.rs:18:13 + --> $DIR/arc_with_non_send_sync.rs:35:13 | LL | let _ = Arc::new(RefCell::new(42)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -10,7 +10,7 @@ LL | let _ = Arc::new(RefCell::new(42)); = note: `-D clippy::arc-with-non-send-sync` implied by `-D warnings` error: usage of an `Arc` that is not `Send` or `Sync` - --> $DIR/arc_with_non_send_sync.rs:21:13 + --> $DIR/arc_with_non_send_sync.rs:40:13 | LL | let _ = Arc::new(mutex.lock().unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -20,7 +20,7 @@ LL | let _ = Arc::new(mutex.lock().unwrap()); = help: consider using an `Rc` instead or wrapping the inner type with a `Mutex` error: usage of an `Arc` that is not `Send` or `Sync` - --> $DIR/arc_with_non_send_sync.rs:23:13 + --> $DIR/arc_with_non_send_sync.rs:44:13 | LL | let _ = Arc::new(&42 as *const i32); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/arithmetic_side_effects.rs b/tests/ui/arithmetic_side_effects.rs index ed75acee8a287..2ac2fa22086b8 100644 --- a/tests/ui/arithmetic_side_effects.rs +++ b/tests/ui/arithmetic_side_effects.rs @@ -486,4 +486,11 @@ pub fn issue_11145() { x += 1; } +pub fn issue_11262() { + let one = 1; + let zero = 0; + let _ = 2 / one; + let _ = 2 / zero; +} + fn main() {} diff --git a/tests/ui/comparison_to_empty.fixed b/tests/ui/comparison_to_empty.fixed index c92dd509ebb2d..af219eed0b845 100644 --- a/tests/ui/comparison_to_empty.fixed +++ b/tests/ui/comparison_to_empty.fixed @@ -1,7 +1,8 @@ //@run-rustfix #![warn(clippy::comparison_to_empty)] -#![allow(clippy::useless_vec)] +#![allow(clippy::borrow_deref_ref, clippy::needless_if, clippy::useless_vec)] +#![feature(let_chains)] fn main() { // Disallow comparisons to empty @@ -12,6 +13,11 @@ fn main() { let v = vec![0]; let _ = v.is_empty(); let _ = !v.is_empty(); + if (*v).is_empty() {} + let s = [0].as_slice(); + if s.is_empty() {} + if s.is_empty() {} + if s.is_empty() && s.is_empty() {} // Allow comparisons to non-empty let s = String::new(); @@ -21,4 +27,8 @@ fn main() { let v = vec![0]; let _ = v == [0]; let _ = v != [0]; + if let [0] = &*v {} + let s = [0].as_slice(); + if let [0] = s {} + if let [0] = &*s && s == [0] {} } diff --git a/tests/ui/comparison_to_empty.rs b/tests/ui/comparison_to_empty.rs index b3489714380aa..21e65184d50d4 100644 --- a/tests/ui/comparison_to_empty.rs +++ b/tests/ui/comparison_to_empty.rs @@ -1,7 +1,8 @@ //@run-rustfix #![warn(clippy::comparison_to_empty)] -#![allow(clippy::useless_vec)] +#![allow(clippy::borrow_deref_ref, clippy::needless_if, clippy::useless_vec)] +#![feature(let_chains)] fn main() { // Disallow comparisons to empty @@ -12,6 +13,11 @@ fn main() { let v = vec![0]; let _ = v == []; let _ = v != []; + if let [] = &*v {} + let s = [0].as_slice(); + if let [] = s {} + if let [] = &*s {} + if let [] = &*s && s == [] {} // Allow comparisons to non-empty let s = String::new(); @@ -21,4 +27,8 @@ fn main() { let v = vec![0]; let _ = v == [0]; let _ = v != [0]; + if let [0] = &*v {} + let s = [0].as_slice(); + if let [0] = s {} + if let [0] = &*s && s == [0] {} } diff --git a/tests/ui/comparison_to_empty.stderr b/tests/ui/comparison_to_empty.stderr index cc09b17eb8951..f29782ed80d1f 100644 --- a/tests/ui/comparison_to_empty.stderr +++ b/tests/ui/comparison_to_empty.stderr @@ -1,5 +1,5 @@ error: comparison to empty slice - --> $DIR/comparison_to_empty.rs:9:13 + --> $DIR/comparison_to_empty.rs:10:13 | LL | let _ = s == ""; | ^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()` @@ -7,22 +7,52 @@ LL | let _ = s == ""; = note: `-D clippy::comparison-to-empty` implied by `-D warnings` error: comparison to empty slice - --> $DIR/comparison_to_empty.rs:10:13 + --> $DIR/comparison_to_empty.rs:11:13 | LL | let _ = s != ""; | ^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!s.is_empty()` error: comparison to empty slice - --> $DIR/comparison_to_empty.rs:13:13 + --> $DIR/comparison_to_empty.rs:14:13 | LL | let _ = v == []; | ^^^^^^^ help: using `is_empty` is clearer and more explicit: `v.is_empty()` error: comparison to empty slice - --> $DIR/comparison_to_empty.rs:14:13 + --> $DIR/comparison_to_empty.rs:15:13 | LL | let _ = v != []; | ^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!v.is_empty()` -error: aborting due to 4 previous errors +error: comparison to empty slice using `if let` + --> $DIR/comparison_to_empty.rs:16:8 + | +LL | if let [] = &*v {} + | ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `(*v).is_empty()` + +error: comparison to empty slice using `if let` + --> $DIR/comparison_to_empty.rs:18:8 + | +LL | if let [] = s {} + | ^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()` + +error: comparison to empty slice using `if let` + --> $DIR/comparison_to_empty.rs:19:8 + | +LL | if let [] = &*s {} + | ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()` + +error: comparison to empty slice using `if let` + --> $DIR/comparison_to_empty.rs:20:8 + | +LL | if let [] = &*s && s == [] {} + | ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()` + +error: comparison to empty slice + --> $DIR/comparison_to_empty.rs:20:24 + | +LL | if let [] = &*s && s == [] {} + | ^^^^^^^ help: using `is_empty` is clearer and more explicit: `s.is_empty()` + +error: aborting due to 9 previous errors diff --git a/tests/ui/error_impl_error.rs b/tests/ui/error_impl_error.rs new file mode 100644 index 0000000000000..40ce4181bf34a --- /dev/null +++ b/tests/ui/error_impl_error.rs @@ -0,0 +1,90 @@ +#![allow(unused)] +#![warn(clippy::error_impl_error)] +#![no_main] + +pub mod a { + #[derive(Debug)] + pub struct Error; + + impl std::fmt::Display for Error { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } + } + + impl std::error::Error for Error {} +} + +mod b { + #[derive(Debug)] + pub(super) enum Error {} + + impl std::fmt::Display for Error { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } + } + + impl std::error::Error for Error {} +} + +pub mod c { + pub union Error { + a: u32, + b: u32, + } + + impl std::fmt::Debug for Error { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } + } + + impl std::fmt::Display for Error { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } + } + + impl std::error::Error for Error {} +} + +pub mod d { + pub type Error = std::fmt::Error; +} + +mod e { + #[derive(Debug)] + pub(super) struct MyError; + + impl std::fmt::Display for MyError { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } + } + + impl std::error::Error for MyError {} +} + +pub mod f { + pub type MyError = std::fmt::Error; +} + +// Do not lint module-private types + +mod g { + #[derive(Debug)] + enum Error {} + + impl std::fmt::Display for Error { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } + } + + impl std::error::Error for Error {} +} + +mod h { + type Error = std::fmt::Error; +} diff --git a/tests/ui/error_impl_error.stderr b/tests/ui/error_impl_error.stderr new file mode 100644 index 0000000000000..f3e04b6416731 --- /dev/null +++ b/tests/ui/error_impl_error.stderr @@ -0,0 +1,45 @@ +error: exported type named `Error` that implements `Error` + --> $DIR/error_impl_error.rs:7:16 + | +LL | pub struct Error; + | ^^^^^ + | +note: `Error` was implemented here + --> $DIR/error_impl_error.rs:15:5 + | +LL | impl std::error::Error for Error {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `-D clippy::error-impl-error` implied by `-D warnings` + +error: exported type named `Error` that implements `Error` + --> $DIR/error_impl_error.rs:20:21 + | +LL | pub(super) enum Error {} + | ^^^^^ + | +note: `Error` was implemented here + --> $DIR/error_impl_error.rs:28:5 + | +LL | impl std::error::Error for Error {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: exported type named `Error` that implements `Error` + --> $DIR/error_impl_error.rs:32:15 + | +LL | pub union Error { + | ^^^^^ + | +note: `Error` was implemented here + --> $DIR/error_impl_error.rs:49:5 + | +LL | impl std::error::Error for Error {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: exported type alias named `Error` that implements `Error` + --> $DIR/error_impl_error.rs:53:14 + | +LL | pub type Error = std::fmt::Error; + | ^^^^^ + +error: aborting due to 4 previous errors + diff --git a/tests/ui/eta.fixed b/tests/ui/eta.fixed index db7bd99e0ae6c..ddabe7616d093 100644 --- a/tests/ui/eta.fixed +++ b/tests/ui/eta.fixed @@ -345,3 +345,58 @@ fn angle_brackets_and_args() { let dyn_opt: Option<&dyn TestTrait> = Some(&test_struct); dyn_opt.map(::method_on_dyn); } + +fn _late_bound_to_early_bound_regions() { + struct Foo<'a>(&'a u32); + impl<'a> Foo<'a> { + fn f(x: &'a u32) -> Self { + Foo(x) + } + } + fn f(f: impl for<'a> Fn(&'a u32) -> Foo<'a>) -> Foo<'static> { + f(&0) + } + + let _ = f(|x| Foo::f(x)); + + struct Bar; + impl<'a> From<&'a u32> for Bar { + fn from(x: &'a u32) -> Bar { + Bar + } + } + fn f2(f: impl for<'a> Fn(&'a u32) -> Bar) -> Bar { + f(&0) + } + + let _ = f2(|x| ::from(x)); + + struct Baz<'a>(&'a u32); + fn f3(f: impl Fn(&u32) -> Baz<'_>) -> Baz<'static> { + f(&0) + } + + let _ = f3(|x| Baz(x)); +} + +fn _mixed_late_bound_and_early_bound_regions() { + fn f(t: T, f: impl Fn(T, &u32) -> u32) -> u32 { + f(t, &0) + } + fn f2<'a, T: 'a>(_: &'a T, y: &u32) -> u32 { + *y + } + let _ = f(&0, f2); +} + +fn _closure_with_types() { + fn f(x: T) -> T { + x + } + fn f2(f: impl Fn(T) -> T) -> T { + f(T::default()) + } + + let _ = f2(|x: u32| f(x)); + let _ = f2(|x| -> u32 { f(x) }); +} diff --git a/tests/ui/eta.rs b/tests/ui/eta.rs index 52fc17686fdfc..92ecff6eb1afb 100644 --- a/tests/ui/eta.rs +++ b/tests/ui/eta.rs @@ -345,3 +345,58 @@ fn angle_brackets_and_args() { let dyn_opt: Option<&dyn TestTrait> = Some(&test_struct); dyn_opt.map(|d| d.method_on_dyn()); } + +fn _late_bound_to_early_bound_regions() { + struct Foo<'a>(&'a u32); + impl<'a> Foo<'a> { + fn f(x: &'a u32) -> Self { + Foo(x) + } + } + fn f(f: impl for<'a> Fn(&'a u32) -> Foo<'a>) -> Foo<'static> { + f(&0) + } + + let _ = f(|x| Foo::f(x)); + + struct Bar; + impl<'a> From<&'a u32> for Bar { + fn from(x: &'a u32) -> Bar { + Bar + } + } + fn f2(f: impl for<'a> Fn(&'a u32) -> Bar) -> Bar { + f(&0) + } + + let _ = f2(|x| ::from(x)); + + struct Baz<'a>(&'a u32); + fn f3(f: impl Fn(&u32) -> Baz<'_>) -> Baz<'static> { + f(&0) + } + + let _ = f3(|x| Baz(x)); +} + +fn _mixed_late_bound_and_early_bound_regions() { + fn f(t: T, f: impl Fn(T, &u32) -> u32) -> u32 { + f(t, &0) + } + fn f2<'a, T: 'a>(_: &'a T, y: &u32) -> u32 { + *y + } + let _ = f(&0, |x, y| f2(x, y)); +} + +fn _closure_with_types() { + fn f(x: T) -> T { + x + } + fn f2(f: impl Fn(T) -> T) -> T { + f(T::default()) + } + + let _ = f2(|x: u32| f(x)); + let _ = f2(|x| -> u32 { f(x) }); +} diff --git a/tests/ui/eta.stderr b/tests/ui/eta.stderr index 0ac0b901df446..ff40a2074e561 100644 --- a/tests/ui/eta.stderr +++ b/tests/ui/eta.stderr @@ -158,5 +158,11 @@ error: redundant closure LL | dyn_opt.map(|d| d.method_on_dyn()); | ^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `::method_on_dyn` -error: aborting due to 26 previous errors +error: redundant closure + --> $DIR/eta.rs:389:19 + | +LL | let _ = f(&0, |x, y| f2(x, y)); + | ^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `f2` + +error: aborting due to 27 previous errors diff --git a/tests/ui/filter_map_bool_then.fixed b/tests/ui/filter_map_bool_then.fixed new file mode 100644 index 0000000000000..3e72fee4b072a --- /dev/null +++ b/tests/ui/filter_map_bool_then.fixed @@ -0,0 +1,44 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow( + clippy::clone_on_copy, + clippy::map_identity, + clippy::unnecessary_lazy_evaluations, + unused +)] +#![warn(clippy::filter_map_bool_then)] + +#[macro_use] +extern crate proc_macros; + +#[derive(Clone, PartialEq)] +struct NonCopy; + +fn main() { + let v = vec![1, 2, 3, 4, 5, 6]; + v.clone().iter().filter(|&i| (i % 2 == 0)).map(|i| i + 1); + v.clone().into_iter().filter(|&i| (i % 2 == 0)).map(|i| i + 1); + v.clone() + .into_iter() + .filter(|&i| (i % 2 == 0)).map(|i| i + 1); + v.clone() + .into_iter() + .filter(|&i| i != 1000) + .filter(|&i| (i % 2 == 0)).map(|i| i + 1); + v.iter() + .copied() + .filter(|&i| i != 1000) + .filter(|&i| (i.clone() % 2 == 0)).map(|i| i + 1); + // Do not lint + let v = vec![NonCopy, NonCopy]; + v.clone().iter().filter_map(|i| (i == &NonCopy).then(|| i)); + external! { + let v = vec![1, 2, 3, 4, 5, 6]; + v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); + } + with_span! { + span + let v = vec![1, 2, 3, 4, 5, 6]; + v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); + } +} diff --git a/tests/ui/filter_map_bool_then.rs b/tests/ui/filter_map_bool_then.rs new file mode 100644 index 0000000000000..38a04e57de456 --- /dev/null +++ b/tests/ui/filter_map_bool_then.rs @@ -0,0 +1,44 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow( + clippy::clone_on_copy, + clippy::map_identity, + clippy::unnecessary_lazy_evaluations, + unused +)] +#![warn(clippy::filter_map_bool_then)] + +#[macro_use] +extern crate proc_macros; + +#[derive(Clone, PartialEq)] +struct NonCopy; + +fn main() { + let v = vec![1, 2, 3, 4, 5, 6]; + v.clone().iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); + v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); + v.clone() + .into_iter() + .filter_map(|i| -> Option<_> { (i % 2 == 0).then(|| i + 1) }); + v.clone() + .into_iter() + .filter(|&i| i != 1000) + .filter_map(|i| (i % 2 == 0).then(|| i + 1)); + v.iter() + .copied() + .filter(|&i| i != 1000) + .filter_map(|i| (i.clone() % 2 == 0).then(|| i + 1)); + // Do not lint + let v = vec![NonCopy, NonCopy]; + v.clone().iter().filter_map(|i| (i == &NonCopy).then(|| i)); + external! { + let v = vec![1, 2, 3, 4, 5, 6]; + v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); + } + with_span! { + span + let v = vec![1, 2, 3, 4, 5, 6]; + v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); + } +} diff --git a/tests/ui/filter_map_bool_then.stderr b/tests/ui/filter_map_bool_then.stderr new file mode 100644 index 0000000000000..b411cd83dfd29 --- /dev/null +++ b/tests/ui/filter_map_bool_then.stderr @@ -0,0 +1,34 @@ +error: usage of `bool::then` in `filter_map` + --> $DIR/filter_map_bool_then.rs:19:22 + | +LL | v.clone().iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i % 2 == 0)).map(|i| i + 1)` + | + = note: `-D clippy::filter-map-bool-then` implied by `-D warnings` + +error: usage of `bool::then` in `filter_map` + --> $DIR/filter_map_bool_then.rs:20:27 + | +LL | v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i % 2 == 0)).map(|i| i + 1)` + +error: usage of `bool::then` in `filter_map` + --> $DIR/filter_map_bool_then.rs:23:10 + | +LL | .filter_map(|i| -> Option<_> { (i % 2 == 0).then(|| i + 1) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i % 2 == 0)).map(|i| i + 1)` + +error: usage of `bool::then` in `filter_map` + --> $DIR/filter_map_bool_then.rs:27:10 + | +LL | .filter_map(|i| (i % 2 == 0).then(|| i + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i % 2 == 0)).map(|i| i + 1)` + +error: usage of `bool::then` in `filter_map` + --> $DIR/filter_map_bool_then.rs:31:10 + | +LL | .filter_map(|i| (i.clone() % 2 == 0).then(|| i + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i.clone() % 2 == 0)).map(|i| i + 1)` + +error: aborting due to 5 previous errors + diff --git a/tests/ui/format_collect.rs b/tests/ui/format_collect.rs new file mode 100644 index 0000000000000..c7f2b7b695074 --- /dev/null +++ b/tests/ui/format_collect.rs @@ -0,0 +1,31 @@ +#![allow(unused, dead_code)] +#![warn(clippy::format_collect)] + +fn hex_encode(bytes: &[u8]) -> String { + bytes.iter().map(|b| format!("{b:02X}")).collect() +} + +#[rustfmt::skip] +fn hex_encode_deep(bytes: &[u8]) -> String { + bytes.iter().map(|b| {{{{{ format!("{b:02X}") }}}}}).collect() +} + +macro_rules! fmt { + ($x:ident) => { + format!("{x:02X}", x = $x) + }; +} + +fn from_macro(bytes: &[u8]) -> String { + bytes.iter().map(|x| fmt!(x)).collect() +} + +fn with_block() -> String { + (1..10) + .map(|s| { + let y = 1; + format!("{s} {y}") + }) + .collect() +} +fn main() {} diff --git a/tests/ui/format_collect.stderr b/tests/ui/format_collect.stderr new file mode 100644 index 0000000000000..d918f1ed466b1 --- /dev/null +++ b/tests/ui/format_collect.stderr @@ -0,0 +1,62 @@ +error: use of `format!` to build up a string from an iterator + --> $DIR/format_collect.rs:5:5 + | +LL | bytes.iter().map(|b| format!("{b:02X}")).collect() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: call `fold` instead + --> $DIR/format_collect.rs:5:18 + | +LL | bytes.iter().map(|b| format!("{b:02X}")).collect() + | ^^^ +help: ... and use the `write!` macro here + --> $DIR/format_collect.rs:5:26 + | +LL | bytes.iter().map(|b| format!("{b:02X}")).collect() + | ^^^^^^^^^^^^^^^^^^ + = note: this can be written more efficiently by appending to a `String` directly + = note: `-D clippy::format-collect` implied by `-D warnings` + +error: use of `format!` to build up a string from an iterator + --> $DIR/format_collect.rs:10:5 + | +LL | bytes.iter().map(|b| {{{{{ format!("{b:02X}") }}}}}).collect() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: call `fold` instead + --> $DIR/format_collect.rs:10:18 + | +LL | bytes.iter().map(|b| {{{{{ format!("{b:02X}") }}}}}).collect() + | ^^^ +help: ... and use the `write!` macro here + --> $DIR/format_collect.rs:10:32 + | +LL | bytes.iter().map(|b| {{{{{ format!("{b:02X}") }}}}}).collect() + | ^^^^^^^^^^^^^^^^^^ + = note: this can be written more efficiently by appending to a `String` directly + +error: use of `format!` to build up a string from an iterator + --> $DIR/format_collect.rs:24:5 + | +LL | / (1..10) +LL | | .map(|s| { +LL | | let y = 1; +LL | | format!("{s} {y}") +LL | | }) +LL | | .collect() + | |__________________^ + | +help: call `fold` instead + --> $DIR/format_collect.rs:25:10 + | +LL | .map(|s| { + | ^^^ +help: ... and use the `write!` macro here + --> $DIR/format_collect.rs:27:13 + | +LL | format!("{s} {y}") + | ^^^^^^^^^^^^^^^^^^ + = note: this can be written more efficiently by appending to a `String` directly + +error: aborting due to 3 previous errors + diff --git a/tests/ui/four_forward_slashes.fixed b/tests/ui/four_forward_slashes.fixed new file mode 100644 index 0000000000000..54b2c414b6208 --- /dev/null +++ b/tests/ui/four_forward_slashes.fixed @@ -0,0 +1,48 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![feature(custom_inner_attributes)] +#![allow(unused)] +#![warn(clippy::four_forward_slashes)] +#![no_main] +#![rustfmt::skip] + +#[macro_use] +extern crate proc_macros; + +/// whoops +fn a() {} + +/// whoops +#[allow(dead_code)] +fn b() {} + +/// whoops +/// two borked comments! +#[track_caller] +fn c() {} + +fn d() {} + +#[test] +/// between attributes +#[allow(dead_code)] +fn g() {} + +/// not very start of contents +fn h() {} + +fn i() { + //// don't lint me bozo + todo!() +} + +external! { + //// don't lint me bozo + fn e() {} +} + +with_span! { + span + //// don't lint me bozo + fn f() {} +} diff --git a/tests/ui/four_forward_slashes.rs b/tests/ui/four_forward_slashes.rs new file mode 100644 index 0000000000000..facdc8cb17dba --- /dev/null +++ b/tests/ui/four_forward_slashes.rs @@ -0,0 +1,48 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![feature(custom_inner_attributes)] +#![allow(unused)] +#![warn(clippy::four_forward_slashes)] +#![no_main] +#![rustfmt::skip] + +#[macro_use] +extern crate proc_macros; + +//// whoops +fn a() {} + +//// whoops +#[allow(dead_code)] +fn b() {} + +//// whoops +//// two borked comments! +#[track_caller] +fn c() {} + +fn d() {} + +#[test] +//// between attributes +#[allow(dead_code)] +fn g() {} + + //// not very start of contents +fn h() {} + +fn i() { + //// don't lint me bozo + todo!() +} + +external! { + //// don't lint me bozo + fn e() {} +} + +with_span! { + span + //// don't lint me bozo + fn f() {} +} diff --git a/tests/ui/four_forward_slashes.stderr b/tests/ui/four_forward_slashes.stderr new file mode 100644 index 0000000000000..89162e6b010e8 --- /dev/null +++ b/tests/ui/four_forward_slashes.stderr @@ -0,0 +1,68 @@ +error: this item has comments with 4 forward slashes (`////`). These look like doc comments, but they aren't + --> $DIR/four_forward_slashes.rs:12:1 + | +LL | / //// whoops +LL | | fn a() {} + | |_ + | + = note: `-D clippy::four-forward-slashes` implied by `-D warnings` +help: make this a doc comment by removing one `/` + | +LL + /// whoops + | + +error: this item has comments with 4 forward slashes (`////`). These look like doc comments, but they aren't + --> $DIR/four_forward_slashes.rs:15:1 + | +LL | / //// whoops +LL | | #[allow(dead_code)] +LL | | fn b() {} + | |_ + | +help: make this a doc comment by removing one `/` + | +LL + /// whoops + | + +error: this item has comments with 4 forward slashes (`////`). These look like doc comments, but they aren't + --> $DIR/four_forward_slashes.rs:19:1 + | +LL | / //// whoops +LL | | //// two borked comments! +LL | | #[track_caller] +LL | | fn c() {} + | |_ + | +help: turn these into doc comments by removing one `/` + | +LL + /// whoops +LL ~ /// two borked comments! + | + +error: this item has comments with 4 forward slashes (`////`). These look like doc comments, but they aren't + --> $DIR/four_forward_slashes.rs:27:1 + | +LL | / //// between attributes +LL | | #[allow(dead_code)] +LL | | fn g() {} + | |_ + | +help: make this a doc comment by removing one `/` + | +LL + /// between attributes + | + +error: this item has comments with 4 forward slashes (`////`). These look like doc comments, but they aren't + --> $DIR/four_forward_slashes.rs:31:1 + | +LL | / //// not very start of contents +LL | | fn h() {} + | |_ + | +help: make this a doc comment by removing one `/` + | +LL + /// not very start of contents + | + +error: aborting due to 5 previous errors + diff --git a/tests/ui/four_forward_slashes_first_line.fixed b/tests/ui/four_forward_slashes_first_line.fixed new file mode 100644 index 0000000000000..ce272b4c6cd78 --- /dev/null +++ b/tests/ui/four_forward_slashes_first_line.fixed @@ -0,0 +1,7 @@ +/// borked doc comment on the first line. doesn't combust! +fn a() {} + +//@run-rustfix +// This test's entire purpose is to make sure we don't panic if the comment with four slashes +// extends to the first line of the file. This is likely pretty rare in production, but an ICE is an +// ICE. diff --git a/tests/ui/four_forward_slashes_first_line.rs b/tests/ui/four_forward_slashes_first_line.rs new file mode 100644 index 0000000000000..d8f82d4410b8a --- /dev/null +++ b/tests/ui/four_forward_slashes_first_line.rs @@ -0,0 +1,7 @@ +//// borked doc comment on the first line. doesn't combust! +fn a() {} + +//@run-rustfix +// This test's entire purpose is to make sure we don't panic if the comment with four slashes +// extends to the first line of the file. This is likely pretty rare in production, but an ICE is an +// ICE. diff --git a/tests/ui/four_forward_slashes_first_line.stderr b/tests/ui/four_forward_slashes_first_line.stderr new file mode 100644 index 0000000000000..7944da14feb0e --- /dev/null +++ b/tests/ui/four_forward_slashes_first_line.stderr @@ -0,0 +1,15 @@ +error: this item has comments with 4 forward slashes (`////`). These look like doc comments, but they aren't + --> $DIR/four_forward_slashes_first_line.rs:1:1 + | +LL | / //// borked doc comment on the first line. doesn't combust! +LL | | fn a() {} + | |_ + | + = note: `-D clippy::four-forward-slashes` implied by `-D warnings` +help: make this a doc comment by removing one `/` + | +LL + /// borked doc comment on the first line. doesn't combust! + | + +error: aborting due to previous error + diff --git a/tests/ui/if_same_then_else.rs b/tests/ui/if_same_then_else.rs index dad4543f84c09..e84b20e9fef07 100644 --- a/tests/ui/if_same_then_else.rs +++ b/tests/ui/if_same_then_else.rs @@ -214,4 +214,45 @@ mod issue_8836 { } } +mod issue_11213 { + fn reproducer(x: bool) -> bool { + if x { + 0_u8.is_power_of_two() + } else { + 0_u16.is_power_of_two() + } + } + + // a more obvious reproducer that shows + // why the code above is problematic: + fn v2(x: bool) -> bool { + trait Helper { + fn is_u8(&self) -> bool; + } + impl Helper for u8 { + fn is_u8(&self) -> bool { + true + } + } + impl Helper for u16 { + fn is_u8(&self) -> bool { + false + } + } + + // this is certainly not the same code in both branches + // it returns a different bool depending on the branch. + if x { 0_u8.is_u8() } else { 0_u16.is_u8() } + } + + fn do_lint(x: bool) -> bool { + // but do lint if the type of the literal is the same + if x { + 0_u8.is_power_of_two() + } else { + 0_u8.is_power_of_two() + } + } +} + fn main() {} diff --git a/tests/ui/if_same_then_else.stderr b/tests/ui/if_same_then_else.stderr index a34fc565590b0..774cc08685a7f 100644 --- a/tests/ui/if_same_then_else.stderr +++ b/tests/ui/if_same_then_else.stderr @@ -108,5 +108,23 @@ LL | | bar + 1; LL | | } | |_____^ -error: aborting due to 5 previous errors +error: this `if` has identical blocks + --> $DIR/if_same_then_else.rs:250:14 + | +LL | if x { + | ______________^ +LL | | 0_u8.is_power_of_two() +LL | | } else { + | |_________^ + | +note: same as this + --> $DIR/if_same_then_else.rs:252:16 + | +LL | } else { + | ________________^ +LL | | 0_u8.is_power_of_two() +LL | | } + | |_________^ + +error: aborting due to 6 previous errors diff --git a/tests/ui/ifs_same_cond.rs b/tests/ui/ifs_same_cond.rs index 5c338e3c5c8f2..ad77346b75f24 100644 --- a/tests/ui/ifs_same_cond.rs +++ b/tests/ui/ifs_same_cond.rs @@ -46,6 +46,10 @@ fn ifs_same_cond() { // ok, functions } else if v.len() == 42 { } + + if let Some(env1) = option_env!("ENV1") { + } else if let Some(env2) = option_env!("ENV2") { + } } fn issue10272() { diff --git a/tests/ui/ifs_same_cond.stderr b/tests/ui/ifs_same_cond.stderr index 8d70934476cbc..3f52c10b7620a 100644 --- a/tests/ui/ifs_same_cond.stderr +++ b/tests/ui/ifs_same_cond.stderr @@ -36,13 +36,13 @@ LL | if 2 * a == 1 { | ^^^^^^^^^^ error: this `if` has the same condition as a previous `if` - --> $DIR/ifs_same_cond.rs:54:15 + --> $DIR/ifs_same_cond.rs:58:15 | LL | } else if a.contains("ah") { | ^^^^^^^^^^^^^^^^ | note: same as this - --> $DIR/ifs_same_cond.rs:53:8 + --> $DIR/ifs_same_cond.rs:57:8 | LL | if a.contains("ah") { | ^^^^^^^^^^^^^^^^ diff --git a/tests/ui/incorrect_partial_ord_impl_on_ord_type.fixed b/tests/ui/incorrect_partial_ord_impl_on_ord_type.fixed index dd4fdd98822c7..2f51bf274804f 100644 --- a/tests/ui/incorrect_partial_ord_impl_on_ord_type.fixed +++ b/tests/ui/incorrect_partial_ord_impl_on_ord_type.fixed @@ -1,5 +1,4 @@ //@run-rustfix -#![allow(unused)] #![no_main] use std::cmp::Ordering; @@ -112,3 +111,35 @@ impl PartialOrd for F { todo!(); } } + +// #11178, do not lint + +#[derive(Eq, PartialEq)] +struct G(u32); + +impl Ord for G { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } +} + +impl PartialOrd for G { + fn partial_cmp(&self, other: &Self) -> Option { + Some(Self::cmp(self, other)) + } +} + +#[derive(Eq, PartialEq)] +struct H(u32); + +impl Ord for H { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } +} + +impl PartialOrd for H { + fn partial_cmp(&self, other: &Self) -> Option { + Some(Ord::cmp(self, other)) + } +} diff --git a/tests/ui/incorrect_partial_ord_impl_on_ord_type.rs b/tests/ui/incorrect_partial_ord_impl_on_ord_type.rs index 522e82299c0a5..47127bdaec229 100644 --- a/tests/ui/incorrect_partial_ord_impl_on_ord_type.rs +++ b/tests/ui/incorrect_partial_ord_impl_on_ord_type.rs @@ -1,5 +1,4 @@ //@run-rustfix -#![allow(unused)] #![no_main] use std::cmp::Ordering; @@ -116,3 +115,35 @@ impl PartialOrd for F { todo!(); } } + +// #11178, do not lint + +#[derive(Eq, PartialEq)] +struct G(u32); + +impl Ord for G { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } +} + +impl PartialOrd for G { + fn partial_cmp(&self, other: &Self) -> Option { + Some(Self::cmp(self, other)) + } +} + +#[derive(Eq, PartialEq)] +struct H(u32); + +impl Ord for H { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } +} + +impl PartialOrd for H { + fn partial_cmp(&self, other: &Self) -> Option { + Some(Ord::cmp(self, other)) + } +} diff --git a/tests/ui/incorrect_partial_ord_impl_on_ord_type.stderr b/tests/ui/incorrect_partial_ord_impl_on_ord_type.stderr index 0e477798c406e..66048fc90005b 100644 --- a/tests/ui/incorrect_partial_ord_impl_on_ord_type.stderr +++ b/tests/ui/incorrect_partial_ord_impl_on_ord_type.stderr @@ -1,5 +1,5 @@ error: incorrect implementation of `partial_cmp` on an `Ord` type - --> $DIR/incorrect_partial_ord_impl_on_ord_type.rs:18:1 + --> $DIR/incorrect_partial_ord_impl_on_ord_type.rs:17:1 | LL | / impl PartialOrd for A { LL | | fn partial_cmp(&self, other: &Self) -> Option { @@ -13,7 +13,7 @@ LL | | } = note: `#[deny(clippy::incorrect_partial_ord_impl_on_ord_type)]` on by default error: incorrect implementation of `partial_cmp` on an `Ord` type - --> $DIR/incorrect_partial_ord_impl_on_ord_type.rs:52:1 + --> $DIR/incorrect_partial_ord_impl_on_ord_type.rs:51:1 | LL | / impl PartialOrd for C { LL | | fn partial_cmp(&self, _: &Self) -> Option { diff --git a/tests/ui/incorrect_partial_ord_impl_on_ord_type_fully_qual.rs b/tests/ui/incorrect_partial_ord_impl_on_ord_type_fully_qual.rs new file mode 100644 index 0000000000000..3a3b84f93c462 --- /dev/null +++ b/tests/ui/incorrect_partial_ord_impl_on_ord_type_fully_qual.rs @@ -0,0 +1,51 @@ +// This test's filename is... a bit verbose. But it ensures we suggest the correct code when `Ord` +// is not in scope. +#![no_main] +#![no_implicit_prelude] + +extern crate std; + +use std::cmp::{self, Eq, Ordering, PartialEq, PartialOrd}; +use std::option::Option::{self, Some}; +use std::todo; + +// lint + +#[derive(Eq, PartialEq)] +struct A(u32); + +impl cmp::Ord for A { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } +} + +impl PartialOrd for A { + fn partial_cmp(&self, other: &Self) -> Option { + // NOTE: This suggestion is wrong, as `Ord` is not in scope. But this should be fine as it isn't + // automatically applied + todo!(); + } +} + +#[derive(Eq, PartialEq)] +struct B(u32); + +impl B { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } +} + +impl cmp::Ord for B { + fn cmp(&self, other: &Self) -> Ordering { + todo!(); + } +} + +impl PartialOrd for B { + fn partial_cmp(&self, other: &Self) -> Option { + // This calls `B.cmp`, not `Ord::cmp`! + Some(self.cmp(other)) + } +} diff --git a/tests/ui/incorrect_partial_ord_impl_on_ord_type_fully_qual.stderr b/tests/ui/incorrect_partial_ord_impl_on_ord_type_fully_qual.stderr new file mode 100644 index 0000000000000..f4374c2812877 --- /dev/null +++ b/tests/ui/incorrect_partial_ord_impl_on_ord_type_fully_qual.stderr @@ -0,0 +1,31 @@ +error: incorrect implementation of `partial_cmp` on an `Ord` type + --> $DIR/incorrect_partial_ord_impl_on_ord_type_fully_qual.rs:23:1 + | +LL | / impl PartialOrd for A { +LL | | fn partial_cmp(&self, other: &Self) -> Option { + | | _____________________________________________________________- +LL | || // NOTE: This suggestion is wrong, as `Ord` is not in scope. But this should be fine as it isn't +LL | || // automatically applied +LL | || todo!(); +LL | || } + | ||_____- help: change this to: `{ Some(self.cmp(other)) }` +LL | | } + | |__^ + | + = note: `#[deny(clippy::incorrect_partial_ord_impl_on_ord_type)]` on by default + +error: incorrect implementation of `partial_cmp` on an `Ord` type + --> $DIR/incorrect_partial_ord_impl_on_ord_type_fully_qual.rs:46:1 + | +LL | / impl PartialOrd for B { +LL | | fn partial_cmp(&self, other: &Self) -> Option { + | | _____________________________________________________________- +LL | || // This calls `B.cmp`, not `Ord::cmp`! +LL | || Some(self.cmp(other)) +LL | || } + | ||_____- help: change this to: `{ Some(std::cmp::Ord::cmp(self, other)) }` +LL | | } + | |__^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/infinite_loop.stderr b/tests/ui/infinite_loop.stderr index 701b3cd190410..04559f9ada431 100644 --- a/tests/ui/infinite_loop.stderr +++ b/tests/ui/infinite_loop.stderr @@ -1,11 +1,3 @@ -error: this argument is a mutable reference, but not used mutably - --> $DIR/infinite_loop.rs:7:17 - | -LL | fn fn_mutref(i: &mut i32) { - | ^^^^^^^^ help: consider changing to: `&i32` - | - = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` - error: variables in the condition are not mutated in the loop body --> $DIR/infinite_loop.rs:20:11 | @@ -99,5 +91,13 @@ LL | while y < 10 { = note: this loop contains `return`s or `break`s = help: rewrite it as `if cond { loop { } }` +error: this argument is a mutable reference, but not used mutably + --> $DIR/infinite_loop.rs:7:17 + | +LL | fn fn_mutref(i: &mut i32) { + | ^^^^^^^^ help: consider changing to: `&i32` + | + = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` + error: aborting due to 12 previous errors diff --git a/tests/ui/inherent_to_string.rs b/tests/ui/inherent_to_string.rs index aeb0a0c1e2e84..adb0389a043f8 100644 --- a/tests/ui/inherent_to_string.rs +++ b/tests/ui/inherent_to_string.rs @@ -1,5 +1,4 @@ -#![warn(clippy::inherent_to_string)] -#![deny(clippy::inherent_to_string_shadow_display)] +#![allow(improper_ctypes_definitions)] use std::fmt; @@ -14,6 +13,9 @@ struct D; struct E; struct F; struct G; +struct H; +struct I; +struct J; impl A { // Should be detected; emit warning @@ -80,6 +82,26 @@ impl G { } } +// Issue #11201 + +impl H { + unsafe fn to_string(&self) -> String { + "G.to_string()".to_string() + } +} + +impl I { + extern "C" fn to_string(&self) -> String { + "G.to_string()".to_string() + } +} + +impl J { + unsafe extern "C" fn to_string(&self) -> String { + "G.to_string()".to_string() + } +} + fn main() { let a = A; a.to_string(); diff --git a/tests/ui/inherent_to_string.stderr b/tests/ui/inherent_to_string.stderr index 443fecae1aadf..579b3c8c56f7c 100644 --- a/tests/ui/inherent_to_string.stderr +++ b/tests/ui/inherent_to_string.stderr @@ -1,5 +1,5 @@ error: implementation of inherent method `to_string(&self) -> String` for type `A` - --> $DIR/inherent_to_string.rs:20:5 + --> $DIR/inherent_to_string.rs:22:5 | LL | / fn to_string(&self) -> String { LL | | "A.to_string()".to_string() @@ -10,7 +10,7 @@ LL | | } = note: `-D clippy::inherent-to-string` implied by `-D warnings` error: type `C` implements inherent method `to_string(&self) -> String` which shadows the implementation of `Display` - --> $DIR/inherent_to_string.rs:44:5 + --> $DIR/inherent_to_string.rs:46:5 | LL | / fn to_string(&self) -> String { LL | | "C.to_string()".to_string() @@ -18,11 +18,7 @@ LL | | } | |_____^ | = help: remove the inherent method from type `C` -note: the lint level is defined here - --> $DIR/inherent_to_string.rs:2:9 - | -LL | #![deny(clippy::inherent_to_string_shadow_display)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `#[deny(clippy::inherent_to_string_shadow_display)]` on by default error: aborting due to 2 previous errors diff --git a/tests/ui/iter_skip_zero.fixed b/tests/ui/iter_skip_zero.fixed new file mode 100644 index 0000000000000..1eb0984fe073f --- /dev/null +++ b/tests/ui/iter_skip_zero.fixed @@ -0,0 +1,25 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow(clippy::useless_vec, unused)] +#![warn(clippy::iter_skip_zero)] + +#[macro_use] +extern crate proc_macros; + +use std::iter::once; + +fn main() { + let _ = [1, 2, 3].iter().skip(1); + let _ = vec![1, 2, 3].iter().skip(1); + let _ = once([1, 2, 3]).skip(1); + let _ = vec![1, 2, 3].iter().chain([1, 2, 3].iter().skip(1)).skip(1); + // Don't lint + let _ = [1, 2, 3].iter().skip(1); + let _ = vec![1, 2, 3].iter().skip(1); + external! { + let _ = [1, 2, 3].iter().skip(0); + } + with_span! { + let _ = [1, 2, 3].iter().skip(0); + } +} diff --git a/tests/ui/iter_skip_zero.rs b/tests/ui/iter_skip_zero.rs new file mode 100644 index 0000000000000..8c103ab1d5b32 --- /dev/null +++ b/tests/ui/iter_skip_zero.rs @@ -0,0 +1,25 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow(clippy::useless_vec, unused)] +#![warn(clippy::iter_skip_zero)] + +#[macro_use] +extern crate proc_macros; + +use std::iter::once; + +fn main() { + let _ = [1, 2, 3].iter().skip(0); + let _ = vec![1, 2, 3].iter().skip(0); + let _ = once([1, 2, 3]).skip(0); + let _ = vec![1, 2, 3].iter().chain([1, 2, 3].iter().skip(0)).skip(0); + // Don't lint + let _ = [1, 2, 3].iter().skip(1); + let _ = vec![1, 2, 3].iter().skip(1); + external! { + let _ = [1, 2, 3].iter().skip(0); + } + with_span! { + let _ = [1, 2, 3].iter().skip(0); + } +} diff --git a/tests/ui/iter_skip_zero.stderr b/tests/ui/iter_skip_zero.stderr new file mode 100644 index 0000000000000..80fecd59e6d10 --- /dev/null +++ b/tests/ui/iter_skip_zero.stderr @@ -0,0 +1,43 @@ +error: usage of `.skip(0)` + --> $DIR/iter_skip_zero.rs:12:35 + | +LL | let _ = [1, 2, 3].iter().skip(0); + | ^ help: if you meant to skip the first element, use: `1` + | + = note: this call to `skip` does nothing and is useless; remove it + = note: `-D clippy::iter-skip-zero` implied by `-D warnings` + +error: usage of `.skip(0)` + --> $DIR/iter_skip_zero.rs:13:39 + | +LL | let _ = vec![1, 2, 3].iter().skip(0); + | ^ help: if you meant to skip the first element, use: `1` + | + = note: this call to `skip` does nothing and is useless; remove it + +error: usage of `.skip(0)` + --> $DIR/iter_skip_zero.rs:14:34 + | +LL | let _ = once([1, 2, 3]).skip(0); + | ^ help: if you meant to skip the first element, use: `1` + | + = note: this call to `skip` does nothing and is useless; remove it + +error: usage of `.skip(0)` + --> $DIR/iter_skip_zero.rs:15:71 + | +LL | let _ = vec![1, 2, 3].iter().chain([1, 2, 3].iter().skip(0)).skip(0); + | ^ help: if you meant to skip the first element, use: `1` + | + = note: this call to `skip` does nothing and is useless; remove it + +error: usage of `.skip(0)` + --> $DIR/iter_skip_zero.rs:15:62 + | +LL | let _ = vec![1, 2, 3].iter().chain([1, 2, 3].iter().skip(0)).skip(0); + | ^ help: if you meant to skip the first element, use: `1` + | + = note: this call to `skip` does nothing and is useless; remove it + +error: aborting due to 5 previous errors + diff --git a/tests/ui/let_and_return.rs b/tests/ui/let_and_return.rs index 7e4d783a026c3..64665cc906f61 100644 --- a/tests/ui/let_and_return.rs +++ b/tests/ui/let_and_return.rs @@ -169,4 +169,14 @@ mod issue_5729 { } } +// https://github.com/rust-lang/rust-clippy/issues/11167 +macro_rules! fn_in_macro { + ($b:block) => { + fn f() -> usize $b + } +} +fn_in_macro!({ + return 1; +}); + fn main() {} diff --git a/tests/ui/let_underscore_future.stderr b/tests/ui/let_underscore_future.stderr index 9e69fb0413309..ff1e2b8c90195 100644 --- a/tests/ui/let_underscore_future.stderr +++ b/tests/ui/let_underscore_future.stderr @@ -1,11 +1,3 @@ -error: this argument is a mutable reference, but not used mutably - --> $DIR/let_underscore_future.rs:11:35 - | -LL | fn do_something_to_future(future: &mut impl Future) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing to: `&impl Future` - | - = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` - error: non-binding `let` on a future --> $DIR/let_underscore_future.rs:14:5 | @@ -31,5 +23,13 @@ LL | let _ = future; | = help: consider awaiting the future or dropping explicitly with `std::mem::drop` +error: this argument is a mutable reference, but not used mutably + --> $DIR/let_underscore_future.rs:11:35 + | +LL | fn do_something_to_future(future: &mut impl Future) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing to: `&impl Future` + | + = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` + error: aborting due to 4 previous errors diff --git a/tests/ui/manual_filter_map.fixed b/tests/ui/manual_filter_map.fixed index 9dd376df2b48a..35872a39a7117 100644 --- a/tests/ui/manual_filter_map.fixed +++ b/tests/ui/manual_filter_map.fixed @@ -120,3 +120,27 @@ fn issue_8920() { .iter() .filter_map(|f| f.result_field.to_owned().ok()); } + +fn issue8010() { + #[derive(Clone)] + enum Enum { + A(i32), + B, + } + + let iter = [Enum::A(123), Enum::B].into_iter(); + + let _x = iter.clone().filter_map(|x| match x { Enum::A(s) => Some(s), _ => None }); + let _x = iter.clone().filter(|x| matches!(x, Enum::B)).map(|x| match x { + Enum::A(s) => s, + _ => unreachable!(), + }); + let _x = iter + .clone() + .filter_map(|x| match x { Enum::A(s) => Some(s), _ => None }); + #[allow(clippy::unused_unit)] + let _x = iter + .clone() + .filter(|x| matches!(x, Enum::B)) + .map(|x| if let Enum::B = x { () } else { unreachable!() }); +} diff --git a/tests/ui/manual_filter_map.rs b/tests/ui/manual_filter_map.rs index 6dd1e066aebdb..50d8d2722c238 100644 --- a/tests/ui/manual_filter_map.rs +++ b/tests/ui/manual_filter_map.rs @@ -133,3 +133,31 @@ fn issue_8920() { .filter(|f| f.result_field.is_ok()) .map(|f| f.result_field.to_owned().unwrap()); } + +fn issue8010() { + #[derive(Clone)] + enum Enum { + A(i32), + B, + } + + let iter = [Enum::A(123), Enum::B].into_iter(); + + let _x = iter.clone().filter(|x| matches!(x, Enum::A(_))).map(|x| match x { + Enum::A(s) => s, + _ => unreachable!(), + }); + let _x = iter.clone().filter(|x| matches!(x, Enum::B)).map(|x| match x { + Enum::A(s) => s, + _ => unreachable!(), + }); + let _x = iter + .clone() + .filter(|x| matches!(x, Enum::A(_))) + .map(|x| if let Enum::A(s) = x { s } else { unreachable!() }); + #[allow(clippy::unused_unit)] + let _x = iter + .clone() + .filter(|x| matches!(x, Enum::B)) + .map(|x| if let Enum::B = x { () } else { unreachable!() }); +} diff --git a/tests/ui/manual_filter_map.stderr b/tests/ui/manual_filter_map.stderr index 882468b0f5f19..0e8672c029309 100644 --- a/tests/ui/manual_filter_map.stderr +++ b/tests/ui/manual_filter_map.stderr @@ -4,6 +4,11 @@ error: `filter(..).map(..)` can be simplified as `filter_map(..)` LL | let _ = (0..).filter(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_opt(a))` | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_filter_map.rs:9:30 + | +LL | let _ = (0..).filter(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap()); + | ^^^^^^^^^^ = note: `-D clippy::manual-filter-map` implied by `-D warnings` error: `filter(..).map(..)` can be simplified as `filter_map(..)` @@ -11,12 +16,24 @@ error: `filter(..).map(..)` can be simplified as `filter_map(..)` | LL | let _ = (0..).filter(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_opt(a))` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_filter_map.rs:12:31 + | +LL | let _ = (0..).filter(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi")); + | ^^^^^^^^^ error: `filter(..).map(..)` can be simplified as `filter_map(..)` --> $DIR/manual_filter_map.rs:15:19 | LL | let _ = (0..).filter(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_res(a).ok())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_filter_map.rs:15:31 + | +LL | let _ = (0..).filter(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); + | ^^^^^^^^^ error: `filter(..).map(..)` can be simplified as `filter_map(..)` --> $DIR/manual_filter_map.rs:18:10 @@ -25,6 +42,12 @@ LL | .filter(|&x| to_ref(to_opt(x)).is_some()) | __________^ LL | | .map(|y| to_ref(to_opt(y)).unwrap()); | |____________________________________________^ help: try: `filter_map(|y| *to_ref(to_opt(y)))` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_filter_map.rs:18:22 + | +LL | .filter(|&x| to_ref(to_opt(x)).is_some()) + | ^^^^^^^^^^^^^^^^^ error: `filter(..).map(..)` can be simplified as `filter_map(..)` --> $DIR/manual_filter_map.rs:21:10 @@ -33,6 +56,12 @@ LL | .filter(|x| to_ref(to_opt(*x)).is_some()) | __________^ LL | | .map(|y| to_ref(to_opt(y)).unwrap()); | |____________________________________________^ help: try: `filter_map(|y| *to_ref(to_opt(y)))` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_filter_map.rs:21:21 + | +LL | .filter(|x| to_ref(to_opt(*x)).is_some()) + | ^^^^^^^^^^^^^^^^^^ error: `filter(..).map(..)` can be simplified as `filter_map(..)` --> $DIR/manual_filter_map.rs:25:10 @@ -41,6 +70,12 @@ LL | .filter(|&x| to_ref(to_res(x)).is_ok()) | __________^ LL | | .map(|y| to_ref(to_res(y)).unwrap()); | |____________________________________________^ help: try: `filter_map(|y| to_ref(to_res(y)).ok())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_filter_map.rs:25:22 + | +LL | .filter(|&x| to_ref(to_res(x)).is_ok()) + | ^^^^^^^^^^^^^^^^^ error: `filter(..).map(..)` can be simplified as `filter_map(..)` --> $DIR/manual_filter_map.rs:28:10 @@ -49,6 +84,12 @@ LL | .filter(|x| to_ref(to_res(*x)).is_ok()) | __________^ LL | | .map(|y| to_ref(to_res(y)).unwrap()); | |____________________________________________^ help: try: `filter_map(|y| to_ref(to_res(y)).ok())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_filter_map.rs:28:21 + | +LL | .filter(|x| to_ref(to_res(*x)).is_ok()) + | ^^^^^^^^^^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_filter_map.rs:34:27 @@ -75,6 +116,12 @@ error: `find(..).map(..)` can be simplified as `find_map(..)` | LL | iter::>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_filter_map.rs:37:41 + | +LL | iter::>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap()); + | ^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_filter_map.rs:39:30 @@ -117,6 +164,12 @@ error: `find(..).map(..)` can be simplified as `find_map(..)` | LL | iter::>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned().ok())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_filter_map.rs:45:45 + | +LL | iter::>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap()); + | ^^^^^^^^^ error: `filter(..).map(..)` can be simplified as `filter_map(..)` --> $DIR/manual_filter_map.rs:93:10 @@ -190,5 +243,23 @@ LL | .filter(|f| f.result_field.is_ok()) LL | | .map(|f| f.result_field.to_owned().unwrap()); | |____________________________________________________^ help: try: `filter_map(|f| f.result_field.to_owned().ok())` -error: aborting due to 27 previous errors +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:146:27 + | +LL | let _x = iter.clone().filter(|x| matches!(x, Enum::A(_))).map(|x| match x { + | ___________________________^ +LL | | Enum::A(s) => s, +LL | | _ => unreachable!(), +LL | | }); + | |______^ help: try: `filter_map(|x| match x { Enum::A(s) => Some(s), _ => None })` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:156:10 + | +LL | .filter(|x| matches!(x, Enum::A(_))) + | __________^ +LL | | .map(|x| if let Enum::A(s) = x { s } else { unreachable!() }); + | |_____________________________________________________________________^ help: try: `filter_map(|x| match x { Enum::A(s) => Some(s), _ => None })` + +error: aborting due to 29 previous errors diff --git a/tests/ui/manual_find_map.stderr b/tests/ui/manual_find_map.stderr index 693a06bb5590b..4e52b5efacf15 100644 --- a/tests/ui/manual_find_map.stderr +++ b/tests/ui/manual_find_map.stderr @@ -4,6 +4,11 @@ error: `find(..).map(..)` can be simplified as `find_map(..)` LL | let _ = (0..).find(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_opt(a))` | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_find_map.rs:9:28 + | +LL | let _ = (0..).find(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap()); + | ^^^^^^^^^^ = note: `-D clippy::manual-find-map` implied by `-D warnings` error: `find(..).map(..)` can be simplified as `find_map(..)` @@ -11,12 +16,24 @@ error: `find(..).map(..)` can be simplified as `find_map(..)` | LL | let _ = (0..).find(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_opt(a))` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_find_map.rs:12:29 + | +LL | let _ = (0..).find(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi")); + | ^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_find_map.rs:15:19 | LL | let _ = (0..).find(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_res(a).ok())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_find_map.rs:15:29 + | +LL | let _ = (0..).find(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); + | ^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_find_map.rs:18:10 @@ -25,6 +42,12 @@ LL | .find(|&x| to_ref(to_opt(x)).is_some()) | __________^ LL | | .map(|y| to_ref(to_opt(y)).unwrap()); | |____________________________________________^ help: try: `find_map(|y| *to_ref(to_opt(y)))` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_find_map.rs:18:20 + | +LL | .find(|&x| to_ref(to_opt(x)).is_some()) + | ^^^^^^^^^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_find_map.rs:21:10 @@ -33,6 +56,12 @@ LL | .find(|x| to_ref(to_opt(*x)).is_some()) | __________^ LL | | .map(|y| to_ref(to_opt(y)).unwrap()); | |____________________________________________^ help: try: `find_map(|y| *to_ref(to_opt(y)))` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_find_map.rs:21:19 + | +LL | .find(|x| to_ref(to_opt(*x)).is_some()) + | ^^^^^^^^^^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_find_map.rs:25:10 @@ -41,6 +70,12 @@ LL | .find(|&x| to_ref(to_res(x)).is_ok()) | __________^ LL | | .map(|y| to_ref(to_res(y)).unwrap()); | |____________________________________________^ help: try: `find_map(|y| to_ref(to_res(y)).ok())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_find_map.rs:25:20 + | +LL | .find(|&x| to_ref(to_res(x)).is_ok()) + | ^^^^^^^^^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_find_map.rs:28:10 @@ -49,6 +84,12 @@ LL | .find(|x| to_ref(to_res(*x)).is_ok()) | __________^ LL | | .map(|y| to_ref(to_res(y)).unwrap()); | |____________________________________________^ help: try: `find_map(|y| to_ref(to_res(y)).ok())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_find_map.rs:28:19 + | +LL | .find(|x| to_ref(to_res(*x)).is_ok()) + | ^^^^^^^^^^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_find_map.rs:34:26 @@ -91,6 +132,12 @@ error: `find(..).map(..)` can be simplified as `find_map(..)` | LL | iter::>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_find_map.rs:40:41 + | +LL | iter::>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap()); + | ^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_find_map.rs:42:30 @@ -133,6 +180,12 @@ error: `find(..).map(..)` can be simplified as `find_map(..)` | LL | iter::>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned().ok())` + | +note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once + --> $DIR/manual_find_map.rs:48:45 + | +LL | iter::>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap()); + | ^^^^^^^^^ error: `find(..).map(..)` can be simplified as `find_map(..)` --> $DIR/manual_find_map.rs:96:10 diff --git a/tests/ui/match_expr_like_matches_macro.fixed b/tests/ui/match_expr_like_matches_macro.fixed index 60f590661735c..f19149cf9def9 100644 --- a/tests/ui/match_expr_like_matches_macro.fixed +++ b/tests/ui/match_expr_like_matches_macro.fixed @@ -5,7 +5,8 @@ unreachable_patterns, dead_code, clippy::equatable_if_let, - clippy::needless_borrowed_reference + clippy::needless_borrowed_reference, + clippy::redundant_guards )] fn main() { diff --git a/tests/ui/match_expr_like_matches_macro.rs b/tests/ui/match_expr_like_matches_macro.rs index afdf1069f5e4a..8f4e58981ea6a 100644 --- a/tests/ui/match_expr_like_matches_macro.rs +++ b/tests/ui/match_expr_like_matches_macro.rs @@ -5,7 +5,8 @@ unreachable_patterns, dead_code, clippy::equatable_if_let, - clippy::needless_borrowed_reference + clippy::needless_borrowed_reference, + clippy::redundant_guards )] fn main() { diff --git a/tests/ui/match_expr_like_matches_macro.stderr b/tests/ui/match_expr_like_matches_macro.stderr index c8c1e5da05fe0..b57b26284ff36 100644 --- a/tests/ui/match_expr_like_matches_macro.stderr +++ b/tests/ui/match_expr_like_matches_macro.stderr @@ -1,5 +1,5 @@ error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:15:14 + --> $DIR/match_expr_like_matches_macro.rs:16:14 | LL | let _y = match x { | ______________^ @@ -11,7 +11,7 @@ LL | | }; = note: `-D clippy::match-like-matches-macro` implied by `-D warnings` error: redundant pattern matching, consider using `is_some()` - --> $DIR/match_expr_like_matches_macro.rs:21:14 + --> $DIR/match_expr_like_matches_macro.rs:22:14 | LL | let _w = match x { | ______________^ @@ -23,7 +23,7 @@ LL | | }; = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings` error: redundant pattern matching, consider using `is_none()` - --> $DIR/match_expr_like_matches_macro.rs:27:14 + --> $DIR/match_expr_like_matches_macro.rs:28:14 | LL | let _z = match x { | ______________^ @@ -33,7 +33,7 @@ LL | | }; | |_____^ help: try: `x.is_none()` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:33:15 + --> $DIR/match_expr_like_matches_macro.rs:34:15 | LL | let _zz = match x { | _______________^ @@ -43,13 +43,13 @@ LL | | }; | |_____^ help: try: `!matches!(x, Some(r) if r == 0)` error: if let .. else expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:39:16 + --> $DIR/match_expr_like_matches_macro.rs:40:16 | LL | let _zzz = if let Some(5) = x { true } else { false }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(x, Some(5))` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:63:20 + --> $DIR/match_expr_like_matches_macro.rs:64:20 | LL | let _ans = match x { | ____________________^ @@ -60,7 +60,7 @@ LL | | }; | |_________^ help: try: `matches!(x, E::A(_) | E::B(_))` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:73:20 + --> $DIR/match_expr_like_matches_macro.rs:74:20 | LL | let _ans = match x { | ____________________^ @@ -73,7 +73,7 @@ LL | | }; | |_________^ help: try: `matches!(x, E::A(_) | E::B(_))` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:83:20 + --> $DIR/match_expr_like_matches_macro.rs:84:20 | LL | let _ans = match x { | ____________________^ @@ -84,7 +84,7 @@ LL | | }; | |_________^ help: try: `!matches!(x, E::B(_) | E::C)` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:143:18 + --> $DIR/match_expr_like_matches_macro.rs:144:18 | LL | let _z = match &z { | __________________^ @@ -94,7 +94,7 @@ LL | | }; | |_________^ help: try: `matches!(z, Some(3))` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:152:18 + --> $DIR/match_expr_like_matches_macro.rs:153:18 | LL | let _z = match &z { | __________________^ @@ -104,7 +104,7 @@ LL | | }; | |_________^ help: try: `matches!(&z, Some(3))` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:169:21 + --> $DIR/match_expr_like_matches_macro.rs:170:21 | LL | let _ = match &z { | _____________________^ @@ -114,7 +114,7 @@ LL | | }; | |_____________^ help: try: `matches!(&z, AnEnum::X)` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:183:20 + --> $DIR/match_expr_like_matches_macro.rs:184:20 | LL | let _res = match &val { | ____________________^ @@ -124,7 +124,7 @@ LL | | }; | |_________^ help: try: `matches!(&val, &Some(ref _a))` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:195:20 + --> $DIR/match_expr_like_matches_macro.rs:196:20 | LL | let _res = match &val { | ____________________^ @@ -134,7 +134,7 @@ LL | | }; | |_________^ help: try: `matches!(&val, &Some(ref _a))` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:253:14 + --> $DIR/match_expr_like_matches_macro.rs:254:14 | LL | let _y = match Some(5) { | ______________^ diff --git a/tests/ui/min_ident_chars.rs b/tests/ui/min_ident_chars.rs index 0fab224a29d3a..03784442e2c9e 100644 --- a/tests/ui/min_ident_chars.rs +++ b/tests/ui/min_ident_chars.rs @@ -81,3 +81,7 @@ fn b() {} fn wrong_pythagoras(a: f32, b: f32) -> f32 { a * a + a * b } + +mod issue_11163 { + struct Array([T; N]); +} diff --git a/tests/ui/mut_key.stderr b/tests/ui/mut_key.stderr index 02a0da86a4b83..3f756f5f0e598 100644 --- a/tests/ui/mut_key.stderr +++ b/tests/ui/mut_key.stderr @@ -12,14 +12,6 @@ error: mutable key type LL | fn should_not_take_this_arg(m: &mut HashMap, _n: usize) -> HashSet { | ^^^^^^^^^^^^ -error: this argument is a mutable reference, but not used mutably - --> $DIR/mut_key.rs:31:32 - | -LL | fn should_not_take_this_arg(m: &mut HashMap, _n: usize) -> HashSet { - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing to: `&HashMap` - | - = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` - error: mutable key type --> $DIR/mut_key.rs:32:5 | @@ -110,5 +102,13 @@ error: mutable key type LL | let _map = HashMap::>, usize>::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +error: this argument is a mutable reference, but not used mutably + --> $DIR/mut_key.rs:31:32 + | +LL | fn should_not_take_this_arg(m: &mut HashMap, _n: usize) -> HashSet { + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing to: `&HashMap` + | + = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` + error: aborting due to 18 previous errors diff --git a/tests/ui/mut_reference.stderr b/tests/ui/mut_reference.stderr index 23c812475c2af..1fdfbf9227e31 100644 --- a/tests/ui/mut_reference.stderr +++ b/tests/ui/mut_reference.stderr @@ -1,17 +1,3 @@ -error: this argument is a mutable reference, but not used mutably - --> $DIR/mut_reference.rs:4:33 - | -LL | fn takes_a_mutable_reference(a: &mut i32) {} - | ^^^^^^^^ help: consider changing to: `&i32` - | - = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` - -error: this argument is a mutable reference, but not used mutably - --> $DIR/mut_reference.rs:11:44 - | -LL | fn takes_a_mutable_reference(&self, a: &mut i32) {} - | ^^^^^^^^ help: consider changing to: `&i32` - error: the function `takes_an_immutable_reference` doesn't need a mutable reference --> $DIR/mut_reference.rs:17:34 | @@ -32,5 +18,13 @@ error: the method `takes_an_immutable_reference` doesn't need a mutable referenc LL | my_struct.takes_an_immutable_reference(&mut 42); | ^^^^^^^ -error: aborting due to 5 previous errors +error: this argument is a mutable reference, but not used mutably + --> $DIR/mut_reference.rs:11:44 + | +LL | fn takes_a_mutable_reference(&self, a: &mut i32) {} + | ^^^^^^^^ help: consider changing to: `&i32` + | + = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` + +error: aborting due to 4 previous errors diff --git a/tests/ui/needless_pass_by_ref_mut.rs b/tests/ui/needless_pass_by_ref_mut.rs index 5e7280995c60f..ae7b018d0e256 100644 --- a/tests/ui/needless_pass_by_ref_mut.rs +++ b/tests/ui/needless_pass_by_ref_mut.rs @@ -1,9 +1,10 @@ -#![allow(unused)] +#![allow(clippy::if_same_then_else, clippy::no_effect)] +#![feature(lint_reasons)] use std::ptr::NonNull; -// Should only warn for `s`. fn foo(s: &mut Vec, b: &u32, x: &mut u32) { + //~^ ERROR: this argument is a mutable reference, but not used mutably *x += *b + s.len() as u32; } @@ -27,8 +28,8 @@ fn foo5(s: &mut Vec) { foo2(s); } -// Should warn. fn foo6(s: &mut Vec) { + //~^ ERROR: this argument is a mutable reference, but not used mutably non_mut_ref(s); } @@ -40,13 +41,13 @@ impl Bar { // Should not warn on `&mut self`. fn bar(&mut self) {} - // Should warn about `vec` fn mushroom(&self, vec: &mut Vec) -> usize { + //~^ ERROR: this argument is a mutable reference, but not used mutably vec.len() } - // Should warn about `vec` (and not `self`). fn badger(&mut self, vec: &mut Vec) -> usize { + //~^ ERROR: this argument is a mutable reference, but not used mutably vec.len() } } @@ -91,6 +92,110 @@ impl Mut { // Should not warn. fn unused(_: &mut u32, _b: &mut u8) {} +// Should not warn. +async fn f1(x: &mut i32) { + *x += 1; +} +// Should not warn. +async fn f2(x: &mut i32, y: String) { + *x += 1; +} +// Should not warn. +async fn f3(x: &mut i32, y: String, z: String) { + *x += 1; +} +// Should not warn. +async fn f4(x: &mut i32, y: i32) { + *x += 1; +} +// Should not warn. +async fn f5(x: i32, y: &mut i32) { + *y += 1; +} +// Should not warn. +async fn f6(x: i32, y: &mut i32, z: &mut i32) { + *y += 1; + *z += 1; +} +// Should not warn. +async fn f7(x: &mut i32, y: i32, z: &mut i32, a: i32) { + *x += 1; + *z += 1; +} + +async fn a1(x: &mut i32) { + //~^ ERROR: this argument is a mutable reference, but not used mutably + println!("{:?}", x); +} +async fn a2(x: &mut i32, y: String) { + //~^ ERROR: this argument is a mutable reference, but not used mutably + println!("{:?}", x); +} +async fn a3(x: &mut i32, y: String, z: String) { + //~^ ERROR: this argument is a mutable reference, but not used mutably + println!("{:?}", x); +} +async fn a4(x: &mut i32, y: i32) { + //~^ ERROR: this argument is a mutable reference, but not used mutably + println!("{:?}", x); +} +async fn a5(x: i32, y: &mut i32) { + //~^ ERROR: this argument is a mutable reference, but not used mutably + println!("{:?}", x); +} +async fn a6(x: i32, y: &mut i32) { + //~^ ERROR: this argument is a mutable reference, but not used mutably + println!("{:?}", x); +} +async fn a7(x: i32, y: i32, z: &mut i32) { + //~^ ERROR: this argument is a mutable reference, but not used mutably + println!("{:?}", z); +} +async fn a8(x: i32, a: &mut i32, y: i32, z: &mut i32) { + //~^ ERROR: this argument is a mutable reference, but not used mutably + println!("{:?}", z); +} + +// Should not warn (passed as closure which takes `&mut`). +fn passed_as_closure(s: &mut u32) {} + +// Should not warn. +fn passed_as_local(s: &mut u32) {} + +// Should not warn. +fn ty_unify_1(s: &mut u32) {} + +// Should not warn. +fn ty_unify_2(s: &mut u32) {} + +// Should not warn. +fn passed_as_field(s: &mut u32) {} + +fn closure_takes_mut(s: fn(&mut u32)) {} + +struct A { + s: fn(&mut u32), +} + +// Should warn. +fn used_as_path(s: &mut u32) {} + +// Make sure lint attributes work fine +#[expect(clippy::needless_pass_by_ref_mut)] +fn lint_attr(s: &mut u32) {} + +#[cfg(not(feature = "a"))] +fn cfg_warn(s: &mut u32) {} +//~^ ERROR: this argument is a mutable reference, but not used mutably +//~| NOTE: this is cfg-gated and may require further changes + +#[cfg(not(feature = "a"))] +mod foo { + fn cfg_warn(s: &mut u32) {} + //~^ ERROR: this argument is a mutable reference, but not used mutably + //~| NOTE: this is cfg-gated and may require further changes +} + fn main() { let mut u = 0; let mut v = vec![0]; @@ -102,4 +207,9 @@ fn main() { alias_check(&mut v); alias_check2(&mut v); println!("{u}"); + closure_takes_mut(passed_as_closure); + A { s: passed_as_field }; + used_as_path; + let _: fn(&mut u32) = passed_as_local; + let _ = if v[0] == 0 { ty_unify_1 } else { ty_unify_2 }; } diff --git a/tests/ui/needless_pass_by_ref_mut.stderr b/tests/ui/needless_pass_by_ref_mut.stderr index 5e9d80bb6c488..0d426ce32f9d5 100644 --- a/tests/ui/needless_pass_by_ref_mut.stderr +++ b/tests/ui/needless_pass_by_ref_mut.stderr @@ -24,5 +24,75 @@ error: this argument is a mutable reference, but not used mutably LL | fn badger(&mut self, vec: &mut Vec) -> usize { | ^^^^^^^^^^^^^ help: consider changing to: `&Vec` -error: aborting due to 4 previous errors +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:126:16 + | +LL | async fn a1(x: &mut i32) { + | ^^^^^^^^ help: consider changing to: `&i32` + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:130:16 + | +LL | async fn a2(x: &mut i32, y: String) { + | ^^^^^^^^ help: consider changing to: `&i32` + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:134:16 + | +LL | async fn a3(x: &mut i32, y: String, z: String) { + | ^^^^^^^^ help: consider changing to: `&i32` + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:138:16 + | +LL | async fn a4(x: &mut i32, y: i32) { + | ^^^^^^^^ help: consider changing to: `&i32` + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:142:24 + | +LL | async fn a5(x: i32, y: &mut i32) { + | ^^^^^^^^ help: consider changing to: `&i32` + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:146:24 + | +LL | async fn a6(x: i32, y: &mut i32) { + | ^^^^^^^^ help: consider changing to: `&i32` + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:150:32 + | +LL | async fn a7(x: i32, y: i32, z: &mut i32) { + | ^^^^^^^^ help: consider changing to: `&i32` + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:154:24 + | +LL | async fn a8(x: i32, a: &mut i32, y: i32, z: &mut i32) { + | ^^^^^^^^ help: consider changing to: `&i32` + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:154:45 + | +LL | async fn a8(x: i32, a: &mut i32, y: i32, z: &mut i32) { + | ^^^^^^^^ help: consider changing to: `&i32` + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:188:16 + | +LL | fn cfg_warn(s: &mut u32) {} + | ^^^^^^^^ help: consider changing to: `&u32` + | + = note: this is cfg-gated and may require further changes + +error: this argument is a mutable reference, but not used mutably + --> $DIR/needless_pass_by_ref_mut.rs:194:20 + | +LL | fn cfg_warn(s: &mut u32) {} + | ^^^^^^^^ help: consider changing to: `&u32` + | + = note: this is cfg-gated and may require further changes + +error: aborting due to 15 previous errors diff --git a/tests/ui/needless_return_with_question_mark.fixed b/tests/ui/needless_return_with_question_mark.fixed new file mode 100644 index 0000000000000..d6e47d07b0f80 --- /dev/null +++ b/tests/ui/needless_return_with_question_mark.fixed @@ -0,0 +1,40 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow( + clippy::needless_return, + clippy::no_effect, + clippy::unit_arg, + clippy::useless_conversion, + unused +)] + +#[macro_use] +extern crate proc_macros; + +fn a() -> u32 { + return 0; +} + +fn b() -> Result { + return Err(0); +} + +// Do not lint +fn c() -> Option<()> { + return None?; +} + +fn main() -> Result<(), ()> { + Err(())?; + return Ok::<(), ()>(()); + Err(())?; + Ok::<(), ()>(()); + return Err(().into()); + external! { + return Err(())?; + } + with_span! { + return Err(())?; + } + Err(()) +} diff --git a/tests/ui/needless_return_with_question_mark.rs b/tests/ui/needless_return_with_question_mark.rs new file mode 100644 index 0000000000000..4fc04d363a9bf --- /dev/null +++ b/tests/ui/needless_return_with_question_mark.rs @@ -0,0 +1,40 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow( + clippy::needless_return, + clippy::no_effect, + clippy::unit_arg, + clippy::useless_conversion, + unused +)] + +#[macro_use] +extern crate proc_macros; + +fn a() -> u32 { + return 0; +} + +fn b() -> Result { + return Err(0); +} + +// Do not lint +fn c() -> Option<()> { + return None?; +} + +fn main() -> Result<(), ()> { + return Err(())?; + return Ok::<(), ()>(()); + Err(())?; + Ok::<(), ()>(()); + return Err(().into()); + external! { + return Err(())?; + } + with_span! { + return Err(())?; + } + Err(()) +} diff --git a/tests/ui/needless_return_with_question_mark.stderr b/tests/ui/needless_return_with_question_mark.stderr new file mode 100644 index 0000000000000..e1d91638d2c77 --- /dev/null +++ b/tests/ui/needless_return_with_question_mark.stderr @@ -0,0 +1,10 @@ +error: unneeded `return` statement with `?` operator + --> $DIR/needless_return_with_question_mark.rs:28:5 + | +LL | return Err(())?; + | ^^^^^^^ help: remove it + | + = note: `-D clippy::needless-return-with-question-mark` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/tests/ui/option_env_unwrap.rs b/tests/ui/option_env_unwrap.rs index 65a1b467f8114..61dbad939db40 100644 --- a/tests/ui/option_env_unwrap.rs +++ b/tests/ui/option_env_unwrap.rs @@ -9,6 +9,7 @@ use proc_macros::{external, inline_macros}; fn main() { let _ = option_env!("PATH").unwrap(); let _ = option_env!("PATH").expect("environment variable PATH isn't set"); + let _ = option_env!("__Y__do_not_use").unwrap(); // This test only works if you don't have a __Y__do_not_use env variable in your environment. let _ = inline!(option_env!($"PATH").unwrap()); let _ = inline!(option_env!($"PATH").expect($"environment variable PATH isn't set")); let _ = external!(option_env!($"PATH").unwrap()); diff --git a/tests/ui/option_env_unwrap.stderr b/tests/ui/option_env_unwrap.stderr index 7bba62686eecf..cfa9dd58a3006 100644 --- a/tests/ui/option_env_unwrap.stderr +++ b/tests/ui/option_env_unwrap.stderr @@ -16,7 +16,15 @@ LL | let _ = option_env!("PATH").expect("environment variable PATH isn't set = help: consider using the `env!` macro instead error: this will panic at run-time if the environment variable doesn't exist at compile-time - --> $DIR/option_env_unwrap.rs:12:21 + --> $DIR/option_env_unwrap.rs:12:13 + | +LL | let _ = option_env!("__Y__do_not_use").unwrap(); // This test only works if you don't have a __Y__do_not_use env variable in your env... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using the `env!` macro instead + +error: this will panic at run-time if the environment variable doesn't exist at compile-time + --> $DIR/option_env_unwrap.rs:13:21 | LL | let _ = inline!(option_env!($"PATH").unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +33,7 @@ LL | let _ = inline!(option_env!($"PATH").unwrap()); = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) error: this will panic at run-time if the environment variable doesn't exist at compile-time - --> $DIR/option_env_unwrap.rs:13:21 + --> $DIR/option_env_unwrap.rs:14:21 | LL | let _ = inline!(option_env!($"PATH").expect($"environment variable PATH isn't set")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -34,7 +42,7 @@ LL | let _ = inline!(option_env!($"PATH").expect($"environment variable PATH = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) error: this will panic at run-time if the environment variable doesn't exist at compile-time - --> $DIR/option_env_unwrap.rs:14:13 + --> $DIR/option_env_unwrap.rs:15:13 | LL | let _ = external!(option_env!($"PATH").unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -43,7 +51,7 @@ LL | let _ = external!(option_env!($"PATH").unwrap()); = note: this error originates in the macro `external` (in Nightly builds, run with -Z macro-backtrace for more info) error: this will panic at run-time if the environment variable doesn't exist at compile-time - --> $DIR/option_env_unwrap.rs:15:13 + --> $DIR/option_env_unwrap.rs:16:13 | LL | let _ = external!(option_env!($"PATH").expect($"environment variable PATH isn't set")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -51,5 +59,5 @@ LL | let _ = external!(option_env!($"PATH").expect($"environment variable PA = help: consider using the `env!` macro instead = note: this error originates in the macro `external` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 6 previous errors +error: aborting due to 7 previous errors diff --git a/tests/ui/option_if_let_else.fixed b/tests/ui/option_if_let_else.fixed index 8e59e4375d2e8..6fee3cce619c6 100644 --- a/tests/ui/option_if_let_else.fixed +++ b/tests/ui/option_if_let_else.fixed @@ -5,7 +5,8 @@ clippy::redundant_closure, clippy::ref_option_ref, clippy::equatable_if_let, - clippy::let_unit_value + clippy::let_unit_value, + clippy::redundant_locals )] fn bad1(string: Option<&str>) -> (bool, &str) { diff --git a/tests/ui/option_if_let_else.rs b/tests/ui/option_if_let_else.rs index e72edf2a8e318..4b3cf948a1bed 100644 --- a/tests/ui/option_if_let_else.rs +++ b/tests/ui/option_if_let_else.rs @@ -5,7 +5,8 @@ clippy::redundant_closure, clippy::ref_option_ref, clippy::equatable_if_let, - clippy::let_unit_value + clippy::let_unit_value, + clippy::redundant_locals )] fn bad1(string: Option<&str>) -> (bool, &str) { diff --git a/tests/ui/option_if_let_else.stderr b/tests/ui/option_if_let_else.stderr index aa2da21740032..350f0f07e136e 100644 --- a/tests/ui/option_if_let_else.stderr +++ b/tests/ui/option_if_let_else.stderr @@ -1,5 +1,5 @@ error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:12:5 + --> $DIR/option_if_let_else.rs:13:5 | LL | / if let Some(x) = string { LL | | (true, x) @@ -11,19 +11,19 @@ LL | | } = note: `-D clippy::option-if-let-else` implied by `-D warnings` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:30:13 + --> $DIR/option_if_let_else.rs:31:13 | LL | let _ = if let Some(s) = *string { s.len() } else { 0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `string.map_or(0, |s| s.len())` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:31:13 + --> $DIR/option_if_let_else.rs:32:13 | LL | let _ = if let Some(s) = &num { s } else { &0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.as_ref().map_or(&0, |s| s)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:32:13 + --> $DIR/option_if_let_else.rs:33:13 | LL | let _ = if let Some(s) = &mut num { | _____________^ @@ -43,13 +43,13 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:38:13 + --> $DIR/option_if_let_else.rs:39:13 | LL | let _ = if let Some(ref s) = num { s } else { &0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.as_ref().map_or(&0, |s| s)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:39:13 + --> $DIR/option_if_let_else.rs:40:13 | LL | let _ = if let Some(mut s) = num { | _____________^ @@ -69,7 +69,7 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:45:13 + --> $DIR/option_if_let_else.rs:46:13 | LL | let _ = if let Some(ref mut s) = num { | _____________^ @@ -89,7 +89,7 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:54:5 + --> $DIR/option_if_let_else.rs:55:5 | LL | / if let Some(x) = arg { LL | | let y = x * x; @@ -108,7 +108,7 @@ LL + }) | error: use Option::map_or_else instead of an if let/else - --> $DIR/option_if_let_else.rs:67:13 + --> $DIR/option_if_let_else.rs:68:13 | LL | let _ = if let Some(x) = arg { | _____________^ @@ -120,7 +120,7 @@ LL | | }; | |_____^ help: try: `arg.map_or_else(|| side_effect(), |x| x)` error: use Option::map_or_else instead of an if let/else - --> $DIR/option_if_let_else.rs:76:13 + --> $DIR/option_if_let_else.rs:77:13 | LL | let _ = if let Some(x) = arg { | _____________^ @@ -143,7 +143,7 @@ LL ~ }, |x| x * x * x * x); | error: use Option::map_or_else instead of an if let/else - --> $DIR/option_if_let_else.rs:109:13 + --> $DIR/option_if_let_else.rs:110:13 | LL | / if let Some(idx) = s.find('.') { LL | | vec![s[..idx].to_string(), s[idx..].to_string()] @@ -153,7 +153,7 @@ LL | | } | |_____________^ help: try: `s.find('.').map_or_else(|| vec![s.to_string()], |idx| vec![s[..idx].to_string(), s[idx..].to_string()])` error: use Option::map_or_else instead of an if let/else - --> $DIR/option_if_let_else.rs:120:5 + --> $DIR/option_if_let_else.rs:121:5 | LL | / if let Ok(binding) = variable { LL | | println!("Ok {binding}"); @@ -172,13 +172,13 @@ LL + }) | error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:142:13 + --> $DIR/option_if_let_else.rs:143:13 | LL | let _ = if let Some(x) = optional { x + 2 } else { 5 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `optional.map_or(5, |x| x + 2)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:152:13 + --> $DIR/option_if_let_else.rs:153:13 | LL | let _ = if let Some(x) = Some(0) { | _____________^ @@ -200,13 +200,13 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:180:13 + --> $DIR/option_if_let_else.rs:181:13 | LL | let _ = if let Some(x) = Some(0) { s.len() + x } else { s.len() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(0).map_or(s.len(), |x| s.len() + x)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:184:13 + --> $DIR/option_if_let_else.rs:185:13 | LL | let _ = if let Some(x) = Some(0) { | _____________^ @@ -226,7 +226,7 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:223:13 + --> $DIR/option_if_let_else.rs:224:13 | LL | let _ = match s { | _____________^ @@ -236,7 +236,7 @@ LL | | }; | |_____^ help: try: `s.map_or(1, |string| string.len())` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:227:13 + --> $DIR/option_if_let_else.rs:228:13 | LL | let _ = match Some(10) { | _____________^ @@ -246,7 +246,7 @@ LL | | }; | |_____^ help: try: `Some(10).map_or(5, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:233:13 + --> $DIR/option_if_let_else.rs:234:13 | LL | let _ = match res { | _____________^ @@ -256,7 +256,7 @@ LL | | }; | |_____^ help: try: `res.map_or(1, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:237:13 + --> $DIR/option_if_let_else.rs:238:13 | LL | let _ = match res { | _____________^ @@ -266,13 +266,13 @@ LL | | }; | |_____^ help: try: `res.map_or(1, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:241:13 + --> $DIR/option_if_let_else.rs:242:13 | LL | let _ = if let Ok(a) = res { a + 1 } else { 5 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `res.map_or(5, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:258:9 + --> $DIR/option_if_let_else.rs:259:9 | LL | / match initial { LL | | Some(value) => do_something(value), @@ -281,7 +281,7 @@ LL | | } | |_________^ help: try: `initial.as_ref().map_or({}, |value| do_something(value))` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:265:9 + --> $DIR/option_if_let_else.rs:266:9 | LL | / match initial { LL | | Some(value) => do_something2(value), diff --git a/tests/ui/or_fun_call.fixed b/tests/ui/or_fun_call.fixed index 6deff0f3240a2..581f3ad45c7d7 100644 --- a/tests/ui/or_fun_call.fixed +++ b/tests/ui/or_fun_call.fixed @@ -190,7 +190,7 @@ mod issue8239 { acc.push_str(&f); acc }) - .unwrap_or_default(); + .unwrap_or(String::new()); } fn more_to_max_suggestion_highest_lines_1() { @@ -203,7 +203,7 @@ mod issue8239 { acc.push_str(&f); acc }) - .unwrap_or_default(); + .unwrap_or(String::new()); } fn equal_to_max_suggestion_highest_lines() { @@ -215,7 +215,7 @@ mod issue8239 { acc.push_str(&f); acc }) - .unwrap_or_default(); + .unwrap_or(String::new()); } fn less_than_max_suggestion_highest_lines() { @@ -226,7 +226,7 @@ mod issue8239 { acc.push_str(&f); acc }) - .unwrap_or_default(); + .unwrap_or(String::new()); } } @@ -257,4 +257,59 @@ mod issue8993 { } } +mod lazy { + use super::*; + + fn foo() { + struct Foo; + + impl Foo { + fn new() -> Foo { + Foo + } + } + + struct FakeDefault; + impl FakeDefault { + fn default() -> Self { + FakeDefault + } + } + + impl Default for FakeDefault { + fn default() -> Self { + FakeDefault + } + } + + let with_new = Some(vec![1]); + with_new.unwrap_or_default(); + + let with_default_trait = Some(1); + with_default_trait.unwrap_or_default(); + + let with_default_type = Some(1); + with_default_type.unwrap_or_default(); + + let real_default = None::; + real_default.unwrap_or_default(); + + let mut map = HashMap::::new(); + map.entry(42).or_default(); + + let mut btree = BTreeMap::::new(); + btree.entry(42).or_default(); + + let stringy = Some(String::new()); + let _ = stringy.unwrap_or_default(); + + // negative tests + let self_default = None::; + self_default.unwrap_or_else(::default); + + let without_default = Some(Foo); + without_default.unwrap_or_else(Foo::new); + } +} + fn main() {} diff --git a/tests/ui/or_fun_call.rs b/tests/ui/or_fun_call.rs index b05b33e6ee2b3..1f3987eb8917a 100644 --- a/tests/ui/or_fun_call.rs +++ b/tests/ui/or_fun_call.rs @@ -257,4 +257,59 @@ mod issue8993 { } } +mod lazy { + use super::*; + + fn foo() { + struct Foo; + + impl Foo { + fn new() -> Foo { + Foo + } + } + + struct FakeDefault; + impl FakeDefault { + fn default() -> Self { + FakeDefault + } + } + + impl Default for FakeDefault { + fn default() -> Self { + FakeDefault + } + } + + let with_new = Some(vec![1]); + with_new.unwrap_or_else(Vec::new); + + let with_default_trait = Some(1); + with_default_trait.unwrap_or_else(Default::default); + + let with_default_type = Some(1); + with_default_type.unwrap_or_else(u64::default); + + let real_default = None::; + real_default.unwrap_or_else(::default); + + let mut map = HashMap::::new(); + map.entry(42).or_insert_with(String::new); + + let mut btree = BTreeMap::::new(); + btree.entry(42).or_insert_with(String::new); + + let stringy = Some(String::new()); + let _ = stringy.unwrap_or_else(String::new); + + // negative tests + let self_default = None::; + self_default.unwrap_or_else(::default); + + let without_default = Some(Foo); + without_default.unwrap_or_else(Foo::new); + } +} + fn main() {} diff --git a/tests/ui/or_fun_call.stderr b/tests/ui/or_fun_call.stderr index 7342b0c2914b0..519f09165626c 100644 --- a/tests/ui/or_fun_call.stderr +++ b/tests/ui/or_fun_call.stderr @@ -6,11 +6,13 @@ LL | with_constructor.unwrap_or(make()); | = note: `-D clippy::or-fun-call` implied by `-D warnings` -error: use of `unwrap_or` followed by a call to `new` +error: use of `unwrap_or` to construct default value --> $DIR/or_fun_call.rs:56:14 | LL | with_new.unwrap_or(Vec::new()); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + | + = note: `-D clippy::unwrap-or-default` implied by `-D warnings` error: use of `unwrap_or` followed by a function call --> $DIR/or_fun_call.rs:59:21 @@ -30,13 +32,13 @@ error: use of `unwrap_or` followed by a function call LL | with_err_args.unwrap_or(Vec::with_capacity(12)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|_| Vec::with_capacity(12))` -error: use of `unwrap_or` followed by a call to `default` +error: use of `unwrap_or` to construct default value --> $DIR/or_fun_call.rs:68:24 | LL | with_default_trait.unwrap_or(Default::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` -error: use of `unwrap_or` followed by a call to `default` +error: use of `unwrap_or` to construct default value --> $DIR/or_fun_call.rs:71:23 | LL | with_default_type.unwrap_or(u64::default()); @@ -48,13 +50,13 @@ error: use of `unwrap_or` followed by a function call LL | self_default.unwrap_or(::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(::default)` -error: use of `unwrap_or` followed by a call to `default` +error: use of `unwrap_or` to construct default value --> $DIR/or_fun_call.rs:77:18 | LL | real_default.unwrap_or(::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` -error: use of `unwrap_or` followed by a call to `new` +error: use of `unwrap_or` to construct default value --> $DIR/or_fun_call.rs:80:14 | LL | with_vec.unwrap_or(vec![]); @@ -66,31 +68,31 @@ error: use of `unwrap_or` followed by a function call LL | without_default.unwrap_or(Foo::new()); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(Foo::new)` -error: use of `or_insert` followed by a call to `new` +error: use of `or_insert` to construct default value --> $DIR/or_fun_call.rs:86:19 | LL | map.entry(42).or_insert(String::new()); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` -error: use of `or_insert` followed by a call to `new` +error: use of `or_insert` to construct default value --> $DIR/or_fun_call.rs:89:23 | LL | map_vec.entry(42).or_insert(vec![]); | ^^^^^^^^^^^^^^^^^ help: try: `or_default()` -error: use of `or_insert` followed by a call to `new` +error: use of `or_insert` to construct default value --> $DIR/or_fun_call.rs:92:21 | LL | btree.entry(42).or_insert(String::new()); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` -error: use of `or_insert` followed by a call to `new` +error: use of `or_insert` to construct default value --> $DIR/or_fun_call.rs:95:25 | LL | btree_vec.entry(42).or_insert(vec![]); | ^^^^^^^^^^^^^^^^^ help: try: `or_default()` -error: use of `unwrap_or` followed by a call to `new` +error: use of `unwrap_or` to construct default value --> $DIR/or_fun_call.rs:98:21 | LL | let _ = stringy.unwrap_or(String::new()); @@ -132,30 +134,6 @@ error: use of `unwrap_or` followed by a function call LL | None.unwrap_or( unsafe { ptr_to_ref(s) } ); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| unsafe { ptr_to_ref(s) })` -error: use of `unwrap_or` followed by a call to `new` - --> $DIR/or_fun_call.rs:193:14 - | -LL | .unwrap_or(String::new()); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` - -error: use of `unwrap_or` followed by a call to `new` - --> $DIR/or_fun_call.rs:206:14 - | -LL | .unwrap_or(String::new()); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` - -error: use of `unwrap_or` followed by a call to `new` - --> $DIR/or_fun_call.rs:218:14 - | -LL | .unwrap_or(String::new()); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` - -error: use of `unwrap_or` followed by a call to `new` - --> $DIR/or_fun_call.rs:229:10 - | -LL | .unwrap_or(String::new()); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` - error: use of `map_or` followed by a function call --> $DIR/or_fun_call.rs:254:25 | @@ -168,5 +146,47 @@ error: use of `map_or` followed by a function call LL | let _ = Some(4).map_or(g(), f); | ^^^^^^^^^^^^^^ help: try: `map_or_else(g, f)` -error: aborting due to 28 previous errors +error: use of `unwrap_or_else` to construct default value + --> $DIR/or_fun_call.rs:286:18 + | +LL | with_new.unwrap_or_else(Vec::new); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/or_fun_call.rs:289:28 + | +LL | with_default_trait.unwrap_or_else(Default::default); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/or_fun_call.rs:292:27 + | +LL | with_default_type.unwrap_or_else(u64::default); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/or_fun_call.rs:295:22 + | +LL | real_default.unwrap_or_else(::default); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `or_insert_with` to construct default value + --> $DIR/or_fun_call.rs:298:23 + | +LL | map.entry(42).or_insert_with(String::new); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` + +error: use of `or_insert_with` to construct default value + --> $DIR/or_fun_call.rs:301:25 + | +LL | btree.entry(42).or_insert_with(String::new); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/or_fun_call.rs:304:25 + | +LL | let _ = stringy.unwrap_or_else(String::new); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: aborting due to 31 previous errors diff --git a/tests/ui/ptr_arg.rs b/tests/ui/ptr_arg.rs index 13e993d247b2c..08075c382a220 100644 --- a/tests/ui/ptr_arg.rs +++ b/tests/ui/ptr_arg.rs @@ -267,3 +267,16 @@ mod issue_9218 { todo!() } } + +mod issue_11181 { + extern "C" fn allowed(_v: &Vec) {} + + struct S; + impl S { + extern "C" fn allowed(_v: &Vec) {} + } + + trait T { + extern "C" fn allowed(_v: &Vec) {} + } +} diff --git a/tests/ui/read_zero_byte_vec.rs b/tests/ui/read_zero_byte_vec.rs index c6025ef1f4da1..ff2ad8644b49f 100644 --- a/tests/ui/read_zero_byte_vec.rs +++ b/tests/ui/read_zero_byte_vec.rs @@ -1,5 +1,9 @@ #![warn(clippy::read_zero_byte_vec)] -#![allow(clippy::unused_io_amount, clippy::needless_pass_by_ref_mut)] +#![allow( + clippy::unused_io_amount, + clippy::needless_pass_by_ref_mut, + clippy::slow_vector_initialization +)] use std::fs::File; use std::io; use std::io::prelude::*; diff --git a/tests/ui/read_zero_byte_vec.stderr b/tests/ui/read_zero_byte_vec.stderr index 08ba9753d7c41..4c7f605f4c2a5 100644 --- a/tests/ui/read_zero_byte_vec.stderr +++ b/tests/ui/read_zero_byte_vec.stderr @@ -1,5 +1,5 @@ error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:17:5 + --> $DIR/read_zero_byte_vec.rs:21:5 | LL | f.read_exact(&mut data).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `data.resize(20, 0); f.read_exact(&mut data).unwrap();` @@ -7,55 +7,55 @@ LL | f.read_exact(&mut data).unwrap(); = note: `-D clippy::read-zero-byte-vec` implied by `-D warnings` error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:21:5 + --> $DIR/read_zero_byte_vec.rs:25:5 | LL | f.read_exact(&mut data2)?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `data2.resize(cap, 0); f.read_exact(&mut data2)?;` error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:25:5 + --> $DIR/read_zero_byte_vec.rs:29:5 | LL | f.read_exact(&mut data3)?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:29:5 + --> $DIR/read_zero_byte_vec.rs:33:5 | LL | let _ = f.read(&mut data4)?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:34:9 + --> $DIR/read_zero_byte_vec.rs:38:9 | LL | f.read(&mut data5) | ^^^^^^^^^^^^^^^^^^ error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:40:9 + --> $DIR/read_zero_byte_vec.rs:44:9 | LL | f.read(&mut data6) | ^^^^^^^^^^^^^^^^^^ error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:70:5 + --> $DIR/read_zero_byte_vec.rs:74:5 | LL | r.read(&mut data).await.unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:74:5 + --> $DIR/read_zero_byte_vec.rs:78:5 | LL | r.read_exact(&mut data2).await.unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:80:5 + --> $DIR/read_zero_byte_vec.rs:84:5 | LL | r.read(&mut data).await.unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: reading zero byte data to `Vec` - --> $DIR/read_zero_byte_vec.rs:84:5 + --> $DIR/read_zero_byte_vec.rs:88:5 | LL | r.read_exact(&mut data2).await.unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/readonly_write_lock.rs b/tests/ui/readonly_write_lock.rs new file mode 100644 index 0000000000000..656b45787e8a0 --- /dev/null +++ b/tests/ui/readonly_write_lock.rs @@ -0,0 +1,42 @@ +#![warn(clippy::readonly_write_lock)] + +use std::sync::RwLock; + +fn mutate_i32(x: &mut i32) { + *x += 1; +} + +fn accept_i32(_: i32) {} + +fn main() { + let lock = RwLock::new(42); + let lock2 = RwLock::new(1234); + + { + let writer = lock.write().unwrap(); + dbg!(&writer); + } + + { + let writer = lock.write().unwrap(); + accept_i32(*writer); + } + + { + let mut writer = lock.write().unwrap(); + mutate_i32(&mut writer); + dbg!(&writer); + } + + { + let mut writer = lock.write().unwrap(); + *writer += 1; + } + + { + let mut writer1 = lock.write().unwrap(); + let mut writer2 = lock2.write().unwrap(); + *writer2 += 1; + *writer1 = *writer2; + } +} diff --git a/tests/ui/readonly_write_lock.stderr b/tests/ui/readonly_write_lock.stderr new file mode 100644 index 0000000000000..e3d8fce7b2ce0 --- /dev/null +++ b/tests/ui/readonly_write_lock.stderr @@ -0,0 +1,16 @@ +error: this write lock is used only for reading + --> $DIR/readonly_write_lock.rs:16:22 + | +LL | let writer = lock.write().unwrap(); + | ^^^^^^^^^^^^ help: consider using a read lock instead: `lock.read()` + | + = note: `-D clippy::readonly-write-lock` implied by `-D warnings` + +error: this write lock is used only for reading + --> $DIR/readonly_write_lock.rs:21:22 + | +LL | let writer = lock.write().unwrap(); + | ^^^^^^^^^^^^ help: consider using a read lock instead: `lock.read()` + +error: aborting due to 2 previous errors + diff --git a/tests/ui/redundant_guards.fixed b/tests/ui/redundant_guards.fixed new file mode 100644 index 0000000000000..77ac76668649b --- /dev/null +++ b/tests/ui/redundant_guards.fixed @@ -0,0 +1,133 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![feature(if_let_guard)] +#![allow(clippy::no_effect, unused)] +#![warn(clippy::redundant_guards)] + +#[macro_use] +extern crate proc_macros; + +struct A(u32); + +struct B { + e: Option, +} + +struct C(u32, u32); + +fn main() { + let c = C(1, 2); + match c { + C(x, 1) => .., + _ => todo!(), + }; + + let x = Some(Some(1)); + match x { + Some(Some(1)) if true => .., + Some(Some(1)) => { + println!("a"); + .. + }, + Some(Some(1)) => .., + Some(Some(2)) => .., + // Don't lint, since x is used in the body + Some(x) if let Some(1) = x => { + x; + .. + } + _ => todo!(), + }; + let y = 1; + match x { + // Don't inline these, since y is not from the pat + Some(x) if matches!(y, 1 if true) => .., + Some(x) if let 1 = y => .., + Some(x) if y == 2 => .., + _ => todo!(), + }; + let a = A(1); + match a { + _ if a.0 == 1 => {}, + _ => todo!(), + } + let b = B { e: Some(A(0)) }; + match b { + B { e: Some(A(2)) } => .., + _ => todo!(), + }; + // Do not lint, since we cannot represent this as a pattern (at least, without a conversion) + let v = Some(vec![1u8, 2, 3]); + match v { + Some(x) if x == [1] => {}, + _ => {}, + } + + external! { + let x = Some(Some(1)); + match x { + Some(x) if let Some(1) = x => .., + _ => todo!(), + }; + } + with_span! { + span + let x = Some(Some(1)); + match x { + Some(x) if let Some(1) = x => .., + _ => todo!(), + }; + } +} + +enum E { + A(&'static str), + B(&'static str), + C(&'static str), +} + +fn i() { + match E::A("") { + // Do not lint + E::A(x) | E::B(x) | E::C(x) if x == "from an or pattern" => {}, + E::A("not from an or pattern") => {}, + _ => {}, + }; +} + +fn h(v: Option) { + match v { + Some(0) => .., + _ => .., + }; +} + +// Do not lint + +fn f(s: Option) { + match s { + Some(x) if x == "a" => {}, + _ => {}, + } +} + +struct S { + a: usize, +} + +impl PartialEq for S { + fn eq(&self, _: &Self) -> bool { + true + } +} + +impl Eq for S {} + +static CONST_S: S = S { a: 1 }; + +fn g(opt_s: Option) { + match opt_s { + Some(x) if x == CONST_S => {}, + _ => {}, + } +} diff --git a/tests/ui/redundant_guards.rs b/tests/ui/redundant_guards.rs new file mode 100644 index 0000000000000..b072e4ea14d1d --- /dev/null +++ b/tests/ui/redundant_guards.rs @@ -0,0 +1,133 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![feature(if_let_guard)] +#![allow(clippy::no_effect, unused)] +#![warn(clippy::redundant_guards)] + +#[macro_use] +extern crate proc_macros; + +struct A(u32); + +struct B { + e: Option, +} + +struct C(u32, u32); + +fn main() { + let c = C(1, 2); + match c { + C(x, y) if let 1 = y => .., + _ => todo!(), + }; + + let x = Some(Some(1)); + match x { + Some(x) if matches!(x, Some(1) if true) => .., + Some(x) if matches!(x, Some(1)) => { + println!("a"); + .. + }, + Some(x) if let Some(1) = x => .., + Some(x) if x == Some(2) => .., + // Don't lint, since x is used in the body + Some(x) if let Some(1) = x => { + x; + .. + } + _ => todo!(), + }; + let y = 1; + match x { + // Don't inline these, since y is not from the pat + Some(x) if matches!(y, 1 if true) => .., + Some(x) if let 1 = y => .., + Some(x) if y == 2 => .., + _ => todo!(), + }; + let a = A(1); + match a { + _ if a.0 == 1 => {}, + _ => todo!(), + } + let b = B { e: Some(A(0)) }; + match b { + B { e } if matches!(e, Some(A(2))) => .., + _ => todo!(), + }; + // Do not lint, since we cannot represent this as a pattern (at least, without a conversion) + let v = Some(vec![1u8, 2, 3]); + match v { + Some(x) if x == [1] => {}, + _ => {}, + } + + external! { + let x = Some(Some(1)); + match x { + Some(x) if let Some(1) = x => .., + _ => todo!(), + }; + } + with_span! { + span + let x = Some(Some(1)); + match x { + Some(x) if let Some(1) = x => .., + _ => todo!(), + }; + } +} + +enum E { + A(&'static str), + B(&'static str), + C(&'static str), +} + +fn i() { + match E::A("") { + // Do not lint + E::A(x) | E::B(x) | E::C(x) if x == "from an or pattern" => {}, + E::A(y) if y == "not from an or pattern" => {}, + _ => {}, + }; +} + +fn h(v: Option) { + match v { + x if matches!(x, Some(0)) => .., + _ => .., + }; +} + +// Do not lint + +fn f(s: Option) { + match s { + Some(x) if x == "a" => {}, + _ => {}, + } +} + +struct S { + a: usize, +} + +impl PartialEq for S { + fn eq(&self, _: &Self) -> bool { + true + } +} + +impl Eq for S {} + +static CONST_S: S = S { a: 1 }; + +fn g(opt_s: Option) { + match opt_s { + Some(x) if x == CONST_S => {}, + _ => {}, + } +} diff --git a/tests/ui/redundant_guards.stderr b/tests/ui/redundant_guards.stderr new file mode 100644 index 0000000000000..c2a92071d1dfb --- /dev/null +++ b/tests/ui/redundant_guards.stderr @@ -0,0 +1,98 @@ +error: redundant guard + --> $DIR/redundant_guards.rs:21:20 + | +LL | C(x, y) if let 1 = y => .., + | ^^^^^^^^^ + | + = note: `-D clippy::redundant-guards` implied by `-D warnings` +help: try + | +LL - C(x, y) if let 1 = y => .., +LL + C(x, 1) => .., + | + +error: redundant guard + --> $DIR/redundant_guards.rs:27:20 + | +LL | Some(x) if matches!(x, Some(1) if true) => .., + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL | Some(Some(1)) if true => .., + | ~~~~~~~ ~~~~~~~ + +error: redundant guard + --> $DIR/redundant_guards.rs:28:20 + | +LL | Some(x) if matches!(x, Some(1)) => { + | ^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - Some(x) if matches!(x, Some(1)) => { +LL + Some(Some(1)) => { + | + +error: redundant guard + --> $DIR/redundant_guards.rs:32:20 + | +LL | Some(x) if let Some(1) = x => .., + | ^^^^^^^^^^^^^^^ + | +help: try + | +LL - Some(x) if let Some(1) = x => .., +LL + Some(Some(1)) => .., + | + +error: redundant guard + --> $DIR/redundant_guards.rs:33:20 + | +LL | Some(x) if x == Some(2) => .., + | ^^^^^^^^^^^^ + | +help: try + | +LL - Some(x) if x == Some(2) => .., +LL + Some(Some(2)) => .., + | + +error: redundant guard + --> $DIR/redundant_guards.rs:56:20 + | +LL | B { e } if matches!(e, Some(A(2))) => .., + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - B { e } if matches!(e, Some(A(2))) => .., +LL + B { e: Some(A(2)) } => .., + | + +error: redundant guard + --> $DIR/redundant_guards.rs:93:20 + | +LL | E::A(y) if y == "not from an or pattern" => {}, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - E::A(y) if y == "not from an or pattern" => {}, +LL + E::A("not from an or pattern") => {}, + | + +error: redundant guard + --> $DIR/redundant_guards.rs:100:14 + | +LL | x if matches!(x, Some(0)) => .., + | ^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - x if matches!(x, Some(0)) => .., +LL + Some(0) => .., + | + +error: aborting due to 8 previous errors + diff --git a/tests/ui/redundant_locals.rs b/tests/ui/redundant_locals.rs new file mode 100644 index 0000000000000..e74c78a50b672 --- /dev/null +++ b/tests/ui/redundant_locals.rs @@ -0,0 +1,111 @@ +//@aux-build:proc_macros.rs:proc-macro +#![allow(unused, clippy::no_effect, clippy::needless_pass_by_ref_mut)] +#![warn(clippy::redundant_locals)] + +extern crate proc_macros; +use proc_macros::{external, with_span}; + +fn main() {} + +fn immutable() { + let x = 1; + let x = x; +} + +fn mutable() { + let mut x = 1; + let mut x = x; +} + +fn upgraded_mutability() { + let x = 1; + let mut x = x; +} + +fn downgraded_mutability() { + let mut x = 1; + let x = x; +} + +fn coercion(par: &mut i32) { + let par: &i32 = par; + + let x: &mut i32 = &mut 1; + let x: &i32 = x; +} + +fn parameter(x: i32) { + let x = x; +} + +fn many() { + let x = 1; + let x = x; + let x = x; + let x = x; + let x = x; +} + +fn interleaved() { + let a = 1; + let b = 2; + let a = a; + let b = b; +} + +fn block() { + { + let x = 1; + let x = x; + } +} + +fn closure() { + || { + let x = 1; + let x = x; + }; + |x: i32| { + let x = x; + }; +} + +fn consequential_drop_order() { + use std::sync::Mutex; + + let mutex = Mutex::new(1); + let guard = mutex.lock().unwrap(); + + { + let guard = guard; + } +} + +fn inconsequential_drop_order() { + let x = 1; + + { + let x = x; + } +} + +fn macros() { + macro_rules! rebind { + ($x:ident) => { + let $x = 1; + let $x = $x; + }; + } + + rebind!(x); + + external! { + let x = 1; + let x = x; + } + with_span! { + span + let x = 1; + let x = x; + } +} diff --git a/tests/ui/redundant_locals.stderr b/tests/ui/redundant_locals.stderr new file mode 100644 index 0000000000000..df07dd2f453b1 --- /dev/null +++ b/tests/ui/redundant_locals.stderr @@ -0,0 +1,136 @@ +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:11:9 + | +LL | let x = 1; + | ^ +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + = note: `-D clippy::redundant-locals` implied by `-D warnings` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:16:9 + | +LL | let mut x = 1; + | ^^^^^ +LL | let mut x = x; + | ^^^^^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:37:14 + | +LL | fn parameter(x: i32) { + | ^ +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:42:9 + | +LL | let x = 1; + | ^ +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:43:9 + | +LL | let x = x; + | ^ +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:44:9 + | +LL | let x = x; + | ^ +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:45:9 + | +LL | let x = x; + | ^ +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:50:9 + | +LL | let a = 1; + | ^ +LL | let b = 2; +LL | let a = a; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `a` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:51:9 + | +LL | let b = 2; + | ^ +LL | let a = a; +LL | let b = b; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `b` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:58:13 + | +LL | let x = 1; + | ^ +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:65:13 + | +LL | let x = 1; + | ^ +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:68:6 + | +LL | |x: i32| { + | ^ +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: redundant redefinition of a binding + --> $DIR/redundant_locals.rs:85:9 + | +LL | let x = 1; + | ^ +... +LL | let x = x; + | ^^^^^^^^^^ + | + = help: remove the redefinition of `x` + +error: aborting due to 13 previous errors + diff --git a/tests/ui/redundant_pattern_matching_option.fixed b/tests/ui/redundant_pattern_matching_option.fixed index a63ba5809e4bb..d9fcd98c56e00 100644 --- a/tests/ui/redundant_pattern_matching_option.fixed +++ b/tests/ui/redundant_pattern_matching_option.fixed @@ -10,6 +10,20 @@ clippy::equatable_if_let, clippy::if_same_then_else )] +#![feature(let_chains, if_let_guard)] + +fn issue_11174(boolean: bool, maybe_some: Option) -> bool { + maybe_some.is_none() && (!boolean) +} + +fn issue_11174_edge_cases(boolean: bool, boolean2: bool, maybe_some: Option) { + let _ = maybe_some.is_none() && (boolean || boolean2); // guard needs parentheses + let _ = match maybe_some { // can't use `matches!` here + // because `expr` metavars in macros don't allow let exprs + None if let Some(x) = Some(0) && x > 5 => true, + _ => false + }; +} fn main() { if None::<()>.is_none() {} diff --git a/tests/ui/redundant_pattern_matching_option.rs b/tests/ui/redundant_pattern_matching_option.rs index 631f9091672d7..cbd9494f15acf 100644 --- a/tests/ui/redundant_pattern_matching_option.rs +++ b/tests/ui/redundant_pattern_matching_option.rs @@ -10,6 +10,20 @@ clippy::equatable_if_let, clippy::if_same_then_else )] +#![feature(let_chains, if_let_guard)] + +fn issue_11174(boolean: bool, maybe_some: Option) -> bool { + matches!(maybe_some, None if !boolean) +} + +fn issue_11174_edge_cases(boolean: bool, boolean2: bool, maybe_some: Option) { + let _ = matches!(maybe_some, None if boolean || boolean2); // guard needs parentheses + let _ = match maybe_some { // can't use `matches!` here + // because `expr` metavars in macros don't allow let exprs + None if let Some(x) = Some(0) && x > 5 => true, + _ => false + }; +} fn main() { if let None = None::<()> {} diff --git a/tests/ui/redundant_pattern_matching_option.stderr b/tests/ui/redundant_pattern_matching_option.stderr index 097ca827a4260..b0e43924dbc9a 100644 --- a/tests/ui/redundant_pattern_matching_option.stderr +++ b/tests/ui/redundant_pattern_matching_option.stderr @@ -1,49 +1,61 @@ error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:15:12 + --> $DIR/redundant_pattern_matching_option.rs:16:5 | -LL | if let None = None::<()> {} - | -------^^^^------------- help: try: `if None::<()>.is_none()` +LL | matches!(maybe_some, None if !boolean) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `maybe_some.is_none() && (!boolean)` | = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings` +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching_option.rs:20:13 + | +LL | let _ = matches!(maybe_some, None if boolean || boolean2); // guard needs parentheses + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `maybe_some.is_none() && (boolean || boolean2)` + +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching_option.rs:29:12 + | +LL | if let None = None::<()> {} + | -------^^^^------------- help: try: `if None::<()>.is_none()` + error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:17:12 + --> $DIR/redundant_pattern_matching_option.rs:31:12 | LL | if let Some(_) = Some(42) {} | -------^^^^^^^----------- help: try: `if Some(42).is_some()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:19:12 + --> $DIR/redundant_pattern_matching_option.rs:33:12 | LL | if let Some(_) = Some(42) { | -------^^^^^^^----------- help: try: `if Some(42).is_some()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:25:15 + --> $DIR/redundant_pattern_matching_option.rs:39:15 | LL | while let Some(_) = Some(42) {} | ----------^^^^^^^----------- help: try: `while Some(42).is_some()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:27:15 + --> $DIR/redundant_pattern_matching_option.rs:41:15 | LL | while let None = Some(42) {} | ----------^^^^----------- help: try: `while Some(42).is_none()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:29:15 + --> $DIR/redundant_pattern_matching_option.rs:43:15 | LL | while let None = None::<()> {} | ----------^^^^------------- help: try: `while None::<()>.is_none()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:32:15 + --> $DIR/redundant_pattern_matching_option.rs:46:15 | LL | while let Some(_) = v.pop() { | ----------^^^^^^^---------- help: try: `while v.pop().is_some()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:40:5 + --> $DIR/redundant_pattern_matching_option.rs:54:5 | LL | / match Some(42) { LL | | Some(_) => true, @@ -52,7 +64,7 @@ LL | | }; | |_____^ help: try: `Some(42).is_some()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:45:5 + --> $DIR/redundant_pattern_matching_option.rs:59:5 | LL | / match None::<()> { LL | | Some(_) => false, @@ -61,7 +73,7 @@ LL | | }; | |_____^ help: try: `None::<()>.is_none()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:50:13 + --> $DIR/redundant_pattern_matching_option.rs:64:13 | LL | let _ = match None::<()> { | _____________^ @@ -71,55 +83,55 @@ LL | | }; | |_____^ help: try: `None::<()>.is_none()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:56:20 + --> $DIR/redundant_pattern_matching_option.rs:70:20 | LL | let _ = if let Some(_) = opt { true } else { false }; | -------^^^^^^^------ help: try: `if opt.is_some()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:62:20 + --> $DIR/redundant_pattern_matching_option.rs:76:20 | LL | let _ = if let Some(_) = gen_opt() { | -------^^^^^^^------------ help: try: `if gen_opt().is_some()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:64:19 + --> $DIR/redundant_pattern_matching_option.rs:78:19 | LL | } else if let None = gen_opt() { | -------^^^^------------ help: try: `if gen_opt().is_none()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:70:12 + --> $DIR/redundant_pattern_matching_option.rs:84:12 | LL | if let Some(..) = gen_opt() {} | -------^^^^^^^^------------ help: try: `if gen_opt().is_some()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:85:12 + --> $DIR/redundant_pattern_matching_option.rs:99:12 | LL | if let Some(_) = Some(42) {} | -------^^^^^^^----------- help: try: `if Some(42).is_some()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:87:12 + --> $DIR/redundant_pattern_matching_option.rs:101:12 | LL | if let None = None::<()> {} | -------^^^^------------- help: try: `if None::<()>.is_none()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:89:15 + --> $DIR/redundant_pattern_matching_option.rs:103:15 | LL | while let Some(_) = Some(42) {} | ----------^^^^^^^----------- help: try: `while Some(42).is_some()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:91:15 + --> $DIR/redundant_pattern_matching_option.rs:105:15 | LL | while let None = None::<()> {} | ----------^^^^------------- help: try: `while None::<()>.is_none()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:93:5 + --> $DIR/redundant_pattern_matching_option.rs:107:5 | LL | / match Some(42) { LL | | Some(_) => true, @@ -128,7 +140,7 @@ LL | | }; | |_____^ help: try: `Some(42).is_some()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:98:5 + --> $DIR/redundant_pattern_matching_option.rs:112:5 | LL | / match None::<()> { LL | | Some(_) => false, @@ -137,19 +149,19 @@ LL | | }; | |_____^ help: try: `None::<()>.is_none()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:106:12 + --> $DIR/redundant_pattern_matching_option.rs:120:12 | LL | if let None = *(&None::<()>) {} | -------^^^^----------------- help: try: `if (&None::<()>).is_none()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:107:12 + --> $DIR/redundant_pattern_matching_option.rs:121:12 | LL | if let None = *&None::<()> {} | -------^^^^--------------- help: try: `if (&None::<()>).is_none()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:113:5 + --> $DIR/redundant_pattern_matching_option.rs:127:5 | LL | / match x { LL | | Some(_) => true, @@ -158,7 +170,7 @@ LL | | }; | |_____^ help: try: `x.is_some()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:118:5 + --> $DIR/redundant_pattern_matching_option.rs:132:5 | LL | / match x { LL | | None => true, @@ -167,7 +179,7 @@ LL | | }; | |_____^ help: try: `x.is_none()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:123:5 + --> $DIR/redundant_pattern_matching_option.rs:137:5 | LL | / match x { LL | | Some(_) => false, @@ -176,7 +188,7 @@ LL | | }; | |_____^ help: try: `x.is_none()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:128:5 + --> $DIR/redundant_pattern_matching_option.rs:142:5 | LL | / match x { LL | | None => false, @@ -185,16 +197,16 @@ LL | | }; | |_____^ help: try: `x.is_some()` error: redundant pattern matching, consider using `is_some()` - --> $DIR/redundant_pattern_matching_option.rs:143:13 + --> $DIR/redundant_pattern_matching_option.rs:157:13 | LL | let _ = matches!(x, Some(_)); | ^^^^^^^^^^^^^^^^^^^^ help: try: `x.is_some()` error: redundant pattern matching, consider using `is_none()` - --> $DIR/redundant_pattern_matching_option.rs:145:13 + --> $DIR/redundant_pattern_matching_option.rs:159:13 | LL | let _ = matches!(x, None); | ^^^^^^^^^^^^^^^^^ help: try: `x.is_none()` -error: aborting due to 28 previous errors +error: aborting due to 30 previous errors diff --git a/tests/ui/rename.fixed b/tests/ui/rename.fixed index 03d9ea41a0b1b..8ec19b120d127 100644 --- a/tests/ui/rename.fixed +++ b/tests/ui/rename.fixed @@ -27,6 +27,7 @@ #![allow(clippy::single_char_add_str)] #![allow(clippy::module_name_repetitions)] #![allow(clippy::recursive_format_impl)] +#![allow(clippy::unwrap_or_default)] #![allow(clippy::invisible_characters)] #![allow(suspicious_double_ref_op)] #![allow(invalid_nan_comparisons)] @@ -78,6 +79,7 @@ #![warn(clippy::single_char_add_str)] #![warn(clippy::module_name_repetitions)] #![warn(clippy::recursive_format_impl)] +#![warn(clippy::unwrap_or_default)] #![warn(clippy::invisible_characters)] #![warn(invalid_reference_casting)] #![warn(suspicious_double_ref_op)] diff --git a/tests/ui/rename.rs b/tests/ui/rename.rs index c028fcb5a3769..51a5976eb6149 100644 --- a/tests/ui/rename.rs +++ b/tests/ui/rename.rs @@ -27,6 +27,7 @@ #![allow(clippy::single_char_add_str)] #![allow(clippy::module_name_repetitions)] #![allow(clippy::recursive_format_impl)] +#![allow(clippy::unwrap_or_default)] #![allow(clippy::invisible_characters)] #![allow(suspicious_double_ref_op)] #![allow(invalid_nan_comparisons)] @@ -78,6 +79,7 @@ #![warn(clippy::single_char_push_str)] #![warn(clippy::stutter)] #![warn(clippy::to_string_in_display)] +#![warn(clippy::unwrap_or_else_default)] #![warn(clippy::zero_width_space)] #![warn(clippy::cast_ref_to_mut)] #![warn(clippy::clone_double_ref)] diff --git a/tests/ui/rename.stderr b/tests/ui/rename.stderr index 4d8a7fd70f454..e420ea1d2adb5 100644 --- a/tests/ui/rename.stderr +++ b/tests/ui/rename.stderr @@ -1,5 +1,5 @@ error: lint `clippy::almost_complete_letter_range` has been renamed to `clippy::almost_complete_range` - --> $DIR/rename.rs:53:9 + --> $DIR/rename.rs:54:9 | LL | #![warn(clippy::almost_complete_letter_range)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::almost_complete_range` @@ -7,316 +7,322 @@ LL | #![warn(clippy::almost_complete_letter_range)] = note: `-D renamed-and-removed-lints` implied by `-D warnings` error: lint `clippy::blacklisted_name` has been renamed to `clippy::disallowed_names` - --> $DIR/rename.rs:54:9 + --> $DIR/rename.rs:55:9 | LL | #![warn(clippy::blacklisted_name)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_names` error: lint `clippy::block_in_if_condition_expr` has been renamed to `clippy::blocks_in_if_conditions` - --> $DIR/rename.rs:55:9 + --> $DIR/rename.rs:56:9 | LL | #![warn(clippy::block_in_if_condition_expr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_if_conditions` error: lint `clippy::block_in_if_condition_stmt` has been renamed to `clippy::blocks_in_if_conditions` - --> $DIR/rename.rs:56:9 + --> $DIR/rename.rs:57:9 | LL | #![warn(clippy::block_in_if_condition_stmt)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_if_conditions` error: lint `clippy::box_vec` has been renamed to `clippy::box_collection` - --> $DIR/rename.rs:57:9 + --> $DIR/rename.rs:58:9 | LL | #![warn(clippy::box_vec)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::box_collection` error: lint `clippy::const_static_lifetime` has been renamed to `clippy::redundant_static_lifetimes` - --> $DIR/rename.rs:58:9 + --> $DIR/rename.rs:59:9 | LL | #![warn(clippy::const_static_lifetime)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_static_lifetimes` error: lint `clippy::cyclomatic_complexity` has been renamed to `clippy::cognitive_complexity` - --> $DIR/rename.rs:59:9 + --> $DIR/rename.rs:60:9 | LL | #![warn(clippy::cyclomatic_complexity)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::cognitive_complexity` error: lint `clippy::derive_hash_xor_eq` has been renamed to `clippy::derived_hash_with_manual_eq` - --> $DIR/rename.rs:60:9 + --> $DIR/rename.rs:61:9 | LL | #![warn(clippy::derive_hash_xor_eq)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::derived_hash_with_manual_eq` error: lint `clippy::disallowed_method` has been renamed to `clippy::disallowed_methods` - --> $DIR/rename.rs:61:9 + --> $DIR/rename.rs:62:9 | LL | #![warn(clippy::disallowed_method)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_methods` error: lint `clippy::disallowed_type` has been renamed to `clippy::disallowed_types` - --> $DIR/rename.rs:62:9 + --> $DIR/rename.rs:63:9 | LL | #![warn(clippy::disallowed_type)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_types` error: lint `clippy::eval_order_dependence` has been renamed to `clippy::mixed_read_write_in_expression` - --> $DIR/rename.rs:63:9 + --> $DIR/rename.rs:64:9 | LL | #![warn(clippy::eval_order_dependence)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::mixed_read_write_in_expression` error: lint `clippy::identity_conversion` has been renamed to `clippy::useless_conversion` - --> $DIR/rename.rs:64:9 + --> $DIR/rename.rs:65:9 | LL | #![warn(clippy::identity_conversion)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::useless_conversion` error: lint `clippy::if_let_some_result` has been renamed to `clippy::match_result_ok` - --> $DIR/rename.rs:65:9 + --> $DIR/rename.rs:66:9 | LL | #![warn(clippy::if_let_some_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::match_result_ok` error: lint `clippy::integer_arithmetic` has been renamed to `clippy::arithmetic_side_effects` - --> $DIR/rename.rs:66:9 + --> $DIR/rename.rs:67:9 | LL | #![warn(clippy::integer_arithmetic)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::arithmetic_side_effects` error: lint `clippy::logic_bug` has been renamed to `clippy::overly_complex_bool_expr` - --> $DIR/rename.rs:67:9 + --> $DIR/rename.rs:68:9 | LL | #![warn(clippy::logic_bug)] | ^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::overly_complex_bool_expr` error: lint `clippy::new_without_default_derive` has been renamed to `clippy::new_without_default` - --> $DIR/rename.rs:68:9 + --> $DIR/rename.rs:69:9 | LL | #![warn(clippy::new_without_default_derive)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::new_without_default` error: lint `clippy::option_and_then_some` has been renamed to `clippy::bind_instead_of_map` - --> $DIR/rename.rs:69:9 + --> $DIR/rename.rs:70:9 | LL | #![warn(clippy::option_and_then_some)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::bind_instead_of_map` error: lint `clippy::option_expect_used` has been renamed to `clippy::expect_used` - --> $DIR/rename.rs:70:9 + --> $DIR/rename.rs:71:9 | LL | #![warn(clippy::option_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::option_map_unwrap_or` has been renamed to `clippy::map_unwrap_or` - --> $DIR/rename.rs:71:9 + --> $DIR/rename.rs:72:9 | LL | #![warn(clippy::option_map_unwrap_or)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> $DIR/rename.rs:72:9 + --> $DIR/rename.rs:73:9 | LL | #![warn(clippy::option_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_unwrap_used` has been renamed to `clippy::unwrap_used` - --> $DIR/rename.rs:73:9 + --> $DIR/rename.rs:74:9 | LL | #![warn(clippy::option_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::ref_in_deref` has been renamed to `clippy::needless_borrow` - --> $DIR/rename.rs:74:9 + --> $DIR/rename.rs:75:9 | LL | #![warn(clippy::ref_in_deref)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::needless_borrow` error: lint `clippy::result_expect_used` has been renamed to `clippy::expect_used` - --> $DIR/rename.rs:75:9 + --> $DIR/rename.rs:76:9 | LL | #![warn(clippy::result_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::result_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> $DIR/rename.rs:76:9 + --> $DIR/rename.rs:77:9 | LL | #![warn(clippy::result_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::result_unwrap_used` has been renamed to `clippy::unwrap_used` - --> $DIR/rename.rs:77:9 + --> $DIR/rename.rs:78:9 | LL | #![warn(clippy::result_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::single_char_push_str` has been renamed to `clippy::single_char_add_str` - --> $DIR/rename.rs:78:9 + --> $DIR/rename.rs:79:9 | LL | #![warn(clippy::single_char_push_str)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::single_char_add_str` error: lint `clippy::stutter` has been renamed to `clippy::module_name_repetitions` - --> $DIR/rename.rs:79:9 + --> $DIR/rename.rs:80:9 | LL | #![warn(clippy::stutter)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::module_name_repetitions` error: lint `clippy::to_string_in_display` has been renamed to `clippy::recursive_format_impl` - --> $DIR/rename.rs:80:9 + --> $DIR/rename.rs:81:9 | LL | #![warn(clippy::to_string_in_display)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::recursive_format_impl` +error: lint `clippy::unwrap_or_else_default` has been renamed to `clippy::unwrap_or_default` + --> $DIR/rename.rs:82:9 + | +LL | #![warn(clippy::unwrap_or_else_default)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_or_default` + error: lint `clippy::zero_width_space` has been renamed to `clippy::invisible_characters` - --> $DIR/rename.rs:81:9 + --> $DIR/rename.rs:83:9 | LL | #![warn(clippy::zero_width_space)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::invisible_characters` error: lint `clippy::cast_ref_to_mut` has been renamed to `invalid_reference_casting` - --> $DIR/rename.rs:82:9 + --> $DIR/rename.rs:84:9 | LL | #![warn(clippy::cast_ref_to_mut)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_reference_casting` error: lint `clippy::clone_double_ref` has been renamed to `suspicious_double_ref_op` - --> $DIR/rename.rs:83:9 + --> $DIR/rename.rs:85:9 | LL | #![warn(clippy::clone_double_ref)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `suspicious_double_ref_op` error: lint `clippy::cmp_nan` has been renamed to `invalid_nan_comparisons` - --> $DIR/rename.rs:84:9 + --> $DIR/rename.rs:86:9 | LL | #![warn(clippy::cmp_nan)] | ^^^^^^^^^^^^^^^ help: use the new name: `invalid_nan_comparisons` error: lint `clippy::drop_bounds` has been renamed to `drop_bounds` - --> $DIR/rename.rs:85:9 + --> $DIR/rename.rs:87:9 | LL | #![warn(clippy::drop_bounds)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `drop_bounds` error: lint `clippy::drop_copy` has been renamed to `dropping_copy_types` - --> $DIR/rename.rs:86:9 + --> $DIR/rename.rs:88:9 | LL | #![warn(clippy::drop_copy)] | ^^^^^^^^^^^^^^^^^ help: use the new name: `dropping_copy_types` error: lint `clippy::drop_ref` has been renamed to `dropping_references` - --> $DIR/rename.rs:87:9 + --> $DIR/rename.rs:89:9 | LL | #![warn(clippy::drop_ref)] | ^^^^^^^^^^^^^^^^ help: use the new name: `dropping_references` error: lint `clippy::for_loop_over_option` has been renamed to `for_loops_over_fallibles` - --> $DIR/rename.rs:88:9 + --> $DIR/rename.rs:90:9 | LL | #![warn(clippy::for_loop_over_option)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::for_loop_over_result` has been renamed to `for_loops_over_fallibles` - --> $DIR/rename.rs:89:9 + --> $DIR/rename.rs:91:9 | LL | #![warn(clippy::for_loop_over_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::for_loops_over_fallibles` has been renamed to `for_loops_over_fallibles` - --> $DIR/rename.rs:90:9 + --> $DIR/rename.rs:92:9 | LL | #![warn(clippy::for_loops_over_fallibles)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::forget_copy` has been renamed to `forgetting_copy_types` - --> $DIR/rename.rs:91:9 + --> $DIR/rename.rs:93:9 | LL | #![warn(clippy::forget_copy)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_copy_types` error: lint `clippy::forget_ref` has been renamed to `forgetting_references` - --> $DIR/rename.rs:92:9 + --> $DIR/rename.rs:94:9 | LL | #![warn(clippy::forget_ref)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_references` error: lint `clippy::fn_null_check` has been renamed to `incorrect_fn_null_checks` - --> $DIR/rename.rs:93:9 + --> $DIR/rename.rs:95:9 | LL | #![warn(clippy::fn_null_check)] | ^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `incorrect_fn_null_checks` error: lint `clippy::into_iter_on_array` has been renamed to `array_into_iter` - --> $DIR/rename.rs:94:9 + --> $DIR/rename.rs:96:9 | LL | #![warn(clippy::into_iter_on_array)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `array_into_iter` error: lint `clippy::invalid_atomic_ordering` has been renamed to `invalid_atomic_ordering` - --> $DIR/rename.rs:95:9 + --> $DIR/rename.rs:97:9 | LL | #![warn(clippy::invalid_atomic_ordering)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_atomic_ordering` error: lint `clippy::invalid_ref` has been renamed to `invalid_value` - --> $DIR/rename.rs:96:9 + --> $DIR/rename.rs:98:9 | LL | #![warn(clippy::invalid_ref)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_value` error: lint `clippy::invalid_utf8_in_unchecked` has been renamed to `invalid_from_utf8_unchecked` - --> $DIR/rename.rs:97:9 + --> $DIR/rename.rs:99:9 | LL | #![warn(clippy::invalid_utf8_in_unchecked)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_from_utf8_unchecked` error: lint `clippy::let_underscore_drop` has been renamed to `let_underscore_drop` - --> $DIR/rename.rs:98:9 + --> $DIR/rename.rs:100:9 | LL | #![warn(clippy::let_underscore_drop)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `let_underscore_drop` error: lint `clippy::mem_discriminant_non_enum` has been renamed to `enum_intrinsics_non_enums` - --> $DIR/rename.rs:99:9 + --> $DIR/rename.rs:101:9 | LL | #![warn(clippy::mem_discriminant_non_enum)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `enum_intrinsics_non_enums` error: lint `clippy::panic_params` has been renamed to `non_fmt_panics` - --> $DIR/rename.rs:100:9 + --> $DIR/rename.rs:102:9 | LL | #![warn(clippy::panic_params)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `non_fmt_panics` error: lint `clippy::positional_named_format_parameters` has been renamed to `named_arguments_used_positionally` - --> $DIR/rename.rs:101:9 + --> $DIR/rename.rs:103:9 | LL | #![warn(clippy::positional_named_format_parameters)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `named_arguments_used_positionally` error: lint `clippy::temporary_cstring_as_ptr` has been renamed to `temporary_cstring_as_ptr` - --> $DIR/rename.rs:102:9 + --> $DIR/rename.rs:104:9 | LL | #![warn(clippy::temporary_cstring_as_ptr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `temporary_cstring_as_ptr` error: lint `clippy::undropped_manually_drops` has been renamed to `undropped_manually_drops` - --> $DIR/rename.rs:103:9 + --> $DIR/rename.rs:105:9 | LL | #![warn(clippy::undropped_manually_drops)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `undropped_manually_drops` error: lint `clippy::unknown_clippy_lints` has been renamed to `unknown_lints` - --> $DIR/rename.rs:104:9 + --> $DIR/rename.rs:106:9 | LL | #![warn(clippy::unknown_clippy_lints)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unknown_lints` error: lint `clippy::unused_label` has been renamed to `unused_labels` - --> $DIR/rename.rs:105:9 + --> $DIR/rename.rs:107:9 | LL | #![warn(clippy::unused_label)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unused_labels` -error: aborting due to 53 previous errors +error: aborting due to 54 previous errors diff --git a/tests/ui/semicolon_if_nothing_returned.fixed b/tests/ui/semicolon_if_nothing_returned.fixed new file mode 100644 index 0000000000000..653f4533b331f --- /dev/null +++ b/tests/ui/semicolon_if_nothing_returned.fixed @@ -0,0 +1,123 @@ +//@run-rustfix +#![warn(clippy::semicolon_if_nothing_returned)] +#![allow(clippy::redundant_closure, clippy::uninlined_format_args, clippy::needless_late_init)] + +fn get_unit() {} + +// the functions below trigger the lint +fn main() { + println!("Hello"); +} + +fn hello() { + get_unit(); +} + +fn basic101(x: i32) { + let y: i32; + y = x + 1; +} + +#[rustfmt::skip] +fn closure_error() { + let _d = || { + hello(); + }; +} + +#[rustfmt::skip] +fn unsafe_checks_error() { + use std::mem::MaybeUninit; + use std::ptr; + + let mut s = MaybeUninit::::uninit(); + let _d = || unsafe { + ptr::drop_in_place(s.as_mut_ptr()); + }; +} + +// this is fine +fn print_sum(a: i32, b: i32) { + println!("{}", a + b); + assert_eq!(true, false); +} + +fn foo(x: i32) { + let y: i32; + if x < 1 { + y = 4; + } else { + y = 5; + } +} + +fn bar(x: i32) { + let y: i32; + match x { + 1 => y = 4, + _ => y = 32, + } +} + +fn foobar(x: i32) { + let y: i32; + 'label: { + y = x + 1; + } +} + +fn loop_test(x: i32) { + let y: i32; + for &ext in &["stdout", "stderr", "fixed"] { + println!("{}", ext); + } +} + +fn closure() { + let _d = || hello(); +} + +#[rustfmt::skip] +fn closure_block() { + let _d = || { hello() }; +} + +unsafe fn some_unsafe_op() {} +unsafe fn some_other_unsafe_fn() {} + +fn do_something() { + unsafe { some_unsafe_op() }; + + unsafe { some_other_unsafe_fn() }; +} + +fn unsafe_checks() { + use std::mem::MaybeUninit; + use std::ptr; + + let mut s = MaybeUninit::::uninit(); + let _d = || unsafe { ptr::drop_in_place(s.as_mut_ptr()) }; +} + +// Issue #7768 +#[rustfmt::skip] +fn macro_with_semicolon() { + macro_rules! repro { + () => { + while false { + } + }; + } + repro!(); +} + +fn function_returning_option() -> Option { + Some(1) +} + +// No warning +fn let_else_stmts() { + let Some(x) = function_returning_option() else { + return; + }; +} diff --git a/tests/ui/semicolon_if_nothing_returned.rs b/tests/ui/semicolon_if_nothing_returned.rs index 8e7f1d862cfdc..9db038219b4ae 100644 --- a/tests/ui/semicolon_if_nothing_returned.rs +++ b/tests/ui/semicolon_if_nothing_returned.rs @@ -1,5 +1,6 @@ +//@run-rustfix #![warn(clippy::semicolon_if_nothing_returned)] -#![allow(clippy::redundant_closure, clippy::uninlined_format_args)] +#![allow(clippy::redundant_closure, clippy::uninlined_format_args, clippy::needless_late_init)] fn get_unit() {} diff --git a/tests/ui/semicolon_if_nothing_returned.stderr b/tests/ui/semicolon_if_nothing_returned.stderr index 8d9a67585cf12..78813e7cc1c39 100644 --- a/tests/ui/semicolon_if_nothing_returned.stderr +++ b/tests/ui/semicolon_if_nothing_returned.stderr @@ -1,5 +1,5 @@ error: consider adding a `;` to the last statement for consistent formatting - --> $DIR/semicolon_if_nothing_returned.rs:8:5 + --> $DIR/semicolon_if_nothing_returned.rs:9:5 | LL | println!("Hello") | ^^^^^^^^^^^^^^^^^ help: add a `;` here: `println!("Hello");` @@ -7,25 +7,25 @@ LL | println!("Hello") = note: `-D clippy::semicolon-if-nothing-returned` implied by `-D warnings` error: consider adding a `;` to the last statement for consistent formatting - --> $DIR/semicolon_if_nothing_returned.rs:12:5 + --> $DIR/semicolon_if_nothing_returned.rs:13:5 | LL | get_unit() | ^^^^^^^^^^ help: add a `;` here: `get_unit();` error: consider adding a `;` to the last statement for consistent formatting - --> $DIR/semicolon_if_nothing_returned.rs:17:5 + --> $DIR/semicolon_if_nothing_returned.rs:18:5 | LL | y = x + 1 | ^^^^^^^^^ help: add a `;` here: `y = x + 1;` error: consider adding a `;` to the last statement for consistent formatting - --> $DIR/semicolon_if_nothing_returned.rs:23:9 + --> $DIR/semicolon_if_nothing_returned.rs:24:9 | LL | hello() | ^^^^^^^ help: add a `;` here: `hello();` error: consider adding a `;` to the last statement for consistent formatting - --> $DIR/semicolon_if_nothing_returned.rs:34:9 + --> $DIR/semicolon_if_nothing_returned.rs:35:9 | LL | ptr::drop_in_place(s.as_mut_ptr()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add a `;` here: `ptr::drop_in_place(s.as_mut_ptr());` diff --git a/tests/ui/shadow.rs b/tests/ui/shadow.rs index 9be8c5e59d014..1b40a43d01962 100644 --- a/tests/ui/shadow.rs +++ b/tests/ui/shadow.rs @@ -1,7 +1,12 @@ //@aux-build:proc_macro_derive.rs:proc-macro #![warn(clippy::shadow_same, clippy::shadow_reuse, clippy::shadow_unrelated)] -#![allow(clippy::let_unit_value, clippy::needless_if)] +#![allow( + clippy::let_unit_value, + clippy::needless_if, + clippy::redundant_guards, + clippy::redundant_locals +)] extern crate proc_macro_derive; diff --git a/tests/ui/shadow.stderr b/tests/ui/shadow.stderr index 8321f6df224cf..88b02f53be12e 100644 --- a/tests/ui/shadow.stderr +++ b/tests/ui/shadow.stderr @@ -1,278 +1,278 @@ error: `x` is shadowed by itself in `x` - --> $DIR/shadow.rs:19:9 + --> $DIR/shadow.rs:24:9 | LL | let x = x; | ^ | note: previous binding is here - --> $DIR/shadow.rs:18:9 + --> $DIR/shadow.rs:23:9 | LL | let x = 1; | ^ = note: `-D clippy::shadow-same` implied by `-D warnings` error: `mut x` is shadowed by itself in `&x` - --> $DIR/shadow.rs:20:13 + --> $DIR/shadow.rs:25:13 | LL | let mut x = &x; | ^ | note: previous binding is here - --> $DIR/shadow.rs:19:9 + --> $DIR/shadow.rs:24:9 | LL | let x = x; | ^ error: `x` is shadowed by itself in `&mut x` - --> $DIR/shadow.rs:21:9 + --> $DIR/shadow.rs:26:9 | LL | let x = &mut x; | ^ | note: previous binding is here - --> $DIR/shadow.rs:20:9 + --> $DIR/shadow.rs:25:9 | LL | let mut x = &x; | ^^^^^ error: `x` is shadowed by itself in `*x` - --> $DIR/shadow.rs:22:9 + --> $DIR/shadow.rs:27:9 | LL | let x = *x; | ^ | note: previous binding is here - --> $DIR/shadow.rs:21:9 + --> $DIR/shadow.rs:26:9 | LL | let x = &mut x; | ^ error: `x` is shadowed - --> $DIR/shadow.rs:27:9 + --> $DIR/shadow.rs:32:9 | LL | let x = x.0; | ^ | note: previous binding is here - --> $DIR/shadow.rs:26:9 + --> $DIR/shadow.rs:31:9 | LL | let x = ([[0]], ()); | ^ = note: `-D clippy::shadow-reuse` implied by `-D warnings` error: `x` is shadowed - --> $DIR/shadow.rs:28:9 + --> $DIR/shadow.rs:33:9 | LL | let x = x[0]; | ^ | note: previous binding is here - --> $DIR/shadow.rs:27:9 + --> $DIR/shadow.rs:32:9 | LL | let x = x.0; | ^ error: `x` is shadowed - --> $DIR/shadow.rs:29:10 + --> $DIR/shadow.rs:34:10 | LL | let [x] = x; | ^ | note: previous binding is here - --> $DIR/shadow.rs:28:9 + --> $DIR/shadow.rs:33:9 | LL | let x = x[0]; | ^ error: `x` is shadowed - --> $DIR/shadow.rs:30:9 + --> $DIR/shadow.rs:35:9 | LL | let x = Some(x); | ^ | note: previous binding is here - --> $DIR/shadow.rs:29:10 + --> $DIR/shadow.rs:34:10 | LL | let [x] = x; | ^ error: `x` is shadowed - --> $DIR/shadow.rs:31:9 + --> $DIR/shadow.rs:36:9 | LL | let x = foo(x); | ^ | note: previous binding is here - --> $DIR/shadow.rs:30:9 + --> $DIR/shadow.rs:35:9 | LL | let x = Some(x); | ^ error: `x` is shadowed - --> $DIR/shadow.rs:32:9 + --> $DIR/shadow.rs:37:9 | LL | let x = || x; | ^ | note: previous binding is here - --> $DIR/shadow.rs:31:9 + --> $DIR/shadow.rs:36:9 | LL | let x = foo(x); | ^ error: `x` is shadowed - --> $DIR/shadow.rs:33:9 + --> $DIR/shadow.rs:38:9 | LL | let x = Some(1).map(|_| x)?; | ^ | note: previous binding is here - --> $DIR/shadow.rs:32:9 + --> $DIR/shadow.rs:37:9 | LL | let x = || x; | ^ error: `y` is shadowed - --> $DIR/shadow.rs:35:9 + --> $DIR/shadow.rs:40:9 | LL | let y = match y { | ^ | note: previous binding is here - --> $DIR/shadow.rs:34:9 + --> $DIR/shadow.rs:39:9 | LL | let y = 1; | ^ error: `x` shadows a previous, unrelated binding - --> $DIR/shadow.rs:50:9 + --> $DIR/shadow.rs:55:9 | LL | let x = 2; | ^ | note: previous binding is here - --> $DIR/shadow.rs:49:9 + --> $DIR/shadow.rs:54:9 | LL | let x = 1; | ^ = note: `-D clippy::shadow-unrelated` implied by `-D warnings` error: `x` shadows a previous, unrelated binding - --> $DIR/shadow.rs:55:13 + --> $DIR/shadow.rs:60:13 | LL | let x = 1; | ^ | note: previous binding is here - --> $DIR/shadow.rs:54:10 + --> $DIR/shadow.rs:59:10 | LL | fn f(x: u32) { | ^ error: `x` shadows a previous, unrelated binding - --> $DIR/shadow.rs:60:14 + --> $DIR/shadow.rs:65:14 | LL | Some(x) => { | ^ | note: previous binding is here - --> $DIR/shadow.rs:57:9 + --> $DIR/shadow.rs:62:9 | LL | let x = 1; | ^ error: `x` shadows a previous, unrelated binding - --> $DIR/shadow.rs:61:17 + --> $DIR/shadow.rs:66:17 | LL | let x = 1; | ^ | note: previous binding is here - --> $DIR/shadow.rs:60:14 + --> $DIR/shadow.rs:65:14 | LL | Some(x) => { | ^ error: `x` shadows a previous, unrelated binding - --> $DIR/shadow.rs:65:17 + --> $DIR/shadow.rs:70:17 | LL | if let Some(x) = Some(1) {} | ^ | note: previous binding is here - --> $DIR/shadow.rs:57:9 + --> $DIR/shadow.rs:62:9 | LL | let x = 1; | ^ error: `x` shadows a previous, unrelated binding - --> $DIR/shadow.rs:66:20 + --> $DIR/shadow.rs:71:20 | LL | while let Some(x) = Some(1) {} | ^ | note: previous binding is here - --> $DIR/shadow.rs:57:9 + --> $DIR/shadow.rs:62:9 | LL | let x = 1; | ^ error: `x` shadows a previous, unrelated binding - --> $DIR/shadow.rs:67:15 + --> $DIR/shadow.rs:72:15 | LL | let _ = |[x]: [u32; 1]| { | ^ | note: previous binding is here - --> $DIR/shadow.rs:57:9 + --> $DIR/shadow.rs:62:9 | LL | let x = 1; | ^ error: `x` shadows a previous, unrelated binding - --> $DIR/shadow.rs:68:13 + --> $DIR/shadow.rs:73:13 | LL | let x = 1; | ^ | note: previous binding is here - --> $DIR/shadow.rs:67:15 + --> $DIR/shadow.rs:72:15 | LL | let _ = |[x]: [u32; 1]| { | ^ error: `y` is shadowed - --> $DIR/shadow.rs:71:17 + --> $DIR/shadow.rs:76:17 | LL | if let Some(y) = y {} | ^ | note: previous binding is here - --> $DIR/shadow.rs:70:9 + --> $DIR/shadow.rs:75:9 | LL | let y = Some(1); | ^ error: `_b` shadows a previous, unrelated binding - --> $DIR/shadow.rs:107:9 + --> $DIR/shadow.rs:112:9 | LL | let _b = _a; | ^^ | note: previous binding is here - --> $DIR/shadow.rs:106:28 + --> $DIR/shadow.rs:111:28 | LL | pub async fn foo2(_a: i32, _b: i64) { | ^^ error: `x` shadows a previous, unrelated binding - --> $DIR/shadow.rs:113:21 + --> $DIR/shadow.rs:118:21 | LL | if let Some(x) = Some(1) { x } else { 1 } | ^ | note: previous binding is here - --> $DIR/shadow.rs:112:13 + --> $DIR/shadow.rs:117:13 | LL | let x = 1; | ^ diff --git a/tests/ui/should_impl_trait/method_list_2.stderr b/tests/ui/should_impl_trait/method_list_2.stderr index 2ae9fa34d142b..a3bb05bf176ba 100644 --- a/tests/ui/should_impl_trait/method_list_2.stderr +++ b/tests/ui/should_impl_trait/method_list_2.stderr @@ -39,15 +39,6 @@ LL | | } | = help: consider implementing the trait `std::hash::Hash` or choosing a less ambiguous method name -error: this argument is a mutable reference, but not used mutably - --> $DIR/method_list_2.rs:38:31 - | -LL | pub fn hash(&self, state: &mut T) { - | ^^^^^^ help: consider changing to: `&T` - | - = warning: changing this function will impact semver compatibility - = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` - error: method `index` can be confused for the standard trait method `std::ops::Index::index` --> $DIR/method_list_2.rs:42:5 | @@ -158,5 +149,14 @@ LL | | } | = help: consider implementing the trait `std::ops::Sub` or choosing a less ambiguous method name +error: this argument is a mutable reference, but not used mutably + --> $DIR/method_list_2.rs:38:31 + | +LL | pub fn hash(&self, state: &mut T) { + | ^^^^^^ help: consider changing to: `&T` + | + = warning: changing this function will impact semver compatibility + = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` + error: aborting due to 16 previous errors diff --git a/tests/ui/significant_drop_tightening.fixed b/tests/ui/significant_drop_tightening.fixed index eb8524167c4a3..8065e9e5fbc92 100644 --- a/tests/ui/significant_drop_tightening.fixed +++ b/tests/ui/significant_drop_tightening.fixed @@ -51,6 +51,33 @@ pub fn issue_11128() { } } +pub fn issue_11160() -> bool { + let mutex = Mutex::new(1i32); + let lock = mutex.lock().unwrap(); + let _ = lock.abs(); + true +} + +pub fn issue_11189() { + struct Number { + pub value: u32, + } + + fn do_something() -> Result<(), ()> { + let number = Mutex::new(Number { value: 1 }); + let number2 = Mutex::new(Number { value: 2 }); + let number3 = Mutex::new(Number { value: 3 }); + let mut lock = number.lock().unwrap(); + let mut lock2 = number2.lock().unwrap(); + let mut lock3 = number3.lock().unwrap(); + lock.value += 1; + lock2.value += 1; + lock3.value += 1; + drop((lock, lock2, lock3)); + Ok(()) + } +} + pub fn path_return_can_be_ignored() -> i32 { let mutex = Mutex::new(1); let lock = mutex.lock().unwrap(); diff --git a/tests/ui/significant_drop_tightening.rs b/tests/ui/significant_drop_tightening.rs index f7fa65ea92270..1620b76843a0b 100644 --- a/tests/ui/significant_drop_tightening.rs +++ b/tests/ui/significant_drop_tightening.rs @@ -50,6 +50,33 @@ pub fn issue_11128() { } } +pub fn issue_11160() -> bool { + let mutex = Mutex::new(1i32); + let lock = mutex.lock().unwrap(); + let _ = lock.abs(); + true +} + +pub fn issue_11189() { + struct Number { + pub value: u32, + } + + fn do_something() -> Result<(), ()> { + let number = Mutex::new(Number { value: 1 }); + let number2 = Mutex::new(Number { value: 2 }); + let number3 = Mutex::new(Number { value: 3 }); + let mut lock = number.lock().unwrap(); + let mut lock2 = number2.lock().unwrap(); + let mut lock3 = number3.lock().unwrap(); + lock.value += 1; + lock2.value += 1; + lock3.value += 1; + drop((lock, lock2, lock3)); + Ok(()) + } +} + pub fn path_return_can_be_ignored() -> i32 { let mutex = Mutex::new(1); let lock = mutex.lock().unwrap(); diff --git a/tests/ui/significant_drop_tightening.stderr b/tests/ui/significant_drop_tightening.stderr index ca4fede17c93a..b5cad88ad3ffe 100644 --- a/tests/ui/significant_drop_tightening.stderr +++ b/tests/ui/significant_drop_tightening.stderr @@ -23,7 +23,7 @@ LL + drop(lock); | error: temporary with significant `Drop` can be early dropped - --> $DIR/significant_drop_tightening.rs:79:13 + --> $DIR/significant_drop_tightening.rs:106:13 | LL | / { LL | | let mutex = Mutex::new(1i32); @@ -43,7 +43,7 @@ LL + drop(lock); | error: temporary with significant `Drop` can be early dropped - --> $DIR/significant_drop_tightening.rs:100:13 + --> $DIR/significant_drop_tightening.rs:127:13 | LL | / { LL | | let mutex = Mutex::new(1i32); @@ -67,7 +67,7 @@ LL + | error: temporary with significant `Drop` can be early dropped - --> $DIR/significant_drop_tightening.rs:106:17 + --> $DIR/significant_drop_tightening.rs:133:17 | LL | / { LL | | let mutex = Mutex::new(vec![1i32]); diff --git a/tests/ui/single_match.fixed b/tests/ui/single_match.fixed index e7b1fd6a85f22..163ba94aff802 100644 --- a/tests/ui/single_match.fixed +++ b/tests/ui/single_match.fixed @@ -4,6 +4,7 @@ unused, clippy::uninlined_format_args, clippy::needless_if, + clippy::redundant_guards, clippy::redundant_pattern_matching )] fn dummy() {} diff --git a/tests/ui/single_match.rs b/tests/ui/single_match.rs index 1515a7053e5dd..0dcdb125ffd8a 100644 --- a/tests/ui/single_match.rs +++ b/tests/ui/single_match.rs @@ -4,6 +4,7 @@ unused, clippy::uninlined_format_args, clippy::needless_if, + clippy::redundant_guards, clippy::redundant_pattern_matching )] fn dummy() {} diff --git a/tests/ui/single_match.stderr b/tests/ui/single_match.stderr index 76f7e78958985..d35361599493e 100644 --- a/tests/ui/single_match.stderr +++ b/tests/ui/single_match.stderr @@ -1,5 +1,5 @@ error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:14:5 + --> $DIR/single_match.rs:15:5 | LL | / match x { LL | | Some(y) => { @@ -18,7 +18,7 @@ LL ~ }; | error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:22:5 + --> $DIR/single_match.rs:23:5 | LL | / match x { LL | | // Note the missing block braces. @@ -30,7 +30,7 @@ LL | | } | |_____^ help: try: `if let Some(y) = x { println!("{:?}", y) }` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:31:5 + --> $DIR/single_match.rs:32:5 | LL | / match z { LL | | (2..=3, 7..=9) => dummy(), @@ -39,7 +39,7 @@ LL | | }; | |_____^ help: try: `if let (2..=3, 7..=9) = z { dummy() }` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:60:5 + --> $DIR/single_match.rs:61:5 | LL | / match x { LL | | Some(y) => dummy(), @@ -48,7 +48,7 @@ LL | | }; | |_____^ help: try: `if let Some(y) = x { dummy() }` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:65:5 + --> $DIR/single_match.rs:66:5 | LL | / match y { LL | | Ok(y) => dummy(), @@ -57,7 +57,7 @@ LL | | }; | |_____^ help: try: `if let Ok(y) = y { dummy() }` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:72:5 + --> $DIR/single_match.rs:73:5 | LL | / match c { LL | | Cow::Borrowed(..) => dummy(), @@ -66,7 +66,7 @@ LL | | }; | |_____^ help: try: `if let Cow::Borrowed(..) = c { dummy() }` error: you seem to be trying to use `match` for an equality check. Consider using `if` - --> $DIR/single_match.rs:93:5 + --> $DIR/single_match.rs:94:5 | LL | / match x { LL | | "test" => println!(), @@ -75,7 +75,7 @@ LL | | } | |_____^ help: try: `if x == "test" { println!() }` error: you seem to be trying to use `match` for an equality check. Consider using `if` - --> $DIR/single_match.rs:106:5 + --> $DIR/single_match.rs:107:5 | LL | / match x { LL | | Foo::A => println!(), @@ -84,7 +84,7 @@ LL | | } | |_____^ help: try: `if x == Foo::A { println!() }` error: you seem to be trying to use `match` for an equality check. Consider using `if` - --> $DIR/single_match.rs:112:5 + --> $DIR/single_match.rs:113:5 | LL | / match x { LL | | FOO_C => println!(), @@ -93,7 +93,7 @@ LL | | } | |_____^ help: try: `if x == FOO_C { println!() }` error: you seem to be trying to use `match` for an equality check. Consider using `if` - --> $DIR/single_match.rs:117:5 + --> $DIR/single_match.rs:118:5 | LL | / match &&x { LL | | Foo::A => println!(), @@ -102,7 +102,7 @@ LL | | } | |_____^ help: try: `if x == Foo::A { println!() }` error: you seem to be trying to use `match` for an equality check. Consider using `if` - --> $DIR/single_match.rs:123:5 + --> $DIR/single_match.rs:124:5 | LL | / match &x { LL | | Foo::A => println!(), @@ -111,7 +111,7 @@ LL | | } | |_____^ help: try: `if x == &Foo::A { println!() }` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:140:5 + --> $DIR/single_match.rs:141:5 | LL | / match x { LL | | Bar::A => println!(), @@ -120,7 +120,7 @@ LL | | } | |_____^ help: try: `if let Bar::A = x { println!() }` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:148:5 + --> $DIR/single_match.rs:149:5 | LL | / match x { LL | | None => println!(), @@ -129,7 +129,7 @@ LL | | }; | |_____^ help: try: `if let None = x { println!() }` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:170:5 + --> $DIR/single_match.rs:171:5 | LL | / match x { LL | | (Some(_), _) => {}, @@ -138,7 +138,7 @@ LL | | } | |_____^ help: try: `if let (Some(_), _) = x {}` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:176:5 + --> $DIR/single_match.rs:177:5 | LL | / match x { LL | | (Some(E::V), _) => todo!(), @@ -147,7 +147,7 @@ LL | | } | |_____^ help: try: `if let (Some(E::V), _) = x { todo!() }` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:182:5 + --> $DIR/single_match.rs:183:5 | LL | / match (Some(42), Some(E::V), Some(42)) { LL | | (.., Some(E::V), _) => {}, @@ -156,7 +156,7 @@ LL | | } | |_____^ help: try: `if let (.., Some(E::V), _) = (Some(42), Some(E::V), Some(42)) {}` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:254:5 + --> $DIR/single_match.rs:255:5 | LL | / match bar { LL | | Some(v) => unsafe { @@ -176,7 +176,7 @@ LL + } } | error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:262:5 + --> $DIR/single_match.rs:263:5 | LL | / match bar { LL | | #[rustfmt::skip] diff --git a/tests/ui/slow_vector_initialization.rs b/tests/ui/slow_vector_initialization.rs index 16be9f6d203aa..cfb856861b8c6 100644 --- a/tests/ui/slow_vector_initialization.rs +++ b/tests/ui/slow_vector_initialization.rs @@ -4,6 +4,7 @@ fn main() { resize_vector(); extend_vector(); mixed_extend_resize_vector(); + from_empty_vec(); } fn extend_vector() { @@ -59,6 +60,21 @@ fn resize_vector() { vec1.resize(10, 0); } +fn from_empty_vec() { + // Resize with constant expression + let len = 300; + let mut vec1 = Vec::new(); + vec1.resize(len, 0); + + // Resize with len expression + let mut vec3 = Vec::new(); + vec3.resize(len - 10, 0); + + // Reinitialization should be warned + vec1 = Vec::new(); + vec1.resize(10, 0); +} + fn do_stuff(vec: &mut [u8]) {} fn extend_vector_with_manipulations_between() { diff --git a/tests/ui/slow_vector_initialization.stderr b/tests/ui/slow_vector_initialization.stderr index 22376680a8e6b..532ce4ac19113 100644 --- a/tests/ui/slow_vector_initialization.stderr +++ b/tests/ui/slow_vector_initialization.stderr @@ -1,84 +1,108 @@ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:13:5 + --> $DIR/slow_vector_initialization.rs:14:5 | LL | let mut vec1 = Vec::with_capacity(len); - | ----------------------- help: consider replace allocation with: `vec![0; len]` + | ----------------------- help: consider replacing this with: `vec![0; len]` LL | vec1.extend(repeat(0).take(len)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::slow-vector-initialization` implied by `-D warnings` error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:17:5 + --> $DIR/slow_vector_initialization.rs:18:5 | LL | let mut vec2 = Vec::with_capacity(len - 10); - | ---------------------------- help: consider replace allocation with: `vec![0; len - 10]` + | ---------------------------- help: consider replacing this with: `vec![0; len - 10]` LL | vec2.extend(repeat(0).take(len - 10)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:24:5 + --> $DIR/slow_vector_initialization.rs:25:5 | LL | let mut vec4 = Vec::with_capacity(len); - | ----------------------- help: consider replace allocation with: `vec![0; len]` + | ----------------------- help: consider replacing this with: `vec![0; len]` LL | vec4.extend(repeat(0).take(vec4.capacity())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:34:5 + --> $DIR/slow_vector_initialization.rs:35:5 | LL | let mut resized_vec = Vec::with_capacity(30); - | ---------------------- help: consider replace allocation with: `vec![0; 30]` + | ---------------------- help: consider replacing this with: `vec![0; 30]` LL | resized_vec.resize(30, 0); | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:37:5 + --> $DIR/slow_vector_initialization.rs:38:5 | LL | let mut extend_vec = Vec::with_capacity(30); - | ---------------------- help: consider replace allocation with: `vec![0; 30]` + | ---------------------- help: consider replacing this with: `vec![0; 30]` LL | extend_vec.extend(repeat(0).take(30)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:44:5 + --> $DIR/slow_vector_initialization.rs:45:5 | LL | let mut vec1 = Vec::with_capacity(len); - | ----------------------- help: consider replace allocation with: `vec![0; len]` + | ----------------------- help: consider replacing this with: `vec![0; len]` LL | vec1.resize(len, 0); | ^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:52:5 + --> $DIR/slow_vector_initialization.rs:53:5 | LL | let mut vec3 = Vec::with_capacity(len - 10); - | ---------------------------- help: consider replace allocation with: `vec![0; len - 10]` + | ---------------------------- help: consider replacing this with: `vec![0; len - 10]` LL | vec3.resize(len - 10, 0); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:55:5 + --> $DIR/slow_vector_initialization.rs:56:5 | LL | let mut vec4 = Vec::with_capacity(len); - | ----------------------- help: consider replace allocation with: `vec![0; len]` + | ----------------------- help: consider replacing this with: `vec![0; len]` LL | vec4.resize(vec4.capacity(), 0); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:59:5 + --> $DIR/slow_vector_initialization.rs:60:5 | LL | vec1 = Vec::with_capacity(10); - | ---------------------- help: consider replace allocation with: `vec![0; 10]` + | ---------------------- help: consider replacing this with: `vec![0; 10]` +LL | vec1.resize(10, 0); + | ^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:67:5 + | +LL | let mut vec1 = Vec::new(); + | ---------- help: consider replacing this with: `vec![0; len]` +LL | vec1.resize(len, 0); + | ^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:71:5 + | +LL | let mut vec3 = Vec::new(); + | ---------- help: consider replacing this with: `vec![0; len - 10]` +LL | vec3.resize(len - 10, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:75:5 + | +LL | vec1 = Vec::new(); + | ---------- help: consider replacing this with: `vec![0; 10]` LL | vec1.resize(10, 0); | ^^^^^^^^^^^^^^^^^^ error: this argument is a mutable reference, but not used mutably - --> $DIR/slow_vector_initialization.rs:62:18 + --> $DIR/slow_vector_initialization.rs:78:18 | LL | fn do_stuff(vec: &mut [u8]) {} | ^^^^^^^^^ help: consider changing to: `&[u8]` | = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` -error: aborting due to 10 previous errors +error: aborting due to 13 previous errors diff --git a/tests/ui/string_lit_chars_any.fixed b/tests/ui/string_lit_chars_any.fixed new file mode 100644 index 0000000000000..d7ab9c3397a12 --- /dev/null +++ b/tests/ui/string_lit_chars_any.fixed @@ -0,0 +1,50 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow(clippy::eq_op, clippy::needless_raw_string_hashes, clippy::no_effect, unused)] +#![warn(clippy::string_lit_chars_any)] + +#[macro_use] +extern crate proc_macros; + +struct NotStringLit; + +impl NotStringLit { + fn chars(&self) -> impl Iterator { + "c".chars() + } +} + +fn main() { + let c = 'c'; + matches!(c, '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + matches!(c, '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + matches!(c, '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + matches!(c, '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + #[rustfmt::skip] + matches!(c, '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + // Do not lint + NotStringLit.chars().any(|x| x == c); + "\\.+*?()|[]{}^$#&-~".chars().any(|x| { + let c = 'c'; + x == c + }); + "\\.+*?()|[]{}^$#&-~".chars().any(|x| { + 1; + x == c + }); + "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == x); + "\\.+*?()|[]{}^$#&-~".chars().any(|_x| c == c); + matches!( + c, + '\\' | '.' | '+' | '*' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~' + ); + external! { + let c = 'c'; + "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == c); + } + with_span! { + span + let c = 'c'; + "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == c); + } +} diff --git a/tests/ui/string_lit_chars_any.rs b/tests/ui/string_lit_chars_any.rs new file mode 100644 index 0000000000000..9408d7bb2390c --- /dev/null +++ b/tests/ui/string_lit_chars_any.rs @@ -0,0 +1,50 @@ +//@run-rustfix +//@aux-build:proc_macros.rs:proc-macro +#![allow(clippy::eq_op, clippy::needless_raw_string_hashes, clippy::no_effect, unused)] +#![warn(clippy::string_lit_chars_any)] + +#[macro_use] +extern crate proc_macros; + +struct NotStringLit; + +impl NotStringLit { + fn chars(&self) -> impl Iterator { + "c".chars() + } +} + +fn main() { + let c = 'c'; + "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == c); + r#"\.+*?()|[]{}^$#&-~"#.chars().any(|x| x == c); + "\\.+*?()|[]{}^$#&-~".chars().any(|x| c == x); + r#"\.+*?()|[]{}^$#&-~"#.chars().any(|x| c == x); + #[rustfmt::skip] + "\\.+*?()|[]{}^$#&-~".chars().any(|x| { x == c }); + // Do not lint + NotStringLit.chars().any(|x| x == c); + "\\.+*?()|[]{}^$#&-~".chars().any(|x| { + let c = 'c'; + x == c + }); + "\\.+*?()|[]{}^$#&-~".chars().any(|x| { + 1; + x == c + }); + "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == x); + "\\.+*?()|[]{}^$#&-~".chars().any(|_x| c == c); + matches!( + c, + '\\' | '.' | '+' | '*' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~' + ); + external! { + let c = 'c'; + "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == c); + } + with_span! { + span + let c = 'c'; + "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == c); + } +} diff --git a/tests/ui/string_lit_chars_any.stderr b/tests/ui/string_lit_chars_any.stderr new file mode 100644 index 0000000000000..ff951b73deddf --- /dev/null +++ b/tests/ui/string_lit_chars_any.stderr @@ -0,0 +1,58 @@ +error: usage of `.chars().any(...)` to check if a char matches any from a string literal + --> $DIR/string_lit_chars_any.rs:19:5 + | +LL | "//.+*?()|[]{}^$#&-~".chars().any(|x| x == c); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::string-lit-chars-any` implied by `-D warnings` +help: use `matches!(...)` instead + | +LL | matches!(c, '//' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: usage of `.chars().any(...)` to check if a char matches any from a string literal + --> $DIR/string_lit_chars_any.rs:20:5 + | +LL | r#"/.+*?()|[]{}^$#&-~"#.chars().any(|x| x == c); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `matches!(...)` instead + | +LL | matches!(c, '//' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: usage of `.chars().any(...)` to check if a char matches any from a string literal + --> $DIR/string_lit_chars_any.rs:21:5 + | +LL | "//.+*?()|[]{}^$#&-~".chars().any(|x| c == x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `matches!(...)` instead + | +LL | matches!(c, '//' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: usage of `.chars().any(...)` to check if a char matches any from a string literal + --> $DIR/string_lit_chars_any.rs:22:5 + | +LL | r#"/.+*?()|[]{}^$#&-~"#.chars().any(|x| c == x); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `matches!(...)` instead + | +LL | matches!(c, '//' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: usage of `.chars().any(...)` to check if a char matches any from a string literal + --> $DIR/string_lit_chars_any.rs:24:5 + | +LL | "//.+*?()|[]{}^$#&-~".chars().any(|x| { x == c }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `matches!(...)` instead + | +LL | matches!(c, '//' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: aborting due to 5 previous errors + diff --git a/tests/ui/swap.fixed b/tests/ui/swap.fixed index 22f904e3fd9e6..7b74a83b6df90 100644 --- a/tests/ui/swap.fixed +++ b/tests/ui/swap.fixed @@ -11,7 +11,8 @@ unused_assignments, unused_variables, clippy::let_and_return, - clippy::useless_vec + clippy::useless_vec, + clippy::redundant_locals )] struct Foo(u32); diff --git a/tests/ui/swap.rs b/tests/ui/swap.rs index ada64f89e6d27..93855cd7b5c29 100644 --- a/tests/ui/swap.rs +++ b/tests/ui/swap.rs @@ -11,7 +11,8 @@ unused_assignments, unused_variables, clippy::let_and_return, - clippy::useless_vec + clippy::useless_vec, + clippy::redundant_locals )] struct Foo(u32); diff --git a/tests/ui/swap.stderr b/tests/ui/swap.stderr index a3b9c2b744c98..1097b29bba022 100644 --- a/tests/ui/swap.stderr +++ b/tests/ui/swap.stderr @@ -1,5 +1,5 @@ error: this looks like you are swapping `bar.a` and `bar.b` manually - --> $DIR/swap.rs:28:5 + --> $DIR/swap.rs:29:5 | LL | / let temp = bar.a; LL | | bar.a = bar.b; @@ -10,7 +10,7 @@ LL | | bar.b = temp; = note: `-D clippy::manual-swap` implied by `-D warnings` error: this looks like you are swapping elements of `foo` manually - --> $DIR/swap.rs:40:5 + --> $DIR/swap.rs:41:5 | LL | / let temp = foo[0]; LL | | foo[0] = foo[1]; @@ -18,7 +18,7 @@ LL | | foo[1] = temp; | |__________________^ help: try: `foo.swap(0, 1);` error: this looks like you are swapping elements of `foo` manually - --> $DIR/swap.rs:49:5 + --> $DIR/swap.rs:50:5 | LL | / let temp = foo[0]; LL | | foo[0] = foo[1]; @@ -26,7 +26,7 @@ LL | | foo[1] = temp; | |__________________^ help: try: `foo.swap(0, 1);` error: this looks like you are swapping elements of `foo` manually - --> $DIR/swap.rs:68:5 + --> $DIR/swap.rs:69:5 | LL | / let temp = foo[0]; LL | | foo[0] = foo[1]; @@ -34,7 +34,7 @@ LL | | foo[1] = temp; | |__________________^ help: try: `foo.swap(0, 1);` error: this looks like you are swapping `a` and `b` manually - --> $DIR/swap.rs:79:5 + --> $DIR/swap.rs:80:5 | LL | / a ^= b; LL | | b ^= a; @@ -42,7 +42,7 @@ LL | | a ^= b; | |___________^ help: try: `std::mem::swap(&mut a, &mut b);` error: this looks like you are swapping `bar.a` and `bar.b` manually - --> $DIR/swap.rs:87:5 + --> $DIR/swap.rs:88:5 | LL | / bar.a ^= bar.b; LL | | bar.b ^= bar.a; @@ -50,7 +50,7 @@ LL | | bar.a ^= bar.b; | |___________________^ help: try: `std::mem::swap(&mut bar.a, &mut bar.b);` error: this looks like you are swapping elements of `foo` manually - --> $DIR/swap.rs:95:5 + --> $DIR/swap.rs:96:5 | LL | / foo[0] ^= foo[1]; LL | | foo[1] ^= foo[0]; @@ -58,7 +58,7 @@ LL | | foo[0] ^= foo[1]; | |_____________________^ help: try: `foo.swap(0, 1);` error: this looks like you are swapping `foo[0][1]` and `bar[1][0]` manually - --> $DIR/swap.rs:124:5 + --> $DIR/swap.rs:125:5 | LL | / let temp = foo[0][1]; LL | | foo[0][1] = bar[1][0]; @@ -68,7 +68,7 @@ LL | | bar[1][0] = temp; = note: or maybe you should use `std::mem::replace`? error: this looks like you are swapping `a` and `b` manually - --> $DIR/swap.rs:138:7 + --> $DIR/swap.rs:139:7 | LL | ; let t = a; | _______^ @@ -79,7 +79,7 @@ LL | | b = t; = note: or maybe you should use `std::mem::replace`? error: this looks like you are swapping `c.0` and `a` manually - --> $DIR/swap.rs:147:7 + --> $DIR/swap.rs:148:7 | LL | ; let t = c.0; | _______^ @@ -90,7 +90,7 @@ LL | | a = t; = note: or maybe you should use `std::mem::replace`? error: this looks like you are swapping `b` and `a` manually - --> $DIR/swap.rs:173:5 + --> $DIR/swap.rs:174:5 | LL | / let t = b; LL | | b = a; @@ -100,7 +100,7 @@ LL | | a = t; = note: or maybe you should use `std::mem::replace`? error: this looks like you are trying to swap `a` and `b` - --> $DIR/swap.rs:135:5 + --> $DIR/swap.rs:136:5 | LL | / a = b; LL | | b = a; @@ -110,7 +110,7 @@ LL | | b = a; = note: `-D clippy::almost-swapped` implied by `-D warnings` error: this looks like you are trying to swap `c.0` and `a` - --> $DIR/swap.rs:144:5 + --> $DIR/swap.rs:145:5 | LL | / c.0 = a; LL | | a = c.0; @@ -119,7 +119,7 @@ LL | | a = c.0; = note: or maybe you should use `std::mem::replace`? error: this looks like you are trying to swap `a` and `b` - --> $DIR/swap.rs:151:5 + --> $DIR/swap.rs:152:5 | LL | / let a = b; LL | | let b = a; @@ -128,7 +128,7 @@ LL | | let b = a; = note: or maybe you should use `std::mem::replace`? error: this looks like you are trying to swap `d` and `c` - --> $DIR/swap.rs:156:5 + --> $DIR/swap.rs:157:5 | LL | / d = c; LL | | c = d; @@ -137,7 +137,7 @@ LL | | c = d; = note: or maybe you should use `std::mem::replace`? error: this looks like you are trying to swap `a` and `b` - --> $DIR/swap.rs:160:5 + --> $DIR/swap.rs:161:5 | LL | / let a = b; LL | | b = a; @@ -146,7 +146,7 @@ LL | | b = a; = note: or maybe you should use `std::mem::replace`? error: this looks like you are swapping `s.0.x` and `s.0.y` manually - --> $DIR/swap.rs:208:5 + --> $DIR/swap.rs:209:5 | LL | / let t = s.0.x; LL | | s.0.x = s.0.y; diff --git a/tests/ui/try_err.fixed b/tests/ui/try_err.fixed index 1816740870ad6..930489fab7646 100644 --- a/tests/ui/try_err.fixed +++ b/tests/ui/try_err.fixed @@ -2,7 +2,11 @@ //@aux-build:proc_macros.rs:proc-macro #![deny(clippy::try_err)] -#![allow(clippy::unnecessary_wraps, clippy::needless_question_mark)] +#![allow( + clippy::unnecessary_wraps, + clippy::needless_question_mark, + clippy::needless_return_with_question_mark +)] extern crate proc_macros; use proc_macros::{external, inline_macros}; diff --git a/tests/ui/try_err.rs b/tests/ui/try_err.rs index 0e47c4d023ef7..f5baf3d8f744d 100644 --- a/tests/ui/try_err.rs +++ b/tests/ui/try_err.rs @@ -2,7 +2,11 @@ //@aux-build:proc_macros.rs:proc-macro #![deny(clippy::try_err)] -#![allow(clippy::unnecessary_wraps, clippy::needless_question_mark)] +#![allow( + clippy::unnecessary_wraps, + clippy::needless_question_mark, + clippy::needless_return_with_question_mark +)] extern crate proc_macros; use proc_macros::{external, inline_macros}; diff --git a/tests/ui/try_err.stderr b/tests/ui/try_err.stderr index 79f7b70224a19..9968b383ebbc9 100644 --- a/tests/ui/try_err.stderr +++ b/tests/ui/try_err.stderr @@ -1,5 +1,5 @@ error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:19:9 + --> $DIR/try_err.rs:23:9 | LL | Err(err)?; | ^^^^^^^^^ help: try: `return Err(err)` @@ -11,25 +11,25 @@ LL | #![deny(clippy::try_err)] | ^^^^^^^^^^^^^^^ error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:29:9 + --> $DIR/try_err.rs:33:9 | LL | Err(err)?; | ^^^^^^^^^ help: try: `return Err(err.into())` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:49:17 + --> $DIR/try_err.rs:53:17 | LL | Err(err)?; | ^^^^^^^^^ help: try: `return Err(err)` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:68:17 + --> $DIR/try_err.rs:72:17 | LL | Err(err)?; | ^^^^^^^^^ help: try: `return Err(err.into())` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:88:23 + --> $DIR/try_err.rs:92:23 | LL | Err(_) => Err(1)?, | ^^^^^^^ help: try: `return Err(1)` @@ -37,7 +37,7 @@ LL | Err(_) => Err(1)?, = note: this error originates in the macro `__inline_mac_fn_calling_macro` (in Nightly builds, run with -Z macro-backtrace for more info) error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:95:23 + --> $DIR/try_err.rs:99:23 | LL | Err(_) => Err(inline!(1))?, | ^^^^^^^^^^^^^^^^ help: try: `return Err(inline!(1))` @@ -45,31 +45,31 @@ LL | Err(_) => Err(inline!(1))?, = note: this error originates in the macro `__inline_mac_fn_calling_macro` (in Nightly builds, run with -Z macro-backtrace for more info) error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:122:9 + --> $DIR/try_err.rs:126:9 | LL | Err(inline!(inline!(String::from("aasdfasdfasdfa"))))?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `return Err(inline!(inline!(String::from("aasdfasdfasdfa"))))` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:129:9 + --> $DIR/try_err.rs:133:9 | LL | Err(io::ErrorKind::WriteZero)? | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `return Poll::Ready(Err(io::ErrorKind::WriteZero.into()))` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:131:9 + --> $DIR/try_err.rs:135:9 | LL | Err(io::Error::new(io::ErrorKind::InvalidInput, "error"))? | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `return Poll::Ready(Err(io::Error::new(io::ErrorKind::InvalidInput, "error")))` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:139:9 + --> $DIR/try_err.rs:143:9 | LL | Err(io::ErrorKind::NotFound)? | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `return Poll::Ready(Some(Err(io::ErrorKind::NotFound.into())))` error: returning an `Err(_)` with the `?` operator - --> $DIR/try_err.rs:148:16 + --> $DIR/try_err.rs:152:16 | LL | return Err(42)?; | ^^^^^^^^ help: try: `Err(42)` diff --git a/tests/ui/tuple_array_conversions.rs b/tests/ui/tuple_array_conversions.rs index f96a7c97f1aa6..569415acbceee 100644 --- a/tests/ui/tuple_array_conversions.rs +++ b/tests/ui/tuple_array_conversions.rs @@ -52,6 +52,36 @@ fn main() { let v1: Vec<[u32; 2]> = t1.iter().map(|&(a, b)| [a, b]).collect(); let t2: Vec<(u32, u32)> = v1.iter().map(|&[a, b]| (a, b)).collect(); } + // FP #11082; needs discussion + let (a, b) = (1.0f64, 2.0f64); + let _: &[f64] = &[a, b]; + // FP #11085; impossible to fix + let [src, dest]: [_; 2] = [1, 2]; + (src, dest); + // FP #11100 + fn issue_11100_array_to_tuple(this: [&mut i32; 2]) -> (&i32, &mut i32) { + let [input, output] = this; + (input, output) + } + + fn issue_11100_tuple_to_array<'a>(this: (&'a mut i32, &'a mut i32)) -> [&'a i32; 2] { + let (input, output) = this; + [input, output] + } + // FP #11124 + // tuple=>array + let (a, b) = (1, 2); + [a, b]; + let x = a; + // array=>tuple + let [a, b] = [1, 2]; + (a, b); + let x = a; + // FP #11144 + let (a, (b, c)) = (1, (2, 3)); + [a, c]; + let [[a, b], [c, d]] = [[1, 2], [3, 4]]; + (a, c); } #[clippy::msrv = "1.70.0"] diff --git a/tests/ui/tuple_array_conversions.stderr b/tests/ui/tuple_array_conversions.stderr index be653e8efb799..50bdcf29d1fad 100644 --- a/tests/ui/tuple_array_conversions.stderr +++ b/tests/ui/tuple_array_conversions.stderr @@ -15,14 +15,6 @@ LL | let x = [x.0, x.1]; | = help: use `.into()` instead, or `<[T; N]>::from` if type annotations are needed -error: it looks like you're trying to convert an array to a tuple - --> $DIR/tuple_array_conversions.rs:13:13 - | -LL | let x = (x[0], x[1]); - | ^^^^^^^^^^^^ - | - = help: use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed - error: it looks like you're trying to convert a tuple to an array --> $DIR/tuple_array_conversions.rs:16:53 | @@ -55,8 +47,24 @@ LL | t1.iter().for_each(|&(a, b)| _ = [a, b]); | = help: use `.into()` instead, or `<[T; N]>::from` if type annotations are needed +error: it looks like you're trying to convert a tuple to an array + --> $DIR/tuple_array_conversions.rs:57:22 + | +LL | let _: &[f64] = &[a, b]; + | ^^^^^^ + | + = help: use `.into()` instead, or `<[T; N]>::from` if type annotations are needed + error: it looks like you're trying to convert an array to a tuple - --> $DIR/tuple_array_conversions.rs:69:13 + --> $DIR/tuple_array_conversions.rs:60:5 + | +LL | (src, dest); + | ^^^^^^^^^^^ + | + = help: use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed + +error: it looks like you're trying to convert an array to a tuple + --> $DIR/tuple_array_conversions.rs:99:13 | LL | let x = (x[0], x[1]); | ^^^^^^^^^^^^ @@ -64,20 +72,12 @@ LL | let x = (x[0], x[1]); = help: use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed error: it looks like you're trying to convert a tuple to an array - --> $DIR/tuple_array_conversions.rs:70:13 + --> $DIR/tuple_array_conversions.rs:100:13 | LL | let x = [x.0, x.1]; | ^^^^^^^^^^ | = help: use `.into()` instead, or `<[T; N]>::from` if type annotations are needed -error: it looks like you're trying to convert an array to a tuple - --> $DIR/tuple_array_conversions.rs:72:13 - | -LL | let x = (x[0], x[1]); - | ^^^^^^^^^^^^ - | - = help: use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed - error: aborting due to 10 previous errors diff --git a/tests/ui/unnecessary_cast.fixed b/tests/ui/unnecessary_cast.fixed index 8efd44baf59fe..2bf02f1341425 100644 --- a/tests/ui/unnecessary_cast.fixed +++ b/tests/ui/unnecessary_cast.fixed @@ -38,6 +38,16 @@ mod fake_libc { } } +fn aaa() -> ::std::primitive::u32 { + 0 +} + +use std::primitive::u32 as UnsignedThirtyTwoBitInteger; + +fn bbb() -> UnsignedThirtyTwoBitInteger { + 0 +} + #[rustfmt::skip] fn main() { // Test cast_unnecessary @@ -105,6 +115,13 @@ fn main() { extern_fake_libc::getpid_SAFE_TRUTH() as i32; let pid = unsafe { fake_libc::getpid() }; pid as i32; + aaa(); + let x = aaa(); + aaa(); + // Will not lint currently. + bbb() as u32; + let x = bbb(); + bbb() as u32; let i8_ptr: *const i8 = &1; let u8_ptr: *const u8 = &1; diff --git a/tests/ui/unnecessary_cast.rs b/tests/ui/unnecessary_cast.rs index c7723ef51f990..25b6b0f9b07ba 100644 --- a/tests/ui/unnecessary_cast.rs +++ b/tests/ui/unnecessary_cast.rs @@ -38,6 +38,16 @@ mod fake_libc { } } +fn aaa() -> ::std::primitive::u32 { + 0 +} + +use std::primitive::u32 as UnsignedThirtyTwoBitInteger; + +fn bbb() -> UnsignedThirtyTwoBitInteger { + 0 +} + #[rustfmt::skip] fn main() { // Test cast_unnecessary @@ -105,6 +115,13 @@ fn main() { extern_fake_libc::getpid_SAFE_TRUTH() as i32; let pid = unsafe { fake_libc::getpid() }; pid as i32; + aaa() as u32; + let x = aaa(); + aaa() as u32; + // Will not lint currently. + bbb() as u32; + let x = bbb(); + bbb() as u32; let i8_ptr: *const i8 = &1; let u8_ptr: *const u8 = &1; diff --git a/tests/ui/unnecessary_cast.stderr b/tests/ui/unnecessary_cast.stderr index f0443556fb4d5..19411a01b67d3 100644 --- a/tests/ui/unnecessary_cast.stderr +++ b/tests/ui/unnecessary_cast.stderr @@ -7,226 +7,238 @@ LL | ptr as *const T = note: `-D clippy::unnecessary-cast` implied by `-D warnings` error: casting integer literal to `i32` is unnecessary - --> $DIR/unnecessary_cast.rs:44:5 + --> $DIR/unnecessary_cast.rs:54:5 | LL | 1i32 as i32; | ^^^^^^^^^^^ help: try: `1_i32` error: casting float literal to `f32` is unnecessary - --> $DIR/unnecessary_cast.rs:45:5 + --> $DIR/unnecessary_cast.rs:55:5 | LL | 1f32 as f32; | ^^^^^^^^^^^ help: try: `1_f32` error: casting to the same type is unnecessary (`bool` -> `bool`) - --> $DIR/unnecessary_cast.rs:46:5 + --> $DIR/unnecessary_cast.rs:56:5 | LL | false as bool; | ^^^^^^^^^^^^^ help: try: `false` error: casting integer literal to `i32` is unnecessary - --> $DIR/unnecessary_cast.rs:49:5 + --> $DIR/unnecessary_cast.rs:59:5 | LL | -1_i32 as i32; | ^^^^^^^^^^^^^ help: try: `-1_i32` error: casting integer literal to `i32` is unnecessary - --> $DIR/unnecessary_cast.rs:50:5 + --> $DIR/unnecessary_cast.rs:60:5 | LL | - 1_i32 as i32; | ^^^^^^^^^^^^^^ help: try: `- 1_i32` error: casting float literal to `f32` is unnecessary - --> $DIR/unnecessary_cast.rs:51:5 + --> $DIR/unnecessary_cast.rs:61:5 | LL | -1f32 as f32; | ^^^^^^^^^^^^ help: try: `-1_f32` error: casting integer literal to `i32` is unnecessary - --> $DIR/unnecessary_cast.rs:52:5 + --> $DIR/unnecessary_cast.rs:62:5 | LL | 1_i32 as i32; | ^^^^^^^^^^^^ help: try: `1_i32` error: casting float literal to `f32` is unnecessary - --> $DIR/unnecessary_cast.rs:53:5 + --> $DIR/unnecessary_cast.rs:63:5 | LL | 1_f32 as f32; | ^^^^^^^^^^^^ help: try: `1_f32` error: casting raw pointers to the same type and constness is unnecessary (`*const u8` -> `*const u8`) - --> $DIR/unnecessary_cast.rs:55:22 + --> $DIR/unnecessary_cast.rs:65:22 | LL | let _: *mut u8 = [1u8, 2].as_ptr() as *const u8 as *mut u8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `[1u8, 2].as_ptr()` error: casting raw pointers to the same type and constness is unnecessary (`*const u8` -> `*const u8`) - --> $DIR/unnecessary_cast.rs:57:5 + --> $DIR/unnecessary_cast.rs:67:5 | LL | [1u8, 2].as_ptr() as *const u8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `[1u8, 2].as_ptr()` error: casting raw pointers to the same type and constness is unnecessary (`*mut u8` -> `*mut u8`) - --> $DIR/unnecessary_cast.rs:59:5 + --> $DIR/unnecessary_cast.rs:69:5 | LL | [1u8, 2].as_mut_ptr() as *mut u8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `[1u8, 2].as_mut_ptr()` error: casting raw pointers to the same type and constness is unnecessary (`*const u32` -> `*const u32`) - --> $DIR/unnecessary_cast.rs:70:5 + --> $DIR/unnecessary_cast.rs:80:5 | LL | owo::([1u32].as_ptr()) as *const u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `owo::([1u32].as_ptr())` error: casting raw pointers to the same type and constness is unnecessary (`*const u8` -> `*const u8`) - --> $DIR/unnecessary_cast.rs:71:5 + --> $DIR/unnecessary_cast.rs:81:5 | LL | uwu::([1u32].as_ptr()) as *const u8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `uwu::([1u32].as_ptr())` error: casting raw pointers to the same type and constness is unnecessary (`*const u32` -> `*const u32`) - --> $DIR/unnecessary_cast.rs:73:5 + --> $DIR/unnecessary_cast.rs:83:5 | LL | uwu::([1u32].as_ptr()) as *const u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `uwu::([1u32].as_ptr())` +error: casting to the same type is unnecessary (`u32` -> `u32`) + --> $DIR/unnecessary_cast.rs:118:5 + | +LL | aaa() as u32; + | ^^^^^^^^^^^^ help: try: `aaa()` + +error: casting to the same type is unnecessary (`u32` -> `u32`) + --> $DIR/unnecessary_cast.rs:120:5 + | +LL | aaa() as u32; + | ^^^^^^^^^^^^ help: try: `aaa()` + error: casting integer literal to `f32` is unnecessary - --> $DIR/unnecessary_cast.rs:139:9 + --> $DIR/unnecessary_cast.rs:156:9 | LL | 100 as f32; | ^^^^^^^^^^ help: try: `100_f32` error: casting integer literal to `f64` is unnecessary - --> $DIR/unnecessary_cast.rs:140:9 + --> $DIR/unnecessary_cast.rs:157:9 | LL | 100 as f64; | ^^^^^^^^^^ help: try: `100_f64` error: casting integer literal to `f64` is unnecessary - --> $DIR/unnecessary_cast.rs:141:9 + --> $DIR/unnecessary_cast.rs:158:9 | LL | 100_i32 as f64; | ^^^^^^^^^^^^^^ help: try: `100_f64` error: casting integer literal to `f32` is unnecessary - --> $DIR/unnecessary_cast.rs:142:17 + --> $DIR/unnecessary_cast.rs:159:17 | LL | let _ = -100 as f32; | ^^^^^^^^^^^ help: try: `-100_f32` error: casting integer literal to `f64` is unnecessary - --> $DIR/unnecessary_cast.rs:143:17 + --> $DIR/unnecessary_cast.rs:160:17 | LL | let _ = -100 as f64; | ^^^^^^^^^^^ help: try: `-100_f64` error: casting integer literal to `f64` is unnecessary - --> $DIR/unnecessary_cast.rs:144:17 + --> $DIR/unnecessary_cast.rs:161:17 | LL | let _ = -100_i32 as f64; | ^^^^^^^^^^^^^^^ help: try: `-100_f64` error: casting float literal to `f32` is unnecessary - --> $DIR/unnecessary_cast.rs:145:9 + --> $DIR/unnecessary_cast.rs:162:9 | LL | 100. as f32; | ^^^^^^^^^^^ help: try: `100_f32` error: casting float literal to `f64` is unnecessary - --> $DIR/unnecessary_cast.rs:146:9 + --> $DIR/unnecessary_cast.rs:163:9 | LL | 100. as f64; | ^^^^^^^^^^^ help: try: `100_f64` error: casting integer literal to `u32` is unnecessary - --> $DIR/unnecessary_cast.rs:158:9 + --> $DIR/unnecessary_cast.rs:175:9 | LL | 1 as u32; | ^^^^^^^^ help: try: `1_u32` error: casting integer literal to `i32` is unnecessary - --> $DIR/unnecessary_cast.rs:159:9 + --> $DIR/unnecessary_cast.rs:176:9 | LL | 0x10 as i32; | ^^^^^^^^^^^ help: try: `0x10_i32` error: casting integer literal to `usize` is unnecessary - --> $DIR/unnecessary_cast.rs:160:9 + --> $DIR/unnecessary_cast.rs:177:9 | LL | 0b10 as usize; | ^^^^^^^^^^^^^ help: try: `0b10_usize` error: casting integer literal to `u16` is unnecessary - --> $DIR/unnecessary_cast.rs:161:9 + --> $DIR/unnecessary_cast.rs:178:9 | LL | 0o73 as u16; | ^^^^^^^^^^^ help: try: `0o73_u16` error: casting integer literal to `u32` is unnecessary - --> $DIR/unnecessary_cast.rs:162:9 + --> $DIR/unnecessary_cast.rs:179:9 | LL | 1_000_000_000 as u32; | ^^^^^^^^^^^^^^^^^^^^ help: try: `1_000_000_000_u32` error: casting float literal to `f64` is unnecessary - --> $DIR/unnecessary_cast.rs:164:9 + --> $DIR/unnecessary_cast.rs:181:9 | LL | 1.0 as f64; | ^^^^^^^^^^ help: try: `1.0_f64` error: casting float literal to `f32` is unnecessary - --> $DIR/unnecessary_cast.rs:165:9 + --> $DIR/unnecessary_cast.rs:182:9 | LL | 0.5 as f32; | ^^^^^^^^^^ help: try: `0.5_f32` error: casting integer literal to `i32` is unnecessary - --> $DIR/unnecessary_cast.rs:169:17 + --> $DIR/unnecessary_cast.rs:186:17 | LL | let _ = -1 as i32; | ^^^^^^^^^ help: try: `-1_i32` error: casting float literal to `f32` is unnecessary - --> $DIR/unnecessary_cast.rs:170:17 + --> $DIR/unnecessary_cast.rs:187:17 | LL | let _ = -1.0 as f32; | ^^^^^^^^^^^ help: try: `-1.0_f32` error: casting to the same type is unnecessary (`i32` -> `i32`) - --> $DIR/unnecessary_cast.rs:176:18 + --> $DIR/unnecessary_cast.rs:193:18 | LL | let _ = &(x as i32); | ^^^^^^^^^^ help: try: `{ x }` error: casting integer literal to `i32` is unnecessary - --> $DIR/unnecessary_cast.rs:182:22 + --> $DIR/unnecessary_cast.rs:199:22 | LL | let _: i32 = -(1) as i32; | ^^^^^^^^^^^ help: try: `-1_i32` error: casting integer literal to `i64` is unnecessary - --> $DIR/unnecessary_cast.rs:184:22 + --> $DIR/unnecessary_cast.rs:201:22 | LL | let _: i64 = -(1) as i64; | ^^^^^^^^^^^ help: try: `-1_i64` error: casting float literal to `f64` is unnecessary - --> $DIR/unnecessary_cast.rs:191:22 + --> $DIR/unnecessary_cast.rs:208:22 | LL | let _: f64 = (-8.0 as f64).exp(); | ^^^^^^^^^^^^^ help: try: `(-8.0_f64)` error: casting float literal to `f64` is unnecessary - --> $DIR/unnecessary_cast.rs:193:23 + --> $DIR/unnecessary_cast.rs:210:23 | LL | let _: f64 = -(8.0 as f64).exp(); // should suggest `-8.0_f64.exp()` here not to change code behavior | ^^^^^^^^^^^^ help: try: `8.0_f64` error: casting to the same type is unnecessary (`f32` -> `f32`) - --> $DIR/unnecessary_cast.rs:201:20 + --> $DIR/unnecessary_cast.rs:218:20 | LL | let _num = foo() as f32; | ^^^^^^^^^^^^ help: try: `foo()` -error: aborting due to 38 previous errors +error: aborting due to 40 previous errors diff --git a/tests/ui/unnecessary_filter_map.rs b/tests/ui/unnecessary_filter_map.rs index 8e01c2674f167..3c8c6ec94c1b6 100644 --- a/tests/ui/unnecessary_filter_map.rs +++ b/tests/ui/unnecessary_filter_map.rs @@ -148,3 +148,9 @@ mod comment_1052978898 { }) } } + +fn issue11260() { + // #11260 is about unnecessary_find_map, but the fix also kind of applies to + // unnecessary_filter_map + let _x = std::iter::once(1).filter_map(|n| (n > 1).then_some(n)); +} diff --git a/tests/ui/unnecessary_filter_map.stderr b/tests/ui/unnecessary_filter_map.stderr index 5585b10ab903d..2d5403ce39444 100644 --- a/tests/ui/unnecessary_filter_map.stderr +++ b/tests/ui/unnecessary_filter_map.stderr @@ -34,5 +34,11 @@ error: this `.filter_map` can be written more simply using `.map` LL | let _ = (0..4).filter_map(|x| Some(x + 1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 4 previous errors +error: this `.filter_map` can be written more simply using `.filter` + --> $DIR/unnecessary_filter_map.rs:155:14 + | +LL | let _x = std::iter::once(1).filter_map(|n| (n > 1).then_some(n)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors diff --git a/tests/ui/unnecessary_find_map.rs b/tests/ui/unnecessary_find_map.rs index a52390861b403..2c228fbbc9597 100644 --- a/tests/ui/unnecessary_find_map.rs +++ b/tests/ui/unnecessary_find_map.rs @@ -21,3 +21,9 @@ fn main() { fn find_map_none_changes_item_type() -> Option { "".chars().find_map(|_| None) } + +fn issue11260() { + let y = Some(1); + let _x = std::iter::once(1).find_map(|n| (n > 1).then_some(n)); + let _x = std::iter::once(1).find_map(|n| (n > 1).then_some(y)); // different option, so can't be just `.find()` +} diff --git a/tests/ui/unnecessary_find_map.stderr b/tests/ui/unnecessary_find_map.stderr index fb33c122fe337..3a995b41b179e 100644 --- a/tests/ui/unnecessary_find_map.stderr +++ b/tests/ui/unnecessary_find_map.stderr @@ -34,5 +34,11 @@ error: this `.find_map` can be written more simply using `.map(..).next()` LL | let _ = (0..4).find_map(|x| Some(x + 1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 4 previous errors +error: this `.find_map` can be written more simply using `.find` + --> $DIR/unnecessary_find_map.rs:27:14 + | +LL | let _x = std::iter::once(1).find_map(|n| (n > 1).then_some(n)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors diff --git a/tests/ui/unnecessary_literal_unwrap.fixed b/tests/ui/unnecessary_literal_unwrap.fixed index 276cd800b8907..72d52c62355c8 100644 --- a/tests/ui/unnecessary_literal_unwrap.fixed +++ b/tests/ui/unnecessary_literal_unwrap.fixed @@ -16,12 +16,23 @@ fn unwrap_option_some() { 1; } +#[rustfmt::skip] // force rustfmt not to remove braces in `|| { 234 }` fn unwrap_option_none() { let _val = panic!(); let _val = panic!("this always happens"); + let _val: String = String::default(); + let _val: u16 = 234; + let _val: u16 = 234; + let _val: u16 = { 234 }; + let _val: u16 = { 234 }; panic!(); panic!("this always happens"); + String::default(); + 234; + 234; + { 234 }; + { 234 }; } fn unwrap_result_ok() { diff --git a/tests/ui/unnecessary_literal_unwrap.rs b/tests/ui/unnecessary_literal_unwrap.rs index 3065778d77901..7d713ea205f3c 100644 --- a/tests/ui/unnecessary_literal_unwrap.rs +++ b/tests/ui/unnecessary_literal_unwrap.rs @@ -16,12 +16,23 @@ fn unwrap_option_some() { Some(1).expect("this never happens"); } +#[rustfmt::skip] // force rustfmt not to remove braces in `|| { 234 }` fn unwrap_option_none() { let _val = None::<()>.unwrap(); let _val = None::<()>.expect("this always happens"); + let _val: String = None.unwrap_or_default(); + let _val: u16 = None.unwrap_or(234); + let _val: u16 = None.unwrap_or_else(|| 234); + let _val: u16 = None.unwrap_or_else(|| { 234 }); + let _val: u16 = None.unwrap_or_else(|| -> u16 { 234 }); None::<()>.unwrap(); None::<()>.expect("this always happens"); + None::.unwrap_or_default(); + None::.unwrap_or(234); + None::.unwrap_or_else(|| 234); + None::.unwrap_or_else(|| { 234 }); + None::.unwrap_or_else(|| -> u16 { 234 }); } fn unwrap_result_ok() { diff --git a/tests/ui/unnecessary_literal_unwrap.stderr b/tests/ui/unnecessary_literal_unwrap.stderr index 5823313b736c9..7f603d6ef5826 100644 --- a/tests/ui/unnecessary_literal_unwrap.stderr +++ b/tests/ui/unnecessary_literal_unwrap.stderr @@ -48,13 +48,13 @@ LL + 1; | error: used `unwrap()` on `None` value - --> $DIR/unnecessary_literal_unwrap.rs:20:16 + --> $DIR/unnecessary_literal_unwrap.rs:21:16 | LL | let _val = None::<()>.unwrap(); | ^^^^^^^^^^^^^^^^^^^ help: remove the `None` and `unwrap()`: `panic!()` error: used `expect()` on `None` value - --> $DIR/unnecessary_literal_unwrap.rs:21:16 + --> $DIR/unnecessary_literal_unwrap.rs:22:16 | LL | let _val = None::<()>.expect("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -64,14 +64,68 @@ help: remove the `None` and `expect()` LL | let _val = panic!("this always happens"); | ~~~~~~~ ~ +error: used `unwrap_or_default()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:23:24 + | +LL | let _val: String = None.unwrap_or_default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the `None` and `unwrap_or_default()`: `String::default()` + +error: used `unwrap_or()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:24:21 + | +LL | let _val: u16 = None.unwrap_or(234); + | ^^^^^^^^^^^^^^^^^^^ + | +help: remove the `None` and `unwrap_or()` + | +LL - let _val: u16 = None.unwrap_or(234); +LL + let _val: u16 = 234; + | + +error: used `unwrap_or_else()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:25:21 + | +LL | let _val: u16 = None.unwrap_or_else(|| 234); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the `None` and `unwrap_or_else()` + | +LL - let _val: u16 = None.unwrap_or_else(|| 234); +LL + let _val: u16 = 234; + | + +error: used `unwrap_or_else()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:26:21 + | +LL | let _val: u16 = None.unwrap_or_else(|| { 234 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the `None` and `unwrap_or_else()` + | +LL - let _val: u16 = None.unwrap_or_else(|| { 234 }); +LL + let _val: u16 = { 234 }; + | + +error: used `unwrap_or_else()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:27:21 + | +LL | let _val: u16 = None.unwrap_or_else(|| -> u16 { 234 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the `None` and `unwrap_or_else()` + | +LL - let _val: u16 = None.unwrap_or_else(|| -> u16 { 234 }); +LL + let _val: u16 = { 234 }; + | + error: used `unwrap()` on `None` value - --> $DIR/unnecessary_literal_unwrap.rs:23:5 + --> $DIR/unnecessary_literal_unwrap.rs:29:5 | LL | None::<()>.unwrap(); | ^^^^^^^^^^^^^^^^^^^ help: remove the `None` and `unwrap()`: `panic!()` error: used `expect()` on `None` value - --> $DIR/unnecessary_literal_unwrap.rs:24:5 + --> $DIR/unnecessary_literal_unwrap.rs:30:5 | LL | None::<()>.expect("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -81,8 +135,62 @@ help: remove the `None` and `expect()` LL | panic!("this always happens"); | ~~~~~~~ ~ +error: used `unwrap_or_default()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:31:5 + | +LL | None::.unwrap_or_default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the `None` and `unwrap_or_default()`: `String::default()` + +error: used `unwrap_or()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:32:5 + | +LL | None::.unwrap_or(234); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the `None` and `unwrap_or()` + | +LL - None::.unwrap_or(234); +LL + 234; + | + +error: used `unwrap_or_else()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:33:5 + | +LL | None::.unwrap_or_else(|| 234); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the `None` and `unwrap_or_else()` + | +LL - None::.unwrap_or_else(|| 234); +LL + 234; + | + +error: used `unwrap_or_else()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:34:5 + | +LL | None::.unwrap_or_else(|| { 234 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the `None` and `unwrap_or_else()` + | +LL - None::.unwrap_or_else(|| { 234 }); +LL + { 234 }; + | + +error: used `unwrap_or_else()` on `None` value + --> $DIR/unnecessary_literal_unwrap.rs:35:5 + | +LL | None::.unwrap_or_else(|| -> u16 { 234 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the `None` and `unwrap_or_else()` + | +LL - None::.unwrap_or_else(|| -> u16 { 234 }); +LL + { 234 }; + | + error: used `unwrap()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:28:16 + --> $DIR/unnecessary_literal_unwrap.rs:39:16 | LL | let _val = Ok::<_, ()>(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -94,7 +202,7 @@ LL + let _val = 1; | error: used `expect()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:29:16 + --> $DIR/unnecessary_literal_unwrap.rs:40:16 | LL | let _val = Ok::<_, ()>(1).expect("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -106,7 +214,7 @@ LL + let _val = 1; | error: used `unwrap_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:30:16 + --> $DIR/unnecessary_literal_unwrap.rs:41:16 | LL | let _val = Ok::<_, ()>(1).unwrap_err(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -117,7 +225,7 @@ LL | let _val = panic!("{:?}", 1); | ~~~~~~~~~~~~~~ ~ error: used `expect_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:31:16 + --> $DIR/unnecessary_literal_unwrap.rs:42:16 | LL | let _val = Ok::<_, ()>(1).expect_err("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -128,7 +236,7 @@ LL | let _val = panic!("{1}: {:?}", 1, "this always happens"); | ~~~~~~~~~~~~~~~~~~~ ~ error: used `unwrap()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:33:5 + --> $DIR/unnecessary_literal_unwrap.rs:44:5 | LL | Ok::<_, ()>(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -140,7 +248,7 @@ LL + 1; | error: used `expect()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:34:5 + --> $DIR/unnecessary_literal_unwrap.rs:45:5 | LL | Ok::<_, ()>(1).expect("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -152,7 +260,7 @@ LL + 1; | error: used `unwrap_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:35:5 + --> $DIR/unnecessary_literal_unwrap.rs:46:5 | LL | Ok::<_, ()>(1).unwrap_err(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -163,7 +271,7 @@ LL | panic!("{:?}", 1); | ~~~~~~~~~~~~~~ ~ error: used `expect_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:36:5 + --> $DIR/unnecessary_literal_unwrap.rs:47:5 | LL | Ok::<_, ()>(1).expect_err("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -174,7 +282,7 @@ LL | panic!("{1}: {:?}", 1, "this always happens"); | ~~~~~~~~~~~~~~~~~~~ ~ error: used `unwrap_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap.rs:40:16 + --> $DIR/unnecessary_literal_unwrap.rs:51:16 | LL | let _val = Err::<(), _>(1).unwrap_err(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -186,7 +294,7 @@ LL + let _val = 1; | error: used `expect_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap.rs:41:16 + --> $DIR/unnecessary_literal_unwrap.rs:52:16 | LL | let _val = Err::<(), _>(1).expect_err("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -198,7 +306,7 @@ LL + let _val = 1; | error: used `unwrap()` on `Err` value - --> $DIR/unnecessary_literal_unwrap.rs:42:16 + --> $DIR/unnecessary_literal_unwrap.rs:53:16 | LL | let _val = Err::<(), _>(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -209,7 +317,7 @@ LL | let _val = panic!("{:?}", 1); | ~~~~~~~~~~~~~~ ~ error: used `expect()` on `Err` value - --> $DIR/unnecessary_literal_unwrap.rs:43:16 + --> $DIR/unnecessary_literal_unwrap.rs:54:16 | LL | let _val = Err::<(), _>(1).expect("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -220,7 +328,7 @@ LL | let _val = panic!("{1}: {:?}", 1, "this always happens"); | ~~~~~~~~~~~~~~~~~~~ ~ error: used `unwrap_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap.rs:45:5 + --> $DIR/unnecessary_literal_unwrap.rs:56:5 | LL | Err::<(), _>(1).unwrap_err(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -232,7 +340,7 @@ LL + 1; | error: used `expect_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap.rs:46:5 + --> $DIR/unnecessary_literal_unwrap.rs:57:5 | LL | Err::<(), _>(1).expect_err("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -244,7 +352,7 @@ LL + 1; | error: used `unwrap()` on `Err` value - --> $DIR/unnecessary_literal_unwrap.rs:47:5 + --> $DIR/unnecessary_literal_unwrap.rs:58:5 | LL | Err::<(), _>(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -255,7 +363,7 @@ LL | panic!("{:?}", 1); | ~~~~~~~~~~~~~~ ~ error: used `expect()` on `Err` value - --> $DIR/unnecessary_literal_unwrap.rs:48:5 + --> $DIR/unnecessary_literal_unwrap.rs:59:5 | LL | Err::<(), _>(1).expect("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -266,7 +374,7 @@ LL | panic!("{1}: {:?}", 1, "this always happens"); | ~~~~~~~~~~~~~~~~~~~ ~ error: used `unwrap_or()` on `Some` value - --> $DIR/unnecessary_literal_unwrap.rs:52:16 + --> $DIR/unnecessary_literal_unwrap.rs:63:16 | LL | let _val = Some(1).unwrap_or(2); | ^^^^^^^^^^^^^^^^^^^^ @@ -278,7 +386,7 @@ LL + let _val = 1; | error: used `unwrap_or_default()` on `Some` value - --> $DIR/unnecessary_literal_unwrap.rs:53:16 + --> $DIR/unnecessary_literal_unwrap.rs:64:16 | LL | let _val = Some(1).unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -290,7 +398,7 @@ LL + let _val = 1; | error: used `unwrap_or_else()` on `Some` value - --> $DIR/unnecessary_literal_unwrap.rs:54:16 + --> $DIR/unnecessary_literal_unwrap.rs:65:16 | LL | let _val = Some(1).unwrap_or_else(|| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -302,7 +410,7 @@ LL + let _val = 1; | error: used `unwrap_or()` on `Some` value - --> $DIR/unnecessary_literal_unwrap.rs:56:5 + --> $DIR/unnecessary_literal_unwrap.rs:67:5 | LL | Some(1).unwrap_or(2); | ^^^^^^^^^^^^^^^^^^^^ @@ -314,7 +422,7 @@ LL + 1; | error: used `unwrap_or_default()` on `Some` value - --> $DIR/unnecessary_literal_unwrap.rs:57:5 + --> $DIR/unnecessary_literal_unwrap.rs:68:5 | LL | Some(1).unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -326,7 +434,7 @@ LL + 1; | error: used `unwrap_or_else()` on `Some` value - --> $DIR/unnecessary_literal_unwrap.rs:58:5 + --> $DIR/unnecessary_literal_unwrap.rs:69:5 | LL | Some(1).unwrap_or_else(|| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -338,7 +446,7 @@ LL + 1; | error: used `unwrap_or()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:62:16 + --> $DIR/unnecessary_literal_unwrap.rs:73:16 | LL | let _val = Ok::<_, ()>(1).unwrap_or(2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -350,7 +458,7 @@ LL + let _val = 1; | error: used `unwrap_or_default()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:63:16 + --> $DIR/unnecessary_literal_unwrap.rs:74:16 | LL | let _val = Ok::<_, ()>(1).unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -362,7 +470,7 @@ LL + let _val = 1; | error: used `unwrap_or_else()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:64:16 + --> $DIR/unnecessary_literal_unwrap.rs:75:16 | LL | let _val = Ok::<_, ()>(1).unwrap_or_else(|_| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -374,7 +482,7 @@ LL + let _val = 1; | error: used `unwrap_or()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:66:5 + --> $DIR/unnecessary_literal_unwrap.rs:77:5 | LL | Ok::<_, ()>(1).unwrap_or(2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -386,7 +494,7 @@ LL + 1; | error: used `unwrap_or_default()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:67:5 + --> $DIR/unnecessary_literal_unwrap.rs:78:5 | LL | Ok::<_, ()>(1).unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -398,7 +506,7 @@ LL + 1; | error: used `unwrap_or_else()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:68:5 + --> $DIR/unnecessary_literal_unwrap.rs:79:5 | LL | Ok::<_, ()>(1).unwrap_or_else(|_| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -410,7 +518,7 @@ LL + 1; | error: used `unwrap_unchecked()` on `Some` value - --> $DIR/unnecessary_literal_unwrap.rs:82:22 + --> $DIR/unnecessary_literal_unwrap.rs:93:22 | LL | let _ = unsafe { Some(1).unwrap_unchecked() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -422,7 +530,7 @@ LL + let _ = 1; | error: used `unwrap_unchecked()` on `Some` value - --> $DIR/unnecessary_literal_unwrap.rs:83:22 + --> $DIR/unnecessary_literal_unwrap.rs:94:22 | LL | let _ = unsafe { Some(1).unwrap_unchecked() + *(&1 as *const i32) }; // needs to keep the unsafe block | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -434,7 +542,7 @@ LL + let _ = unsafe { 1 + *(&1 as *const i32) }; // needs to keep the unsafe | error: used `unwrap_unchecked()` on `Some` value - --> $DIR/unnecessary_literal_unwrap.rs:84:22 + --> $DIR/unnecessary_literal_unwrap.rs:95:22 | LL | let _ = unsafe { Some(1).unwrap_unchecked() } + 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -446,7 +554,7 @@ LL + let _ = 1 + 1; | error: used `unwrap_unchecked()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:85:22 + --> $DIR/unnecessary_literal_unwrap.rs:96:22 | LL | let _ = unsafe { Ok::<_, ()>(1).unwrap_unchecked() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -458,7 +566,7 @@ LL + let _ = 1; | error: used `unwrap_unchecked()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:86:22 + --> $DIR/unnecessary_literal_unwrap.rs:97:22 | LL | let _ = unsafe { Ok::<_, ()>(1).unwrap_unchecked() + *(&1 as *const i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -470,7 +578,7 @@ LL + let _ = unsafe { 1 + *(&1 as *const i32) }; | error: used `unwrap_unchecked()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap.rs:87:22 + --> $DIR/unnecessary_literal_unwrap.rs:98:22 | LL | let _ = unsafe { Ok::<_, ()>(1).unwrap_unchecked() } + 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -482,7 +590,7 @@ LL + let _ = 1 + 1; | error: used `unwrap_err_unchecked()` on `Err` value - --> $DIR/unnecessary_literal_unwrap.rs:88:22 + --> $DIR/unnecessary_literal_unwrap.rs:99:22 | LL | let _ = unsafe { Err::<(), i32>(123).unwrap_err_unchecked() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -493,5 +601,5 @@ LL - let _ = unsafe { Err::<(), i32>(123).unwrap_err_unchecked() }; LL + let _ = 123; | -error: aborting due to 43 previous errors +error: aborting due to 53 previous errors diff --git a/tests/ui/unnecessary_literal_unwrap_unfixable.rs b/tests/ui/unnecessary_literal_unwrap_unfixable.rs index 711fdce39624e..41300aceb4153 100644 --- a/tests/ui/unnecessary_literal_unwrap_unfixable.rs +++ b/tests/ui/unnecessary_literal_unwrap_unfixable.rs @@ -21,6 +21,8 @@ fn unwrap_option_none() { let val = None::<()>; let _val2 = val.unwrap(); let _val2 = val.expect("this always happens"); + let _val3: u8 = None.unwrap_or_default(); + None::<()>.unwrap_or_default(); } fn unwrap_result_ok() { diff --git a/tests/ui/unnecessary_literal_unwrap_unfixable.stderr b/tests/ui/unnecessary_literal_unwrap_unfixable.stderr index feb9325b77a67..2d1270d47174f 100644 --- a/tests/ui/unnecessary_literal_unwrap_unfixable.stderr +++ b/tests/ui/unnecessary_literal_unwrap_unfixable.stderr @@ -95,509 +95,521 @@ help: remove the `None` and `expect()` LL | let val = None::<()>; | ^^^^^^^^^^ +error: used `unwrap_or_default()` on `None` value + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:24:21 + | +LL | let _val3: u8 = None.unwrap_or_default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the `None` and `unwrap_or_default()`: `Default::default()` + +error: used `unwrap_or_default()` on `None` value + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:25:5 + | +LL | None::<()>.unwrap_or_default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the `None` and `unwrap_or_default()`: `Default::default()` + error: used `unwrap()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:28:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:30:17 | LL | let _val2 = val.unwrap(); | ^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:27:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:29:15 | LL | let val = Ok::<_, ()>(1); | ^^^^^^^^^^^^^^ error: used `expect()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:29:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:31:17 | LL | let _val2 = val.expect("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `expect()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:27:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:29:15 | LL | let val = Ok::<_, ()>(1); | ^^^^^^^^^^^^^^ error: used `unwrap_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:30:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:32:17 | LL | let _val2 = val.unwrap_err(); | ^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:27:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:29:15 | LL | let val = Ok::<_, ()>(1); | ^^^^^^^^^^^^^^ error: used `expect_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:31:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:33:17 | LL | let _val2 = val.expect_err("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `expect_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:27:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:29:15 | LL | let val = Ok::<_, ()>(1); | ^^^^^^^^^^^^^^ error: used `unwrap()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:35:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:37:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:35:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:37:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `expect()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:36:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:38:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).expect("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `expect()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:36:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:38:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).expect("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:37:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:39:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap_err(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:37:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:39:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap_err(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `expect_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:38:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:40:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).expect_err("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `expect_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:38:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:40:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).expect_err("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:41:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:43:17 | LL | let _val2 = val.unwrap(); | ^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:40:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:42:15 | LL | let val = Ok::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `expect()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:42:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:44:17 | LL | let _val2 = val.expect("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `expect()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:40:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:42:15 | LL | let val = Ok::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:43:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:45:17 | LL | let _val2 = val.unwrap_err(); | ^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:40:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:42:15 | LL | let val = Ok::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `expect_err()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:44:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:46:17 | LL | let _val2 = val.expect_err("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `expect_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:40:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:42:15 | LL | let val = Ok::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:49:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:51:17 | LL | let _val2 = val.unwrap_err(); | ^^^^^^^^^^^^^^^^ | help: remove the `Err` and `unwrap_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:48:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:50:15 | LL | let val = Err::<(), _>(1); | ^^^^^^^^^^^^^^^ error: used `expect_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:50:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:52:17 | LL | let _val2 = val.expect_err("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Err` and `expect_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:48:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:50:15 | LL | let val = Err::<(), _>(1); | ^^^^^^^^^^^^^^^ error: used `unwrap()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:51:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:53:17 | LL | let _val2 = val.unwrap(); | ^^^^^^^^^^^^ | help: remove the `Err` and `unwrap()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:48:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:50:15 | LL | let val = Err::<(), _>(1); | ^^^^^^^^^^^^^^^ error: used `expect()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:52:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:54:17 | LL | let _val2 = val.expect("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Err` and `expect()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:48:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:50:15 | LL | let val = Err::<(), _>(1); | ^^^^^^^^^^^^^^^ error: used `unwrap_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:56:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:58:16 | LL | let _val = Err::<(), usize>([1, 2, 3].iter().sum()).unwrap_err(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Err` and `unwrap_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:56:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:58:16 | LL | let _val = Err::<(), usize>([1, 2, 3].iter().sum()).unwrap_err(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `expect_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:57:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:59:16 | LL | let _val = Err::<(), usize>([1, 2, 3].iter().sum()).expect_err("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Err` and `expect_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:57:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:59:16 | LL | let _val = Err::<(), usize>([1, 2, 3].iter().sum()).expect_err("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:58:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:60:16 | LL | let _val = Err::<(), usize>([1, 2, 3].iter().sum()).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Err` and `unwrap()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:58:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:60:16 | LL | let _val = Err::<(), usize>([1, 2, 3].iter().sum()).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `expect()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:59:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:61:16 | LL | let _val = Err::<(), usize>([1, 2, 3].iter().sum()).expect("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Err` and `expect()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:59:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:61:16 | LL | let _val = Err::<(), usize>([1, 2, 3].iter().sum()).expect("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:62:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:64:17 | LL | let _val2 = val.unwrap_err(); | ^^^^^^^^^^^^^^^^ | help: remove the `Err` and `unwrap_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:61:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:63:15 | LL | let val = Err::<(), usize>([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `expect_err()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:63:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:65:17 | LL | let _val2 = val.expect_err("this never happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Err` and `expect_err()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:61:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:63:15 | LL | let val = Err::<(), usize>([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:64:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:66:17 | LL | let _val2 = val.unwrap(); | ^^^^^^^^^^^^ | help: remove the `Err` and `unwrap()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:61:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:63:15 | LL | let val = Err::<(), usize>([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `expect()` on `Err` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:65:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:67:17 | LL | let _val2 = val.expect("this always happens"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Err` and `expect()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:61:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:63:15 | LL | let val = Err::<(), usize>([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or()` on `Some` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:70:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:72:17 | LL | let _val2 = val.unwrap_or(2); | ^^^^^^^^^^^^^^^^ | help: remove the `Some` and `unwrap_or()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:69:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:71:15 | LL | let val = Some(1); | ^^^^^^^ error: used `unwrap_or_default()` on `Some` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:71:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:73:17 | LL | let _val2 = val.unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Some` and `unwrap_or_default()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:69:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:71:15 | LL | let val = Some(1); | ^^^^^^^ error: used `unwrap_or_else()` on `Some` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:72:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:74:17 | LL | let _val2 = val.unwrap_or_else(|| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Some` and `unwrap_or_else()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:69:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:71:15 | LL | let val = Some(1); | ^^^^^^^ error: used `unwrap_or()` on `Some` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:76:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:78:16 | LL | let _val = Some::([1, 2, 3].iter().sum()).unwrap_or(2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Some` and `unwrap_or()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:76:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:78:16 | LL | let _val = Some::([1, 2, 3].iter().sum()).unwrap_or(2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or_default()` on `Some` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:77:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:79:16 | LL | let _val = Some::([1, 2, 3].iter().sum()).unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Some` and `unwrap_or_default()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:77:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:79:16 | LL | let _val = Some::([1, 2, 3].iter().sum()).unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or_else()` on `Some` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:78:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:80:16 | LL | let _val = Some::([1, 2, 3].iter().sum()).unwrap_or_else(|| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Some` and `unwrap_or_else()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:78:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:80:16 | LL | let _val = Some::([1, 2, 3].iter().sum()).unwrap_or_else(|| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or()` on `Some` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:81:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:83:17 | LL | let _val2 = val.unwrap_or(2); | ^^^^^^^^^^^^^^^^ | help: remove the `Some` and `unwrap_or()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:80:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:82:15 | LL | let val = Some::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or_default()` on `Some` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:82:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:84:17 | LL | let _val2 = val.unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Some` and `unwrap_or_default()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:80:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:82:15 | LL | let val = Some::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or_else()` on `Some` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:83:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:85:17 | LL | let _val2 = val.unwrap_or_else(|| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Some` and `unwrap_or_else()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:80:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:82:15 | LL | let val = Some::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:88:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:90:17 | LL | let _val2 = val.unwrap_or(2); | ^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_or()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:87:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:89:15 | LL | let val = Ok::<_, ()>(1); | ^^^^^^^^^^^^^^ error: used `unwrap_or_default()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:89:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:91:17 | LL | let _val2 = val.unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_or_default()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:87:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:89:15 | LL | let val = Ok::<_, ()>(1); | ^^^^^^^^^^^^^^ error: used `unwrap_or_else()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:90:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:92:17 | LL | let _val2 = val.unwrap_or_else(|_| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_or_else()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:87:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:89:15 | LL | let val = Ok::<_, ()>(1); | ^^^^^^^^^^^^^^ error: used `unwrap_or()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:94:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:96:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap_or(2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_or()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:94:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:96:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap_or(2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or_default()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:95:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:97:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_or_default()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:95:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:97:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or_else()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:96:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:98:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap_or_else(|_| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_or_else()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:96:16 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:98:16 | LL | let _val = Ok::([1, 2, 3].iter().sum()).unwrap_or_else(|_| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:99:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:101:17 | LL | let _val2 = val.unwrap_or(2); | ^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_or()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:98:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:100:15 | LL | let val = Ok::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or_default()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:100:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:102:17 | LL | let _val2 = val.unwrap_or_default(); | ^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_or_default()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:98:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:100:15 | LL | let val = Ok::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: used `unwrap_or_else()` on `Ok` value - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:101:17 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:103:17 | LL | let _val2 = val.unwrap_or_else(|_| 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the `Ok` and `unwrap_or_else()` - --> $DIR/unnecessary_literal_unwrap_unfixable.rs:98:15 + --> $DIR/unnecessary_literal_unwrap_unfixable.rs:100:15 | LL | let val = Ok::([1, 2, 3].iter().sum()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 50 previous errors +error: aborting due to 52 previous errors diff --git a/tests/ui/unused_async.rs b/tests/ui/unused_async.rs index 69e46ab473622..1d188025e418d 100644 --- a/tests/ui/unused_async.rs +++ b/tests/ui/unused_async.rs @@ -37,6 +37,23 @@ mod issue10459 { } } +mod issue9695 { + use std::future::Future; + + async fn f() {} + async fn f2() {} + async fn f3() {} + + fn needs_async_fn>(_: fn() -> F) {} + + fn test() { + let x = f; + needs_async_fn(x); // async needed in f + needs_async_fn(f2); // async needed in f2 + f3(); // async not needed in f3 + } +} + async fn foo() -> i32 { 4 } diff --git a/tests/ui/unused_async.stderr b/tests/ui/unused_async.stderr index ffae8366b88c3..8d9b72c4886c2 100644 --- a/tests/ui/unused_async.stderr +++ b/tests/ui/unused_async.stderr @@ -17,7 +17,15 @@ LL | ready(()).await; = note: `-D clippy::unused-async` implied by `-D warnings` error: unused `async` for function with no await statements - --> $DIR/unused_async.rs:40:1 + --> $DIR/unused_async.rs:45:5 + | +LL | async fn f3() {} + | ^^^^^^^^^^^^^^^^ + | + = help: consider removing the `async` from this function + +error: unused `async` for function with no await statements + --> $DIR/unused_async.rs:57:1 | LL | / async fn foo() -> i32 { LL | | 4 @@ -27,7 +35,7 @@ LL | | } = help: consider removing the `async` from this function error: unused `async` for function with no await statements - --> $DIR/unused_async.rs:51:5 + --> $DIR/unused_async.rs:68:5 | LL | / async fn unused(&self) -> i32 { LL | | 1 @@ -36,5 +44,5 @@ LL | | } | = help: consider removing the `async` from this function -error: aborting due to 3 previous errors +error: aborting due to 4 previous errors diff --git a/tests/ui/unwrap_or_else_default.fixed b/tests/ui/unwrap_or_else_default.fixed index 08b89a18bbbd4..acdb96942ba1a 100644 --- a/tests/ui/unwrap_or_else_default.fixed +++ b/tests/ui/unwrap_or_else_default.fixed @@ -1,10 +1,10 @@ //@run-rustfix -#![warn(clippy::unwrap_or_else_default)] +#![warn(clippy::unwrap_or_default)] #![allow(dead_code)] #![allow(clippy::unnecessary_wraps, clippy::unnecessary_literal_unwrap)] -/// Checks implementation of the `UNWRAP_OR_ELSE_DEFAULT` lint. +/// Checks implementation of the `UNWRAP_OR_DEFAULT` lint. fn unwrap_or_else_default() { struct Foo; @@ -74,4 +74,62 @@ fn unwrap_or_else_default() { empty_string.unwrap_or_default(); } +fn type_certainty(option: Option>) { + option.unwrap_or_default().push(1); + + let option: std::option::Option> = None; + option.unwrap_or_default().push(1); + + let option: Option> = None; + option.unwrap_or_default().push(1); + + let option = std::option::Option::>::None; + option.unwrap_or_default().push(1); + + let option = Option::>::None; + option.unwrap_or_default().push(1); + + let option = std::option::Option::None::>; + option.unwrap_or_default().push(1); + + let option = Option::None::>; + option.unwrap_or_default().push(1); + + let option = None::>; + option.unwrap_or_default().push(1); + + // should not be changed: type annotation with infer, unconcretized initializer + let option: Option> = None; + option.unwrap_or_else(Vec::new).push(1); + + // should not be changed: no type annotation, unconcretized initializer + let option = Option::None; + option.unwrap_or_else(Vec::new).push(1); + + // should not be changed: no type annotation, unconcretized initializer + let option = None; + option.unwrap_or_else(Vec::new).push(1); + + type Alias = Option>; + let option: Alias = Option::>::Some(Vec::new()); + option.unwrap_or_default().push(1); +} + +fn method_call_with_deref() { + use std::cell::RefCell; + use std::collections::HashMap; + + let cell = RefCell::new(HashMap::>::new()); + + let mut outer_map = cell.borrow_mut(); + + #[allow(unused_assignments)] + let mut option = None; + option = Some(0); + + let inner_map = outer_map.get_mut(&option.unwrap()).unwrap(); + + let _ = inner_map.entry(0).or_default(); +} + fn main() {} diff --git a/tests/ui/unwrap_or_else_default.rs b/tests/ui/unwrap_or_else_default.rs index ad2a744908fcb..55ccd00e1a2aa 100644 --- a/tests/ui/unwrap_or_else_default.rs +++ b/tests/ui/unwrap_or_else_default.rs @@ -1,10 +1,10 @@ //@run-rustfix -#![warn(clippy::unwrap_or_else_default)] +#![warn(clippy::unwrap_or_default)] #![allow(dead_code)] #![allow(clippy::unnecessary_wraps, clippy::unnecessary_literal_unwrap)] -/// Checks implementation of the `UNWRAP_OR_ELSE_DEFAULT` lint. +/// Checks implementation of the `UNWRAP_OR_DEFAULT` lint. fn unwrap_or_else_default() { struct Foo; @@ -74,4 +74,62 @@ fn unwrap_or_else_default() { empty_string.unwrap_or_else(|| "".to_string()); } +fn type_certainty(option: Option>) { + option.unwrap_or_else(Vec::new).push(1); + + let option: std::option::Option> = None; + option.unwrap_or_else(Vec::new).push(1); + + let option: Option> = None; + option.unwrap_or_else(Vec::new).push(1); + + let option = std::option::Option::>::None; + option.unwrap_or_else(Vec::new).push(1); + + let option = Option::>::None; + option.unwrap_or_else(Vec::new).push(1); + + let option = std::option::Option::None::>; + option.unwrap_or_else(Vec::new).push(1); + + let option = Option::None::>; + option.unwrap_or_else(Vec::new).push(1); + + let option = None::>; + option.unwrap_or_else(Vec::new).push(1); + + // should not be changed: type annotation with infer, unconcretized initializer + let option: Option> = None; + option.unwrap_or_else(Vec::new).push(1); + + // should not be changed: no type annotation, unconcretized initializer + let option = Option::None; + option.unwrap_or_else(Vec::new).push(1); + + // should not be changed: no type annotation, unconcretized initializer + let option = None; + option.unwrap_or_else(Vec::new).push(1); + + type Alias = Option>; + let option: Alias = Option::>::Some(Vec::new()); + option.unwrap_or_else(Vec::new).push(1); +} + +fn method_call_with_deref() { + use std::cell::RefCell; + use std::collections::HashMap; + + let cell = RefCell::new(HashMap::>::new()); + + let mut outer_map = cell.borrow_mut(); + + #[allow(unused_assignments)] + let mut option = None; + option = Some(0); + + let inner_map = outer_map.get_mut(&option.unwrap()).unwrap(); + + let _ = inner_map.entry(0).or_insert_with(Default::default); +} + fn main() {} diff --git a/tests/ui/unwrap_or_else_default.stderr b/tests/ui/unwrap_or_else_default.stderr index d2b9212223f77..af662c6def7e3 100644 --- a/tests/ui/unwrap_or_else_default.stderr +++ b/tests/ui/unwrap_or_else_default.stderr @@ -1,40 +1,100 @@ -error: use of `.unwrap_or_else(..)` to construct default value - --> $DIR/unwrap_or_else_default.rs:48:5 +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:48:14 | LL | with_new.unwrap_or_else(Vec::new); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `with_new.unwrap_or_default()` + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` | - = note: `-D clippy::unwrap-or-else-default` implied by `-D warnings` + = note: `-D clippy::unwrap-or-default` implied by `-D warnings` -error: use of `.unwrap_or_else(..)` to construct default value - --> $DIR/unwrap_or_else_default.rs:62:5 +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:62:23 | LL | with_real_default.unwrap_or_else(::default); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `with_real_default.unwrap_or_default()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` -error: use of `.unwrap_or_else(..)` to construct default value - --> $DIR/unwrap_or_else_default.rs:65:5 +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:65:24 | LL | with_default_trait.unwrap_or_else(Default::default); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `with_default_trait.unwrap_or_default()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` -error: use of `.unwrap_or_else(..)` to construct default value - --> $DIR/unwrap_or_else_default.rs:68:5 +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:68:23 | LL | with_default_type.unwrap_or_else(u64::default); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `with_default_type.unwrap_or_default()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` -error: use of `.unwrap_or_else(..)` to construct default value - --> $DIR/unwrap_or_else_default.rs:71:5 +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:71:23 | LL | with_default_type.unwrap_or_else(Vec::new); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `with_default_type.unwrap_or_default()` + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` -error: use of `.unwrap_or_else(..)` to construct default value - --> $DIR/unwrap_or_else_default.rs:74:5 +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:74:18 | LL | empty_string.unwrap_or_else(|| "".to_string()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `empty_string.unwrap_or_default()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` -error: aborting due to 6 previous errors +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:78:12 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:81:12 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:84:12 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:87:12 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:90:12 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:93:12 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:96:12 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:99:12 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or_else` to construct default value + --> $DIR/unwrap_or_else_default.rs:115:12 + | +LL | option.unwrap_or_else(Vec::new).push(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `or_insert_with` to construct default value + --> $DIR/unwrap_or_else_default.rs:132:32 + | +LL | let _ = inner_map.entry(0).or_insert_with(Default::default); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` + +error: aborting due to 16 previous errors diff --git a/triagebot.toml b/triagebot.toml index c40b71f6ca7d4..6856bb0ab3759 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -9,6 +9,9 @@ allow-unauthenticated = [ # See https://github.com/rust-lang/triagebot/wiki/Shortcuts [shortcut] +# Have rustbot inform users about the *No Merge Policy* +[no-merges] + [autolabel."S-waiting-on-review"] new_pr = true @@ -27,4 +30,6 @@ contributing_url = "https://github.com/rust-lang/rust-clippy/blob/master/CONTRIB "@Alexendoo", "@dswij", "@Jarcho", + "@blyxyas", + "@Centri3", ] From 7ef1a54ffeefc3f3e8e18afc2dbb9ed21b890d8e Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 13 Jul 2023 12:23:06 +0200 Subject: [PATCH 14/49] Rename incorrect_fn_null_checks to useless_ptr_null_checks (clippy side) --- clippy_lints/src/renamed_lints.rs | 2 +- tests/ui/rename.fixed | 4 ++-- tests/ui/rename.rs | 2 +- tests/ui/rename.stderr | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/renamed_lints.rs b/clippy_lints/src/renamed_lints.rs index 49bdc6796049a..fc1fabcc0ae59 100644 --- a/clippy_lints/src/renamed_lints.rs +++ b/clippy_lints/src/renamed_lints.rs @@ -43,7 +43,7 @@ pub static RENAMED_LINTS: &[(&str, &str)] = &[ ("clippy::for_loops_over_fallibles", "for_loops_over_fallibles"), ("clippy::forget_copy", "forgetting_copy_types"), ("clippy::forget_ref", "forgetting_references"), - ("clippy::fn_null_check", "incorrect_fn_null_checks"), + ("clippy::fn_null_check", "useless_ptr_null_checks"), ("clippy::into_iter_on_array", "array_into_iter"), ("clippy::invalid_atomic_ordering", "invalid_atomic_ordering"), ("clippy::invalid_ref", "invalid_value"), diff --git a/tests/ui/rename.fixed b/tests/ui/rename.fixed index 8ec19b120d127..8257bf2947ab0 100644 --- a/tests/ui/rename.fixed +++ b/tests/ui/rename.fixed @@ -38,7 +38,7 @@ #![allow(for_loops_over_fallibles)] #![allow(forgetting_copy_types)] #![allow(forgetting_references)] -#![allow(incorrect_fn_null_checks)] +#![allow(useless_ptr_null_checks)] #![allow(array_into_iter)] #![allow(invalid_atomic_ordering)] #![allow(invalid_value)] @@ -92,7 +92,7 @@ #![warn(for_loops_over_fallibles)] #![warn(forgetting_copy_types)] #![warn(forgetting_references)] -#![warn(incorrect_fn_null_checks)] +#![warn(useless_ptr_null_checks)] #![warn(array_into_iter)] #![warn(invalid_atomic_ordering)] #![warn(invalid_value)] diff --git a/tests/ui/rename.rs b/tests/ui/rename.rs index 51a5976eb6149..6569dad18d406 100644 --- a/tests/ui/rename.rs +++ b/tests/ui/rename.rs @@ -38,7 +38,7 @@ #![allow(for_loops_over_fallibles)] #![allow(forgetting_copy_types)] #![allow(forgetting_references)] -#![allow(incorrect_fn_null_checks)] +#![allow(useless_ptr_null_checks)] #![allow(array_into_iter)] #![allow(invalid_atomic_ordering)] #![allow(invalid_value)] diff --git a/tests/ui/rename.stderr b/tests/ui/rename.stderr index e420ea1d2adb5..57e991e5695a1 100644 --- a/tests/ui/rename.stderr +++ b/tests/ui/rename.stderr @@ -246,11 +246,11 @@ error: lint `clippy::forget_ref` has been renamed to `forgetting_references` LL | #![warn(clippy::forget_ref)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_references` -error: lint `clippy::fn_null_check` has been renamed to `incorrect_fn_null_checks` +error: lint `clippy::fn_null_check` has been renamed to `useless_ptr_null_checks` --> $DIR/rename.rs:95:9 | LL | #![warn(clippy::fn_null_check)] - | ^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `incorrect_fn_null_checks` + | ^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `useless_ptr_null_checks` error: lint `clippy::into_iter_on_array` has been renamed to `array_into_iter` --> $DIR/rename.rs:96:9 From 0ff6579eac7154e82b20ed149a3be83467b6cd10 Mon Sep 17 00:00:00 2001 From: yukang Date: Wed, 2 Aug 2023 16:17:31 +0800 Subject: [PATCH 15/49] fix RedundantLocals clippy caused by async and await --- clippy_lints/src/redundant_locals.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/redundant_locals.rs b/clippy_lints/src/redundant_locals.rs index 140ae837a1723..896bd79b20bf4 100644 --- a/clippy_lints/src/redundant_locals.rs +++ b/clippy_lints/src/redundant_locals.rs @@ -2,9 +2,11 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::is_from_proc_macro; use clippy_utils::ty::needs_ordered_drop; use rustc_hir::def::Res; -use rustc_hir::{BindingAnnotation, ByRef, Expr, ExprKind, HirId, Local, Node, Pat, PatKind, QPath}; +use rustc_hir::{ + BindingAnnotation, ByRef, Expr, ExprKind, HirId, Local, Node, Pat, PatKind, QPath, +}; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::lint::in_external_macro; +use rustc_middle::lint::{in_external_macro, is_from_async_await}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::Ident; @@ -65,6 +67,9 @@ impl<'tcx> LateLintPass<'tcx> for RedundantLocals { // the local is user-controlled if !in_external_macro(cx.sess(), local.span); if !is_from_proc_macro(cx, expr); + // Async function parameters are lowered into the closure body, so we can't lint them. + // see `lower_maybe_async_body` in `rust_ast_lowering` + if !is_from_async_await(local.span); then { span_lint_and_help( cx, @@ -93,7 +98,12 @@ fn find_binding(pat: &Pat<'_>, name: Ident) -> Option { } /// Check if a rebinding of a local affects the code's drop behavior. -fn affects_drop_behavior<'tcx>(cx: &LateContext<'tcx>, bind: HirId, rebind: HirId, rebind_expr: &Expr<'tcx>) -> bool { +fn affects_drop_behavior<'tcx>( + cx: &LateContext<'tcx>, + bind: HirId, + rebind: HirId, + rebind_expr: &Expr<'tcx>, +) -> bool { let hir = cx.tcx.hir(); // the rebinding is in a different scope than the original binding From 72074a0f002dd67aa87f2bd913e8811972dcc415 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 2 Aug 2023 13:38:59 +0800 Subject: [PATCH 16/49] Alphabetically order arms in methods/mod.rs match --- clippy_lints/src/methods/mod.rs | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index dd694ce7393e2..62d565bcc5c97 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -3881,6 +3881,13 @@ impl Methods { unnecessary_lazy_eval::check(cx, expr, recv, arg, "and"); } }, + ("any", [arg]) if let ExprKind::Closure(arg) = arg.kind + && let body = cx.tcx.hir().body(arg.body) + && let [param] = body.params + && let Some(("chars", recv, _, _, _)) = method_call(recv) => + { + string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv); + } ("arg", [arg]) => { suspicious_command_arg_space::check(cx, recv, arg, span); } @@ -3999,15 +4006,6 @@ impl Methods { unnecessary_join::check(cx, expr, recv, join_arg, span); } }, - ("skip", [arg]) => { - iter_skip_zero::check(cx, expr, arg); - - if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) { - if let ("cloned", []) = (name2, args2) { - iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); - } - } - } ("last", []) => { if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) { if let ("cloned", []) = (name2, args2) { @@ -4060,13 +4058,6 @@ impl Methods { } } }, - ("any", [arg]) if let ExprKind::Closure(arg) = arg.kind - && let body = cx.tcx.hir().body(arg.body) - && let [param] = body.params - && let Some(("chars", recv, _, _, _)) = method_call(recv) => - { - string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv); - } ("nth", [n_arg]) => match method_call(recv) { Some(("bytes", recv2, [], _, _)) => bytes_nth::check(cx, expr, recv2, n_arg), Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false), @@ -4120,6 +4111,15 @@ impl Methods { seek_to_start_instead_of_rewind::check(cx, expr, recv, arg, span); } }, + ("skip", [arg]) => { + iter_skip_zero::check(cx, expr, arg); + + if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) { + if let ("cloned", []) = (name2, args2) { + iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); + } + } + } ("sort", []) => { stable_sort_primitive::check(cx, expr, recv); }, @@ -4214,6 +4214,9 @@ impl Methods { } unnecessary_literal_unwrap::check(cx, expr, recv, name, args); }, + ("write", []) => { + readonly_write_lock::check(cx, expr, recv); + } ("zip", [arg]) => { if let ExprKind::MethodCall(name, iter_recv, [], _) = recv.kind && name.ident.name == sym::iter @@ -4221,9 +4224,6 @@ impl Methods { range_zip_with_len::check(cx, expr, iter_recv, arg); } }, - ("write", []) => { - readonly_write_lock::check(cx, expr, recv); - } _ => {}, } } From b07de24a5838d4324b43c3d161bbd35454a5eba0 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Sat, 29 Jul 2023 08:20:25 +0000 Subject: [PATCH 17/49] Remove constness from `TraitPredicate` --- clippy_lints/src/derive.rs | 4 +--- clippy_lints/src/eta_reduction.rs | 3 +-- clippy_utils/src/qualify_min_const_fn.rs | 5 +++-- clippy_utils/src/ty.rs | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index e3f2026cfe9e1..9900dbdee6be1 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -15,7 +15,7 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_middle::traits::Reveal; use rustc_middle::ty::{ - self, BoundConstness, ClauseKind, GenericArgKind, GenericParamDefKind, ImplPolarity, ParamEnv, + self, ClauseKind, GenericArgKind, GenericParamDefKind, ImplPolarity, ParamEnv, ToPredicate, TraitPredicate, Ty, TyCtxt, }; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -523,7 +523,6 @@ fn param_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> if let ClauseKind::Trait(p) = p.kind().skip_binder() && p.trait_ref.def_id == eq_trait_id && let ty::Param(self_ty) = p.trait_ref.self_ty().kind() - && p.constness == BoundConstness::NotConst { // Flag types which already have an `Eq` bound. params[self_ty.index as usize].1 = false; @@ -535,7 +534,6 @@ fn param_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> params.iter().filter(|&&(_, needs_eq)| needs_eq).map(|&(param, _)| { ClauseKind::Trait(TraitPredicate { trait_ref: ty::TraitRef::new(tcx, eq_trait_id, [tcx.mk_param_from_def(param)]), - constness: BoundConstness::NotConst, polarity: ImplPolarity::Positive, }) .to_predicate(tcx) diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 8d6fb8438b658..ba7957b0decc2 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -10,7 +10,7 @@ use rustc_hir::{BindingAnnotation, Expr, ExprKind, FnRetTy, Param, PatKind, QPat use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{ - self, Binder, BoundConstness, ClosureArgs, ClosureKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, + self, Binder, ClosureArgs, ClosureKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, GenericArgsRef, ImplPolarity, List, Region, RegionKind, Ty, TypeVisitableExt, TypeckResults, }; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -171,7 +171,6 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction { = cx.tcx.infer_ctxt().build().type_implements_fn_trait( cx.param_env, Binder::bind_with_vars(callee_ty_adjusted, List::empty()), - BoundConstness::NotConst, ImplPolarity::Positive, ) && path_to_local(callee) .map_or( diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index a98e2038d5f04..f0a777c5b895d 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -16,7 +16,7 @@ use rustc_middle::mir::{ }; use rustc_middle::traits::{ImplSource, ObligationCause, BuiltinImplSource}; use rustc_middle::ty::adjustment::PointerCoercion; -use rustc_middle::ty::{self, BoundConstness, GenericArgKind, TraitRef, Ty, TyCtxt}; +use rustc_middle::ty::{self, GenericArgKind, TraitRef, Ty, TyCtxt}; use rustc_semver::RustcVersion; use rustc_span::symbol::sym; use rustc_span::Span; @@ -399,11 +399,12 @@ fn is_ty_const_destruct<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, body: &Body<'tcx> return true; } + // FIXME(effects) constness let obligation = Obligation::new( tcx, ObligationCause::dummy_with_span(body.span), ConstCx::new(tcx, body).param_env, - TraitRef::from_lang_item(tcx, LangItem::Destruct, body.span, [ty]).with_constness(BoundConstness::ConstIfConst), + TraitRef::from_lang_item(tcx, LangItem::Destruct, body.span, [ty]), ); let infcx = tcx.infer_ctxt().build(); diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index d0e15aa8bb328..7176648053562 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -267,7 +267,7 @@ pub fn implements_trait_with_env_from_iter<'tcx>( cause: ObligationCause::dummy(), param_env, recursion_depth: 0, - predicate: ty::Binder::dummy(trait_ref).without_const().to_predicate(tcx), + predicate: ty::Binder::dummy(trait_ref).to_predicate(tcx), }; infcx .evaluate_obligation(&obligation) From 779e0f4021b591919c34126f623e3bc2383cb185 Mon Sep 17 00:00:00 2001 From: Catherine Flores Date: Sat, 29 Jul 2023 00:06:08 -0500 Subject: [PATCH 18/49] Do not lint unwrapping on `!` or never-like enums --- clippy_lints/src/methods/expect_used.rs | 44 ---------- clippy_lints/src/methods/mod.rs | 39 +++++++-- .../src/methods/unwrap_expect_used.rs | 87 +++++++++++++++++++ clippy_lints/src/methods/unwrap_used.rs | 53 ----------- tests/ui/unwrap_expect_used.rs | 11 +++ tests/ui/unwrap_expect_used.stderr | 12 +-- 6 files changed, 137 insertions(+), 109 deletions(-) delete mode 100644 clippy_lints/src/methods/expect_used.rs create mode 100644 clippy_lints/src/methods/unwrap_expect_used.rs delete mode 100644 clippy_lints/src/methods/unwrap_used.rs diff --git a/clippy_lints/src/methods/expect_used.rs b/clippy_lints/src/methods/expect_used.rs deleted file mode 100644 index 614610335a134..0000000000000 --- a/clippy_lints/src/methods/expect_used.rs +++ /dev/null @@ -1,44 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_in_cfg_test, is_in_test_function}; -use rustc_hir as hir; -use rustc_lint::LateContext; -use rustc_span::sym; - -use super::EXPECT_USED; - -/// lint use of `expect()` or `expect_err` for `Result` and `expect()` for `Option`. -pub(super) fn check( - cx: &LateContext<'_>, - expr: &hir::Expr<'_>, - recv: &hir::Expr<'_>, - is_err: bool, - allow_expect_in_tests: bool, -) { - let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs(); - - let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) && !is_err { - Some((EXPECT_USED, "an `Option`", "None", "")) - } else if is_type_diagnostic_item(cx, obj_ty, sym::Result) { - Some((EXPECT_USED, "a `Result`", if is_err { "Ok" } else { "Err" }, "an ")) - } else { - None - }; - - let method = if is_err { "expect_err" } else { "expect" }; - - if allow_expect_in_tests && (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id)) { - return; - } - - if let Some((lint, kind, none_value, none_prefix)) = mess { - span_lint_and_help( - cx, - lint, - expr.span, - &format!("used `{method}()` on {kind} value"), - None, - &format!("if this value is {none_prefix}`{none_value}`, it will panic"), - ); - } -} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 62d565bcc5c97..17483656cec02 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -17,7 +17,6 @@ mod collapsible_str_replace; mod drain_collect; mod err_expect; mod expect_fun_call; -mod expect_used; mod extend_with_drain; mod filetype_is_file; mod filter_map; @@ -105,7 +104,7 @@ mod unnecessary_lazy_eval; mod unnecessary_literal_unwrap; mod unnecessary_sort_by; mod unnecessary_to_owned; -mod unwrap_used; +mod unwrap_expect_used; mod useless_asref; mod utils; mod vec_resize_to_zero; @@ -3948,13 +3947,27 @@ impl Methods { match method_call(recv) { Some(("ok", recv, [], _, _)) => ok_expect::check(cx, expr, recv), Some(("err", recv, [], err_span, _)) => err_expect::check(cx, expr, recv, span, err_span, &self.msrv), - _ => expect_used::check(cx, expr, recv, false, self.allow_expect_in_tests), + _ => unwrap_expect_used::check( + cx, + expr, + recv, + false, + self.allow_expect_in_tests, + unwrap_expect_used::Variant::Expect, + ), } unnecessary_literal_unwrap::check(cx, expr, recv, name, args); }, ("expect_err", [_]) => { unnecessary_literal_unwrap::check(cx, expr, recv, name, args); - expect_used::check(cx, expr, recv, true, self.allow_expect_in_tests); + unwrap_expect_used::check( + cx, + expr, + recv, + true, + self.allow_expect_in_tests, + unwrap_expect_used::Variant::Expect, + ); }, ("extend", [arg]) => { string_extend_chars::check(cx, expr, recv, arg); @@ -4180,11 +4193,25 @@ impl Methods { _ => {}, } unnecessary_literal_unwrap::check(cx, expr, recv, name, args); - unwrap_used::check(cx, expr, recv, false, self.allow_unwrap_in_tests); + unwrap_expect_used::check( + cx, + expr, + recv, + false, + self.allow_unwrap_in_tests, + unwrap_expect_used::Variant::Unwrap, + ); }, ("unwrap_err", []) => { unnecessary_literal_unwrap::check(cx, expr, recv, name, args); - unwrap_used::check(cx, expr, recv, true, self.allow_unwrap_in_tests); + unwrap_expect_used::check( + cx, + expr, + recv, + true, + self.allow_unwrap_in_tests, + unwrap_expect_used::Variant::Unwrap, + ); }, ("unwrap_or", [u_arg]) => { match method_call(recv) { diff --git a/clippy_lints/src/methods/unwrap_expect_used.rs b/clippy_lints/src/methods/unwrap_expect_used.rs new file mode 100644 index 0000000000000..9b93eda688d3b --- /dev/null +++ b/clippy_lints/src/methods/unwrap_expect_used.rs @@ -0,0 +1,87 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{is_in_cfg_test, is_in_test_function, is_lint_allowed}; +use rustc_hir::Expr; +use rustc_lint::{LateContext, Lint}; +use rustc_middle::ty; +use rustc_span::sym; + +use super::{EXPECT_USED, UNWRAP_USED}; + +#[derive(Clone, Copy, Eq, PartialEq)] +pub(super) enum Variant { + Unwrap, + Expect, +} + +impl Variant { + fn method_name(self, is_err: bool) -> &'static str { + match (self, is_err) { + (Variant::Unwrap, true) => "unwrap_err", + (Variant::Unwrap, false) => "unwrap", + (Variant::Expect, true) => "expect_err", + (Variant::Expect, false) => "expect", + } + } + + fn lint(self) -> &'static Lint { + match self { + Variant::Unwrap => UNWRAP_USED, + Variant::Expect => EXPECT_USED, + } + } +} + +/// lint use of `unwrap()` or `unwrap_err` for `Result` and `unwrap()` for `Option`. +pub(super) fn check( + cx: &LateContext<'_>, + expr: &Expr<'_>, + recv: &Expr<'_>, + is_err: bool, + allow_unwrap_in_tests: bool, + variant: Variant, +) { + let ty = cx.typeck_results().expr_ty(recv).peel_refs(); + + let (kind, none_value, none_prefix) = if is_type_diagnostic_item(cx, ty, sym::Option) && !is_err { + ("an `Option`", "None", "") + } else if is_type_diagnostic_item(cx, ty, sym::Result) + && let ty::Adt(_, substs) = ty.kind() + && let Some(t_or_e_ty) = substs[usize::from(!is_err)].as_type() + { + // Issue #11245: Do not lint `!` or never-like enums + if t_or_e_ty.is_never() + || (t_or_e_ty.is_enum() && t_or_e_ty.ty_adt_def().is_some_and(|def| def.variants().is_empty())) + { + return; + } + + ("a `Result`", if is_err { "Ok" } else { "Err" }, "an ") + } else { + return; + }; + + let method_suffix = if is_err { "_err" } else { "" }; + + if allow_unwrap_in_tests && (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id)) { + return; + } + + let help = if variant == Variant::Unwrap && is_lint_allowed(cx, EXPECT_USED, expr.hir_id) { + format!( + "if you don't want to handle the `{none_value}` case gracefully, consider \ + using `expect{method_suffix}()` to provide a better panic message" + ) + } else { + format!("if this value is {none_prefix}`{none_value}`, it will panic") + }; + + span_lint_and_help( + cx, + variant.lint(), + expr.span, + &format!("used `{}()` on {kind} value", variant.method_name(is_err)), + None, + &help, + ); +} diff --git a/clippy_lints/src/methods/unwrap_used.rs b/clippy_lints/src/methods/unwrap_used.rs deleted file mode 100644 index 5e4c3daee6447..0000000000000 --- a/clippy_lints/src/methods/unwrap_used.rs +++ /dev/null @@ -1,53 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_in_cfg_test, is_in_test_function, is_lint_allowed}; -use rustc_hir as hir; -use rustc_lint::LateContext; -use rustc_span::sym; - -use super::{EXPECT_USED, UNWRAP_USED}; - -/// lint use of `unwrap()` or `unwrap_err` for `Result` and `unwrap()` for `Option`. -pub(super) fn check( - cx: &LateContext<'_>, - expr: &hir::Expr<'_>, - recv: &hir::Expr<'_>, - is_err: bool, - allow_unwrap_in_tests: bool, -) { - let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs(); - - let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) && !is_err { - Some((UNWRAP_USED, "an `Option`", "None", "")) - } else if is_type_diagnostic_item(cx, obj_ty, sym::Result) { - Some((UNWRAP_USED, "a `Result`", if is_err { "Ok" } else { "Err" }, "an ")) - } else { - None - }; - - let method_suffix = if is_err { "_err" } else { "" }; - - if allow_unwrap_in_tests && (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id)) { - return; - } - - if let Some((lint, kind, none_value, none_prefix)) = mess { - let help = if is_lint_allowed(cx, EXPECT_USED, expr.hir_id) { - format!( - "if you don't want to handle the `{none_value}` case gracefully, consider \ - using `expect{method_suffix}()` to provide a better panic message" - ) - } else { - format!("if this value is {none_prefix}`{none_value}`, it will panic") - }; - - span_lint_and_help( - cx, - lint, - expr.span, - &format!("used `unwrap{method_suffix}()` on {kind} value"), - None, - &help, - ); - } -} diff --git a/tests/ui/unwrap_expect_used.rs b/tests/ui/unwrap_expect_used.rs index 7f57efc53c9c8..26f92ccdefaa3 100644 --- a/tests/ui/unwrap_expect_used.rs +++ b/tests/ui/unwrap_expect_used.rs @@ -1,5 +1,8 @@ #![warn(clippy::unwrap_used, clippy::expect_used)] #![allow(clippy::unnecessary_literal_unwrap)] +#![feature(never_type)] + +use std::convert::Infallible; trait OptionExt { type Item; @@ -28,6 +31,14 @@ fn main() { Some(3).unwrap_err(); Some(3).expect_err("Hellow none!"); + // Issue #11245: The `Err` variant can never be constructed so do not lint this. + let x: Result<(), !> = Ok(()); + x.unwrap(); + x.expect("is `!` (never)"); + let x: Result<(), Infallible> = Ok(()); + x.unwrap(); + x.expect("is never-like (0 variants)"); + let a: Result = Ok(3); a.unwrap(); a.expect("Hello world!"); diff --git a/tests/ui/unwrap_expect_used.stderr b/tests/ui/unwrap_expect_used.stderr index 1a551ab5ab8ea..8cbde8bf94601 100644 --- a/tests/ui/unwrap_expect_used.stderr +++ b/tests/ui/unwrap_expect_used.stderr @@ -1,5 +1,5 @@ error: used `unwrap()` on an `Option` value - --> $DIR/unwrap_expect_used.rs:24:5 + --> $DIR/unwrap_expect_used.rs:27:5 | LL | Some(3).unwrap(); | ^^^^^^^^^^^^^^^^ @@ -8,7 +8,7 @@ LL | Some(3).unwrap(); = note: `-D clippy::unwrap-used` implied by `-D warnings` error: used `expect()` on an `Option` value - --> $DIR/unwrap_expect_used.rs:25:5 + --> $DIR/unwrap_expect_used.rs:28:5 | LL | Some(3).expect("Hello world!"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -17,7 +17,7 @@ LL | Some(3).expect("Hello world!"); = note: `-D clippy::expect-used` implied by `-D warnings` error: used `unwrap()` on a `Result` value - --> $DIR/unwrap_expect_used.rs:32:5 + --> $DIR/unwrap_expect_used.rs:43:5 | LL | a.unwrap(); | ^^^^^^^^^^ @@ -25,7 +25,7 @@ LL | a.unwrap(); = help: if this value is an `Err`, it will panic error: used `expect()` on a `Result` value - --> $DIR/unwrap_expect_used.rs:33:5 + --> $DIR/unwrap_expect_used.rs:44:5 | LL | a.expect("Hello world!"); | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -33,7 +33,7 @@ LL | a.expect("Hello world!"); = help: if this value is an `Err`, it will panic error: used `unwrap_err()` on a `Result` value - --> $DIR/unwrap_expect_used.rs:34:5 + --> $DIR/unwrap_expect_used.rs:45:5 | LL | a.unwrap_err(); | ^^^^^^^^^^^^^^ @@ -41,7 +41,7 @@ LL | a.unwrap_err(); = help: if this value is an `Ok`, it will panic error: used `expect_err()` on a `Result` value - --> $DIR/unwrap_expect_used.rs:35:5 + --> $DIR/unwrap_expect_used.rs:46:5 | LL | a.expect_err("Hello error!"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 71c54137eadc730d01fe15c17df8bf3ba5d3b04b Mon Sep 17 00:00:00 2001 From: Catherine Flores Date: Wed, 2 Aug 2023 13:57:06 -0500 Subject: [PATCH 19/49] Extract never-like into `clippy_utils` --- .../src/methods/unwrap_expect_used.rs | 34 +++++++--------- clippy_utils/src/ty.rs | 5 +++ tests/ui-toml/expect_used/expect_used.stderr | 4 +- tests/ui-toml/unwrap_used/unwrap_used.stderr | 39 ++++++++++++------- tests/ui/expect.stderr | 6 +-- tests/ui/get_unwrap.stderr | 39 ++++++++++++------- tests/ui/unwrap.stderr | 9 +++-- tests/ui/unwrap_expect_used.stderr | 12 +++--- 8 files changed, 89 insertions(+), 59 deletions(-) diff --git a/clippy_lints/src/methods/unwrap_expect_used.rs b/clippy_lints/src/methods/unwrap_expect_used.rs index 9b93eda688d3b..7bd16b473ce40 100644 --- a/clippy_lints/src/methods/unwrap_expect_used.rs +++ b/clippy_lints/src/methods/unwrap_expect_used.rs @@ -1,5 +1,5 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::ty::{is_never_like, is_type_diagnostic_item}; use clippy_utils::{is_in_cfg_test, is_in_test_function, is_lint_allowed}; use rustc_hir::Expr; use rustc_lint::{LateContext, Lint}; @@ -32,7 +32,8 @@ impl Variant { } } -/// lint use of `unwrap()` or `unwrap_err` for `Result` and `unwrap()` for `Option`. +/// Lint usage of `unwrap` or `unwrap_err` for `Result` and `unwrap()` for `Option` (and their +/// `expect` counterparts). pub(super) fn check( cx: &LateContext<'_>, expr: &Expr<'_>, @@ -49,10 +50,7 @@ pub(super) fn check( && let ty::Adt(_, substs) = ty.kind() && let Some(t_or_e_ty) = substs[usize::from(!is_err)].as_type() { - // Issue #11245: Do not lint `!` or never-like enums - if t_or_e_ty.is_never() - || (t_or_e_ty.is_enum() && t_or_e_ty.ty_adt_def().is_some_and(|def| def.variants().is_empty())) - { + if is_never_like(t_or_e_ty) { return; } @@ -67,21 +65,19 @@ pub(super) fn check( return; } - let help = if variant == Variant::Unwrap && is_lint_allowed(cx, EXPECT_USED, expr.hir_id) { - format!( - "if you don't want to handle the `{none_value}` case gracefully, consider \ - using `expect{method_suffix}()` to provide a better panic message" - ) - } else { - format!("if this value is {none_prefix}`{none_value}`, it will panic") - }; - - span_lint_and_help( + span_lint_and_then( cx, variant.lint(), expr.span, &format!("used `{}()` on {kind} value", variant.method_name(is_err)), - None, - &help, + |diag| { + diag.note(format!("if this value is {none_prefix}`{none_value}`, it will panic")); + + if variant == Variant::Unwrap && is_lint_allowed(cx, EXPECT_USED, expr.hir_id) { + diag.help(format!( + "consider using `expect{method_suffix}()` to provide a better panic message" + )); + } + }, ); } diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index d0e15aa8bb328..98e163abc58af 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -1093,6 +1093,11 @@ fn assert_generic_args_match<'tcx>(tcx: TyCtxt<'tcx>, did: DefId, args: &[Generi } } +/// Returns whether `ty` is never-like; i.e., `!` (never) or an enum with zero variants. +pub fn is_never_like(ty: Ty<'_>) -> bool { + ty.is_never() || (ty.is_enum() && ty.ty_adt_def().is_some_and(|def| def.variants().is_empty())) +} + /// Makes the projection type for the named associated type in the given impl or trait impl. /// /// This function is for associated types which are "known" to exist, and as such, will only return diff --git a/tests/ui-toml/expect_used/expect_used.stderr b/tests/ui-toml/expect_used/expect_used.stderr index 9eef0e1bfaa12..815d009350f3c 100644 --- a/tests/ui-toml/expect_used/expect_used.stderr +++ b/tests/ui-toml/expect_used/expect_used.stderr @@ -4,7 +4,7 @@ error: used `expect()` on an `Option` value LL | let _ = opt.expect(""); | ^^^^^^^^^^^^^^ | - = help: if this value is `None`, it will panic + = note: if this value is `None`, it will panic = note: `-D clippy::expect-used` implied by `-D warnings` error: used `expect()` on a `Result` value @@ -13,7 +13,7 @@ error: used `expect()` on a `Result` value LL | let _ = res.expect(""); | ^^^^^^^^^^^^^^ | - = help: if this value is an `Err`, it will panic + = note: if this value is an `Err`, it will panic error: aborting due to 2 previous errors diff --git a/tests/ui-toml/unwrap_used/unwrap_used.stderr b/tests/ui-toml/unwrap_used/unwrap_used.stderr index 4c9bdfa9dba73..10219beaf9719 100644 --- a/tests/ui-toml/unwrap_used/unwrap_used.stderr +++ b/tests/ui-toml/unwrap_used/unwrap_used.stderr @@ -12,7 +12,8 @@ error: used `unwrap()` on an `Option` value LL | let _ = boxed_slice.get(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message = note: `-D clippy::unwrap-used` implied by `-D warnings` error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise @@ -27,7 +28,8 @@ error: used `unwrap()` on an `Option` value LL | let _ = some_slice.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise --> $DIR/unwrap_used.rs:40:17 @@ -41,7 +43,8 @@ error: used `unwrap()` on an `Option` value LL | let _ = some_vec.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a VecDeque. Using `[]` is more clear and more concise --> $DIR/unwrap_used.rs:41:17 @@ -55,7 +58,8 @@ error: used `unwrap()` on an `Option` value LL | let _ = some_vecdeque.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a HashMap. Using `[]` is more clear and more concise --> $DIR/unwrap_used.rs:42:17 @@ -69,7 +73,8 @@ error: used `unwrap()` on an `Option` value LL | let _ = some_hashmap.get(&1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a BTreeMap. Using `[]` is more clear and more concise --> $DIR/unwrap_used.rs:43:17 @@ -83,7 +88,8 @@ error: used `unwrap()` on an `Option` value LL | let _ = some_btreemap.get(&1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise --> $DIR/unwrap_used.rs:47:21 @@ -97,7 +103,8 @@ error: used `unwrap()` on an `Option` value LL | let _: u8 = *boxed_slice.get(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise --> $DIR/unwrap_used.rs:52:9 @@ -111,7 +118,8 @@ error: used `unwrap()` on an `Option` value LL | *boxed_slice.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise --> $DIR/unwrap_used.rs:53:9 @@ -125,7 +133,8 @@ error: used `unwrap()` on an `Option` value LL | *some_slice.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise --> $DIR/unwrap_used.rs:54:9 @@ -139,7 +148,8 @@ error: used `unwrap()` on an `Option` value LL | *some_vec.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a VecDeque. Using `[]` is more clear and more concise --> $DIR/unwrap_used.rs:55:9 @@ -153,7 +163,8 @@ error: used `unwrap()` on an `Option` value LL | *some_vecdeque.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise --> $DIR/unwrap_used.rs:67:17 @@ -167,7 +178,8 @@ error: used `unwrap()` on an `Option` value LL | let _ = some_vec.get(0..1).unwrap().to_vec(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise --> $DIR/unwrap_used.rs:68:17 @@ -181,7 +193,8 @@ error: used `unwrap()` on an `Option` value LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise --> $DIR/unwrap_used.rs:75:13 diff --git a/tests/ui/expect.stderr b/tests/ui/expect.stderr index be340340d4773..f787fa973a32e 100644 --- a/tests/ui/expect.stderr +++ b/tests/ui/expect.stderr @@ -4,7 +4,7 @@ error: used `expect()` on an `Option` value LL | let _ = opt.expect(""); | ^^^^^^^^^^^^^^ | - = help: if this value is `None`, it will panic + = note: if this value is `None`, it will panic = note: `-D clippy::expect-used` implied by `-D warnings` error: used `expect()` on a `Result` value @@ -13,7 +13,7 @@ error: used `expect()` on a `Result` value LL | let _ = res.expect(""); | ^^^^^^^^^^^^^^ | - = help: if this value is an `Err`, it will panic + = note: if this value is an `Err`, it will panic error: used `expect_err()` on a `Result` value --> $DIR/expect.rs:12:13 @@ -21,7 +21,7 @@ error: used `expect_err()` on a `Result` value LL | let _ = res.expect_err(""); | ^^^^^^^^^^^^^^^^^^ | - = help: if this value is an `Ok`, it will panic + = note: if this value is an `Ok`, it will panic error: aborting due to 3 previous errors diff --git a/tests/ui/get_unwrap.stderr b/tests/ui/get_unwrap.stderr index c567ed319b5b9..19dc9071fe7bb 100644 --- a/tests/ui/get_unwrap.stderr +++ b/tests/ui/get_unwrap.stderr @@ -16,7 +16,8 @@ error: used `unwrap()` on an `Option` value LL | let _ = boxed_slice.get(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message = note: `-D clippy::unwrap-used` implied by `-D warnings` error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise @@ -31,7 +32,8 @@ error: used `unwrap()` on an `Option` value LL | let _ = some_slice.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:40:17 @@ -45,7 +47,8 @@ error: used `unwrap()` on an `Option` value LL | let _ = some_vec.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a VecDeque. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:41:17 @@ -59,7 +62,8 @@ error: used `unwrap()` on an `Option` value LL | let _ = some_vecdeque.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a HashMap. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:42:17 @@ -73,7 +77,8 @@ error: used `unwrap()` on an `Option` value LL | let _ = some_hashmap.get(&1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a BTreeMap. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:43:17 @@ -87,7 +92,8 @@ error: used `unwrap()` on an `Option` value LL | let _ = some_btreemap.get(&1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:47:21 @@ -101,7 +107,8 @@ error: used `unwrap()` on an `Option` value LL | let _: u8 = *boxed_slice.get(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:52:9 @@ -115,7 +122,8 @@ error: used `unwrap()` on an `Option` value LL | *boxed_slice.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:53:9 @@ -129,7 +137,8 @@ error: used `unwrap()` on an `Option` value LL | *some_slice.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:54:9 @@ -143,7 +152,8 @@ error: used `unwrap()` on an `Option` value LL | *some_vec.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a VecDeque. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:55:9 @@ -157,7 +167,8 @@ error: used `unwrap()` on an `Option` value LL | *some_vecdeque.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:67:17 @@ -171,7 +182,8 @@ error: used `unwrap()` on an `Option` value LL | let _ = some_vec.get(0..1).unwrap().to_vec(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:68:17 @@ -185,7 +197,8 @@ error: used `unwrap()` on an `Option` value LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:78:24 diff --git a/tests/ui/unwrap.stderr b/tests/ui/unwrap.stderr index 3796d942ff9fc..41db819f6fb75 100644 --- a/tests/ui/unwrap.stderr +++ b/tests/ui/unwrap.stderr @@ -4,7 +4,8 @@ error: used `unwrap()` on an `Option` value LL | let _ = opt.unwrap(); | ^^^^^^^^^^^^ | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is `None`, it will panic + = help: consider using `expect()` to provide a better panic message = note: `-D clippy::unwrap-used` implied by `-D warnings` error: used `unwrap()` on a `Result` value @@ -13,7 +14,8 @@ error: used `unwrap()` on a `Result` value LL | let _ = res.unwrap(); | ^^^^^^^^^^^^ | - = help: if you don't want to handle the `Err` case gracefully, consider using `expect()` to provide a better panic message + = note: if this value is an `Err`, it will panic + = help: consider using `expect()` to provide a better panic message error: used `unwrap_err()` on a `Result` value --> $DIR/unwrap.rs:12:13 @@ -21,7 +23,8 @@ error: used `unwrap_err()` on a `Result` value LL | let _ = res.unwrap_err(); | ^^^^^^^^^^^^^^^^ | - = help: if you don't want to handle the `Ok` case gracefully, consider using `expect_err()` to provide a better panic message + = note: if this value is an `Ok`, it will panic + = help: consider using `expect_err()` to provide a better panic message error: aborting due to 3 previous errors diff --git a/tests/ui/unwrap_expect_used.stderr b/tests/ui/unwrap_expect_used.stderr index 8cbde8bf94601..f66e47612add4 100644 --- a/tests/ui/unwrap_expect_used.stderr +++ b/tests/ui/unwrap_expect_used.stderr @@ -4,7 +4,7 @@ error: used `unwrap()` on an `Option` value LL | Some(3).unwrap(); | ^^^^^^^^^^^^^^^^ | - = help: if this value is `None`, it will panic + = note: if this value is `None`, it will panic = note: `-D clippy::unwrap-used` implied by `-D warnings` error: used `expect()` on an `Option` value @@ -13,7 +13,7 @@ error: used `expect()` on an `Option` value LL | Some(3).expect("Hello world!"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if this value is `None`, it will panic + = note: if this value is `None`, it will panic = note: `-D clippy::expect-used` implied by `-D warnings` error: used `unwrap()` on a `Result` value @@ -22,7 +22,7 @@ error: used `unwrap()` on a `Result` value LL | a.unwrap(); | ^^^^^^^^^^ | - = help: if this value is an `Err`, it will panic + = note: if this value is an `Err`, it will panic error: used `expect()` on a `Result` value --> $DIR/unwrap_expect_used.rs:44:5 @@ -30,7 +30,7 @@ error: used `expect()` on a `Result` value LL | a.expect("Hello world!"); | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if this value is an `Err`, it will panic + = note: if this value is an `Err`, it will panic error: used `unwrap_err()` on a `Result` value --> $DIR/unwrap_expect_used.rs:45:5 @@ -38,7 +38,7 @@ error: used `unwrap_err()` on a `Result` value LL | a.unwrap_err(); | ^^^^^^^^^^^^^^ | - = help: if this value is an `Ok`, it will panic + = note: if this value is an `Ok`, it will panic error: used `expect_err()` on a `Result` value --> $DIR/unwrap_expect_used.rs:46:5 @@ -46,7 +46,7 @@ error: used `expect_err()` on a `Result` value LL | a.expect_err("Hello error!"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: if this value is an `Ok`, it will panic + = note: if this value is an `Ok`, it will panic error: aborting due to 6 previous errors From 4d49065a6cf1bd3d469919a961f35eac9e8d1e2a Mon Sep 17 00:00:00 2001 From: Catherine Flores Date: Wed, 2 Aug 2023 14:13:16 -0500 Subject: [PATCH 20/49] Suppress `question_mark` if `question_mark_used` is not allowed --- clippy_lints/src/question_mark.rs | 12 +++++++++--- tests/ui/question_mark.fixed | 17 +++++++++++++++++ tests/ui/question_mark.rs | 17 +++++++++++++++++ tests/ui/question_mark.stderr | 6 +++--- 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index f5502cffb6693..734ca2914f5d4 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -1,11 +1,13 @@ use crate::manual_let_else::{MatchLintBehaviour, MANUAL_LET_ELSE}; +use crate::question_mark_used::QUESTION_MARK_USED; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::Msrv; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{ - eq_expr_value, get_parent_node, higher, in_constant, is_else_clause, is_path_lang_item, is_res_lang_ctor, - pat_and_expr_can_be_question_mark, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt, + eq_expr_value, get_parent_node, higher, in_constant, is_else_clause, is_lint_allowed, is_path_lang_item, + is_res_lang_ctor, pat_and_expr_can_be_question_mark, path_to_local, path_to_local_id, peel_blocks, + peel_blocks_with_stmt, }; use if_chain::if_chain; use rustc_errors::Applicability; @@ -299,13 +301,17 @@ fn is_try_block(cx: &LateContext<'_>, bl: &rustc_hir::Block<'_>) -> bool { impl<'tcx> LateLintPass<'tcx> for QuestionMark { fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { + if !is_lint_allowed(cx, QUESTION_MARK_USED, stmt.hir_id) { + return; + } + if !in_constant(cx, stmt.hir_id) { check_let_some_else_return_none(cx, stmt); } self.check_manual_let_else(cx, stmt); } fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if !in_constant(cx, expr.hir_id) { + if !in_constant(cx, expr.hir_id) && is_lint_allowed(cx, QUESTION_MARK_USED, expr.hir_id) { self.check_is_none_or_err_and_early_return(cx, expr); self.check_if_let_some_or_err_and_early_return(cx, expr); } diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index 2d8920ccc42f9..20b9e42a7aab0 100644 --- a/tests/ui/question_mark.fixed +++ b/tests/ui/question_mark.fixed @@ -138,6 +138,23 @@ fn result_func(x: Result) -> Result { // no warning let _ = if let Err(e) = x { Err(e) } else { Ok(0) }; + // issue #11283 + // no warning + #[warn(clippy::question_mark_used)] + { + if let Err(err) = Ok(()) { + return Err(err); + } + + if Err::(0).is_err() { + return Err(0); + } else { + return Ok(0); + } + + unreachable!() + } + Ok(y) } diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs index 69451c17ee7a6..8bdafd46e8fa9 100644 --- a/tests/ui/question_mark.rs +++ b/tests/ui/question_mark.rs @@ -170,6 +170,23 @@ fn result_func(x: Result) -> Result { // no warning let _ = if let Err(e) = x { Err(e) } else { Ok(0) }; + // issue #11283 + // no warning + #[warn(clippy::question_mark_used)] + { + if let Err(err) = Ok(()) { + return Err(err); + } + + if Err::(0).is_err() { + return Err(0); + } else { + return Ok(0); + } + + unreachable!() + } + Ok(y) } diff --git a/tests/ui/question_mark.stderr b/tests/ui/question_mark.stderr index 2cfd75863081b..62489c8c8c479 100644 --- a/tests/ui/question_mark.stderr +++ b/tests/ui/question_mark.stderr @@ -115,7 +115,7 @@ LL | | } | |_____^ help: replace it with: `x?;` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:197:5 + --> $DIR/question_mark.rs:214:5 | LL | / if let Err(err) = func_returning_result() { LL | | return Err(err); @@ -123,7 +123,7 @@ LL | | } | |_____^ help: replace it with: `func_returning_result()?;` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:204:5 + --> $DIR/question_mark.rs:221:5 | LL | / if let Err(err) = func_returning_result() { LL | | return Err(err); @@ -131,7 +131,7 @@ LL | | } | |_____^ help: replace it with: `func_returning_result()?;` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:281:13 + --> $DIR/question_mark.rs:298:13 | LL | / if a.is_none() { LL | | return None; From fef85c90837eca1655cacd3168fd7c3501899503 Mon Sep 17 00:00:00 2001 From: Catherine Flores Date: Wed, 2 Aug 2023 15:00:41 -0500 Subject: [PATCH 21/49] Take snippet instead of pretty printing type --- clippy_lints/src/casts/ptr_as_ptr.rs | 64 +++++++++++++++------------- tests/ui/ptr_as_ptr.fixed | 16 ++++++- tests/ui/ptr_as_ptr.rs | 16 ++++++- tests/ui/ptr_as_ptr.stderr | 28 +++++++----- 4 files changed, 82 insertions(+), 42 deletions(-) diff --git a/clippy_lints/src/casts/ptr_as_ptr.rs b/clippy_lints/src/casts/ptr_as_ptr.rs index 15ffb00da88ba..181dbcf6e9a86 100644 --- a/clippy_lints/src/casts/ptr_as_ptr.rs +++ b/clippy_lints/src/casts/ptr_as_ptr.rs @@ -1,9 +1,7 @@ -use std::borrow::Cow; - use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; -use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, Mutability, TyKind}; use rustc_lint::LateContext; @@ -16,33 +14,41 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: &Msrv) { return; } - if_chain! { - if let ExprKind::Cast(cast_expr, cast_to_hir_ty) = expr.kind; - let (cast_from, cast_to) = (cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(expr)); - if let ty::RawPtr(TypeAndMut { mutbl: from_mutbl, .. }) = cast_from.kind(); - if let ty::RawPtr(TypeAndMut { ty: to_pointee_ty, mutbl: to_mutbl }) = cast_to.kind(); - if matches!((from_mutbl, to_mutbl), - (Mutability::Not, Mutability::Not) | (Mutability::Mut, Mutability::Mut)); + if let ExprKind::Cast(cast_expr, cast_to_hir_ty) = expr.kind + && let (cast_from, cast_to) = (cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(expr)) + && let ty::RawPtr(TypeAndMut { mutbl: from_mutbl, .. }) = cast_from.kind() + && let ty::RawPtr(TypeAndMut { ty: to_pointee_ty, mutbl: to_mutbl }) = cast_to.kind() + && matches!((from_mutbl, to_mutbl), + (Mutability::Not, Mutability::Not) | (Mutability::Mut, Mutability::Mut)) // The `U` in `pointer::cast` have to be `Sized` // as explained here: https://github.com/rust-lang/rust/issues/60602. - if to_pointee_ty.is_sized(cx.tcx, cx.param_env); - then { - let mut applicability = Applicability::MachineApplicable; - let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut applicability); - let turbofish = match &cast_to_hir_ty.kind { - TyKind::Infer => Cow::Borrowed(""), - TyKind::Ptr(mut_ty) if matches!(mut_ty.ty.kind, TyKind::Infer) => Cow::Borrowed(""), - _ => Cow::Owned(format!("::<{to_pointee_ty}>")), - }; - span_lint_and_sugg( - cx, - PTR_AS_PTR, - expr.span, - "`as` casting between raw pointers without changing its mutability", - "try `pointer::cast`, a safer alternative", - format!("{}.cast{turbofish}()", cast_expr_sugg.maybe_par()), - applicability, - ); - } + && to_pointee_ty.is_sized(cx.tcx, cx.param_env) + { + let mut app = Applicability::MachineApplicable; + let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app); + let turbofish = match &cast_to_hir_ty.kind { + TyKind::Infer => String::new(), + TyKind::Ptr(mut_ty) => { + if matches!(mut_ty.ty.kind, TyKind::Infer) { + String::new() + } else { + format!( + "::<{}>", + snippet_with_applicability(cx, mut_ty.ty.span, "/* type */", &mut app) + ) + } + }, + _ => return, + }; + + span_lint_and_sugg( + cx, + PTR_AS_PTR, + expr.span, + "`as` casting between raw pointers without changing its mutability", + "try `pointer::cast`, a safer alternative", + format!("{}.cast{turbofish}()", cast_expr_sugg.maybe_par()), + app, + ); } } diff --git a/tests/ui/ptr_as_ptr.fixed b/tests/ui/ptr_as_ptr.fixed index 26a64c861cf4d..84babb974161d 100644 --- a/tests/ui/ptr_as_ptr.fixed +++ b/tests/ui/ptr_as_ptr.fixed @@ -3,8 +3,22 @@ #![warn(clippy::ptr_as_ptr)] +#[macro_use] extern crate proc_macros; -use proc_macros::{external, inline_macros}; + +mod issue_11278_a { + #[derive(Debug)] + pub struct T { + pub p: D, + } +} + +mod issue_11278_b { + pub fn f(o: &mut super::issue_11278_a::T) -> super::issue_11278_a::T { + // Retain `super` + *unsafe { Box::from_raw(Box::into_raw(Box::new(o)).cast::>()) } + } +} #[inline_macros] fn main() { diff --git a/tests/ui/ptr_as_ptr.rs b/tests/ui/ptr_as_ptr.rs index ea40d4947331b..34fd76428b229 100644 --- a/tests/ui/ptr_as_ptr.rs +++ b/tests/ui/ptr_as_ptr.rs @@ -3,8 +3,22 @@ #![warn(clippy::ptr_as_ptr)] +#[macro_use] extern crate proc_macros; -use proc_macros::{external, inline_macros}; + +mod issue_11278_a { + #[derive(Debug)] + pub struct T { + pub p: D, + } +} + +mod issue_11278_b { + pub fn f(o: &mut super::issue_11278_a::T) -> super::issue_11278_a::T { + // Retain `super` + *unsafe { Box::from_raw(Box::into_raw(Box::new(o)) as *mut super::issue_11278_a::T) } + } +} #[inline_macros] fn main() { diff --git a/tests/ui/ptr_as_ptr.stderr b/tests/ui/ptr_as_ptr.stderr index 78d733994ac69..e64f3351505f9 100644 --- a/tests/ui/ptr_as_ptr.stderr +++ b/tests/ui/ptr_as_ptr.stderr @@ -1,37 +1,43 @@ error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:14:13 + --> $DIR/ptr_as_ptr.rs:19:33 | -LL | let _ = ptr as *const i32; - | ^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `ptr.cast::()` +LL | *unsafe { Box::from_raw(Box::into_raw(Box::new(o)) as *mut super::issue_11278_a::T) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `Box::into_raw(Box::new(o)).cast::>()` | = note: `-D clippy::ptr-as-ptr` implied by `-D warnings` error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:15:13 + --> $DIR/ptr_as_ptr.rs:28:13 + | +LL | let _ = ptr as *const i32; + | ^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `ptr.cast::()` + +error: `as` casting between raw pointers without changing its mutability + --> $DIR/ptr_as_ptr.rs:29:13 | LL | let _ = mut_ptr as *mut i32; | ^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast::()` error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:20:17 + --> $DIR/ptr_as_ptr.rs:34:17 | LL | let _ = *ptr_ptr as *const i32; | ^^^^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `(*ptr_ptr).cast::()` error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:33:25 + --> $DIR/ptr_as_ptr.rs:47:25 | LL | let _: *const i32 = ptr as *const _; | ^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `ptr.cast()` error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:34:23 + --> $DIR/ptr_as_ptr.rs:48:23 | LL | let _: *mut i32 = mut_ptr as _; | ^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast()` error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:37:21 + --> $DIR/ptr_as_ptr.rs:51:21 | LL | let _ = inline!($ptr as *const i32); | ^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `$ptr.cast::()` @@ -39,16 +45,16 @@ LL | let _ = inline!($ptr as *const i32); = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:58:13 + --> $DIR/ptr_as_ptr.rs:72:13 | LL | let _ = ptr as *const i32; | ^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `ptr.cast::()` error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:59:13 + --> $DIR/ptr_as_ptr.rs:73:13 | LL | let _ = mut_ptr as *mut i32; | ^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast::()` -error: aborting due to 8 previous errors +error: aborting due to 9 previous errors From 85b5e98ea26c6f66c49d7a26a160f6435d0dd161 Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Thu, 9 Mar 2023 20:54:53 +0000 Subject: [PATCH 22/49] Add `internal_features` lint It lints against features that are inteded to be internal to the compiler and standard library. Implements MCP #596. We allow `internal_features` in the standard library and compiler as those use many features and this _is_ the standard library from the "internal to the compiler and standard library" after all. Marking some features as internal wasn't exactly the most scientific approach, I just marked some mostly obvious features. While there is a categorization in the macro, it's not very well upheld (should probably be fixed in another PR). We always pass `-Ainternal_features` in the testsuite About 400 UI tests and several other tests use internal features. Instead of throwing the attribute on each one, just always allow them. There's nothing wrong with testing internal features^^ --- tests/compile-test.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 385e25ce6b8f0..e46f8bf6fabdf 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -140,6 +140,7 @@ fn base_config(test_dir: &str) -> compiletest::Config { [ "--emit=metadata", "-Aunused", + "-Ainternal_features", "-Zui-testing", "-Dwarnings", &format!("-Ldependency={}", deps_path.display()), From 3fb84415cdfa671bc16680a31667ce70399f4079 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Sat, 29 Jul 2023 20:37:21 +0200 Subject: [PATCH 23/49] Fix `suspicious_xor_used_as_pow.rs` performance --- .../src/suspicious_xor_used_as_pow.rs | 48 ++++++++++--------- tests/ui/suspicious_xor_used_as_pow.stderr | 10 ++-- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/clippy_lints/src/suspicious_xor_used_as_pow.rs b/clippy_lints/src/suspicious_xor_used_as_pow.rs index 6fa49afe0ecd7..8e156b8829b9c 100644 --- a/clippy_lints/src/suspicious_xor_used_as_pow.rs +++ b/clippy_lints/src/suspicious_xor_used_as_pow.rs @@ -1,5 +1,7 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::numeric_literal::NumericLiteral; -use clippy_utils::source::snippet_with_context; +use clippy_utils::source::snippet; +use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -28,27 +30,29 @@ declare_lint_pass!(ConfusingXorAndPow => [SUSPICIOUS_XOR_USED_AS_POW]); impl LateLintPass<'_> for ConfusingXorAndPow { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - if !in_external_macro(cx.sess(), expr.span) && - let ExprKind::Binary(op, left, right) = &expr.kind && - op.node == BinOpKind::BitXor && - left.span.ctxt() == right.span.ctxt() && - let ExprKind::Lit(lit_left) = &left.kind && - let ExprKind::Lit(lit_right) = &right.kind && - let snip_left = snippet_with_context(cx, lit_left.span, lit_left.span.ctxt(), "..", &mut Applicability::MaybeIncorrect) && - let snip_right = snippet_with_context(cx, lit_right.span, lit_right.span.ctxt(), "..", &mut Applicability::MaybeIncorrect) && - let Some(left_val) = NumericLiteral::from_lit_kind(&snip_left.0, &lit_left.node) && - let Some(right_val) = NumericLiteral::from_lit_kind(&snip_right.0, &lit_right.node) && - left_val.is_decimal() && - right_val.is_decimal() { - clippy_utils::diagnostics::span_lint_and_sugg( - cx, - SUSPICIOUS_XOR_USED_AS_POW, - expr.span, - "`^` is not the exponentiation operator", - "did you mean to write", - format!("{}.pow({})", left_val.format(), right_val.format()), - Applicability::MaybeIncorrect, - ); + if !in_external_macro(cx.sess(), expr.span) + && let ExprKind::Binary(op, left, right) = &expr.kind + && op.node == BinOpKind::BitXor + && left.span.ctxt() == right.span.ctxt() + && let ExprKind::Lit(lit_left) = &left.kind + && let ExprKind::Lit(lit_right) = &right.kind + && matches!(lit_right.node, LitKind::Int(..) | LitKind::Float(..)) + && matches!(lit_left.node, LitKind::Int(..) | LitKind::Float(..)) + && NumericLiteral::from_lit_kind(&snippet(cx, lit_right.span, ".."), &lit_right.node).is_some_and(|x| x.is_decimal()) + { + span_lint_and_sugg( + cx, + SUSPICIOUS_XOR_USED_AS_POW, + expr.span, + "`^` is not the exponentiation operator", + "did you mean to write", + format!( + "{}.pow({})", + lit_left.node, + lit_right.node + ), + Applicability::MaybeIncorrect, + ); } } } diff --git a/tests/ui/suspicious_xor_used_as_pow.stderr b/tests/ui/suspicious_xor_used_as_pow.stderr index 8bb3c8fbeebd3..d93a55ba9065c 100644 --- a/tests/ui/suspicious_xor_used_as_pow.stderr +++ b/tests/ui/suspicious_xor_used_as_pow.stderr @@ -10,31 +10,31 @@ error: `^` is not the exponentiation operator --> $DIR/suspicious_xor_used_as_pow.rs:20:13 | LL | let _ = 2i32 ^ 9i32; - | ^^^^^^^^^^^ help: did you mean to write: `2_i32.pow(9_i32)` + | ^^^^^^^^^^^ help: did you mean to write: `2i32.pow(9i32)` error: `^` is not the exponentiation operator --> $DIR/suspicious_xor_used_as_pow.rs:21:13 | LL | let _ = 2i32 ^ 2i32; - | ^^^^^^^^^^^ help: did you mean to write: `2_i32.pow(2_i32)` + | ^^^^^^^^^^^ help: did you mean to write: `2i32.pow(2i32)` error: `^` is not the exponentiation operator --> $DIR/suspicious_xor_used_as_pow.rs:22:13 | LL | let _ = 50i32 ^ 3i32; - | ^^^^^^^^^^^^ help: did you mean to write: `50_i32.pow(3_i32)` + | ^^^^^^^^^^^^ help: did you mean to write: `50i32.pow(3i32)` error: `^` is not the exponentiation operator --> $DIR/suspicious_xor_used_as_pow.rs:23:13 | LL | let _ = 5i32 ^ 8i32; - | ^^^^^^^^^^^ help: did you mean to write: `5_i32.pow(8_i32)` + | ^^^^^^^^^^^ help: did you mean to write: `5i32.pow(8i32)` error: `^` is not the exponentiation operator --> $DIR/suspicious_xor_used_as_pow.rs:24:13 | LL | let _ = 2i32 ^ 32i32; - | ^^^^^^^^^^^^ help: did you mean to write: `2_i32.pow(32_i32)` + | ^^^^^^^^^^^^ help: did you mean to write: `2i32.pow(32i32)` error: `^` is not the exponentiation operator --> $DIR/suspicious_xor_used_as_pow.rs:13:9 From ed0dfed24f988cb2b52c819cd57c8eb65da400be Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Thu, 3 Aug 2023 21:43:17 +0200 Subject: [PATCH 24/49] Improve spans for indexing expressions Indexing is similar to method calls in having an arbitrary left-hand-side and then something on the right, which is the main part of the expression. Method calls already have a span for that right part, but indexing does not. This means that long method chains that use indexing have really bad spans, especially when the indexing panics and that span in coverted into a panic location. This does the same thing as method calls for the AST and HIR, storing an extra span which is then put into the `fn_span` field in THIR. --- clippy_lints/src/dereference.rs | 2 +- clippy_lints/src/functions/must_use.rs | 2 +- clippy_lints/src/index_refutable_slice.rs | 2 +- clippy_lints/src/indexing_slicing.rs | 2 +- clippy_lints/src/loops/manual_memcpy.rs | 4 ++-- clippy_lints/src/loops/needless_range_loop.rs | 2 +- clippy_lints/src/loops/never_loop.rs | 2 +- clippy_lints/src/loops/while_let_on_iterator.rs | 2 +- clippy_lints/src/manual_strip.rs | 2 +- clippy_lints/src/matches/match_on_vec_items.rs | 4 ++-- clippy_lints/src/methods/filter_next.rs | 2 +- clippy_lints/src/methods/iter_next_slice.rs | 2 +- clippy_lints/src/mixed_read_write_in_expression.rs | 2 +- clippy_lints/src/needless_bool.rs | 2 +- clippy_lints/src/no_effect.rs | 4 ++-- clippy_lints/src/non_copy_const.rs | 2 +- clippy_lints/src/ptr.rs | 2 +- clippy_lints/src/redundant_slicing.rs | 2 +- clippy_lints/src/strings.rs | 4 ++-- clippy_lints/src/suspicious_operation_groupings.rs | 2 +- clippy_lints/src/swap.rs | 4 ++-- clippy_lints/src/temporary_assignment.rs | 2 +- clippy_lints/src/tuple_array_conversions.rs | 4 ++-- clippy_lints/src/utils/author.rs | 2 +- clippy_lints/src/vec_init_then_push.rs | 2 +- clippy_utils/src/ast_utils.rs | 2 +- clippy_utils/src/check_proc_macro.rs | 2 +- clippy_utils/src/consts.rs | 2 +- clippy_utils/src/eager_or_lazy.rs | 2 +- clippy_utils/src/hir_utils.rs | 4 ++-- clippy_utils/src/lib.rs | 4 ++-- clippy_utils/src/ty/type_certainty/mod.rs | 2 +- clippy_utils/src/visitors.rs | 4 ++-- 33 files changed, 42 insertions(+), 42 deletions(-) diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index a5ec979ccd978..137bd9c945101 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -800,7 +800,7 @@ fn in_postfix_position<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> boo && parent.span.ctxt() == e.span.ctxt() { match parent.kind { - ExprKind::Call(child, _) | ExprKind::MethodCall(_, child, _, _) | ExprKind::Index(child, _) + ExprKind::Call(child, _) | ExprKind::MethodCall(_, child, _, _) | ExprKind::Index(child, _, _) if child.hir_id == e.hir_id => true, ExprKind::Field(_, _) | ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar) => true, _ => false, diff --git a/clippy_lints/src/functions/must_use.rs b/clippy_lints/src/functions/must_use.rs index 83445c7a0ca5c..57df5683c9d39 100644 --- a/clippy_lints/src/functions/must_use.rs +++ b/clippy_lints/src/functions/must_use.rs @@ -221,7 +221,7 @@ fn is_mutated_static(e: &hir::Expr<'_>) -> bool { match e.kind { Path(QPath::Resolved(_, path)) => !matches!(path.res, Res::Local(_)), Path(_) => true, - Field(inner, _) | Index(inner, _) => is_mutated_static(inner), + Field(inner, _) | Index(inner, _, _) => is_mutated_static(inner), _ => false, } } diff --git a/clippy_lints/src/index_refutable_slice.rs b/clippy_lints/src/index_refutable_slice.rs index 01a7c497cbe70..f507f45d5bb9e 100644 --- a/clippy_lints/src/index_refutable_slice.rs +++ b/clippy_lints/src/index_refutable_slice.rs @@ -254,7 +254,7 @@ impl<'a, 'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'a, 'tcx> { // Checking for slice indexing let parent_id = map.parent_id(expr.hir_id); if let Some(hir::Node::Expr(parent_expr)) = map.find(parent_id); - if let hir::ExprKind::Index(_, index_expr) = parent_expr.kind; + if let hir::ExprKind::Index(_, index_expr, _) = parent_expr.kind; if let Some(Constant::Int(index_value)) = constant(cx, cx.typeck_results(), index_expr); if let Ok(index_value) = index_value.try_into(); if index_value < max_suggested_slice; diff --git a/clippy_lints/src/indexing_slicing.rs b/clippy_lints/src/indexing_slicing.rs index 22c14d9b04dd1..4f4f571773fbf 100644 --- a/clippy_lints/src/indexing_slicing.rs +++ b/clippy_lints/src/indexing_slicing.rs @@ -103,7 +103,7 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing { return; } - if let ExprKind::Index(array, index) = &expr.kind { + if let ExprKind::Index(array, index, _) = &expr.kind { let note = "the suggestion might not be applicable in constant blocks"; let ty = cx.typeck_results().expr_ty(array).peel_refs(); if let Some(range) = higher::Range::hir(index) { diff --git a/clippy_lints/src/loops/manual_memcpy.rs b/clippy_lints/src/loops/manual_memcpy.rs index 7d1f8ef29c817..d3fd0e8639e97 100644 --- a/clippy_lints/src/loops/manual_memcpy.rs +++ b/clippy_lints/src/loops/manual_memcpy.rs @@ -60,8 +60,8 @@ pub(super) fn check<'tcx>( o.and_then(|(lhs, rhs)| { let rhs = fetch_cloned_expr(rhs); if_chain! { - if let ExprKind::Index(base_left, idx_left) = lhs.kind; - if let ExprKind::Index(base_right, idx_right) = rhs.kind; + if let ExprKind::Index(base_left, idx_left, _) = lhs.kind; + if let ExprKind::Index(base_right, idx_right, _) = rhs.kind; if let Some(ty) = get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_left)); if get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_right)).is_some(); if let Some((start_left, offset_left)) = get_details_from_idx(cx, idx_left, &starts); diff --git a/clippy_lints/src/loops/needless_range_loop.rs b/clippy_lints/src/loops/needless_range_loop.rs index 2c20e9e86933d..c4af46b8fd3a7 100644 --- a/clippy_lints/src/loops/needless_range_loop.rs +++ b/clippy_lints/src/loops/needless_range_loop.rs @@ -319,7 +319,7 @@ impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> { if_chain! { // an index op - if let ExprKind::Index(seqexpr, idx) = expr.kind; + if let ExprKind::Index(seqexpr, idx, _) = expr.kind; if !self.check(idx, seqexpr, expr); then { return; diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index 7da9121fbb383..dd77c69ef88a7 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -162,7 +162,7 @@ fn never_loop_expr<'tcx>( ExprKind::Binary(_, e1, e2) | ExprKind::Assign(e1, e2, _) | ExprKind::AssignOp(_, e1, e2) - | ExprKind::Index(e1, e2) => never_loop_expr_all(cx, &mut [e1, e2].iter().copied(), ignore_ids, main_loop_id), + | ExprKind::Index(e1, e2, _) => never_loop_expr_all(cx, &mut [e1, e2].iter().copied(), ignore_ids, main_loop_id), ExprKind::Loop(b, _, _, _) => { // Break can come from the inner loop so remove them. absorb_break(never_loop_block(cx, b, ignore_ids, main_loop_id)) diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs index 8019f906aca4b..5153070cfe66b 100644 --- a/clippy_lints/src/loops/while_let_on_iterator.rs +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -113,7 +113,7 @@ fn try_parse_iter_expr(cx: &LateContext<'_>, mut e: &Expr<'_>) -> Option { + ExprKind::Index(base, idx, _) if !idx.can_have_side_effects() => { can_move = false; fields.clear(); e = base; diff --git a/clippy_lints/src/manual_strip.rs b/clippy_lints/src/manual_strip.rs index 2b9def1a6884a..201bb56efcddb 100644 --- a/clippy_lints/src/manual_strip.rs +++ b/clippy_lints/src/manual_strip.rs @@ -204,7 +204,7 @@ fn find_stripping<'tcx>( if_chain! { if is_ref_str(self.cx, ex); let unref = peel_ref(ex); - if let ExprKind::Index(indexed, index) = &unref.kind; + if let ExprKind::Index(indexed, index, _) = &unref.kind; if let Some(higher::Range { start, end, .. }) = higher::Range::hir(index); if let ExprKind::Path(path) = &indexed.kind; if self.cx.qpath_res(path, ex.hir_id) == self.target; diff --git a/clippy_lints/src/matches/match_on_vec_items.rs b/clippy_lints/src/matches/match_on_vec_items.rs index 89dcac4d8494c..bd53ebd48c887 100644 --- a/clippy_lints/src/matches/match_on_vec_items.rs +++ b/clippy_lints/src/matches/match_on_vec_items.rs @@ -12,7 +12,7 @@ use super::MATCH_ON_VEC_ITEMS; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'_>) { if_chain! { if let Some(idx_expr) = is_vec_indexing(cx, scrutinee); - if let ExprKind::Index(vec, idx) = idx_expr.kind; + if let ExprKind::Index(vec, idx, _) = idx_expr.kind; then { // FIXME: could be improved to suggest surrounding every pattern with Some(_), @@ -36,7 +36,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'_>) { fn is_vec_indexing<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { if_chain! { - if let ExprKind::Index(array, index) = expr.kind; + if let ExprKind::Index(array, index, _) = expr.kind; if is_vector(cx, array); if !is_full_range(cx, index); diff --git a/clippy_lints/src/methods/filter_next.rs b/clippy_lints/src/methods/filter_next.rs index ce7f9997b5a63..ac7bc9bcca47f 100644 --- a/clippy_lints/src/methods/filter_next.rs +++ b/clippy_lints/src/methods/filter_next.rs @@ -12,7 +12,7 @@ use super::FILTER_NEXT; fn path_to_local(expr: &hir::Expr<'_>) -> Option { match expr.kind { hir::ExprKind::Field(f, _) => path_to_local(f), - hir::ExprKind::Index(recv, _) => path_to_local(recv), + hir::ExprKind::Index(recv, _, _) => path_to_local(recv), hir::ExprKind::Path(hir::QPath::Resolved( _, hir::Path { diff --git a/clippy_lints/src/methods/iter_next_slice.rs b/clippy_lints/src/methods/iter_next_slice.rs index e2029da8081f4..8f885e9f729bb 100644 --- a/clippy_lints/src/methods/iter_next_slice.rs +++ b/clippy_lints/src/methods/iter_next_slice.rs @@ -27,7 +27,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, cal if derefs_to_slice(cx, caller_expr, cx.typeck_results().expr_ty(caller_expr)).is_some() { // caller is a Slice if_chain! { - if let hir::ExprKind::Index(caller_var, index_expr) = &caller_expr.kind; + if let hir::ExprKind::Index(caller_var, index_expr, _) = &caller_expr.kind; if let Some(higher::Range { start: Some(start_expr), end: None, limits: ast::RangeLimits::HalfOpen }) = higher::Range::hir(index_expr); if let hir::ExprKind::Lit(start_lit) = &start_expr.kind; diff --git a/clippy_lints/src/mixed_read_write_in_expression.rs b/clippy_lints/src/mixed_read_write_in_expression.rs index 57ec3a1f1e63c..367cd6bd41337 100644 --- a/clippy_lints/src/mixed_read_write_in_expression.rs +++ b/clippy_lints/src/mixed_read_write_in_expression.rs @@ -239,7 +239,7 @@ fn check_expr<'tcx>(vis: &mut ReadVisitor<'_, 'tcx>, expr: &'tcx Expr<'_>) -> St | ExprKind::MethodCall(..) | ExprKind::Call(_, _) | ExprKind::Assign(..) - | ExprKind::Index(_, _) + | ExprKind::Index(..) | ExprKind::Repeat(_, _) | ExprKind::Struct(_, _, _) => { walk_expr(vis, expr); diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index 46457400abcbe..f6b87b071b944 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -119,7 +119,7 @@ fn condition_needs_parentheses(e: &Expr<'_>) -> bool { | ExprKind::Call(i, _) | ExprKind::Cast(i, _) | ExprKind::Type(i, _) - | ExprKind::Index(i, _) = inner.kind + | ExprKind::Index(i, _, _) = inner.kind { if matches!( i.kind, diff --git a/clippy_lints/src/no_effect.rs b/clippy_lints/src/no_effect.rs index e6d3e72d1e621..5f2a324b05fb9 100644 --- a/clippy_lints/src/no_effect.rs +++ b/clippy_lints/src/no_effect.rs @@ -160,7 +160,7 @@ fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { match peel_blocks(expr).kind { ExprKind::Lit(..) | ExprKind::Closure { .. } => true, ExprKind::Path(..) => !has_drop(cx, cx.typeck_results().expr_ty(expr)), - ExprKind::Index(a, b) | ExprKind::Binary(_, a, b) => has_no_effect(cx, a) && has_no_effect(cx, b), + ExprKind::Index(a, b, _) | ExprKind::Binary(_, a, b) => has_no_effect(cx, a) && has_no_effect(cx, b), ExprKind::Array(v) | ExprKind::Tup(v) => v.iter().all(|val| has_no_effect(cx, val)), ExprKind::Repeat(inner, _) | ExprKind::Cast(inner, _) @@ -263,7 +263,7 @@ fn reduce_expression<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option Some(vec![a, b]), + ExprKind::Index(a, b, _) => Some(vec![a, b]), ExprKind::Binary(ref binop, a, b) if binop.node != BinOpKind::And && binop.node != BinOpKind::Or => { Some(vec![a, b]) }, diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index a70692d8ff864..243192385c256 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -438,7 +438,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst { dereferenced_expr = parent_expr; }, - ExprKind::Index(e, _) if ptr::eq(&**e, cur_expr) => { + ExprKind::Index(e, _, _) if ptr::eq(&**e, cur_expr) => { // `e[i]` => desugared to `*Index::index(&e, i)`, // meaning `e` must be referenced. // no need to go further up since a method call is involved now. diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 42299d8d42f54..8009b00b4b9cb 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -695,7 +695,7 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args: } }, // Indexing is fine for currently supported types. - ExprKind::Index(e, _) if e.hir_id == child_id => (), + ExprKind::Index(e, _, _) if e.hir_id == child_id => (), _ => set_skip_flag(), }, _ => set_skip_flag(), diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index 8277ce724d428..4abfa0fc35c64 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -81,7 +81,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { if_chain! { if let ExprKind::AddrOf(BorrowKind::Ref, mutability, addressee) = expr.kind; if addressee.span.ctxt() == ctxt; - if let ExprKind::Index(indexed, range) = addressee.kind; + if let ExprKind::Index(indexed, range, _) = addressee.kind; if is_type_lang_item(cx, cx.typeck_results().expr_ty_adjusted(range), LangItem::RangeFull); then { let (expr_ty, expr_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(expr)); diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 4d45091f4b5b7..76f463fff7dcf 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -190,7 +190,7 @@ impl<'tcx> LateLintPass<'tcx> for StringAdd { ); } }, - ExprKind::Index(target, _idx) => { + ExprKind::Index(target, _idx, _) => { let e_ty = cx.typeck_results().expr_ty(target).peel_refs(); if e_ty.is_str() || is_type_lang_item(cx, e_ty, LangItem::String) { span_lint( @@ -262,7 +262,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes { // Find string::as_bytes if let ExprKind::AddrOf(BorrowKind::Ref, _, args) = args[0].kind; - if let ExprKind::Index(left, right) = args.kind; + if let ExprKind::Index(left, right, _) = args.kind; let (method_names, expressions, _) = method_calls(left, 1); if method_names.len() == 1; if expressions.len() == 1; diff --git a/clippy_lints/src/suspicious_operation_groupings.rs b/clippy_lints/src/suspicious_operation_groupings.rs index e2cdc48b583c8..23d6e2a845fd6 100644 --- a/clippy_lints/src/suspicious_operation_groupings.rs +++ b/clippy_lints/src/suspicious_operation_groupings.rs @@ -572,7 +572,7 @@ fn ident_difference_expr_with_base_location( | (AddrOf(_, _, _), AddrOf(_, _, _)) | (Path(_, _), Path(_, _)) | (Range(_, _, _), Range(_, _, _)) - | (Index(_, _), Index(_, _)) + | (Index(_, _, _), Index(_, _, _)) | (Field(_, _), Field(_, _)) | (AssignOp(_, _, _), AssignOp(_, _, _)) | (Assign(_, _, _), Assign(_, _, _)) diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index 98158ed0aee88..548fabb8b736f 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -86,8 +86,8 @@ fn generate_swap_warning(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>, spa let mut applicability = Applicability::MachineApplicable; if !can_mut_borrow_both(cx, e1, e2) { - if let ExprKind::Index(lhs1, idx1) = e1.kind - && let ExprKind::Index(lhs2, idx2) = e2.kind + if let ExprKind::Index(lhs1, idx1, _) = e1.kind + && let ExprKind::Index(lhs2, idx2, _) = e2.kind && eq_expr_value(cx, lhs1, lhs2) && e1.span.ctxt() == ctxt && e2.span.ctxt() == ctxt diff --git a/clippy_lints/src/temporary_assignment.rs b/clippy_lints/src/temporary_assignment.rs index 3766b8f8ed10d..b6b653f6610d3 100644 --- a/clippy_lints/src/temporary_assignment.rs +++ b/clippy_lints/src/temporary_assignment.rs @@ -33,7 +33,7 @@ impl<'tcx> LateLintPass<'tcx> for TemporaryAssignment { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let ExprKind::Assign(target, ..) = &expr.kind { let mut base = target; - while let ExprKind::Field(f, _) | ExprKind::Index(f, _) = &base.kind { + while let ExprKind::Field(f, _) | ExprKind::Index(f, _, _) = &base.kind { base = f; } if is_temporary(base) && !is_adjusted(cx, base) { diff --git a/clippy_lints/src/tuple_array_conversions.rs b/clippy_lints/src/tuple_array_conversions.rs index 7eec69820924d..78ad52d8a8794 100644 --- a/clippy_lints/src/tuple_array_conversions.rs +++ b/clippy_lints/src/tuple_array_conversions.rs @@ -103,11 +103,11 @@ fn check_tuple<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, elements: & // Fix #11100 && tys.iter().all_equal() && let Some(locals) = (match first.kind { - ExprKind::Index(_, _) => elements + ExprKind::Index(..) => elements .iter() .enumerate() .map(|(i, i_expr)| -> Option<&'tcx Expr<'tcx>> { - if let ExprKind::Index(lhs, index) = i_expr.kind + if let ExprKind::Index(lhs, index, _) = i_expr.kind && let ExprKind::Lit(lit) = index.kind && let LitKind::Int(val, _) = lit.node { diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 6b51974d739af..f02c33cc6743e 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -526,7 +526,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { self.ident(field_name); self.expr(object); }, - ExprKind::Index(object, index) => { + ExprKind::Index(object, index, _) => { bind!(self, object, index); kind!("Index({object}, {index})"); self.expr(object); diff --git a/clippy_lints/src/vec_init_then_push.rs b/clippy_lints/src/vec_init_then_push.rs index 1d4fc24eb6c27..3fa51216c7737 100644 --- a/clippy_lints/src/vec_init_then_push.rs +++ b/clippy_lints/src/vec_init_then_push.rs @@ -88,7 +88,7 @@ impl VecPushSearcher { let mut last_place = parent; while let Some(parent) = get_parent_expr(cx, last_place) { if matches!(parent.kind, ExprKind::Unary(UnOp::Deref, _) | ExprKind::Field(..)) - || matches!(parent.kind, ExprKind::Index(e, _) if e.hir_id == last_place.hir_id) + || matches!(parent.kind, ExprKind::Index(e, _, _) if e.hir_id == last_place.hir_id) { last_place = parent; } else { diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index 7e42924603a41..2d0d6f559ad67 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -178,7 +178,7 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool { (Yield(l), Yield(r)) | (Ret(l), Ret(r)) => eq_expr_opt(l, r), (Break(ll, le), Break(rl, re)) => eq_label(ll, rl) && eq_expr_opt(le, re), (Continue(ll), Continue(rl)) => eq_label(ll, rl), - (Assign(l1, l2, _), Assign(r1, r2, _)) | (Index(l1, l2), Index(r1, r2)) => eq_expr(l1, r1) && eq_expr(l2, r2), + (Assign(l1, l2, _), Assign(r1, r2, _)) | (Index(l1, l2, _), Index(r1, r2, _)) => eq_expr(l1, r1) && eq_expr(l2, r2), (AssignOp(lo, lp, lv), AssignOp(ro, rp, rv)) => lo.node == ro.node && eq_expr(lp, rp) && eq_expr(lv, rv), (Field(lp, lf), Field(rp, rf)) => eq_id(*lf, *rf) && eq_expr(lp, rp), (Match(ls, la), Match(rs, ra)) => eq_expr(ls, rs) && over(la, ra, eq_arm), diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs index 89f8a65c5ae0d..60fab1ec41aeb 100644 --- a/clippy_utils/src/check_proc_macro.rs +++ b/clippy_utils/src/check_proc_macro.rs @@ -163,7 +163,7 @@ fn expr_search_pat(tcx: TyCtxt<'_>, e: &Expr<'_>) -> (Pat, Pat) { ) => (Pat::Str("unsafe"), Pat::Str("}")), ExprKind::Block(_, None) => (Pat::Str("{"), Pat::Str("}")), ExprKind::Field(e, name) => (expr_search_pat(tcx, e).0, Pat::Sym(name.name)), - ExprKind::Index(e, _) => (expr_search_pat(tcx, e).0, Pat::Str("]")), + ExprKind::Index(e, _, _) => (expr_search_pat(tcx, e).0, Pat::Str("]")), ExprKind::Path(ref path) => qpath_search_pat(path), ExprKind::AddrOf(_, _, e) => (Pat::Str("&"), expr_search_pat(tcx, e).1), ExprKind::Break(Destination { label: None, .. }, None) => (Pat::Str("break"), Pat::Str("break")), diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index f19e09a18ec56..d38e3f1ae76bd 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -394,7 +394,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { } } }, - ExprKind::Index(arr, index) => self.index(arr, index), + ExprKind::Index(arr, index, _) => self.index(arr, index), ExprKind::AddrOf(_, _, inner) => self.expr(inner).map(|r| Constant::Ref(Box::new(r))), ExprKind::Field(local_expr, ref field) => { let result = self.expr(local_expr); diff --git a/clippy_utils/src/eager_or_lazy.rs b/clippy_utils/src/eager_or_lazy.rs index 3c969572b8e4f..0bcefba75a7b8 100644 --- a/clippy_utils/src/eager_or_lazy.rs +++ b/clippy_utils/src/eager_or_lazy.rs @@ -185,7 +185,7 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS .type_dependent_def_id(e.hir_id) .map_or(Lazy, |id| fn_eagerness(self.cx, id, name.ident.name, true)); }, - ExprKind::Index(_, e) => { + ExprKind::Index(_, e, _) => { let ty = self.cx.typeck_results().expr_ty_adjusted(e); if is_copy(self.cx, ty) && !ty.is_ref() { self.eagerness |= NoChange; diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 85b3b005f93df..fdc35cd4ddf8b 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -299,7 +299,7 @@ impl HirEqInterExpr<'_, '_, '_> { (&ExprKind::Field(l_f_exp, ref l_f_ident), &ExprKind::Field(r_f_exp, ref r_f_ident)) => { l_f_ident.name == r_f_ident.name && self.eq_expr(l_f_exp, r_f_exp) }, - (&ExprKind::Index(la, li), &ExprKind::Index(ra, ri)) => self.eq_expr(la, ra) && self.eq_expr(li, ri), + (&ExprKind::Index(la, li, _), &ExprKind::Index(ra, ri, _)) => self.eq_expr(la, ra) && self.eq_expr(li, ri), (&ExprKind::If(lc, lt, ref le), &ExprKind::If(rc, rt, ref re)) => { self.eq_expr(lc, rc) && self.eq_expr(lt, rt) && both(le, re, |l, r| self.eq_expr(l, r)) }, @@ -730,7 +730,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_expr(e); self.hash_name(f.name); }, - ExprKind::Index(a, i) => { + ExprKind::Index(a, i, _) => { self.hash_expr(a); self.hash_expr(i); }, diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index cf30930b76eac..4cd8a8c3325c1 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -735,7 +735,7 @@ fn projection_stack<'a, 'hir>(mut e: &'a Expr<'hir>) -> (Vec<&'a Expr<'hir>>, &' let mut result = vec![]; let root = loop { match e.kind { - ExprKind::Index(ep, _) | ExprKind::Field(ep, _) => { + ExprKind::Index(ep, _, _) | ExprKind::Field(ep, _) => { result.push(e); e = ep; }, @@ -782,7 +782,7 @@ pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) - return true; } }, - (ExprKind::Index(_, i1), ExprKind::Index(_, i2)) => { + (ExprKind::Index(_, i1, _), ExprKind::Index(_, i2, _)) => { if !eq_expr_value(cx, i1, i2) { return false; } diff --git a/clippy_utils/src/ty/type_certainty/mod.rs b/clippy_utils/src/ty/type_certainty/mod.rs index 45b5483e323d5..0669ea72eb3e2 100644 --- a/clippy_utils/src/ty/type_certainty/mod.rs +++ b/clippy_utils/src/ty/type_certainty/mod.rs @@ -31,7 +31,7 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty { let certainty = match &expr.kind { ExprKind::Unary(_, expr) | ExprKind::Field(expr, _) - | ExprKind::Index(expr, _) + | ExprKind::Index(expr, _, _) | ExprKind::AddrOf(_, _, expr) => expr_type_certainty(cx, expr), ExprKind::Array(exprs) => join(exprs.iter().map(|expr| expr_type_certainty(cx, expr))), diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index 09f447b27eb4f..f83988a6e325a 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -329,7 +329,7 @@ pub fn is_const_evaluatable<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> && self.cx.typeck_results().expr_ty(rhs).peel_refs().is_primitive_ty() => {}, ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_ref() => (), ExprKind::Unary(_, e) if self.cx.typeck_results().expr_ty(e).peel_refs().is_primitive_ty() => (), - ExprKind::Index(base, _) + ExprKind::Index(base, _, _) if matches!( self.cx.typeck_results().expr_ty(base).peel_refs().kind(), ty::Slice(_) | ty::Array(..) @@ -629,7 +629,7 @@ pub fn for_each_unconsumed_temporary<'tcx, B>( helper(typeck, true, arg, f)?; } }, - ExprKind::Index(borrowed, consumed) + ExprKind::Index(borrowed, consumed, _) | ExprKind::Assign(borrowed, consumed, _) | ExprKind::AssignOp(_, borrowed, consumed) => { helper(typeck, false, borrowed, f)?; From e5b0483c852ed801b18fd1a43afa471f13937a47 Mon Sep 17 00:00:00 2001 From: lengyijun Date: Sat, 5 Aug 2023 12:39:31 +0800 Subject: [PATCH 25/49] Small code style adjustments --- clippy_lints/src/methods/mod.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 17483656cec02..42756b27d014d 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -4020,10 +4020,8 @@ impl Methods { } }, ("last", []) => { - if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) { - if let ("cloned", []) = (name2, args2) { - iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); - } + if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) { + iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); } }, ("lock", []) => { @@ -4127,10 +4125,8 @@ impl Methods { ("skip", [arg]) => { iter_skip_zero::check(cx, expr, arg); - if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) { - if let ("cloned", []) = (name2, args2) { - iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); - } + if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) { + iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); } } ("sort", []) => { @@ -4155,10 +4151,8 @@ impl Methods { }, ("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg), ("take", [_arg]) => { - if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) { - if let ("cloned", []) = (name2, args2) { - iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); - } + if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) { + iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); } }, ("take", []) => needless_option_take::check(cx, expr, recv), From ab1281f54a94bf59d0fab2941c0c642158fae122 Mon Sep 17 00:00:00 2001 From: Morten Lohne Date: Mon, 29 May 2023 21:39:05 +0200 Subject: [PATCH 26/49] fix: Make ConstEvalLateContext::new() public, to match the 'constant_context()' function that it replaced --- clippy_utils/src/consts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 061086c4fc208..63dfd2145b84f 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -329,7 +329,7 @@ pub struct ConstEvalLateContext<'a, 'tcx> { } impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { - fn new(lcx: &'a LateContext<'tcx>, typeck_results: &'a ty::TypeckResults<'tcx>) -> Self { + pub fn new(lcx: &'a LateContext<'tcx>, typeck_results: &'a ty::TypeckResults<'tcx>) -> Self { Self { lcx, typeck_results, From 046d3df35eccafd73f84c47bb3b45a3cec33bcf4 Mon Sep 17 00:00:00 2001 From: Morten Lohne Date: Mon, 29 May 2023 21:40:28 +0200 Subject: [PATCH 27/49] New lints: `impossible_double_const_comparisons` and `ineffective_double_const_comparisons` --- CHANGELOG.md | 2 + clippy_lints/src/declared_lints.rs | 2 + .../src/operators/double_const_comparison.rs | 204 ++++++++++++++++++ clippy_lints/src/operators/mod.rs | 41 ++++ tests/ui/double_const_comparisons.rs | 52 +++++ tests/ui/double_const_comparisons.stderr | 180 ++++++++++++++++ tests/ui/range_contains.fixed | 2 + tests/ui/range_contains.rs | 2 + tests/ui/range_contains.stderr | 42 ++-- 9 files changed, 506 insertions(+), 21 deletions(-) create mode 100644 clippy_lints/src/operators/double_const_comparison.rs create mode 100644 tests/ui/double_const_comparisons.rs create mode 100644 tests/ui/double_const_comparisons.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ef2255597952..3f9ec3a4df909 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4897,6 +4897,7 @@ Released 2018-09-13 [`implicit_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_return [`implicit_saturating_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_saturating_add [`implicit_saturating_sub`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_saturating_sub +[`impossible_double_const_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#impossible_double_const_comparisons [`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops [`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping [`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor @@ -4905,6 +4906,7 @@ Released 2018-09-13 [`index_refutable_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice [`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing [`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask +[`ineffective_double_const_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_double_const_comparisons [`inefficient_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_to_string [`infallible_destructuring_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#infallible_destructuring_match [`infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#infinite_iter diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index ea66c760a2f86..897e6c7bd107b 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -518,7 +518,9 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::operators::FLOAT_CMP_CONST_INFO, crate::operators::FLOAT_EQUALITY_WITHOUT_ABS_INFO, crate::operators::IDENTITY_OP_INFO, + crate::operators::IMPOSSIBLE_DOUBLE_CONST_COMPARISONS_INFO, crate::operators::INEFFECTIVE_BIT_MASK_INFO, + crate::operators::INEFFECTIVE_DOUBLE_CONST_COMPARISONS_INFO, crate::operators::INTEGER_DIVISION_INFO, crate::operators::MISREFACTORED_ASSIGN_OP_INFO, crate::operators::MODULO_ARITHMETIC_INFO, diff --git a/clippy_lints/src/operators/double_const_comparison.rs b/clippy_lints/src/operators/double_const_comparison.rs new file mode 100644 index 0000000000000..d029a3d4ebe28 --- /dev/null +++ b/clippy_lints/src/operators/double_const_comparison.rs @@ -0,0 +1,204 @@ +#![allow(clippy::match_same_arms)] + +use std::cmp::Ordering; + +use clippy_utils::consts; +use clippy_utils::consts::{ConstEvalLateContext, Constant}; +use if_chain::if_chain; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{layout::HasTyCtxt, Ty, TypeckResults}; +use rustc_span::source_map::{Span, Spanned}; + +use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::source::snippet; +use clippy_utils::SpanlessEq; + +use super::IMPOSSIBLE_DOUBLE_CONST_COMPARISONS; +use super::INEFFECTIVE_DOUBLE_CONST_COMPARISONS; + +// Extract a comparison between a const and non-const +// Flip yoda conditionals, turnings expressions like `42 < x` into `x > 42` +fn comparison_to_const<'tcx>( + ctx: &mut ConstEvalLateContext<'_, 'tcx>, + typeck: &TypeckResults<'tcx>, + expr: &'tcx Expr<'tcx>, +) -> Option<(CmpOp, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Constant, Ty<'tcx>)> { + if_chain! { + if let ExprKind::Binary(operator, left, right) = expr.kind; + if let Ok(cmp_op) = CmpOp::try_from(operator.node); + then { + match (ctx.expr(left), ctx.expr(right)) { + (Some(_), Some(_)) => None, + (_, Some(con)) => Some((cmp_op, left, right, con, typeck.expr_ty(right))), + (Some(con), _) => Some((cmp_op.reverse(), right, left, con, typeck.expr_ty(left))), + _ => None, + } + } else { + None + } + } +} + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + and_op: Spanned, + left_cond: &'tcx Expr<'tcx>, + right_cond: &'tcx Expr<'tcx>, + span: Span, +) { + if_chain! { + // Ensure that the binary operator is && + if and_op.node == BinOpKind::And; + + let typeck_results = cx.typeck_results(); + let mut const_context = consts::ConstEvalLateContext::new(cx, typeck_results); + + // Check that both operands to '&&' compare a non-literal to a literal + if let Some((left_cmp_op, left_expr, left_const_expr, left_const, left_type)) = + comparison_to_const(&mut const_context, typeck_results, left_cond); + if let Some((right_cmp_op, right_expr, right_const_expr, right_const, right_type)) = + comparison_to_const(&mut const_context, typeck_results, right_cond); + + if left_type == right_type; + + // Check that the same expression is compared in both comparisons + if SpanlessEq::new(cx).eq_expr(left_expr, right_expr); + + if !left_expr.can_have_side_effects(); + + // Compare the two constant expressions + if let Some(ordering) = Constant::partial_cmp(cx.tcx(), left_type, &left_const, &right_const); + + // Rule out the `x >= 42 && x <= 42` corner case immediately + // Mostly to simplify the implementation, but it is also covered by `clippy::double_comparisons` + if !matches!( + (&left_cmp_op, &right_cmp_op, ordering), + (CmpOp::Le | CmpOp::Ge, CmpOp::Le | CmpOp::Ge, Ordering::Equal) + ); + + then { + if left_cmp_op.direction() == right_cmp_op.direction() { + let lhs_str = snippet(cx, left_cond.span, ""); + let rhs_str = snippet(cx, right_cond.span, ""); + // We already know that either side of `&&` has no effect, + // but emit a different error message depending on which side it is + if left_side_is_useless(left_cmp_op, ordering) { + span_lint_and_note( + cx, + INEFFECTIVE_DOUBLE_CONST_COMPARISONS, + span, + "left-hand side of `&&` operator has no effect", + Some(left_cond.span.until(right_cond.span)), + &format!("`if `{rhs_str}` evaluates to true, {lhs_str}` will always evaluate to true as well"), + ); + } else { + span_lint_and_note( + cx, + INEFFECTIVE_DOUBLE_CONST_COMPARISONS, + span, + "right-hand side of `&&` operator has no effect", + Some(and_op.span.to(right_cond.span)), + &format!("`if `{lhs_str}` evaluates to true, {rhs_str}` will always evaluate to true as well"), + ); + } + // We could autofix this error but choose not to, + // because code triggering this lint probably not behaving correctly in the first place + } + else if !comparison_is_possible(left_cmp_op.direction(), ordering) { + let expr_str = snippet(cx, left_expr.span, ""); + let lhs_str = snippet(cx, left_const_expr.span, ""); + let rhs_str = snippet(cx, right_const_expr.span, ""); + let note = match ordering { + Ordering::Less => format!("since `{lhs_str}` < `{rhs_str}`, the expression evaluates to false for any value of `{expr_str}`"), + Ordering::Equal => format!("`{expr_str}` cannot simultaneously be greater than and less than `{lhs_str}`"), + Ordering::Greater => format!("since `{lhs_str}` > `{rhs_str}`, the expression evaluates to false for any value of `{expr_str}`"), + }; + span_lint_and_note( + cx, + IMPOSSIBLE_DOUBLE_CONST_COMPARISONS, + span, + "boolean expression will never evaluate to 'true'", + None, + ¬e, + ); + }; + } + } +} + +fn left_side_is_useless(left_cmp_op: CmpOp, ordering: Ordering) -> bool { + // Special-case for equal constants with an inclusive comparison + if ordering == Ordering::Equal { + match left_cmp_op { + CmpOp::Lt | CmpOp::Gt => false, + CmpOp::Le | CmpOp::Ge => true, + } + } else { + match (left_cmp_op.direction(), ordering) { + (CmpOpDirection::Lesser, Ordering::Less) => false, + (CmpOpDirection::Lesser, Ordering::Equal) => false, + (CmpOpDirection::Lesser, Ordering::Greater) => true, + (CmpOpDirection::Greater, Ordering::Less) => true, + (CmpOpDirection::Greater, Ordering::Equal) => false, + (CmpOpDirection::Greater, Ordering::Greater) => false, + } + } +} + +fn comparison_is_possible(left_cmp_direction: CmpOpDirection, ordering: Ordering) -> bool { + match (left_cmp_direction, ordering) { + (CmpOpDirection::Lesser, Ordering::Less | Ordering::Equal) => false, + (CmpOpDirection::Lesser, Ordering::Greater) => true, + (CmpOpDirection::Greater, Ordering::Greater | Ordering::Equal) => false, + (CmpOpDirection::Greater, Ordering::Less) => true, + } +} + +#[derive(PartialEq, Eq, Clone, Copy)] +enum CmpOpDirection { + Lesser, + Greater, +} + +#[derive(Clone, Copy)] +enum CmpOp { + Lt, + Le, + Ge, + Gt, +} + +impl CmpOp { + fn reverse(self) -> Self { + match self { + CmpOp::Lt => CmpOp::Gt, + CmpOp::Le => CmpOp::Ge, + CmpOp::Ge => CmpOp::Le, + CmpOp::Gt => CmpOp::Lt, + } + } + + fn direction(self) -> CmpOpDirection { + match self { + CmpOp::Lt => CmpOpDirection::Lesser, + CmpOp::Le => CmpOpDirection::Lesser, + CmpOp::Ge => CmpOpDirection::Greater, + CmpOp::Gt => CmpOpDirection::Greater, + } + } +} + +impl TryFrom for CmpOp { + type Error = (); + + fn try_from(bin_op: BinOpKind) -> Result { + match bin_op { + BinOpKind::Lt => Ok(CmpOp::Lt), + BinOpKind::Le => Ok(CmpOp::Le), + BinOpKind::Ge => Ok(CmpOp::Ge), + BinOpKind::Gt => Ok(CmpOp::Gt), + _ => Err(()), + } + } +} diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index 2cf15adda01a2..41efcef86c8af 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -3,6 +3,7 @@ mod assign_op_pattern; mod bit_mask; mod cmp_owned; mod double_comparison; +mod double_const_comparison; mod duration_subsec; mod eq_op; mod erasing_op; @@ -298,6 +299,43 @@ declare_clippy_lint! { "unnecessary double comparisons that can be simplified" } +declare_clippy_lint! { + /// ### What it does + /// Checks for double comparisons that can never succeed + /// + /// ### Why is this bad? + /// The whole expression can be replaced by `false`, + /// which is probably not the programmer's intention + /// + /// ### Example + /// ```rust + /// status_code <= 400 && status_code > 500; + /// ``` + #[clippy::version = "1.71.0"] + pub IMPOSSIBLE_DOUBLE_CONST_COMPARISONS, + correctness, + "default lint description" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for ineffective double comparisons against constants + /// + /// ### Why is this bad? + /// Only one of the comparisons has any effect on the result + /// The programmer probably intended to flip one of the comparison operators, + /// or compare a different value entirely + /// + /// ### Example + /// ```rust + /// status_code <= 400 && status_code < 500; + /// ``` + #[clippy::version = "1.71.0"] + pub INEFFECTIVE_DOUBLE_CONST_COMPARISONS, + correctness, + "default lint description" +} + declare_clippy_lint! { /// ### What it does /// Checks for calculation of subsecond microseconds or milliseconds @@ -742,6 +780,8 @@ impl_lint_pass!(Operators => [ INEFFECTIVE_BIT_MASK, VERBOSE_BIT_MASK, DOUBLE_COMPARISONS, + IMPOSSIBLE_DOUBLE_CONST_COMPARISONS, + INEFFECTIVE_DOUBLE_CONST_COMPARISONS, DURATION_SUBSEC, EQ_OP, OP_REF, @@ -786,6 +826,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators { bit_mask::check(cx, e, op.node, lhs, rhs); verbose_bit_mask::check(cx, e, op.node, lhs, rhs, self.verbose_bit_mask_threshold); double_comparison::check(cx, op.node, lhs, rhs, e.span); + double_const_comparison::check(cx, op, lhs, rhs, e.span); duration_subsec::check(cx, e, op.node, lhs, rhs); float_equality_without_abs::check(cx, e, op.node, lhs, rhs); integer_division::check(cx, e, op.node, lhs, rhs); diff --git a/tests/ui/double_const_comparisons.rs b/tests/ui/double_const_comparisons.rs new file mode 100644 index 0000000000000..0f57fbba1eb25 --- /dev/null +++ b/tests/ui/double_const_comparisons.rs @@ -0,0 +1,52 @@ +#![allow(unused)] +#![warn(clippy::impossible_double_const_comparisons)] +#![warn(clippy::ineffective_double_const_comparisons)] +#![allow(clippy::no_effect)] +#![allow(clippy::short_circuit_statement)] +#![allow(clippy::manual_range_contains)] + +const STATUS_BAD_REQUEST: u16 = 400; +const STATUS_SERVER_ERROR: u16 = 500; + +fn main() { + let status_code = 500; // Value doesn't matter for the lint + + status_code >= 400 && status_code < 500; // Correct + status_code <= 400 && status_code > 500; + status_code > 500 && status_code < 400; + status_code < 500 && status_code > 500; + + // More complex expressions + status_code < { 400 } && status_code > { 500 }; + status_code < STATUS_BAD_REQUEST && status_code > STATUS_SERVER_ERROR; + status_code <= u16::MIN + 1 && status_code > STATUS_SERVER_ERROR; + status_code < STATUS_SERVER_ERROR && status_code > STATUS_SERVER_ERROR; + + // Yoda conditions + 500 <= status_code && 600 > status_code; // Correct + 500 <= status_code && status_code <= 600; // Correct + 500 >= status_code && 600 < status_code; // Incorrect + 500 >= status_code && status_code > 600; // Incorrect + + // Expressions where one of the sides has no effect + status_code < 200 && status_code <= 299; + status_code > 200 && status_code >= 299; + + status_code >= 500 && status_code > 500; // Useless left + status_code > 500 && status_code >= 500; // Useless right + status_code <= 500 && status_code < 500; // Useless left + status_code < 500 && status_code <= 500; // Useless right + + // Other types + let name = "Steve"; + name < "Jennifer" && name > "Shannon"; + + let numbers = [1, 2]; + numbers < [3, 4] && numbers > [5, 6]; + + let letter = 'a'; + letter < 'b' && letter > 'c'; + + let area = 42.0; + area < std::f32::consts::E && area > std::f32::consts::PI; +} diff --git a/tests/ui/double_const_comparisons.stderr b/tests/ui/double_const_comparisons.stderr new file mode 100644 index 0000000000000..76cfc82c0a4ab --- /dev/null +++ b/tests/ui/double_const_comparisons.stderr @@ -0,0 +1,180 @@ +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:15:5 + | +LL | status_code <= 400 && status_code > 500; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: since `400` < `500`, the expression evaluates to false for any value of `status_code` + = note: `-D clippy::impossible-double-const-comparisons` implied by `-D warnings` + +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:16:5 + | +LL | status_code > 500 && status_code < 400; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: since `500` > `400`, the expression evaluates to false for any value of `status_code` + +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:17:5 + | +LL | status_code < 500 && status_code > 500; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `status_code` cannot simultaneously be greater than and less than `500` + +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:20:5 + | +LL | status_code < { 400 } && status_code > { 500 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: since `{ 400 }` < `{ 500 }`, the expression evaluates to false for any value of `status_code` + +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:21:5 + | +LL | status_code < STATUS_BAD_REQUEST && status_code > STATUS_SERVER_ERROR; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: since `STATUS_BAD_REQUEST` < `STATUS_SERVER_ERROR`, the expression evaluates to false for any value of `status_code` + +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:22:5 + | +LL | status_code <= u16::MIN + 1 && status_code > STATUS_SERVER_ERROR; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: since `u16::MIN + 1` < `STATUS_SERVER_ERROR`, the expression evaluates to false for any value of `status_code` + +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:23:5 + | +LL | status_code < STATUS_SERVER_ERROR && status_code > STATUS_SERVER_ERROR; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `status_code` cannot simultaneously be greater than and less than `STATUS_SERVER_ERROR` + +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:28:5 + | +LL | 500 >= status_code && 600 < status_code; // Incorrect + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: since `500` < `600`, the expression evaluates to false for any value of `status_code` + +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:29:5 + | +LL | 500 >= status_code && status_code > 600; // Incorrect + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: since `500` < `600`, the expression evaluates to false for any value of `status_code` + +error: right-hand side of `&&` operator has no effect + --> $DIR/double_const_comparisons.rs:32:5 + | +LL | status_code < 200 && status_code <= 299; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: `if `status_code < 200` evaluates to true, status_code <= 299` will always evaluate to true as well + --> $DIR/double_const_comparisons.rs:32:23 + | +LL | status_code < 200 && status_code <= 299; + | ^^^^^^^^^^^^^^^^^^^^^ + = note: `-D clippy::ineffective-double-const-comparisons` implied by `-D warnings` + +error: left-hand side of `&&` operator has no effect + --> $DIR/double_const_comparisons.rs:33:5 + | +LL | status_code > 200 && status_code >= 299; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: `if `status_code >= 299` evaluates to true, status_code > 200` will always evaluate to true as well + --> $DIR/double_const_comparisons.rs:33:5 + | +LL | status_code > 200 && status_code >= 299; + | ^^^^^^^^^^^^^^^^^^^^^ + +error: left-hand side of `&&` operator has no effect + --> $DIR/double_const_comparisons.rs:35:5 + | +LL | status_code >= 500 && status_code > 500; // Useless left + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: `if `status_code > 500` evaluates to true, status_code >= 500` will always evaluate to true as well + --> $DIR/double_const_comparisons.rs:35:5 + | +LL | status_code >= 500 && status_code > 500; // Useless left + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: right-hand side of `&&` operator has no effect + --> $DIR/double_const_comparisons.rs:36:5 + | +LL | status_code > 500 && status_code >= 500; // Useless right + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: `if `status_code > 500` evaluates to true, status_code >= 500` will always evaluate to true as well + --> $DIR/double_const_comparisons.rs:36:23 + | +LL | status_code > 500 && status_code >= 500; // Useless right + | ^^^^^^^^^^^^^^^^^^^^^ + +error: left-hand side of `&&` operator has no effect + --> $DIR/double_const_comparisons.rs:37:5 + | +LL | status_code <= 500 && status_code < 500; // Useless left + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: `if `status_code < 500` evaluates to true, status_code <= 500` will always evaluate to true as well + --> $DIR/double_const_comparisons.rs:37:5 + | +LL | status_code <= 500 && status_code < 500; // Useless left + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: right-hand side of `&&` operator has no effect + --> $DIR/double_const_comparisons.rs:38:5 + | +LL | status_code < 500 && status_code <= 500; // Useless right + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: `if `status_code < 500` evaluates to true, status_code <= 500` will always evaluate to true as well + --> $DIR/double_const_comparisons.rs:38:23 + | +LL | status_code < 500 && status_code <= 500; // Useless right + | ^^^^^^^^^^^^^^^^^^^^^ + +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:42:5 + | +LL | name < "Jennifer" && name > "Shannon"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: since `"Jennifer"` < `"Shannon"`, the expression evaluates to false for any value of `name` + +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:45:5 + | +LL | numbers < [3, 4] && numbers > [5, 6]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: since `[3, 4]` < `[5, 6]`, the expression evaluates to false for any value of `numbers` + +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:48:5 + | +LL | letter < 'b' && letter > 'c'; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: since `'b'` < `'c'`, the expression evaluates to false for any value of `letter` + +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:51:5 + | +LL | area < std::f32::consts::E && area > std::f32::consts::PI; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: since `std::f32::consts::E` < `std::f32::consts::PI`, the expression evaluates to false for any value of `area` + +error: aborting due to 19 previous errors + diff --git a/tests/ui/range_contains.fixed b/tests/ui/range_contains.fixed index 0a92ee7c8dd30..429d860829116 100644 --- a/tests/ui/range_contains.fixed +++ b/tests/ui/range_contains.fixed @@ -5,6 +5,8 @@ #![allow(clippy::no_effect)] #![allow(clippy::short_circuit_statement)] #![allow(clippy::unnecessary_operation)] +#![allow(clippy::impossible_double_const_comparisons)] +#![allow(clippy::ineffective_double_const_comparisons)] fn main() { let x = 9_i32; diff --git a/tests/ui/range_contains.rs b/tests/ui/range_contains.rs index 7a83be609571f..e47e29481471b 100644 --- a/tests/ui/range_contains.rs +++ b/tests/ui/range_contains.rs @@ -5,6 +5,8 @@ #![allow(clippy::no_effect)] #![allow(clippy::short_circuit_statement)] #![allow(clippy::unnecessary_operation)] +#![allow(clippy::impossible_double_const_comparisons)] +#![allow(clippy::ineffective_double_const_comparisons)] fn main() { let x = 9_i32; diff --git a/tests/ui/range_contains.stderr b/tests/ui/range_contains.stderr index ea34023a46645..1265db695bfb1 100644 --- a/tests/ui/range_contains.stderr +++ b/tests/ui/range_contains.stderr @@ -1,5 +1,5 @@ error: manual `Range::contains` implementation - --> $DIR/range_contains.rs:13:5 + --> $DIR/range_contains.rs:15:5 | LL | x >= 8 && x < 12; | ^^^^^^^^^^^^^^^^ help: use: `(8..12).contains(&x)` @@ -7,121 +7,121 @@ LL | x >= 8 && x < 12; = note: `-D clippy::manual-range-contains` implied by `-D warnings` error: manual `Range::contains` implementation - --> $DIR/range_contains.rs:14:5 + --> $DIR/range_contains.rs:16:5 | LL | x < 42 && x >= 21; | ^^^^^^^^^^^^^^^^^ help: use: `(21..42).contains(&x)` error: manual `Range::contains` implementation - --> $DIR/range_contains.rs:15:5 + --> $DIR/range_contains.rs:17:5 | LL | 100 > x && 1 <= x; | ^^^^^^^^^^^^^^^^^ help: use: `(1..100).contains(&x)` error: manual `RangeInclusive::contains` implementation - --> $DIR/range_contains.rs:18:5 + --> $DIR/range_contains.rs:20:5 | LL | x >= 9 && x <= 99; | ^^^^^^^^^^^^^^^^^ help: use: `(9..=99).contains(&x)` error: manual `RangeInclusive::contains` implementation - --> $DIR/range_contains.rs:19:5 + --> $DIR/range_contains.rs:21:5 | LL | x <= 33 && x >= 1; | ^^^^^^^^^^^^^^^^^ help: use: `(1..=33).contains(&x)` error: manual `RangeInclusive::contains` implementation - --> $DIR/range_contains.rs:20:5 + --> $DIR/range_contains.rs:22:5 | LL | 999 >= x && 1 <= x; | ^^^^^^^^^^^^^^^^^^ help: use: `(1..=999).contains(&x)` error: manual `!Range::contains` implementation - --> $DIR/range_contains.rs:23:5 + --> $DIR/range_contains.rs:25:5 | LL | x < 8 || x >= 12; | ^^^^^^^^^^^^^^^^ help: use: `!(8..12).contains(&x)` error: manual `!Range::contains` implementation - --> $DIR/range_contains.rs:24:5 + --> $DIR/range_contains.rs:26:5 | LL | x >= 42 || x < 21; | ^^^^^^^^^^^^^^^^^ help: use: `!(21..42).contains(&x)` error: manual `!Range::contains` implementation - --> $DIR/range_contains.rs:25:5 + --> $DIR/range_contains.rs:27:5 | LL | 100 <= x || 1 > x; | ^^^^^^^^^^^^^^^^^ help: use: `!(1..100).contains(&x)` error: manual `!RangeInclusive::contains` implementation - --> $DIR/range_contains.rs:28:5 + --> $DIR/range_contains.rs:30:5 | LL | x < 9 || x > 99; | ^^^^^^^^^^^^^^^ help: use: `!(9..=99).contains(&x)` error: manual `!RangeInclusive::contains` implementation - --> $DIR/range_contains.rs:29:5 + --> $DIR/range_contains.rs:31:5 | LL | x > 33 || x < 1; | ^^^^^^^^^^^^^^^ help: use: `!(1..=33).contains(&x)` error: manual `!RangeInclusive::contains` implementation - --> $DIR/range_contains.rs:30:5 + --> $DIR/range_contains.rs:32:5 | LL | 999 < x || 1 > x; | ^^^^^^^^^^^^^^^^ help: use: `!(1..=999).contains(&x)` error: manual `Range::contains` implementation - --> $DIR/range_contains.rs:45:5 + --> $DIR/range_contains.rs:47:5 | LL | y >= 0. && y < 1.; | ^^^^^^^^^^^^^^^^^ help: use: `(0. ..1.).contains(&y)` error: manual `!RangeInclusive::contains` implementation - --> $DIR/range_contains.rs:46:5 + --> $DIR/range_contains.rs:48:5 | LL | y < 0. || y > 1.; | ^^^^^^^^^^^^^^^^ help: use: `!(0. ..=1.).contains(&y)` error: manual `RangeInclusive::contains` implementation - --> $DIR/range_contains.rs:49:5 + --> $DIR/range_contains.rs:51:5 | LL | x >= -10 && x <= 10; | ^^^^^^^^^^^^^^^^^^^ help: use: `(-10..=10).contains(&x)` error: manual `RangeInclusive::contains` implementation - --> $DIR/range_contains.rs:51:5 + --> $DIR/range_contains.rs:53:5 | LL | y >= -3. && y <= 3.; | ^^^^^^^^^^^^^^^^^^^ help: use: `(-3. ..=3.).contains(&y)` error: manual `RangeInclusive::contains` implementation - --> $DIR/range_contains.rs:56:30 + --> $DIR/range_contains.rs:58:30 | LL | (x >= 0) && (x <= 10) && (z >= 0) && (z <= 10); | ^^^^^^^^^^^^^^^^^^^^^ help: use: `(0..=10).contains(&z)` error: manual `RangeInclusive::contains` implementation - --> $DIR/range_contains.rs:56:5 + --> $DIR/range_contains.rs:58:5 | LL | (x >= 0) && (x <= 10) && (z >= 0) && (z <= 10); | ^^^^^^^^^^^^^^^^^^^^^ help: use: `(0..=10).contains(&x)` error: manual `!Range::contains` implementation - --> $DIR/range_contains.rs:57:29 + --> $DIR/range_contains.rs:59:29 | LL | (x < 0) || (x >= 10) || (z < 0) || (z >= 10); | ^^^^^^^^^^^^^^^^^^^^ help: use: `!(0..10).contains(&z)` error: manual `!Range::contains` implementation - --> $DIR/range_contains.rs:57:5 + --> $DIR/range_contains.rs:59:5 | LL | (x < 0) || (x >= 10) || (z < 0) || (z >= 10); | ^^^^^^^^^^^^^^^^^^^^ help: use: `!(0..10).contains(&x)` error: manual `Range::contains` implementation - --> $DIR/range_contains.rs:76:5 + --> $DIR/range_contains.rs:78:5 | LL | x >= 8 && x < 35; | ^^^^^^^^^^^^^^^^ help: use: `(8..35).contains(&x)` From e16a2ac0c65c233ab52f61cdb38ad76ea53b11f4 Mon Sep 17 00:00:00 2001 From: Morten Lohne Date: Mon, 29 May 2023 23:49:54 +0200 Subject: [PATCH 28/49] Add descriptions for 'impossible_double_const_comparisons' and 'ineffective_double_const_comparisons' --- clippy_lints/src/operators/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index 41efcef86c8af..25e1fce1980b7 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -314,7 +314,7 @@ declare_clippy_lint! { #[clippy::version = "1.71.0"] pub IMPOSSIBLE_DOUBLE_CONST_COMPARISONS, correctness, - "default lint description" + "double comparisons that will never evaluate to `true`" } declare_clippy_lint! { @@ -333,7 +333,7 @@ declare_clippy_lint! { #[clippy::version = "1.71.0"] pub INEFFECTIVE_DOUBLE_CONST_COMPARISONS, correctness, - "default lint description" + "double comparisons where one of them can be removed" } declare_clippy_lint! { From 08e1333fa6467290b9951fc28f0616db3b13c51d Mon Sep 17 00:00:00 2001 From: Morten Lohne Date: Thu, 1 Jun 2023 23:50:33 +0200 Subject: [PATCH 29/49] Add missing variable decl to doc comment --- clippy_lints/src/operators/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index 25e1fce1980b7..ef76812d9dd1d 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -309,7 +309,8 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// status_code <= 400 && status_code > 500; + /// # let status_code = 200; + /// if status_code <= 400 && status_code > 500 {} /// ``` #[clippy::version = "1.71.0"] pub IMPOSSIBLE_DOUBLE_CONST_COMPARISONS, @@ -328,7 +329,8 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// status_code <= 400 && status_code < 500; + /// # let status_code = 200; + /// if status_code <= 400 && status_code < 500 {} /// ``` #[clippy::version = "1.71.0"] pub INEFFECTIVE_DOUBLE_CONST_COMPARISONS, From 8f40d09e0f08273e8edbb22493d845029a92d58f Mon Sep 17 00:00:00 2001 From: Morten Lohne Date: Sat, 3 Jun 2023 13:36:12 +0200 Subject: [PATCH 30/49] Add tests for const comparisons that compare two different types --- tests/ui/double_const_comparisons.rs | 41 ++++++++++ tests/ui/double_const_comparisons.stderr | 100 +++++++++++++++++------ 2 files changed, 115 insertions(+), 26 deletions(-) diff --git a/tests/ui/double_const_comparisons.rs b/tests/ui/double_const_comparisons.rs index 0f57fbba1eb25..de01d8e0c3683 100644 --- a/tests/ui/double_const_comparisons.rs +++ b/tests/ui/double_const_comparisons.rs @@ -8,8 +8,37 @@ const STATUS_BAD_REQUEST: u16 = 400; const STATUS_SERVER_ERROR: u16 = 500; +struct Status { + code: u16, +} + +impl PartialEq for Status { + fn eq(&self, other: &u16) -> bool { + self.code == *other + } +} + +impl PartialOrd for Status { + fn partial_cmp(&self, other: &u16) -> Option { + self.code.partial_cmp(other) + } +} + +impl PartialEq for u16 { + fn eq(&self, other: &Status) -> bool { + *self == other.code + } +} + +impl PartialOrd for u16 { + fn partial_cmp(&self, other: &Status) -> Option { + self.partial_cmp(&other.code) + } +} + fn main() { let status_code = 500; // Value doesn't matter for the lint + let status = Status { code: status_code }; status_code >= 400 && status_code < 500; // Correct status_code <= 400 && status_code > 500; @@ -22,12 +51,24 @@ fn main() { status_code <= u16::MIN + 1 && status_code > STATUS_SERVER_ERROR; status_code < STATUS_SERVER_ERROR && status_code > STATUS_SERVER_ERROR; + // Comparing two different types, via the `impl PartialOrd for Status` + status < { 400 } && status > { 500 }; + status < STATUS_BAD_REQUEST && status > STATUS_SERVER_ERROR; + status <= u16::MIN + 1 && status > STATUS_SERVER_ERROR; + status < STATUS_SERVER_ERROR && status > STATUS_SERVER_ERROR; + // Yoda conditions 500 <= status_code && 600 > status_code; // Correct 500 <= status_code && status_code <= 600; // Correct 500 >= status_code && 600 < status_code; // Incorrect 500 >= status_code && status_code > 600; // Incorrect + // Yoda conditions, comparing two different types + 500 <= status && 600 > status; // Correct + 500 <= status && status <= 600; // Correct + 500 >= status && 600 < status; // Incorrect + 500 >= status && status > 600; // Incorrect + // Expressions where one of the sides has no effect status_code < 200 && status_code <= 299; status_code > 200 && status_code >= 299; diff --git a/tests/ui/double_const_comparisons.stderr b/tests/ui/double_const_comparisons.stderr index 76cfc82c0a4ab..3d8ef3c6d6412 100644 --- a/tests/ui/double_const_comparisons.stderr +++ b/tests/ui/double_const_comparisons.stderr @@ -1,5 +1,5 @@ error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:15:5 + --> $DIR/double_const_comparisons.rs:44:5 | LL | status_code <= 400 && status_code > 500; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -8,7 +8,7 @@ LL | status_code <= 400 && status_code > 500; = note: `-D clippy::impossible-double-const-comparisons` implied by `-D warnings` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:16:5 + --> $DIR/double_const_comparisons.rs:45:5 | LL | status_code > 500 && status_code < 400; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -16,7 +16,7 @@ LL | status_code > 500 && status_code < 400; = note: since `500` > `400`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:17:5 + --> $DIR/double_const_comparisons.rs:46:5 | LL | status_code < 500 && status_code > 500; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -24,7 +24,7 @@ LL | status_code < 500 && status_code > 500; = note: `status_code` cannot simultaneously be greater than and less than `500` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:20:5 + --> $DIR/double_const_comparisons.rs:49:5 | LL | status_code < { 400 } && status_code > { 500 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -32,7 +32,7 @@ LL | status_code < { 400 } && status_code > { 500 }; = note: since `{ 400 }` < `{ 500 }`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:21:5 + --> $DIR/double_const_comparisons.rs:50:5 | LL | status_code < STATUS_BAD_REQUEST && status_code > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -40,7 +40,7 @@ LL | status_code < STATUS_BAD_REQUEST && status_code > STATUS_SERVER_ERROR; = note: since `STATUS_BAD_REQUEST` < `STATUS_SERVER_ERROR`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:22:5 + --> $DIR/double_const_comparisons.rs:51:5 | LL | status_code <= u16::MIN + 1 && status_code > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -48,7 +48,7 @@ LL | status_code <= u16::MIN + 1 && status_code > STATUS_SERVER_ERROR; = note: since `u16::MIN + 1` < `STATUS_SERVER_ERROR`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:23:5 + --> $DIR/double_const_comparisons.rs:52:5 | LL | status_code < STATUS_SERVER_ERROR && status_code > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -56,7 +56,39 @@ LL | status_code < STATUS_SERVER_ERROR && status_code > STATUS_SERVER_ERROR; = note: `status_code` cannot simultaneously be greater than and less than `STATUS_SERVER_ERROR` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:28:5 + --> $DIR/double_const_comparisons.rs:55:5 + | +LL | status < { 400 } && status > { 500 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: since `{ 400 }` < `{ 500 }`, the expression evaluates to false for any value of `status` + +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:56:5 + | +LL | status < STATUS_BAD_REQUEST && status > STATUS_SERVER_ERROR; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: since `STATUS_BAD_REQUEST` < `STATUS_SERVER_ERROR`, the expression evaluates to false for any value of `status` + +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:57:5 + | +LL | status <= u16::MIN + 1 && status > STATUS_SERVER_ERROR; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: since `u16::MIN + 1` < `STATUS_SERVER_ERROR`, the expression evaluates to false for any value of `status` + +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:58:5 + | +LL | status < STATUS_SERVER_ERROR && status > STATUS_SERVER_ERROR; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `status` cannot simultaneously be greater than and less than `STATUS_SERVER_ERROR` + +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:63:5 | LL | 500 >= status_code && 600 < status_code; // Incorrect | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -64,88 +96,104 @@ LL | 500 >= status_code && 600 < status_code; // Incorrect = note: since `500` < `600`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:29:5 + --> $DIR/double_const_comparisons.rs:64:5 | LL | 500 >= status_code && status_code > 600; // Incorrect | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: since `500` < `600`, the expression evaluates to false for any value of `status_code` +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:69:5 + | +LL | 500 >= status && 600 < status; // Incorrect + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: since `500` < `600`, the expression evaluates to false for any value of `status` + +error: boolean expression will never evaluate to 'true' + --> $DIR/double_const_comparisons.rs:70:5 + | +LL | 500 >= status && status > 600; // Incorrect + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: since `500` < `600`, the expression evaluates to false for any value of `status` + error: right-hand side of `&&` operator has no effect - --> $DIR/double_const_comparisons.rs:32:5 + --> $DIR/double_const_comparisons.rs:73:5 | LL | status_code < 200 && status_code <= 299; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code < 200` evaluates to true, status_code <= 299` will always evaluate to true as well - --> $DIR/double_const_comparisons.rs:32:23 + --> $DIR/double_const_comparisons.rs:73:23 | LL | status_code < 200 && status_code <= 299; | ^^^^^^^^^^^^^^^^^^^^^ = note: `-D clippy::ineffective-double-const-comparisons` implied by `-D warnings` error: left-hand side of `&&` operator has no effect - --> $DIR/double_const_comparisons.rs:33:5 + --> $DIR/double_const_comparisons.rs:74:5 | LL | status_code > 200 && status_code >= 299; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code >= 299` evaluates to true, status_code > 200` will always evaluate to true as well - --> $DIR/double_const_comparisons.rs:33:5 + --> $DIR/double_const_comparisons.rs:74:5 | LL | status_code > 200 && status_code >= 299; | ^^^^^^^^^^^^^^^^^^^^^ error: left-hand side of `&&` operator has no effect - --> $DIR/double_const_comparisons.rs:35:5 + --> $DIR/double_const_comparisons.rs:76:5 | LL | status_code >= 500 && status_code > 500; // Useless left | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code > 500` evaluates to true, status_code >= 500` will always evaluate to true as well - --> $DIR/double_const_comparisons.rs:35:5 + --> $DIR/double_const_comparisons.rs:76:5 | LL | status_code >= 500 && status_code > 500; // Useless left | ^^^^^^^^^^^^^^^^^^^^^^ error: right-hand side of `&&` operator has no effect - --> $DIR/double_const_comparisons.rs:36:5 + --> $DIR/double_const_comparisons.rs:77:5 | LL | status_code > 500 && status_code >= 500; // Useless right | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code > 500` evaluates to true, status_code >= 500` will always evaluate to true as well - --> $DIR/double_const_comparisons.rs:36:23 + --> $DIR/double_const_comparisons.rs:77:23 | LL | status_code > 500 && status_code >= 500; // Useless right | ^^^^^^^^^^^^^^^^^^^^^ error: left-hand side of `&&` operator has no effect - --> $DIR/double_const_comparisons.rs:37:5 + --> $DIR/double_const_comparisons.rs:78:5 | LL | status_code <= 500 && status_code < 500; // Useless left | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code < 500` evaluates to true, status_code <= 500` will always evaluate to true as well - --> $DIR/double_const_comparisons.rs:37:5 + --> $DIR/double_const_comparisons.rs:78:5 | LL | status_code <= 500 && status_code < 500; // Useless left | ^^^^^^^^^^^^^^^^^^^^^^ error: right-hand side of `&&` operator has no effect - --> $DIR/double_const_comparisons.rs:38:5 + --> $DIR/double_const_comparisons.rs:79:5 | LL | status_code < 500 && status_code <= 500; // Useless right | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code < 500` evaluates to true, status_code <= 500` will always evaluate to true as well - --> $DIR/double_const_comparisons.rs:38:23 + --> $DIR/double_const_comparisons.rs:79:23 | LL | status_code < 500 && status_code <= 500; // Useless right | ^^^^^^^^^^^^^^^^^^^^^ error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:42:5 + --> $DIR/double_const_comparisons.rs:83:5 | LL | name < "Jennifer" && name > "Shannon"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -153,7 +201,7 @@ LL | name < "Jennifer" && name > "Shannon"; = note: since `"Jennifer"` < `"Shannon"`, the expression evaluates to false for any value of `name` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:45:5 + --> $DIR/double_const_comparisons.rs:86:5 | LL | numbers < [3, 4] && numbers > [5, 6]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -161,7 +209,7 @@ LL | numbers < [3, 4] && numbers > [5, 6]; = note: since `[3, 4]` < `[5, 6]`, the expression evaluates to false for any value of `numbers` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:48:5 + --> $DIR/double_const_comparisons.rs:89:5 | LL | letter < 'b' && letter > 'c'; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -169,12 +217,12 @@ LL | letter < 'b' && letter > 'c'; = note: since `'b'` < `'c'`, the expression evaluates to false for any value of `letter` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:51:5 + --> $DIR/double_const_comparisons.rs:92:5 | LL | area < std::f32::consts::E && area > std::f32::consts::PI; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: since `std::f32::consts::E` < `std::f32::consts::PI`, the expression evaluates to false for any value of `area` -error: aborting due to 19 previous errors +error: aborting due to 25 previous errors From b5ef66f442229ada09a83454b47a9671c2d098c9 Mon Sep 17 00:00:00 2001 From: Morten Lohne Date: Sat, 10 Jun 2023 14:29:24 +0200 Subject: [PATCH 31/49] Optimize by doing a cheap check for double binary expression first --- .../src/operators/double_const_comparison.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/operators/double_const_comparison.rs b/clippy_lints/src/operators/double_const_comparison.rs index d029a3d4ebe28..80db455d2e64b 100644 --- a/clippy_lints/src/operators/double_const_comparison.rs +++ b/clippy_lints/src/operators/double_const_comparison.rs @@ -51,14 +51,19 @@ pub(super) fn check<'tcx>( // Ensure that the binary operator is && if and_op.node == BinOpKind::And; - let typeck_results = cx.typeck_results(); - let mut const_context = consts::ConstEvalLateContext::new(cx, typeck_results); + // Check that both operands to '&&' are themselves a binary operation + // The `comparison_to_const` step also checks this, so this step is just an optimization + if let ExprKind::Binary(_, _, _) = left_cond.kind; + if let ExprKind::Binary(_, _, _) = right_cond.kind; + + let typeck = cx.typeck_results(); + let mut const_context = consts::ConstEvalLateContext::new(cx, typeck); // Check that both operands to '&&' compare a non-literal to a literal if let Some((left_cmp_op, left_expr, left_const_expr, left_const, left_type)) = - comparison_to_const(&mut const_context, typeck_results, left_cond); + comparison_to_const(&mut const_context, typeck, left_cond); if let Some((right_cmp_op, right_expr, right_const_expr, right_const, right_type)) = - comparison_to_const(&mut const_context, typeck_results, right_cond); + comparison_to_const(&mut const_context, typeck, right_cond); if left_type == right_type; From 1d61fc1b0a02bb8f4a597279f03e897de0e521d1 Mon Sep 17 00:00:00 2001 From: Morten Lohne Date: Sat, 10 Jun 2023 15:58:46 +0200 Subject: [PATCH 32/49] Rename 'impossible_double_const_comparisons' -> 'impossible_comparisons' and 'ineffective_double_const_comparisons' -> 'redundant_comparisons', after discussion on Zulip --- CHANGELOG.md | 4 +- clippy_lints/src/declared_lints.rs | 4 +- ...nst_comparison.rs => const_comparisons.rs} | 10 +-- clippy_lints/src/operators/mod.rs | 12 ++-- ...st_comparisons.rs => const_comparisons.rs} | 4 +- ...risons.stderr => const_comparisons.stderr} | 66 +++++++++---------- tests/ui/range_contains.fixed | 4 +- tests/ui/range_contains.rs | 4 +- 8 files changed, 54 insertions(+), 54 deletions(-) rename clippy_lints/src/operators/{double_const_comparison.rs => const_comparisons.rs} (96%) rename tests/ui/{double_const_comparisons.rs => const_comparisons.rs} (96%) rename tests/ui/{double_const_comparisons.stderr => const_comparisons.stderr} (83%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f9ec3a4df909..71671273c57b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4897,7 +4897,7 @@ Released 2018-09-13 [`implicit_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_return [`implicit_saturating_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_saturating_add [`implicit_saturating_sub`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_saturating_sub -[`impossible_double_const_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#impossible_double_const_comparisons +[`impossible_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#impossible_comparisons [`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops [`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping [`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor @@ -4906,7 +4906,6 @@ Released 2018-09-13 [`index_refutable_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice [`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing [`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask -[`ineffective_double_const_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_double_const_comparisons [`inefficient_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_to_string [`infallible_destructuring_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#infallible_destructuring_match [`infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#infinite_iter @@ -5193,6 +5192,7 @@ Released 2018-09-13 [`redundant_closure`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure [`redundant_closure_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_call [`redundant_closure_for_method_calls`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls +[`redundant_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_comparisons [`redundant_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_else [`redundant_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_feature_names [`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 897e6c7bd107b..db114abfc869c 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -518,9 +518,8 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::operators::FLOAT_CMP_CONST_INFO, crate::operators::FLOAT_EQUALITY_WITHOUT_ABS_INFO, crate::operators::IDENTITY_OP_INFO, - crate::operators::IMPOSSIBLE_DOUBLE_CONST_COMPARISONS_INFO, + crate::operators::IMPOSSIBLE_COMPARISONS_INFO, crate::operators::INEFFECTIVE_BIT_MASK_INFO, - crate::operators::INEFFECTIVE_DOUBLE_CONST_COMPARISONS_INFO, crate::operators::INTEGER_DIVISION_INFO, crate::operators::MISREFACTORED_ASSIGN_OP_INFO, crate::operators::MODULO_ARITHMETIC_INFO, @@ -528,6 +527,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::operators::NEEDLESS_BITWISE_BOOL_INFO, crate::operators::OP_REF_INFO, crate::operators::PTR_EQ_INFO, + crate::operators::REDUNDANT_COMPARISONS_INFO, crate::operators::SELF_ASSIGNMENT_INFO, crate::operators::VERBOSE_BIT_MASK_INFO, crate::option_env_unwrap::OPTION_ENV_UNWRAP_INFO, diff --git a/clippy_lints/src/operators/double_const_comparison.rs b/clippy_lints/src/operators/const_comparisons.rs similarity index 96% rename from clippy_lints/src/operators/double_const_comparison.rs rename to clippy_lints/src/operators/const_comparisons.rs index 80db455d2e64b..357981c10ad09 100644 --- a/clippy_lints/src/operators/double_const_comparison.rs +++ b/clippy_lints/src/operators/const_comparisons.rs @@ -14,8 +14,8 @@ use clippy_utils::diagnostics::span_lint_and_note; use clippy_utils::source::snippet; use clippy_utils::SpanlessEq; -use super::IMPOSSIBLE_DOUBLE_CONST_COMPARISONS; -use super::INEFFECTIVE_DOUBLE_CONST_COMPARISONS; +use super::IMPOSSIBLE_COMPARISONS; +use super::REDUNDANT_COMPARISONS; // Extract a comparison between a const and non-const // Flip yoda conditionals, turnings expressions like `42 < x` into `x > 42` @@ -91,7 +91,7 @@ pub(super) fn check<'tcx>( if left_side_is_useless(left_cmp_op, ordering) { span_lint_and_note( cx, - INEFFECTIVE_DOUBLE_CONST_COMPARISONS, + REDUNDANT_COMPARISONS, span, "left-hand side of `&&` operator has no effect", Some(left_cond.span.until(right_cond.span)), @@ -100,7 +100,7 @@ pub(super) fn check<'tcx>( } else { span_lint_and_note( cx, - INEFFECTIVE_DOUBLE_CONST_COMPARISONS, + REDUNDANT_COMPARISONS, span, "right-hand side of `&&` operator has no effect", Some(and_op.span.to(right_cond.span)), @@ -121,7 +121,7 @@ pub(super) fn check<'tcx>( }; span_lint_and_note( cx, - IMPOSSIBLE_DOUBLE_CONST_COMPARISONS, + IMPOSSIBLE_COMPARISONS, span, "boolean expression will never evaluate to 'true'", None, diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index ef76812d9dd1d..fa8e4c00a9748 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -2,8 +2,8 @@ mod absurd_extreme_comparisons; mod assign_op_pattern; mod bit_mask; mod cmp_owned; +mod const_comparisons; mod double_comparison; -mod double_const_comparison; mod duration_subsec; mod eq_op; mod erasing_op; @@ -313,7 +313,7 @@ declare_clippy_lint! { /// if status_code <= 400 && status_code > 500 {} /// ``` #[clippy::version = "1.71.0"] - pub IMPOSSIBLE_DOUBLE_CONST_COMPARISONS, + pub IMPOSSIBLE_COMPARISONS, correctness, "double comparisons that will never evaluate to `true`" } @@ -333,7 +333,7 @@ declare_clippy_lint! { /// if status_code <= 400 && status_code < 500 {} /// ``` #[clippy::version = "1.71.0"] - pub INEFFECTIVE_DOUBLE_CONST_COMPARISONS, + pub REDUNDANT_COMPARISONS, correctness, "double comparisons where one of them can be removed" } @@ -782,8 +782,8 @@ impl_lint_pass!(Operators => [ INEFFECTIVE_BIT_MASK, VERBOSE_BIT_MASK, DOUBLE_COMPARISONS, - IMPOSSIBLE_DOUBLE_CONST_COMPARISONS, - INEFFECTIVE_DOUBLE_CONST_COMPARISONS, + IMPOSSIBLE_COMPARISONS, + REDUNDANT_COMPARISONS, DURATION_SUBSEC, EQ_OP, OP_REF, @@ -828,7 +828,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators { bit_mask::check(cx, e, op.node, lhs, rhs); verbose_bit_mask::check(cx, e, op.node, lhs, rhs, self.verbose_bit_mask_threshold); double_comparison::check(cx, op.node, lhs, rhs, e.span); - double_const_comparison::check(cx, op, lhs, rhs, e.span); + const_comparisons::check(cx, op, lhs, rhs, e.span); duration_subsec::check(cx, e, op.node, lhs, rhs); float_equality_without_abs::check(cx, e, op.node, lhs, rhs); integer_division::check(cx, e, op.node, lhs, rhs); diff --git a/tests/ui/double_const_comparisons.rs b/tests/ui/const_comparisons.rs similarity index 96% rename from tests/ui/double_const_comparisons.rs rename to tests/ui/const_comparisons.rs index de01d8e0c3683..8e265c9141c16 100644 --- a/tests/ui/double_const_comparisons.rs +++ b/tests/ui/const_comparisons.rs @@ -1,6 +1,6 @@ #![allow(unused)] -#![warn(clippy::impossible_double_const_comparisons)] -#![warn(clippy::ineffective_double_const_comparisons)] +#![warn(clippy::impossible_comparisons)] +#![warn(clippy::redundant_comparisons)] #![allow(clippy::no_effect)] #![allow(clippy::short_circuit_statement)] #![allow(clippy::manual_range_contains)] diff --git a/tests/ui/double_const_comparisons.stderr b/tests/ui/const_comparisons.stderr similarity index 83% rename from tests/ui/double_const_comparisons.stderr rename to tests/ui/const_comparisons.stderr index 3d8ef3c6d6412..90e6db647621a 100644 --- a/tests/ui/double_const_comparisons.stderr +++ b/tests/ui/const_comparisons.stderr @@ -1,14 +1,14 @@ error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:44:5 + --> $DIR/const_comparisons.rs:44:5 | LL | status_code <= 400 && status_code > 500; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: since `400` < `500`, the expression evaluates to false for any value of `status_code` - = note: `-D clippy::impossible-double-const-comparisons` implied by `-D warnings` + = note: `-D clippy::impossible-comparisons` implied by `-D warnings` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:45:5 + --> $DIR/const_comparisons.rs:45:5 | LL | status_code > 500 && status_code < 400; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -16,7 +16,7 @@ LL | status_code > 500 && status_code < 400; = note: since `500` > `400`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:46:5 + --> $DIR/const_comparisons.rs:46:5 | LL | status_code < 500 && status_code > 500; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -24,7 +24,7 @@ LL | status_code < 500 && status_code > 500; = note: `status_code` cannot simultaneously be greater than and less than `500` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:49:5 + --> $DIR/const_comparisons.rs:49:5 | LL | status_code < { 400 } && status_code > { 500 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -32,7 +32,7 @@ LL | status_code < { 400 } && status_code > { 500 }; = note: since `{ 400 }` < `{ 500 }`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:50:5 + --> $DIR/const_comparisons.rs:50:5 | LL | status_code < STATUS_BAD_REQUEST && status_code > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -40,7 +40,7 @@ LL | status_code < STATUS_BAD_REQUEST && status_code > STATUS_SERVER_ERROR; = note: since `STATUS_BAD_REQUEST` < `STATUS_SERVER_ERROR`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:51:5 + --> $DIR/const_comparisons.rs:51:5 | LL | status_code <= u16::MIN + 1 && status_code > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -48,7 +48,7 @@ LL | status_code <= u16::MIN + 1 && status_code > STATUS_SERVER_ERROR; = note: since `u16::MIN + 1` < `STATUS_SERVER_ERROR`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:52:5 + --> $DIR/const_comparisons.rs:52:5 | LL | status_code < STATUS_SERVER_ERROR && status_code > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -56,7 +56,7 @@ LL | status_code < STATUS_SERVER_ERROR && status_code > STATUS_SERVER_ERROR; = note: `status_code` cannot simultaneously be greater than and less than `STATUS_SERVER_ERROR` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:55:5 + --> $DIR/const_comparisons.rs:55:5 | LL | status < { 400 } && status > { 500 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -64,7 +64,7 @@ LL | status < { 400 } && status > { 500 }; = note: since `{ 400 }` < `{ 500 }`, the expression evaluates to false for any value of `status` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:56:5 + --> $DIR/const_comparisons.rs:56:5 | LL | status < STATUS_BAD_REQUEST && status > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -72,7 +72,7 @@ LL | status < STATUS_BAD_REQUEST && status > STATUS_SERVER_ERROR; = note: since `STATUS_BAD_REQUEST` < `STATUS_SERVER_ERROR`, the expression evaluates to false for any value of `status` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:57:5 + --> $DIR/const_comparisons.rs:57:5 | LL | status <= u16::MIN + 1 && status > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -80,7 +80,7 @@ LL | status <= u16::MIN + 1 && status > STATUS_SERVER_ERROR; = note: since `u16::MIN + 1` < `STATUS_SERVER_ERROR`, the expression evaluates to false for any value of `status` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:58:5 + --> $DIR/const_comparisons.rs:58:5 | LL | status < STATUS_SERVER_ERROR && status > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -88,7 +88,7 @@ LL | status < STATUS_SERVER_ERROR && status > STATUS_SERVER_ERROR; = note: `status` cannot simultaneously be greater than and less than `STATUS_SERVER_ERROR` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:63:5 + --> $DIR/const_comparisons.rs:63:5 | LL | 500 >= status_code && 600 < status_code; // Incorrect | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -96,7 +96,7 @@ LL | 500 >= status_code && 600 < status_code; // Incorrect = note: since `500` < `600`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:64:5 + --> $DIR/const_comparisons.rs:64:5 | LL | 500 >= status_code && status_code > 600; // Incorrect | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -104,7 +104,7 @@ LL | 500 >= status_code && status_code > 600; // Incorrect = note: since `500` < `600`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:69:5 + --> $DIR/const_comparisons.rs:69:5 | LL | 500 >= status && 600 < status; // Incorrect | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -112,7 +112,7 @@ LL | 500 >= status && 600 < status; // Incorrect = note: since `500` < `600`, the expression evaluates to false for any value of `status` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:70:5 + --> $DIR/const_comparisons.rs:70:5 | LL | 500 >= status && status > 600; // Incorrect | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -120,80 +120,80 @@ LL | 500 >= status && status > 600; // Incorrect = note: since `500` < `600`, the expression evaluates to false for any value of `status` error: right-hand side of `&&` operator has no effect - --> $DIR/double_const_comparisons.rs:73:5 + --> $DIR/const_comparisons.rs:73:5 | LL | status_code < 200 && status_code <= 299; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code < 200` evaluates to true, status_code <= 299` will always evaluate to true as well - --> $DIR/double_const_comparisons.rs:73:23 + --> $DIR/const_comparisons.rs:73:23 | LL | status_code < 200 && status_code <= 299; | ^^^^^^^^^^^^^^^^^^^^^ - = note: `-D clippy::ineffective-double-const-comparisons` implied by `-D warnings` + = note: `-D clippy::redundant-comparisons` implied by `-D warnings` error: left-hand side of `&&` operator has no effect - --> $DIR/double_const_comparisons.rs:74:5 + --> $DIR/const_comparisons.rs:74:5 | LL | status_code > 200 && status_code >= 299; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code >= 299` evaluates to true, status_code > 200` will always evaluate to true as well - --> $DIR/double_const_comparisons.rs:74:5 + --> $DIR/const_comparisons.rs:74:5 | LL | status_code > 200 && status_code >= 299; | ^^^^^^^^^^^^^^^^^^^^^ error: left-hand side of `&&` operator has no effect - --> $DIR/double_const_comparisons.rs:76:5 + --> $DIR/const_comparisons.rs:76:5 | LL | status_code >= 500 && status_code > 500; // Useless left | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code > 500` evaluates to true, status_code >= 500` will always evaluate to true as well - --> $DIR/double_const_comparisons.rs:76:5 + --> $DIR/const_comparisons.rs:76:5 | LL | status_code >= 500 && status_code > 500; // Useless left | ^^^^^^^^^^^^^^^^^^^^^^ error: right-hand side of `&&` operator has no effect - --> $DIR/double_const_comparisons.rs:77:5 + --> $DIR/const_comparisons.rs:77:5 | LL | status_code > 500 && status_code >= 500; // Useless right | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code > 500` evaluates to true, status_code >= 500` will always evaluate to true as well - --> $DIR/double_const_comparisons.rs:77:23 + --> $DIR/const_comparisons.rs:77:23 | LL | status_code > 500 && status_code >= 500; // Useless right | ^^^^^^^^^^^^^^^^^^^^^ error: left-hand side of `&&` operator has no effect - --> $DIR/double_const_comparisons.rs:78:5 + --> $DIR/const_comparisons.rs:78:5 | LL | status_code <= 500 && status_code < 500; // Useless left | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code < 500` evaluates to true, status_code <= 500` will always evaluate to true as well - --> $DIR/double_const_comparisons.rs:78:5 + --> $DIR/const_comparisons.rs:78:5 | LL | status_code <= 500 && status_code < 500; // Useless left | ^^^^^^^^^^^^^^^^^^^^^^ error: right-hand side of `&&` operator has no effect - --> $DIR/double_const_comparisons.rs:79:5 + --> $DIR/const_comparisons.rs:79:5 | LL | status_code < 500 && status_code <= 500; // Useless right | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code < 500` evaluates to true, status_code <= 500` will always evaluate to true as well - --> $DIR/double_const_comparisons.rs:79:23 + --> $DIR/const_comparisons.rs:79:23 | LL | status_code < 500 && status_code <= 500; // Useless right | ^^^^^^^^^^^^^^^^^^^^^ error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:83:5 + --> $DIR/const_comparisons.rs:83:5 | LL | name < "Jennifer" && name > "Shannon"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -201,7 +201,7 @@ LL | name < "Jennifer" && name > "Shannon"; = note: since `"Jennifer"` < `"Shannon"`, the expression evaluates to false for any value of `name` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:86:5 + --> $DIR/const_comparisons.rs:86:5 | LL | numbers < [3, 4] && numbers > [5, 6]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -209,7 +209,7 @@ LL | numbers < [3, 4] && numbers > [5, 6]; = note: since `[3, 4]` < `[5, 6]`, the expression evaluates to false for any value of `numbers` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:89:5 + --> $DIR/const_comparisons.rs:89:5 | LL | letter < 'b' && letter > 'c'; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -217,7 +217,7 @@ LL | letter < 'b' && letter > 'c'; = note: since `'b'` < `'c'`, the expression evaluates to false for any value of `letter` error: boolean expression will never evaluate to 'true' - --> $DIR/double_const_comparisons.rs:92:5 + --> $DIR/const_comparisons.rs:92:5 | LL | area < std::f32::consts::E && area > std::f32::consts::PI; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/range_contains.fixed b/tests/ui/range_contains.fixed index 429d860829116..47c5248118eac 100644 --- a/tests/ui/range_contains.fixed +++ b/tests/ui/range_contains.fixed @@ -5,8 +5,8 @@ #![allow(clippy::no_effect)] #![allow(clippy::short_circuit_statement)] #![allow(clippy::unnecessary_operation)] -#![allow(clippy::impossible_double_const_comparisons)] -#![allow(clippy::ineffective_double_const_comparisons)] +#![allow(clippy::impossible_comparisons)] +#![allow(clippy::redundant_comparisons)] fn main() { let x = 9_i32; diff --git a/tests/ui/range_contains.rs b/tests/ui/range_contains.rs index e47e29481471b..a35315a649d85 100644 --- a/tests/ui/range_contains.rs +++ b/tests/ui/range_contains.rs @@ -5,8 +5,8 @@ #![allow(clippy::no_effect)] #![allow(clippy::short_circuit_statement)] #![allow(clippy::unnecessary_operation)] -#![allow(clippy::impossible_double_const_comparisons)] -#![allow(clippy::ineffective_double_const_comparisons)] +#![allow(clippy::impossible_comparisons)] +#![allow(clippy::redundant_comparisons)] fn main() { let x = 9_i32; From 964644692324c41fae286208417086c45b5c878e Mon Sep 17 00:00:00 2001 From: Morten Lohne Date: Sun, 6 Aug 2023 13:29:50 +0200 Subject: [PATCH 33/49] Add lifetime parameter to 'Constant', after rebasing on upstream --- clippy_lints/src/operators/const_comparisons.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/operators/const_comparisons.rs b/clippy_lints/src/operators/const_comparisons.rs index 357981c10ad09..16ac964e93a89 100644 --- a/clippy_lints/src/operators/const_comparisons.rs +++ b/clippy_lints/src/operators/const_comparisons.rs @@ -7,15 +7,15 @@ use clippy_utils::consts::{ConstEvalLateContext, Constant}; use if_chain::if_chain; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; -use rustc_middle::ty::{layout::HasTyCtxt, Ty, TypeckResults}; +use rustc_middle::ty::layout::HasTyCtxt; +use rustc_middle::ty::{Ty, TypeckResults}; use rustc_span::source_map::{Span, Spanned}; use clippy_utils::diagnostics::span_lint_and_note; use clippy_utils::source::snippet; use clippy_utils::SpanlessEq; -use super::IMPOSSIBLE_COMPARISONS; -use super::REDUNDANT_COMPARISONS; +use super::{IMPOSSIBLE_COMPARISONS, REDUNDANT_COMPARISONS}; // Extract a comparison between a const and non-const // Flip yoda conditionals, turnings expressions like `42 < x` into `x > 42` @@ -23,7 +23,7 @@ fn comparison_to_const<'tcx>( ctx: &mut ConstEvalLateContext<'_, 'tcx>, typeck: &TypeckResults<'tcx>, expr: &'tcx Expr<'tcx>, -) -> Option<(CmpOp, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Constant, Ty<'tcx>)> { +) -> Option<(CmpOp, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Constant<'tcx>, Ty<'tcx>)> { if_chain! { if let ExprKind::Binary(operator, left, right) = expr.kind; if let Ok(cmp_op) = CmpOp::try_from(operator.node); From 0e064d5d0480bf471c26a4566a9e3a09751a6262 Mon Sep 17 00:00:00 2001 From: Morten Lohne Date: Sun, 6 Aug 2023 13:48:28 +0200 Subject: [PATCH 34/49] Replace ConstEvalLateContext::new() with two calls to constant() to simplify the code, after PR suggestion --- clippy_lints/src/operators/const_comparisons.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/operators/const_comparisons.rs b/clippy_lints/src/operators/const_comparisons.rs index 16ac964e93a89..77043b0a3eb0b 100644 --- a/clippy_lints/src/operators/const_comparisons.rs +++ b/clippy_lints/src/operators/const_comparisons.rs @@ -2,8 +2,7 @@ use std::cmp::Ordering; -use clippy_utils::consts; -use clippy_utils::consts::{ConstEvalLateContext, Constant}; +use clippy_utils::consts::{constant, Constant}; use if_chain::if_chain; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; @@ -20,7 +19,7 @@ use super::{IMPOSSIBLE_COMPARISONS, REDUNDANT_COMPARISONS}; // Extract a comparison between a const and non-const // Flip yoda conditionals, turnings expressions like `42 < x` into `x > 42` fn comparison_to_const<'tcx>( - ctx: &mut ConstEvalLateContext<'_, 'tcx>, + cx: &LateContext<'tcx>, typeck: &TypeckResults<'tcx>, expr: &'tcx Expr<'tcx>, ) -> Option<(CmpOp, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Constant<'tcx>, Ty<'tcx>)> { @@ -28,7 +27,7 @@ fn comparison_to_const<'tcx>( if let ExprKind::Binary(operator, left, right) = expr.kind; if let Ok(cmp_op) = CmpOp::try_from(operator.node); then { - match (ctx.expr(left), ctx.expr(right)) { + match (constant(cx, typeck, left), constant(cx, typeck, right)) { (Some(_), Some(_)) => None, (_, Some(con)) => Some((cmp_op, left, right, con, typeck.expr_ty(right))), (Some(con), _) => Some((cmp_op.reverse(), right, left, con, typeck.expr_ty(left))), @@ -57,13 +56,12 @@ pub(super) fn check<'tcx>( if let ExprKind::Binary(_, _, _) = right_cond.kind; let typeck = cx.typeck_results(); - let mut const_context = consts::ConstEvalLateContext::new(cx, typeck); // Check that both operands to '&&' compare a non-literal to a literal if let Some((left_cmp_op, left_expr, left_const_expr, left_const, left_type)) = - comparison_to_const(&mut const_context, typeck, left_cond); + comparison_to_const(cx, typeck, left_cond); if let Some((right_cmp_op, right_expr, right_const_expr, right_const, right_type)) = - comparison_to_const(&mut const_context, typeck, right_cond); + comparison_to_const(cx, typeck, right_cond); if left_type == right_type; From 3157b96a5bc352336889399c68d0248eb67073c0 Mon Sep 17 00:00:00 2001 From: Morten Lohne Date: Sun, 6 Aug 2023 13:49:17 +0200 Subject: [PATCH 35/49] Provide fallback code snippets, if the snippet is not available --- clippy_lints/src/operators/const_comparisons.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/operators/const_comparisons.rs b/clippy_lints/src/operators/const_comparisons.rs index 77043b0a3eb0b..abe8df1954348 100644 --- a/clippy_lints/src/operators/const_comparisons.rs +++ b/clippy_lints/src/operators/const_comparisons.rs @@ -82,8 +82,8 @@ pub(super) fn check<'tcx>( then { if left_cmp_op.direction() == right_cmp_op.direction() { - let lhs_str = snippet(cx, left_cond.span, ""); - let rhs_str = snippet(cx, right_cond.span, ""); + let lhs_str = snippet(cx, left_cond.span, ""); + let rhs_str = snippet(cx, right_cond.span, ""); // We already know that either side of `&&` has no effect, // but emit a different error message depending on which side it is if left_side_is_useless(left_cmp_op, ordering) { @@ -109,9 +109,9 @@ pub(super) fn check<'tcx>( // because code triggering this lint probably not behaving correctly in the first place } else if !comparison_is_possible(left_cmp_op.direction(), ordering) { - let expr_str = snippet(cx, left_expr.span, ""); - let lhs_str = snippet(cx, left_const_expr.span, ""); - let rhs_str = snippet(cx, right_const_expr.span, ""); + let expr_str = snippet(cx, left_expr.span, ".."); + let lhs_str = snippet(cx, left_const_expr.span, ""); + let rhs_str = snippet(cx, right_const_expr.span, ""); let note = match ordering { Ordering::Less => format!("since `{lhs_str}` < `{rhs_str}`, the expression evaluates to false for any value of `{expr_str}`"), Ordering::Equal => format!("`{expr_str}` cannot simultaneously be greater than and less than `{lhs_str}`"), From 44cd3bcbbad87b9ffd1cf4c2cc97d8bc1795f660 Mon Sep 17 00:00:00 2001 From: ouz-a Date: Sat, 5 Aug 2023 12:02:39 +0300 Subject: [PATCH 36/49] cleanup misinformation regarding has_deref --- clippy_lints/src/dereference.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index a5ec979ccd978..aeaf9fae89142 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -1170,7 +1170,7 @@ fn referent_used_exactly_once<'tcx>( && let [location] = *local_assignments(mir, local).as_slice() && let Some(statement) = mir.basic_blocks[location.block].statements.get(location.statement_index) && let StatementKind::Assign(box (_, Rvalue::Ref(_, _, place))) = statement.kind - && !place.has_deref() + && !place.is_indirect_first_projection() // Ensure not in a loop (https://github.com/rust-lang/rust-clippy/issues/9710) && TriColorDepthFirstSearch::new(&mir.basic_blocks).run_from(location.block, &mut CycleDetector).is_none() { From d62804624441c2f7bf3de45cd2c9b5c411b2d6a6 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Sun, 6 Aug 2023 17:19:43 +0000 Subject: [PATCH 37/49] Update clippy_lints/src/operators/mod.rs Co-authored-by: Catherine Flores --- clippy_lints/src/operators/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index fa8e4c00a9748..4635e1164cd31 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -320,12 +320,12 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for ineffective double comparisons against constants + /// Checks for ineffective double comparisons against constants. /// /// ### Why is this bad? - /// Only one of the comparisons has any effect on the result - /// The programmer probably intended to flip one of the comparison operators, - /// or compare a different value entirely + /// Only one of the comparisons has any effect on the result, the programmer + /// probably intended to flip one of the comparison operators, or compare a + /// different value entirely. /// /// ### Example /// ```rust From 3ff6fd2ac7022bcece0053aad942b3b604aec7c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 6 Aug 2023 23:02:27 +0200 Subject: [PATCH 38/49] Store the laziness of type aliases in the DefKind --- clippy_lints/src/init_numbered_fields.rs | 2 +- clippy_utils/src/lib.rs | 2 +- clippy_utils/src/ty/type_certainty/mod.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/init_numbered_fields.rs b/clippy_lints/src/init_numbered_fields.rs index f95d2c2edb1ef..b00fa104f9825 100644 --- a/clippy_lints/src/init_numbered_fields.rs +++ b/clippy_lints/src/init_numbered_fields.rs @@ -50,7 +50,7 @@ impl<'tcx> LateLintPass<'tcx> for NumberedFields { && fields .iter() .all(|f| f.ident.as_str().as_bytes().iter().all(u8::is_ascii_digit)) - && !matches!(cx.qpath_res(path, e.hir_id), Res::Def(DefKind::TyAlias, ..)) + && !matches!(cx.qpath_res(path, e.hir_id), Res::Def(DefKind::TyAlias { .. }, ..)) { let expr_spans = fields .iter() diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index cf30930b76eac..aeef7499ee0b0 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -286,7 +286,7 @@ pub fn is_wild(pat: &Pat<'_>) -> bool { /// Checks if the given `QPath` belongs to a type alias. pub fn is_ty_alias(qpath: &QPath<'_>) -> bool { match *qpath { - QPath::Resolved(_, path) => matches!(path.res, Res::Def(DefKind::TyAlias | DefKind::AssocTy, ..)), + QPath::Resolved(_, path) => matches!(path.res, Res::Def(DefKind::TyAlias { .. } | DefKind::AssocTy, ..)), QPath::TypeRelative(ty, _) if let TyKind::Path(qpath) = ty.kind => { is_ty_alias(&qpath) }, _ => false, } diff --git a/clippy_utils/src/ty/type_certainty/mod.rs b/clippy_utils/src/ty/type_certainty/mod.rs index 45b5483e323d5..3e8e694a2ac4e 100644 --- a/clippy_utils/src/ty/type_certainty/mod.rs +++ b/clippy_utils/src/ty/type_certainty/mod.rs @@ -219,7 +219,7 @@ fn path_segment_certainty( // See the comment preceding `qpath_certainty`. `def_id` could refer to a type or a value. let certainty = lhs.join_clearing_def_ids(rhs); if resolves_to_type { - if cx.tcx.def_kind(def_id) == DefKind::TyAlias { + if let DefKind::TyAlias { .. } = cx.tcx.def_kind(def_id) { adt_def_id(cx.tcx.type_of(def_id).instantiate_identity()) .map_or(certainty, |def_id| certainty.with_def_id(def_id)) } else { From f959ccc09b28b9aaa49b8282691b45564c9f1326 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Tue, 8 Aug 2023 17:19:53 +0200 Subject: [PATCH 39/49] [`redundant_guards`]: don't lint on floats --- clippy_lints/src/matches/redundant_guards.rs | 5 +++-- tests/ui/redundant_guards.fixed | 7 +++++++ tests/ui/redundant_guards.rs | 7 +++++++ tests/ui/redundant_guards.stderr | 16 ++++++++-------- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/clippy_lints/src/matches/redundant_guards.rs b/clippy_lints/src/matches/redundant_guards.rs index 6383326aa38df..0e7059caadb64 100644 --- a/clippy_lints/src/matches/redundant_guards.rs +++ b/clippy_lints/src/matches/redundant_guards.rs @@ -2,6 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::path_to_local; use clippy_utils::source::snippet_with_applicability; use clippy_utils::visitors::{for_each_expr, is_local_used}; +use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, Guard, MatchSource, Node, Pat, PatKind}; @@ -177,8 +178,8 @@ fn expr_can_be_pat(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { ExprKind::AddrOf(..) | ExprKind::Array(..) | ExprKind::Tup(..) - | ExprKind::Struct(..) - | ExprKind::Lit(..) => true, + | ExprKind::Struct(..) => true, + ExprKind::Lit(lit) if !matches!(lit.node, LitKind::Float(..)) => true, _ => false, } { return ControlFlow::Continue(()); diff --git a/tests/ui/redundant_guards.fixed b/tests/ui/redundant_guards.fixed index 77ac76668649b..2adedf3d02982 100644 --- a/tests/ui/redundant_guards.fixed +++ b/tests/ui/redundant_guards.fixed @@ -15,6 +15,13 @@ struct B { struct C(u32, u32); +fn issue11304() { + match 0.1 { + x if x == 0.0 => todo!(), + _ => todo!(), + } +} + fn main() { let c = C(1, 2); match c { diff --git a/tests/ui/redundant_guards.rs b/tests/ui/redundant_guards.rs index b072e4ea14d1d..cf2859952144c 100644 --- a/tests/ui/redundant_guards.rs +++ b/tests/ui/redundant_guards.rs @@ -15,6 +15,13 @@ struct B { struct C(u32, u32); +fn issue11304() { + match 0.1 { + x if x == 0.0 => todo!(), + _ => todo!(), + } +} + fn main() { let c = C(1, 2); match c { diff --git a/tests/ui/redundant_guards.stderr b/tests/ui/redundant_guards.stderr index c2a92071d1dfb..f1b3fd083814e 100644 --- a/tests/ui/redundant_guards.stderr +++ b/tests/ui/redundant_guards.stderr @@ -1,5 +1,5 @@ error: redundant guard - --> $DIR/redundant_guards.rs:21:20 + --> $DIR/redundant_guards.rs:28:20 | LL | C(x, y) if let 1 = y => .., | ^^^^^^^^^ @@ -12,7 +12,7 @@ LL + C(x, 1) => .., | error: redundant guard - --> $DIR/redundant_guards.rs:27:20 + --> $DIR/redundant_guards.rs:34:20 | LL | Some(x) if matches!(x, Some(1) if true) => .., | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -23,7 +23,7 @@ LL | Some(Some(1)) if true => .., | ~~~~~~~ ~~~~~~~ error: redundant guard - --> $DIR/redundant_guards.rs:28:20 + --> $DIR/redundant_guards.rs:35:20 | LL | Some(x) if matches!(x, Some(1)) => { | ^^^^^^^^^^^^^^^^^^^^ @@ -35,7 +35,7 @@ LL + Some(Some(1)) => { | error: redundant guard - --> $DIR/redundant_guards.rs:32:20 + --> $DIR/redundant_guards.rs:39:20 | LL | Some(x) if let Some(1) = x => .., | ^^^^^^^^^^^^^^^ @@ -47,7 +47,7 @@ LL + Some(Some(1)) => .., | error: redundant guard - --> $DIR/redundant_guards.rs:33:20 + --> $DIR/redundant_guards.rs:40:20 | LL | Some(x) if x == Some(2) => .., | ^^^^^^^^^^^^ @@ -59,7 +59,7 @@ LL + Some(Some(2)) => .., | error: redundant guard - --> $DIR/redundant_guards.rs:56:20 + --> $DIR/redundant_guards.rs:63:20 | LL | B { e } if matches!(e, Some(A(2))) => .., | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -71,7 +71,7 @@ LL + B { e: Some(A(2)) } => .., | error: redundant guard - --> $DIR/redundant_guards.rs:93:20 + --> $DIR/redundant_guards.rs:100:20 | LL | E::A(y) if y == "not from an or pattern" => {}, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -83,7 +83,7 @@ LL + E::A("not from an or pattern") => {}, | error: redundant guard - --> $DIR/redundant_guards.rs:100:14 + --> $DIR/redundant_guards.rs:107:14 | LL | x if matches!(x, Some(0)) => .., | ^^^^^^^^^^^^^^^^^^^^ From b6156502afc765bac1ef48242009fe03c53232cc Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Tue, 8 Aug 2023 17:52:17 +0200 Subject: [PATCH 40/49] document the new behavior and add test for float in struct --- clippy_lints/src/matches/redundant_guards.rs | 5 +++++ tests/ui/redundant_guards.fixed | 6 ++++++ tests/ui/redundant_guards.rs | 6 ++++++ tests/ui/redundant_guards.stderr | 16 ++++++++-------- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/matches/redundant_guards.rs b/clippy_lints/src/matches/redundant_guards.rs index 0e7059caadb64..29af4812351e3 100644 --- a/clippy_lints/src/matches/redundant_guards.rs +++ b/clippy_lints/src/matches/redundant_guards.rs @@ -161,6 +161,11 @@ fn emit_redundant_guards<'tcx>( } /// Checks if the given `Expr` can also be represented as a `Pat`. +/// +/// All literals generally also work as patterns, however float literals are special. +/// They are currently (as of 2023/08/08) still allowed in patterns, but that will become +/// an error in the future, and rustc already actively warns against this (see rust#41620), +/// so we don't consider those as usable within patterns for linting purposes. fn expr_can_be_pat(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { for_each_expr(expr, |expr| { if match expr.kind { diff --git a/tests/ui/redundant_guards.fixed b/tests/ui/redundant_guards.fixed index 2adedf3d02982..49d7336ee3717 100644 --- a/tests/ui/redundant_guards.fixed +++ b/tests/ui/redundant_guards.fixed @@ -15,11 +15,17 @@ struct B { struct C(u32, u32); +#[derive(PartialEq)] +struct FloatWrapper(f32); fn issue11304() { match 0.1 { x if x == 0.0 => todo!(), _ => todo!(), } + match FloatWrapper(0.1) { + x if x == FloatWrapper(0.0) => todo!(), + _ => todo!(), + } } fn main() { diff --git a/tests/ui/redundant_guards.rs b/tests/ui/redundant_guards.rs index cf2859952144c..87761010de2ce 100644 --- a/tests/ui/redundant_guards.rs +++ b/tests/ui/redundant_guards.rs @@ -15,11 +15,17 @@ struct B { struct C(u32, u32); +#[derive(PartialEq)] +struct FloatWrapper(f32); fn issue11304() { match 0.1 { x if x == 0.0 => todo!(), _ => todo!(), } + match FloatWrapper(0.1) { + x if x == FloatWrapper(0.0) => todo!(), + _ => todo!(), + } } fn main() { diff --git a/tests/ui/redundant_guards.stderr b/tests/ui/redundant_guards.stderr index f1b3fd083814e..5bdf43d23c533 100644 --- a/tests/ui/redundant_guards.stderr +++ b/tests/ui/redundant_guards.stderr @@ -1,5 +1,5 @@ error: redundant guard - --> $DIR/redundant_guards.rs:28:20 + --> $DIR/redundant_guards.rs:34:20 | LL | C(x, y) if let 1 = y => .., | ^^^^^^^^^ @@ -12,7 +12,7 @@ LL + C(x, 1) => .., | error: redundant guard - --> $DIR/redundant_guards.rs:34:20 + --> $DIR/redundant_guards.rs:40:20 | LL | Some(x) if matches!(x, Some(1) if true) => .., | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -23,7 +23,7 @@ LL | Some(Some(1)) if true => .., | ~~~~~~~ ~~~~~~~ error: redundant guard - --> $DIR/redundant_guards.rs:35:20 + --> $DIR/redundant_guards.rs:41:20 | LL | Some(x) if matches!(x, Some(1)) => { | ^^^^^^^^^^^^^^^^^^^^ @@ -35,7 +35,7 @@ LL + Some(Some(1)) => { | error: redundant guard - --> $DIR/redundant_guards.rs:39:20 + --> $DIR/redundant_guards.rs:45:20 | LL | Some(x) if let Some(1) = x => .., | ^^^^^^^^^^^^^^^ @@ -47,7 +47,7 @@ LL + Some(Some(1)) => .., | error: redundant guard - --> $DIR/redundant_guards.rs:40:20 + --> $DIR/redundant_guards.rs:46:20 | LL | Some(x) if x == Some(2) => .., | ^^^^^^^^^^^^ @@ -59,7 +59,7 @@ LL + Some(Some(2)) => .., | error: redundant guard - --> $DIR/redundant_guards.rs:63:20 + --> $DIR/redundant_guards.rs:69:20 | LL | B { e } if matches!(e, Some(A(2))) => .., | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -71,7 +71,7 @@ LL + B { e: Some(A(2)) } => .., | error: redundant guard - --> $DIR/redundant_guards.rs:100:20 + --> $DIR/redundant_guards.rs:106:20 | LL | E::A(y) if y == "not from an or pattern" => {}, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -83,7 +83,7 @@ LL + E::A("not from an or pattern") => {}, | error: redundant guard - --> $DIR/redundant_guards.rs:107:14 + --> $DIR/redundant_guards.rs:113:14 | LL | x if matches!(x, Some(0)) => .., | ^^^^^^^^^^^^^^^^^^^^ From 830bac554898fa6e3969876134324df4b41670c1 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Wed, 9 Aug 2023 14:00:06 +0200 Subject: [PATCH 41/49] clarify why `Vec::new()` + resize is worse than `vec![0; N]` --- clippy_lints/src/slow_vector_initialization.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index 54a33eb2986d3..2117bcae22cbe 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -20,6 +20,20 @@ declare_clippy_lint! { /// These structures are non-idiomatic and less efficient than simply using /// `vec![0; len]`. /// + /// More specifically, for `vec![0; len]`, the compiler can use a more specialized type of allocation + /// that also zero-initializes the allocated memory in the same call + /// (see: [alloc_zeroed](https://doc.rust-lang.org/stable/std/alloc/trait.GlobalAlloc.html#method.alloc_zeroed)). + /// + /// Writing `Vec::new()` followed by `vec.resize(len, 0)` is suboptimal because, + /// while it does do the same number of allocations, + /// it involves two operations for allocating and initializing. + /// The `resize` call first allocates memory (since `Vec::new()` did not), and only *then* zero-initializes it. + /// + /// Writing `Vec::with_capacity(size)` followed by `vec.resize(len, 0)` is similar. + /// The allocation shifts from `resize` to `with_capacity`, + /// but the zero-initialization still happens separately, + /// when it could be done in one call with `vec![0; len]` (`alloc_zeroed`). + /// /// ### Example /// ```rust /// # use core::iter::repeat; @@ -32,6 +46,9 @@ declare_clippy_lint! { /// /// let mut vec2 = Vec::with_capacity(len); /// vec2.extend(repeat(0).take(len)); + /// + /// let mut vec3 = Vec::new(); + /// vec3.resize(len, 0); /// ``` /// /// Use instead: @@ -39,6 +56,7 @@ declare_clippy_lint! { /// # let len = 4; /// let mut vec1 = vec![0; len]; /// let mut vec2 = vec![0; len]; + /// let mut vec3 = vec![0; len]; /// ``` #[clippy::version = "1.32.0"] pub SLOW_VECTOR_INITIALIZATION, From d2acfb37b3344de3405457e02c4703cfc74bcb95 Mon Sep 17 00:00:00 2001 From: Timo <30553356+y21@users.noreply.github.com> Date: Wed, 9 Aug 2023 14:44:42 +0200 Subject: [PATCH 42/49] Reword paragraph Co-authored-by: Dirkjan Ochtman --- clippy_lints/src/slow_vector_initialization.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index 2117bcae22cbe..1001441b03d51 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -20,7 +20,7 @@ declare_clippy_lint! { /// These structures are non-idiomatic and less efficient than simply using /// `vec![0; len]`. /// - /// More specifically, for `vec![0; len]`, the compiler can use a more specialized type of allocation + /// Specifically, for `vec![0; len]`, the compiler can use a specialized type of allocation /// that also zero-initializes the allocated memory in the same call /// (see: [alloc_zeroed](https://doc.rust-lang.org/stable/std/alloc/trait.GlobalAlloc.html#method.alloc_zeroed)). /// From dd25cc349be3dae9b7c7f1ad0465ab14fab773e3 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Wed, 9 Aug 2023 14:48:31 +0200 Subject: [PATCH 43/49] Remove unnecessary paragraph, move examples --- clippy_lints/src/slow_vector_initialization.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index 1001441b03d51..c9ab622ad25d3 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -29,26 +29,18 @@ declare_clippy_lint! { /// it involves two operations for allocating and initializing. /// The `resize` call first allocates memory (since `Vec::new()` did not), and only *then* zero-initializes it. /// - /// Writing `Vec::with_capacity(size)` followed by `vec.resize(len, 0)` is similar. - /// The allocation shifts from `resize` to `with_capacity`, - /// but the zero-initialization still happens separately, - /// when it could be done in one call with `vec![0; len]` (`alloc_zeroed`). - /// /// ### Example /// ```rust /// # use core::iter::repeat; /// # let len = 4; - /// let mut vec1 = Vec::with_capacity(len); + /// let mut vec1 = Vec::new(); /// vec1.resize(len, 0); /// - /// let mut vec1 = Vec::with_capacity(len); - /// vec1.resize(vec1.capacity(), 0); - /// /// let mut vec2 = Vec::with_capacity(len); - /// vec2.extend(repeat(0).take(len)); + /// vec2.resize(len, 0); /// - /// let mut vec3 = Vec::new(); - /// vec3.resize(len, 0); + /// let mut vec3 = Vec::with_capacity(len); + /// vec3.extend(repeat(0).take(len)); /// ``` /// /// Use instead: From 61e9b157e2f81962f17de4274eb44cb0dc25983f Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 10 Aug 2023 21:49:36 +0200 Subject: [PATCH 44/49] Bump nightly version -> 2023-08-10 --- rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain b/rust-toolchain index 833608faff06f..8b3f819f0cde1 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2023-07-28" +channel = "nightly-2023-08-10" components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] From beb57f074eb83643dde6ad960edadd3dbe4e6109 Mon Sep 17 00:00:00 2001 From: Catherine Flores Date: Thu, 10 Aug 2023 16:21:35 -0500 Subject: [PATCH 45/49] Don't ICE with late bound regions --- .../src/methods/filter_map_bool_then.rs | 14 +++++++++++--- tests/ui/filter_map_bool_then.fixed | 12 +++++++++++- tests/ui/filter_map_bool_then.rs | 12 +++++++++++- tests/ui/filter_map_bool_then.stderr | 18 ++++++++++++------ 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/clippy_lints/src/methods/filter_map_bool_then.rs b/clippy_lints/src/methods/filter_map_bool_then.rs index 4aee22a4afc3b..fafc970977009 100644 --- a/clippy_lints/src/methods/filter_map_bool_then.rs +++ b/clippy_lints/src/methods/filter_map_bool_then.rs @@ -1,3 +1,4 @@ +use super::FILTER_MAP_BOOL_THEN; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::paths::BOOL_THEN; use clippy_utils::source::snippet_opt; @@ -7,10 +8,9 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::Binder; use rustc_span::{sym, Span}; -use super::FILTER_MAP_BOOL_THEN; - pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &Expr<'_>, call_span: Span) { if !in_external_macro(cx.sess(), expr.span) && is_trait_method(cx, expr, sym::Iterator) @@ -21,7 +21,15 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: & // `inputs` and `params` here as we need both the type and the span && let param_ty = closure.fn_decl.inputs[0] && let param = body.params[0] - && is_copy(cx, cx.typeck_results().node_type(param_ty.hir_id).peel_refs()) + // Issue #11309 + && let param_ty = cx.tcx.liberate_late_bound_regions( + closure.def_id.to_def_id(), + Binder::bind_with_vars( + cx.typeck_results().node_type(param_ty.hir_id), + cx.tcx.late_bound_vars(cx.tcx.hir().local_def_id_to_hir_id(closure.def_id)), + ), + ) + && is_copy(cx, param_ty) && let ExprKind::MethodCall(_, recv, [then_arg], _) = value.kind && let ExprKind::Closure(then_closure) = then_arg.kind && let then_body = peel_blocks(cx.tcx.hir().body(then_closure.body).value) diff --git a/tests/ui/filter_map_bool_then.fixed b/tests/ui/filter_map_bool_then.fixed index 3e72fee4b072a..b85e3659a4c77 100644 --- a/tests/ui/filter_map_bool_then.fixed +++ b/tests/ui/filter_map_bool_then.fixed @@ -4,6 +4,7 @@ clippy::clone_on_copy, clippy::map_identity, clippy::unnecessary_lazy_evaluations, + clippy::unnecessary_filter_map, unused )] #![warn(clippy::filter_map_bool_then)] @@ -29,9 +30,14 @@ fn main() { .copied() .filter(|&i| i != 1000) .filter(|&i| (i.clone() % 2 == 0)).map(|i| i + 1); + // Despite this is non-copy, `is_copy` still returns true (at least now) because it's `&NonCopy`, + // and any `&` is `Copy`. So since we can dereference it in `filter` (since it's then `&&NonCopy`), + // we can lint this and still get the same input type. + let v = vec![NonCopy, NonCopy]; + v.clone().iter().filter(|&i| (i == &NonCopy)).map(|i| i); // Do not lint let v = vec![NonCopy, NonCopy]; - v.clone().iter().filter_map(|i| (i == &NonCopy).then(|| i)); + v.clone().into_iter().filter_map(|i| (i == NonCopy).then(|| i)); external! { let v = vec![1, 2, 3, 4, 5, 6]; v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); @@ -42,3 +48,7 @@ fn main() { v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); } } + +fn issue11309<'a>(iter: impl Iterator) -> Vec<&'a str> { + iter.filter_map(|(_, s): (&str, _)| Some(s)).collect() +} diff --git a/tests/ui/filter_map_bool_then.rs b/tests/ui/filter_map_bool_then.rs index 38a04e57de456..c009b4e6ebeb0 100644 --- a/tests/ui/filter_map_bool_then.rs +++ b/tests/ui/filter_map_bool_then.rs @@ -4,6 +4,7 @@ clippy::clone_on_copy, clippy::map_identity, clippy::unnecessary_lazy_evaluations, + clippy::unnecessary_filter_map, unused )] #![warn(clippy::filter_map_bool_then)] @@ -29,9 +30,14 @@ fn main() { .copied() .filter(|&i| i != 1000) .filter_map(|i| (i.clone() % 2 == 0).then(|| i + 1)); - // Do not lint + // Despite this is non-copy, `is_copy` still returns true (at least now) because it's `&NonCopy`, + // and any `&` is `Copy`. So since we can dereference it in `filter` (since it's then `&&NonCopy`), + // we can lint this and still get the same input type. let v = vec![NonCopy, NonCopy]; v.clone().iter().filter_map(|i| (i == &NonCopy).then(|| i)); + // Do not lint + let v = vec![NonCopy, NonCopy]; + v.clone().into_iter().filter_map(|i| (i == NonCopy).then(|| i)); external! { let v = vec![1, 2, 3, 4, 5, 6]; v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); @@ -42,3 +48,7 @@ fn main() { v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); } } + +fn issue11309<'a>(iter: impl Iterator) -> Vec<&'a str> { + iter.filter_map(|(_, s): (&str, _)| Some(s)).collect() +} diff --git a/tests/ui/filter_map_bool_then.stderr b/tests/ui/filter_map_bool_then.stderr index b411cd83dfd29..b47f8eac7f005 100644 --- a/tests/ui/filter_map_bool_then.stderr +++ b/tests/ui/filter_map_bool_then.stderr @@ -1,5 +1,5 @@ error: usage of `bool::then` in `filter_map` - --> $DIR/filter_map_bool_then.rs:19:22 + --> $DIR/filter_map_bool_then.rs:20:22 | LL | v.clone().iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i % 2 == 0)).map(|i| i + 1)` @@ -7,28 +7,34 @@ LL | v.clone().iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); = note: `-D clippy::filter-map-bool-then` implied by `-D warnings` error: usage of `bool::then` in `filter_map` - --> $DIR/filter_map_bool_then.rs:20:27 + --> $DIR/filter_map_bool_then.rs:21:27 | LL | v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i % 2 == 0)).map(|i| i + 1)` error: usage of `bool::then` in `filter_map` - --> $DIR/filter_map_bool_then.rs:23:10 + --> $DIR/filter_map_bool_then.rs:24:10 | LL | .filter_map(|i| -> Option<_> { (i % 2 == 0).then(|| i + 1) }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i % 2 == 0)).map(|i| i + 1)` error: usage of `bool::then` in `filter_map` - --> $DIR/filter_map_bool_then.rs:27:10 + --> $DIR/filter_map_bool_then.rs:28:10 | LL | .filter_map(|i| (i % 2 == 0).then(|| i + 1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i % 2 == 0)).map(|i| i + 1)` error: usage of `bool::then` in `filter_map` - --> $DIR/filter_map_bool_then.rs:31:10 + --> $DIR/filter_map_bool_then.rs:32:10 | LL | .filter_map(|i| (i.clone() % 2 == 0).then(|| i + 1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i.clone() % 2 == 0)).map(|i| i + 1)` -error: aborting due to 5 previous errors +error: usage of `bool::then` in `filter_map` + --> $DIR/filter_map_bool_then.rs:37:22 + | +LL | v.clone().iter().filter_map(|i| (i == &NonCopy).then(|| i)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i == &NonCopy)).map(|i| i)` + +error: aborting due to 6 previous errors From 7b2fd81f43b5f0ddd1f39916224a0e1c334acedc Mon Sep 17 00:00:00 2001 From: Catherine Flores Date: Thu, 10 Aug 2023 16:33:07 -0500 Subject: [PATCH 46/49] Add test for `&mut` --- tests/ui/filter_map_bool_then.fixed | 4 ++++ tests/ui/filter_map_bool_then.rs | 4 ++++ tests/ui/filter_map_bool_then.stderr | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/ui/filter_map_bool_then.fixed b/tests/ui/filter_map_bool_then.fixed index b85e3659a4c77..e5c9f783f6b32 100644 --- a/tests/ui/filter_map_bool_then.fixed +++ b/tests/ui/filter_map_bool_then.fixed @@ -33,11 +33,15 @@ fn main() { // Despite this is non-copy, `is_copy` still returns true (at least now) because it's `&NonCopy`, // and any `&` is `Copy`. So since we can dereference it in `filter` (since it's then `&&NonCopy`), // we can lint this and still get the same input type. + // See: let v = vec![NonCopy, NonCopy]; v.clone().iter().filter(|&i| (i == &NonCopy)).map(|i| i); // Do not lint let v = vec![NonCopy, NonCopy]; v.clone().into_iter().filter_map(|i| (i == NonCopy).then(|| i)); + // `&mut` is `!Copy`. + let v = vec![NonCopy, NonCopy]; + v.clone().iter_mut().filter_map(|i| (i == &mut NonCopy).then(|| i)); external! { let v = vec![1, 2, 3, 4, 5, 6]; v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); diff --git a/tests/ui/filter_map_bool_then.rs b/tests/ui/filter_map_bool_then.rs index c009b4e6ebeb0..7c9b99df78cbb 100644 --- a/tests/ui/filter_map_bool_then.rs +++ b/tests/ui/filter_map_bool_then.rs @@ -33,11 +33,15 @@ fn main() { // Despite this is non-copy, `is_copy` still returns true (at least now) because it's `&NonCopy`, // and any `&` is `Copy`. So since we can dereference it in `filter` (since it's then `&&NonCopy`), // we can lint this and still get the same input type. + // See: let v = vec![NonCopy, NonCopy]; v.clone().iter().filter_map(|i| (i == &NonCopy).then(|| i)); // Do not lint let v = vec![NonCopy, NonCopy]; v.clone().into_iter().filter_map(|i| (i == NonCopy).then(|| i)); + // `&mut` is `!Copy`. + let v = vec![NonCopy, NonCopy]; + v.clone().iter_mut().filter_map(|i| (i == &mut NonCopy).then(|| i)); external! { let v = vec![1, 2, 3, 4, 5, 6]; v.clone().into_iter().filter_map(|i| (i % 2 == 0).then(|| i + 1)); diff --git a/tests/ui/filter_map_bool_then.stderr b/tests/ui/filter_map_bool_then.stderr index b47f8eac7f005..fffa5252e5f6c 100644 --- a/tests/ui/filter_map_bool_then.stderr +++ b/tests/ui/filter_map_bool_then.stderr @@ -31,7 +31,7 @@ LL | .filter_map(|i| (i.clone() % 2 == 0).then(|| i + 1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i.clone() % 2 == 0)).map(|i| i + 1)` error: usage of `bool::then` in `filter_map` - --> $DIR/filter_map_bool_then.rs:37:22 + --> $DIR/filter_map_bool_then.rs:38:22 | LL | v.clone().iter().filter_map(|i| (i == &NonCopy).then(|| i)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&i| (i == &NonCopy)).map(|i| i)` From a5f62bdfcdf826c1b03603e9a5bc3a052db853c2 Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Thu, 10 Aug 2023 19:42:59 -0700 Subject: [PATCH 47/49] redundant_locals: fix FPs on mutated shadows When a mutable binding is shadowed by a mutable binding of the same name in a different scope, mutations in that scope have different meaning. This commit fixes spurious `redundant_locals` emissions on such locals. --- clippy_lints/src/redundant_locals.rs | 11 +++++++++++ tests/ui/redundant_locals.rs | 9 +++++++++ tests/ui/redundant_locals.stderr | 22 +++++++++++----------- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/clippy_lints/src/redundant_locals.rs b/clippy_lints/src/redundant_locals.rs index 140ae837a1723..0fc297d9111f2 100644 --- a/clippy_lints/src/redundant_locals.rs +++ b/clippy_lints/src/redundant_locals.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::is_from_proc_macro; use clippy_utils::ty::needs_ordered_drop; +use rustc_ast::Mutability; use rustc_hir::def::Res; use rustc_hir::{BindingAnnotation, ByRef, Expr, ExprKind, HirId, Local, Node, Pat, PatKind, QPath}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -60,6 +61,8 @@ impl<'tcx> LateLintPass<'tcx> for RedundantLocals { if let Node::Pat(binding_pat) = cx.tcx.hir().get(binding_id); // the previous binding has the same mutability if find_binding(binding_pat, ident).unwrap().1 == mutability; + // the local does not change the effect of assignments to the binding. see #11290 + if !affects_assignments(cx, mutability, binding_id, local.hir_id); // the local does not affect the code's drop behavior if !affects_drop_behavior(cx, binding_id, local.hir_id, expr); // the local is user-controlled @@ -92,6 +95,14 @@ fn find_binding(pat: &Pat<'_>, name: Ident) -> Option { ret } +/// Check if a rebinding of a local changes the effect of assignments to the binding. +fn affects_assignments(cx: &LateContext<'_>, mutability: Mutability, bind: HirId, rebind: HirId) -> bool { + let hir = cx.tcx.hir(); + + // the binding is mutable and the rebinding is in a different scope than the original binding + mutability == Mutability::Mut && hir.get_enclosing_scope(bind) != hir.get_enclosing_scope(rebind) +} + /// Check if a rebinding of a local affects the code's drop behavior. fn affects_drop_behavior<'tcx>(cx: &LateContext<'tcx>, bind: HirId, rebind: HirId, rebind_expr: &Expr<'tcx>) -> bool { let hir = cx.tcx.hir(); diff --git a/tests/ui/redundant_locals.rs b/tests/ui/redundant_locals.rs index e74c78a50b672..80af38f47b86d 100644 --- a/tests/ui/redundant_locals.rs +++ b/tests/ui/redundant_locals.rs @@ -27,6 +27,15 @@ fn downgraded_mutability() { let x = x; } +// see #11290 +fn shadow_mutation() { + let mut x = 1; + { + let mut x = x; + x = 2; + } +} + fn coercion(par: &mut i32) { let par: &i32 = par; diff --git a/tests/ui/redundant_locals.stderr b/tests/ui/redundant_locals.stderr index df07dd2f453b1..587de05752476 100644 --- a/tests/ui/redundant_locals.stderr +++ b/tests/ui/redundant_locals.stderr @@ -20,7 +20,7 @@ LL | let mut x = x; = help: remove the redefinition of `x` error: redundant redefinition of a binding - --> $DIR/redundant_locals.rs:37:14 + --> $DIR/redundant_locals.rs:46:14 | LL | fn parameter(x: i32) { | ^ @@ -30,7 +30,7 @@ LL | let x = x; = help: remove the redefinition of `x` error: redundant redefinition of a binding - --> $DIR/redundant_locals.rs:42:9 + --> $DIR/redundant_locals.rs:51:9 | LL | let x = 1; | ^ @@ -40,7 +40,7 @@ LL | let x = x; = help: remove the redefinition of `x` error: redundant redefinition of a binding - --> $DIR/redundant_locals.rs:43:9 + --> $DIR/redundant_locals.rs:52:9 | LL | let x = x; | ^ @@ -50,7 +50,7 @@ LL | let x = x; = help: remove the redefinition of `x` error: redundant redefinition of a binding - --> $DIR/redundant_locals.rs:44:9 + --> $DIR/redundant_locals.rs:53:9 | LL | let x = x; | ^ @@ -60,7 +60,7 @@ LL | let x = x; = help: remove the redefinition of `x` error: redundant redefinition of a binding - --> $DIR/redundant_locals.rs:45:9 + --> $DIR/redundant_locals.rs:54:9 | LL | let x = x; | ^ @@ -70,7 +70,7 @@ LL | let x = x; = help: remove the redefinition of `x` error: redundant redefinition of a binding - --> $DIR/redundant_locals.rs:50:9 + --> $DIR/redundant_locals.rs:59:9 | LL | let a = 1; | ^ @@ -81,7 +81,7 @@ LL | let a = a; = help: remove the redefinition of `a` error: redundant redefinition of a binding - --> $DIR/redundant_locals.rs:51:9 + --> $DIR/redundant_locals.rs:60:9 | LL | let b = 2; | ^ @@ -92,7 +92,7 @@ LL | let b = b; = help: remove the redefinition of `b` error: redundant redefinition of a binding - --> $DIR/redundant_locals.rs:58:13 + --> $DIR/redundant_locals.rs:67:13 | LL | let x = 1; | ^ @@ -102,7 +102,7 @@ LL | let x = x; = help: remove the redefinition of `x` error: redundant redefinition of a binding - --> $DIR/redundant_locals.rs:65:13 + --> $DIR/redundant_locals.rs:74:13 | LL | let x = 1; | ^ @@ -112,7 +112,7 @@ LL | let x = x; = help: remove the redefinition of `x` error: redundant redefinition of a binding - --> $DIR/redundant_locals.rs:68:6 + --> $DIR/redundant_locals.rs:77:6 | LL | |x: i32| { | ^ @@ -122,7 +122,7 @@ LL | let x = x; = help: remove the redefinition of `x` error: redundant redefinition of a binding - --> $DIR/redundant_locals.rs:85:9 + --> $DIR/redundant_locals.rs:94:9 | LL | let x = 1; | ^ From 0fc051ddbaaa4daf567c46240c6effabcde176f5 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 11 Aug 2023 10:51:18 +0200 Subject: [PATCH 48/49] Fix redundant_locals for Async desugaring --- clippy_lints/src/redundant_locals.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clippy_lints/src/redundant_locals.rs b/clippy_lints/src/redundant_locals.rs index 140ae837a1723..1271cf385382f 100644 --- a/clippy_lints/src/redundant_locals.rs +++ b/clippy_lints/src/redundant_locals.rs @@ -7,6 +7,7 @@ use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::Ident; +use rustc_span::DesugaringKind; declare_clippy_lint! { /// ### What it does @@ -45,6 +46,7 @@ declare_lint_pass!(RedundantLocals => [REDUNDANT_LOCALS]); impl<'tcx> LateLintPass<'tcx> for RedundantLocals { fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { if_chain! { + if !local.span.is_desugaring(DesugaringKind::Async); // the pattern is a single by-value binding if let PatKind::Binding(BindingAnnotation(ByRef::No, mutability), _, ident, None) = local.pat.kind; // the binding is not type-ascribed From 3927677234aa0200b2af36e7f6e08ce867fb74e2 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 11 Aug 2023 10:53:18 +0200 Subject: [PATCH 49/49] Dogfood and bless tests --- clippy_utils/src/sugg.rs | 2 +- tests/ui-internal/custom_ice_message.stderr | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index a43a81bc63a13..ee5a49a207331 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -1010,7 +1010,7 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> { projections_handled = true; }, // note: unable to trigger `Subslice` kind in tests - ProjectionKind::Subslice => (), + ProjectionKind::Subslice | // Doesn't have surface syntax. Only occurs in patterns. ProjectionKind::OpaqueCast => (), ProjectionKind::Deref => { diff --git a/tests/ui-internal/custom_ice_message.stderr b/tests/ui-internal/custom_ice_message.stderr index 31df0ebd9fd6b..d8b158816c9f8 100644 --- a/tests/ui-internal/custom_ice_message.stderr +++ b/tests/ui-internal/custom_ice_message.stderr @@ -1,4 +1,5 @@ -thread '' panicked at 'Would you like some help with that?', clippy_lints/src/utils/internal_lints/produce_ice.rs +thread '' panicked at clippy_lints/src/utils/internal_lints/produce_ice.rs: +Would you like some help with that? note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: the compiler unexpectedly panicked. this is a bug.