From dff9164ac5a0118e78285869c16f35447b720354 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Thu, 2 May 2024 23:32:53 +0200 Subject: [PATCH 01/56] consider `copy_deref` a possible borrower --- clippy_utils/src/mir/possible_borrower.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/mir/possible_borrower.rs b/clippy_utils/src/mir/possible_borrower.rs index 06229ac938f9..b3210fa155c7 100644 --- a/clippy_utils/src/mir/possible_borrower.rs +++ b/clippy_utils/src/mir/possible_borrower.rs @@ -69,7 +69,7 @@ impl<'a, 'b, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'b, fn visit_assign(&mut self, place: &mir::Place<'tcx>, rvalue: &mir::Rvalue<'_>, _location: mir::Location) { let lhs = place.local; match rvalue { - mir::Rvalue::Ref(_, _, borrowed) => { + mir::Rvalue::Ref(_, _, borrowed) | mir::Rvalue::CopyForDeref(borrowed) => { self.possible_borrower.add(borrowed.local, lhs); }, other => { From 20d1bb1867e6b5b5c79d1af7cb56b91e290d0283 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Thu, 2 May 2024 23:38:31 +0200 Subject: [PATCH 02/56] [`assigning_clones`]: bail out when the source borrows from target --- clippy_lints/src/assigning_clones.rs | 65 +++++++++++++++++++++++++++- tests/ui/assigning_clones.fixed | 29 +++++++++++++ tests/ui/assigning_clones.rs | 29 +++++++++++++ 3 files changed, 122 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/assigning_clones.rs b/clippy_lints/src/assigning_clones.rs index 9202c46e8b6b..4819471746f0 100644 --- a/clippy_lints/src/assigning_clones.rs +++ b/clippy_lints/src/assigning_clones.rs @@ -1,16 +1,18 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::HirNode; +use clippy_utils::mir::{enclosing_mir, PossibleBorrowerMap}; use clippy_utils::sugg::Sugg; use clippy_utils::{is_trait_method, local_is_initialized, path_to_local}; use rustc_errors::Applicability; use rustc_hir::{self as hir, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::mir; use rustc_middle::ty::{self, Instance, Mutability}; use rustc_session::impl_lint_pass; use rustc_span::def_id::DefId; use rustc_span::symbol::sym; -use rustc_span::{ExpnKind, SyntaxContext}; +use rustc_span::{ExpnKind, Span, SyntaxContext}; declare_clippy_lint! { /// ### What it does @@ -144,6 +146,7 @@ fn extract_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option< }; Some(CallCandidate { + span: expr.span, target, kind, method_def_id: resolved_method.def_id(), @@ -215,6 +218,10 @@ fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallC return false; }; + if clone_source_borrows_from_dest(cx, lhs, call.span) { + return false; + } + // Now take a look if the impl block defines an implementation for the method that we're interested // in. If not, then we're using a default implementation, which is not interesting, so we will // not suggest the lint. @@ -222,6 +229,61 @@ fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallC implemented_fns.contains_key(&provided_fn.def_id) } +/// Checks if the data being cloned borrows from the place that is being assigned to: +/// +/// ``` +/// let mut s = String::new(); +/// let s2 = &s; +/// s = s2.to_owned(); +/// ``` +/// +/// This cannot be written `s2.clone_into(&mut s)` because it has conflicting borrows. +fn clone_source_borrows_from_dest(cx: &LateContext<'_>, lhs: &Expr<'_>, call_span: Span) -> bool { + let Some(mir) = enclosing_mir(cx.tcx, lhs.hir_id) else { + return false; + }; + let PossibleBorrowerMap { map: borrow_map, .. } = PossibleBorrowerMap::new(cx, mir); + + // The operation `dest = src.to_owned()` in MIR is split up across 3 blocks. + // For the doc example above that would be roughly: + // + // bb0: + // s2 = &s + // s_temp = ToOwned::to_owned(move s2) -> bb1 + // + // bb1: + // drop(s) -> bb2 // drop the old string + // + // bb2: + // s = s_temp + for bb in mir.basic_blocks.iter() { + let terminator = bb.terminator(); + + // Look for the to_owned/clone call. + if terminator.source_info.span == call_span + && let mir::TerminatorKind::Call { + args, + target: Some(drop_bb), + .. + } = &terminator.kind + && let [source] = &**args + && let mir::Operand::Move(source) = &source.node + // Block 2 only has the `drop()` terminator from to the assignment + && let drop_bb = &mir.basic_blocks[*drop_bb] + && let mir::TerminatorKind::Drop { target: assign_bb, .. } = drop_bb.terminator().kind + // Block 3 has the final assignment to the original local + && let assign_bb = &mir.basic_blocks[assign_bb] + && let [assignment, ..] = &*assign_bb.statements + && let mir::StatementKind::Assign(box (borrowed, _)) = &assignment.kind + && let Some(borrowers) = borrow_map.get(&borrowed.local) + && borrowers.contains(source.local) + { + return true; + } + } + false +} + fn suggest<'tcx>( cx: &LateContext<'tcx>, ctxt: SyntaxContext, @@ -255,6 +317,7 @@ enum TargetTrait { #[derive(Debug)] struct CallCandidate<'tcx> { + span: Span, target: TargetTrait, kind: CallKind<'tcx>, // DefId of the called method from an impl block that implements the target trait diff --git a/tests/ui/assigning_clones.fixed b/tests/ui/assigning_clones.fixed index 70ab43b49b3a..64a1f02eb051 100644 --- a/tests/ui/assigning_clones.fixed +++ b/tests/ui/assigning_clones.fixed @@ -272,3 +272,32 @@ impl<'a> Add for &'a mut HasCloneFrom { self } } + +mod borrowck_conflicts { + //! Cases where clone_into and friends cannot be used because the src/dest have conflicting + //! borrows. + use std::path::PathBuf; + + fn issue12444(mut name: String) { + let parts = name.split(", ").collect::>(); + let first = *parts.first().unwrap(); + name = first.to_owned(); + } + + fn issue12444_simple() { + let mut s = String::new(); + let s2 = &s; + s = s2.to_owned(); + } + + fn issue12460(mut name: String) { + if let Some(stripped_name) = name.strip_prefix("baz-") { + name = stripped_name.to_owned(); + } + } + + fn issue12749() { + let mut path = PathBuf::from("/a/b/c"); + path = path.components().as_path().to_owned(); + } +} diff --git a/tests/ui/assigning_clones.rs b/tests/ui/assigning_clones.rs index 9699fed100c8..b92452c35772 100644 --- a/tests/ui/assigning_clones.rs +++ b/tests/ui/assigning_clones.rs @@ -272,3 +272,32 @@ impl<'a> Add for &'a mut HasCloneFrom { self } } + +mod borrowck_conflicts { + //! Cases where clone_into and friends cannot be used because the src/dest have conflicting + //! borrows. + use std::path::PathBuf; + + fn issue12444(mut name: String) { + let parts = name.split(", ").collect::>(); + let first = *parts.first().unwrap(); + name = first.to_owned(); + } + + fn issue12444_simple() { + let mut s = String::new(); + let s2 = &s; + s = s2.to_owned(); + } + + fn issue12460(mut name: String) { + if let Some(stripped_name) = name.strip_prefix("baz-") { + name = stripped_name.to_owned(); + } + } + + fn issue12749() { + let mut path = PathBuf::from("/a/b/c"); + path = path.components().as_path().to_owned(); + } +} From 60508f546a07fa51169736766b7e16676724c496 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Fri, 3 May 2024 01:45:49 +0200 Subject: [PATCH 03/56] deal with non-`Drop` types and add a test case for projections --- clippy_lints/src/assigning_clones.rs | 39 ++++++++++++++++++---------- tests/ui/assigning_clones.fixed | 27 +++++++++++++++++++ tests/ui/assigning_clones.rs | 27 +++++++++++++++++++ 3 files changed, 80 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/assigning_clones.rs b/clippy_lints/src/assigning_clones.rs index 4819471746f0..920d1bae0b17 100644 --- a/clippy_lints/src/assigning_clones.rs +++ b/clippy_lints/src/assigning_clones.rs @@ -239,12 +239,23 @@ fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallC /// /// This cannot be written `s2.clone_into(&mut s)` because it has conflicting borrows. fn clone_source_borrows_from_dest(cx: &LateContext<'_>, lhs: &Expr<'_>, call_span: Span) -> bool { + /// If this basic block only exists to drop a local as part of an assignment, returns its + /// successor. Otherwise returns the basic block that was passed in. + fn skip_drop_block(mir: &mir::Body<'_>, bb: mir::BasicBlock) -> mir::BasicBlock { + if let mir::TerminatorKind::Drop { target, .. } = mir.basic_blocks[bb].terminator().kind { + target + } else { + bb + } + } + let Some(mir) = enclosing_mir(cx.tcx, lhs.hir_id) else { return false; }; let PossibleBorrowerMap { map: borrow_map, .. } = PossibleBorrowerMap::new(cx, mir); - // The operation `dest = src.to_owned()` in MIR is split up across 3 blocks. + // The operation `dest = src.to_owned()` in MIR is split up across 3 blocks *if* the type has `Drop` + // code. For types that don't, the second basic block is simply skipped. // For the doc example above that would be roughly: // // bb0: @@ -260,26 +271,28 @@ fn clone_source_borrows_from_dest(cx: &LateContext<'_>, lhs: &Expr<'_>, call_spa let terminator = bb.terminator(); // Look for the to_owned/clone call. - if terminator.source_info.span == call_span - && let mir::TerminatorKind::Call { - args, - target: Some(drop_bb), - .. - } = &terminator.kind + if terminator.source_info.span != call_span { + continue; + } + + if let mir::TerminatorKind::Call { ref args, target: Some(assign_bb), .. } = terminator.kind && let [source] = &**args && let mir::Operand::Move(source) = &source.node - // Block 2 only has the `drop()` terminator from to the assignment - && let drop_bb = &mir.basic_blocks[*drop_bb] - && let mir::TerminatorKind::Drop { target: assign_bb, .. } = drop_bb.terminator().kind - // Block 3 has the final assignment to the original local - && let assign_bb = &mir.basic_blocks[assign_bb] - && let [assignment, ..] = &*assign_bb.statements + && let assign_bb = skip_drop_block(mir, assign_bb) + // Skip any storage statements as they are just noise + && let Some(assignment) = mir.basic_blocks[assign_bb].statements + .iter() + .find(|stmt| { + !matches!(stmt.kind, mir::StatementKind::StorageDead(_) | mir::StatementKind::StorageLive(_)) + }) && let mir::StatementKind::Assign(box (borrowed, _)) = &assignment.kind && let Some(borrowers) = borrow_map.get(&borrowed.local) && borrowers.contains(source.local) { return true; } + + return false; } false } diff --git a/tests/ui/assigning_clones.fixed b/tests/ui/assigning_clones.fixed index 64a1f02eb051..60f6a385fc50 100644 --- a/tests/ui/assigning_clones.fixed +++ b/tests/ui/assigning_clones.fixed @@ -290,6 +290,33 @@ mod borrowck_conflicts { s = s2.to_owned(); } + fn issue12444_nodrop_projections() { + struct NoDrop; + + impl Clone for NoDrop { + fn clone(&self) -> Self { + todo!() + } + fn clone_from(&mut self, other: &Self) { + todo!() + } + } + + let mut s = NoDrop; + let s2 = &s; + s = s2.clone(); + + let mut s = (NoDrop, NoDrop); + let s2 = &s.0; + s.0 = s2.clone(); + + // This *could* emit a warning, but PossibleBorrowerMap only works with locals so it + // considers `s` fully borrowed + let mut s = (NoDrop, NoDrop); + let s2 = &s.1; + s.0 = s2.clone(); + } + fn issue12460(mut name: String) { if let Some(stripped_name) = name.strip_prefix("baz-") { name = stripped_name.to_owned(); diff --git a/tests/ui/assigning_clones.rs b/tests/ui/assigning_clones.rs index b92452c35772..6eb85be511a8 100644 --- a/tests/ui/assigning_clones.rs +++ b/tests/ui/assigning_clones.rs @@ -290,6 +290,33 @@ mod borrowck_conflicts { s = s2.to_owned(); } + fn issue12444_nodrop_projections() { + struct NoDrop; + + impl Clone for NoDrop { + fn clone(&self) -> Self { + todo!() + } + fn clone_from(&mut self, other: &Self) { + todo!() + } + } + + let mut s = NoDrop; + let s2 = &s; + s = s2.clone(); + + let mut s = (NoDrop, NoDrop); + let s2 = &s.0; + s.0 = s2.clone(); + + // This *could* emit a warning, but PossibleBorrowerMap only works with locals so it + // considers `s` fully borrowed + let mut s = (NoDrop, NoDrop); + let s2 = &s.1; + s.0 = s2.clone(); + } + fn issue12460(mut name: String) { if let Some(stripped_name) = name.strip_prefix("baz-") { name = stripped_name.to_owned(); From 7f66e567b2de4307ae49c01d832c93901e647a07 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 29 May 2024 09:08:22 +0000 Subject: [PATCH 04/56] Don't require `visit_body` to take a lifetime that must outlive the function call --- clippy_lints/src/default_numeric_fallback.rs | 2 +- clippy_lints/src/dereference.rs | 2 +- clippy_lints/src/implicit_hasher.rs | 2 +- clippy_lints/src/macro_metavars_in_unsafe.rs | 2 +- clippy_lints/src/needless_borrows_for_generic_args.rs | 2 +- clippy_lints/src/only_used_in_recursion.rs | 4 ++-- clippy_lints/src/operators/mod.rs | 4 ++-- clippy_lints/src/ptr.rs | 6 +++--- clippy_lints/src/question_mark.rs | 4 ++-- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index 1d6c4ce72e18..4d8c066fea54 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -49,7 +49,7 @@ declare_clippy_lint! { declare_lint_pass!(DefaultNumericFallback => [DEFAULT_NUMERIC_FALLBACK]); impl<'tcx> LateLintPass<'tcx> for DefaultNumericFallback { - fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) { + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { let hir = cx.tcx.hir(); let is_parent_const = matches!( hir.body_const_context(hir.body_owner_def_id(body.id())), diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index c6aef9ac2d60..297082354ff1 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -641,7 +641,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { } } - fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) { + fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &Body<'_>) { if Some(body.id()) == self.current_body { for pat in self.ref_locals.drain(..).filter_map(|(_, x)| x) { let replacements = pat.replacements; diff --git a/clippy_lints/src/implicit_hasher.rs b/clippy_lints/src/implicit_hasher.rs index a46aae36d5c5..ca830af3b2f3 100644 --- a/clippy_lints/src/implicit_hasher.rs +++ b/clippy_lints/src/implicit_hasher.rs @@ -328,7 +328,7 @@ impl<'a, 'b, 'tcx> ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> { impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; - fn visit_body(&mut self, body: &'tcx Body<'_>) { + fn visit_body(&mut self, body: &Body<'tcx>) { let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body.id())); walk_body(self, body); self.maybe_typeck_results = old_maybe_typeck_results; diff --git a/clippy_lints/src/macro_metavars_in_unsafe.rs b/clippy_lints/src/macro_metavars_in_unsafe.rs index aea3d26e1878..d1ae243877d7 100644 --- a/clippy_lints/src/macro_metavars_in_unsafe.rs +++ b/clippy_lints/src/macro_metavars_in_unsafe.rs @@ -186,7 +186,7 @@ impl<'a, 'tcx> Visitor<'tcx> for BodyVisitor<'a, 'tcx> { } impl<'tcx> LateLintPass<'tcx> for ExprMetavarsInUnsafe { - fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx rustc_hir::Body<'tcx>) { + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &rustc_hir::Body<'tcx>) { if is_lint_allowed(cx, MACRO_METAVARS_IN_UNSAFE, body.value.hir_id) { return; } diff --git a/clippy_lints/src/needless_borrows_for_generic_args.rs b/clippy_lints/src/needless_borrows_for_generic_args.rs index daf166bad90d..5b5e1c234245 100644 --- a/clippy_lints/src/needless_borrows_for_generic_args.rs +++ b/clippy_lints/src/needless_borrows_for_generic_args.rs @@ -129,7 +129,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowsForGenericArgs<'tcx> { } } - fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) { + fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &Body<'_>) { if self.possible_borrowers.last().map_or(false, |&(local_def_id, _)| { local_def_id == cx.tcx.hir().body_owner_def_id(body.id()) }) { diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs index b3ff5ecae862..8b8aabe7accc 100644 --- a/clippy_lints/src/only_used_in_recursion.rs +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -221,7 +221,7 @@ pub struct OnlyUsedInRecursion { } impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion { - fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'tcx>) { + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { if body.value.span.from_expansion() { return; } @@ -350,7 +350,7 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion { } } - fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'tcx>) { + fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { if self.entered_body == Some(body.value.hir_id) { self.entered_body = None; self.params.flag_for_linting(); diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index e002429e3a47..9e77d0e09c48 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -868,11 +868,11 @@ impl<'tcx> LateLintPass<'tcx> for Operators { self.arithmetic_context.expr_post(e.hir_id); } - fn check_body(&mut self, cx: &LateContext<'tcx>, b: &'tcx Body<'_>) { + fn check_body(&mut self, cx: &LateContext<'tcx>, b: &Body<'_>) { self.arithmetic_context.enter_body(cx, b); } - fn check_body_post(&mut self, cx: &LateContext<'tcx>, b: &'tcx Body<'_>) { + fn check_body_post(&mut self, cx: &LateContext<'tcx>, b: &Body<'_>) { self.arithmetic_context.body_post(cx, b); } } diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 38580dc58226..02c05e0aaf9c 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -188,7 +188,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { } } - fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) { + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { let hir = cx.tcx.hir(); let mut parents = hir.parent_iter(body.value.hir_id); let (item_id, sig, is_trait_item) = match parents.next() { @@ -525,7 +525,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>( }) } -fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&'tcx Body<'_>>) { +fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&Body<'tcx>>) { if let FnRetTy::Return(ty) = sig.decl.output && let Some((out, Mutability::Mut, _)) = get_ref_lm(ty) { @@ -559,7 +559,7 @@ fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Optio } #[expect(clippy::too_many_lines)] -fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args: &[PtrArg<'tcx>]) -> Vec { +fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[PtrArg<'tcx>]) -> Vec { struct V<'cx, 'tcx> { cx: &'cx LateContext<'tcx>, /// Map from a local id to which argument it came from (index into `Self::args` and diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 1f1ce147ca24..7cf98ad9e09a 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -370,11 +370,11 @@ impl<'tcx> LateLintPass<'tcx> for QuestionMark { } } - fn check_body(&mut self, _: &LateContext<'tcx>, _: &'tcx Body<'tcx>) { + fn check_body(&mut self, _: &LateContext<'tcx>, _: &Body<'tcx>) { self.try_block_depth_stack.push(0); } - fn check_body_post(&mut self, _: &LateContext<'tcx>, _: &'tcx Body<'tcx>) { + fn check_body_post(&mut self, _: &LateContext<'tcx>, _: &Body<'tcx>) { self.try_block_depth_stack.pop(); } From f44a6a7cb59ddff2964e26c9eb0ad50a6c555c99 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 29 May 2024 10:03:40 +0000 Subject: [PATCH 05/56] Make `body_owned_by` return the body directly. Almost all callers want this anyway, and now we can use it to also return fed bodies --- clippy_lints/src/methods/option_map_unwrap_or.rs | 4 ++-- clippy_lints/src/single_call_fn.rs | 1 - clippy_lints/src/transmute/missing_transmute_annotations.rs | 4 ++-- clippy_lints/src/unconditional_recursion.rs | 2 +- clippy_lints/src/utils/author.rs | 4 ++-- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints/src/methods/option_map_unwrap_or.rs index efec9dd716dc..ca331f3e7568 100644 --- a/clippy_lints/src/methods/option_map_unwrap_or.rs +++ b/clippy_lints/src/methods/option_map_unwrap_or.rs @@ -59,8 +59,8 @@ pub(super) fn check<'tcx>( }; let map = cx.tcx.hir(); - let body = map.body(map.body_owned_by(map.enclosing_body_owner(expr.hir_id))); - reference_visitor.visit_body(body); + let body = map.body_owned_by(map.enclosing_body_owner(expr.hir_id)); + reference_visitor.visit_body(&body); if reference_visitor.found_reference { return; diff --git a/clippy_lints/src/single_call_fn.rs b/clippy_lints/src/single_call_fn.rs index 2ce7e714c642..f8e09d517f57 100644 --- a/clippy_lints/src/single_call_fn.rs +++ b/clippy_lints/src/single_call_fn.rs @@ -79,7 +79,6 @@ impl SingleCallFn { .tcx .hir() .maybe_body_owned_by(fn_def_id) - .map(|body| cx.tcx.hir().body(body)) .map_or(true, |body| is_in_test_function(cx.tcx, body.value.hir_id)) || match cx.tcx.hir_node(fn_hir_id) { Node::Item(item) => is_from_proc_macro(cx, item), diff --git a/clippy_lints/src/transmute/missing_transmute_annotations.rs b/clippy_lints/src/transmute/missing_transmute_annotations.rs index cc6ff1cf3b42..f98ea59a15d8 100644 --- a/clippy_lints/src/transmute/missing_transmute_annotations.rs +++ b/clippy_lints/src/transmute/missing_transmute_annotations.rs @@ -30,8 +30,8 @@ fn get_parent_local_binding_ty<'tcx>(cx: &LateContext<'tcx>, expr_hir_id: HirId) fn is_function_block(cx: &LateContext<'_>, expr_hir_id: HirId) -> bool { let def_id = cx.tcx.hir().enclosing_body_owner(expr_hir_id); - if let Some(body_id) = cx.tcx.hir().maybe_body_owned_by(def_id) { - let body = cx.tcx.hir().body(body_id); + if let Some(body) = cx.tcx.hir().maybe_body_owned_by(def_id) { + let body = cx.tcx.hir().body(body.id()); return body.value.peel_blocks().hir_id == expr_hir_id; } false diff --git a/clippy_lints/src/unconditional_recursion.rs b/clippy_lints/src/unconditional_recursion.rs index 0c4e2c91aec5..5e41b3f4914f 100644 --- a/clippy_lints/src/unconditional_recursion.rs +++ b/clippy_lints/src/unconditional_recursion.rs @@ -336,7 +336,7 @@ impl UnconditionalRecursion { // We need to use typeck here to infer the actual function being called. && let body_def_id = cx.tcx.hir().enclosing_body_owner(call_expr.hir_id) && let Some(body_owner) = cx.tcx.hir().maybe_body_owned_by(body_def_id) - && let typeck = cx.tcx.typeck_body(body_owner) + && let typeck = cx.tcx.typeck_body(body_owner.id()) && let Some(call_def_id) = typeck.type_dependent_def_id(call_expr.hir_id) { self.default_impl_for_type.insert(self_def_id, call_def_id); diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 4448c9ae3df7..18a31abddd0a 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -137,9 +137,9 @@ impl<'tcx> LateLintPass<'tcx> for Author { fn check_item(cx: &LateContext<'_>, hir_id: HirId) { let hir = cx.tcx.hir(); - if let Some(body_id) = hir.maybe_body_owned_by(hir_id.expect_owner().def_id) { + if let Some(body) = hir.maybe_body_owned_by(hir_id.expect_owner().def_id) { check_node(cx, hir_id, |v| { - v.expr(&v.bind("expr", hir.body(body_id).value)); + v.expr(&v.bind("expr", body.value)); }); } } From d38920f67752c270dcdbf7444f668c019864f12f Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Wed, 29 May 2024 21:30:21 +0300 Subject: [PATCH 06/56] ast: Revert a breaking attribute visiting order change --- tests/ui/tabs_in_doc_comments.stderr | 48 ++++++++++++++-------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/ui/tabs_in_doc_comments.stderr b/tests/ui/tabs_in_doc_comments.stderr index aef6c3914526..23d5dcd3a8da 100644 --- a/tests/ui/tabs_in_doc_comments.stderr +++ b/tests/ui/tabs_in_doc_comments.stderr @@ -1,53 +1,53 @@ error: using tabs in doc comments is not recommended - --> tests/ui/tabs_in_doc_comments.rs:6:5 + --> tests/ui/tabs_in_doc_comments.rs:10:9 | -LL | /// - first one - | ^^^^ help: consider using four spaces per tab +LL | /// - First String: + | ^^^^ help: consider using four spaces per tab | = note: `-D clippy::tabs-in-doc-comments` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::tabs_in_doc_comments)]` error: using tabs in doc comments is not recommended - --> tests/ui/tabs_in_doc_comments.rs:6:13 + --> tests/ui/tabs_in_doc_comments.rs:11:9 | -LL | /// - first one - | ^^^^^^^^ help: consider using four spaces per tab +LL | /// - needs to be inside here + | ^^^^^^^^ help: consider using four spaces per tab error: using tabs in doc comments is not recommended - --> tests/ui/tabs_in_doc_comments.rs:7:5 + --> tests/ui/tabs_in_doc_comments.rs:14:9 | -LL | /// - second one - | ^^^^ help: consider using four spaces per tab +LL | /// - Second String: + | ^^^^ help: consider using four spaces per tab error: using tabs in doc comments is not recommended - --> tests/ui/tabs_in_doc_comments.rs:7:14 + --> tests/ui/tabs_in_doc_comments.rs:15:9 | -LL | /// - second one - | ^^^^ help: consider using four spaces per tab +LL | /// - needs to be inside here + | ^^^^^^^^ help: consider using four spaces per tab error: using tabs in doc comments is not recommended - --> tests/ui/tabs_in_doc_comments.rs:10:9 + --> tests/ui/tabs_in_doc_comments.rs:6:5 | -LL | /// - First String: - | ^^^^ help: consider using four spaces per tab +LL | /// - first one + | ^^^^ help: consider using four spaces per tab error: using tabs in doc comments is not recommended - --> tests/ui/tabs_in_doc_comments.rs:11:9 + --> tests/ui/tabs_in_doc_comments.rs:6:13 | -LL | /// - needs to be inside here - | ^^^^^^^^ help: consider using four spaces per tab +LL | /// - first one + | ^^^^^^^^ help: consider using four spaces per tab error: using tabs in doc comments is not recommended - --> tests/ui/tabs_in_doc_comments.rs:14:9 + --> tests/ui/tabs_in_doc_comments.rs:7:5 | -LL | /// - Second String: - | ^^^^ help: consider using four spaces per tab +LL | /// - second one + | ^^^^ help: consider using four spaces per tab error: using tabs in doc comments is not recommended - --> tests/ui/tabs_in_doc_comments.rs:15:9 + --> tests/ui/tabs_in_doc_comments.rs:7:14 | -LL | /// - needs to be inside here - | ^^^^^^^^ help: consider using four spaces per tab +LL | /// - second one + | ^^^^ help: consider using four spaces per tab error: aborting due to 8 previous errors From f67f72695a2ac5e7a1d39383583a6abb1cf9f4ad Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 30 May 2024 10:49:05 +0200 Subject: [PATCH 07/56] Merge commit 'c9139bd546d9cd69df817faeab62c5f9b1a51337' into clippy-subtree-update --- book/src/configuration.md | 2 +- book/src/development/adding_lints.md | 5 + clippy_dev/src/new_lint.rs | 7 +- clippy_lints/src/absolute_paths.rs | 2 +- clippy_lints/src/allow_attributes.rs | 7 +- clippy_lints/src/as_conversions.rs | 2 +- clippy_lints/src/asm_syntax.rs | 10 +- .../src/assertions_on_result_states.rs | 20 +- clippy_lints/src/attrs/mod.rs | 6 +- clippy_lints/src/casts/mod.rs | 6 +- clippy_lints/src/create_dir.rs | 6 +- clippy_lints/src/dbg_macro.rs | 2 +- clippy_lints/src/default_numeric_fallback.rs | 5 +- .../src/default_union_representation.rs | 2 +- clippy_lints/src/dereference.rs | 14 +- clippy_lints/src/derive.rs | 31 +- clippy_lints/src/disallowed_script_idents.rs | 4 +- clippy_lints/src/doc/markdown.rs | 28 +- clippy_lints/src/doc/mod.rs | 2 +- clippy_lints/src/drop_forget_ref.rs | 7 +- clippy_lints/src/else_if_without_else.rs | 2 +- clippy_lints/src/empty_drop.rs | 2 +- clippy_lints/src/empty_enum.rs | 45 +- clippy_lints/src/empty_with_brackets.rs | 24 +- clippy_lints/src/endian_bytes.rs | 15 +- clippy_lints/src/error_impl_error.rs | 2 +- clippy_lints/src/exhaustive_items.rs | 16 +- clippy_lints/src/exit.rs | 12 +- clippy_lints/src/float_literal.rs | 6 +- clippy_lints/src/format_push_string.rs | 2 +- .../src/functions/misnamed_getters.rs | 2 +- clippy_lints/src/functions/mod.rs | 11 +- clippy_lints/src/if_then_some_else_none.rs | 2 +- clippy_lints/src/implicit_return.rs | 13 +- clippy_lints/src/indexing_slicing.rs | 7 +- clippy_lints/src/inherent_impl.rs | 2 +- clippy_lints/src/init_numbered_fields.rs | 4 +- .../src/integer_division_remainder_used.rs | 6 +- clippy_lints/src/iter_over_hash_type.rs | 4 +- clippy_lints/src/large_include_file.rs | 8 +- clippy_lints/src/let_underscore.rs | 9 +- clippy_lints/src/literal_representation.rs | 4 +- clippy_lints/src/loops/mod.rs | 6 +- clippy_lints/src/matches/mod.rs | 6 +- .../matches/significant_drop_in_scrutinee.rs | 418 ++++++++++-------- .../iter_on_single_or_empty_collections.rs | 47 +- clippy_lints/src/methods/mod.rs | 21 +- .../src/methods/unnecessary_iter_cloned.rs | 16 +- clippy_lints/src/methods/utils.rs | 16 +- clippy_lints/src/min_ident_chars.rs | 7 +- clippy_lints/src/misc_early/mod.rs | 6 +- clippy_lints/src/missing_assert_message.rs | 2 +- .../src/missing_asserts_for_indexing.rs | 2 +- clippy_lints/src/missing_doc.rs | 4 +- clippy_lints/src/missing_inline.rs | 19 +- clippy_lints/src/missing_trait_methods.rs | 10 +- .../src/mixed_read_write_in_expression.rs | 9 +- clippy_lints/src/module_style.rs | 6 +- .../src/multiple_unsafe_ops_per_block.rs | 4 +- clippy_lints/src/mutex_atomic.rs | 2 +- clippy_lints/src/needless_bool.rs | 13 +- clippy_lints/src/non_expressive_names.rs | 4 + clippy_lints/src/operators/mod.rs | 12 +- clippy_lints/src/panic_in_result_fn.rs | 4 +- clippy_lints/src/panic_unimplemented.rs | 18 +- clippy_lints/src/partial_pub_fields.rs | 9 +- clippy_lints/src/pattern_type_mismatch.rs | 5 +- clippy_lints/src/pub_use.rs | 8 +- clippy_lints/src/question_mark_used.rs | 2 +- clippy_lints/src/raw_strings.rs | 6 +- clippy_lints/src/redundant_slicing.rs | 2 +- .../src/redundant_type_annotations.rs | 2 +- clippy_lints/src/ref_patterns.rs | 4 +- clippy_lints/src/returns.rs | 34 +- clippy_lints/src/same_name_method.rs | 2 +- clippy_lints/src/semicolon_block.rs | 6 +- clippy_lints/src/shadow.rs | 26 +- clippy_lints/src/single_call_fn.rs | 19 +- .../src/single_char_lifetime_names.rs | 5 +- clippy_lints/src/std_instead_of_core.rs | 12 +- clippy_lints/src/strings.rs | 19 +- .../src/suspicious_xor_used_as_pow.rs | 4 +- clippy_lints/src/tests_outside_test_module.rs | 4 +- clippy_lints/src/types/mod.rs | 4 +- .../src/undocumented_unsafe_blocks.rs | 9 +- clippy_lints/src/unicode.rs | 2 +- clippy_lints/src/unnecessary_self_imports.rs | 2 +- clippy_lints/src/unwrap_in_result.rs | 6 +- clippy_lints/src/visibility.rs | 4 +- clippy_lints/src/write.rs | 13 +- clippy_utils/src/consts.rs | 6 +- clippy_utils/src/hir_utils.rs | 2 +- clippy_utils/src/lib.rs | 22 + clippy_utils/src/ty.rs | 6 +- rust-toolchain | 2 +- tests/ui-internal/disallow_span_lint.rs | 8 +- tests/ui-internal/disallow_span_lint.stderr | 14 +- tests/ui/derive_partial_eq_without_eq.fixed | 17 + tests/ui/derive_partial_eq_without_eq.rs | 17 + tests/ui/derive_partial_eq_without_eq.stderr | 26 +- tests/ui/doc/doc-fixable.fixed | 3 + tests/ui/doc/doc-fixable.rs | 3 + tests/ui/doc/doc-fixable.stderr | 8 +- tests/ui/doc/issue_12795.fixed | 9 + tests/ui/doc/issue_12795.rs | 9 + tests/ui/doc/issue_12795.stderr | 48 ++ tests/ui/iter_on_empty_collections.fixed | 22 + tests/ui/iter_on_empty_collections.rs | 22 + tests/ui/iter_on_empty_collections.stderr | 8 +- tests/ui/let_and_return.fixed | 34 ++ tests/ui/let_and_return.rs | 34 ++ tests/ui/let_and_return.stderr | 72 ++- tests/ui/many_single_char_names.rs | 2 - tests/ui/many_single_char_names.stderr | 10 +- tests/ui/needless_return.fixed | 4 + tests/ui/needless_return.rs | 4 + tests/ui/needless_return.stderr | 14 +- tests/ui/numbered_fields.fixed | 5 + tests/ui/numbered_fields.rs | 5 + tests/ui/numbered_fields.stderr | 8 +- tests/ui/significant_drop_in_scrutinee.rs | 98 +++- tests/ui/significant_drop_in_scrutinee.stderr | 206 +++++---- tests/ui/unnecessary_iter_cloned.fixed | 29 ++ tests/ui/unnecessary_iter_cloned.rs | 29 ++ tests/ui/unnecessary_iter_cloned.stderr | 44 +- tests/ui/unnecessary_to_owned.stderr | 2 +- tests/ui/unsafe_derive_deserialize.rs | 11 + tests/ui/unsafe_derive_deserialize.stderr | 8 +- 128 files changed, 1422 insertions(+), 676 deletions(-) create mode 100644 tests/ui/doc/issue_12795.fixed create mode 100644 tests/ui/doc/issue_12795.rs create mode 100644 tests/ui/doc/issue_12795.stderr diff --git a/book/src/configuration.md b/book/src/configuration.md index 9eb067abd91e..ea549e4df4a5 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -133,7 +133,7 @@ Very rarely, you may wish to prevent Clippy from evaluating certain sections of `clippy` cfg is not set. You may need to provide a stub so that the code compiles: ```rust -#[cfg(not(clippy)] +#[cfg(not(clippy))] include!(concat!(env!("OUT_DIR"), "/my_big_function-generated.rs")); #[cfg(clippy)] diff --git a/book/src/development/adding_lints.md b/book/src/development/adding_lints.md index 415022612caa..48c00bcbf341 100644 --- a/book/src/development/adding_lints.md +++ b/book/src/development/adding_lints.md @@ -587,6 +587,11 @@ declare_clippy_lint! { } ``` +If the lint is in the `restriction` group because it lints things that are not +necessarily “bad” but are more of a style choice, then replace the +“Why is this bad?” section heading with “Why restrict this?”, to avoid writing +“Why is this bad? It isn't, but ...”. + Once your lint is merged, this documentation will show up in the [lint list][lint_list]. diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index b6481dde4dde..2e56eb8ec15f 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -331,12 +331,17 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { } fn get_lint_declaration(name_upper: &str, category: &str) -> String { + let justification_heading = if category == "restriction" { + "Why restrict this?" + } else { + "Why is this bad?" + }; formatdoc!( r#" declare_clippy_lint! {{ /// ### What it does /// - /// ### Why is this bad? + /// ### {justification_heading} /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/absolute_paths.rs b/clippy_lints/src/absolute_paths.rs index 8ba661afeeb6..461117cf965d 100644 --- a/clippy_lints/src/absolute_paths.rs +++ b/clippy_lints/src/absolute_paths.rs @@ -12,7 +12,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of items through absolute paths, like `std::env::current_dir`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// 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. diff --git a/clippy_lints/src/allow_attributes.rs b/clippy_lints/src/allow_attributes.rs index 39fc49dee377..123d0e51eeee 100644 --- a/clippy_lints/src/allow_attributes.rs +++ b/clippy_lints/src/allow_attributes.rs @@ -19,10 +19,11 @@ declare_clippy_lint! { /// This lint only warns outer attributes (`#[allow]`), as inner attributes /// (`#![allow]`) are usually used to enable or disable lints on a global scale. /// - /// ### Why is this bad? - /// `#[expect]` attributes suppress the lint emission, but emit a warning, if + /// ### Why restrict this? + /// `#[allow]` attributes can linger after their reason for existence is gone. + /// `#[expect]` attributes suppress the lint emission, but emit a warning if /// the expectation is unfulfilled. This can be useful to be notified when the - /// lint is no longer triggered. + /// lint is no longer triggered, which may indicate the attribute can be removed. /// /// ### Example /// ```rust,ignore diff --git a/clippy_lints/src/as_conversions.rs b/clippy_lints/src/as_conversions.rs index e3daf75c3eb6..cfa25005a05e 100644 --- a/clippy_lints/src/as_conversions.rs +++ b/clippy_lints/src/as_conversions.rs @@ -17,7 +17,7 @@ declare_clippy_lint! { /// There is a good explanation the reason why this lint should work in this way and how it is useful /// [in this issue](https://github.com/rust-lang/rust-clippy/issues/5122). /// - /// ### Why is this bad? + /// ### Why restrict this? /// `as` conversions will perform many kinds of /// conversions, including silently lossy conversions and dangerous coercions. /// There are cases when it makes sense to use `as`, so the lint is diff --git a/clippy_lints/src/asm_syntax.rs b/clippy_lints/src/asm_syntax.rs index 7c88bfc97ca4..0db1456d40bf 100644 --- a/clippy_lints/src/asm_syntax.rs +++ b/clippy_lints/src/asm_syntax.rs @@ -65,9 +65,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of Intel x86 assembly syntax. /// - /// ### Why is this bad? - /// The lint has been enabled to indicate a preference - /// for AT&T x86 assembly syntax. + /// ### Why restrict this? + /// To enforce consistent use of AT&T x86 assembly syntax. /// /// ### Example /// @@ -114,9 +113,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of AT&T x86 assembly syntax. /// - /// ### Why is this bad? - /// The lint has been enabled to indicate a preference - /// for Intel x86 assembly syntax. + /// ### Why restrict this? + /// To enforce consistent use of Intel x86 assembly syntax. /// /// ### Example /// diff --git a/clippy_lints/src/assertions_on_result_states.rs b/clippy_lints/src/assertions_on_result_states.rs index aec22965b1b0..7217686dcca5 100644 --- a/clippy_lints/src/assertions_on_result_states.rs +++ b/clippy_lints/src/assertions_on_result_states.rs @@ -16,23 +16,33 @@ declare_clippy_lint! { /// ### What it does /// Checks for `assert!(r.is_ok())` or `assert!(r.is_err())` calls. /// - /// ### Why is this bad? - /// An assertion failure cannot output an useful message of the error. + /// ### Why restrict this? + /// This form of assertion does not show any of the information present in the `Result` + /// other than which variant it isn’t. /// /// ### Known problems /// The suggested replacement decreases the readability of code and log output. /// /// ### Example - /// ```rust,ignore + /// ```rust,no_run /// # let r = Ok::<_, ()>(()); /// assert!(r.is_ok()); - /// # let r = Err::<_, ()>(()); + /// # let r = Err::<(), _>(()); /// assert!(r.is_err()); /// ``` + /// + /// Use instead: + /// + /// ```rust,no_run + /// # let r = Ok::<_, ()>(()); + /// r.unwrap(); + /// # let r = Err::<(), _>(()); + /// r.unwrap_err(); + /// ``` #[clippy::version = "1.64.0"] pub ASSERTIONS_ON_RESULT_STATES, restriction, - "`assert!(r.is_ok())`/`assert!(r.is_err())` gives worse error message than directly calling `r.unwrap()`/`r.unwrap_err()`" + "`assert!(r.is_ok())` or `assert!(r.is_err())` gives worse panic messages than directly calling `r.unwrap()` or `r.unwrap_err()`" } declare_lint_pass!(AssertionsOnResultStates => [ASSERTIONS_ON_RESULT_STATES]); diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs index 39f406077995..a24bd5ed44be 100644 --- a/clippy_lints/src/attrs/mod.rs +++ b/clippy_lints/src/attrs/mod.rs @@ -309,9 +309,9 @@ declare_clippy_lint! { /// /// (This requires the `lint_reasons` feature) /// - /// ### Why is this bad? - /// Allowing a lint should always have a reason. This reason should be documented to - /// ensure that others understand the reasoning + /// ### Why restrict this? + /// Justifying each `allow` helps readers understand the reasoning, + /// and may allow removing `allow` attributes if their purpose is obsolete. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index bd2c96f01f6f..e60c36ced75d 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -303,7 +303,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for casts of a function pointer to any integer type. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Casting a function pointer to an integer can have surprising results and can occur /// accidentally if parentheses are omitted from a function call. If you aren't doing anything /// low-level with function pointers then you can opt-out of casting functions to integers in @@ -535,8 +535,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for the usage of `as _` conversion using inferred type. /// - /// ### Why is this bad? - /// The conversion might include lossy conversion and dangerous cast that might go + /// ### Why restrict this? + /// The conversion might include lossy conversion or a dangerous cast that might go /// undetected due to the type being inferred. /// /// The lint is allowed by default as using `_` is less wordy than always specifying the type. diff --git a/clippy_lints/src/create_dir.rs b/clippy_lints/src/create_dir.rs index 7a3d5a070912..27c00948a8f2 100644 --- a/clippy_lints/src/create_dir.rs +++ b/clippy_lints/src/create_dir.rs @@ -10,8 +10,10 @@ declare_clippy_lint! { /// ### What it does /// Checks usage of `std::fs::create_dir` and suggest using `std::fs::create_dir_all` instead. /// - /// ### Why is this bad? - /// Sometimes `std::fs::create_dir` is mistakenly chosen over `std::fs::create_dir_all`. + /// ### Why restrict this? + /// Sometimes `std::fs::create_dir` is mistakenly chosen over `std::fs::create_dir_all`, + /// resulting in failure when more than one directory needs to be created or when the directory already exists. + /// Crates which never need to specifically create a single directory may wish to prevent this mistake. /// /// ### Example /// ```rust,ignore diff --git a/clippy_lints/src/dbg_macro.rs b/clippy_lints/src/dbg_macro.rs index db5937266047..b0590b0a71cb 100644 --- a/clippy_lints/src/dbg_macro.rs +++ b/clippy_lints/src/dbg_macro.rs @@ -14,7 +14,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of the [`dbg!`](https://doc.rust-lang.org/std/macro.dbg.html) macro. /// - /// ### Why is this bad? + /// ### Why restrict this? /// The `dbg!` macro is intended as a debugging tool. It should not be present in released /// software or committed to a version control system. /// diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index 1d6c4ce72e18..fbc4ede37b1c 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -22,9 +22,8 @@ declare_clippy_lint! { /// /// See [RFC0212](https://github.com/rust-lang/rfcs/blob/master/text/0212-restore-int-fallback.md) for more information about the fallback. /// - /// ### Why is this bad? - /// For those who are very careful about types, default numeric fallback - /// can be a pitfall that cause unexpected runtime behavior. + /// ### Why restrict this? + /// To ensure that every numeric type is chosen explicitly rather than implicitly. /// /// ### Known problems /// This lint can only be allowed at the function level or above. diff --git a/clippy_lints/src/default_union_representation.rs b/clippy_lints/src/default_union_representation.rs index b4290b6437f2..3fa9bad0d03d 100644 --- a/clippy_lints/src/default_union_representation.rs +++ b/clippy_lints/src/default_union_representation.rs @@ -10,7 +10,7 @@ declare_clippy_lint! { /// ### What it does /// Displays a warning when a union is declared with the default representation (without a `#[repr(C)]` attribute). /// - /// ### Why is this bad? + /// ### Why restrict this? /// Unions in Rust have unspecified layout by default, despite many people thinking that they /// lay out each field at the start of the union (like C does). That is, there are no guarantees /// about the offset of the fields for unions with multiple non-ZST fields without an explicitly diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index c6aef9ac2d60..0276c64ef9b7 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -2,7 +2,9 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::ty::{implements_trait, is_manually_drop, peel_mid_ty_refs}; -use clippy_utils::{expr_use_ctxt, get_parent_expr, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode}; +use clippy_utils::{ + expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode, +}; use core::mem; use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX}; use rustc_data_structures::fx::FxIndexMap; @@ -1038,14 +1040,8 @@ fn report<'tcx>( ); }, State::ExplicitDeref { mutability } => { - if matches!( - expr.kind, - ExprKind::Block(..) - | ExprKind::ConstBlock(_) - | ExprKind::If(..) - | ExprKind::Loop(..) - | ExprKind::Match(..) - ) && let ty::Ref(_, ty, _) = data.adjusted_ty.kind() + if is_block_like(expr) + && 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. diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 2a644922fb1c..636698e96f6d 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -1,17 +1,17 @@ -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_note, span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy}; use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, match_def_path, paths}; 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, Safety, Impl, Item, ItemKind, UnsafeSource, + self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Impl, Item, ItemKind, Safety, UnsafeSource, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_middle::traits::Reveal; use rustc_middle::ty::{ - self, ClauseKind, GenericArgKind, GenericParamDefKind, ParamEnv, Upcast, TraitPredicate, Ty, TyCtxt, + self, ClauseKind, GenericArgKind, GenericParamDefKind, ParamEnv, TraitPredicate, Ty, TyCtxt, Upcast, }; use rustc_session::declare_lint_pass; use rustc_span::def_id::LocalDefId; @@ -390,13 +390,17 @@ fn check_unsafe_derive_deserialize<'tcx>( .map(|imp_did| cx.tcx.hir().expect_item(imp_did.expect_local())) .any(|imp| has_unsafe(cx, imp)) { - span_lint_and_help( + span_lint_hir_and_then( cx, UNSAFE_DERIVE_DESERIALIZE, + adt_hir_id, item.span, "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`", - None, - "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html", + |diag| { + diag.help( + "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html", + ); + }, ); } } @@ -452,20 +456,27 @@ fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_r && !has_non_exhaustive_attr(cx.tcx, *adt) && !ty_implements_eq_trait(cx.tcx, ty, eq_trait_def_id) && let param_env = param_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id) + && let Some(local_def_id) = adt.did().as_local() // If all of our fields implement `Eq`, we can implement `Eq` too && 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, None, &[])) { - span_lint_and_sugg( + span_lint_hir_and_then( cx, DERIVE_PARTIAL_EQ_WITHOUT_EQ, + cx.tcx.local_def_id_to_hir_id(local_def_id), span.ctxt().outer_expn_data().call_site, "you are deriving `PartialEq` and can implement `Eq`", - "consider deriving `Eq` as well", - "PartialEq, Eq".to_string(), - Applicability::MachineApplicable, + |diag| { + diag.span_suggestion( + span.ctxt().outer_expn_data().call_site, + "consider deriving `Eq` as well", + "PartialEq, Eq", + Applicability::MachineApplicable, + ); + }, ); } } diff --git a/clippy_lints/src/disallowed_script_idents.rs b/clippy_lints/src/disallowed_script_idents.rs index def4b5932b4b..a995f06fb73d 100644 --- a/clippy_lints/src/disallowed_script_idents.rs +++ b/clippy_lints/src/disallowed_script_idents.rs @@ -20,11 +20,11 @@ declare_clippy_lint! { /// [aliases]: http://www.unicode.org/reports/tr24/tr24-31.html#Script_Value_Aliases /// [supported_scripts]: https://www.unicode.org/iso15924/iso15924-codes.html /// - /// ### Why is this bad? + /// ### Why restrict this? /// It may be not desired to have many different scripts for /// identifiers in the codebase. /// - /// Note that if you only want to allow plain English, you might want to use + /// Note that if you only want to allow typical English, you might want to use /// built-in [`non_ascii_idents`] lint instead. /// /// [`non_ascii_idents`]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#non-ascii-idents diff --git a/clippy_lints/src/doc/markdown.rs b/clippy_lints/src/doc/markdown.rs index 1add02af3101..41c0bcd55adc 100644 --- a/clippy_lints/src/doc/markdown.rs +++ b/clippy_lints/src/doc/markdown.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::snippet_with_applicability; use rustc_data_structures::fx::FxHashSet; use rustc_errors::{Applicability, SuggestionStyle}; @@ -30,6 +30,7 @@ pub fn check( word = tmp_word; } + let original_len = word.len(); word = word.trim_start_matches(trim_pattern); // Remove leading or trailing single `:` which may be part of a sentence. @@ -44,6 +45,25 @@ pub fn check( continue; } + // Ensure that all reachable matching closing parens are included as well. + let size_diff = original_len - word.len(); + let mut open_parens = 0; + let mut close_parens = 0; + for c in word.chars() { + if c == '(' { + open_parens += 1; + } else if c == ')' { + close_parens += 1; + } + } + while close_parens < open_parens + && let Some(tmp_word) = orig_word.get(size_diff..=(word.len() + size_diff)) + && tmp_word.ends_with(')') + { + word = tmp_word; + close_parens += 1; + } + // Adjust for the current word let offset = word.as_ptr() as usize - text.as_ptr() as usize; let span = Span::new( @@ -92,13 +112,15 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span, code_level: isize, b if let Ok(url) = Url::parse(word) { // try to get around the fact that `foo::bar` parses as a valid URL if !url.cannot_be_a_base() { - span_lint( + span_lint_and_sugg( cx, DOC_MARKDOWN, span, "you should put bare URLs between `<`/`>` or make a proper Markdown link", + "try", + format!("<{word}>"), + Applicability::MachineApplicable, ); - return; } } diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 9f08973948a3..3d875e7ac2d3 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -261,7 +261,7 @@ declare_clippy_lint! { /// Checks for the doc comments of publicly visible /// safe functions and traits and warns if there is a `# Safety` section. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Safe functions and traits are safe to implement and therefore do not /// need to describe safety preconditions that users are required to uphold. /// diff --git a/clippy_lints/src/drop_forget_ref.rs b/clippy_lints/src/drop_forget_ref.rs index 119473c2454b..4a6ffcd9a788 100644 --- a/clippy_lints/src/drop_forget_ref.rs +++ b/clippy_lints/src/drop_forget_ref.rs @@ -52,9 +52,10 @@ declare_clippy_lint! { /// Checks for usage of `std::mem::forget(t)` where `t` is /// `Drop` or has a field that implements `Drop`. /// - /// ### Why is this bad? - /// `std::mem::forget(t)` prevents `t` from running its - /// destructor, possibly causing leaks. + /// ### Why restrict this? + /// `std::mem::forget(t)` prevents `t` from running its destructor, possibly causing leaks. + /// It is not possible to detect all means of creating leaks, but it may be desirable to + /// prohibit the simple ones. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/else_if_without_else.rs b/clippy_lints/src/else_if_without_else.rs index a6ca7fe9e0bb..bb6f9aac2236 100644 --- a/clippy_lints/src/else_if_without_else.rs +++ b/clippy_lints/src/else_if_without_else.rs @@ -11,7 +11,7 @@ declare_clippy_lint! { /// Checks for usage of if expressions with an `else if` branch, /// but without a final `else` branch. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Some coding guidelines require this (e.g., MISRA-C:2004 Rule 14.10). /// /// ### Example diff --git a/clippy_lints/src/empty_drop.rs b/clippy_lints/src/empty_drop.rs index 74db250b3ae9..c5fc72b5e2d8 100644 --- a/clippy_lints/src/empty_drop.rs +++ b/clippy_lints/src/empty_drop.rs @@ -9,7 +9,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for empty `Drop` implementations. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Empty `Drop` implementations have no effect when dropping an instance of the type. They are /// most likely useless. However, an empty `Drop` implementation prevents a type from being /// destructured, which might be the intention behind adding the implementation as a marker. diff --git a/clippy_lints/src/empty_enum.rs b/clippy_lints/src/empty_enum.rs index 420888b6ccb3..d16714695cb7 100644 --- a/clippy_lints/src/empty_enum.rs +++ b/clippy_lints/src/empty_enum.rs @@ -7,32 +7,53 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does - /// Checks for `enum`s with no variants. + /// Checks for `enum`s with no variants, which therefore are uninhabited types + /// (cannot be instantiated). /// - /// As of this writing, the `never_type` is still a - /// nightly-only experimental API. Therefore, this lint is only triggered - /// if the `never_type` is enabled. + /// As of this writing, the `never_type` is still a nightly-only experimental API. + /// Therefore, this lint is only triggered if `#![feature(never_type)]` is enabled. /// /// ### Why is this bad? - /// If you want to introduce a type which - /// can't be instantiated, you should use `!` (the primitive type "never"), - /// or a wrapper around it, because `!` has more extensive - /// compiler support (type inference, etc...) and wrappers - /// around it are the conventional way to define an uninhabited type. - /// For further information visit [never type documentation](https://doc.rust-lang.org/std/primitive.never.html) + /// * If you only want a type which can’t be instantiated, you should use [`!`] + /// (the primitive type "never"), because [`!`] has more extensive compiler support + /// (type inference, etc.) and implementations of common traits. /// + /// * If you need to introduce a distinct type, consider using a [newtype] `struct` + /// containing [`!`] instead (`struct MyType(pub !)`), because it is more idiomatic + /// to use a `struct` rather than an `enum` when an `enum` is unnecessary. + /// + /// If you do this, note that the [visibility] of the [`!`] field determines whether + /// the uninhabitedness is visible in documentation, and whether it can be pattern + /// matched to mark code unreachable. If the field is not visible, then the struct + /// acts like any other struct with private fields. + /// + /// * If the enum has no variants only because all variants happen to be + /// [disabled by conditional compilation][cfg], then it would be appropriate + /// to allow the lint, with `#[allow(empty_enum)]`. + /// + /// For further information, visit + /// [the never type’s documentation][`!`]. /// /// ### Example /// ```no_run - /// enum Test {} + /// enum CannotExist {} /// ``` /// /// Use instead: /// ```no_run /// #![feature(never_type)] /// - /// struct Test(!); + /// /// Use the `!` type directly... + /// type CannotExist = !; + /// + /// /// ...or define a newtype which is distinct. + /// struct CannotExist2(pub !); /// ``` + /// + /// [`!`]: https://doc.rust-lang.org/std/primitive.never.html + /// [cfg]: https://doc.rust-lang.org/reference/conditional-compilation.html + /// [newtype]: https://doc.rust-lang.org/book/ch19-04-advanced-types.html#using-the-newtype-pattern-for-type-safety-and-abstraction + /// [visibility]: https://doc.rust-lang.org/reference/visibility-and-privacy.html #[clippy::version = "pre 1.29.0"] pub EMPTY_ENUM, pedantic, diff --git a/clippy_lints/src/empty_with_brackets.rs b/clippy_lints/src/empty_with_brackets.rs index 969df6d85b5a..745599b0e57a 100644 --- a/clippy_lints/src/empty_with_brackets.rs +++ b/clippy_lints/src/empty_with_brackets.rs @@ -11,16 +11,23 @@ declare_clippy_lint! { /// ### What it does /// Finds structs without fields (a so-called "empty struct") that are declared with brackets. /// - /// ### Why is this bad? - /// Empty brackets after a struct declaration can be omitted. + /// ### Why restrict this? + /// Empty brackets after a struct declaration can be omitted, + /// and it may be desirable to do so consistently for style. + /// + /// However, removing the brackets also introduces a public constant named after the struct, + /// so this is not just a syntactic simplification but an an API change, and adding them back + /// is a *breaking* API change. /// /// ### Example /// ```no_run /// struct Cookie {} + /// struct Biscuit(); /// ``` /// Use instead: /// ```no_run /// struct Cookie; + /// struct Biscuit; /// ``` #[clippy::version = "1.62.0"] pub EMPTY_STRUCTS_WITH_BRACKETS, @@ -32,14 +39,20 @@ declare_clippy_lint! { /// ### What it does /// Finds enum variants without fields that are declared with empty brackets. /// - /// ### Why is this bad? - /// Empty brackets while defining enum variants are redundant and can be omitted. + /// ### Why restrict this? + /// Empty brackets after a enum variant declaration are redundant and can be omitted, + /// and it may be desirable to do so consistently for style. + /// + /// However, removing the brackets also introduces a public constant named after the variant, + /// so this is not just a syntactic simplification but an an API change, and adding them back + /// is a *breaking* API change. /// /// ### Example /// ```no_run /// enum MyEnum { /// HasData(u8), - /// HasNoData(), // redundant parentheses + /// HasNoData(), // redundant parentheses + /// NoneHereEither {}, // redundant braces /// } /// ``` /// @@ -48,6 +61,7 @@ declare_clippy_lint! { /// enum MyEnum { /// HasData(u8), /// HasNoData, + /// NoneHereEither, /// } /// ``` #[clippy::version = "1.77.0"] diff --git a/clippy_lints/src/endian_bytes.rs b/clippy_lints/src/endian_bytes.rs index dd03df797de3..bb766e963387 100644 --- a/clippy_lints/src/endian_bytes.rs +++ b/clippy_lints/src/endian_bytes.rs @@ -13,8 +13,9 @@ declare_clippy_lint! { /// ### What it does /// Checks for the usage of the `to_ne_bytes` method and/or the function `from_ne_bytes`. /// - /// ### Why is this bad? - /// It's not, but some may prefer to specify the target endianness explicitly. + /// ### Why restrict this? + /// To ensure use of explicitly chosen endianness rather than the target’s endianness, + /// such as when implementing network protocols or file formats rather than FFI. /// /// ### Example /// ```rust,ignore @@ -31,9 +32,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for the usage of the `to_le_bytes` method and/or the function `from_le_bytes`. /// - /// ### Why is this bad? - /// It's not, but some may wish to lint usage of this method, either to suggest using the host - /// endianness or big endian. + /// ### Why restrict this? + /// To ensure use of big endian or the target’s endianness rather than little endian. /// /// ### Example /// ```rust,ignore @@ -50,9 +50,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for the usage of the `to_be_bytes` method and/or the function `from_be_bytes`. /// - /// ### Why is this bad? - /// It's not, but some may wish to lint usage of this method, either to suggest using the host - /// endianness or little endian. + /// ### Why restrict this? + /// To ensure use of little endian or the target’s endianness rather than big endian. /// /// ### Example /// ```rust,ignore diff --git a/clippy_lints/src/error_impl_error.rs b/clippy_lints/src/error_impl_error.rs index 8dbb47fadc5d..8e49138cd26b 100644 --- a/clippy_lints/src/error_impl_error.rs +++ b/clippy_lints/src/error_impl_error.rs @@ -12,7 +12,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for types named `Error` that implement `Error`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// 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` diff --git a/clippy_lints/src/exhaustive_items.rs b/clippy_lints/src/exhaustive_items.rs index 9ffda6457424..436dc8611bde 100644 --- a/clippy_lints/src/exhaustive_items.rs +++ b/clippy_lints/src/exhaustive_items.rs @@ -10,10 +10,10 @@ declare_clippy_lint! { /// ### What it does /// Warns on any exported `enum`s that are not tagged `#[non_exhaustive]` /// - /// ### Why is this bad? - /// Exhaustive enums are typically fine, but a project which does - /// not wish to make a stability commitment around exported enums may wish to - /// disable them by default. + /// ### Why restrict this? + /// Making an `enum` exhaustive is a stability commitment: adding a variant is a breaking change. + /// A project may wish to ensure that there are no exhaustive enums or that every exhaustive + /// `enum` is explicitly `#[allow]`ed. /// /// ### Example /// ```no_run @@ -40,10 +40,10 @@ declare_clippy_lint! { /// ### What it does /// Warns on any exported `struct`s that are not tagged `#[non_exhaustive]` /// - /// ### Why is this bad? - /// Exhaustive structs are typically fine, but a project which does - /// not wish to make a stability commitment around exported structs may wish to - /// disable them by default. + /// ### Why restrict this? + /// Making a `struct` exhaustive is a stability commitment: adding a field is a breaking change. + /// A project may wish to ensure that there are no exhaustive structs or that every exhaustive + /// `struct` is explicitly `#[allow]`ed. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/exit.rs b/clippy_lints/src/exit.rs index 106844dd4348..91c94d66458d 100644 --- a/clippy_lints/src/exit.rs +++ b/clippy_lints/src/exit.rs @@ -9,11 +9,13 @@ declare_clippy_lint! { /// ### What it does /// Detects calls to the `exit()` function which terminates the program. /// - /// ### Why is this bad? - /// Exit terminates the program at the location it is called. For unrecoverable - /// errors `panics` should be used to provide a stacktrace and potentially other - /// information. A normal termination or one with an error code should happen in - /// the main function. + /// ### Why restrict this? + /// `exit()` immediately terminates the program with no information other than an exit code. + /// This provides no means to troubleshoot a problem, and may be an unexpected side effect. + /// + /// Codebases may use this lint to require that all exits are performed either by panicking + /// (which produces a message, a code location, and optionally a backtrace) + /// or by returning from `main()` (which is a single place to look). /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/float_literal.rs b/clippy_lints/src/float_literal.rs index 2cd4e9e99a56..4ec9bd757ff4 100644 --- a/clippy_lints/src/float_literal.rs +++ b/clippy_lints/src/float_literal.rs @@ -38,9 +38,9 @@ declare_clippy_lint! { /// Checks for whole number float literals that /// cannot be represented as the underlying type without loss. /// - /// ### Why is this bad? - /// Rust will silently lose precision during - /// conversion to a float. + /// ### Why restrict this? + /// If the value was intended to be exact, it will not be. + /// This may be especially surprising when the lost precision is to the left of the decimal point. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/format_push_string.rs b/clippy_lints/src/format_push_string.rs index 2b08437d8271..a75538dd329b 100644 --- a/clippy_lints/src/format_push_string.rs +++ b/clippy_lints/src/format_push_string.rs @@ -11,7 +11,7 @@ declare_clippy_lint! { /// Detects cases where the result of a `format!` call is /// appended to an existing `String`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Introduces an extra, avoidable heap allocation. /// /// ### Known problems diff --git a/clippy_lints/src/functions/misnamed_getters.rs b/clippy_lints/src/functions/misnamed_getters.rs index 7729c556e1fc..2cbc4b072341 100644 --- a/clippy_lints/src/functions/misnamed_getters.rs +++ b/clippy_lints/src/functions/misnamed_getters.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; -use rustc_hir::{Body, ExprKind, FnDecl, Safety, ImplicitSelfKind}; +use rustc_hir::{Body, ExprKind, FnDecl, ImplicitSelfKind, Safety}; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::Span; diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index dfcaac9abefc..26534492dddd 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -338,8 +338,10 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does /// Lints when `impl Trait` is being used in a function's parameters. - /// ### Why is this bad? - /// Turbofish syntax (`::<>`) cannot be used when `impl Trait` is being used, making `impl Trait` less powerful. Readability may also be a factor. + /// + /// ### Why restrict this? + /// Turbofish syntax (`::<>`) cannot be used to specify the type of an `impl Trait` parameter, + /// making `impl Trait` less powerful. Readability may also be a factor. /// /// ### Example /// ```no_run @@ -366,9 +368,8 @@ declare_clippy_lint! { /// Lints when the name of function parameters from trait impl is /// different than its default implementation. /// - /// ### Why is this bad? - /// Using the default name for parameters of a trait method is often - /// more desirable for consistency's sake. + /// ### Why restrict this? + /// Using the default name for parameters of a trait method is more consistent. /// /// ### Example /// ```rust diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index f5ba62ae432e..0b2008152198 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -15,7 +15,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for if-else that could be written using either `bool::then` or `bool::then_some`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Looks a little redundant. Using `bool::then` is more concise and incurs no loss of clarity. /// For simple calculations and known values, use `bool::then_some`, which is eagerly evaluated /// in comparison to `bool::then`. diff --git a/clippy_lints/src/implicit_return.rs b/clippy_lints/src/implicit_return.rs index 5288efd8df8c..cc342007ec61 100644 --- a/clippy_lints/src/implicit_return.rs +++ b/clippy_lints/src/implicit_return.rs @@ -16,12 +16,13 @@ declare_clippy_lint! { /// ### What it does /// Checks for missing return statements at the end of a block. /// - /// ### Why is this bad? - /// Actually omitting the return keyword is idiomatic Rust code. Programmers - /// coming from other languages might prefer the expressiveness of `return`. It's possible to miss - /// the last returning statement because the only difference is a missing `;`. Especially in bigger - /// code with multiple return paths having a `return` keyword makes it easier to find the - /// corresponding statements. + /// ### Why restrict this? + /// Omitting the return keyword whenever possible is idiomatic Rust code, but: + /// + /// * Programmers coming from other languages might prefer the expressiveness of `return`. + /// * It's possible to miss the last returning statement because the only difference is a missing `;`. + /// * Especially in bigger code with multiple return paths, having a `return` keyword makes it easier to find the + /// corresponding statements. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/indexing_slicing.rs b/clippy_lints/src/indexing_slicing.rs index 35fcd8cdd354..e3e79749bea6 100644 --- a/clippy_lints/src/indexing_slicing.rs +++ b/clippy_lints/src/indexing_slicing.rs @@ -45,9 +45,10 @@ declare_clippy_lint! { /// does report on arrays if we can tell that slicing operations are in bounds and does not /// lint on constant `usize` indexing on arrays because that is handled by rustc's `const_err` lint. /// - /// ### Why is this bad? - /// Indexing and slicing can panic at runtime and there are - /// safe alternatives. + /// ### Why restrict this? + /// To avoid implicit panics from indexing and slicing. + /// There are “checked” alternatives which do not panic, and can be used with `unwrap()` to make + /// an explicit panic when it is desired. /// /// ### Example /// ```rust,no_run diff --git a/clippy_lints/src/inherent_impl.rs b/clippy_lints/src/inherent_impl.rs index 1127f00abde0..95ae591884bc 100644 --- a/clippy_lints/src/inherent_impl.rs +++ b/clippy_lints/src/inherent_impl.rs @@ -14,7 +14,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for multiple inherent implementations of a struct /// - /// ### Why is this bad? + /// ### Why restrict this? /// Splitting the implementation of a type makes the code harder to navigate. /// /// ### Example diff --git a/clippy_lints/src/init_numbered_fields.rs b/clippy_lints/src/init_numbered_fields.rs index e486563808a5..1c8fd0a27f98 100644 --- a/clippy_lints/src/init_numbered_fields.rs +++ b/clippy_lints/src/init_numbered_fields.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Expr, ExprKind}; @@ -62,7 +62,7 @@ impl<'tcx> LateLintPass<'tcx> for NumberedFields { snippet_with_applicability(cx, path.span(), "..", &mut appl), expr_spans .into_iter_sorted() - .map(|(_, span)| snippet_with_applicability(cx, span, "..", &mut appl)) + .map(|(_, span)| snippet_with_context(cx, span, path.span().ctxt(), "..", &mut appl).0) .intersperse(Cow::Borrowed(", ")) .collect::() ); diff --git a/clippy_lints/src/integer_division_remainder_used.rs b/clippy_lints/src/integer_division_remainder_used.rs index a3577b765c03..cf598d5045ec 100644 --- a/clippy_lints/src/integer_division_remainder_used.rs +++ b/clippy_lints/src/integer_division_remainder_used.rs @@ -7,10 +7,10 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does - /// Checks for the usage of division (/) and remainder (%) operations - /// when performed on any integer types using the default Div and Rem trait implementations. + /// Checks for the usage of division (`/`) and remainder (`%`) operations + /// when performed on any integer types using the default `Div` and `Rem` trait implementations. /// - /// ### Why is this bad? + /// ### Why restrict this? /// In cryptographic contexts, division can result in timing sidechannel vulnerabilities, /// and needs to be replaced with constant-time code instead (e.g. Barrett reduction). /// diff --git a/clippy_lints/src/iter_over_hash_type.rs b/clippy_lints/src/iter_over_hash_type.rs index 6c6eff9ba48b..fb29d9824179 100644 --- a/clippy_lints/src/iter_over_hash_type.rs +++ b/clippy_lints/src/iter_over_hash_type.rs @@ -14,8 +14,8 @@ declare_clippy_lint! { /// ### What it does /// This is a restriction lint which prevents the use of hash types (i.e., `HashSet` and `HashMap`) in for loops. /// - /// ### Why is this bad? - /// Because hash types are unordered, when iterated through such as in a for loop, the values are returned in + /// ### Why restrict this? + /// Because hash types are unordered, when iterated through such as in a `for` loop, the values are returned in /// an undefined order. As a result, on redundant systems this may cause inconsistencies and anomalies. /// In addition, the unknown order of the elements may reduce readability or introduce other undesired /// side effects. diff --git a/clippy_lints/src/large_include_file.rs b/clippy_lints/src/large_include_file.rs index 790bed580fd1..07efee159aab 100644 --- a/clippy_lints/src/large_include_file.rs +++ b/clippy_lints/src/large_include_file.rs @@ -10,10 +10,12 @@ use rustc_span::sym; declare_clippy_lint! { /// ### What it does /// Checks for the inclusion of large files via `include_bytes!()` - /// and `include_str!()` + /// or `include_str!()`. /// - /// ### Why is this bad? - /// Including large files can increase the size of the binary + /// ### Why restrict this? + /// Including large files can undesirably increase the size of the binary produced by the compiler. + /// This lint may be used to catch mistakes where an unexpectedly large file is included, or + /// temporarily to obtain a list of all large files. /// /// ### Example /// ```rust,ignore diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs index 619e933b4fff..9fd4f509aa47 100644 --- a/clippy_lints/src/let_underscore.rs +++ b/clippy_lints/src/let_underscore.rs @@ -12,9 +12,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for `let _ = ` where expr is `#[must_use]` /// - /// ### Why is this bad? - /// It's better to explicitly handle the value of a `#[must_use]` - /// expr + /// ### Why restrict this? + /// To ensure that all `#[must_use]` types are used rather than ignored. /// /// ### Example /// ```no_run @@ -96,8 +95,8 @@ declare_clippy_lint! { /// Checks for `let _ = ` without a type annotation, and suggests to either provide one, /// or remove the `let` keyword altogether. /// - /// ### Why is this bad? - /// The `let _ = ` expression ignores the value of `` but will remain doing so even + /// ### Why restrict this? + /// The `let _ = ` expression ignores the value of ``, but will continue to do so even /// if the type were to change, thus potentially introducing subtle bugs. By supplying a type /// annotation, one will be forced to re-visit the decision to ignore the value in such cases. /// diff --git a/clippy_lints/src/literal_representation.rs b/clippy_lints/src/literal_representation.rs index 2348dd18220f..d2a140a36a83 100644 --- a/clippy_lints/src/literal_representation.rs +++ b/clippy_lints/src/literal_representation.rs @@ -132,8 +132,8 @@ declare_clippy_lint! { /// ### What it does /// Warns if there is a better representation for a numeric literal. /// - /// ### Why is this bad? - /// Especially for big powers of 2 a hexadecimal representation is more + /// ### Why restrict this? + /// Especially for big powers of 2, a hexadecimal representation is usually more /// readable than a decimal representation. /// /// ### Example diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 3dcb050d77e6..086829421530 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -675,9 +675,9 @@ declare_clippy_lint! { /// Checks for infinite loops in a function where the return type is not `!` /// and lint accordingly. /// - /// ### Why is this bad? - /// A loop should be gently exited somewhere, or at least mark its parent function as - /// never return (`!`). + /// ### Why restrict this? + /// Making the return type `!` serves as documentation that the function does not return. + /// If the function is not intended to loop infinitely, then this lint may detect a bug. /// /// ### Example /// ```no_run,ignore diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index ee9f48d71ad8..691ecd57535a 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -260,7 +260,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for wildcard enum matches using `_`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// New enum variants added by library updates can be missed. /// /// ### Known problems @@ -435,7 +435,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Correctness and readability. It's like having a wildcard pattern after /// matching all enum variants explicitly. /// @@ -861,7 +861,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `Err(x)?`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// The `?` operator is designed to allow calls that /// can fail to be easily chained. For example, `foo()?.bar()` or /// `foo(bar()?)`. Because `Err(x)?` can't be used that way (it will diff --git a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 6ac0705abb21..2f72e59834fa 100644 --- a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -1,12 +1,17 @@ +use std::ops::ControlFlow; + use crate::FxHashSet; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{indent_of, snippet}; +use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy}; use clippy_utils::{get_attr, is_lint_allowed}; +use itertools::Itertools; +use rustc_ast::Mutability; use rustc_errors::{Applicability, Diag}; use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_hir::{Arm, Expr, ExprKind, MatchSource}; use rustc_lint::{LateContext, LintContext}; -use rustc_middle::ty::{GenericArgKind, Ty}; +use rustc_middle::ty::{GenericArgKind, Region, RegionKind, Ty, TyCtxt, TypeVisitable, TypeVisitor}; use rustc_span::Span; use super::SIGNIFICANT_DROP_IN_SCRUTINEE; @@ -22,43 +27,34 @@ pub(super) fn check<'tcx>( return; } - if let Some((suggestions, message)) = has_significant_drop_in_scrutinee(cx, scrutinee, source) { - for found in suggestions { - span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| { - set_diagnostic(diag, cx, expr, found); - let s = Span::new(expr.span.hi(), expr.span.hi(), expr.span.ctxt(), None); - diag.span_label(s, "temporary lives until here"); - for span in has_significant_drop_in_arms(cx, arms) { - diag.span_label(span, "another value with significant `Drop` created here"); - } - diag.note("this might lead to deadlocks or other unexpected behavior"); - }); - } + let (suggestions, message) = has_significant_drop_in_scrutinee(cx, scrutinee, source); + for found in suggestions { + span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| { + set_diagnostic(diag, cx, expr, found); + let s = Span::new(expr.span.hi(), expr.span.hi(), expr.span.ctxt(), None); + diag.span_label(s, "temporary lives until here"); + for span in has_significant_drop_in_arms(cx, arms) { + diag.span_label(span, "another value with significant `Drop` created here"); + } + diag.note("this might lead to deadlocks or other unexpected behavior"); + }); } } fn set_diagnostic<'tcx>(diag: &mut Diag<'_, ()>, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, found: FoundSigDrop) { - if found.lint_suggestion == LintSuggestion::MoveAndClone { - // If our suggestion is to move and clone, then we want to leave it to the user to - // decide how to address this lint, since it may be that cloning is inappropriate. - // Therefore, we won't to emit a suggestion. - return; - } - let original = snippet(cx, found.found_span, ".."); let trailing_indent = " ".repeat(indent_of(cx, found.found_span).unwrap_or(0)); - let replacement = if found.lint_suggestion == LintSuggestion::MoveAndDerefToCopy { - format!("let value = *{original};\n{trailing_indent}") - } else if found.is_unit_return_val { - // If the return value of the expression to be moved is unit, then we don't need to - // capture the result in a temporary -- we can just replace it completely with `()`. - format!("{original};\n{trailing_indent}") - } else { - format!("let value = {original};\n{trailing_indent}") + let replacement = { + let (def_part, deref_part) = if found.is_unit_return_val { + ("", String::new()) + } else { + ("let value = ", "*".repeat(found.peel_ref_times)) + }; + format!("{def_part}{deref_part}{original};\n{trailing_indent}") }; - let suggestion_message = if found.lint_suggestion == LintSuggestion::MoveOnly { + let suggestion_message = if found.peel_ref_times == 0 { "try moving the temporary above the match" } else { "try moving the temporary above the match and create a copy" @@ -66,8 +62,11 @@ fn set_diagnostic<'tcx>(diag: &mut Diag<'_, ()>, cx: &LateContext<'tcx>, expr: & let scrutinee_replacement = if found.is_unit_return_val { "()".to_owned() - } else { + } else if found.peel_ref_times == 0 { "value".to_owned() + } else { + let ref_part = "&".repeat(found.peel_ref_times); + format!("({ref_part}value)") }; diag.multipart_suggestion( @@ -86,20 +85,18 @@ fn has_significant_drop_in_scrutinee<'tcx>( cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'tcx>, source: MatchSource, -) -> Option<(Vec, &'static str)> { +) -> (Vec, &'static str) { let mut helper = SigDropHelper::new(cx); let scrutinee = match (source, &scrutinee.kind) { (MatchSource::ForLoopDesugar, ExprKind::Call(_, [e])) => e, _ => scrutinee, }; - helper.find_sig_drop(scrutinee).map(|drops| { - let message = if source == MatchSource::Normal { - "temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression" - } else { - "temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression" - }; - (drops, message) - }) + let message = if source == MatchSource::Normal { + "temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression" + } else { + "temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression" + }; + (helper.find_sig_drop(scrutinee), message) } struct SigDropChecker<'a, 'tcx> { @@ -172,205 +169,248 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> { } } +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +enum SigDropHolder { + /// No values with significant drop present in this expression. + /// + /// Expressions that we've emited lints do not count. + None, + /// Some field in this expression references to values with significant drop. + /// + /// Example: `(1, &data.lock().field)`. + PackedRef, + /// The value of this expression references to values with significant drop. + /// + /// Example: `data.lock().field`. + DirectRef, + /// This expression should be moved out to avoid significant drop in scrutinee. + Moved, +} + +impl Default for SigDropHolder { + fn default() -> Self { + Self::None + } +} + struct SigDropHelper<'a, 'tcx> { cx: &'a LateContext<'tcx>, - is_chain_end: bool, - has_significant_drop: bool, - current_sig_drop: Option, - sig_drop_spans: Option>, - special_handling_for_binary_op: bool, + parent_expr: Option<&'tcx Expr<'tcx>>, + sig_drop_holder: SigDropHolder, + sig_drop_spans: Vec, sig_drop_checker: SigDropChecker<'a, 'tcx>, } -#[expect(clippy::enum_variant_names)] -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -enum LintSuggestion { - MoveOnly, - MoveAndDerefToCopy, - MoveAndClone, -} - -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] struct FoundSigDrop { found_span: Span, is_unit_return_val: bool, - lint_suggestion: LintSuggestion, + peel_ref_times: usize, } impl<'a, 'tcx> SigDropHelper<'a, 'tcx> { fn new(cx: &'a LateContext<'tcx>) -> SigDropHelper<'a, 'tcx> { SigDropHelper { cx, - is_chain_end: true, - has_significant_drop: false, - current_sig_drop: None, - sig_drop_spans: None, - special_handling_for_binary_op: false, + parent_expr: None, + sig_drop_holder: SigDropHolder::None, + sig_drop_spans: Vec::new(), sig_drop_checker: SigDropChecker::new(cx), } } - fn find_sig_drop(&mut self, match_expr: &'tcx Expr<'_>) -> Option> { + fn find_sig_drop(&mut self, match_expr: &'tcx Expr<'_>) -> Vec { self.visit_expr(match_expr); - // If sig drop spans is empty but we found a significant drop, it means that we didn't find - // a type that was trivially copyable as we moved up the chain after finding a significant - // drop, so move the entire scrutinee. - if self.has_significant_drop && self.sig_drop_spans.is_none() { - self.try_setting_current_suggestion(match_expr, true); - self.move_current_suggestion(); - } - - self.sig_drop_spans.take() + core::mem::take(&mut self.sig_drop_spans) } - fn replace_current_sig_drop( - &mut self, - found_span: Span, - is_unit_return_val: bool, - lint_suggestion: LintSuggestion, - ) { - self.current_sig_drop.replace(FoundSigDrop { + fn replace_current_sig_drop(&mut self, found_span: Span, is_unit_return_val: bool, peel_ref_times: usize) { + self.sig_drop_spans.clear(); + self.sig_drop_spans.push(FoundSigDrop { found_span, is_unit_return_val, - lint_suggestion, + peel_ref_times, }); } - /// This will try to set the current suggestion (so it can be moved into the suggestions vec - /// later). If `allow_move_and_clone` is false, the suggestion *won't* be set -- this gives us - /// an opportunity to look for another type in the chain that will be trivially copyable. - /// However, if we are at the end of the chain, we want to accept whatever is there. (The - /// suggestion won't actually be output, but the diagnostic message will be output, so the user - /// can determine the best way to handle the lint.) - fn try_setting_current_suggestion(&mut self, expr: &'tcx Expr<'_>, allow_move_and_clone: bool) { - if self.current_sig_drop.is_some() { - return; + fn try_move_sig_drop(&mut self, expr: &'tcx Expr<'_>, parent_expr: &'tcx Expr<'_>) { + if self.sig_drop_holder == SigDropHolder::Moved { + self.sig_drop_holder = SigDropHolder::None; } - let ty = self.cx.typeck_results().expr_ty(expr); - if ty.is_ref() { - // We checked that the type was ref, so builtin_deref will return Some, - // but let's avoid any chance of an ICE. - if let Some(ty) = ty.builtin_deref(true) { - if ty.is_trivially_pure_clone_copy() { - self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndDerefToCopy); - } else if allow_move_and_clone { - self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndClone); - } + + if self.sig_drop_holder == SigDropHolder::DirectRef { + self.sig_drop_holder = SigDropHolder::PackedRef; + self.try_move_sig_drop_direct_ref(expr, parent_expr); + } else if self.sig_drop_checker.is_sig_drop_expr(expr) { + // The values with significant drop can be moved to some other functions. For example, consider + // `drop(data.lock())`. We use `SigDropHolder::None` here to avoid emitting lints in such scenarios. + self.sig_drop_holder = SigDropHolder::None; + self.try_move_sig_drop_direct_ref(expr, parent_expr); + } + + if self.sig_drop_holder != SigDropHolder::None { + let parent_ty = self.cx.typeck_results().expr_ty(parent_expr); + if !ty_has_erased_regions(parent_ty) && !parent_expr.is_syntactic_place_expr() { + self.replace_current_sig_drop(parent_expr.span, parent_ty.is_unit(), 0); + self.sig_drop_holder = SigDropHolder::Moved; + } + + let (peel_ref_ty, peel_ref_times) = ty_peel_refs(parent_ty); + if !ty_has_erased_regions(peel_ref_ty) && is_copy(self.cx, peel_ref_ty) { + self.replace_current_sig_drop(parent_expr.span, peel_ref_ty.is_unit(), peel_ref_times); + self.sig_drop_holder = SigDropHolder::Moved; } - } else if ty.is_trivially_pure_clone_copy() { - self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveOnly); - } else if allow_move_and_clone { - self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndClone); } } - fn move_current_suggestion(&mut self) { - if let Some(current) = self.current_sig_drop.take() { - self.sig_drop_spans.get_or_insert_with(Vec::new).push(current); + fn try_move_sig_drop_direct_ref(&mut self, expr: &'tcx Expr<'_>, parent_expr: &'tcx Expr<'_>) { + let arg_idx = match parent_expr.kind { + ExprKind::MethodCall(_, receiver, exprs, _) => std::iter::once(receiver) + .chain(exprs.iter()) + .find_position(|ex| ex.hir_id == expr.hir_id) + .map(|(idx, _)| idx), + ExprKind::Call(_, exprs) => exprs + .iter() + .find_position(|ex| ex.hir_id == expr.hir_id) + .map(|(idx, _)| idx), + ExprKind::Binary(_, lhs, rhs) | ExprKind::AssignOp(_, lhs, rhs) => [lhs, rhs] + .iter() + .find_position(|ex| ex.hir_id == expr.hir_id) + .map(|(idx, _)| idx), + ExprKind::Unary(_, ex) => (ex.hir_id == expr.hir_id).then_some(0), + _ => { + // Here we assume that all other expressions create or propagate the reference to the value with + // significant drop. + self.sig_drop_holder = SigDropHolder::DirectRef; + return; + }, + }; + let Some(arg_idx) = arg_idx else { + return; + }; + + let fn_sig = if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(parent_expr.hir_id) { + self.cx.tcx.fn_sig(def_id).instantiate_identity() + } else { + return; + }; + + let input_re = if let Some(input_ty) = fn_sig.skip_binder().inputs().get(arg_idx) + && let rustc_middle::ty::Ref(input_re, _, _) = input_ty.kind() + { + input_re + } else { + return; + }; + + // Late bound lifetime parameters are not related to any constraints, so we can track them in a very + // simple manner. For other lifetime parameters, we give up and update the state to `PackedRef`. + let RegionKind::ReBound(_, input_re_bound) = input_re.kind() else { + self.sig_drop_holder = SigDropHolder::PackedRef; + return; + }; + let contains_input_re = |re_bound| { + if re_bound == input_re_bound { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }; + + let output_ty = fn_sig.skip_binder().output(); + if let rustc_middle::ty::Ref(output_re, peel_ref_ty, _) = output_ty.kind() + && input_re == output_re + && for_each_top_level_late_bound_region(*peel_ref_ty, contains_input_re).is_continue() + { + // We're lucky! The output type is still a direct reference to the value with significant drop. + self.sig_drop_holder = SigDropHolder::DirectRef; + } else if for_each_top_level_late_bound_region(output_ty, contains_input_re).is_continue() { + // The lifetime to the value with significant drop goes away. So we can emit a lint that suggests to + // move the expression out. + self.replace_current_sig_drop(parent_expr.span, output_ty.is_unit(), 0); + self.sig_drop_holder = SigDropHolder::Moved; + } else { + // TODO: The lifetime is still there but it's for a inner type. For instance, consider + // `Some(&mutex.lock().field)`, which has a type of `Option<&u32>`. How to address this scenario? + self.sig_drop_holder = SigDropHolder::PackedRef; } } +} - fn visit_exprs_for_binary_ops( - &mut self, - left: &'tcx Expr<'_>, - right: &'tcx Expr<'_>, - is_unit_return_val: bool, - span: Span, - ) { - self.special_handling_for_binary_op = true; - self.visit_expr(left); - self.visit_expr(right); - - // If either side had a significant drop, suggest moving the entire scrutinee to avoid - // unnecessary copies and to simplify cases where both sides have significant drops. - if self.has_significant_drop { - self.replace_current_sig_drop(span, is_unit_return_val, LintSuggestion::MoveOnly); - } +fn ty_peel_refs(mut ty: Ty<'_>) -> (Ty<'_>, usize) { + let mut n = 0; + while let rustc_middle::ty::Ref(_, new_ty, Mutability::Not) = ty.kind() { + ty = *new_ty; + n += 1; + } + (ty, n) +} + +fn ty_has_erased_regions(ty: Ty<'_>) -> bool { + struct V; - self.special_handling_for_binary_op = false; + impl<'tcx> TypeVisitor> for V { + type Result = ControlFlow<()>; + + fn visit_region(&mut self, region: Region<'tcx>) -> Self::Result { + if region.is_erased() { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + } } + + ty.visit_with(&mut V).is_break() } impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> { fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { - if !self.is_chain_end && self.sig_drop_checker.is_sig_drop_expr(ex) { - self.has_significant_drop = true; + // We've emited a lint on some neighborhood expression. That lint will suggest to move out the + // _parent_ expression (not the expression itself). Since we decide to move out the parent + // expression, it is pointless to continue to process the current expression. + if self.sig_drop_holder == SigDropHolder::Moved { return; } - self.is_chain_end = false; + + // These states are of neighborhood expressions. We save and clear them here, and we'll later merge + // the states of the current expression with them at the end of the method. + let sig_drop_holder_before = core::mem::take(&mut self.sig_drop_holder); + let sig_drop_spans_before = core::mem::take(&mut self.sig_drop_spans); + let parent_expr_before = self.parent_expr.replace(ex); match ex.kind { - ExprKind::MethodCall(_, expr, ..) => { - self.visit_expr(expr); - } - ExprKind::Binary(_, left, right) => { - self.visit_exprs_for_binary_ops(left, right, false, ex.span); - } - ExprKind::Assign(left, right, _) | ExprKind::AssignOp(_, left, right) => { - self.visit_exprs_for_binary_ops(left, right, true, ex.span); - } - ExprKind::Tup(exprs) => { - for expr in exprs { - self.visit_expr(expr); - if self.has_significant_drop { - // We may have not have set current_sig_drop if all the suggestions were - // MoveAndClone, so add this tuple item's full expression in that case. - if self.current_sig_drop.is_none() { - self.try_setting_current_suggestion(expr, true); - } - - // Now we are guaranteed to have something, so add it to the final vec. - self.move_current_suggestion(); - } - // Reset `has_significant_drop` after each tuple expression so we can look for - // additional cases. - self.has_significant_drop = false; - } - if self.sig_drop_spans.is_some() { - self.has_significant_drop = true; - } - } - ExprKind::Array(..) | - ExprKind::Call(..) | - ExprKind::Unary(..) | - ExprKind::If(..) | - ExprKind::Match(..) | - ExprKind::Field(..) | - ExprKind::Index(..) | - ExprKind::Ret(..) | - ExprKind::Become(..) | - ExprKind::Repeat(..) | - ExprKind::Yield(..) => walk_expr(self, ex), - ExprKind::AddrOf(_, _, _) | - ExprKind::Block(_, _) | - ExprKind::Break(_, _) | - ExprKind::Cast(_, _) | - // Don't want to check the closure itself, only invocation, which is covered by MethodCall - ExprKind::Closure { .. } | - ExprKind::ConstBlock(_) | - ExprKind::Continue(_) | - ExprKind::DropTemps(_) | - ExprKind::Err(_) | - ExprKind::InlineAsm(_) | - ExprKind::OffsetOf(_, _) | - ExprKind::Let(_) | - ExprKind::Lit(_) | - ExprKind::Loop(_, _, _, _) | - ExprKind::Path(_) | - ExprKind::Struct(_, _, _) | - ExprKind::Type(_, _) => { - return; + // Skip blocks because values in blocks will be dropped as usual. + ExprKind::Block(..) => (), + _ => walk_expr(self, ex), + } + + if let Some(parent_ex) = parent_expr_before { + match parent_ex.kind { + ExprKind::Assign(lhs, _, _) | ExprKind::AssignOp(_, lhs, _) + if lhs.hir_id == ex.hir_id && self.sig_drop_holder == SigDropHolder::Moved => + { + // Never move out only the assignee. Instead, we should always move out the whole assigment. + self.replace_current_sig_drop(parent_ex.span, true, 0); + }, + _ => { + self.try_move_sig_drop(ex, parent_ex); + }, } } - // Once a significant temporary has been found, we need to go back up at least 1 level to - // find the span to extract for replacement, so the temporary gets dropped. However, for - // binary ops, we want to move the whole scrutinee so we avoid unnecessary copies and to - // simplify cases where both sides have significant drops. - if self.has_significant_drop && !self.special_handling_for_binary_op { - self.try_setting_current_suggestion(ex, false); + self.sig_drop_holder = std::cmp::max(self.sig_drop_holder, sig_drop_holder_before); + + // We do not need those old spans in neighborhood expressions if we emit a lint that suggests to + // move out the _parent_ expression (i.e., `self.sig_drop_holder == SigDropHolder::Moved`). + if self.sig_drop_holder != SigDropHolder::Moved { + let mut sig_drop_spans = sig_drop_spans_before; + sig_drop_spans.append(&mut self.sig_drop_spans); + self.sig_drop_spans = sig_drop_spans; } + + self.parent_expr = parent_expr_before; } } diff --git a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs index 6c9bdcff8262..f4397212cf66 100644 --- a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs +++ b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs @@ -1,8 +1,12 @@ +use std::iter::once; + use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; use clippy_utils::{get_expr_use_or_unification_node, is_res_lang_ctor, path_res, std_or_core}; use rustc_errors::Applicability; +use rustc_hir::def_id::DefId; +use rustc_hir::hir_id::HirId; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind, Node}; use rustc_lint::LateContext; @@ -25,7 +29,29 @@ impl IterType { } } -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: &str, recv: &Expr<'_>) { +fn is_arg_ty_unified_in_fn<'tcx>( + cx: &LateContext<'tcx>, + fn_id: DefId, + arg_id: HirId, + args: impl IntoIterator>, +) -> bool { + let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity(); + let arg_id_in_args = args.into_iter().position(|e| e.hir_id == arg_id).unwrap(); + let arg_ty_in_args = fn_sig.input(arg_id_in_args).skip_binder(); + + cx.tcx.predicates_of(fn_id).predicates.iter().any(|(clause, _)| { + clause + .as_projection_clause() + .and_then(|p| p.map_bound(|p| p.term.ty()).transpose()) + .is_some_and(|ty| ty.skip_binder() == arg_ty_in_args) + }) || fn_sig + .inputs() + .iter() + .enumerate() + .any(|(i, ty)| i != arg_id_in_args && ty.skip_binder().walk().any(|arg| arg.as_type() == Some(arg_ty_in_args))) +} + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: &str, recv: &'tcx Expr<'tcx>) { let item = match recv.kind { ExprKind::Array([]) => None, ExprKind::Array([e]) => Some(e), @@ -43,6 +69,25 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: &str, re let is_unified = match get_expr_use_or_unification_node(cx.tcx, expr) { Some((Node::Expr(parent), child_id)) => match parent.kind { ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id == child_id => false, + ExprKind::Call( + Expr { + kind: ExprKind::Path(path), + hir_id, + .. + }, + args, + ) => cx + .typeck_results() + .qpath_res(path, *hir_id) + .opt_def_id() + .filter(|fn_id| cx.tcx.def_kind(fn_id).is_fn_like()) + .is_some_and(|fn_id| is_arg_ty_unified_in_fn(cx, fn_id, child_id, args)), + ExprKind::MethodCall(_name, recv, args, _span) => is_arg_ty_unified_in_fn( + cx, + cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(), + child_id, + once(recv).chain(args.iter()), + ), ExprKind::If(_, _, _) | ExprKind::Match(_, _, _) | ExprKind::Closure(_) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 9d67aa233797..75a86c0c8344 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -257,7 +257,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `.unwrap()` or `.unwrap_err()` calls on `Result`s and `.unwrap()` call on `Option`s. /// - /// ### Why is this bad? + /// ### Why restrict this? /// It is better to handle the `None` or `Err` case, /// or at least call `.expect(_)` with a more helpful message. Still, for a lot of /// quick-and-dirty code, `unwrap` is a good choice, which is why this lint is @@ -333,7 +333,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `.expect()` or `.expect_err()` calls on `Result`s and `.expect()` call on `Option`s. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Usually it is better to handle the `None` or `Err` case. /// Still, for a lot of quick-and-dirty code, `expect` is a good choice, which is why /// this lint is `Allow` by default. @@ -1029,8 +1029,8 @@ declare_clippy_lint! { /// (`Rc`, `Arc`, `rc::Weak`, or `sync::Weak`), and suggests calling Clone via unified /// function syntax instead (e.g., `Rc::clone(foo)`). /// - /// ### Why is this bad? - /// Calling '.clone()' on an Rc, Arc, or Weak + /// ### Why restrict this? + /// Calling `.clone()` on an `Rc`, `Arc`, or `Weak` /// can obscure the fact that only the pointer is being cloned, not the underlying /// data. /// @@ -1051,7 +1051,7 @@ declare_clippy_lint! { #[clippy::version = "pre 1.29.0"] pub CLONE_ON_REF_PTR, restriction, - "using 'clone' on a ref-counted pointer" + "using `clone` on a ref-counted pointer" } declare_clippy_lint! { @@ -1359,7 +1359,7 @@ declare_clippy_lint! { /// Checks for usage of `.get().unwrap()` (or /// `.get_mut().unwrap`) on a standard library type which implements `Index` /// - /// ### Why is this bad? + /// ### Why restrict this? /// Using the Index trait (`[]`) is more clear and more /// concise. /// @@ -1743,7 +1743,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `FileType::is_file()`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// When people testing a file type with `FileType::is_file` /// they are testing whether a path is something they can get bytes from. But /// `is_file` doesn't cover special file types in unix-like systems, and doesn't cover @@ -2688,8 +2688,9 @@ declare_clippy_lint! { /// ### What it does /// Checks for instances of `map_err(|_| Some::Enum)` /// - /// ### Why is this bad? - /// This `map_err` throws away the original error rather than allowing the enum to contain and report the cause of the error + /// ### Why restrict this? + /// This `map_err` throws away the original error rather than allowing the enum to + /// contain and report the cause of the error. /// /// ### Example /// Before: @@ -3145,7 +3146,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of File::read_to_end and File::read_to_string. /// - /// ### Why is this bad? + /// ### Why restrict this? /// `fs::{read, read_to_string}` provide the same functionality when `buf` is empty with fewer imports and no intermediate values. /// See also: [fs::read docs](https://doc.rust-lang.org/std/fs/fn.read.html), [fs::read_to_string docs](https://doc.rust-lang.org/std/fs/fn.read_to_string.html) /// diff --git a/clippy_lints/src/methods/unnecessary_iter_cloned.rs b/clippy_lints/src/methods/unnecessary_iter_cloned.rs index 7431dc1cf0b1..d1300dd43c28 100644 --- a/clippy_lints/src/methods/unnecessary_iter_cloned.rs +++ b/clippy_lints/src/methods/unnecessary_iter_cloned.rs @@ -38,7 +38,7 @@ pub fn check_for_loop_iter( ) -> bool { if let Some(grandparent) = get_parent_expr(cx, expr).and_then(|parent| get_parent_expr(cx, parent)) && let Some(ForLoop { pat, body, .. }) = ForLoop::hir(grandparent) - && let (clone_or_copy_needed, addr_of_exprs) = clone_or_copy_needed(cx, pat, body) + && let (clone_or_copy_needed, references_to_binding) = clone_or_copy_needed(cx, pat, body) && !clone_or_copy_needed && let Some(receiver_snippet) = snippet_opt(cx, receiver.span) { @@ -123,14 +123,12 @@ pub fn check_for_loop_iter( Applicability::MachineApplicable }; diag.span_suggestion(expr.span, "use", snippet, applicability); - for addr_of_expr in addr_of_exprs { - match addr_of_expr.kind { - ExprKind::AddrOf(_, _, referent) => { - let span = addr_of_expr.span.with_hi(referent.span.lo()); - diag.span_suggestion(span, "remove this `&`", "", applicability); - }, - _ => unreachable!(), - } + if !references_to_binding.is_empty() { + diag.multipart_suggestion( + "remove any references to the binding", + references_to_binding, + applicability, + ); } }, ); diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints/src/methods/utils.rs index 34d7b9acbe4b..1a55b7160fb1 100644 --- a/clippy_lints/src/methods/utils.rs +++ b/clippy_lints/src/methods/utils.rs @@ -9,6 +9,7 @@ use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, Ty}; use rustc_span::symbol::sym; +use rustc_span::Span; pub(super) fn derefs_to_slice<'tcx>( cx: &LateContext<'tcx>, @@ -96,15 +97,15 @@ pub(super) fn clone_or_copy_needed<'tcx>( cx: &LateContext<'tcx>, pat: &Pat<'tcx>, body: &'tcx Expr<'tcx>, -) -> (bool, Vec<&'tcx Expr<'tcx>>) { +) -> (bool, Vec<(Span, String)>) { let mut visitor = CloneOrCopyVisitor { cx, binding_hir_ids: pat_bindings(pat), clone_or_copy_needed: false, - addr_of_exprs: Vec::new(), + references_to_binding: Vec::new(), }; visitor.visit_expr(body); - (visitor.clone_or_copy_needed, visitor.addr_of_exprs) + (visitor.clone_or_copy_needed, visitor.references_to_binding) } /// Returns a vector of all `HirId`s bound by the pattern. @@ -127,7 +128,7 @@ struct CloneOrCopyVisitor<'cx, 'tcx> { cx: &'cx LateContext<'tcx>, binding_hir_ids: Vec, clone_or_copy_needed: bool, - addr_of_exprs: Vec<&'tcx Expr<'tcx>>, + references_to_binding: Vec<(Span, String)>, } impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> { @@ -142,8 +143,11 @@ impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> { if self.is_binding(expr) { if let Some(parent) = get_parent_expr(self.cx, expr) { match parent.kind { - ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => { - self.addr_of_exprs.push(parent); + ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, referent) => { + if !parent.span.from_expansion() { + self.references_to_binding + .push((parent.span.until(referent.span), String::new())); + } return; }, ExprKind::MethodCall(.., args, _) => { diff --git a/clippy_lints/src/min_ident_chars.rs b/clippy_lints/src/min_ident_chars.rs index c6b7f5b0ce27..e43b712021ae 100644 --- a/clippy_lints/src/min_ident_chars.rs +++ b/clippy_lints/src/min_ident_chars.rs @@ -12,14 +12,13 @@ use std::borrow::Cow; declare_clippy_lint! { /// ### What it does - /// Checks for idents which comprise of a single letter. + /// Checks for identifiers which consist of a single character (or fewer than the configured threshold). /// /// Note: This lint can be very noisy when enabled; it may be desirable to only enable it /// temporarily. /// - /// ### Why is this bad? - /// In many cases it's not, but at times it can severely hinder readability. Some codebases may - /// wish to disallow this to improve readability. + /// ### Why restrict this? + /// To improve readability by requiring that every variable has a name more specific than a single letter can be. /// /// ### Example /// ```rust,ignore diff --git a/clippy_lints/src/misc_early/mod.rs b/clippy_lints/src/misc_early/mod.rs index 2f5499d7656f..fedcfd11fdcc 100644 --- a/clippy_lints/src/misc_early/mod.rs +++ b/clippy_lints/src/misc_early/mod.rs @@ -23,7 +23,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for structure field patterns bound to wildcards. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Using `..` instead is shorter and leaves the focus on /// the fields that are actually bound. /// @@ -138,7 +138,7 @@ declare_clippy_lint! { /// To enforce unseparated literal suffix style, /// see the `separated_literal_suffix` lint. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Suffix style should be consistent. /// /// ### Example @@ -166,7 +166,7 @@ declare_clippy_lint! { /// To enforce separated literal suffix style, /// see the `unseparated_literal_suffix` lint. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Suffix style should be consistent. /// /// ### Example diff --git a/clippy_lints/src/missing_assert_message.rs b/clippy_lints/src/missing_assert_message.rs index 04df7b7a7e5a..dd98352da860 100644 --- a/clippy_lints/src/missing_assert_message.rs +++ b/clippy_lints/src/missing_assert_message.rs @@ -10,7 +10,7 @@ declare_clippy_lint! { /// ### What it does /// Checks assertions without a custom panic message. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Without a good custom message, it'd be hard to understand what went wrong when the assertion fails. /// A good custom message should be more about why the failure of the assertion is problematic /// and not what is failed because the assertion already conveys that. diff --git a/clippy_lints/src/missing_asserts_for_indexing.rs b/clippy_lints/src/missing_asserts_for_indexing.rs index c29e46b941c6..752723a0c68e 100644 --- a/clippy_lints/src/missing_asserts_for_indexing.rs +++ b/clippy_lints/src/missing_asserts_for_indexing.rs @@ -21,7 +21,7 @@ declare_clippy_lint! { /// Checks for repeated slice indexing without asserting beforehand that the length /// is greater than the largest index used to index into the slice. /// - /// ### Why is this bad? + /// ### Why restrict this? /// In the general case where the compiler does not have a lot of information /// about the length of a slice, indexing it repeatedly will generate a bounds check /// for every single index. diff --git a/clippy_lints/src/missing_doc.rs b/clippy_lints/src/missing_doc.rs index 2fb784dae1cd..ca344dc5c810 100644 --- a/clippy_lints/src/missing_doc.rs +++ b/clippy_lints/src/missing_doc.rs @@ -20,9 +20,9 @@ use rustc_span::{sym, Span}; declare_clippy_lint! { /// ### What it does - /// Warns if there is missing doc for any private documentable item + /// Warns if there is missing documentation for any private documentable item. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Doc is good. *rustc* has a `MISSING_DOCS` /// allowed-by-default lint for /// public members, but has no way to enforce documentation of private items. diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index c6a76478806a..33a14d8b7fed 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -10,13 +10,16 @@ declare_clippy_lint! { /// It lints if an exported function, method, trait method with default impl, /// or trait method impl is not `#[inline]`. /// - /// ### Why is this bad? - /// In general, it is not. Functions can be inlined across - /// crates when that's profitable as long as any form of LTO is used. When LTO is disabled, - /// functions that are not `#[inline]` cannot be inlined across crates. Certain types of crates - /// might intend for most of the methods in their public API to be able to be inlined across - /// crates even when LTO is disabled. For these types of crates, enabling this lint might make - /// sense. It allows the crate to require all exported methods to be `#[inline]` by default, and + /// ### Why restrict this? + /// When a function is not marked `#[inline]`, it is not + /// [a “small” candidate for automatic inlining][small], and LTO is not in use, then it is not + /// possible for the function to be inlined into the code of any crate other than the one in + /// which it is defined. Depending on the role of the function and the relationship of the crates, + /// this could significantly reduce performance. + /// + /// Certain types of crates might intend for most of the methods in their public API to be able + /// to be inlined across crates even when LTO is disabled. + /// This lint allows those crates to require all exported methods to be `#[inline]` by default, and /// then opt out for specific methods where this might not make sense. /// /// ### Example @@ -51,6 +54,8 @@ declare_clippy_lint! { /// fn def_bar() {} // missing #[inline] /// } /// ``` + /// + /// [small]: https://github.com/rust-lang/rust/pull/116505 #[clippy::version = "pre 1.29.0"] pub MISSING_INLINE_IN_PUBLIC_ITEMS, restriction, diff --git a/clippy_lints/src/missing_trait_methods.rs b/clippy_lints/src/missing_trait_methods.rs index 6f844bc646a2..85029a5e6a0d 100644 --- a/clippy_lints/src/missing_trait_methods.rs +++ b/clippy_lints/src/missing_trait_methods.rs @@ -10,16 +10,16 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does /// Checks if a provided method is used implicitly by a trait - /// implementation. A usage example would be a wrapper where every method - /// should perform some operation before delegating to the inner type's /// implementation. /// + /// ### Why restrict this? + /// To ensure that a certain implementation implements every method; for example, + /// a wrapper type where every method should delegate to the corresponding method of + /// the inner type's implementation. + /// /// This lint should typically be enabled on a specific trait `impl` item /// rather than globally. /// - /// ### Why is this bad? - /// Indicates that a method is missing. - /// /// ### Example /// ```no_run /// trait Trait { diff --git a/clippy_lints/src/mixed_read_write_in_expression.rs b/clippy_lints/src/mixed_read_write_in_expression.rs index 181351910db6..9c5a8a0cfcdf 100644 --- a/clippy_lints/src/mixed_read_write_in_expression.rs +++ b/clippy_lints/src/mixed_read_write_in_expression.rs @@ -12,9 +12,10 @@ declare_clippy_lint! { /// whether the read occurs before or after the write depends on the evaluation /// order of sub-expressions. /// - /// ### Why is this bad? - /// It is often confusing to read. As described [here](https://doc.rust-lang.org/reference/expressions.html?highlight=subexpression#evaluation-order-of-operands), - /// the operands of these expressions are evaluated before applying the effects of the expression. + /// ### Why restrict this? + /// While [the evaluation order of sub-expressions] is fully specified in Rust, + /// it still may be confusing to read an expression where the evaluation order + /// affects its behavior. /// /// ### Known problems /// Code which intentionally depends on the evaluation @@ -40,6 +41,8 @@ declare_clippy_lint! { /// }; /// let a = tmp + x; /// ``` + /// + /// [order]: (https://doc.rust-lang.org/reference/expressions.html?highlight=subexpression#evaluation-order-of-operands) #[clippy::version = "pre 1.29.0"] pub MIXED_READ_WRITE_IN_EXPRESSION, restriction, diff --git a/clippy_lints/src/module_style.rs b/clippy_lints/src/module_style.rs index 6c031c081750..305499f9da43 100644 --- a/clippy_lints/src/module_style.rs +++ b/clippy_lints/src/module_style.rs @@ -10,9 +10,9 @@ use std::path::{Component, Path}; declare_clippy_lint! { /// ### What it does - /// Checks that module layout uses only self named module files, bans `mod.rs` files. + /// Checks that module layout uses only self named module files; bans `mod.rs` files. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Having multiple module layout styles in a project can be confusing. /// /// ### Example @@ -41,7 +41,7 @@ declare_clippy_lint! { /// ### What it does /// Checks that module layout uses only `mod.rs` files. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Having multiple module layout styles in a project can be confusing. /// /// ### Example diff --git a/clippy_lints/src/multiple_unsafe_ops_per_block.rs b/clippy_lints/src/multiple_unsafe_ops_per_block.rs index 5306205aed7e..5b4ef852f0d9 100644 --- a/clippy_lints/src/multiple_unsafe_ops_per_block.rs +++ b/clippy_lints/src/multiple_unsafe_ops_per_block.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::visitors::{for_each_expr_with_closures, Descend, Visitable}; use core::ops::ControlFlow::Continue; use hir::def::{DefKind, Res}; -use hir::{BlockCheckMode, ExprKind, Safety, QPath, UnOp}; +use hir::{BlockCheckMode, ExprKind, QPath, Safety, UnOp}; use rustc_ast::Mutability; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; @@ -15,7 +15,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `unsafe` blocks that contain more than one unsafe operation. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Combined with `undocumented_unsafe_blocks`, /// this lint ensures that each unsafe operation must be independently justified. /// Combined with `unused_unsafe`, this lint also ensures diff --git a/clippy_lints/src/mutex_atomic.rs b/clippy_lints/src/mutex_atomic.rs index 7ecc86176942..853e476a006c 100644 --- a/clippy_lints/src/mutex_atomic.rs +++ b/clippy_lints/src/mutex_atomic.rs @@ -14,7 +14,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `Mutex` where an atomic will do. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Using a mutex just to make access to a plain bool or /// reference sequential is shooting flies with cannons. /// `std::sync::atomic::AtomicBool` and `std::sync::atomic::AtomicPtr` are leaner and diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index f9ee4a3dc93a..e1866eaa18a7 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -6,8 +6,8 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use clippy_utils::{ - higher, is_else_clause, is_expn_of, is_parent_stmt, peel_blocks, peel_blocks_with_stmt, span_extract_comment, - SpanlessEq, + higher, is_block_like, is_else_clause, is_expn_of, is_parent_stmt, peel_blocks, peel_blocks_with_stmt, + span_extract_comment, SpanlessEq, }; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; @@ -121,14 +121,7 @@ fn condition_needs_parentheses(e: &Expr<'_>) -> bool { | ExprKind::Type(i, _) | ExprKind::Index(i, _, _) = inner.kind { - if matches!( - i.kind, - ExprKind::Block(..) - | ExprKind::ConstBlock(..) - | ExprKind::If(..) - | ExprKind::Loop(..) - | ExprKind::Match(..) - ) { + if is_block_like(i) { return true; } inner = i; diff --git a/clippy_lints/src/non_expressive_names.rs b/clippy_lints/src/non_expressive_names.rs index 7b26235291a5..eacfe9ff328d 100644 --- a/clippy_lints/src/non_expressive_names.rs +++ b/clippy_lints/src/non_expressive_names.rs @@ -98,6 +98,10 @@ struct SimilarNamesLocalVisitor<'a, 'tcx> { impl<'a, 'tcx> SimilarNamesLocalVisitor<'a, 'tcx> { fn check_single_char_names(&self) { + if self.single_char_names.last().map(Vec::len) == Some(0) { + return; + } + let num_single_char_names = self.single_char_names.iter().flatten().count(); let threshold = self.lint.single_char_binding_names_threshold; if num_single_char_names as u64 > threshold { diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index e002429e3a47..0948973df5a3 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -70,7 +70,7 @@ declare_clippy_lint! { /// Known safe built-in types like `Wrapping` or `Saturating`, floats, operations in constant /// environments, allowed types and non-constant operations that won't overflow are ignored. /// - /// ### Why is this bad? + /// ### Why restrict this? /// For integers, overflow will trigger a panic in debug builds or wrap the result in /// release mode; division by zero will cause a panic in either mode. As a result, it is /// desirable to explicitly call checked, wrapping or saturating arithmetic methods. @@ -100,7 +100,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for float arithmetic. /// - /// ### Why is this bad? + /// ### Why restrict this? /// For some embedded systems or kernel development, it /// can be useful to rule out floating-point numbers. /// @@ -502,7 +502,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for division of integers /// - /// ### Why is this bad? + /// ### Why restrict this? /// When outside of some very specific algorithms, /// integer division is very often a mistake because it discards the /// remainder. @@ -596,7 +596,7 @@ declare_clippy_lint! { /// value and constant, except in functions called `*eq*` (which probably /// implement equality for a type involving floats). /// - /// ### Why is this bad? + /// ### Why restrict this? /// Floating point calculations are usually imprecise, so /// asking if two values are *exactly* equal is asking for trouble. For a good /// guide on what to do, see [the floating point @@ -653,8 +653,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for modulo arithmetic. /// - /// ### Why is this bad? - /// The results of modulo (%) operation might differ + /// ### Why restrict this? + /// The results of modulo (`%`) operation might differ /// depending on the language, when negative numbers are involved. /// If you interop with different languages it might be beneficial /// to double check all places that use modulo arithmetic. diff --git a/clippy_lints/src/panic_in_result_fn.rs b/clippy_lints/src/panic_in_result_fn.rs index f821a4efee7b..806638c0505b 100644 --- a/clippy_lints/src/panic_in_result_fn.rs +++ b/clippy_lints/src/panic_in_result_fn.rs @@ -13,9 +13,9 @@ use rustc_span::{sym, Span}; declare_clippy_lint! { /// ### What it does - /// Checks for usage of `panic!` or assertions in a function of type result. + /// Checks for usage of `panic!` or assertions in a function whose return type is `Result`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// For some codebases, it is desirable for functions of type result to return an error instead of crashing. Hence panicking macros should be avoided. /// /// ### Known problems diff --git a/clippy_lints/src/panic_unimplemented.rs b/clippy_lints/src/panic_unimplemented.rs index 75066c1f0d2e..80ef761906e0 100644 --- a/clippy_lints/src/panic_unimplemented.rs +++ b/clippy_lints/src/panic_unimplemented.rs @@ -14,8 +14,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `panic!`. /// - /// ### Why is this bad? - /// `panic!` will stop the execution of the executable. + /// ### Why restrict this? + /// This macro, or panics in general, may be unwanted in production code. /// /// ### Example /// ```no_run @@ -31,8 +31,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `unimplemented!`. /// - /// ### Why is this bad? - /// This macro should not be present in production code. + /// ### Why restrict this? + /// This macro, or panics in general, may be unwanted in production code. /// /// ### Example /// ```no_run @@ -48,9 +48,9 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `todo!`. /// - /// ### Why is this bad? - /// The `todo!` macro is often used for unfinished code, and it causes - /// code to panic. It should not be present in production code. + /// ### Why restrict this? + /// The `todo!` macro indicates the presence of unfinished code, + /// so it should not be present in production code. /// /// ### Example /// ```no_run @@ -70,8 +70,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `unreachable!`. /// - /// ### Why is this bad? - /// This macro can cause code to panic. + /// ### Why restrict this? + /// This macro, or panics in general, may be unwanted in production code. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/partial_pub_fields.rs b/clippy_lints/src/partial_pub_fields.rs index ffa403e27ca3..2d20cbea698f 100644 --- a/clippy_lints/src/partial_pub_fields.rs +++ b/clippy_lints/src/partial_pub_fields.rs @@ -5,15 +5,16 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does - /// Checks whether partial fields of a struct are public. + /// Checks whether some but not all fields of a `struct` are public. /// /// Either make all fields of a type public, or make none of them public /// - /// ### Why is this bad? + /// ### Why restrict this? /// Most types should either be: /// * Abstract data types: complex objects with opaque implementation which guard - /// interior invariants and expose intentionally limited API to the outside world. - /// * Data: relatively simple objects which group a bunch of related attributes together. + /// interior invariants and expose intentionally limited API to the outside world. + /// * Data: relatively simple objects which group a bunch of related attributes together, + /// but have no invariants. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/pattern_type_mismatch.rs b/clippy_lints/src/pattern_type_mismatch.rs index 44db061b8bee..9661a57b8b95 100644 --- a/clippy_lints/src/pattern_type_mismatch.rs +++ b/clippy_lints/src/pattern_type_mismatch.rs @@ -30,9 +30,8 @@ declare_clippy_lint! { /// this lint can still be used to highlight areas of interest and ensure a good understanding /// of ownership semantics. /// - /// ### Why is this bad? - /// It isn't bad in general. But in some contexts it can be desirable - /// because it increases ownership hints in the code, and will guard against some changes + /// ### Why restrict this? + /// It increases ownership hints in the code, and will guard against some changes /// in ownership. /// /// ### Example diff --git a/clippy_lints/src/pub_use.rs b/clippy_lints/src/pub_use.rs index c0e999e76ef2..ab8f8a1689dc 100644 --- a/clippy_lints/src/pub_use.rs +++ b/clippy_lints/src/pub_use.rs @@ -5,13 +5,11 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does - /// /// Restricts the usage of `pub use ...` /// - /// ### Why is this bad? - /// - /// `pub use` is usually fine, but a project may wish to limit `pub use` instances to prevent - /// unintentional exports or to encourage placing exported items directly in public modules + /// ### Why restrict this? + /// A project may wish to limit `pub use` instances to prevent + /// unintentional exports, or to encourage placing exported items directly in public modules. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/question_mark_used.rs b/clippy_lints/src/question_mark_used.rs index ddfc53083c46..f5e6cb804da1 100644 --- a/clippy_lints/src/question_mark_used.rs +++ b/clippy_lints/src/question_mark_used.rs @@ -9,7 +9,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for expressions that use the question mark operator and rejects them. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Sometimes code wants to avoid the question mark operator because for instance a local /// block requires a macro to re-throw errors to attach additional information to the /// error. diff --git a/clippy_lints/src/raw_strings.rs b/clippy_lints/src/raw_strings.rs index 7e71f48c6d9a..3a0042454592 100644 --- a/clippy_lints/src/raw_strings.rs +++ b/clippy_lints/src/raw_strings.rs @@ -15,8 +15,10 @@ declare_clippy_lint! { /// ### What it does /// Checks for raw string literals where a string literal can be used instead. /// - /// ### Why is this bad? - /// It's just unnecessary, but there are many cases where using a raw string literal is more + /// ### Why restrict this? + /// For consistent style by using simpler string literals whenever possible. + /// + /// However, there are many cases where using a raw string literal is more /// idiomatic than a string literal, so it's opt-in. /// /// ### Example diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index c99b657c23a2..7f87d18e5023 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -46,7 +46,7 @@ declare_clippy_lint! { /// Checks for slicing expressions which are equivalent to dereferencing the /// value. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Some people may prefer to dereference rather than slice. /// /// ### Example diff --git a/clippy_lints/src/redundant_type_annotations.rs b/clippy_lints/src/redundant_type_annotations.rs index 11b95ee3a54c..81556f396141 100644 --- a/clippy_lints/src/redundant_type_annotations.rs +++ b/clippy_lints/src/redundant_type_annotations.rs @@ -11,7 +11,7 @@ declare_clippy_lint! { /// ### What it does /// Warns about needless / redundant type annotations. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Code without type annotations is shorter and in most cases /// more idiomatic and easier to modify. /// diff --git a/clippy_lints/src/ref_patterns.rs b/clippy_lints/src/ref_patterns.rs index 607a0740b843..467038523b49 100644 --- a/clippy_lints/src/ref_patterns.rs +++ b/clippy_lints/src/ref_patterns.rs @@ -6,9 +6,11 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does /// Checks for usages of the `ref` keyword. - /// ### Why is this bad? + /// + /// ### Why restrict this? /// The `ref` keyword can be confusing for people unfamiliar with it, and often /// it is more concise to use `&` instead. + /// /// ### Example /// ```no_run /// let opt = Some(5); diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index e8f9d4381047..48d6fb3c0378 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -3,8 +3,8 @@ use clippy_utils::source::{snippet_opt, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::visitors::{for_each_expr_with_closures, Descend}; use clippy_utils::{ - fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res, path_to_local_id, span_contains_cfg, - span_find_starting_semi, + binary_expr_needs_parentheses, fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res, + path_to_local_id, span_contains_cfg, span_find_starting_semi, }; use core::ops::ControlFlow; use rustc_errors::Applicability; @@ -129,7 +129,7 @@ enum RetReplacement<'tcx> { Empty, Block, Unit, - IfSequence(Cow<'tcx, str>, Applicability), + NeedsPar(Cow<'tcx, str>, Applicability), Expr(Cow<'tcx, str>, Applicability), } @@ -139,13 +139,13 @@ impl<'tcx> RetReplacement<'tcx> { Self::Empty | Self::Expr(..) => "remove `return`", Self::Block => "replace `return` with an empty block", Self::Unit => "replace `return` with a unit value", - Self::IfSequence(..) => "remove `return` and wrap the sequence with parentheses", + Self::NeedsPar(..) => "remove `return` and wrap the sequence with parentheses", } } fn applicability(&self) -> Applicability { match self { - Self::Expr(_, ap) | Self::IfSequence(_, ap) => *ap, + Self::Expr(_, ap) | Self::NeedsPar(_, ap) => *ap, _ => Applicability::MachineApplicable, } } @@ -157,7 +157,7 @@ impl<'tcx> Display for RetReplacement<'tcx> { Self::Empty => write!(f, ""), Self::Block => write!(f, "{{}}"), Self::Unit => write!(f, "()"), - Self::IfSequence(inner, _) => write!(f, "({inner})"), + Self::NeedsPar(inner, _) => write!(f, "({inner})"), Self::Expr(inner, _) => write!(f, "{inner}"), } } @@ -244,7 +244,11 @@ impl<'tcx> LateLintPass<'tcx> for Return { err.span_label(local.span, "unnecessary `let` binding"); if let Some(mut snippet) = snippet_opt(cx, initexpr.span) { - if !cx.typeck_results().expr_adjustments(retexpr).is_empty() { + if binary_expr_needs_parentheses(initexpr) { + if !has_enclosing_paren(&snippet) { + snippet = format!("({snippet})"); + } + } else if !cx.typeck_results().expr_adjustments(retexpr).is_empty() { if !has_enclosing_paren(&snippet) { snippet = format!("({snippet})"); } @@ -349,8 +353,8 @@ fn check_final_expr<'tcx>( let mut applicability = Applicability::MachineApplicable; let (snippet, _) = snippet_with_context(cx, inner_expr.span, ret_span.ctxt(), "..", &mut applicability); - if expr_contains_conjunctive_ifs(inner_expr) { - RetReplacement::IfSequence(snippet, applicability) + if binary_expr_needs_parentheses(inner_expr) { + RetReplacement::NeedsPar(snippet, applicability) } else { RetReplacement::Expr(snippet, applicability) } @@ -404,18 +408,6 @@ fn check_final_expr<'tcx>( } } -fn expr_contains_conjunctive_ifs<'tcx>(expr: &'tcx Expr<'tcx>) -> bool { - fn contains_if(expr: &Expr<'_>, on_if: bool) -> bool { - match expr.kind { - ExprKind::If(..) => on_if, - ExprKind::Binary(_, left, right) => contains_if(left, true) || contains_if(right, true), - _ => false, - } - } - - contains_if(expr, false) -} - fn emit_return_lint( cx: &LateContext<'_>, ret_span: Span, diff --git a/clippy_lints/src/same_name_method.rs b/clippy_lints/src/same_name_method.rs index 7e27f70bcf94..8fdd19c549f5 100644 --- a/clippy_lints/src/same_name_method.rs +++ b/clippy_lints/src/same_name_method.rs @@ -14,7 +14,7 @@ declare_clippy_lint! { /// It lints if a struct has two methods with the same name: /// one from a trait, another not from trait. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Confusing. /// /// ### Example diff --git a/clippy_lints/src/semicolon_block.rs b/clippy_lints/src/semicolon_block.rs index 0b3adfb7a4b8..0e77acdfd77f 100644 --- a/clippy_lints/src/semicolon_block.rs +++ b/clippy_lints/src/semicolon_block.rs @@ -11,8 +11,7 @@ declare_clippy_lint! { /// Suggests moving the semicolon after a block to the inside of the block, after its last /// expression. /// - /// ### Why is this bad? - /// + /// ### Why restrict this? /// For consistency it's best to have the semicolon inside/outside the block. Either way is fine /// and this lint suggests inside the block. /// Take a look at `semicolon_outside_block` for the other alternative. @@ -40,8 +39,7 @@ declare_clippy_lint! { /// /// Suggests moving the semicolon from a block's final expression outside of the block. /// - /// ### Why is this bad? - /// + /// ### Why restrict this? /// For consistency it's best to have the semicolon inside/outside the block. Either way is fine /// and this lint suggests outside the block. /// Take a look at `semicolon_inside_block` for the other alternative. diff --git a/clippy_lints/src/shadow.rs b/clippy_lints/src/shadow.rs index 9db08acb03b2..80f5fd0b4944 100644 --- a/clippy_lints/src/shadow.rs +++ b/clippy_lints/src/shadow.rs @@ -15,10 +15,10 @@ declare_clippy_lint! { /// Checks for bindings that shadow other bindings already in /// scope, while just changing reference level or mutability. /// - /// ### Why is this bad? - /// Not much, in fact it's a very common pattern in Rust - /// code. Still, some may opt to avoid it in their code base, they can set this - /// lint to `Warn`. + /// ### Why restrict this? + /// To require that what are formally distinct variables be given distinct names. + /// + /// See also `shadow_reuse` and `shadow_unrelated` for other restrictions on shadowing. /// /// ### Example /// ```no_run @@ -42,12 +42,13 @@ declare_clippy_lint! { /// Checks for bindings that shadow other bindings already in /// scope, while reusing the original value. /// - /// ### Why is this bad? - /// Not too much, in fact it's a common pattern in Rust - /// code. Still, some argue that name shadowing like this hurts readability, + /// ### Why restrict this? + /// Some argue that name shadowing like this hurts readability, /// because a value may be bound to different things depending on position in /// the code. /// + /// See also `shadow_same` and `shadow_unrelated` for other restrictions on shadowing. + /// /// ### Example /// ```no_run /// let x = 2; @@ -70,11 +71,14 @@ declare_clippy_lint! { /// scope, either without an initialization or with one that does not even use /// the original value. /// - /// ### Why is this bad? - /// Name shadowing can hurt readability, especially in + /// ### Why restrict this? + /// Shadowing a binding with a closely related one is part of idiomatic Rust, + /// but shadowing a binding by accident with an unrelated one may indicate a mistake. + /// + /// Additionally, name shadowing in general can hurt readability, especially in /// large code bases, because it is easy to lose track of the active binding at - /// any place in the code. This can be alleviated by either giving more specific - /// names to bindings or introducing more scopes to contain the bindings. + /// any place in the code. If linting against all shadowing is desired, you may wish + /// to use the `shadow_same` and `shadow_reuse` lints as well. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/single_call_fn.rs b/clippy_lints/src/single_call_fn.rs index 2ce7e714c642..c71bc3f7fcde 100644 --- a/clippy_lints/src/single_call_fn.rs +++ b/clippy_lints/src/single_call_fn.rs @@ -13,13 +13,20 @@ declare_clippy_lint! { /// ### What it does /// Checks for functions that are only used once. Does not lint tests. /// - /// ### Why is this bad? - /// It's usually not, splitting a function into multiple parts often improves readability and in - /// the case of generics, can prevent the compiler from duplicating the function dozens of - /// time; instead, only duplicating a thunk. But this can prevent segmentation across a - /// codebase, where many small functions are used only once. + /// ### Why restrict this? + /// If a function is only used once (perhaps because it used to be used more widely), + /// then the code could be simplified by moving that function's code into its caller. /// - /// Note: If this lint is used, prepare to allow this a lot. + /// However, there are reasons not to do this everywhere: + /// + /// * Splitting a large function into multiple parts often improves readability + /// by giving names to its parts. + /// * A function’s signature might serve a necessary purpose, such as constraining + /// the type of a closure passed to it. + /// * Generic functions might call non-generic functions to reduce duplication + /// in the produced machine code. + /// + /// If this lint is used, prepare to `#[allow]` it a lot. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/single_char_lifetime_names.rs b/clippy_lints/src/single_char_lifetime_names.rs index 42f1564db353..72feb977c310 100644 --- a/clippy_lints/src/single_char_lifetime_names.rs +++ b/clippy_lints/src/single_char_lifetime_names.rs @@ -9,11 +9,10 @@ declare_clippy_lint! { /// Checks for lifetimes with names which are one character /// long. /// - /// ### Why is this bad? + /// ### Why restrict this? /// A single character is likely not enough to express the /// purpose of a lifetime. Using a longer name can make code - /// easier to understand, especially for those who are new to - /// Rust. + /// easier to understand. /// /// ### Known problems /// Rust programmers and learning resources tend to use single diff --git a/clippy_lints/src/std_instead_of_core.rs b/clippy_lints/src/std_instead_of_core.rs index 926c56332cc1..12b70075a3d5 100644 --- a/clippy_lints/src/std_instead_of_core.rs +++ b/clippy_lints/src/std_instead_of_core.rs @@ -12,11 +12,9 @@ use rustc_span::{sym, Span}; declare_clippy_lint! { /// ### What it does - /// /// Finds items imported through `std` when available through `core`. /// - /// ### Why is this bad? - /// + /// ### Why restrict this? /// Crates which have `no_std` compatibility may wish to ensure types are imported from core to ensure /// disabling `std` does not cause the crate to fail to compile. This lint is also useful for crates /// migrating to become `no_std` compatible. @@ -37,11 +35,9 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// /// Finds items imported through `std` when available through `alloc`. /// - /// ### Why is this bad? - /// + /// ### Why restrict this? /// Crates which have `no_std` compatibility and require alloc may wish to ensure types are imported from /// alloc to ensure disabling `std` does not cause the crate to fail to compile. This lint is also useful /// for crates migrating to become `no_std` compatible. @@ -63,11 +59,9 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// /// Finds items imported through `alloc` when available through `core`. /// - /// ### Why is this bad? - /// + /// ### Why restrict this? /// Crates which have `no_std` compatibility and may optionally require alloc may wish to ensure types are /// imported from core to ensure disabling `alloc` does not cause the crate to fail to compile. This lint /// is also useful for crates migrating to become `no_std` compatible. diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 292124196ff6..f8d36d6b92f6 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -45,10 +45,10 @@ declare_clippy_lint! { /// `String`, but only if [`string_add_assign`](#string_add_assign) does *not* /// match. /// - /// ### Why is this bad? - /// It's not bad in and of itself. However, this particular + /// ### Why restrict this? + /// This particular /// `Add` implementation is asymmetric (the other operand need not be `String`, - /// but `x` does), while addition as mathematically defined is symmetric, also + /// but `x` does), while addition as mathematically defined is symmetric, and /// the `String::push_str(_)` function is a perfectly good replacement. /// Therefore, some dislike it and wish not to have it in their code. /// @@ -123,7 +123,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for slice operations on strings /// - /// ### Why is this bad? + /// ### Why restrict this? /// UTF-8 characters span multiple bytes, and it is easy to inadvertently confuse character /// counts and string indices. This may lead to panics, and should warrant some test cases /// containing wide UTF-8 characters. This lint is most useful in code that should avoid @@ -364,10 +364,10 @@ declare_clippy_lint! { /// ### What it does /// This lint checks for `.to_string()` method calls on values of type `&str`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// The `to_string` method is also used on other types to convert them to a string. - /// When called on a `&str` it turns the `&str` into the owned variant `String`, which can be better - /// expressed with `.to_owned()`. + /// When called on a `&str` it turns the `&str` into the owned variant `String`, which can be + /// more specifically expressed with `.to_owned()`. /// /// ### Example /// ```no_run @@ -415,9 +415,10 @@ declare_clippy_lint! { /// ### What it does /// This lint checks for `.to_string()` method calls on values of type `String`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// The `to_string` method is also used on other types to convert them to a string. - /// When called on a `String` it only clones the `String`, which can be better expressed with `.clone()`. + /// When called on a `String` it only clones the `String`, which can be more specifically + /// expressed with `.clone()`. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/suspicious_xor_used_as_pow.rs b/clippy_lints/src/suspicious_xor_used_as_pow.rs index 1cc27670fa8b..d150a5f858aa 100644 --- a/clippy_lints/src/suspicious_xor_used_as_pow.rs +++ b/clippy_lints/src/suspicious_xor_used_as_pow.rs @@ -11,8 +11,10 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does /// Warns for a Bitwise XOR (`^`) operator being probably confused as a powering. It will not trigger if any of the numbers are not in decimal. - /// ### Why is this bad? + /// + /// ### Why restrict this? /// It's most probably a typo and may lead to unexpected behaviours. + /// /// ### Example /// ```no_run /// let x = 3_i32 ^ 4_i32; diff --git a/clippy_lints/src/tests_outside_test_module.rs b/clippy_lints/src/tests_outside_test_module.rs index da5575826475..58e42892c41d 100644 --- a/clippy_lints/src/tests_outside_test_module.rs +++ b/clippy_lints/src/tests_outside_test_module.rs @@ -11,9 +11,11 @@ declare_clippy_lint! { /// ### What it does /// Triggers when a testing function (marked with the `#[test]` attribute) isn't inside a testing module /// (marked with `#[cfg(test)]`). - /// ### Why is this bad? + /// + /// ### Why restrict this? /// The idiomatic (and more performant) way of writing tests is inside a testing module (flagged with `#[cfg(test)]`), /// having test functions outside of this module is confusing and may lead to them being "hidden". + /// /// ### Example /// ```no_run /// #[test] diff --git a/clippy_lints/src/types/mod.rs b/clippy_lints/src/types/mod.rs index 5e45ab211efd..62ef65ca122f 100644 --- a/clippy_lints/src/types/mod.rs +++ b/clippy_lints/src/types/mod.rs @@ -217,7 +217,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `Rc` and `Arc` when `T` is a mutable buffer type such as `String` or `Vec`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Expressions such as `Rc` usually have no advantage over `Rc`, since /// it is larger and involves an extra level of indirection, and doesn't implement `Borrow`. /// @@ -274,7 +274,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `Rc>`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// `Rc` is used in single thread and `Mutex` is used in multi thread. /// Consider using `Rc>` in single thread or `Arc>` in multi thread. /// diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index 4120bb1331bd..6cf9229fdd08 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -38,10 +38,9 @@ declare_clippy_lint! { /// ); /// ``` /// - /// ### Why is this bad? - /// Undocumented unsafe blocks and impls can make it difficult to - /// read and maintain code, as well as uncover unsoundness - /// and bugs. + /// ### Why restrict this? + /// Undocumented unsafe blocks and impls can make it difficult to read and maintain code. + /// Writing out the safety justification may help in discovering unsoundness or bugs. /// /// ### Example /// ```no_run @@ -67,7 +66,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `// SAFETY: ` comments on safe code. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Safe code has no safety requirements, so there is no need to /// describe safety invariants. /// diff --git a/clippy_lints/src/unicode.rs b/clippy_lints/src/unicode.rs index 3d319b9fe767..d42697b31d1f 100644 --- a/clippy_lints/src/unicode.rs +++ b/clippy_lints/src/unicode.rs @@ -31,7 +31,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for non-ASCII characters in string and char literals. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Yeah, we know, the 90's called and wanted their charset /// back. Even so, there still are editors and other programs out there that /// don't work well with Unicode. So if the code is meant to be used diff --git a/clippy_lints/src/unnecessary_self_imports.rs b/clippy_lints/src/unnecessary_self_imports.rs index 528a1dfcfc10..93dff1b85796 100644 --- a/clippy_lints/src/unnecessary_self_imports.rs +++ b/clippy_lints/src/unnecessary_self_imports.rs @@ -9,7 +9,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for imports ending in `::{self}`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// In most cases, this can be written much more cleanly by omitting `::{self}`. /// /// ### Known problems diff --git a/clippy_lints/src/unwrap_in_result.rs b/clippy_lints/src/unwrap_in_result.rs index aca500590cef..197ab0f173bd 100644 --- a/clippy_lints/src/unwrap_in_result.rs +++ b/clippy_lints/src/unwrap_in_result.rs @@ -13,8 +13,10 @@ declare_clippy_lint! { /// ### What it does /// Checks for functions of type `Result` that contain `expect()` or `unwrap()` /// - /// ### Why is this bad? - /// These functions promote recoverable errors to non-recoverable errors which may be undesirable in code bases which wish to avoid panics. + /// ### Why restrict this? + /// These functions promote recoverable errors to non-recoverable errors, + /// which may be undesirable in code bases which wish to avoid panics, + /// or be a bug in the specific function. /// /// ### Known problems /// This can cause false positives in functions that handle both recoverable and non recoverable errors. diff --git a/clippy_lints/src/visibility.rs b/clippy_lints/src/visibility.rs index 9818b98dd5b4..11dcceca7abb 100644 --- a/clippy_lints/src/visibility.rs +++ b/clippy_lints/src/visibility.rs @@ -32,7 +32,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `pub()` with `in`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Consistency. Use it or don't, just be consistent about it. /// /// Also see the `pub_without_shorthand` lint for an alternative. @@ -57,7 +57,7 @@ declare_clippy_lint! { /// Note: As you cannot write a module's path in `pub()`, this will only trigger on /// `pub(super)` and the like. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Consistency. Use it or don't, just be consistent about it. /// /// Also see the `pub_with_shorthand` lint for an alternative. diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index ff6ee0d10ad5..652ce88bd951 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -66,7 +66,7 @@ declare_clippy_lint! { /// Checks for printing on *stdout*. The purpose of this lint /// is to catch debugging remnants. /// - /// ### Why is this bad? + /// ### Why restrict this? /// People often print on *stdout* while debugging an /// application and might forget to remove those prints afterward. /// @@ -88,7 +88,7 @@ declare_clippy_lint! { /// Checks for printing on *stderr*. The purpose of this lint /// is to catch debugging remnants. /// - /// ### Why is this bad? + /// ### Why restrict this? /// People often print on *stderr* while debugging an /// application and might forget to remove those prints afterward. /// @@ -110,15 +110,18 @@ declare_clippy_lint! { /// Checks for usage of `Debug` formatting. The purpose of this /// lint is to catch debugging remnants. /// - /// ### Why is this bad? - /// The purpose of the `Debug` trait is to facilitate - /// debugging Rust code. It should not be used in user-facing output. + /// ### Why restrict this? + /// The purpose of the `Debug` trait is to facilitate debugging Rust code, + /// and [no guarantees are made about its output][stability]. + /// It should not be used in user-facing output. /// /// ### Example /// ```no_run /// # let foo = "bar"; /// println!("{:?}", foo); /// ``` + /// + /// [stability]: https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html#stability #[clippy::version = "pre 1.29.0"] pub USE_DEBUG, restriction, diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index f5d3967d130d..cd88ccd87cf0 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -412,8 +412,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { /// Simple constant folding: Insert an expression, get a constant or none. pub fn expr(&mut self, e: &Expr<'_>) -> Option> { match e.kind { - ExprKind::ConstBlock(e) | - ExprKind::DropTemps(e) => self.expr(e), + ExprKind::ConstBlock(e) | ExprKind::DropTemps(e) => self.expr(e), ExprKind::Path(ref qpath) => { self.fetch_path_and_apply(qpath, e.hir_id, self.typeck_results.expr_ty(e), |this, result| { let result = mir_to_const(this.lcx, result)?; @@ -491,8 +490,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { /// leaves the local crate. pub fn expr_is_empty(&mut self, e: &Expr<'_>) -> Option { match e.kind { - ExprKind::ConstBlock(e) | - ExprKind::DropTemps(e) => self.expr_is_empty(e), + ExprKind::ConstBlock(e) | ExprKind::DropTemps(e) => self.expr_is_empty(e), ExprKind::Path(ref qpath) => { if !self .typeck_results diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index cc5ccd4053a2..817d4095eb53 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -769,7 +769,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { // closures inherit TypeckResults self.hash_expr(self.cx.tcx.hir().body(body).value); }, - ExprKind::ConstBlock(ref l_id) => { + ExprKind::ConstBlock(l_id) => { self.hash_expr(l_id); }, ExprKind::DropTemps(e) | ExprKind::Yield(e, _) => { diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 94d4656377f9..b10830b24e1f 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -3370,3 +3370,25 @@ pub fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool { Node::Stmt(..) | Node::Block(Block { stmts: &[], .. }) ) } + +/// Returns true if the given `expr` is a block or resembled as a block, +/// such as `if`, `loop`, `match` expressions etc. +pub fn is_block_like(expr: &Expr<'_>) -> bool { + matches!( + expr.kind, + ExprKind::Block(..) | ExprKind::ConstBlock(..) | ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..) + ) +} + +/// Returns true if the given `expr` is binary expression that needs to be wrapped in parentheses. +pub fn binary_expr_needs_parentheses(expr: &Expr<'_>) -> bool { + fn contains_block(expr: &Expr<'_>, is_operand: bool) -> bool { + match expr.kind { + ExprKind::Binary(_, lhs, _) => contains_block(lhs, true), + _ if is_block_like(expr) => is_operand, + _ => false, + } + } + + contains_block(expr, false) +} diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 2dacc34867f0..3414b5ef680c 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -9,7 +9,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; use rustc_hir::def_id::DefId; -use rustc_hir::{Expr, FnDecl, Safety, LangItem, TyKind}; +use rustc_hir::{Expr, FnDecl, LangItem, Safety, TyKind}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; use rustc_middle::mir::interpret::Scalar; @@ -18,8 +18,8 @@ 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, - GenericParamDefKind, IntTy, ParamEnv, Region, RegionKind, Upcast, TraitRef, Ty, TyCtxt, - TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, VariantDef, VariantDiscr, + GenericParamDefKind, IntTy, ParamEnv, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, + TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr, }; use rustc_span::symbol::Ident; use rustc_span::{sym, Span, Symbol, DUMMY_SP}; diff --git a/rust-toolchain b/rust-toolchain index a0585ffdb45b..dd8b9ece773e 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2024-05-16" +channel = "nightly-2024-05-30" components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/tests/ui-internal/disallow_span_lint.rs b/tests/ui-internal/disallow_span_lint.rs index b91a83308b5f..ca71dddcc24f 100644 --- a/tests/ui-internal/disallow_span_lint.rs +++ b/tests/ui-internal/disallow_span_lint.rs @@ -11,11 +11,15 @@ use rustc_lint::{Lint, LintContext}; use rustc_middle::ty::TyCtxt; pub fn a(cx: impl LintContext, lint: &'static Lint, span: impl Into, msg: impl Into) { - cx.span_lint(lint, span, |lint| { lint.primary_message(msg); }); + cx.span_lint(lint, span, |lint| { + lint.primary_message(msg); + }); } pub fn b(tcx: TyCtxt<'_>, lint: &'static Lint, hir_id: HirId, span: impl Into, msg: impl Into) { - tcx.node_span_lint(lint, hir_id, span, |lint| { lint.primary_message(msg); }); + tcx.node_span_lint(lint, hir_id, span, |lint| { + lint.primary_message(msg); + }); } fn main() {} diff --git a/tests/ui-internal/disallow_span_lint.stderr b/tests/ui-internal/disallow_span_lint.stderr index 1cfbc8efc8ed..1be4b665bcba 100644 --- a/tests/ui-internal/disallow_span_lint.stderr +++ b/tests/ui-internal/disallow_span_lint.stderr @@ -1,18 +1,22 @@ error: use of a disallowed method `rustc_lint::context::LintContext::span_lint` --> tests/ui-internal/disallow_span_lint.rs:14:5 | -LL | cx.span_lint(lint, span, |lint| { lint.primary_message(msg); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | / cx.span_lint(lint, span, |lint| { +LL | | lint.primary_message(msg); +LL | | }); + | |______^ | = note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead (from clippy.toml) = note: `-D clippy::disallowed-methods` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]` error: use of a disallowed method `rustc_middle::ty::context::TyCtxt::node_span_lint` - --> tests/ui-internal/disallow_span_lint.rs:18:5 + --> tests/ui-internal/disallow_span_lint.rs:20:5 | -LL | tcx.node_span_lint(lint, hir_id, span, |lint| { lint.primary_message(msg); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | / tcx.node_span_lint(lint, hir_id, span, |lint| { +LL | | lint.primary_message(msg); +LL | | }); + | |______^ | = note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead (from clippy.toml) diff --git a/tests/ui/derive_partial_eq_without_eq.fixed b/tests/ui/derive_partial_eq_without_eq.fixed index 6f42487bbf49..eb93eb8e8ed4 100644 --- a/tests/ui/derive_partial_eq_without_eq.fixed +++ b/tests/ui/derive_partial_eq_without_eq.fixed @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![allow(unused)] #![warn(clippy::derive_partial_eq_without_eq)] @@ -14,6 +15,22 @@ pub struct MissingEq { bar: String, } +// Check that we honor the `allow` attribute +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, PartialEq)] +pub struct AllowedMissingEq { + foo: u32, + bar: String, +} + +// Check that we honor the `expect` attribute +#[expect(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, PartialEq)] +pub struct ExpectedMissingEq { + foo: u32, + bar: String, +} + // Eq is derived #[derive(PartialEq, Eq)] pub struct NotMissingEq { diff --git a/tests/ui/derive_partial_eq_without_eq.rs b/tests/ui/derive_partial_eq_without_eq.rs index 24f687c6c9db..42dc435bdd52 100644 --- a/tests/ui/derive_partial_eq_without_eq.rs +++ b/tests/ui/derive_partial_eq_without_eq.rs @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![allow(unused)] #![warn(clippy::derive_partial_eq_without_eq)] @@ -14,6 +15,22 @@ pub struct MissingEq { bar: String, } +// Check that we honor the `allow` attribute +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, PartialEq)] +pub struct AllowedMissingEq { + foo: u32, + bar: String, +} + +// Check that we honor the `expect` attribute +#[expect(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, PartialEq)] +pub struct ExpectedMissingEq { + foo: u32, + bar: String, +} + // Eq is derived #[derive(PartialEq, Eq)] pub struct NotMissingEq { diff --git a/tests/ui/derive_partial_eq_without_eq.stderr b/tests/ui/derive_partial_eq_without_eq.stderr index 3d92112dc36d..29cd7da6b77d 100644 --- a/tests/ui/derive_partial_eq_without_eq.stderr +++ b/tests/ui/derive_partial_eq_without_eq.stderr @@ -1,5 +1,5 @@ error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:11:17 + --> tests/ui/derive_partial_eq_without_eq.rs:12:17 | LL | #[derive(Debug, PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` @@ -8,73 +8,73 @@ LL | #[derive(Debug, PartialEq)] = help: to override `-D warnings` add `#[allow(clippy::derive_partial_eq_without_eq)]` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:53:10 + --> tests/ui/derive_partial_eq_without_eq.rs:70:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:59:10 + --> tests/ui/derive_partial_eq_without_eq.rs:76:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:65:10 + --> tests/ui/derive_partial_eq_without_eq.rs:82:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:68:10 + --> tests/ui/derive_partial_eq_without_eq.rs:85:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:74:10 + --> tests/ui/derive_partial_eq_without_eq.rs:91:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:80:10 + --> tests/ui/derive_partial_eq_without_eq.rs:97:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:93:17 + --> tests/ui/derive_partial_eq_without_eq.rs:110:17 | LL | #[derive(Debug, PartialEq, Clone)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:96:10 + --> tests/ui/derive_partial_eq_without_eq.rs:113:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:103:14 + --> tests/ui/derive_partial_eq_without_eq.rs:120:14 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:106:14 + --> tests/ui/derive_partial_eq_without_eq.rs:123:14 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:166:14 + --> tests/ui/derive_partial_eq_without_eq.rs:183:14 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:174:14 + --> tests/ui/derive_partial_eq_without_eq.rs:191:14 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` diff --git a/tests/ui/doc/doc-fixable.fixed b/tests/ui/doc/doc-fixable.fixed index 7e22c847b1bc..84673f1f43fd 100644 --- a/tests/ui/doc/doc-fixable.fixed +++ b/tests/ui/doc/doc-fixable.fixed @@ -240,3 +240,6 @@ extern { /// `foo()` fn in_extern(); } + +/// +fn check_autofix_for_base_urls() {} diff --git a/tests/ui/doc/doc-fixable.rs b/tests/ui/doc/doc-fixable.rs index 3e2cb0df54b0..4d017a99e0fb 100644 --- a/tests/ui/doc/doc-fixable.rs +++ b/tests/ui/doc/doc-fixable.rs @@ -240,3 +240,6 @@ extern { /// foo() fn in_extern(); } + +/// https://github.com/rust-lang/rust-clippy/pull/12836 +fn check_autofix_for_base_urls() {} diff --git a/tests/ui/doc/doc-fixable.stderr b/tests/ui/doc/doc-fixable.stderr index cd2228c47e35..a9263f62d38d 100644 --- a/tests/ui/doc/doc-fixable.stderr +++ b/tests/ui/doc/doc-fixable.stderr @@ -363,5 +363,11 @@ help: try LL | /// `foo()` | ~~~~~~~ -error: aborting due to 33 previous errors +error: you should put bare URLs between `<`/`>` or make a proper Markdown link + --> tests/ui/doc/doc-fixable.rs:244:5 + | +LL | /// https://github.com/rust-lang/rust-clippy/pull/12836 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `` + +error: aborting due to 34 previous errors diff --git a/tests/ui/doc/issue_12795.fixed b/tests/ui/doc/issue_12795.fixed new file mode 100644 index 000000000000..ade23bf975c2 --- /dev/null +++ b/tests/ui/doc/issue_12795.fixed @@ -0,0 +1,9 @@ +#![warn(clippy::doc_markdown)] + +//! A comment with `a_b(x)` and `a_c` in it and (`a_b((c))` ) too and (maybe `a_b((c))`) +//~^ ERROR: item in documentation is missing backticks +//~| ERROR: item in documentation is missing backticks +//~| ERROR: item in documentation is missing backticks +//~| ERROR: item in documentation is missing backticks + +pub fn main() {} diff --git a/tests/ui/doc/issue_12795.rs b/tests/ui/doc/issue_12795.rs new file mode 100644 index 000000000000..6d94a07e3036 --- /dev/null +++ b/tests/ui/doc/issue_12795.rs @@ -0,0 +1,9 @@ +#![warn(clippy::doc_markdown)] + +//! A comment with a_b(x) and a_c in it and (a_b((c)) ) too and (maybe a_b((c))) +//~^ ERROR: item in documentation is missing backticks +//~| ERROR: item in documentation is missing backticks +//~| ERROR: item in documentation is missing backticks +//~| ERROR: item in documentation is missing backticks + +pub fn main() {} diff --git a/tests/ui/doc/issue_12795.stderr b/tests/ui/doc/issue_12795.stderr new file mode 100644 index 000000000000..5700145ec8f7 --- /dev/null +++ b/tests/ui/doc/issue_12795.stderr @@ -0,0 +1,48 @@ +error: item in documentation is missing backticks + --> tests/ui/doc/issue_12795.rs:3:20 + | +LL | //! A comment with a_b(x) and a_c in it and (a_b((c)) ) too and (maybe a_b((c))) + | ^^^^^^ + | + = note: `-D clippy::doc-markdown` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::doc_markdown)]` +help: try + | +LL | //! A comment with `a_b(x)` and a_c in it and (a_b((c)) ) too and (maybe a_b((c))) + | ~~~~~~~~ + +error: item in documentation is missing backticks + --> tests/ui/doc/issue_12795.rs:3:31 + | +LL | //! A comment with a_b(x) and a_c in it and (a_b((c)) ) too and (maybe a_b((c))) + | ^^^ + | +help: try + | +LL | //! A comment with a_b(x) and `a_c` in it and (a_b((c)) ) too and (maybe a_b((c))) + | ~~~~~ + +error: item in documentation is missing backticks + --> tests/ui/doc/issue_12795.rs:3:46 + | +LL | //! A comment with a_b(x) and a_c in it and (a_b((c)) ) too and (maybe a_b((c))) + | ^^^^^^^^ + | +help: try + | +LL | //! A comment with a_b(x) and a_c in it and (`a_b((c))` ) too and (maybe a_b((c))) + | ~~~~~~~~~~ + +error: item in documentation is missing backticks + --> tests/ui/doc/issue_12795.rs:3:72 + | +LL | //! A comment with a_b(x) and a_c in it and (a_b((c)) ) too and (maybe a_b((c))) + | ^^^^^^^^ + | +help: try + | +LL | //! A comment with a_b(x) and a_c in it and (a_b((c)) ) too and (maybe `a_b((c))`) + | ~~~~~~~~~~ + +error: aborting due to 4 previous errors + diff --git a/tests/ui/iter_on_empty_collections.fixed b/tests/ui/iter_on_empty_collections.fixed index 794629f240eb..0f28b48d9ab8 100644 --- a/tests/ui/iter_on_empty_collections.fixed +++ b/tests/ui/iter_on_empty_collections.fixed @@ -20,6 +20,28 @@ fn array() { }; let _ = if false { ["test"].iter() } else { [].iter() }; + + let smth = Some(vec![1, 2, 3]); + + // Don't trigger when the empty collection iter is relied upon for its concrete type + // But do trigger if it is just an iterator, despite being an argument to a method + for i in smth.as_ref().map_or([].iter(), |s| s.iter()).chain(std::iter::empty()) { + println!("{i}"); + } + + // Same as above, but for empty collection iters with extra layers + for i in smth.as_ref().map_or({ [].iter() }, |s| s.iter()) { + println!("{y}", y = i + 1); + } + + // Same as above, but for regular function calls + for i in Option::map_or(smth.as_ref(), [].iter(), |s| s.iter()) { + println!("{i}"); + } + + // Same as above, but when there are no predicates that mention the collection iter type. + let mut iter = [34, 228, 35].iter(); + let _ = std::mem::replace(&mut iter, [].iter()); } macro_rules! in_macros { diff --git a/tests/ui/iter_on_empty_collections.rs b/tests/ui/iter_on_empty_collections.rs index a6461a702eb2..702da514df7d 100644 --- a/tests/ui/iter_on_empty_collections.rs +++ b/tests/ui/iter_on_empty_collections.rs @@ -20,6 +20,28 @@ fn array() { }; let _ = if false { ["test"].iter() } else { [].iter() }; + + let smth = Some(vec![1, 2, 3]); + + // Don't trigger when the empty collection iter is relied upon for its concrete type + // But do trigger if it is just an iterator, despite being an argument to a method + for i in smth.as_ref().map_or([].iter(), |s| s.iter()).chain([].iter()) { + println!("{i}"); + } + + // Same as above, but for empty collection iters with extra layers + for i in smth.as_ref().map_or({ [].iter() }, |s| s.iter()) { + println!("{y}", y = i + 1); + } + + // Same as above, but for regular function calls + for i in Option::map_or(smth.as_ref(), [].iter(), |s| s.iter()) { + println!("{i}"); + } + + // Same as above, but when there are no predicates that mention the collection iter type. + let mut iter = [34, 228, 35].iter(); + let _ = std::mem::replace(&mut iter, [].iter()); } macro_rules! in_macros { diff --git a/tests/ui/iter_on_empty_collections.stderr b/tests/ui/iter_on_empty_collections.stderr index ade20ff26a01..da9caa6925bd 100644 --- a/tests/ui/iter_on_empty_collections.stderr +++ b/tests/ui/iter_on_empty_collections.stderr @@ -37,5 +37,11 @@ error: `iter` call on an empty collection LL | assert_eq!(None.iter().next(), Option::<&i32>::None); | ^^^^^^^^^^^ help: try: `std::iter::empty()` -error: aborting due to 6 previous errors +error: `iter` call on an empty collection + --> tests/ui/iter_on_empty_collections.rs:28:66 + | +LL | for i in smth.as_ref().map_or([].iter(), |s| s.iter()).chain([].iter()) { + | ^^^^^^^^^ help: try: `std::iter::empty()` + +error: aborting due to 7 previous errors diff --git a/tests/ui/let_and_return.fixed b/tests/ui/let_and_return.fixed index 4187019e5894..b68b41cdca23 100644 --- a/tests/ui/let_and_return.fixed +++ b/tests/ui/let_and_return.fixed @@ -210,4 +210,38 @@ fn issue9150() -> usize { x } +fn issue12801() { + fn left_is_if() -> String { + + (if true { "a".to_string() } else { "b".to_string() } + "c") + //~^ ERROR: returning the result of a `let` binding from a block + } + + fn no_par_needed() -> String { + + "c".to_string() + if true { "a" } else { "b" } + //~^ ERROR: returning the result of a `let` binding from a block + } + + fn conjunctive_blocks() -> String { + + ({ "a".to_string() } + "b" + { "c" } + "d") + //~^ ERROR: returning the result of a `let` binding from a block + } + + #[allow(clippy::overly_complex_bool_expr)] + fn other_ops() { + let _ = || { + + (if true { 2 } else { 3 } << 4) + //~^ ERROR: returning the result of a `let` binding from a block + }; + let _ = || { + + ({ true } || { false } && { 2 <= 3 }) + //~^ ERROR: returning the result of a `let` binding from a block + }; + } +} + fn main() {} diff --git a/tests/ui/let_and_return.rs b/tests/ui/let_and_return.rs index 54444957b7d5..6b9035f94288 100644 --- a/tests/ui/let_and_return.rs +++ b/tests/ui/let_and_return.rs @@ -210,4 +210,38 @@ fn issue9150() -> usize { x } +fn issue12801() { + fn left_is_if() -> String { + let s = if true { "a".to_string() } else { "b".to_string() } + "c"; + s + //~^ ERROR: returning the result of a `let` binding from a block + } + + fn no_par_needed() -> String { + let s = "c".to_string() + if true { "a" } else { "b" }; + s + //~^ ERROR: returning the result of a `let` binding from a block + } + + fn conjunctive_blocks() -> String { + let s = { "a".to_string() } + "b" + { "c" } + "d"; + s + //~^ ERROR: returning the result of a `let` binding from a block + } + + #[allow(clippy::overly_complex_bool_expr)] + fn other_ops() { + let _ = || { + let s = if true { 2 } else { 3 } << 4; + s + //~^ ERROR: returning the result of a `let` binding from a block + }; + let _ = || { + let s = { true } || { false } && { 2 <= 3 }; + s + //~^ ERROR: returning the result of a `let` binding from a block + }; + } +} + fn main() {} diff --git a/tests/ui/let_and_return.stderr b/tests/ui/let_and_return.stderr index ff5962ec196e..75efa05d770a 100644 --- a/tests/ui/let_and_return.stderr +++ b/tests/ui/let_and_return.stderr @@ -78,5 +78,75 @@ LL + E::B(x) => x, LL + }) as _ | -error: aborting due to 5 previous errors +error: returning the result of a `let` binding from a block + --> tests/ui/let_and_return.rs:216:9 + | +LL | let s = if true { "a".to_string() } else { "b".to_string() } + "c"; + | ------------------------------------------------------------------- unnecessary `let` binding +LL | s + | ^ + | +help: return the expression directly + | +LL ~ +LL ~ (if true { "a".to_string() } else { "b".to_string() } + "c") + | + +error: returning the result of a `let` binding from a block + --> tests/ui/let_and_return.rs:222:9 + | +LL | let s = "c".to_string() + if true { "a" } else { "b" }; + | ------------------------------------------------------- unnecessary `let` binding +LL | s + | ^ + | +help: return the expression directly + | +LL ~ +LL ~ "c".to_string() + if true { "a" } else { "b" } + | + +error: returning the result of a `let` binding from a block + --> tests/ui/let_and_return.rs:228:9 + | +LL | let s = { "a".to_string() } + "b" + { "c" } + "d"; + | -------------------------------------------------- unnecessary `let` binding +LL | s + | ^ + | +help: return the expression directly + | +LL ~ +LL ~ ({ "a".to_string() } + "b" + { "c" } + "d") + | + +error: returning the result of a `let` binding from a block + --> tests/ui/let_and_return.rs:236:13 + | +LL | let s = if true { 2 } else { 3 } << 4; + | -------------------------------------- unnecessary `let` binding +LL | s + | ^ + | +help: return the expression directly + | +LL ~ +LL ~ (if true { 2 } else { 3 } << 4) + | + +error: returning the result of a `let` binding from a block + --> tests/ui/let_and_return.rs:241:13 + | +LL | let s = { true } || { false } && { 2 <= 3 }; + | -------------------------------------------- unnecessary `let` binding +LL | s + | ^ + | +help: return the expression directly + | +LL ~ +LL ~ ({ true } || { false } && { 2 <= 3 }) + | + +error: aborting due to 10 previous errors diff --git a/tests/ui/many_single_char_names.rs b/tests/ui/many_single_char_names.rs index 2af45eaab8af..68578340d90e 100644 --- a/tests/ui/many_single_char_names.rs +++ b/tests/ui/many_single_char_names.rs @@ -1,5 +1,3 @@ -//@compile-flags: -Zdeduplicate-diagnostics=yes - #![allow(clippy::too_many_arguments, clippy::diverging_sub_expression)] #![warn(clippy::many_single_char_names)] diff --git a/tests/ui/many_single_char_names.stderr b/tests/ui/many_single_char_names.stderr index 3b2460b5c077..131836ef7c88 100644 --- a/tests/ui/many_single_char_names.stderr +++ b/tests/ui/many_single_char_names.stderr @@ -1,5 +1,5 @@ error: 5 bindings with single-character names in scope - --> tests/ui/many_single_char_names.rs:7:9 + --> tests/ui/many_single_char_names.rs:5:9 | LL | let a: i32; | ^ @@ -14,7 +14,7 @@ LL | let e: i32; = help: to override `-D warnings` add `#[allow(clippy::many_single_char_names)]` error: 6 bindings with single-character names in scope - --> tests/ui/many_single_char_names.rs:7:9 + --> tests/ui/many_single_char_names.rs:5:9 | LL | let a: i32; | ^ @@ -28,7 +28,7 @@ LL | let f: i32; | ^ error: 5 bindings with single-character names in scope - --> tests/ui/many_single_char_names.rs:7:9 + --> tests/ui/many_single_char_names.rs:5:9 | LL | let a: i32; | ^ @@ -40,13 +40,13 @@ LL | e => panic!(), | ^ error: 8 bindings with single-character names in scope - --> tests/ui/many_single_char_names.rs:36:13 + --> tests/ui/many_single_char_names.rs:34:13 | LL | fn bindings(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32, h: i32) {} | ^ ^ ^ ^ ^ ^ ^ ^ error: 8 bindings with single-character names in scope - --> tests/ui/many_single_char_names.rs:40:10 + --> tests/ui/many_single_char_names.rs:38:10 | LL | let (a, b, c, d, e, f, g, h): (bool, bool, bool, bool, bool, bool, bool, bool) = unimplemented!(); | ^ ^ ^ ^ ^ ^ ^ ^ diff --git a/tests/ui/needless_return.fixed b/tests/ui/needless_return.fixed index 2575f2449e18..a9271cb399d8 100644 --- a/tests/ui/needless_return.fixed +++ b/tests/ui/needless_return.fixed @@ -323,4 +323,8 @@ fn allow_works() -> i32 { } } +fn conjunctive_blocks() -> String { + ({ "a".to_string() } + "b" + { "c" }) +} + fn main() {} diff --git a/tests/ui/needless_return.rs b/tests/ui/needless_return.rs index 04f21834d885..dc888bf667f1 100644 --- a/tests/ui/needless_return.rs +++ b/tests/ui/needless_return.rs @@ -333,4 +333,8 @@ fn allow_works() -> i32 { } } +fn conjunctive_blocks() -> String { + return { "a".to_string() } + "b" + { "c" }; +} + fn main() {} diff --git a/tests/ui/needless_return.stderr b/tests/ui/needless_return.stderr index 758ff6d985cd..bf5a89d8b75d 100644 --- a/tests/ui/needless_return.stderr +++ b/tests/ui/needless_return.stderr @@ -653,5 +653,17 @@ LL - return if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 LL + (if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else { 5 }) | -error: aborting due to 52 previous errors +error: unneeded `return` statement + --> tests/ui/needless_return.rs:337:5 + | +LL | return { "a".to_string() } + "b" + { "c" }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove `return` and wrap the sequence with parentheses + | +LL - return { "a".to_string() } + "b" + { "c" }; +LL + ({ "a".to_string() } + "b" + { "c" }) + | + +error: aborting due to 53 previous errors diff --git a/tests/ui/numbered_fields.fixed b/tests/ui/numbered_fields.fixed index dc88081ba0a7..108520eed38d 100644 --- a/tests/ui/numbered_fields.fixed +++ b/tests/ui/numbered_fields.fixed @@ -34,4 +34,9 @@ fn main() { // Aliases can't be tuple constructed #8638 let _ = Alias { 0: 0, 1: 1, 2: 2 }; + + // Issue #12367 + struct TupleStructVec(Vec); + + let _ = TupleStructVec(vec![0, 1, 2, 3]); } diff --git a/tests/ui/numbered_fields.rs b/tests/ui/numbered_fields.rs index e8fa652e3c1d..c718661a6826 100644 --- a/tests/ui/numbered_fields.rs +++ b/tests/ui/numbered_fields.rs @@ -42,4 +42,9 @@ fn main() { // Aliases can't be tuple constructed #8638 let _ = Alias { 0: 0, 1: 1, 2: 2 }; + + // Issue #12367 + struct TupleStructVec(Vec); + + let _ = TupleStructVec { 0: vec![0, 1, 2, 3] }; } diff --git a/tests/ui/numbered_fields.stderr b/tests/ui/numbered_fields.stderr index 96426cab1e62..9d3f59cd3769 100644 --- a/tests/ui/numbered_fields.stderr +++ b/tests/ui/numbered_fields.stderr @@ -23,5 +23,11 @@ LL | | 1: 3u32, LL | | }; | |_____^ help: try: `TupleStruct(1u32, 3u32, 2u8)` -error: aborting due to 2 previous errors +error: used a field initializer for a tuple struct + --> tests/ui/numbered_fields.rs:49:13 + | +LL | let _ = TupleStructVec { 0: vec![0, 1, 2, 3] }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `TupleStructVec(vec![0, 1, 2, 3])` + +error: aborting due to 3 previous errors diff --git a/tests/ui/significant_drop_in_scrutinee.rs b/tests/ui/significant_drop_in_scrutinee.rs index 7fc89bb95380..8ee15440ccf0 100644 --- a/tests/ui/significant_drop_in_scrutinee.rs +++ b/tests/ui/significant_drop_in_scrutinee.rs @@ -274,11 +274,20 @@ fn should_trigger_lint_for_tuple_in_scrutinee() { }, (_, _, _) => {}, }; + } +} + +// Should not trigger lint since `String::as_str` returns a reference (i.e., `&str`) +// to the locked data (i.e., the `String`) and it is not surprising that matching such +// a reference needs to keep the data locked until the end of the match block. +fn should_not_trigger_lint_for_string_as_str() { + let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() }); + { + let mutex2 = Mutex::new(StateWithField { s: "two".to_owned() }); let mutex3 = Mutex::new(StateWithField { s: "three".to_owned() }); + match mutex3.lock().unwrap().s.as_str() { - //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until - //~| NOTE: this might lead to deadlocks or other unexpected behavior "three" => { println!("started"); mutex1.lock().unwrap().s.len(); @@ -289,8 +298,6 @@ fn should_trigger_lint_for_tuple_in_scrutinee() { }; match (true, mutex3.lock().unwrap().s.as_str()) { - //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until - //~| NOTE: this might lead to deadlocks or other unexpected behavior (_, "three") => { println!("started"); mutex1.lock().unwrap().s.len(); @@ -514,16 +521,15 @@ impl StateStringWithBoxedMutexGuard { } } -fn should_trigger_lint_for_boxed_mutex_guard_holding_string() { +fn should_not_trigger_lint_for_string_ref() { let s = StateStringWithBoxedMutexGuard::new(); let matcher = String::from("A String"); - // Should trigger lint because a temporary Box holding a type with a significant drop in a match - // scrutinee may have a potentially surprising lifetime. + // Should not trigger lint because the second `deref` returns a string reference (`&String`). + // So it is not surprising that matching the reference implies that the lock needs to be held + // until the end of the block. match s.lock().deref().deref() { - //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the - //~| NOTE: this might lead to deadlocks or other unexpected behavior matcher => println!("Value is {}", s.lock().deref()), _ => println!("Value was not a match"), }; @@ -639,13 +645,12 @@ fn should_trigger_lint_for_non_ref_move_and_clone_suggestion() { }; } -fn should_trigger_lint_for_read_write_lock_for_loop() { - // For-in loops desugar to match expressions and are prone to the type of deadlock this lint is - // designed to look for. +fn should_not_trigger_lint_for_read_write_lock_for_loop() { let rwlock = RwLock::>::new(vec!["1".to_string()]); + // Should not trigger lint. Since we're iterating over the data, it's obvious that the lock + // has to be held until the iteration finishes. + // https://github.com/rust-lang/rust-clippy/issues/8987 for s in rwlock.read().unwrap().iter() { - //~^ ERROR: temporary with significant `Drop` in `for` loop condition will live until - //~| NOTE: this might lead to deadlocks or other unexpected behavior println!("{}", s); } } @@ -731,4 +736,69 @@ fn should_not_trigger_for_significant_drop_ref() { }; } +struct Foo<'a>(&'a Vec); + +impl<'a> Foo<'a> { + fn copy_old_lifetime(&self) -> &'a Vec { + self.0 + } + + fn reborrow_new_lifetime(&self) -> &Vec { + self.0 + } +} + +fn should_trigger_lint_if_and_only_if_lifetime_is_irrelevant() { + let vec = Vec::new(); + let mutex = Mutex::new(Foo(&vec)); + + // Should trigger lint even if `copy_old_lifetime()` has a lifetime, as the lifetime of + // `&vec` is unrelated to the temporary with significant drop (i.e., the `MutexGuard`). + for val in mutex.lock().unwrap().copy_old_lifetime() { + //~^ ERROR: temporary with significant `Drop` in `for` loop condition will live until the + //~| NOTE: this might lead to deadlocks or other unexpected behavior + println!("{}", val); + } + + // Should not trigger lint because `reborrow_new_lifetime()` has a lifetime and the + // lifetime is related to the temporary with significant drop (i.e., the `MutexGuard`). + for val in mutex.lock().unwrap().reborrow_new_lifetime() { + println!("{}", val); + } +} + +fn should_not_trigger_lint_for_complex_lifetime() { + let mutex = Mutex::new(vec!["hello".to_owned()]); + let string = "world".to_owned(); + + // Should not trigger lint due to the relevant lifetime. + for c in mutex.lock().unwrap().first().unwrap().chars() { + println!("{}", c); + } + + // Should trigger lint due to the irrelevant lifetime. + // + // FIXME: The lifetime is too complex to analyze. In order to avoid false positives, we do not + // trigger lint. + for c in mutex.lock().unwrap().first().map(|_| &string).unwrap().chars() { + println!("{}", c); + } +} + +fn should_not_trigger_lint_with_explicit_drop() { + let mutex = Mutex::new(vec![1]); + + // Should not trigger lint since the drop explicitly happens. + for val in [drop(mutex.lock().unwrap()), ()] { + println!("{:?}", val); + } + + // Should trigger lint if there is no explicit drop. + for val in [mutex.lock().unwrap()[0], 2] { + //~^ ERROR: temporary with significant `Drop` in `for` loop condition will live until the + //~| NOTE: this might lead to deadlocks or other unexpected behavior + println!("{:?}", val); + } +} + fn main() {} diff --git a/tests/ui/significant_drop_in_scrutinee.stderr b/tests/ui/significant_drop_in_scrutinee.stderr index 811bb0655279..4a483e79d8ad 100644 --- a/tests/ui/significant_drop_in_scrutinee.stderr +++ b/tests/ui/significant_drop_in_scrutinee.stderr @@ -160,45 +160,53 @@ LL ~ match (mutex1.lock().unwrap().s.len(), true, value) { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:279:15 + --> tests/ui/significant_drop_in_scrutinee.rs:319:11 | -LL | match mutex3.lock().unwrap().s.as_str() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | match mutex.lock().unwrap().s.len() > 1 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... -LL | mutex1.lock().unwrap().s.len(); - | ---------------------- another value with significant `Drop` created here -LL | mutex2.lock().unwrap().s.len(); - | ---------------------- another value with significant `Drop` created here +LL | mutex.lock().unwrap().s.len(); + | --------------------- another value with significant `Drop` created here ... -LL | }; - | - temporary lives until here +LL | }; + | - temporary lives until here | = note: this might lead to deadlocks or other unexpected behavior +help: try moving the temporary above the match + | +LL ~ let value = mutex.lock().unwrap().s.len(); +LL ~ match value > 1 { + | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:291:22 + --> tests/ui/significant_drop_in_scrutinee.rs:328:15 | -LL | match (true, mutex3.lock().unwrap().s.as_str()) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | match 1 < mutex.lock().unwrap().s.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... -LL | mutex1.lock().unwrap().s.len(); - | ---------------------- another value with significant `Drop` created here -LL | mutex2.lock().unwrap().s.len(); - | ---------------------- another value with significant `Drop` created here +LL | mutex.lock().unwrap().s.len(); + | --------------------- another value with significant `Drop` created here ... -LL | }; - | - temporary lives until here +LL | }; + | - temporary lives until here | = note: this might lead to deadlocks or other unexpected behavior +help: try moving the temporary above the match + | +LL ~ let value = mutex.lock().unwrap().s.len(); +LL ~ match 1 < value { + | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:312:11 + --> tests/ui/significant_drop_in_scrutinee.rs:348:11 | -LL | match mutex.lock().unwrap().s.len() > 1 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | match mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... -LL | mutex.lock().unwrap().s.len(); - | --------------------- another value with significant `Drop` created here +LL | mutex1.lock().unwrap().s.len(), + | ---------------------- another value with significant `Drop` created here +LL | mutex2.lock().unwrap().s.len() + | ---------------------- another value with significant `Drop` created here ... LL | }; | - temporary lives until here @@ -206,18 +214,20 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ let value = mutex.lock().unwrap().s.len() > 1; -LL ~ match value { +LL ~ let value = mutex1.lock().unwrap().s.len(); +LL ~ match value < mutex2.lock().unwrap().s.len() { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:321:11 + --> tests/ui/significant_drop_in_scrutinee.rs:348:44 | -LL | match 1 < mutex.lock().unwrap().s.len() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | match mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... -LL | mutex.lock().unwrap().s.len(); - | --------------------- another value with significant `Drop` created here +LL | mutex1.lock().unwrap().s.len(), + | ---------------------- another value with significant `Drop` created here +LL | mutex2.lock().unwrap().s.len() + | ---------------------- another value with significant `Drop` created here ... LL | }; | - temporary lives until here @@ -225,15 +235,15 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ let value = 1 < mutex.lock().unwrap().s.len(); -LL ~ match value { +LL ~ let value = mutex2.lock().unwrap().s.len(); +LL ~ match mutex1.lock().unwrap().s.len() < value { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:341:11 + --> tests/ui/significant_drop_in_scrutinee.rs:361:11 | -LL | match mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | match mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... LL | mutex1.lock().unwrap().s.len(), | ---------------------- another value with significant `Drop` created here @@ -246,15 +256,15 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ let value = mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len(); -LL ~ match value { +LL ~ let value = mutex1.lock().unwrap().s.len(); +LL ~ match value >= mutex2.lock().unwrap().s.len() { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:354:11 + --> tests/ui/significant_drop_in_scrutinee.rs:361:45 | LL | match mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... LL | mutex1.lock().unwrap().s.len(), | ---------------------- another value with significant `Drop` created here @@ -267,15 +277,15 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ let value = mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len(); -LL ~ match value { +LL ~ let value = mutex2.lock().unwrap().s.len(); +LL ~ match mutex1.lock().unwrap().s.len() >= value { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:391:11 + --> tests/ui/significant_drop_in_scrutinee.rs:398:11 | LL | match get_mutex_guard().s.len() > 1 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^ ... LL | mutex1.lock().unwrap().s.len(); | ---------------------- another value with significant `Drop` created here @@ -286,12 +296,12 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ let value = get_mutex_guard().s.len() > 1; -LL ~ match value { +LL ~ let value = get_mutex_guard().s.len(); +LL ~ match value > 1 { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:410:11 + --> tests/ui/significant_drop_in_scrutinee.rs:417:11 | LL | match match i { | ___________^ @@ -299,9 +309,9 @@ LL | | LL | | LL | | 100 => mutex1.lock().unwrap(), ... | +LL | | .s LL | | .len() -LL | | > 1 - | |___________^ + | |__________^ ... LL | mutex1.lock().unwrap().s.len(); | ---------------------- another value with significant `Drop` created here @@ -319,13 +329,12 @@ LL + 100 => mutex1.lock().unwrap(), LL + _ => mutex2.lock().unwrap(), LL + } LL + .s -LL + .len() -LL + > 1; +LL + .len(); LL ~ match value | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:438:11 + --> tests/ui/significant_drop_in_scrutinee.rs:445:11 | LL | match if i > 1 { | ___________^ @@ -333,9 +342,9 @@ LL | | LL | | LL | | mutex1.lock().unwrap() ... | +LL | | .s LL | | .len() -LL | | > 1 - | |___________^ + | |__________^ ... LL | mutex1.lock().unwrap().s.len(); | ---------------------- another value with significant `Drop` created here @@ -354,13 +363,12 @@ LL + } else { LL + mutex2.lock().unwrap() LL + } LL + .s -LL + .len() -LL + > 1; +LL + .len(); LL ~ match value | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:494:11 + --> tests/ui/significant_drop_in_scrutinee.rs:501:11 | LL | match s.lock().deref().deref() { | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -374,25 +382,11 @@ LL | }; help: try moving the temporary above the match and create a copy | LL ~ let value = *s.lock().deref().deref(); -LL ~ match value { +LL ~ match (&value) { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:524:11 - | -LL | match s.lock().deref().deref() { - | ^^^^^^^^^^^^^^^^^^^^^^^^ -... -LL | matcher => println!("Value is {}", s.lock().deref()), - | -------- another value with significant `Drop` created here -LL | _ => println!("Value was not a match"), -LL | }; - | - temporary lives until here - | - = note: this might lead to deadlocks or other unexpected behavior - -error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:545:11 + --> tests/ui/significant_drop_in_scrutinee.rs:551:11 | LL | match mutex.lock().unwrap().i = i { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -411,10 +405,10 @@ LL ~ match () { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:553:11 + --> tests/ui/significant_drop_in_scrutinee.rs:559:15 | LL | match i = mutex.lock().unwrap().i { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^ ... LL | println!("{}", mutex.lock().unwrap().i); | --------------------- another value with significant `Drop` created here @@ -425,12 +419,12 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ i = mutex.lock().unwrap().i; -LL ~ match () { +LL ~ let value = mutex.lock().unwrap().i; +LL ~ match i = value { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:561:11 + --> tests/ui/significant_drop_in_scrutinee.rs:567:11 | LL | match mutex.lock().unwrap().i += 1 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -449,10 +443,10 @@ LL ~ match () { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:569:11 + --> tests/ui/significant_drop_in_scrutinee.rs:575:16 | LL | match i += mutex.lock().unwrap().i { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^ ... LL | println!("{}", mutex.lock().unwrap().i); | --------------------- another value with significant `Drop` created here @@ -463,12 +457,12 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ i += mutex.lock().unwrap().i; -LL ~ match () { +LL ~ let value = mutex.lock().unwrap().i; +LL ~ match i += value { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:634:11 + --> tests/ui/significant_drop_in_scrutinee.rs:640:11 | LL | match rwlock.read().unwrap().to_number() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -477,20 +471,14 @@ LL | }; | - temporary lives until here | = note: this might lead to deadlocks or other unexpected behavior - -error: temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression - --> tests/ui/significant_drop_in_scrutinee.rs:646:14 +help: try moving the temporary above the match | -LL | for s in rwlock.read().unwrap().iter() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -LL | } - | - temporary lives until here +LL ~ let value = rwlock.read().unwrap().to_number(); +LL ~ match value { | - = note: this might lead to deadlocks or other unexpected behavior error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:663:11 + --> tests/ui/significant_drop_in_scrutinee.rs:668:11 | LL | match mutex.lock().unwrap().foo() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -506,7 +494,7 @@ LL ~ match value { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:726:11 + --> tests/ui/significant_drop_in_scrutinee.rs:731:11 | LL | match guard.take().len() { | ^^^^^^^^^^^^^^^^^^ @@ -521,5 +509,37 @@ LL ~ let value = guard.take().len(); LL ~ match value { | +error: temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression + --> tests/ui/significant_drop_in_scrutinee.rs:757:16 + | +LL | for val in mutex.lock().unwrap().copy_old_lifetime() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | } + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior +help: try moving the temporary above the match + | +LL ~ let value = mutex.lock().unwrap().copy_old_lifetime(); +LL ~ for val in value { + | + +error: temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression + --> tests/ui/significant_drop_in_scrutinee.rs:797:17 + | +LL | for val in [mutex.lock().unwrap()[0], 2] { + | ^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | } + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior +help: try moving the temporary above the match + | +LL ~ let value = mutex.lock().unwrap()[0]; +LL ~ for val in [value, 2] { + | + error: aborting due to 27 previous errors diff --git a/tests/ui/unnecessary_iter_cloned.fixed b/tests/ui/unnecessary_iter_cloned.fixed index 2c582c90ba8c..dc5e163ff04e 100644 --- a/tests/ui/unnecessary_iter_cloned.fixed +++ b/tests/ui/unnecessary_iter_cloned.fixed @@ -170,3 +170,32 @@ fn check_mut_iteratee_and_modify_inner_variable() { } } } + +mod issue_12821 { + fn foo() { + let v: Vec<_> = "hello".chars().collect(); + for c in v.iter() { + //~^ ERROR: unnecessary use of `cloned` + println!("{c}"); // should not suggest to remove `&` + } + } + + fn bar() { + let v: Vec<_> = "hello".chars().collect(); + for c in v.iter() { + //~^ ERROR: unnecessary use of `cloned` + let ref_c = c; //~ HELP: remove any references to the binding + println!("{ref_c}"); + } + } + + fn baz() { + let v: Vec<_> = "hello".chars().enumerate().collect(); + for (i, c) in v.iter() { + //~^ ERROR: unnecessary use of `cloned` + let ref_c = c; //~ HELP: remove any references to the binding + let ref_i = i; + println!("{i} {ref_c}"); // should not suggest to remove `&` from `i` + } + } +} diff --git a/tests/ui/unnecessary_iter_cloned.rs b/tests/ui/unnecessary_iter_cloned.rs index a28ccd1efef2..8f797ac717fb 100644 --- a/tests/ui/unnecessary_iter_cloned.rs +++ b/tests/ui/unnecessary_iter_cloned.rs @@ -170,3 +170,32 @@ fn check_mut_iteratee_and_modify_inner_variable() { } } } + +mod issue_12821 { + fn foo() { + let v: Vec<_> = "hello".chars().collect(); + for c in v.iter().cloned() { + //~^ ERROR: unnecessary use of `cloned` + println!("{c}"); // should not suggest to remove `&` + } + } + + fn bar() { + let v: Vec<_> = "hello".chars().collect(); + for c in v.iter().cloned() { + //~^ ERROR: unnecessary use of `cloned` + let ref_c = &c; //~ HELP: remove any references to the binding + println!("{ref_c}"); + } + } + + fn baz() { + let v: Vec<_> = "hello".chars().enumerate().collect(); + for (i, c) in v.iter().cloned() { + //~^ ERROR: unnecessary use of `cloned` + let ref_c = &c; //~ HELP: remove any references to the binding + let ref_i = &i; + println!("{i} {ref_c}"); // should not suggest to remove `&` from `i` + } + } +} diff --git a/tests/ui/unnecessary_iter_cloned.stderr b/tests/ui/unnecessary_iter_cloned.stderr index fb98cfddc262..0bdb37a521fc 100644 --- a/tests/ui/unnecessary_iter_cloned.stderr +++ b/tests/ui/unnecessary_iter_cloned.stderr @@ -10,7 +10,7 @@ help: use | LL | for (t, path) in files { | ~~~~~ -help: remove this `&` +help: remove any references to the binding | LL - let other = match get_file_path(&t) { LL + let other = match get_file_path(t) { @@ -26,11 +26,49 @@ help: use | LL | for (t, path) in files.iter() { | ~~~~~~~~~~~~ -help: remove this `&` +help: remove any references to the binding | LL - let other = match get_file_path(&t) { LL + let other = match get_file_path(t) { | -error: aborting due to 2 previous errors +error: unnecessary use of `cloned` + --> tests/ui/unnecessary_iter_cloned.rs:177:18 + | +LL | for c in v.iter().cloned() { + | ^^^^^^^^^^^^^^^^^ help: use: `v.iter()` + +error: unnecessary use of `cloned` + --> tests/ui/unnecessary_iter_cloned.rs:185:18 + | +LL | for c in v.iter().cloned() { + | ^^^^^^^^^^^^^^^^^ + | +help: use + | +LL | for c in v.iter() { + | ~~~~~~~~ +help: remove any references to the binding + | +LL - let ref_c = &c; +LL + let ref_c = c; + | + +error: unnecessary use of `cloned` + --> tests/ui/unnecessary_iter_cloned.rs:194:23 + | +LL | for (i, c) in v.iter().cloned() { + | ^^^^^^^^^^^^^^^^^ + | +help: use + | +LL | for (i, c) in v.iter() { + | ~~~~~~~~ +help: remove any references to the binding + | +LL ~ let ref_c = c; +LL ~ let ref_i = i; + | + +error: aborting due to 5 previous errors diff --git a/tests/ui/unnecessary_to_owned.stderr b/tests/ui/unnecessary_to_owned.stderr index 5475df9c7b93..2829f3cd6e98 100644 --- a/tests/ui/unnecessary_to_owned.stderr +++ b/tests/ui/unnecessary_to_owned.stderr @@ -487,7 +487,7 @@ help: use | LL | for t in file_types { | ~~~~~~~~~~ -help: remove this `&` +help: remove any references to the binding | LL - let path = match get_file_path(&t) { LL + let path = match get_file_path(t) { diff --git a/tests/ui/unsafe_derive_deserialize.rs b/tests/ui/unsafe_derive_deserialize.rs index 70dcaa3afa45..5187e0790423 100644 --- a/tests/ui/unsafe_derive_deserialize.rs +++ b/tests/ui/unsafe_derive_deserialize.rs @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![warn(clippy::unsafe_derive_deserialize)] #![allow(unused, clippy::missing_safety_doc)] @@ -71,4 +72,14 @@ impl G { } } +// Check that we honor the `expect` attribute on the ADT +#[expect(clippy::unsafe_derive_deserialize)] +#[derive(Deserialize)] +pub struct H; +impl H { + pub fn unsafe_block(&self) { + unsafe {} + } +} + fn main() {} diff --git a/tests/ui/unsafe_derive_deserialize.stderr b/tests/ui/unsafe_derive_deserialize.stderr index f2d4429f707a..06719f23d57f 100644 --- a/tests/ui/unsafe_derive_deserialize.stderr +++ b/tests/ui/unsafe_derive_deserialize.stderr @@ -1,5 +1,5 @@ error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` - --> tests/ui/unsafe_derive_deserialize.rs:8:10 + --> tests/ui/unsafe_derive_deserialize.rs:9:10 | LL | #[derive(Deserialize)] | ^^^^^^^^^^^ @@ -10,7 +10,7 @@ LL | #[derive(Deserialize)] = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` - --> tests/ui/unsafe_derive_deserialize.rs:17:10 + --> tests/ui/unsafe_derive_deserialize.rs:18:10 | LL | #[derive(Deserialize)] | ^^^^^^^^^^^ @@ -19,7 +19,7 @@ LL | #[derive(Deserialize)] = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` - --> tests/ui/unsafe_derive_deserialize.rs:24:10 + --> tests/ui/unsafe_derive_deserialize.rs:25:10 | LL | #[derive(Deserialize)] | ^^^^^^^^^^^ @@ -28,7 +28,7 @@ LL | #[derive(Deserialize)] = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` - --> tests/ui/unsafe_derive_deserialize.rs:33:10 + --> tests/ui/unsafe_derive_deserialize.rs:34:10 | LL | #[derive(Deserialize)] | ^^^^^^^^^^^ From 040edea332a2767aed540b725c38a37167e17fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Mon, 27 May 2024 23:53:46 +0200 Subject: [PATCH 08/56] Rename HIR `TypeBinding` to `AssocItemConstraint` and related cleanup --- clippy_lints/src/implied_bounds_in_impls.rs | 54 ++++++++++----------- clippy_lints/src/len_zero.rs | 17 +++---- clippy_lints/src/manual_async_fn.rs | 10 ++-- clippy_utils/src/ast_utils.rs | 6 +-- clippy_utils/src/hir_utils.rs | 8 +-- 5 files changed, 44 insertions(+), 51 deletions(-) diff --git a/clippy_lints/src/implied_bounds_in_impls.rs b/clippy_lints/src/implied_bounds_in_impls.rs index dc935ed3d7fe..2b389d4f9b19 100644 --- a/clippy_lints/src/implied_bounds_in_impls.rs +++ b/clippy_lints/src/implied_bounds_in_impls.rs @@ -3,7 +3,7 @@ use clippy_utils::source::snippet; use rustc_errors::{Applicability, SuggestionStyle}; use rustc_hir::def_id::DefId; use rustc_hir::{ - GenericArg, GenericBound, GenericBounds, ItemKind, PredicateOrigin, TraitBoundModifier, TyKind, TypeBinding, + GenericArg, GenericBound, GenericBounds, ItemKind, PredicateOrigin, TraitBoundModifier, TyKind, AssocItemConstraint, WherePredicate, }; use rustc_hir_analysis::lower_ty; @@ -54,9 +54,9 @@ fn emit_lint( poly_trait: &rustc_hir::PolyTraitRef<'_>, bounds: GenericBounds<'_>, index: usize, - // The bindings that were implied, used for suggestion purposes since removing a bound with associated types - // means we might need to then move it to a different bound - implied_bindings: &[TypeBinding<'_>], + // The constraints that were implied, used for suggestion purposes since removing a bound with + // associated types means we might need to then move it to a different bound. + implied_constraints: &[AssocItemConstraint<'_>], bound: &ImplTraitBound<'_>, ) { let implied_by = snippet(cx, bound.span, ".."); @@ -83,29 +83,29 @@ fn emit_lint( let mut sugg = vec![(implied_span_extended, String::new())]; - // We also might need to include associated type binding that were specified in the implied bound, + // We also might need to include associated item constraints that were specified in the implied bound, // but omitted in the implied-by bound: // `fn f() -> impl Deref + DerefMut` // If we're going to suggest removing `Deref<..>`, we'll need to put `` on `DerefMut` - let omitted_assoc_tys: Vec<_> = implied_bindings + let omitted_constraints: Vec<_> = implied_constraints .iter() - .filter(|binding| !bound.bindings.iter().any(|b| b.ident == binding.ident)) + .filter(|constraint| !bound.constraints.iter().any(|c| c.ident == constraint.ident)) .collect(); - if !omitted_assoc_tys.is_empty() { - // `<>` needs to be added if there aren't yet any generic arguments or bindings - let needs_angle_brackets = bound.args.is_empty() && bound.bindings.is_empty(); - let insert_span = match (bound.args, bound.bindings) { - ([.., arg], [.., binding]) => arg.span().max(binding.span).shrink_to_hi(), + if !omitted_constraints.is_empty() { + // `<>` needs to be added if there aren't yet any generic arguments or constraints + let needs_angle_brackets = bound.args.is_empty() && bound.constraints.is_empty(); + let insert_span = match (bound.args, bound.constraints) { + ([.., arg], [.., constraint]) => arg.span().max(constraint.span).shrink_to_hi(), ([.., arg], []) => arg.span().shrink_to_hi(), - ([], [.., binding]) => binding.span.shrink_to_hi(), + ([], [.., constraint]) => constraint.span.shrink_to_hi(), ([], []) => bound.span.shrink_to_hi(), }; - let mut associated_tys_sugg = if needs_angle_brackets { + let mut constraints_sugg = if needs_angle_brackets { "<".to_owned() } else { - // If angle brackets aren't needed (i.e., there are already generic arguments or bindings), + // If angle brackets aren't needed (i.e., there are already generic arguments or constraints), // we need to add a comma: // `impl A` // ^ if we insert `Assoc=i32` without a comma here, that'd be invalid syntax: @@ -113,16 +113,16 @@ fn emit_lint( ", ".to_owned() }; - for (index, binding) in omitted_assoc_tys.into_iter().enumerate() { + for (index, constraint) in omitted_constraints.into_iter().enumerate() { if index > 0 { - associated_tys_sugg += ", "; + constraints_sugg += ", "; } - associated_tys_sugg += &snippet(cx, binding.span, ".."); + constraints_sugg += &snippet(cx, constraint.span, ".."); } if needs_angle_brackets { - associated_tys_sugg += ">"; + constraints_sugg += ">"; } - sugg.push((insert_span, associated_tys_sugg)); + sugg.push((insert_span, constraints_sugg)); } diag.multipart_suggestion_with_style( @@ -229,8 +229,8 @@ struct ImplTraitBound<'tcx> { trait_def_id: DefId, /// The generic arguments on the `impl Trait` bound args: &'tcx [GenericArg<'tcx>], - /// The associated types on this bound - bindings: &'tcx [TypeBinding<'tcx>], + /// The associated item constraints of this bound + constraints: &'tcx [AssocItemConstraint<'tcx>], } /// Given an `impl Trait` type, gets all the supertraits from each bound ("implied bounds"). @@ -253,7 +253,7 @@ fn collect_supertrait_bounds<'tcx>(cx: &LateContext<'tcx>, bounds: GenericBounds Some(ImplTraitBound { predicates, args: path.args.map_or([].as_slice(), |p| p.args), - bindings: path.args.map_or([].as_slice(), |p| p.bindings), + constraints: path.args.map_or([].as_slice(), |p| p.constraints), trait_def_id, span: bound.span(), }) @@ -310,20 +310,20 @@ fn check<'tcx>(cx: &LateContext<'tcx>, bounds: GenericBounds<'tcx>) { if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound && let [.., path] = poly_trait.trait_ref.path.segments && let implied_args = path.args.map_or([].as_slice(), |a| a.args) - && let implied_bindings = path.args.map_or([].as_slice(), |a| a.bindings) + && let implied_constraints = path.args.map_or([].as_slice(), |a| a.constraints) && let Some(def_id) = poly_trait.trait_ref.path.res.opt_def_id() && let Some(bound) = find_bound_in_supertraits(cx, def_id, implied_args, &supertraits) // If the implied bound has a type binding that also exists in the implied-by trait, // then we shouldn't lint. See #11880 for an example. && let assocs = cx.tcx.associated_items(bound.trait_def_id) - && !implied_bindings.iter().any(|binding| { + && !implied_constraints.iter().any(|constraint| { assocs - .filter_by_name_unhygienic(binding.ident.name) + .filter_by_name_unhygienic(constraint.ident.name) .next() .is_some_and(|assoc| assoc.kind == ty::AssocKind::Type) }) { - emit_lint(cx, poly_trait, bounds, index, implied_bindings, bound); + emit_lint(cx, poly_trait, bounds, index, implied_constraints, bound); } } } diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 97a245b76d44..2091e74665fb 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -9,7 +9,7 @@ use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_hir::{ AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind, ImplicitSelfKind, Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatKind, PathSegment, PrimTy, QPath, - TraitItemRef, TyKind, TypeBindingKind, + TraitItemRef, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, AssocKind, FnSig, Ty}; @@ -307,17 +307,12 @@ fn extract_future_output<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<& && let [GenericBound::Trait(trait_ref, _)] = &opaque.bounds && let Some(segment) = trait_ref.trait_ref.path.segments.last() && let Some(generic_args) = segment.args - && generic_args.bindings.len() == 1 - && let TypeBindingKind::Equality { - term: - rustc_hir::Term::Ty(rustc_hir::Ty { - kind: TyKind::Path(QPath::Resolved(_, path)), - .. - }), - } = &generic_args.bindings[0].kind - && path.segments.len() == 1 + && let [constraint] = generic_args.constraints + && let Some(ty) = constraint.ty() + && let TyKind::Path(QPath::Resolved(_, path)) = ty.kind + && let [segment] = path.segments { - return Some(&path.segments[0]); + return Some(segment); } None diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs index 4cd5f3b81e52..25c7e5d38b31 100644 --- a/clippy_lints/src/manual_async_fn.rs +++ b/clippy_lints/src/manual_async_fn.rs @@ -4,8 +4,7 @@ use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{ Block, Body, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, FnDecl, - FnRetTy, GenericArg, GenericBound, ImplItem, Item, ItemKind, LifetimeName, Node, Term, TraitRef, Ty, TyKind, - TypeBindingKind, + FnRetTy, GenericArg, GenericBound, ImplItem, Item, ItemKind, LifetimeName, Node, TraitRef, Ty, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -138,10 +137,9 @@ fn future_trait_ref<'tcx>( fn future_output_ty<'tcx>(trait_ref: &'tcx TraitRef<'tcx>) -> Option<&'tcx Ty<'tcx>> { if let Some(segment) = trait_ref.path.segments.last() && let Some(args) = segment.args - && args.bindings.len() == 1 - && let binding = &args.bindings[0] - && binding.ident.name == sym::Output - && let TypeBindingKind::Equality { term: Term::Ty(output) } = binding.kind + && let [constraint] = args.constraints + && constraint.ident.name == sym::Output + && let Some(output) = constraint.ty() { return Some(output); } diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index d4a5f547211a..bbdde3049dbd 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -108,7 +108,7 @@ pub fn eq_generic_args(l: &GenericArgs, r: &GenericArgs) -> bool { pub fn eq_angle_arg(l: &AngleBracketedArg, r: &AngleBracketedArg) -> bool { match (l, r) { (AngleBracketedArg::Arg(l), AngleBracketedArg::Arg(r)) => eq_generic_arg(l, r), - (AngleBracketedArg::Constraint(l), AngleBracketedArg::Constraint(r)) => eq_assoc_constraint(l, r), + (AngleBracketedArg::Constraint(l), AngleBracketedArg::Constraint(r)) => eq_assoc_item_constraint(l, r), _ => false, } } @@ -802,8 +802,8 @@ fn eq_term(l: &Term, r: &Term) -> bool { } } -pub fn eq_assoc_constraint(l: &AssocConstraint, r: &AssocConstraint) -> bool { - use AssocConstraintKind::*; +pub fn eq_assoc_item_constraint(l: &AssocItemConstraint, r: &AssocItemConstraint) -> bool { + use AssocItemConstraintKind::*; eq_id(l.ident, r.ident) && match (&l.kind, &r.kind) { (Equality { term: l }, Equality { term: r }) => eq_term(l, r), diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 9f285621e0c9..36634817fc91 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -9,7 +9,7 @@ use rustc_hir::MatchSource::TryDesugar; use rustc_hir::{ ArrayLen, BinOpKind, BindingMode, Block, BodyId, Closure, Expr, ExprField, ExprKind, FnRetTy, GenericArg, GenericArgs, HirId, HirIdMap, InlineAsmOperand, LetExpr, Lifetime, LifetimeName, Pat, PatField, PatKind, Path, - PathSegment, PrimTy, QPath, Stmt, StmtKind, Ty, TyKind, TypeBinding, + PathSegment, PrimTy, QPath, Stmt, StmtKind, Ty, TyKind, AssocItemConstraint, }; use rustc_lexer::{tokenize, TokenKind}; use rustc_lint::LateContext; @@ -486,7 +486,7 @@ impl HirEqInterExpr<'_, '_, '_> { fn eq_path_parameters(&mut self, left: &GenericArgs<'_>, right: &GenericArgs<'_>) -> bool { if left.parenthesized == right.parenthesized { over(left.args, right.args, |l, r| self.eq_generic_arg(l, r)) // FIXME(flip1995): may not work - && over(left.bindings, right.bindings, |l, r| self.eq_type_binding(l, r)) + && over(left.constraints, right.constraints, |l, r| self.eq_assoc_type_binding(l, r)) } else { false } @@ -518,8 +518,8 @@ impl HirEqInterExpr<'_, '_, '_> { } } - fn eq_type_binding(&mut self, left: &TypeBinding<'_>, right: &TypeBinding<'_>) -> bool { - left.ident.name == right.ident.name && self.eq_ty(left.ty(), right.ty()) + fn eq_assoc_type_binding(&mut self, left: &AssocItemConstraint<'_>, right: &AssocItemConstraint<'_>) -> bool { + left.ident.name == right.ident.name && self.eq_ty(left.ty().expect("expected assoc type binding"), right.ty().expect("expected assoc type binding")) } fn check_ctxt(&mut self, left: SyntaxContext, right: SyntaxContext) -> bool { From 2334264463eee8aab8c4ebd642f80131d60603b8 Mon Sep 17 00:00:00 2001 From: Mark Rousskov Date: Sat, 27 Apr 2024 13:58:37 -0400 Subject: [PATCH 09/56] Deduplicate supertrait_def_ids code --- clippy_lints/src/len_zero.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 2091e74665fb..57e0a7aa2c7e 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -253,7 +253,7 @@ fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, trait_items // fill the set with current and super traits fn fill_trait_set(traitt: DefId, set: &mut DefIdSet, cx: &LateContext<'_>) { if set.insert(traitt) { - for supertrait in rustc_trait_selection::traits::supertrait_def_ids(cx.tcx, traitt) { + for supertrait in cx.tcx.supertrait_def_ids(traitt) { fill_trait_set(supertrait, set, cx); } } From 5a44877a394569261cd53b23003113272349cf68 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Fri, 31 May 2024 14:13:46 -0400 Subject: [PATCH 10/56] Uplift TypeRelation and Relate --- clippy_lints/src/eta_reduction.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index b58018ca0353..48c4c4206fe8 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -10,7 +10,7 @@ use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{ self, Binder, ClosureArgs, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, RegionKind, Ty, - TypeVisitableExt, TypeckResults, + TypeVisitableExt, TypeckResults, TyCtxt, }; use rustc_session::declare_lint_pass; use rustc_span::symbol::sym; @@ -240,7 +240,7 @@ fn check_inputs( }) } -fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure: ClosureArgs<'tcx>, call_sig: FnSig<'_>) -> bool { +fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure: ClosureArgs>, call_sig: FnSig<'_>) -> bool { call_sig.safety == Safety::Safe && !has_late_bound_to_non_late_bound_regions( cx.tcx.signature_unclosure(closure.sig(), Safety::Safe).skip_binder(), From 5e60afb6cc733bd78ffd0e4f6233309c2cd4ea99 Mon Sep 17 00:00:00 2001 From: John Arundel Date: Mon, 3 Jun 2024 12:33:03 +0100 Subject: [PATCH 11/56] [ arc_with_non_send_sync ]: fix doc nits --- clippy_lints/src/arc_with_non_send_sync.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/arc_with_non_send_sync.rs b/clippy_lints/src/arc_with_non_send_sync.rs index 389338973894..d57ab539fff4 100644 --- a/clippy_lints/src/arc_with_non_send_sync.rs +++ b/clippy_lints/src/arc_with_non_send_sync.rs @@ -17,7 +17,7 @@ declare_clippy_lint! { /// `Arc` is a thread-safe `Rc` and guarantees that updates to the reference counter /// use atomic operations. To send an `Arc` across thread boundaries and /// share ownership between multiple threads, `T` must be [both `Send` and `Sync`](https://doc.rust-lang.org/std/sync/struct.Arc.html#thread-safety), - /// so either `T` should be made `Send + Sync` or an `Rc` should be used instead of an `Arc` + /// so either `T` should be made `Send + Sync` or an `Rc` should be used instead of an `Arc`. /// /// ### Example /// ```no_run From e94779a396619b63b99dd6494a5a940c59518d8e Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 1 Jun 2024 14:51:31 -0400 Subject: [PATCH 12/56] Opt-in diagnostics reporting to avoid doing extra work in the new solver --- clippy_lints/src/future_not_send.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/future_not_send.rs b/clippy_lints/src/future_not_send.rs index 192fb611c2d4..cb1d0de1edff 100644 --- a/clippy_lints/src/future_not_send.rs +++ b/clippy_lints/src/future_not_send.rs @@ -79,7 +79,7 @@ impl<'tcx> LateLintPass<'tcx> for FutureNotSend { let send_trait = cx.tcx.get_diagnostic_item(sym::Send).unwrap(); let span = decl.output.span(); let infcx = cx.tcx.infer_ctxt().build(); - let ocx = ObligationCtxt::new(&infcx); + let ocx = ObligationCtxt::new_with_diagnostics(&infcx); let cause = traits::ObligationCause::misc(span, fn_def_id); ocx.register_bound(cause, cx.param_env, ret_ty, send_trait); let send_errors = ocx.select_all_or_error(); From 9f4a2dd1475638fda91f5cf54650b55b320575fe Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 29 May 2024 22:23:49 -0400 Subject: [PATCH 13/56] Align Term methods with GenericArg methods --- .../src/methods/iter_on_single_or_empty_collections.rs | 2 +- clippy_lints/src/needless_borrows_for_generic_args.rs | 4 ++-- clippy_lints/src/unit_return_expecting_ord.rs | 4 ++-- clippy_utils/src/ty.rs | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs index f4397212cf66..7f6b666e434e 100644 --- a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs +++ b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs @@ -42,7 +42,7 @@ fn is_arg_ty_unified_in_fn<'tcx>( cx.tcx.predicates_of(fn_id).predicates.iter().any(|(clause, _)| { clause .as_projection_clause() - .and_then(|p| p.map_bound(|p| p.term.ty()).transpose()) + .and_then(|p| p.map_bound(|p| p.term.as_type()).transpose()) .is_some_and(|ty| ty.skip_binder() == arg_ty_in_args) }) || fn_sig .inputs() diff --git a/clippy_lints/src/needless_borrows_for_generic_args.rs b/clippy_lints/src/needless_borrows_for_generic_args.rs index 5b5e1c234245..4f99eaa40c29 100644 --- a/clippy_lints/src/needless_borrows_for_generic_args.rs +++ b/clippy_lints/src/needless_borrows_for_generic_args.rs @@ -311,7 +311,7 @@ fn is_mixed_projection_predicate<'tcx>( ) -> bool { let generics = cx.tcx.generics_of(callee_def_id); // The predicate requires the projected type to equal a type parameter from the parent context. - if let Some(term_ty) = projection_predicate.term.ty() + if let Some(term_ty) = projection_predicate.term.as_type() && let ty::Param(term_param_ty) = term_ty.kind() && (term_param_ty.index as usize) < generics.parent_count { @@ -370,7 +370,7 @@ fn replace_types<'tcx>( if replaced.insert(param_ty.index) { for projection_predicate in projection_predicates { if projection_predicate.projection_term.self_ty() == param_ty.to_ty(cx.tcx) - && let Some(term_ty) = projection_predicate.term.ty() + && let Some(term_ty) = projection_predicate.term.as_type() && let ty::Param(term_param_ty) = term_ty.kind() { let projection = projection_predicate diff --git a/clippy_lints/src/unit_return_expecting_ord.rs b/clippy_lints/src/unit_return_expecting_ord.rs index f0d1458a59b2..a8cc2f979633 100644 --- a/clippy_lints/src/unit_return_expecting_ord.rs +++ b/clippy_lints/src/unit_return_expecting_ord.rs @@ -100,12 +100,12 @@ fn get_args_to_check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Ve { if ord_preds .iter() - .any(|ord| Some(ord.self_ty()) == return_ty_pred.term.ty()) + .any(|ord| Some(ord.self_ty()) == return_ty_pred.term.as_type()) { args_to_check.push((i, "Ord".to_string())); } else if partial_ord_preds .iter() - .any(|pord| pord.self_ty() == return_ty_pred.term.ty().unwrap()) + .any(|pord| pord.self_ty() == return_ty_pred.term.expect_type()) { args_to_check.push((i, "PartialOrd".to_string())); } diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 3414b5ef680c..f0dac6f5d9c4 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -750,7 +750,7 @@ pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option None, @@ -798,7 +798,7 @@ fn sig_from_bounds<'tcx>( // Multiple different fn trait impls. Is this even allowed? return None; } - output = Some(pred.kind().rebind(p.term.ty().unwrap())); + output = Some(pred.kind().rebind(p.term.expect_type())); }, _ => (), } @@ -836,7 +836,7 @@ fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: AliasTy<'tcx>) -> Option // Multiple different fn trait impls. Is this even allowed? return None; } - output = pred.kind().rebind(p.term.ty()).transpose(); + output = pred.kind().rebind(p.term.as_type()).transpose(); }, _ => (), } From 537ce5c8c6d4f592461ff883b95b61a14a4932ae Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 23 May 2024 10:01:05 -0300 Subject: [PATCH 14/56] Handle safety keyword for extern block inner items --- clippy_utils/src/ast_utils.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index bbdde3049dbd..14f9ef8966d6 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -451,13 +451,15 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool { ty: lt, mutability: lm, expr: le, + safety: ls, }), Static(box StaticForeignItem { ty: rt, mutability: rm, expr: re, + safety: rs, }), - ) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re), + ) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re) && ls == rs, ( Fn(box ast::Fn { defaultness: ld, From bd0f90824274d20bcace9d0149f1ffb0450e23cc Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 7 May 2024 14:43:23 +0200 Subject: [PATCH 15/56] Add safe/unsafe to static inside extern blocks --- clippy_utils/src/ast_utils.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index 14f9ef8966d6..c70f5c2df842 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -308,13 +308,15 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { ty: lt, mutability: lm, expr: le, + safety: ls, }), Static(box StaticItem { ty: rt, mutability: rm, expr: re, + safety: rs, }), - ) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re), + ) => lm == rm && ls == rs && eq_ty(lt, rt) && eq_expr_opt(le, re), ( Const(box ConstItem { defaultness: ld, From e8d02fe1cb20a21e0bee2f80f1e16945eb3b9437 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 31 May 2024 15:43:18 +1000 Subject: [PATCH 16/56] Make top-level `rustc_parse` functions fallible. Currently we have an awkward mix of fallible and infallible functions: ``` new_parser_from_source_str maybe_new_parser_from_source_str new_parser_from_file (maybe_new_parser_from_file) // missing (new_parser_from_source_file) // missing maybe_new_parser_from_source_file source_str_to_stream maybe_source_file_to_stream ``` We could add the two missing functions, but instead this commit removes of all the infallible ones and renames the fallible ones leaving us with these which are all fallible: ``` new_parser_from_source_str new_parser_from_file new_parser_from_source_file source_str_to_stream source_file_to_stream ``` This requires making `unwrap_or_emit_fatal` public so callers of formerly infallible functions can still work. This does make some of the call sites slightly more verbose, but I think it's worth it for the simpler API. Also, there are two `catch_unwind` calls and one `catch_fatal_errors` call in this diff that become removable thanks this change. (I will do that in a follow-up PR.) --- clippy_lints/src/doc/needless_doctest_main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/doc/needless_doctest_main.rs b/clippy_lints/src/doc/needless_doctest_main.rs index 651f2ebaee6f..c3e3c0431e6b 100644 --- a/clippy_lints/src/doc/needless_doctest_main.rs +++ b/clippy_lints/src/doc/needless_doctest_main.rs @@ -8,7 +8,7 @@ use rustc_data_structures::sync::Lrc; use rustc_errors::emitter::HumanEmitter; use rustc_errors::{Diag, DiagCtxt}; use rustc_lint::LateContext; -use rustc_parse::maybe_new_parser_from_source_str; +use rustc_parse::new_parser_from_source_str; use rustc_parse::parser::ForceCollect; use rustc_session::parse::ParseSess; use rustc_span::edition::Edition; @@ -50,7 +50,7 @@ pub fn check( let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); let psess = ParseSess::with_dcx(dcx, sm); - let mut parser = match maybe_new_parser_from_source_str(&psess, filename, code) { + let mut parser = match new_parser_from_source_str(&psess, filename, code) { Ok(p) => p, Err(errs) => { errs.into_iter().for_each(Diag::cancel); From 38de6e1f3a48fc7a18448a3117f2db72c5fb356e Mon Sep 17 00:00:00 2001 From: Boxy Date: Tue, 4 Jun 2024 07:01:58 +0100 Subject: [PATCH 17/56] Misc fixes to cranelift/clippy/miri --- clippy_lints/src/large_const_arrays.rs | 2 +- clippy_lints/src/large_stack_arrays.rs | 2 +- clippy_lints/src/matches/overlapping_arms.rs | 4 ++-- clippy_lints/src/zero_repeat_side_effects.rs | 2 +- clippy_utils/src/lib.rs | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/large_const_arrays.rs b/clippy_lints/src/large_const_arrays.rs index b561054b5824..77d05020c828 100644 --- a/clippy_lints/src/large_const_arrays.rs +++ b/clippy_lints/src/large_const_arrays.rs @@ -54,7 +54,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeConstArrays { && generics.params.is_empty() && !generics.has_where_clause_predicates && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() && let ty::Array(element_type, cst) = ty.kind() - && let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind() + && let ConstKind::Value(_, ty::ValTree::Leaf(element_count)) = cst.kind() && let Ok(element_count) = element_count.try_to_target_usize(cx.tcx) && let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()) && self.maximum_allowed_size < u128::from(element_count) * u128::from(element_size) diff --git a/clippy_lints/src/large_stack_arrays.rs b/clippy_lints/src/large_stack_arrays.rs index 208d1bb6e68a..f0f3f53647b9 100644 --- a/clippy_lints/src/large_stack_arrays.rs +++ b/clippy_lints/src/large_stack_arrays.rs @@ -64,7 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackArrays { if let ExprKind::Repeat(_, _) | ExprKind::Array(_) = expr.kind && !self.is_from_vec_macro(cx, expr.span) && let ty::Array(element_type, cst) = cx.typeck_results().expr_ty(expr).kind() - && let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind() + && let ConstKind::Value(_, ty::ValTree::Leaf(element_count)) = cst.kind() && let Ok(element_count) = element_count.try_to_target_usize(cx.tcx) && let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()) && !cx.tcx.hir().parent_iter(expr.hir_id).any(|(_, node)| { diff --git a/clippy_lints/src/matches/overlapping_arms.rs b/clippy_lints/src/matches/overlapping_arms.rs index 8199366d175f..45b375dbe3d7 100644 --- a/clippy_lints/src/matches/overlapping_arms.rs +++ b/clippy_lints/src/matches/overlapping_arms.rs @@ -37,14 +37,14 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) Some(lhs) => constant(cx, cx.typeck_results(), lhs)?, None => { let min_val_const = ty.numeric_min_val(cx.tcx)?; - mir_to_const(cx, mir::Const::from_ty_const(min_val_const, cx.tcx))? + mir_to_const(cx, mir::Const::from_ty_const(min_val_const, ty, cx.tcx))? }, }; let rhs_const = match rhs { Some(rhs) => constant(cx, cx.typeck_results(), rhs)?, None => { let max_val_const = ty.numeric_max_val(cx.tcx)?; - mir_to_const(cx, mir::Const::from_ty_const(max_val_const, cx.tcx))? + mir_to_const(cx, mir::Const::from_ty_const(max_val_const, ty, cx.tcx))? }, }; let lhs_val = lhs_const.int_value(cx, ty)?; diff --git a/clippy_lints/src/zero_repeat_side_effects.rs b/clippy_lints/src/zero_repeat_side_effects.rs index 143fecdd237d..848b49130dc2 100644 --- a/clippy_lints/src/zero_repeat_side_effects.rs +++ b/clippy_lints/src/zero_repeat_side_effects.rs @@ -55,7 +55,7 @@ impl LateLintPass<'_> for ZeroRepeatSideEffects { inner_check(cx, expr, inner_expr, true); } else if let ExprKind::Repeat(inner_expr, _) = expr.kind && let ty::Array(_, cst) = cx.typeck_results().expr_ty(expr).kind() - && let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind() + && let ConstKind::Value(_, ty::ValTree::Leaf(element_count)) = cst.kind() && let Ok(element_count) = element_count.try_to_target_usize(cx.tcx) && element_count == 0 { diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index b10830b24e1f..1147dce6215f 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1534,7 +1534,7 @@ pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Opti if let rustc_ty::Adt(_, subst) = ty.kind() && let bnd_ty = subst.type_at(0) && let Some(min_val) = bnd_ty.numeric_min_val(cx.tcx) - && let Some(min_const) = mir_to_const(cx, Const::from_ty_const(min_val, cx.tcx)) + && let Some(min_const) = mir_to_const(cx, Const::from_ty_const(min_val, bnd_ty, cx.tcx)) && let Some(start_const) = constant(cx, cx.typeck_results(), start) { start_const == min_const @@ -1547,7 +1547,7 @@ pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Opti if let rustc_ty::Adt(_, subst) = ty.kind() && let bnd_ty = subst.type_at(0) && let Some(max_val) = bnd_ty.numeric_max_val(cx.tcx) - && let Some(max_const) = mir_to_const(cx, Const::from_ty_const(max_val, cx.tcx)) + && let Some(max_const) = mir_to_const(cx, Const::from_ty_const(max_val, bnd_ty, cx.tcx)) && let Some(end_const) = constant(cx, cx.typeck_results(), end) { end_const == max_const From c245cde61cbe914dcf5c46a072a2b3cd0c694f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Thu, 6 Jun 2024 09:45:50 +0000 Subject: [PATCH 18/56] Revert "Rollup merge of #124976 - petrochenkov:usedcrates, r=oli-obk" This reverts commit eda4a35f365535af72118118a3597edf5a13c12d, reversing changes made to eb6b35b5bcb3c2a594cb29cd478aeb2893f49d30. --- clippy_utils/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 1147dce6215f..2f6bf9209677 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -647,7 +647,7 @@ fn item_children_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) -> Vec, path: &[&str]) -> Vec { fn find_crates(tcx: TyCtxt<'_>, name: Symbol) -> impl Iterator + '_ { - tcx.crates_including_speculative(()) + tcx.crates(()) .iter() .copied() .filter(move |&num| tcx.crate_name(num) == name) From 7ab4af336cdb8b13cb39e192913f84e8b7f30dda Mon Sep 17 00:00:00 2001 From: John Arundel Date: Mon, 3 Jun 2024 12:34:05 +0100 Subject: [PATCH 19/56] [ needless_for_each ]: fix doc nits --- clippy_lints/src/needless_for_each.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/needless_for_each.rs b/clippy_lints/src/needless_for_each.rs index 630018238f4c..143acc2b1cb7 100644 --- a/clippy_lints/src/needless_for_each.rs +++ b/clippy_lints/src/needless_for_each.rs @@ -25,14 +25,14 @@ declare_clippy_lint! { /// ```no_run /// let v = vec![0, 1, 2]; /// v.iter().for_each(|elem| { - /// println!("{}", elem); + /// println!("{elem}"); /// }) /// ``` /// Use instead: /// ```no_run /// let v = vec![0, 1, 2]; - /// for elem in v.iter() { - /// println!("{}", elem); + /// for elem in &v { + /// println!("{elem}"); /// } /// ``` /// From 35d284fcbf947089315beccb6d1af7396d09d5d3 Mon Sep 17 00:00:00 2001 From: John Arundel Date: Mon, 3 Jun 2024 12:36:01 +0100 Subject: [PATCH 20/56] [ allow_attributes ]: fix doc nits --- clippy_lints/src/allow_attributes.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/allow_attributes.rs b/clippy_lints/src/allow_attributes.rs index 123d0e51eeee..990f724ab9d9 100644 --- a/clippy_lints/src/allow_attributes.rs +++ b/clippy_lints/src/allow_attributes.rs @@ -10,20 +10,19 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does /// Checks for usage of the `#[allow]` attribute and suggests replacing it with - /// the `#[expect]` (See [RFC 2383](https://rust-lang.github.io/rfcs/2383-lint-reasons.html)) + /// `#[expect]`. (See [RFC 2383](https://rust-lang.github.io/rfcs/2383-lint-reasons.html)) /// - /// The expect attribute is still unstable and requires the `lint_reasons` + /// The expect attribute is still unstable and requires the `lint_reasons` feature /// on nightly. It can be enabled by adding `#![feature(lint_reasons)]` to /// the crate root. /// - /// This lint only warns outer attributes (`#[allow]`), as inner attributes + /// This lint only warns on outer attributes (`#[allow]`), as inner attributes /// (`#![allow]`) are usually used to enable or disable lints on a global scale. /// /// ### Why restrict this? - /// `#[allow]` attributes can linger after their reason for existence is gone. - /// `#[expect]` attributes suppress the lint emission, but emit a warning if - /// the expectation is unfulfilled. This can be useful to be notified when the - /// lint is no longer triggered, which may indicate the attribute can be removed. + /// The `#[allow]` attribute does not warn when the expected lint is no longer triggered, + /// whereas `#[expect]` calls attention to this fact. This can be a useful reminder to + /// remove attributes that are no longer needed. /// /// ### Example /// ```rust,ignore From 8da5d64669c18b52c4edaaf71a739585d97a5767 Mon Sep 17 00:00:00 2001 From: John Arundel Date: Mon, 3 Jun 2024 12:38:25 +0100 Subject: [PATCH 21/56] [ allow_attributes_without_reason ]: fix doc nits --- clippy_lints/src/attrs/mod.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs index 83c828e8e223..bd10ebbe5f5d 100644 --- a/clippy_lints/src/attrs/mod.rs +++ b/clippy_lints/src/attrs/mod.rs @@ -270,13 +270,14 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for attributes that allow lints without a reason. - /// - /// (This requires the `lint_reasons` feature) + /// Checks for attributes that allow lints without specifying the reason + /// they should be allowed. (This requires the `lint_reasons` feature.) /// /// ### Why restrict this? - /// Justifying each `allow` helps readers understand the reasoning, - /// and may allow removing `allow` attributes if their purpose is obsolete. + /// There should always be a specific reason to allow a lint. This reason + /// should be documented using the `reason` parameter, so that readers can + /// understand why the `allow` is required, or remove it if it's no + /// longer needed. /// /// ### Example /// ```no_run From 7c86db4ea5f051e0b355e37959264bd9998a17a9 Mon Sep 17 00:00:00 2001 From: John Arundel Date: Mon, 3 Jun 2024 12:41:21 +0100 Subject: [PATCH 22/56] [ mut_range_bound ]: fix doc nits --- clippy_lints/src/loops/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 086829421530..64ea591993da 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -356,10 +356,10 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for loops which have a range bound that is a mutable variable + /// Checks for loops with a range bound that is a mutable variable. /// /// ### Why is this bad? - /// One might think that modifying the mutable variable changes the loop bounds + /// One might think that modifying the mutable variable changes the loop bounds. It doesn't. /// /// ### Known problems /// False positive when mutation is followed by a `break`, but the `break` is not immediately @@ -381,7 +381,7 @@ declare_clippy_lint! { /// let mut foo = 42; /// for i in 0..foo { /// foo -= 1; - /// println!("{}", i); // prints numbers from 0 to 42, not 0 to 21 + /// println!("{i}"); // prints numbers from 0 to 41, not 0 to 21 /// } /// ``` #[clippy::version = "pre 1.29.0"] From 1e23449678e4c986b609ed7a82ae6497e7b6ddd1 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 25 May 2024 18:15:14 +0200 Subject: [PATCH 23/56] Move `is_receiver_of_method_call` to `clippy_utils` for reuse --- clippy_lints/src/methods/search_is_some.rs | 12 +----------- clippy_utils/src/lib.rs | 11 +++++++++++ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/clippy_lints/src/methods/search_is_some.rs b/clippy_lints/src/methods/search_is_some.rs index f5f1e94bbf45..3b2dd285b8c9 100644 --- a/clippy_lints/src/methods/search_is_some.rs +++ b/clippy_lints/src/methods/search_is_some.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::sugg::deref_closure_args; use clippy_utils::ty::is_type_lang_item; -use clippy_utils::{get_parent_expr, is_trait_method, strip_pat_refs}; +use clippy_utils::{is_receiver_of_method_call, is_trait_method, strip_pat_refs}; use hir::ExprKind; use rustc_errors::Applicability; use rustc_hir as hir; @@ -156,13 +156,3 @@ pub(super) fn check<'tcx>( } } } - -fn is_receiver_of_method_call(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - if let Some(parent_expr) = get_parent_expr(cx, expr) - && let ExprKind::MethodCall(_, receiver, ..) = parent_expr.kind - && receiver.hir_id == expr.hir_id - { - return true; - } - false -} diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 6d6c4ffbe5d6..6b00e793eaab 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -3401,3 +3401,14 @@ pub fn binary_expr_needs_parentheses(expr: &Expr<'_>) -> bool { contains_block(expr, false) } + +/// Returns true if the specified expression is in a receiver position. +pub fn is_receiver_of_method_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let Some(parent_expr) = get_parent_expr(cx, expr) + && let ExprKind::MethodCall(_, receiver, ..) = parent_expr.kind + && receiver.hir_id == expr.hir_id + { + return true; + } + false +} From 35b2aa99f3357a69d09f247ed5aff64cdd7765b1 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 25 May 2024 18:08:14 +0200 Subject: [PATCH 24/56] Add required parentheses around method receiver --- clippy_lints/src/needless_bool.rs | 13 ++++++++++--- tests/ui/needless_bool/fixable.fixed | 12 ++++++++++++ tests/ui/needless_bool/fixable.rs | 12 ++++++++++++ tests/ui/needless_bool/fixable.stderr | 20 +++++++++++++++++++- 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index e1866eaa18a7..9cb4fa41c73f 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -6,8 +6,8 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use clippy_utils::{ - higher, is_block_like, is_else_clause, is_expn_of, is_parent_stmt, peel_blocks, peel_blocks_with_stmt, - span_extract_comment, SpanlessEq, + get_parent_expr, higher, is_block_like, is_else_clause, is_expn_of, is_parent_stmt, is_receiver_of_method_call, + peel_blocks, peel_blocks_with_stmt, span_extract_comment, SpanlessEq, }; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; @@ -154,7 +154,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool { snip = snip.blockify(); } - if condition_needs_parentheses(cond) && is_parent_stmt(cx, e.hir_id) { + if (condition_needs_parentheses(cond) && is_parent_stmt(cx, e.hir_id)) + || is_receiver_of_method_call(cx, e) + || is_as_argument(cx, e) + { snip = snip.maybe_par(); } @@ -442,3 +445,7 @@ fn fetch_assign<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, bool) None } } + +fn is_as_argument(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { + matches!(get_parent_expr(cx, e).map(|e| e.kind), Some(ExprKind::Cast(_, _))) +} diff --git a/tests/ui/needless_bool/fixable.fixed b/tests/ui/needless_bool/fixable.fixed index 3059de8f89c4..ec63c4fd6a26 100644 --- a/tests/ui/needless_bool/fixable.fixed +++ b/tests/ui/needless_bool/fixable.fixed @@ -131,3 +131,15 @@ fn needless_bool_condition() -> bool { foo() } + +fn issue12846() { + let a = true; + let b = false; + + // parentheses are needed here + let _x = (a && b).then(|| todo!()); + let _x = (a && b) as u8; + + // parentheses are not needed here + let _x = a.then(|| todo!()); +} diff --git a/tests/ui/needless_bool/fixable.rs b/tests/ui/needless_bool/fixable.rs index b2cbe86e2235..8694aa715908 100644 --- a/tests/ui/needless_bool/fixable.rs +++ b/tests/ui/needless_bool/fixable.rs @@ -191,3 +191,15 @@ fn needless_bool_condition() -> bool { foo() } + +fn issue12846() { + let a = true; + let b = false; + + // parentheses are needed here + let _x = if a && b { true } else { false }.then(|| todo!()); + let _x = if a && b { true } else { false } as u8; + + // parentheses are not needed here + let _x = if a { true } else { false }.then(|| todo!()); +} diff --git a/tests/ui/needless_bool/fixable.stderr b/tests/ui/needless_bool/fixable.stderr index 9746e931f50f..99b5b9983448 100644 --- a/tests/ui/needless_bool/fixable.stderr +++ b/tests/ui/needless_bool/fixable.stderr @@ -191,5 +191,23 @@ error: this if-then-else expression returns a bool literal LL | if unsafe { no(4) } & 1 != 0 { true } else { false } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `(unsafe { no(4) } & 1 != 0)` -error: aborting due to 21 previous errors +error: this if-then-else expression returns a bool literal + --> tests/ui/needless_bool/fixable.rs:200:14 + | +LL | let _x = if a && b { true } else { false }.then(|| todo!()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `(a && b)` + +error: this if-then-else expression returns a bool literal + --> tests/ui/needless_bool/fixable.rs:201:14 + | +LL | let _x = if a && b { true } else { false } as u8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `(a && b)` + +error: this if-then-else expression returns a bool literal + --> tests/ui/needless_bool/fixable.rs:204:14 + | +LL | let _x = if a { true } else { false }.then(|| todo!()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `a` + +error: aborting due to 24 previous errors From 5ea5f6351e29ebbac17d1d45d87322bec8793b83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Thu, 6 Jun 2024 20:39:54 +0000 Subject: [PATCH 25/56] Revert "Rollup merge of #124099 - voidc:disallow-ambiguous-expr-attrs, r=davidtwco" This reverts commit 57dad1d75e562ff73051c1c43b07eaf65c7dbd74, reversing changes made to 36316df9fe6c3e246153fe6e78967643cf08c148. --- tests/ui/cfg_attr_rustfmt.fixed | 6 +++--- tests/ui/cfg_attr_rustfmt.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/ui/cfg_attr_rustfmt.fixed b/tests/ui/cfg_attr_rustfmt.fixed index 84dac431169a..05d5b3d10eaf 100644 --- a/tests/ui/cfg_attr_rustfmt.fixed +++ b/tests/ui/cfg_attr_rustfmt.fixed @@ -16,7 +16,7 @@ fn foo( fn skip_on_statements() { #[rustfmt::skip] - { 5+3; } + 5+3; } #[rustfmt::skip] @@ -33,11 +33,11 @@ mod foo { #[clippy::msrv = "1.29"] fn msrv_1_29() { #[cfg_attr(rustfmt, rustfmt::skip)] - { 1+29; } + 1+29; } #[clippy::msrv = "1.30"] fn msrv_1_30() { #[rustfmt::skip] - { 1+30; } + 1+30; } diff --git a/tests/ui/cfg_attr_rustfmt.rs b/tests/ui/cfg_attr_rustfmt.rs index 4ab5c70e13b5..bc29e20210e8 100644 --- a/tests/ui/cfg_attr_rustfmt.rs +++ b/tests/ui/cfg_attr_rustfmt.rs @@ -16,7 +16,7 @@ fn foo( fn skip_on_statements() { #[cfg_attr(rustfmt, rustfmt::skip)] - { 5+3; } + 5+3; } #[cfg_attr(rustfmt, rustfmt_skip)] @@ -33,11 +33,11 @@ mod foo { #[clippy::msrv = "1.29"] fn msrv_1_29() { #[cfg_attr(rustfmt, rustfmt::skip)] - { 1+29; } + 1+29; } #[clippy::msrv = "1.30"] fn msrv_1_30() { #[cfg_attr(rustfmt, rustfmt::skip)] - { 1+30; } + 1+30; } From 246d4fe79120976d484a4a57359ee951aedb4ce9 Mon Sep 17 00:00:00 2001 From: Slanterns Date: Fri, 7 Jun 2024 08:37:05 +0800 Subject: [PATCH 26/56] bless `std_instead_of_core` --- tests/ui/std_instead_of_core.fixed | 4 ++-- tests/ui/std_instead_of_core.rs | 2 +- tests/ui/std_instead_of_core.stderr | 8 +++++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/ui/std_instead_of_core.fixed b/tests/ui/std_instead_of_core.fixed index ec4ae2ea13c5..6ede7bfcd9f6 100644 --- a/tests/ui/std_instead_of_core.fixed +++ b/tests/ui/std_instead_of_core.fixed @@ -45,8 +45,8 @@ fn std_instead_of_core() { let _ = std::env!("PATH"); - // do not lint until `error_in_core` is stable - use std::error::Error; + use core::error::Error; + //~^ ERROR: used import from `std` instead of `core` // lint items re-exported from private modules, `core::iter::traits::iterator::Iterator` use core::iter::Iterator; diff --git a/tests/ui/std_instead_of_core.rs b/tests/ui/std_instead_of_core.rs index c12c459c7eb4..e22b4f61f3ec 100644 --- a/tests/ui/std_instead_of_core.rs +++ b/tests/ui/std_instead_of_core.rs @@ -45,8 +45,8 @@ fn std_instead_of_core() { let _ = std::env!("PATH"); - // do not lint until `error_in_core` is stable use std::error::Error; + //~^ ERROR: used import from `std` instead of `core` // lint items re-exported from private modules, `core::iter::traits::iterator::Iterator` use std::iter::Iterator; diff --git a/tests/ui/std_instead_of_core.stderr b/tests/ui/std_instead_of_core.stderr index 8f920511cc5d..22cb9db7050b 100644 --- a/tests/ui/std_instead_of_core.stderr +++ b/tests/ui/std_instead_of_core.stderr @@ -49,6 +49,12 @@ error: used import from `std` instead of `core` LL | let cell_absolute = ::std::cell::Cell::new(8u32); | ^^^ help: consider importing the item from `core`: `core` +error: used import from `std` instead of `core` + --> tests/ui/std_instead_of_core.rs:48:9 + | +LL | use std::error::Error; + | ^^^ help: consider importing the item from `core`: `core` + error: used import from `std` instead of `core` --> tests/ui/std_instead_of_core.rs:52:9 | @@ -79,5 +85,5 @@ LL | use alloc::slice::from_ref; = note: `-D clippy::alloc-instead-of-core` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::alloc_instead_of_core)]` -error: aborting due to 12 previous errors +error: aborting due to 13 previous errors From abd011638dffe2586a382b4e89823bf2b84b5a4d Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Mon, 3 Jun 2024 09:11:58 +0000 Subject: [PATCH 27/56] Revert "Create const block DefIds in typeck instead of ast lowering" This reverts commit ddc5f9b6c1f21da5d4596bf7980185a00984ac42. --- clippy_utils/src/consts.rs | 6 ++--- clippy_utils/src/hir_utils.rs | 6 ++--- tests/ui/arithmetic_side_effects.stderr | 32 ++++--------------------- 3 files changed, 10 insertions(+), 34 deletions(-) diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index cd88ccd87cf0..5c9cad2b45d4 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -6,7 +6,7 @@ use crate::{clip, is_direct_expn_of, sext, unsext}; use rustc_ast::ast::{self, LitFloatType, LitKind}; use rustc_data_structures::sync::Lrc; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{BinOp, BinOpKind, Block, Expr, ExprKind, HirId, Item, ItemKind, Node, QPath, UnOp}; +use rustc_hir::{BinOp, BinOpKind, Block, ConstBlock, Expr, ExprKind, HirId, Item, ItemKind, Node, QPath, UnOp}; use rustc_lexer::tokenize; use rustc_lint::LateContext; use rustc_middle::mir::interpret::{alloc_range, Scalar}; @@ -412,7 +412,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { /// Simple constant folding: Insert an expression, get a constant or none. pub fn expr(&mut self, e: &Expr<'_>) -> Option> { match e.kind { - ExprKind::ConstBlock(e) | ExprKind::DropTemps(e) => self.expr(e), + ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr(self.lcx.tcx.hir().body(body).value), ExprKind::DropTemps(e) => self.expr(e), ExprKind::Path(ref qpath) => { self.fetch_path_and_apply(qpath, e.hir_id, self.typeck_results.expr_ty(e), |this, result| { let result = mir_to_const(this.lcx, result)?; @@ -490,7 +490,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { /// leaves the local crate. pub fn expr_is_empty(&mut self, e: &Expr<'_>) -> Option { match e.kind { - ExprKind::ConstBlock(e) | ExprKind::DropTemps(e) => self.expr_is_empty(e), + ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr_is_empty(self.lcx.tcx.hir().body(body).value), ExprKind::DropTemps(e) => self.expr_is_empty(e), ExprKind::Path(ref qpath) => { if !self .typeck_results diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index c649c1794684..36634817fc91 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -295,7 +295,7 @@ impl HirEqInterExpr<'_, '_, '_> { self.eq_expr(lx, rx) && self.eq_ty(lt, rt) }, (&ExprKind::Closure(_l), &ExprKind::Closure(_r)) => false, - (&ExprKind::ConstBlock(lb), &ExprKind::ConstBlock(rb)) => self.eq_expr(lb, rb), + (&ExprKind::ConstBlock(lb), &ExprKind::ConstBlock(rb)) => self.eq_body(lb.body, rb.body), (&ExprKind::Continue(li), &ExprKind::Continue(ri)) => { both(&li.label, &ri.label, |l, r| l.ident.name == r.ident.name) }, @@ -769,8 +769,8 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { // closures inherit TypeckResults self.hash_expr(self.cx.tcx.hir().body(body).value); }, - ExprKind::ConstBlock(l_id) => { - self.hash_expr(l_id); + ExprKind::ConstBlock(ref l_id) => { + self.hash_body(l_id.body); }, ExprKind::DropTemps(e) | ExprKind::Yield(e, _) => { self.hash_expr(e); diff --git a/tests/ui/arithmetic_side_effects.stderr b/tests/ui/arithmetic_side_effects.stderr index df14ff396f6c..8039c0bfa248 100644 --- a/tests/ui/arithmetic_side_effects.stderr +++ b/tests/ui/arithmetic_side_effects.stderr @@ -1,35 +1,11 @@ -error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:188:36 - | -LL | let _ = const { let mut n = 1; n += 1; n }; - | ^^^^^^ - | - = note: `-D clippy::arithmetic-side-effects` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::arithmetic_side_effects)]` - -error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:191:40 - | -LL | let _ = const { let mut n = 1; n = n + 1; n }; - | ^^^^^ - -error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:194:40 - | -LL | let _ = const { let mut n = 1; n = 1 + n; n }; - | ^^^^^ - -error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:200:59 - | -LL | let _ = const { let mut n = 1; n = -1; n = -(-1); n = -n; n }; - | ^^ - error: arithmetic operation that can potentially result in unexpected side-effects --> tests/ui/arithmetic_side_effects.rs:304:5 | LL | _n += 1; | ^^^^^^^ + | + = note: `-D clippy::arithmetic-side-effects` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::arithmetic_side_effects)]` error: arithmetic operation that can potentially result in unexpected side-effects --> tests/ui/arithmetic_side_effects.rs:305:5 @@ -751,5 +727,5 @@ error: arithmetic operation that can potentially result in unexpected side-effec LL | one.sub_assign(1); | ^^^^^^^^^^^^^^^^^ -error: aborting due to 125 previous errors +error: aborting due to 121 previous errors From b21ee38fa91b8a917e7cde983806c77a9b0c5c99 Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Wed, 15 May 2024 17:30:11 +0000 Subject: [PATCH 28/56] Don't lint blocks in closures for blocks_in_conditions --- clippy_lints/src/blocks_in_conditions.rs | 32 +------ tests/ui/blocks_in_conditions.fixed | 13 +++ tests/ui/blocks_in_conditions.rs | 13 +++ tests/ui/blocks_in_conditions_closure.rs | 89 -------------------- tests/ui/blocks_in_conditions_closure.stderr | 39 --------- 5 files changed, 28 insertions(+), 158 deletions(-) delete mode 100644 tests/ui/blocks_in_conditions_closure.rs delete mode 100644 tests/ui/blocks_in_conditions_closure.stderr diff --git a/clippy_lints/src/blocks_in_conditions.rs b/clippy_lints/src/blocks_in_conditions.rs index 869ed332b9c9..eb05dc96cdeb 100644 --- a/clippy_lints/src/blocks_in_conditions.rs +++ b/clippy_lints/src/blocks_in_conditions.rs @@ -1,15 +1,11 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; +use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_block_with_applicability; -use clippy_utils::ty::implements_trait; -use clippy_utils::visitors::{for_each_expr_without_closures, Descend}; -use clippy_utils::{get_parent_expr, higher, is_from_proc_macro}; -use core::ops::ControlFlow; +use clippy_utils::{higher, is_from_proc_macro}; use rustc_errors::Applicability; use rustc_hir::{BlockCheckMode, Expr, ExprKind, MatchSource}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_session::declare_lint_pass; -use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -124,30 +120,6 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInConditions { ); } } - } else { - let _: Option = for_each_expr_without_closures(cond, |e| { - if let ExprKind::Closure(closure) = e.kind { - // do not lint if the closure is called using an iterator (see #1141) - if let Some(parent) = get_parent_expr(cx, e) - && let ExprKind::MethodCall(_, self_arg, _, _) = &parent.kind - && let caller = cx.typeck_results().expr_ty(self_arg) - && let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator) - && implements_trait(cx, caller, iter_id, &[]) - { - return ControlFlow::Continue(Descend::No); - } - - let body = cx.tcx.hir().body(closure.body); - let ex = &body.value; - if let ExprKind::Block(block, _) = ex.kind { - if !body.value.span.from_expansion() && !block.stmts.is_empty() { - span_lint(cx, BLOCKS_IN_CONDITIONS, ex.span, complex_block_message.clone()); - return ControlFlow::Continue(Descend::No); - } - } - } - ControlFlow::Continue(Descend::Yes) - }); } } } diff --git a/tests/ui/blocks_in_conditions.fixed b/tests/ui/blocks_in_conditions.fixed index a2da5f9c5fb9..af8e65270d09 100644 --- a/tests/ui/blocks_in_conditions.fixed +++ b/tests/ui/blocks_in_conditions.fixed @@ -117,4 +117,17 @@ mod issue_12016 { } } +fn in_closure() { + let v = vec![1, 2, 3]; + if v.into_iter() + .filter(|x| { + let y = x + 1; + y > 3 + }) + .any(|x| x == 5) + { + println!("contains 4!"); + } +} + fn main() {} diff --git a/tests/ui/blocks_in_conditions.rs b/tests/ui/blocks_in_conditions.rs index 608ca4cf267f..6adae951a290 100644 --- a/tests/ui/blocks_in_conditions.rs +++ b/tests/ui/blocks_in_conditions.rs @@ -117,4 +117,17 @@ mod issue_12016 { } } +fn in_closure() { + let v = vec![1, 2, 3]; + if v.into_iter() + .filter(|x| { + let y = x + 1; + y > 3 + }) + .any(|x| x == 5) + { + println!("contains 4!"); + } +} + fn main() {} diff --git a/tests/ui/blocks_in_conditions_closure.rs b/tests/ui/blocks_in_conditions_closure.rs deleted file mode 100644 index db31e4ae1a9a..000000000000 --- a/tests/ui/blocks_in_conditions_closure.rs +++ /dev/null @@ -1,89 +0,0 @@ -#![warn(clippy::blocks_in_conditions)] -#![allow( - unused, - clippy::let_and_return, - clippy::needless_if, - clippy::unnecessary_literal_unwrap -)] - -fn predicate bool, T>(pfn: F, val: T) -> bool { - pfn(val) -} - -fn pred_test() { - let v = 3; - let sky = "blue"; - // This is a sneaky case, where the block isn't directly in the condition, - // but is actually inside a closure that the condition is using. - // The same principle applies -- add some extra expressions to make sure - // linter isn't confused by them. - if v == 3 - && sky == "blue" - && predicate( - |x| { - //~^ ERROR: in an `if` condition, avoid complex blocks or closures with blocks - //~| NOTE: `-D clippy::blocks-in-conditions` implied by `-D warnings` - let target = 3; - x == target - }, - v, - ) - {} - - if predicate( - |x| { - //~^ ERROR: in an `if` condition, avoid complex blocks or closures with blocks; in - let target = 3; - x == target - }, - v, - ) {} -} - -fn closure_without_block() { - if predicate(|x| x == 3, 6) {} -} - -fn macro_in_closure() { - let option = Some(true); - - if option.unwrap_or_else(|| unimplemented!()) { - unimplemented!() - } -} - -fn closure(_: impl FnMut()) -> bool { - true -} - -fn function_with_empty_closure() { - if closure(|| {}) {} -} - -// issue #11814 -fn match_with_pred() { - let v = 3; - match Some(predicate( - |x| { - //~^ ERROR: in a `match` scrutinee, avoid complex blocks or closures with blocks - let target = 3; - x == target - }, - v, - )) { - Some(true) => 1, - Some(false) => 2, - None => 3, - }; -} - -#[rustfmt::skip] -fn main() { - let mut range = 0..10; - range.all(|i| {i < 10} ); - - let v = vec![1, 2, 3]; - if v.into_iter().any(|x| {x == 4}) { - println!("contains 4!"); - } -} diff --git a/tests/ui/blocks_in_conditions_closure.stderr b/tests/ui/blocks_in_conditions_closure.stderr deleted file mode 100644 index 2faae680ec02..000000000000 --- a/tests/ui/blocks_in_conditions_closure.stderr +++ /dev/null @@ -1,39 +0,0 @@ -error: in an `if` condition, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let` - --> tests/ui/blocks_in_conditions_closure.rs:23:17 - | -LL | |x| { - | _________________^ -LL | | -LL | | -LL | | let target = 3; -LL | | x == target -LL | | }, - | |_____________^ - | - = note: `-D clippy::blocks-in-conditions` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::blocks_in_conditions)]` - -error: in an `if` condition, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let` - --> tests/ui/blocks_in_conditions_closure.rs:34:13 - | -LL | |x| { - | _____________^ -LL | | -LL | | let target = 3; -LL | | x == target -LL | | }, - | |_________^ - -error: in a `match` scrutinee, avoid complex blocks or closures with blocks; instead, move the block or closure higher and bind it with a `let` - --> tests/ui/blocks_in_conditions_closure.rs:67:13 - | -LL | |x| { - | _____________^ -LL | | -LL | | let target = 3; -LL | | x == target -LL | | }, - | |_________^ - -error: aborting due to 3 previous errors - From 65af5d7b22ecd29a5aad8b6849659c9eaf62a96a Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Fri, 7 Jun 2024 12:00:08 +0000 Subject: [PATCH 29/56] Remove `lazy_static` mention --- clippy_lints/src/non_copy_const.rs | 32 ++++++++------ tests/ui/crashes/ice-9445.rs | 2 - tests/ui/crashes/ice-9445.stderr | 7 ++- tests/ui/crashes/mut_mut_macro.rs | 33 -------------- .../enums.stderr | 43 +++++++++---------- .../others.stderr | 27 ++++++------ .../traits.stderr | 26 +++++------ 7 files changed, 69 insertions(+), 101 deletions(-) delete mode 100644 tests/ui/crashes/mut_mut_macro.rs diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index 76d9cee18aa7..464c658b2b43 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -7,7 +7,7 @@ use std::ptr; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::in_constant; use clippy_utils::macros::macro_backtrace; -use clippy_utils::ty::InteriorMut; +use clippy_utils::ty::{implements_trait, InteriorMut}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_hir::{ @@ -18,7 +18,7 @@ use rustc_middle::mir::interpret::{ErrorHandled, EvalToValTreeResult, GlobalId}; use rustc_middle::ty::adjustment::Adjust; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_session::impl_lint_pass; -use rustc_span::{sym, InnerSpan, Span, DUMMY_SP}; +use rustc_span::{sym, Span, DUMMY_SP}; use rustc_target::abi::VariantIdx; // FIXME: this is a correctness problem but there's no suitable @@ -127,19 +127,19 @@ declare_clippy_lint! { } #[derive(Copy, Clone)] -enum Source { - Item { item: Span }, +enum Source<'tcx> { + Item { item: Span, ty: Ty<'tcx> }, Assoc { item: Span }, Expr { expr: Span }, } -impl Source { +impl Source<'_> { #[must_use] fn lint(&self) -> (&'static Lint, &'static str, Span) { match self { - Self::Item { item } | Self::Assoc { item, .. } => ( + Self::Item { item, .. } | Self::Assoc { item, .. } => ( DECLARE_INTERIOR_MUTABLE_CONST, - "a `const` item should never be interior mutable", + "a `const` item should not be interior mutable", *item, ), Self::Expr { expr } => ( @@ -151,16 +151,24 @@ impl Source { } } -fn lint(cx: &LateContext<'_>, source: Source) { +fn lint<'tcx>(cx: &LateContext<'tcx>, source: Source<'tcx>) { let (lint, msg, span) = source.lint(); span_lint_and_then(cx, lint, span, msg, |diag| { if span.from_expansion() { return; // Don't give suggestions into macros. } match source { - Source::Item { .. } => { - let const_kw_span = span.from_inner(InnerSpan::new(0, 5)); - diag.span_label(const_kw_span, "make this a static item (maybe with lazy_static)"); + Source::Item { ty, .. } => { + let Some(sync_trait) = cx.tcx.lang_items().sync_trait() else { + return; + }; + if implements_trait(cx, ty, sync_trait, &[]) { + diag.help("consider making this a static item"); + } else { + diag.help( + "consider making this `Sync` so that it can go in a static item or using a `thread_local`", + ); + } }, Source::Assoc { .. } => (), Source::Expr { .. } => { @@ -311,7 +319,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst<'tcx> { && self.interior_mut.is_interior_mut_ty(cx, ty) && Self::is_value_unfrozen_poly(cx, body_id, ty) { - lint(cx, Source::Item { item: it.span }); + lint(cx, Source::Item { item: it.span, ty }); } } } diff --git a/tests/ui/crashes/ice-9445.rs b/tests/ui/crashes/ice-9445.rs index b6afbd33c79f..c67b22f6f8c4 100644 --- a/tests/ui/crashes/ice-9445.rs +++ b/tests/ui/crashes/ice-9445.rs @@ -1,5 +1,3 @@ const UNINIT: core::mem::MaybeUninit> = core::mem::MaybeUninit::uninit(); -//~^ ERROR: a `const` item should never be interior mutable -//~| NOTE: `-D clippy::declare-interior-mutable-const` implied by `-D warnings` fn main() {} diff --git a/tests/ui/crashes/ice-9445.stderr b/tests/ui/crashes/ice-9445.stderr index d6957e9549d6..76689cd6f5c2 100644 --- a/tests/ui/crashes/ice-9445.stderr +++ b/tests/ui/crashes/ice-9445.stderr @@ -1,11 +1,10 @@ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/crashes/ice-9445.rs:1:1 | LL | const UNINIT: core::mem::MaybeUninit> = core::mem::MaybeUninit::uninit(); - | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | make this a static item (maybe with lazy_static) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = help: consider making this `Sync` so that it can go in a static item or using a `thread_local` = note: `-D clippy::declare-interior-mutable-const` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::declare_interior_mutable_const)]` diff --git a/tests/ui/crashes/mut_mut_macro.rs b/tests/ui/crashes/mut_mut_macro.rs deleted file mode 100644 index 92821b6ecbba..000000000000 --- a/tests/ui/crashes/mut_mut_macro.rs +++ /dev/null @@ -1,33 +0,0 @@ -#![deny(clippy::mut_mut, clippy::zero_ptr)] -#![allow(dead_code)] - -// FIXME: compiletest + extern crates doesn't work together. To make this test work, it would need -// the following three lines and the lazy_static crate. -// -// #[macro_use] -// extern crate lazy_static; -// use std::collections::HashMap; - -/// ensure that we don't suggest `is_null` inside constants -/// FIXME: once const fn is stable, suggest these functions again in constants - -const BAA: *const i32 = 0 as *const i32; -static mut BAR: *const i32 = BAA; -static mut FOO: *const i32 = 0 as *const i32; - -#[allow(unused_variables, unused_mut)] -fn main() { - /* - lazy_static! { - static ref MUT_MAP : HashMap = { - let mut m = HashMap::new(); - m.insert(0, "zero"); - m - }; - static ref MUT_COUNT : usize = MUT_MAP.len(); - } - assert_eq!(*MUT_COUNT, 1); - */ - // FIXME: don't lint in array length, requires `check_body` - //let _ = [""; (42.0 < f32::NAN) as usize]; -} diff --git a/tests/ui/declare_interior_mutable_const/enums.stderr b/tests/ui/declare_interior_mutable_const/enums.stderr index 6c0dce6b5eaf..22329172c3af 100644 --- a/tests/ui/declare_interior_mutable_const/enums.stderr +++ b/tests/ui/declare_interior_mutable_const/enums.stderr @@ -1,87 +1,84 @@ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/enums.rs:12:1 | LL | const UNFROZEN_VARIANT: OptionalCell = OptionalCell::Unfrozen(Cell::new(true)); - | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | make this a static item (maybe with lazy_static) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = help: consider making this `Sync` so that it can go in a static item or using a `thread_local` = note: `-D clippy::declare-interior-mutable-const` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::declare_interior_mutable_const)]` -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/enums.rs:23:1 | LL | const UNFROZEN_VARIANT_FROM_FN: OptionalCell = unfrozen_variant(); - | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | make this a static item (maybe with lazy_static) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider making this `Sync` so that it can go in a static item or using a `thread_local` -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/enums.rs:45:1 | -LL | const NESTED_UNFROZEN_VARIANT: NestedOutermost = NestedOutermost { - | ^---- - | | - | _make this a static item (maybe with lazy_static) - | | +LL | / const NESTED_UNFROZEN_VARIANT: NestedOutermost = NestedOutermost { LL | | LL | | outer: NestedOuter::NestedInner(NestedInner { LL | | inner: NestedInnermost::Unfrozen(AtomicUsize::new(2)), LL | | }), LL | | }; | |__^ + | + = help: consider making this a static item -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/enums.rs:60:5 | LL | const TO_BE_UNFROZEN_VARIANT: OptionalCell; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/enums.rs:61:5 | LL | const TO_BE_FROZEN_VARIANT: OptionalCell; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/enums.rs:64:5 | LL | const DEFAULTED_ON_UNFROZEN_VARIANT: OptionalCell = OptionalCell::Unfrozen(Cell::new(false)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/enums.rs:90:5 | LL | const TO_BE_UNFROZEN_VARIANT: Option = Some(Self::ToBeUnfrozen::new(4)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/enums.rs:102:5 | LL | const UNFROZEN_VARIANT: BothOfCellAndGeneric = BothOfCellAndGeneric::Unfrozen(Cell::new(std::ptr::null())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/enums.rs:105:5 | LL | const GENERIC_VARIANT: BothOfCellAndGeneric = BothOfCellAndGeneric::Generic(std::ptr::null()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/enums.rs:111:5 | LL | const NO_ENUM: Cell<*const T> = Cell::new(std::ptr::null()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/enums.rs:118:5 | LL | / const UNFROZEN_VARIANT: BothOfCellAndGeneric = LL | | BothOfCellAndGeneric::Unfrozen(Cell::new(std::ptr::null())); | |____________________________________________________________________^ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/enums.rs:120:5 | LL | const GENERIC_VARIANT: BothOfCellAndGeneric = BothOfCellAndGeneric::Generic(std::ptr::null()); diff --git a/tests/ui/declare_interior_mutable_const/others.stderr b/tests/ui/declare_interior_mutable_const/others.stderr index 9dba0c952214..1f2b9561ce50 100644 --- a/tests/ui/declare_interior_mutable_const/others.stderr +++ b/tests/ui/declare_interior_mutable_const/others.stderr @@ -1,31 +1,30 @@ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/others.rs:9:1 | LL | const ATOMIC: AtomicUsize = AtomicUsize::new(5); - | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | make this a static item (maybe with lazy_static) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = help: consider making this a static item = note: `-D clippy::declare-interior-mutable-const` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::declare_interior_mutable_const)]` -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/others.rs:10:1 | LL | const CELL: Cell = Cell::new(6); - | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | make this a static item (maybe with lazy_static) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider making this `Sync` so that it can go in a static item or using a `thread_local` -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/others.rs:11:1 | LL | const ATOMIC_TUPLE: ([AtomicUsize; 1], Vec, u8) = ([ATOMIC], Vec::new(), 7); - | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | make this a static item (maybe with lazy_static) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider making this a static item -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/others.rs:16:9 | LL | const $name: $ty = $e; @@ -36,7 +35,7 @@ LL | declare_const!(_ONCE: Once = Once::new()); | = note: this error originates in the macro `declare_const` (in Nightly builds, run with -Z macro-backtrace for more info) -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/others.rs:44:13 | LL | const _BAZ: Cell = Cell::new(0); diff --git a/tests/ui/declare_interior_mutable_const/traits.stderr b/tests/ui/declare_interior_mutable_const/traits.stderr index 1d1e9e2002fa..4a793d985e5e 100644 --- a/tests/ui/declare_interior_mutable_const/traits.stderr +++ b/tests/ui/declare_interior_mutable_const/traits.stderr @@ -1,4 +1,4 @@ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/traits.rs:16:5 | LL | const ATOMIC: AtomicUsize; @@ -7,7 +7,7 @@ LL | const ATOMIC: AtomicUsize; = note: `-D clippy::declare-interior-mutable-const` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::declare_interior_mutable_const)]` -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/traits.rs:9:9 | LL | const $name: $ty = $e; @@ -18,67 +18,67 @@ LL | declare_const!(ANOTHER_ATOMIC: AtomicUsize = Self::ATOMIC); | = note: this error originates in the macro `declare_const` (in Nightly builds, run with -Z macro-backtrace for more info) -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/traits.rs:44:5 | LL | const TO_BE_CONCRETE: AtomicUsize = AtomicUsize::new(11); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/traits.rs:69:5 | LL | const TO_BE_UNFROZEN: Self::ToBeUnfrozen = AtomicUsize::new(13); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/traits.rs:70:5 | LL | const WRAPPED_TO_BE_UNFROZEN: Wrapper = Wrapper(AtomicUsize::new(14)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/traits.rs:89:5 | LL | const BOUNDED: T::ToBeBounded; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/traits.rs:117:5 | LL | const SELF: Self = AtomicUsize::new(17); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/traits.rs:118:5 | LL | const WRAPPED_SELF: Option = Some(AtomicUsize::new(21)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/traits.rs:124:5 | LL | const DIRECT: Cell; | ^^^^^^^^^^^^^^^^^^^^^^ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/traits.rs:125:5 | LL | const INDIRECT: Cell<*const T>; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/traits.rs:129:5 | LL | const DIRECT: Cell = Cell::new(T::DEFAULT); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/traits.rs:141:5 | LL | const ATOMIC: AtomicUsize = AtomicUsize::new(18); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: a `const` item should never be interior mutable +error: a `const` item should not be interior mutable --> tests/ui/declare_interior_mutable_const/traits.rs:147:5 | LL | const BOUNDED_ASSOC_TYPE: T::ToBeBounded = AtomicUsize::new(19); From 1781333ec75b9222446405aa0ab960f2fea004bb Mon Sep 17 00:00:00 2001 From: J-ZhengLi Date: Sat, 8 Jun 2024 00:06:20 +0800 Subject: [PATCH 30/56] [`match_same_arms`]: add a test case with lifetimes --- tests/ui/match_same_arms2.fixed | 17 +++++++++++++++++ tests/ui/match_same_arms2.rs | 18 ++++++++++++++++++ tests/ui/match_same_arms2.stderr | 18 +++++++++++++++++- 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/tests/ui/match_same_arms2.fixed b/tests/ui/match_same_arms2.fixed index fba0cf33b3c2..09e960ddd6a9 100644 --- a/tests/ui/match_same_arms2.fixed +++ b/tests/ui/match_same_arms2.fixed @@ -239,3 +239,20 @@ fn main() { _ => false, }; } + +// issue #8919, fixed on https://github.com/rust-lang/rust/pull/97312 +mod with_lifetime { + enum MaybeStaticStr<'a> { + Static(&'static str), + Borrowed(&'a str), + } + + impl<'a> MaybeStaticStr<'a> { + fn get(&self) -> &'a str { + match *self { + MaybeStaticStr::Borrowed(s) | MaybeStaticStr::Static(s) => s, + //~^ ERROR: this match arm has an identical body to another arm + } + } + } +} diff --git a/tests/ui/match_same_arms2.rs b/tests/ui/match_same_arms2.rs index 8a4e3b325bbf..cc7425135cc4 100644 --- a/tests/ui/match_same_arms2.rs +++ b/tests/ui/match_same_arms2.rs @@ -262,3 +262,21 @@ fn main() { _ => false, }; } + +// issue #8919, fixed on https://github.com/rust-lang/rust/pull/97312 +mod with_lifetime { + enum MaybeStaticStr<'a> { + Static(&'static str), + Borrowed(&'a str), + } + + impl<'a> MaybeStaticStr<'a> { + fn get(&self) -> &'a str { + match *self { + MaybeStaticStr::Static(s) => s, + MaybeStaticStr::Borrowed(s) => s, + //~^ ERROR: this match arm has an identical body to another arm + } + } + } +} diff --git a/tests/ui/match_same_arms2.stderr b/tests/ui/match_same_arms2.stderr index 3d15176ccf99..a5d137c658b7 100644 --- a/tests/ui/match_same_arms2.stderr +++ b/tests/ui/match_same_arms2.stderr @@ -221,5 +221,21 @@ help: and remove this obsolete arm LL - 0 => cfg!(not_enable), | -error: aborting due to 13 previous errors +error: this match arm has an identical body to another arm + --> tests/ui/match_same_arms2.rs:277:17 + | +LL | MaybeStaticStr::Borrowed(s) => s, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | MaybeStaticStr::Borrowed(s) | MaybeStaticStr::Static(s) => s, + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +help: and remove this obsolete arm + | +LL - MaybeStaticStr::Static(s) => s, + | + +error: aborting due to 14 previous errors From 6fdf295664acc28ebb018e1d7b97b9e9e1e8f0ea Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Tue, 16 Apr 2024 20:25:35 +0200 Subject: [PATCH 31/56] let `qualify_min_const_fn` deal with drop checks --- clippy_lints/src/missing_const_for_fn.rs | 15 +-------------- .../ui/missing_const_for_fn/could_be_const.rs | 18 ++++++++++++++++++ .../missing_const_for_fn/could_be_const.stderr | 18 +++++++++++++++++- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs index 9ba19e0a8658..fe636f27a0f0 100644 --- a/clippy_lints/src/missing_const_for_fn.rs +++ b/clippy_lints/src/missing_const_for_fn.rs @@ -1,7 +1,6 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::span_lint; use clippy_utils::qualify_min_const_fn::is_min_const_fn; -use clippy_utils::ty::has_drop; use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, is_from_proc_macro, trait_ref_of_method}; use rustc_hir as hir; use rustc_hir::def_id::CRATE_DEF_ID; @@ -121,10 +120,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { } }, FnKind::Method(_, sig, ..) => { - if trait_ref_of_method(cx, def_id).is_some() - || already_const(sig.header) - || method_accepts_droppable(cx, def_id) - { + if trait_ref_of_method(cx, def_id).is_some() || already_const(sig.header) { return; } }, @@ -162,15 +158,6 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { extract_msrv_attr!(LateContext); } -/// Returns true if any of the method parameters is a type that implements `Drop`. The method -/// can't be made const then, because `drop` can't be const-evaluated. -fn method_accepts_droppable(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { - let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder(); - - // If any of the params are droppable, return true - sig.inputs().iter().any(|&ty| has_drop(cx, ty)) -} - // We don't have to lint on something that's already `const` #[must_use] fn already_const(header: hir::FnHeader) -> bool { 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 06dbbeb31c0d..8e0ede478b56 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.rs +++ b/tests/ui/missing_const_for_fn/could_be_const.rs @@ -141,3 +141,21 @@ mod msrv { let _ = unsafe { bar.val }; } } + +mod issue12677 { + pub struct Wrapper { + pub strings: Vec, + } + + impl Wrapper { + #[must_use] + pub fn new(strings: Vec) -> Self { + Self { strings } + } + + #[must_use] + pub fn empty() -> Self { + Self { strings: Vec::new() } + } + } +} 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 b2cade305637..84ad6357b7bc 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.stderr +++ b/tests/ui/missing_const_for_fn/could_be_const.stderr @@ -130,5 +130,21 @@ LL | | let _ = unsafe { bar.val }; LL | | } | |_____^ -error: aborting due to 14 previous errors +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const.rs:152:9 + | +LL | / pub fn new(strings: Vec) -> Self { +LL | | Self { strings } +LL | | } + | |_________^ + +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const.rs:157:9 + | +LL | / pub fn empty() -> Self { +LL | | Self { strings: Vec::new() } +LL | | } + | |_________^ + +error: aborting due to 16 previous errors From 973f318514cec40bd388f9e6f1acacc9e2bed436 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Tue, 16 Apr 2024 21:27:34 +0200 Subject: [PATCH 32/56] qualify_min_const_fn: ignore cleanup bbs --- clippy_utils/src/qualify_min_const_fn.rs | 10 +++++++--- tests/ui/missing_const_for_fn/could_be_const.rs | 12 ++++++++++++ tests/ui/missing_const_for_fn/could_be_const.stderr | 11 ++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 81e94725a70c..42b10f69c0cd 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -40,9 +40,13 @@ pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv) )?; for bb in &*body.basic_blocks { - check_terminator(tcx, body, bb.terminator(), msrv)?; - for stmt in &bb.statements { - check_statement(tcx, body, def_id, stmt, msrv)?; + // Cleanup blocks are ignored entirely by const eval, so we can too: + // https://github.com/rust-lang/rust/blob/1dea922ea6e74f99a0e97de5cdb8174e4dea0444/compiler/rustc_const_eval/src/transform/check_consts/check.rs#L382 + if !bb.is_cleanup { + check_terminator(tcx, body, bb.terminator(), msrv)?; + for stmt in &bb.statements { + check_statement(tcx, body, def_id, stmt, msrv)?; + } } } Ok(()) 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 8e0ede478b56..58e639cc7fd1 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.rs +++ b/tests/ui/missing_const_for_fn/could_be_const.rs @@ -158,4 +158,16 @@ mod issue12677 { Self { strings: Vec::new() } } } + + pub struct Other { + pub text: String, + pub vec: Vec, + } + + impl Other { + pub fn new(text: String) -> Self { + let vec = Vec::new(); + Self { text, vec } + } + } } 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 84ad6357b7bc..1c61c3e87132 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.stderr +++ b/tests/ui/missing_const_for_fn/could_be_const.stderr @@ -146,5 +146,14 @@ LL | | Self { strings: Vec::new() } LL | | } | |_________^ -error: aborting due to 16 previous errors +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const.rs:168:9 + | +LL | / pub fn new(text: String) -> Self { +LL | | let vec = Vec::new(); +LL | | Self { text, vec } +LL | | } + | |_________^ + +error: aborting due to 17 previous errors From c3d3a3f30164644d64c09812c863415f28604e80 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Fri, 7 Jun 2024 21:54:33 +0200 Subject: [PATCH 33/56] remove unnecessary const error handling in `missing_const_for_fn` --- clippy_lints/src/missing_const_for_fn.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs index fe636f27a0f0..4592324809fa 100644 --- a/clippy_lints/src/missing_const_for_fn.rs +++ b/clippy_lints/src/missing_const_for_fn.rs @@ -147,11 +147,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { let mir = cx.tcx.optimized_mir(def_id); - if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, &self.msrv) { - if cx.tcx.is_const_fn_raw(def_id.to_def_id()) { - cx.tcx.dcx().span_err(span, err); - } - } else { + if let Ok(()) = is_min_const_fn(cx.tcx, mir, &self.msrv) { span_lint(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`"); } } From 059eaf1386e36791785275e22d00b999a5dc12f4 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Fri, 7 Jun 2024 23:44:36 -0400 Subject: [PATCH 34/56] Fix ICE in `upper_case_acronyms` and remove most of the string allocations. --- clippy_lints/src/upper_case_acronyms.rs | 102 ++++++++++++++---------- tests/ui/crashes/ice-12284.rs | 10 +++ 2 files changed, 69 insertions(+), 43 deletions(-) create mode 100644 tests/ui/crashes/ice-12284.rs diff --git a/clippy_lints/src/upper_case_acronyms.rs b/clippy_lints/src/upper_case_acronyms.rs index f376d3496461..72392f8e1f74 100644 --- a/clippy_lints/src/upper_case_acronyms.rs +++ b/clippy_lints/src/upper_case_acronyms.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; -use itertools::Itertools; +use core::mem::replace; use rustc_errors::Applicability; use rustc_hir::{HirId, Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -56,55 +56,71 @@ impl UpperCaseAcronyms { impl_lint_pass!(UpperCaseAcronyms => [UPPER_CASE_ACRONYMS]); -fn correct_ident(ident: &str) -> String { - let ident = ident.chars().rev().collect::(); - let fragments = ident - .split_inclusive(|x: char| !x.is_ascii_lowercase()) - .rev() - .map(|x| x.chars().rev().collect::()); - - let mut ident = fragments.clone().next().unwrap(); - for (ref prev, ref curr) in fragments.tuple_windows() { - if <[&String; 2]>::from((prev, curr)) - .iter() - .all(|s| s.len() == 1 && s.chars().next().unwrap().is_ascii_uppercase()) - { - ident.push_str(&curr.to_ascii_lowercase()); +fn contains_acronym(s: &str) -> bool { + let mut count = 0; + for c in s.chars() { + if c.is_ascii_uppercase() { + count += 1; + if count == 3 { + return true; + } } else { - ident.push_str(curr); + count = 0; } } - ident + count == 2 } fn check_ident(cx: &LateContext<'_>, ident: &Ident, hir_id: HirId, be_aggressive: bool) { - let span = ident.span; - let ident = ident.as_str(); - let corrected = correct_ident(ident); - // warn if we have pure-uppercase idents - // assume that two-letter words are some kind of valid abbreviation like FP for false positive - // (and don't warn) - if (ident.chars().all(|c| c.is_ascii_uppercase()) && ident.len() > 2) - // otherwise, warn if we have SOmeTHING lIKE THIs but only warn with the aggressive - // upper-case-acronyms-aggressive config option enabled - || (be_aggressive && ident != corrected) + let s = ident.as_str(); + + // By default, only warn for upper case identifiers with at least 3 characters. + let replacement = if s.len() > 2 && s.bytes().all(|c| c.is_ascii_uppercase()) { + let mut r = String::with_capacity(s.len()); + let mut s = s.chars(); + r.push(s.next().unwrap()); + r.extend(s.map(|c| c.to_ascii_lowercase())); + r + } else if be_aggressive + // Only lint if the ident starts with an upper case character. + && let unprefixed = s.trim_start_matches('_') + && unprefixed.starts_with(|c: char| c.is_ascii_uppercase()) + && contains_acronym(unprefixed) { - span_lint_hir_and_then( - cx, - UPPER_CASE_ACRONYMS, - hir_id, - span, - format!("name `{ident}` contains a capitalized acronym"), - |diag| { - diag.span_suggestion( - span, - "consider making the acronym lowercase, except the initial letter", - corrected, - Applicability::MaybeIncorrect, - ); - }, - ); - } + let mut r = String::with_capacity(s.len()); + let mut s = s.chars(); + let mut prev_upper = false; + while let Some(c) = s.next() { + r.push( + if replace(&mut prev_upper, c.is_ascii_uppercase()) + && s.clone().next().map_or(true, |c| c.is_ascii_uppercase()) + { + c.to_ascii_lowercase() + } else { + c + }, + ); + } + r + } else { + return; + }; + + span_lint_hir_and_then( + cx, + UPPER_CASE_ACRONYMS, + hir_id, + ident.span, + format!("name `{ident}` contains a capitalized acronym"), + |diag| { + diag.span_suggestion( + ident.span, + "consider making the acronym lowercase, except the initial letter", + replacement, + Applicability::MaybeIncorrect, + ); + }, + ); } impl LateLintPass<'_> for UpperCaseAcronyms { diff --git a/tests/ui/crashes/ice-12284.rs b/tests/ui/crashes/ice-12284.rs new file mode 100644 index 000000000000..8d1dbface8eb --- /dev/null +++ b/tests/ui/crashes/ice-12284.rs @@ -0,0 +1,10 @@ +#![allow(incomplete_features)] +#![feature(unnamed_fields)] + +#[repr(C)] +struct Foo { + _: struct { + }, +} + +fn main() {} From 75e1d72093262ad955262b7cefea05a3d412de7c Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sat, 8 Jun 2024 01:19:05 -0400 Subject: [PATCH 35/56] Normalize type aliases when checking significant drops. --- .../src/significant_drop_tightening.rs | 50 +++++++------------ 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/clippy_lints/src/significant_drop_tightening.rs b/clippy_lints/src/significant_drop_tightening.rs index 038eb92d652b..979d6dc77aed 100644 --- a/clippy_lints/src/significant_drop_tightening.rs +++ b/clippy_lints/src/significant_drop_tightening.rs @@ -1,7 +1,7 @@ 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, peel_hir_expr_unary}; -use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap}; +use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::intravisit::{walk_expr, Visitor}; @@ -12,6 +12,7 @@ use rustc_session::impl_lint_pass; use rustc_span::symbol::Ident; use rustc_span::{sym, Span, DUMMY_SP}; use std::borrow::Cow; +use std::collections::hash_map::Entry; declare_clippy_lint! { /// ### What it does @@ -57,7 +58,6 @@ impl_lint_pass!(SignificantDropTightening<'_> => [SIGNIFICANT_DROP_TIGHTENING]); pub struct SignificantDropTightening<'tcx> { apas: FxIndexMap, /// Auxiliary structure used to avoid having to verify the same type multiple times. - seen_types: FxHashSet>, type_cache: FxHashMap, bool>, } @@ -74,7 +74,7 @@ impl<'tcx> LateLintPass<'tcx> for SignificantDropTightening<'tcx> { self.apas.clear(); let initial_dummy_stmt = dummy_stmt_expr(body.value); let mut ap = AuxParams::new(&mut self.apas, &initial_dummy_stmt); - StmtsChecker::new(&mut ap, cx, &mut self.seen_types, &mut self.type_cache).visit_body(body); + StmtsChecker::new(&mut ap, cx, &mut self.type_cache).visit_body(body); for apa in ap.apas.values() { if apa.counter <= 1 || !apa.has_expensive_expr_after_last_attr { continue; @@ -142,28 +142,25 @@ impl<'tcx> LateLintPass<'tcx> for SignificantDropTightening<'tcx> { /// Checks the existence of the `#[has_significant_drop]` attribute. struct AttrChecker<'cx, 'others, 'tcx> { cx: &'cx LateContext<'tcx>, - seen_types: &'others mut FxHashSet>, type_cache: &'others mut FxHashMap, bool>, } impl<'cx, 'others, 'tcx> AttrChecker<'cx, 'others, 'tcx> { - pub(crate) fn new( - cx: &'cx LateContext<'tcx>, - seen_types: &'others mut FxHashSet>, - type_cache: &'others mut FxHashMap, bool>, - ) -> Self { - seen_types.clear(); - Self { - cx, - seen_types, - type_cache, - } + pub(crate) fn new(cx: &'cx LateContext<'tcx>, type_cache: &'others mut FxHashMap, bool>) -> Self { + Self { cx, type_cache } } fn has_sig_drop_attr(&mut self, ty: Ty<'tcx>) -> bool { - // The borrow checker prevents us from using something fancier like or_insert_with. - if let Some(ty) = self.type_cache.get(&ty) { - return *ty; + let ty = self + .cx + .tcx + .try_normalize_erasing_regions(self.cx.param_env, ty) + .unwrap_or(ty); + match self.type_cache.entry(ty) { + Entry::Occupied(e) => return *e.get(), + Entry::Vacant(e) => { + e.insert(false); + }, } let value = self.has_sig_drop_attr_uncached(ty); self.type_cache.insert(ty, value); @@ -185,7 +182,7 @@ impl<'cx, 'others, 'tcx> AttrChecker<'cx, 'others, 'tcx> { rustc_middle::ty::Adt(a, b) => { for f in a.all_fields() { let ty = f.ty(self.cx.tcx, b); - if !self.has_seen_ty(ty) && self.has_sig_drop_attr(ty) { + if self.has_sig_drop_attr(ty) { return true; } } @@ -205,16 +202,11 @@ impl<'cx, 'others, 'tcx> AttrChecker<'cx, 'others, 'tcx> { _ => false, } } - - fn has_seen_ty(&mut self, ty: Ty<'tcx>) -> bool { - !self.seen_types.insert(ty) - } } struct StmtsChecker<'ap, 'lc, 'others, 'stmt, 'tcx> { ap: &'ap mut AuxParams<'others, 'stmt, 'tcx>, cx: &'lc LateContext<'tcx>, - seen_types: &'others mut FxHashSet>, type_cache: &'others mut FxHashMap, bool>, } @@ -222,15 +214,9 @@ impl<'ap, 'lc, 'others, 'stmt, 'tcx> StmtsChecker<'ap, 'lc, 'others, 'stmt, 'tcx fn new( ap: &'ap mut AuxParams<'others, 'stmt, 'tcx>, cx: &'lc LateContext<'tcx>, - seen_types: &'others mut FxHashSet>, type_cache: &'others mut FxHashMap, bool>, ) -> Self { - Self { - ap, - cx, - seen_types, - type_cache, - } + Self { ap, cx, type_cache } } fn manage_has_expensive_expr_after_last_attr(&mut self) { @@ -288,7 +274,7 @@ impl<'ap, 'lc, 'others, 'stmt, 'tcx> Visitor<'tcx> for StmtsChecker<'ap, 'lc, 'o apa.counter = apa.counter.wrapping_add(1); apa.has_expensive_expr_after_last_attr = false; }; - let mut ac = AttrChecker::new(self.cx, self.seen_types, self.type_cache); + let mut ac = AttrChecker::new(self.cx, self.type_cache); if ac.has_sig_drop_attr(self.cx.typeck_results().expr_ty(expr)) { if let hir::StmtKind::Let(local) = self.ap.curr_stmt.kind && let hir::PatKind::Binding(_, hir_id, ident, _) = local.pat.kind From 2855a876a76e52cee0db2df0488f22b10e228d0f Mon Sep 17 00:00:00 2001 From: Lzu Tao Date: Sun, 26 May 2024 10:50:30 +0700 Subject: [PATCH 36/56] fix typos of author lint --- clippy_lints/src/utils/author.rs | 2 +- tests/ui/author.stdout | 2 +- tests/ui/author/blocks.stdout | 6 +++--- tests/ui/author/call.stdout | 2 +- tests/ui/author/if.stdout | 2 +- tests/ui/author/issue_3849.stdout | 2 +- tests/ui/author/loop.stdout | 2 +- tests/ui/author/macro_in_closure.stdout | 2 +- tests/ui/author/matches.stdout | 4 ++-- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 4448c9ae3df7..a3339cc62a9f 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -733,7 +733,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { match stmt.value.kind { StmtKind::Let(local) => { bind!(self, local); - kind!("Local({local})"); + kind!("Let({local})"); self.option(field!(local.init), "init", |init| { self.expr(init); }); diff --git a/tests/ui/author.stdout b/tests/ui/author.stdout index d448db097a7e..eed704e82fe1 100644 --- a/tests/ui/author.stdout +++ b/tests/ui/author.stdout @@ -1,4 +1,4 @@ -if let StmtKind::Local(local) = stmt.kind +if let StmtKind::Let(local) = stmt.kind && let Some(init) = local.init && let ExprKind::Cast(expr, cast_ty) = init.kind && let TyKind::Path(ref qpath) = cast_ty.kind diff --git a/tests/ui/author/blocks.stdout b/tests/ui/author/blocks.stdout index 80b928dd6cb5..6bf48d5ba4ef 100644 --- a/tests/ui/author/blocks.stdout +++ b/tests/ui/author/blocks.stdout @@ -1,12 +1,12 @@ if let ExprKind::Block(block, None) = expr.kind && block.stmts.len() == 3 - && let StmtKind::Local(local) = block.stmts[0].kind + && let StmtKind::Let(local) = block.stmts[0].kind && let Some(init) = local.init && let ExprKind::Lit(ref lit) = init.kind && let LitKind::Int(42, LitIntType::Signed(IntTy::I32)) = lit.node && let PatKind::Binding(BindingMode::NONE, _, name, None) = local.pat.kind && name.as_str() == "x" - && let StmtKind::Local(local1) = block.stmts[1].kind + && let StmtKind::Let(local1) = block.stmts[1].kind && let Some(init1) = local1.init && let ExprKind::Lit(ref lit1) = init1.kind && let LitKind::Float(_, LitFloatType::Suffixed(FloatTy::F32)) = lit1.node @@ -22,7 +22,7 @@ if let ExprKind::Block(block, None) = expr.kind } if let ExprKind::Block(block, None) = expr.kind && block.stmts.len() == 1 - && let StmtKind::Local(local) = block.stmts[0].kind + && let StmtKind::Let(local) = block.stmts[0].kind && let Some(init) = local.init && let ExprKind::Call(func, args) = init.kind && let ExprKind::Path(ref qpath) = func.kind diff --git a/tests/ui/author/call.stdout b/tests/ui/author/call.stdout index f040f6330a64..59d4da490fe5 100644 --- a/tests/ui/author/call.stdout +++ b/tests/ui/author/call.stdout @@ -1,4 +1,4 @@ -if let StmtKind::Local(local) = stmt.kind +if let StmtKind::Let(local) = stmt.kind && let Some(init) = local.init && let ExprKind::Call(func, args) = init.kind && let ExprKind::Path(ref qpath) = func.kind diff --git a/tests/ui/author/if.stdout b/tests/ui/author/if.stdout index 5d79618820d8..a85dcddd3315 100644 --- a/tests/ui/author/if.stdout +++ b/tests/ui/author/if.stdout @@ -1,4 +1,4 @@ -if let StmtKind::Local(local) = stmt.kind +if let StmtKind::Let(local) = stmt.kind && let Some(init) = local.init && let ExprKind::If(cond, then, Some(else_expr)) = init.kind && let ExprKind::DropTemps(expr) = cond.kind diff --git a/tests/ui/author/issue_3849.stdout b/tests/ui/author/issue_3849.stdout index 32a3127b85a3..a5a8c0304ee4 100644 --- a/tests/ui/author/issue_3849.stdout +++ b/tests/ui/author/issue_3849.stdout @@ -1,4 +1,4 @@ -if let StmtKind::Local(local) = stmt.kind +if let StmtKind::Let(local) = stmt.kind && let Some(init) = local.init && let ExprKind::Call(func, args) = init.kind && let ExprKind::Path(ref qpath) = func.kind diff --git a/tests/ui/author/loop.stdout b/tests/ui/author/loop.stdout index 631105a2238d..609d24910610 100644 --- a/tests/ui/author/loop.stdout +++ b/tests/ui/author/loop.stdout @@ -12,7 +12,7 @@ if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::Fo && let LitKind::Int(10, LitIntType::Unsuffixed) = lit1.node && let ExprKind::Block(block, None) = body.kind && block.stmts.len() == 1 - && let StmtKind::Local(local) = block.stmts[0].kind + && let StmtKind::Let(local) = block.stmts[0].kind && let Some(init) = local.init && let ExprKind::Path(ref qpath1) = init.kind && match_qpath(qpath1, &["y"]) diff --git a/tests/ui/author/macro_in_closure.stdout b/tests/ui/author/macro_in_closure.stdout index b90c830e0307..66caf382d897 100644 --- a/tests/ui/author/macro_in_closure.stdout +++ b/tests/ui/author/macro_in_closure.stdout @@ -1,4 +1,4 @@ -if let StmtKind::Local(local) = stmt.kind +if let StmtKind::Let(local) = stmt.kind && let Some(init) = local.init && let ExprKind::Closure { capture_clause: CaptureBy::Ref, fn_decl: fn_decl, body: body_id, closure_kind: ClosureKind::Closure, .. } = init.kind && let FnRetTy::DefaultReturn(_) = fn_decl.output diff --git a/tests/ui/author/matches.stdout b/tests/ui/author/matches.stdout index 30e4a9b2560a..91b3b6f6877e 100644 --- a/tests/ui/author/matches.stdout +++ b/tests/ui/author/matches.stdout @@ -1,4 +1,4 @@ -if let StmtKind::Local(local) = stmt.kind +if let StmtKind::Let(local) = stmt.kind && let Some(init) = local.init && let ExprKind::Match(scrutinee, arms, MatchSource::Normal) = init.kind && let ExprKind::Lit(ref lit) = scrutinee.kind @@ -16,7 +16,7 @@ if let StmtKind::Local(local) = stmt.kind && arms[1].guard.is_none() && let ExprKind::Block(block, None) = arms[1].body.kind && block.stmts.len() == 1 - && let StmtKind::Local(local1) = block.stmts[0].kind + && let StmtKind::Let(local1) = block.stmts[0].kind && let Some(init1) = local1.init && let ExprKind::Lit(ref lit4) = init1.kind && let LitKind::Int(3, LitIntType::Unsuffixed) = lit4.node From 5acc25e700363b4c3e5cddf5a32dfc5b4f28135c Mon Sep 17 00:00:00 2001 From: clubby789 Date: Sat, 8 Jun 2024 20:18:31 +0100 Subject: [PATCH 37/56] Update `icu4x` dependencies --- tests/ui/unicode.stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/unicode.stderr b/tests/ui/unicode.stderr index 9c365e1097db..b004493300ee 100644 --- a/tests/ui/unicode.stderr +++ b/tests/ui/unicode.stderr @@ -11,7 +11,7 @@ error: invisible character detected --> tests/ui/unicode.rs:7:12 | LL | print!("Here >­< is a SHY, and ­another"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider replacing the string with: `"Here >\u{AD}< is a SHY, and \u{AD}another"` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider replacing the string with: `"Here >\u{AD}< is a SHY, and \u{AD}another"` error: invisible character detected --> tests/ui/unicode.rs:9:12 From 3f9c5872b9307b7674555a97493dc5223724a184 Mon Sep 17 00:00:00 2001 From: alexey semenyuk Date: Sun, 9 Jun 2024 17:37:05 +0500 Subject: [PATCH 38/56] Filter from first typed symbol --- util/gh-pages/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 7fd779fe9a46..7cca298df8e4 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -397,7 +397,7 @@ $scope.bySearch = function (lint, index, array) { let searchStr = $scope.search; // It can be `null` I haven't missed this value - if (searchStr == null || searchStr.length < 3) { + if (searchStr == null) { return true; } searchStr = searchStr.toLowerCase(); From 55344c84a59412afeb6965945e24cbae7389ecab Mon Sep 17 00:00:00 2001 From: Artem Belyakov Date: Sun, 9 Jun 2024 21:01:00 +0300 Subject: [PATCH 39/56] Handle single chars with `to_string()` for `single_char_add_str` --- .../src/methods/single_char_insert_string.rs | 42 +++++++++++++- .../src/methods/single_char_push_string.rs | 41 ++++++++++++- tests/ui/single_char_add_str.fixed | 10 ++++ tests/ui/single_char_add_str.rs | 10 ++++ tests/ui/single_char_add_str.stderr | 58 +++++++++++++++---- 5 files changed, 148 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/methods/single_char_insert_string.rs b/clippy_lints/src/methods/single_char_insert_string.rs index 20ec2b74d81e..f2af793a8691 100644 --- a/clippy_lints/src/methods/single_char_insert_string.rs +++ b/clippy_lints/src/methods/single_char_insert_string.rs @@ -1,8 +1,9 @@ use super::utils::get_hint_if_single_char_arg; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; +use rustc_ast::BorrowKind; use rustc_errors::Applicability; -use rustc_hir as hir; +use rustc_hir::{self as hir, ExprKind}; use rustc_lint::LateContext; use super::SINGLE_CHAR_ADD_STR; @@ -25,4 +26,43 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir:: applicability, ); } + + if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[1].kind + && let ExprKind::MethodCall(path_segment, method_arg, _, _) = &arg.kind + && path_segment.ident.as_str() == "to_string" + && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) + { + let base_string_snippet = + snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); + let extension_string = + snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability); + let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability); + let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" }; + + let sugg = format!("{base_string_snippet}.insert({pos_arg}, {deref_string}{extension_string})"); + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + "calling `insert_str()` using a single-character converted to string", + "consider using `insert` without `to_string()`", + sugg, + applicability, + ); + } +} + +fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + if cx.typeck_results().expr_ty(expr).is_ref() + && let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind() + && ty.is_char() + { + return true; + } + + false +} + +fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + cx.typeck_results().expr_ty(expr).is_char() } diff --git a/clippy_lints/src/methods/single_char_push_string.rs b/clippy_lints/src/methods/single_char_push_string.rs index 97c13825bc10..8c6e490a38ff 100644 --- a/clippy_lints/src/methods/single_char_push_string.rs +++ b/clippy_lints/src/methods/single_char_push_string.rs @@ -1,8 +1,9 @@ use super::utils::get_hint_if_single_char_arg; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; +use rustc_ast::BorrowKind; use rustc_errors::Applicability; -use rustc_hir as hir; +use rustc_hir::{self as hir, ExprKind}; use rustc_lint::LateContext; use super::SINGLE_CHAR_ADD_STR; @@ -24,4 +25,42 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir:: applicability, ); } + + if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[0].kind + && let ExprKind::MethodCall(path_segment, method_arg, _, _) = &arg.kind + && path_segment.ident.as_str() == "to_string" + && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) + { + let base_string_snippet = + snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); + let extension_string = + snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability); + let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" }; + + let sugg = format!("{base_string_snippet}.push({deref_string}{extension_string})"); + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + "calling `push_str()` using a single-character converted to string", + "consider using `push` without `to_string()`", + sugg, + applicability, + ); + } +} + +fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + if cx.typeck_results().expr_ty(expr).is_ref() + && let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind() + && ty.is_char() + { + return true; + } + + false +} + +fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + cx.typeck_results().expr_ty(expr).is_char() } diff --git a/tests/ui/single_char_add_str.fixed b/tests/ui/single_char_add_str.fixed index eafd17f53874..aef15252b1bc 100644 --- a/tests/ui/single_char_add_str.fixed +++ b/tests/ui/single_char_add_str.fixed @@ -21,6 +21,12 @@ fn main() { string.push('\u{0052}'); string.push('a'); + let c_ref = &'a'; + string.push(*c_ref); + let c = 'a'; + string.push(c); + string.push('a'); + get_string!().push('ö'); // `insert_str` tests @@ -41,5 +47,9 @@ fn main() { string.insert(Y, '"'); string.insert(Y, '\''); + string.insert(0, *c_ref); + string.insert(0, c); + string.insert(0, 'a'); + get_string!().insert(1, '?'); } diff --git a/tests/ui/single_char_add_str.rs b/tests/ui/single_char_add_str.rs index 5326c7cf24c6..7f97250dacd4 100644 --- a/tests/ui/single_char_add_str.rs +++ b/tests/ui/single_char_add_str.rs @@ -21,6 +21,12 @@ fn main() { string.push_str("\u{0052}"); string.push_str(r##"a"##); + let c_ref = &'a'; + string.push_str(&c_ref.to_string()); + let c = 'a'; + string.push_str(&c.to_string()); + string.push_str(&'a'.to_string()); + get_string!().push_str("ö"); // `insert_str` tests @@ -41,5 +47,9 @@ fn main() { string.insert_str(Y, r##"""##); string.insert_str(Y, r##"'"##); + string.insert_str(0, &c_ref.to_string()); + string.insert_str(0, &c.to_string()); + string.insert_str(0, &'a'.to_string()); + get_string!().insert_str(1, "?"); } diff --git a/tests/ui/single_char_add_str.stderr b/tests/ui/single_char_add_str.stderr index 89d75f20f55a..7791c67578aa 100644 --- a/tests/ui/single_char_add_str.stderr +++ b/tests/ui/single_char_add_str.stderr @@ -31,65 +31,101 @@ error: calling `push_str()` using a single-character string literal LL | string.push_str(r##"a"##); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `string.push('a')` +error: calling `push_str()` using a single-character converted to string + --> tests/ui/single_char_add_str.rs:25:5 + | +LL | string.push_str(&c_ref.to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` without `to_string()`: `string.push(*c_ref)` + +error: calling `push_str()` using a single-character converted to string + --> tests/ui/single_char_add_str.rs:27:5 + | +LL | string.push_str(&c.to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` without `to_string()`: `string.push(c)` + +error: calling `push_str()` using a single-character converted to string + --> tests/ui/single_char_add_str.rs:28:5 + | +LL | string.push_str(&'a'.to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` without `to_string()`: `string.push('a')` + error: calling `push_str()` using a single-character string literal - --> tests/ui/single_char_add_str.rs:24:5 + --> tests/ui/single_char_add_str.rs:30:5 | LL | get_string!().push_str("ö"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `push` with a character literal: `get_string!().push('ö')` error: calling `insert_str()` using a single-character string literal - --> tests/ui/single_char_add_str.rs:29:5 + --> tests/ui/single_char_add_str.rs:35:5 | LL | string.insert_str(0, "R"); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(0, 'R')` error: calling `insert_str()` using a single-character string literal - --> tests/ui/single_char_add_str.rs:30:5 + --> tests/ui/single_char_add_str.rs:36:5 | LL | string.insert_str(1, "'"); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(1, '\'')` error: calling `insert_str()` using a single-character string literal - --> tests/ui/single_char_add_str.rs:35:5 + --> tests/ui/single_char_add_str.rs:41:5 | LL | string.insert_str(0, "\x52"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(0, '\x52')` error: calling `insert_str()` using a single-character string literal - --> tests/ui/single_char_add_str.rs:36:5 + --> tests/ui/single_char_add_str.rs:42:5 | LL | string.insert_str(0, "\u{0052}"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(0, '\u{0052}')` error: calling `insert_str()` using a single-character string literal - --> tests/ui/single_char_add_str.rs:38:5 + --> tests/ui/single_char_add_str.rs:44:5 | LL | string.insert_str(x, r##"a"##); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(x, 'a')` error: calling `insert_str()` using a single-character string literal - --> tests/ui/single_char_add_str.rs:40:5 + --> tests/ui/single_char_add_str.rs:46:5 | LL | string.insert_str(Y, r##"a"##); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(Y, 'a')` error: calling `insert_str()` using a single-character string literal - --> tests/ui/single_char_add_str.rs:41:5 + --> tests/ui/single_char_add_str.rs:47:5 | LL | string.insert_str(Y, r##"""##); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(Y, '"')` error: calling `insert_str()` using a single-character string literal - --> tests/ui/single_char_add_str.rs:42:5 + --> tests/ui/single_char_add_str.rs:48:5 | LL | string.insert_str(Y, r##"'"##); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `string.insert(Y, '\'')` +error: calling `insert_str()` using a single-character converted to string + --> tests/ui/single_char_add_str.rs:50:5 + | +LL | string.insert_str(0, &c_ref.to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` without `to_string()`: `string.insert(0, *c_ref)` + +error: calling `insert_str()` using a single-character converted to string + --> tests/ui/single_char_add_str.rs:51:5 + | +LL | string.insert_str(0, &c.to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` without `to_string()`: `string.insert(0, c)` + +error: calling `insert_str()` using a single-character converted to string + --> tests/ui/single_char_add_str.rs:52:5 + | +LL | string.insert_str(0, &'a'.to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` without `to_string()`: `string.insert(0, 'a')` + error: calling `insert_str()` using a single-character string literal - --> tests/ui/single_char_add_str.rs:44:5 + --> tests/ui/single_char_add_str.rs:54:5 | LL | get_string!().insert_str(1, "?"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `insert` with a character literal: `get_string!().insert(1, '?')` -error: aborting due to 15 previous errors +error: aborting due to 21 previous errors From f7723cada7191fba2313d410ea5a4327a878d589 Mon Sep 17 00:00:00 2001 From: Artem Belyakov Date: Sun, 9 Jun 2024 21:23:59 +0300 Subject: [PATCH 40/56] Fix dogfood test --- clippy_lints/src/methods/single_char_insert_string.rs | 2 +- clippy_lints/src/methods/single_char_push_string.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/methods/single_char_insert_string.rs b/clippy_lints/src/methods/single_char_insert_string.rs index f2af793a8691..ba9ef9c84f9c 100644 --- a/clippy_lints/src/methods/single_char_insert_string.rs +++ b/clippy_lints/src/methods/single_char_insert_string.rs @@ -29,7 +29,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir:: if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[1].kind && let ExprKind::MethodCall(path_segment, method_arg, _, _) = &arg.kind - && path_segment.ident.as_str() == "to_string" + && path_segment.ident.name == rustc_span::sym::to_string && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) { let base_string_snippet = diff --git a/clippy_lints/src/methods/single_char_push_string.rs b/clippy_lints/src/methods/single_char_push_string.rs index 8c6e490a38ff..f00a5ab455e8 100644 --- a/clippy_lints/src/methods/single_char_push_string.rs +++ b/clippy_lints/src/methods/single_char_push_string.rs @@ -28,7 +28,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir:: if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[0].kind && let ExprKind::MethodCall(path_segment, method_arg, _, _) = &arg.kind - && path_segment.ident.as_str() == "to_string" + && path_segment.ident.name == rustc_span::sym::to_string && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) { let base_string_snippet = From f7515ae9058786a61ffa30529f30575065b73450 Mon Sep 17 00:00:00 2001 From: Renato Lochetti Date: Sun, 9 Jun 2024 10:33:00 +0100 Subject: [PATCH 41/56] Don't lint indexing_slicing lints on proc macros --- clippy_lints/src/indexing_slicing.rs | 6 ++-- tests/ui/indexing_slicing_index.rs | 20 ++++++++++++++ tests/ui/indexing_slicing_index.stderr | 32 +++++++++++----------- tests/ui/indexing_slicing_slice.rs | 21 ++++++++++++++ tests/ui/indexing_slicing_slice.stderr | 38 +++++++++++++------------- 5 files changed, 80 insertions(+), 37 deletions(-) diff --git a/clippy_lints/src/indexing_slicing.rs b/clippy_lints/src/indexing_slicing.rs index b13b4d145dfb..d54f2af65cde 100644 --- a/clippy_lints/src/indexing_slicing.rs +++ b/clippy_lints/src/indexing_slicing.rs @@ -2,8 +2,8 @@ use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; -use clippy_utils::higher; use clippy_utils::ty::{deref_chain, get_adt_inherent_method}; +use clippy_utils::{higher, is_from_proc_macro}; use rustc_ast::ast::RangeLimits; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -102,7 +102,9 @@ impl IndexingSlicing { impl<'tcx> LateLintPass<'tcx> for IndexingSlicing { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if self.suppress_restriction_lint_in_const && cx.tcx.hir().is_inside_const_context(expr.hir_id) { + if (self.suppress_restriction_lint_in_const && cx.tcx.hir().is_inside_const_context(expr.hir_id)) + || is_from_proc_macro(cx, expr) + { return; } diff --git a/tests/ui/indexing_slicing_index.rs b/tests/ui/indexing_slicing_index.rs index 2e726141649e..2af5fcc82a9b 100644 --- a/tests/ui/indexing_slicing_index.rs +++ b/tests/ui/indexing_slicing_index.rs @@ -1,4 +1,5 @@ //@compile-flags: -Zdeduplicate-diagnostics=yes +//@aux-build: proc_macros.rs #![warn(clippy::indexing_slicing)] // We also check the out_of_bounds_indexing lint here, because it lints similar things and @@ -11,6 +12,9 @@ clippy::useless_vec )] +extern crate proc_macros; +use proc_macros::with_span; + const ARR: [i32; 2] = [1, 2]; const REF: &i32 = &ARR[idx()]; // This should be linted, since `suppress-restriction-lint-in-const` default is false. //~^ ERROR: indexing may panic @@ -22,6 +26,22 @@ const fn idx4() -> usize { 4 } +with_span!( + span + + fn dont_lint_proc_macro_array() { + let x = [1, 2, 3, 4]; + let index: usize = 1; + x[index]; + x[10]; + + let x = vec![0; 5]; + let index: usize = 1; + x[index]; + x[10]; + } +); + fn main() { let x = [1, 2, 3, 4]; let index: usize = 1; diff --git a/tests/ui/indexing_slicing_index.stderr b/tests/ui/indexing_slicing_index.stderr index 386f91becf14..71677584d25b 100644 --- a/tests/ui/indexing_slicing_index.stderr +++ b/tests/ui/indexing_slicing_index.stderr @@ -1,5 +1,5 @@ error: indexing may panic - --> tests/ui/indexing_slicing_index.rs:15:20 + --> tests/ui/indexing_slicing_index.rs:19:20 | LL | const REF: &i32 = &ARR[idx()]; // This should be linted, since `suppress-restriction-lint-in-const` default is false. | ^^^^^^^^^^ @@ -10,19 +10,19 @@ LL | const REF: &i32 = &ARR[idx()]; // This should be linted, since `suppress-re = help: to override `-D warnings` add `#[allow(clippy::indexing_slicing)]` error[E0080]: evaluation of `main::{constant#3}` failed - --> tests/ui/indexing_slicing_index.rs:47:14 + --> tests/ui/indexing_slicing_index.rs:67:14 | LL | const { &ARR[idx4()] }; | ^^^^^^^^^^^ index out of bounds: the length is 2 but the index is 4 note: erroneous constant encountered - --> tests/ui/indexing_slicing_index.rs:47:5 + --> tests/ui/indexing_slicing_index.rs:67:5 | LL | const { &ARR[idx4()] }; | ^^^^^^^^^^^^^^^^^^^^^^ error: indexing may panic - --> tests/ui/indexing_slicing_index.rs:28:5 + --> tests/ui/indexing_slicing_index.rs:48:5 | LL | x[index]; | ^^^^^^^^ @@ -30,7 +30,7 @@ LL | x[index]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: index is out of bounds - --> tests/ui/indexing_slicing_index.rs:31:5 + --> tests/ui/indexing_slicing_index.rs:51:5 | LL | x[4]; | ^^^^ @@ -39,13 +39,13 @@ LL | x[4]; = help: to override `-D warnings` add `#[allow(clippy::out_of_bounds_indexing)]` error: index is out of bounds - --> tests/ui/indexing_slicing_index.rs:33:5 + --> tests/ui/indexing_slicing_index.rs:53:5 | LL | x[1 << 3]; | ^^^^^^^^^ error: indexing may panic - --> tests/ui/indexing_slicing_index.rs:44:14 + --> tests/ui/indexing_slicing_index.rs:64:14 | LL | const { &ARR[idx()] }; | ^^^^^^^^^^ @@ -54,7 +54,7 @@ LL | const { &ARR[idx()] }; = note: the suggestion might not be applicable in constant blocks error: indexing may panic - --> tests/ui/indexing_slicing_index.rs:47:14 + --> tests/ui/indexing_slicing_index.rs:67:14 | LL | const { &ARR[idx4()] }; | ^^^^^^^^^^^ @@ -63,13 +63,13 @@ LL | const { &ARR[idx4()] }; = note: the suggestion might not be applicable in constant blocks error: index is out of bounds - --> tests/ui/indexing_slicing_index.rs:54:5 + --> tests/ui/indexing_slicing_index.rs:74:5 | LL | y[4]; | ^^^^ error: indexing may panic - --> tests/ui/indexing_slicing_index.rs:57:5 + --> tests/ui/indexing_slicing_index.rs:77:5 | LL | v[0]; | ^^^^ @@ -77,7 +77,7 @@ LL | v[0]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: indexing may panic - --> tests/ui/indexing_slicing_index.rs:59:5 + --> tests/ui/indexing_slicing_index.rs:79:5 | LL | v[10]; | ^^^^^ @@ -85,7 +85,7 @@ LL | v[10]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: indexing may panic - --> tests/ui/indexing_slicing_index.rs:61:5 + --> tests/ui/indexing_slicing_index.rs:81:5 | LL | v[1 << 3]; | ^^^^^^^^^ @@ -93,13 +93,13 @@ LL | v[1 << 3]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: index is out of bounds - --> tests/ui/indexing_slicing_index.rs:69:5 + --> tests/ui/indexing_slicing_index.rs:89:5 | LL | x[N]; | ^^^^ error: indexing may panic - --> tests/ui/indexing_slicing_index.rs:72:5 + --> tests/ui/indexing_slicing_index.rs:92:5 | LL | v[N]; | ^^^^ @@ -107,7 +107,7 @@ LL | v[N]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: indexing may panic - --> tests/ui/indexing_slicing_index.rs:74:5 + --> tests/ui/indexing_slicing_index.rs:94:5 | LL | v[M]; | ^^^^ @@ -115,7 +115,7 @@ LL | v[M]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: index is out of bounds - --> tests/ui/indexing_slicing_index.rs:78:13 + --> tests/ui/indexing_slicing_index.rs:98:13 | LL | let _ = x[4]; | ^^^^ diff --git a/tests/ui/indexing_slicing_slice.rs b/tests/ui/indexing_slicing_slice.rs index 69291acd9c77..f37bcc4aa0ca 100644 --- a/tests/ui/indexing_slicing_slice.rs +++ b/tests/ui/indexing_slicing_slice.rs @@ -1,3 +1,5 @@ +//@aux-build: proc_macros.rs + #![warn(clippy::indexing_slicing)] // We also check the out_of_bounds_indexing lint here, because it lints similar things and // we want to avoid false positives. @@ -11,6 +13,9 @@ )] #![warn(clippy::indexing_slicing)] +extern crate proc_macros; +use proc_macros::with_span; + use std::ops::Index; struct BoolMap { @@ -86,6 +91,22 @@ impl Index for Z { } } +with_span!( + span + + fn dont_lint_proc_macro() { + let x = [1, 2, 3, 4]; + let index: usize = 1; + &x[index..]; + &x[..10]; + + let x = vec![0; 5]; + let index: usize = 1; + &x[index..]; + &x[..10]; + } +); + fn main() { let x = [1, 2, 3, 4]; let index: usize = 1; diff --git a/tests/ui/indexing_slicing_slice.stderr b/tests/ui/indexing_slicing_slice.stderr index a7da3fe3faa7..1e72506746ec 100644 --- a/tests/ui/indexing_slicing_slice.stderr +++ b/tests/ui/indexing_slicing_slice.stderr @@ -1,5 +1,5 @@ error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:94:6 + --> tests/ui/indexing_slicing_slice.rs:115:6 | LL | &x[index..]; | ^^^^^^^^^^ @@ -9,7 +9,7 @@ LL | &x[index..]; = help: to override `-D warnings` add `#[allow(clippy::indexing_slicing)]` error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:96:6 + --> tests/ui/indexing_slicing_slice.rs:117:6 | LL | &x[..index]; | ^^^^^^^^^^ @@ -17,7 +17,7 @@ LL | &x[..index]; = help: consider using `.get(..n)`or `.get_mut(..n)` instead error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:98:6 + --> tests/ui/indexing_slicing_slice.rs:119:6 | LL | &x[index_from..index_to]; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL | &x[index_from..index_to]; = help: consider using `.get(n..m)` or `.get_mut(n..m)` instead error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:100:6 + --> tests/ui/indexing_slicing_slice.rs:121:6 | LL | &x[index_from..][..index_to]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -33,7 +33,7 @@ LL | &x[index_from..][..index_to]; = help: consider using `.get(..n)`or `.get_mut(..n)` instead error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:100:6 + --> tests/ui/indexing_slicing_slice.rs:121:6 | LL | &x[index_from..][..index_to]; | ^^^^^^^^^^^^^^^ @@ -41,7 +41,7 @@ LL | &x[index_from..][..index_to]; = help: consider using `.get(n..)` or .get_mut(n..)` instead error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:103:6 + --> tests/ui/indexing_slicing_slice.rs:124:6 | LL | &x[5..][..10]; | ^^^^^^^^^^^^ @@ -49,7 +49,7 @@ LL | &x[5..][..10]; = help: consider using `.get(..n)`or `.get_mut(..n)` instead error: range is out of bounds - --> tests/ui/indexing_slicing_slice.rs:103:8 + --> tests/ui/indexing_slicing_slice.rs:124:8 | LL | &x[5..][..10]; | ^ @@ -58,7 +58,7 @@ LL | &x[5..][..10]; = help: to override `-D warnings` add `#[allow(clippy::out_of_bounds_indexing)]` error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:107:6 + --> tests/ui/indexing_slicing_slice.rs:128:6 | LL | &x[0..][..3]; | ^^^^^^^^^^^ @@ -66,7 +66,7 @@ LL | &x[0..][..3]; = help: consider using `.get(..n)`or `.get_mut(..n)` instead error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:109:6 + --> tests/ui/indexing_slicing_slice.rs:130:6 | LL | &x[1..][..5]; | ^^^^^^^^^^^ @@ -74,19 +74,19 @@ LL | &x[1..][..5]; = help: consider using `.get(..n)`or `.get_mut(..n)` instead error: range is out of bounds - --> tests/ui/indexing_slicing_slice.rs:117:12 + --> tests/ui/indexing_slicing_slice.rs:138:12 | LL | &y[0..=4]; | ^ error: range is out of bounds - --> tests/ui/indexing_slicing_slice.rs:119:11 + --> tests/ui/indexing_slicing_slice.rs:140:11 | LL | &y[..=4]; | ^ error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:125:6 + --> tests/ui/indexing_slicing_slice.rs:146:6 | LL | &v[10..100]; | ^^^^^^^^^^ @@ -94,7 +94,7 @@ LL | &v[10..100]; = help: consider using `.get(n..m)` or `.get_mut(n..m)` instead error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:127:6 + --> tests/ui/indexing_slicing_slice.rs:148:6 | LL | &x[10..][..100]; | ^^^^^^^^^^^^^^ @@ -102,13 +102,13 @@ LL | &x[10..][..100]; = help: consider using `.get(..n)`or `.get_mut(..n)` instead error: range is out of bounds - --> tests/ui/indexing_slicing_slice.rs:127:8 + --> tests/ui/indexing_slicing_slice.rs:148:8 | LL | &x[10..][..100]; | ^^ error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:130:6 + --> tests/ui/indexing_slicing_slice.rs:151:6 | LL | &v[10..]; | ^^^^^^^ @@ -116,7 +116,7 @@ LL | &v[10..]; = help: consider using `.get(n..)` or .get_mut(n..)` instead error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:132:6 + --> tests/ui/indexing_slicing_slice.rs:153:6 | LL | &v[..100]; | ^^^^^^^^ @@ -124,7 +124,7 @@ LL | &v[..100]; = help: consider using `.get(..n)`or `.get_mut(..n)` instead error: indexing may panic - --> tests/ui/indexing_slicing_slice.rs:150:5 + --> tests/ui/indexing_slicing_slice.rs:171:5 | LL | map_with_get[true]; | ^^^^^^^^^^^^^^^^^^ @@ -132,7 +132,7 @@ LL | map_with_get[true]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: indexing may panic - --> tests/ui/indexing_slicing_slice.rs:153:5 + --> tests/ui/indexing_slicing_slice.rs:174:5 | LL | s[0]; | ^^^^ @@ -140,7 +140,7 @@ LL | s[0]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: indexing may panic - --> tests/ui/indexing_slicing_slice.rs:156:5 + --> tests/ui/indexing_slicing_slice.rs:177:5 | LL | y[0]; | ^^^^ From 8c1f953772a025dcfec6dbd36544e415842ad1d1 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 8 Jun 2024 16:13:45 +0200 Subject: [PATCH 42/56] ScalarInt: size mismatches are a bug, do not delay the panic --- clippy_lints/src/large_const_arrays.rs | 2 +- clippy_lints/src/large_stack_arrays.rs | 2 +- clippy_lints/src/non_copy_const.rs | 2 +- clippy_lints/src/zero_repeat_side_effects.rs | 3 +-- clippy_utils/src/consts.rs | 4 ++-- clippy_utils/src/ty.rs | 20 +++----------------- 6 files changed, 9 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/large_const_arrays.rs b/clippy_lints/src/large_const_arrays.rs index 77d05020c828..7f8197c0cc01 100644 --- a/clippy_lints/src/large_const_arrays.rs +++ b/clippy_lints/src/large_const_arrays.rs @@ -55,7 +55,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeConstArrays { && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() && let ty::Array(element_type, cst) = ty.kind() && let ConstKind::Value(_, ty::ValTree::Leaf(element_count)) = cst.kind() - && let Ok(element_count) = element_count.try_to_target_usize(cx.tcx) + && let element_count = element_count.to_target_usize(cx.tcx) && let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()) && self.maximum_allowed_size < u128::from(element_count) * u128::from(element_size) { diff --git a/clippy_lints/src/large_stack_arrays.rs b/clippy_lints/src/large_stack_arrays.rs index f0f3f53647b9..c9bfc9c85d95 100644 --- a/clippy_lints/src/large_stack_arrays.rs +++ b/clippy_lints/src/large_stack_arrays.rs @@ -65,7 +65,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackArrays { && !self.is_from_vec_macro(cx, expr.span) && let ty::Array(element_type, cst) = cx.typeck_results().expr_ty(expr).kind() && let ConstKind::Value(_, ty::ValTree::Leaf(element_count)) = cst.kind() - && let Ok(element_count) = element_count.try_to_target_usize(cx.tcx) + && let element_count = element_count.to_target_usize(cx.tcx) && let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()) && !cx.tcx.hir().parent_iter(expr.hir_id).any(|(_, node)| { matches!( diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index 76d9cee18aa7..20a97645af95 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -199,7 +199,7 @@ impl<'tcx> NonCopyConst<'tcx> { .any(|field| Self::is_value_unfrozen_raw_inner(cx, *field, ty)), 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()); + let variant_index = VariantIdx::from_u32(variant_index.unwrap_leaf().to_u32()); fields .iter() .copied() diff --git a/clippy_lints/src/zero_repeat_side_effects.rs b/clippy_lints/src/zero_repeat_side_effects.rs index 848b49130dc2..8796b8f61d16 100644 --- a/clippy_lints/src/zero_repeat_side_effects.rs +++ b/clippy_lints/src/zero_repeat_side_effects.rs @@ -56,8 +56,7 @@ impl LateLintPass<'_> for ZeroRepeatSideEffects { } else if let ExprKind::Repeat(inner_expr, _) = expr.kind && let ty::Array(_, cst) = cx.typeck_results().expr_ty(expr).kind() && let ConstKind::Value(_, ty::ValTree::Leaf(element_count)) = cst.kind() - && let Ok(element_count) = element_count.try_to_target_usize(cx.tcx) - && element_count == 0 + && element_count.to_target_usize(cx.tcx) == 0 { inner_check(cx, expr, inner_expr, false); } diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 5c9cad2b45d4..e9e1aa7e4453 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -810,14 +810,14 @@ pub fn mir_to_const<'tcx>(lcx: &LateContext<'tcx>, result: mir::Const<'tcx>) -> (ConstValue::Scalar(Scalar::Int(int)), _) => match result.ty().kind() { ty::Adt(adt_def, _) if adt_def.is_struct() => Some(Constant::Adt(result)), ty::Bool => Some(Constant::Bool(int == ScalarInt::TRUE)), - ty::Uint(_) | ty::Int(_) => Some(Constant::Int(int.assert_bits(int.size()))), + ty::Uint(_) | ty::Int(_) => Some(Constant::Int(int.to_bits(int.size()))), ty::Float(FloatTy::F32) => Some(Constant::F32(f32::from_bits( int.try_into().expect("invalid f32 bit representation"), ))), ty::Float(FloatTy::F64) => Some(Constant::F64(f64::from_bits( int.try_into().expect("invalid f64 bit representation"), ))), - ty::RawPtr(_, _) => Some(Constant::RawPtr(int.assert_bits(int.size()))), + ty::RawPtr(_, _) => Some(Constant::RawPtr(int.to_bits(int.size()))), _ => None, }, (_, ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Str) => { diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index f0dac6f5d9c4..6e5626297c95 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -23,7 +23,7 @@ use rustc_middle::ty::{ }; use rustc_span::symbol::Ident; use rustc_span::{sym, Span, Symbol, DUMMY_SP}; -use rustc_target::abi::{Size, VariantIdx}; +use rustc_target::abi::VariantIdx; 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}; @@ -865,22 +865,8 @@ impl core::ops::Add for EnumValue { pub fn read_explicit_enum_value(tcx: TyCtxt<'_>, id: DefId) -> Option { if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) { match tcx.type_of(id).instantiate_identity().kind() { - ty::Int(_) => Some(EnumValue::Signed(match value.size().bytes() { - 1 => i128::from(value.assert_bits(Size::from_bytes(1)) as u8 as i8), - 2 => i128::from(value.assert_bits(Size::from_bytes(2)) as u16 as i16), - 4 => i128::from(value.assert_bits(Size::from_bytes(4)) as u32 as i32), - 8 => i128::from(value.assert_bits(Size::from_bytes(8)) as u64 as i64), - 16 => value.assert_bits(Size::from_bytes(16)) as i128, - _ => return None, - })), - ty::Uint(_) => Some(EnumValue::Unsigned(match value.size().bytes() { - 1 => value.assert_bits(Size::from_bytes(1)), - 2 => value.assert_bits(Size::from_bytes(2)), - 4 => value.assert_bits(Size::from_bytes(4)), - 8 => value.assert_bits(Size::from_bytes(8)), - 16 => value.assert_bits(Size::from_bytes(16)), - _ => return None, - })), + ty::Int(_) => Some(EnumValue::Signed(value.to_int(value.size()))), + ty::Uint(_) => Some(EnumValue::Unsigned(value.to_uint(value.size()))), _ => None, } } else { From c86b19f1ef262eeb1992a91879b38ffbed07a1a7 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Tue, 11 Jun 2024 21:56:14 +0200 Subject: [PATCH 43/56] Add lint to check manual pattern char comparison and merge its code with single_char_pattern lint --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 3 +- clippy_lints/src/float_literal.rs | 2 +- clippy_lints/src/lib.rs | 2 + clippy_lints/src/methods/mod.rs | 35 --- .../src/methods/path_buf_push_overwrite.rs | 2 +- .../src/methods/single_char_insert_string.rs | 5 +- .../src/methods/single_char_pattern.rs | 64 ----- .../src/methods/single_char_push_string.rs | 5 +- clippy_lints/src/methods/utils.rs | 45 ---- .../src/misc_early/zero_prefixed_literal.rs | 4 +- clippy_lints/src/string_patterns.rs | 227 ++++++++++++++++++ clippy_utils/src/source.rs | 45 ++++ tests/ui/manual_pattern_char_comparison.fixed | 49 ++++ tests/ui/manual_pattern_char_comparison.rs | 49 ++++ .../ui/manual_pattern_char_comparison.stderr | 59 +++++ tests/ui/search_is_some.rs | 1 + tests/ui/search_is_some.stderr | 16 +- 18 files changed, 451 insertions(+), 163 deletions(-) delete mode 100644 clippy_lints/src/methods/single_char_pattern.rs create mode 100644 clippy_lints/src/string_patterns.rs create mode 100644 tests/ui/manual_pattern_char_comparison.fixed create mode 100644 tests/ui/manual_pattern_char_comparison.rs create mode 100644 tests/ui/manual_pattern_char_comparison.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 711b7fb79141..d7bcd7a19687 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5532,6 +5532,7 @@ Released 2018-09-13 [`manual_next_back`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_next_back [`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive [`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or +[`manual_pattern_char_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_pattern_char_comparison [`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains [`manual_range_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_patterns [`manual_rem_euclid`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index a9f2dd4499a3..7e43a99e9f24 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -448,7 +448,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::SEEK_TO_START_INSTEAD_OF_REWIND_INFO, crate::methods::SHOULD_IMPLEMENT_TRAIT_INFO, crate::methods::SINGLE_CHAR_ADD_STR_INFO, - crate::methods::SINGLE_CHAR_PATTERN_INFO, crate::methods::SKIP_WHILE_NEXT_INFO, crate::methods::STABLE_SORT_PRIMITIVE_INFO, crate::methods::STRING_EXTEND_CHARS_INFO, @@ -656,6 +655,8 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::std_instead_of_core::ALLOC_INSTEAD_OF_CORE_INFO, crate::std_instead_of_core::STD_INSTEAD_OF_ALLOC_INFO, crate::std_instead_of_core::STD_INSTEAD_OF_CORE_INFO, + crate::string_patterns::MANUAL_PATTERN_CHAR_COMPARISON_INFO, + crate::string_patterns::SINGLE_CHAR_PATTERN_INFO, crate::strings::STRING_ADD_INFO, crate::strings::STRING_ADD_ASSIGN_INFO, crate::strings::STRING_FROM_UTF8_AS_BYTES_INFO, diff --git a/clippy_lints/src/float_literal.rs b/clippy_lints/src/float_literal.rs index 4ec9bd757ff4..4d301daabe4c 100644 --- a/clippy_lints/src/float_literal.rs +++ b/clippy_lints/src/float_literal.rs @@ -103,7 +103,7 @@ impl<'tcx> LateLintPass<'tcx> for FloatLiteral { return; } - if is_whole && !sym_str.contains(|c| c == 'e' || c == 'E') { + if is_whole && !sym_str.contains(['e', 'E']) { // Normalize the literal by stripping the fractional portion if sym_str.split('.').next().unwrap() != float_str { // If the type suffix is missing the suggestion would be diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 6946c2986f48..c65581d5203e 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -326,6 +326,7 @@ mod size_of_in_element_count; mod size_of_ref; mod slow_vector_initialization; mod std_instead_of_core; +mod string_patterns; mod strings; mod strlen_on_c_strings; mod suspicious_operation_groupings; @@ -1167,6 +1168,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { ..Default::default() }) }); + store.register_late_pass(|_| Box::new(string_patterns::StringPatterns)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index c3699d6341a0..6200716afbe9 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -94,7 +94,6 @@ mod seek_from_current; mod seek_to_start_instead_of_rewind; mod single_char_add_str; mod single_char_insert_string; -mod single_char_pattern; mod single_char_push_string; mod skip_while_next; mod stable_sort_primitive; @@ -1141,38 +1140,6 @@ declare_clippy_lint! { "not returning type containing `Self` in a `new` method" } -declare_clippy_lint! { - /// ### What it does - /// Checks for string methods that receive a single-character - /// `str` as an argument, e.g., `_.split("x")`. - /// - /// ### Why is this bad? - /// While this can make a perf difference on some systems, - /// benchmarks have proven inconclusive. But at least using a - /// char literal makes it clear that we are looking at a single - /// character. - /// - /// ### Known problems - /// Does not catch multi-byte unicode characters. This is by - /// design, on many machines, splitting by a non-ascii char is - /// actually slower. Please do your own measurements instead of - /// relying solely on the results of this lint. - /// - /// ### Example - /// ```rust,ignore - /// _.split("x"); - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// _.split('x'); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub SINGLE_CHAR_PATTERN, - pedantic, - "using a single-character str where a char could be used, e.g., `_.split(\"x\")`" -} - declare_clippy_lint! { /// ### What it does /// Checks for calling `.step_by(0)` on iterators which panics. @@ -4169,7 +4136,6 @@ impl_lint_pass!(Methods => [ FLAT_MAP_OPTION, INEFFICIENT_TO_STRING, NEW_RET_NO_SELF, - SINGLE_CHAR_PATTERN, SINGLE_CHAR_ADD_STR, SEARCH_IS_SOME, FILTER_NEXT, @@ -4324,7 +4290,6 @@ impl<'tcx> LateLintPass<'tcx> for Methods { inefficient_to_string::check(cx, expr, method_call.ident.name, receiver, args); single_char_add_str::check(cx, expr, receiver, args); into_iter_on_ref::check(cx, expr, method_span, method_call.ident.name, receiver); - single_char_pattern::check(cx, expr, method_call.ident.name, receiver, args); unnecessary_to_owned::check(cx, expr, method_call.ident.name, receiver, args, &self.msrv); }, ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => { diff --git a/clippy_lints/src/methods/path_buf_push_overwrite.rs b/clippy_lints/src/methods/path_buf_push_overwrite.rs index 04a27cc98f3c..2d3007e50b81 100644 --- a/clippy_lints/src/methods/path_buf_push_overwrite.rs +++ b/clippy_lints/src/methods/path_buf_push_overwrite.rs @@ -27,7 +27,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t lit.span, "calling `push` with '/' or '\\' (file system root) will overwrite the previous path definition", "try", - format!("\"{}\"", pushed_path_lit.trim_start_matches(|c| c == '/' || c == '\\')), + format!("\"{}\"", pushed_path_lit.trim_start_matches(['/', '\\'])), Applicability::MachineApplicable, ); } diff --git a/clippy_lints/src/methods/single_char_insert_string.rs b/clippy_lints/src/methods/single_char_insert_string.rs index ba9ef9c84f9c..e2f76ac114c6 100644 --- a/clippy_lints/src/methods/single_char_insert_string.rs +++ b/clippy_lints/src/methods/single_char_insert_string.rs @@ -1,6 +1,5 @@ -use super::utils::get_hint_if_single_char_arg; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal}; use rustc_ast::BorrowKind; use rustc_errors::Applicability; use rustc_hir::{self as hir, ExprKind}; @@ -11,7 +10,7 @@ use super::SINGLE_CHAR_ADD_STR; /// lint for length-1 `str`s as argument for `insert_str` pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { let mut applicability = Applicability::MachineApplicable; - if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[1], &mut applicability, false) { + if let Some(extension_string) = str_literal_to_char_literal(cx, &args[1], &mut applicability, false) { let base_string_snippet = snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability); let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability); diff --git a/clippy_lints/src/methods/single_char_pattern.rs b/clippy_lints/src/methods/single_char_pattern.rs deleted file mode 100644 index 982a7901c453..000000000000 --- a/clippy_lints/src/methods/single_char_pattern.rs +++ /dev/null @@ -1,64 +0,0 @@ -use super::utils::get_hint_if_single_char_arg; -use clippy_utils::diagnostics::span_lint_and_sugg; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_lint::LateContext; -use rustc_middle::ty; -use rustc_span::symbol::Symbol; - -use super::SINGLE_CHAR_PATTERN; - -const PATTERN_METHODS: [(&str, usize); 22] = [ - ("contains", 0), - ("starts_with", 0), - ("ends_with", 0), - ("find", 0), - ("rfind", 0), - ("split", 0), - ("split_inclusive", 0), - ("rsplit", 0), - ("split_terminator", 0), - ("rsplit_terminator", 0), - ("splitn", 1), - ("rsplitn", 1), - ("split_once", 0), - ("rsplit_once", 0), - ("matches", 0), - ("rmatches", 0), - ("match_indices", 0), - ("rmatch_indices", 0), - ("trim_start_matches", 0), - ("trim_end_matches", 0), - ("replace", 0), - ("replacen", 0), -]; - -/// lint for length-1 `str`s for methods in `PATTERN_METHODS` -pub(super) fn check( - cx: &LateContext<'_>, - _expr: &hir::Expr<'_>, - method_name: Symbol, - receiver: &hir::Expr<'_>, - args: &[hir::Expr<'_>], -) { - for &(method, pos) in &PATTERN_METHODS { - if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(receiver).kind() - && ty.is_str() - && method_name.as_str() == method - && args.len() > pos - && let arg = &args[pos] - && let mut applicability = Applicability::MachineApplicable - && let Some(hint) = get_hint_if_single_char_arg(cx, arg, &mut applicability, true) - { - span_lint_and_sugg( - cx, - SINGLE_CHAR_PATTERN, - arg.span, - "single-character string constant used as pattern", - "consider using a `char`", - hint, - applicability, - ); - } - } -} diff --git a/clippy_lints/src/methods/single_char_push_string.rs b/clippy_lints/src/methods/single_char_push_string.rs index f00a5ab455e8..4ae8634305da 100644 --- a/clippy_lints/src/methods/single_char_push_string.rs +++ b/clippy_lints/src/methods/single_char_push_string.rs @@ -1,6 +1,5 @@ -use super::utils::get_hint_if_single_char_arg; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal}; use rustc_ast::BorrowKind; use rustc_errors::Applicability; use rustc_hir::{self as hir, ExprKind}; @@ -11,7 +10,7 @@ use super::SINGLE_CHAR_ADD_STR; /// lint for length-1 `str`s as argument for `push_str` pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { let mut applicability = Applicability::MachineApplicable; - if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[0], &mut applicability, false) { + if let Some(extension_string) = str_literal_to_char_literal(cx, &args[0], &mut applicability, false) { let base_string_snippet = snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); let sugg = format!("{base_string_snippet}.push({extension_string})"); diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints/src/methods/utils.rs index 5d58c73f8b20..0d2b0a313176 100644 --- a/clippy_lints/src/methods/utils.rs +++ b/clippy_lints/src/methods/utils.rs @@ -1,8 +1,5 @@ -use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{get_parent_expr, path_to_local_id, usage}; -use rustc_ast::ast; -use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, QPath, Stmt, StmtKind}; use rustc_lint::LateContext; @@ -49,48 +46,6 @@ pub(super) fn derefs_to_slice<'tcx>( } } -pub(super) fn get_hint_if_single_char_arg( - cx: &LateContext<'_>, - arg: &Expr<'_>, - applicability: &mut Applicability, - ascii_only: bool, -) -> Option { - if let ExprKind::Lit(lit) = &arg.kind - && let ast::LitKind::Str(r, style) = lit.node - && let string = r.as_str() - && let len = if ascii_only { - string.len() - } else { - string.chars().count() - } - && len == 1 - { - let snip = snippet_with_applicability(cx, arg.span, string, applicability); - let ch = if let ast::StrStyle::Raw(nhash) = style { - let nhash = nhash as usize; - // for raw string: r##"a"## - &snip[(nhash + 2)..(snip.len() - 1 - nhash)] - } else { - // for regular string: "a" - &snip[1..(snip.len() - 1)] - }; - - let hint = format!( - "'{}'", - match ch { - "'" => "\\'", - r"\" => "\\\\", - "\\\"" => "\"", // no need to escape `"` in `'"'` - _ => ch, - } - ); - - Some(hint) - } else { - None - } -} - /// The core logic of `check_for_loop_iter` in `unnecessary_iter_cloned.rs`, this function wraps a /// use of `CloneOrCopyVisitor`. pub(super) fn clone_or_copy_needed<'tcx>( diff --git a/clippy_lints/src/misc_early/zero_prefixed_literal.rs b/clippy_lints/src/misc_early/zero_prefixed_literal.rs index 4f9578d1b257..61f4684c9e37 100644 --- a/clippy_lints/src/misc_early/zero_prefixed_literal.rs +++ b/clippy_lints/src/misc_early/zero_prefixed_literal.rs @@ -6,7 +6,7 @@ use rustc_span::Span; use super::ZERO_PREFIXED_LITERAL; pub(super) fn check(cx: &EarlyContext<'_>, lit_span: Span, lit_snip: &str) { - let trimmed_lit_snip = lit_snip.trim_start_matches(|c| c == '_' || c == '0'); + let trimmed_lit_snip = lit_snip.trim_start_matches(['_', '0']); span_lint_and_then( cx, ZERO_PREFIXED_LITERAL, @@ -20,7 +20,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, lit_span: Span, lit_snip: &str) { Applicability::MaybeIncorrect, ); // do not advise to use octal form if the literal cannot be expressed in base 8. - if !lit_snip.contains(|c| c == '8' || c == '9') { + if !lit_snip.contains(['8', '9']) { diag.span_suggestion( lit_span, "if you mean to use an octal constant, use `0o`", diff --git a/clippy_lints/src/string_patterns.rs b/clippy_lints/src/string_patterns.rs new file mode 100644 index 000000000000..64b5b8f9f27b --- /dev/null +++ b/clippy_lints/src/string_patterns.rs @@ -0,0 +1,227 @@ +use std::ops::ControlFlow; + +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::eager_or_lazy::switch_to_eager_eval; +use clippy_utils::macros::matching_root_macro_call; +use clippy_utils::path_to_local_id; +use clippy_utils::source::{snippet, str_literal_to_char_literal}; +use clippy_utils::visitors::{for_each_expr, Descend}; +use itertools::Itertools; +use rustc_ast::{BinOpKind, LitKind}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, PatKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::declare_lint_pass; +use rustc_span::{sym, Span}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for manual `char` comparison in string patterns + /// + /// ### Why is this bad? + /// This can be written more concisely using a `char` or an array of `char`. + /// This is more readable and more optimized when comparing to only one `char`. + /// + /// ### Example + /// ```no_run + /// "Hello World!".trim_end_matches(|c| c == '.' || c == ',' || c == '!' || c == '?'); + /// ``` + /// Use instead: + /// ```no_run + /// "Hello World!".trim_end_matches(['.', ',', '!', '?']); + /// ``` + #[clippy::version = "1.80.0"] + pub MANUAL_PATTERN_CHAR_COMPARISON, + style, + "manual char comparison in string patterns" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for string methods that receive a single-character + /// `str` as an argument, e.g., `_.split("x")`. + /// + /// ### Why is this bad? + /// While this can make a perf difference on some systems, + /// benchmarks have proven inconclusive. But at least using a + /// char literal makes it clear that we are looking at a single + /// character. + /// + /// ### Known problems + /// Does not catch multi-byte unicode characters. This is by + /// design, on many machines, splitting by a non-ascii char is + /// actually slower. Please do your own measurements instead of + /// relying solely on the results of this lint. + /// + /// ### Example + /// ```rust,ignore + /// _.split("x"); + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// _.split('x'); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub SINGLE_CHAR_PATTERN, + pedantic, + "using a single-character str where a char could be used, e.g., `_.split(\"x\")`" +} + +declare_lint_pass!(StringPatterns => [MANUAL_PATTERN_CHAR_COMPARISON, SINGLE_CHAR_PATTERN]); + +const PATTERN_METHODS: [(&str, usize); 22] = [ + ("contains", 0), + ("starts_with", 0), + ("ends_with", 0), + ("find", 0), + ("rfind", 0), + ("split", 0), + ("split_inclusive", 0), + ("rsplit", 0), + ("split_terminator", 0), + ("rsplit_terminator", 0), + ("splitn", 1), + ("rsplitn", 1), + ("split_once", 0), + ("rsplit_once", 0), + ("matches", 0), + ("rmatches", 0), + ("match_indices", 0), + ("rmatch_indices", 0), + ("trim_start_matches", 0), + ("trim_end_matches", 0), + ("replace", 0), + ("replacen", 0), +]; + +fn check_single_char_pattern_lint(cx: &LateContext<'_>, arg: &Expr<'_>) { + let mut applicability = Applicability::MachineApplicable; + if let Some(hint) = str_literal_to_char_literal(cx, arg, &mut applicability, true) { + span_lint_and_sugg( + cx, + SINGLE_CHAR_PATTERN, + arg.span, + "single-character string constant used as pattern", + "consider using a `char`", + hint, + applicability, + ); + } +} + +fn get_char_span<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option { + if cx.typeck_results().expr_ty_adjusted(expr).is_char() + && !expr.span.from_expansion() + && switch_to_eager_eval(cx, expr) + { + Some(expr.span) + } else { + None + } +} + +fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr<'_>) { + if let ExprKind::Closure(closure) = method_arg.kind + && let body = cx.tcx.hir().body(closure.body) + && let Some(PatKind::Binding(_, binding, ..)) = body.params.first().map(|p| p.pat.kind) + { + let mut set_char_spans: Vec = Vec::new(); + + // We want to retrieve all the comparisons done. + // They are ordered in a nested way and so we need to traverse the AST to collect them all. + if for_each_expr(cx, body.value, |sub_expr| -> ControlFlow<(), Descend> { + match sub_expr.kind { + ExprKind::Binary(op, left, right) if op.node == BinOpKind::Eq => { + if path_to_local_id(left, binding) + && let Some(span) = get_char_span(cx, right) + { + set_char_spans.push(span); + ControlFlow::Continue(Descend::No) + } else if path_to_local_id(right, binding) + && let Some(span) = get_char_span(cx, left) + { + set_char_spans.push(span); + ControlFlow::Continue(Descend::No) + } else { + ControlFlow::Break(()) + } + }, + ExprKind::Binary(op, _, _) if op.node == BinOpKind::Or => ControlFlow::Continue(Descend::Yes), + ExprKind::Match(match_value, [arm, _], _) => { + if matching_root_macro_call(cx, sub_expr.span, sym::matches_macro).is_none() + || arm.guard.is_some() + || !path_to_local_id(match_value, binding) + { + return ControlFlow::Break(()); + } + if arm.pat.walk_short(|pat| match pat.kind { + PatKind::Lit(expr) if let ExprKind::Lit(lit) = expr.kind => { + if let LitKind::Char(_) = lit.node { + set_char_spans.push(lit.span); + } + true + }, + PatKind::Or(_) => true, + _ => false, + }) { + ControlFlow::Continue(Descend::No) + } else { + ControlFlow::Break(()) + } + }, + _ => ControlFlow::Break(()), + } + }) + .is_some() + { + return; + } + span_lint_and_then( + cx, + MANUAL_PATTERN_CHAR_COMPARISON, + method_arg.span, + "this manual char comparison can be written more succinctly", + |diag| { + if let [set_char_span] = set_char_spans[..] { + diag.span_suggestion( + method_arg.span, + "consider using a `char`", + snippet(cx, set_char_span, "c"), + Applicability::MachineApplicable, + ); + } else { + diag.span_suggestion( + method_arg.span, + "consider using an array of `char`", + format!( + "[{}]", + set_char_spans.into_iter().map(|span| snippet(cx, span, "c")).join(", ") + ), + Applicability::MachineApplicable, + ); + } + }, + ); + } +} + +impl<'tcx> LateLintPass<'tcx> for StringPatterns { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if !expr.span.from_expansion() + && let ExprKind::MethodCall(method, receiver, args, _) = expr.kind + && let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(receiver).kind() + && ty.is_str() + && let method_name = method.ident.name.as_str() + && let Some(&(_, pos)) = PATTERN_METHODS + .iter() + .find(|(array_method_name, _)| *array_method_name == method_name) + && let Some(arg) = args.get(pos) + { + check_single_char_pattern_lint(cx, arg); + + check_manual_pattern_char_comparison(cx, arg); + } + } +} diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index 69f593fe04ac..b7ff7ebe9105 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -2,6 +2,7 @@ #![allow(clippy::module_name_repetitions)] +use rustc_ast::{LitKind, StrStyle}; use rustc_data_structures::sync::Lrc; use rustc_errors::Applicability; use rustc_hir::{BlockCheckMode, Expr, ExprKind, UnsafeSource}; @@ -500,6 +501,50 @@ pub fn expand_past_previous_comma(cx: &LateContext<'_>, span: Span) -> Span { extended.with_lo(extended.lo() - BytePos(1)) } +/// Converts `expr` to a `char` literal if it's a `str` literal containing a single +/// character (or a single byte with `ascii_only`) +pub fn str_literal_to_char_literal( + cx: &LateContext<'_>, + expr: &Expr<'_>, + applicability: &mut Applicability, + ascii_only: bool, +) -> Option { + if let ExprKind::Lit(lit) = &expr.kind + && let LitKind::Str(r, style) = lit.node + && let string = r.as_str() + && let len = if ascii_only { + string.len() + } else { + string.chars().count() + } + && len == 1 + { + let snip = snippet_with_applicability(cx, expr.span, string, applicability); + let ch = if let StrStyle::Raw(nhash) = style { + let nhash = nhash as usize; + // for raw string: r##"a"## + &snip[(nhash + 2)..(snip.len() - 1 - nhash)] + } else { + // for regular string: "a" + &snip[1..(snip.len() - 1)] + }; + + let hint = format!( + "'{}'", + match ch { + "'" => "\\'", + r"\" => "\\\\", + "\\\"" => "\"", // no need to escape `"` in `'"'` + _ => ch, + } + ); + + Some(hint) + } else { + None + } +} + #[cfg(test)] mod test { use super::{reindent_multiline, without_block_comments}; diff --git a/tests/ui/manual_pattern_char_comparison.fixed b/tests/ui/manual_pattern_char_comparison.fixed new file mode 100644 index 000000000000..588226b87e87 --- /dev/null +++ b/tests/ui/manual_pattern_char_comparison.fixed @@ -0,0 +1,49 @@ +#![warn(clippy::manual_pattern_char_comparison)] + +struct NotStr; + +impl NotStr { + fn find(&self, _: impl FnMut(char) -> bool) {} +} + +fn main() { + let sentence = "Hello, world!"; + sentence.trim_end_matches(['.', ',', '!', '?']); + sentence.split(['\n', 'X']); + sentence.split(['\n', 'X']); + sentence.splitn(3, 'X'); + sentence.splitn(3, |c: char| c.is_whitespace() || c == 'X'); + let char_compare = 'X'; + sentence.splitn(3, char_compare); + sentence.split(['\n', 'X', 'Y']); + sentence.splitn(3, 'X'); + sentence.splitn(3, ['X', 'W']); + sentence.find('🎈'); + + let not_str = NotStr; + not_str.find(|c: char| c == 'X'); + + "".find(|c| c == 'a' || c > 'z'); + + let x = true; + "".find(|c| c == 'a' || x || c == 'b'); + + let d = 'd'; + "".find(|c| c == 'a' || d == 'b'); + + "".find(|c| match c { + 'a' | 'b' => true, + _ => c.is_ascii(), + }); + + "".find(|c| matches!(c, 'a' | 'b' if false)); + + "".find(|c| matches!(c, 'a' | '1'..'4')); + "".find(|c| c == 'a' || matches!(c, '1'..'4')); + macro_rules! m { + ($e:expr) => { + $e == '?' + }; + } + "".find(|c| m!(c)); +} diff --git a/tests/ui/manual_pattern_char_comparison.rs b/tests/ui/manual_pattern_char_comparison.rs new file mode 100644 index 000000000000..5078f3ee27f3 --- /dev/null +++ b/tests/ui/manual_pattern_char_comparison.rs @@ -0,0 +1,49 @@ +#![warn(clippy::manual_pattern_char_comparison)] + +struct NotStr; + +impl NotStr { + fn find(&self, _: impl FnMut(char) -> bool) {} +} + +fn main() { + let sentence = "Hello, world!"; + sentence.trim_end_matches(|c: char| c == '.' || c == ',' || c == '!' || c == '?'); + sentence.split(|c: char| c == '\n' || c == 'X'); + sentence.split(|c| c == '\n' || c == 'X'); + sentence.splitn(3, |c: char| c == 'X'); + sentence.splitn(3, |c: char| c.is_whitespace() || c == 'X'); + let char_compare = 'X'; + sentence.splitn(3, |c: char| c == char_compare); + sentence.split(|c: char| matches!(c, '\n' | 'X' | 'Y')); + sentence.splitn(3, |c: char| matches!(c, 'X')); + sentence.splitn(3, |c: char| matches!(c, 'X' | 'W')); + sentence.find(|c| c == '🎈'); + + let not_str = NotStr; + not_str.find(|c: char| c == 'X'); + + "".find(|c| c == 'a' || c > 'z'); + + let x = true; + "".find(|c| c == 'a' || x || c == 'b'); + + let d = 'd'; + "".find(|c| c == 'a' || d == 'b'); + + "".find(|c| match c { + 'a' | 'b' => true, + _ => c.is_ascii(), + }); + + "".find(|c| matches!(c, 'a' | 'b' if false)); + + "".find(|c| matches!(c, 'a' | '1'..'4')); + "".find(|c| c == 'a' || matches!(c, '1'..'4')); + macro_rules! m { + ($e:expr) => { + $e == '?' + }; + } + "".find(|c| m!(c)); +} diff --git a/tests/ui/manual_pattern_char_comparison.stderr b/tests/ui/manual_pattern_char_comparison.stderr new file mode 100644 index 000000000000..b6b51794a11f --- /dev/null +++ b/tests/ui/manual_pattern_char_comparison.stderr @@ -0,0 +1,59 @@ +error: this manual char comparison can be written more succinctly + --> tests/ui/manual_pattern_char_comparison.rs:11:31 + | +LL | sentence.trim_end_matches(|c: char| c == '.' || c == ',' || c == '!' || c == '?'); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using an array of `char`: `['.', ',', '!', '?']` + | + = note: `-D clippy::manual-pattern-char-comparison` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_pattern_char_comparison)]` + +error: this manual char comparison can be written more succinctly + --> tests/ui/manual_pattern_char_comparison.rs:12:20 + | +LL | sentence.split(|c: char| c == '\n' || c == 'X'); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using an array of `char`: `['\n', 'X']` + +error: this manual char comparison can be written more succinctly + --> tests/ui/manual_pattern_char_comparison.rs:13:20 + | +LL | sentence.split(|c| c == '\n' || c == 'X'); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using an array of `char`: `['\n', 'X']` + +error: this manual char comparison can be written more succinctly + --> tests/ui/manual_pattern_char_comparison.rs:14:24 + | +LL | sentence.splitn(3, |c: char| c == 'X'); + | ^^^^^^^^^^^^^^^^^^ help: consider using a `char`: `'X'` + +error: this manual char comparison can be written more succinctly + --> tests/ui/manual_pattern_char_comparison.rs:17:24 + | +LL | sentence.splitn(3, |c: char| c == char_compare); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using a `char`: `char_compare` + +error: this manual char comparison can be written more succinctly + --> tests/ui/manual_pattern_char_comparison.rs:18:20 + | +LL | sentence.split(|c: char| matches!(c, '\n' | 'X' | 'Y')); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using an array of `char`: `['\n', 'X', 'Y']` + +error: this manual char comparison can be written more succinctly + --> tests/ui/manual_pattern_char_comparison.rs:19:24 + | +LL | sentence.splitn(3, |c: char| matches!(c, 'X')); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using a `char`: `'X'` + +error: this manual char comparison can be written more succinctly + --> tests/ui/manual_pattern_char_comparison.rs:20:24 + | +LL | sentence.splitn(3, |c: char| matches!(c, 'X' | 'W')); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using an array of `char`: `['X', 'W']` + +error: this manual char comparison can be written more succinctly + --> tests/ui/manual_pattern_char_comparison.rs:21:19 + | +LL | sentence.find(|c| c == '🎈'); + | ^^^^^^^^^^^^^ help: consider using a `char`: `'🎈'` + +error: aborting due to 9 previous errors + diff --git a/tests/ui/search_is_some.rs b/tests/ui/search_is_some.rs index e8a0920b645d..9a9aaba56adc 100644 --- a/tests/ui/search_is_some.rs +++ b/tests/ui/search_is_some.rs @@ -1,5 +1,6 @@ //@aux-build:option_helpers.rs #![warn(clippy::search_is_some)] +#![allow(clippy::manual_pattern_char_comparison)] #![allow(clippy::useless_vec)] #![allow(dead_code)] extern crate option_helpers; diff --git a/tests/ui/search_is_some.stderr b/tests/ui/search_is_some.stderr index b5f84d23284a..b5ef55341770 100644 --- a/tests/ui/search_is_some.stderr +++ b/tests/ui/search_is_some.stderr @@ -1,5 +1,5 @@ error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some.rs:15:13 + --> tests/ui/search_is_some.rs:16:13 | LL | let _ = v.iter().find(|&x| { | _____________^ @@ -13,7 +13,7 @@ LL | | ).is_some(); = help: to override `-D warnings` add `#[allow(clippy::search_is_some)]` error: called `is_some()` after searching an `Iterator` with `position` - --> tests/ui/search_is_some.rs:21:13 + --> tests/ui/search_is_some.rs:22:13 | LL | let _ = v.iter().position(|&x| { | _____________^ @@ -25,7 +25,7 @@ LL | | ).is_some(); = help: this is more succinctly expressed by calling `any()` error: called `is_some()` after searching an `Iterator` with `rposition` - --> tests/ui/search_is_some.rs:27:13 + --> tests/ui/search_is_some.rs:28:13 | LL | let _ = v.iter().rposition(|&x| { | _____________^ @@ -37,13 +37,13 @@ LL | | ).is_some(); = help: this is more succinctly expressed by calling `any()` error: called `is_some()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some.rs:42:20 + --> tests/ui/search_is_some.rs:43:20 | LL | let _ = (0..1).find(some_closure).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(some_closure)` error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some.rs:52:13 + --> tests/ui/search_is_some.rs:53:13 | LL | let _ = v.iter().find(|&x| { | _____________^ @@ -55,7 +55,7 @@ LL | | ).is_none(); = help: this is more succinctly expressed by calling `any()` with negation error: called `is_none()` after searching an `Iterator` with `position` - --> tests/ui/search_is_some.rs:58:13 + --> tests/ui/search_is_some.rs:59:13 | LL | let _ = v.iter().position(|&x| { | _____________^ @@ -67,7 +67,7 @@ LL | | ).is_none(); = help: this is more succinctly expressed by calling `any()` with negation error: called `is_none()` after searching an `Iterator` with `rposition` - --> tests/ui/search_is_some.rs:64:13 + --> tests/ui/search_is_some.rs:65:13 | LL | let _ = v.iter().rposition(|&x| { | _____________^ @@ -79,7 +79,7 @@ LL | | ).is_none(); = help: this is more succinctly expressed by calling `any()` with negation error: called `is_none()` after searching an `Iterator` with `find` - --> tests/ui/search_is_some.rs:79:13 + --> tests/ui/search_is_some.rs:80:13 | LL | let _ = (0..1).find(some_closure).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!(0..1).any(some_closure)` From 614966b0c3caf7558b7ea58c09c4cf17f2497b98 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 13 Jun 2024 12:24:14 +0200 Subject: [PATCH 44/56] Bump Clippy version -> 0.1.81 --- Cargo.toml | 2 +- clippy_config/Cargo.toml | 2 +- clippy_lints/Cargo.toml | 2 +- clippy_utils/Cargo.toml | 2 +- declare_clippy_lint/Cargo.toml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b48f3ab3919c..437884990551 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy" -version = "0.1.80" +version = "0.1.81" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/clippy_config/Cargo.toml b/clippy_config/Cargo.toml index 7f7dc9d6cfb0..be0b048ac0c7 100644 --- a/clippy_config/Cargo.toml +++ b/clippy_config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_config" -version = "0.1.80" +version = "0.1.81" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 5e3a119337cc..5708ffba08fd 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_lints" -version = "0.1.80" +version = "0.1.81" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml index ab883c25338b..3a3aeb882164 100644 --- a/clippy_utils/Cargo.toml +++ b/clippy_utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_utils" -version = "0.1.80" +version = "0.1.81" edition = "2021" publish = false diff --git a/declare_clippy_lint/Cargo.toml b/declare_clippy_lint/Cargo.toml index c8c734c3a7c9..86d945c14a58 100644 --- a/declare_clippy_lint/Cargo.toml +++ b/declare_clippy_lint/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "declare_clippy_lint" -version = "0.1.80" +version = "0.1.81" edition = "2021" publish = false From 89658ef8203328cf766bb34a6fa38a23143e4137 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 13 Jun 2024 12:24:17 +0200 Subject: [PATCH 45/56] Bump nightly version -> 2024-06-13 --- rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain b/rust-toolchain index dd8b9ece773e..842c2f3de0d1 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2024-05-30" +channel = "nightly-2024-06-13" components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] From 477b0c6a78328656d4223cfdcfda2c8a05cbbe9c Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Tue, 1 Nov 2022 14:42:20 +0000 Subject: [PATCH 46/56] lintcheck: Add JSON output, diff subcommand --- lintcheck/Cargo.toml | 2 + lintcheck/src/config.rs | 37 +++++++-- lintcheck/src/json.rs | 122 +++++++++++++++++++++++++++++ lintcheck/src/main.rs | 167 +++++++++++++++++++++++++--------------- 4 files changed, 261 insertions(+), 67 deletions(-) create mode 100644 lintcheck/src/json.rs diff --git a/lintcheck/Cargo.toml b/lintcheck/Cargo.toml index 8c5a409e25b1..623af922e2a8 100644 --- a/lintcheck/Cargo.toml +++ b/lintcheck/Cargo.toml @@ -16,11 +16,13 @@ cargo_metadata = "0.15.3" clap = { version = "4.4", features = ["derive", "env"] } crates_io_api = "0.8.1" crossbeam-channel = "0.5.6" +diff = "0.1.13" flate2 = "1.0" indicatif = "0.17.3" rayon = "1.5.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.85" +strip-ansi-escapes = "0.1.1" tar = "0.4" toml = "0.7.3" ureq = "2.2" diff --git a/lintcheck/src/config.rs b/lintcheck/src/config.rs index 3f712f453fa0..c3540bbe4ce7 100644 --- a/lintcheck/src/config.rs +++ b/lintcheck/src/config.rs @@ -1,8 +1,9 @@ -use clap::Parser; +use clap::{Parser, Subcommand, ValueEnum}; use std::num::NonZero; use std::path::PathBuf; -#[derive(Clone, Debug, Parser)] +#[derive(Parser, Clone, Debug)] +#[command(args_conflicts_with_subcommands = true)] pub(crate) struct LintcheckConfig { /// Number of threads to use (default: all unless --fix or --recursive) #[clap( @@ -35,12 +36,36 @@ pub(crate) struct LintcheckConfig { /// Apply a filter to only collect specified lints, this also overrides `allow` attributes #[clap(long = "filter", value_name = "clippy_lint_name", use_value_delimiter = true)] pub lint_filter: Vec, - /// Change the reports table to use markdown links - #[clap(long)] - pub markdown: bool, + /// Set the output format of the log file + #[clap(long, short, default_value = "text")] + pub format: OutputFormat, /// Run clippy on the dependencies of crates specified in crates-toml #[clap(long, conflicts_with("max_jobs"))] pub recursive: bool, + #[command(subcommand)] + pub subcommand: Option, +} + +#[derive(Subcommand, Clone, Debug)] +pub(crate) enum Commands { + Diff { old: PathBuf, new: PathBuf }, +} + +#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum OutputFormat { + Text, + Markdown, + Json, +} + +impl OutputFormat { + fn file_extension(self) -> &'static str { + match self { + OutputFormat::Text => "txt", + OutputFormat::Markdown => "md", + OutputFormat::Json => "json", + } + } } impl LintcheckConfig { @@ -53,7 +78,7 @@ impl LintcheckConfig { config.lintcheck_results_path = PathBuf::from(format!( "lintcheck-logs/{}_logs.{}", filename.display(), - if config.markdown { "md" } else { "txt" } + config.format.file_extension(), )); // look at the --threads arg, if 0 is passed, use the threads count diff --git a/lintcheck/src/json.rs b/lintcheck/src/json.rs new file mode 100644 index 000000000000..43d0413c7cee --- /dev/null +++ b/lintcheck/src/json.rs @@ -0,0 +1,122 @@ +use std::collections::HashMap; +use std::fmt::Write; +use std::fs; +use std::hash::Hash; +use std::path::Path; + +use crate::ClippyWarning; + +/// Creates the log file output for [`crate::config::OutputFormat::Json`] +pub(crate) fn output(clippy_warnings: &[ClippyWarning]) -> String { + serde_json::to_string(&clippy_warnings).unwrap() +} + +fn load_warnings(path: &Path) -> Vec { + let file = fs::read(path).unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display())); + + serde_json::from_slice(&file).unwrap_or_else(|e| panic!("failed to deserialize {}: {e}", path.display())) +} + +/// Group warnings by their primary span location + lint name +fn create_map(warnings: &[ClippyWarning]) -> HashMap> { + let mut map = HashMap::<_, Vec<_>>::with_capacity(warnings.len()); + + for warning in warnings { + let span = warning.span(); + let key = (&warning.lint_type, &span.file_name, span.byte_start, span.byte_end); + + map.entry(key).or_default().push(warning); + } + + map +} + +fn print_warnings(title: &str, warnings: &[&ClippyWarning]) { + if warnings.is_empty() { + return; + } + + println!("### {title}"); + println!("```"); + for warning in warnings { + print!("{}", warning.diag); + } + println!("```"); +} + +fn print_changed_diff(changed: &[(&[&ClippyWarning], &[&ClippyWarning])]) { + fn render(warnings: &[&ClippyWarning]) -> String { + let mut rendered = String::new(); + for warning in warnings { + write!(&mut rendered, "{}", warning.diag).unwrap(); + } + rendered + } + + if changed.is_empty() { + return; + } + + println!("### Changed"); + println!("```diff"); + for &(old, new) in changed { + let old_rendered = render(old); + let new_rendered = render(new); + + for change in diff::lines(&old_rendered, &new_rendered) { + use diff::Result::{Both, Left, Right}; + + match change { + Both(unchanged, _) => { + println!(" {unchanged}"); + }, + Left(removed) => { + println!("-{removed}"); + }, + Right(added) => { + println!("+{added}"); + }, + } + } + } + println!("```"); +} + +pub(crate) fn diff(old_path: &Path, new_path: &Path) { + let old_warnings = load_warnings(old_path); + let new_warnings = load_warnings(new_path); + + let old_map = create_map(&old_warnings); + let new_map = create_map(&new_warnings); + + let mut added = Vec::new(); + let mut removed = Vec::new(); + let mut changed = Vec::new(); + + for (key, new) in &new_map { + if let Some(old) = old_map.get(key) { + if old != new { + changed.push((old.as_slice(), new.as_slice())); + } + } else { + added.extend(new); + } + } + + for (key, old) in &old_map { + if !new_map.contains_key(key) { + removed.extend(old); + } + } + + print!( + "{} added, {} removed, {} changed\n\n", + added.len(), + removed.len(), + changed.len() + ); + + print_warnings("Added", &added); + print_warnings("Removed", &removed); + print_changed_diff(&changed); +} diff --git a/lintcheck/src/main.rs b/lintcheck/src/main.rs index deb06094d83b..985df9647d82 100644 --- a/lintcheck/src/main.rs +++ b/lintcheck/src/main.rs @@ -12,18 +12,20 @@ unused_lifetimes, unused_qualifications )] -#![allow(clippy::collapsible_else_if)] +#![allow(clippy::collapsible_else_if, clippy::needless_borrows_for_generic_args)] mod config; mod driver; +mod json; mod recursive; -use crate::config::LintcheckConfig; +use crate::config::{Commands, LintcheckConfig, OutputFormat}; use crate::recursive::LintcheckServer; use std::collections::{HashMap, HashSet}; use std::env::consts::EXE_SUFFIX; -use std::fmt::{self, Write as _}; +use std::fmt::{self, Display, Write as _}; +use std::hash::Hash; use std::io::{self, ErrorKind}; use std::path::{Path, PathBuf}; use std::process::{Command, ExitStatus}; @@ -31,7 +33,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::Duration; use std::{env, fs, thread}; -use cargo_metadata::diagnostic::Diagnostic; +use cargo_metadata::diagnostic::{Diagnostic, DiagnosticSpan}; use cargo_metadata::Message; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -111,6 +113,17 @@ struct RustcIce { pub crate_name: String, pub ice_content: String, } + +impl Display for RustcIce { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}:\n{}\n========================================\n", + self.crate_name, self.ice_content + ) + } +} + impl RustcIce { pub fn from_stderr_and_status(crate_name: &str, status: ExitStatus, stderr: &str) -> Option { if status.code().unwrap_or(0) == 101 @@ -127,60 +140,58 @@ impl RustcIce { } /// A single warning that clippy issued while checking a `Crate` -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] struct ClippyWarning { - file: String, - line: usize, - column: usize, + crate_name: String, + crate_version: String, lint_type: String, - message: String, + diag: Diagnostic, } #[allow(unused)] impl ClippyWarning { - fn new(diag: Diagnostic, crate_name: &str, crate_version: &str) -> Option { - let lint_type = diag.code?.code; + fn new(mut diag: Diagnostic, crate_name: &str, crate_version: &str) -> Option { + let lint_type = diag.code.clone()?.code; if !(lint_type.contains("clippy") || diag.message.contains("clippy")) || diag.message.contains("could not read cargo metadata") { return None; } - let span = diag.spans.into_iter().find(|span| span.is_primary)?; - - let file = if let Ok(stripped) = Path::new(&span.file_name).strip_prefix(env!("CARGO_HOME")) { - format!("$CARGO_HOME/{}", stripped.display()) - } else { - format!( - "target/lintcheck/sources/{crate_name}-{crate_version}/{}", - span.file_name - ) - }; + // --recursive bypasses cargo so we have to strip the rendered output ourselves + let rendered = diag.rendered.as_mut().unwrap(); + *rendered = String::from_utf8(strip_ansi_escapes::strip(&rendered).unwrap()).unwrap(); Some(Self { - file, - line: span.line_start, - column: span.column_start, + crate_name: crate_name.to_owned(), + crate_version: crate_version.to_owned(), lint_type, - message: diag.message, + diag, }) } - fn to_output(&self, markdown: bool) -> String { - let file_with_pos = format!("{}:{}:{}", &self.file, &self.line, &self.column); - if markdown { - let mut file = self.file.clone(); - if !file.starts_with('$') { - file.insert_str(0, "../"); - } + fn span(&self) -> &DiagnosticSpan { + self.diag.spans.iter().find(|span| span.is_primary).unwrap() + } - let mut output = String::from("| "); - let _: fmt::Result = write!(output, "[`{file_with_pos}`]({file}#L{})", self.line); - let _: fmt::Result = write!(output, r#" | `{:<50}` | "{}" |"#, self.lint_type, self.message); - output.push('\n'); - output - } else { - format!("{file_with_pos} {} \"{}\"\n", self.lint_type, self.message) + fn to_output(&self, format: OutputFormat) -> String { + let span = self.span(); + let mut file = span.file_name.clone(); + let file_with_pos = format!("{file}:{}:{}", span.line_start, span.line_end); + match format { + OutputFormat::Text => format!("{file_with_pos} {} \"{}\"\n", self.lint_type, self.diag.message), + OutputFormat::Markdown => { + if file.starts_with("target") { + file.insert_str(0, "../"); + } + + let mut output = String::from("| "); + write!(output, "[`{file_with_pos}`]({file}#L{})", span.line_start).unwrap(); + write!(output, r#" | `{:<50}` | "{}" |"#, self.lint_type, self.diag.message).unwrap(); + output.push('\n'); + output + }, + OutputFormat::Json => unreachable!("JSON output is handled via serde"), } } } @@ -333,7 +344,7 @@ impl CrateSource { impl Crate { /// Run `cargo clippy` on the `Crate` and collect and return all the lint warnings that clippy /// issued - #[allow(clippy::too_many_arguments)] + #[allow(clippy::too_many_arguments, clippy::too_many_lines)] fn run_clippy_lints( &self, cargo_clippy_path: &Path, @@ -372,7 +383,25 @@ impl Crate { vec!["--quiet", "--message-format=json", "--"] }; - let mut clippy_args = Vec::<&str>::new(); + let cargo_home = env!("CARGO_HOME"); + + // `src/lib.rs` -> `target/lintcheck/sources/crate-1.2.3/src/lib.rs` + let remap_relative = format!("={}", self.path.display()); + // Fallback for other sources, `~/.cargo/...` -> `$CARGO_HOME/...` + let remap_cargo_home = format!("{cargo_home}=$CARGO_HOME"); + // `~/.cargo/registry/src/github.com-1ecc6299db9ec823/crate-2.3.4/src/lib.rs` + // -> `crate-2.3.4/src/lib.rs` + let remap_crates_io = format!("{cargo_home}/registry/src/github.com-1ecc6299db9ec823/="); + + let mut clippy_args = vec![ + "--remap-path-prefix", + &remap_relative, + "--remap-path-prefix", + &remap_cargo_home, + "--remap-path-prefix", + &remap_crates_io, + ]; + if let Some(options) = &self.options { for opt in options { clippy_args.push(opt); @@ -554,10 +583,10 @@ fn read_crates(toml_path: &Path) -> (Vec, RecursiveOptions) { } /// Generate a short list of occurring lints-types and their count -fn gather_stats(clippy_warnings: &[ClippyWarning]) -> (String, HashMap<&String, usize>) { +fn gather_stats(warnings: &[ClippyWarning]) -> (String, HashMap<&String, usize>) { // count lint type occurrences let mut counter: HashMap<&String, usize> = HashMap::new(); - clippy_warnings + warnings .iter() .for_each(|wrn| *counter.entry(&wrn.lint_type).or_insert(0) += 1); @@ -595,6 +624,11 @@ fn main() { let config = LintcheckConfig::new(); + if let Some(Commands::Diff { old, new }) = config.subcommand { + json::diff(&old, &new); + return; + } + println!("Compiling clippy..."); build_clippy(); println!("Done compiling"); @@ -619,7 +653,6 @@ fn main() { // flatten into one big list of warnings let (crates, recursive_options) = read_crates(&config.sources_toml_path); - let old_stats = read_stats_from_file(&config.lintcheck_results_path); let counter = AtomicUsize::new(1); let lint_filter: Vec = config @@ -711,39 +744,51 @@ fn main() { } } + let text = match config.format { + OutputFormat::Text | OutputFormat::Markdown => output(&warnings, &raw_ices, clippy_ver, &config), + OutputFormat::Json => { + if !raw_ices.is_empty() { + for ice in raw_ices { + println!("{ice}"); + } + panic!("Some crates ICEd"); + } + + json::output(&warnings) + }, + }; + + println!("Writing logs to {}", config.lintcheck_results_path.display()); + fs::create_dir_all(config.lintcheck_results_path.parent().unwrap()).unwrap(); + fs::write(&config.lintcheck_results_path, text).unwrap(); +} + +/// Creates the log file output for [`OutputFormat::Text`] and [`OutputFormat::Markdown`] +fn output(warnings: &[ClippyWarning], ices: &[RustcIce], clippy_ver: String, config: &LintcheckConfig) -> String { // generate some stats - let (stats_formatted, new_stats) = gather_stats(&warnings); + let (stats_formatted, new_stats) = gather_stats(warnings); + let old_stats = read_stats_from_file(&config.lintcheck_results_path); - let mut all_msgs: Vec = warnings.iter().map(|warn| warn.to_output(config.markdown)).collect(); + let mut all_msgs: Vec = warnings.iter().map(|warn| warn.to_output(config.format)).collect(); all_msgs.sort(); all_msgs.push("\n\n### Stats:\n\n".into()); all_msgs.push(stats_formatted); - // save the text into lintcheck-logs/logs.txt let mut text = clippy_ver; // clippy version number on top text.push_str("\n### Reports\n\n"); - if config.markdown { + if config.format == OutputFormat::Markdown { text.push_str("| file | lint | message |\n"); text.push_str("| --- | --- | --- |\n"); } write!(text, "{}", all_msgs.join("")).unwrap(); text.push_str("\n\n### ICEs:\n"); - for ice in &raw_ices { - let _: fmt::Result = write!( - text, - "{}:\n{}\n========================================\n\n", - ice.crate_name, ice.ice_content - ); + for ice in ices { + writeln!(text, "{ice}").unwrap(); } - println!("Writing logs to {}", config.lintcheck_results_path.display()); - if !raw_ices.is_empty() { - println!("WARNING: at least one ICE reported, check log file"); - } - fs::create_dir_all(config.lintcheck_results_path.parent().unwrap()).unwrap(); - fs::write(&config.lintcheck_results_path, text).unwrap(); - print_stats(old_stats, new_stats, &config.lint_filter); + + text } /// read the previous stats from the lintcheck-log file From feb0671893505272bdad18be595ea2c4dcea2063 Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Fri, 24 Feb 2023 17:35:27 +0000 Subject: [PATCH 47/56] Add lintcheck diff github action --- .github/workflows/lintcheck.yml | 115 ++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 .github/workflows/lintcheck.yml diff --git a/.github/workflows/lintcheck.yml b/.github/workflows/lintcheck.yml new file mode 100644 index 000000000000..0816e5334e20 --- /dev/null +++ b/.github/workflows/lintcheck.yml @@ -0,0 +1,115 @@ +name: Lintcheck + +on: pull_request + +env: + RUST_BACKTRACE: 1 + CARGO_INCREMENTAL: 0 + +concurrency: + # For a given workflow, if we push to the same PR, cancel all previous builds on that PR. + group: "${{ github.workflow }}-${{ github.event.pull_request.number}}" + cancel-in-progress: true + +jobs: + # Generates `lintcheck-logs/base.json` and stores it in a cache + base: + runs-on: ubuntu-latest + + outputs: + key: ${{ steps.key.outputs.key }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + # HEAD is the generated merge commit `refs/pull/N/merge` between the PR and `master`, `HEAD^` + # being the commit from `master` that is the base of the merge + - name: Checkout base + run: git checkout HEAD^ + + # Use the lintcheck from the PR to generate the JSON in case the PR modifies lintcheck in some + # way + - name: Checkout current lintcheck + run: | + rm -rf lintcheck + git checkout ${{ github.sha }} -- lintcheck + + - name: Create cache key + id: key + run: echo "key=lintcheck-base-${{ hashfiles('lintcheck/**') }}-$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + + - name: Cache results + id: cache + uses: actions/cache@v4 + with: + path: lintcheck-logs/base.json + key: ${{ steps.key.outputs.key }} + + - name: Run lintcheck + if: steps.cache.outputs.cache-hit != 'true' + run: cargo lintcheck --format json + + - name: Rename JSON file + if: steps.cache.outputs.cache-hit != 'true' + run: mv lintcheck-logs/lintcheck_crates_logs.json lintcheck-logs/base.json + + # Generates `lintcheck-logs/head.json` and stores it in a cache + head: + runs-on: ubuntu-latest + + outputs: + key: ${{ steps.key.outputs.key }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Create cache key + id: key + run: echo "key=lintcheck-head-${{ github.sha }}" >> "$GITHUB_OUTPUT" + + - name: Cache results + id: cache + uses: actions/cache@v4 + with: + path: lintcheck-logs/head.json + key: ${{ steps.key.outputs.key }} + + - name: Run lintcheck + if: steps.cache.outputs.cache-hit != 'true' + run: cargo lintcheck --format json + + - name: Rename JSON file + if: steps.cache.outputs.cache-hit != 'true' + run: mv lintcheck-logs/lintcheck_crates_logs.json lintcheck-logs/head.json + + # Retrieves `lintcheck-logs/base.json` and `lintcheck-logs/head.json` from the cache and prints + # the diff to the GH actions step summary + diff: + runs-on: ubuntu-latest + + needs: [base, head] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Restore base JSON + uses: actions/cache/restore@v4 + with: + key: ${{ needs.base.outputs.key }} + path: lintcheck-logs/base.json + fail-on-cache-miss: true + + - name: Restore head JSON + uses: actions/cache/restore@v4 + with: + key: ${{ needs.head.outputs.key }} + path: lintcheck-logs/head.json + fail-on-cache-miss: true + + - name: Diff results + run: cargo lintcheck diff lintcheck-logs/base.json lintcheck-logs/head.json >> $GITHUB_STEP_SUMMARY From 63388cbab88a7207d3a3959a92a9ac84e9211336 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Sat, 15 Jun 2024 21:45:35 +0200 Subject: [PATCH 48/56] add MSRV for manual_pattern_char_comparison --- book/src/lint_configuration.md | 1 + clippy_config/src/conf.rs | 2 +- clippy_lints/src/lib.rs | 2 +- clippy_lints/src/string_patterns.rs | 22 ++++++++++++++++--- tests/ui/manual_pattern_char_comparison.fixed | 12 ++++++++++ tests/ui/manual_pattern_char_comparison.rs | 12 ++++++++++ .../ui/manual_pattern_char_comparison.stderr | 8 ++++++- 7 files changed, 53 insertions(+), 6 deletions(-) diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index c8223007df7b..adb5b0a54909 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -693,6 +693,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`manual_is_ascii_check`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check) * [`manual_let_else`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else) * [`manual_non_exhaustive`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive) +* [`manual_pattern_char_comparison`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_pattern_char_comparison) * [`manual_range_contains`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains) * [`manual_rem_euclid`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid) * [`manual_retain`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain) diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index cfdf620b7d07..93ca535a7a46 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -265,7 +265,7 @@ define_Conf! { /// /// Suppress lints whenever the suggested change would cause breakage for other crates. (avoid_breaking_exported_api: bool = true), - /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, MANUAL_HASH_ONE, ITER_KV_MAP, MANUAL_C_STR_LITERALS, ASSIGNING_CLONES, LEGACY_NUMERIC_CONSTANTS. + /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, MANUAL_HASH_ONE, ITER_KV_MAP, MANUAL_C_STR_LITERALS, ASSIGNING_CLONES, LEGACY_NUMERIC_CONSTANTS, MANUAL_PATTERN_CHAR_COMPARISON. /// /// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml` #[default_text = ""] diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c65581d5203e..50274a715ec1 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1168,7 +1168,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { ..Default::default() }) }); - store.register_late_pass(|_| Box::new(string_patterns::StringPatterns)); + store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(msrv()))); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/string_patterns.rs b/clippy_lints/src/string_patterns.rs index 64b5b8f9f27b..084a4b597322 100644 --- a/clippy_lints/src/string_patterns.rs +++ b/clippy_lints/src/string_patterns.rs @@ -1,5 +1,6 @@ use std::ops::ControlFlow; +use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::macros::matching_root_macro_call; @@ -12,7 +13,7 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; use rustc_span::{sym, Span}; declare_clippy_lint! { @@ -69,7 +70,18 @@ declare_clippy_lint! { "using a single-character str where a char could be used, e.g., `_.split(\"x\")`" } -declare_lint_pass!(StringPatterns => [MANUAL_PATTERN_CHAR_COMPARISON, SINGLE_CHAR_PATTERN]); +pub struct StringPatterns { + msrv: Msrv, +} + +impl StringPatterns { + #[must_use] + pub fn new(msrv: Msrv) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(StringPatterns => [MANUAL_PATTERN_CHAR_COMPARISON, SINGLE_CHAR_PATTERN]); const PATTERN_METHODS: [(&str, usize); 22] = [ ("contains", 0), @@ -220,8 +232,12 @@ impl<'tcx> LateLintPass<'tcx> for StringPatterns { && let Some(arg) = args.get(pos) { check_single_char_pattern_lint(cx, arg); - + if !self.msrv.meets(msrvs::PATTERN_TRAIT_CHAR_ARRAY) { + return; + } check_manual_pattern_char_comparison(cx, arg); } } + + extract_msrv_attr!(LateContext); } diff --git a/tests/ui/manual_pattern_char_comparison.fixed b/tests/ui/manual_pattern_char_comparison.fixed index 588226b87e87..6020d7071bdf 100644 --- a/tests/ui/manual_pattern_char_comparison.fixed +++ b/tests/ui/manual_pattern_char_comparison.fixed @@ -47,3 +47,15 @@ fn main() { } "".find(|c| m!(c)); } + +#[clippy::msrv = "1.57"] +fn msrv_1_57() { + let sentence = "Hello, world!"; + sentence.trim_end_matches(|c: char| c == '.' || c == ',' || c == '!' || c == '?'); +} + +#[clippy::msrv = "1.58"] +fn msrv_1_58() { + let sentence = "Hello, world!"; + sentence.trim_end_matches(['.', ',', '!', '?']); +} \ No newline at end of file diff --git a/tests/ui/manual_pattern_char_comparison.rs b/tests/ui/manual_pattern_char_comparison.rs index 5078f3ee27f3..43e883cd325a 100644 --- a/tests/ui/manual_pattern_char_comparison.rs +++ b/tests/ui/manual_pattern_char_comparison.rs @@ -47,3 +47,15 @@ fn main() { } "".find(|c| m!(c)); } + +#[clippy::msrv = "1.57"] +fn msrv_1_57() { + let sentence = "Hello, world!"; + sentence.trim_end_matches(|c: char| c == '.' || c == ',' || c == '!' || c == '?'); +} + +#[clippy::msrv = "1.58"] +fn msrv_1_58() { + let sentence = "Hello, world!"; + sentence.trim_end_matches(|c: char| c == '.' || c == ',' || c == '!' || c == '?'); +} diff --git a/tests/ui/manual_pattern_char_comparison.stderr b/tests/ui/manual_pattern_char_comparison.stderr index b6b51794a11f..f185d7c8f676 100644 --- a/tests/ui/manual_pattern_char_comparison.stderr +++ b/tests/ui/manual_pattern_char_comparison.stderr @@ -55,5 +55,11 @@ error: this manual char comparison can be written more succinctly LL | sentence.find(|c| c == '🎈'); | ^^^^^^^^^^^^^ help: consider using a `char`: `'🎈'` -error: aborting due to 9 previous errors +error: this manual char comparison can be written more succinctly + --> tests/ui/manual_pattern_char_comparison.rs:60:31 + | +LL | sentence.trim_end_matches(|c: char| c == '.' || c == ',' || c == '!' || c == '?'); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using an array of `char`: `['.', ',', '!', '?']` + +error: aborting due to 10 previous errors From 51c6630d4f70d3c371cbda470358f85e9e2c3918 Mon Sep 17 00:00:00 2001 From: AurelienFT Date: Sat, 15 Jun 2024 21:52:42 +0200 Subject: [PATCH 49/56] Change MSRV check for manual_pattern_char_comparison only for pattern arrays --- clippy_lints/src/string_patterns.rs | 11 ++++++----- tests/ui/manual_pattern_char_comparison.fixed | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/string_patterns.rs b/clippy_lints/src/string_patterns.rs index 084a4b597322..7ba58942a175 100644 --- a/clippy_lints/src/string_patterns.rs +++ b/clippy_lints/src/string_patterns.rs @@ -134,7 +134,7 @@ fn get_char_span<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Optio } } -fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr<'_>) { +fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr<'_>, msrv: &Msrv) { if let ExprKind::Closure(closure) = method_arg.kind && let body = cx.tcx.hir().body(closure.body) && let Some(PatKind::Binding(_, binding, ..)) = body.params.first().map(|p| p.pat.kind) @@ -190,6 +190,9 @@ fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr< { return; } + if set_char_spans.len() > 1 && !msrv.meets(msrvs::PATTERN_TRAIT_CHAR_ARRAY) { + return; + } span_lint_and_then( cx, MANUAL_PATTERN_CHAR_COMPARISON, @@ -232,10 +235,8 @@ impl<'tcx> LateLintPass<'tcx> for StringPatterns { && let Some(arg) = args.get(pos) { check_single_char_pattern_lint(cx, arg); - if !self.msrv.meets(msrvs::PATTERN_TRAIT_CHAR_ARRAY) { - return; - } - check_manual_pattern_char_comparison(cx, arg); + + check_manual_pattern_char_comparison(cx, arg, &self.msrv); } } diff --git a/tests/ui/manual_pattern_char_comparison.fixed b/tests/ui/manual_pattern_char_comparison.fixed index 6020d7071bdf..03e621d95ba1 100644 --- a/tests/ui/manual_pattern_char_comparison.fixed +++ b/tests/ui/manual_pattern_char_comparison.fixed @@ -58,4 +58,4 @@ fn msrv_1_57() { fn msrv_1_58() { let sentence = "Hello, world!"; sentence.trim_end_matches(['.', ',', '!', '?']); -} \ No newline at end of file +} From bcc7b0e7000ccd13736e6d9f039858fa2f919e28 Mon Sep 17 00:00:00 2001 From: Elijah Potter Date: Sat, 15 Jun 2024 13:55:16 -0600 Subject: [PATCH 50/56] Fix minor typo --- book/src/usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/usage.md b/book/src/usage.md index 36448e4cccfa..7a0be6994fe1 100644 --- a/book/src/usage.md +++ b/book/src/usage.md @@ -36,7 +36,7 @@ You can configure lint levels on the command line by adding cargo clippy -- -Aclippy::style -Wclippy::double_neg -Dclippy::perf ``` -For [CI] all warnings can be elevated to errors which will inturn fail +For [CI] all warnings can be elevated to errors which will in turn fail the build and cause Clippy to exit with a code other than `0`. ``` From 3a983c399aecc348c5f01a6dcaa03f4f8ac0ea7e Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Sun, 16 Jun 2024 18:28:00 +0000 Subject: [PATCH 51/56] Merge lintcheck popular-crates bin as a subcommand --- lintcheck/Cargo.toml | 13 +------ lintcheck/README.md | 6 +-- lintcheck/src/config.rs | 9 +++++ lintcheck/src/main.rs | 20 ++++++---- lintcheck/src/popular-crates.rs | 65 --------------------------------- lintcheck/src/popular_crates.rs | 52 ++++++++++++++++++++++++++ 6 files changed, 77 insertions(+), 88 deletions(-) delete mode 100644 lintcheck/src/popular-crates.rs create mode 100644 lintcheck/src/popular_crates.rs diff --git a/lintcheck/Cargo.toml b/lintcheck/Cargo.toml index 623af922e2a8..e0e94d7fec3e 100644 --- a/lintcheck/Cargo.toml +++ b/lintcheck/Cargo.toml @@ -11,30 +11,19 @@ publish = false default-run = "lintcheck" [dependencies] -anyhow = "1.0.69" cargo_metadata = "0.15.3" clap = { version = "4.4", features = ["derive", "env"] } -crates_io_api = "0.8.1" crossbeam-channel = "0.5.6" diff = "0.1.13" flate2 = "1.0" -indicatif = "0.17.3" rayon = "1.5.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.85" strip-ansi-escapes = "0.1.1" tar = "0.4" toml = "0.7.3" -ureq = "2.2" +ureq = { version = "2.2", features = ["json"] } walkdir = "2.3" [features] deny-warnings = [] - -[[bin]] -name = "lintcheck" -path = "src/main.rs" - -[[bin]] -name = "popular-crates" -path = "src/popular-crates.rs" diff --git a/lintcheck/README.md b/lintcheck/README.md index 61b581ba0fae..2d6039caeef0 100644 --- a/lintcheck/README.md +++ b/lintcheck/README.md @@ -26,11 +26,11 @@ the repo root. The results will then be saved to `lintcheck-logs/custom_logs.toml`. The `custom.toml` file may be built using recently most -downloaded crates by using the `popular-crates` binary from the `lintcheck` -directory. For example, to retrieve the 100 recently most downloaded crates: +downloaded crates by using `cargo lintcheck popular`. For example, to retrieve +the 200 recently most downloaded crates: ``` -cargo run --release --bin popular-crates -- -n 100 custom.toml +cargo lintcheck popular -n 200 custom.toml ``` diff --git a/lintcheck/src/config.rs b/lintcheck/src/config.rs index c3540bbe4ce7..e6cd7c9fdc2b 100644 --- a/lintcheck/src/config.rs +++ b/lintcheck/src/config.rs @@ -48,7 +48,16 @@ pub(crate) struct LintcheckConfig { #[derive(Subcommand, Clone, Debug)] pub(crate) enum Commands { + /// Display a markdown diff between two lintcheck log files in JSON format Diff { old: PathBuf, new: PathBuf }, + /// Create a lintcheck crates TOML file containing the top N popular crates + Popular { + /// Output TOML file name + output: PathBuf, + /// Number of crate names to download + #[clap(short, long, default_value_t = 100)] + number: usize, + }, } #[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)] diff --git a/lintcheck/src/main.rs b/lintcheck/src/main.rs index 985df9647d82..c246883c7045 100644 --- a/lintcheck/src/main.rs +++ b/lintcheck/src/main.rs @@ -17,6 +17,7 @@ mod config; mod driver; mod json; +mod popular_crates; mod recursive; use crate::config::{Commands, LintcheckConfig, OutputFormat}; @@ -43,21 +44,21 @@ const LINTCHECK_DOWNLOADS: &str = "target/lintcheck/downloads"; const LINTCHECK_SOURCES: &str = "target/lintcheck/sources"; /// List of sources to check, loaded from a .toml file -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize)] struct SourceList { crates: HashMap, #[serde(default)] recursive: RecursiveOptions, } -#[derive(Debug, Serialize, Deserialize, Default)] +#[derive(Debug, Deserialize, Default)] struct RecursiveOptions { ignore: HashSet, } /// A crate source stored inside the .toml /// will be translated into on one of the `CrateSource` variants -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize)] struct TomlCrate { name: String, versions: Option>, @@ -69,7 +70,7 @@ struct TomlCrate { /// Represents an archive we download from crates.io, or a git repo, or a local repo/folder /// Once processed (downloaded/extracted/cloned/copied...), this will be translated into a `Crate` -#[derive(Debug, Serialize, Deserialize, Eq, Hash, PartialEq, Ord, PartialOrd)] +#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Ord, PartialOrd)] enum CrateSource { CratesIo { name: String, @@ -609,7 +610,6 @@ fn gather_stats(warnings: &[ClippyWarning]) -> (String, HashMap<&String, usize>) (stats_string, counter) } -#[allow(clippy::too_many_lines)] fn main() { // We're being executed as a `RUSTC_WRAPPER` as part of `--recursive` if let Ok(addr) = env::var("LINTCHECK_SERVER") { @@ -624,11 +624,15 @@ fn main() { let config = LintcheckConfig::new(); - if let Some(Commands::Diff { old, new }) = config.subcommand { - json::diff(&old, &new); - return; + match config.subcommand { + Some(Commands::Diff { old, new }) => json::diff(&old, &new), + Some(Commands::Popular { output, number }) => popular_crates::fetch(output, number).unwrap(), + None => lintcheck(config), } +} +#[allow(clippy::too_many_lines)] +fn lintcheck(config: LintcheckConfig) { println!("Compiling clippy..."); build_clippy(); println!("Done compiling"); diff --git a/lintcheck/src/popular-crates.rs b/lintcheck/src/popular-crates.rs deleted file mode 100644 index fdab984ad86c..000000000000 --- a/lintcheck/src/popular-crates.rs +++ /dev/null @@ -1,65 +0,0 @@ -#![deny(clippy::pedantic)] - -use clap::Parser; -use crates_io_api::{CratesQueryBuilder, Sort, SyncClient}; -use indicatif::ProgressBar; -use std::collections::HashSet; -use std::fs::File; -use std::io::{BufWriter, Write}; -use std::path::PathBuf; -use std::time::Duration; - -#[derive(Parser)] -struct Opts { - /// Output TOML file name - output: PathBuf, - /// Number of crate names to download - #[clap(short, long, default_value_t = 100)] - number: usize, - /// Do not output progress - #[clap(short, long)] - quiet: bool, -} - -fn main() -> anyhow::Result<()> { - let opts = Opts::parse(); - let mut output = BufWriter::new(File::create(opts.output)?); - output.write_all(b"[crates]\n")?; - let client = SyncClient::new( - "clippy/lintcheck (github.com/rust-lang/rust-clippy/)", - Duration::from_secs(1), - )?; - let mut seen_crates = HashSet::new(); - let pb = if opts.quiet { - None - } else { - Some(ProgressBar::new(opts.number as u64)) - }; - let mut query = CratesQueryBuilder::new() - .sort(Sort::RecentDownloads) - .page_size(100) - .build(); - while seen_crates.len() < opts.number { - let retrieved = client.crates(query.clone())?.crates; - if retrieved.is_empty() { - eprintln!("No more than {} crates available from API", seen_crates.len()); - break; - } - for c in retrieved { - if seen_crates.insert(c.name.clone()) { - output.write_all( - format!( - "{} = {{ name = '{}', versions = ['{}'] }}\n", - c.name, c.name, c.max_version - ) - .as_bytes(), - )?; - if let Some(pb) = &pb { - pb.inc(1); - } - } - } - query.set_page(query.page() + 1); - } - Ok(()) -} diff --git a/lintcheck/src/popular_crates.rs b/lintcheck/src/popular_crates.rs new file mode 100644 index 000000000000..880a8bd81f08 --- /dev/null +++ b/lintcheck/src/popular_crates.rs @@ -0,0 +1,52 @@ +use serde::Deserialize; +use std::error::Error; +use std::fmt::Write; +use std::fs; +use std::path::PathBuf; + +#[derive(Deserialize, Debug)] +struct Page { + crates: Vec, + meta: Meta, +} + +#[derive(Deserialize, Debug)] +struct Crate { + name: String, + max_version: String, +} + +#[derive(Deserialize, Debug)] +struct Meta { + next_page: String, +} + +pub(crate) fn fetch(output: PathBuf, number: usize) -> Result<(), Box> { + let agent = ureq::builder() + .user_agent("clippy/lintcheck (github.com/rust-lang/rust-clippy/)") + .build(); + + let mut crates = Vec::with_capacity(number); + let mut query = "?sort=recent-downloads&per_page=100".to_string(); + while crates.len() < number { + let page: Page = agent + .get(&format!("https://crates.io/api/v1/crates{query}")) + .call()? + .into_json()?; + + query = page.meta.next_page; + crates.extend(page.crates); + crates.truncate(number); + + let width = number.ilog10() as usize + 1; + println!("Fetched {:>width$}/{number} crates", crates.len()); + } + + let mut out = "[crates]\n".to_string(); + for Crate { name, max_version } in crates { + writeln!(out, "{name} = {{ name = '{name}', versions = ['{max_version}'] }}").unwrap(); + } + fs::write(output, out)?; + + Ok(()) +} From 3405ce3bcaeb445bc780a5ad863524dea151a385 Mon Sep 17 00:00:00 2001 From: kyle oneill Date: Sun, 16 Jun 2024 15:54:48 -0400 Subject: [PATCH 52/56] Add field_scoped_visibility_modifiers lint --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + .../src/field_scoped_visibility_modifiers.rs | 75 +++++++++++++++++++ clippy_lints/src/lib.rs | 2 + tests/ui/field_scoped_visibility_modifiers.rs | 21 ++++++ .../field_scoped_visibility_modifiers.stderr | 28 +++++++ 6 files changed, 128 insertions(+) create mode 100644 clippy_lints/src/field_scoped_visibility_modifiers.rs create mode 100644 tests/ui/field_scoped_visibility_modifiers.rs create mode 100644 tests/ui/field_scoped_visibility_modifiers.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index d7bcd7a19687..4de3124bd3da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5362,6 +5362,7 @@ Released 2018-09-13 [`extra_unused_type_parameters`]: https://rust-lang.github.io/rust-clippy/master/index.html#extra_unused_type_parameters [`fallible_impl_from`]: https://rust-lang.github.io/rust-clippy/master/index.html#fallible_impl_from [`field_reassign_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#field_reassign_with_default +[`field_scoped_visibility_modifiers`]: https://rust-lang.github.io/rust-clippy/master/index.html#field_scoped_visibility_modifiers [`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 diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 7e43a99e9f24..88986f9ad727 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -178,6 +178,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::explicit_write::EXPLICIT_WRITE_INFO, crate::extra_unused_type_parameters::EXTRA_UNUSED_TYPE_PARAMETERS_INFO, crate::fallible_impl_from::FALLIBLE_IMPL_FROM_INFO, + crate::field_scoped_visibility_modifiers::FIELD_SCOPED_VISIBILITY_MODIFIERS_INFO, crate::float_literal::EXCESSIVE_PRECISION_INFO, crate::float_literal::LOSSY_FLOAT_LITERAL_INFO, crate::floating_point_arithmetic::IMPRECISE_FLOPS_INFO, diff --git a/clippy_lints/src/field_scoped_visibility_modifiers.rs b/clippy_lints/src/field_scoped_visibility_modifiers.rs new file mode 100644 index 000000000000..bb74e345703f --- /dev/null +++ b/clippy_lints/src/field_scoped_visibility_modifiers.rs @@ -0,0 +1,75 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use rustc_ast::ast::{Item, ItemKind, VisibilityKind}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::declare_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of scoped visibility modifiers, like `pub(crate)`, on fields. These + /// make a field visible within a scope between public and private. + /// + /// ### Why restrict this? + /// Scoped visibility modifiers cause a field to be accessible within some scope between + /// public and private, potentially within an entire crate. This allows for fields to be + /// non-private while upholding internal invariants, but can be a code smell. Scoped visibility + /// requires checking a greater area, potentially an entire crate, to verify that an invariant + /// is upheld, and global analysis requires a lot of effort. + /// + /// ### Example + /// ```no_run + /// pub mod public_module { + /// struct MyStruct { + /// pub(crate) first_field: bool, + /// pub(super) second_field: bool + /// } + /// } + /// ``` + /// Use instead: + /// ```no_run + /// pub mod public_module { + /// struct MyStruct { + /// first_field: bool, + /// second_field: bool + /// } + /// impl MyStruct { + /// pub(crate) fn get_first_field(&self) -> bool { + /// self.first_field + /// } + /// pub(super) fn get_second_field(&self) -> bool { + /// self.second_field + /// } + /// } + /// } + /// ``` + #[clippy::version = "1.78.0"] + pub FIELD_SCOPED_VISIBILITY_MODIFIERS, + restriction, + "checks for usage of a scoped visibility modifier, like `pub(crate)`, on fields" +} + +declare_lint_pass!(FieldScopedVisibilityModifiers => [FIELD_SCOPED_VISIBILITY_MODIFIERS]); + +impl EarlyLintPass for FieldScopedVisibilityModifiers { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + let ItemKind::Struct(ref st, _) = item.kind else { + return; + }; + for field in st.fields() { + let VisibilityKind::Restricted { path, .. } = &field.vis.kind else { + continue; + }; + if !path.segments.is_empty() && path.segments[0].ident.name == rustc_span::symbol::kw::SelfLower { + // pub(self) is equivalent to not using pub at all, so we ignore it + continue; + } + span_lint_and_help( + cx, + FIELD_SCOPED_VISIBILITY_MODIFIERS, + field.vis.span, + "scoped visibility modifier on a field", + None, + "consider making the field private and adding a scoped visibility method for it", + ); + } + } +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 50274a715ec1..1bd6dfe24617 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -136,6 +136,7 @@ mod exit; mod explicit_write; mod extra_unused_type_parameters; mod fallible_impl_from; +mod field_scoped_visibility_modifiers; mod float_literal; mod floating_point_arithmetic; mod format; @@ -1169,6 +1170,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { }) }); store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(msrv()))); + store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/tests/ui/field_scoped_visibility_modifiers.rs b/tests/ui/field_scoped_visibility_modifiers.rs new file mode 100644 index 000000000000..5789dbf9b1d7 --- /dev/null +++ b/tests/ui/field_scoped_visibility_modifiers.rs @@ -0,0 +1,21 @@ +#![warn(clippy::field_scoped_visibility_modifiers)] + +pub mod pub_module { + pub(in crate::pub_module) mod pub_in_path_module {} + pub(super) mod pub_super_module {} + struct MyStruct { + private_field: bool, + pub pub_field: bool, + pub(crate) pub_crate_field: bool, + pub(in crate::pub_module) pub_in_path_field: bool, + pub(super) pub_super_field: bool, + #[allow(clippy::needless_pub_self)] + pub(self) pub_self_field: bool, + } +} +pub(crate) mod pub_crate_module {} + +#[allow(clippy::needless_pub_self)] +pub(self) mod pub_self_module {} + +fn main() {} diff --git a/tests/ui/field_scoped_visibility_modifiers.stderr b/tests/ui/field_scoped_visibility_modifiers.stderr new file mode 100644 index 000000000000..beea6c92107c --- /dev/null +++ b/tests/ui/field_scoped_visibility_modifiers.stderr @@ -0,0 +1,28 @@ +error: scoped visibility modifier on a field + --> tests/ui/field_scoped_visibility_modifiers.rs:9:9 + | +LL | pub(crate) pub_crate_field: bool, + | ^^^^^^^^^^ + | + = help: consider making the field private and adding a scoped visibility method for it + = note: `-D clippy::field-scoped-visibility-modifiers` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::field_scoped_visibility_modifiers)]` + +error: scoped visibility modifier on a field + --> tests/ui/field_scoped_visibility_modifiers.rs:10:9 + | +LL | pub(in crate::pub_module) pub_in_path_field: bool, + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider making the field private and adding a scoped visibility method for it + +error: scoped visibility modifier on a field + --> tests/ui/field_scoped_visibility_modifiers.rs:11:9 + | +LL | pub(super) pub_super_field: bool, + | ^^^^^^^^^^ + | + = help: consider making the field private and adding a scoped visibility method for it + +error: aborting due to 3 previous errors + From a002f93e51e66204e81779d7d449a50e30571c6c Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Mon, 12 Feb 2024 18:33:10 -0500 Subject: [PATCH 53/56] `expr_use_ctxt` changes: * Delay the parsing of the use node * Mark when the `SyntaxContext` changes rather than return `None` * Return a default value if the HIR tree is broken rather than `None` --- clippy_lints/src/casts/ref_as_ptr.rs | 4 +- clippy_lints/src/dereference.rs | 46 +++-- .../src/needless_borrows_for_generic_args.rs | 8 +- clippy_utils/src/lib.rs | 164 ++++++++++-------- 4 files changed, 120 insertions(+), 102 deletions(-) diff --git a/clippy_lints/src/casts/ref_as_ptr.rs b/clippy_lints/src/casts/ref_as_ptr.rs index f42bafce4dde..5f48b8bd2063 100644 --- a/clippy_lints/src/casts/ref_as_ptr.rs +++ b/clippy_lints/src/casts/ref_as_ptr.rs @@ -22,9 +22,9 @@ pub(super) fn check<'tcx>( if matches!(cast_from.kind(), ty::Ref(..)) && let ty::RawPtr(_, to_mutbl) = cast_to.kind() - && let Some(use_cx) = expr_use_ctxt(cx, expr) + && let use_cx = expr_use_ctxt(cx, expr) // TODO: only block the lint if `cast_expr` is a temporary - && !matches!(use_cx.node, ExprUseNode::LetStmt(_) | ExprUseNode::ConstStatic(_)) + && !matches!(use_cx.use_node(cx), ExprUseNode::LetStmt(_) | ExprUseNode::ConstStatic(_)) { let core_or_std = if is_no_std_crate(cx) { "core" } else { "std" }; let fn_name = match to_mutbl { diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index d60320d82825..3c137b6b70fa 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -260,18 +260,13 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { (None, kind) => { let expr_ty = typeck.expr_ty(expr); 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), - }; + let adjusted_ty = use_cx.adjustments.last().map_or(expr_ty, |a| a.target); - match (use_cx, kind) { - (Some(use_cx), RefOp::Deref) => { + match kind { + RefOp::Deref if use_cx.same_ctxt => { + let use_node = use_cx.use_node(cx); let sub_ty = typeck.expr_ty(sub_expr); - if let ExprUseNode::FieldAccess(name) = use_cx.node + if let ExprUseNode::FieldAccess(name) = use_node && !use_cx.moved_before_use && !ty_contains_field(sub_ty, name.name) { @@ -288,9 +283,9 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { } 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() + && !use_node.is_recv() + && let Some(ty) = use_node.defined_ty(cx) + && TyCoercionStability::for_defined_ty(cx, ty, use_node.is_return()).is_deref_stable() { self.state = Some(( State::ExplicitDeref { mutability: None }, @@ -301,7 +296,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { )); } }, - (_, RefOp::Method { mutbl, is_ufcs }) + RefOp::Method { mutbl, is_ufcs } if !is_lint_allowed(cx, EXPLICIT_DEREF_METHODS, expr.hir_id) // Allow explicit deref in method chains. e.g. `foo.deref().bar()` && (is_ufcs || !in_postfix_position(cx, expr)) => @@ -319,7 +314,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { }, )); }, - (Some(use_cx), RefOp::AddrOf(mutability)) => { + RefOp::AddrOf(mutability) if use_cx.same_ctxt => { // Find the number of times the borrow is auto-derefed. let mut iter = use_cx.adjustments.iter(); let mut deref_count = 0usize; @@ -338,10 +333,11 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { }; }; - let stability = use_cx.node.defined_ty(cx).map_or(TyCoercionStability::None, |ty| { - TyCoercionStability::for_defined_ty(cx, ty, use_cx.node.is_return()) + let use_node = use_cx.use_node(cx); + let stability = use_node.defined_ty(cx).map_or(TyCoercionStability::None, |ty| { + TyCoercionStability::for_defined_ty(cx, ty, use_node.is_return()) }); - let can_auto_borrow = match use_cx.node { + let can_auto_borrow = match use_node { ExprUseNode::FieldAccess(_) if !use_cx.moved_before_use && matches!(sub_expr.kind, ExprKind::Field(..)) => { @@ -353,7 +349,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { // deref through `ManuallyDrop<_>` will not compile. !adjust_derefs_manually_drop(use_cx.adjustments, expr_ty) }, - ExprUseNode::Callee | ExprUseNode::FieldAccess(_) => true, + ExprUseNode::Callee | ExprUseNode::FieldAccess(_) if !use_cx.moved_before_use => true, 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. @@ -363,9 +359,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { // 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 arg_ty = cx.tcx.erase_regions(adjusted_ty) && let ty::Ref(_, sub_ty, _) = *arg_ty.kind() && let args = typeck.node_args_opt(hir_id).map(|args| &args[1..]).unwrap_or_default() @@ -443,7 +437,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { count: deref_count - required_refs, msg, stability, - for_field_access: if let ExprUseNode::FieldAccess(name) = use_cx.node + for_field_access: if let ExprUseNode::FieldAccess(name) = use_node && !use_cx.moved_before_use { Some(name.name) @@ -453,7 +447,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { }), StateData { first_expr: expr, - adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target), + adjusted_ty, }, )); } else if stability.is_deref_stable() @@ -465,12 +459,12 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { State::Borrow { mutability }, StateData { first_expr: expr, - adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target), + adjusted_ty, }, )); } }, - (None, _) | (_, RefOp::Method { .. }) => (), + _ => {}, } }, ( diff --git a/clippy_lints/src/needless_borrows_for_generic_args.rs b/clippy_lints/src/needless_borrows_for_generic_args.rs index 4f99eaa40c29..9c3ead855a7a 100644 --- a/clippy_lints/src/needless_borrows_for_generic_args.rs +++ b/clippy_lints/src/needless_borrows_for_generic_args.rs @@ -80,11 +80,13 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowsForGenericArgs<'tcx> { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if matches!(expr.kind, ExprKind::AddrOf(..)) && !expr.span.from_expansion() - && let Some(use_cx) = expr_use_ctxt(cx, expr) + && let use_cx = expr_use_ctxt(cx, expr) + && use_cx.same_ctxt && !use_cx.is_ty_unified - && let Some(DefinedTy::Mir(ty)) = use_cx.node.defined_ty(cx) + && let use_node = use_cx.use_node(cx) + && let Some(DefinedTy::Mir(ty)) = use_node.defined_ty(cx) && let ty::Param(ty) = *ty.value.skip_binder().kind() - && let Some((hir_id, fn_id, i)) = match use_cx.node { + && let Some((hir_id, fn_id, i)) = match use_node { ExprUseNode::MethodArg(_, _, 0) => None, ExprUseNode::MethodArg(hir_id, None, i) => cx .typeck_results() diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 7dc341ec8d71..fda870f435a5 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1,6 +1,7 @@ #![feature(array_chunks)] #![feature(box_patterns)] #![feature(control_flow_enum)] +#![feature(exhaustive_patterns)] #![feature(if_let_guard)] #![feature(let_chains)] #![feature(lint_reasons)] @@ -2664,13 +2665,80 @@ pub enum DefinedTy<'tcx> { /// The context an expressions value is used in. pub struct ExprUseCtxt<'tcx> { /// The parent node which consumes the value. - pub node: ExprUseNode<'tcx>, + pub node: Node<'tcx>, + /// The child id of the node the value came from. + pub child_id: HirId, /// Any adjustments applied to the type. pub adjustments: &'tcx [Adjustment<'tcx>], - /// Whether or not the type must unify with another code path. + /// Whether 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. + /// Whether the value will be moved before it's used. pub moved_before_use: bool, + /// Whether the use site has the same `SyntaxContext` as the value. + pub same_ctxt: bool, +} +impl<'tcx> ExprUseCtxt<'tcx> { + pub fn use_node(&self, cx: &LateContext<'tcx>) -> ExprUseNode<'tcx> { + match self.node { + Node::LetStmt(l) => ExprUseNode::LetStmt(l), + Node::ExprField(field) => ExprUseNode::Field(field), + + Node::Item(&Item { + kind: ItemKind::Static(..) | ItemKind::Const(..), + owner_id, + .. + }) + | Node::TraitItem(&TraitItem { + kind: TraitItemKind::Const(..), + owner_id, + .. + }) + | Node::ImplItem(&ImplItem { + kind: ImplItemKind::Const(..), + owner_id, + .. + }) => ExprUseNode::ConstStatic(owner_id), + + Node::Item(&Item { + kind: ItemKind::Fn(..), + owner_id, + .. + }) + | Node::TraitItem(&TraitItem { + kind: TraitItemKind::Fn(..), + owner_id, + .. + }) + | Node::ImplItem(&ImplItem { + kind: ImplItemKind::Fn(..), + owner_id, + .. + }) => ExprUseNode::Return(owner_id), + + Node::Expr(use_expr) => match use_expr.kind { + ExprKind::Ret(_) => ExprUseNode::Return(OwnerId { + def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()), + }), + + ExprKind::Closure(closure) => ExprUseNode::Return(OwnerId { def_id: closure.def_id }), + ExprKind::Call(func, args) => match args.iter().position(|arg| arg.hir_id == self.child_id) { + Some(i) => ExprUseNode::FnArg(func, i), + None => ExprUseNode::Callee, + }, + ExprKind::MethodCall(name, _, args, _) => ExprUseNode::MethodArg( + use_expr.hir_id, + name.args, + args.iter() + .position(|arg| arg.hir_id == self.child_id) + .map_or(0, |i| i + 1), + ), + ExprKind::Field(_, name) => ExprUseNode::FieldAccess(name), + ExprKind::AddrOf(kind, mutbl, _) => ExprUseNode::AddrOf(kind, mutbl), + _ => ExprUseNode::Other, + }, + _ => ExprUseNode::Other, + } + } } /// The node which consumes a value. @@ -2691,7 +2759,8 @@ pub enum ExprUseNode<'tcx> { Callee, /// Access of a field. FieldAccess(Ident), - Expr, + /// Borrow expression. + AddrOf(ast::BorrowKind, Mutability), Other, } impl<'tcx> ExprUseNode<'tcx> { @@ -2768,26 +2837,25 @@ impl<'tcx> ExprUseNode<'tcx> { let sig = cx.tcx.fn_sig(id).skip_binder(); Some(DefinedTy::Mir(cx.tcx.param_env(id).and(sig.input(i)))) }, - Self::LetStmt(_) | Self::FieldAccess(..) | Self::Callee | Self::Expr | Self::Other => None, + Self::LetStmt(_) | Self::FieldAccess(..) | Self::Callee | Self::Other | Self::AddrOf(..) => None, } } } /// Gets the context an expression's value is used in. -pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Option> { +pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> ExprUseCtxt<'tcx> { let mut adjustments = [].as_slice(); let mut is_ty_unified = false; let mut moved_before_use = false; + let mut same_ctxt = true; let ctxt = e.span.ctxt(); - walk_to_expr_usage(cx, e, &mut |parent_id, parent, child_id| { + let node = walk_to_expr_usage(cx, e, &mut |parent_id, parent, child_id| -> ControlFlow { if adjustments.is_empty() && let Node::Expr(e) = cx.tcx.hir_node(child_id) { adjustments = cx.typeck_results().expr_adjustments(e); } - if cx.tcx.hir().span(parent_id).ctxt() != ctxt { - return ControlFlow::Break(()); - } + same_ctxt &= cx.tcx.hir().span(parent_id).ctxt() == ctxt; if let Node::Expr(e) = parent { match e.kind { ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => { @@ -2803,71 +2871,25 @@ pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Optio } } ControlFlow::Continue(()) - })? - .continue_value() - .map(|(use_node, child_id)| { - let node = match use_node { - Node::LetStmt(l) => ExprUseNode::LetStmt(l), - Node::ExprField(field) => ExprUseNode::Field(field), - - Node::Item(&Item { - kind: ItemKind::Static(..) | ItemKind::Const(..), - owner_id, - .. - }) - | Node::TraitItem(&TraitItem { - kind: TraitItemKind::Const(..), - owner_id, - .. - }) - | Node::ImplItem(&ImplItem { - kind: ImplItemKind::Const(..), - owner_id, - .. - }) => ExprUseNode::ConstStatic(owner_id), - - Node::Item(&Item { - kind: ItemKind::Fn(..), - owner_id, - .. - }) - | Node::TraitItem(&TraitItem { - kind: TraitItemKind::Fn(..), - owner_id, - .. - }) - | Node::ImplItem(&ImplItem { - kind: ImplItemKind::Fn(..), - owner_id, - .. - }) => ExprUseNode::Return(owner_id), - - Node::Expr(use_expr) => match use_expr.kind { - ExprKind::Ret(_) => ExprUseNode::Return(OwnerId { - def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()), - }), - ExprKind::Closure(closure) => ExprUseNode::Return(OwnerId { def_id: closure.def_id }), - ExprKind::Call(func, args) => match args.iter().position(|arg| arg.hir_id == child_id) { - Some(i) => ExprUseNode::FnArg(func, i), - None => ExprUseNode::Callee, - }, - ExprKind::MethodCall(name, _, args, _) => ExprUseNode::MethodArg( - use_expr.hir_id, - name.args, - args.iter().position(|arg| arg.hir_id == child_id).map_or(0, |i| i + 1), - ), - ExprKind::Field(child, name) if child.hir_id == e.hir_id => ExprUseNode::FieldAccess(name), - _ => ExprUseNode::Expr, - }, - _ => ExprUseNode::Other, - }; - ExprUseCtxt { + }); + match node { + Some(ControlFlow::Continue((node, child_id))) => ExprUseCtxt { node, + child_id, adjustments, is_ty_unified, moved_before_use, - } - }) + same_ctxt, + }, + None => ExprUseCtxt { + node: Node::Crate(cx.tcx.hir().root_module()), + child_id: HirId::INVALID, + adjustments: &[], + is_ty_unified: true, + moved_before_use: true, + same_ctxt: false, + }, + } } /// Tokenizes the input while keeping the text associated with each token. From 22710f33a8ad99be51bb69f91339f8d815bd9e92 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Mon, 12 Feb 2024 18:33:22 -0500 Subject: [PATCH 54/56] Add lint `manual_inspect` --- CHANGELOG.md | 1 + clippy_config/src/msrvs.rs | 2 +- clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/methods/manual_inspect.rs | 233 +++++++++++++++++++++ clippy_lints/src/methods/mod.rs | 24 +++ clippy_utils/src/source.rs | 27 ++- clippy_utils/src/ty.rs | 14 ++ tests/ui/copy_iterator.rs | 1 + tests/ui/copy_iterator.stderr | 2 +- tests/ui/manual_inspect.fixed | 174 +++++++++++++++ tests/ui/manual_inspect.rs | 186 ++++++++++++++++ tests/ui/manual_inspect.stderr | 182 ++++++++++++++++ 12 files changed, 844 insertions(+), 3 deletions(-) create mode 100644 clippy_lints/src/methods/manual_inspect.rs create mode 100644 tests/ui/manual_inspect.fixed create mode 100644 tests/ui/manual_inspect.rs create mode 100644 tests/ui/manual_inspect.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 4de3124bd3da..3973edddb6d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5521,6 +5521,7 @@ Released 2018-09-13 [`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map [`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten [`manual_hash_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one +[`manual_inspect`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_inspect [`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed [`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check [`manual_is_finite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_finite diff --git a/clippy_config/src/msrvs.rs b/clippy_config/src/msrvs.rs index a3e7d0c3fa5f..5327a1256474 100644 --- a/clippy_config/src/msrvs.rs +++ b/clippy_config/src/msrvs.rs @@ -18,7 +18,7 @@ macro_rules! msrv_aliases { // names may refer to stabilized feature flags or library items msrv_aliases! { 1,77,0 { C_STR_LITERALS } - 1,76,0 { PTR_FROM_REF } + 1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT } 1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE } 1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN } 1,68,0 { PATH_MAIN_SEPARATOR_STR } diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 88986f9ad727..8abe5d47b65e 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -403,6 +403,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::MANUAL_C_STR_LITERALS_INFO, crate::methods::MANUAL_FILTER_MAP_INFO, crate::methods::MANUAL_FIND_MAP_INFO, + crate::methods::MANUAL_INSPECT_INFO, crate::methods::MANUAL_IS_VARIANT_AND_INFO, crate::methods::MANUAL_NEXT_BACK_INFO, crate::methods::MANUAL_OK_OR_INFO, diff --git a/clippy_lints/src/methods/manual_inspect.rs b/clippy_lints/src/methods/manual_inspect.rs new file mode 100644 index 000000000000..2f9b951c6a76 --- /dev/null +++ b/clippy_lints/src/methods/manual_inspect.rs @@ -0,0 +1,233 @@ +use clippy_config::msrvs::{self, Msrv}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::{get_source_text, with_leading_whitespace, SpanRange}; +use clippy_utils::ty::get_field_by_name; +use clippy_utils::visitors::{for_each_expr, for_each_expr_without_closures}; +use clippy_utils::{expr_use_ctxt, is_diag_item_method, is_diag_trait_item, path_to_local_id, ExprUseNode}; +use core::ops::ControlFlow; +use rustc_errors::Applicability; +use rustc_hir::{BindingMode, BorrowKind, ByRef, ClosureKind, Expr, ExprKind, Mutability, Node, PatKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; +use rustc_span::{sym, BytePos, Span, Symbol, DUMMY_SP}; + +use super::MANUAL_INSPECT; + +#[expect(clippy::too_many_lines)] +pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: &str, name_span: Span, msrv: &Msrv) { + if let ExprKind::Closure(c) = arg.kind + && matches!(c.kind, ClosureKind::Closure) + && let typeck = cx.typeck_results() + && let Some(fn_id) = typeck.type_dependent_def_id(expr.hir_id) + && (is_diag_trait_item(cx, fn_id, sym::Iterator) + || (msrv.meets(msrvs::OPTION_RESULT_INSPECT) + && (is_diag_item_method(cx, fn_id, sym::Option) || is_diag_item_method(cx, fn_id, sym::Result)))) + && let body = cx.tcx.hir().body(c.body) + && let [param] = body.params + && let PatKind::Binding(BindingMode(ByRef::No, Mutability::Not), arg_id, _, None) = param.pat.kind + && let arg_ty = typeck.node_type(arg_id) + && let ExprKind::Block(block, _) = body.value.kind + && let Some(final_expr) = block.expr + && !block.stmts.is_empty() + && path_to_local_id(final_expr, arg_id) + && typeck.expr_adjustments(final_expr).is_empty() + { + let mut requires_copy = false; + let mut requires_deref = false; + + // The number of unprocessed return expressions. + let mut ret_count = 0u32; + + // The uses for which processing is delayed until after the visitor. + let mut delayed = vec![]; + + let ctxt = arg.span.ctxt(); + let can_lint = for_each_expr_without_closures(block.stmts, |e| { + if let ExprKind::Closure(c) = e.kind { + // Nested closures don't need to treat returns specially. + let _: Option = for_each_expr(cx, cx.tcx.hir().body(c.body).value, |e| { + if path_to_local_id(e, arg_id) { + let (kind, same_ctxt) = check_use(cx, e); + match (kind, same_ctxt && e.span.ctxt() == ctxt) { + (_, false) | (UseKind::Deref | UseKind::Return(..), true) => { + requires_copy = true; + requires_deref = true; + }, + (UseKind::AutoBorrowed, true) => {}, + (UseKind::WillAutoDeref, true) => { + requires_copy = true; + }, + (kind, true) => delayed.push(kind), + } + } + ControlFlow::Continue(()) + }); + } else if matches!(e.kind, ExprKind::Ret(_)) { + ret_count += 1; + } else if path_to_local_id(e, arg_id) { + let (kind, same_ctxt) = check_use(cx, e); + match (kind, same_ctxt && e.span.ctxt() == ctxt) { + (UseKind::Return(..), false) => { + return ControlFlow::Break(()); + }, + (_, false) | (UseKind::Deref, true) => { + requires_copy = true; + requires_deref = true; + }, + (UseKind::AutoBorrowed, true) => {}, + (UseKind::WillAutoDeref, true) => { + requires_copy = true; + }, + (kind @ UseKind::Return(_), true) => { + ret_count -= 1; + delayed.push(kind); + }, + (kind, true) => delayed.push(kind), + } + } + ControlFlow::Continue(()) + }) + .is_none(); + + if ret_count != 0 { + // A return expression that didn't return the original value was found. + return; + } + + let mut edits = Vec::with_capacity(delayed.len() + 3); + let mut addr_of_edits = Vec::with_capacity(delayed.len()); + for x in delayed { + match x { + UseKind::Return(s) => edits.push((with_leading_whitespace(cx, s).set_span_pos(s), String::new())), + UseKind::Borrowed(s) => { + if let Some(src) = get_source_text(cx, s) + && let Some(src) = src.as_str() + && let trim_src = src.trim_start_matches([' ', '\t', '\n', '\r', '(']) + && trim_src.starts_with('&') + { + let range = s.into_range(); + #[expect(clippy::cast_possible_truncation)] + let start = BytePos(range.start.0 + (src.len() - trim_src.len()) as u32); + addr_of_edits.push(((start..BytePos(start.0 + 1)).set_span_pos(s), String::new())); + } else { + requires_copy = true; + requires_deref = true; + } + }, + UseKind::FieldAccess(name, e) => { + let Some(mut ty) = get_field_by_name(cx.tcx, arg_ty.peel_refs(), name) else { + requires_copy = true; + continue; + }; + let mut prev_expr = e; + + for (_, parent) in cx.tcx.hir().parent_iter(e.hir_id) { + if let Node::Expr(e) = parent { + match e.kind { + ExprKind::Field(_, name) + if let Some(fty) = get_field_by_name(cx.tcx, ty.peel_refs(), name.name) => + { + ty = fty; + prev_expr = e; + continue; + }, + ExprKind::AddrOf(BorrowKind::Ref, ..) => break, + _ if matches!( + typeck.expr_adjustments(prev_expr).first(), + Some(Adjustment { + kind: Adjust::Borrow(AutoBorrow::Ref(_, AutoBorrowMutability::Not)) + | Adjust::Deref(_), + .. + }) + ) => + { + break; + }, + _ => {}, + } + } + requires_copy |= !ty.is_copy_modulo_regions(cx.tcx, cx.param_env); + break; + } + }, + // Already processed uses. + UseKind::AutoBorrowed | UseKind::WillAutoDeref | UseKind::Deref => {}, + } + } + + if can_lint + && (!requires_copy || arg_ty.is_copy_modulo_regions(cx.tcx, cx.param_env)) + // This case could be handled, but a fair bit of care would need to be taken. + && (!requires_deref || arg_ty.is_freeze(cx.tcx, cx.param_env)) + { + if requires_deref { + edits.push((param.span.shrink_to_lo(), "&".into())); + } else { + edits.extend(addr_of_edits); + } + edits.push(( + name_span, + String::from(match name { + "map" => "inspect", + "map_err" => "inspect_err", + _ => return, + }), + )); + edits.push(( + with_leading_whitespace(cx, final_expr.span).set_span_pos(final_expr.span), + String::new(), + )); + let app = if edits.iter().any(|(s, _)| s.from_expansion()) { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + }; + span_lint_and_then(cx, MANUAL_INSPECT, name_span, "", |diag| { + diag.multipart_suggestion("try", edits, app); + }); + } + } +} + +enum UseKind<'tcx> { + AutoBorrowed, + WillAutoDeref, + Deref, + Return(Span), + Borrowed(Span), + FieldAccess(Symbol, &'tcx Expr<'tcx>), +} + +/// Checks how the value is used, and whether it was used in the same `SyntaxContext`. +fn check_use<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (UseKind<'tcx>, bool) { + let use_cx = expr_use_ctxt(cx, e); + if use_cx + .adjustments + .first() + .is_some_and(|a| matches!(a.kind, Adjust::Deref(_))) + { + return (UseKind::AutoBorrowed, use_cx.same_ctxt); + } + let res = match use_cx.use_node(cx) { + ExprUseNode::Return(_) => { + if let ExprKind::Ret(Some(e)) = use_cx.node.expect_expr().kind { + UseKind::Return(e.span) + } else { + return (UseKind::Return(DUMMY_SP), false); + } + }, + ExprUseNode::FieldAccess(name) => UseKind::FieldAccess(name.name, use_cx.node.expect_expr()), + ExprUseNode::Callee | ExprUseNode::MethodArg(_, _, 0) + if use_cx + .adjustments + .first() + .is_some_and(|a| matches!(a.kind, Adjust::Borrow(AutoBorrow::Ref(_, AutoBorrowMutability::Not)))) => + { + UseKind::AutoBorrowed + }, + ExprUseNode::Callee | ExprUseNode::MethodArg(_, _, 0) => UseKind::WillAutoDeref, + ExprUseNode::AddrOf(BorrowKind::Ref, _) => UseKind::Borrowed(use_cx.node.expect_expr().span), + _ => UseKind::Deref, + }; + (res, use_cx.same_ctxt) +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 6200716afbe9..01438b8e8db0 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -53,6 +53,7 @@ mod iter_with_drain; mod iterator_step_by_zero; mod join_absolute_paths; mod manual_c_str_literals; +mod manual_inspect; mod manual_is_variant_and; mod manual_next_back; mod manual_ok_or; @@ -4079,6 +4080,27 @@ declare_clippy_lint! { "is_ascii() called on a char iterator" } +declare_clippy_lint! { + /// ### What it does + /// Checks for uses of `map` which return the original item. + /// + /// ### Why is this bad? + /// `inspect` is both clearer in intent and shorter. + /// + /// ### Example + /// ```no_run + /// let x = Some(0).map(|x| { println!("{x}"); x }); + /// ``` + /// Use instead: + /// ```no_run + /// let x = Some(0).inspect(|x| println!("{x}")); + /// ``` + #[clippy::version = "1.78.0"] + pub MANUAL_INSPECT, + complexity, + "use of `map` returning the original item" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -4244,6 +4266,7 @@ impl_lint_pass!(Methods => [ MANUAL_C_STR_LITERALS, UNNECESSARY_GET_THEN_CHECK, NEEDLESS_CHARACTER_ITERATION, + MANUAL_INSPECT, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4747,6 +4770,7 @@ impl Methods { } } map_identity::check(cx, expr, recv, m_arg, name, span); + manual_inspect::check(cx, expr, m_arg, name, span, &self.msrv); }, ("map_or", [def, map]) => { option_map_or_none::check(cx, expr, recv, def, map); diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index b7ff7ebe9105..69b122cbfada 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -14,8 +14,17 @@ use std::borrow::Cow; use std::ops::Range; /// A type which can be converted to the range portion of a `Span`. -pub trait SpanRange { +pub trait SpanRange: Sized { fn into_range(self) -> Range; + fn set_span_pos(self, sp: Span) -> Span { + let range = self.into_range(); + SpanData { + lo: range.start, + hi: range.end, + ..sp.data() + } + .span() + } } impl SpanRange for Span { fn into_range(self) -> Range { @@ -61,6 +70,22 @@ pub fn get_source_text(cx: &impl LintContext, sp: impl SpanRange) -> Option Range { + #[expect(clippy::needless_pass_by_value, clippy::cast_possible_truncation)] + fn f(src: SourceFileRange, sp: Range) -> Range { + let Some(text) = &src.sf.src else { + return sp; + }; + let len = src.range.start - text[..src.range.start].trim_end().len(); + BytePos(sp.start.0 - len as u32)..sp.end + } + let sp = sp.into_range(); + match get_source_text(cx, sp.clone()) { + Some(src) => f(src, sp), + None => sp, + } +} + /// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`. pub fn expr_block( cx: &T, diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 7d4332a3d9de..3f5ed73c3ef8 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -1349,3 +1349,17 @@ pub fn get_adt_inherent_method<'a>(cx: &'a LateContext<'_>, ty: Ty<'_>, method_n None } } + +/// Get's the type of a field by name. +pub fn get_field_by_name<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, name: Symbol) -> Option> { + match *ty.kind() { + ty::Adt(def, args) if def.is_union() || def.is_struct() => def + .non_enum_variant() + .fields + .iter() + .find(|f| f.name == name) + .map(|f| f.ty(tcx, args)), + ty::Tuple(args) => name.as_str().parse::().ok().and_then(|i| args.get(i).copied()), + _ => None, + } +} diff --git a/tests/ui/copy_iterator.rs b/tests/ui/copy_iterator.rs index c0e5fc3e4467..ea3a4face93f 100644 --- a/tests/ui/copy_iterator.rs +++ b/tests/ui/copy_iterator.rs @@ -1,4 +1,5 @@ #![warn(clippy::copy_iterator)] +#![allow(clippy::manual_inspect)] #[derive(Copy, Clone)] struct Countdown(u8); diff --git a/tests/ui/copy_iterator.stderr b/tests/ui/copy_iterator.stderr index 533bddaadb5e..990b1ce628de 100644 --- a/tests/ui/copy_iterator.stderr +++ b/tests/ui/copy_iterator.stderr @@ -1,5 +1,5 @@ error: you are implementing `Iterator` on a `Copy` type - --> tests/ui/copy_iterator.rs:6:1 + --> tests/ui/copy_iterator.rs:7:1 | LL | / impl Iterator for Countdown { LL | | diff --git a/tests/ui/manual_inspect.fixed b/tests/ui/manual_inspect.fixed new file mode 100644 index 000000000000..0e1b8fe3edb5 --- /dev/null +++ b/tests/ui/manual_inspect.fixed @@ -0,0 +1,174 @@ +#![warn(clippy::manual_inspect)] +#![allow(clippy::no_effect, clippy::op_ref)] + +fn main() { + let _ = Some(0).inspect(|&x| { + println!("{}", x); + }); + + let _ = Some(0).inspect(|&x| { + println!("{x}"); + }); + + let _ = Some(0).inspect(|&x| { + println!("{}", x * 5 + 1); + }); + + let _ = Some(0).inspect(|&x| { + if x == 0 { + panic!(); + } + }); + + let _ = Some(0).inspect(|&x| { + if &x == &0 { + let _y = x; + panic!(); + } + }); + + let _ = Some(0).map(|x| { + let y = x + 1; + if y > 5 { + return y; + } + x + }); + + { + #[derive(PartialEq)] + struct Foo(i32); + + let _ = Some(Foo(0)).map(|x| { + if x == Foo(0) { + panic!(); + } + x + }); + + let _ = Some(Foo(0)).map(|x| { + if &x == &Foo(0) { + let _y = x; + panic!(); + } + x + }); + } + + { + macro_rules! maybe_ret { + ($e:expr) => { + if $e == 0 { + return $e; + } + }; + } + + let _ = Some(0).map(|x| { + maybe_ret!(x); + x + }); + } + + let _ = Some((String::new(), 0u32)).inspect(|x| { + if x.1 == 0 { + let _x = x.1; + panic!(); + } + }); + + let _ = Some((String::new(), 0u32)).map(|x| { + if x.1 == 0 { + let _x = x.0; + panic!(); + } + x + }); + + let _ = Some(String::new()).map(|x| { + if x.is_empty() { + let _ = || { + let _x = x; + }; + panic!(); + } + x + }); + + let _ = Some(String::new()).inspect(|x| { + if x.is_empty() { + let _ = || { + let _x = x; + }; + return; + } + println!("test"); + }); + + let _ = Some(0).inspect(|&x| { + if x == 0 { + let _ = || { + let _x = x; + }; + panic!(); + } + }); + + { + use core::cell::Cell; + #[derive(Debug)] + struct Cell2(core::cell::Cell); + + let _ = Some(Cell2(Cell::new(0u32))).inspect(|x| { + x.0.set(1); + }); + + let _ = Some(Cell2(Cell::new(0u32))).map(|x| { + let y = &x; + if x.0.get() == 0 { + y.0.set(1) + } else { + println!("{x:?}"); + } + x + }); + } + + let _: Result<_, ()> = Ok(0).inspect(|&x| { + println!("{}", x); + }); + + let _: Result<(), _> = Err(0).inspect_err(|&x| { + println!("{}", x); + }); + + let _ = [0] + .into_iter() + .inspect(|&x| { + println!("{}", x); + }) + .count(); + + { + struct S(T); + impl S { + fn map(self, f: impl FnOnce(T) -> U) -> S { + S(f(self.0)) + } + + fn map_err(self, f: impl FnOnce(T) -> U) -> S { + S(f(self.0)) + } + } + + let _ = S(0).map(|x| { + println!("{}", x); + x + }); + + let _ = S(0).map_err(|x| { + println!("{}", x); + x + }); + } +} diff --git a/tests/ui/manual_inspect.rs b/tests/ui/manual_inspect.rs new file mode 100644 index 000000000000..94cdfe391400 --- /dev/null +++ b/tests/ui/manual_inspect.rs @@ -0,0 +1,186 @@ +#![warn(clippy::manual_inspect)] +#![allow(clippy::no_effect, clippy::op_ref)] + +fn main() { + let _ = Some(0).map(|x| { + println!("{}", x); + x + }); + + let _ = Some(0).map(|x| { + println!("{x}"); + x + }); + + let _ = Some(0).map(|x| { + println!("{}", x * 5 + 1); + x + }); + + let _ = Some(0).map(|x| { + if x == 0 { + panic!(); + } + x + }); + + let _ = Some(0).map(|x| { + if &x == &0 { + let _y = x; + panic!(); + } + x + }); + + let _ = Some(0).map(|x| { + let y = x + 1; + if y > 5 { + return y; + } + x + }); + + { + #[derive(PartialEq)] + struct Foo(i32); + + let _ = Some(Foo(0)).map(|x| { + if x == Foo(0) { + panic!(); + } + x + }); + + let _ = Some(Foo(0)).map(|x| { + if &x == &Foo(0) { + let _y = x; + panic!(); + } + x + }); + } + + { + macro_rules! maybe_ret { + ($e:expr) => { + if $e == 0 { + return $e; + } + }; + } + + let _ = Some(0).map(|x| { + maybe_ret!(x); + x + }); + } + + let _ = Some((String::new(), 0u32)).map(|x| { + if x.1 == 0 { + let _x = x.1; + panic!(); + } + x + }); + + let _ = Some((String::new(), 0u32)).map(|x| { + if x.1 == 0 { + let _x = x.0; + panic!(); + } + x + }); + + let _ = Some(String::new()).map(|x| { + if x.is_empty() { + let _ = || { + let _x = x; + }; + panic!(); + } + x + }); + + let _ = Some(String::new()).map(|x| { + if x.is_empty() { + let _ = || { + let _x = &x; + }; + return x; + } + println!("test"); + x + }); + + let _ = Some(0).map(|x| { + if x == 0 { + let _ = || { + let _x = x; + }; + panic!(); + } + x + }); + + { + use core::cell::Cell; + #[derive(Debug)] + struct Cell2(core::cell::Cell); + + let _ = Some(Cell2(Cell::new(0u32))).map(|x| { + x.0.set(1); + x + }); + + let _ = Some(Cell2(Cell::new(0u32))).map(|x| { + let y = &x; + if x.0.get() == 0 { + y.0.set(1) + } else { + println!("{x:?}"); + } + x + }); + } + + let _: Result<_, ()> = Ok(0).map(|x| { + println!("{}", x); + x + }); + + let _: Result<(), _> = Err(0).map_err(|x| { + println!("{}", x); + x + }); + + let _ = [0] + .into_iter() + .map(|x| { + println!("{}", x); + x + }) + .count(); + + { + struct S(T); + impl S { + fn map(self, f: impl FnOnce(T) -> U) -> S { + S(f(self.0)) + } + + fn map_err(self, f: impl FnOnce(T) -> U) -> S { + S(f(self.0)) + } + } + + let _ = S(0).map(|x| { + println!("{}", x); + x + }); + + let _ = S(0).map_err(|x| { + println!("{}", x); + x + }); + } +} diff --git a/tests/ui/manual_inspect.stderr b/tests/ui/manual_inspect.stderr new file mode 100644 index 000000000000..efdf115b84e5 --- /dev/null +++ b/tests/ui/manual_inspect.stderr @@ -0,0 +1,182 @@ +error: + --> tests/ui/manual_inspect.rs:5:21 + | +LL | let _ = Some(0).map(|x| { + | ^^^ + | + = note: `-D clippy::manual-inspect` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_inspect)]` +help: try + | +LL ~ let _ = Some(0).inspect(|&x| { +LL ~ println!("{}", x); + | + +error: + --> tests/ui/manual_inspect.rs:10:21 + | +LL | let _ = Some(0).map(|x| { + | ^^^ + | +help: try + | +LL ~ let _ = Some(0).inspect(|&x| { +LL ~ println!("{x}"); + | + +error: + --> tests/ui/manual_inspect.rs:15:21 + | +LL | let _ = Some(0).map(|x| { + | ^^^ + | +help: try + | +LL ~ let _ = Some(0).inspect(|&x| { +LL ~ println!("{}", x * 5 + 1); + | + +error: + --> tests/ui/manual_inspect.rs:20:21 + | +LL | let _ = Some(0).map(|x| { + | ^^^ + | +help: try + | +LL ~ let _ = Some(0).inspect(|&x| { +LL | if x == 0 { +LL | panic!(); +LL ~ } + | + +error: + --> tests/ui/manual_inspect.rs:27:21 + | +LL | let _ = Some(0).map(|x| { + | ^^^ + | +help: try + | +LL ~ let _ = Some(0).inspect(|&x| { +LL | if &x == &0 { +LL | let _y = x; +LL | panic!(); +LL ~ } + | + +error: + --> tests/ui/manual_inspect.rs:78:41 + | +LL | let _ = Some((String::new(), 0u32)).map(|x| { + | ^^^ + | +help: try + | +LL ~ let _ = Some((String::new(), 0u32)).inspect(|x| { +LL | if x.1 == 0 { +LL | let _x = x.1; +LL | panic!(); +LL ~ } + | + +error: + --> tests/ui/manual_inspect.rs:104:33 + | +LL | let _ = Some(String::new()).map(|x| { + | ^^^ + | +help: try + | +LL ~ let _ = Some(String::new()).inspect(|x| { +LL | if x.is_empty() { +LL | let _ = || { +LL ~ let _x = x; +LL | }; +LL ~ return; +LL | } +LL ~ println!("test"); + | + +error: + --> tests/ui/manual_inspect.rs:115:21 + | +LL | let _ = Some(0).map(|x| { + | ^^^ + | +help: try + | +LL ~ let _ = Some(0).inspect(|&x| { +LL | if x == 0 { + ... +LL | panic!(); +LL ~ } + | + +error: + --> tests/ui/manual_inspect.rs:130:46 + | +LL | let _ = Some(Cell2(Cell::new(0u32))).map(|x| { + | ^^^ + | +help: try + | +LL ~ let _ = Some(Cell2(Cell::new(0u32))).inspect(|x| { +LL ~ x.0.set(1); + | + +error: + --> tests/ui/manual_inspect.rs:146:34 + | +LL | let _: Result<_, ()> = Ok(0).map(|x| { + | ^^^ + | +help: try + | +LL ~ let _: Result<_, ()> = Ok(0).inspect(|&x| { +LL ~ println!("{}", x); + | + +error: + --> tests/ui/manual_inspect.rs:151:35 + | +LL | let _: Result<(), _> = Err(0).map_err(|x| { + | ^^^^^^^ + | +help: try + | +LL ~ let _: Result<(), _> = Err(0).inspect_err(|&x| { +LL ~ println!("{}", x); + | + +error: this call to `map()` won't have an effect on the call to `count()` + --> tests/ui/manual_inspect.rs:156:13 + | +LL | let _ = [0] + | _____________^ +LL | | .into_iter() +LL | | .map(|x| { +LL | | println!("{}", x); +LL | | x +LL | | }) +LL | | .count(); + | |________________^ + | + = help: make sure you did not confuse `map` with `filter`, `for_each` or `inspect` + = note: `-D clippy::suspicious-map` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::suspicious_map)]` + +error: + --> tests/ui/manual_inspect.rs:158:10 + | +LL | .map(|x| { + | ^^^ + | +help: try + | +LL ~ .inspect(|&x| { +LL ~ println!("{}", x); + | + +error: aborting due to 13 previous errors + From f09650b347f6f0d38747662db4382729cd058247 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 16 Jun 2024 14:38:58 -0400 Subject: [PATCH 55/56] Use symbols when raising range expressions. --- clippy_utils/src/higher.rs | 63 ++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 801a98521590..277ba8427e05 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -218,51 +218,56 @@ pub struct Range<'a> { impl<'a> Range<'a> { /// Higher a `hir` range to something similar to `ast::ExprKind::Range`. + #[allow(clippy::similar_names)] pub fn hir(expr: &'a Expr<'_>) -> Option> { - /// Finds the field named `name` in the field. Always return `Some` for - /// convenience. - fn get_field<'c>(name: &str, fields: &'c [hir::ExprField<'_>]) -> Option<&'c Expr<'c>> { - let expr = &fields.iter().find(|field| field.ident.name.as_str() == name)?.expr; - Some(expr) - } - match expr.kind { - ExprKind::Call(path, args) + ExprKind::Call(path, [arg1, arg2]) if matches!( path.kind, ExprKind::Path(QPath::LangItem(hir::LangItem::RangeInclusiveNew, ..)) ) => { Some(Range { - start: Some(&args[0]), - end: Some(&args[1]), + start: Some(arg1), + end: Some(arg2), limits: ast::RangeLimits::Closed, }) }, - ExprKind::Struct(path, fields, None) => match &path { - QPath::LangItem(hir::LangItem::RangeFull, ..) => Some(Range { + ExprKind::Struct(path, fields, None) => match (path, fields) { + (QPath::LangItem(hir::LangItem::RangeFull, ..), []) => Some(Range { start: None, end: None, limits: ast::RangeLimits::HalfOpen, }), - QPath::LangItem(hir::LangItem::RangeFrom, ..) => Some(Range { - start: Some(get_field("start", fields)?), - end: None, - limits: ast::RangeLimits::HalfOpen, - }), - QPath::LangItem(hir::LangItem::Range, ..) => Some(Range { - start: Some(get_field("start", fields)?), - end: Some(get_field("end", fields)?), - limits: ast::RangeLimits::HalfOpen, - }), - QPath::LangItem(hir::LangItem::RangeToInclusive, ..) => Some(Range { - start: None, - end: Some(get_field("end", fields)?), - limits: ast::RangeLimits::Closed, - }), - QPath::LangItem(hir::LangItem::RangeTo, ..) => Some(Range { + (QPath::LangItem(hir::LangItem::RangeFrom, ..), [field]) if field.ident.name == sym::start => { + Some(Range { + start: Some(field.expr), + end: None, + limits: ast::RangeLimits::HalfOpen, + }) + }, + (QPath::LangItem(hir::LangItem::Range, ..), [field1, field2]) => { + let (start, end) = match (field1.ident.name, field2.ident.name) { + (sym::start, sym::end) => (field1.expr, field2.expr), + (sym::end, sym::start) => (field2.expr, field1.expr), + _ => return None, + }; + Some(Range { + start: Some(start), + end: Some(end), + limits: ast::RangeLimits::HalfOpen, + }) + }, + (QPath::LangItem(hir::LangItem::RangeToInclusive, ..), [field]) if field.ident.name == sym::end => { + Some(Range { + start: None, + end: Some(field.expr), + limits: ast::RangeLimits::Closed, + }) + }, + (QPath::LangItem(hir::LangItem::RangeTo, ..), [field]) if field.ident.name == sym::end => Some(Range { start: None, - end: Some(get_field("end", fields)?), + end: Some(field.expr), limits: ast::RangeLimits::HalfOpen, }), _ => None, From 198bbf87a0ff94f7f297a1de8ee18fa3d3f34c1a Mon Sep 17 00:00:00 2001 From: xFrednet Date: Mon, 17 Jun 2024 15:22:06 +0200 Subject: [PATCH 56/56] Pause assignments to @xFrednet for summer break :beach_umbrella: I'm only removing myself from the assignment roulette as I want to allow `r? xFrednet` for `gh-pages` and other updates. So feel free to still `r?` me. --- triagebot.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/triagebot.toml b/triagebot.toml index 4d66b728b76d..ecada0514f7a 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -32,7 +32,6 @@ users_on_vacation = [ "*" = [ "@Manishearth", "@llogiq", - "@xFrednet", "@Alexendoo", "@dswij", "@Jarcho",