From 50da77521e922f17735b9d97461925ca0f3dec1c Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 8 Jun 2023 18:47:44 +0200 Subject: [PATCH 01/20] 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 d24215c22924..e532dd61a829 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 cab02bb93c9c..03d9ea41a0b1 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 e5e31452149d..c028fcb5a376 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 783608a08410..4d8a7fd70f45 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 1662fdb2d0c64bb5d11111cc993231d33c13a45d Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Thu, 25 May 2023 05:21:44 +0000 Subject: [PATCH 02/20] 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 4d72b58cdf86..4c0b0d8f275e 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 fcd945de3386..bc5da35e52e3 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 362e559b21a5..e4d2fe3a1c3d 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 03/20] 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 5d7e1494fcfd..a43a81bc63a1 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 04/20] 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 c343f248d06d..91bc30ab5774 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 085c73a5f9fb..ccd2f06aad27 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 e563e41ab2aa..aed3d601bcac 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 05/20] 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 ccd2f06aad27..f48a5d9d2456 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 aed3d601bcac..a98e2038d5f0 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 b1980b1b523f..3aaee67e1d97 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 7be2cc0ca930..66cf4e315293 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 06/20] 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 4dc750c03b48..9b26c3573e18 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 87699fd0ca6e..8bb2fa925856 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 3c873a5901d4..79f9d45d597e 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 8cc01f1ef974..7e42924603a4 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 061086c4fc20..f19e09a18ec5 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 035511e89125..45b99df46b01 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 07/20] 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 1fddf0f50e32..c146f3ae95b3 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 119ff25918a7..6850eeb7a4cd 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 eeeef830af0a..8e1518144078 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 08/20] 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 00f70e586f42..573838ce63ef 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 09/20] 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 573838ce63ef..2c4d93e33ba7 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 b0e64a9c09773e5ac908d89bff3db95d35491308 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Mon, 31 Jul 2023 23:53:53 +0200 Subject: [PATCH 10/20] 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 4eb11a3ac857..5c69714bc1e9 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 b3b6e3b865fc..2655d93599e7 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 f8073dac3301..caaad6d11736 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 4624451cff4f..c4ae4f0e2bdd 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 f39bc06e6d73..e64cf2c87496 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 000000000000..04417c4c4600 --- /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 7adcd9ad0555..35a04b5e44a3 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 0ac6ef6496a8..d34de305f5dc 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 ae56f38d9ad5..86057bb74ee9 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 498d657b31f9..d4e0d2863348 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 8a9d978a1063..a5ec979ccd97 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 91bc30ab5774..e3f2026cfe9e 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 000000000000..379af9b2234c --- /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 22e10accd357..8d6fb8438b65 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 000000000000..419c77343441 --- /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 e6c42ebb8329..3c59b839a39c 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 d43e5cc9b2c3..bc4ec33b7334 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 50c433d31613..deba232bdd23 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 5e62cfd70ec0..9d6096ccb2ae 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 852f6736585b..0004a150d51b 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 a84a0a6eeb82..7b8c88235a97 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 744fd61bd135..dfb800ccf714 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 28ee24309cc4..6edca2d55f64 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 d1061171e4d6..6d16d1887540 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 000000000000..6383326aa38d --- /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 ad38f1394b46..9a7c00823b67 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 597a423b5377..c9eaa185acce 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 000000000000..4aee22a4afc3 --- /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 000000000000..1f8863f85218 --- /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 000000000000..6b696b42a693 --- /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 123ab520e485..dd694ce7393e 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 9165c1248f0a..8b2f57160af4 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 8add0656101e..81f9e2a77fce 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 000000000000..e3ec921da0ce --- /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 000000000000..70da6ad58bdc --- /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 35137c97101e..3404bdfe79be 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 cc64a2e79487..fabf3fa0c0c8 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 111bcaaecec3..937aac8d25ef 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 474a33b67e1c..000000000000 --- 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 2a60f2faca0a..c79a1a7b9d42 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 d323a16c2a73..c634de960d13 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 5e26601537f3..5ee26966fa71 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 8bb2fa925856..a70692d8ff86 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 35dd8fabe6e6..f9108145cdb6 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 377bddeaa5fe..9c7f7e1cd7f8 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 264c38df11f5..42299d8d42f5 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 000000000000..140ae837a172 --- /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 038dfe8e4803..ed42a422b4b5 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 e532dd61a829..49bdc6796049 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 2a494ff1fa67..351bacf5691f 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 355f907e2577..c9547cd95dca 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 3b282dbfa048..4b248c9c7900 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 858135c8d464..54a33eb2986d 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 2e00ed042a8b..7eec69820924 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 5e42cf7e4f3f..bc7c3897a6e8 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 76654bfe5360..58ae0656db73 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 fb08256931f6..4fef8c0717d8 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 357ac40b4559..802adbd4d2d5 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 fb359ee3bbe0..85b3b005f93d 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 45b99df46b01..cf30930b76ea 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 8e7513d740ab..da04266863f9 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 f3677e6f6141..914ea85ac280 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 fd39a246f48d..d0e15aa8bb32 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 000000000000..0e69ffa2212d --- /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 000000000000..45b5483e323d --- /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 20993398d1b7..39ef76348d75 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 8dafa723afa0..09f447b27eb4 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 b658101a10d8..833608faff06 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 ee17feed77a0..1d89477dcc16 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 cdc85cb33ca2..26b655076cf6 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 f714b2962331..385e25ce6b8f 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 a771d8b87c81..031982edbe9e 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 1a69bb24101e..c67166fc4b00 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 1a69bb24101e..c67166fc4b00 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 1a69bb24101e..c67166fc4b00 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 1a69bb24101e..c67166fc4b00 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 1a69bb24101e..c67166fc4b00 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 1a69bb24101e..c67166fc4b00 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 4dd9582aff89..74e40c09ebc8 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 4dd9582aff89..74e40c09ebc8 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 c70d92e359e4..ac21b3a44229 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 ece260b743df..4bc61dd62992 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 ece260b743df..4bc61dd62992 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 ece260b743df..4bc61dd62992 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 bb3a39d0720a..3491ccb0d47d 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 bb3a39d0720a..3491ccb0d47d 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 b88aeae2a9b8..31df0ebd9fd6 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 000000000000..a8900da4e6eb --- /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 000000000000..41b70644be8c --- /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 000000000000..d4c250a8ff23 --- /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 000000000000..59a621e9d1db --- /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 000000000000..8e2678f5fe69 --- /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 000000000000..d44d648c6411 --- /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 6ba26e977302..cdabe6460cdc 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 b6fcca0a7919..2940c2732552 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 7633b38dfb59..de3f2fb9e16e 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 ed75acee8a28..2ac2fa22086b 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 c92dd509ebb2..af219eed0b84 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 b3489714380a..21e65184d50d 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 cc09b17eb895..f29782ed80d1 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 000000000000..40ce4181bf34 --- /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 000000000000..f3e04b641673 --- /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 db7bd99e0ae6..ddabe7616d09 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 52fc17686fdf..92ecff6eb1af 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 0ac0b901df44..ff40a2074e56 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 000000000000..3e72fee4b072 --- /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 000000000000..38a04e57de45 --- /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 000000000000..b411cd83dfd2 --- /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 000000000000..c7f2b7b69507 --- /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 000000000000..d918f1ed466b --- /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 000000000000..54b2c414b620 --- /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 000000000000..facdc8cb17db --- /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 000000000000..89162e6b010e --- /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 000000000000..ce272b4c6cd7 --- /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 000000000000..d8f82d4410b8 --- /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 000000000000..7944da14feb0 --- /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 dad4543f84c0..e84b20e9fef0 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 a34fc565590b..774cc08685a7 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 5c338e3c5c8f..ad77346b75f2 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 8d70934476cb..3f52c10b7620 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 dd4fdd98822c..2f51bf274804 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 522e82299c0a..47127bdaec22 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 0e477798c406..66048fc90005 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 000000000000..3a3b84f93c46 --- /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 000000000000..f4374c281287 --- /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 701b3cd19041..04559f9ada43 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 aeb0a0c1e2e8..adb0389a043f 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 443fecae1aad..579b3c8c56f7 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 000000000000..1eb0984fe073 --- /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 000000000000..8c103ab1d5b3 --- /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 000000000000..80fecd59e6d1 --- /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 7e4d783a026c..64665cc906f6 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 9e69fb041330..ff1e2b8c9019 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 9dd376df2b48..35872a39a711 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 6dd1e066aebd..50d8d2722c23 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 882468b0f5f1..0e8672c02930 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 693a06bb5590..4e52b5efacf1 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 60f590661735..f19149cf9def 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 afdf1069f5e4..8f4e58981ea6 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 c8c1e5da05fe..b57b26284ff3 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 0fab224a29d3..03784442e2c9 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 02a0da86a4b8..3f756f5f0e59 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 23c812475c2a..1fdfbf9227e3 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 5e7280995c60..ae7b018d0e25 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 5e9d80bb6c48..0d426ce32f9d 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 000000000000..d6e47d07b0f8 --- /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 000000000000..4fc04d363a9b --- /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 000000000000..e1d91638d2c7 --- /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 65a1b467f811..61dbad939db4 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 7bba62686eec..cfa9dd58a300 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 8e59e4375d2e..6fee3cce619c 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 e72edf2a8e31..4b3cf948a1be 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 aa2da2174003..350f0f07e136 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 6deff0f3240a..581f3ad45c7d 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 b05b33e6ee2b..1f3987eb8917 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 7342b0c2914b..519f09165626 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 13e993d247b2..08075c382a22 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 c6025ef1f4da..ff2ad8644b49 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 08ba9753d7c4..4c7f605f4c2a 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 000000000000..656b45787e8a --- /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 000000000000..e3d8fce7b2ce --- /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 000000000000..77ac76668649 --- /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 000000000000..b072e4ea14d1 --- /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 000000000000..c2a92071d1df --- /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 000000000000..e74c78a50b67 --- /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 000000000000..df07dd2f453b --- /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 a63ba5809e4b..d9fcd98c56e0 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 631f9091672d..cbd9494f15ac 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 097ca827a426..b0e43924dbc9 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 03d9ea41a0b1..8ec19b120d12 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 c028fcb5a376..51a5976eb614 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 4d8a7fd70f45..e420ea1d2adb 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 000000000000..653f4533b331 --- /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 8e7f1d862cfd..9db038219b4a 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 8d9a67585cf1..78813e7cc1c3 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 9be8c5e59d01..1b40a43d0196 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 8321f6df224c..88b02f53be12 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 2ae9fa34d142..a3bb05bf176b 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 eb8524167c4a..8065e9e5fbc9 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 f7fa65ea9227..1620b76843a0 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 ca4fede17c93..b5cad88ad3ff 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 e7b1fd6a85f2..163ba94aff80 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 1515a7053e5d..0dcdb125ffd8 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 76f7e7895898..d35361599493 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 16be9f6d203a..cfb856861b8c 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 22376680a8e6..532ce4ac1911 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 000000000000..d7ab9c3397a1 --- /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 000000000000..9408d7bb2390 --- /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 000000000000..ff951b73dedd --- /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 22f904e3fd9e..7b74a83b6df9 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 ada64f89e6d2..93855cd7b5c2 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 a3b9c2b744c9..1097b29bba02 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 1816740870ad..930489fab764 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 0e47c4d023ef..f5baf3d8f744 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 79f7b70224a1..9968b383ebbc 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 f96a7c97f1aa..569415acbcee 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 be653e8efb79..50bdcf29d1fa 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 8efd44baf59f..2bf02f134142 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 c7723ef51f99..25b6b0f9b07b 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 f0443556fb4d..19411a01b67d 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 8e01c2674f16..3c8c6ec94c1b 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 5585b10ab903..2d5403ce3944 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 a52390861b40..2c228fbbc959 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 fb33c122fe33..3a995b41b179 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 276cd800b890..72d52c62355c 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 3065778d7790..7d713ea205f3 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 5823313b736c..7f603d6ef582 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 711fdce39624..41300aceb415 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 feb9325b77a6..2d1270d47174 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 69e46ab47362..1d188025e418 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 ffae8366b88c..8d9b72c4886c 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 08b89a18bbbd..acdb96942ba1 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 ad2a744908fc..55ccd00e1a2a 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 d2b9212223f7..af662c6def7e 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 c40b71f6ca7d..6856bb0ab375 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 11/20] 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 49bdc6796049..fc1fabcc0ae5 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 8ec19b120d12..8257bf2947ab 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 51a5976eb614..6569dad18d40 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 e420ea1d2adb..57e991e5695a 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 12/20] 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 140ae837a172..896bd79b20bf 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 b07de24a5838d4324b43c3d161bbd35454a5eba0 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Sat, 29 Jul 2023 08:20:25 +0000 Subject: [PATCH 13/20] 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 e3f2026cfe9e..9900dbdee6be 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 8d6fb8438b65..ba7957b0decc 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 a98e2038d5f0..f0a777c5b895 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 d0e15aa8bb32..717664805356 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 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 14/20] 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 385e25ce6b8f..e46f8bf6fabd 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 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 15/20] 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 a5ec979ccd97..137bd9c94510 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 83445c7a0ca5..57df5683c9d3 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 01a7c497cbe7..f507f45d5bb9 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 22c14d9b04dd..4f4f571773fb 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 7d1f8ef29c81..d3fd0e8639e9 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 2c20e9e86933..c4af46b8fd3a 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 7da9121fbb38..dd77c69ef88a 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 8019f906aca4..5153070cfe66 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 2b9def1a6884..201bb56efcdd 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 89dcac4d8494..bd53ebd48c88 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 ce7f9997b5a6..ac7bc9bcca47 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 e2029da8081f..8f885e9f729b 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 57ec3a1f1e63..367cd6bd4133 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 46457400abcb..f6b87b071b94 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 e6d3e72d1e62..5f2a324b05fb 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 a70692d8ff86..243192385c25 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 42299d8d42f5..8009b00b4b9c 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 8277ce724d42..4abfa0fc35c6 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 4d45091f4b5b..76f463fff7dc 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 e2cdc48b583c..23d6e2a845fd 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 98158ed0aee8..548fabb8b736 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 3766b8f8ed10..b6b653f6610d 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 7eec69820924..78ad52d8a879 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 6b51974d739a..f02c33cc6743 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 1d4fc24eb6c2..3fa51216c773 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 7e42924603a4..2d0d6f559ad6 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 89f8a65c5ae0..60fab1ec41ae 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 f19e09a18ec5..d38e3f1ae76b 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 3c969572b8e4..0bcefba75a7b 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 85b3b005f93d..fdc35cd4ddf8 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 cf30930b76ea..4cd8a8c3325c 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 45b5483e323d..0669ea72eb3e 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 09f447b27eb4..f83988a6e325 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 44cd3bcbbad87b9ffd1cf4c2cc97d8bc1795f660 Mon Sep 17 00:00:00 2001 From: ouz-a Date: Sat, 5 Aug 2023 12:02:39 +0300 Subject: [PATCH 16/20] 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 a5ec979ccd97..aeaf9fae8914 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 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 17/20] 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 f95d2c2edb1e..b00fa104f982 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 cf30930b76ea..aeef7499ee0b 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 45b5483e323d..3e8e694a2ac4 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 61e9b157e2f81962f17de4274eb44cb0dc25983f Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 10 Aug 2023 21:49:36 +0200 Subject: [PATCH 18/20] 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 833608faff06..8b3f819f0cde 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 0fc051ddbaaa4daf567c46240c6effabcde176f5 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 11 Aug 2023 10:51:18 +0200 Subject: [PATCH 19/20] 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 140ae837a172..1271cf385382 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 20/20] 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 a43a81bc63a1..ee5a49a20733 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 31df0ebd9fd6..d8b158816c9f 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.