From a8d417d1a53dc22c42054327aca52a3b6a034150 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Sat, 29 Mar 2025 22:46:36 +0100 Subject: [PATCH 001/116] rework `useless_vec` lint --- clippy_lints/src/vec.rs | 226 ++++++++++++++++++++++++---------------- 1 file changed, 136 insertions(+), 90 deletions(-) diff --git a/clippy_lints/src/vec.rs b/clippy_lints/src/vec.rs index 7b6a25123e85..1646f2220212 100644 --- a/clippy_lints/src/vec.rs +++ b/clippy_lints/src/vec.rs @@ -1,4 +1,6 @@ use std::collections::BTreeMap; +use std::collections::btree_map::Entry; +use std::mem; use std::ops::ControlFlow; use clippy_config::Conf; @@ -20,15 +22,36 @@ use rustc_span::{DesugaringKind, Span}; pub struct UselessVec { too_large_for_stack: u64, msrv: Msrv, - span_to_lint_map: BTreeMap>, + /// Maps from a `vec![]` source callsite invocation span to the "state" (i.e., whether we can + /// emit a warning there or not). + /// + /// The purpose of this is to buffer lints up until `check_expr_post` so that we can cancel a + /// lint while visiting, because a `vec![]` invocation span can appear multiple times when + /// it is passed as a macro argument, once in a context that doesn't require a `Vec<_>` and + /// another time that does. Consider: + /// ``` + /// macro_rules! m { + /// ($v:expr) => { + /// let a = $v; + /// $v.push(3); + /// } + /// } + /// m!(vec![1, 2]); + /// ``` + /// The macro invocation expands to two `vec![1, 2]` invocations. If we eagerly suggest changing + /// the first `vec![1, 2]` (which is shared with the other expn) to an array which indeed would + /// work, we get a false positive warning on the `$v.push(3)` which really requires `$v` to + /// be a vector. + span_to_state: BTreeMap, allow_in_test: bool, } + impl UselessVec { pub fn new(conf: &'static Conf) -> Self { Self { too_large_for_stack: conf.too_large_for_stack, msrv: conf.msrv, - span_to_lint_map: BTreeMap::new(), + span_to_state: BTreeMap::new(), allow_in_test: conf.allow_useless_vec_in_tests, } } @@ -62,17 +85,28 @@ declare_clippy_lint! { impl_lint_pass!(UselessVec => [USELESS_VEC]); -impl<'tcx> LateLintPass<'tcx> for UselessVec { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let Some(vec_args) = higher::VecArgs::hir(cx, expr.peel_borrows()) else { - return; - }; - if self.allow_in_test && is_in_test(cx.tcx, expr.hir_id) { - return; - } - // the parent callsite of this `vec!` expression, or span to the borrowed one such as `&vec!` - let callsite = expr.span.parent_callsite().unwrap_or(expr.span); +/// The "state" of a `vec![]` invocation, indicating whether it can or cannot be changed. +enum VecState { + Change { + suggest_ty: SuggestedType, + vec_snippet: String, + expr_hir_id: HirId, + }, + NoChange, +} +enum VecToArray { + /// Expression does not need to be a `Vec<_>` and its type can be changed to an array (or + /// slice). + Possible, + /// Expression must be a `Vec<_>`. Type cannot change. + Impossible, +} + +impl UselessVec { + /// Checks if the surrounding environment requires this expression to actually be of type + /// `Vec<_>`, or if it can be changed to `&[]`/`[]` without causing type errors. + fn expr_usage_requires_vec(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) -> VecToArray { match cx.tcx.parent_hir_node(expr.hir_id) { // search for `let foo = vec![_]` expressions where all uses of `foo` // adjust to slices or call a method that exist on slices (e.g. len) @@ -100,107 +134,119 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec { .is_continue(); if only_slice_uses { - self.check_vec_macro(cx, &vec_args, callsite, expr.hir_id, SuggestedType::Array); + VecToArray::Possible } else { - self.span_to_lint_map.insert(callsite, None); + VecToArray::Impossible } }, // if the local pattern has a specified type, do not lint. Node::LetStmt(LetStmt { ty: Some(_), .. }) if higher::VecArgs::hir(cx, expr).is_some() => { - self.span_to_lint_map.insert(callsite, None); + VecToArray::Impossible }, // search for `for _ in vec![...]` Node::Expr(Expr { span, .. }) if span.is_desugaring(DesugaringKind::ForLoop) && self.msrv.meets(cx, msrvs::ARRAY_INTO_ITERATOR) => { - let suggest_slice = suggest_type(expr); - self.check_vec_macro(cx, &vec_args, callsite, expr.hir_id, suggest_slice); + VecToArray::Possible }, // search for `&vec![_]` or `vec![_]` expressions where the adjusted type is `&[_]` _ => { - let suggest_slice = suggest_type(expr); - if adjusts_to_slice(cx, expr) { - self.check_vec_macro(cx, &vec_args, callsite, expr.hir_id, suggest_slice); + VecToArray::Possible } else { - self.span_to_lint_map.insert(callsite, None); + VecToArray::Impossible } }, } } - - fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { - for (span, lint_opt) in &self.span_to_lint_map { - if let Some((hir_id, suggest_slice, snippet, applicability)) = lint_opt { - let help_msg = format!("you can use {} directly", suggest_slice.desc()); - span_lint_hir_and_then(cx, USELESS_VEC, *hir_id, *span, "useless use of `vec!`", |diag| { - // If the `vec!` macro contains comment, better not make the suggestion machine - // applicable as it would remove them. - let applicability = if *applicability != Applicability::Unspecified - && let source_map = cx.tcx.sess.source_map() - && span_contains_comment(source_map, *span) - { - Applicability::Unspecified - } else { - *applicability - }; - diag.span_suggestion(*span, help_msg, snippet, applicability); - }); - } - } - } } -impl UselessVec { - fn check_vec_macro<'tcx>( - &mut self, - cx: &LateContext<'tcx>, - vec_args: &higher::VecArgs<'tcx>, - span: Span, - hir_id: HirId, - suggest_slice: SuggestedType, - ) { - if span.from_expansion() { - return; +impl<'tcx> LateLintPass<'tcx> for UselessVec { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let Some(vec_args) = higher::VecArgs::hir(cx, expr.peel_borrows()) + // The `vec![]` or `&vec![]` invocation span. + && let vec_span = expr.span.parent_callsite().unwrap_or(expr.span) + && !vec_span.from_expansion() + { + if self.allow_in_test && is_in_test(cx.tcx, expr.hir_id) { + return; + } + + match self.expr_usage_requires_vec(cx, expr) { + VecToArray::Possible => { + let suggest_ty = suggest_type(expr); + + // Size and `Copy` checks don't depend on the enclosing usage of the expression + // and don't need to be inserted into the state map. + let vec_snippet = match vec_args { + higher::VecArgs::Repeat(expr, len) => { + if is_copy(cx, cx.typeck_results().expr_ty(expr)) + && let Some(Constant::Int(length)) = ConstEvalCtxt::new(cx).eval(len) + && let Ok(length) = u64::try_from(length) + && size_of(cx, expr) + .checked_mul(length) + .is_some_and(|size| size <= self.too_large_for_stack) + { + suggest_ty.snippet(cx, Some(expr.span), Some(len.span)) + } else { + return; + } + }, + higher::VecArgs::Vec(args) => { + if let Ok(length) = u64::try_from(args.len()) + && size_of(cx, expr) + .checked_mul(length) + .is_some_and(|size| size <= self.too_large_for_stack) + { + suggest_ty.snippet( + cx, + args.first().zip(args.last()).map(|(first, last)| { + first.span.source_callsite().to(last.span.source_callsite()) + }), + None, + ) + } else { + return; + } + }, + }; + + if let Entry::Vacant(entry) = self.span_to_state.entry(vec_span) { + entry.insert(VecState::Change { + suggest_ty, + vec_snippet, + expr_hir_id: expr.hir_id, + }); + } + }, + VecToArray::Impossible => { + self.span_to_state.insert(vec_span, VecState::NoChange); + }, + } } + } - let snippet = match *vec_args { - higher::VecArgs::Repeat(elem, len) => { - if let Some(Constant::Int(len_constant)) = ConstEvalCtxt::new(cx).eval(len) { - // vec![ty; N] works when ty is Clone, [ty; N] requires it to be Copy also - if !is_copy(cx, cx.typeck_results().expr_ty(elem)) { - return; - } - - #[expect(clippy::cast_possible_truncation)] - if len_constant as u64 * size_of(cx, elem) > self.too_large_for_stack { - return; - } - - suggest_slice.snippet(cx, Some(elem.span), Some(len.span)) - } else { - return; - } - }, - higher::VecArgs::Vec(args) => { - let args_span = if let Some(last) = args.iter().last() { - if args.len() as u64 * size_of(cx, last) > self.too_large_for_stack { - return; - } - Some(args[0].span.source_callsite().to(last.span.source_callsite())) - } else { - None - }; - suggest_slice.snippet(cx, args_span, None) - }, - }; - - self.span_to_lint_map.entry(span).or_insert(Some(( - hir_id, - suggest_slice, - snippet, - Applicability::MachineApplicable, - ))); + fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { + for (span, state) in mem::take(&mut self.span_to_state) { + if let VecState::Change { + suggest_ty, + vec_snippet, + expr_hir_id, + } = state + { + span_lint_hir_and_then(cx, USELESS_VEC, expr_hir_id, span, "useless use of `vec!`", |diag| { + let help_msg = format!("you can use {} directly", suggest_ty.desc()); + // If the `vec!` macro contains comment, better not make the suggestion machine applicable as it + // would remove them. + let applicability = if span_contains_comment(cx.tcx.sess.source_map(), span) { + Applicability::Unspecified + } else { + Applicability::MachineApplicable + }; + diag.span_suggestion(span, help_msg, vec_snippet, applicability); + }); + } + } } } From bfd73ccae051b32686486d1849ea92750d259ad6 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Thu, 3 Apr 2025 17:26:22 +0200 Subject: [PATCH 002/116] Add test case for array false positive --- tests/ui/vec.fixed | 9 +++++++++ tests/ui/vec.rs | 9 +++++++++ tests/ui/vec.stderr | 8 +++++++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/ui/vec.fixed b/tests/ui/vec.fixed index f360a8afadf6..55742459c92c 100644 --- a/tests/ui/vec.fixed +++ b/tests/ui/vec.fixed @@ -242,3 +242,12 @@ fn issue_12101() { for a in &[1, 2] {} //~^ useless_vec } + +fn issue_14531() { + // The lint used to suggest using an array rather than a reference to a slice. + + fn requires_ref_slice(v: &[()]) {} + let v = &[]; + //~^ useless_vec + requires_ref_slice(v); +} diff --git a/tests/ui/vec.rs b/tests/ui/vec.rs index a779d33557cb..fbf7131323c3 100644 --- a/tests/ui/vec.rs +++ b/tests/ui/vec.rs @@ -242,3 +242,12 @@ fn issue_12101() { for a in &(vec![1, 2]) {} //~^ useless_vec } + +fn issue_14531() { + // The lint used to suggest using an array rather than a reference to a slice. + + fn requires_ref_slice(v: &[()]) {} + let v = &vec![]; + //~^ useless_vec + requires_ref_slice(v); +} diff --git a/tests/ui/vec.stderr b/tests/ui/vec.stderr index 806d6617200f..d16c8a8944a2 100644 --- a/tests/ui/vec.stderr +++ b/tests/ui/vec.stderr @@ -127,5 +127,11 @@ error: useless use of `vec!` LL | for a in &(vec![1, 2]) {} | ^^^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` -error: aborting due to 21 previous errors +error: useless use of `vec!` + --> tests/ui/vec.rs:250:13 + | +LL | let v = &vec![]; + | ^^^^^^^ help: you can use a slice directly: `&[]` + +error: aborting due to 22 previous errors From 47e8c315f3e600357f0147b867de798b1b96cd03 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Sat, 21 Jun 2025 15:34:31 +0200 Subject: [PATCH 003/116] Make sure spans are from the root --- clippy_lints/src/vec.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/vec.rs b/clippy_lints/src/vec.rs index 1646f2220212..52b30ddce12b 100644 --- a/clippy_lints/src/vec.rs +++ b/clippy_lints/src/vec.rs @@ -25,7 +25,7 @@ pub struct UselessVec { /// Maps from a `vec![]` source callsite invocation span to the "state" (i.e., whether we can /// emit a warning there or not). /// - /// The purpose of this is to buffer lints up until `check_expr_post` so that we can cancel a + /// The purpose of this is to buffer lints up until `check_crate_post` so that we can cancel a /// lint while visiting, because a `vec![]` invocation span can appear multiple times when /// it is passed as a macro argument, once in a context that doesn't require a `Vec<_>` and /// another time that does. Consider: @@ -187,7 +187,11 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec { .checked_mul(length) .is_some_and(|size| size <= self.too_large_for_stack) { - suggest_ty.snippet(cx, Some(expr.span), Some(len.span)) + suggest_ty.snippet( + cx, + Some(expr.span.source_callsite()), + Some(len.span.source_callsite()), + ) } else { return; } @@ -267,11 +271,17 @@ impl SuggestedType { } fn snippet(self, cx: &LateContext<'_>, args_span: Option, len_span: Option) -> String { + // Invariant of the lint as implemented: all spans are from the root context (and as a result, + // always trivially crate-local). + assert!(args_span.is_none_or(|s| !s.from_expansion())); + assert!(len_span.is_none_or(|s| !s.from_expansion())); + let maybe_args = args_span - .and_then(|sp| sp.get_source_text(cx)) + .map(|sp| sp.get_source_text(cx).expect("spans are always crate-local")) .map_or(String::new(), |x| x.to_owned()); let maybe_len = len_span - .and_then(|sp| sp.get_source_text(cx).map(|s| format!("; {s}"))) + .map(|sp| sp.get_source_text(cx).expect("spans are always crate-local")) + .map(|st| format!("; {st}")) .unwrap_or_default(); match self { From bd33a02ea6c01a7053451b0205edb01e4172a601 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 7 Jun 2025 08:25:20 +0200 Subject: [PATCH 004/116] Get the block content from the proper context The first statement of the block might have been in a different context from the expression. Walk up to the right context to get bounds properly. Also, switch to `snippet_with_applicability()` since we know that we are in the right context already. --- clippy_lints/src/if_then_some_else_none.rs | 32 +++++++------- tests/ui/if_then_some_else_none.fixed | 41 +++++++++++++++++ tests/ui/if_then_some_else_none.rs | 51 ++++++++++++++++++++++ tests/ui/if_then_some_else_none.stderr | 25 ++++++++++- 4 files changed, 133 insertions(+), 16 deletions(-) diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index 7158f9419c1c..b50d91f10146 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -2,16 +2,16 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::source::snippet_with_context; +use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context}; use clippy_utils::sugg::Sugg; use clippy_utils::{ contains_return, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, is_res_lang_ctor, - path_res, peel_blocks, + path_res, peel_blocks, sym, }; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; declare_clippy_lint! { @@ -71,21 +71,21 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { && let ExprKind::Block(then_block, _) = then.kind && let Some(then_expr) = then_block.expr && let ExprKind::Call(then_call, [then_arg]) = then_expr.kind - && let ctxt = expr.span.ctxt() - && then_expr.span.ctxt() == ctxt + && !expr.span.from_expansion() + && !then_expr.span.from_expansion() && is_res_lang_ctor(cx, path_res(cx, then_call), OptionSome) && is_res_lang_ctor(cx, path_res(cx, peel_blocks(els)), OptionNone) && !is_else_clause(cx.tcx, expr) && !is_in_const_context(cx) - && !expr.span.in_external_macro(cx.sess().source_map()) && self.msrv.meets(cx, msrvs::BOOL_THEN) && !contains_return(then_block.stmts) { let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(cx, msrvs::BOOL_THEN_SOME) { - "then_some" + sym::then_some } else { - "then" + sym::then }; + let ctxt = expr.span.ctxt(); span_lint_and_then( cx, @@ -98,16 +98,18 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { } let mut app = Applicability::MachineApplicable; - let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app) + let cond_snip = Sugg::hir_with_context(cx, cond, ctxt, "[condition]", &mut app) .maybe_paren() .to_string(); let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0; - let method_body = if let Some(first_stmt) = then_block.stmts.first() { - let (block_snippet, _) = - snippet_with_context(cx, first_stmt.span.until(then_arg.span), ctxt, "..", &mut app); - let closure = if method_name == "then" { "|| " } else { "" }; - format!("{closure} {{ {block_snippet}; {arg_snip} }}") - } else if method_name == "then" { + let method_body = if let Some(first_stmt) = then_block.stmts.first() + && let Some(first_stmt_span) = walk_span_to_context(first_stmt.span, ctxt) + { + let block_snippet = + snippet_with_applicability(cx, first_stmt_span.until(then_expr.span), "..", &mut app); + let closure = if method_name == sym::then { "|| " } else { "" }; + format!("{closure} {{ {} {arg_snip} }}", block_snippet.trim_end()) + } else if method_name == sym::then { (std::borrow::Cow::Borrowed("|| ") + arg_snip).into_owned() } else { arg_snip.into_owned() diff --git a/tests/ui/if_then_some_else_none.fixed b/tests/ui/if_then_some_else_none.fixed index d14a805b6667..0fd130609aee 100644 --- a/tests/ui/if_then_some_else_none.fixed +++ b/tests/ui/if_then_some_else_none.fixed @@ -165,3 +165,44 @@ mod issue15257 { do_something((i % 2 == 0).then_some(closure_fn)); } } + +fn issue15005() { + struct Counter { + count: u32, + } + + impl Counter { + fn new() -> Counter { + Counter { count: 0 } + } + } + + impl Iterator for Counter { + type Item = u32; + + fn next(&mut self) -> Option { + //~v if_then_some_else_none + (self.count < 5).then(|| { self.count += 1; self.count }) + } + } +} + +fn statements_from_macro() { + macro_rules! mac { + () => { + println!("foo"); + println!("bar"); + }; + } + //~v if_then_some_else_none + let _ = true.then(|| { mac!(); 42 }); +} + +fn dont_lint_inside_macros() { + macro_rules! mac { + ($cond:expr, $res:expr) => { + if $cond { Some($res) } else { None } + }; + } + let _: Option = mac!(true, 42); +} diff --git a/tests/ui/if_then_some_else_none.rs b/tests/ui/if_then_some_else_none.rs index bb0072f31573..640828aa9bf6 100644 --- a/tests/ui/if_then_some_else_none.rs +++ b/tests/ui/if_then_some_else_none.rs @@ -211,3 +211,54 @@ mod issue15257 { }); } } + +fn issue15005() { + struct Counter { + count: u32, + } + + impl Counter { + fn new() -> Counter { + Counter { count: 0 } + } + } + + impl Iterator for Counter { + type Item = u32; + + fn next(&mut self) -> Option { + //~v if_then_some_else_none + if self.count < 5 { + self.count += 1; + Some(self.count) + } else { + None + } + } + } +} + +fn statements_from_macro() { + macro_rules! mac { + () => { + println!("foo"); + println!("bar"); + }; + } + //~v if_then_some_else_none + let _ = if true { + mac!(); + Some(42) + } else { + None + }; +} + +fn dont_lint_inside_macros() { + macro_rules! mac { + ($cond:expr, $res:expr) => { + if $cond { Some($res) } else { None } + }; + } + let _: Option = mac!(true, 42); +} diff --git a/tests/ui/if_then_some_else_none.stderr b/tests/ui/if_then_some_else_none.stderr index c2e624a0a73b..58651a055942 100644 --- a/tests/ui/if_then_some_else_none.stderr +++ b/tests/ui/if_then_some_else_none.stderr @@ -116,5 +116,28 @@ LL | | None LL | | }); | |_________^ help: try: `(i % 2 == 0).then_some(closure_fn)` -error: aborting due to 11 previous errors +error: this could be simplified with `bool::then` + --> tests/ui/if_then_some_else_none.rs:231:13 + | +LL | / if self.count < 5 { +LL | | self.count += 1; +LL | | Some(self.count) +LL | | } else { +LL | | None +LL | | } + | |_____________^ help: try: `(self.count < 5).then(|| { self.count += 1; self.count })` + +error: this could be simplified with `bool::then` + --> tests/ui/if_then_some_else_none.rs:249:13 + | +LL | let _ = if true { + | _____________^ +LL | | mac!(); +LL | | Some(42) +LL | | } else { +LL | | None +LL | | }; + | |_____^ help: try: `true.then(|| { mac!(); 42 })` + +error: aborting due to 13 previous errors From a7162e416e73afd4fc4c72f171b8166bb9d35b8c Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 2 Aug 2025 23:42:57 +0200 Subject: [PATCH 005/116] Remove references to two unknown lints in config `option_map_unwrap_or` and `seek_rewind` are not current lints, and cannot be referenced as having the `msrv` configuration option. --- book/src/lint_configuration.md | 2 -- clippy_config/src/conf.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 7f16f3a98105..6d554170f1fe 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -873,7 +873,6 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`needless_borrow`](https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow) * [`non_std_lazy_statics`](https://rust-lang.github.io/rust-clippy/master/index.html#non_std_lazy_statics) * [`option_as_ref_deref`](https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref) -* [`option_map_unwrap_or`](https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or) * [`ptr_as_ptr`](https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr) * [`question_mark`](https://rust-lang.github.io/rust-clippy/master/index.html#question_mark) * [`redundant_field_names`](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names) @@ -881,7 +880,6 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`repeat_vec_with_capacity`](https://rust-lang.github.io/rust-clippy/master/index.html#repeat_vec_with_capacity) * [`same_item_push`](https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push) * [`seek_from_current`](https://rust-lang.github.io/rust-clippy/master/index.html#seek_from_current) -* [`seek_rewind`](https://rust-lang.github.io/rust-clippy/master/index.html#seek_rewind) * [`to_digit_is_some`](https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some) * [`transmute_ptr_to_ref`](https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref) * [`tuple_array_conversions`](https://rust-lang.github.io/rust-clippy/master/index.html#tuple_array_conversions) diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 8167d75583ee..a9987b4e2ec5 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -775,7 +775,6 @@ define_Conf! { needless_borrow, non_std_lazy_statics, option_as_ref_deref, - option_map_unwrap_or, ptr_as_ptr, question_mark, redundant_field_names, @@ -783,7 +782,6 @@ define_Conf! { repeat_vec_with_capacity, same_item_push, seek_from_current, - seek_rewind, to_digit_is_some, transmute_ptr_to_ref, tuple_array_conversions, From 07b04a2e04348283acf9565f3de2c500cce4df9a Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 2 Aug 2025 23:52:39 +0200 Subject: [PATCH 006/116] Check that all configuration options reference existing lints --- tests/config-consistency.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tests/config-consistency.rs diff --git a/tests/config-consistency.rs b/tests/config-consistency.rs new file mode 100644 index 000000000000..9e7ca26c7d40 --- /dev/null +++ b/tests/config-consistency.rs @@ -0,0 +1,30 @@ +#![feature(rustc_private)] + +// This test checks that all lints defined in `clippy_config::conf` in `#[lints]` +// attributes exist as Clippy lints. +// +// This test is a no-op if run as part of the compiler test suite +// and will always succeed. + +use std::collections::HashSet; + +#[test] +fn config_consistency() { + if option_env!("RUSTC_TEST_SUITE").is_some() { + return; + } + + let lint_names: HashSet = clippy_lints::declared_lints::LINTS + .iter() + .map(|lint_info| lint_info.lint.name.strip_prefix("clippy::").unwrap().to_lowercase()) + .collect(); + for conf in clippy_config::get_configuration_metadata() { + for lint in conf.lints { + assert!( + lint_names.contains(*lint), + "Configuration option {} references lint `{lint}` which does not exist", + conf.name + ); + } + } +} From 0170d6cecd1e8592774ccd94dd2a5bf30ff2495a Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Thu, 14 Aug 2025 00:53:34 +0100 Subject: [PATCH 007/116] or_then_unwrap suggestion preserves macro calls Before this change, the suggestion for `Option.or(Some(vec![])).unwrap()` expanded the `vec!` macro which broke the code, both in terms of readability, and because the expansion references `$crate` which cannot be inlined. --- clippy_lints/src/methods/or_then_unwrap.rs | 2 +- tests/ui/or_then_unwrap.fixed | 6 ++++++ tests/ui/or_then_unwrap.rs | 6 ++++++ tests/ui/or_then_unwrap.stderr | 10 ++++++++-- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/methods/or_then_unwrap.rs b/clippy_lints/src/methods/or_then_unwrap.rs index 3e64e15dc860..1a760ea733d7 100644 --- a/clippy_lints/src/methods/or_then_unwrap.rs +++ b/clippy_lints/src/methods/or_then_unwrap.rs @@ -62,7 +62,7 @@ fn get_content_if_ctor_matches(cx: &LateContext<'_>, expr: &Expr<'_>, item: Lang if let ExprKind::Call(some_expr, [arg]) = expr.kind && is_res_lang_ctor(cx, path_res(cx, some_expr), item) { - Some(arg.span) + Some(arg.span.source_callsite()) } else { None } diff --git a/tests/ui/or_then_unwrap.fixed b/tests/ui/or_then_unwrap.fixed index ba9beef57afa..9660b82fe7d6 100644 --- a/tests/ui/or_then_unwrap.fixed +++ b/tests/ui/or_then_unwrap.fixed @@ -28,6 +28,12 @@ fn main() { // //~^^ or_then_unwrap + // Call with macro should preserve the macro call rather than expand it + let option: Option> = None; + let _ = option.unwrap_or(vec!["fallback"]); // should trigger lint + // + //~^^ or_then_unwrap + // as part of a method chain let option: Option<&str> = None; let _ = option.map(|v| v).unwrap_or("fallback").to_string().chars(); // should trigger lint diff --git a/tests/ui/or_then_unwrap.rs b/tests/ui/or_then_unwrap.rs index fac90249a243..c38733521164 100644 --- a/tests/ui/or_then_unwrap.rs +++ b/tests/ui/or_then_unwrap.rs @@ -28,6 +28,12 @@ fn main() { // //~^^ or_then_unwrap + // Call with macro should preserve the macro call rather than expand it + let option: Option> = None; + let _ = option.or(Some(vec!["fallback"])).unwrap(); // should trigger lint + // + //~^^ or_then_unwrap + // as part of a method chain let option: Option<&str> = None; let _ = option.map(|v| v).or(Some("fallback")).unwrap().to_string().chars(); // should trigger lint diff --git a/tests/ui/or_then_unwrap.stderr b/tests/ui/or_then_unwrap.stderr index 1160498c6053..3e66b15edbd6 100644 --- a/tests/ui/or_then_unwrap.stderr +++ b/tests/ui/or_then_unwrap.stderr @@ -14,10 +14,16 @@ LL | let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger l | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or("fallback")` error: found `.or(Some(…)).unwrap()` - --> tests/ui/or_then_unwrap.rs:33:31 + --> tests/ui/or_then_unwrap.rs:33:20 + | +LL | let _ = option.or(Some(vec!["fallback"])).unwrap(); // should trigger lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or(vec!["fallback"])` + +error: found `.or(Some(…)).unwrap()` + --> tests/ui/or_then_unwrap.rs:39:31 | LL | let _ = option.map(|v| v).or(Some("fallback")).unwrap().to_string().chars(); // should trigger lint | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or("fallback")` -error: aborting due to 3 previous errors +error: aborting due to 4 previous errors From 41405456daf54af9182e73277c417a6572d14200 Mon Sep 17 00:00:00 2001 From: Zihan Date: Sat, 16 Aug 2025 01:23:37 -0400 Subject: [PATCH 008/116] `missing_inline_in_public_items`: fix lint emission source HirId use trait item's HirId when emitting lint at `check_item` level changelog: [`missing_inline_in_public_items`]: fix trait item lint emission Signed-off-by: Zihan --- clippy_lints/src/missing_inline.rs | 38 ++++++++++++++---------------- tests/ui/missing_inline.rs | 7 ++++++ 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index d02952eb4870..28555a610900 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::{span_lint, span_lint_hir}; use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, Attribute, find_attr}; @@ -64,14 +64,20 @@ declare_clippy_lint! { "detects missing `#[inline]` attribute for public callables (functions, trait methods, methods...)" } -fn check_missing_inline_attrs(cx: &LateContext<'_>, attrs: &[Attribute], sp: Span, desc: &'static str) { +fn check_missing_inline_attrs( + cx: &LateContext<'_>, + attrs: &[Attribute], + sp: Span, + desc: &'static str, + hir_id: Option, +) { if !find_attr!(attrs, AttributeKind::Inline(..)) { - span_lint( - cx, - MISSING_INLINE_IN_PUBLIC_ITEMS, - sp, - format!("missing `#[inline]` for {desc}"), - ); + let msg = format!("missing `#[inline]` for {desc}"); + if let Some(hir_id) = hir_id { + span_lint_hir(cx, MISSING_INLINE_IN_PUBLIC_ITEMS, hir_id, sp, msg); + } else { + span_lint(cx, MISSING_INLINE_IN_PUBLIC_ITEMS, sp, msg); + } } } @@ -103,17 +109,9 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { let desc = "a function"; let attrs = cx.tcx.hir_attrs(it.hir_id()); - check_missing_inline_attrs(cx, attrs, it.span, desc); + check_missing_inline_attrs(cx, attrs, it.span, desc, None); }, - hir::ItemKind::Trait( - ref _constness, - ref _is_auto, - ref _unsafe, - _ident, - _generics, - _bounds, - trait_items, - ) => { + hir::ItemKind::Trait(.., trait_items) => { // note: we need to check if the trait is exported so we can't use // `LateLintPass::check_trait_item` here. for &tit in trait_items { @@ -127,7 +125,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { let desc = "a default trait method"; let item = cx.tcx.hir_trait_item(tit); let attrs = cx.tcx.hir_attrs(item.hir_id()); - check_missing_inline_attrs(cx, attrs, item.span, desc); + check_missing_inline_attrs(cx, attrs, item.span, desc, Some(tit.hir_id())); } }, } @@ -182,7 +180,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { } let attrs = cx.tcx.hir_attrs(impl_item.hir_id()); - check_missing_inline_attrs(cx, attrs, impl_item.span, desc); + check_missing_inline_attrs(cx, attrs, impl_item.span, desc, None); } } diff --git a/tests/ui/missing_inline.rs b/tests/ui/missing_inline.rs index 223c7447975a..8e937d609512 100644 --- a/tests/ui/missing_inline.rs +++ b/tests/ui/missing_inline.rs @@ -97,3 +97,10 @@ pub mod issue15301 { println!("Just called a Rust function from Rust!"); } } + +pub mod issue15491 { + pub trait Foo { + #[allow(clippy::missing_inline_in_public_items)] + fn foo(&self) {} + } +} From 443860d1ae4310e12188e05278fc34fd01ace1c6 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Mon, 18 Aug 2025 22:15:03 -0700 Subject: [PATCH 009/116] ptr_as_ptr: use correct span context for `pointer::cast` sugg --- clippy_lints/src/casts/ptr_as_ptr.rs | 2 +- tests/ui/ptr_as_ptr.fixed | 6 ++++++ tests/ui/ptr_as_ptr.rs | 6 ++++++ tests/ui/ptr_as_ptr.stderr | 8 +++++++- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/casts/ptr_as_ptr.rs b/clippy_lints/src/casts/ptr_as_ptr.rs index 890754090989..30e4adefd1f1 100644 --- a/clippy_lints/src/casts/ptr_as_ptr.rs +++ b/clippy_lints/src/casts/ptr_as_ptr.rs @@ -78,7 +78,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) let method = snippet_with_applicability(cx, qpath_span_without_turbofish(method), "..", &mut app); ("try call directly", format!("{method}{turbofish}()")) } else { - let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app); + let cast_expr_sugg = Sugg::hir_with_context(cx, cast_expr, expr.span.ctxt(), "_", &mut app); ( "try `pointer::cast`, a safer alternative", diff --git a/tests/ui/ptr_as_ptr.fixed b/tests/ui/ptr_as_ptr.fixed index 78e1ceb480a5..4457878b6faf 100644 --- a/tests/ui/ptr_as_ptr.fixed +++ b/tests/ui/ptr_as_ptr.fixed @@ -229,3 +229,9 @@ fn issue15283() { //~^ ptr_as_ptr } } + +fn issue15232() { + let mut b = Box::new(0i32); + let _ptr = std::ptr::addr_of_mut!(*b).cast::<()>(); + //~^ ptr_as_ptr +} diff --git a/tests/ui/ptr_as_ptr.rs b/tests/ui/ptr_as_ptr.rs index 70732cf0a6c1..9124fc912d2c 100644 --- a/tests/ui/ptr_as_ptr.rs +++ b/tests/ui/ptr_as_ptr.rs @@ -229,3 +229,9 @@ fn issue15283() { //~^ ptr_as_ptr } } + +fn issue15232() { + let mut b = Box::new(0i32); + let _ptr = std::ptr::addr_of_mut!(*b) as *mut (); + //~^ ptr_as_ptr +} diff --git a/tests/ui/ptr_as_ptr.stderr b/tests/ui/ptr_as_ptr.stderr index c0a2a4b6d204..af21c1e35f52 100644 --- a/tests/ui/ptr_as_ptr.stderr +++ b/tests/ui/ptr_as_ptr.stderr @@ -199,5 +199,11 @@ error: `as` casting between raw pointers without changing their constness LL | let _: fn() = std::mem::transmute(std::ptr::null::<()>() as *const u8); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null::()` -error: aborting due to 33 previous errors +error: `as` casting between raw pointers without changing their constness + --> tests/ui/ptr_as_ptr.rs:235:16 + | +LL | let _ptr = std::ptr::addr_of_mut!(*b) as *mut (); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `std::ptr::addr_of_mut!(*b).cast::<()>()` + +error: aborting due to 34 previous errors From 220249eebb4cbd96fd77abb5878727f4ae4f6a6f Mon Sep 17 00:00:00 2001 From: yanglsh Date: Wed, 18 Jun 2025 21:45:47 +0800 Subject: [PATCH 010/116] fix: `redundant_closure` suggests wrongly with deref overload --- clippy_lints/src/eta_reduction.rs | 21 ++++++++++++--- tests/ui/eta.fixed | 45 +++++++++++++++++++++++++++++++ tests/ui/eta.rs | 45 +++++++++++++++++++++++++++++++ tests/ui/eta.stderr | 28 +++++++++++++++---- 4 files changed, 130 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 0eefc2f61096..2da1c2bad117 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -12,6 +12,7 @@ use rustc_hir::attrs::AttributeKind; use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, GenericArgs, Param, PatKind, QPath, Safety, TyKind, find_attr}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::adjustment::Adjust; use rustc_middle::ty::{ self, Binder, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt, TypeckResults, }; @@ -148,10 +149,9 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx { return; } - let callee_ty_adjusted = typeck - .expr_adjustments(callee) - .last() - .map_or(callee_ty, |a| a.target.peel_refs()); + + let callee_ty_adjustments = typeck.expr_adjustments(callee); + let callee_ty_adjusted = callee_ty_adjustments.last().map_or(callee_ty, |a| a.target); let sig = match callee_ty_adjusted.kind() { ty::FnDef(def, _) => { @@ -230,7 +230,20 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx }, _ => (), } + } else if let n_refs = + callee_ty_adjustments + .iter() + .rev() + .fold(0, |acc, adjustment| match adjustment.kind { + Adjust::Deref(Some(_)) => acc + 1, + Adjust::Deref(_) if acc > 0 => acc + 1, + _ => acc, + }) + && n_refs > 0 + { + snippet = format!("{}{snippet}", "*".repeat(n_refs)); } + let replace_with = match callee_ty_adjusted.kind() { ty::FnDef(def, _) => cx.tcx.def_descr(*def), _ => "function", diff --git a/tests/ui/eta.fixed b/tests/ui/eta.fixed index 3d2b41b8fb81..6944a979c05e 100644 --- a/tests/ui/eta.fixed +++ b/tests/ui/eta.fixed @@ -565,6 +565,51 @@ fn issue_14789() { ); } +fn issue_15072() { + use std::ops::Deref; + + struct Foo; + impl Deref for Foo { + type Target = fn() -> &'static str; + + fn deref(&self) -> &Self::Target { + fn hello() -> &'static str { + "Hello, world!" + } + &(hello as fn() -> &'static str) + } + } + + fn accepts_fn(f: impl Fn() -> &'static str) { + println!("{}", f()); + } + + fn some_fn() -> &'static str { + todo!() + } + + let f = &Foo; + accepts_fn(**f); + //~^ redundant_closure + + let g = &some_fn; + accepts_fn(g); + //~^ redundant_closure + + struct Bar(Foo); + impl Deref for Bar { + type Target = Foo; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + let b = &Bar(Foo); + accepts_fn(***b); + //~^ redundant_closure +} + fn issue8817() { fn f(_: u32) -> u32 { todo!() diff --git a/tests/ui/eta.rs b/tests/ui/eta.rs index 79d1103410d9..5bcc1cb26fd7 100644 --- a/tests/ui/eta.rs +++ b/tests/ui/eta.rs @@ -565,6 +565,51 @@ fn issue_14789() { ); } +fn issue_15072() { + use std::ops::Deref; + + struct Foo; + impl Deref for Foo { + type Target = fn() -> &'static str; + + fn deref(&self) -> &Self::Target { + fn hello() -> &'static str { + "Hello, world!" + } + &(hello as fn() -> &'static str) + } + } + + fn accepts_fn(f: impl Fn() -> &'static str) { + println!("{}", f()); + } + + fn some_fn() -> &'static str { + todo!() + } + + let f = &Foo; + accepts_fn(|| f()); + //~^ redundant_closure + + let g = &some_fn; + accepts_fn(|| g()); + //~^ redundant_closure + + struct Bar(Foo); + impl Deref for Bar { + type Target = Foo; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + let b = &Bar(Foo); + accepts_fn(|| b()); + //~^ redundant_closure +} + fn issue8817() { fn f(_: u32) -> u32 { todo!() diff --git a/tests/ui/eta.stderr b/tests/ui/eta.stderr index aa32ed1a38ef..0b401cdea987 100644 --- a/tests/ui/eta.stderr +++ b/tests/ui/eta.stderr @@ -215,28 +215,46 @@ LL | let _field = bind.or_else(|| get_default()).unwrap(); | ^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `get_default` error: redundant closure - --> tests/ui/eta.rs:588:14 + --> tests/ui/eta.rs:592:16 + | +LL | accepts_fn(|| f()); + | ^^^^^^ help: replace the closure with the function itself: `**f` + +error: redundant closure + --> tests/ui/eta.rs:596:16 + | +LL | accepts_fn(|| g()); + | ^^^^^^ help: replace the closure with the function itself: `g` + +error: redundant closure + --> tests/ui/eta.rs:609:16 + | +LL | accepts_fn(|| b()); + | ^^^^^^ help: replace the closure with the function itself: `***b` + +error: redundant closure + --> tests/ui/eta.rs:633:14 | LL | .map(|n| MyError::A(n)) | ^^^^^^^^^^^^^^^^^ help: replace the closure with the tuple variant itself: `MyError::A` error: redundant closure - --> tests/ui/eta.rs:585:14 + --> tests/ui/eta.rs:630:14 | LL | .map(|n| S(n)) | ^^^^^^^^ help: replace the closure with the tuple struct itself: `S` error: redundant closure - --> tests/ui/eta.rs:582:14 + --> tests/ui/eta.rs:627:14 | LL | .map(|n| g(n)) | ^^^^^^^^ help: replace the closure with the function itself: `g` error: redundant closure - --> tests/ui/eta.rs:579:14 + --> tests/ui/eta.rs:624:14 | LL | .map(|n| f(n)) | ^^^^^^^^ help: replace the closure with the function itself: `f` -error: aborting due to 39 previous errors +error: aborting due to 42 previous errors From f11d51273ccb540154748d9668c069bb8e50de3a Mon Sep 17 00:00:00 2001 From: yanglsh Date: Wed, 13 Aug 2025 22:30:35 +0800 Subject: [PATCH 011/116] fix: `semicolon_inside_block` FP when attribute over expr is not enabled --- clippy_lints/src/semicolon_block.rs | 5 +++++ tests/ui/semicolon_inside_block.fixed | 5 +++++ tests/ui/semicolon_inside_block.rs | 5 +++++ .../semicolon_inside_block_stmt_expr_attrs.fixed | 11 +++++++++++ .../ui/semicolon_inside_block_stmt_expr_attrs.rs | 11 +++++++++++ ...semicolon_inside_block_stmt_expr_attrs.stderr | 16 ++++++++++++++++ 6 files changed, 53 insertions(+) create mode 100644 tests/ui/semicolon_inside_block_stmt_expr_attrs.fixed create mode 100644 tests/ui/semicolon_inside_block_stmt_expr_attrs.rs create mode 100644 tests/ui/semicolon_inside_block_stmt_expr_attrs.stderr diff --git a/clippy_lints/src/semicolon_block.rs b/clippy_lints/src/semicolon_block.rs index db91c57b1816..1dea8f17c34b 100644 --- a/clippy_lints/src/semicolon_block.rs +++ b/clippy_lints/src/semicolon_block.rs @@ -155,6 +155,11 @@ impl LateLintPass<'_> for SemicolonBlock { kind: ExprKind::Block(block, _), .. }) if !block.span.from_expansion() => { + let attrs = cx.tcx.hir_attrs(stmt.hir_id); + if !attrs.is_empty() && !cx.tcx.features().stmt_expr_attributes() { + return; + } + if let Some(tail) = block.expr { self.semicolon_inside_block(cx, block, tail, stmt.span); } diff --git a/tests/ui/semicolon_inside_block.fixed b/tests/ui/semicolon_inside_block.fixed index 7eb53e733ad5..7308e78aae26 100644 --- a/tests/ui/semicolon_inside_block.fixed +++ b/tests/ui/semicolon_inside_block.fixed @@ -86,3 +86,8 @@ fn main() { unit_fn_block() } + +pub fn issue15388() { + #[rustfmt::skip] + {0; 0}; +} diff --git a/tests/ui/semicolon_inside_block.rs b/tests/ui/semicolon_inside_block.rs index 9fa5b117194d..467bf4d779f2 100644 --- a/tests/ui/semicolon_inside_block.rs +++ b/tests/ui/semicolon_inside_block.rs @@ -86,3 +86,8 @@ fn main() { unit_fn_block() } + +pub fn issue15388() { + #[rustfmt::skip] + {0; 0}; +} diff --git a/tests/ui/semicolon_inside_block_stmt_expr_attrs.fixed b/tests/ui/semicolon_inside_block_stmt_expr_attrs.fixed new file mode 100644 index 000000000000..5b93a91da003 --- /dev/null +++ b/tests/ui/semicolon_inside_block_stmt_expr_attrs.fixed @@ -0,0 +1,11 @@ +// Test when the feature `stmt_expr_attributes` is enabled + +#![feature(stmt_expr_attributes)] +#![allow(clippy::no_effect)] +#![warn(clippy::semicolon_inside_block)] + +pub fn issue15388() { + #[rustfmt::skip] + {0; 0;} + //~^ semicolon_inside_block +} diff --git a/tests/ui/semicolon_inside_block_stmt_expr_attrs.rs b/tests/ui/semicolon_inside_block_stmt_expr_attrs.rs new file mode 100644 index 000000000000..aa2c0cb50299 --- /dev/null +++ b/tests/ui/semicolon_inside_block_stmt_expr_attrs.rs @@ -0,0 +1,11 @@ +// Test when the feature `stmt_expr_attributes` is enabled + +#![feature(stmt_expr_attributes)] +#![allow(clippy::no_effect)] +#![warn(clippy::semicolon_inside_block)] + +pub fn issue15388() { + #[rustfmt::skip] + {0; 0}; + //~^ semicolon_inside_block +} diff --git a/tests/ui/semicolon_inside_block_stmt_expr_attrs.stderr b/tests/ui/semicolon_inside_block_stmt_expr_attrs.stderr new file mode 100644 index 000000000000..5bb91915a5fd --- /dev/null +++ b/tests/ui/semicolon_inside_block_stmt_expr_attrs.stderr @@ -0,0 +1,16 @@ +error: consider moving the `;` inside the block for consistent formatting + --> tests/ui/semicolon_inside_block_stmt_expr_attrs.rs:9:5 + | +LL | {0; 0}; + | ^^^^^^^ + | + = note: `-D clippy::semicolon-inside-block` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::semicolon_inside_block)]` +help: put the `;` here + | +LL - {0; 0}; +LL + {0; 0;} + | + +error: aborting due to 1 previous error + From 71b79ab46603f542fce1bf125d9429920693abbb Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 22 Aug 2025 14:57:22 +0200 Subject: [PATCH 012/116] Merge commit '877967959ae8da9814df4f2614971f4d784bf53f' into clippy-subtree-update --- .github/workflows/clippy_dev.yml | 2 +- .github/workflows/clippy_mq.yml | 8 +- .github/workflows/clippy_pr.yml | 2 +- .github/workflows/deploy.yml | 4 +- .github/workflows/feature_freeze.yml | 2 +- .github/workflows/lintcheck.yml | 6 +- .github/workflows/remark.yml | 2 +- CONTRIBUTING.md | 12 +- Cargo.toml | 11 + .../continuous_integration/github_actions.md | 2 +- book/src/development/basics.md | 4 +- .../development/common_tools_writing_lints.md | 2 +- book/src/lint_configuration.md | 2 +- clippy_config/src/conf.rs | 2 +- clippy_dev/src/dogfood.rs | 49 ++--- clippy_dev/src/lib.rs | 7 +- clippy_dev/src/lint.rs | 56 ++--- clippy_dev/src/main.rs | 11 +- clippy_dev/src/serve.rs | 107 ++++++---- clippy_dev/src/setup/git_hook.rs | 6 - clippy_dev/src/setup/mod.rs | 20 -- clippy_dev/src/setup/toolchain.rs | 19 +- clippy_dev/src/setup/vscode.rs | 6 - clippy_dev/src/utils.rs | 62 +++--- clippy_lints/src/casts/borrow_as_ptr.rs | 9 +- clippy_lints/src/casts/char_lit_as_u8.rs | 11 +- clippy_lints/src/casts/mod.rs | 2 +- clippy_lints/src/casts/ptr_as_ptr.rs | 4 +- clippy_lints/src/cognitive_complexity.rs | 2 +- clippy_lints/src/collapsible_if.rs | 22 +- clippy_lints/src/declared_lints.rs | 2 +- clippy_lints/src/dereference.rs | 11 +- clippy_lints/src/doc/mod.rs | 4 +- clippy_lints/src/eta_reduction.rs | 6 +- clippy_lints/src/from_str_radix_10.rs | 4 +- .../duplicate_underscore_argument.rs | 34 +++ clippy_lints/src/functions/mod.rs | 39 +++- .../src/functions/renamed_function_params.rs | 21 +- clippy_lints/src/functions/result.rs | 6 +- clippy_lints/src/functions/too_many_lines.rs | 6 +- clippy_lints/src/len_zero.rs | 41 ++-- clippy_lints/src/lib.rs | 3 +- clippy_lints/src/loops/infinite_loop.rs | 48 ++++- clippy_lints/src/manual_let_else.rs | 2 +- clippy_lints/src/matches/match_bool.rs | 6 +- clippy_lints/src/matches/match_ref_pats.rs | 5 + .../src/matches/match_str_case_mismatch.rs | 2 +- .../src/methods/double_ended_iterator_last.rs | 4 +- clippy_lints/src/methods/expect_fun_call.rs | 75 ++++--- clippy_lints/src/methods/filter_map.rs | 4 +- clippy_lints/src/methods/filter_next.rs | 18 +- clippy_lints/src/misc_early/mod.rs | 57 +---- .../src/mixed_read_write_in_expression.rs | 2 +- clippy_lints/src/needless_bool.rs | 4 +- .../src/needless_borrows_for_generic_args.rs | 2 +- clippy_lints/src/no_effect.rs | 3 +- clippy_lints/src/non_copy_const.rs | 2 +- clippy_lints/src/non_expressive_names.rs | 5 + clippy_lints/src/only_used_in_recursion.rs | 2 +- .../src/operators/arithmetic_side_effects.rs | 2 +- .../src/operators/numeric_arithmetic.rs | 2 +- clippy_lints/src/pass_by_ref_or_value.rs | 2 +- clippy_lints/src/ptr.rs | 61 +++--- clippy_lints/src/raw_strings.rs | 10 +- clippy_lints/src/redundant_pub_crate.rs | 3 +- clippy_lints/src/reference.rs | 149 ++++++++------ clippy_lints/src/swap.rs | 4 +- clippy_lints/src/transmute/eager_transmute.rs | 8 +- clippy_lints/src/transmute/mod.rs | 2 +- .../src/undocumented_unsafe_blocks.rs | 194 ++++++++++-------- clippy_lints/src/unnecessary_box_returns.rs | 2 +- clippy_lints/src/unnecessary_semicolon.rs | 4 +- clippy_lints/src/unnested_or_patterns.rs | 8 +- clippy_lints/src/unwrap.rs | 72 +++---- clippy_lints/src/zero_sized_map_values.rs | 3 + .../derive_deserialize_allowing_unknown.rs | 15 +- clippy_test_deps/Cargo.lock | 4 +- clippy_utils/README.md | 2 +- clippy_utils/src/ast_utils/mod.rs | 55 ++--- clippy_utils/src/check_proc_macro.rs | 9 +- clippy_utils/src/diagnostics.rs | 15 +- clippy_utils/src/hir_utils.rs | 2 +- clippy_utils/src/lib.rs | 17 ++ clippy_utils/src/msrvs.rs | 36 ++-- clippy_utils/src/ty/mod.rs | 12 +- rust-toolchain.toml | 2 +- .../fail/Cargo.stderr | 26 +++ .../fail/Cargo.toml | 12 ++ .../fail/src/main.rs | 7 + tests/ui-toml/functions_maxlines/test.stderr | 35 +--- tests/ui/as_ptr_cast_mut.fixed | 38 ++++ tests/ui/as_ptr_cast_mut.rs | 4 - tests/ui/as_ptr_cast_mut.stderr | 10 +- tests/ui/as_ptr_cast_mut_unfixable.rs | 16 ++ tests/ui/as_ptr_cast_mut_unfixable.stderr | 11 + tests/ui/borrow_as_ptr.fixed | 12 ++ tests/ui/borrow_as_ptr.rs | 12 ++ tests/ui/borrow_as_ptr.stderr | 14 +- ...suggestions.fixed => char_lit_as_u8.fixed} | 0 tests/ui/char_lit_as_u8.rs | 9 +- tests/ui/char_lit_as_u8.stderr | 32 ++- tests/ui/char_lit_as_u8_suggestions.rs | 12 -- tests/ui/char_lit_as_u8_suggestions.stderr | 36 ---- tests/ui/char_lit_as_u8_unfixable.rs | 8 + tests/ui/char_lit_as_u8_unfixable.stderr | 12 ++ tests/ui/deref_addrof.fixed | 88 ++++++-- tests/ui/deref_addrof.rs | 88 ++++++-- tests/ui/deref_addrof.stderr | 58 +++++- tests/ui/doc/doc-fixable.fixed | 2 +- tests/ui/doc/doc-fixable.rs | 2 +- tests/ui/double_ended_iterator_last.fixed | 11 + tests/ui/double_ended_iterator_last.rs | 11 + tests/ui/double_ended_iterator_last.stderr | 17 +- .../double_ended_iterator_last_unfixable.rs | 39 ---- ...ouble_ended_iterator_last_unfixable.stderr | 19 -- tests/ui/eta.fixed | 29 +++ tests/ui/eta.rs | 29 +++ tests/ui/eta.stderr | 96 +++++---- tests/ui/from_str_radix_10.fixed | 10 + tests/ui/from_str_radix_10.rs | 10 + tests/ui/from_str_radix_10.stderr | 14 +- tests/ui/functions_maxlines.rs | 115 ++++++++++- tests/ui/functions_maxlines.stderr | 22 +- tests/ui/infinite_loops.rs | 71 +++++++ tests/ui/infinite_loops.stderr | 24 ++- tests/ui/match_bool.fixed | 13 ++ tests/ui/match_bool.rs | 13 ++ tests/ui/match_ref_pats.fixed | 36 +++- tests/ui/match_ref_pats.rs | 36 +++- tests/ui/match_ref_pats.stderr | 10 +- tests/ui/ptr_as_ptr.fixed | 8 +- tests/ui/ptr_as_ptr.rs | 6 +- tests/ui/ptr_as_ptr.stderr | 64 +++--- tests/ui/similar_names.rs | 4 + tests/ui/similar_names.stderr | 4 +- tests/ui/unnecessary_operation.fixed | 10 +- tests/ui/unnecessary_operation.stderr | 10 +- .../unnecessary_semicolon.edition2021.fixed | 9 + .../unnecessary_semicolon.edition2024.fixed | 9 + tests/ui/unnecessary_semicolon.rs | 9 + tests/ui/zero_sized_hashmap_values.rs | 8 + tests/ui/zero_sized_hashmap_values.stderr | 6 +- 142 files changed, 1828 insertions(+), 1055 deletions(-) create mode 100644 clippy_lints/src/functions/duplicate_underscore_argument.rs create mode 100644 tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.stderr create mode 100644 tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.toml create mode 100644 tests/ui-cargo/undocumented_unsafe_blocks/fail/src/main.rs create mode 100644 tests/ui/as_ptr_cast_mut.fixed create mode 100644 tests/ui/as_ptr_cast_mut_unfixable.rs create mode 100644 tests/ui/as_ptr_cast_mut_unfixable.stderr rename tests/ui/{char_lit_as_u8_suggestions.fixed => char_lit_as_u8.fixed} (100%) delete mode 100644 tests/ui/char_lit_as_u8_suggestions.rs delete mode 100644 tests/ui/char_lit_as_u8_suggestions.stderr create mode 100644 tests/ui/char_lit_as_u8_unfixable.rs create mode 100644 tests/ui/char_lit_as_u8_unfixable.stderr delete mode 100644 tests/ui/double_ended_iterator_last_unfixable.rs delete mode 100644 tests/ui/double_ended_iterator_last_unfixable.stderr diff --git a/.github/workflows/clippy_dev.yml b/.github/workflows/clippy_dev.yml index d6534fbaff94..d530eb6c73a3 100644 --- a/.github/workflows/clippy_dev.yml +++ b/.github/workflows/clippy_dev.yml @@ -16,7 +16,7 @@ jobs: steps: # Setup - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # Unsetting this would make so that any malicious package could get our Github Token persist-credentials: false diff --git a/.github/workflows/clippy_mq.yml b/.github/workflows/clippy_mq.yml index 07d5a08304e8..0bcb71359359 100644 --- a/.github/workflows/clippy_mq.yml +++ b/.github/workflows/clippy_mq.yml @@ -36,7 +36,7 @@ jobs: steps: # Setup - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: persist-credentials: false @@ -96,7 +96,7 @@ jobs: steps: # Setup - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: persist-credentials: false @@ -114,7 +114,7 @@ jobs: steps: # Setup - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: persist-credentials: false @@ -170,7 +170,7 @@ jobs: steps: # Setup - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: persist-credentials: false diff --git a/.github/workflows/clippy_pr.yml b/.github/workflows/clippy_pr.yml index 880ebd6e5d5c..d91c638a8fb5 100644 --- a/.github/workflows/clippy_pr.yml +++ b/.github/workflows/clippy_pr.yml @@ -24,7 +24,7 @@ jobs: steps: # Setup - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # Unsetting this would make so that any malicious package could get our Github Token persist-credentials: false diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ede19c11257e..48c5bd36dbcd 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -25,13 +25,13 @@ jobs: steps: # Setup - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # Unsetting this would make so that any malicious package could get our Github Token persist-credentials: false - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: ${{ env.TARGET_BRANCH }} path: 'out' diff --git a/.github/workflows/feature_freeze.yml b/.github/workflows/feature_freeze.yml index ec59be3e7f67..5b139e767007 100644 --- a/.github/workflows/feature_freeze.yml +++ b/.github/workflows/feature_freeze.yml @@ -16,7 +16,7 @@ jobs: permissions: pull-requests: write - # Do not in any case add code that runs anything coming from the the content + # Do not in any case add code that runs anything coming from the content # of the pull request, as malicious code would be able to access the private # GitHub token. steps: diff --git a/.github/workflows/lintcheck.yml b/.github/workflows/lintcheck.yml index 003d03957399..390d6a0f7475 100644 --- a/.github/workflows/lintcheck.yml +++ b/.github/workflows/lintcheck.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 2 # Unsetting this would make so that any malicious package could get our Github Token @@ -80,7 +80,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # Unsetting this would make so that any malicious package could get our Github Token persist-credentials: false @@ -113,7 +113,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # Unsetting this would make so that any malicious package could get our Github Token persist-credentials: false diff --git a/.github/workflows/remark.yml b/.github/workflows/remark.yml index 7e7e26818c09..c9d350ee0b30 100644 --- a/.github/workflows/remark.yml +++ b/.github/workflows/remark.yml @@ -11,7 +11,7 @@ jobs: steps: # Setup - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # Unsetting this would make so that any malicious package could get our Github Token persist-credentials: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 42ed624ec212..f7f0a1ce2499 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,7 +17,7 @@ All contributors are expected to follow the [Rust Code of Conduct]. - [High level approach](#high-level-approach) - [Finding something to fix/improve](#finding-something-to-fiximprove) - [Getting code-completion for rustc internals to work](#getting-code-completion-for-rustc-internals-to-work) - - [IntelliJ Rust](#intellij-rust) + - [RustRover](#rustrover) - [Rust Analyzer](#rust-analyzer) - [How Clippy works](#how-clippy-works) - [Issue and PR triage](#issue-and-pr-triage) @@ -92,22 +92,22 @@ an AST expression). ## Getting code-completion for rustc internals to work -### IntelliJ Rust -Unfortunately, [`IntelliJ Rust`][IntelliJ_rust_homepage] does not (yet?) understand how Clippy uses compiler-internals +### RustRover +Unfortunately, [`RustRover`][RustRover_homepage] does not (yet?) understand how Clippy uses compiler-internals using `extern crate` and it also needs to be able to read the source files of the rustc-compiler which are not available via a `rustup` component at the time of writing. To work around this, you need to have a copy of the [rustc-repo][rustc_repo] available which can be obtained via `git clone https://github.com/rust-lang/rust/`. Then you can run a `cargo dev` command to automatically make Clippy use the rustc-repo via path-dependencies -which `IntelliJ Rust` will be able to understand. +which `RustRover` will be able to understand. Run `cargo dev setup intellij --repo-path ` where `` is a path to the rustc repo you just cloned. The command will add path-dependencies pointing towards rustc-crates inside the rustc repo to -Clippy's `Cargo.toml`s and should allow `IntelliJ Rust` to understand most of the types that Clippy uses. +Clippy's `Cargo.toml`s and should allow `RustRover` to understand most of the types that Clippy uses. Just make sure to remove the dependencies again before finally making a pull request! [rustc_repo]: https://github.com/rust-lang/rust/ -[IntelliJ_rust_homepage]: https://intellij-rust.github.io/ +[RustRover_homepage]: https://www.jetbrains.com/rust/ ### Rust Analyzer For [`rust-analyzer`][ra_homepage] to work correctly make sure that in the `rust-analyzer` configuration you set diff --git a/Cargo.toml b/Cargo.toml index daf1c98cdc94..2add525b7e8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,3 +64,14 @@ harness = false [[test]] name = "dogfood" harness = false + +# quine-mc_cluskey makes up a significant part of the runtime in dogfood +# due to the number of conditions in the clippy_lints crate +# and enabling optimizations for that specific dependency helps a bit +# without increasing total build times. +[profile.dev.package.quine-mc_cluskey] +opt-level = 3 + +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = ['cfg(bootstrap)'] diff --git a/book/src/continuous_integration/github_actions.md b/book/src/continuous_integration/github_actions.md index b588c8f0f02c..62d32446d920 100644 --- a/book/src/continuous_integration/github_actions.md +++ b/book/src/continuous_integration/github_actions.md @@ -15,7 +15,7 @@ jobs: clippy_check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Run Clippy run: cargo clippy --all-targets --all-features ``` diff --git a/book/src/development/basics.md b/book/src/development/basics.md index fc405249bcfe..19f626ab804e 100644 --- a/book/src/development/basics.md +++ b/book/src/development/basics.md @@ -95,7 +95,7 @@ cargo dev new_lint cargo dev deprecate # automatically formatting all code before each commit cargo dev setup git-hook -# (experimental) Setup Clippy to work with IntelliJ-Rust +# (experimental) Setup Clippy to work with RustRover cargo dev setup intellij # runs the `dogfood` tests cargo dev dogfood @@ -103,7 +103,7 @@ cargo dev dogfood More about [intellij] command usage and reasons. -[intellij]: https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md#intellij-rust +[intellij]: https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md#rustrover ## lintcheck diff --git a/book/src/development/common_tools_writing_lints.md b/book/src/development/common_tools_writing_lints.md index e23b32039c90..3bec3ce33af3 100644 --- a/book/src/development/common_tools_writing_lints.md +++ b/book/src/development/common_tools_writing_lints.md @@ -141,7 +141,7 @@ impl LateLintPass<'_> for MyStructLint { // we are looking for the `DefId` of `Drop` trait in lang items .drop_trait() // then we use it with our type `ty` by calling `implements_trait` from Clippy's utils - .map_or(false, |id| implements_trait(cx, ty, id, &[])) { + .is_some_and(|id| implements_trait(cx, ty, id, &[])) { // `expr` implements `Drop` trait } } diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 7f16f3a98105..05590ff7b1c9 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -555,7 +555,7 @@ default configuration of Clippy. By default, any configuration will replace the * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`. * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. -**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` +**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "PowerPC", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` --- **Affected lints:** diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 8167d75583ee..2ad3f2efcdd7 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -34,7 +34,7 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[ "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", - "WebAssembly", + "PowerPC", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", diff --git a/clippy_dev/src/dogfood.rs b/clippy_dev/src/dogfood.rs index 7e9d92458d05..d0fca952b932 100644 --- a/clippy_dev/src/dogfood.rs +++ b/clippy_dev/src/dogfood.rs @@ -1,35 +1,28 @@ -use crate::utils::exit_if_err; -use std::process::Command; +use crate::utils::{cargo_cmd, run_exit_on_err}; +use itertools::Itertools; /// # Panics /// /// Panics if unable to run the dogfood test #[allow(clippy::fn_params_excessive_bools)] pub fn dogfood(fix: bool, allow_dirty: bool, allow_staged: bool, allow_no_vcs: bool) { - let mut cmd = Command::new("cargo"); - - cmd.args(["test", "--test", "dogfood"]) - .args(["--features", "internal"]) - .args(["--", "dogfood_clippy", "--nocapture"]); - - let mut dogfood_args = Vec::new(); - if fix { - dogfood_args.push("--fix"); - } - - if allow_dirty { - dogfood_args.push("--allow-dirty"); - } - - if allow_staged { - dogfood_args.push("--allow-staged"); - } - - if allow_no_vcs { - dogfood_args.push("--allow-no-vcs"); - } - - cmd.env("__CLIPPY_DOGFOOD_ARGS", dogfood_args.join(" ")); - - exit_if_err(cmd.status()); + run_exit_on_err( + "cargo test", + cargo_cmd() + .args(["test", "--test", "dogfood"]) + .args(["--features", "internal"]) + .args(["--", "dogfood_clippy", "--nocapture"]) + .env( + "__CLIPPY_DOGFOOD_ARGS", + [ + if fix { "--fix" } else { "" }, + if allow_dirty { "--allow-dirty" } else { "" }, + if allow_staged { "--allow-staged" } else { "" }, + if allow_no_vcs { "--allow-no-vcs" } else { "" }, + ] + .iter() + .filter(|x| !x.is_empty()) + .join(" "), + ), + ); } diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 40aadf4589a7..16f413e0c862 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -15,8 +15,7 @@ )] #![allow(clippy::missing_panics_doc)] -// The `rustc_driver` crate seems to be required in order to use the `rust_lexer` crate. -#[allow(unused_extern_crates)] +#[expect(unused_extern_crates, reason = "required to link to rustc crates")] extern crate rustc_driver; extern crate rustc_lexer; extern crate rustc_literal_escaper; @@ -32,4 +31,6 @@ pub mod serve; pub mod setup; pub mod sync; pub mod update_lints; -pub mod utils; + +mod utils; +pub use utils::{ClippyInfo, UpdateMode}; diff --git a/clippy_dev/src/lint.rs b/clippy_dev/src/lint.rs index 0d66f167a386..2d9f563cdae2 100644 --- a/clippy_dev/src/lint.rs +++ b/clippy_dev/src/lint.rs @@ -1,19 +1,18 @@ -use crate::utils::{cargo_clippy_path, exit_if_err}; -use std::process::{self, Command}; +use crate::utils::{ErrAction, cargo_cmd, expect_action, run_exit_on_err}; +use std::process::Command; use std::{env, fs}; -pub fn run<'a>(path: &str, edition: &str, args: impl Iterator) { - let is_file = match fs::metadata(path) { - Ok(metadata) => metadata.is_file(), - Err(e) => { - eprintln!("Failed to read {path}: {e:?}"); - process::exit(1); - }, - }; +#[cfg(not(windows))] +static CARGO_CLIPPY_EXE: &str = "cargo-clippy"; +#[cfg(windows)] +static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe"; +pub fn run<'a>(path: &str, edition: &str, args: impl Iterator) { + let is_file = expect_action(fs::metadata(path), ErrAction::Read, path).is_file(); if is_file { - exit_if_err( - Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into())) + run_exit_on_err( + "cargo run", + cargo_cmd() .args(["run", "--bin", "clippy-driver", "--"]) .args(["-L", "./target/debug"]) .args(["-Z", "no-codegen"]) @@ -21,24 +20,25 @@ pub fn run<'a>(path: &str, edition: &str, args: impl Iterator .arg(path) .args(args) // Prevent rustc from creating `rustc-ice-*` files the console output is enough. - .env("RUSTC_ICE", "0") - .status(), + .env("RUSTC_ICE", "0"), ); } else { - exit_if_err( - Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into())) - .arg("build") - .status(), + // Ideally this would just be `cargo run`, but the working directory needs to be + // set to clippy's directory when building, and the target project's directory + // when running clippy. `cargo` can only set a single working directory for both + // when using `run`. + run_exit_on_err("cargo build", cargo_cmd().arg("build")); + + let mut exe = env::current_exe().expect("failed to get current executable name"); + exe.set_file_name(CARGO_CLIPPY_EXE); + run_exit_on_err( + "cargo clippy", + Command::new(exe) + .arg("clippy") + .args(args) + // Prevent rustc from creating `rustc-ice-*` files the console output is enough. + .env("RUSTC_ICE", "0") + .current_dir(path), ); - - let status = Command::new(cargo_clippy_path()) - .arg("clippy") - .args(args) - // Prevent rustc from creating `rustc-ice-*` files the console output is enough. - .env("RUSTC_ICE", "0") - .current_dir(path) - .status(); - - exit_if_err(status); } } diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 26aa269fb638..5fef231f6ca1 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -4,14 +4,15 @@ use clap::{Args, Parser, Subcommand}; use clippy_dev::{ - deprecate_lint, dogfood, fmt, lint, new_lint, release, rename_lint, serve, setup, sync, update_lints, utils, + ClippyInfo, UpdateMode, deprecate_lint, dogfood, fmt, lint, new_lint, release, rename_lint, serve, setup, sync, + update_lints, }; use std::convert::Infallible; use std::env; fn main() { let dev = Dev::parse(); - let clippy = utils::ClippyInfo::search_for_manifest(); + let clippy = ClippyInfo::search_for_manifest(); if let Err(e) = env::set_current_dir(&clippy.path) { panic!("error setting current directory to `{}`: {e}", clippy.path.display()); } @@ -26,8 +27,8 @@ fn main() { allow_staged, allow_no_vcs, } => dogfood::dogfood(fix, allow_dirty, allow_staged, allow_no_vcs), - DevCommand::Fmt { check } => fmt::run(utils::UpdateMode::from_check(check)), - DevCommand::UpdateLints { check } => update_lints::update(utils::UpdateMode::from_check(check)), + DevCommand::Fmt { check } => fmt::run(UpdateMode::from_check(check)), + DevCommand::UpdateLints { check } => update_lints::update(UpdateMode::from_check(check)), DevCommand::NewLint { pass, name, @@ -35,7 +36,7 @@ fn main() { r#type, msrv, } => match new_lint::create(clippy.version, pass, &name, &category, r#type.as_deref(), msrv) { - Ok(()) => update_lints::update(utils::UpdateMode::Change), + Ok(()) => update_lints::update(UpdateMode::Change), Err(e) => eprintln!("Unable to create lint: {e}"), }, DevCommand::Setup(SetupCommand { subcommand }) => match subcommand { diff --git a/clippy_dev/src/serve.rs b/clippy_dev/src/serve.rs index 498ffeba9d67..d9e018133813 100644 --- a/clippy_dev/src/serve.rs +++ b/clippy_dev/src/serve.rs @@ -1,7 +1,11 @@ +use crate::utils::{ErrAction, cargo_cmd, expect_action}; +use core::fmt::Display; +use core::mem; use std::path::Path; use std::process::Command; use std::time::{Duration, SystemTime}; -use std::{env, thread}; +use std::{fs, thread}; +use walkdir::WalkDir; #[cfg(windows)] const PYTHON: &str = "python"; @@ -18,56 +22,83 @@ pub fn run(port: u16, lint: Option) -> ! { Some(lint) => format!("http://localhost:{port}/#{lint}"), }); + let mut last_update = mtime("util/gh-pages/index.html"); loop { - let index_time = mtime("util/gh-pages/index.html"); - let times = [ - "clippy_lints/src", - "util/gh-pages/index_template.html", - "tests/compile-test.rs", - ] - .map(mtime); - - if times.iter().any(|&time| index_time < time) { - Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into())) - .arg("collect-metadata") - .spawn() - .unwrap() - .wait() - .unwrap(); + if is_metadata_outdated(mem::replace(&mut last_update, SystemTime::now())) { + // Ignore the command result; we'll fall back to displaying the old metadata. + let _ = expect_action( + cargo_cmd().arg("collect-metadata").status(), + ErrAction::Run, + "cargo collect-metadata", + ); + last_update = SystemTime::now(); } + + // Only start the web server the first time around. if let Some(url) = url.take() { thread::spawn(move || { - let mut child = Command::new(PYTHON) - .arg("-m") - .arg("http.server") - .arg(port.to_string()) - .current_dir("util/gh-pages") - .spawn() - .unwrap(); + let mut child = expect_action( + Command::new(PYTHON) + .args(["-m", "http.server", port.to_string().as_str()]) + .current_dir("util/gh-pages") + .spawn(), + ErrAction::Run, + "python -m http.server", + ); // Give some time for python to start thread::sleep(Duration::from_millis(500)); // Launch browser after first export.py has completed and http.server is up let _result = opener::open(url); - child.wait().unwrap(); + expect_action(child.wait(), ErrAction::Run, "python -m http.server"); }); } + + // Delay to avoid updating the metadata too aggressively. thread::sleep(Duration::from_millis(1000)); } } -fn mtime(path: impl AsRef) -> SystemTime { - let path = path.as_ref(); - if path.is_dir() { - path.read_dir() - .into_iter() - .flatten() - .flatten() - .map(|entry| mtime(entry.path())) - .max() - .unwrap_or(SystemTime::UNIX_EPOCH) - } else { - path.metadata() - .and_then(|metadata| metadata.modified()) - .unwrap_or(SystemTime::UNIX_EPOCH) +fn log_err_and_continue(res: Result, path: &Path) -> Option { + match res { + Ok(x) => Some(x), + Err(ref e) => { + eprintln!("error reading `{}`: {e}", path.display()); + None + }, } } + +fn mtime(path: &str) -> SystemTime { + log_err_and_continue(fs::metadata(path), path.as_ref()) + .and_then(|metadata| log_err_and_continue(metadata.modified(), path.as_ref())) + .unwrap_or(SystemTime::UNIX_EPOCH) +} + +fn is_metadata_outdated(time: SystemTime) -> bool { + // Ignore all IO errors here. We don't want to stop them from hosting the server. + if time < mtime("util/gh-pages/index_template.html") || time < mtime("tests/compile-test.rs") { + return true; + } + let Some(dir) = log_err_and_continue(fs::read_dir("."), ".".as_ref()) else { + return false; + }; + dir.map_while(|e| log_err_and_continue(e, ".".as_ref())).any(|e| { + let name = e.file_name(); + let name_bytes = name.as_encoded_bytes(); + if (name_bytes.starts_with(b"clippy_lints") && name_bytes != b"clippy_lints_internal") + || name_bytes == b"clippy_config" + { + WalkDir::new(&name) + .into_iter() + .map_while(|e| log_err_and_continue(e, name.as_ref())) + .filter(|e| e.file_type().is_file()) + .filter_map(|e| { + log_err_and_continue(e.metadata(), e.path()) + .and_then(|m| log_err_and_continue(m.modified(), e.path())) + }) + .any(|ftime| time < ftime) + } else { + false + } + }) +} diff --git a/clippy_dev/src/setup/git_hook.rs b/clippy_dev/src/setup/git_hook.rs index c7c53bc69d0b..c5a1e8264c7f 100644 --- a/clippy_dev/src/setup/git_hook.rs +++ b/clippy_dev/src/setup/git_hook.rs @@ -1,8 +1,6 @@ use std::fs; use std::path::Path; -use super::verify_inside_clippy_dir; - /// Rusts setup uses `git rev-parse --git-common-dir` to get the root directory of the repo. /// I've decided against this for the sake of simplicity and to make sure that it doesn't install /// the hook if `clippy_dev` would be used in the rust tree. The hook also references this tool @@ -35,10 +33,6 @@ pub fn install_hook(force_override: bool) { } fn check_precondition(force_override: bool) -> bool { - if !verify_inside_clippy_dir() { - return false; - } - // Make sure that we can find the git repository let git_path = Path::new(REPO_GIT_DIR); if !git_path.exists() || !git_path.is_dir() { diff --git a/clippy_dev/src/setup/mod.rs b/clippy_dev/src/setup/mod.rs index b0d318146391..5e938fff126d 100644 --- a/clippy_dev/src/setup/mod.rs +++ b/clippy_dev/src/setup/mod.rs @@ -2,23 +2,3 @@ pub mod git_hook; pub mod intellij; pub mod toolchain; pub mod vscode; - -use std::path::Path; - -const CLIPPY_DEV_DIR: &str = "clippy_dev"; - -/// This function verifies that the tool is being executed in the clippy directory. -/// This is useful to ensure that setups only modify Clippy's resources. The verification -/// is done by checking that `clippy_dev` is a sub directory of the current directory. -/// -/// It will print an error message and return `false` if the directory could not be -/// verified. -fn verify_inside_clippy_dir() -> bool { - let path = Path::new(CLIPPY_DEV_DIR); - if path.exists() && path.is_dir() { - true - } else { - eprintln!("error: unable to verify that the working directory is clippy's directory"); - false - } -} diff --git a/clippy_dev/src/setup/toolchain.rs b/clippy_dev/src/setup/toolchain.rs index ecd80215f7e8..c64ae4ef3c36 100644 --- a/clippy_dev/src/setup/toolchain.rs +++ b/clippy_dev/src/setup/toolchain.rs @@ -1,20 +1,12 @@ +use crate::utils::{cargo_cmd, run_exit_on_err}; use std::env::consts::EXE_SUFFIX; use std::env::current_dir; use std::ffi::OsStr; use std::fs; use std::path::{Path, PathBuf}; -use std::process::Command; use walkdir::WalkDir; -use crate::utils::exit_if_err; - -use super::verify_inside_clippy_dir; - pub fn create(standalone: bool, force: bool, release: bool, name: &str) { - if !verify_inside_clippy_dir() { - return; - } - let rustup_home = std::env::var("RUSTUP_HOME").unwrap(); let toolchain = std::env::var("RUSTUP_TOOLCHAIN").unwrap(); @@ -51,11 +43,10 @@ pub fn create(standalone: bool, force: bool, release: bool, name: &str) { } } - let status = Command::new("cargo") - .arg("build") - .args(release.then_some("--release")) - .status(); - exit_if_err(status); + run_exit_on_err( + "cargo build", + cargo_cmd().arg("build").args(release.then_some("--release")), + ); install_bin("cargo-clippy", &dest, standalone, release); install_bin("clippy-driver", &dest, standalone, release); diff --git a/clippy_dev/src/setup/vscode.rs b/clippy_dev/src/setup/vscode.rs index a37c873eed4f..a24aef65991f 100644 --- a/clippy_dev/src/setup/vscode.rs +++ b/clippy_dev/src/setup/vscode.rs @@ -1,8 +1,6 @@ use std::fs; use std::path::Path; -use super::verify_inside_clippy_dir; - const VSCODE_DIR: &str = ".vscode"; const TASK_SOURCE_FILE: &str = "util/etc/vscode-tasks.json"; const TASK_TARGET_FILE: &str = ".vscode/tasks.json"; @@ -22,10 +20,6 @@ pub fn install_tasks(force_override: bool) { } fn check_install_precondition(force_override: bool) -> bool { - if !verify_inside_clippy_dir() { - return false; - } - let vs_dir_path = Path::new(VSCODE_DIR); if vs_dir_path.exists() { // verify the target will be valid diff --git a/clippy_dev/src/utils.rs b/clippy_dev/src/utils.rs index 89962a110341..057951d0e33b 100644 --- a/clippy_dev/src/utils.rs +++ b/clippy_dev/src/utils.rs @@ -8,15 +8,10 @@ use std::ffi::OsStr; use std::fs::{self, OpenOptions}; use std::io::{self, Read as _, Seek as _, SeekFrom, Write}; use std::path::{Path, PathBuf}; -use std::process::{self, Command, ExitStatus, Stdio}; +use std::process::{self, Command, Stdio}; use std::{env, thread}; use walkdir::WalkDir; -#[cfg(not(windows))] -static CARGO_CLIPPY_EXE: &str = "cargo-clippy"; -#[cfg(windows)] -static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe"; - #[derive(Clone, Copy)] pub enum ErrAction { Open, @@ -118,16 +113,14 @@ impl<'a> File<'a> { } } -/// Returns the path to the `cargo-clippy` binary -/// -/// # Panics -/// -/// Panics if the path of current executable could not be retrieved. +/// Creates a `Command` for running cargo. #[must_use] -pub fn cargo_clippy_path() -> PathBuf { - let mut path = env::current_exe().expect("failed to get current executable name"); - path.set_file_name(CARGO_CLIPPY_EXE); - path +pub fn cargo_cmd() -> Command { + if let Some(path) = env::var_os("CARGO") { + Command::new(path) + } else { + Command::new("cargo") + } } #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -288,19 +281,6 @@ impl ClippyInfo { } } -/// # Panics -/// Panics if given command result was failed. -pub fn exit_if_err(status: io::Result) { - match status.expect("failed to run command").code() { - Some(0) => {}, - Some(n) => process::exit(n), - None => { - eprintln!("Killed by signal"); - process::exit(1); - }, - } -} - #[derive(Clone, Copy)] pub enum UpdateStatus { Unchanged, @@ -341,6 +321,7 @@ pub struct FileUpdater { dst_buf: String, } impl FileUpdater { + #[track_caller] fn update_file_checked_inner( &mut self, tool: &str, @@ -364,6 +345,7 @@ impl FileUpdater { } } + #[track_caller] fn update_file_inner(&mut self, path: &Path, update: &mut dyn FnMut(&Path, &str, &mut String) -> UpdateStatus) { let mut file = File::open(path, OpenOptions::new().read(true).write(true)); file.read_to_cleared_string(&mut self.src_buf); @@ -373,6 +355,7 @@ impl FileUpdater { } } + #[track_caller] pub fn update_file_checked( &mut self, tool: &str, @@ -383,6 +366,7 @@ impl FileUpdater { self.update_file_checked_inner(tool, mode, path.as_ref(), update); } + #[track_caller] pub fn update_file( &mut self, path: impl AsRef, @@ -450,7 +434,6 @@ pub enum Token<'a> { OpenParen, Pound, Semi, - Slash, } pub struct RustSearcher<'txt> { @@ -528,7 +511,6 @@ impl<'txt> RustSearcher<'txt> { | (Token::OpenParen, lexer::TokenKind::OpenParen) | (Token::Pound, lexer::TokenKind::Pound) | (Token::Semi, lexer::TokenKind::Semi) - | (Token::Slash, lexer::TokenKind::Slash) | ( Token::LitStr, lexer::TokenKind::Literal { @@ -601,7 +583,7 @@ impl<'txt> RustSearcher<'txt> { } } -#[expect(clippy::must_use_candidate)] +#[track_caller] pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool { match OpenOptions::new().create_new(true).write(true).open(new_name) { Ok(file) => drop(file), @@ -623,7 +605,7 @@ pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool { } } -#[expect(clippy::must_use_candidate)] +#[track_caller] pub fn try_rename_dir(old_name: &Path, new_name: &Path) -> bool { match fs::create_dir(new_name) { Ok(()) => {}, @@ -649,10 +631,19 @@ pub fn try_rename_dir(old_name: &Path, new_name: &Path) -> bool { } } -pub fn write_file(path: &Path, contents: &str) { - expect_action(fs::write(path, contents), ErrAction::Write, path); +#[track_caller] +pub fn run_exit_on_err(path: &(impl AsRef + ?Sized), cmd: &mut Command) { + match expect_action(cmd.status(), ErrAction::Run, path.as_ref()).code() { + Some(0) => {}, + Some(n) => process::exit(n), + None => { + eprintln!("{} killed by signal", path.as_ref().display()); + process::exit(1); + }, + } } +#[track_caller] #[must_use] pub fn run_with_output(path: &(impl AsRef + ?Sized), cmd: &mut Command) -> Vec { fn f(path: &Path, cmd: &mut Command) -> Vec { @@ -738,7 +729,7 @@ pub fn split_args_for_threads( } } -#[expect(clippy::must_use_candidate)] +#[track_caller] pub fn delete_file_if_exists(path: &Path) -> bool { match fs::remove_file(path) { Ok(()) => true, @@ -747,6 +738,7 @@ pub fn delete_file_if_exists(path: &Path) -> bool { } } +#[track_caller] pub fn delete_dir_if_exists(path: &Path) { match fs::remove_dir_all(path) { Ok(()) => {}, diff --git a/clippy_lints/src/casts/borrow_as_ptr.rs b/clippy_lints/src/casts/borrow_as_ptr.rs index e3b125a8d5b9..eb75d5576f5c 100644 --- a/clippy_lints/src/casts/borrow_as_ptr.rs +++ b/clippy_lints/src/casts/borrow_as_ptr.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::Msrv; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; -use clippy_utils::{get_parent_expr, is_expr_temporary_value, is_lint_allowed, msrvs, std_or_core}; +use clippy_utils::{get_parent_expr, is_expr_temporary_value, is_from_proc_macro, is_lint_allowed, msrvs, std_or_core}; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Ty, TyKind}; use rustc_lint::LateContext; @@ -22,13 +22,12 @@ pub(super) fn check<'tcx>( && !matches!(target.ty.kind, TyKind::TraitObject(..)) && let ExprKind::AddrOf(BorrowKind::Ref, mutability, e) = cast_expr.kind && !is_lint_allowed(cx, BORROW_AS_PTR, expr.hir_id) + // Fix #9884 + && !is_expr_temporary_value(cx, e) + && !is_from_proc_macro(cx, expr) { let mut app = Applicability::MachineApplicable; let snip = snippet_with_context(cx, e.span, cast_expr.span.ctxt(), "..", &mut app).0; - // Fix #9884 - if is_expr_temporary_value(cx, e) { - return false; - } let (suggestion, span) = if msrv.meets(cx, msrvs::RAW_REF_OP) { // Make sure that the span to be replaced doesn't include parentheses, that could break the diff --git a/clippy_lints/src/casts/char_lit_as_u8.rs b/clippy_lints/src/casts/char_lit_as_u8.rs index a7d3868f76c6..964eaf2a0a2d 100644 --- a/clippy_lints/src/casts/char_lit_as_u8.rs +++ b/clippy_lints/src/casts/char_lit_as_u8.rs @@ -4,18 +4,17 @@ use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, UintTy}; +use rustc_middle::ty::{self, Ty, UintTy}; use super::CHAR_LIT_AS_U8; -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::Cast(e, _) = &expr.kind - && let ExprKind::Lit(l) = &e.kind +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from_expr: &Expr<'_>, cast_to: Ty<'_>) { + if let ExprKind::Lit(l) = &cast_from_expr.kind && let LitKind::Char(c) = l.node - && ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(expr).kind() + && ty::Uint(UintTy::U8) == *cast_to.kind() { let mut applicability = Applicability::MachineApplicable; - let snippet = snippet_with_applicability(cx, e.span, "'x'", &mut applicability); + let snippet = snippet_with_applicability(cx, cast_from_expr.span, "'x'", &mut applicability); span_lint_and_then( cx, diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index dcc439a272cf..e25df9dd249a 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -871,6 +871,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts { if !expr.span.from_expansion() && unnecessary_cast::check(cx, expr, cast_from_expr, cast_from, cast_to) { return; } + char_lit_as_u8::check(cx, expr, cast_from_expr, cast_to); cast_slice_from_raw_parts::check(cx, expr, cast_from_expr, cast_to, self.msrv); ptr_cast_constness::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv); as_ptr_cast_mut::check(cx, expr, cast_from_expr, cast_to); @@ -911,7 +912,6 @@ impl<'tcx> LateLintPass<'tcx> for Casts { borrow_as_ptr::check_implicit_cast(cx, expr); } cast_ptr_alignment::check(cx, expr); - char_lit_as_u8::check(cx, expr); ptr_as_ptr::check(cx, expr, self.msrv); cast_slice_different_sizes::check(cx, expr, self.msrv); ptr_cast_constness::check_null_ptr_cast_method(cx, expr); diff --git a/clippy_lints/src/casts/ptr_as_ptr.rs b/clippy_lints/src/casts/ptr_as_ptr.rs index ee0f3fa81c6d..890754090989 100644 --- a/clippy_lints/src/casts/ptr_as_ptr.rs +++ b/clippy_lints/src/casts/ptr_as_ptr.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_from_proc_macro; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; @@ -25,7 +26,7 @@ impl OmitFollowedCastReason<'_> { } } -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) { +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) { if let ExprKind::Cast(cast_expr, cast_to_hir_ty) = expr.kind && let (cast_from, cast_to) = (cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(expr)) && let ty::RawPtr(_, from_mutbl) = cast_from.kind() @@ -36,6 +37,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) { // as explained here: https://github.com/rust-lang/rust/issues/60602. && to_pointee_ty.is_sized(cx.tcx, cx.typing_env()) && msrv.meets(cx, msrvs::POINTER_CAST) + && !is_from_proc_macro(cx, expr) { let mut app = Applicability::MachineApplicable; let turbofish = match &cast_to_hir_ty.kind { diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs index 8f1c02965244..8f95c63a854f 100644 --- a/clippy_lints/src/cognitive_complexity.rs +++ b/clippy_lints/src/cognitive_complexity.rs @@ -56,7 +56,7 @@ impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]); impl CognitiveComplexity { fn check<'tcx>( - &mut self, + &self, cx: &LateContext<'tcx>, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index e3103e2d3016..ad610fbd8d2c 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -5,7 +5,7 @@ use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block_w use clippy_utils::{span_contains_non_whitespace, tokenize_with_text}; use rustc_ast::BinOpKind; use rustc_errors::Applicability; -use rustc_hir::{Block, Expr, ExprKind, Stmt, StmtKind}; +use rustc_hir::{Block, Expr, ExprKind, StmtKind}; use rustc_lexer::TokenKind; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -141,11 +141,7 @@ impl CollapsibleIf { // Prevent "elseif" // Check that the "else" is followed by whitespace - let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() { - !c.is_whitespace() - } else { - false - }; + let requires_space = snippet(cx, up_to_else, "..").ends_with(|c: char| !c.is_whitespace()); let mut applicability = Applicability::MachineApplicable; diag.span_suggestion( else_block.span, @@ -173,8 +169,7 @@ impl CollapsibleIf { && cx.tcx.hir_attrs(inner.hir_id).is_empty() && let ExprKind::If(check_inner, _, None) = &inner.kind && self.eligible_condition(cx, check_inner) - && let ctxt = expr.span.ctxt() - && inner.span.ctxt() == ctxt + && expr.span.eq_ctxt(inner.span) && !block_starts_with_significant_tokens(cx, then, inner, self.lint_commented_code) { span_lint_and_then( @@ -262,14 +257,9 @@ fn block_starts_with_significant_tokens( /// If `block` is a block with either one expression or a statement containing an expression, /// return the expression. We don't peel blocks recursively, as extra blocks might be intentional. fn expr_block<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { - match block.stmts { - [] => block.expr, - [ - Stmt { - kind: StmtKind::Semi(expr), - .. - }, - ] if block.expr.is_none() => Some(expr), + match (block.stmts, block.expr) { + ([], expr) => expr, + ([stmt], None) if let StmtKind::Semi(expr) = stmt.kind => Some(expr), _ => None, } } diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index e1cb08e361ca..e67e8d9070f2 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -187,6 +187,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::from_raw_with_void_ptr::FROM_RAW_WITH_VOID_PTR_INFO, crate::from_str_radix_10::FROM_STR_RADIX_10_INFO, crate::functions::DOUBLE_MUST_USE_INFO, + crate::functions::DUPLICATE_UNDERSCORE_ARGUMENT_INFO, crate::functions::IMPL_TRAIT_IN_PARAMS_INFO, crate::functions::MISNAMED_GETTERS_INFO, crate::functions::MUST_USE_CANDIDATE_INFO, @@ -505,7 +506,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::misc::USED_UNDERSCORE_BINDING_INFO, crate::misc::USED_UNDERSCORE_ITEMS_INFO, crate::misc_early::BUILTIN_TYPE_SHADOW_INFO, - crate::misc_early::DUPLICATE_UNDERSCORE_ARGUMENT_INFO, crate::misc_early::MIXED_CASE_HEX_LITERALS_INFO, crate::misc_early::REDUNDANT_AT_REST_PATTERN_INFO, crate::misc_early::REDUNDANT_PATTERN_INFO, diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 995a1209595e..9aa2f3cf0a5b 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -1,12 +1,11 @@ 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}; +use clippy_utils::ty::{adjust_derefs_manually_drop, implements_trait, is_manually_drop}; use clippy_utils::{ DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, peel_middle_ty_refs, }; -use core::mem; use rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::Applicability; @@ -707,14 +706,6 @@ fn try_parse_ref_op<'tcx>( )) } -// Checks if the adjustments contains a deref of `ManuallyDrop<_>` -fn adjust_derefs_manually_drop<'tcx>(adjustments: &'tcx [Adjustment<'tcx>], mut ty: Ty<'tcx>) -> bool { - adjustments.iter().any(|a| { - let ty = mem::replace(&mut ty, a.target); - matches!(a.kind, Adjust::Deref(Some(ref op)) if op.mutbl == Mutability::Mut) && is_manually_drop(ty) - }) -} - // Checks whether the type for a deref call actually changed the type, not just the mutability of // the reference. fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool { diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index d27d68d38664..eca3bc390d77 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -1139,12 +1139,12 @@ fn check_doc<'a, Events: Iterator, Range in_footnote_definition = true, End(TagEnd::FootnoteDefinition) => in_footnote_definition = false, diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index e467246741ce..0eefc2f61096 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -231,9 +231,13 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx _ => (), } } + let replace_with = match callee_ty_adjusted.kind() { + ty::FnDef(def, _) => cx.tcx.def_descr(*def), + _ => "function", + }; diag.span_suggestion( expr.span, - "replace the closure with the function itself", + format!("replace the closure with the {replace_with} itself"), snippet, Applicability::MachineApplicable, ); diff --git a/clippy_lints/src/from_str_radix_10.rs b/clippy_lints/src/from_str_radix_10.rs index b816963cc825..d5873b3f85aa 100644 --- a/clippy_lints/src/from_str_radix_10.rs +++ b/clippy_lints/src/from_str_radix_10.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; +use clippy_utils::ty::is_type_lang_item; use clippy_utils::{is_in_const_context, is_integer_literal, sym}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem, PrimTy, QPath, TyKind, def}; @@ -89,5 +89,5 @@ impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 { /// Checks if a Ty is `String` or `&str` fn is_ty_stringish(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { - is_type_lang_item(cx, ty, LangItem::String) || is_type_diagnostic_item(cx, ty, sym::str) + is_type_lang_item(cx, ty, LangItem::String) || ty.peel_refs().is_str() } diff --git a/clippy_lints/src/functions/duplicate_underscore_argument.rs b/clippy_lints/src/functions/duplicate_underscore_argument.rs new file mode 100644 index 000000000000..b15d1b1bb79a --- /dev/null +++ b/clippy_lints/src/functions/duplicate_underscore_argument.rs @@ -0,0 +1,34 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_ast::PatKind; +use rustc_ast::visit::FnKind; +use rustc_data_structures::fx::FxHashMap; +use rustc_lint::EarlyContext; +use rustc_span::Span; + +use super::DUPLICATE_UNDERSCORE_ARGUMENT; + +pub(super) fn check(cx: &EarlyContext<'_>, fn_kind: FnKind<'_>) { + let mut registered_names: FxHashMap = FxHashMap::default(); + + for arg in &fn_kind.decl().inputs { + if let PatKind::Ident(_, ident, None) = arg.pat.kind { + let arg_name = ident.to_string(); + + if let Some(arg_name) = arg_name.strip_prefix('_') { + if let Some(correspondence) = registered_names.get(arg_name) { + span_lint( + cx, + DUPLICATE_UNDERSCORE_ARGUMENT, + *correspondence, + format!( + "`{arg_name}` already exists, having another argument having almost the same \ + name makes code comprehension and documentation more difficult" + ), + ); + } + } else { + registered_names.insert(arg_name, arg.pat.span); + } + } + } +} diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index 6051dc9479ba..ca5ea9018149 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -1,3 +1,4 @@ +mod duplicate_underscore_argument; mod impl_trait_in_params; mod misnamed_getters; mod must_use; @@ -11,14 +12,38 @@ mod too_many_lines; use clippy_config::Conf; use clippy_utils::msrvs::Msrv; use clippy_utils::paths::{PathNS, lookup_path_str}; +use rustc_ast::{self as ast, visit}; use rustc_hir as hir; use rustc_hir::intravisit; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; use rustc_middle::ty::TyCtxt; -use rustc_session::impl_lint_pass; +use rustc_session::{declare_lint_pass, impl_lint_pass}; use rustc_span::Span; use rustc_span::def_id::{DefIdSet, LocalDefId}; +declare_clippy_lint! { + /// ### What it does + /// Checks for function arguments having the similar names + /// differing by an underscore. + /// + /// ### Why is this bad? + /// It affects code readability. + /// + /// ### Example + /// ```no_run + /// fn foo(a: i32, _a: i32) {} + /// ``` + /// + /// Use instead: + /// ```no_run + /// fn bar(a: i32, _b: i32) {} + /// ``` + #[clippy::version = "pre 1.29.0"] + pub DUPLICATE_UNDERSCORE_ARGUMENT, + style, + "function arguments having names which only differ by an underscore" +} + declare_clippy_lint! { /// ### What it does /// Checks for functions with too many parameters. @@ -448,6 +473,14 @@ declare_clippy_lint! { "function signature uses `&Option` instead of `Option<&T>`" } +declare_lint_pass!(EarlyFunctions => [DUPLICATE_UNDERSCORE_ARGUMENT]); + +impl EarlyLintPass for EarlyFunctions { + fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: visit::FnKind<'_>, _: Span, _: ast::NodeId) { + duplicate_underscore_argument::check(cx, fn_kind); + } +} + pub struct Functions { too_many_arguments_threshold: u64, too_many_lines_threshold: u64, @@ -503,7 +536,7 @@ impl<'tcx> LateLintPass<'tcx> for Functions { ) { let hir_id = cx.tcx.local_def_id_to_hir_id(def_id); too_many_arguments::check_fn(cx, kind, decl, span, hir_id, self.too_many_arguments_threshold); - too_many_lines::check_fn(cx, kind, span, body, self.too_many_lines_threshold); + too_many_lines::check_fn(cx, kind, body, span, def_id, self.too_many_lines_threshold); not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, def_id); misnamed_getters::check_fn(cx, kind, decl, body, span); impl_trait_in_params::check_fn(cx, &kind, body, hir_id); diff --git a/clippy_lints/src/functions/renamed_function_params.rs b/clippy_lints/src/functions/renamed_function_params.rs index 0a7c6e9d5f8e..f8e8f5544b99 100644 --- a/clippy_lints/src/functions/renamed_function_params.rs +++ b/clippy_lints/src/functions/renamed_function_params.rs @@ -6,6 +6,7 @@ use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind, Node, TraitRef}; use rustc_lint::LateContext; use rustc_span::Span; use rustc_span::symbol::{Ident, kw}; +use std::iter; use super::RENAMED_FUNCTION_PARAMS; @@ -58,16 +59,11 @@ impl RenamedFnArgs { let mut renamed: Vec<(Span, String)> = vec![]; debug_assert!(default_idents.size_hint() == current_idents.size_hint()); - while let (Some(default_ident), Some(current_ident)) = (default_idents.next(), current_idents.next()) { + for (default_ident, current_ident) in iter::zip(default_idents, current_idents) { let has_name_to_check = |ident: Option| { - if let Some(ident) = ident - && ident.name != kw::Underscore - && !ident.name.as_str().starts_with('_') - { - Some(ident) - } else { - None - } + ident + .filter(|ident| ident.name != kw::Underscore) + .filter(|ident| !ident.name.as_str().starts_with('_')) }; if let Some(default_ident) = has_name_to_check(default_ident) @@ -97,8 +93,7 @@ fn trait_item_def_id_of_impl(cx: &LateContext<'_>, target: OwnerId) -> Option, ignored_traits: &DefIdSet) -> bool { - let Some(trait_did) = of_trait.trait_def_id() else { - return false; - }; - ignored_traits.contains(&trait_did) + of_trait + .trait_def_id() + .is_some_and(|trait_did| ignored_traits.contains(&trait_did)) } diff --git a/clippy_lints/src/functions/result.rs b/clippy_lints/src/functions/result.rs index bb98ae826111..1f2fce687ed1 100644 --- a/clippy_lints/src/functions/result.rs +++ b/clippy_lints/src/functions/result.rs @@ -97,11 +97,7 @@ fn check_result_unit_err(cx: &LateContext<'_>, err_ty: Ty<'_>, fn_header_span: S fn check_result_large_err<'tcx>(cx: &LateContext<'tcx>, err_ty: Ty<'tcx>, hir_ty_span: Span, large_err_threshold: u64) { if let ty::Adt(adt, subst) = err_ty.kind() - && let Some(local_def_id) = err_ty - .ty_adt_def() - .expect("already checked this is adt") - .did() - .as_local() + && let Some(local_def_id) = adt.did().as_local() && let hir::Node::Item(item) = cx.tcx.hir_node_by_def_id(local_def_id) && let hir::ItemKind::Enum(_, _, ref def) = item.kind { diff --git a/clippy_lints/src/functions/too_many_lines.rs b/clippy_lints/src/functions/too_many_lines.rs index 4f90d9655b44..33eede8e65ac 100644 --- a/clippy_lints/src/functions/too_many_lines.rs +++ b/clippy_lints/src/functions/too_many_lines.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::source::SpanRangeExt; use rustc_hir as hir; +use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::FnKind; use rustc_lint::{LateContext, LintContext}; use rustc_span::Span; @@ -10,8 +11,9 @@ use super::TOO_MANY_LINES; pub(super) fn check_fn( cx: &LateContext<'_>, kind: FnKind<'_>, - span: Span, body: &hir::Body<'_>, + span: Span, + def_id: LocalDefId, too_many_lines_threshold: u64, ) { // Closures must be contained in a parent body, which will be checked for `too_many_lines`. @@ -74,7 +76,7 @@ pub(super) fn check_fn( span_lint( cx, TOO_MANY_LINES, - span, + cx.tcx.def_span(def_id), format!("this function has too many lines ({line_count}/{too_many_lines_threshold})"), ); } diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 6beddc1be144..57deb011f2b0 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -176,12 +176,11 @@ impl<'tcx> LateLintPass<'tcx> for LenZero { if let ExprKind::Let(lt) = expr.kind && match lt.pat.kind { PatKind::Slice([], None, []) => true, - PatKind::Expr(lit) => match lit.kind { - PatExprKind::Lit { lit, .. } => match lit.node { - LitKind::Str(lit, _) => lit.as_str().is_empty(), - _ => false, - }, - _ => false, + PatKind::Expr(lit) + if let PatExprKind::Lit { lit, .. } = lit.kind + && let LitKind::Str(lit, _) = lit.node => + { + lit.as_str().is_empty() }, _ => false, } @@ -336,33 +335,23 @@ fn extract_future_output<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<& } fn is_first_generic_integral<'tcx>(segment: &'tcx PathSegment<'tcx>) -> bool { - if let Some(generic_args) = segment.args { - if generic_args.args.is_empty() { - return false; - } - let arg = &generic_args.args[0]; - if let GenericArg::Type(rustc_hir::Ty { - kind: TyKind::Path(QPath::Resolved(_, path)), - .. - }) = arg - { - let segments = &path.segments; - let segment = &segments[0]; - let res = &segment.res; - if matches!(res, Res::PrimTy(PrimTy::Uint(_))) || matches!(res, Res::PrimTy(PrimTy::Int(_))) { - return true; - } - } + if let Some(generic_args) = segment.args + && let [GenericArg::Type(ty), ..] = &generic_args.args + && let TyKind::Path(QPath::Resolved(_, path)) = ty.kind + && let [segment, ..] = &path.segments + && matches!(segment.res, Res::PrimTy(PrimTy::Uint(_) | PrimTy::Int(_))) + { + true + } else { + false } - - false } fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option { if let Some(segment) = extract_future_output(cx, sig.output()) { let res = segment.res; - if matches!(res, Res::PrimTy(PrimTy::Uint(_))) || matches!(res, Res::PrimTy(PrimTy::Int(_))) { + if matches!(res, Res::PrimTy(PrimTy::Uint(_) | PrimTy::Int(_))) { return Some(LenOutput::Integral); } diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 844bc1b0e390..d468993e7444 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -556,6 +556,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(panicking_overflow_checks::PanickingOverflowChecks)); store.register_late_pass(|_| Box::::default()); store.register_late_pass(move |_| Box::new(disallowed_names::DisallowedNames::new(conf))); + store.register_early_pass(|| Box::new(functions::EarlyFunctions)); store.register_late_pass(move |tcx| Box::new(functions::Functions::new(tcx, conf))); store.register_late_pass(move |_| Box::new(doc::Documentation::new(conf))); store.register_early_pass(move || Box::new(doc::Documentation::new(conf))); @@ -600,7 +601,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(move |_| Box::new(trait_bounds::TraitBounds::new(conf))); store.register_late_pass(|_| Box::new(comparison_chain::ComparisonChain)); store.register_late_pass(move |tcx| Box::new(mut_key::MutableKeyType::new(tcx, conf))); - store.register_early_pass(|| Box::new(reference::DerefAddrOf)); + store.register_late_pass(|_| Box::new(reference::DerefAddrOf)); store.register_early_pass(|| Box::new(double_parens::DoubleParens)); let format_args = format_args_storage.clone(); store.register_late_pass(move |_| Box::new(format_impl::FormatImpl::new(format_args.clone()))); diff --git a/clippy_lints/src/loops/infinite_loop.rs b/clippy_lints/src/loops/infinite_loop.rs index 797ff1f39866..a71e6963f8ca 100644 --- a/clippy_lints/src/loops/infinite_loop.rs +++ b/clippy_lints/src/loops/infinite_loop.rs @@ -1,10 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::{fn_def_id, is_from_proc_macro, is_lint_allowed}; use hir::intravisit::{Visitor, walk_expr}; -use hir::{Expr, ExprKind, FnRetTy, FnSig, Node, TyKind}; use rustc_ast::Label; use rustc_errors::Applicability; -use rustc_hir as hir; +use rustc_hir::{ + self as hir, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Expr, ExprKind, FnRetTy, FnSig, Node, TyKind, +}; use rustc_lint::{LateContext, LintContext}; use rustc_span::sym; @@ -29,6 +30,10 @@ pub(super) fn check<'tcx>( return; } + if is_inside_unawaited_async_block(cx, expr) { + return; + } + if expr.span.in_external_macro(cx.sess().source_map()) || is_from_proc_macro(cx, expr) { return; } @@ -60,6 +65,39 @@ pub(super) fn check<'tcx>( } } +/// Check if the given expression is inside an async block that is not being awaited. +/// This helps avoid false positives when async blocks are spawned or assigned to variables. +fn is_inside_unawaited_async_block(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let current_hir_id = expr.hir_id; + for (_, parent_node) in cx.tcx.hir_parent_iter(current_hir_id) { + if let Node::Expr(Expr { + kind: + ExprKind::Closure(Closure { + kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)), + .. + }), + .. + }) = parent_node + { + return !is_async_block_awaited(cx, expr); + } + } + false +} + +fn is_async_block_awaited(cx: &LateContext<'_>, async_expr: &Expr<'_>) -> bool { + for (_, parent_node) in cx.tcx.hir_parent_iter(async_expr.hir_id) { + if let Node::Expr(Expr { + kind: ExprKind::Match(_, _, hir::MatchSource::AwaitDesugar), + .. + }) = parent_node + { + return true; + } + } + false +} + fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option> { for (_, parent_node) in cx.tcx.hir_parent_iter(expr.hir_id) { match parent_node { @@ -67,8 +105,8 @@ fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option // This is because we still need to backtrack one parent node to get the `OpaqueDef` ty. Node::Expr(Expr { kind: - ExprKind::Closure(hir::Closure { - kind: hir::ClosureKind::Coroutine(_), + ExprKind::Closure(Closure { + kind: ClosureKind::Coroutine(_), .. }), .. @@ -90,7 +128,7 @@ fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option .. }) | Node::Expr(Expr { - kind: ExprKind::Closure(hir::Closure { fn_decl: decl, .. }), + kind: ExprKind::Closure(Closure { fn_decl: decl, .. }), .. }) => return Some(decl.output), _ => (), diff --git a/clippy_lints/src/manual_let_else.rs b/clippy_lints/src/manual_let_else.rs index 1f9a943f13dc..5a7967bbf946 100644 --- a/clippy_lints/src/manual_let_else.rs +++ b/clippy_lints/src/manual_let_else.rs @@ -49,7 +49,7 @@ declare_clippy_lint! { } impl<'tcx> QuestionMark { - pub(crate) fn check_manual_let_else(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) { + pub(crate) fn check_manual_let_else(&self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) { if let StmtKind::Let(local) = stmt.kind && let Some(init) = local.init && local.els.is_none() diff --git a/clippy_lints/src/matches/match_bool.rs b/clippy_lints/src/matches/match_bool.rs index b90cf6357c5c..a2c8741f4f74 100644 --- a/clippy_lints/src/matches/match_bool.rs +++ b/clippy_lints/src/matches/match_bool.rs @@ -12,7 +12,11 @@ use super::MATCH_BOOL; pub(crate) fn check(cx: &LateContext<'_>, scrutinee: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { // Type of expression is `bool`. - if *cx.typeck_results().expr_ty(scrutinee).kind() == ty::Bool { + if *cx.typeck_results().expr_ty(scrutinee).kind() == ty::Bool + && arms + .iter() + .all(|arm| arm.pat.walk_short(|p| !matches!(p.kind, PatKind::Binding(..)))) + { span_lint_and_then( cx, MATCH_BOOL, diff --git a/clippy_lints/src/matches/match_ref_pats.rs b/clippy_lints/src/matches/match_ref_pats.rs index 5445ee1f0429..5934ec409935 100644 --- a/clippy_lints/src/matches/match_ref_pats.rs +++ b/clippy_lints/src/matches/match_ref_pats.rs @@ -17,6 +17,11 @@ where return; } + // `!` cannot be deref-ed + if cx.typeck_results().expr_ty(scrutinee).is_never() { + return; + } + let (first_sugg, msg, title); let ctxt = expr.span.ctxt(); let mut app = Applicability::Unspecified; diff --git a/clippy_lints/src/matches/match_str_case_mismatch.rs b/clippy_lints/src/matches/match_str_case_mismatch.rs index 8b4c17000519..eb8b16e1561b 100644 --- a/clippy_lints/src/matches/match_str_case_mismatch.rs +++ b/clippy_lints/src/matches/match_str_case_mismatch.rs @@ -54,7 +54,7 @@ impl<'tcx> Visitor<'tcx> for MatchExprVisitor<'_, 'tcx> { } impl MatchExprVisitor<'_, '_> { - fn case_altered(&mut self, segment_ident: Symbol, receiver: &Expr<'_>) -> ControlFlow { + fn case_altered(&self, segment_ident: Symbol, receiver: &Expr<'_>) -> ControlFlow { if let Some(case_method) = get_case_method(segment_ident) { let ty = self.cx.typeck_results().expr_ty(receiver).peel_refs(); diff --git a/clippy_lints/src/methods/double_ended_iterator_last.rs b/clippy_lints/src/methods/double_ended_iterator_last.rs index 6d841853fbe5..578865c32918 100644 --- a/clippy_lints/src/methods/double_ended_iterator_last.rs +++ b/clippy_lints/src/methods/double_ended_iterator_last.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::{has_non_owning_mutable_access, implements_trait}; -use clippy_utils::{is_mutable, is_trait_method, path_to_local, sym}; +use clippy_utils::{is_mutable, is_trait_method, path_to_local_with_projections, sym}; use rustc_errors::Applicability; use rustc_hir::{Expr, Node, PatKind}; use rustc_lint::LateContext; @@ -37,7 +37,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Exp // TODO: Change this to lint only when the referred iterator is not used later. If it is used later, // changing to `next_back()` may change its behavior. if !(is_mutable(cx, self_expr) || self_type.is_ref()) { - if let Some(hir_id) = path_to_local(self_expr) + if let Some(hir_id) = path_to_local_with_projections(self_expr) && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) && let PatKind::Binding(_, _, ident, _) = pat.kind { diff --git a/clippy_lints/src/methods/expect_fun_call.rs b/clippy_lints/src/methods/expect_fun_call.rs index 6e5da5bda8c9..818e26f8aa1d 100644 --- a/clippy_lints/src/methods/expect_fun_call.rs +++ b/clippy_lints/src/methods/expect_fun_call.rs @@ -15,7 +15,6 @@ use std::ops::ControlFlow; use super::EXPECT_FUN_CALL; /// Checks for the `EXPECT_FUN_CALL` lint. -#[allow(clippy::too_many_lines)] pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, format_args_storage: &FormatArgsStorage, @@ -25,43 +24,6 @@ pub(super) fn check<'tcx>( receiver: &'tcx hir::Expr<'tcx>, args: &'tcx [hir::Expr<'tcx>], ) { - // Strip `{}`, `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or - // `&str` - fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { - let mut arg_root = peel_blocks(arg); - loop { - arg_root = match &arg_root.kind { - hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr, - hir::ExprKind::MethodCall(method_name, receiver, [], ..) => { - if (method_name.ident.name == sym::as_str || method_name.ident.name == sym::as_ref) && { - let arg_type = cx.typeck_results().expr_ty(receiver); - let base_type = arg_type.peel_refs(); - base_type.is_str() || is_type_lang_item(cx, base_type, hir::LangItem::String) - } { - receiver - } else { - break; - } - }, - _ => break, - }; - } - arg_root - } - - fn contains_call<'a>(cx: &LateContext<'a>, arg: &'a hir::Expr<'a>) -> bool { - for_each_expr(cx, arg, |expr| { - if matches!(expr.kind, hir::ExprKind::MethodCall { .. } | hir::ExprKind::Call { .. }) - && !is_inside_always_const_context(cx.tcx, expr.hir_id) - { - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) - } - }) - .is_some() - } - if name == sym::expect && let [arg] = args && let arg_root = get_arg_root(cx, arg) @@ -114,3 +76,40 @@ pub(super) fn check<'tcx>( ); } } + +/// Strip `{}`, `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or +/// `&str` +fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { + let mut arg_root = peel_blocks(arg); + loop { + arg_root = match &arg_root.kind { + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr, + hir::ExprKind::MethodCall(method_name, receiver, [], ..) => { + if (method_name.ident.name == sym::as_str || method_name.ident.name == sym::as_ref) && { + let arg_type = cx.typeck_results().expr_ty(receiver); + let base_type = arg_type.peel_refs(); + base_type.is_str() || is_type_lang_item(cx, base_type, hir::LangItem::String) + } { + receiver + } else { + break; + } + }, + _ => break, + }; + } + arg_root +} + +fn contains_call<'a>(cx: &LateContext<'a>, arg: &'a hir::Expr<'a>) -> bool { + for_each_expr(cx, arg, |expr| { + if matches!(expr.kind, hir::ExprKind::MethodCall { .. } | hir::ExprKind::Call { .. }) + && !is_inside_always_const_context(cx.tcx, expr.hir_id) + { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_some() +} diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index 4dd54cf19745..5b8457bdd164 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -106,7 +106,7 @@ enum CheckResult<'tcx> { impl<'tcx> OffendingFilterExpr<'tcx> { pub fn check_map_call( - &mut self, + &self, cx: &LateContext<'tcx>, map_body: &'tcx Body<'tcx>, map_param_id: HirId, @@ -413,7 +413,7 @@ fn is_find_or_filter<'a>( } && let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind - && let Some(mut offending_expr) = OffendingFilterExpr::hir(cx, filter_body.value, filter_param_id) + && let Some(offending_expr) = OffendingFilterExpr::hir(cx, filter_body.value, filter_param_id) && let ExprKind::Closure(&Closure { body: map_body_id, .. }) = map_arg.kind && let map_body = cx.tcx.hir_body(map_body_id) diff --git a/clippy_lints/src/methods/filter_next.rs b/clippy_lints/src/methods/filter_next.rs index 6c1a14fc8829..72f83b245a0c 100644 --- a/clippy_lints/src/methods/filter_next.rs +++ b/clippy_lints/src/methods/filter_next.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; +use clippy_utils::path_to_local_with_projections; use clippy_utils::source::snippet; use clippy_utils::ty::implements_trait; use rustc_ast::{BindingMode, Mutability}; @@ -9,21 +10,6 @@ use rustc_span::sym; use super::FILTER_NEXT; -fn path_to_local(expr: &hir::Expr<'_>) -> Option { - match expr.kind { - hir::ExprKind::Field(f, _) => path_to_local(f), - hir::ExprKind::Index(recv, _, _) => path_to_local(recv), - hir::ExprKind::Path(hir::QPath::Resolved( - _, - hir::Path { - res: rustc_hir::def::Res::Local(local), - .. - }, - )) => Some(*local), - _ => None, - } -} - /// lint use of `filter().next()` for `Iterators` pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, @@ -44,7 +30,7 @@ pub(super) fn check<'tcx>( let iter_snippet = snippet(cx, recv.span, ".."); // add note if not multi-line span_lint_and_then(cx, FILTER_NEXT, expr.span, msg, |diag| { - let (applicability, pat) = if let Some(id) = path_to_local(recv) + let (applicability, pat) = if let Some(id) = path_to_local_with_projections(recv) && let hir::Node::Pat(pat) = cx.tcx.hir_node(id) && let hir::PatKind::Binding(BindingMode(_, Mutability::Not), _, ident, _) = pat.kind { diff --git a/clippy_lints/src/misc_early/mod.rs b/clippy_lints/src/misc_early/mod.rs index f880f1f329ff..f988323a8c13 100644 --- a/clippy_lints/src/misc_early/mod.rs +++ b/clippy_lints/src/misc_early/mod.rs @@ -7,12 +7,9 @@ mod unneeded_field_pattern; mod unneeded_wildcard_pattern; mod zero_prefixed_literal; -use clippy_utils::diagnostics::span_lint; use clippy_utils::source::snippet_opt; -use rustc_ast::ast::{Expr, ExprKind, Generics, LitFloatType, LitIntType, LitKind, NodeId, Pat, PatKind}; +use rustc_ast::ast::{Expr, ExprKind, Generics, LitFloatType, LitIntType, LitKind, Pat}; use rustc_ast::token; -use rustc_ast::visit::FnKind; -use rustc_data_structures::fx::FxHashMap; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use rustc_session::declare_lint_pass; use rustc_span::Span; @@ -60,29 +57,6 @@ declare_clippy_lint! { "struct fields bound to a wildcard instead of using `..`" } -declare_clippy_lint! { - /// ### What it does - /// Checks for function arguments having the similar names - /// differing by an underscore. - /// - /// ### Why is this bad? - /// It affects code readability. - /// - /// ### Example - /// ```no_run - /// fn foo(a: i32, _a: i32) {} - /// ``` - /// - /// Use instead: - /// ```no_run - /// fn bar(a: i32, _b: i32) {} - /// ``` - #[clippy::version = "pre 1.29.0"] - pub DUPLICATE_UNDERSCORE_ARGUMENT, - style, - "function arguments having names which only differ by an underscore" -} - declare_clippy_lint! { /// ### What it does /// Warns on hexadecimal literals with mixed-case letter @@ -330,7 +304,6 @@ declare_clippy_lint! { declare_lint_pass!(MiscEarlyLints => [ UNNEEDED_FIELD_PATTERN, - DUPLICATE_UNDERSCORE_ARGUMENT, MIXED_CASE_HEX_LITERALS, UNSEPARATED_LITERAL_SUFFIX, SEPARATED_LITERAL_SUFFIX, @@ -359,32 +332,6 @@ impl EarlyLintPass for MiscEarlyLints { unneeded_wildcard_pattern::check(cx, pat); } - fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) { - let mut registered_names: FxHashMap = FxHashMap::default(); - - for arg in &fn_kind.decl().inputs { - if let PatKind::Ident(_, ident, None) = arg.pat.kind { - let arg_name = ident.to_string(); - - if let Some(arg_name) = arg_name.strip_prefix('_') { - if let Some(correspondence) = registered_names.get(arg_name) { - span_lint( - cx, - DUPLICATE_UNDERSCORE_ARGUMENT, - *correspondence, - format!( - "`{arg_name}` already exists, having another argument having almost the same \ - name makes code comprehension and documentation more difficult" - ), - ); - } - } else { - registered_names.insert(arg_name, arg.pat.span); - } - } - } - } - fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { if expr.span.in_external_macro(cx.sess().source_map()) { return; @@ -404,7 +351,7 @@ impl MiscEarlyLints { // See for a regression. // FIXME: Find a better way to detect those cases. let lit_snip = match snippet_opt(cx, span) { - Some(snip) if snip.chars().next().is_some_and(|c| c.is_ascii_digit()) => snip, + Some(snip) if snip.starts_with(|c: char| c.is_ascii_digit()) => snip, _ => return, }; diff --git a/clippy_lints/src/mixed_read_write_in_expression.rs b/clippy_lints/src/mixed_read_write_in_expression.rs index a489c0a4a5a1..3b44d4b60d32 100644 --- a/clippy_lints/src/mixed_read_write_in_expression.rs +++ b/clippy_lints/src/mixed_read_write_in_expression.rs @@ -134,7 +134,7 @@ impl<'tcx> DivergenceVisitor<'_, 'tcx> { } } - fn report_diverging_sub_expr(&mut self, e: &Expr<'_>) { + fn report_diverging_sub_expr(&self, e: &Expr<'_>) { if let Some(macro_call) = root_macro_call_first_node(self.cx, e) && self.cx.tcx.is_diagnostic_item(sym::todo_macro, macro_call.def_id) { diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index fa5afcc00874..6ae26156bc44 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -166,7 +166,9 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool { applicability, ); }; - if let Some((a, b)) = fetch_bool_block(then).and_then(|a| Some((a, fetch_bool_block(else_expr)?))) { + if let Some(a) = fetch_bool_block(then) + && let Some(b) = fetch_bool_block(else_expr) + { match (a, b) { (RetBool(true), RetBool(true)) | (Bool(true), Bool(true)) => { span_lint( diff --git a/clippy_lints/src/needless_borrows_for_generic_args.rs b/clippy_lints/src/needless_borrows_for_generic_args.rs index 120a4b98a65a..c7c4976aeb7b 100644 --- a/clippy_lints/src/needless_borrows_for_generic_args.rs +++ b/clippy_lints/src/needless_borrows_for_generic_args.rs @@ -59,7 +59,7 @@ declare_clippy_lint! { pub struct NeedlessBorrowsForGenericArgs<'tcx> { /// Stack of (body owner, `PossibleBorrowerMap`) pairs. Used by - /// `needless_borrow_impl_arg_position` to determine when a borrowed expression can instead + /// [`needless_borrow_count`] to determine when a borrowed expression can instead /// be moved. possible_borrowers: Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>, diff --git a/clippy_lints/src/no_effect.rs b/clippy_lints/src/no_effect.rs index 72e6503e7e49..0d6666eed455 100644 --- a/clippy_lints/src/no_effect.rs +++ b/clippy_lints/src/no_effect.rs @@ -305,11 +305,12 @@ fn check_unnecessary_operation(cx: &LateContext<'_>, stmt: &Stmt<'_>) { for e in reduced { if let Some(snip) = e.span.get_source_text(cx) { snippet.push_str(&snip); - snippet.push(';'); + snippet.push_str("; "); } else { return; } } + snippet.pop(); // remove the last space span_lint_hir_and_then( cx, UNNECESSARY_OPERATION, diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index 8a5a6f4a4dc1..2fffc4244a73 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -92,7 +92,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "pre 1.29.0"] pub DECLARE_INTERIOR_MUTABLE_CONST, - style, + suspicious, "declaring `const` with interior mutability" } diff --git a/clippy_lints/src/non_expressive_names.rs b/clippy_lints/src/non_expressive_names.rs index c5873589b26f..1961ac1516da 100644 --- a/clippy_lints/src/non_expressive_names.rs +++ b/clippy_lints/src/non_expressive_names.rs @@ -248,6 +248,11 @@ impl SimilarNamesNameVisitor<'_, '_, '_> { continue; } + // Skip similarity check if both names are exactly 3 characters + if count == 3 && existing_name.len == 3 { + continue; + } + let dissimilar = match existing_name.len.cmp(&count) { Ordering::Greater => existing_name.len - count != 1 || levenstein_not_1(interned_name, existing_str), Ordering::Less => count - existing_name.len != 1 || levenstein_not_1(existing_str, interned_name), diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs index ba8f6354d976..a42763172f56 100644 --- a/clippy_lints/src/only_used_in_recursion.rs +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -173,7 +173,7 @@ impl Params { } /// Sets the `apply_lint` flag on each parameter. - fn flag_for_linting(&mut self) { + fn flag_for_linting(&self) { // Stores the list of parameters currently being resolved. Needed to avoid cycles. let mut eval_stack = Vec::new(); for param in &self.params { diff --git a/clippy_lints/src/operators/arithmetic_side_effects.rs b/clippy_lints/src/operators/arithmetic_side_effects.rs index 466beb04b074..ea5b81aec31e 100644 --- a/clippy_lints/src/operators/arithmetic_side_effects.rs +++ b/clippy_lints/src/operators/arithmetic_side_effects.rs @@ -325,7 +325,7 @@ impl ArithmeticSideEffects { self.issue_lint(cx, expr); } - fn should_skip_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) -> bool { + fn should_skip_expr<'tcx>(&self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) -> bool { is_lint_allowed(cx, ARITHMETIC_SIDE_EFFECTS, expr.hir_id) || self.expr_span.is_some() || self.const_span.is_some_and(|sp| sp.contains(expr.span)) diff --git a/clippy_lints/src/operators/numeric_arithmetic.rs b/clippy_lints/src/operators/numeric_arithmetic.rs index e6be536ca0f4..9b1b063c4737 100644 --- a/clippy_lints/src/operators/numeric_arithmetic.rs +++ b/clippy_lints/src/operators/numeric_arithmetic.rs @@ -13,7 +13,7 @@ pub struct Context { const_span: Option, } impl Context { - fn skip_expr(&mut self, e: &hir::Expr<'_>) -> bool { + fn skip_expr(&self, e: &hir::Expr<'_>) -> bool { self.expr_id.is_some() || self.const_span.is_some_and(|span| span.contains(e.span)) } diff --git a/clippy_lints/src/pass_by_ref_or_value.rs b/clippy_lints/src/pass_by_ref_or_value.rs index d7b4a03aa537..1b1e77bbea8f 100644 --- a/clippy_lints/src/pass_by_ref_or_value.rs +++ b/clippy_lints/src/pass_by_ref_or_value.rs @@ -120,7 +120,7 @@ impl PassByRefOrValue { } } - fn check_poly_fn(&mut self, cx: &LateContext<'_>, def_id: LocalDefId, decl: &FnDecl<'_>, span: Option) { + fn check_poly_fn(&self, cx: &LateContext<'_>, def_id: LocalDefId, decl: &FnDecl<'_>, span: Option) { if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) { return; } diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index b3058c51afdb..9eed46460a61 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -237,7 +237,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { .collect(); let results = check_ptr_arg_usage(cx, body, &lint_args); - for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) { + for (result, args) in iter::zip(&results, &lint_args).filter(|(r, _)| !r.skip) { span_lint_hir_and_then(cx, PTR_ARG, args.emission_id, args.span, args.build_msg(), |diag| { diag.multipart_suggestion( "change this to", @@ -386,7 +386,6 @@ impl<'tcx> DerefTy<'tcx> { } } -#[expect(clippy::too_many_lines)] fn check_fn_args<'cx, 'tcx: 'cx>( cx: &'cx LateContext<'tcx>, fn_sig: ty::FnSig<'tcx>, @@ -413,13 +412,13 @@ fn check_fn_args<'cx, 'tcx: 'cx>( Some(sym::Vec) => ( [(sym::clone, ".to_owned()")].as_slice(), DerefTy::Slice( - name.args.and_then(|args| args.args.first()).and_then(|arg| { - if let GenericArg::Type(ty) = arg { - Some(ty.span) - } else { - None - } - }), + if let Some(name_args) = name.args + && let [GenericArg::Type(ty), ..] = name_args.args + { + Some(ty.span) + } else { + None + }, args.type_at(0), ), ), @@ -432,33 +431,29 @@ fn check_fn_args<'cx, 'tcx: 'cx>( DerefTy::Path, ), Some(sym::Cow) if mutability == Mutability::Not => { - if let Some((lifetime, ty)) = name.args.and_then(|args| { - if let [GenericArg::Lifetime(lifetime), ty] = args.args { - return Some((lifetime, ty)); - } - None - }) { + if let Some(name_args) = name.args + && let [GenericArg::Lifetime(lifetime), ty] = name_args.args + { if let LifetimeKind::Param(param_def_id) = lifetime.kind && !lifetime.is_anonymous() && fn_sig .output() .walk() - .filter_map(|arg| { - arg.as_region().and_then(|lifetime| match lifetime.kind() { - ty::ReEarlyParam(r) => Some( - cx.tcx - .generics_of(cx.tcx.parent(param_def_id.to_def_id())) - .region_param(r, cx.tcx) - .def_id, - ), - ty::ReBound(_, r) => r.kind.get_id(), - ty::ReLateParam(r) => r.kind.get_id(), - ty::ReStatic - | ty::ReVar(_) - | ty::RePlaceholder(_) - | ty::ReErased - | ty::ReError(_) => None, - }) + .filter_map(ty::GenericArg::as_region) + .filter_map(|lifetime| match lifetime.kind() { + ty::ReEarlyParam(r) => Some( + cx.tcx + .generics_of(cx.tcx.parent(param_def_id.to_def_id())) + .region_param(r, cx.tcx) + .def_id, + ), + ty::ReBound(_, r) => r.kind.get_id(), + ty::ReLateParam(r) => r.kind.get_id(), + ty::ReStatic + | ty::ReVar(_) + | ty::RePlaceholder(_) + | ty::ReErased + | ty::ReError(_) => None, }) .any(|def_id| def_id.as_local().is_some_and(|def_id| def_id == param_def_id)) { @@ -627,12 +622,16 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[ } } + // If the expression's type gets adjusted down to the deref type, we might as + // well have started with that deref type -- the lint should fire let deref_ty = args.deref_ty.ty(self.cx); let adjusted_ty = self.cx.typeck_results().expr_ty_adjusted(e).peel_refs(); if adjusted_ty == deref_ty { return; } + // If the expression's type is constrained by `dyn Trait`, see if the deref + // type implements the trait(s) as well, and if so, the lint should fire if let ty::Dynamic(preds, ..) = adjusted_ty.kind() && matches_preds(self.cx, deref_ty, preds) { diff --git a/clippy_lints/src/raw_strings.rs b/clippy_lints/src/raw_strings.rs index 6a79cae32a59..943e662479e9 100644 --- a/clippy_lints/src/raw_strings.rs +++ b/clippy_lints/src/raw_strings.rs @@ -103,15 +103,7 @@ impl EarlyLintPass for RawStrings { } impl RawStrings { - fn check_raw_string( - &mut self, - cx: &EarlyContext<'_>, - str: &str, - lit_span: Span, - prefix: &str, - max: u8, - descr: &str, - ) { + fn check_raw_string(&self, cx: &EarlyContext<'_>, str: &str, lit_span: Span, prefix: &str, max: u8, descr: &str) { if !str.contains(['\\', '"']) { span_lint_and_then( cx, diff --git a/clippy_lints/src/redundant_pub_crate.rs b/clippy_lints/src/redundant_pub_crate.rs index 902e8af7ec48..0c1c664f1117 100644 --- a/clippy_lints/src/redundant_pub_crate.rs +++ b/clippy_lints/src/redundant_pub_crate.rs @@ -88,8 +88,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantPubCrate { // We ignore macro exports. And `ListStem` uses, which aren't interesting. fn is_ignorable_export<'tcx>(item: &'tcx Item<'tcx>) -> bool { if let ItemKind::Use(path, kind) = item.kind { - let ignore = matches!(path.res.macro_ns, Some(Res::Def(DefKind::Macro(_), _))) - || kind == UseKind::ListStem; + let ignore = matches!(path.res.macro_ns, Some(Res::Def(DefKind::Macro(_), _))) || kind == UseKind::ListStem; if ignore { return true; } diff --git a/clippy_lints/src/reference.rs b/clippy_lints/src/reference.rs index 4bff37216eda..3bbcad12a319 100644 --- a/clippy_lints/src/reference.rs +++ b/clippy_lints/src/reference.rs @@ -1,10 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{SpanRangeExt, snippet_with_applicability}; -use rustc_ast::ast::{Expr, ExprKind, Mutability, UnOp}; +use clippy_utils::source::snippet; +use clippy_utils::sugg::{Sugg, has_enclosing_paren}; +use clippy_utils::ty::adjust_derefs_manually_drop; use rustc_errors::Applicability; -use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_hir::{Expr, ExprKind, HirId, Node, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; -use rustc_span::{BytePos, Span}; declare_clippy_lint! { /// ### What it does @@ -37,17 +38,12 @@ declare_clippy_lint! { declare_lint_pass!(DerefAddrOf => [DEREF_ADDROF]); -fn without_parens(mut e: &Expr) -> &Expr { - while let ExprKind::Paren(ref child_e) = e.kind { - e = child_e; - } - e -} - -impl EarlyLintPass for DerefAddrOf { - fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) { - if let ExprKind::Unary(UnOp::Deref, ref deref_target) = e.kind - && let ExprKind::AddrOf(_, ref mutability, ref addrof_target) = without_parens(deref_target).kind +impl LateLintPass<'_> for DerefAddrOf { + fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) { + if !e.span.from_expansion() + && let ExprKind::Unary(UnOp::Deref, deref_target) = e.kind + && !deref_target.span.from_expansion() + && let ExprKind::AddrOf(_, _, addrof_target) = deref_target.kind // NOTE(tesuji): `*&` forces rustc to const-promote the array to `.rodata` section. // See #12854 for details. && !matches!(addrof_target.kind, ExprKind::Array(_)) @@ -55,57 +51,82 @@ impl EarlyLintPass for DerefAddrOf { && !addrof_target.span.from_expansion() { let mut applicability = Applicability::MachineApplicable; - let sugg = if e.span.from_expansion() { - if let Some(macro_source) = e.span.get_source_text(cx) { - // Remove leading whitespace from the given span - // e.g: ` $visitor` turns into `$visitor` - let trim_leading_whitespaces = |span: Span| { - span.get_source_text(cx) - .and_then(|snip| { - #[expect(clippy::cast_possible_truncation)] - snip.find(|c: char| !c.is_whitespace()) - .map(|pos| span.lo() + BytePos(pos as u32)) - }) - .map_or(span, |start_no_whitespace| e.span.with_lo(start_no_whitespace)) - }; + let mut sugg = || Sugg::hir_with_applicability(cx, addrof_target, "_", &mut applicability); - let mut generate_snippet = |pattern: &str| { - #[expect(clippy::cast_possible_truncation)] - macro_source.rfind(pattern).map(|pattern_pos| { - let rpos = pattern_pos + pattern.len(); - let span_after_ref = e.span.with_lo(BytePos(e.span.lo().0 + rpos as u32)); - let span = trim_leading_whitespaces(span_after_ref); - snippet_with_applicability(cx, span, "_", &mut applicability) - }) - }; - - if *mutability == Mutability::Mut { - generate_snippet("mut") - } else { - generate_snippet("&") - } - } else { - Some(snippet_with_applicability(cx, e.span, "_", &mut applicability)) - } - } else { - Some(snippet_with_applicability( - cx, - addrof_target.span, - "_", - &mut applicability, - )) + // If this expression is an explicit `DerefMut` of a `ManuallyDrop` reached through a + // union, we may remove the reference if we are at the point where the implicit + // dereference would take place. Otherwise, we should not lint. + let sugg = match is_manually_drop_through_union(cx, e.hir_id, addrof_target) { + ManuallyDropThroughUnion::Directly => sugg().deref(), + ManuallyDropThroughUnion::Indirect => return, + ManuallyDropThroughUnion::No => sugg(), }; - if let Some(sugg) = sugg { - span_lint_and_sugg( - cx, - DEREF_ADDROF, - e.span, - "immediately dereferencing a reference", - "try", - sugg.to_string(), - applicability, - ); - } + + let sugg = if has_enclosing_paren(snippet(cx, e.span, "")) { + sugg.maybe_paren() + } else { + sugg + }; + + span_lint_and_sugg( + cx, + DEREF_ADDROF, + e.span, + "immediately dereferencing a reference", + "try", + sugg.to_string(), + applicability, + ); } } } + +/// Is this a `ManuallyDrop` reached through a union, and when is `DerefMut` called on it? +enum ManuallyDropThroughUnion { + /// `ManuallyDrop` reached through a union and immediately explicitely dereferenced + Directly, + /// `ManuallyDrop` reached through a union, and dereferenced later on + Indirect, + /// Any other situation + No, +} + +/// Check if `addrof_target` is part of an access to a `ManuallyDrop` entity reached through a +/// union, and when it is dereferenced using `DerefMut` starting from `expr_id` and going up. +fn is_manually_drop_through_union( + cx: &LateContext<'_>, + expr_id: HirId, + addrof_target: &Expr<'_>, +) -> ManuallyDropThroughUnion { + if is_reached_through_union(cx, addrof_target) { + let typeck = cx.typeck_results(); + for (idx, id) in std::iter::once(expr_id) + .chain(cx.tcx.hir_parent_id_iter(expr_id)) + .enumerate() + { + if let Node::Expr(expr) = cx.tcx.hir_node(id) { + if adjust_derefs_manually_drop(typeck.expr_adjustments(expr), typeck.expr_ty(expr)) { + return if idx == 0 { + ManuallyDropThroughUnion::Directly + } else { + ManuallyDropThroughUnion::Indirect + }; + } + } else { + break; + } + } + } + ManuallyDropThroughUnion::No +} + +/// Checks whether `expr` denotes an object reached through a union +fn is_reached_through_union(cx: &LateContext<'_>, mut expr: &Expr<'_>) -> bool { + while let ExprKind::Field(parent, _) | ExprKind::Index(parent, _, _) = expr.kind { + if cx.typeck_results().expr_ty_adjusted(parent).is_union() { + return true; + } + expr = parent; + } + false +} diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index 5ecbb56925ec..76ab3cdae22e 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -380,7 +380,7 @@ impl<'tcx> IndexBinding<'_, 'tcx> { } } - fn is_used_other_than_swapping(&mut self, idx_ident: Ident) -> bool { + fn is_used_other_than_swapping(&self, idx_ident: Ident) -> bool { if Self::is_used_slice_indexed(self.swap1_idx, idx_ident) || Self::is_used_slice_indexed(self.swap2_idx, idx_ident) { @@ -389,7 +389,7 @@ impl<'tcx> IndexBinding<'_, 'tcx> { self.is_used_after_swap(idx_ident) } - fn is_used_after_swap(&mut self, idx_ident: Ident) -> bool { + fn is_used_after_swap(&self, idx_ident: Ident) -> bool { let mut v = IndexBindingVisitor { idx: idx_ident, suggest_span: self.suggest_span, diff --git a/clippy_lints/src/transmute/eager_transmute.rs b/clippy_lints/src/transmute/eager_transmute.rs index 535c044f49e6..97e68b3df94e 100644 --- a/clippy_lints/src/transmute/eager_transmute.rs +++ b/clippy_lints/src/transmute/eager_transmute.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{eq_expr_value, path_to_local, sym}; +use clippy_utils::{eq_expr_value, path_to_local_with_projections, sym}; use rustc_abi::WrappingRange; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, Node}; @@ -63,11 +63,7 @@ fn binops_with_local(cx: &LateContext<'_>, local_expr: &Expr<'_>, expr: &Expr<'_ /// Checks if an expression is a path to a local variable (with optional projections), e.g. /// `x.field[0].field2` would return true. fn is_local_with_projections(expr: &Expr<'_>) -> bool { - match expr.kind { - ExprKind::Path(_) => path_to_local(expr).is_some(), - ExprKind::Field(expr, _) | ExprKind::Index(expr, ..) => is_local_with_projections(expr), - _ => false, - } + path_to_local_with_projections(expr).is_some() } pub(super) fn check<'tcx>( diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index d5112e2c3f97..1c7bb4314ddf 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -105,7 +105,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "pre 1.29.0"] pub CROSSPOINTER_TRANSMUTE, - complexity, + suspicious, "transmutes that have to or from types that are a pointer to the other" } diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index 1c52de52619e..ba0d4de5f3b3 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -202,79 +202,41 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks { }; let item_has_safety_comment = item_has_safety_comment(cx, item); - match (&item.kind, item_has_safety_comment) { - // lint unsafe impl without safety comment - (ItemKind::Impl(Impl { of_trait: Some(of_trait), .. }), HasSafetyComment::No) if of_trait.safety.is_unsafe() => { - if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, item.hir_id()) - && !is_unsafe_from_proc_macro(cx, item.span) - { - let source_map = cx.tcx.sess.source_map(); - let span = if source_map.is_multiline(item.span) { - source_map.span_until_char(item.span, '\n') - } else { - item.span - }; - - #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] - span_lint_and_then( - cx, - UNDOCUMENTED_UNSAFE_BLOCKS, - span, - "unsafe impl missing a safety comment", - |diag| { - diag.help("consider adding a safety comment on the preceding line"); - }, - ); - } - }, - // lint safe impl with unnecessary safety comment - (ItemKind::Impl(Impl { of_trait: Some(of_trait), .. }), HasSafetyComment::Yes(pos)) if of_trait.safety.is_safe() => { - if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) { - let (span, help_span) = mk_spans(pos); - - span_lint_and_then( - cx, - UNNECESSARY_SAFETY_COMMENT, - span, - "impl has unnecessary safety comment", - |diag| { - diag.span_help(help_span, "consider removing the safety comment"); - }, - ); - } - }, - (ItemKind::Impl(_), _) => {}, - // const and static items only need a safety comment if their body is an unsafe block, lint otherwise - (&ItemKind::Const(.., body) | &ItemKind::Static(.., body), HasSafetyComment::Yes(pos)) => { - if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, body.hir_id) { - let body = cx.tcx.hir_body(body); - if !matches!( - body.value.kind, hir::ExprKind::Block(block, _) - if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) - ) { - let (span, help_span) = mk_spans(pos); - - span_lint_and_then( - cx, - UNNECESSARY_SAFETY_COMMENT, - span, - format!( - "{} has unnecessary safety comment", - cx.tcx.def_descr(item.owner_id.to_def_id()), - ), - |diag| { - diag.span_help(help_span, "consider removing the safety comment"); - }, - ); - } - } - }, - // Aside from unsafe impls and consts/statics with an unsafe block, items in general - // do not have safety invariants that need to be documented, so lint those. - (_, HasSafetyComment::Yes(pos)) => { - if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) { - let (span, help_span) = mk_spans(pos); + match item_has_safety_comment { + HasSafetyComment::Yes(pos) => check_has_safety_comment(cx, item, mk_spans(pos)), + HasSafetyComment::No => check_has_no_safety_comment(cx, item), + HasSafetyComment::Maybe => {}, + } + } +} +fn check_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>, (span, help_span): (Span, Span)) { + match &item.kind { + ItemKind::Impl(Impl { + of_trait: Some(of_trait), + .. + }) if of_trait.safety.is_safe() => { + if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) { + span_lint_and_then( + cx, + UNNECESSARY_SAFETY_COMMENT, + span, + "impl has unnecessary safety comment", + |diag| { + diag.span_help(help_span, "consider removing the safety comment"); + }, + ); + } + }, + ItemKind::Impl(_) => {}, + // const and static items only need a safety comment if their body is an unsafe block, lint otherwise + &ItemKind::Const(.., body) | &ItemKind::Static(.., body) => { + if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, body.hir_id) { + let body = cx.tcx.hir_body(body); + if !matches!( + body.value.kind, hir::ExprKind::Block(block, _) + if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) + ) { span_lint_and_then( cx, UNNECESSARY_SAFETY_COMMENT, @@ -288,12 +250,56 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks { }, ); } - }, - _ => (), - } + } + }, + // Aside from unsafe impls and consts/statics with an unsafe block, items in general + // do not have safety invariants that need to be documented, so lint those. + _ => { + if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) { + span_lint_and_then( + cx, + UNNECESSARY_SAFETY_COMMENT, + span, + format!( + "{} has unnecessary safety comment", + cx.tcx.def_descr(item.owner_id.to_def_id()), + ), + |diag| { + diag.span_help(help_span, "consider removing the safety comment"); + }, + ); + } + }, } } +fn check_has_no_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) { + if let ItemKind::Impl(Impl { + of_trait: Some(of_trait), + .. + }) = item.kind + && of_trait.safety.is_unsafe() + && !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, item.hir_id()) + && !is_unsafe_from_proc_macro(cx, item.span) + { + let source_map = cx.tcx.sess.source_map(); + let span = if source_map.is_multiline(item.span) { + source_map.span_until_char(item.span, '\n') + } else { + item.span + }; + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( + cx, + UNDOCUMENTED_UNSAFE_BLOCKS, + span, + "unsafe impl missing a safety comment", + |diag| { + diag.help("consider adding a safety comment on the preceding line"); + }, + ); + } +} fn expr_has_unnecessary_safety_comment<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, @@ -505,7 +511,8 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf }, Node::Stmt(stmt) => { if let Node::Block(block) = cx.tcx.parent_hir_node(stmt.hir_id) { - walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo) + walk_span_to_context(block.span, SyntaxContext::root()) + .map(|sp| CommentStartBeforeItem::Offset(sp.lo())) } else { // Problem getting the parent node. Pretend a comment was found. return HasSafetyComment::Maybe; @@ -518,10 +525,12 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf }; let source_map = cx.sess().source_map(); + // If the comment is in the first line of the file, there is no preceding line if let Some(comment_start) = comment_start && let Ok(unsafe_line) = source_map.lookup_line(item.span.lo()) - && let Ok(comment_start_line) = source_map.lookup_line(comment_start) - && Arc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf) + && let Ok(comment_start_line) = source_map.lookup_line(comment_start.into()) + && let include_first_line_of_file = matches!(comment_start, CommentStartBeforeItem::Start) + && (include_first_line_of_file || Arc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)) && let Some(src) = unsafe_line.sf.src.as_deref() { return if comment_start_line.line >= unsafe_line.line { @@ -529,7 +538,8 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf } else { match text_has_safety_comment( src, - &unsafe_line.sf.lines()[comment_start_line.line + 1..=unsafe_line.line], + &unsafe_line.sf.lines() + [(comment_start_line.line + usize::from(!include_first_line_of_file))..=unsafe_line.line], unsafe_line.sf.start_pos, ) { Some(b) => HasSafetyComment::Yes(b), @@ -592,12 +602,27 @@ fn stmt_has_safety_comment( HasSafetyComment::Maybe } +#[derive(Clone, Copy, Debug)] +enum CommentStartBeforeItem { + Offset(BytePos), + Start, +} + +impl From for BytePos { + fn from(value: CommentStartBeforeItem) -> Self { + match value { + CommentStartBeforeItem::Offset(loc) => loc, + CommentStartBeforeItem::Start => BytePos(0), + } + } +} + fn comment_start_before_item_in_mod( cx: &LateContext<'_>, parent_mod: &hir::Mod<'_>, parent_mod_span: Span, item: &hir::Item<'_>, -) -> Option { +) -> Option { parent_mod.item_ids.iter().enumerate().find_map(|(idx, item_id)| { if *item_id == item.item_id() { if idx == 0 { @@ -605,15 +630,18 @@ fn comment_start_before_item_in_mod( // ^------------------------------------------^ returns the start of this span // ^---------------------^ finally checks comments in this range if let Some(sp) = walk_span_to_context(parent_mod_span, SyntaxContext::root()) { - return Some(sp.lo()); + return Some(CommentStartBeforeItem::Offset(sp.lo())); } } else { // some_item /* comment */ unsafe impl T {} // ^-------^ returns the end of this span // ^---------------^ finally checks comments in this range let prev_item = cx.tcx.hir_item(parent_mod.item_ids[idx - 1]); + if prev_item.span.is_dummy() { + return Some(CommentStartBeforeItem::Start); + } if let Some(sp) = walk_span_to_context(prev_item.span, SyntaxContext::root()) { - return Some(sp.hi()); + return Some(CommentStartBeforeItem::Offset(sp.hi())); } } } @@ -668,7 +696,7 @@ fn get_body_search_span(cx: &LateContext<'_>) -> Option { }) => { return maybe_mod_item .and_then(|item| comment_start_before_item_in_mod(cx, mod_, *span, &item)) - .map(|comment_start| mod_.spans.inner_span.with_lo(comment_start)) + .map(|comment_start| mod_.spans.inner_span.with_lo(comment_start.into())) .or(Some(*span)); }, node if let Some((span, _)) = span_and_hid_of_item_alike_node(&node) diff --git a/clippy_lints/src/unnecessary_box_returns.rs b/clippy_lints/src/unnecessary_box_returns.rs index 2b7d3dc0c90a..6e3e41f08ee5 100644 --- a/clippy_lints/src/unnecessary_box_returns.rs +++ b/clippy_lints/src/unnecessary_box_returns.rs @@ -55,7 +55,7 @@ impl UnnecessaryBoxReturns { } } - fn check_fn_item(&mut self, cx: &LateContext<'_>, decl: &FnDecl<'_>, def_id: LocalDefId, name: Symbol) { + fn check_fn_item(&self, cx: &LateContext<'_>, decl: &FnDecl<'_>, def_id: LocalDefId, name: Symbol) { // we don't want to tell someone to break an exported function if they ask us not to if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) { return; diff --git a/clippy_lints/src/unnecessary_semicolon.rs b/clippy_lints/src/unnecessary_semicolon.rs index f1d1a76d0c2d..76e24b6bf805 100644 --- a/clippy_lints/src/unnecessary_semicolon.rs +++ b/clippy_lints/src/unnecessary_semicolon.rs @@ -86,7 +86,9 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessarySemicolon { expr.kind, ExprKind::If(..) | ExprKind::Match(_, _, MatchSource::Normal | MatchSource::Postfix) ) - && cx.typeck_results().expr_ty(expr) == cx.tcx.types.unit + && cx.typeck_results().expr_ty(expr).is_unit() + // if a stmt has attrs, then turning it into an expr will break the code, since attrs aren't allowed on exprs + && cx.tcx.hir_attrs(stmt.hir_id).is_empty() { if let Some(block_is_unit) = self.is_last_in_block(stmt) { if cx.tcx.sess.edition() <= Edition2021 && leaks_droppable_temporary_with_limited_lifetime(cx, expr) { diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs index e9ad578da2f5..8b278d98a30e 100644 --- a/clippy_lints/src/unnested_or_patterns.rs +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -284,14 +284,14 @@ fn transform_with_focus_on_idx(alternatives: &mut ThinVec>, focus_idx: |k, ps1, idx| matches!( k, TupleStruct(qself2, path2, ps2) - if eq_maybe_qself(qself1.as_ref(), qself2.as_ref()) + if eq_maybe_qself(qself1.as_deref(), qself2.as_deref()) && eq_path(path1, path2) && eq_pre_post(ps1, ps2, idx) ), |k| always_pat!(k, TupleStruct(_, _, ps) => ps), ), // Transform a record pattern `S { fp_0, ..., fp_n }`. Struct(qself1, path1, fps1, rest1) => { - extend_with_struct_pat(qself1.as_ref(), path1, fps1, *rest1, start, alternatives) + extend_with_struct_pat(qself1.as_deref(), path1, fps1, *rest1, start, alternatives) }, }; @@ -304,7 +304,7 @@ fn transform_with_focus_on_idx(alternatives: &mut ThinVec>, focus_idx: /// So when we fixate on some `ident_k: pat_k`, we try to find `ident_k` in the other pattern /// and check that all `fp_i` where `i ∈ ((0...n) \ k)` between two patterns are equal. fn extend_with_struct_pat( - qself1: Option<&Box>, + qself1: Option<&ast::QSelf>, path1: &ast::Path, fps1: &mut [ast::PatField], rest1: ast::PatFieldsRest, @@ -319,7 +319,7 @@ fn extend_with_struct_pat( |k| { matches!(k, Struct(qself2, path2, fps2, rest2) if rest1 == *rest2 // If one struct pattern has `..` so must the other. - && eq_maybe_qself(qself1, qself2.as_ref()) + && eq_maybe_qself(qself1, qself2.as_deref()) && eq_path(path1, path2) && fps1.len() == fps2.len() && fps1.iter().enumerate().all(|(idx_1, fp1)| { diff --git a/clippy_lints/src/unwrap.rs b/clippy_lints/src/unwrap.rs index c641d4e55b94..490da4f1e037 100644 --- a/clippy_lints/src/unwrap.rs +++ b/clippy_lints/src/unwrap.rs @@ -141,43 +141,45 @@ fn collect_unwrap_info<'tcx>( is_type_diagnostic_item(cx, ty, sym::Result) && matches!(method_name, sym::is_err | sym::is_ok) } - if let ExprKind::Binary(op, left, right) = &expr.kind { - match (invert, op.node) { - (false, BinOpKind::And | BinOpKind::BitAnd) | (true, BinOpKind::Or | BinOpKind::BitOr) => { - let mut unwrap_info = collect_unwrap_info(cx, if_expr, left, branch, invert, false); - unwrap_info.append(&mut collect_unwrap_info(cx, if_expr, right, branch, invert, false)); - return unwrap_info; - }, - _ => (), - } - } else if let ExprKind::Unary(UnOp::Not, expr) = &expr.kind { - return collect_unwrap_info(cx, if_expr, expr, branch, !invert, false); - } else if let ExprKind::MethodCall(method_name, receiver, [], _) = &expr.kind - && let Some(local_id) = path_to_local(receiver) - && let ty = cx.typeck_results().expr_ty(receiver) - && let name = method_name.ident.name - && (is_relevant_option_call(cx, ty, name) || is_relevant_result_call(cx, ty, name)) - { - let unwrappable = matches!(name, sym::is_some | sym::is_ok); - let safe_to_unwrap = unwrappable != invert; - let kind = if is_type_diagnostic_item(cx, ty, sym::Option) { - UnwrappableKind::Option - } else { - UnwrappableKind::Result - }; + match expr.kind { + ExprKind::Binary(op, left, right) + if matches!( + (invert, op.node), + (false, BinOpKind::And | BinOpKind::BitAnd) | (true, BinOpKind::Or | BinOpKind::BitOr) + ) => + { + let mut unwrap_info = collect_unwrap_info(cx, if_expr, left, branch, invert, false); + unwrap_info.extend(collect_unwrap_info(cx, if_expr, right, branch, invert, false)); + unwrap_info + }, + ExprKind::Unary(UnOp::Not, expr) => collect_unwrap_info(cx, if_expr, expr, branch, !invert, false), + ExprKind::MethodCall(method_name, receiver, [], _) + if let Some(local_id) = path_to_local(receiver) + && let ty = cx.typeck_results().expr_ty(receiver) + && let name = method_name.ident.name + && (is_relevant_option_call(cx, ty, name) || is_relevant_result_call(cx, ty, name)) => + { + let unwrappable = matches!(name, sym::is_some | sym::is_ok); + let safe_to_unwrap = unwrappable != invert; + let kind = if is_type_diagnostic_item(cx, ty, sym::Option) { + UnwrappableKind::Option + } else { + UnwrappableKind::Result + }; - return vec![UnwrapInfo { - local_id, - if_expr, - check: expr, - check_name: name, - branch, - safe_to_unwrap, - kind, - is_entire_condition, - }]; + vec![UnwrapInfo { + local_id, + if_expr, + check: expr, + check_name: name, + branch, + safe_to_unwrap, + kind, + is_entire_condition, + }] + }, + _ => vec![], } - Vec::new() } /// A HIR visitor delegate that checks if a local variable of type `Option` or `Result` is mutated, diff --git a/clippy_lints/src/zero_sized_map_values.rs b/clippy_lints/src/zero_sized_map_values.rs index 1550872bca2b..f1572fd65bbf 100644 --- a/clippy_lints/src/zero_sized_map_values.rs +++ b/clippy_lints/src/zero_sized_map_values.rs @@ -56,6 +56,9 @@ impl LateLintPass<'_> for ZeroSizedMapValues { // cannot check if it is `Sized` or not, such as an incomplete associated type in a // type alias. See an example in `issue14822()` of `tests/ui/zero_sized_hashmap_values.rs`. && !ty.has_non_region_param() + // Ensure that no region escapes to avoid an assertion error when computing the layout. + // See an example in `issue15429()` of `tests/ui/zero_sized_hashmap_values.rs`. + && !ty.has_escaping_bound_vars() && let Ok(layout) = cx.layout_of(ty) && layout.is_zst() { diff --git a/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs b/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs index 5e6a40ac2eb6..0fd1e11b0333 100644 --- a/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs +++ b/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs @@ -6,7 +6,8 @@ use rustc_hir::attrs::AttributeKind; use rustc_hir::def::Res; use rustc_hir::def_id::LocalDefId; use rustc_hir::{ - AttrArgs, AttrItem, AttrPath, Attribute, HirId, Impl, Item, ItemKind, Path, QPath, TraitRef, Ty, TyKind, find_attr, + AttrArgs, AttrItem, AttrPath, Attribute, HirId, Impl, Item, ItemKind, Path, QPath, TraitImplHeader, TraitRef, Ty, + TyKind, find_attr, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_lint_defs::declare_tool_lint; @@ -56,10 +57,14 @@ impl<'tcx> LateLintPass<'tcx> for DeriveDeserializeAllowingUnknown { // Is this an `impl` (of a certain form)? let ItemKind::Impl(Impl { of_trait: - Some(TraitRef { - path: - Path { - res: Res::Def(_, trait_def_id), + Some(TraitImplHeader { + trait_ref: + TraitRef { + path: + Path { + res: Res::Def(_, trait_def_id), + .. + }, .. }, .. diff --git a/clippy_test_deps/Cargo.lock b/clippy_test_deps/Cargo.lock index 2f987c0137c9..b22cf9d107d7 100644 --- a/clippy_test_deps/Cargo.lock +++ b/clippy_test_deps/Cargo.lock @@ -377,9 +377,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" diff --git a/clippy_utils/README.md b/clippy_utils/README.md index 6d8dd92d55d6..2dfe28953d0c 100644 --- a/clippy_utils/README.md +++ b/clippy_utils/README.md @@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain: ``` -nightly-2025-08-07 +nightly-2025-08-22 ``` diff --git a/clippy_utils/src/ast_utils/mod.rs b/clippy_utils/src/ast_utils/mod.rs index d80a9d9dd07d..40c00568a3bd 100644 --- a/clippy_utils/src/ast_utils/mod.rs +++ b/clippy_utils/src/ast_utils/mod.rs @@ -41,21 +41,23 @@ pub fn eq_pat(l: &Pat, r: &Pat) -> bool { b1 == b2 && eq_id(*i1, *i2) && both(s1.as_deref(), s2.as_deref(), eq_pat) }, (Range(lf, lt, le), Range(rf, rt, re)) => { - eq_expr_opt(lf.as_ref(), rf.as_ref()) - && eq_expr_opt(lt.as_ref(), rt.as_ref()) + eq_expr_opt(lf.as_deref(), rf.as_deref()) + && eq_expr_opt(lt.as_deref(), rt.as_deref()) && eq_range_end(&le.node, &re.node) }, (Box(l), Box(r)) | (Ref(l, Mutability::Not), Ref(r, Mutability::Not)) | (Ref(l, Mutability::Mut), Ref(r, Mutability::Mut)) => eq_pat(l, r), (Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, |l, r| eq_pat(l, r)), - (Path(lq, lp), Path(rq, rp)) => both(lq.as_ref(), rq.as_ref(), eq_qself) && eq_path(lp, rp), + (Path(lq, lp), Path(rq, rp)) => both(lq.as_deref(), rq.as_deref(), eq_qself) && eq_path(lp, rp), (TupleStruct(lqself, lp, lfs), TupleStruct(rqself, rp, rfs)) => { - eq_maybe_qself(lqself.as_ref(), rqself.as_ref()) && eq_path(lp, rp) && over(lfs, rfs, |l, r| eq_pat(l, r)) + eq_maybe_qself(lqself.as_deref(), rqself.as_deref()) + && eq_path(lp, rp) + && over(lfs, rfs, |l, r| eq_pat(l, r)) }, (Struct(lqself, lp, lfs, lr), Struct(rqself, rp, rfs, rr)) => { lr == rr - && eq_maybe_qself(lqself.as_ref(), rqself.as_ref()) + && eq_maybe_qself(lqself.as_deref(), rqself.as_deref()) && eq_path(lp, rp) && unordered_over(lfs, rfs, eq_field_pat) }, @@ -82,11 +84,11 @@ pub fn eq_field_pat(l: &PatField, r: &PatField) -> bool { && over(&l.attrs, &r.attrs, eq_attr) } -pub fn eq_qself(l: &Box, r: &Box) -> bool { +pub fn eq_qself(l: &QSelf, r: &QSelf) -> bool { l.position == r.position && eq_ty(&l.ty, &r.ty) } -pub fn eq_maybe_qself(l: Option<&Box>, r: Option<&Box>) -> bool { +pub fn eq_maybe_qself(l: Option<&QSelf>, r: Option<&QSelf>) -> bool { match (l, r) { (Some(l), Some(r)) => eq_qself(l, r), (None, None) => true, @@ -129,8 +131,8 @@ pub fn eq_generic_arg(l: &GenericArg, r: &GenericArg) -> bool { } } -pub fn eq_expr_opt(l: Option<&Box>, r: Option<&Box>) -> bool { - both(l, r, |l, r| eq_expr(l, r)) +pub fn eq_expr_opt(l: Option<&Expr>, r: Option<&Expr>) -> bool { + both(l, r, eq_expr) } pub fn eq_struct_rest(l: &StructRest, r: &StructRest) -> bool { @@ -177,7 +179,7 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool { (Cast(l, lt), Cast(r, rt)) | (Type(l, lt), Type(r, rt)) => eq_expr(l, r) && eq_ty(lt, rt), (Let(lp, le, _, _), Let(rp, re, _, _)) => eq_pat(lp, rp) && eq_expr(le, re), (If(lc, lt, le), If(rc, rt, re)) => { - eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le.as_ref(), re.as_ref()) + eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le.as_deref(), re.as_deref()) }, (While(lc, lt, ll), While(rc, rt, rl)) => { eq_label(ll.as_ref(), rl.as_ref()) && eq_expr(lc, rc) && eq_block(lt, rt) @@ -201,9 +203,11 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool { (Loop(lt, ll, _), Loop(rt, rl, _)) => eq_label(ll.as_ref(), rl.as_ref()) && eq_block(lt, rt), (Block(lb, ll), Block(rb, rl)) => eq_label(ll.as_ref(), rl.as_ref()) && eq_block(lb, rb), (TryBlock(l), TryBlock(r)) => eq_block(l, r), - (Yield(l), Yield(r)) => eq_expr_opt(l.expr(), r.expr()) && l.same_kind(r), - (Ret(l), Ret(r)) => eq_expr_opt(l.as_ref(), r.as_ref()), - (Break(ll, le), Break(rl, re)) => eq_label(ll.as_ref(), rl.as_ref()) && eq_expr_opt(le.as_ref(), re.as_ref()), + (Yield(l), Yield(r)) => eq_expr_opt(l.expr().map(Box::as_ref), r.expr().map(Box::as_ref)) && l.same_kind(r), + (Ret(l), Ret(r)) => eq_expr_opt(l.as_deref(), r.as_deref()), + (Break(ll, le), Break(rl, re)) => { + eq_label(ll.as_ref(), rl.as_ref()) && eq_expr_opt(le.as_deref(), re.as_deref()) + }, (Continue(ll), Continue(rl)) => eq_label(ll.as_ref(), rl.as_ref()), (Assign(l1, l2, _), Assign(r1, r2, _)) | (Index(l1, l2, _), Index(r1, r2, _)) => { eq_expr(l1, r1) && eq_expr(l2, r2) @@ -240,13 +244,13 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool { }, (Gen(lc, lb, lk, _), Gen(rc, rb, rk, _)) => lc == rc && eq_block(lb, rb) && lk == rk, (Range(lf, lt, ll), Range(rf, rt, rl)) => { - ll == rl && eq_expr_opt(lf.as_ref(), rf.as_ref()) && eq_expr_opt(lt.as_ref(), rt.as_ref()) + ll == rl && eq_expr_opt(lf.as_deref(), rf.as_deref()) && eq_expr_opt(lt.as_deref(), rt.as_deref()) }, (AddrOf(lbk, lm, le), AddrOf(rbk, rm, re)) => lbk == rbk && lm == rm && eq_expr(le, re), - (Path(lq, lp), Path(rq, rp)) => both(lq.as_ref(), rq.as_ref(), eq_qself) && eq_path(lp, rp), + (Path(lq, lp), Path(rq, rp)) => both(lq.as_deref(), rq.as_deref(), eq_qself) && eq_path(lp, rp), (MacCall(l), MacCall(r)) => eq_mac_call(l, r), (Struct(lse), Struct(rse)) => { - eq_maybe_qself(lse.qself.as_ref(), rse.qself.as_ref()) + eq_maybe_qself(lse.qself.as_deref(), rse.qself.as_deref()) && eq_path(&lse.path, &rse.path) && eq_struct_rest(&lse.rest, &rse.rest) && unordered_over(&lse.fields, &rse.fields, eq_field) @@ -278,8 +282,8 @@ pub fn eq_field(l: &ExprField, r: &ExprField) -> bool { pub fn eq_arm(l: &Arm, r: &Arm) -> bool { l.is_placeholder == r.is_placeholder && eq_pat(&l.pat, &r.pat) - && eq_expr_opt(l.body.as_ref(), r.body.as_ref()) - && eq_expr_opt(l.guard.as_ref(), r.guard.as_ref()) + && eq_expr_opt(l.body.as_deref(), r.body.as_deref()) + && eq_expr_opt(l.guard.as_deref(), r.guard.as_deref()) && over(&l.attrs, &r.attrs, eq_attr) } @@ -324,7 +328,7 @@ pub fn eq_item(l: &Item, r: &Item, mut eq_kind: impl FnMut(&K, &K) -> b over(&l.attrs, &r.attrs, eq_attr) && eq_vis(&l.vis, &r.vis) && eq_kind(&l.kind, &r.kind) } -#[expect(clippy::similar_names, clippy::too_many_lines)] // Just a big match statement +#[expect(clippy::too_many_lines)] // Just a big match statement pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { use ItemKind::*; match (l, r) { @@ -347,7 +351,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { safety: rs, define_opaque: _, }), - ) => eq_id(*li, *ri) && lm == rm && ls == rs && eq_ty(lt, rt) && eq_expr_opt(le.as_ref(), re.as_ref()), + ) => eq_id(*li, *ri) && lm == rm && ls == rs && eq_ty(lt, rt) && eq_expr_opt(le.as_deref(), re.as_deref()), ( Const(box ConstItem { defaultness: ld, @@ -370,7 +374,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { && eq_id(*li, *ri) && eq_generics(lg, rg) && eq_ty(lt, rt) - && eq_expr_opt(le.as_ref(), re.as_ref()) + && eq_expr_opt(le.as_deref(), re.as_deref()) }, ( Fn(box ast::Fn { @@ -525,7 +529,7 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool { safety: rs, define_opaque: _, }), - ) => eq_id(*li, *ri) && eq_ty(lt, rt) && lm == rm && eq_expr_opt(le.as_ref(), re.as_ref()) && ls == rs, + ) => eq_id(*li, *ri) && eq_ty(lt, rt) && lm == rm && eq_expr_opt(le.as_deref(), re.as_deref()) && ls == rs, ( Fn(box ast::Fn { defaultness: ld, @@ -607,7 +611,7 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { && eq_id(*li, *ri) && eq_generics(lg, rg) && eq_ty(lt, rt) - && eq_expr_opt(le.as_ref(), re.as_ref()) + && eq_expr_opt(le.as_deref(), re.as_deref()) }, ( Fn(box ast::Fn { @@ -723,7 +727,8 @@ pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool { pub fn eq_opt_fn_contract(l: &Option>, r: &Option>) -> bool { match (l, r) { (Some(l), Some(r)) => { - eq_expr_opt(l.requires.as_ref(), r.requires.as_ref()) && eq_expr_opt(l.ensures.as_ref(), r.ensures.as_ref()) + eq_expr_opt(l.requires.as_deref(), r.requires.as_deref()) + && eq_expr_opt(l.ensures.as_deref(), r.ensures.as_deref()) }, (None, None) => true, (Some(_), None) | (None, Some(_)) => false, @@ -841,7 +846,7 @@ pub fn eq_ty(l: &Ty, r: &Ty) -> bool { && eq_fn_decl(&l.decl, &r.decl) }, (Tup(l), Tup(r)) => over(l, r, |l, r| eq_ty(l, r)), - (Path(lq, lp), Path(rq, rp)) => both(lq.as_ref(), rq.as_ref(), eq_qself) && eq_path(lp, rp), + (Path(lq, lp), Path(rq, rp)) => both(lq.as_deref(), rq.as_deref(), eq_qself) && eq_path(lp, rp), (TraitObject(lg, ls), TraitObject(rg, rs)) => ls == rs && over(lg, rg, eq_generic_bound), (ImplTrait(_, lg), ImplTrait(_, rg)) => over(lg, rg, eq_generic_bound), (Typeof(l), Typeof(r)) => eq_expr(&l.value, &r.value), diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs index e0c1b9d445a2..c4a759e919b1 100644 --- a/clippy_utils/src/check_proc_macro.rs +++ b/clippy_utils/src/check_proc_macro.rs @@ -19,8 +19,8 @@ use rustc_ast::token::CommentKind; use rustc_hir::intravisit::FnKind; use rustc_hir::{ Block, BlockCheckMode, Body, Closure, Destination, Expr, ExprKind, FieldDef, FnHeader, FnRetTy, HirId, Impl, - ImplItem, ImplItemKind, TraitImplHeader, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, Path, - QPath, Safety, TraitItem, TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, YieldSource, + ImplItem, ImplItemKind, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, Path, QPath, Safety, + TraitImplHeader, TraitItem, TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, YieldSource, }; use rustc_lint::{EarlyContext, LateContext, LintContext}; use rustc_middle::ty::TyCtxt; @@ -254,7 +254,10 @@ fn item_search_pat(item: &Item<'_>) -> (Pat, Pat) { ItemKind::Union(..) => (Pat::Str("union"), Pat::Str("}")), ItemKind::Trait(_, _, Safety::Unsafe, ..) | ItemKind::Impl(Impl { - of_trait: Some(TraitImplHeader { safety: Safety::Unsafe, .. }), .. + of_trait: Some(TraitImplHeader { + safety: Safety::Unsafe, .. + }), + .. }) => (Pat::Str("unsafe"), Pat::Str("}")), ItemKind::Trait(_, IsAuto::Yes, ..) => (Pat::Str("auto"), Pat::Str("}")), ItemKind::Trait(..) => (Pat::Str("trait"), Pat::Str("}")), diff --git a/clippy_utils/src/diagnostics.rs b/clippy_utils/src/diagnostics.rs index 625e1eead213..8a19039a7fe7 100644 --- a/clippy_utils/src/diagnostics.rs +++ b/clippy_utils/src/diagnostics.rs @@ -22,13 +22,14 @@ fn docs_link(diag: &mut Diag<'_, ()>, lint: &'static Lint) { { diag.help(format!( "for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{lint}", - &option_env!("RUST_RELEASE_NUM").map_or_else( - || "master".to_string(), - |n| { - // extract just major + minor version and ignore patch versions - format!("rust-{}", n.rsplit_once('.').unwrap().1) - } - ) + match option_env!("CFG_RELEASE_CHANNEL") { + // Clippy version is 0.1.xx + // + // Always use .0 because we do not generate separate lint doc pages for rust patch releases + Some("stable") => concat!("rust-1.", env!("CARGO_PKG_VERSION_PATCH"), ".0"), + Some("beta") => "beta", + _ => "master", + } )); } } diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index f0d7fb89c446..8160443f4132 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -258,7 +258,7 @@ impl HirEqInterExpr<'_, '_, '_> { }) } - fn should_ignore(&mut self, expr: &Expr<'_>) -> bool { + fn should_ignore(&self, expr: &Expr<'_>) -> bool { macro_backtrace(expr.span).last().is_some_and(|macro_call| { matches!( self.inner.cx.tcx.get_diagnostic_name(macro_call.def_id), diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index fcc120656e3e..8533fa855419 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -460,6 +460,23 @@ pub fn path_to_local_id(expr: &Expr<'_>, id: HirId) -> bool { path_to_local(expr) == Some(id) } +/// If the expression is a path to a local (with optional projections), +/// returns the canonical `HirId` of the local. +/// +/// For example, `x.field[0].field2` would return the `HirId` of `x`. +pub fn path_to_local_with_projections(expr: &Expr<'_>) -> Option { + match expr.kind { + ExprKind::Field(recv, _) | ExprKind::Index(recv, _, _) => path_to_local_with_projections(recv), + ExprKind::Path(QPath::Resolved( + _, + Path { + res: Res::Local(local), .. + }, + )) => Some(*local), + _ => None, + } +} + pub trait MaybePath<'hir> { fn hir_id(&self) -> HirId; fn qpath_opt(&self) -> Option<&QPath<'hir>>; diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index 89a83e2c48f9..896d607fbcdd 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -189,25 +189,25 @@ impl MsrvStack { fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt]) -> Option { let mut msrv_attrs = attrs.iter().filter(|attr| attr.path_matches(&[sym::clippy, sym::msrv])); - if let Some(msrv_attr) = msrv_attrs.next() { - if let Some(duplicate) = msrv_attrs.next_back() { - sess.dcx() - .struct_span_err(duplicate.span(), "`clippy::msrv` is defined multiple times") - .with_span_note(msrv_attr.span(), "first definition found here") - .emit(); - } + let msrv_attr = msrv_attrs.next()?; - if let Some(msrv) = msrv_attr.value_str() { - if let Some(version) = parse_version(msrv) { - return Some(version); - } - - sess.dcx() - .span_err(msrv_attr.span(), format!("`{msrv}` is not a valid Rust version")); - } else { - sess.dcx().span_err(msrv_attr.span(), "bad clippy attribute"); - } + if let Some(duplicate) = msrv_attrs.next_back() { + sess.dcx() + .struct_span_err(duplicate.span(), "`clippy::msrv` is defined multiple times") + .with_span_note(msrv_attr.span(), "first definition found here") + .emit(); } - None + let Some(msrv) = msrv_attr.value_str() else { + sess.dcx().span_err(msrv_attr.span(), "bad clippy attribute"); + return None; + }; + + let Some(version) = parse_version(msrv) else { + sess.dcx() + .span_err(msrv_attr.span(), format!("`{msrv}` is not a valid Rust version")); + return None; + }; + + Some(version) } diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index d79773f83211..fafc1d07e51e 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -18,6 +18,7 @@ use rustc_lint::LateContext; use rustc_middle::mir::ConstValue; use rustc_middle::mir::interpret::Scalar; use rustc_middle::traits::EvaluationResult; +use rustc_middle::ty::adjustment::{Adjust, Adjustment}; use rustc_middle::ty::layout::ValidityRequirement; use rustc_middle::ty::{ self, AdtDef, AliasTy, AssocItem, AssocTag, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef, @@ -31,7 +32,7 @@ use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt; use rustc_trait_selection::traits::{Obligation, ObligationCause}; use std::assert_matches::debug_assert_matches; use std::collections::hash_map::Entry; -use std::iter; +use std::{iter, mem}; use crate::path_res; use crate::paths::{PathNS, lookup_path_str}; @@ -1382,7 +1383,6 @@ pub fn is_slice_like<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { || matches!(ty.kind(), ty::Adt(adt_def, _) if cx.tcx.is_diagnostic_item(sym::Vec, adt_def.did())) } -/// Gets the index of a field by name. pub fn get_field_idx_by_name(ty: Ty<'_>, name: Symbol) -> Option { match *ty.kind() { ty::Adt(def, _) if def.is_union() || def.is_struct() => { @@ -1392,3 +1392,11 @@ pub fn get_field_idx_by_name(ty: Ty<'_>, name: Symbol) -> Option { _ => None, } } + +/// Checks if the adjustments contain a mutable dereference of a `ManuallyDrop<_>`. +pub fn adjust_derefs_manually_drop<'tcx>(adjustments: &'tcx [Adjustment<'tcx>], mut ty: Ty<'tcx>) -> bool { + adjustments.iter().any(|a| { + let ty = mem::replace(&mut ty, a.target); + matches!(a.kind, Adjust::Deref(Some(op)) if op.mutbl == Mutability::Mut) && is_manually_drop(ty) + }) +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ac51ec2d61b5..5497e77e8ad1 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2025-08-07" +channel = "nightly-2025-08-22" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.stderr b/tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.stderr new file mode 100644 index 000000000000..59a7146ac90f --- /dev/null +++ b/tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.stderr @@ -0,0 +1,26 @@ +error: module has unnecessary safety comment + --> src/main.rs:2:1 + | +2 | mod x {} + | ^^^^^^^^ + | +help: consider removing the safety comment + --> src/main.rs:1:1 + | +1 | // SAFETY: ... + | ^^^^^^^^^^^^^^ + = note: requested on the command line with `-D clippy::unnecessary-safety-comment` + +error: module has unnecessary safety comment + --> src/main.rs:5:1 + | +5 | mod y {} + | ^^^^^^^^ + | +help: consider removing the safety comment + --> src/main.rs:4:1 + | +4 | // SAFETY: ... + | ^^^^^^^^^^^^^^ + +error: could not compile `undocumented_unsafe_blocks` (bin "undocumented_unsafe_blocks") due to 2 previous errors diff --git a/tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.toml b/tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.toml new file mode 100644 index 000000000000..36bb3472df07 --- /dev/null +++ b/tests/ui-cargo/undocumented_unsafe_blocks/fail/Cargo.toml @@ -0,0 +1,12 @@ +# Reproducing #14553 requires the `# Safety` comment to be in the first line of +# the file. Since `unnecessary_safety_comment` is not enabled by default, we +# will set it up here. + +[package] +name = "undocumented_unsafe_blocks" +edition = "2024" +publish = false +version = "0.1.0" + +[lints.clippy] +unnecessary_safety_comment = "deny" diff --git a/tests/ui-cargo/undocumented_unsafe_blocks/fail/src/main.rs b/tests/ui-cargo/undocumented_unsafe_blocks/fail/src/main.rs new file mode 100644 index 000000000000..5cafcff99ddc --- /dev/null +++ b/tests/ui-cargo/undocumented_unsafe_blocks/fail/src/main.rs @@ -0,0 +1,7 @@ +// SAFETY: ... +mod x {} + +// SAFETY: ... +mod y {} + +fn main() {} diff --git a/tests/ui-toml/functions_maxlines/test.stderr b/tests/ui-toml/functions_maxlines/test.stderr index 14a49cb76c1c..e856963c87d1 100644 --- a/tests/ui-toml/functions_maxlines/test.stderr +++ b/tests/ui-toml/functions_maxlines/test.stderr @@ -1,12 +1,8 @@ error: this function has too many lines (2/1) --> tests/ui-toml/functions_maxlines/test.rs:19:1 | -LL | / fn too_many_lines() { -LL | | -LL | | println!("This is bad."); -LL | | println!("This is bad."); -LL | | } - | |_^ +LL | fn too_many_lines() { + | ^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::too-many-lines` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::too_many_lines)]` @@ -14,35 +10,20 @@ LL | | } error: this function has too many lines (4/1) --> tests/ui-toml/functions_maxlines/test.rs:26:1 | -LL | / async fn async_too_many_lines() { -LL | | -LL | | println!("This is bad."); -LL | | println!("This is bad."); -LL | | } - | |_^ +LL | async fn async_too_many_lines() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this function has too many lines (4/1) --> tests/ui-toml/functions_maxlines/test.rs:33:1 | -LL | / fn closure_too_many_lines() { -LL | | -LL | | let _ = { -LL | | println!("This is bad."); -LL | | println!("This is bad."); -LL | | }; -LL | | } - | |_^ +LL | fn closure_too_many_lines() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this function has too many lines (2/1) --> tests/ui-toml/functions_maxlines/test.rs:56:1 | -LL | / fn comment_before_code() { -LL | | -LL | | let _ = "test"; -LL | | /* This comment extends to the front of -LL | | the code but this line should still count. */ let _ = 5; -LL | | } - | |_^ +LL | fn comment_before_code() { + | ^^^^^^^^^^^^^^^^^^^^^^^^ error: aborting due to 4 previous errors diff --git a/tests/ui/as_ptr_cast_mut.fixed b/tests/ui/as_ptr_cast_mut.fixed new file mode 100644 index 000000000000..fe9c5dca5ba5 --- /dev/null +++ b/tests/ui/as_ptr_cast_mut.fixed @@ -0,0 +1,38 @@ +#![allow(unused)] +#![warn(clippy::as_ptr_cast_mut)] +#![allow(clippy::wrong_self_convention, clippy::unnecessary_cast)] + +struct MutPtrWrapper(Vec); +impl MutPtrWrapper { + fn as_ptr(&mut self) -> *const u8 { + self.0.as_mut_ptr() as *const u8 + } +} + +struct Covariant(*const T); +impl Covariant { + fn as_ptr(self) -> *const T { + self.0 + } +} + +fn main() { + let mut string = String::new(); + let _ = string.as_mut_ptr(); + //~^ as_ptr_cast_mut + + let _ = string.as_ptr() as *const i8; + let _ = string.as_mut_ptr(); + let _ = string.as_mut_ptr() as *mut u8; + let _ = string.as_mut_ptr() as *const u8; + + let nn = std::ptr::NonNull::new(4 as *mut u8).unwrap(); + let _ = nn.as_ptr() as *mut u8; + + let mut wrap = MutPtrWrapper(Vec::new()); + let _ = wrap.as_ptr() as *mut u8; + + let mut local = 4; + let ref_with_write_perm = Covariant(std::ptr::addr_of_mut!(local) as *const _); + let _ = ref_with_write_perm.as_ptr() as *mut u8; +} diff --git a/tests/ui/as_ptr_cast_mut.rs b/tests/ui/as_ptr_cast_mut.rs index baf7279adc4a..3f22c2058d00 100644 --- a/tests/ui/as_ptr_cast_mut.rs +++ b/tests/ui/as_ptr_cast_mut.rs @@ -1,7 +1,6 @@ #![allow(unused)] #![warn(clippy::as_ptr_cast_mut)] #![allow(clippy::wrong_self_convention, clippy::unnecessary_cast)] -//@no-rustfix: incorrect suggestion struct MutPtrWrapper(Vec); impl MutPtrWrapper { @@ -22,9 +21,6 @@ fn main() { let _ = string.as_ptr() as *mut u8; //~^ as_ptr_cast_mut - let _: *mut i8 = string.as_ptr() as *mut _; - //~^ as_ptr_cast_mut - let _ = string.as_ptr() as *const i8; let _ = string.as_mut_ptr(); let _ = string.as_mut_ptr() as *mut u8; diff --git a/tests/ui/as_ptr_cast_mut.stderr b/tests/ui/as_ptr_cast_mut.stderr index b3fc223ccdba..fa9fb23e2d00 100644 --- a/tests/ui/as_ptr_cast_mut.stderr +++ b/tests/ui/as_ptr_cast_mut.stderr @@ -1,5 +1,5 @@ error: casting the result of `as_ptr` to *mut u8 - --> tests/ui/as_ptr_cast_mut.rs:22:13 + --> tests/ui/as_ptr_cast_mut.rs:21:13 | LL | let _ = string.as_ptr() as *mut u8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `string.as_mut_ptr()` @@ -7,11 +7,5 @@ LL | let _ = string.as_ptr() as *mut u8; = note: `-D clippy::as-ptr-cast-mut` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::as_ptr_cast_mut)]` -error: casting the result of `as_ptr` to *mut i8 - --> tests/ui/as_ptr_cast_mut.rs:25:22 - | -LL | let _: *mut i8 = string.as_ptr() as *mut _; - | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `string.as_mut_ptr()` - -error: aborting due to 2 previous errors +error: aborting due to 1 previous error diff --git a/tests/ui/as_ptr_cast_mut_unfixable.rs b/tests/ui/as_ptr_cast_mut_unfixable.rs new file mode 100644 index 000000000000..a8f6b06bd4fd --- /dev/null +++ b/tests/ui/as_ptr_cast_mut_unfixable.rs @@ -0,0 +1,16 @@ +//@no-rustfix +#![allow(unused)] +#![warn(clippy::as_ptr_cast_mut)] + +fn main() { + let mut string = String::new(); + + // the `*mut _` is actually necessary since it does two things at once: + // - changes the mutability (caught by the lint) + // - changes the type + // + // and so replacing this with `as_mut_ptr` removes the second thing, + // resulting in a type mismatch + let _: *mut i8 = string.as_ptr() as *mut _; + //~^ as_ptr_cast_mut +} diff --git a/tests/ui/as_ptr_cast_mut_unfixable.stderr b/tests/ui/as_ptr_cast_mut_unfixable.stderr new file mode 100644 index 000000000000..c5bcad6f4dfc --- /dev/null +++ b/tests/ui/as_ptr_cast_mut_unfixable.stderr @@ -0,0 +1,11 @@ +error: casting the result of `as_ptr` to *mut i8 + --> tests/ui/as_ptr_cast_mut_unfixable.rs:14:22 + | +LL | let _: *mut i8 = string.as_ptr() as *mut _; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `string.as_mut_ptr()` + | + = note: `-D clippy::as-ptr-cast-mut` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::as_ptr_cast_mut)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/borrow_as_ptr.fixed b/tests/ui/borrow_as_ptr.fixed index 3f6e5245b878..bfe826508f36 100644 --- a/tests/ui/borrow_as_ptr.fixed +++ b/tests/ui/borrow_as_ptr.fixed @@ -1,6 +1,9 @@ +//@aux-build:proc_macros.rs #![warn(clippy::borrow_as_ptr)] #![allow(clippy::useless_vec)] +extern crate proc_macros; + fn a() -> i32 { 0 } @@ -53,3 +56,12 @@ fn issue_15141() { // Don't lint cast to dyn trait pointers let b = &a as *const dyn std::any::Any; } + +fn issue15389() { + proc_macros::with_span! { + span + let var = 0u32; + // Don't lint in proc-macros + let _ = &var as *const u32; + }; +} diff --git a/tests/ui/borrow_as_ptr.rs b/tests/ui/borrow_as_ptr.rs index 20f4f40e0019..ce248f157c6e 100644 --- a/tests/ui/borrow_as_ptr.rs +++ b/tests/ui/borrow_as_ptr.rs @@ -1,6 +1,9 @@ +//@aux-build:proc_macros.rs #![warn(clippy::borrow_as_ptr)] #![allow(clippy::useless_vec)] +extern crate proc_macros; + fn a() -> i32 { 0 } @@ -53,3 +56,12 @@ fn issue_15141() { // Don't lint cast to dyn trait pointers let b = &a as *const dyn std::any::Any; } + +fn issue15389() { + proc_macros::with_span! { + span + let var = 0u32; + // Don't lint in proc-macros + let _ = &var as *const u32; + }; +} diff --git a/tests/ui/borrow_as_ptr.stderr b/tests/ui/borrow_as_ptr.stderr index b1fcce49403c..b371b477a50d 100644 --- a/tests/ui/borrow_as_ptr.stderr +++ b/tests/ui/borrow_as_ptr.stderr @@ -1,5 +1,5 @@ error: borrow as raw pointer - --> tests/ui/borrow_as_ptr.rs:11:14 + --> tests/ui/borrow_as_ptr.rs:14:14 | LL | let _p = &val as *const i32; | ^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::addr_of!(val)` @@ -8,25 +8,25 @@ LL | let _p = &val as *const i32; = help: to override `-D warnings` add `#[allow(clippy::borrow_as_ptr)]` error: borrow as raw pointer - --> tests/ui/borrow_as_ptr.rs:19:18 + --> tests/ui/borrow_as_ptr.rs:22:18 | LL | let _p_mut = &mut val_mut as *mut i32; | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::addr_of_mut!(val_mut)` error: borrow as raw pointer - --> tests/ui/borrow_as_ptr.rs:23:16 + --> tests/ui/borrow_as_ptr.rs:26:16 | LL | let _raw = (&mut x[1] as *mut i32).wrapping_offset(-1); | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::addr_of_mut!(x[1])` error: borrow as raw pointer - --> tests/ui/borrow_as_ptr.rs:29:17 + --> tests/ui/borrow_as_ptr.rs:32:17 | LL | let _raw = (&mut x[1] as *mut i32).wrapping_offset(-1); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `&raw mut x[1]` error: implicit borrow as raw pointer - --> tests/ui/borrow_as_ptr.rs:35:25 + --> tests/ui/borrow_as_ptr.rs:38:25 | LL | let p: *const i32 = &val; | ^^^^ @@ -37,7 +37,7 @@ LL | let p: *const i32 = &raw const val; | +++++++++ error: implicit borrow as raw pointer - --> tests/ui/borrow_as_ptr.rs:39:23 + --> tests/ui/borrow_as_ptr.rs:42:23 | LL | let p: *mut i32 = &mut val; | ^^^^^^^^ @@ -48,7 +48,7 @@ LL | let p: *mut i32 = &raw mut val; | +++ error: implicit borrow as raw pointer - --> tests/ui/borrow_as_ptr.rs:44:19 + --> tests/ui/borrow_as_ptr.rs:47:19 | LL | core::ptr::eq(&val, &1); | ^^^^ diff --git a/tests/ui/char_lit_as_u8_suggestions.fixed b/tests/ui/char_lit_as_u8.fixed similarity index 100% rename from tests/ui/char_lit_as_u8_suggestions.fixed rename to tests/ui/char_lit_as_u8.fixed diff --git a/tests/ui/char_lit_as_u8.rs b/tests/ui/char_lit_as_u8.rs index c8774c7f3091..a8f39e27605e 100644 --- a/tests/ui/char_lit_as_u8.rs +++ b/tests/ui/char_lit_as_u8.rs @@ -1,7 +1,12 @@ #![warn(clippy::char_lit_as_u8)] fn main() { - // no suggestion, since a byte literal won't work. - let _ = '❤' as u8; + let _ = 'a' as u8; + //~^ char_lit_as_u8 + let _ = '\n' as u8; + //~^ char_lit_as_u8 + let _ = '\0' as u8; + //~^ char_lit_as_u8 + let _ = '\x01' as u8; //~^ char_lit_as_u8 } diff --git a/tests/ui/char_lit_as_u8.stderr b/tests/ui/char_lit_as_u8.stderr index ec02f1341c0a..9bcded7b0ff4 100644 --- a/tests/ui/char_lit_as_u8.stderr +++ b/tests/ui/char_lit_as_u8.stderr @@ -1,12 +1,36 @@ error: casting a character literal to `u8` truncates - --> tests/ui/char_lit_as_u8.rs:5:13 + --> tests/ui/char_lit_as_u8.rs:4:13 | -LL | let _ = '❤' as u8; - | ^^^^^^^^^ +LL | let _ = 'a' as u8; + | ^^^^^^^^^ help: use a byte literal instead: `b'a'` | = note: `char` is four bytes wide, but `u8` is a single byte = note: `-D clippy::char-lit-as-u8` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::char_lit_as_u8)]` -error: aborting due to 1 previous error +error: casting a character literal to `u8` truncates + --> tests/ui/char_lit_as_u8.rs:6:13 + | +LL | let _ = '\n' as u8; + | ^^^^^^^^^^ help: use a byte literal instead: `b'\n'` + | + = note: `char` is four bytes wide, but `u8` is a single byte + +error: casting a character literal to `u8` truncates + --> tests/ui/char_lit_as_u8.rs:8:13 + | +LL | let _ = '\0' as u8; + | ^^^^^^^^^^ help: use a byte literal instead: `b'\0'` + | + = note: `char` is four bytes wide, but `u8` is a single byte + +error: casting a character literal to `u8` truncates + --> tests/ui/char_lit_as_u8.rs:10:13 + | +LL | let _ = '\x01' as u8; + | ^^^^^^^^^^^^ help: use a byte literal instead: `b'\x01'` + | + = note: `char` is four bytes wide, but `u8` is a single byte + +error: aborting due to 4 previous errors diff --git a/tests/ui/char_lit_as_u8_suggestions.rs b/tests/ui/char_lit_as_u8_suggestions.rs deleted file mode 100644 index a8f39e27605e..000000000000 --- a/tests/ui/char_lit_as_u8_suggestions.rs +++ /dev/null @@ -1,12 +0,0 @@ -#![warn(clippy::char_lit_as_u8)] - -fn main() { - let _ = 'a' as u8; - //~^ char_lit_as_u8 - let _ = '\n' as u8; - //~^ char_lit_as_u8 - let _ = '\0' as u8; - //~^ char_lit_as_u8 - let _ = '\x01' as u8; - //~^ char_lit_as_u8 -} diff --git a/tests/ui/char_lit_as_u8_suggestions.stderr b/tests/ui/char_lit_as_u8_suggestions.stderr deleted file mode 100644 index 158dfd6bed26..000000000000 --- a/tests/ui/char_lit_as_u8_suggestions.stderr +++ /dev/null @@ -1,36 +0,0 @@ -error: casting a character literal to `u8` truncates - --> tests/ui/char_lit_as_u8_suggestions.rs:4:13 - | -LL | let _ = 'a' as u8; - | ^^^^^^^^^ help: use a byte literal instead: `b'a'` - | - = note: `char` is four bytes wide, but `u8` is a single byte - = note: `-D clippy::char-lit-as-u8` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::char_lit_as_u8)]` - -error: casting a character literal to `u8` truncates - --> tests/ui/char_lit_as_u8_suggestions.rs:6:13 - | -LL | let _ = '\n' as u8; - | ^^^^^^^^^^ help: use a byte literal instead: `b'\n'` - | - = note: `char` is four bytes wide, but `u8` is a single byte - -error: casting a character literal to `u8` truncates - --> tests/ui/char_lit_as_u8_suggestions.rs:8:13 - | -LL | let _ = '\0' as u8; - | ^^^^^^^^^^ help: use a byte literal instead: `b'\0'` - | - = note: `char` is four bytes wide, but `u8` is a single byte - -error: casting a character literal to `u8` truncates - --> tests/ui/char_lit_as_u8_suggestions.rs:10:13 - | -LL | let _ = '\x01' as u8; - | ^^^^^^^^^^^^ help: use a byte literal instead: `b'\x01'` - | - = note: `char` is four bytes wide, but `u8` is a single byte - -error: aborting due to 4 previous errors - diff --git a/tests/ui/char_lit_as_u8_unfixable.rs b/tests/ui/char_lit_as_u8_unfixable.rs new file mode 100644 index 000000000000..e5c094f158ec --- /dev/null +++ b/tests/ui/char_lit_as_u8_unfixable.rs @@ -0,0 +1,8 @@ +//@no-rustfix +#![warn(clippy::char_lit_as_u8)] + +fn main() { + // no suggestion, since a byte literal won't work. + let _ = '❤' as u8; + //~^ char_lit_as_u8 +} diff --git a/tests/ui/char_lit_as_u8_unfixable.stderr b/tests/ui/char_lit_as_u8_unfixable.stderr new file mode 100644 index 000000000000..49e555ae638a --- /dev/null +++ b/tests/ui/char_lit_as_u8_unfixable.stderr @@ -0,0 +1,12 @@ +error: casting a character literal to `u8` truncates + --> tests/ui/char_lit_as_u8_unfixable.rs:6:13 + | +LL | let _ = '❤' as u8; + | ^^^^^^^^^ + | + = note: `char` is four bytes wide, but `u8` is a single byte + = note: `-D clippy::char-lit-as-u8` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::char_lit_as_u8)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/deref_addrof.fixed b/tests/ui/deref_addrof.fixed index 35dbd790e890..ffe7f7d14408 100644 --- a/tests/ui/deref_addrof.fixed +++ b/tests/ui/deref_addrof.fixed @@ -1,11 +1,11 @@ -//@aux-build:proc_macros.rs - -#![allow(clippy::return_self_not_must_use, clippy::useless_vec)] +#![allow( + dangerous_implicit_autorefs, + clippy::explicit_auto_deref, + clippy::return_self_not_must_use, + clippy::useless_vec +)] #![warn(clippy::deref_addrof)] -extern crate proc_macros; -use proc_macros::inline_macros; - fn get_number() -> usize { 10 } @@ -56,19 +56,75 @@ fn main() { //~^ deref_addrof // do NOT lint for array as semantic differences with/out `*&`. let _arr = *&[0, 1, 2, 3, 4]; + + // Do not lint when text comes from macro + macro_rules! mac { + (dr) => { + *&0 + }; + (dr $e:expr) => { + *&$e + }; + (r $e:expr) => { + &$e + }; + } + let b = mac!(dr); + let b = mac!(dr a); + let b = *mac!(r a); } -#[derive(Copy, Clone)] -pub struct S; -#[inline_macros] -impl S { - pub fn f(&self) -> &Self { - inline!($(@expr self)) - //~^ deref_addrof +fn issue14386() { + use std::mem::ManuallyDrop; + + #[derive(Copy, Clone)] + struct Data { + num: u64, } - #[allow(unused_mut)] // mut will be unused, once the macro is fixed - pub fn f_mut(mut self) -> Self { - inline!($(@expr self)) + + #[derive(Clone, Copy)] + struct M { + md: ManuallyDrop<[u8; 4]>, + } + + union DataWithPadding<'lt> { + data: ManuallyDrop, + prim: ManuallyDrop, + padding: [u8; size_of::()], + tup: (ManuallyDrop, ()), + indirect: M, + indirect_arr: [M; 2], + indirect_ref: &'lt mut M, + } + + let mut a = DataWithPadding { + padding: [0; size_of::()], + }; + unsafe { + a.padding = [1; size_of::()]; //~^ deref_addrof + a.tup.1 = (); + //~^ deref_addrof + *a.prim = 0; + //~^ deref_addrof + + (*a.data).num = 42; + //~^ deref_addrof + (*a.indirect.md)[3] = 1; + //~^ deref_addrof + (*a.indirect_arr[1].md)[3] = 1; + //~^ deref_addrof + (*a.indirect_ref.md)[3] = 1; + //~^ deref_addrof + + // Check that raw pointers are properly considered as well + *a.prim = 0; + //~^ deref_addrof + (*a.data).num = 42; + //~^ deref_addrof + + // Do not lint, as the dereference happens later, we cannot + // just remove `&mut` + (*&mut a.tup).0.num = 42; } } diff --git a/tests/ui/deref_addrof.rs b/tests/ui/deref_addrof.rs index 96d1b92ef7be..bc253716affd 100644 --- a/tests/ui/deref_addrof.rs +++ b/tests/ui/deref_addrof.rs @@ -1,11 +1,11 @@ -//@aux-build:proc_macros.rs - -#![allow(clippy::return_self_not_must_use, clippy::useless_vec)] +#![allow( + dangerous_implicit_autorefs, + clippy::explicit_auto_deref, + clippy::return_self_not_must_use, + clippy::useless_vec +)] #![warn(clippy::deref_addrof)] -extern crate proc_macros; -use proc_macros::inline_macros; - fn get_number() -> usize { 10 } @@ -56,19 +56,75 @@ fn main() { //~^ deref_addrof // do NOT lint for array as semantic differences with/out `*&`. let _arr = *&[0, 1, 2, 3, 4]; + + // Do not lint when text comes from macro + macro_rules! mac { + (dr) => { + *&0 + }; + (dr $e:expr) => { + *&$e + }; + (r $e:expr) => { + &$e + }; + } + let b = mac!(dr); + let b = mac!(dr a); + let b = *mac!(r a); } -#[derive(Copy, Clone)] -pub struct S; -#[inline_macros] -impl S { - pub fn f(&self) -> &Self { - inline!(*& $(@expr self)) - //~^ deref_addrof +fn issue14386() { + use std::mem::ManuallyDrop; + + #[derive(Copy, Clone)] + struct Data { + num: u64, } - #[allow(unused_mut)] // mut will be unused, once the macro is fixed - pub fn f_mut(mut self) -> Self { - inline!(*&mut $(@expr self)) + + #[derive(Clone, Copy)] + struct M { + md: ManuallyDrop<[u8; 4]>, + } + + union DataWithPadding<'lt> { + data: ManuallyDrop, + prim: ManuallyDrop, + padding: [u8; size_of::()], + tup: (ManuallyDrop, ()), + indirect: M, + indirect_arr: [M; 2], + indirect_ref: &'lt mut M, + } + + let mut a = DataWithPadding { + padding: [0; size_of::()], + }; + unsafe { + (*&mut a.padding) = [1; size_of::()]; //~^ deref_addrof + (*&mut a.tup).1 = (); + //~^ deref_addrof + **&mut a.prim = 0; + //~^ deref_addrof + + (*&mut a.data).num = 42; + //~^ deref_addrof + (*&mut a.indirect.md)[3] = 1; + //~^ deref_addrof + (*&mut a.indirect_arr[1].md)[3] = 1; + //~^ deref_addrof + (*&mut a.indirect_ref.md)[3] = 1; + //~^ deref_addrof + + // Check that raw pointers are properly considered as well + **&raw mut a.prim = 0; + //~^ deref_addrof + (*&raw mut a.data).num = 42; + //~^ deref_addrof + + // Do not lint, as the dereference happens later, we cannot + // just remove `&mut` + (*&mut a.tup).0.num = 42; } } diff --git a/tests/ui/deref_addrof.stderr b/tests/ui/deref_addrof.stderr index 81414b625b2f..65dd904a8f75 100644 --- a/tests/ui/deref_addrof.stderr +++ b/tests/ui/deref_addrof.stderr @@ -56,20 +56,58 @@ LL | let _repeat = *&[0; 64]; | ^^^^^^^^^ help: try: `[0; 64]` error: immediately dereferencing a reference - --> tests/ui/deref_addrof.rs:66:17 + --> tests/ui/deref_addrof.rs:104:9 | -LL | inline!(*& $(@expr self)) - | ^^^^^^^^^^^^^^^^ help: try: `$(@expr self)` - | - = note: this error originates in the macro `__inline_mac_impl` (in Nightly builds, run with -Z macro-backtrace for more info) +LL | (*&mut a.padding) = [1; size_of::()]; + | ^^^^^^^^^^^^^^^^^ help: try: `a.padding` error: immediately dereferencing a reference - --> tests/ui/deref_addrof.rs:71:17 + --> tests/ui/deref_addrof.rs:106:9 | -LL | inline!(*&mut $(@expr self)) - | ^^^^^^^^^^^^^^^^^^^ help: try: `$(@expr self)` +LL | (*&mut a.tup).1 = (); + | ^^^^^^^^^^^^^ help: try: `a.tup` + +error: immediately dereferencing a reference + --> tests/ui/deref_addrof.rs:108:10 | - = note: this error originates in the macro `__inline_mac_impl` (in Nightly builds, run with -Z macro-backtrace for more info) +LL | **&mut a.prim = 0; + | ^^^^^^^^^^^^ help: try: `a.prim` -error: aborting due to 11 previous errors +error: immediately dereferencing a reference + --> tests/ui/deref_addrof.rs:111:9 + | +LL | (*&mut a.data).num = 42; + | ^^^^^^^^^^^^^^ help: try: `(*a.data)` + +error: immediately dereferencing a reference + --> tests/ui/deref_addrof.rs:113:9 + | +LL | (*&mut a.indirect.md)[3] = 1; + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `(*a.indirect.md)` + +error: immediately dereferencing a reference + --> tests/ui/deref_addrof.rs:115:9 + | +LL | (*&mut a.indirect_arr[1].md)[3] = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(*a.indirect_arr[1].md)` + +error: immediately dereferencing a reference + --> tests/ui/deref_addrof.rs:117:9 + | +LL | (*&mut a.indirect_ref.md)[3] = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(*a.indirect_ref.md)` + +error: immediately dereferencing a reference + --> tests/ui/deref_addrof.rs:121:10 + | +LL | **&raw mut a.prim = 0; + | ^^^^^^^^^^^^^^^^ help: try: `a.prim` + +error: immediately dereferencing a reference + --> tests/ui/deref_addrof.rs:123:9 + | +LL | (*&raw mut a.data).num = 42; + | ^^^^^^^^^^^^^^^^^^ help: try: `(*a.data)` + +error: aborting due to 18 previous errors diff --git a/tests/ui/doc/doc-fixable.fixed b/tests/ui/doc/doc-fixable.fixed index bbbd5973036e..423a73734daa 100644 --- a/tests/ui/doc/doc-fixable.fixed +++ b/tests/ui/doc/doc-fixable.fixed @@ -74,7 +74,7 @@ fn test_units() { /// GitHub GitLab /// IPv4 IPv6 /// ClojureScript CoffeeScript JavaScript PostScript PureScript TypeScript -/// WebAssembly +/// PowerPC WebAssembly /// NaN NaNs /// OAuth GraphQL /// OCaml diff --git a/tests/ui/doc/doc-fixable.rs b/tests/ui/doc/doc-fixable.rs index 1077d3580d3c..8deffb4210e4 100644 --- a/tests/ui/doc/doc-fixable.rs +++ b/tests/ui/doc/doc-fixable.rs @@ -74,7 +74,7 @@ fn test_units() { /// GitHub GitLab /// IPv4 IPv6 /// ClojureScript CoffeeScript JavaScript PostScript PureScript TypeScript -/// WebAssembly +/// PowerPC WebAssembly /// NaN NaNs /// OAuth GraphQL /// OCaml diff --git a/tests/ui/double_ended_iterator_last.fixed b/tests/ui/double_ended_iterator_last.fixed index be31ee5fb486..180a513d0f8e 100644 --- a/tests/ui/double_ended_iterator_last.fixed +++ b/tests/ui/double_ended_iterator_last.fixed @@ -81,6 +81,11 @@ fn issue_14139() { let (subindex, _) = (index.by_ref().take(3), 42); let _ = subindex.last(); let _ = index.next(); + + let mut index = [true, true, false, false, false, true].iter(); + let subindex = (index.by_ref().take(3), 42); + let _ = subindex.0.last(); + let _ = index.next(); } fn drop_order() { @@ -108,6 +113,12 @@ fn drop_order() { let mut v = DropDeIterator(v.into_iter()); println!("Last element is {}", v.next_back().unwrap().0); //~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator` + + let v = vec![S("four"), S("five"), S("six")]; + let mut v = (DropDeIterator(v.into_iter()), 42); + println!("Last element is {}", v.0.next_back().unwrap().0); + //~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator` + println!("Done"); } diff --git a/tests/ui/double_ended_iterator_last.rs b/tests/ui/double_ended_iterator_last.rs index 30864e15bce7..3dd72cfeaac7 100644 --- a/tests/ui/double_ended_iterator_last.rs +++ b/tests/ui/double_ended_iterator_last.rs @@ -81,6 +81,11 @@ fn issue_14139() { let (subindex, _) = (index.by_ref().take(3), 42); let _ = subindex.last(); let _ = index.next(); + + let mut index = [true, true, false, false, false, true].iter(); + let subindex = (index.by_ref().take(3), 42); + let _ = subindex.0.last(); + let _ = index.next(); } fn drop_order() { @@ -108,6 +113,12 @@ fn drop_order() { let v = DropDeIterator(v.into_iter()); println!("Last element is {}", v.last().unwrap().0); //~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator` + + let v = vec![S("four"), S("five"), S("six")]; + let v = (DropDeIterator(v.into_iter()), 42); + println!("Last element is {}", v.0.last().unwrap().0); + //~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator` + println!("Done"); } diff --git a/tests/ui/double_ended_iterator_last.stderr b/tests/ui/double_ended_iterator_last.stderr index 72a6ead47a93..0f0056be3769 100644 --- a/tests/ui/double_ended_iterator_last.stderr +++ b/tests/ui/double_ended_iterator_last.stderr @@ -18,7 +18,7 @@ LL | let _ = DeIterator.last(); | help: try: `next_back()` error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator - --> tests/ui/double_ended_iterator_last.rs:109:36 + --> tests/ui/double_ended_iterator_last.rs:114:36 | LL | println!("Last element is {}", v.last().unwrap().0); | ^^^^^^^^ @@ -30,5 +30,18 @@ LL ~ let mut v = DropDeIterator(v.into_iter()); LL ~ println!("Last element is {}", v.next_back().unwrap().0); | -error: aborting due to 3 previous errors +error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator + --> tests/ui/double_ended_iterator_last.rs:119:36 + | +LL | println!("Last element is {}", v.0.last().unwrap().0); + | ^^^^^^^^^^ + | + = note: this change will alter drop order which may be undesirable +help: try + | +LL ~ let mut v = (DropDeIterator(v.into_iter()), 42); +LL ~ println!("Last element is {}", v.0.next_back().unwrap().0); + | + +error: aborting due to 4 previous errors diff --git a/tests/ui/double_ended_iterator_last_unfixable.rs b/tests/ui/double_ended_iterator_last_unfixable.rs deleted file mode 100644 index 73f62ac12469..000000000000 --- a/tests/ui/double_ended_iterator_last_unfixable.rs +++ /dev/null @@ -1,39 +0,0 @@ -//@no-rustfix: requires manual changes -#![warn(clippy::double_ended_iterator_last)] - -// Should not be linted because applying the lint would move the original iterator. This can only be -// linted if the iterator is used thereafter. -fn main() { - let mut index = [true, true, false, false, false, true].iter(); - let subindex = (index.by_ref().take(3), 42); - let _ = subindex.0.last(); - let _ = index.next(); -} - -fn drop_order() { - struct DropDeIterator(std::vec::IntoIter); - impl Iterator for DropDeIterator { - type Item = S; - fn next(&mut self) -> Option { - self.0.next() - } - } - impl DoubleEndedIterator for DropDeIterator { - fn next_back(&mut self) -> Option { - self.0.next_back() - } - } - - struct S(&'static str); - impl std::ops::Drop for S { - fn drop(&mut self) { - println!("Dropping {}", self.0); - } - } - - let v = vec![S("one"), S("two"), S("three")]; - let v = (DropDeIterator(v.into_iter()), 42); - println!("Last element is {}", v.0.last().unwrap().0); - //~^ ERROR: called `Iterator::last` on a `DoubleEndedIterator` - println!("Done"); -} diff --git a/tests/ui/double_ended_iterator_last_unfixable.stderr b/tests/ui/double_ended_iterator_last_unfixable.stderr deleted file mode 100644 index e330a22a3548..000000000000 --- a/tests/ui/double_ended_iterator_last_unfixable.stderr +++ /dev/null @@ -1,19 +0,0 @@ -error: called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator - --> tests/ui/double_ended_iterator_last_unfixable.rs:36:36 - | -LL | println!("Last element is {}", v.0.last().unwrap().0); - | ^^^^------ - | | - | help: try: `next_back()` - | - = note: this change will alter drop order which may be undesirable -note: this must be made mutable to use `.next_back()` - --> tests/ui/double_ended_iterator_last_unfixable.rs:36:36 - | -LL | println!("Last element is {}", v.0.last().unwrap().0); - | ^^^ - = note: `-D clippy::double-ended-iterator-last` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::double_ended_iterator_last)]` - -error: aborting due to 1 previous error - diff --git a/tests/ui/eta.fixed b/tests/ui/eta.fixed index c93b83f53ecb..3d2b41b8fb81 100644 --- a/tests/ui/eta.fixed +++ b/tests/ui/eta.fixed @@ -1,3 +1,6 @@ +// we have some HELP annotations -- don't complain about them not being present everywhere +//@require-annotations-for-level: ERROR + #![warn(clippy::redundant_closure, clippy::redundant_closure_for_method_calls)] #![allow(unused)] #![allow( @@ -561,3 +564,29 @@ fn issue_14789() { std::convert::identity, ); } + +fn issue8817() { + fn f(_: u32) -> u32 { + todo!() + } + let g = |_: u32| -> u32 { todo!() }; + struct S(u32); + enum MyError { + A(S), + } + + Some(5) + .map(f) + //~^ redundant_closure + //~| HELP: replace the closure with the function itself + .map(g) + //~^ redundant_closure + //~| HELP: replace the closure with the function itself + .map(S) + //~^ redundant_closure + //~| HELP: replace the closure with the tuple struct itself + .map(MyError::A) + //~^ redundant_closure + //~| HELP: replace the closure with the tuple variant itself + .unwrap(); // just for nicer formatting +} diff --git a/tests/ui/eta.rs b/tests/ui/eta.rs index 273c8b21f4ad..79d1103410d9 100644 --- a/tests/ui/eta.rs +++ b/tests/ui/eta.rs @@ -1,3 +1,6 @@ +// we have some HELP annotations -- don't complain about them not being present everywhere +//@require-annotations-for-level: ERROR + #![warn(clippy::redundant_closure, clippy::redundant_closure_for_method_calls)] #![allow(unused)] #![allow( @@ -561,3 +564,29 @@ fn issue_14789() { std::convert::identity, ); } + +fn issue8817() { + fn f(_: u32) -> u32 { + todo!() + } + let g = |_: u32| -> u32 { todo!() }; + struct S(u32); + enum MyError { + A(S), + } + + Some(5) + .map(|n| f(n)) + //~^ redundant_closure + //~| HELP: replace the closure with the function itself + .map(|n| g(n)) + //~^ redundant_closure + //~| HELP: replace the closure with the function itself + .map(|n| S(n)) + //~^ redundant_closure + //~| HELP: replace the closure with the tuple struct itself + .map(|n| MyError::A(n)) + //~^ redundant_closure + //~| HELP: replace the closure with the tuple variant itself + .unwrap(); // just for nicer formatting +} diff --git a/tests/ui/eta.stderr b/tests/ui/eta.stderr index 8bc08add2fab..aa32ed1a38ef 100644 --- a/tests/ui/eta.stderr +++ b/tests/ui/eta.stderr @@ -1,5 +1,5 @@ error: redundant closure - --> tests/ui/eta.rs:31:27 + --> tests/ui/eta.rs:34:27 | LL | let a = Some(1u8).map(|a| foo(a)); | ^^^^^^^^^^ help: replace the closure with the function itself: `foo` @@ -8,31 +8,31 @@ LL | let a = Some(1u8).map(|a| foo(a)); = help: to override `-D warnings` add `#[allow(clippy::redundant_closure)]` error: redundant closure - --> tests/ui/eta.rs:36:40 + --> tests/ui/eta.rs:39:40 | LL | let _: Option> = true.then(|| vec![]); // special case vec! | ^^^^^^^^^ help: replace the closure with `Vec::new`: `std::vec::Vec::new` error: redundant closure - --> tests/ui/eta.rs:39:35 + --> tests/ui/eta.rs:42:35 | LL | let d = Some(1u8).map(|a| foo((|b| foo2(b))(a))); //is adjusted? | ^^^^^^^^^^^^^ help: replace the closure with the function itself: `foo2` error: redundant closure - --> tests/ui/eta.rs:42:26 + --> tests/ui/eta.rs:45:26 | LL | all(&[1, 2, 3], &&2, |x, y| below(x, y)); //is adjusted | ^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `below` error: redundant closure - --> tests/ui/eta.rs:51:27 + --> tests/ui/eta.rs:54:27 | LL | let e = Some(1u8).map(|a| generic(a)); | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `generic` error: redundant closure - --> tests/ui/eta.rs:104:51 + --> tests/ui/eta.rs:107:51 | LL | let e = Some(TestStruct { some_ref: &i }).map(|a| a.foo()); | ^^^^^^^^^^^ help: replace the closure with the method itself: `TestStruct::foo` @@ -41,178 +41,202 @@ LL | let e = Some(TestStruct { some_ref: &i }).map(|a| a.foo()); = help: to override `-D warnings` add `#[allow(clippy::redundant_closure_for_method_calls)]` error: redundant closure - --> tests/ui/eta.rs:106:51 + --> tests/ui/eta.rs:109:51 | LL | let e = Some(TestStruct { some_ref: &i }).map(|a| a.trait_foo()); | ^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `TestTrait::trait_foo` error: redundant closure - --> tests/ui/eta.rs:109:42 + --> tests/ui/eta.rs:112:42 | LL | let e = Some(&mut vec![1, 2, 3]).map(|v| v.clear()); | ^^^^^^^^^^^^^ help: replace the closure with the method itself: `std::vec::Vec::clear` error: redundant closure - --> tests/ui/eta.rs:114:29 + --> tests/ui/eta.rs:117:29 | LL | let e = Some("str").map(|s| s.to_string()); | ^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `std::string::ToString::to_string` error: redundant closure - --> tests/ui/eta.rs:116:27 + --> tests/ui/eta.rs:119:27 | LL | let e = Some('a').map(|s| s.to_uppercase()); | ^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `char::to_uppercase` error: redundant closure - --> tests/ui/eta.rs:119:65 + --> tests/ui/eta.rs:122:65 | LL | let e: std::vec::Vec = vec!['a', 'b', 'c'].iter().map(|c| c.to_ascii_uppercase()).collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `char::to_ascii_uppercase` error: redundant closure - --> tests/ui/eta.rs:136:23 + --> tests/ui/eta.rs:139:23 | LL | let _ = x.map(|x| x.parse::()); | ^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `str::parse::` error: redundant closure - --> tests/ui/eta.rs:189:22 + --> tests/ui/eta.rs:192:22 | LL | requires_fn_once(|| x()); | ^^^^^^ help: replace the closure with the function itself: `x` error: redundant closure - --> tests/ui/eta.rs:197:27 + --> tests/ui/eta.rs:200:27 | LL | let a = Some(1u8).map(|a| foo_ptr(a)); | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `foo_ptr` error: redundant closure - --> tests/ui/eta.rs:203:27 + --> tests/ui/eta.rs:206:27 | LL | let a = Some(1u8).map(|a| closure(a)); | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `closure` error: redundant closure - --> tests/ui/eta.rs:236:28 + --> tests/ui/eta.rs:239:28 | LL | x.into_iter().for_each(|x| add_to_res(x)); | ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut add_to_res` error: redundant closure - --> tests/ui/eta.rs:238:28 + --> tests/ui/eta.rs:241:28 | LL | y.into_iter().for_each(|x| add_to_res(x)); | ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut add_to_res` error: redundant closure - --> tests/ui/eta.rs:240:28 + --> tests/ui/eta.rs:243:28 | LL | z.into_iter().for_each(|x| add_to_res(x)); | ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `add_to_res` error: redundant closure - --> tests/ui/eta.rs:248:21 + --> tests/ui/eta.rs:251:21 | LL | Some(1).map(|n| closure(n)); | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut closure` error: redundant closure - --> tests/ui/eta.rs:253:21 + --> tests/ui/eta.rs:256:21 | LL | Some(1).map(|n| in_loop(n)); | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `in_loop` error: redundant closure - --> tests/ui/eta.rs:347:18 + --> tests/ui/eta.rs:350:18 | LL | takes_fn_mut(|| f()); | ^^^^^^ help: replace the closure with the function itself: `&mut f` error: redundant closure - --> tests/ui/eta.rs:351:19 + --> tests/ui/eta.rs:354:19 | LL | takes_fn_once(|| f()); | ^^^^^^ help: replace the closure with the function itself: `&mut f` error: redundant closure - --> tests/ui/eta.rs:356:26 + --> tests/ui/eta.rs:359:26 | LL | move || takes_fn_mut(|| f_used_once()) | ^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut f_used_once` error: redundant closure - --> tests/ui/eta.rs:369:19 + --> tests/ui/eta.rs:372:19 | LL | array_opt.map(|a| a.as_slice()); | ^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `<[u8; 3]>::as_slice` error: redundant closure - --> tests/ui/eta.rs:373:19 + --> tests/ui/eta.rs:376:19 | LL | slice_opt.map(|s| s.len()); | ^^^^^^^^^^^ help: replace the closure with the method itself: `<[u8]>::len` error: redundant closure - --> tests/ui/eta.rs:377:17 + --> tests/ui/eta.rs:380:17 | LL | ptr_opt.map(|p| p.is_null()); | ^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `<*const usize>::is_null` error: redundant closure - --> tests/ui/eta.rs:382:17 + --> tests/ui/eta.rs:385:17 | LL | dyn_opt.map(|d| d.method_on_dyn()); | ^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `::method_on_dyn` error: redundant closure - --> tests/ui/eta.rs:443:19 + --> tests/ui/eta.rs:446:19 | LL | let _ = f(&0, |x, y| f2(x, y)); | ^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `f2` error: redundant closure - --> tests/ui/eta.rs:472:22 + --> tests/ui/eta.rs:475:22 | LL | test.map(|t| t.method()) | ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `Test::method` error: redundant closure - --> tests/ui/eta.rs:477:22 + --> tests/ui/eta.rs:480:22 | LL | test.map(|t| t.method()) | ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `super::Outer::method` error: redundant closure - --> tests/ui/eta.rs:491:18 + --> tests/ui/eta.rs:494:18 | LL | test.map(|t| t.method()) | ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `test_mod::Test::method` error: redundant closure - --> tests/ui/eta.rs:499:30 + --> tests/ui/eta.rs:502:30 | LL | test.map(|t| t.method()) | ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `crate::issue_10854::d::Test::method` error: redundant closure - --> tests/ui/eta.rs:519:38 + --> tests/ui/eta.rs:522:38 | LL | let x = Box::new(|| None.map(|x| f(x))); | ^^^^^^^^ help: replace the closure with the function itself: `&f` error: redundant closure - --> tests/ui/eta.rs:524:38 + --> tests/ui/eta.rs:527:38 | LL | let x = Box::new(|| None.map(|x| f(x))); | ^^^^^^^^ help: replace the closure with the function itself: `f` error: redundant closure - --> tests/ui/eta.rs:542:35 + --> tests/ui/eta.rs:545:35 | LL | let _field = bind.or_else(|| get_default()).unwrap(); | ^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `get_default` -error: aborting due to 35 previous errors +error: redundant closure + --> tests/ui/eta.rs:588:14 + | +LL | .map(|n| MyError::A(n)) + | ^^^^^^^^^^^^^^^^^ help: replace the closure with the tuple variant itself: `MyError::A` + +error: redundant closure + --> tests/ui/eta.rs:585:14 + | +LL | .map(|n| S(n)) + | ^^^^^^^^ help: replace the closure with the tuple struct itself: `S` + +error: redundant closure + --> tests/ui/eta.rs:582:14 + | +LL | .map(|n| g(n)) + | ^^^^^^^^ help: replace the closure with the function itself: `g` + +error: redundant closure + --> tests/ui/eta.rs:579:14 + | +LL | .map(|n| f(n)) + | ^^^^^^^^ help: replace the closure with the function itself: `f` + +error: aborting due to 39 previous errors diff --git a/tests/ui/from_str_radix_10.fixed b/tests/ui/from_str_radix_10.fixed index 4b8fd778685e..47d24167e56c 100644 --- a/tests/ui/from_str_radix_10.fixed +++ b/tests/ui/from_str_radix_10.fixed @@ -74,3 +74,13 @@ fn issue_12731() { let _ = u32::from_str_radix("123", 10); } } + +fn fix_str_ref_check() { + #![allow(clippy::needless_borrow)] + let s = "1"; + let _ = s.parse::().unwrap(); + //~^ from_str_radix_10 + let s_ref = &s; + let _ = s_ref.parse::().unwrap(); + //~^ from_str_radix_10 +} diff --git a/tests/ui/from_str_radix_10.rs b/tests/ui/from_str_radix_10.rs index 89002b11a995..952e19b57a00 100644 --- a/tests/ui/from_str_radix_10.rs +++ b/tests/ui/from_str_radix_10.rs @@ -74,3 +74,13 @@ fn issue_12731() { let _ = u32::from_str_radix("123", 10); } } + +fn fix_str_ref_check() { + #![allow(clippy::needless_borrow)] + let s = "1"; + let _ = u32::from_str_radix(&s, 10).unwrap(); + //~^ from_str_radix_10 + let s_ref = &s; + let _ = u32::from_str_radix(&s_ref, 10).unwrap(); + //~^ from_str_radix_10 +} diff --git a/tests/ui/from_str_radix_10.stderr b/tests/ui/from_str_radix_10.stderr index c693e8f50ff6..d4e6c3657f20 100644 --- a/tests/ui/from_str_radix_10.stderr +++ b/tests/ui/from_str_radix_10.stderr @@ -49,5 +49,17 @@ error: this call to `from_str_radix` can be replaced with a call to `str::parse` LL | i32::from_str_radix(&stringier, 10)?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `stringier.parse::()` -error: aborting due to 8 previous errors +error: this call to `from_str_radix` can be replaced with a call to `str::parse` + --> tests/ui/from_str_radix_10.rs:81:13 + | +LL | let _ = u32::from_str_radix(&s, 10).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `s.parse::()` + +error: this call to `from_str_radix` can be replaced with a call to `str::parse` + --> tests/ui/from_str_radix_10.rs:84:13 + | +LL | let _ = u32::from_str_radix(&s_ref, 10).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `s_ref.parse::()` + +error: aborting due to 10 previous errors diff --git a/tests/ui/functions_maxlines.rs b/tests/ui/functions_maxlines.rs index e0990dadaaa9..b07145524618 100644 --- a/tests/ui/functions_maxlines.rs +++ b/tests/ui/functions_maxlines.rs @@ -1,3 +1,4 @@ +#![allow(clippy::unused_unit, clippy::missing_safety_doc)] #![warn(clippy::too_many_lines)] fn good_lines() { @@ -55,7 +56,8 @@ fn good_lines() { println!("This is good."); } -fn bad_lines() { +#[allow(unused)] // the attr shouldn't get included in the highlight +pub async unsafe extern "Rust" fn bad_lines() -> () { //~^ too_many_lines println!("Dont get confused by braces: {{}}"); @@ -162,4 +164,115 @@ fn bad_lines() { println!("This is bad."); } +struct Foo; +impl Foo { + #[allow(unused)] // the attr shouldn't get included in the highlight + pub async unsafe extern "Rust" fn bad_lines() -> () { + //~^ too_many_lines + + println!("Dont get confused by braces: {{}}"); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + println!("This is bad."); + } +} + fn main() {} diff --git a/tests/ui/functions_maxlines.stderr b/tests/ui/functions_maxlines.stderr index f42a2b2a22a2..4c3faf45c472 100644 --- a/tests/ui/functions_maxlines.stderr +++ b/tests/ui/functions_maxlines.stderr @@ -1,17 +1,17 @@ -error: this function has too many lines (102/100) - --> tests/ui/functions_maxlines.rs:58:1 +error: this function has too many lines (104/100) + --> tests/ui/functions_maxlines.rs:60:1 | -LL | / fn bad_lines() { -LL | | -LL | | -LL | | println!("Dont get confused by braces: {{}}"); -... | -LL | | println!("This is bad."); -LL | | } - | |_^ +LL | pub async unsafe extern "Rust" fn bad_lines() -> () { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::too-many-lines` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::too_many_lines)]` -error: aborting due to 1 previous error +error: this function has too many lines (104/100) + --> tests/ui/functions_maxlines.rs:170:5 + | +LL | pub async unsafe extern "Rust" fn bad_lines() -> () { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors diff --git a/tests/ui/infinite_loops.rs b/tests/ui/infinite_loops.rs index fcd1f795fff0..9b8c39331970 100644 --- a/tests/ui/infinite_loops.rs +++ b/tests/ui/infinite_loops.rs @@ -450,4 +450,75 @@ mod issue_12338 { } } +#[allow(clippy::let_underscore_future, clippy::empty_loop)] +mod issue_14000 { + use super::do_something; + + async fn foo() { + let _ = async move { + loop { + //~^ infinite_loop + do_something(); + } + } + .await; + let _ = async move { + loop { + //~^ infinite_loop + continue; + } + } + .await; + } + + fn bar() { + let _ = async move { + loop { + do_something(); + } + }; + + let _ = async move { + loop { + continue; + } + }; + } +} + +#[allow(clippy::let_underscore_future)] +mod tokio_spawn_test { + use super::do_something; + + fn install_ticker() { + // This should NOT trigger the lint because the async block is spawned, not awaited + std::thread::spawn(move || { + async move { + loop { + // This loop should not trigger infinite_loop lint + do_something(); + } + } + }); + } + + fn spawn_async_block() { + // This should NOT trigger the lint because the async block is not awaited + let _handle = async move { + loop { + do_something(); + } + }; + } + + fn await_async_block() { + // This SHOULD trigger the lint because the async block is awaited + let _ = async move { + loop { + do_something(); + } + }; + } +} + fn main() {} diff --git a/tests/ui/infinite_loops.stderr b/tests/ui/infinite_loops.stderr index 4d02636ef4ae..4c6b6f725f13 100644 --- a/tests/ui/infinite_loops.stderr +++ b/tests/ui/infinite_loops.stderr @@ -311,5 +311,27 @@ help: if this is intentional, consider specifying `!` as function return LL | fn continue_outer() -> ! { | ++++ -error: aborting due to 21 previous errors +error: infinite loop detected + --> tests/ui/infinite_loops.rs:459:13 + | +LL | / loop { +LL | | +LL | | do_something(); +LL | | } + | |_____________^ + | + = help: if this is not intended, try adding a `break` or `return` condition in the loop + +error: infinite loop detected + --> tests/ui/infinite_loops.rs:466:13 + | +LL | / loop { +LL | | +LL | | continue; +LL | | } + | |_____________^ + | + = help: if this is not intended, try adding a `break` or `return` condition in the loop + +error: aborting due to 23 previous errors diff --git a/tests/ui/match_bool.fixed b/tests/ui/match_bool.fixed index 1dfb82db1206..876ae935afde 100644 --- a/tests/ui/match_bool.fixed +++ b/tests/ui/match_bool.fixed @@ -61,4 +61,17 @@ fn issue14099() { } } } +fn issue15351() { + let mut d = false; + match d { + false => println!("foo"), + ref mut d => *d = false, + } + + match d { + false => println!("foo"), + e => println!("{e}"), + } +} + fn main() {} diff --git a/tests/ui/match_bool.rs b/tests/ui/match_bool.rs index 719b4e51eb6d..a134ad8346e2 100644 --- a/tests/ui/match_bool.rs +++ b/tests/ui/match_bool.rs @@ -113,4 +113,17 @@ fn issue14099() { } } +fn issue15351() { + let mut d = false; + match d { + false => println!("foo"), + ref mut d => *d = false, + } + + match d { + false => println!("foo"), + e => println!("{e}"), + } +} + fn main() {} diff --git a/tests/ui/match_ref_pats.fixed b/tests/ui/match_ref_pats.fixed index 8add3da0c99f..f727546838b4 100644 --- a/tests/ui/match_ref_pats.fixed +++ b/tests/ui/match_ref_pats.fixed @@ -1,6 +1,12 @@ #![warn(clippy::match_ref_pats)] #![allow(dead_code, unused_variables)] -#![allow(clippy::enum_variant_names, clippy::equatable_if_let, clippy::uninlined_format_args)] +#![allow( + clippy::enum_variant_names, + clippy::equatable_if_let, + clippy::uninlined_format_args, + clippy::empty_loop, + clippy::diverging_sub_expression +)] fn ref_pats() { { @@ -120,4 +126,32 @@ mod issue_7740 { } } +mod issue15378 { + fn never_in_match() { + match unimplemented!() { + &_ => {}, + &&&42 => { + todo!() + }, + _ => {}, + } + + match panic!() { + &_ => {}, + &&&42 => { + todo!() + }, + _ => {}, + } + + match loop {} { + &_ => {}, + &&&42 => { + todo!() + }, + _ => {}, + } + } +} + fn main() {} diff --git a/tests/ui/match_ref_pats.rs b/tests/ui/match_ref_pats.rs index 07889b0dfc24..eca4d584acd2 100644 --- a/tests/ui/match_ref_pats.rs +++ b/tests/ui/match_ref_pats.rs @@ -1,6 +1,12 @@ #![warn(clippy::match_ref_pats)] #![allow(dead_code, unused_variables)] -#![allow(clippy::enum_variant_names, clippy::equatable_if_let, clippy::uninlined_format_args)] +#![allow( + clippy::enum_variant_names, + clippy::equatable_if_let, + clippy::uninlined_format_args, + clippy::empty_loop, + clippy::diverging_sub_expression +)] fn ref_pats() { { @@ -120,4 +126,32 @@ mod issue_7740 { } } +mod issue15378 { + fn never_in_match() { + match unimplemented!() { + &_ => {}, + &&&42 => { + todo!() + }, + _ => {}, + } + + match panic!() { + &_ => {}, + &&&42 => { + todo!() + }, + _ => {}, + } + + match loop {} { + &_ => {}, + &&&42 => { + todo!() + }, + _ => {}, + } + } +} + fn main() {} diff --git a/tests/ui/match_ref_pats.stderr b/tests/ui/match_ref_pats.stderr index f81b290b32cb..ecb08e6972d9 100644 --- a/tests/ui/match_ref_pats.stderr +++ b/tests/ui/match_ref_pats.stderr @@ -1,5 +1,5 @@ error: you don't need to add `&` to all patterns - --> tests/ui/match_ref_pats.rs:8:9 + --> tests/ui/match_ref_pats.rs:14:9 | LL | / match v { LL | | @@ -19,7 +19,7 @@ LL ~ None => println!("none"), | error: you don't need to add `&` to both the expression and the patterns - --> tests/ui/match_ref_pats.rs:26:5 + --> tests/ui/match_ref_pats.rs:32:5 | LL | / match &w { LL | | @@ -37,7 +37,7 @@ LL ~ None => println!("none"), | error: redundant pattern matching, consider using `is_none()` - --> tests/ui/match_ref_pats.rs:39:12 + --> tests/ui/match_ref_pats.rs:45:12 | LL | if let &None = a { | -------^^^^^---- help: try: `if a.is_none()` @@ -46,13 +46,13 @@ LL | if let &None = a { = help: to override `-D warnings` add `#[allow(clippy::redundant_pattern_matching)]` error: redundant pattern matching, consider using `is_none()` - --> tests/ui/match_ref_pats.rs:45:12 + --> tests/ui/match_ref_pats.rs:51:12 | LL | if let &None = &b { | -------^^^^^----- help: try: `if b.is_none()` error: you don't need to add `&` to all patterns - --> tests/ui/match_ref_pats.rs:106:9 + --> tests/ui/match_ref_pats.rs:112:9 | LL | / match foobar_variant!(0) { LL | | diff --git a/tests/ui/ptr_as_ptr.fixed b/tests/ui/ptr_as_ptr.fixed index 71fea6144e76..78e1ceb480a5 100644 --- a/tests/ui/ptr_as_ptr.fixed +++ b/tests/ui/ptr_as_ptr.fixed @@ -2,8 +2,8 @@ #![warn(clippy::ptr_as_ptr)] -#[macro_use] extern crate proc_macros; +use proc_macros::{external, inline_macros, with_span}; mod issue_11278_a { #[derive(Debug)] @@ -53,11 +53,13 @@ fn main() { //~^ ptr_as_ptr // Make sure the lint is triggered inside a macro - let _ = inline!($ptr.cast::()); - //~^ ptr_as_ptr + // FIXME: `is_from_proc_macro` incorrectly stops the lint from firing here + let _ = inline!($ptr as *const i32); // Do not lint inside macros from external crates let _ = external!($ptr as *const i32); + + let _ = with_span!(expr $ptr as *const i32); } #[clippy::msrv = "1.37"] diff --git a/tests/ui/ptr_as_ptr.rs b/tests/ui/ptr_as_ptr.rs index 4d507592a1e3..70732cf0a6c1 100644 --- a/tests/ui/ptr_as_ptr.rs +++ b/tests/ui/ptr_as_ptr.rs @@ -2,8 +2,8 @@ #![warn(clippy::ptr_as_ptr)] -#[macro_use] extern crate proc_macros; +use proc_macros::{external, inline_macros, with_span}; mod issue_11278_a { #[derive(Debug)] @@ -53,11 +53,13 @@ fn main() { //~^ ptr_as_ptr // Make sure the lint is triggered inside a macro + // FIXME: `is_from_proc_macro` incorrectly stops the lint from firing here let _ = inline!($ptr as *const i32); - //~^ ptr_as_ptr // Do not lint inside macros from external crates let _ = external!($ptr as *const i32); + + let _ = with_span!(expr $ptr as *const i32); } #[clippy::msrv = "1.37"] diff --git a/tests/ui/ptr_as_ptr.stderr b/tests/ui/ptr_as_ptr.stderr index adad159bb0f2..c0a2a4b6d204 100644 --- a/tests/ui/ptr_as_ptr.stderr +++ b/tests/ui/ptr_as_ptr.stderr @@ -38,174 +38,166 @@ LL | let _: *mut i32 = mut_ptr as _; | ^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:56:21 - | -LL | let _ = inline!($ptr as *const i32); - | ^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `$ptr.cast::()` - | - = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:78:13 + --> tests/ui/ptr_as_ptr.rs:80:13 | LL | let _ = ptr as *const i32; | ^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `ptr.cast::()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:80:13 + --> tests/ui/ptr_as_ptr.rs:82:13 | LL | let _ = mut_ptr as *mut i32; | ^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast::()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:88:9 + --> tests/ui/ptr_as_ptr.rs:90:9 | LL | ptr::null_mut() as *mut u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null_mut::()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:93:9 + --> tests/ui/ptr_as_ptr.rs:95:9 | LL | std::ptr::null_mut() as *mut u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null_mut::()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:99:9 + --> tests/ui/ptr_as_ptr.rs:101:9 | LL | ptr::null_mut() as *mut u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null_mut::()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:104:9 + --> tests/ui/ptr_as_ptr.rs:106:9 | LL | core::ptr::null_mut() as *mut u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null_mut::()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:110:9 + --> tests/ui/ptr_as_ptr.rs:112:9 | LL | ptr::null() as *const u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null::()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:115:9 + --> tests/ui/ptr_as_ptr.rs:117:9 | LL | std::ptr::null() as *const u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null::()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:121:9 + --> tests/ui/ptr_as_ptr.rs:123:9 | LL | ptr::null() as *const u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null::()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:126:9 + --> tests/ui/ptr_as_ptr.rs:128:9 | LL | core::ptr::null() as *const u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null::()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:134:9 + --> tests/ui/ptr_as_ptr.rs:136:9 | LL | ptr::null_mut() as *mut _ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null_mut()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:139:9 + --> tests/ui/ptr_as_ptr.rs:141:9 | LL | std::ptr::null_mut() as *mut _ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null_mut()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:145:9 + --> tests/ui/ptr_as_ptr.rs:147:9 | LL | ptr::null_mut() as *mut _ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null_mut()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:150:9 + --> tests/ui/ptr_as_ptr.rs:152:9 | LL | core::ptr::null_mut() as *mut _ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null_mut()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:156:9 + --> tests/ui/ptr_as_ptr.rs:158:9 | LL | ptr::null() as *const _ | ^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:161:9 + --> tests/ui/ptr_as_ptr.rs:163:9 | LL | std::ptr::null() as *const _ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:167:9 + --> tests/ui/ptr_as_ptr.rs:169:9 | LL | ptr::null() as *const _ | ^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:172:9 + --> tests/ui/ptr_as_ptr.rs:174:9 | LL | core::ptr::null() as *const _ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:180:9 + --> tests/ui/ptr_as_ptr.rs:182:9 | LL | ptr::null_mut() as _ | ^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null_mut()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:185:9 + --> tests/ui/ptr_as_ptr.rs:187:9 | LL | std::ptr::null_mut() as _ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null_mut()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:191:9 + --> tests/ui/ptr_as_ptr.rs:193:9 | LL | ptr::null_mut() as _ | ^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null_mut()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:196:9 + --> tests/ui/ptr_as_ptr.rs:198:9 | LL | core::ptr::null_mut() as _ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null_mut()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:202:9 + --> tests/ui/ptr_as_ptr.rs:204:9 | LL | ptr::null() as _ | ^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:207:9 + --> tests/ui/ptr_as_ptr.rs:209:9 | LL | std::ptr::null() as _ | ^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:213:9 + --> tests/ui/ptr_as_ptr.rs:215:9 | LL | ptr::null() as _ | ^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:218:9 + --> tests/ui/ptr_as_ptr.rs:220:9 | LL | core::ptr::null() as _ | ^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null()` error: `as` casting between raw pointers without changing their constness - --> tests/ui/ptr_as_ptr.rs:226:43 + --> tests/ui/ptr_as_ptr.rs:228:43 | LL | let _: fn() = std::mem::transmute(std::ptr::null::<()>() as *const u8); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null::()` -error: aborting due to 34 previous errors +error: aborting due to 33 previous errors diff --git a/tests/ui/similar_names.rs b/tests/ui/similar_names.rs index 69b6ab6220bf..55a141209f0f 100644 --- a/tests/ui/similar_names.rs +++ b/tests/ui/similar_names.rs @@ -89,6 +89,10 @@ fn main() { let iter: i32; let item: i32; + + // 3 letter names are allowed to be similar + let kta: i32; + let ktv: i32; } fn foo() { diff --git a/tests/ui/similar_names.stderr b/tests/ui/similar_names.stderr index 8d722fb8b564..c226f73d4db1 100644 --- a/tests/ui/similar_names.stderr +++ b/tests/ui/similar_names.stderr @@ -49,13 +49,13 @@ LL | let parser: i32; | ^^^^^^ error: binding's name is too similar to existing binding - --> tests/ui/similar_names.rs:98:16 + --> tests/ui/similar_names.rs:102:16 | LL | bpple: sprang, | ^^^^^^ | note: existing binding defined here - --> tests/ui/similar_names.rs:97:16 + --> tests/ui/similar_names.rs:101:16 | LL | apple: spring, | ^^^^^^ diff --git a/tests/ui/unnecessary_operation.fixed b/tests/ui/unnecessary_operation.fixed index ac9fa4de20a6..db5409bc491e 100644 --- a/tests/ui/unnecessary_operation.fixed +++ b/tests/ui/unnecessary_operation.fixed @@ -78,25 +78,25 @@ fn main() { //~^ unnecessary_operation get_number(); //~^ unnecessary_operation - 5;get_number(); + 5; get_number(); //~^ unnecessary_operation get_number(); //~^ unnecessary_operation get_number(); //~^ unnecessary_operation - 5;6;get_number(); + 5; 6; get_number(); //~^ unnecessary_operation get_number(); //~^ unnecessary_operation get_number(); //~^ unnecessary_operation - 5;get_number(); + 5; get_number(); //~^ unnecessary_operation - 42;get_number(); + 42; get_number(); //~^ unnecessary_operation assert!([42, 55].len() > get_usize()); //~^ unnecessary_operation - 42;get_number(); + 42; get_number(); //~^ unnecessary_operation get_number(); //~^ unnecessary_operation diff --git a/tests/ui/unnecessary_operation.stderr b/tests/ui/unnecessary_operation.stderr index 0fda1dfde190..3439ba2e33e5 100644 --- a/tests/ui/unnecessary_operation.stderr +++ b/tests/ui/unnecessary_operation.stderr @@ -35,7 +35,7 @@ error: unnecessary operation --> tests/ui/unnecessary_operation.rs:81:5 | LL | 5 + get_number(); - | ^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `5;get_number();` + | ^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `5; get_number();` error: unnecessary operation --> tests/ui/unnecessary_operation.rs:83:5 @@ -53,7 +53,7 @@ error: unnecessary operation --> tests/ui/unnecessary_operation.rs:87:5 | LL | (5, 6, get_number()); - | ^^^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `5;6;get_number();` + | ^^^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `5; 6; get_number();` error: unnecessary operation --> tests/ui/unnecessary_operation.rs:89:5 @@ -71,13 +71,13 @@ error: unnecessary operation --> tests/ui/unnecessary_operation.rs:93:5 | LL | 5..get_number(); - | ^^^^^^^^^^^^^^^^ help: statement can be reduced to: `5;get_number();` + | ^^^^^^^^^^^^^^^^ help: statement can be reduced to: `5; get_number();` error: unnecessary operation --> tests/ui/unnecessary_operation.rs:95:5 | LL | [42, get_number()]; - | ^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `42;get_number();` + | ^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `42; get_number();` error: unnecessary operation --> tests/ui/unnecessary_operation.rs:97:5 @@ -89,7 +89,7 @@ error: unnecessary operation --> tests/ui/unnecessary_operation.rs:99:5 | LL | (42, get_number()).1; - | ^^^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `42;get_number();` + | ^^^^^^^^^^^^^^^^^^^^^ help: statement can be reduced to: `42; get_number();` error: unnecessary operation --> tests/ui/unnecessary_operation.rs:101:5 diff --git a/tests/ui/unnecessary_semicolon.edition2021.fixed b/tests/ui/unnecessary_semicolon.edition2021.fixed index f10d804c8ccc..797f1505f499 100644 --- a/tests/ui/unnecessary_semicolon.edition2021.fixed +++ b/tests/ui/unnecessary_semicolon.edition2021.fixed @@ -63,3 +63,12 @@ fn issue14100() -> bool { // cast into the `bool` function return type. if return true {}; } + +fn issue15426(x: u32) { + // removing the `;` would turn the stmt into an expr, but attrs aren't allowed on exprs + #[rustfmt::skip] + match x { + 0b00 => {} 0b01 => {} + 0b11 => {} _ => {} + }; +} diff --git a/tests/ui/unnecessary_semicolon.edition2024.fixed b/tests/ui/unnecessary_semicolon.edition2024.fixed index 32a3bb9b4081..d2609cea0002 100644 --- a/tests/ui/unnecessary_semicolon.edition2024.fixed +++ b/tests/ui/unnecessary_semicolon.edition2024.fixed @@ -63,3 +63,12 @@ fn issue14100() -> bool { // cast into the `bool` function return type. if return true {}; } + +fn issue15426(x: u32) { + // removing the `;` would turn the stmt into an expr, but attrs aren't allowed on exprs + #[rustfmt::skip] + match x { + 0b00 => {} 0b01 => {} + 0b11 => {} _ => {} + }; +} diff --git a/tests/ui/unnecessary_semicolon.rs b/tests/ui/unnecessary_semicolon.rs index 91b282180224..55f1ec84cb0e 100644 --- a/tests/ui/unnecessary_semicolon.rs +++ b/tests/ui/unnecessary_semicolon.rs @@ -63,3 +63,12 @@ fn issue14100() -> bool { // cast into the `bool` function return type. if return true {}; } + +fn issue15426(x: u32) { + // removing the `;` would turn the stmt into an expr, but attrs aren't allowed on exprs + #[rustfmt::skip] + match x { + 0b00 => {} 0b01 => {} + 0b11 => {} _ => {} + }; +} diff --git a/tests/ui/zero_sized_hashmap_values.rs b/tests/ui/zero_sized_hashmap_values.rs index dcbfd16843de..ee2fd19b5ee5 100644 --- a/tests/ui/zero_sized_hashmap_values.rs +++ b/tests/ui/zero_sized_hashmap_values.rs @@ -92,6 +92,14 @@ fn issue14822() { //~^ zero_sized_map_values } +fn issue15429() { + struct E<'a>(&'a [E<'a>]); + + // The assertion error happens when the type being evaluated has escaping bound vars + // as it cannot be wrapped in a dummy binder during size computation. + type F = dyn for<'a> FnOnce(HashMap>) -> u32; +} + fn main() { let _: HashMap = HashMap::new(); //~^ zero_sized_map_values diff --git a/tests/ui/zero_sized_hashmap_values.stderr b/tests/ui/zero_sized_hashmap_values.stderr index d29491fa05c7..52ffef280c1d 100644 --- a/tests/ui/zero_sized_hashmap_values.stderr +++ b/tests/ui/zero_sized_hashmap_values.stderr @@ -89,7 +89,7 @@ LL | type D = HashMap>; = help: consider using a set instead error: map with zero-sized value type - --> tests/ui/zero_sized_hashmap_values.rs:96:34 + --> tests/ui/zero_sized_hashmap_values.rs:104:34 | LL | let _: HashMap = HashMap::new(); | ^^^^^^^ @@ -97,7 +97,7 @@ LL | let _: HashMap = HashMap::new(); = help: consider using a set instead error: map with zero-sized value type - --> tests/ui/zero_sized_hashmap_values.rs:96:12 + --> tests/ui/zero_sized_hashmap_values.rs:104:12 | LL | let _: HashMap = HashMap::new(); | ^^^^^^^^^^^^^^^^^^^ @@ -105,7 +105,7 @@ LL | let _: HashMap = HashMap::new(); = help: consider using a set instead error: map with zero-sized value type - --> tests/ui/zero_sized_hashmap_values.rs:102:12 + --> tests/ui/zero_sized_hashmap_values.rs:110:12 | LL | let _: HashMap<_, _> = std::iter::empty::<(String, ())>().collect(); | ^^^^^^^^^^^^^ From 02bc3c94d2489cf7f8a77fb0a5ac2e5c89f0ed0b Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 19 Aug 2025 23:44:02 +0200 Subject: [PATCH 013/116] `get_diagnostic_name` in other places --- clippy_lints/src/casts/ptr_as_ptr.rs | 10 ++--- clippy_lints/src/large_include_file.rs | 4 +- clippy_lints/src/manual_retain.rs | 10 ++--- clippy_lints/src/manual_strip.rs | 10 ++--- .../src/methods/iter_out_of_bounds.rs | 45 +++++++++---------- .../src/methods/option_as_ref_deref.rs | 28 +++++------- .../src/methods/unnecessary_min_or_max.rs | 3 +- clippy_lints/src/operators/cmp_owned.rs | 12 ++--- clippy_lints/src/redundant_clone.rs | 13 +++--- clippy_lints/src/strings.rs | 7 +-- clippy_lints/src/types/rc_buffer.rs | 5 ++- .../src/types/redundant_allocation.rs | 13 +++--- .../src/unnecessary_owned_empty_strings.rs | 5 ++- clippy_lints/src/unnecessary_wraps.rs | 10 ++--- clippy_lints/src/zombie_processes.rs | 15 ++++--- 15 files changed, 88 insertions(+), 102 deletions(-) diff --git a/clippy_lints/src/casts/ptr_as_ptr.rs b/clippy_lints/src/casts/ptr_as_ptr.rs index 890754090989..c5be504543c2 100644 --- a/clippy_lints/src/casts/ptr_as_ptr.rs +++ b/clippy_lints/src/casts/ptr_as_ptr.rs @@ -62,12 +62,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) && let ExprKind::Path(ref qpath @ QPath::Resolved(None, path)) = func.kind && let Some(method_defid) = path.res.opt_def_id() { - if cx.tcx.is_diagnostic_item(sym::ptr_null, method_defid) { - OmitFollowedCastReason::Null(qpath) - } else if cx.tcx.is_diagnostic_item(sym::ptr_null_mut, method_defid) { - OmitFollowedCastReason::NullMut(qpath) - } else { - OmitFollowedCastReason::None + match cx.tcx.get_diagnostic_name(method_defid) { + Some(sym::ptr_null) => OmitFollowedCastReason::Null(qpath), + Some(sym::ptr_null_mut) => OmitFollowedCastReason::NullMut(qpath), + _ => OmitFollowedCastReason::None, } } else { OmitFollowedCastReason::None diff --git a/clippy_lints/src/large_include_file.rs b/clippy_lints/src/large_include_file.rs index 8707612fbdd0..48ce1afc6e69 100644 --- a/clippy_lints/src/large_include_file.rs +++ b/clippy_lints/src/large_include_file.rs @@ -64,8 +64,8 @@ impl LateLintPass<'_> for LargeIncludeFile { } && len as u64 > self.max_file_size && let Some(macro_call) = root_macro_call_first_node(cx, expr) - && (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id) - || cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id)) + && let Some(macro_name) = cx.tcx.get_diagnostic_name(macro_call.def_id) + && matches!(macro_name, sym::include_bytes_macro | sym::include_str_macro) { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then( diff --git a/clippy_lints/src/manual_retain.rs b/clippy_lints/src/manual_retain.rs index 98e8b1f5cf92..7fb88763e640 100644 --- a/clippy_lints/src/manual_retain.rs +++ b/clippy_lints/src/manual_retain.rs @@ -123,8 +123,8 @@ fn check_iter( ) { if let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind && let Some(copied_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id) - && (cx.tcx.is_diagnostic_item(sym::iter_copied, copied_def_id) - || cx.tcx.is_diagnostic_item(sym::iter_cloned, copied_def_id)) + && let Some(copied_name) = cx.tcx.get_diagnostic_name(copied_def_id) + && matches!(copied_name, sym::iter_copied | sym::iter_cloned) && let hir::ExprKind::MethodCall(_, iter_expr, [_], _) = &filter_expr.kind && let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(filter_expr.hir_id) && cx.tcx.is_diagnostic_item(sym::iter_filter, filter_def_id) @@ -243,9 +243,9 @@ fn make_sugg( } fn match_acceptable_sym(cx: &LateContext<'_>, collect_def_id: DefId) -> bool { - ACCEPTABLE_METHODS - .iter() - .any(|&method| cx.tcx.is_diagnostic_item(method, collect_def_id)) + cx.tcx + .get_diagnostic_name(collect_def_id) + .is_some_and(|collect_name| ACCEPTABLE_METHODS.contains(&collect_name)) } fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: Msrv) -> bool { diff --git a/clippy_lints/src/manual_strip.rs b/clippy_lints/src/manual_strip.rs index 6bf43a1c6d47..07cce4046ca4 100644 --- a/clippy_lints/src/manual_strip.rs +++ b/clippy_lints/src/manual_strip.rs @@ -75,12 +75,10 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip { && let ExprKind::Path(target_path) = &target_arg.kind && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id) { - let strip_kind = if cx.tcx.is_diagnostic_item(sym::str_starts_with, method_def_id) { - StripKind::Prefix - } else if cx.tcx.is_diagnostic_item(sym::str_ends_with, method_def_id) { - StripKind::Suffix - } else { - return; + let strip_kind = match cx.tcx.get_diagnostic_name(method_def_id) { + Some(sym::str_starts_with) => StripKind::Prefix, + Some(sym::str_ends_with) => StripKind::Suffix, + _ => return, }; let target_res = cx.qpath_res(target_path, target_arg.hir_id); if target_res == Res::Err { diff --git a/clippy_lints/src/methods/iter_out_of_bounds.rs b/clippy_lints/src/methods/iter_out_of_bounds.rs index 9a62b719a8fb..fa8f9d640ee6 100644 --- a/clippy_lints/src/methods/iter_out_of_bounds.rs +++ b/clippy_lints/src/methods/iter_out_of_bounds.rs @@ -24,32 +24,29 @@ fn get_iterator_length<'tcx>(cx: &LateContext<'tcx>, iter: &'tcx Expr<'tcx>) -> let ty::Adt(adt, substs) = cx.typeck_results().expr_ty(iter).kind() else { return None; }; - let did = adt.did(); - if cx.tcx.is_diagnostic_item(sym::ArrayIntoIter, did) { - // For array::IntoIter, the length is the second generic - // parameter. - substs.const_at(1).try_to_target_usize(cx.tcx).map(u128::from) - } else if cx.tcx.is_diagnostic_item(sym::SliceIter, did) - && let ExprKind::MethodCall(_, recv, ..) = iter.kind - { - if let ty::Array(_, len) = cx.typeck_results().expr_ty(recv).peel_refs().kind() { - // For slice::Iter<'_, T>, the receiver might be an array literal: [1,2,3].iter().skip(..) - len.try_to_target_usize(cx.tcx).map(u128::from) - } else if let Some(args) = VecArgs::hir(cx, expr_or_init(cx, recv)) { - match args { - VecArgs::Vec(vec) => vec.len().try_into().ok(), - VecArgs::Repeat(_, len) => expr_as_u128(cx, len), + match cx.tcx.get_diagnostic_name(adt.did()) { + Some(sym::ArrayIntoIter) => { + // For array::IntoIter, the length is the second generic + // parameter. + substs.const_at(1).try_to_target_usize(cx.tcx).map(u128::from) + }, + Some(sym::SliceIter) if let ExprKind::MethodCall(_, recv, ..) = iter.kind => { + if let ty::Array(_, len) = cx.typeck_results().expr_ty(recv).peel_refs().kind() { + // For slice::Iter<'_, T>, the receiver might be an array literal: [1,2,3].iter().skip(..) + len.try_to_target_usize(cx.tcx).map(u128::from) + } else if let Some(args) = VecArgs::hir(cx, expr_or_init(cx, recv)) { + match args { + VecArgs::Vec(vec) => vec.len().try_into().ok(), + VecArgs::Repeat(_, len) => expr_as_u128(cx, len), + } + } else { + None } - } else { - None - } - } else if cx.tcx.is_diagnostic_item(sym::IterEmpty, did) { - Some(0) - } else if cx.tcx.is_diagnostic_item(sym::IterOnce, did) { - Some(1) - } else { - None + }, + Some(sym::IterEmpty) => Some(0), + Some(sym::IterOnce) => Some(1), + _ => None, } } diff --git a/clippy_lints/src/methods/option_as_ref_deref.rs b/clippy_lints/src/methods/option_as_ref_deref.rs index 63ee922acfa0..906ead16fd0d 100644 --- a/clippy_lints/src/methods/option_as_ref_deref.rs +++ b/clippy_lints/src/methods/option_as_ref_deref.rs @@ -38,17 +38,13 @@ pub(super) fn check( ]; let is_deref = match map_arg.kind { - hir::ExprKind::Path(ref expr_qpath) => { - cx.qpath_res(expr_qpath, map_arg.hir_id) - .opt_def_id() - .is_some_and(|fun_def_id| { - cx.tcx.is_diagnostic_item(sym::deref_method, fun_def_id) - || cx.tcx.is_diagnostic_item(sym::deref_mut_method, fun_def_id) - || deref_aliases - .iter() - .any(|&sym| cx.tcx.is_diagnostic_item(sym, fun_def_id)) - }) - }, + hir::ExprKind::Path(ref expr_qpath) => cx + .qpath_res(expr_qpath, map_arg.hir_id) + .opt_def_id() + .and_then(|fun_def_id| cx.tcx.get_diagnostic_name(fun_def_id)) + .is_some_and(|fun_name| { + matches!(fun_name, sym::deref_method | sym::deref_mut_method) || deref_aliases.contains(&fun_name) + }), hir::ExprKind::Closure(&hir::Closure { body, .. }) => { let closure_body = cx.tcx.hir_body(body); let closure_expr = peel_blocks(closure_body.value); @@ -63,13 +59,11 @@ pub(super) fn check( .map(|x| &x.kind) .collect::>() && let [ty::adjustment::Adjust::Deref(None), ty::adjustment::Adjust::Borrow(_)] = *adj + && let method_did = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id).unwrap() + && let Some(method_name) = cx.tcx.get_diagnostic_name(method_did) { - let method_did = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id).unwrap(); - cx.tcx.is_diagnostic_item(sym::deref_method, method_did) - || cx.tcx.is_diagnostic_item(sym::deref_mut_method, method_did) - || deref_aliases - .iter() - .any(|&sym| cx.tcx.is_diagnostic_item(sym, method_did)) + matches!(method_name, sym::deref_method | sym::deref_mut_method) + || deref_aliases.contains(&method_name) } else { false } diff --git a/clippy_lints/src/methods/unnecessary_min_or_max.rs b/clippy_lints/src/methods/unnecessary_min_or_max.rs index 413881d5ec99..b87d81b71026 100644 --- a/clippy_lints/src/methods/unnecessary_min_or_max.rs +++ b/clippy_lints/src/methods/unnecessary_min_or_max.rs @@ -22,7 +22,8 @@ pub(super) fn check<'tcx>( let typeck_results = cx.typeck_results(); let ecx = ConstEvalCtxt::with_env(cx.tcx, cx.typing_env(), typeck_results); if let Some(id) = typeck_results.type_dependent_def_id(expr.hir_id) - && (cx.tcx.is_diagnostic_item(sym::cmp_ord_min, id) || cx.tcx.is_diagnostic_item(sym::cmp_ord_max, id)) + && let Some(fn_name) = cx.tcx.get_diagnostic_name(id) + && matches!(fn_name, sym::cmp_ord_min | sym::cmp_ord_max) { if let Some((left, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(recv) && let Some((right, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(arg) diff --git a/clippy_lints/src/operators/cmp_owned.rs b/clippy_lints/src/operators/cmp_owned.rs index 22ec4fe60fb0..604f8f5da0b8 100644 --- a/clippy_lints/src/operators/cmp_owned.rs +++ b/clippy_lints/src/operators/cmp_owned.rs @@ -47,14 +47,10 @@ fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) (arg, arg.span) }, ExprKind::Call(path, [arg]) - if path_def_id(cx, path).is_some_and(|did| { - if cx.tcx.is_diagnostic_item(sym::from_str_method, did) { - true - } else if cx.tcx.is_diagnostic_item(sym::from_fn, did) { - !is_copy(cx, typeck.expr_ty(expr)) - } else { - false - } + if path_def_id(cx, path).is_some_and(|did| match cx.tcx.get_diagnostic_name(did) { + Some(sym::from_str_method) => true, + Some(sym::from_fn) => !is_copy(cx, typeck.expr_ty(expr)), + _ => false, }) => { (arg, arg.span) diff --git a/clippy_lints/src/redundant_clone.rs b/clippy_lints/src/redundant_clone.rs index e57b8cc2d84e..193d9dda8845 100644 --- a/clippy_lints/src/redundant_clone.rs +++ b/clippy_lints/src/redundant_clone.rs @@ -96,14 +96,13 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone { let (fn_def_id, arg, arg_ty, clone_ret) = unwrap_or_continue!(is_call_with_ref_arg(cx, mir, &terminator.kind)); - let from_borrow = cx.tcx.lang_items().get(LangItem::CloneFn) == Some(fn_def_id) - || cx.tcx.is_diagnostic_item(sym::to_owned_method, fn_def_id) - || (cx.tcx.is_diagnostic_item(sym::to_string_method, fn_def_id) - && is_type_lang_item(cx, arg_ty, LangItem::String)); + let fn_name = cx.tcx.get_diagnostic_name(fn_def_id); - let from_deref = !from_borrow - && (cx.tcx.is_diagnostic_item(sym::path_to_pathbuf, fn_def_id) - || cx.tcx.is_diagnostic_item(sym::os_str_to_os_string, fn_def_id)); + let from_borrow = cx.tcx.lang_items().get(LangItem::CloneFn) == Some(fn_def_id) + || fn_name == Some(sym::to_owned_method) + || (fn_name == Some(sym::to_string_method) && is_type_lang_item(cx, arg_ty, LangItem::String)); + + let from_deref = !from_borrow && matches!(fn_name, Some(sym::path_to_pathbuf | sym::os_str_to_os_string)); if !from_borrow && !from_deref { continue; diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 490e6c974ae1..57d5900b045e 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -457,7 +457,8 @@ impl<'tcx> LateLintPass<'tcx> for TrimSplitWhitespace { } fn is_one_of_trim_diagnostic_items(cx: &LateContext<'_>, trim_def_id: DefId) -> bool { - cx.tcx.is_diagnostic_item(sym::str_trim, trim_def_id) - || cx.tcx.is_diagnostic_item(sym::str_trim_start, trim_def_id) - || cx.tcx.is_diagnostic_item(sym::str_trim_end, trim_def_id) + matches!( + cx.tcx.get_diagnostic_name(trim_def_id), + Some(sym::str_trim | sym::str_trim_start | sym::str_trim_end) + ) } diff --git a/clippy_lints/src/types/rc_buffer.rs b/clippy_lints/src/types/rc_buffer.rs index d691f1878b11..c4fd0fbf87a9 100644 --- a/clippy_lints/src/types/rc_buffer.rs +++ b/clippy_lints/src/types/rc_buffer.rs @@ -11,7 +11,8 @@ use super::RC_BUFFER; pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { let app = Applicability::Unspecified; - if cx.tcx.is_diagnostic_item(sym::Rc, def_id) { + let name = cx.tcx.get_diagnostic_name(def_id); + if name == Some(sym::Rc) { if let Some(alternate) = match_buffer_type(cx, qpath) { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then( @@ -56,7 +57,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ ); return true; } - } else if cx.tcx.is_diagnostic_item(sym::Arc, def_id) { + } else if name == Some(sym::Arc) { if let Some(alternate) = match_buffer_type(cx, qpath) { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then( diff --git a/clippy_lints/src/types/redundant_allocation.rs b/clippy_lints/src/types/redundant_allocation.rs index de3456a8ba5f..0ba51daf027d 100644 --- a/clippy_lints/src/types/redundant_allocation.rs +++ b/clippy_lints/src/types/redundant_allocation.rs @@ -13,14 +13,11 @@ use super::{REDUNDANT_ALLOCATION, utils}; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'tcx>, qpath: &QPath<'tcx>, def_id: DefId) -> bool { let mut applicability = Applicability::MaybeIncorrect; - let outer_sym = if Some(def_id) == cx.tcx.lang_items().owned_box() { - "Box" - } else if cx.tcx.is_diagnostic_item(sym::Rc, def_id) { - "Rc" - } else if cx.tcx.is_diagnostic_item(sym::Arc, def_id) { - "Arc" - } else { - return false; + let outer_sym = match cx.tcx.get_diagnostic_name(def_id) { + _ if Some(def_id) == cx.tcx.lang_items().owned_box() => "Box", + Some(sym::Rc) => "Rc", + Some(sym::Arc) => "Arc", + _ => return false, }; if let Some(span) = utils::match_borrows_parameter(cx, qpath) { diff --git a/clippy_lints/src/unnecessary_owned_empty_strings.rs b/clippy_lints/src/unnecessary_owned_empty_strings.rs index 7d996775a58c..28f4884fa311 100644 --- a/clippy_lints/src/unnecessary_owned_empty_strings.rs +++ b/clippy_lints/src/unnecessary_owned_empty_strings.rs @@ -41,7 +41,8 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryOwnedEmptyStrings { && let ty::Ref(_, inner_str, _) = cx.typeck_results().expr_ty_adjusted(expr).kind() && inner_str.is_str() { - if cx.tcx.is_diagnostic_item(sym::string_new, fun_def_id) { + let fun_name = cx.tcx.get_diagnostic_name(fun_def_id); + if fun_name == Some(sym::string_new) { span_lint_and_sugg( cx, UNNECESSARY_OWNED_EMPTY_STRINGS, @@ -51,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryOwnedEmptyStrings { "\"\"".to_owned(), Applicability::MachineApplicable, ); - } else if cx.tcx.is_diagnostic_item(sym::from_fn, fun_def_id) + } else if fun_name == Some(sym::from_fn) && let [arg] = args && let ExprKind::Lit(spanned) = &arg.kind && let LitKind::Str(symbol, _) = spanned.node diff --git a/clippy_lints/src/unnecessary_wraps.rs b/clippy_lints/src/unnecessary_wraps.rs index 54a7efc090ad..849c0b438a5a 100644 --- a/clippy_lints/src/unnecessary_wraps.rs +++ b/clippy_lints/src/unnecessary_wraps.rs @@ -107,12 +107,10 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { // Get the wrapper and inner types, if can't, abort. let (return_type_label, lang_item, inner_type) = if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id.expect_owner()).kind() { - if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did()) { - ("Option", OptionSome, subst.type_at(0)) - } else if cx.tcx.is_diagnostic_item(sym::Result, adt_def.did()) { - ("Result", ResultOk, subst.type_at(0)) - } else { - return; + match cx.tcx.get_diagnostic_name(adt_def.did()) { + Some(sym::Option) => ("Option", OptionSome, subst.type_at(0)), + Some(sym::Result) => ("Result", ResultOk, subst.type_at(0)), + _ => return, } } else { return; diff --git a/clippy_lints/src/zombie_processes.rs b/clippy_lints/src/zombie_processes.rs index 6ab94a522109..06d36276dfc0 100644 --- a/clippy_lints/src/zombie_processes.rs +++ b/clippy_lints/src/zombie_processes.rs @@ -177,8 +177,8 @@ impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> { Node::Expr(expr) if let ExprKind::AddrOf(_, Mutability::Not, _) = expr.kind => {}, Node::Expr(expr) if let Some(fn_did) = fn_def_id(self.cx, expr) - && (self.cx.tcx.is_diagnostic_item(sym::child_id, fn_did) - || self.cx.tcx.is_diagnostic_item(sym::child_kill, fn_did)) => {}, + && let Some(fn_name) = self.cx.tcx.get_diagnostic_name(fn_did) + && matches!(fn_name, sym::child_id | sym::child_kill) => {}, // Conservatively assume that all other kinds of nodes call `.wait()` somehow. _ => return Break(MaybeWait(ex.span)), @@ -351,9 +351,14 @@ fn check<'tcx>(cx: &LateContext<'tcx>, spawn_expr: &'tcx Expr<'tcx>, cause: Caus /// Checks if the given expression exits the process. fn is_exit_expression(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - fn_def_id(cx, expr).is_some_and(|fn_did| { - cx.tcx.is_diagnostic_item(sym::process_exit, fn_did) || cx.tcx.is_diagnostic_item(sym::process_abort, fn_did) - }) + if let Some(fn_did) = fn_def_id(cx, expr) + && let Some(fn_name) = cx.tcx.get_diagnostic_name(fn_did) + && matches!(fn_name, sym::process_exit | sym::process_abort) + { + true + } else { + false + } } #[derive(Debug)] From abb89608af8e5eae6f89a9b06bd9b5a10ca2afe9 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 10:59:02 +0200 Subject: [PATCH 014/116] higher: use `get_diagnostic_name` --- clippy_utils/src/higher.rs | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 4e0b00df9508..9db18f66e9a2 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -288,22 +288,23 @@ impl<'a> VecArgs<'a> { && is_expn_of(fun.span, sym::vec).is_some() && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id() { - return if cx.tcx.is_diagnostic_item(sym::vec_from_elem, fun_def_id) && args.len() == 2 { - // `vec![elem; size]` case - Some(VecArgs::Repeat(&args[0], &args[1])) - } else if cx.tcx.is_diagnostic_item(sym::slice_into_vec, fun_def_id) && args.len() == 1 { - // `vec![a, b, c]` case - if let ExprKind::Call(_, [arg]) = &args[0].kind - && let ExprKind::Array(args) = arg.kind - { - Some(VecArgs::Vec(args)) - } else { - None - } - } else if cx.tcx.is_diagnostic_item(sym::vec_new, fun_def_id) && args.is_empty() { - Some(VecArgs::Vec(&[])) - } else { - None + return match (cx.tcx.get_diagnostic_name(fun_def_id), args.len()) { + (Some(sym::vec_from_elem), 2) => { + // `vec![elem; size]` case + Some(VecArgs::Repeat(&args[0], &args[1])) + }, + (Some(sym::slice_into_vec), 1) => { + // `vec![a, b, c]` case + if let ExprKind::Call(_, [arg]) = &args[0].kind + && let ExprKind::Array(args) = arg.kind + { + Some(VecArgs::Vec(args)) + } else { + None + } + }, + (Some(sym::vec_new), 0) => Some(VecArgs::Vec(&[])), + _ => None, }; } From e6833462eb00a5aaa60c9be3472448cba600b404 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 19 Aug 2025 23:46:47 +0200 Subject: [PATCH 015/116] misc: destruct `args` directly avoids bounds checks --- clippy_utils/src/higher.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 9db18f66e9a2..425de63ae198 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -288,14 +288,14 @@ impl<'a> VecArgs<'a> { && is_expn_of(fun.span, sym::vec).is_some() && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id() { - return match (cx.tcx.get_diagnostic_name(fun_def_id), args.len()) { - (Some(sym::vec_from_elem), 2) => { + return match (cx.tcx.get_diagnostic_name(fun_def_id), args) { + (Some(sym::vec_from_elem), [elem, size]) => { // `vec![elem; size]` case - Some(VecArgs::Repeat(&args[0], &args[1])) + Some(VecArgs::Repeat(elem, size)) }, - (Some(sym::slice_into_vec), 1) => { + (Some(sym::slice_into_vec), [slice]) => { // `vec![a, b, c]` case - if let ExprKind::Call(_, [arg]) = &args[0].kind + if let ExprKind::Call(_, [arg]) = slice.kind && let ExprKind::Array(args) = arg.kind { Some(VecArgs::Vec(args)) @@ -303,7 +303,7 @@ impl<'a> VecArgs<'a> { None } }, - (Some(sym::vec_new), 0) => Some(VecArgs::Vec(&[])), + (Some(sym::vec_new), []) => Some(VecArgs::Vec(&[])), _ => None, }; } From 34f96a20827a26420878791d9a02a1d244937f2b Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 19 Aug 2025 23:47:38 +0200 Subject: [PATCH 016/116] misc: pull condition into guard `None` is the fallback case anyway --- clippy_utils/src/higher.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 425de63ae198..0caeddd9f088 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -293,15 +293,12 @@ impl<'a> VecArgs<'a> { // `vec![elem; size]` case Some(VecArgs::Repeat(elem, size)) }, - (Some(sym::slice_into_vec), [slice]) => { - // `vec![a, b, c]` case + (Some(sym::slice_into_vec), [slice]) if let ExprKind::Call(_, [arg]) = slice.kind - && let ExprKind::Array(args) = arg.kind - { - Some(VecArgs::Vec(args)) - } else { - None - } + && let ExprKind::Array(args) = arg.kind => + { + // `vec![a, b, c]` case + Some(VecArgs::Vec(args)) }, (Some(sym::vec_new), []) => Some(VecArgs::Vec(&[])), _ => None, From e243f89e3ae2257419699daf9dad6bf5b8a255cd Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 19 Aug 2025 23:56:00 +0200 Subject: [PATCH 017/116] misc: return earlier on `name = None` --- clippy_utils/src/higher.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 0caeddd9f088..bda28a663fb0 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -287,20 +287,21 @@ impl<'a> VecArgs<'a> { && let ExprKind::Path(ref qpath) = fun.kind && is_expn_of(fun.span, sym::vec).is_some() && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id() + && let Some(name) = cx.tcx.get_diagnostic_name(fun_def_id) { - return match (cx.tcx.get_diagnostic_name(fun_def_id), args) { - (Some(sym::vec_from_elem), [elem, size]) => { + return match (name, args) { + (sym::vec_from_elem, [elem, size]) => { // `vec![elem; size]` case Some(VecArgs::Repeat(elem, size)) }, - (Some(sym::slice_into_vec), [slice]) + (sym::slice_into_vec, [slice]) if let ExprKind::Call(_, [arg]) = slice.kind && let ExprKind::Array(args) = arg.kind => { // `vec![a, b, c]` case Some(VecArgs::Vec(args)) }, - (Some(sym::vec_new), []) => Some(VecArgs::Vec(&[])), + (sym::vec_new, []) => Some(VecArgs::Vec(&[])), _ => None, }; } From d32796da852940f5e9ba60990dfb662ce00dddf7 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 11:04:20 +0200 Subject: [PATCH 018/116] non_octal_unix_permissions: `get_diagnostic_name` once --- clippy_lints/src/non_octal_unix_permissions.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/non_octal_unix_permissions.rs b/clippy_lints/src/non_octal_unix_permissions.rs index 23a1622f30ff..1aec5412cda7 100644 --- a/clippy_lints/src/non_octal_unix_permissions.rs +++ b/clippy_lints/src/non_octal_unix_permissions.rs @@ -43,13 +43,11 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { match &expr.kind { ExprKind::MethodCall(path, func, [param], _) => { if let Some(adt) = cx.typeck_results().expr_ty(func).peel_refs().ty_adt_def() - && ((path.ident.name == sym::mode - && matches!( - cx.tcx.get_diagnostic_name(adt.did()), - Some(sym::FsOpenOptions | sym::DirBuilder) - )) - || (path.ident.name == sym::set_mode - && cx.tcx.is_diagnostic_item(sym::FsPermissions, adt.did()))) + && matches!( + (path.ident.name, cx.tcx.get_diagnostic_name(adt.did())), + (sym::mode, Some(sym::FsOpenOptions | sym::DirBuilder)) + | (sym::set_mode, Some(sym::FsPermissions)) + ) && let ExprKind::Lit(_) = param.kind && param.span.eq_ctxt(expr.span) && param From f0daf23b28d72557cf8d5a859ec3e32c9dee9fb4 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 00:41:05 +0200 Subject: [PATCH 019/116] misc: reorder to have struct first, then its method --- clippy_lints/src/non_octal_unix_permissions.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/non_octal_unix_permissions.rs b/clippy_lints/src/non_octal_unix_permissions.rs index 1aec5412cda7..cb934466bd89 100644 --- a/clippy_lints/src/non_octal_unix_permissions.rs +++ b/clippy_lints/src/non_octal_unix_permissions.rs @@ -44,9 +44,9 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { ExprKind::MethodCall(path, func, [param], _) => { if let Some(adt) = cx.typeck_results().expr_ty(func).peel_refs().ty_adt_def() && matches!( - (path.ident.name, cx.tcx.get_diagnostic_name(adt.did())), - (sym::mode, Some(sym::FsOpenOptions | sym::DirBuilder)) - | (sym::set_mode, Some(sym::FsPermissions)) + (cx.tcx.get_diagnostic_name(adt.did()), path.ident.name), + (Some(sym::FsOpenOptions | sym::DirBuilder), sym::mode) + | (Some(sym::FsPermissions), sym::set_mode) ) && let ExprKind::Lit(_) = param.kind && param.span.eq_ctxt(expr.span) From ff831ed40eaab37a13cb5f9a90b80ec97d74299c Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 00:49:30 +0200 Subject: [PATCH 020/116] redundant_clone: partially inline `is_type_diagnostic_item` to be able to reuse `did` --- clippy_lints/src/redundant_clone.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/redundant_clone.rs b/clippy_lints/src/redundant_clone.rs index 193d9dda8845..1d58cdd26d88 100644 --- a/clippy_lints/src/redundant_clone.rs +++ b/clippy_lints/src/redundant_clone.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; use clippy_utils::fn_has_unsatisfiable_preds; use clippy_utils::mir::{LocalUsage, PossibleBorrowerMap, visit_local_usage}; use clippy_utils::source::SpanRangeExt; -use clippy_utils::ty::{has_drop, is_copy, is_type_diagnostic_item, is_type_lang_item, walk_ptrs_ty_depth}; +use clippy_utils::ty::{has_drop, is_copy, is_type_lang_item, walk_ptrs_ty_depth}; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{Body, FnDecl, LangItem, def_id}; @@ -147,8 +147,9 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone { is_call_with_ref_arg(cx, mir, &pred_terminator.kind) && res == cloned && cx.tcx.is_diagnostic_item(sym::deref_method, pred_fn_def_id) - && (is_type_diagnostic_item(cx, pred_arg_ty, sym::PathBuf) - || is_type_diagnostic_item(cx, pred_arg_ty, sym::OsString)) + && let ty::Adt(pred_arg_def, _) = pred_arg_ty.kind() + && let Some(pred_arg_name) = cx.tcx.get_diagnostic_name(pred_arg_def.did()) + && matches!(pred_arg_name, sym::PathBuf | sym::OsString) { (pred_arg, res) } else { From 40e67abb64a561f1e6a6fb4fbe0588b00a0da553 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 01:01:09 +0200 Subject: [PATCH 021/116] len_zero: use `get_diagnostic_name` --- clippy_lints/src/len_zero.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 57deb011f2b0..28a0fbc05115 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -355,12 +355,15 @@ fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option Some(LenOutput::Option(def_id)), + Some(sym::Result) => Some(LenOutput::Result(def_id)), + _ => None, } + && is_first_generic_integral(segment) + { + return Some(res); } return None; @@ -368,11 +371,10 @@ fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option Some(LenOutput::Integral), - ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) => { - subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did())) - }, - ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => { - subs.type_at(0).is_integral().then(|| LenOutput::Result(adt.did())) + ty::Adt(adt, subs) => match cx.tcx.get_diagnostic_name(adt.did()) { + Some(sym::Option) => subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did())), + Some(sym::Result) => subs.type_at(0).is_integral().then(|| LenOutput::Result(adt.did())), + _ => None, }, _ => None, } From ff5f09512021e8956a79871bb0e0f33ddab0e453 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 01:02:07 +0200 Subject: [PATCH 022/116] misc: pull another condition into match arm --- clippy_lints/src/len_zero.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 28a0fbc05115..f44a5fdf715e 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -371,9 +371,9 @@ fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option Some(LenOutput::Integral), - ty::Adt(adt, subs) => match cx.tcx.get_diagnostic_name(adt.did()) { - Some(sym::Option) => subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did())), - Some(sym::Result) => subs.type_at(0).is_integral().then(|| LenOutput::Result(adt.did())), + ty::Adt(adt, subs) if subs.type_at(0).is_integral() => match cx.tcx.get_diagnostic_name(adt.did()) { + Some(sym::Option) => Some(LenOutput::Option(adt.did())), + Some(sym::Result) => Some(LenOutput::Result(adt.did())), _ => None, }, _ => None, From 0a8a7f92e8c3c5d6326d006deef36af158e6cbd0 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 10:57:23 +0200 Subject: [PATCH 023/116] non_canonical_impls: save diagnostic name to a variable --- clippy_lints/src/non_canonical_impls.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/non_canonical_impls.rs b/clippy_lints/src/non_canonical_impls.rs index 04b092769666..19fd1925e908 100644 --- a/clippy_lints/src/non_canonical_impls.rs +++ b/clippy_lints/src/non_canonical_impls.rs @@ -134,7 +134,8 @@ impl LateLintPass<'_> for NonCanonicalImpls { return; } - if cx.tcx.is_diagnostic_item(sym::Clone, trait_impl.def_id) + let trait_name = cx.tcx.get_diagnostic_name(trait_impl.def_id); + if trait_name == Some(sym::Clone) && let Some(copy_def_id) = cx.tcx.get_diagnostic_item(sym::Copy) && implements_trait(cx, trait_impl.self_ty(), copy_def_id, &[]) { @@ -175,7 +176,7 @@ impl LateLintPass<'_> for NonCanonicalImpls { } } - if cx.tcx.is_diagnostic_item(sym::PartialOrd, trait_impl.def_id) + if trait_name == Some(sym::PartialOrd) && impl_item.ident.name == sym::partial_cmp && let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord) && implements_trait(cx, trait_impl.self_ty(), ord_def_id, &[]) From 29f19a9013d0e2d770ec95f32bbd5b76265db7e9 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 01:16:23 +0200 Subject: [PATCH 024/116] misc: don't bother checking the other case `trait_name` can only be _one_ of those at the same time --- clippy_lints/src/non_canonical_impls.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/clippy_lints/src/non_canonical_impls.rs b/clippy_lints/src/non_canonical_impls.rs index 19fd1925e908..ba67dc62abbd 100644 --- a/clippy_lints/src/non_canonical_impls.rs +++ b/clippy_lints/src/non_canonical_impls.rs @@ -171,12 +171,8 @@ impl LateLintPass<'_> for NonCanonicalImpls { String::new(), Applicability::MaybeIncorrect, ); - - return; } - } - - if trait_name == Some(sym::PartialOrd) + } else if trait_name == Some(sym::PartialOrd) && impl_item.ident.name == sym::partial_cmp && let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord) && implements_trait(cx, trait_impl.self_ty(), ord_def_id, &[]) From 1be146f64310f062e6c322acaf9bc2a8bcd79acb Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 20:56:39 +0200 Subject: [PATCH 025/116] entry: use `get_diagnostic_item` --- clippy_lints/src/entry.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index 182cb4e46d2b..4e24f69357db 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -274,12 +274,10 @@ fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Optio key, call_ctxt: expr.span.ctxt(), }; - if cx.tcx.is_diagnostic_item(sym::btreemap_contains_key, id) { - Some((MapType::BTree, expr)) - } else if cx.tcx.is_diagnostic_item(sym::hashmap_contains_key, id) { - Some((MapType::Hash, expr)) - } else { - None + match cx.tcx.get_diagnostic_name(id) { + Some(sym::btreemap_contains_key) => Some((MapType::BTree, expr)), + Some(sym::hashmap_contains_key) => Some((MapType::Hash, expr)), + _ => None, } }, _ => None, From b987e0178e3b17d7d7d1f39308c44b93668a100b Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 01:39:23 +0200 Subject: [PATCH 026/116] misc: rewrite as `if let` --- clippy_lints/src/entry.rs | 53 ++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index 4e24f69357db..dd38aab6e3fb 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -254,33 +254,34 @@ fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Optio _ => None, }); - match expr.kind { - ExprKind::MethodCall( - _, + if let ExprKind::MethodCall( + _, + map, + [ + Expr { + kind: ExprKind::AddrOf(_, _, key), + span: key_span, + .. + }, + ], + _, + ) = expr.kind + && key_span.eq_ctxt(expr.span) + { + let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?; + let expr = ContainsExpr { + negated, map, - [ - Expr { - kind: ExprKind::AddrOf(_, _, key), - span: key_span, - .. - }, - ], - _, - ) if key_span.eq_ctxt(expr.span) => { - let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?; - let expr = ContainsExpr { - negated, - map, - key, - call_ctxt: expr.span.ctxt(), - }; - match cx.tcx.get_diagnostic_name(id) { - Some(sym::btreemap_contains_key) => Some((MapType::BTree, expr)), - Some(sym::hashmap_contains_key) => Some((MapType::Hash, expr)), - _ => None, - } - }, - _ => None, + key, + call_ctxt: expr.span.ctxt(), + }; + match cx.tcx.get_diagnostic_name(id) { + Some(sym::btreemap_contains_key) => Some((MapType::BTree, expr)), + Some(sym::hashmap_contains_key) => Some((MapType::Hash, expr)), + _ => None, + } + } else { + None } } From 804c9ac52a9329a4a5bcf53ef6d4326d72d34e25 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 01:41:58 +0200 Subject: [PATCH 027/116] misc: match in multiple steps --- clippy_lints/src/entry.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index dd38aab6e3fb..53dc2c05add2 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -254,18 +254,12 @@ fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Optio _ => None, }); - if let ExprKind::MethodCall( - _, - map, - [ - Expr { - kind: ExprKind::AddrOf(_, _, key), - span: key_span, - .. - }, - ], - _, - ) = expr.kind + if let ExprKind::MethodCall(_, map, [arg], _) = expr.kind + && let Expr { + kind: ExprKind::AddrOf(_, _, key), + span: key_span, + .. + } = arg && key_span.eq_ctxt(expr.span) { let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?; From 5abee438bcd8bb2882464aeaf7afe7dd4f9d4426 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 21 Aug 2025 00:40:21 +0200 Subject: [PATCH 028/116] other uses of `get_diagnostic_name` --- clippy_lints/src/entry.rs | 4 +++- clippy_lints/src/mem_replace.rs | 7 +++---- clippy_lints/src/methods/filter_map.rs | 14 ++++++-------- clippy_lints/src/methods/map_flatten.rs | 8 ++++---- clippy_lints/src/methods/single_char_add_str.rs | 8 ++++---- .../src/operators/float_equality_without_abs.rs | 3 ++- clippy_lints/src/unused_io_amount.rs | 4 +++- clippy_utils/src/usage.rs | 8 +++----- 8 files changed, 28 insertions(+), 28 deletions(-) diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index 53dc2c05add2..bac5e22a9ceb 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -304,7 +304,9 @@ struct InsertExpr<'tcx> { fn try_parse_insert<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option> { if let ExprKind::MethodCall(_, map, [key, value], _) = expr.kind { let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?; - if cx.tcx.is_diagnostic_item(sym::btreemap_insert, id) || cx.tcx.is_diagnostic_item(sym::hashmap_insert, id) { + if let Some(insert) = cx.tcx.get_diagnostic_name(id) + && matches!(insert, sym::btreemap_insert | sym::hashmap_insert) + { Some(InsertExpr { map, key, value }) } else { None diff --git a/clippy_lints/src/mem_replace.rs b/clippy_lints/src/mem_replace.rs index 28efd2038b38..e39916f733d5 100644 --- a/clippy_lints/src/mem_replace.rs +++ b/clippy_lints/src/mem_replace.rs @@ -215,7 +215,8 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<' && let ExprKind::Path(ref repl_func_qpath) = repl_func.kind && let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id() { - if cx.tcx.is_diagnostic_item(sym::mem_uninitialized, repl_def_id) { + let repl_name = cx.tcx.get_diagnostic_name(repl_def_id); + if repl_name == Some(sym::mem_uninitialized) { let Some(top_crate) = std_or_core(cx) else { return }; let mut applicability = Applicability::MachineApplicable; span_lint_and_sugg( @@ -230,9 +231,7 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<' ), applicability, ); - } else if cx.tcx.is_diagnostic_item(sym::mem_zeroed, repl_def_id) - && !cx.typeck_results().expr_ty(src).is_primitive() - { + } else if repl_name == Some(sym::mem_zeroed) && !cx.typeck_results().expr_ty(src).is_primitive() { span_lint_and_help( cx, MEM_REPLACE_WITH_UNINIT, diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index 5b8457bdd164..2da0f8341b17 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -233,18 +233,16 @@ impl<'tcx> OffendingFilterExpr<'tcx> { // the latter only calls `effect` once let side_effect_expr_span = receiver.can_have_side_effects().then_some(receiver.span); - if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did()) && path.ident.name == sym::is_some { - Some(Self::IsSome { + match (cx.tcx.get_diagnostic_name(recv_ty.did()), path.ident.name) { + (Some(sym::Option), sym::is_some) => Some(Self::IsSome { receiver, side_effect_expr_span, - }) - } else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did()) && path.ident.name == sym::is_ok { - Some(Self::IsOk { + }), + (Some(sym::Result), sym::is_ok) => Some(Self::IsOk { receiver, side_effect_expr_span, - }) - } else { - None + }), + _ => None, } } else if matching_root_macro_call(cx, expr.span, sym::matches_macro).is_some() // we know for a fact that the wildcard pattern is the second arm diff --git a/clippy_lints/src/methods/map_flatten.rs b/clippy_lints/src/methods/map_flatten.rs index f7bb8c1d696d..750f933330a2 100644 --- a/clippy_lints/src/methods/map_flatten.rs +++ b/clippy_lints/src/methods/map_flatten.rs @@ -50,10 +50,10 @@ fn try_get_caller_ty_name_and_method_name( } } else { if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(caller_expr).kind() { - if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) { - return Some(("Option", "and_then")); - } else if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) { - return Some(("Result", "and_then")); + match cx.tcx.get_diagnostic_name(adt.did()) { + Some(sym::Option) => return Some(("Option", "and_then")), + Some(sym::Result) => return Some(("Result", "and_then")), + _ => {}, } } None diff --git a/clippy_lints/src/methods/single_char_add_str.rs b/clippy_lints/src/methods/single_char_add_str.rs index ccdf5529d537..ef3d7acdc01e 100644 --- a/clippy_lints/src/methods/single_char_add_str.rs +++ b/clippy_lints/src/methods/single_char_add_str.rs @@ -5,10 +5,10 @@ use rustc_span::sym; pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { - if cx.tcx.is_diagnostic_item(sym::string_push_str, fn_def_id) { - single_char_push_string::check(cx, expr, receiver, args); - } else if cx.tcx.is_diagnostic_item(sym::string_insert_str, fn_def_id) { - single_char_insert_string::check(cx, expr, receiver, args); + match cx.tcx.get_diagnostic_name(fn_def_id) { + Some(sym::string_push_str) => single_char_push_string::check(cx, expr, receiver, args), + Some(sym::string_insert_str) => single_char_insert_string::check(cx, expr, receiver, args), + _ => {}, } } } diff --git a/clippy_lints/src/operators/float_equality_without_abs.rs b/clippy_lints/src/operators/float_equality_without_abs.rs index 047a5a0159cb..b5f0d7197bbc 100644 --- a/clippy_lints/src/operators/float_equality_without_abs.rs +++ b/clippy_lints/src/operators/float_equality_without_abs.rs @@ -37,7 +37,8 @@ pub(crate) fn check<'tcx>( // right hand side matches either f32::EPSILON or f64::EPSILON && let ExprKind::Path(ref epsilon_path) = rhs.kind && let Res::Def(DefKind::AssocConst, def_id) = cx.qpath_res(epsilon_path, rhs.hir_id) - && ([sym::f32_epsilon, sym::f64_epsilon].into_iter().any(|sym| cx.tcx.is_diagnostic_item(sym, def_id))) + && let Some(epsilon) = cx.tcx.get_diagnostic_name(def_id) + && matches!(epsilon, sym::f32_epsilon| sym::f64_epsilon) // values of the subtractions on the left hand side are of the type float && let t_val_l = cx.typeck_results().expr_ty(val_l) diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs index f3cd3f1bb286..af3ad4566c46 100644 --- a/clippy_lints/src/unused_io_amount.rs +++ b/clippy_lints/src/unused_io_amount.rs @@ -89,7 +89,9 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount { { // We don't want to lint inside io::Read or io::Write implementations, as the author has more // information about their trait implementation than our lint, see https://github.com/rust-lang/rust-clippy/issues/4836 - if cx.tcx.is_diagnostic_item(sym::IoRead, trait_id) || cx.tcx.is_diagnostic_item(sym::IoWrite, trait_id) { + if let Some(trait_name) = cx.tcx.get_diagnostic_name(trait_id) + && matches!(trait_name, sym::IoRead | sym::IoWrite) + { return; } diff --git a/clippy_utils/src/usage.rs b/clippy_utils/src/usage.rs index 76d43feee12e..6eccbcdb1228 100644 --- a/clippy_utils/src/usage.rs +++ b/clippy_utils/src/usage.rs @@ -144,11 +144,9 @@ impl<'tcx> Visitor<'tcx> for BindingUsageFinder<'_, 'tcx> { /// Checks if the given expression is a macro call to `todo!()` or `unimplemented!()`. pub fn is_todo_unimplemented_macro(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - root_macro_call_first_node(cx, expr).is_some_and(|macro_call| { - [sym::todo_macro, sym::unimplemented_macro] - .iter() - .any(|&sym| cx.tcx.is_diagnostic_item(sym, macro_call.def_id)) - }) + root_macro_call_first_node(cx, expr) + .and_then(|macro_call| cx.tcx.get_diagnostic_name(macro_call.def_id)) + .is_some_and(|macro_name| matches!(macro_name, sym::todo_macro | sym::unimplemented_macro)) } /// Checks if the given expression is a stub, i.e., a `todo!()` or `unimplemented!()` expression, From f51b5afd5043c5514c0eccd9e8083694bfea967b Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 21 Aug 2025 00:00:25 +0200 Subject: [PATCH 029/116] is_default_equivalent_ctor: check for String once --- clippy_utils/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 8533fa855419..7a2c1bf40d9c 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -640,9 +640,10 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath< && let Some(impl_did) = cx.tcx.impl_of_assoc(def_id) && let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def() { - return std_types_symbols.iter().any(|&symbol| { - cx.tcx.is_diagnostic_item(symbol, adt.did()) || Some(adt.did()) == cx.tcx.lang_items().string() - }); + return Some(adt.did()) == cx.tcx.lang_items().string() + || std_types_symbols + .iter() + .any(|&symbol| cx.tcx.is_diagnostic_item(symbol, adt.did())); } false } From 81b4de649fd7795b99cdf6a77f0b5c49cfc778b3 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 21 Aug 2025 00:01:10 +0200 Subject: [PATCH 030/116] is_default_equivalent_ctor: store name in a var --- clippy_utils/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 7a2c1bf40d9c..3776a34ce33a 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -641,9 +641,7 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath< && let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def() { return Some(adt.did()) == cx.tcx.lang_items().string() - || std_types_symbols - .iter() - .any(|&symbol| cx.tcx.is_diagnostic_item(symbol, adt.did())); + || (cx.tcx.get_diagnostic_name(adt.did())).is_some_and(|adt_name| std_types_symbols.contains(&adt_name)); } false } From 79a95eee88fd17cea24a8ee9835baf3873b18fac Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 21 Aug 2025 23:48:09 +0200 Subject: [PATCH 031/116] use `option_arg_ty` --- clippy_lints/src/matches/try_err.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/matches/try_err.rs b/clippy_lints/src/matches/try_err.rs index ff7769af1df4..c133ed084241 100644 --- a/clippy_lints/src/matches/try_err.rs +++ b/clippy_lints/src/matches/try_err.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::ty::{is_type_diagnostic_item, option_arg_ty}; use clippy_utils::{get_parent_expr, is_res_lang_ctor, path_res}; use rustc_errors::Applicability; use rustc_hir::LangItem::ResultErr; @@ -116,9 +116,7 @@ fn poll_option_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> if let ty::Adt(def, subst) = ty.kind() && cx.tcx.lang_items().get(LangItem::Poll) == Some(def.did()) && let ready_ty = subst.type_at(0) - && let ty::Adt(ready_def, ready_subst) = ready_ty.kind() - && cx.tcx.is_diagnostic_item(sym::Option, ready_def.did()) - && let some_ty = ready_subst.type_at(0) + && let Some(some_ty) = option_arg_ty(cx, ready_ty) && let ty::Adt(some_def, some_subst) = some_ty.kind() && cx.tcx.is_diagnostic_item(sym::Result, some_def.did()) { From 575b613650aeb40d5fb7262fb35f4b36c8c30bd6 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 21 Aug 2025 01:12:57 +0200 Subject: [PATCH 032/116] use `is_type_diagnostic_item` --- clippy_lints/src/loops/missing_spin_loop.rs | 6 +++--- clippy_lints/src/loops/unused_enumerate_index.rs | 5 ++--- clippy_lints/src/methods/read_line_without_trim.rs | 4 ++-- clippy_lints/src/methods/suspicious_to_owned.rs | 5 ++--- clippy_lints/src/methods/unused_enumerate_index.rs | 7 +++---- clippy_lints/src/zombie_processes.rs | 5 +++-- clippy_utils/src/ty/mod.rs | 9 ++------- 7 files changed, 17 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/loops/missing_spin_loop.rs b/clippy_lints/src/loops/missing_spin_loop.rs index a9944d64ce2d..8a2d0036203a 100644 --- a/clippy_lints/src/loops/missing_spin_loop.rs +++ b/clippy_lints/src/loops/missing_spin_loop.rs @@ -1,10 +1,10 @@ use super::MISSING_SPIN_LOOP; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::std_or_core; +use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind}; use rustc_lint::LateContext; -use rustc_middle::ty; use rustc_span::sym; fn unpack_cond<'tcx>(cond: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { @@ -39,8 +39,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, body: &' ) = body.kind && let ExprKind::MethodCall(method, callee, ..) = unpack_cond(cond).kind && [sym::load, sym::compare_exchange, sym::compare_exchange_weak].contains(&method.ident.name) - && let ty::Adt(def, _args) = cx.typeck_results().expr_ty(callee).kind() - && cx.tcx.is_diagnostic_item(sym::AtomicBool, def.did()) + && let callee_ty = cx.typeck_results().expr_ty(callee) + && is_type_diagnostic_item(cx, callee_ty, sym::AtomicBool) && let Some(std_or_core) = std_or_core(cx) { span_lint_and_sugg( diff --git a/clippy_lints/src/loops/unused_enumerate_index.rs b/clippy_lints/src/loops/unused_enumerate_index.rs index 51e21aa9734e..13b93d2c0097 100644 --- a/clippy_lints/src/loops/unused_enumerate_index.rs +++ b/clippy_lints/src/loops/unused_enumerate_index.rs @@ -1,12 +1,12 @@ use super::UNUSED_ENUMERATE_INDEX; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; +use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{pat_is_wild, sugg}; use rustc_errors::Applicability; use rustc_hir::def::DefKind; use rustc_hir::{Expr, ExprKind, Pat, PatKind}; use rustc_lint::LateContext; -use rustc_middle::ty; use rustc_span::sym; /// Checks for the `UNUSED_ENUMERATE_INDEX` lint. @@ -17,8 +17,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_ && let ExprKind::MethodCall(_method, self_arg, [], _) = arg.kind && let ty = cx.typeck_results().expr_ty(arg) && pat_is_wild(cx, &index.kind, body) - && let ty::Adt(base, _) = *ty.kind() - && cx.tcx.is_diagnostic_item(sym::Enumerate, base.did()) + && is_type_diagnostic_item(cx, ty, sym::Enumerate) && let Some((DefKind::AssocFn, call_id)) = cx.typeck_results().type_dependent_def(arg.hir_id) && cx.tcx.is_diagnostic_item(sym::enumerate_method, call_id) { diff --git a/clippy_lints/src/methods/read_line_without_trim.rs b/clippy_lints/src/methods/read_line_without_trim.rs index 407f2e80aff2..6738bbf0a12b 100644 --- a/clippy_lints/src/methods/read_line_without_trim.rs +++ b/clippy_lints/src/methods/read_line_without_trim.rs @@ -31,8 +31,8 @@ fn parse_fails_on_trailing_newline(ty: Ty<'_>) -> bool { } pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) { - if let Some(recv_adt) = cx.typeck_results().expr_ty(recv).ty_adt_def() - && cx.tcx.is_diagnostic_item(sym::Stdin, recv_adt.did()) + let recv_ty = cx.typeck_results().expr_ty(recv); + if is_type_diagnostic_item(cx, recv_ty, sym::Stdin) && let ExprKind::Path(QPath::Resolved(_, path)) = arg.peel_borrows().kind && let Res::Local(local_id) = path.res { diff --git a/clippy_lints/src/methods/suspicious_to_owned.rs b/clippy_lints/src/methods/suspicious_to_owned.rs index ce7aefed01f4..ffc237e3c24c 100644 --- a/clippy_lints/src/methods/suspicious_to_owned.rs +++ b/clippy_lints/src/methods/suspicious_to_owned.rs @@ -1,11 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_diag_trait_item; use clippy_utils::source::snippet_with_context; +use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_middle::ty::print::with_forced_trimmed_paths; -use rustc_middle::ty::{self}; use rustc_span::sym; use super::SUSPICIOUS_TO_OWNED; @@ -14,8 +14,7 @@ pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) - if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) && is_diag_trait_item(cx, method_def_id, sym::ToOwned) && let input_type = cx.typeck_results().expr_ty(expr) - && let ty::Adt(adt, _) = cx.typeck_results().expr_ty(expr).kind() - && cx.tcx.is_diagnostic_item(sym::Cow, adt.did()) + && is_type_diagnostic_item(cx, input_type, sym::Cow) { let mut app = Applicability::MaybeIncorrect; let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut app).0; diff --git a/clippy_lints/src/methods/unused_enumerate_index.rs b/clippy_lints/src/methods/unused_enumerate_index.rs index af466fe091c2..af4ade3cc0f7 100644 --- a/clippy_lints/src/methods/unused_enumerate_index.rs +++ b/clippy_lints/src/methods/unused_enumerate_index.rs @@ -1,10 +1,10 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::{SpanRangeExt, snippet}; +use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{expr_or_init, is_trait_method, pat_is_wild}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, FnDecl, PatKind, TyKind}; use rustc_lint::LateContext; -use rustc_middle::ty::AdtDef; use rustc_span::{Span, sym}; use crate::loops::UNUSED_ENUMERATE_INDEX; @@ -39,9 +39,8 @@ use crate::loops::UNUSED_ENUMERATE_INDEX; /// * `closure_arg`: The argument to the map function call containing the closure/function to apply pub(super) fn check(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) { let recv_ty = cx.typeck_results().expr_ty(recv); - if let Some(recv_ty_defid) = recv_ty.ty_adt_def().map(AdtDef::did) - // If we call a method on a `std::iter::Enumerate` instance - && cx.tcx.is_diagnostic_item(sym::Enumerate, recv_ty_defid) + // If we call a method on a `std::iter::Enumerate` instance + if is_type_diagnostic_item(cx, recv_ty, sym::Enumerate) // If we are calling a method of the `Iterator` trait && is_trait_method(cx, call_expr, sym::Iterator) // And the map argument is a closure diff --git a/clippy_lints/src/zombie_processes.rs b/clippy_lints/src/zombie_processes.rs index 6ab94a522109..ceb7609cf4b5 100644 --- a/clippy_lints/src/zombie_processes.rs +++ b/clippy_lints/src/zombie_processes.rs @@ -1,5 +1,6 @@ use ControlFlow::{Break, Continue}; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{fn_def_id, get_enclosing_block, path_to_local_id}; use rustc_ast::Mutability; use rustc_ast::visit::visit_opt; @@ -58,8 +59,8 @@ declare_lint_pass!(ZombieProcesses => [ZOMBIE_PROCESSES]); impl<'tcx> LateLintPass<'tcx> for ZombieProcesses { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if let ExprKind::Call(..) | ExprKind::MethodCall(..) = expr.kind - && let Some(child_adt) = cx.typeck_results().expr_ty(expr).ty_adt_def() - && cx.tcx.is_diagnostic_item(sym::Child, child_adt.did()) + && let child_ty = cx.typeck_results().expr_ty(expr) + && is_type_diagnostic_item(cx, child_ty, sym::Child) { match cx.tcx.parent_hir_node(expr.hir_id) { Node::LetStmt(local) diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index fafc1d07e51e..8e302f9d2ad1 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -387,10 +387,7 @@ pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool { /// Checks if the type is a reference equals to a diagnostic item pub fn is_type_ref_to_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool { match ty.kind() { - ty::Ref(_, ref_ty, _) => match ref_ty.kind() { - ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did()), - _ => false, - }, + ty::Ref(_, ref_ty, _) => is_type_diagnostic_item(cx, *ref_ty, diag_item), _ => false, } } @@ -1378,9 +1375,7 @@ pub fn has_non_owning_mutable_access<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<' /// Check if `ty` is slice-like, i.e., `&[T]`, `[T; N]`, or `Vec`. pub fn is_slice_like<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { - ty.is_slice() - || ty.is_array() - || matches!(ty.kind(), ty::Adt(adt_def, _) if cx.tcx.is_diagnostic_item(sym::Vec, adt_def.did())) + ty.is_slice() || ty.is_array() || is_type_diagnostic_item(cx, ty, sym::Vec) } pub fn get_field_idx_by_name(ty: Ty<'_>, name: Symbol) -> Option { From f0420110fedcbc5a34ec2ffeab6ef1238e171b88 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 21 Aug 2025 23:34:48 +0200 Subject: [PATCH 033/116] useless_conversion: store name in a var --- clippy_lints/src/useless_conversion.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index e5b20c0e0a13..df0789db559a 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -386,12 +386,13 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { ExprKind::Call(path, [arg]) => { if let ExprKind::Path(ref qpath) = path.kind - && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() && !is_ty_alias(qpath) + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && let Some(name) = cx.tcx.get_diagnostic_name(def_id) { let a = cx.typeck_results().expr_ty(e); let b = cx.typeck_results().expr_ty(arg); - if cx.tcx.is_diagnostic_item(sym::try_from_fn, def_id) + if name == sym::try_from_fn && is_type_diagnostic_item(cx, a, sym::Result) && let ty::Adt(_, args) = a.kind() && let Some(a_type) = args.types().next() @@ -408,7 +409,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { ); } - if cx.tcx.is_diagnostic_item(sym::from_fn, def_id) && same_type_and_consts(a, b) { + if name == sym::from_fn && same_type_and_consts(a, b) { let mut app = Applicability::MachineApplicable; let sugg = Sugg::hir_with_context(cx, arg, e.span.ctxt(), "", &mut app).maybe_paren(); let sugg_msg = format!("consider removing `{}()`", snippet(cx, path.span, "From::from")); From d0de0ec4143cf7c486c02efea0d6f8879cada356 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 21 Aug 2025 23:51:26 +0200 Subject: [PATCH 034/116] use `result_error_type` --- clippy_lints/src/matches/try_err.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/matches/try_err.rs b/clippy_lints/src/matches/try_err.rs index c133ed084241..f6c319b753cd 100644 --- a/clippy_lints/src/matches/try_err.rs +++ b/clippy_lints/src/matches/try_err.rs @@ -101,11 +101,9 @@ fn result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> { if let ty::Adt(def, subst) = ty.kind() && cx.tcx.lang_items().get(LangItem::Poll) == Some(def.did()) - && let ready_ty = subst.type_at(0) - && let ty::Adt(ready_def, ready_subst) = ready_ty.kind() - && cx.tcx.is_diagnostic_item(sym::Result, ready_def.did()) { - Some(ready_subst.type_at(1)) + let ready_ty = subst.type_at(0); + result_error_type(cx, ready_ty) } else { None } @@ -117,10 +115,8 @@ fn poll_option_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> && cx.tcx.lang_items().get(LangItem::Poll) == Some(def.did()) && let ready_ty = subst.type_at(0) && let Some(some_ty) = option_arg_ty(cx, ready_ty) - && let ty::Adt(some_def, some_subst) = some_ty.kind() - && cx.tcx.is_diagnostic_item(sym::Result, some_def.did()) { - Some(some_subst.type_at(1)) + result_error_type(cx, some_ty) } else { None } From a5745a732de52067892e1474ec63b5edd7c28903 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 21 Aug 2025 18:42:37 +0200 Subject: [PATCH 035/116] use `is_path_diagnostic_item` --- clippy_lints/src/instant_subtraction.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/instant_subtraction.rs b/clippy_lints/src/instant_subtraction.rs index 91f65d0b79ca..13117f60abd5 100644 --- a/clippy_lints/src/instant_subtraction.rs +++ b/clippy_lints/src/instant_subtraction.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::Sugg; -use clippy_utils::ty; +use clippy_utils::{is_path_diagnostic_item, ty}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -107,8 +107,7 @@ impl LateLintPass<'_> for InstantSubtraction { fn is_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool { if let ExprKind::Call(fn_expr, []) = expr_block.kind - && let Some(fn_id) = clippy_utils::path_def_id(cx, fn_expr) - && cx.tcx.is_diagnostic_item(sym::instant_now, fn_id) + && is_path_diagnostic_item(cx, fn_expr, sym::instant_now) { true } else { From 3c3dc6da6fb49e7126fb84317dc2fb62bb869f19 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 21 Aug 2025 23:35:49 +0200 Subject: [PATCH 036/116] misc: don't bother checking the other branch --- clippy_lints/src/useless_conversion.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index df0789db559a..70ae982a4458 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -407,9 +407,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { None, hint, ); - } - - if name == sym::from_fn && same_type_and_consts(a, b) { + } else if name == sym::from_fn && same_type_and_consts(a, b) { let mut app = Applicability::MachineApplicable; let sugg = Sugg::hir_with_context(cx, arg, e.span.ctxt(), "", &mut app).maybe_paren(); let sugg_msg = format!("consider removing `{}()`", snippet(cx, path.span, "From::from")); From 043346b6ddaaf685a8374a4c6026ddc6d2220efd Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 21 Aug 2025 00:41:01 +0200 Subject: [PATCH 037/116] use a let-chain --- .../transmute/transmute_int_to_non_zero.rs | 54 +++++++++---------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/clippy_lints/src/transmute/transmute_int_to_non_zero.rs b/clippy_lints/src/transmute/transmute_int_to_non_zero.rs index f27aaa2fa77a..24489ee2e7a0 100644 --- a/clippy_lints/src/transmute/transmute_int_to_non_zero.rs +++ b/clippy_lints/src/transmute/transmute_int_to_non_zero.rs @@ -16,35 +16,29 @@ pub(super) fn check<'tcx>( to_ty: Ty<'tcx>, arg: &'tcx Expr<'_>, ) -> bool { - let tcx = cx.tcx; - - let (ty::Int(_) | ty::Uint(_), ty::Adt(adt, substs)) = (&from_ty.kind(), to_ty.kind()) else { - return false; - }; - - if !tcx.is_diagnostic_item(sym::NonZero, adt.did()) { - return false; + if let ty::Int(_) | ty::Uint(_) = from_ty.kind() + && let ty::Adt(adt, substs) = to_ty.kind() + && cx.tcx.is_diagnostic_item(sym::NonZero, adt.did()) + && let int_ty = substs.type_at(0) + && from_ty == int_ty + { + span_lint_and_then( + cx, + TRANSMUTE_INT_TO_NON_ZERO, + e.span, + format!("transmute from a `{from_ty}` to a `{}<{int_ty}>`", sym::NonZero), + |diag| { + let arg = sugg::Sugg::hir(cx, arg, ".."); + diag.span_suggestion( + e.span, + "consider using", + format!("{}::{}({arg})", sym::NonZero, sym::new_unchecked), + Applicability::Unspecified, + ); + }, + ); + true + } else { + false } - - let int_ty = substs.type_at(0); - if from_ty != int_ty { - return false; - } - - span_lint_and_then( - cx, - TRANSMUTE_INT_TO_NON_ZERO, - e.span, - format!("transmute from a `{from_ty}` to a `{}<{int_ty}>`", sym::NonZero), - |diag| { - let arg = sugg::Sugg::hir(cx, arg, ".."); - diag.span_suggestion( - e.span, - "consider using", - format!("{}::{}({arg})", sym::NonZero, sym::new_unchecked), - Applicability::Unspecified, - ); - }, - ); - true } From cb3e5af74658fefa55b513a55d2789c84ef4bfd5 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 21 Aug 2025 23:52:20 +0200 Subject: [PATCH 038/116] don't repeat the `ty::Adt` check is done already inside `is_type_diagnostic_item` --- clippy_lints/src/matches/try_err.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/matches/try_err.rs b/clippy_lints/src/matches/try_err.rs index f6c319b753cd..d595f21aa629 100644 --- a/clippy_lints/src/matches/try_err.rs +++ b/clippy_lints/src/matches/try_err.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::{is_type_diagnostic_item, option_arg_ty}; +use clippy_utils::ty::option_arg_ty; use clippy_utils::{get_parent_expr, is_res_lang_ctor, path_res}; use rustc_errors::Applicability; use rustc_hir::LangItem::ResultErr; @@ -88,8 +88,8 @@ fn find_return_type<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx ExprKind<'_>) -> O /// Extracts the error type from Result. fn result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> { - if let ty::Adt(_, subst) = ty.kind() - && is_type_diagnostic_item(cx, ty, sym::Result) + if let ty::Adt(def, subst) = ty.kind() + && cx.tcx.is_diagnostic_item(sym::Result, def.did()) { Some(subst.type_at(1)) } else { From ea7672a1fbdc890ed25b45cdbc95e13919cdc6c7 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 21 Aug 2025 00:42:39 +0200 Subject: [PATCH 039/116] use `span_lint_and_sugg` --- .../src/transmute/transmute_int_to_non_zero.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/clippy_lints/src/transmute/transmute_int_to_non_zero.rs b/clippy_lints/src/transmute/transmute_int_to_non_zero.rs index 24489ee2e7a0..d1085d7c59fa 100644 --- a/clippy_lints/src/transmute/transmute_int_to_non_zero.rs +++ b/clippy_lints/src/transmute/transmute_int_to_non_zero.rs @@ -1,5 +1,5 @@ use super::TRANSMUTE_INT_TO_NON_ZERO; -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::sugg; use rustc_errors::Applicability; use rustc_hir::Expr; @@ -22,20 +22,15 @@ pub(super) fn check<'tcx>( && let int_ty = substs.type_at(0) && from_ty == int_ty { - span_lint_and_then( + let arg = sugg::Sugg::hir(cx, arg, ".."); + span_lint_and_sugg( cx, TRANSMUTE_INT_TO_NON_ZERO, e.span, format!("transmute from a `{from_ty}` to a `{}<{int_ty}>`", sym::NonZero), - |diag| { - let arg = sugg::Sugg::hir(cx, arg, ".."); - diag.span_suggestion( - e.span, - "consider using", - format!("{}::{}({arg})", sym::NonZero, sym::new_unchecked), - Applicability::Unspecified, - ); - }, + "consider using", + format!("{}::{}({arg})", sym::NonZero, sym::new_unchecked), + Applicability::Unspecified, ); true } else { From ed69835374e72a2c08f8bad0959b212d35cc267c Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 22 Aug 2025 00:00:23 +0200 Subject: [PATCH 040/116] basically `clippy::let_if_seq` --- clippy_lints/src/matches/try_err.rs | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/clippy_lints/src/matches/try_err.rs b/clippy_lints/src/matches/try_err.rs index d595f21aa629..af90cb5e6733 100644 --- a/clippy_lints/src/matches/try_err.rs +++ b/clippy_lints/src/matches/try_err.rs @@ -28,25 +28,15 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, scrutine && is_res_lang_ctor(cx, path_res(cx, err_fun), ResultErr) && let Some(return_ty) = find_return_type(cx, &expr.kind) { - let prefix; - let suffix; - let err_ty; - - if let Some(ty) = result_error_type(cx, return_ty) { - prefix = "Err("; - suffix = ")"; - err_ty = ty; + let (prefix, suffix, err_ty) = if let Some(ty) = result_error_type(cx, return_ty) { + ("Err(", ")", ty) } else if let Some(ty) = poll_result_error_type(cx, return_ty) { - prefix = "Poll::Ready(Err("; - suffix = "))"; - err_ty = ty; + ("Poll::Ready(Err(", "))", ty) } else if let Some(ty) = poll_option_result_error_type(cx, return_ty) { - prefix = "Poll::Ready(Some(Err("; - suffix = ")))"; - err_ty = ty; + ("Poll::Ready(Some(Err(", ")))", ty) } else { return; - } + }; span_lint_and_then( cx, From a50c339fb1ff320412896eeb28371b1af49c65b0 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 21 Aug 2025 00:46:48 +0200 Subject: [PATCH 041/116] replace qualified path with `use` --- clippy_lints/src/transmute/transmute_int_to_non_zero.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/transmute/transmute_int_to_non_zero.rs b/clippy_lints/src/transmute/transmute_int_to_non_zero.rs index d1085d7c59fa..2257aa1b73c8 100644 --- a/clippy_lints/src/transmute/transmute_int_to_non_zero.rs +++ b/clippy_lints/src/transmute/transmute_int_to_non_zero.rs @@ -1,6 +1,6 @@ use super::TRANSMUTE_INT_TO_NON_ZERO; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::sugg; +use clippy_utils::sugg::Sugg; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; @@ -22,7 +22,7 @@ pub(super) fn check<'tcx>( && let int_ty = substs.type_at(0) && from_ty == int_ty { - let arg = sugg::Sugg::hir(cx, arg, ".."); + let arg = Sugg::hir(cx, arg, ".."); span_lint_and_sugg( cx, TRANSMUTE_INT_TO_NON_ZERO, From 4ac5e9c027550bed984973f0bd655939eaacf30a Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 19 Aug 2025 23:44:02 +0200 Subject: [PATCH 042/116] `get_diagnostic_name` in other places --- clippy_lints/src/casts/ptr_as_ptr.rs | 10 ++--- clippy_lints/src/large_include_file.rs | 4 +- clippy_lints/src/manual_retain.rs | 10 ++--- clippy_lints/src/manual_strip.rs | 10 ++--- .../src/methods/iter_out_of_bounds.rs | 45 +++++++++---------- .../src/methods/option_as_ref_deref.rs | 28 +++++------- .../src/methods/unnecessary_min_or_max.rs | 3 +- clippy_lints/src/operators/cmp_owned.rs | 12 ++--- clippy_lints/src/redundant_clone.rs | 13 +++--- clippy_lints/src/strings.rs | 7 +-- clippy_lints/src/types/rc_buffer.rs | 5 ++- .../src/types/redundant_allocation.rs | 13 +++--- .../src/unnecessary_owned_empty_strings.rs | 5 ++- clippy_lints/src/unnecessary_wraps.rs | 10 ++--- clippy_lints/src/zombie_processes.rs | 15 ++++--- 15 files changed, 88 insertions(+), 102 deletions(-) diff --git a/clippy_lints/src/casts/ptr_as_ptr.rs b/clippy_lints/src/casts/ptr_as_ptr.rs index 890754090989..c5be504543c2 100644 --- a/clippy_lints/src/casts/ptr_as_ptr.rs +++ b/clippy_lints/src/casts/ptr_as_ptr.rs @@ -62,12 +62,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) && let ExprKind::Path(ref qpath @ QPath::Resolved(None, path)) = func.kind && let Some(method_defid) = path.res.opt_def_id() { - if cx.tcx.is_diagnostic_item(sym::ptr_null, method_defid) { - OmitFollowedCastReason::Null(qpath) - } else if cx.tcx.is_diagnostic_item(sym::ptr_null_mut, method_defid) { - OmitFollowedCastReason::NullMut(qpath) - } else { - OmitFollowedCastReason::None + match cx.tcx.get_diagnostic_name(method_defid) { + Some(sym::ptr_null) => OmitFollowedCastReason::Null(qpath), + Some(sym::ptr_null_mut) => OmitFollowedCastReason::NullMut(qpath), + _ => OmitFollowedCastReason::None, } } else { OmitFollowedCastReason::None diff --git a/clippy_lints/src/large_include_file.rs b/clippy_lints/src/large_include_file.rs index 8707612fbdd0..48ce1afc6e69 100644 --- a/clippy_lints/src/large_include_file.rs +++ b/clippy_lints/src/large_include_file.rs @@ -64,8 +64,8 @@ impl LateLintPass<'_> for LargeIncludeFile { } && len as u64 > self.max_file_size && let Some(macro_call) = root_macro_call_first_node(cx, expr) - && (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id) - || cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id)) + && let Some(macro_name) = cx.tcx.get_diagnostic_name(macro_call.def_id) + && matches!(macro_name, sym::include_bytes_macro | sym::include_str_macro) { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then( diff --git a/clippy_lints/src/manual_retain.rs b/clippy_lints/src/manual_retain.rs index 98e8b1f5cf92..7fb88763e640 100644 --- a/clippy_lints/src/manual_retain.rs +++ b/clippy_lints/src/manual_retain.rs @@ -123,8 +123,8 @@ fn check_iter( ) { if let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind && let Some(copied_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id) - && (cx.tcx.is_diagnostic_item(sym::iter_copied, copied_def_id) - || cx.tcx.is_diagnostic_item(sym::iter_cloned, copied_def_id)) + && let Some(copied_name) = cx.tcx.get_diagnostic_name(copied_def_id) + && matches!(copied_name, sym::iter_copied | sym::iter_cloned) && let hir::ExprKind::MethodCall(_, iter_expr, [_], _) = &filter_expr.kind && let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(filter_expr.hir_id) && cx.tcx.is_diagnostic_item(sym::iter_filter, filter_def_id) @@ -243,9 +243,9 @@ fn make_sugg( } fn match_acceptable_sym(cx: &LateContext<'_>, collect_def_id: DefId) -> bool { - ACCEPTABLE_METHODS - .iter() - .any(|&method| cx.tcx.is_diagnostic_item(method, collect_def_id)) + cx.tcx + .get_diagnostic_name(collect_def_id) + .is_some_and(|collect_name| ACCEPTABLE_METHODS.contains(&collect_name)) } fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: Msrv) -> bool { diff --git a/clippy_lints/src/manual_strip.rs b/clippy_lints/src/manual_strip.rs index 6bf43a1c6d47..07cce4046ca4 100644 --- a/clippy_lints/src/manual_strip.rs +++ b/clippy_lints/src/manual_strip.rs @@ -75,12 +75,10 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip { && let ExprKind::Path(target_path) = &target_arg.kind && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id) { - let strip_kind = if cx.tcx.is_diagnostic_item(sym::str_starts_with, method_def_id) { - StripKind::Prefix - } else if cx.tcx.is_diagnostic_item(sym::str_ends_with, method_def_id) { - StripKind::Suffix - } else { - return; + let strip_kind = match cx.tcx.get_diagnostic_name(method_def_id) { + Some(sym::str_starts_with) => StripKind::Prefix, + Some(sym::str_ends_with) => StripKind::Suffix, + _ => return, }; let target_res = cx.qpath_res(target_path, target_arg.hir_id); if target_res == Res::Err { diff --git a/clippy_lints/src/methods/iter_out_of_bounds.rs b/clippy_lints/src/methods/iter_out_of_bounds.rs index 9a62b719a8fb..fa8f9d640ee6 100644 --- a/clippy_lints/src/methods/iter_out_of_bounds.rs +++ b/clippy_lints/src/methods/iter_out_of_bounds.rs @@ -24,32 +24,29 @@ fn get_iterator_length<'tcx>(cx: &LateContext<'tcx>, iter: &'tcx Expr<'tcx>) -> let ty::Adt(adt, substs) = cx.typeck_results().expr_ty(iter).kind() else { return None; }; - let did = adt.did(); - if cx.tcx.is_diagnostic_item(sym::ArrayIntoIter, did) { - // For array::IntoIter, the length is the second generic - // parameter. - substs.const_at(1).try_to_target_usize(cx.tcx).map(u128::from) - } else if cx.tcx.is_diagnostic_item(sym::SliceIter, did) - && let ExprKind::MethodCall(_, recv, ..) = iter.kind - { - if let ty::Array(_, len) = cx.typeck_results().expr_ty(recv).peel_refs().kind() { - // For slice::Iter<'_, T>, the receiver might be an array literal: [1,2,3].iter().skip(..) - len.try_to_target_usize(cx.tcx).map(u128::from) - } else if let Some(args) = VecArgs::hir(cx, expr_or_init(cx, recv)) { - match args { - VecArgs::Vec(vec) => vec.len().try_into().ok(), - VecArgs::Repeat(_, len) => expr_as_u128(cx, len), + match cx.tcx.get_diagnostic_name(adt.did()) { + Some(sym::ArrayIntoIter) => { + // For array::IntoIter, the length is the second generic + // parameter. + substs.const_at(1).try_to_target_usize(cx.tcx).map(u128::from) + }, + Some(sym::SliceIter) if let ExprKind::MethodCall(_, recv, ..) = iter.kind => { + if let ty::Array(_, len) = cx.typeck_results().expr_ty(recv).peel_refs().kind() { + // For slice::Iter<'_, T>, the receiver might be an array literal: [1,2,3].iter().skip(..) + len.try_to_target_usize(cx.tcx).map(u128::from) + } else if let Some(args) = VecArgs::hir(cx, expr_or_init(cx, recv)) { + match args { + VecArgs::Vec(vec) => vec.len().try_into().ok(), + VecArgs::Repeat(_, len) => expr_as_u128(cx, len), + } + } else { + None } - } else { - None - } - } else if cx.tcx.is_diagnostic_item(sym::IterEmpty, did) { - Some(0) - } else if cx.tcx.is_diagnostic_item(sym::IterOnce, did) { - Some(1) - } else { - None + }, + Some(sym::IterEmpty) => Some(0), + Some(sym::IterOnce) => Some(1), + _ => None, } } diff --git a/clippy_lints/src/methods/option_as_ref_deref.rs b/clippy_lints/src/methods/option_as_ref_deref.rs index 63ee922acfa0..906ead16fd0d 100644 --- a/clippy_lints/src/methods/option_as_ref_deref.rs +++ b/clippy_lints/src/methods/option_as_ref_deref.rs @@ -38,17 +38,13 @@ pub(super) fn check( ]; let is_deref = match map_arg.kind { - hir::ExprKind::Path(ref expr_qpath) => { - cx.qpath_res(expr_qpath, map_arg.hir_id) - .opt_def_id() - .is_some_and(|fun_def_id| { - cx.tcx.is_diagnostic_item(sym::deref_method, fun_def_id) - || cx.tcx.is_diagnostic_item(sym::deref_mut_method, fun_def_id) - || deref_aliases - .iter() - .any(|&sym| cx.tcx.is_diagnostic_item(sym, fun_def_id)) - }) - }, + hir::ExprKind::Path(ref expr_qpath) => cx + .qpath_res(expr_qpath, map_arg.hir_id) + .opt_def_id() + .and_then(|fun_def_id| cx.tcx.get_diagnostic_name(fun_def_id)) + .is_some_and(|fun_name| { + matches!(fun_name, sym::deref_method | sym::deref_mut_method) || deref_aliases.contains(&fun_name) + }), hir::ExprKind::Closure(&hir::Closure { body, .. }) => { let closure_body = cx.tcx.hir_body(body); let closure_expr = peel_blocks(closure_body.value); @@ -63,13 +59,11 @@ pub(super) fn check( .map(|x| &x.kind) .collect::>() && let [ty::adjustment::Adjust::Deref(None), ty::adjustment::Adjust::Borrow(_)] = *adj + && let method_did = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id).unwrap() + && let Some(method_name) = cx.tcx.get_diagnostic_name(method_did) { - let method_did = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id).unwrap(); - cx.tcx.is_diagnostic_item(sym::deref_method, method_did) - || cx.tcx.is_diagnostic_item(sym::deref_mut_method, method_did) - || deref_aliases - .iter() - .any(|&sym| cx.tcx.is_diagnostic_item(sym, method_did)) + matches!(method_name, sym::deref_method | sym::deref_mut_method) + || deref_aliases.contains(&method_name) } else { false } diff --git a/clippy_lints/src/methods/unnecessary_min_or_max.rs b/clippy_lints/src/methods/unnecessary_min_or_max.rs index 413881d5ec99..b87d81b71026 100644 --- a/clippy_lints/src/methods/unnecessary_min_or_max.rs +++ b/clippy_lints/src/methods/unnecessary_min_or_max.rs @@ -22,7 +22,8 @@ pub(super) fn check<'tcx>( let typeck_results = cx.typeck_results(); let ecx = ConstEvalCtxt::with_env(cx.tcx, cx.typing_env(), typeck_results); if let Some(id) = typeck_results.type_dependent_def_id(expr.hir_id) - && (cx.tcx.is_diagnostic_item(sym::cmp_ord_min, id) || cx.tcx.is_diagnostic_item(sym::cmp_ord_max, id)) + && let Some(fn_name) = cx.tcx.get_diagnostic_name(id) + && matches!(fn_name, sym::cmp_ord_min | sym::cmp_ord_max) { if let Some((left, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(recv) && let Some((right, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(arg) diff --git a/clippy_lints/src/operators/cmp_owned.rs b/clippy_lints/src/operators/cmp_owned.rs index 22ec4fe60fb0..604f8f5da0b8 100644 --- a/clippy_lints/src/operators/cmp_owned.rs +++ b/clippy_lints/src/operators/cmp_owned.rs @@ -47,14 +47,10 @@ fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) (arg, arg.span) }, ExprKind::Call(path, [arg]) - if path_def_id(cx, path).is_some_and(|did| { - if cx.tcx.is_diagnostic_item(sym::from_str_method, did) { - true - } else if cx.tcx.is_diagnostic_item(sym::from_fn, did) { - !is_copy(cx, typeck.expr_ty(expr)) - } else { - false - } + if path_def_id(cx, path).is_some_and(|did| match cx.tcx.get_diagnostic_name(did) { + Some(sym::from_str_method) => true, + Some(sym::from_fn) => !is_copy(cx, typeck.expr_ty(expr)), + _ => false, }) => { (arg, arg.span) diff --git a/clippy_lints/src/redundant_clone.rs b/clippy_lints/src/redundant_clone.rs index e57b8cc2d84e..193d9dda8845 100644 --- a/clippy_lints/src/redundant_clone.rs +++ b/clippy_lints/src/redundant_clone.rs @@ -96,14 +96,13 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone { let (fn_def_id, arg, arg_ty, clone_ret) = unwrap_or_continue!(is_call_with_ref_arg(cx, mir, &terminator.kind)); - let from_borrow = cx.tcx.lang_items().get(LangItem::CloneFn) == Some(fn_def_id) - || cx.tcx.is_diagnostic_item(sym::to_owned_method, fn_def_id) - || (cx.tcx.is_diagnostic_item(sym::to_string_method, fn_def_id) - && is_type_lang_item(cx, arg_ty, LangItem::String)); + let fn_name = cx.tcx.get_diagnostic_name(fn_def_id); - let from_deref = !from_borrow - && (cx.tcx.is_diagnostic_item(sym::path_to_pathbuf, fn_def_id) - || cx.tcx.is_diagnostic_item(sym::os_str_to_os_string, fn_def_id)); + let from_borrow = cx.tcx.lang_items().get(LangItem::CloneFn) == Some(fn_def_id) + || fn_name == Some(sym::to_owned_method) + || (fn_name == Some(sym::to_string_method) && is_type_lang_item(cx, arg_ty, LangItem::String)); + + let from_deref = !from_borrow && matches!(fn_name, Some(sym::path_to_pathbuf | sym::os_str_to_os_string)); if !from_borrow && !from_deref { continue; diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 490e6c974ae1..57d5900b045e 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -457,7 +457,8 @@ impl<'tcx> LateLintPass<'tcx> for TrimSplitWhitespace { } fn is_one_of_trim_diagnostic_items(cx: &LateContext<'_>, trim_def_id: DefId) -> bool { - cx.tcx.is_diagnostic_item(sym::str_trim, trim_def_id) - || cx.tcx.is_diagnostic_item(sym::str_trim_start, trim_def_id) - || cx.tcx.is_diagnostic_item(sym::str_trim_end, trim_def_id) + matches!( + cx.tcx.get_diagnostic_name(trim_def_id), + Some(sym::str_trim | sym::str_trim_start | sym::str_trim_end) + ) } diff --git a/clippy_lints/src/types/rc_buffer.rs b/clippy_lints/src/types/rc_buffer.rs index d691f1878b11..c4fd0fbf87a9 100644 --- a/clippy_lints/src/types/rc_buffer.rs +++ b/clippy_lints/src/types/rc_buffer.rs @@ -11,7 +11,8 @@ use super::RC_BUFFER; pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { let app = Applicability::Unspecified; - if cx.tcx.is_diagnostic_item(sym::Rc, def_id) { + let name = cx.tcx.get_diagnostic_name(def_id); + if name == Some(sym::Rc) { if let Some(alternate) = match_buffer_type(cx, qpath) { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then( @@ -56,7 +57,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ ); return true; } - } else if cx.tcx.is_diagnostic_item(sym::Arc, def_id) { + } else if name == Some(sym::Arc) { if let Some(alternate) = match_buffer_type(cx, qpath) { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then( diff --git a/clippy_lints/src/types/redundant_allocation.rs b/clippy_lints/src/types/redundant_allocation.rs index de3456a8ba5f..0ba51daf027d 100644 --- a/clippy_lints/src/types/redundant_allocation.rs +++ b/clippy_lints/src/types/redundant_allocation.rs @@ -13,14 +13,11 @@ use super::{REDUNDANT_ALLOCATION, utils}; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'tcx>, qpath: &QPath<'tcx>, def_id: DefId) -> bool { let mut applicability = Applicability::MaybeIncorrect; - let outer_sym = if Some(def_id) == cx.tcx.lang_items().owned_box() { - "Box" - } else if cx.tcx.is_diagnostic_item(sym::Rc, def_id) { - "Rc" - } else if cx.tcx.is_diagnostic_item(sym::Arc, def_id) { - "Arc" - } else { - return false; + let outer_sym = match cx.tcx.get_diagnostic_name(def_id) { + _ if Some(def_id) == cx.tcx.lang_items().owned_box() => "Box", + Some(sym::Rc) => "Rc", + Some(sym::Arc) => "Arc", + _ => return false, }; if let Some(span) = utils::match_borrows_parameter(cx, qpath) { diff --git a/clippy_lints/src/unnecessary_owned_empty_strings.rs b/clippy_lints/src/unnecessary_owned_empty_strings.rs index 7d996775a58c..28f4884fa311 100644 --- a/clippy_lints/src/unnecessary_owned_empty_strings.rs +++ b/clippy_lints/src/unnecessary_owned_empty_strings.rs @@ -41,7 +41,8 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryOwnedEmptyStrings { && let ty::Ref(_, inner_str, _) = cx.typeck_results().expr_ty_adjusted(expr).kind() && inner_str.is_str() { - if cx.tcx.is_diagnostic_item(sym::string_new, fun_def_id) { + let fun_name = cx.tcx.get_diagnostic_name(fun_def_id); + if fun_name == Some(sym::string_new) { span_lint_and_sugg( cx, UNNECESSARY_OWNED_EMPTY_STRINGS, @@ -51,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryOwnedEmptyStrings { "\"\"".to_owned(), Applicability::MachineApplicable, ); - } else if cx.tcx.is_diagnostic_item(sym::from_fn, fun_def_id) + } else if fun_name == Some(sym::from_fn) && let [arg] = args && let ExprKind::Lit(spanned) = &arg.kind && let LitKind::Str(symbol, _) = spanned.node diff --git a/clippy_lints/src/unnecessary_wraps.rs b/clippy_lints/src/unnecessary_wraps.rs index 54a7efc090ad..849c0b438a5a 100644 --- a/clippy_lints/src/unnecessary_wraps.rs +++ b/clippy_lints/src/unnecessary_wraps.rs @@ -107,12 +107,10 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { // Get the wrapper and inner types, if can't, abort. let (return_type_label, lang_item, inner_type) = if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id.expect_owner()).kind() { - if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did()) { - ("Option", OptionSome, subst.type_at(0)) - } else if cx.tcx.is_diagnostic_item(sym::Result, adt_def.did()) { - ("Result", ResultOk, subst.type_at(0)) - } else { - return; + match cx.tcx.get_diagnostic_name(adt_def.did()) { + Some(sym::Option) => ("Option", OptionSome, subst.type_at(0)), + Some(sym::Result) => ("Result", ResultOk, subst.type_at(0)), + _ => return, } } else { return; diff --git a/clippy_lints/src/zombie_processes.rs b/clippy_lints/src/zombie_processes.rs index 6ab94a522109..06d36276dfc0 100644 --- a/clippy_lints/src/zombie_processes.rs +++ b/clippy_lints/src/zombie_processes.rs @@ -177,8 +177,8 @@ impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> { Node::Expr(expr) if let ExprKind::AddrOf(_, Mutability::Not, _) = expr.kind => {}, Node::Expr(expr) if let Some(fn_did) = fn_def_id(self.cx, expr) - && (self.cx.tcx.is_diagnostic_item(sym::child_id, fn_did) - || self.cx.tcx.is_diagnostic_item(sym::child_kill, fn_did)) => {}, + && let Some(fn_name) = self.cx.tcx.get_diagnostic_name(fn_did) + && matches!(fn_name, sym::child_id | sym::child_kill) => {}, // Conservatively assume that all other kinds of nodes call `.wait()` somehow. _ => return Break(MaybeWait(ex.span)), @@ -351,9 +351,14 @@ fn check<'tcx>(cx: &LateContext<'tcx>, spawn_expr: &'tcx Expr<'tcx>, cause: Caus /// Checks if the given expression exits the process. fn is_exit_expression(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - fn_def_id(cx, expr).is_some_and(|fn_did| { - cx.tcx.is_diagnostic_item(sym::process_exit, fn_did) || cx.tcx.is_diagnostic_item(sym::process_abort, fn_did) - }) + if let Some(fn_did) = fn_def_id(cx, expr) + && let Some(fn_name) = cx.tcx.get_diagnostic_name(fn_did) + && matches!(fn_name, sym::process_exit | sym::process_abort) + { + true + } else { + false + } } #[derive(Debug)] From 246f9bde06d7b040993ff7885a2dd75d68313cd3 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 10:59:02 +0200 Subject: [PATCH 043/116] higher: use `get_diagnostic_name` --- clippy_utils/src/higher.rs | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 4e0b00df9508..9db18f66e9a2 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -288,22 +288,23 @@ impl<'a> VecArgs<'a> { && is_expn_of(fun.span, sym::vec).is_some() && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id() { - return if cx.tcx.is_diagnostic_item(sym::vec_from_elem, fun_def_id) && args.len() == 2 { - // `vec![elem; size]` case - Some(VecArgs::Repeat(&args[0], &args[1])) - } else if cx.tcx.is_diagnostic_item(sym::slice_into_vec, fun_def_id) && args.len() == 1 { - // `vec![a, b, c]` case - if let ExprKind::Call(_, [arg]) = &args[0].kind - && let ExprKind::Array(args) = arg.kind - { - Some(VecArgs::Vec(args)) - } else { - None - } - } else if cx.tcx.is_diagnostic_item(sym::vec_new, fun_def_id) && args.is_empty() { - Some(VecArgs::Vec(&[])) - } else { - None + return match (cx.tcx.get_diagnostic_name(fun_def_id), args.len()) { + (Some(sym::vec_from_elem), 2) => { + // `vec![elem; size]` case + Some(VecArgs::Repeat(&args[0], &args[1])) + }, + (Some(sym::slice_into_vec), 1) => { + // `vec![a, b, c]` case + if let ExprKind::Call(_, [arg]) = &args[0].kind + && let ExprKind::Array(args) = arg.kind + { + Some(VecArgs::Vec(args)) + } else { + None + } + }, + (Some(sym::vec_new), 0) => Some(VecArgs::Vec(&[])), + _ => None, }; } From 30743966c0f1ffe817fba44e5f91d2fbd340906f Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 19 Aug 2025 23:46:47 +0200 Subject: [PATCH 044/116] misc: destruct `args` directly avoids bounds checks --- clippy_utils/src/higher.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 9db18f66e9a2..425de63ae198 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -288,14 +288,14 @@ impl<'a> VecArgs<'a> { && is_expn_of(fun.span, sym::vec).is_some() && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id() { - return match (cx.tcx.get_diagnostic_name(fun_def_id), args.len()) { - (Some(sym::vec_from_elem), 2) => { + return match (cx.tcx.get_diagnostic_name(fun_def_id), args) { + (Some(sym::vec_from_elem), [elem, size]) => { // `vec![elem; size]` case - Some(VecArgs::Repeat(&args[0], &args[1])) + Some(VecArgs::Repeat(elem, size)) }, - (Some(sym::slice_into_vec), 1) => { + (Some(sym::slice_into_vec), [slice]) => { // `vec![a, b, c]` case - if let ExprKind::Call(_, [arg]) = &args[0].kind + if let ExprKind::Call(_, [arg]) = slice.kind && let ExprKind::Array(args) = arg.kind { Some(VecArgs::Vec(args)) @@ -303,7 +303,7 @@ impl<'a> VecArgs<'a> { None } }, - (Some(sym::vec_new), 0) => Some(VecArgs::Vec(&[])), + (Some(sym::vec_new), []) => Some(VecArgs::Vec(&[])), _ => None, }; } From 5420d07d0768a81655e269d005fedc739511b22e Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 19 Aug 2025 23:47:38 +0200 Subject: [PATCH 045/116] misc: pull condition into guard `None` is the fallback case anyway --- clippy_utils/src/higher.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 425de63ae198..0caeddd9f088 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -293,15 +293,12 @@ impl<'a> VecArgs<'a> { // `vec![elem; size]` case Some(VecArgs::Repeat(elem, size)) }, - (Some(sym::slice_into_vec), [slice]) => { - // `vec![a, b, c]` case + (Some(sym::slice_into_vec), [slice]) if let ExprKind::Call(_, [arg]) = slice.kind - && let ExprKind::Array(args) = arg.kind - { - Some(VecArgs::Vec(args)) - } else { - None - } + && let ExprKind::Array(args) = arg.kind => + { + // `vec![a, b, c]` case + Some(VecArgs::Vec(args)) }, (Some(sym::vec_new), []) => Some(VecArgs::Vec(&[])), _ => None, From d7458294a1ea4f4d76f5fab602fe5fc84f9c8c3a Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 19 Aug 2025 23:56:00 +0200 Subject: [PATCH 046/116] misc: return earlier on `name = None` --- clippy_utils/src/higher.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 0caeddd9f088..bda28a663fb0 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -287,20 +287,21 @@ impl<'a> VecArgs<'a> { && let ExprKind::Path(ref qpath) = fun.kind && is_expn_of(fun.span, sym::vec).is_some() && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id() + && let Some(name) = cx.tcx.get_diagnostic_name(fun_def_id) { - return match (cx.tcx.get_diagnostic_name(fun_def_id), args) { - (Some(sym::vec_from_elem), [elem, size]) => { + return match (name, args) { + (sym::vec_from_elem, [elem, size]) => { // `vec![elem; size]` case Some(VecArgs::Repeat(elem, size)) }, - (Some(sym::slice_into_vec), [slice]) + (sym::slice_into_vec, [slice]) if let ExprKind::Call(_, [arg]) = slice.kind && let ExprKind::Array(args) = arg.kind => { // `vec![a, b, c]` case Some(VecArgs::Vec(args)) }, - (Some(sym::vec_new), []) => Some(VecArgs::Vec(&[])), + (sym::vec_new, []) => Some(VecArgs::Vec(&[])), _ => None, }; } From 0938652791822c64de8366d84ac06710b509314f Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 11:04:20 +0200 Subject: [PATCH 047/116] non_octal_unix_permissions: `get_diagnostic_name` once --- clippy_lints/src/non_octal_unix_permissions.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/non_octal_unix_permissions.rs b/clippy_lints/src/non_octal_unix_permissions.rs index 23a1622f30ff..1aec5412cda7 100644 --- a/clippy_lints/src/non_octal_unix_permissions.rs +++ b/clippy_lints/src/non_octal_unix_permissions.rs @@ -43,13 +43,11 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { match &expr.kind { ExprKind::MethodCall(path, func, [param], _) => { if let Some(adt) = cx.typeck_results().expr_ty(func).peel_refs().ty_adt_def() - && ((path.ident.name == sym::mode - && matches!( - cx.tcx.get_diagnostic_name(adt.did()), - Some(sym::FsOpenOptions | sym::DirBuilder) - )) - || (path.ident.name == sym::set_mode - && cx.tcx.is_diagnostic_item(sym::FsPermissions, adt.did()))) + && matches!( + (path.ident.name, cx.tcx.get_diagnostic_name(adt.did())), + (sym::mode, Some(sym::FsOpenOptions | sym::DirBuilder)) + | (sym::set_mode, Some(sym::FsPermissions)) + ) && let ExprKind::Lit(_) = param.kind && param.span.eq_ctxt(expr.span) && param From c4e905cf93b3628fab6544fe31c077af8a14ca3d Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 00:41:05 +0200 Subject: [PATCH 048/116] misc: reorder to have struct first, then its method --- clippy_lints/src/non_octal_unix_permissions.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/non_octal_unix_permissions.rs b/clippy_lints/src/non_octal_unix_permissions.rs index 1aec5412cda7..cb934466bd89 100644 --- a/clippy_lints/src/non_octal_unix_permissions.rs +++ b/clippy_lints/src/non_octal_unix_permissions.rs @@ -44,9 +44,9 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { ExprKind::MethodCall(path, func, [param], _) => { if let Some(adt) = cx.typeck_results().expr_ty(func).peel_refs().ty_adt_def() && matches!( - (path.ident.name, cx.tcx.get_diagnostic_name(adt.did())), - (sym::mode, Some(sym::FsOpenOptions | sym::DirBuilder)) - | (sym::set_mode, Some(sym::FsPermissions)) + (cx.tcx.get_diagnostic_name(adt.did()), path.ident.name), + (Some(sym::FsOpenOptions | sym::DirBuilder), sym::mode) + | (Some(sym::FsPermissions), sym::set_mode) ) && let ExprKind::Lit(_) = param.kind && param.span.eq_ctxt(expr.span) From dc94cbeb2a7aefabd1601f2dfc9226a68e682792 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 00:49:30 +0200 Subject: [PATCH 049/116] redundant_clone: partially inline `is_type_diagnostic_item` to be able to reuse `did` --- clippy_lints/src/redundant_clone.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/redundant_clone.rs b/clippy_lints/src/redundant_clone.rs index 193d9dda8845..1d58cdd26d88 100644 --- a/clippy_lints/src/redundant_clone.rs +++ b/clippy_lints/src/redundant_clone.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; use clippy_utils::fn_has_unsatisfiable_preds; use clippy_utils::mir::{LocalUsage, PossibleBorrowerMap, visit_local_usage}; use clippy_utils::source::SpanRangeExt; -use clippy_utils::ty::{has_drop, is_copy, is_type_diagnostic_item, is_type_lang_item, walk_ptrs_ty_depth}; +use clippy_utils::ty::{has_drop, is_copy, is_type_lang_item, walk_ptrs_ty_depth}; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{Body, FnDecl, LangItem, def_id}; @@ -147,8 +147,9 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone { is_call_with_ref_arg(cx, mir, &pred_terminator.kind) && res == cloned && cx.tcx.is_diagnostic_item(sym::deref_method, pred_fn_def_id) - && (is_type_diagnostic_item(cx, pred_arg_ty, sym::PathBuf) - || is_type_diagnostic_item(cx, pred_arg_ty, sym::OsString)) + && let ty::Adt(pred_arg_def, _) = pred_arg_ty.kind() + && let Some(pred_arg_name) = cx.tcx.get_diagnostic_name(pred_arg_def.did()) + && matches!(pred_arg_name, sym::PathBuf | sym::OsString) { (pred_arg, res) } else { From 74731748cd6f9ca6538ce20c59859aaf2b0a93ba Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 01:01:09 +0200 Subject: [PATCH 050/116] len_zero: use `get_diagnostic_name` --- clippy_lints/src/len_zero.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 57deb011f2b0..28a0fbc05115 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -355,12 +355,15 @@ fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option Some(LenOutput::Option(def_id)), + Some(sym::Result) => Some(LenOutput::Result(def_id)), + _ => None, } + && is_first_generic_integral(segment) + { + return Some(res); } return None; @@ -368,11 +371,10 @@ fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option Some(LenOutput::Integral), - ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) => { - subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did())) - }, - ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => { - subs.type_at(0).is_integral().then(|| LenOutput::Result(adt.did())) + ty::Adt(adt, subs) => match cx.tcx.get_diagnostic_name(adt.did()) { + Some(sym::Option) => subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did())), + Some(sym::Result) => subs.type_at(0).is_integral().then(|| LenOutput::Result(adt.did())), + _ => None, }, _ => None, } From 37b6237cd31798037cfdf5556ce44ab8fabd7883 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 01:02:07 +0200 Subject: [PATCH 051/116] misc: pull another condition into match arm --- clippy_lints/src/len_zero.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 28a0fbc05115..f44a5fdf715e 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -371,9 +371,9 @@ fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option Some(LenOutput::Integral), - ty::Adt(adt, subs) => match cx.tcx.get_diagnostic_name(adt.did()) { - Some(sym::Option) => subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did())), - Some(sym::Result) => subs.type_at(0).is_integral().then(|| LenOutput::Result(adt.did())), + ty::Adt(adt, subs) if subs.type_at(0).is_integral() => match cx.tcx.get_diagnostic_name(adt.did()) { + Some(sym::Option) => Some(LenOutput::Option(adt.did())), + Some(sym::Result) => Some(LenOutput::Result(adt.did())), _ => None, }, _ => None, From 6cf7634323374cfbd618c5bd3ca7040cfdf96f55 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 10:57:23 +0200 Subject: [PATCH 052/116] non_canonical_impls: save diagnostic name to a variable --- clippy_lints/src/non_canonical_impls.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/non_canonical_impls.rs b/clippy_lints/src/non_canonical_impls.rs index 04b092769666..19fd1925e908 100644 --- a/clippy_lints/src/non_canonical_impls.rs +++ b/clippy_lints/src/non_canonical_impls.rs @@ -134,7 +134,8 @@ impl LateLintPass<'_> for NonCanonicalImpls { return; } - if cx.tcx.is_diagnostic_item(sym::Clone, trait_impl.def_id) + let trait_name = cx.tcx.get_diagnostic_name(trait_impl.def_id); + if trait_name == Some(sym::Clone) && let Some(copy_def_id) = cx.tcx.get_diagnostic_item(sym::Copy) && implements_trait(cx, trait_impl.self_ty(), copy_def_id, &[]) { @@ -175,7 +176,7 @@ impl LateLintPass<'_> for NonCanonicalImpls { } } - if cx.tcx.is_diagnostic_item(sym::PartialOrd, trait_impl.def_id) + if trait_name == Some(sym::PartialOrd) && impl_item.ident.name == sym::partial_cmp && let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord) && implements_trait(cx, trait_impl.self_ty(), ord_def_id, &[]) From 58b646239d2ad0f757eb6930e0bb42ad69bcac15 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 01:16:23 +0200 Subject: [PATCH 053/116] misc: don't bother checking the other case `trait_name` can only be _one_ of those at the same time --- clippy_lints/src/non_canonical_impls.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/clippy_lints/src/non_canonical_impls.rs b/clippy_lints/src/non_canonical_impls.rs index 19fd1925e908..ba67dc62abbd 100644 --- a/clippy_lints/src/non_canonical_impls.rs +++ b/clippy_lints/src/non_canonical_impls.rs @@ -171,12 +171,8 @@ impl LateLintPass<'_> for NonCanonicalImpls { String::new(), Applicability::MaybeIncorrect, ); - - return; } - } - - if trait_name == Some(sym::PartialOrd) + } else if trait_name == Some(sym::PartialOrd) && impl_item.ident.name == sym::partial_cmp && let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord) && implements_trait(cx, trait_impl.self_ty(), ord_def_id, &[]) From 8a969f709034375eed5d13dee6b44b31fcef2cf5 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 20:56:39 +0200 Subject: [PATCH 054/116] entry: use `get_diagnostic_item` --- clippy_lints/src/entry.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index 182cb4e46d2b..4e24f69357db 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -274,12 +274,10 @@ fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Optio key, call_ctxt: expr.span.ctxt(), }; - if cx.tcx.is_diagnostic_item(sym::btreemap_contains_key, id) { - Some((MapType::BTree, expr)) - } else if cx.tcx.is_diagnostic_item(sym::hashmap_contains_key, id) { - Some((MapType::Hash, expr)) - } else { - None + match cx.tcx.get_diagnostic_name(id) { + Some(sym::btreemap_contains_key) => Some((MapType::BTree, expr)), + Some(sym::hashmap_contains_key) => Some((MapType::Hash, expr)), + _ => None, } }, _ => None, From 51426954182661dbe82424f790cb2b53b6a720d2 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 01:39:23 +0200 Subject: [PATCH 055/116] misc: rewrite as `if let` --- clippy_lints/src/entry.rs | 53 ++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index 4e24f69357db..dd38aab6e3fb 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -254,33 +254,34 @@ fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Optio _ => None, }); - match expr.kind { - ExprKind::MethodCall( - _, + if let ExprKind::MethodCall( + _, + map, + [ + Expr { + kind: ExprKind::AddrOf(_, _, key), + span: key_span, + .. + }, + ], + _, + ) = expr.kind + && key_span.eq_ctxt(expr.span) + { + let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?; + let expr = ContainsExpr { + negated, map, - [ - Expr { - kind: ExprKind::AddrOf(_, _, key), - span: key_span, - .. - }, - ], - _, - ) if key_span.eq_ctxt(expr.span) => { - let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?; - let expr = ContainsExpr { - negated, - map, - key, - call_ctxt: expr.span.ctxt(), - }; - match cx.tcx.get_diagnostic_name(id) { - Some(sym::btreemap_contains_key) => Some((MapType::BTree, expr)), - Some(sym::hashmap_contains_key) => Some((MapType::Hash, expr)), - _ => None, - } - }, - _ => None, + key, + call_ctxt: expr.span.ctxt(), + }; + match cx.tcx.get_diagnostic_name(id) { + Some(sym::btreemap_contains_key) => Some((MapType::BTree, expr)), + Some(sym::hashmap_contains_key) => Some((MapType::Hash, expr)), + _ => None, + } + } else { + None } } From c9878185a87826b632920a4b1cb44a30685f81a9 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 01:41:58 +0200 Subject: [PATCH 056/116] misc: match in multiple steps --- clippy_lints/src/entry.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index dd38aab6e3fb..53dc2c05add2 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -254,18 +254,12 @@ fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Optio _ => None, }); - if let ExprKind::MethodCall( - _, - map, - [ - Expr { - kind: ExprKind::AddrOf(_, _, key), - span: key_span, - .. - }, - ], - _, - ) = expr.kind + if let ExprKind::MethodCall(_, map, [arg], _) = expr.kind + && let Expr { + kind: ExprKind::AddrOf(_, _, key), + span: key_span, + .. + } = arg && key_span.eq_ctxt(expr.span) { let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?; From ebbea345726e04cd26b69b8334169ba0571b2d34 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 10 Aug 2025 14:09:49 +0200 Subject: [PATCH 057/116] `is_expr_identity_of_pat`: simplify using `(unordered_)over` --- clippy_utils/src/ast_utils/mod.rs | 2 +- clippy_utils/src/hir_utils.rs | 2 +- clippy_utils/src/lib.rs | 11 +++++------ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/clippy_utils/src/ast_utils/mod.rs b/clippy_utils/src/ast_utils/mod.rs index 40c00568a3bd..ad69e6eb184e 100644 --- a/clippy_utils/src/ast_utils/mod.rs +++ b/clippy_utils/src/ast_utils/mod.rs @@ -21,7 +21,7 @@ pub fn is_useless_with_eq_exprs(kind: BinOpKind) -> bool { } /// Checks if each element in the first slice is contained within the latter as per `eq_fn`. -pub fn unordered_over(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool { +pub fn unordered_over(left: &[X], right: &[Y], mut eq_fn: impl FnMut(&X, &Y) -> bool) -> bool { left.len() == right.len() && left.iter().all(|l| right.iter().any(|r| eq_fn(l, r))) } diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 8160443f4132..b79e15cd7170 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -757,7 +757,7 @@ pub fn both_some_and(l: Option, r: Option, mut pred: impl FnMut(X, Y } /// Checks if two slices are equal as per `eq_fn`. -pub fn over(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool { +pub fn over(left: &[X], right: &[Y], mut eq_fn: impl FnMut(&X, &Y) -> bool) -> bool { left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y)) } diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 8533fa855419..1e0833001cf7 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -126,6 +126,7 @@ use rustc_span::{InnerSpan, Span}; use source::{SpanRangeExt, walk_span_to_context}; use visitors::{Visitable, for_each_unconsumed_temporary}; +use crate::ast_utils::unordered_over; use crate::consts::{ConstEvalCtxt, Constant, mir_to_const}; use crate::higher::Range; use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type}; @@ -1992,7 +1993,7 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr< (PatKind::Tuple(pats, dotdot), ExprKind::Tup(tup)) if dotdot.as_opt_usize().is_none() && pats.len() == tup.len() => { - zip(pats, tup).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir)) + over(pats, tup, |pat, expr| is_expr_identity_of_pat(cx, pat, expr, by_hir)) }, (PatKind::Slice(before, None, after), ExprKind::Array(arr)) if before.len() + after.len() == arr.len() => { zip(before.iter().chain(after), arr).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir)) @@ -2004,7 +2005,7 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr< if let ExprKind::Path(ident) = &ident.kind && qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id) // check fields - && zip(field_pats, fields).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr,by_hir)) + && over(field_pats, fields, |pat, expr| is_expr_identity_of_pat(cx, pat, expr,by_hir)) { true } else { @@ -2017,10 +2018,8 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr< // check ident qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id) // check fields - && field_pats.iter().all(|field_pat| { - fields.iter().any(|field| { - field_pat.ident == field.ident && is_expr_identity_of_pat(cx, field_pat.pat, field.expr, by_hir) - }) + && unordered_over(field_pats, fields, |field_pat, field| { + field_pat.ident == field.ident && is_expr_identity_of_pat(cx, field_pat.pat, field.expr, by_hir) }) }, _ => false, From 0d479c19e875918a6c5248f031fa50cf0997af85 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 25 Jul 2025 08:50:20 +0200 Subject: [PATCH 058/116] fix: `unnested_or_patterns` FP on structs with only shorthand field pats --- clippy_lints/src/unnested_or_patterns.rs | 6 ++- tests/ui/unnested_or_patterns.fixed | 25 +++++++++-- tests/ui/unnested_or_patterns.rs | 25 +++++++++-- tests/ui/unnested_or_patterns.stderr | 54 +++++++++++++++--------- 4 files changed, 80 insertions(+), 30 deletions(-) diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs index 8b278d98a30e..f3410c98973f 100644 --- a/clippy_lints/src/unnested_or_patterns.rs +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -326,7 +326,11 @@ fn extend_with_struct_pat( if idx_1 == idx { // In the case of `k`, we merely require identical field names // so that we will transform into `ident_k: p1_k | p2_k`. - let pos = fps2.iter().position(|fp2| eq_id(fp1.ident, fp2.ident)); + let pos = fps2.iter().position(|fp2| { + // Avoid `Foo { bar } | Foo { bar }` => `Foo { bar | bar }` + !(fp1.is_shorthand && fp2.is_shorthand) + && eq_id(fp1.ident, fp2.ident) + }); pos_in_2.set(pos); pos.is_some() } else { diff --git a/tests/ui/unnested_or_patterns.fixed b/tests/ui/unnested_or_patterns.fixed index 2081772d06b3..339d4a95084a 100644 --- a/tests/ui/unnested_or_patterns.fixed +++ b/tests/ui/unnested_or_patterns.fixed @@ -9,6 +9,11 @@ )] #![allow(unreachable_patterns, irrefutable_let_patterns, unused)] +struct S { + x: u8, + y: u8, +} + fn main() { // Should be ignored by this lint, as nesting requires more characters. if let &0 | &2 = &0 {} @@ -45,10 +50,6 @@ fn main() { //~^ unnested_or_patterns if let TS(x, ..) | TS(x, 1 | 2) = TS(0, 0) {} //~^ unnested_or_patterns - struct S { - x: u8, - y: u8, - } if let S { x: 0 | 1, y } = (S { x: 0, y: 1 }) {} //~^ unnested_or_patterns if let S { x: 0, y, .. } | S { y, x: 1 } = (S { x: 0, y: 1 }) {} @@ -77,3 +78,19 @@ mod issue9952 { fn or_in_param((x | x | x): i32) {} //~^ unnested_or_patterns } + +fn issue15219() { + struct Foo { + x: u8, + } + + // the original repro + if let Foo { x } | Foo { x } = (Foo { x: 0 }) {} + + // also works with more fields + if let S { x, y } | S { x, y } = (S { x: 0, y: 0 }) {} + + // `y` not triggering the lint doesn't stop the `x` from getting flagged + if let S { y, x: 0 | 1 } = (S { x: 0, y: 1 }) {} + //~^ unnested_or_patterns +} diff --git a/tests/ui/unnested_or_patterns.rs b/tests/ui/unnested_or_patterns.rs index 6bf8fce36616..f5c99183b0c5 100644 --- a/tests/ui/unnested_or_patterns.rs +++ b/tests/ui/unnested_or_patterns.rs @@ -9,6 +9,11 @@ )] #![allow(unreachable_patterns, irrefutable_let_patterns, unused)] +struct S { + x: u8, + y: u8, +} + fn main() { // Should be ignored by this lint, as nesting requires more characters. if let &0 | &2 = &0 {} @@ -45,10 +50,6 @@ fn main() { //~^ unnested_or_patterns if let TS(x, ..) | TS(x, 1) | TS(x, 2) = TS(0, 0) {} //~^ unnested_or_patterns - struct S { - x: u8, - y: u8, - } if let S { x: 0, y } | S { y, x: 1 } = (S { x: 0, y: 1 }) {} //~^ unnested_or_patterns if let S { x: 0, y, .. } | S { y, x: 1 } = (S { x: 0, y: 1 }) {} @@ -77,3 +78,19 @@ mod issue9952 { fn or_in_param((x | (x | x)): i32) {} //~^ unnested_or_patterns } + +fn issue15219() { + struct Foo { + x: u8, + } + + // the original repro + if let Foo { x } | Foo { x } = (Foo { x: 0 }) {} + + // also works with more fields + if let S { x, y } | S { x, y } = (S { x: 0, y: 0 }) {} + + // `y` not triggering the lint doesn't stop the `x` from getting flagged + if let S { y, x: 0 } | S { y, x: 1 } = (S { x: 0, y: 1 }) {} + //~^ unnested_or_patterns +} diff --git a/tests/ui/unnested_or_patterns.stderr b/tests/ui/unnested_or_patterns.stderr index c805dc992b1c..d2b617c322c4 100644 --- a/tests/ui/unnested_or_patterns.stderr +++ b/tests/ui/unnested_or_patterns.stderr @@ -1,5 +1,5 @@ error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:16:12 + --> tests/ui/unnested_or_patterns.rs:21:12 | LL | if let box 0 | box 2 = Box::new(0) {} | ^^^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL + if let box (0 | 2) = Box::new(0) {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:18:12 + --> tests/ui/unnested_or_patterns.rs:23:12 | LL | if let box ((0 | 1)) | box (2 | 3) | box 4 = Box::new(0) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL + if let box (0 | 1 | 2 | 3 | 4) = Box::new(0) {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:21:12 + --> tests/ui/unnested_or_patterns.rs:26:12 | LL | if let Some(1) | C0 | Some(2) = None {} | ^^^^^^^^^^^^^^^^^^^^^^ @@ -37,7 +37,7 @@ LL + if let Some(1 | 2) | C0 = None {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:23:12 + --> tests/ui/unnested_or_patterns.rs:28:12 | LL | if let &mut 0 | &mut 2 = &mut 0 {} | ^^^^^^^^^^^^^^^ @@ -49,7 +49,7 @@ LL + if let &mut (0 | 2) = &mut 0 {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:25:12 + --> tests/ui/unnested_or_patterns.rs:30:12 | LL | if let x @ 0 | x @ 2 = 0 {} | ^^^^^^^^^^^^^ @@ -61,7 +61,7 @@ LL + if let x @ (0 | 2) = 0 {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:27:12 + --> tests/ui/unnested_or_patterns.rs:32:12 | LL | if let (0, 1) | (0, 2) | (0, 3) = (0, 0) {} | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -73,7 +73,7 @@ LL + if let (0, 1 | 2 | 3) = (0, 0) {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:29:12 + --> tests/ui/unnested_or_patterns.rs:34:12 | LL | if let (1, 0) | (2, 0) | (3, 0) = (0, 0) {} | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -85,7 +85,7 @@ LL + if let (1 | 2 | 3, 0) = (0, 0) {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:31:12 + --> tests/ui/unnested_or_patterns.rs:36:12 | LL | if let (x, ..) | (x, 1) | (x, 2) = (0, 1) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -97,7 +97,7 @@ LL + if let (x, ..) | (x, 1 | 2) = (0, 1) {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:33:12 + --> tests/ui/unnested_or_patterns.rs:38:12 | LL | if let [0] | [1] = [0] {} | ^^^^^^^^^ @@ -109,7 +109,7 @@ LL + if let [0 | 1] = [0] {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:35:12 + --> tests/ui/unnested_or_patterns.rs:40:12 | LL | if let [x, 0] | [x, 1] = [0, 1] {} | ^^^^^^^^^^^^^^^ @@ -121,7 +121,7 @@ LL + if let [x, 0 | 1] = [0, 1] {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:37:12 + --> tests/ui/unnested_or_patterns.rs:42:12 | LL | if let [x, 0] | [x, 1] | [x, 2] = [0, 1] {} | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -133,7 +133,7 @@ LL + if let [x, 0 | 1 | 2] = [0, 1] {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:39:12 + --> tests/ui/unnested_or_patterns.rs:44:12 | LL | if let [x, ..] | [x, 1] | [x, 2] = [0, 1] {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -145,7 +145,7 @@ LL + if let [x, ..] | [x, 1 | 2] = [0, 1] {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:42:12 + --> tests/ui/unnested_or_patterns.rs:47:12 | LL | if let TS(0, x) | TS(1, x) = TS(0, 0) {} | ^^^^^^^^^^^^^^^^^^^ @@ -157,7 +157,7 @@ LL + if let TS(0 | 1, x) = TS(0, 0) {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:44:12 + --> tests/ui/unnested_or_patterns.rs:49:12 | LL | if let TS(1, 0) | TS(2, 0) | TS(3, 0) = TS(0, 0) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -169,7 +169,7 @@ LL + if let TS(1 | 2 | 3, 0) = TS(0, 0) {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:46:12 + --> tests/ui/unnested_or_patterns.rs:51:12 | LL | if let TS(x, ..) | TS(x, 1) | TS(x, 2) = TS(0, 0) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -181,7 +181,7 @@ LL + if let TS(x, ..) | TS(x, 1 | 2) = TS(0, 0) {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:52:12 + --> tests/ui/unnested_or_patterns.rs:53:12 | LL | if let S { x: 0, y } | S { y, x: 1 } = (S { x: 0, y: 1 }) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -193,7 +193,7 @@ LL + if let S { x: 0 | 1, y } = (S { x: 0, y: 1 }) {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:64:12 + --> tests/ui/unnested_or_patterns.rs:65:12 | LL | if let [1] | [53] = [0] {} | ^^^^^^^^^^ @@ -205,7 +205,7 @@ LL + if let [1 | 53] = [0] {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:70:13 + --> tests/ui/unnested_or_patterns.rs:71:13 | LL | let (0 | (1 | _)) = 0; | ^^^^^^^^^^^^^ @@ -217,7 +217,7 @@ LL + let (0 | 1 | _) = 0; | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:73:16 + --> tests/ui/unnested_or_patterns.rs:74:16 | LL | if let (0 | (1 | _)) = 0 {} | ^^^^^^^^^^^^^ @@ -229,7 +229,7 @@ LL + if let (0 | 1 | _) = 0 {} | error: unnested or-patterns - --> tests/ui/unnested_or_patterns.rs:77:20 + --> tests/ui/unnested_or_patterns.rs:78:20 | LL | fn or_in_param((x | (x | x)): i32) {} | ^^^^^^^^^^^^^ @@ -240,5 +240,17 @@ LL - fn or_in_param((x | (x | x)): i32) {} LL + fn or_in_param((x | x | x): i32) {} | -error: aborting due to 20 previous errors +error: unnested or-patterns + --> tests/ui/unnested_or_patterns.rs:94:12 + | +LL | if let S { y, x: 0 } | S { y, x: 1 } = (S { x: 0, y: 1 }) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: nest the patterns + | +LL - if let S { y, x: 0 } | S { y, x: 1 } = (S { x: 0, y: 1 }) {} +LL + if let S { y, x: 0 | 1 } = (S { x: 0, y: 1 }) {} + | + +error: aborting due to 21 previous errors From 86beeccb6519032d1bc067a05f87d797162d137d Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Wed, 16 Jul 2025 15:37:55 +0200 Subject: [PATCH 059/116] Better check for `assign_op_pattern` in `const` context `assign_op_pattern` can be used in a `const` context if the trait definition as well as the implementation of the corresponding `Assign` pattern is `const` as well. --- .../src/operators/assign_op_pattern.rs | 27 ++++++++-- tests/ui/assign_ops.fixed | 39 +++++++++++++-- tests/ui/assign_ops.rs | 41 +++++++++++++-- tests/ui/assign_ops.stderr | 50 ++++++++++++++----- 4 files changed, 133 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/operators/assign_op_pattern.rs b/clippy_lints/src/operators/assign_op_pattern.rs index 7317c62df7fe..2d303e40bd1c 100644 --- a/clippy_lints/src/operators/assign_op_pattern.rs +++ b/clippy_lints/src/operators/assign_op_pattern.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::Msrv; +use clippy_utils::qualify_min_const_fn::is_stable_const_fn; use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::implements_trait; use clippy_utils::visitors::for_each_expr_without_closures; @@ -20,7 +21,7 @@ pub(super) fn check<'tcx>( expr: &'tcx hir::Expr<'_>, assignee: &'tcx hir::Expr<'_>, e: &'tcx hir::Expr<'_>, - _msrv: Msrv, + msrv: Msrv, ) { if let hir::ExprKind::Binary(op, l, r) = &e.kind { let lint = |assignee: &hir::Expr<'_>, rhs: &hir::Expr<'_>| { @@ -43,10 +44,28 @@ pub(super) fn check<'tcx>( } } - // Skip if the trait is not stable in const contexts - // FIXME: reintroduce a better check after this is merged back into Clippy + // Skip if the trait or the implementation is not stable in const contexts if is_in_const_context(cx) { - return; + if cx + .tcx + .associated_item_def_ids(trait_id) + .first() + .is_none_or(|binop_id| !is_stable_const_fn(cx, *binop_id, msrv)) + { + return; + } + + let impls = cx.tcx.non_blanket_impls_for_ty(trait_id, rty).collect::>(); + if impls.is_empty() + || impls.into_iter().any(|impl_id| { + cx.tcx + .associated_item_def_ids(impl_id) + .first() + .is_none_or(|fn_id| !is_stable_const_fn(cx, *fn_id, msrv)) + }) + { + return; + } } span_lint_and_then( diff --git a/tests/ui/assign_ops.fixed b/tests/ui/assign_ops.fixed index 3754b9dfe744..2046d089d6a6 100644 --- a/tests/ui/assign_ops.fixed +++ b/tests/ui/assign_ops.fixed @@ -1,8 +1,9 @@ #![allow(clippy::useless_vec)] #![warn(clippy::assign_op_pattern)] -#![feature(const_trait_impl, const_ops)] +#![feature(const_ops)] +#![feature(const_trait_impl)] -use core::num::Wrapping; +use std::num::Wrapping; use std::ops::{Mul, MulAssign}; fn main() { @@ -82,6 +83,18 @@ mod issue14871 { pub trait Number: Copy + Add + AddAssign { const ZERO: Self; const ONE: Self; + + fn non_constant(value: usize) -> Self { + let mut res = Self::ZERO; + let mut count = 0; + while count < value { + res += Self::ONE; + //~^ assign_op_pattern + count += 1; + //~^ assign_op_pattern + } + res + } } #[rustfmt::skip] // rustfmt doesn't understand the order of pub const on traits (yet) @@ -91,7 +104,7 @@ mod issue14871 { impl const NumberConstants for T where - T: Number + [const] core::ops::Add, + T: Number + [const] std::ops::Add, { fn constant(value: usize) -> Self { let mut res = Self::ZERO; @@ -99,8 +112,28 @@ mod issue14871 { while count < value { res = res + Self::ONE; count += 1; + //~^ assign_op_pattern } res } } + + pub struct S; + + impl const std::ops::Add for S { + type Output = S; + fn add(self, _rhs: S) -> S { + S + } + } + + impl const std::ops::AddAssign for S { + fn add_assign(&mut self, rhs: S) {} + } + + const fn do_add() { + let mut s = S; + s += S; + //~^ assign_op_pattern + } } diff --git a/tests/ui/assign_ops.rs b/tests/ui/assign_ops.rs index 0b878d4f490b..f83a40f55473 100644 --- a/tests/ui/assign_ops.rs +++ b/tests/ui/assign_ops.rs @@ -1,8 +1,9 @@ #![allow(clippy::useless_vec)] #![warn(clippy::assign_op_pattern)] -#![feature(const_trait_impl, const_ops)] +#![feature(const_ops)] +#![feature(const_trait_impl)] -use core::num::Wrapping; +use std::num::Wrapping; use std::ops::{Mul, MulAssign}; fn main() { @@ -82,6 +83,18 @@ mod issue14871 { pub trait Number: Copy + Add + AddAssign { const ZERO: Self; const ONE: Self; + + fn non_constant(value: usize) -> Self { + let mut res = Self::ZERO; + let mut count = 0; + while count < value { + res = res + Self::ONE; + //~^ assign_op_pattern + count = count + 1; + //~^ assign_op_pattern + } + res + } } #[rustfmt::skip] // rustfmt doesn't understand the order of pub const on traits (yet) @@ -91,16 +104,36 @@ mod issue14871 { impl const NumberConstants for T where - T: Number + [const] core::ops::Add, + T: Number + [const] std::ops::Add, { fn constant(value: usize) -> Self { let mut res = Self::ZERO; let mut count = 0; while count < value { res = res + Self::ONE; - count += 1; + count = count + 1; + //~^ assign_op_pattern } res } } + + pub struct S; + + impl const std::ops::Add for S { + type Output = S; + fn add(self, _rhs: S) -> S { + S + } + } + + impl const std::ops::AddAssign for S { + fn add_assign(&mut self, rhs: S) {} + } + + const fn do_add() { + let mut s = S; + s = s + S; + //~^ assign_op_pattern + } } diff --git a/tests/ui/assign_ops.stderr b/tests/ui/assign_ops.stderr index c5e698b3ee11..a4fca04893c4 100644 --- a/tests/ui/assign_ops.stderr +++ b/tests/ui/assign_ops.stderr @@ -1,5 +1,5 @@ error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:10:5 + --> tests/ui/assign_ops.rs:11:5 | LL | a = a + 1; | ^^^^^^^^^ help: replace it with: `a += 1` @@ -8,70 +8,94 @@ LL | a = a + 1; = help: to override `-D warnings` add `#[allow(clippy::assign_op_pattern)]` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:12:5 + --> tests/ui/assign_ops.rs:13:5 | LL | a = 1 + a; | ^^^^^^^^^ help: replace it with: `a += 1` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:14:5 + --> tests/ui/assign_ops.rs:15:5 | LL | a = a - 1; | ^^^^^^^^^ help: replace it with: `a -= 1` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:16:5 + --> tests/ui/assign_ops.rs:17:5 | LL | a = a * 99; | ^^^^^^^^^^ help: replace it with: `a *= 99` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:18:5 + --> tests/ui/assign_ops.rs:19:5 | LL | a = 42 * a; | ^^^^^^^^^^ help: replace it with: `a *= 42` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:20:5 + --> tests/ui/assign_ops.rs:21:5 | LL | a = a / 2; | ^^^^^^^^^ help: replace it with: `a /= 2` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:22:5 + --> tests/ui/assign_ops.rs:23:5 | LL | a = a % 5; | ^^^^^^^^^ help: replace it with: `a %= 5` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:24:5 + --> tests/ui/assign_ops.rs:25:5 | LL | a = a & 1; | ^^^^^^^^^ help: replace it with: `a &= 1` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:31:5 + --> tests/ui/assign_ops.rs:32:5 | LL | s = s + "bla"; | ^^^^^^^^^^^^^ help: replace it with: `s += "bla"` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:36:5 + --> tests/ui/assign_ops.rs:37:5 | LL | a = a + Wrapping(1u32); | ^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `a += Wrapping(1u32)` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:39:5 + --> tests/ui/assign_ops.rs:40:5 | LL | v[0] = v[0] + v[1]; | ^^^^^^^^^^^^^^^^^^ help: replace it with: `v[0] += v[1]` error: manual implementation of an assign operation - --> tests/ui/assign_ops.rs:52:5 + --> tests/ui/assign_ops.rs:53:5 | LL | buf = buf + cows.clone(); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `buf += cows.clone()` -error: aborting due to 12 previous errors +error: manual implementation of an assign operation + --> tests/ui/assign_ops.rs:91:17 + | +LL | res = res + Self::ONE; + | ^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `res += Self::ONE` + +error: manual implementation of an assign operation + --> tests/ui/assign_ops.rs:93:17 + | +LL | count = count + 1; + | ^^^^^^^^^^^^^^^^^ help: replace it with: `count += 1` + +error: manual implementation of an assign operation + --> tests/ui/assign_ops.rs:114:17 + | +LL | count = count + 1; + | ^^^^^^^^^^^^^^^^^ help: replace it with: `count += 1` + +error: manual implementation of an assign operation + --> tests/ui/assign_ops.rs:136:9 + | +LL | s = s + S; + | ^^^^^^^^^ help: replace it with: `s += S` + +error: aborting due to 16 previous errors From 008b8a546a619da3333629b9418fbb4d5b44f6ae Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 22 Aug 2025 17:45:53 +0200 Subject: [PATCH 060/116] Add 1.89 beta-accepted changes to changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc60b1c57f7d..874002237d0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ Current stable, released 2025-08-07 * Removed superseded lints: `transmute_float_to_int`, `transmute_int_to_char`, `transmute_int_to_float`, `transmute_num_to_bytes` (now in rustc) [#14703](https://github.com/rust-lang/rust-clippy/pull/14703) +* Move [`uninlined_format_args`] to `pedantic` (from `style`, now allow-by-default) + [#15287](https://github.com/rust-lang/rust-clippy/pull/15287) ### Enhancements @@ -74,6 +76,9 @@ Current stable, released 2025-08-07 [#14719](https://github.com/rust-lang/rust-clippy/pull/14719) * [`unnecessary_to_owned`] fixed FP when map key is a reference [#14834](https://github.com/rust-lang/rust-clippy/pull/14834) +* [`swap_with_temporary`]: fix false positive leading to different semantics + being suggested, and use the right number of dereferences in suggestion + [#15172](https://github.com/rust-lang/rust-clippy/pull/15172) ### ICE Fixes From 9e3360f414281019c7836c9e6d2affb1c6cefa9b Mon Sep 17 00:00:00 2001 From: okaneco <47607823+okaneco@users.noreply.github.com> Date: Fri, 22 Aug 2025 11:58:36 -0400 Subject: [PATCH 061/116] Stabilize `round_char_boundary` feature --- clippy_lints/src/lib.rs | 2 +- tests/ui/char_indices_as_byte_indices.fixed | 1 - tests/ui/char_indices_as_byte_indices.rs | 1 - tests/ui/char_indices_as_byte_indices.stderr | 28 ++++++++++---------- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 844bc1b0e390..edc91718263d 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -7,7 +7,7 @@ #![feature(iter_intersperse)] #![feature(iter_partition_in_place)] #![feature(never_type)] -#![feature(round_char_boundary)] +#![cfg_attr(bootstrap, feature(round_char_boundary))] #![feature(rustc_private)] #![feature(stmt_expr_attributes)] #![feature(unwrap_infallible)] diff --git a/tests/ui/char_indices_as_byte_indices.fixed b/tests/ui/char_indices_as_byte_indices.fixed index 04c8f6782c51..375a101c2e3a 100644 --- a/tests/ui/char_indices_as_byte_indices.fixed +++ b/tests/ui/char_indices_as_byte_indices.fixed @@ -1,4 +1,3 @@ -#![feature(round_char_boundary)] #![warn(clippy::char_indices_as_byte_indices)] trait StrExt { diff --git a/tests/ui/char_indices_as_byte_indices.rs b/tests/ui/char_indices_as_byte_indices.rs index 773a4fc65f12..eebc39962a2d 100644 --- a/tests/ui/char_indices_as_byte_indices.rs +++ b/tests/ui/char_indices_as_byte_indices.rs @@ -1,4 +1,3 @@ -#![feature(round_char_boundary)] #![warn(clippy::char_indices_as_byte_indices)] trait StrExt { diff --git a/tests/ui/char_indices_as_byte_indices.stderr b/tests/ui/char_indices_as_byte_indices.stderr index e2b4c1db78cf..fae81fd772db 100644 --- a/tests/ui/char_indices_as_byte_indices.stderr +++ b/tests/ui/char_indices_as_byte_indices.stderr @@ -1,12 +1,12 @@ error: indexing into a string with a character position where a byte index is expected - --> tests/ui/char_indices_as_byte_indices.rs:13:24 + --> tests/ui/char_indices_as_byte_indices.rs:12:24 | LL | let _ = prim[..idx]; | ^^^ | = note: a character can take up more than one byte, so they are not interchangeable note: position comes from the enumerate iterator - --> tests/ui/char_indices_as_byte_indices.rs:12:10 + --> tests/ui/char_indices_as_byte_indices.rs:11:10 | LL | for (idx, _) in prim.chars().enumerate() { | ^^^ ^^^^^^^^^^^ @@ -19,14 +19,14 @@ LL + for (idx, _) in prim.char_indices() { | error: passing a character position to a method that expects a byte index - --> tests/ui/char_indices_as_byte_indices.rs:15:23 + --> tests/ui/char_indices_as_byte_indices.rs:14:23 | LL | prim.split_at(idx); | ^^^ | = note: a character can take up more than one byte, so they are not interchangeable note: position comes from the enumerate iterator - --> tests/ui/char_indices_as_byte_indices.rs:12:10 + --> tests/ui/char_indices_as_byte_indices.rs:11:10 | LL | for (idx, _) in prim.chars().enumerate() { | ^^^ ^^^^^^^^^^^ @@ -37,14 +37,14 @@ LL + for (idx, _) in prim.char_indices() { | error: passing a character position to a method that expects a byte index - --> tests/ui/char_indices_as_byte_indices.rs:19:49 + --> tests/ui/char_indices_as_byte_indices.rs:18:49 | LL | let _ = prim[..prim.floor_char_boundary(idx)]; | ^^^ | = note: a character can take up more than one byte, so they are not interchangeable note: position comes from the enumerate iterator - --> tests/ui/char_indices_as_byte_indices.rs:12:10 + --> tests/ui/char_indices_as_byte_indices.rs:11:10 | LL | for (idx, _) in prim.chars().enumerate() { | ^^^ ^^^^^^^^^^^ @@ -55,14 +55,14 @@ LL + for (idx, _) in prim.char_indices() { | error: indexing into a string with a character position where a byte index is expected - --> tests/ui/char_indices_as_byte_indices.rs:29:24 + --> tests/ui/char_indices_as_byte_indices.rs:28:24 | LL | let _ = prim[..c.0]; | ^^^ | = note: a character can take up more than one byte, so they are not interchangeable note: position comes from the enumerate iterator - --> tests/ui/char_indices_as_byte_indices.rs:28:9 + --> tests/ui/char_indices_as_byte_indices.rs:27:9 | LL | for c in prim.chars().enumerate() { | ^ ^^^^^^^^^^^ @@ -73,14 +73,14 @@ LL + for c in prim.char_indices() { | error: passing a character position to a method that expects a byte index - --> tests/ui/char_indices_as_byte_indices.rs:31:23 + --> tests/ui/char_indices_as_byte_indices.rs:30:23 | LL | prim.split_at(c.0); | ^^^ | = note: a character can take up more than one byte, so they are not interchangeable note: position comes from the enumerate iterator - --> tests/ui/char_indices_as_byte_indices.rs:28:9 + --> tests/ui/char_indices_as_byte_indices.rs:27:9 | LL | for c in prim.chars().enumerate() { | ^ ^^^^^^^^^^^ @@ -91,14 +91,14 @@ LL + for c in prim.char_indices() { | error: indexing into a string with a character position where a byte index is expected - --> tests/ui/char_indices_as_byte_indices.rs:36:26 + --> tests/ui/char_indices_as_byte_indices.rs:35:26 | LL | let _ = string[..idx]; | ^^^ | = note: a character can take up more than one byte, so they are not interchangeable note: position comes from the enumerate iterator - --> tests/ui/char_indices_as_byte_indices.rs:35:10 + --> tests/ui/char_indices_as_byte_indices.rs:34:10 | LL | for (idx, _) in string.chars().enumerate() { | ^^^ ^^^^^^^^^^^ @@ -109,14 +109,14 @@ LL + for (idx, _) in string.char_indices() { | error: passing a character position to a method that expects a byte index - --> tests/ui/char_indices_as_byte_indices.rs:38:25 + --> tests/ui/char_indices_as_byte_indices.rs:37:25 | LL | string.split_at(idx); | ^^^ | = note: a character can take up more than one byte, so they are not interchangeable note: position comes from the enumerate iterator - --> tests/ui/char_indices_as_byte_indices.rs:35:10 + --> tests/ui/char_indices_as_byte_indices.rs:34:10 | LL | for (idx, _) in string.chars().enumerate() { | ^^^ ^^^^^^^^^^^ From 450e25b2412f76391260220539c41e4a0aa8530e Mon Sep 17 00:00:00 2001 From: yanglsh Date: Sat, 23 Aug 2025 01:46:24 +0800 Subject: [PATCH 062/116] fix: `derivable_impls` suggests wrongly on `derive_const` --- clippy_lints/src/derivable_impls.rs | 46 +++++++++++++++++--- tests/ui/derivable_impls.fixed | 26 +++++++++++ tests/ui/derivable_impls.rs | 26 +++++++++++ tests/ui/derivable_impls.stderr | 22 +++++----- tests/ui/derivable_impls_derive_const.fixed | 25 +++++++++++ tests/ui/derivable_impls_derive_const.rs | 32 ++++++++++++++ tests/ui/derivable_impls_derive_const.stderr | 46 ++++++++++++++++++++ 7 files changed, 207 insertions(+), 16 deletions(-) create mode 100644 tests/ui/derivable_impls_derive_const.fixed create mode 100644 tests/ui/derivable_impls_derive_const.rs create mode 100644 tests/ui/derivable_impls_derive_const.stderr diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index 7580d6cab66d..c58aca6a52ba 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -85,6 +85,17 @@ fn contains_trait_object(ty: Ty<'_>) -> bool { } } +fn determine_derive_macro(cx: &LateContext<'_>, is_const: bool) -> Option<&'static str> { + if is_const { + if !cx.tcx.features().enabled(sym::derive_const) { + return None; + } + return Some("derive_const"); + } + Some("derive") +} + +#[expect(clippy::too_many_arguments)] fn check_struct<'tcx>( cx: &LateContext<'tcx>, item: &'tcx Item<'_>, @@ -93,6 +104,7 @@ fn check_struct<'tcx>( adt_def: AdtDef<'_>, ty_args: GenericArgsRef<'_>, typeck_results: &'tcx TypeckResults<'tcx>, + is_const: bool, ) { if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind && let Some(PathSegment { args, .. }) = p.segments.last() @@ -128,11 +140,15 @@ fn check_struct<'tcx>( _ => false, }; + let Some(derive_snippet) = determine_derive_macro(cx, is_const) else { + return; + }; + if should_emit { let struct_span = cx.tcx.def_span(adt_def.did()); let suggestions = vec![ (item.span, String::new()), // Remove the manual implementation - (struct_span.shrink_to_lo(), "#[derive(Default)]\n".to_string()), // Add the derive attribute + (struct_span.shrink_to_lo(), format!("#[{derive_snippet}(Default)]\n")), // Add the derive attribute ]; span_lint_and_then(cx, DERIVABLE_IMPLS, item.span, "this `impl` can be derived", |diag| { @@ -145,7 +161,13 @@ fn check_struct<'tcx>( } } -fn check_enum<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>, func_expr: &Expr<'_>, adt_def: AdtDef<'_>) { +fn check_enum<'tcx>( + cx: &LateContext<'tcx>, + item: &'tcx Item<'_>, + func_expr: &Expr<'_>, + adt_def: AdtDef<'_>, + is_const: bool, +) { if let ExprKind::Path(QPath::Resolved(None, p)) = &peel_blocks(func_expr).kind && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res && let variant_id = cx.tcx.parent(id) @@ -158,11 +180,15 @@ fn check_enum<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>, func_expr: &Ex let variant_span = cx.tcx.def_span(variant_def.def_id); let indent_variant = indent_of(cx, variant_span).unwrap_or(0); + let Some(derive_snippet) = determine_derive_macro(cx, is_const) else { + return; + }; + let suggestions = vec![ (item.span, String::new()), // Remove the manual implementation ( enum_span.shrink_to_lo(), - format!("#[derive(Default)]\n{}", " ".repeat(indent_enum)), + format!("#[{derive_snippet}(Default)]\n{}", " ".repeat(indent_enum)), ), // Add the derive attribute ( variant_span.shrink_to_lo(), @@ -201,10 +227,20 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls { && !attrs.iter().any(|attr| attr.doc_str().is_some()) && cx.tcx.hir_attrs(impl_item_hir).is_empty() { + let is_const = of_trait.constness == hir::Constness::Const; if adt_def.is_struct() { - check_struct(cx, item, self_ty, func_expr, adt_def, args, cx.tcx.typeck_body(*b)); + check_struct( + cx, + item, + self_ty, + func_expr, + adt_def, + args, + cx.tcx.typeck_body(*b), + is_const, + ); } else if adt_def.is_enum() && self.msrv.meets(cx, msrvs::DEFAULT_ENUM_ATTRIBUTE) { - check_enum(cx, item, func_expr, adt_def); + check_enum(cx, item, func_expr, adt_def, is_const); } } } diff --git a/tests/ui/derivable_impls.fixed b/tests/ui/derivable_impls.fixed index 65bfded38835..f549aee9eb1e 100644 --- a/tests/ui/derivable_impls.fixed +++ b/tests/ui/derivable_impls.fixed @@ -1,4 +1,6 @@ #![allow(dead_code)] +#![feature(const_trait_impl)] +#![feature(const_default)] use std::collections::HashMap; @@ -326,4 +328,28 @@ mod issue11368 { } } +mod issue15493 { + #[derive(Copy, Clone)] + #[repr(transparent)] + struct Foo(u64); + + impl const Default for Foo { + fn default() -> Self { + Self(0) + } + } + + #[derive(Copy, Clone)] + enum Bar { + A, + B, + } + + impl const Default for Bar { + fn default() -> Self { + Bar::A + } + } +} + fn main() {} diff --git a/tests/ui/derivable_impls.rs b/tests/ui/derivable_impls.rs index 4826c5497b4a..1e06ff6120bd 100644 --- a/tests/ui/derivable_impls.rs +++ b/tests/ui/derivable_impls.rs @@ -1,4 +1,6 @@ #![allow(dead_code)] +#![feature(const_trait_impl)] +#![feature(const_default)] use std::collections::HashMap; @@ -396,4 +398,28 @@ mod issue11368 { } } +mod issue15493 { + #[derive(Copy, Clone)] + #[repr(transparent)] + struct Foo(u64); + + impl const Default for Foo { + fn default() -> Self { + Self(0) + } + } + + #[derive(Copy, Clone)] + enum Bar { + A, + B, + } + + impl const Default for Bar { + fn default() -> Self { + Bar::A + } + } +} + fn main() {} diff --git a/tests/ui/derivable_impls.stderr b/tests/ui/derivable_impls.stderr index 0f73ad55a85e..d473f2a379c5 100644 --- a/tests/ui/derivable_impls.stderr +++ b/tests/ui/derivable_impls.stderr @@ -1,5 +1,5 @@ error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:20:1 + --> tests/ui/derivable_impls.rs:22:1 | LL | / impl std::default::Default for FooDefault<'_> { LL | | @@ -18,7 +18,7 @@ LL ~ struct FooDefault<'a> { | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:42:1 + --> tests/ui/derivable_impls.rs:44:1 | LL | / impl std::default::Default for TupleDefault { LL | | @@ -35,7 +35,7 @@ LL ~ struct TupleDefault(bool, i32, u64); | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:95:1 + --> tests/ui/derivable_impls.rs:97:1 | LL | / impl Default for StrDefault<'_> { LL | | @@ -52,7 +52,7 @@ LL ~ struct StrDefault<'a>(&'a str); | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:122:1 + --> tests/ui/derivable_impls.rs:124:1 | LL | / impl Default for Y { LL | | @@ -69,7 +69,7 @@ LL ~ struct Y(u32); | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:162:1 + --> tests/ui/derivable_impls.rs:164:1 | LL | / impl Default for WithoutSelfCurly { LL | | @@ -86,7 +86,7 @@ LL ~ struct WithoutSelfCurly { | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:171:1 + --> tests/ui/derivable_impls.rs:173:1 | LL | / impl Default for WithoutSelfParan { LL | | @@ -103,7 +103,7 @@ LL ~ struct WithoutSelfParan(bool); | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:194:1 + --> tests/ui/derivable_impls.rs:196:1 | LL | / impl Default for DirectDefaultDefaultCall { LL | | @@ -119,7 +119,7 @@ LL ~ pub struct DirectDefaultDefaultCall { | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:206:1 + --> tests/ui/derivable_impls.rs:208:1 | LL | / impl Default for EquivalentToDefaultDefaultCallVec { LL | | @@ -135,7 +135,7 @@ LL ~ pub struct EquivalentToDefaultDefaultCallVec { | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:234:1 + --> tests/ui/derivable_impls.rs:236:1 | LL | / impl Default for EquivalentToDefaultDefaultCallLocal { LL | | @@ -151,7 +151,7 @@ LL ~ pub struct EquivalentToDefaultDefaultCallLocal { | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:274:1 + --> tests/ui/derivable_impls.rs:276:1 | LL | / impl Default for RepeatDefault1 { LL | | @@ -168,7 +168,7 @@ LL ~ pub struct RepeatDefault1 { | error: this `impl` can be derived - --> tests/ui/derivable_impls.rs:309:1 + --> tests/ui/derivable_impls.rs:311:1 | LL | / impl Default for SimpleEnum { LL | | diff --git a/tests/ui/derivable_impls_derive_const.fixed b/tests/ui/derivable_impls_derive_const.fixed new file mode 100644 index 000000000000..6df43f7fb763 --- /dev/null +++ b/tests/ui/derivable_impls_derive_const.fixed @@ -0,0 +1,25 @@ +#![allow(dead_code)] +#![feature(const_trait_impl)] +#![feature(const_default)] +#![feature(derive_const)] + +mod issue15493 { + #[derive(Copy, Clone)] + #[repr(transparent)] + #[derive_const(Default)] +struct Foo(u64); + + + + #[derive(Copy, Clone)] + #[derive_const(Default)] + enum Bar { + #[default] + A, + B, + } + + +} + +fn main() {} diff --git a/tests/ui/derivable_impls_derive_const.rs b/tests/ui/derivable_impls_derive_const.rs new file mode 100644 index 000000000000..7d70db1c097d --- /dev/null +++ b/tests/ui/derivable_impls_derive_const.rs @@ -0,0 +1,32 @@ +#![allow(dead_code)] +#![feature(const_trait_impl)] +#![feature(const_default)] +#![feature(derive_const)] + +mod issue15493 { + #[derive(Copy, Clone)] + #[repr(transparent)] + struct Foo(u64); + + impl const Default for Foo { + //~^ derivable_impls + fn default() -> Self { + Self(0) + } + } + + #[derive(Copy, Clone)] + enum Bar { + A, + B, + } + + impl const Default for Bar { + //~^ derivable_impls + fn default() -> Self { + Bar::A + } + } +} + +fn main() {} diff --git a/tests/ui/derivable_impls_derive_const.stderr b/tests/ui/derivable_impls_derive_const.stderr new file mode 100644 index 000000000000..dd185676c770 --- /dev/null +++ b/tests/ui/derivable_impls_derive_const.stderr @@ -0,0 +1,46 @@ +error: this `impl` can be derived + --> tests/ui/derivable_impls_derive_const.rs:11:5 + | +LL | / impl const Default for Foo { +LL | | +LL | | fn default() -> Self { +LL | | Self(0) +LL | | } +LL | | } + | |_____^ + | + = note: `-D clippy::derivable-impls` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::derivable_impls)]` +help: replace the manual implementation with a derive attribute + | +LL ~ #[derive_const(Default)] +LL ~ struct Foo(u64); +LL | +LL ~ + | + +error: this `impl` can be derived + --> tests/ui/derivable_impls_derive_const.rs:24:5 + | +LL | / impl const Default for Bar { +LL | | +LL | | fn default() -> Self { +LL | | Bar::A +LL | | } +LL | | } + | |_____^ + | +help: replace the manual implementation with a derive attribute and mark the default variant + | +LL ~ #[derive_const(Default)] +LL ~ enum Bar { +LL ~ #[default] +LL ~ A, +LL | B, +LL | } +LL | +LL ~ + | + +error: aborting due to 2 previous errors + From f64fbe118409e29e2e3000c79d2ad2bbafc09788 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 15 Aug 2025 09:33:00 +0200 Subject: [PATCH 063/116] fix: also get `ty_sugg` in the `matches!` case --- clippy_lints/src/manual_is_ascii_check.rs | 26 +++++++++++--------- tests/ui/manual_is_ascii_check.fixed | 4 +++ tests/ui/manual_is_ascii_check.rs | 4 +++ tests/ui/manual_is_ascii_check.stderr | 30 ++++++++++++++++++++--- 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/clippy_lints/src/manual_is_ascii_check.rs b/clippy_lints/src/manual_is_ascii_check.rs index ac8c88f02057..2eebb2430fd9 100644 --- a/clippy_lints/src/manual_is_ascii_check.rs +++ b/clippy_lints/src/manual_is_ascii_check.rs @@ -97,11 +97,12 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck { return; } - if let Some(macro_call) = matching_root_macro_call(cx, expr.span, sym::matches_macro) { - if let ExprKind::Match(recv, [arm, ..], _) = expr.kind { - let range = check_pat(&arm.pat.kind); - check_is_ascii(cx, macro_call.span, recv, &range, None); - } + let (arg, span, range) = if let Some(macro_call) = matching_root_macro_call(cx, expr.span, sym::matches_macro) + && let ExprKind::Match(recv, [arm, ..], _) = expr.kind + { + let recv = peel_ref_operators(cx, recv); + let range = check_pat(&arm.pat.kind); + (recv, macro_call.span, range) } else if let ExprKind::MethodCall(path, receiver, [arg], ..) = expr.kind && path.ident.name == sym::contains && let Some(higher::Range { @@ -112,10 +113,14 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck { && !matches!(cx.typeck_results().expr_ty(arg).peel_refs().kind(), ty::Param(_)) { let arg = peel_ref_operators(cx, arg); - let ty_sugg = get_ty_sugg(cx, arg); let range = check_expr_range(start, end); - check_is_ascii(cx, expr.span, arg, &range, ty_sugg); - } + (arg, expr.span, range) + } else { + return; + }; + + let ty_sugg = get_ty_sugg(cx, arg); + check_is_ascii(cx, span, arg, &range, ty_sugg); } } @@ -146,9 +151,8 @@ fn check_is_ascii( CharRange::HexDigit => "is_ascii_hexdigit", CharRange::Otherwise | CharRange::LowerHexLetter | CharRange::UpperHexLetter => return, }; - let default_snip = ".."; let mut app = Applicability::MachineApplicable; - let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), default_snip, &mut app).maybe_paren(); + let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), "_", &mut app).maybe_paren(); let mut suggestion = vec![(span, format!("{recv}.{sugg}()"))]; if let Some((ty_span, ty)) = ty_sugg { suggestion.push((ty_span, format!("{recv}: {ty}"))); @@ -182,7 +186,7 @@ fn check_pat(pat_kind: &PatKind<'_>) -> CharRange { CharRange::Otherwise } }, - PatKind::Range(Some(start), Some(end), kind) if *kind == RangeEnd::Included => check_range(start, end), + PatKind::Range(Some(start), Some(end), RangeEnd::Included) => check_range(start, end), _ => CharRange::Otherwise, } } diff --git a/tests/ui/manual_is_ascii_check.fixed b/tests/ui/manual_is_ascii_check.fixed index 7b0d19068346..406336dbd095 100644 --- a/tests/ui/manual_is_ascii_check.fixed +++ b/tests/ui/manual_is_ascii_check.fixed @@ -108,6 +108,8 @@ fn generics() { //~^ manual_is_ascii_check take_while(|c: char| c.is_ascii_uppercase()); //~^ manual_is_ascii_check + take_while(|c: char| c.is_ascii_uppercase()); + //~^ manual_is_ascii_check } fn adds_type_reference() { @@ -115,4 +117,6 @@ fn adds_type_reference() { //~^ manual_is_ascii_check let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c: &&mut char| c.is_ascii_digit()).collect(); //~^ manual_is_ascii_check + let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c: &&mut char| c.is_ascii_digit()).collect(); + //~^ manual_is_ascii_check } diff --git a/tests/ui/manual_is_ascii_check.rs b/tests/ui/manual_is_ascii_check.rs index e4f7fe9f5838..a624497e75e5 100644 --- a/tests/ui/manual_is_ascii_check.rs +++ b/tests/ui/manual_is_ascii_check.rs @@ -108,6 +108,8 @@ fn generics() { //~^ manual_is_ascii_check take_while(|c: char| ('A'..='Z').contains(&c)); //~^ manual_is_ascii_check + take_while(|c| matches!(c, 'A'..='Z')); + //~^ manual_is_ascii_check } fn adds_type_reference() { @@ -115,4 +117,6 @@ fn adds_type_reference() { //~^ manual_is_ascii_check let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c| ('0'..='9').contains(c)).collect(); //~^ manual_is_ascii_check + let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c| matches!(c, '0'..='9')).collect(); + //~^ manual_is_ascii_check } diff --git a/tests/ui/manual_is_ascii_check.stderr b/tests/ui/manual_is_ascii_check.stderr index 9fd7f457b420..cb2548ea7316 100644 --- a/tests/ui/manual_is_ascii_check.stderr +++ b/tests/ui/manual_is_ascii_check.stderr @@ -176,7 +176,19 @@ LL | take_while(|c: char| ('A'..='Z').contains(&c)); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `c.is_ascii_uppercase()` error: manual check for common ascii range - --> tests/ui/manual_is_ascii_check.rs:114:63 + --> tests/ui/manual_is_ascii_check.rs:111:20 + | +LL | take_while(|c| matches!(c, 'A'..='Z')); + | ^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - take_while(|c| matches!(c, 'A'..='Z')); +LL + take_while(|c: char| c.is_ascii_uppercase()); + | + +error: manual check for common ascii range + --> tests/ui/manual_is_ascii_check.rs:116:63 | LL | let digits: Vec<&char> = ['1', 'A'].iter().take_while(|c| ('0'..='9').contains(c)).collect(); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -188,7 +200,7 @@ LL + let digits: Vec<&char> = ['1', 'A'].iter().take_while(|c: &&char| c.is_ | error: manual check for common ascii range - --> tests/ui/manual_is_ascii_check.rs:116:71 + --> tests/ui/manual_is_ascii_check.rs:118:71 | LL | let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c| ('0'..='9').contains(c)).collect(); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -199,5 +211,17 @@ LL - let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c| ('0'. LL + let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c: &&mut char| c.is_ascii_digit()).collect(); | -error: aborting due to 29 previous errors +error: manual check for common ascii range + --> tests/ui/manual_is_ascii_check.rs:120:71 + | +LL | let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c| matches!(c, '0'..='9')).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c| matches!(c, '0'..='9')).collect(); +LL + let digits: Vec<&mut char> = ['1', 'A'].iter_mut().take_while(|c: &&mut char| c.is_ascii_digit()).collect(); + | + +error: aborting due to 31 previous errors From 7697b611543031a83e242dc3904debffb8023829 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Mon, 18 Aug 2025 21:48:21 -0700 Subject: [PATCH 064/116] bool_comparison: use correct span context for `>`/`<` sugg --- clippy_lints/src/needless_bool.rs | 5 +++-- tests/ui/bool_comparison.fixed | 19 +++++++++++++--- tests/ui/bool_comparison.rs | 19 +++++++++++++--- tests/ui/bool_comparison.stderr | 36 ++++++++++++++++++++----------- 4 files changed, 59 insertions(+), 20 deletions(-) diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index 6ae26156bc44..d8d66837405f 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -381,8 +381,9 @@ fn check_comparison<'a, 'tcx>( suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); }), (None, None) => no_literal.map_or((), |(h, m)| { - let left_side = Sugg::hir_with_applicability(cx, left_side, "..", &mut applicability); - let right_side = Sugg::hir_with_applicability(cx, right_side, "..", &mut applicability); + let left_side = Sugg::hir_with_context(cx, left_side, binop_span.ctxt(), "..", &mut applicability); + let right_side = + Sugg::hir_with_context(cx, right_side, binop_span.ctxt(), "..", &mut applicability); span_lint_and_sugg( cx, BOOL_COMPARISON, diff --git a/tests/ui/bool_comparison.fixed b/tests/ui/bool_comparison.fixed index 166abbe549c3..e8edc33b56a0 100644 --- a/tests/ui/bool_comparison.fixed +++ b/tests/ui/bool_comparison.fixed @@ -91,7 +91,6 @@ fn main() { }; } -#[allow(dead_code)] fn issue3703() { struct Foo; impl PartialEq for Foo { @@ -127,7 +126,6 @@ fn issue3703() { if false < Foo {} } -#[allow(dead_code)] fn issue4983() { let a = true; let b = false; @@ -157,7 +155,6 @@ fn func() -> bool { true } -#[allow(dead_code)] fn issue3973() { // ok, don't lint on `cfg` invocation if false == cfg!(feature = "debugging") {} @@ -199,3 +196,19 @@ fn issue9907() { let _ = ((1 < 2) != m!(func)) as usize; //~^ bool_comparison } + +fn issue15497() { + fn func() -> bool { + true + } + + fn foo(x: bool) -> bool { + x & !m!(func) + //~^ bool_comparison + } + + fn bar(x: bool) -> bool { + !x & m!(func) + //~^ bool_comparison + } +} diff --git a/tests/ui/bool_comparison.rs b/tests/ui/bool_comparison.rs index 7265c2b4c5a6..18004639029f 100644 --- a/tests/ui/bool_comparison.rs +++ b/tests/ui/bool_comparison.rs @@ -91,7 +91,6 @@ fn main() { }; } -#[allow(dead_code)] fn issue3703() { struct Foo; impl PartialEq for Foo { @@ -127,7 +126,6 @@ fn issue3703() { if false < Foo {} } -#[allow(dead_code)] fn issue4983() { let a = true; let b = false; @@ -157,7 +155,6 @@ fn func() -> bool { true } -#[allow(dead_code)] fn issue3973() { // ok, don't lint on `cfg` invocation if false == cfg!(feature = "debugging") {} @@ -199,3 +196,19 @@ fn issue9907() { let _ = ((1 < 2) == !m!(func)) as usize; //~^ bool_comparison } + +fn issue15497() { + fn func() -> bool { + true + } + + fn foo(x: bool) -> bool { + x > m!(func) + //~^ bool_comparison + } + + fn bar(x: bool) -> bool { + x < m!(func) + //~^ bool_comparison + } +} diff --git a/tests/ui/bool_comparison.stderr b/tests/ui/bool_comparison.stderr index ddbb9ff78edb..a398870e7ee8 100644 --- a/tests/ui/bool_comparison.stderr +++ b/tests/ui/bool_comparison.stderr @@ -86,70 +86,82 @@ LL | if x > y { | ^^^^^ help: try simplifying it as shown: `x & !y` error: this comparison might be written more concisely - --> tests/ui/bool_comparison.rs:135:8 + --> tests/ui/bool_comparison.rs:133:8 | LL | if a == !b {}; | ^^^^^^^ help: try simplifying it as shown: `a != b` error: this comparison might be written more concisely - --> tests/ui/bool_comparison.rs:137:8 + --> tests/ui/bool_comparison.rs:135:8 | LL | if !a == b {}; | ^^^^^^^ help: try simplifying it as shown: `a != b` error: this comparison might be written more concisely - --> tests/ui/bool_comparison.rs:142:8 + --> tests/ui/bool_comparison.rs:140:8 | LL | if b == !a {}; | ^^^^^^^ help: try simplifying it as shown: `b != a` error: this comparison might be written more concisely - --> tests/ui/bool_comparison.rs:144:8 + --> tests/ui/bool_comparison.rs:142:8 | LL | if !b == a {}; | ^^^^^^^ help: try simplifying it as shown: `b != a` error: equality checks against false can be replaced by a negation - --> tests/ui/bool_comparison.rs:169:8 + --> tests/ui/bool_comparison.rs:166:8 | LL | if false == m!(func) {} | ^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `!m!(func)` error: equality checks against false can be replaced by a negation - --> tests/ui/bool_comparison.rs:171:8 + --> tests/ui/bool_comparison.rs:168:8 | LL | if m!(func) == false {} | ^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `!m!(func)` error: equality checks against true are unnecessary - --> tests/ui/bool_comparison.rs:173:8 + --> tests/ui/bool_comparison.rs:170:8 | LL | if true == m!(func) {} | ^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `m!(func)` error: equality checks against true are unnecessary - --> tests/ui/bool_comparison.rs:175:8 + --> tests/ui/bool_comparison.rs:172:8 | LL | if m!(func) == true {} | ^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `m!(func)` error: equality checks against false can be replaced by a negation - --> tests/ui/bool_comparison.rs:193:14 + --> tests/ui/bool_comparison.rs:190:14 | LL | let _ = ((1 < 2) == false) as usize; | ^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `1 >= 2` error: equality checks against false can be replaced by a negation - --> tests/ui/bool_comparison.rs:195:14 + --> tests/ui/bool_comparison.rs:192:14 | LL | let _ = (false == m!(func)) as usize; | ^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `!m!(func)` error: this comparison might be written more concisely - --> tests/ui/bool_comparison.rs:199:14 + --> tests/ui/bool_comparison.rs:196:14 | LL | let _ = ((1 < 2) == !m!(func)) as usize; | ^^^^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `(1 < 2) != m!(func)` -error: aborting due to 25 previous errors +error: order comparisons between booleans can be simplified + --> tests/ui/bool_comparison.rs:206:9 + | +LL | x > m!(func) + | ^^^^^^^^^^^^ help: try simplifying it as shown: `x & !m!(func)` + +error: order comparisons between booleans can be simplified + --> tests/ui/bool_comparison.rs:211:9 + | +LL | x < m!(func) + | ^^^^^^^^^^^^ help: try simplifying it as shown: `!x & m!(func)` + +error: aborting due to 27 previous errors From c2c3e0990b4430cd2ce9c27b1eb8ff5dd57091cb Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 15 Aug 2025 12:42:47 +0200 Subject: [PATCH 065/116] fix: don't check for `!x = y`/`x = !y` misc: rm "as shown" from help message - clippy guidelines recommend against this misc: pull conditions into let-chain misc: use `Span::to` misc: inline `{l,r}_ty` misc: move the type checks out of `check_comparison` misc: make test cases much less verbose --- clippy_lints/src/needless_bool.rs | 141 +++++++---------------- tests/ui/bool_comparison.fixed | 148 +++++++------------------ tests/ui/bool_comparison.rs | 148 +++++++------------------ tests/ui/bool_comparison.stderr | 154 +++++++++++--------------- tests/ui/needless_bool/fixable.stderr | 8 +- tests/ui/nonminimal_bool.rs | 2 - tests/ui/nonminimal_bool.stderr | 54 ++++----- 7 files changed, 216 insertions(+), 439 deletions(-) diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index d8d66837405f..49d41a3af3dd 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -7,7 +7,7 @@ use clippy_utils::{ }; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; +use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::Span; @@ -232,7 +232,12 @@ impl<'tcx> LateLintPass<'tcx> for BoolComparison { return; } - if let ExprKind::Binary(Spanned { node, .. }, ..) = e.kind { + if let ExprKind::Binary(Spanned { node, .. }, left_side, right_side) = e.kind + && is_expn_of(left_side.span, sym::cfg).is_none() + && is_expn_of(right_side.span, sym::cfg).is_none() + && cx.typeck_results().expr_ty(left_side).is_bool() + && cx.typeck_results().expr_ty(right_side).is_bool() + { let ignore_case = None::<(fn(_) -> _, &str)>; let ignore_no_literal = None::<(fn(_, _) -> _, &str)>; match node { @@ -288,30 +293,6 @@ impl<'tcx> LateLintPass<'tcx> for BoolComparison { } } -struct ExpressionInfoWithSpan { - one_side_is_unary_not: bool, - left_span: Span, - right_span: Span, -} - -fn is_unary_not(e: &Expr<'_>) -> (bool, Span) { - if let ExprKind::Unary(UnOp::Not, operand) = e.kind { - return (true, operand.span); - } - (false, e.span) -} - -fn one_side_is_unary_not<'tcx>(left_side: &'tcx Expr<'_>, right_side: &'tcx Expr<'_>) -> ExpressionInfoWithSpan { - let left = is_unary_not(left_side); - let right = is_unary_not(right_side); - - ExpressionInfoWithSpan { - one_side_is_unary_not: left.0 != right.0, - left_span: left.1, - right_span: right.1, - } -} - fn check_comparison<'a, 'tcx>( cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, @@ -321,81 +302,39 @@ fn check_comparison<'a, 'tcx>( right_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, no_literal: Option<(impl FnOnce(Sugg<'a>, Sugg<'a>) -> Sugg<'a>, &'static str)>, ) { - if let ExprKind::Binary(op, left_side, right_side) = e.kind { - let (l_ty, r_ty) = ( - cx.typeck_results().expr_ty(left_side), - cx.typeck_results().expr_ty(right_side), - ); - if is_expn_of(left_side.span, sym::cfg).is_some() || is_expn_of(right_side.span, sym::cfg).is_some() { - return; - } - if l_ty.is_bool() && r_ty.is_bool() { - let mut applicability = Applicability::MachineApplicable; - // Eliminate parentheses in `e` by using the lo pos of lhs and hi pos of rhs, - // calling `source_callsite` make sure macros are handled correctly, see issue #9907 - let binop_span = left_side - .span - .source_callsite() - .with_hi(right_side.span.source_callsite().hi()); + if let ExprKind::Binary(_, left_side, right_side) = e.kind { + let mut applicability = Applicability::MachineApplicable; + // Eliminate parentheses in `e` by using the lo pos of lhs and hi pos of rhs, + // calling `source_callsite` make sure macros are handled correctly, see issue #9907 + let binop_span = left_side.span.source_callsite().to(right_side.span.source_callsite()); - if op.node == BinOpKind::Eq { - let expression_info = one_side_is_unary_not(left_side, right_side); - if expression_info.one_side_is_unary_not { - span_lint_and_sugg( - cx, - BOOL_COMPARISON, - binop_span, - "this comparison might be written more concisely", - "try simplifying it as shown", - format!( - "{} != {}", - snippet_with_applicability( - cx, - expression_info.left_span.source_callsite(), - "..", - &mut applicability - ), - snippet_with_applicability( - cx, - expression_info.right_span.source_callsite(), - "..", - &mut applicability - ) - ), - applicability, - ); - } - } - - match (fetch_bool_expr(left_side), fetch_bool_expr(right_side)) { - (Some(true), None) => left_true.map_or((), |(h, m)| { - suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h); - }), - (None, Some(true)) => right_true.map_or((), |(h, m)| { - suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); - }), - (Some(false), None) => left_false.map_or((), |(h, m)| { - suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h); - }), - (None, Some(false)) => right_false.map_or((), |(h, m)| { - suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); - }), - (None, None) => no_literal.map_or((), |(h, m)| { - let left_side = Sugg::hir_with_context(cx, left_side, binop_span.ctxt(), "..", &mut applicability); - let right_side = - Sugg::hir_with_context(cx, right_side, binop_span.ctxt(), "..", &mut applicability); - span_lint_and_sugg( - cx, - BOOL_COMPARISON, - binop_span, - m, - "try simplifying it as shown", - h(left_side, right_side).into_string(), - applicability, - ); - }), - _ => (), - } + match (fetch_bool_expr(left_side), fetch_bool_expr(right_side)) { + (Some(true), None) => left_true.map_or((), |(h, m)| { + suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h); + }), + (None, Some(true)) => right_true.map_or((), |(h, m)| { + suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); + }), + (Some(false), None) => left_false.map_or((), |(h, m)| { + suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h); + }), + (None, Some(false)) => right_false.map_or((), |(h, m)| { + suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); + }), + (None, None) => no_literal.map_or((), |(h, m)| { + let left_side = Sugg::hir_with_context(cx, left_side, binop_span.ctxt(), "..", &mut applicability); + let right_side = Sugg::hir_with_context(cx, right_side, binop_span.ctxt(), "..", &mut applicability); + span_lint_and_sugg( + cx, + BOOL_COMPARISON, + binop_span, + m, + "try", + h(left_side, right_side).into_string(), + applicability, + ); + }), + _ => (), } } } @@ -414,7 +353,7 @@ fn suggest_bool_comparison<'a, 'tcx>( BOOL_COMPARISON, span, message, - "try simplifying it as shown", + "try", conv_hint(hint).into_string(), app, ); diff --git a/tests/ui/bool_comparison.fixed b/tests/ui/bool_comparison.fixed index e8edc33b56a0..b0b60104c0b9 100644 --- a/tests/ui/bool_comparison.fixed +++ b/tests/ui/bool_comparison.fixed @@ -1,94 +1,39 @@ #![allow(non_local_definitions, clippy::needless_if)] #![warn(clippy::bool_comparison)] -#![allow(clippy::non_canonical_partial_ord_impl, clippy::nonminimal_bool)] +#![allow(clippy::non_canonical_partial_ord_impl)] fn main() { let x = true; - if x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if !x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if !x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if !x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if !x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if !x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if !x { - //~^ bool_comparison - "yes" - } else { - "no" - }; + let _ = if x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if !x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if !x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if !x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if !x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if !x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if !x { "yes" } else { "no" }; + //~^ bool_comparison + let y = true; - if !x & y { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x & !y { - //~^ bool_comparison - "yes" - } else { - "no" - }; + let _ = if !x & y { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x & !y { "yes" } else { "no" }; + //~^ bool_comparison } fn issue3703() { @@ -126,25 +71,6 @@ fn issue3703() { if false < Foo {} } -fn issue4983() { - let a = true; - let b = false; - - if a != b {}; - //~^ bool_comparison - if a != b {}; - //~^ bool_comparison - if a == b {}; - if !a == !b {}; - - if b != a {}; - //~^ bool_comparison - if b != a {}; - //~^ bool_comparison - if b == a {}; - if !b == !a {}; -} - macro_rules! m { ($func:ident) => { $func() @@ -193,10 +119,22 @@ fn issue9907() { //~^ bool_comparison // This is not part of the issue, but an unexpected found when fixing the issue, // the provided span was inside of macro rather than the macro callsite. - let _ = ((1 < 2) != m!(func)) as usize; + let _ = ((1 < 2) & !m!(func)) as usize; //~^ bool_comparison } +#[allow(clippy::nonminimal_bool)] +fn issue15367() { + let a = true; + let b = false; + + // these cases are handled by `nonminimal_bool`, so don't double-lint + if a == !b {}; + if !a == b {}; + if b == !a {}; + if !b == a {}; +} + fn issue15497() { fn func() -> bool { true diff --git a/tests/ui/bool_comparison.rs b/tests/ui/bool_comparison.rs index 18004639029f..1b1108d6ce50 100644 --- a/tests/ui/bool_comparison.rs +++ b/tests/ui/bool_comparison.rs @@ -1,94 +1,39 @@ #![allow(non_local_definitions, clippy::needless_if)] #![warn(clippy::bool_comparison)] -#![allow(clippy::non_canonical_partial_ord_impl, clippy::nonminimal_bool)] +#![allow(clippy::non_canonical_partial_ord_impl)] fn main() { let x = true; - if x == true { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x == false { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if true == x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if false == x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x != true { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x != false { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if true != x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if false != x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x < true { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if false < x { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x > false { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if true > x { - //~^ bool_comparison - "yes" - } else { - "no" - }; + let _ = if x == true { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x == false { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if true == x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if false == x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x != true { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x != false { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if true != x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if false != x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x < true { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if false < x { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x > false { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if true > x { "yes" } else { "no" }; + //~^ bool_comparison + let y = true; - if x < y { - //~^ bool_comparison - "yes" - } else { - "no" - }; - if x > y { - //~^ bool_comparison - "yes" - } else { - "no" - }; + let _ = if x < y { "yes" } else { "no" }; + //~^ bool_comparison + let _ = if x > y { "yes" } else { "no" }; + //~^ bool_comparison } fn issue3703() { @@ -126,25 +71,6 @@ fn issue3703() { if false < Foo {} } -fn issue4983() { - let a = true; - let b = false; - - if a == !b {}; - //~^ bool_comparison - if !a == b {}; - //~^ bool_comparison - if a == b {}; - if !a == !b {}; - - if b == !a {}; - //~^ bool_comparison - if !b == a {}; - //~^ bool_comparison - if b == a {}; - if !b == !a {}; -} - macro_rules! m { ($func:ident) => { $func() @@ -193,10 +119,22 @@ fn issue9907() { //~^ bool_comparison // This is not part of the issue, but an unexpected found when fixing the issue, // the provided span was inside of macro rather than the macro callsite. - let _ = ((1 < 2) == !m!(func)) as usize; + let _ = ((1 < 2) > m!(func)) as usize; //~^ bool_comparison } +#[allow(clippy::nonminimal_bool)] +fn issue15367() { + let a = true; + let b = false; + + // these cases are handled by `nonminimal_bool`, so don't double-lint + if a == !b {}; + if !a == b {}; + if b == !a {}; + if !b == a {}; +} + fn issue15497() { fn func() -> bool { true diff --git a/tests/ui/bool_comparison.stderr b/tests/ui/bool_comparison.stderr index a398870e7ee8..98881a2d20fd 100644 --- a/tests/ui/bool_comparison.stderr +++ b/tests/ui/bool_comparison.stderr @@ -1,167 +1,143 @@ error: equality checks against true are unnecessary - --> tests/ui/bool_comparison.rs:7:8 + --> tests/ui/bool_comparison.rs:7:16 | -LL | if x == true { - | ^^^^^^^^^ help: try simplifying it as shown: `x` +LL | let _ = if x == true { "yes" } else { "no" }; + | ^^^^^^^^^ help: try: `x` | = note: `-D clippy::bool-comparison` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::bool_comparison)]` error: equality checks against false can be replaced by a negation - --> tests/ui/bool_comparison.rs:13:8 + --> tests/ui/bool_comparison.rs:9:16 | -LL | if x == false { - | ^^^^^^^^^^ help: try simplifying it as shown: `!x` +LL | let _ = if x == false { "yes" } else { "no" }; + | ^^^^^^^^^^ help: try: `!x` error: equality checks against true are unnecessary - --> tests/ui/bool_comparison.rs:19:8 + --> tests/ui/bool_comparison.rs:11:16 | -LL | if true == x { - | ^^^^^^^^^ help: try simplifying it as shown: `x` +LL | let _ = if true == x { "yes" } else { "no" }; + | ^^^^^^^^^ help: try: `x` error: equality checks against false can be replaced by a negation - --> tests/ui/bool_comparison.rs:25:8 + --> tests/ui/bool_comparison.rs:13:16 | -LL | if false == x { - | ^^^^^^^^^^ help: try simplifying it as shown: `!x` +LL | let _ = if false == x { "yes" } else { "no" }; + | ^^^^^^^^^^ help: try: `!x` error: inequality checks against true can be replaced by a negation - --> tests/ui/bool_comparison.rs:31:8 + --> tests/ui/bool_comparison.rs:15:16 | -LL | if x != true { - | ^^^^^^^^^ help: try simplifying it as shown: `!x` +LL | let _ = if x != true { "yes" } else { "no" }; + | ^^^^^^^^^ help: try: `!x` error: inequality checks against false are unnecessary - --> tests/ui/bool_comparison.rs:37:8 + --> tests/ui/bool_comparison.rs:17:16 | -LL | if x != false { - | ^^^^^^^^^^ help: try simplifying it as shown: `x` +LL | let _ = if x != false { "yes" } else { "no" }; + | ^^^^^^^^^^ help: try: `x` error: inequality checks against true can be replaced by a negation - --> tests/ui/bool_comparison.rs:43:8 + --> tests/ui/bool_comparison.rs:19:16 | -LL | if true != x { - | ^^^^^^^^^ help: try simplifying it as shown: `!x` +LL | let _ = if true != x { "yes" } else { "no" }; + | ^^^^^^^^^ help: try: `!x` error: inequality checks against false are unnecessary - --> tests/ui/bool_comparison.rs:49:8 + --> tests/ui/bool_comparison.rs:21:16 | -LL | if false != x { - | ^^^^^^^^^^ help: try simplifying it as shown: `x` +LL | let _ = if false != x { "yes" } else { "no" }; + | ^^^^^^^^^^ help: try: `x` error: less than comparison against true can be replaced by a negation - --> tests/ui/bool_comparison.rs:55:8 + --> tests/ui/bool_comparison.rs:23:16 | -LL | if x < true { - | ^^^^^^^^ help: try simplifying it as shown: `!x` +LL | let _ = if x < true { "yes" } else { "no" }; + | ^^^^^^^^ help: try: `!x` error: greater than checks against false are unnecessary - --> tests/ui/bool_comparison.rs:61:8 + --> tests/ui/bool_comparison.rs:25:16 | -LL | if false < x { - | ^^^^^^^^^ help: try simplifying it as shown: `x` +LL | let _ = if false < x { "yes" } else { "no" }; + | ^^^^^^^^^ help: try: `x` error: greater than checks against false are unnecessary - --> tests/ui/bool_comparison.rs:67:8 + --> tests/ui/bool_comparison.rs:27:16 | -LL | if x > false { - | ^^^^^^^^^ help: try simplifying it as shown: `x` +LL | let _ = if x > false { "yes" } else { "no" }; + | ^^^^^^^^^ help: try: `x` error: less than comparison against true can be replaced by a negation - --> tests/ui/bool_comparison.rs:73:8 + --> tests/ui/bool_comparison.rs:29:16 | -LL | if true > x { - | ^^^^^^^^ help: try simplifying it as shown: `!x` +LL | let _ = if true > x { "yes" } else { "no" }; + | ^^^^^^^^ help: try: `!x` error: order comparisons between booleans can be simplified - --> tests/ui/bool_comparison.rs:80:8 + --> tests/ui/bool_comparison.rs:33:16 | -LL | if x < y { - | ^^^^^ help: try simplifying it as shown: `!x & y` +LL | let _ = if x < y { "yes" } else { "no" }; + | ^^^^^ help: try: `!x & y` error: order comparisons between booleans can be simplified - --> tests/ui/bool_comparison.rs:86:8 + --> tests/ui/bool_comparison.rs:35:16 | -LL | if x > y { - | ^^^^^ help: try simplifying it as shown: `x & !y` - -error: this comparison might be written more concisely - --> tests/ui/bool_comparison.rs:133:8 - | -LL | if a == !b {}; - | ^^^^^^^ help: try simplifying it as shown: `a != b` - -error: this comparison might be written more concisely - --> tests/ui/bool_comparison.rs:135:8 - | -LL | if !a == b {}; - | ^^^^^^^ help: try simplifying it as shown: `a != b` - -error: this comparison might be written more concisely - --> tests/ui/bool_comparison.rs:140:8 - | -LL | if b == !a {}; - | ^^^^^^^ help: try simplifying it as shown: `b != a` - -error: this comparison might be written more concisely - --> tests/ui/bool_comparison.rs:142:8 - | -LL | if !b == a {}; - | ^^^^^^^ help: try simplifying it as shown: `b != a` +LL | let _ = if x > y { "yes" } else { "no" }; + | ^^^^^ help: try: `x & !y` error: equality checks against false can be replaced by a negation - --> tests/ui/bool_comparison.rs:166:8 + --> tests/ui/bool_comparison.rs:92:8 | LL | if false == m!(func) {} - | ^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `!m!(func)` + | ^^^^^^^^^^^^^^^^^ help: try: `!m!(func)` error: equality checks against false can be replaced by a negation - --> tests/ui/bool_comparison.rs:168:8 + --> tests/ui/bool_comparison.rs:94:8 | LL | if m!(func) == false {} - | ^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `!m!(func)` + | ^^^^^^^^^^^^^^^^^ help: try: `!m!(func)` error: equality checks against true are unnecessary - --> tests/ui/bool_comparison.rs:170:8 + --> tests/ui/bool_comparison.rs:96:8 | LL | if true == m!(func) {} - | ^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `m!(func)` + | ^^^^^^^^^^^^^^^^ help: try: `m!(func)` error: equality checks against true are unnecessary - --> tests/ui/bool_comparison.rs:172:8 + --> tests/ui/bool_comparison.rs:98:8 | LL | if m!(func) == true {} - | ^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `m!(func)` + | ^^^^^^^^^^^^^^^^ help: try: `m!(func)` error: equality checks against false can be replaced by a negation - --> tests/ui/bool_comparison.rs:190:14 + --> tests/ui/bool_comparison.rs:116:14 | LL | let _ = ((1 < 2) == false) as usize; - | ^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `1 >= 2` + | ^^^^^^^^^^^^^^^^ help: try: `1 >= 2` error: equality checks against false can be replaced by a negation - --> tests/ui/bool_comparison.rs:192:14 + --> tests/ui/bool_comparison.rs:118:14 | LL | let _ = (false == m!(func)) as usize; - | ^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `!m!(func)` - -error: this comparison might be written more concisely - --> tests/ui/bool_comparison.rs:196:14 - | -LL | let _ = ((1 < 2) == !m!(func)) as usize; - | ^^^^^^^^^^^^^^^^^^^^ help: try simplifying it as shown: `(1 < 2) != m!(func)` + | ^^^^^^^^^^^^^^^^^ help: try: `!m!(func)` error: order comparisons between booleans can be simplified - --> tests/ui/bool_comparison.rs:206:9 + --> tests/ui/bool_comparison.rs:122:14 + | +LL | let _ = ((1 < 2) > m!(func)) as usize; + | ^^^^^^^^^^^^^^^^^^ help: try: `(1 < 2) & !m!(func)` + +error: order comparisons between booleans can be simplified + --> tests/ui/bool_comparison.rs:144:9 | LL | x > m!(func) - | ^^^^^^^^^^^^ help: try simplifying it as shown: `x & !m!(func)` + | ^^^^^^^^^^^^ help: try: `x & !m!(func)` error: order comparisons between booleans can be simplified - --> tests/ui/bool_comparison.rs:211:9 + --> tests/ui/bool_comparison.rs:149:9 | LL | x < m!(func) - | ^^^^^^^^^^^^ help: try simplifying it as shown: `!x & m!(func)` + | ^^^^^^^^^^^^ help: try: `!x & m!(func)` -error: aborting due to 27 previous errors +error: aborting due to 23 previous errors diff --git a/tests/ui/needless_bool/fixable.stderr b/tests/ui/needless_bool/fixable.stderr index 3f117ee5a502..9404d07ba0e0 100644 --- a/tests/ui/needless_bool/fixable.stderr +++ b/tests/ui/needless_bool/fixable.stderr @@ -135,7 +135,7 @@ error: equality checks against true are unnecessary --> tests/ui/needless_bool/fixable.rs:157:8 | LL | if x == true {}; - | ^^^^^^^^^ help: try simplifying it as shown: `x` + | ^^^^^^^^^ help: try: `x` | = note: `-D clippy::bool-comparison` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::bool_comparison)]` @@ -144,19 +144,19 @@ error: equality checks against false can be replaced by a negation --> tests/ui/needless_bool/fixable.rs:162:8 | LL | if x == false {}; - | ^^^^^^^^^^ help: try simplifying it as shown: `!x` + | ^^^^^^^^^^ help: try: `!x` error: equality checks against true are unnecessary --> tests/ui/needless_bool/fixable.rs:173:8 | LL | if x == true {}; - | ^^^^^^^^^ help: try simplifying it as shown: `x` + | ^^^^^^^^^ help: try: `x` error: equality checks against false can be replaced by a negation --> tests/ui/needless_bool/fixable.rs:175:8 | LL | if x == false {}; - | ^^^^^^^^^^ help: try simplifying it as shown: `!x` + | ^^^^^^^^^^ help: try: `!x` error: this if-then-else expression returns a bool literal --> tests/ui/needless_bool/fixable.rs:185:12 diff --git a/tests/ui/nonminimal_bool.rs b/tests/ui/nonminimal_bool.rs index cacce9a7d1cb..f03f74dfafe2 100644 --- a/tests/ui/nonminimal_bool.rs +++ b/tests/ui/nonminimal_bool.rs @@ -182,14 +182,12 @@ fn issue_5794() { if !b == true {} //~^ nonminimal_bool //~| bool_comparison - //~| bool_comparison if !b != true {} //~^ nonminimal_bool //~| bool_comparison if true == !b {} //~^ nonminimal_bool //~| bool_comparison - //~| bool_comparison if true != !b {} //~^ nonminimal_bool //~| bool_comparison diff --git a/tests/ui/nonminimal_bool.stderr b/tests/ui/nonminimal_bool.stderr index c20412974b20..6a20b9216da5 100644 --- a/tests/ui/nonminimal_bool.stderr +++ b/tests/ui/nonminimal_bool.stderr @@ -154,98 +154,86 @@ error: this boolean expression can be simplified LL | if !b == true {} | ^^^^^^^^^^ help: try: `b != true` -error: this comparison might be written more concisely - --> tests/ui/nonminimal_bool.rs:182:8 - | -LL | if !b == true {} - | ^^^^^^^^^^ help: try simplifying it as shown: `b != true` - | - = note: `-D clippy::bool-comparison` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::bool_comparison)]` - error: equality checks against true are unnecessary --> tests/ui/nonminimal_bool.rs:182:8 | LL | if !b == true {} - | ^^^^^^^^^^ help: try simplifying it as shown: `!b` + | ^^^^^^^^^^ help: try: `!b` + | + = note: `-D clippy::bool-comparison` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::bool_comparison)]` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool.rs:186:8 + --> tests/ui/nonminimal_bool.rs:185:8 | LL | if !b != true {} | ^^^^^^^^^^ help: try: `b == true` error: inequality checks against true can be replaced by a negation - --> tests/ui/nonminimal_bool.rs:186:8 + --> tests/ui/nonminimal_bool.rs:185:8 | LL | if !b != true {} - | ^^^^^^^^^^ help: try simplifying it as shown: `b` + | ^^^^^^^^^^ help: try: `b` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool.rs:189:8 + --> tests/ui/nonminimal_bool.rs:188:8 | LL | if true == !b {} | ^^^^^^^^^^ help: try: `true != b` -error: this comparison might be written more concisely - --> tests/ui/nonminimal_bool.rs:189:8 - | -LL | if true == !b {} - | ^^^^^^^^^^ help: try simplifying it as shown: `true != b` - error: equality checks against true are unnecessary - --> tests/ui/nonminimal_bool.rs:189:8 + --> tests/ui/nonminimal_bool.rs:188:8 | LL | if true == !b {} - | ^^^^^^^^^^ help: try simplifying it as shown: `!b` + | ^^^^^^^^^^ help: try: `!b` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool.rs:193:8 + --> tests/ui/nonminimal_bool.rs:191:8 | LL | if true != !b {} | ^^^^^^^^^^ help: try: `true == b` error: inequality checks against true can be replaced by a negation - --> tests/ui/nonminimal_bool.rs:193:8 + --> tests/ui/nonminimal_bool.rs:191:8 | LL | if true != !b {} - | ^^^^^^^^^^ help: try simplifying it as shown: `b` + | ^^^^^^^^^^ help: try: `b` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool.rs:196:8 + --> tests/ui/nonminimal_bool.rs:194:8 | LL | if !b == !c {} | ^^^^^^^^ help: try: `b == c` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool.rs:198:8 + --> tests/ui/nonminimal_bool.rs:196:8 | LL | if !b != !c {} | ^^^^^^^^ help: try: `b != c` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool.rs:214:8 + --> tests/ui/nonminimal_bool.rs:212:8 | LL | if !(a < 2.0 && !b) { | ^^^^^^^^^^^^^^^^ help: try: `a >= 2.0 || b` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool.rs:233:12 + --> tests/ui/nonminimal_bool.rs:231:12 | LL | if !(matches!(ty, TyKind::Ref(_, _, _)) && !is_mutable(&expr)) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `!matches!(ty, TyKind::Ref(_, _, _)) || is_mutable(&expr)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool.rs:253:8 + --> tests/ui/nonminimal_bool.rs:251:8 | LL | if !S != true {} | ^^^^^^^^^^ help: try: `S == true` error: inequality checks against true can be replaced by a negation - --> tests/ui/nonminimal_bool.rs:253:8 + --> tests/ui/nonminimal_bool.rs:251:8 | LL | if !S != true {} - | ^^^^^^^^^^ help: try simplifying it as shown: `!!S` + | ^^^^^^^^^^ help: try: `!!S` -error: aborting due to 33 previous errors +error: aborting due to 31 previous errors From f0563ade3bdbfa86b7d9f48ad371226b97c22189 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 15 Aug 2025 19:34:12 +0200 Subject: [PATCH 066/116] extract `bool_comparison` to a separate file --- clippy_lints/src/bool_comparison.rs | 179 ++++++++++++++++++++++++++++ clippy_lints/src/declared_lints.rs | 2 +- clippy_lints/src/lib.rs | 3 +- clippy_lints/src/needless_bool.rs | 168 +------------------------- 4 files changed, 185 insertions(+), 167 deletions(-) create mode 100644 clippy_lints/src/bool_comparison.rs diff --git a/clippy_lints/src/bool_comparison.rs b/clippy_lints/src/bool_comparison.rs new file mode 100644 index 000000000000..722095909a6f --- /dev/null +++ b/clippy_lints/src/bool_comparison.rs @@ -0,0 +1,179 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use clippy_utils::{is_expn_of, peel_blocks, sym}; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; +use rustc_span::Span; +use rustc_span::source_map::Spanned; + +declare_clippy_lint! { + /// ### What it does + /// Checks for expressions of the form `x == true`, + /// `x != true` and order comparisons such as `x < true` (or vice versa) and + /// suggest using the variable directly. + /// + /// ### Why is this bad? + /// Unnecessary code. + /// + /// ### Example + /// ```rust,ignore + /// if x == true {} + /// if y == false {} + /// ``` + /// use `x` directly: + /// ```rust,ignore + /// if x {} + /// if !y {} + /// ``` + #[clippy::version = "pre 1.29.0"] + pub BOOL_COMPARISON, + complexity, + "comparing a variable to a boolean, e.g., `if x == true` or `if x != true`" +} + +declare_lint_pass!(BoolComparison => [BOOL_COMPARISON]); + +impl<'tcx> LateLintPass<'tcx> for BoolComparison { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if e.span.from_expansion() { + return; + } + + if let ExprKind::Binary(Spanned { node, .. }, left_side, right_side) = e.kind + && is_expn_of(left_side.span, sym::cfg).is_none() + && is_expn_of(right_side.span, sym::cfg).is_none() + && cx.typeck_results().expr_ty(left_side).is_bool() + && cx.typeck_results().expr_ty(right_side).is_bool() + { + let ignore_case = None::<(fn(_) -> _, &str)>; + let ignore_no_literal = None::<(fn(_, _) -> _, &str)>; + match node { + BinOpKind::Eq => { + let true_case = Some((|h| h, "equality checks against true are unnecessary")); + let false_case = Some(( + |h: Sugg<'tcx>| !h, + "equality checks against false can be replaced by a negation", + )); + check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal); + }, + BinOpKind::Ne => { + let true_case = Some(( + |h: Sugg<'tcx>| !h, + "inequality checks against true can be replaced by a negation", + )); + let false_case = Some((|h| h, "inequality checks against false are unnecessary")); + check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal); + }, + BinOpKind::Lt => check_comparison( + cx, + e, + ignore_case, + Some((|h| h, "greater than checks against false are unnecessary")), + Some(( + |h: Sugg<'tcx>| !h, + "less than comparison against true can be replaced by a negation", + )), + ignore_case, + Some(( + |l: Sugg<'tcx>, r: Sugg<'tcx>| (!l).bit_and(&r), + "order comparisons between booleans can be simplified", + )), + ), + BinOpKind::Gt => check_comparison( + cx, + e, + Some(( + |h: Sugg<'tcx>| !h, + "less than comparison against true can be replaced by a negation", + )), + ignore_case, + ignore_case, + Some((|h| h, "greater than checks against false are unnecessary")), + Some(( + |l: Sugg<'tcx>, r: Sugg<'tcx>| l.bit_and(&(!r)), + "order comparisons between booleans can be simplified", + )), + ), + _ => (), + } + } + } +} + +fn check_comparison<'a, 'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + left_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, + left_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, + right_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, + right_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, + no_literal: Option<(impl FnOnce(Sugg<'a>, Sugg<'a>) -> Sugg<'a>, &'static str)>, +) { + if let ExprKind::Binary(_, left_side, right_side) = e.kind { + let mut applicability = Applicability::MachineApplicable; + // Eliminate parentheses in `e` by using the lo pos of lhs and hi pos of rhs, + // calling `source_callsite` make sure macros are handled correctly, see issue #9907 + let binop_span = left_side.span.source_callsite().to(right_side.span.source_callsite()); + + match (fetch_bool_expr(left_side), fetch_bool_expr(right_side)) { + (Some(true), None) => left_true.map_or((), |(h, m)| { + suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h); + }), + (None, Some(true)) => right_true.map_or((), |(h, m)| { + suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); + }), + (Some(false), None) => left_false.map_or((), |(h, m)| { + suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h); + }), + (None, Some(false)) => right_false.map_or((), |(h, m)| { + suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); + }), + (None, None) => no_literal.map_or((), |(h, m)| { + let left_side = Sugg::hir_with_context(cx, left_side, binop_span.ctxt(), "..", &mut applicability); + let right_side = Sugg::hir_with_context(cx, right_side, binop_span.ctxt(), "..", &mut applicability); + span_lint_and_sugg( + cx, + BOOL_COMPARISON, + binop_span, + m, + "try", + h(left_side, right_side).into_string(), + applicability, + ); + }), + _ => (), + } + } +} + +fn suggest_bool_comparison<'a, 'tcx>( + cx: &LateContext<'tcx>, + span: Span, + expr: &Expr<'_>, + mut app: Applicability, + message: &'static str, + conv_hint: impl FnOnce(Sugg<'a>) -> Sugg<'a>, +) { + let hint = Sugg::hir_with_context(cx, expr, span.ctxt(), "..", &mut app); + span_lint_and_sugg( + cx, + BOOL_COMPARISON, + span, + message, + "try", + conv_hint(hint).into_string(), + app, + ); +} + +fn fetch_bool_expr(expr: &Expr<'_>) -> Option { + if let ExprKind::Lit(lit_ptr) = peel_blocks(expr).kind + && let LitKind::Bool(value) = lit_ptr.node + { + return Some(value); + } + None +} diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index e67e8d9070f2..d0c7443a4a4b 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -35,6 +35,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::await_holding_invalid::AWAIT_HOLDING_REFCELL_REF_INFO, crate::blocks_in_conditions::BLOCKS_IN_CONDITIONS_INFO, crate::bool_assert_comparison::BOOL_ASSERT_COMPARISON_INFO, + crate::bool_comparison::BOOL_COMPARISON_INFO, crate::bool_to_int_with_if::BOOL_TO_INT_WITH_IF_INFO, crate::booleans::NONMINIMAL_BOOL_INFO, crate::booleans::OVERLY_COMPLEX_BOOL_EXPR_INFO, @@ -538,7 +539,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::mutex_atomic::MUTEX_ATOMIC_INFO, crate::mutex_atomic::MUTEX_INTEGER_INFO, crate::needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE_INFO, - crate::needless_bool::BOOL_COMPARISON_INFO, crate::needless_bool::NEEDLESS_BOOL_INFO, crate::needless_bool::NEEDLESS_BOOL_ASSIGN_INFO, crate::needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index d468993e7444..5960f8e54cd4 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -84,6 +84,7 @@ mod attrs; mod await_holding_invalid; mod blocks_in_conditions; mod bool_assert_comparison; +mod bool_comparison; mod bool_to_int_with_if; mod booleans; mod borrow_deref_ref; @@ -477,7 +478,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(float_literal::FloatLiteral)); store.register_late_pass(|_| Box::new(ptr::Ptr)); store.register_late_pass(|_| Box::new(needless_bool::NeedlessBool)); - store.register_late_pass(|_| Box::new(needless_bool::BoolComparison)); + store.register_late_pass(|_| Box::new(bool_comparison::BoolComparison)); store.register_late_pass(|_| Box::new(needless_for_each::NeedlessForEach)); store.register_late_pass(|_| Box::new(misc::LintPass)); store.register_late_pass(|_| Box::new(eta_reduction::EtaReduction)); diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index 49d41a3af3dd..854e927aa2f7 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -2,16 +2,14 @@ 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::{ - 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_contains_comment, sym, + SpanlessEq, get_parent_expr, higher, is_block_like, is_else_clause, is_parent_stmt, is_receiver_of_method_call, + peel_blocks, peel_blocks_with_stmt, span_contains_comment, }; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; -use rustc_span::Span; -use rustc_span::source_map::Spanned; declare_clippy_lint! { /// ### What it does @@ -50,31 +48,6 @@ declare_clippy_lint! { "if-statements with plain booleans in the then- and else-clause, e.g., `if p { true } else { false }`" } -declare_clippy_lint! { - /// ### What it does - /// Checks for expressions of the form `x == true`, - /// `x != true` and order comparisons such as `x < true` (or vice versa) and - /// suggest using the variable directly. - /// - /// ### Why is this bad? - /// Unnecessary code. - /// - /// ### Example - /// ```rust,ignore - /// if x == true {} - /// if y == false {} - /// ``` - /// use `x` directly: - /// ```rust,ignore - /// if x {} - /// if !y {} - /// ``` - #[clippy::version = "pre 1.29.0"] - pub BOOL_COMPARISON, - complexity, - "comparing a variable to a boolean, e.g., `if x == true` or `if x != true`" -} - declare_clippy_lint! { /// ### What it does /// Checks for expressions of the form `if c { x = true } else { x = false }` @@ -224,141 +197,6 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool { } } -declare_lint_pass!(BoolComparison => [BOOL_COMPARISON]); - -impl<'tcx> LateLintPass<'tcx> for BoolComparison { - fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - if e.span.from_expansion() { - return; - } - - if let ExprKind::Binary(Spanned { node, .. }, left_side, right_side) = e.kind - && is_expn_of(left_side.span, sym::cfg).is_none() - && is_expn_of(right_side.span, sym::cfg).is_none() - && cx.typeck_results().expr_ty(left_side).is_bool() - && cx.typeck_results().expr_ty(right_side).is_bool() - { - let ignore_case = None::<(fn(_) -> _, &str)>; - let ignore_no_literal = None::<(fn(_, _) -> _, &str)>; - match node { - BinOpKind::Eq => { - let true_case = Some((|h| h, "equality checks against true are unnecessary")); - let false_case = Some(( - |h: Sugg<'tcx>| !h, - "equality checks against false can be replaced by a negation", - )); - check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal); - }, - BinOpKind::Ne => { - let true_case = Some(( - |h: Sugg<'tcx>| !h, - "inequality checks against true can be replaced by a negation", - )); - let false_case = Some((|h| h, "inequality checks against false are unnecessary")); - check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal); - }, - BinOpKind::Lt => check_comparison( - cx, - e, - ignore_case, - Some((|h| h, "greater than checks against false are unnecessary")), - Some(( - |h: Sugg<'tcx>| !h, - "less than comparison against true can be replaced by a negation", - )), - ignore_case, - Some(( - |l: Sugg<'tcx>, r: Sugg<'tcx>| (!l).bit_and(&r), - "order comparisons between booleans can be simplified", - )), - ), - BinOpKind::Gt => check_comparison( - cx, - e, - Some(( - |h: Sugg<'tcx>| !h, - "less than comparison against true can be replaced by a negation", - )), - ignore_case, - ignore_case, - Some((|h| h, "greater than checks against false are unnecessary")), - Some(( - |l: Sugg<'tcx>, r: Sugg<'tcx>| l.bit_and(&(!r)), - "order comparisons between booleans can be simplified", - )), - ), - _ => (), - } - } - } -} - -fn check_comparison<'a, 'tcx>( - cx: &LateContext<'tcx>, - e: &'tcx Expr<'_>, - left_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, - left_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, - right_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, - right_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, - no_literal: Option<(impl FnOnce(Sugg<'a>, Sugg<'a>) -> Sugg<'a>, &'static str)>, -) { - if let ExprKind::Binary(_, left_side, right_side) = e.kind { - let mut applicability = Applicability::MachineApplicable; - // Eliminate parentheses in `e` by using the lo pos of lhs and hi pos of rhs, - // calling `source_callsite` make sure macros are handled correctly, see issue #9907 - let binop_span = left_side.span.source_callsite().to(right_side.span.source_callsite()); - - match (fetch_bool_expr(left_side), fetch_bool_expr(right_side)) { - (Some(true), None) => left_true.map_or((), |(h, m)| { - suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h); - }), - (None, Some(true)) => right_true.map_or((), |(h, m)| { - suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); - }), - (Some(false), None) => left_false.map_or((), |(h, m)| { - suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h); - }), - (None, Some(false)) => right_false.map_or((), |(h, m)| { - suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); - }), - (None, None) => no_literal.map_or((), |(h, m)| { - let left_side = Sugg::hir_with_context(cx, left_side, binop_span.ctxt(), "..", &mut applicability); - let right_side = Sugg::hir_with_context(cx, right_side, binop_span.ctxt(), "..", &mut applicability); - span_lint_and_sugg( - cx, - BOOL_COMPARISON, - binop_span, - m, - "try", - h(left_side, right_side).into_string(), - applicability, - ); - }), - _ => (), - } - } -} - -fn suggest_bool_comparison<'a, 'tcx>( - cx: &LateContext<'tcx>, - span: Span, - expr: &Expr<'_>, - mut app: Applicability, - message: &'static str, - conv_hint: impl FnOnce(Sugg<'a>) -> Sugg<'a>, -) { - let hint = Sugg::hir_with_context(cx, expr, span.ctxt(), "..", &mut app); - span_lint_and_sugg( - cx, - BOOL_COMPARISON, - span, - message, - "try", - conv_hint(hint).into_string(), - app, - ); -} - enum Expression { Bool(bool), RetBool(bool), From eb009082321f15f2a6faed1198d4e31d2d163ff9 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Sat, 23 Aug 2025 02:30:44 +0800 Subject: [PATCH 067/116] fix: `derivable_impls` FN when enum is qualified with `Self` --- clippy_lints/src/derivable_impls.rs | 64 +++++++++++++------- tests/ui/derivable_impls.fixed | 12 ++++ tests/ui/derivable_impls.rs | 15 +++++ tests/ui/derivable_impls.stderr | 25 +++++++- tests/ui/derivable_impls_derive_const.fixed | 2 +- tests/ui/derivable_impls_derive_const.stderr | 2 +- 6 files changed, 95 insertions(+), 25 deletions(-) diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index c58aca6a52ba..06c2393e0a39 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -10,7 +10,7 @@ use rustc_hir::{ }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, PointerCoercion}; -use rustc_middle::ty::{self, AdtDef, GenericArgsRef, Ty, TypeckResults}; +use rustc_middle::ty::{self, AdtDef, GenericArgsRef, Ty, TypeckResults, VariantDef}; use rustc_session::impl_lint_pass; use rustc_span::sym; @@ -86,13 +86,9 @@ fn contains_trait_object(ty: Ty<'_>) -> bool { } fn determine_derive_macro(cx: &LateContext<'_>, is_const: bool) -> Option<&'static str> { - if is_const { - if !cx.tcx.features().enabled(sym::derive_const) { - return None; - } - return Some("derive_const"); - } - Some("derive") + (!is_const) + .then_some("derive") + .or_else(|| cx.tcx.features().enabled(sym::derive_const).then_some("derive_const")) } #[expect(clippy::too_many_arguments)] @@ -137,18 +133,18 @@ fn check_struct<'tcx>( ExprKind::Tup(fields) => fields.iter().all(is_default_without_adjusts), ExprKind::Call(callee, args) if is_path_self(callee) => args.iter().all(is_default_without_adjusts), ExprKind::Struct(_, fields, _) => fields.iter().all(|ef| is_default_without_adjusts(ef.expr)), - _ => false, + _ => return, }; - let Some(derive_snippet) = determine_derive_macro(cx, is_const) else { - return; - }; - - if should_emit { + if should_emit && let Some(derive_snippet) = determine_derive_macro(cx, is_const) { let struct_span = cx.tcx.def_span(adt_def.did()); + let indent_enum = indent_of(cx, struct_span).unwrap_or(0); let suggestions = vec![ (item.span, String::new()), // Remove the manual implementation - (struct_span.shrink_to_lo(), format!("#[{derive_snippet}(Default)]\n")), // Add the derive attribute + ( + struct_span.shrink_to_lo(), + format!("#[{derive_snippet}(Default)]\n{}", " ".repeat(indent_enum)), + ), // Add the derive attribute ]; span_lint_and_then(cx, DERIVABLE_IMPLS, item.span, "this `impl` can be derived", |diag| { @@ -161,17 +157,41 @@ fn check_struct<'tcx>( } } +fn extract_enum_variant<'tcx>( + cx: &LateContext<'tcx>, + func_expr: &'tcx Expr<'tcx>, + adt_def: AdtDef<'tcx>, +) -> Option<&'tcx VariantDef> { + match &peel_blocks(func_expr).kind { + ExprKind::Path(QPath::Resolved(None, p)) + if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res + && let variant_id = cx.tcx.parent(id) + && let Some(variant_def) = adt_def.variants().iter().find(|v| v.def_id == variant_id) => + { + Some(variant_def) + }, + ExprKind::Path(QPath::TypeRelative(ty, segment)) + if let TyKind::Path(QPath::Resolved(None, p)) = &ty.kind + && let Res::SelfTyAlias { + is_trait_impl: true, .. + } = p.res + && let variant_ident = segment.ident + && let Some(variant_def) = adt_def.variants().iter().find(|v| v.ident(cx.tcx) == variant_ident) => + { + Some(variant_def) + }, + _ => None, + } +} + fn check_enum<'tcx>( cx: &LateContext<'tcx>, - item: &'tcx Item<'_>, - func_expr: &Expr<'_>, - adt_def: AdtDef<'_>, + item: &'tcx Item<'tcx>, + func_expr: &'tcx Expr<'tcx>, + adt_def: AdtDef<'tcx>, is_const: bool, ) { - if let ExprKind::Path(QPath::Resolved(None, p)) = &peel_blocks(func_expr).kind - && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res - && let variant_id = cx.tcx.parent(id) - && let Some(variant_def) = adt_def.variants().iter().find(|v| v.def_id == variant_id) + if let Some(variant_def) = extract_enum_variant(cx, func_expr, adt_def) && variant_def.fields.is_empty() && !variant_def.is_field_list_non_exhaustive() { diff --git a/tests/ui/derivable_impls.fixed b/tests/ui/derivable_impls.fixed index f549aee9eb1e..9f9e4e253c3c 100644 --- a/tests/ui/derivable_impls.fixed +++ b/tests/ui/derivable_impls.fixed @@ -352,4 +352,16 @@ mod issue15493 { } } +mod issue15536 { + #[derive(Copy, Clone)] + #[derive(Default)] + enum Bar { + #[default] + A, + B, + } + + +} + fn main() {} diff --git a/tests/ui/derivable_impls.rs b/tests/ui/derivable_impls.rs index 1e06ff6120bd..74a793b9a70e 100644 --- a/tests/ui/derivable_impls.rs +++ b/tests/ui/derivable_impls.rs @@ -422,4 +422,19 @@ mod issue15493 { } } +mod issue15536 { + #[derive(Copy, Clone)] + enum Bar { + A, + B, + } + + impl Default for Bar { + //~^ derivable_impls + fn default() -> Self { + Self::A + } + } +} + fn main() {} diff --git a/tests/ui/derivable_impls.stderr b/tests/ui/derivable_impls.stderr index d473f2a379c5..cd46414cb4a8 100644 --- a/tests/ui/derivable_impls.stderr +++ b/tests/ui/derivable_impls.stderr @@ -187,5 +187,28 @@ LL ~ #[default] LL ~ Bar, | -error: aborting due to 11 previous errors +error: this `impl` can be derived + --> tests/ui/derivable_impls.rs:432:5 + | +LL | / impl Default for Bar { +LL | | +LL | | fn default() -> Self { +LL | | Self::A +LL | | } +LL | | } + | |_____^ + | +help: replace the manual implementation with a derive attribute and mark the default variant + | +LL ~ #[derive(Default)] +LL ~ enum Bar { +LL ~ #[default] +LL ~ A, +LL | B, +LL | } +LL | +LL ~ + | + +error: aborting due to 12 previous errors diff --git a/tests/ui/derivable_impls_derive_const.fixed b/tests/ui/derivable_impls_derive_const.fixed index 6df43f7fb763..f0d8d2d24092 100644 --- a/tests/ui/derivable_impls_derive_const.fixed +++ b/tests/ui/derivable_impls_derive_const.fixed @@ -7,7 +7,7 @@ mod issue15493 { #[derive(Copy, Clone)] #[repr(transparent)] #[derive_const(Default)] -struct Foo(u64); + struct Foo(u64); diff --git a/tests/ui/derivable_impls_derive_const.stderr b/tests/ui/derivable_impls_derive_const.stderr index dd185676c770..196bac185dda 100644 --- a/tests/ui/derivable_impls_derive_const.stderr +++ b/tests/ui/derivable_impls_derive_const.stderr @@ -14,7 +14,7 @@ LL | | } help: replace the manual implementation with a derive attribute | LL ~ #[derive_const(Default)] -LL ~ struct Foo(u64); +LL ~ struct Foo(u64); LL | LL ~ | From e01cf9bd4845cb1b1e3b89e93d6731540bc489b7 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Sat, 23 Aug 2025 06:13:35 +0800 Subject: [PATCH 068/116] Apply `derivable_impls` to Clippy itself --- .../src/matches/significant_drop_in_scrutinee.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 027dd7ce0534..81fecc87256c 100644 --- a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -226,11 +226,12 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> { } } -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)] enum SigDropHolder { /// No values with significant drop present in this expression. /// /// Expressions that we've emitted lints do not count. + #[default] None, /// Some field in this expression references to values with significant drop. /// @@ -244,12 +245,6 @@ enum SigDropHolder { Moved, } -impl Default for SigDropHolder { - fn default() -> Self { - Self::None - } -} - struct SigDropHelper<'a, 'tcx> { cx: &'a LateContext<'tcx>, parent_expr: Option<&'tcx Expr<'tcx>>, From f794588895972477a2639e7949d5be7d253e30da Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 23 Aug 2025 00:43:00 +0200 Subject: [PATCH 069/116] Activate `range-diff` feature of triagebot This [feature](https://forge.rust-lang.org/triagebot/range-diff.html) shows the changes when a PR is rebased, while the GitHub UI would mix the actual changes with the ones coming from the rebase. --- triagebot.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/triagebot.toml b/triagebot.toml index 805baf2af6dd..79b6c2b792ff 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -49,6 +49,9 @@ new_pr = true # These labels are set when there are unresolved concerns, removed otherwise labels = ["S-waiting-on-concerns"] +# Show differences when a PR is rebased +[range-diff] + [assign] contributing_url = "https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md" users_on_vacation = [ From 0c24079986c89c4613f43e31b7098c4ba1096ce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 23 Aug 2025 10:14:52 +0200 Subject: [PATCH 070/116] Remove profile section from Clippy To avoid workspace warnings. --- Cargo.toml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2add525b7e8a..b3618932ded7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,13 +65,6 @@ harness = false name = "dogfood" harness = false -# quine-mc_cluskey makes up a significant part of the runtime in dogfood -# due to the number of conditions in the clippy_lints crate -# and enabling optimizations for that specific dependency helps a bit -# without increasing total build times. -[profile.dev.package.quine-mc_cluskey] -opt-level = 3 - [lints.rust.unexpected_cfgs] level = "warn" check-cfg = ['cfg(bootstrap)'] From 2c28135116a99f1939ffc7f1bcb3b8580f4153c6 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 23 Aug 2025 10:31:38 +0200 Subject: [PATCH 071/116] Use `.cargo/config.toml` for local compilation options This will be ignored when Clippy is part of the compiler workspace. Using `Cargo.toml` would complain about profiles defined outside the top-level when run from the compiler repository. --- .cargo/config.toml | 7 +++++++ Cargo.toml | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index d9c635df5dc7..a09bf95e87bd 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -23,3 +23,10 @@ split-debuginfo = "unpacked" rustflags = ["--remap-path-prefix", "=clippy_dev"] [profile.dev.package.lintcheck] rustflags = ["--remap-path-prefix", "=lintcheck"] + +# quine-mc_cluskey makes up a significant part of the runtime in dogfood +# due to the number of conditions in the clippy_lints crate +# and enabling optimizations for that specific dependency helps a bit +# without increasing total build times. +[profile.dev.package.quine-mc_cluskey] +opt-level = 3 diff --git a/Cargo.toml b/Cargo.toml index 2add525b7e8a..b3618932ded7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,13 +65,6 @@ harness = false name = "dogfood" harness = false -# quine-mc_cluskey makes up a significant part of the runtime in dogfood -# due to the number of conditions in the clippy_lints crate -# and enabling optimizations for that specific dependency helps a bit -# without increasing total build times. -[profile.dev.package.quine-mc_cluskey] -opt-level = 3 - [lints.rust.unexpected_cfgs] level = "warn" check-cfg = ['cfg(bootstrap)'] From 8a988658a7caba4d7caf6926ba3b78e5f52d54f6 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 23 Aug 2025 11:46:49 +0200 Subject: [PATCH 072/116] Detect infinite loop in `async fn` not returning `!` This fixes an overzealous change made to avoid signaling infinite loops in anonymous blocks that may never be used. --- clippy_lints/src/loops/infinite_loop.rs | 9 +++++++-- tests/ui/infinite_loops.rs | 15 +++++++++++++++ tests/ui/infinite_loops.stderr | 12 +++++++++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/loops/infinite_loop.rs b/clippy_lints/src/loops/infinite_loop.rs index a71e6963f8ca..74c0b1780189 100644 --- a/clippy_lints/src/loops/infinite_loop.rs +++ b/clippy_lints/src/loops/infinite_loop.rs @@ -4,7 +4,8 @@ use hir::intravisit::{Visitor, walk_expr}; use rustc_ast::Label; use rustc_errors::Applicability; use rustc_hir::{ - self as hir, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Expr, ExprKind, FnRetTy, FnSig, Node, TyKind, + self as hir, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, FnRetTy, + FnSig, Node, TyKind, }; use rustc_lint::{LateContext, LintContext}; use rustc_span::sym; @@ -73,7 +74,11 @@ fn is_inside_unawaited_async_block(cx: &LateContext<'_>, expr: &Expr<'_>) -> boo if let Node::Expr(Expr { kind: ExprKind::Closure(Closure { - kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)), + kind: + ClosureKind::Coroutine(CoroutineKind::Desugared( + CoroutineDesugaring::Async, + CoroutineSource::Block | CoroutineSource::Closure, + )), .. }), .. diff --git a/tests/ui/infinite_loops.rs b/tests/ui/infinite_loops.rs index 9b8c39331970..7d01a7fb61fc 100644 --- a/tests/ui/infinite_loops.rs +++ b/tests/ui/infinite_loops.rs @@ -521,4 +521,19 @@ mod tokio_spawn_test { } } +mod issue15541 { + async fn good() -> ! { + loop { + std::future::pending().await + } + } + + async fn bad() { + //~v infinite_loop + loop { + std::future::pending().await + } + } +} + fn main() {} diff --git a/tests/ui/infinite_loops.stderr b/tests/ui/infinite_loops.stderr index 4c6b6f725f13..319f1e5012b6 100644 --- a/tests/ui/infinite_loops.stderr +++ b/tests/ui/infinite_loops.stderr @@ -333,5 +333,15 @@ LL | | } | = help: if this is not intended, try adding a `break` or `return` condition in the loop -error: aborting due to 23 previous errors +error: infinite loop detected + --> tests/ui/infinite_loops.rs:533:9 + | +LL | / loop { +LL | | std::future::pending().await +LL | | } + | |_________^ + | + = help: if this is not intended, try adding a `break` or `return` condition in the loop + +error: aborting due to 24 previous errors From d32e5633732cfa124240c3ea8abd45d77f46c469 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 23 Aug 2025 10:46:59 +0200 Subject: [PATCH 073/116] Check that no profile is present in `Cargo.toml` files --- tests/no-profile-in-cargo-toml.rs | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/no-profile-in-cargo-toml.rs diff --git a/tests/no-profile-in-cargo-toml.rs b/tests/no-profile-in-cargo-toml.rs new file mode 100644 index 000000000000..2ad9bfb75dee --- /dev/null +++ b/tests/no-profile-in-cargo-toml.rs @@ -0,0 +1,34 @@ +// Check that we do not have `profile.*` sections in our `Cargo.toml` files, +// as this causes warnings when run from the compiler repository which includes +// Clippy in a workspace. +// +// Those sections can be put into `.cargo/config.toml` which will be read +// when commands are issued from the top-level Clippy directory, outside of +// a workspace. + +use std::fs::File; +use std::io::{self, BufRead as _}; +use walkdir::WalkDir; + +#[test] +fn no_profile_in_cargo_toml() { + // This check could parse `Cargo.toml` using a TOML deserializer, but in practice + // profile sections would be added at the beginning of a line as `[profile.*]`, so + // keep it fast and simple. + for entry in WalkDir::new(".") + .into_iter() + .filter_map(Result::ok) + .filter(|e| e.file_name().to_str() == Some("Cargo.toml")) + { + for line in io::BufReader::new(File::open(entry.path()).unwrap()) + .lines() + .map(Result::unwrap) + { + if line.starts_with("[profile.") { + eprintln!("Profile section `{line}` found in file `{}`.", entry.path().display()); + eprintln!("Use `.cargo/config.toml` for profiles specific to the standalone Clippy repository."); + panic!("Profile section found in `Cargo.toml`"); + } + } + } +} From ac3492af823a0397b832c9868216a25f81700d00 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 12 Aug 2025 01:01:18 +0200 Subject: [PATCH 074/116] simplify `too_many_args` import `FnKind` completely save `header` as an intermediate variable match on `header.abi` directly use `FnKind::header` nest `if`s use `cx.tcx.def_span`, as recommended in https://github.com/rust-lang/rust-clippy/pull/15461#discussion_r2269961969 --- clippy_lints/src/functions/mod.rs | 2 +- .../src/functions/too_many_arguments.rs | 37 +++++-------------- 2 files changed, 10 insertions(+), 29 deletions(-) diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index ca5ea9018149..5a40af421942 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -535,7 +535,7 @@ impl<'tcx> LateLintPass<'tcx> for Functions { def_id: LocalDefId, ) { let hir_id = cx.tcx.local_def_id_to_hir_id(def_id); - too_many_arguments::check_fn(cx, kind, decl, span, hir_id, self.too_many_arguments_threshold); + too_many_arguments::check_fn(cx, kind, decl, hir_id, def_id, self.too_many_arguments_threshold); too_many_lines::check_fn(cx, kind, body, span, def_id, self.too_many_lines_threshold); not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, def_id); misnamed_getters::check_fn(cx, kind, decl, body, span); diff --git a/clippy_lints/src/functions/too_many_arguments.rs b/clippy_lints/src/functions/too_many_arguments.rs index 48d050aa36aa..6c3c3d354ecc 100644 --- a/clippy_lints/src/functions/too_many_arguments.rs +++ b/clippy_lints/src/functions/too_many_arguments.rs @@ -1,5 +1,7 @@ use rustc_abi::ExternAbi; -use rustc_hir::{self as hir, intravisit}; +use rustc_hir as hir; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit::FnKind; use rustc_lint::LateContext; use rustc_span::Span; @@ -10,39 +12,18 @@ use super::TOO_MANY_ARGUMENTS; pub(super) fn check_fn( cx: &LateContext<'_>, - kind: intravisit::FnKind<'_>, + kind: FnKind<'_>, decl: &hir::FnDecl<'_>, - span: Span, hir_id: hir::HirId, + def_id: LocalDefId, too_many_arguments_threshold: u64, ) { // don't warn for implementations, it's not their fault - if !is_trait_impl_item(cx, hir_id) { + if !is_trait_impl_item(cx, hir_id) // don't lint extern functions decls, it's not their fault either - match kind { - intravisit::FnKind::Method( - _, - &hir::FnSig { - header: hir::FnHeader { - abi: ExternAbi::Rust, .. - }, - .. - }, - ) - | intravisit::FnKind::ItemFn( - _, - _, - hir::FnHeader { - abi: ExternAbi::Rust, .. - }, - ) => check_arg_number( - cx, - decl, - span.with_hi(decl.output.span().hi()), - too_many_arguments_threshold, - ), - _ => {}, - } + && kind.header().is_some_and(|header| header.abi == ExternAbi::Rust) + { + check_arg_number(cx, decl, cx.tcx.def_span(def_id), too_many_arguments_threshold); } } From cf3f61ce161e057199c935caeb4ca88063e6bb94 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 9 Aug 2025 14:03:29 +0200 Subject: [PATCH 075/116] `entry`: link to `Entry` API if can't make a suggestion --- clippy_lints/src/entry.rs | 14 ++++++++++++-- tests/ui/entry_unfixable.stderr | 5 +++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index 182cb4e46d2b..fd3475243e39 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; +use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; use clippy_utils::source::{reindent_multiline, snippet_indent, snippet_with_applicability, snippet_with_context}; use clippy_utils::ty::is_copy; use clippy_utils::visitors::for_each_expr; @@ -194,7 +194,17 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass { if let Some(sugg) = sugg { span_lint_and_sugg(cx, MAP_ENTRY, expr.span, lint_msg, "try", sugg, app); } else { - span_lint(cx, MAP_ENTRY, expr.span, lint_msg); + span_lint_and_help( + cx, + MAP_ENTRY, + expr.span, + lint_msg, + None, + format!( + "consider using the `Entry` API: https://doc.rust-lang.org/std/collections/struct.{}.html#entry-api", + map_ty.name() + ), + ); } } } diff --git a/tests/ui/entry_unfixable.stderr b/tests/ui/entry_unfixable.stderr index 0197d2ab4cf9..f92f472512f6 100644 --- a/tests/ui/entry_unfixable.stderr +++ b/tests/ui/entry_unfixable.stderr @@ -10,6 +10,7 @@ LL | | false LL | | } | |_____________^ | + = help: consider using the `Entry` API: https://doc.rust-lang.org/std/collections/struct.HashMap.html#entry-api = note: `-D clippy::map-entry` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::map_entry)]` @@ -24,6 +25,8 @@ LL | | } else { LL | | hm.insert(key, true); LL | | } | |_____^ + | + = help: consider using the `Entry` API: https://doc.rust-lang.org/std/collections/struct.HashMap.html#entry-api error: usage of `contains_key` followed by `insert` on a `HashMap` --> tests/ui/entry_unfixable.rs:80:13 @@ -36,6 +39,8 @@ LL | | let interner = INTERNER.lock().unwrap(); LL | | return Err(interner.resolve(name).unwrap().to_owned()); LL | | } | |_____________^ + | + = help: consider using the `Entry` API: https://doc.rust-lang.org/std/collections/struct.HashMap.html#entry-api error: aborting due to 3 previous errors From 4d8b1aec08fad8a76dd8b6a38e384442f0446405 Mon Sep 17 00:00:00 2001 From: Urgau Date: Sun, 27 Jul 2025 21:21:49 +0200 Subject: [PATCH 076/116] Adjust clippy lints for rustc `integer_to_ptr_transmutes` lint --- .../src/transmute/useless_transmute.rs | 12 +---- tests/ui/transmute.rs | 3 +- tests/ui/transmute.stderr | 46 +++++++------------ .../transmutes_expressible_as_ptr_casts.fixed | 3 -- .../ui/transmutes_expressible_as_ptr_casts.rs | 3 -- ...transmutes_expressible_as_ptr_casts.stderr | 32 ++++++------- 6 files changed, 32 insertions(+), 67 deletions(-) diff --git a/clippy_lints/src/transmute/useless_transmute.rs b/clippy_lints/src/transmute/useless_transmute.rs index ec5fb2793f97..b898920baefc 100644 --- a/clippy_lints/src/transmute/useless_transmute.rs +++ b/clippy_lints/src/transmute/useless_transmute.rs @@ -49,17 +49,7 @@ pub(super) fn check<'tcx>( true }, (ty::Int(_) | ty::Uint(_), ty::RawPtr(_, _)) => { - span_lint_and_then( - cx, - USELESS_TRANSMUTE, - e.span, - "transmute from an integer to a pointer", - |diag| { - if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) { - diag.span_suggestion(e.span, "try", arg.as_ty(to_ty.to_string()), Applicability::Unspecified); - } - }, - ); + // Handled by the upstream rustc `integer_to_ptr_transmutes` lint true }, _ => false, diff --git a/tests/ui/transmute.rs b/tests/ui/transmute.rs index e968e7a59244..e7099104f942 100644 --- a/tests/ui/transmute.rs +++ b/tests/ui/transmute.rs @@ -4,6 +4,7 @@ dead_code, clippy::borrow_as_ptr, unnecessary_transmutes, + integer_to_ptr_transmutes, clippy::needless_lifetimes, clippy::missing_transmute_annotations )] @@ -60,12 +61,10 @@ fn useless() { //~^ useless_transmute let _: *const usize = std::mem::transmute(5_isize); - //~^ useless_transmute let _ = std::ptr::dangling::(); let _: *const usize = std::mem::transmute(1 + 1usize); - //~^ useless_transmute let _ = (1 + 1_usize) as *const usize; } diff --git a/tests/ui/transmute.stderr b/tests/ui/transmute.stderr index 79528ec06f1b..9478db09481a 100644 --- a/tests/ui/transmute.stderr +++ b/tests/ui/transmute.stderr @@ -1,5 +1,5 @@ error: transmute from a reference to a pointer - --> tests/ui/transmute.rs:33:27 + --> tests/ui/transmute.rs:34:27 | LL | let _: *const T = core::mem::transmute(t); | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T` @@ -8,61 +8,49 @@ LL | let _: *const T = core::mem::transmute(t); = help: to override `-D warnings` add `#[allow(clippy::useless_transmute)]` error: transmute from a reference to a pointer - --> tests/ui/transmute.rs:36:25 + --> tests/ui/transmute.rs:37:25 | LL | let _: *mut T = core::mem::transmute(t); | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T as *mut T` error: transmute from a reference to a pointer - --> tests/ui/transmute.rs:39:27 + --> tests/ui/transmute.rs:40:27 | LL | let _: *const U = core::mem::transmute(t); | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t as *const T as *const U` error: transmute from a type (`std::vec::Vec`) to itself - --> tests/ui/transmute.rs:47:27 + --> tests/ui/transmute.rs:48:27 | LL | let _: Vec = core::mem::transmute(my_vec()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: transmute from a type (`std::vec::Vec`) to itself - --> tests/ui/transmute.rs:50:27 + --> tests/ui/transmute.rs:51:27 | LL | let _: Vec = core::mem::transmute(my_vec()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: transmute from a type (`std::vec::Vec`) to itself - --> tests/ui/transmute.rs:53:27 + --> tests/ui/transmute.rs:54:27 | LL | let _: Vec = std::mem::transmute(my_vec()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: transmute from a type (`std::vec::Vec`) to itself - --> tests/ui/transmute.rs:56:27 + --> tests/ui/transmute.rs:57:27 | LL | let _: Vec = std::mem::transmute(my_vec()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: transmute from a type (`std::vec::Vec`) to itself - --> tests/ui/transmute.rs:59:27 + --> tests/ui/transmute.rs:60:27 | LL | let _: Vec = my_transmute(my_vec()); | ^^^^^^^^^^^^^^^^^^^^^^ -error: transmute from an integer to a pointer - --> tests/ui/transmute.rs:62:31 - | -LL | let _: *const usize = std::mem::transmute(5_isize); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `5_isize as *const usize` - -error: transmute from an integer to a pointer - --> tests/ui/transmute.rs:67:31 - | -LL | let _: *const usize = std::mem::transmute(1 + 1usize); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(1 + 1usize) as *const usize` - error: transmute from a type (`*const Usize`) to the type that it points to (`Usize`) - --> tests/ui/transmute.rs:99:24 + --> tests/ui/transmute.rs:98:24 | LL | let _: Usize = core::mem::transmute(int_const_ptr); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -71,25 +59,25 @@ LL | let _: Usize = core::mem::transmute(int_const_ptr); = help: to override `-D warnings` add `#[allow(clippy::crosspointer_transmute)]` error: transmute from a type (`*mut Usize`) to the type that it points to (`Usize`) - --> tests/ui/transmute.rs:102:24 + --> tests/ui/transmute.rs:101:24 | LL | let _: Usize = core::mem::transmute(int_mut_ptr); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: transmute from a type (`Usize`) to a pointer to that type (`*const Usize`) - --> tests/ui/transmute.rs:105:31 + --> tests/ui/transmute.rs:104:31 | LL | let _: *const Usize = core::mem::transmute(my_int()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: transmute from a type (`Usize`) to a pointer to that type (`*mut Usize`) - --> tests/ui/transmute.rs:108:29 + --> tests/ui/transmute.rs:107:29 | LL | let _: *mut Usize = core::mem::transmute(my_int()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: transmute from a `u8` to a `bool` - --> tests/ui/transmute.rs:115:28 + --> tests/ui/transmute.rs:114:28 | LL | let _: bool = unsafe { std::mem::transmute(0_u8) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `0_u8 != 0` @@ -98,7 +86,7 @@ LL | let _: bool = unsafe { std::mem::transmute(0_u8) }; = help: to override `-D warnings` add `#[allow(clippy::transmute_int_to_bool)]` error: transmute from a `&[u8]` to a `&str` - --> tests/ui/transmute.rs:122:28 + --> tests/ui/transmute.rs:121:28 | LL | let _: &str = unsafe { std::mem::transmute(B) }; | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8(B).unwrap()` @@ -107,16 +95,16 @@ LL | let _: &str = unsafe { std::mem::transmute(B) }; = help: to override `-D warnings` add `#[allow(clippy::transmute_bytes_to_str)]` error: transmute from a `&mut [u8]` to a `&mut str` - --> tests/ui/transmute.rs:125:32 + --> tests/ui/transmute.rs:124:32 | LL | let _: &mut str = unsafe { std::mem::transmute(mb) }; | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8_mut(mb).unwrap()` error: transmute from a `&[u8]` to a `&str` - --> tests/ui/transmute.rs:128:30 + --> tests/ui/transmute.rs:127:30 | LL | const _: &str = unsafe { std::mem::transmute(B) }; | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::str::from_utf8_unchecked(B)` -error: aborting due to 18 previous errors +error: aborting due to 16 previous errors diff --git a/tests/ui/transmutes_expressible_as_ptr_casts.fixed b/tests/ui/transmutes_expressible_as_ptr_casts.fixed index e7ad2a1cbbcb..02f67f79e2b1 100644 --- a/tests/ui/transmutes_expressible_as_ptr_casts.fixed +++ b/tests/ui/transmutes_expressible_as_ptr_casts.fixed @@ -13,9 +13,6 @@ fn main() { // We should see an error message for each transmute, and no error messages for // the casts, since the casts are the recommended fixes. - // e is an integer and U is *U_0, while U_0: Sized; addr-ptr-cast - let _ptr_i32_transmute = unsafe { usize::MAX as *const i32 }; - //~^ useless_transmute let ptr_i32 = usize::MAX as *const i32; // e has type *T, U is *U_0, and either U_0: Sized ... diff --git a/tests/ui/transmutes_expressible_as_ptr_casts.rs b/tests/ui/transmutes_expressible_as_ptr_casts.rs index 42a81777a826..c5e156405ebc 100644 --- a/tests/ui/transmutes_expressible_as_ptr_casts.rs +++ b/tests/ui/transmutes_expressible_as_ptr_casts.rs @@ -13,9 +13,6 @@ fn main() { // We should see an error message for each transmute, and no error messages for // the casts, since the casts are the recommended fixes. - // e is an integer and U is *U_0, while U_0: Sized; addr-ptr-cast - let _ptr_i32_transmute = unsafe { transmute::(usize::MAX) }; - //~^ useless_transmute let ptr_i32 = usize::MAX as *const i32; // e has type *T, U is *U_0, and either U_0: Sized ... diff --git a/tests/ui/transmutes_expressible_as_ptr_casts.stderr b/tests/ui/transmutes_expressible_as_ptr_casts.stderr index 7746f087cc71..f39a64d57eb4 100644 --- a/tests/ui/transmutes_expressible_as_ptr_casts.stderr +++ b/tests/ui/transmutes_expressible_as_ptr_casts.stderr @@ -1,14 +1,5 @@ -error: transmute from an integer to a pointer - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:17:39 - | -LL | let _ptr_i32_transmute = unsafe { transmute::(usize::MAX) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `usize::MAX as *const i32` - | - = note: `-D clippy::useless-transmute` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::useless_transmute)]` - error: transmute from a pointer to a pointer - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:22:38 + --> tests/ui/transmutes_expressible_as_ptr_casts.rs:19:38 | LL | let _ptr_i8_transmute = unsafe { transmute::<*const i32, *const i8>(ptr_i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -22,7 +13,7 @@ LL + let _ptr_i8_transmute = unsafe { ptr_i32.cast::() }; | error: transmute from a pointer to a pointer - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:29:46 + --> tests/ui/transmutes_expressible_as_ptr_casts.rs:26:46 | LL | let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u32]>(slice_ptr) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -34,7 +25,7 @@ LL + let _ptr_to_unsized_transmute = unsafe { slice_ptr as *const [u32] }; | error: transmute from `*const i32` to `usize` which could be expressed as a pointer cast instead - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:36:50 + --> tests/ui/transmutes_expressible_as_ptr_casts.rs:33:50 | LL | let _usize_from_int_ptr_transmute = unsafe { transmute::<*const i32, usize>(ptr_i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr_i32 as usize` @@ -43,40 +34,43 @@ LL | let _usize_from_int_ptr_transmute = unsafe { transmute::<*const i32, us = help: to override `-D warnings` add `#[allow(clippy::transmutes_expressible_as_ptr_casts)]` error: transmute from a reference to a pointer - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:43:41 + --> tests/ui/transmutes_expressible_as_ptr_casts.rs:40:41 | LL | let _array_ptr_transmute = unsafe { transmute::<&[i32; 4], *const [i32; 4]>(array_ref) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `array_ref as *const [i32; 4]` + | + = note: `-D clippy::useless-transmute` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::useless_transmute)]` error: transmute from `fn(usize) -> u8` to `*const usize` which could be expressed as a pointer cast instead - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:52:41 + --> tests/ui/transmutes_expressible_as_ptr_casts.rs:49:41 | LL | let _usize_ptr_transmute = unsafe { transmute:: u8, *const usize>(foo) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `foo as *const usize` error: transmute from `fn(usize) -> u8` to `usize` which could be expressed as a pointer cast instead - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:57:49 + --> tests/ui/transmutes_expressible_as_ptr_casts.rs:54:49 | LL | let _usize_from_fn_ptr_transmute = unsafe { transmute:: u8, usize>(foo) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `foo as usize` error: transmute from `*const u32` to `usize` which could be expressed as a pointer cast instead - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:61:36 + --> tests/ui/transmutes_expressible_as_ptr_casts.rs:58:36 | LL | let _usize_from_ref = unsafe { transmute::<*const u32, usize>(&1u32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&1u32 as *const u32 as usize` error: transmute from a reference to a pointer - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:73:14 + --> tests/ui/transmutes_expressible_as_ptr_casts.rs:70:14 | LL | unsafe { transmute::<&[i32; 1], *const u8>(in_param) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `in_param as *const [i32; 1] as *const u8` error: transmute from `fn()` to `*const u8` which could be expressed as a pointer cast instead - --> tests/ui/transmutes_expressible_as_ptr_casts.rs:92:28 + --> tests/ui/transmutes_expressible_as_ptr_casts.rs:89:28 | LL | let _x: u8 = unsafe { *std::mem::transmute::(f) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(f as *const u8)` -error: aborting due to 10 previous errors +error: aborting due to 9 previous errors From fdde09b8c3afc7712a511096f37e54fb5efbd78e Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 23 Aug 2025 20:54:02 +0000 Subject: [PATCH 077/116] Use unnamed lifetime spans as primary spans for MISMATCHED_LIFETIME_SYNTAXES --- tests/ui/ptr_arg.stderr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ui/ptr_arg.stderr b/tests/ui/ptr_arg.stderr index 87235057349e..f32e83d8b818 100644 --- a/tests/ui/ptr_arg.stderr +++ b/tests/ui/ptr_arg.stderr @@ -268,10 +268,10 @@ LL | fn barbar(_x: &mut Vec, y: &mut String) { | ^^^^^^^^^^^ help: change this to: `&mut str` error: eliding a lifetime that's named elsewhere is confusing - --> tests/ui/ptr_arg.rs:314:36 + --> tests/ui/ptr_arg.rs:314:56 | LL | fn cow_good_ret_ty<'a>(input: &'a Cow<'a, str>) -> &str { - | ^^ ^^ ---- the same lifetime is elided here + | -- -- ^^^^ the same lifetime is elided here | | | | | the lifetime is named here | the lifetime is named here From 02111a4d30fafde6db2a26edc60217a76670a5dc Mon Sep 17 00:00:00 2001 From: Zihan Date: Thu, 7 Aug 2025 18:27:13 -0400 Subject: [PATCH 078/116] `cast_slice_from_raw_parts`: check for implicit cast to raw slice pointer changelog: [`cast_slice_from_raw_parts`]: check for implicit cast to raw slice pointer Signed-off-by: Zihan --- .../src/casts/cast_slice_from_raw_parts.rs | 39 ++++++++++++++ clippy_lints/src/casts/mod.rs | 3 ++ tests/ui/cast_raw_slice_pointer_cast.fixed | 25 ++++++++- tests/ui/cast_raw_slice_pointer_cast.rs | 25 ++++++++- tests/ui/cast_raw_slice_pointer_cast.stderr | 52 ++++++++++++++++--- 5 files changed, 134 insertions(+), 10 deletions(-) diff --git a/clippy_lints/src/casts/cast_slice_from_raw_parts.rs b/clippy_lints/src/casts/cast_slice_from_raw_parts.rs index 46b0c88d3fed..24bfb7355648 100644 --- a/clippy_lints/src/casts/cast_slice_from_raw_parts.rs +++ b/clippy_lints/src/casts/cast_slice_from_raw_parts.rs @@ -1,10 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::get_parent_expr; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_context; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; +use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; use rustc_middle::ty::{self, Ty}; use rustc_span::sym; @@ -53,3 +55,40 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, ); } } + +/// Checks for implicit cast from slice reference to raw slice pointer. +pub(super) fn check_implicit_cast(cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::Call(fun, [ptr_arg, len_arg]) = expr.peel_blocks().kind + && let ExprKind::Path(ref qpath) = fun.kind + && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id() + && let Some(rpk) = raw_parts_kind(cx, fun_def_id) + && !matches!(get_parent_expr(cx, expr).map(|e| e.kind), Some(ExprKind::Cast(..))) + && let [deref, borrow] = cx.typeck_results().expr_adjustments(expr) + && matches!(deref.kind, Adjust::Deref(..)) + && let Adjustment { + kind: Adjust::Borrow(AutoBorrow::RawPtr(..)), + target, + } = borrow + && let ty::RawPtr(pointee_ty, _) = target.kind() + && pointee_ty.is_slice() + && !expr.span.from_expansion() + { + let func = match rpk { + RawPartsKind::Immutable => "from_raw_parts", + RawPartsKind::Mutable => "from_raw_parts_mut", + }; + let mut applicability = Applicability::MachineApplicable; + let ctxt = expr.span.ctxt(); + let ptr = snippet_with_context(cx, ptr_arg.span, ctxt, "ptr", &mut applicability).0; + let len = snippet_with_context(cx, len_arg.span, ctxt, "len", &mut applicability).0; + span_lint_and_sugg( + cx, + CAST_SLICE_FROM_RAW_PARTS, + expr.span, + format!("implicitly casting the result of `{func}` to `{target}`"), + "replace_with", + format!("core::ptr::slice_{func}({ptr}, {len})"), + applicability, + ); + } +} diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index dcc439a272cf..bbdb0eabbabd 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -910,6 +910,9 @@ impl<'tcx> LateLintPass<'tcx> for Casts { if self.msrv.meets(cx, msrvs::RAW_REF_OP) { borrow_as_ptr::check_implicit_cast(cx, expr); } + if self.msrv.meets(cx, msrvs::PTR_SLICE_RAW_PARTS) { + cast_slice_from_raw_parts::check_implicit_cast(cx, expr); + } cast_ptr_alignment::check(cx, expr); char_lit_as_u8::check(cx, expr); ptr_as_ptr::check(cx, expr, self.msrv); diff --git a/tests/ui/cast_raw_slice_pointer_cast.fixed b/tests/ui/cast_raw_slice_pointer_cast.fixed index bddcb0ebf64e..14e232d0d2a6 100644 --- a/tests/ui/cast_raw_slice_pointer_cast.fixed +++ b/tests/ui/cast_raw_slice_pointer_cast.fixed @@ -1,6 +1,7 @@ #![warn(clippy::cast_slice_from_raw_parts)] -#[allow(unused_imports, unused_unsafe)] +const fn require_raw_slice_ptr(_: *const [T]) {} + fn main() { let mut vec = vec![0u8; 1]; let ptr: *const u8 = vec.as_ptr(); @@ -27,4 +28,26 @@ fn main() { let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); //~^ cast_slice_from_raw_parts } + + // implicit cast + { + let _: *const [u8] = unsafe { core::ptr::slice_from_raw_parts(ptr, 1) }; + //~^ cast_slice_from_raw_parts + let _: *mut [u8] = unsafe { core::ptr::slice_from_raw_parts_mut(mptr, 1) }; + //~^ cast_slice_from_raw_parts + require_raw_slice_ptr(unsafe { core::ptr::slice_from_raw_parts(ptr, 1) }); + //~^ cast_slice_from_raw_parts + } + + // implicit cast in const context + const { + const PTR: *const u8 = std::ptr::null(); + const MPTR: *mut u8 = std::ptr::null_mut(); + let _: *const [u8] = unsafe { std::ptr::slice_from_raw_parts(PTR, 1) }; + //~^ cast_slice_from_raw_parts + let _: *mut [u8] = unsafe { std::ptr::slice_from_raw_parts_mut(MPTR, 1) }; + //~^ cast_slice_from_raw_parts + require_raw_slice_ptr(unsafe { std::ptr::slice_from_raw_parts(PTR, 1) }); + //~^ cast_slice_from_raw_parts + }; } diff --git a/tests/ui/cast_raw_slice_pointer_cast.rs b/tests/ui/cast_raw_slice_pointer_cast.rs index 0a1eb276d5e9..8f57b1f96192 100644 --- a/tests/ui/cast_raw_slice_pointer_cast.rs +++ b/tests/ui/cast_raw_slice_pointer_cast.rs @@ -1,6 +1,7 @@ #![warn(clippy::cast_slice_from_raw_parts)] -#[allow(unused_imports, unused_unsafe)] +const fn require_raw_slice_ptr(_: *const [T]) {} + fn main() { let mut vec = vec![0u8; 1]; let ptr: *const u8 = vec.as_ptr(); @@ -27,4 +28,26 @@ fn main() { let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; //~^ cast_slice_from_raw_parts } + + // implicit cast + { + let _: *const [u8] = unsafe { std::slice::from_raw_parts(ptr, 1) }; + //~^ cast_slice_from_raw_parts + let _: *mut [u8] = unsafe { std::slice::from_raw_parts_mut(mptr, 1) }; + //~^ cast_slice_from_raw_parts + require_raw_slice_ptr(unsafe { std::slice::from_raw_parts(ptr, 1) }); + //~^ cast_slice_from_raw_parts + } + + // implicit cast in const context + const { + const PTR: *const u8 = std::ptr::null(); + const MPTR: *mut u8 = std::ptr::null_mut(); + let _: *const [u8] = unsafe { std::slice::from_raw_parts(PTR, 1) }; + //~^ cast_slice_from_raw_parts + let _: *mut [u8] = unsafe { std::slice::from_raw_parts_mut(MPTR, 1) }; + //~^ cast_slice_from_raw_parts + require_raw_slice_ptr(unsafe { std::slice::from_raw_parts(PTR, 1) }); + //~^ cast_slice_from_raw_parts + }; } diff --git a/tests/ui/cast_raw_slice_pointer_cast.stderr b/tests/ui/cast_raw_slice_pointer_cast.stderr index 60794a988db7..e70cc593bb04 100644 --- a/tests/ui/cast_raw_slice_pointer_cast.stderr +++ b/tests/ui/cast_raw_slice_pointer_cast.stderr @@ -1,5 +1,5 @@ error: casting the result of `from_raw_parts` to *const [u8] - --> tests/ui/cast_raw_slice_pointer_cast.rs:8:35 + --> tests/ui/cast_raw_slice_pointer_cast.rs:9:35 | LL | let _: *const [u8] = unsafe { std::slice::from_raw_parts(ptr, 1) as *const [u8] }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` @@ -8,40 +8,76 @@ LL | let _: *const [u8] = unsafe { std::slice::from_raw_parts(ptr, 1) as *co = help: to override `-D warnings` add `#[allow(clippy::cast_slice_from_raw_parts)]` error: casting the result of `from_raw_parts_mut` to *mut [u8] - --> tests/ui/cast_raw_slice_pointer_cast.rs:10:35 + --> tests/ui/cast_raw_slice_pointer_cast.rs:11:35 | LL | let _: *const [u8] = unsafe { std::slice::from_raw_parts_mut(mptr, 1) as *mut [u8] }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts_mut(mptr, 1)` error: casting the result of `from_raw_parts` to *const [u8] - --> tests/ui/cast_raw_slice_pointer_cast.rs:12:26 + --> tests/ui/cast_raw_slice_pointer_cast.rs:13:26 | LL | let _: *const [u8] = unsafe { std::slice::from_raw_parts(ptr, 1) } as *const [u8]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` error: casting the result of `from_raw_parts` to *const [u8] - --> tests/ui/cast_raw_slice_pointer_cast.rs:16:30 + --> tests/ui/cast_raw_slice_pointer_cast.rs:17:30 | LL | let _: *const [u8] = unsafe { slice::from_raw_parts(ptr, 1) } as *const [u8]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` error: casting the result of `from_raw_parts` to *const [u8] - --> tests/ui/cast_raw_slice_pointer_cast.rs:19:30 + --> tests/ui/cast_raw_slice_pointer_cast.rs:20:30 | LL | let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` error: casting the result of `from_raw_parts` to *const [u8] - --> tests/ui/cast_raw_slice_pointer_cast.rs:24:30 + --> tests/ui/cast_raw_slice_pointer_cast.rs:25:30 | LL | let _: *const [u8] = unsafe { slice::from_raw_parts(ptr, 1) } as *const [u8]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` error: casting the result of `from_raw_parts` to *const [u8] - --> tests/ui/cast_raw_slice_pointer_cast.rs:27:30 + --> tests/ui/cast_raw_slice_pointer_cast.rs:28:30 | LL | let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` -error: aborting due to 7 previous errors +error: implicitly casting the result of `from_raw_parts` to `*const [u8]` + --> tests/ui/cast_raw_slice_pointer_cast.rs:34:39 + | +LL | let _: *const [u8] = unsafe { std::slice::from_raw_parts(ptr, 1) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: implicitly casting the result of `from_raw_parts_mut` to `*mut [u8]` + --> tests/ui/cast_raw_slice_pointer_cast.rs:36:37 + | +LL | let _: *mut [u8] = unsafe { std::slice::from_raw_parts_mut(mptr, 1) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `core::ptr::slice_from_raw_parts_mut(mptr, 1)` + +error: implicitly casting the result of `from_raw_parts` to `*const [u8]` + --> tests/ui/cast_raw_slice_pointer_cast.rs:38:40 + | +LL | require_raw_slice_ptr(unsafe { std::slice::from_raw_parts(ptr, 1) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: implicitly casting the result of `from_raw_parts` to `*const [u8]` + --> tests/ui/cast_raw_slice_pointer_cast.rs:46:39 + | +LL | let _: *const [u8] = unsafe { std::slice::from_raw_parts(PTR, 1) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `std::ptr::slice_from_raw_parts(PTR, 1)` + +error: implicitly casting the result of `from_raw_parts_mut` to `*mut [u8]` + --> tests/ui/cast_raw_slice_pointer_cast.rs:48:37 + | +LL | let _: *mut [u8] = unsafe { std::slice::from_raw_parts_mut(MPTR, 1) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `std::ptr::slice_from_raw_parts_mut(MPTR, 1)` + +error: implicitly casting the result of `from_raw_parts` to `*const [u8]` + --> tests/ui/cast_raw_slice_pointer_cast.rs:50:40 + | +LL | require_raw_slice_ptr(unsafe { std::slice::from_raw_parts(PTR, 1) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `std::ptr::slice_from_raw_parts(PTR, 1)` + +error: aborting due to 13 previous errors From 42cc75973fa1713530b588e651d0d9203c226fb2 Mon Sep 17 00:00:00 2001 From: Zihan Date: Thu, 7 Aug 2025 19:29:22 -0400 Subject: [PATCH 079/116] changelog: [`cast_slice_from_raw_parts`]: properly select std/core Signed-off-by: Zihan --- .../src/casts/cast_slice_from_raw_parts.rs | 8 +- tests/ui/cast_raw_slice_pointer_cast.fixed | 20 ++--- tests/ui/cast_raw_slice_pointer_cast.stderr | 20 ++--- .../cast_raw_slice_pointer_cast_no_std.fixed | 55 ++++++++++++ .../ui/cast_raw_slice_pointer_cast_no_std.rs | 55 ++++++++++++ .../cast_raw_slice_pointer_cast_no_std.stderr | 83 +++++++++++++++++++ 6 files changed, 218 insertions(+), 23 deletions(-) create mode 100644 tests/ui/cast_raw_slice_pointer_cast_no_std.fixed create mode 100644 tests/ui/cast_raw_slice_pointer_cast_no_std.rs create mode 100644 tests/ui/cast_raw_slice_pointer_cast_no_std.stderr diff --git a/clippy_lints/src/casts/cast_slice_from_raw_parts.rs b/clippy_lints/src/casts/cast_slice_from_raw_parts.rs index 24bfb7355648..bce7b4c69cc6 100644 --- a/clippy_lints/src/casts/cast_slice_from_raw_parts.rs +++ b/clippy_lints/src/casts/cast_slice_from_raw_parts.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::get_parent_expr; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_context; +use clippy_utils::{get_parent_expr, is_no_std_crate}; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::{Expr, ExprKind}; @@ -44,13 +44,14 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, let mut applicability = Applicability::MachineApplicable; let ptr = snippet_with_context(cx, ptr_arg.span, ctxt, "ptr", &mut applicability).0; let len = snippet_with_context(cx, len_arg.span, ctxt, "len", &mut applicability).0; + let krate = if is_no_std_crate(cx) { "core" } else { "std" }; span_lint_and_sugg( cx, CAST_SLICE_FROM_RAW_PARTS, span, format!("casting the result of `{func}` to {cast_to}"), "replace with", - format!("core::ptr::slice_{func}({ptr}, {len})"), + format!("{krate}::ptr::slice_{func}({ptr}, {len})"), applicability, ); } @@ -81,13 +82,14 @@ pub(super) fn check_implicit_cast(cx: &LateContext<'_>, expr: &Expr<'_>) { let ctxt = expr.span.ctxt(); let ptr = snippet_with_context(cx, ptr_arg.span, ctxt, "ptr", &mut applicability).0; let len = snippet_with_context(cx, len_arg.span, ctxt, "len", &mut applicability).0; + let krate = if is_no_std_crate(cx) { "core" } else { "std" }; span_lint_and_sugg( cx, CAST_SLICE_FROM_RAW_PARTS, expr.span, format!("implicitly casting the result of `{func}` to `{target}`"), "replace_with", - format!("core::ptr::slice_{func}({ptr}, {len})"), + format!("{krate}::ptr::slice_{func}({ptr}, {len})"), applicability, ); } diff --git a/tests/ui/cast_raw_slice_pointer_cast.fixed b/tests/ui/cast_raw_slice_pointer_cast.fixed index 14e232d0d2a6..3c1cf8845957 100644 --- a/tests/ui/cast_raw_slice_pointer_cast.fixed +++ b/tests/ui/cast_raw_slice_pointer_cast.fixed @@ -6,36 +6,36 @@ fn main() { let mut vec = vec![0u8; 1]; let ptr: *const u8 = vec.as_ptr(); let mptr = vec.as_mut_ptr(); - let _: *const [u8] = unsafe { core::ptr::slice_from_raw_parts(ptr, 1) }; + let _: *const [u8] = unsafe { std::ptr::slice_from_raw_parts(ptr, 1) }; //~^ cast_slice_from_raw_parts - let _: *const [u8] = unsafe { core::ptr::slice_from_raw_parts_mut(mptr, 1) }; + let _: *const [u8] = unsafe { std::ptr::slice_from_raw_parts_mut(mptr, 1) }; //~^ cast_slice_from_raw_parts - let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, 1); //~^ cast_slice_from_raw_parts { use core::slice; - let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, 1); //~^ cast_slice_from_raw_parts use slice as one; - let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, 1); //~^ cast_slice_from_raw_parts } { use std::slice; - let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, 1); //~^ cast_slice_from_raw_parts use slice as one; - let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, 1); //~^ cast_slice_from_raw_parts } // implicit cast { - let _: *const [u8] = unsafe { core::ptr::slice_from_raw_parts(ptr, 1) }; + let _: *const [u8] = unsafe { std::ptr::slice_from_raw_parts(ptr, 1) }; //~^ cast_slice_from_raw_parts - let _: *mut [u8] = unsafe { core::ptr::slice_from_raw_parts_mut(mptr, 1) }; + let _: *mut [u8] = unsafe { std::ptr::slice_from_raw_parts_mut(mptr, 1) }; //~^ cast_slice_from_raw_parts - require_raw_slice_ptr(unsafe { core::ptr::slice_from_raw_parts(ptr, 1) }); + require_raw_slice_ptr(unsafe { std::ptr::slice_from_raw_parts(ptr, 1) }); //~^ cast_slice_from_raw_parts } diff --git a/tests/ui/cast_raw_slice_pointer_cast.stderr b/tests/ui/cast_raw_slice_pointer_cast.stderr index e70cc593bb04..328dbafbafe7 100644 --- a/tests/ui/cast_raw_slice_pointer_cast.stderr +++ b/tests/ui/cast_raw_slice_pointer_cast.stderr @@ -2,7 +2,7 @@ error: casting the result of `from_raw_parts` to *const [u8] --> tests/ui/cast_raw_slice_pointer_cast.rs:9:35 | LL | let _: *const [u8] = unsafe { std::slice::from_raw_parts(ptr, 1) as *const [u8] }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::ptr::slice_from_raw_parts(ptr, 1)` | = note: `-D clippy::cast-slice-from-raw-parts` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_slice_from_raw_parts)]` @@ -11,55 +11,55 @@ error: casting the result of `from_raw_parts_mut` to *mut [u8] --> tests/ui/cast_raw_slice_pointer_cast.rs:11:35 | LL | let _: *const [u8] = unsafe { std::slice::from_raw_parts_mut(mptr, 1) as *mut [u8] }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts_mut(mptr, 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::ptr::slice_from_raw_parts_mut(mptr, 1)` error: casting the result of `from_raw_parts` to *const [u8] --> tests/ui/cast_raw_slice_pointer_cast.rs:13:26 | LL | let _: *const [u8] = unsafe { std::slice::from_raw_parts(ptr, 1) } as *const [u8]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::ptr::slice_from_raw_parts(ptr, 1)` error: casting the result of `from_raw_parts` to *const [u8] --> tests/ui/cast_raw_slice_pointer_cast.rs:17:30 | LL | let _: *const [u8] = unsafe { slice::from_raw_parts(ptr, 1) } as *const [u8]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::ptr::slice_from_raw_parts(ptr, 1)` error: casting the result of `from_raw_parts` to *const [u8] --> tests/ui/cast_raw_slice_pointer_cast.rs:20:30 | LL | let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::ptr::slice_from_raw_parts(ptr, 1)` error: casting the result of `from_raw_parts` to *const [u8] --> tests/ui/cast_raw_slice_pointer_cast.rs:25:30 | LL | let _: *const [u8] = unsafe { slice::from_raw_parts(ptr, 1) } as *const [u8]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::ptr::slice_from_raw_parts(ptr, 1)` error: casting the result of `from_raw_parts` to *const [u8] --> tests/ui/cast_raw_slice_pointer_cast.rs:28:30 | LL | let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::ptr::slice_from_raw_parts(ptr, 1)` error: implicitly casting the result of `from_raw_parts` to `*const [u8]` --> tests/ui/cast_raw_slice_pointer_cast.rs:34:39 | LL | let _: *const [u8] = unsafe { std::slice::from_raw_parts(ptr, 1) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `core::ptr::slice_from_raw_parts(ptr, 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `std::ptr::slice_from_raw_parts(ptr, 1)` error: implicitly casting the result of `from_raw_parts_mut` to `*mut [u8]` --> tests/ui/cast_raw_slice_pointer_cast.rs:36:37 | LL | let _: *mut [u8] = unsafe { std::slice::from_raw_parts_mut(mptr, 1) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `core::ptr::slice_from_raw_parts_mut(mptr, 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `std::ptr::slice_from_raw_parts_mut(mptr, 1)` error: implicitly casting the result of `from_raw_parts` to `*const [u8]` --> tests/ui/cast_raw_slice_pointer_cast.rs:38:40 | LL | require_raw_slice_ptr(unsafe { std::slice::from_raw_parts(ptr, 1) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `core::ptr::slice_from_raw_parts(ptr, 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `std::ptr::slice_from_raw_parts(ptr, 1)` error: implicitly casting the result of `from_raw_parts` to `*const [u8]` --> tests/ui/cast_raw_slice_pointer_cast.rs:46:39 diff --git a/tests/ui/cast_raw_slice_pointer_cast_no_std.fixed b/tests/ui/cast_raw_slice_pointer_cast_no_std.fixed new file mode 100644 index 000000000000..f71fb8d863c6 --- /dev/null +++ b/tests/ui/cast_raw_slice_pointer_cast_no_std.fixed @@ -0,0 +1,55 @@ +#![warn(clippy::cast_slice_from_raw_parts)] +#![no_std] +#![crate_type = "lib"] + +const fn require_raw_slice_ptr(_: *const [T]) {} + +fn main() { + let mut arr = [0u8; 1]; + let ptr: *const u8 = arr.as_ptr(); + let mptr = arr.as_mut_ptr(); + let _: *const [u8] = unsafe { core::ptr::slice_from_raw_parts(ptr, 1) }; + //~^ cast_slice_from_raw_parts + let _: *const [u8] = unsafe { core::ptr::slice_from_raw_parts_mut(mptr, 1) }; + //~^ cast_slice_from_raw_parts + let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + //~^ cast_slice_from_raw_parts + { + use core::slice; + let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + //~^ cast_slice_from_raw_parts + use slice as one; + let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + //~^ cast_slice_from_raw_parts + } + { + use core::slice; + let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + //~^ cast_slice_from_raw_parts + use slice as one; + let _: *const [u8] = core::ptr::slice_from_raw_parts(ptr, 1); + //~^ cast_slice_from_raw_parts + } + + // implicit cast + { + let _: *const [u8] = unsafe { core::ptr::slice_from_raw_parts(ptr, 1) }; + //~^ cast_slice_from_raw_parts + let _: *mut [u8] = unsafe { core::ptr::slice_from_raw_parts_mut(mptr, 1) }; + //~^ cast_slice_from_raw_parts + require_raw_slice_ptr(unsafe { core::ptr::slice_from_raw_parts(ptr, 1) }); + //~^ cast_slice_from_raw_parts + } + + // implicit cast in const context + const { + const PTR: *const u8 = core::ptr::null(); + const MPTR: *mut u8 = core::ptr::null_mut(); + let _: *const [u8] = unsafe { core::ptr::slice_from_raw_parts(PTR, 1) }; + //~^ cast_slice_from_raw_parts + let _: *mut [u8] = unsafe { core::ptr::slice_from_raw_parts_mut(MPTR, 1) }; + //~^ cast_slice_from_raw_parts + require_raw_slice_ptr(unsafe { core::ptr::slice_from_raw_parts(PTR, 1) }); + //~^ cast_slice_from_raw_parts + }; +} diff --git a/tests/ui/cast_raw_slice_pointer_cast_no_std.rs b/tests/ui/cast_raw_slice_pointer_cast_no_std.rs new file mode 100644 index 000000000000..743e44c97dc1 --- /dev/null +++ b/tests/ui/cast_raw_slice_pointer_cast_no_std.rs @@ -0,0 +1,55 @@ +#![warn(clippy::cast_slice_from_raw_parts)] +#![no_std] +#![crate_type = "lib"] + +const fn require_raw_slice_ptr(_: *const [T]) {} + +fn main() { + let mut arr = [0u8; 1]; + let ptr: *const u8 = arr.as_ptr(); + let mptr = arr.as_mut_ptr(); + let _: *const [u8] = unsafe { core::slice::from_raw_parts(ptr, 1) as *const [u8] }; + //~^ cast_slice_from_raw_parts + let _: *const [u8] = unsafe { core::slice::from_raw_parts_mut(mptr, 1) as *mut [u8] }; + //~^ cast_slice_from_raw_parts + let _: *const [u8] = unsafe { core::slice::from_raw_parts(ptr, 1) } as *const [u8]; + //~^ cast_slice_from_raw_parts + { + use core::slice; + let _: *const [u8] = unsafe { slice::from_raw_parts(ptr, 1) } as *const [u8]; + //~^ cast_slice_from_raw_parts + use slice as one; + let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; + //~^ cast_slice_from_raw_parts + } + { + use core::slice; + let _: *const [u8] = unsafe { slice::from_raw_parts(ptr, 1) } as *const [u8]; + //~^ cast_slice_from_raw_parts + use slice as one; + let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; + //~^ cast_slice_from_raw_parts + } + + // implicit cast + { + let _: *const [u8] = unsafe { core::slice::from_raw_parts(ptr, 1) }; + //~^ cast_slice_from_raw_parts + let _: *mut [u8] = unsafe { core::slice::from_raw_parts_mut(mptr, 1) }; + //~^ cast_slice_from_raw_parts + require_raw_slice_ptr(unsafe { core::slice::from_raw_parts(ptr, 1) }); + //~^ cast_slice_from_raw_parts + } + + // implicit cast in const context + const { + const PTR: *const u8 = core::ptr::null(); + const MPTR: *mut u8 = core::ptr::null_mut(); + let _: *const [u8] = unsafe { core::slice::from_raw_parts(PTR, 1) }; + //~^ cast_slice_from_raw_parts + let _: *mut [u8] = unsafe { core::slice::from_raw_parts_mut(MPTR, 1) }; + //~^ cast_slice_from_raw_parts + require_raw_slice_ptr(unsafe { core::slice::from_raw_parts(PTR, 1) }); + //~^ cast_slice_from_raw_parts + }; +} diff --git a/tests/ui/cast_raw_slice_pointer_cast_no_std.stderr b/tests/ui/cast_raw_slice_pointer_cast_no_std.stderr new file mode 100644 index 000000000000..5488fbcfa1ce --- /dev/null +++ b/tests/ui/cast_raw_slice_pointer_cast_no_std.stderr @@ -0,0 +1,83 @@ +error: casting the result of `from_raw_parts` to *const [u8] + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:11:35 + | +LL | let _: *const [u8] = unsafe { core::slice::from_raw_parts(ptr, 1) as *const [u8] }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + | + = note: `-D clippy::cast-slice-from-raw-parts` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::cast_slice_from_raw_parts)]` + +error: casting the result of `from_raw_parts_mut` to *mut [u8] + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:13:35 + | +LL | let _: *const [u8] = unsafe { core::slice::from_raw_parts_mut(mptr, 1) as *mut [u8] }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts_mut(mptr, 1)` + +error: casting the result of `from_raw_parts` to *const [u8] + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:15:26 + | +LL | let _: *const [u8] = unsafe { core::slice::from_raw_parts(ptr, 1) } as *const [u8]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: casting the result of `from_raw_parts` to *const [u8] + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:19:30 + | +LL | let _: *const [u8] = unsafe { slice::from_raw_parts(ptr, 1) } as *const [u8]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: casting the result of `from_raw_parts` to *const [u8] + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:22:30 + | +LL | let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: casting the result of `from_raw_parts` to *const [u8] + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:27:30 + | +LL | let _: *const [u8] = unsafe { slice::from_raw_parts(ptr, 1) } as *const [u8]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: casting the result of `from_raw_parts` to *const [u8] + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:30:30 + | +LL | let _: *const [u8] = unsafe { one::from_raw_parts(ptr, 1) } as *const [u8]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: implicitly casting the result of `from_raw_parts` to `*const [u8]` + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:36:39 + | +LL | let _: *const [u8] = unsafe { core::slice::from_raw_parts(ptr, 1) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: implicitly casting the result of `from_raw_parts_mut` to `*mut [u8]` + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:38:37 + | +LL | let _: *mut [u8] = unsafe { core::slice::from_raw_parts_mut(mptr, 1) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `core::ptr::slice_from_raw_parts_mut(mptr, 1)` + +error: implicitly casting the result of `from_raw_parts` to `*const [u8]` + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:40:40 + | +LL | require_raw_slice_ptr(unsafe { core::slice::from_raw_parts(ptr, 1) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `core::ptr::slice_from_raw_parts(ptr, 1)` + +error: implicitly casting the result of `from_raw_parts` to `*const [u8]` + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:48:39 + | +LL | let _: *const [u8] = unsafe { core::slice::from_raw_parts(PTR, 1) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `core::ptr::slice_from_raw_parts(PTR, 1)` + +error: implicitly casting the result of `from_raw_parts_mut` to `*mut [u8]` + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:50:37 + | +LL | let _: *mut [u8] = unsafe { core::slice::from_raw_parts_mut(MPTR, 1) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `core::ptr::slice_from_raw_parts_mut(MPTR, 1)` + +error: implicitly casting the result of `from_raw_parts` to `*const [u8]` + --> tests/ui/cast_raw_slice_pointer_cast_no_std.rs:52:40 + | +LL | require_raw_slice_ptr(unsafe { core::slice::from_raw_parts(PTR, 1) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace_with: `core::ptr::slice_from_raw_parts(PTR, 1)` + +error: aborting due to 13 previous errors + From 2427ec250a0a6b3c9d03bd45b1a71e62a6a6b060 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 13 Aug 2025 14:32:31 +0200 Subject: [PATCH 080/116] misc: remove one level of nesting --- clippy_lints/src/casts/cast_ptr_alignment.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/casts/cast_ptr_alignment.rs b/clippy_lints/src/casts/cast_ptr_alignment.rs index a1543cabd2f9..5e206165842a 100644 --- a/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -74,14 +74,13 @@ fn is_used_as_unaligned(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { ExprKind::Call(func, [arg, ..]) if arg.hir_id == e.hir_id => { if let ExprKind::Path(path) = &func.kind && let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id() + && let Some(name) = cx.tcx.get_diagnostic_name(def_id) && matches!( - cx.tcx.get_diagnostic_name(def_id), - Some( - sym::ptr_write_unaligned - | sym::ptr_read_unaligned - | sym::intrinsics_unaligned_volatile_load - | sym::intrinsics_unaligned_volatile_store - ) + name, + sym::ptr_write_unaligned + | sym::ptr_read_unaligned + | sym::intrinsics_unaligned_volatile_load + | sym::intrinsics_unaligned_volatile_store ) { true From 9acb48dbbe4bdb5ab7049e99a2b13c4aebc45981 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 13 Aug 2025 14:42:42 +0200 Subject: [PATCH 081/116] `cast_ptr_alignment`: move the `.cast()` check into a separate fn --- clippy_lints/src/casts/cast_ptr_alignment.rs | 6 +++++- clippy_lints/src/casts/mod.rs | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/casts/cast_ptr_alignment.rs b/clippy_lints/src/casts/cast_ptr_alignment.rs index 5e206165842a..fd8302e4417f 100644 --- a/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -18,7 +18,11 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { cx.typeck_results().expr_ty(expr), ); lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); - } else if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind + } +} + +pub(super) fn check_cast_method(cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind && method_path.ident.name == sym::cast && let Some(generic_args) = method_path.args && let [GenericArg::Type(cast_to)] = generic_args.args diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 58de51a32291..976e0d196575 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -915,6 +915,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts { cast_slice_from_raw_parts::check_implicit_cast(cx, expr); } cast_ptr_alignment::check(cx, expr); + cast_ptr_alignment::check_cast_method(cx, expr); ptr_as_ptr::check(cx, expr, self.msrv); cast_slice_different_sizes::check(cx, expr, self.msrv); ptr_cast_constness::check_null_ptr_cast_method(cx, expr); From 74be6f769ab3a503ed01dee59b459027dbced016 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 13 Aug 2025 14:47:29 +0200 Subject: [PATCH 082/116] `cast_ptr_alignment`: move the regular check into the `if` this allows reusing `cast_from` and `cast_to` one possible problem is that the `if` has an additional `is_hir_ty_cfg_dependant` check which the let-chain of the original `check` didn't have.. but maybe this is actually more correct --- clippy_lints/src/casts/cast_ptr_alignment.rs | 13 ++----------- clippy_lints/src/casts/mod.rs | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/casts/cast_ptr_alignment.rs b/clippy_lints/src/casts/cast_ptr_alignment.rs index fd8302e4417f..d78da9396faf 100644 --- a/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -8,17 +8,8 @@ use rustc_middle::ty::{self, Ty}; use super::CAST_PTR_ALIGNMENT; -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::Cast(cast_expr, cast_to) = expr.kind { - if is_hir_ty_cfg_dependant(cx, cast_to) { - return; - } - let (cast_from, cast_to) = ( - cx.typeck_results().expr_ty(cast_expr), - cx.typeck_results().expr_ty(expr), - ); - lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); - } +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, cast_from: Ty<'tcx>, cast_to: Ty<'tcx>) { + lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); } pub(super) fn check_cast_method(cx: &LateContext<'_>, expr: &Expr<'_>) { diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 976e0d196575..94940b32f27f 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -873,6 +873,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts { } char_lit_as_u8::check(cx, expr, cast_from_expr, cast_to); cast_slice_from_raw_parts::check(cx, expr, cast_from_expr, cast_to, self.msrv); + cast_ptr_alignment::check(cx, expr, cast_from, cast_to); ptr_cast_constness::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv); as_ptr_cast_mut::check(cx, expr, cast_from_expr, cast_to); fn_to_numeric_cast_any::check(cx, expr, cast_from_expr, cast_from, cast_to); @@ -914,7 +915,6 @@ impl<'tcx> LateLintPass<'tcx> for Casts { if self.msrv.meets(cx, msrvs::PTR_SLICE_RAW_PARTS) { cast_slice_from_raw_parts::check_implicit_cast(cx, expr); } - cast_ptr_alignment::check(cx, expr); cast_ptr_alignment::check_cast_method(cx, expr); ptr_as_ptr::check(cx, expr, self.msrv); cast_slice_different_sizes::check(cx, expr, self.msrv); From 0e5b1b19f540936fa12f89ae307ab129485a3d86 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 13 Aug 2025 17:12:33 +0200 Subject: [PATCH 083/116] clean-up `ptr_cast_constness` use `Mutability::ptr_str` replace `matches!` with an equality check avoid always allocating a `String` s/cast_expr/cast_from_expr -- for more consistency with the naming in `mod.rs` --- clippy_lints/src/casts/ptr_cast_constness.rs | 27 ++++++++------------ 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/clippy_lints/src/casts/ptr_cast_constness.rs b/clippy_lints/src/casts/ptr_cast_constness.rs index c0c0a47f8551..5ab399152519 100644 --- a/clippy_lints/src/casts/ptr_cast_constness.rs +++ b/clippy_lints/src/casts/ptr_cast_constness.rs @@ -1,9 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use clippy_utils::{std_or_core, sym}; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, Mutability, QPath}; +use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty, TypeVisitableExt}; @@ -12,26 +13,23 @@ use super::PTR_CAST_CONSTNESS; pub(super) fn check<'tcx>( cx: &LateContext<'_>, expr: &Expr<'_>, - cast_expr: &Expr<'_>, + cast_from_expr: &Expr<'_>, cast_from: Ty<'tcx>, cast_to: Ty<'tcx>, msrv: Msrv, ) { if let ty::RawPtr(from_ty, from_mutbl) = cast_from.kind() && let ty::RawPtr(to_ty, to_mutbl) = cast_to.kind() - && matches!( - (from_mutbl, to_mutbl), - (Mutability::Not, Mutability::Mut) | (Mutability::Mut, Mutability::Not) - ) + && from_mutbl != to_mutbl && from_ty == to_ty && !from_ty.has_erased_regions() { - if let ExprKind::Call(func, []) = cast_expr.kind + if let ExprKind::Call(func, []) = cast_from_expr.kind && let ExprKind::Path(QPath::Resolved(None, path)) = func.kind && let Some(defid) = path.res.opt_def_id() && let Some(prefix) = std_or_core(cx) && let mut app = Applicability::MachineApplicable - && let sugg = format!("{}", Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app)) + && let sugg = snippet_with_applicability(cx, cast_from_expr.span, "_", &mut app) && let Some((_, after_lt)) = sugg.split_once("::<") && let Some((source, target, target_func)) = match cx.tcx.get_diagnostic_name(defid) { Some(sym::ptr_null) => Some(("const", "mutable", "null_mut")), @@ -53,11 +51,8 @@ pub(super) fn check<'tcx>( if msrv.meets(cx, msrvs::POINTER_CAST_CONSTNESS) { let mut app = Applicability::MachineApplicable; - let sugg = Sugg::hir_with_context(cx, cast_expr, expr.span.ctxt(), "_", &mut app); - let constness = match *to_mutbl { - Mutability::Not => "const", - Mutability::Mut => "mut", - }; + let sugg = Sugg::hir_with_context(cx, cast_from_expr, expr.span.ctxt(), "_", &mut app); + let constness = to_mutbl.ptr_str(); span_lint_and_sugg( cx, @@ -73,8 +68,8 @@ pub(super) fn check<'tcx>( } pub(super) fn check_null_ptr_cast_method(cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::MethodCall(method, cast_expr, [], _) = expr.kind - && let ExprKind::Call(func, []) = cast_expr.kind + if let ExprKind::MethodCall(method, cast_from_expr, [], _) = expr.kind + && let ExprKind::Call(func, []) = cast_from_expr.kind && let ExprKind::Path(QPath::Resolved(None, path)) = func.kind && let Some(defid) = path.res.opt_def_id() && let method = match (cx.tcx.get_diagnostic_name(defid), method.ident.name) { @@ -84,7 +79,7 @@ pub(super) fn check_null_ptr_cast_method(cx: &LateContext<'_>, expr: &Expr<'_>) } && let Some(prefix) = std_or_core(cx) && let mut app = Applicability::MachineApplicable - && let sugg = format!("{}", Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app)) + && let sugg = snippet_with_applicability(cx, cast_from_expr.span, "_", &mut app) && let Some((_, after_lt)) = sugg.split_once("::<") { span_lint_and_sugg( From 9d05ac43969dec9ee26f507e6c9390c26d95a8bf Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Fri, 8 Aug 2025 22:00:50 +0200 Subject: [PATCH 084/116] Rewrite `unwrap_in_result` lint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Lint non-`impl` functions as well. - Lint function calls in addition to method calls (such as `Option::unwrap(…)`). - Put the lint on the `unwrap` and `expect` node instead of the function signature. This lets the user `#[allow]` specific `unwrap()` or `expect()` calls. - Do not lint inside closures, `const` and `static`. - Do not mix warnings about `Option` and `Result`. --- clippy_lints/src/lib.rs | 2 +- clippy_lints/src/unwrap_in_result.rs | 202 ++++++++++++++++++++------- tests/ui/unwrap_in_result.rs | 70 ++++++++-- tests/ui/unwrap_in_result.stderr | 132 +++++++++++------ 4 files changed, 306 insertions(+), 100 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 844bc1b0e390..df2cb50d6478 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -654,7 +654,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_early_pass(move || Box::new(nonstandard_macro_braces::MacroBraces::new(conf))); store.register_late_pass(|_| Box::::default()); store.register_late_pass(|_| Box::new(pattern_type_mismatch::PatternTypeMismatch)); - store.register_late_pass(|_| Box::new(unwrap_in_result::UnwrapInResult)); + store.register_late_pass(|_| Box::::default()); store.register_late_pass(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned)); store.register_late_pass(|_| Box::new(async_yields_async::AsyncYieldsAsync)); let attrs = attr_storage.clone(); diff --git a/clippy_lints/src/unwrap_in_result.rs b/clippy_lints/src/unwrap_in_result.rs index 7bec212a23ca..f26647fa3485 100644 --- a/clippy_lints/src/unwrap_in_result.rs +++ b/clippy_lints/src/unwrap_in_result.rs @@ -1,13 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::visitors::for_each_expr; -use clippy_utils::{method_chain_args, return_ty}; -use core::ops::ControlFlow; -use rustc_hir as hir; -use rustc_hir::ImplItemKind; +use clippy_utils::{return_ty, sym}; +use rustc_hir::{ + Body, BodyOwnerKind, Expr, ExprKind, FnSig, ImplItem, ImplItemKind, Item, ItemKind, OwnerId, PathSegment, QPath, +}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; -use rustc_span::{Span, sym}; +use rustc_middle::ty::Ty; +use rustc_session::impl_lint_pass; +use rustc_span::{Ident, Span, Symbol}; declare_clippy_lint! { /// ### What it does @@ -57,62 +56,165 @@ declare_clippy_lint! { "functions of type `Result<..>` or `Option`<...> that contain `expect()` or `unwrap()`" } -declare_lint_pass!(UnwrapInResult=> [UNWRAP_IN_RESULT]); +impl_lint_pass!(UnwrapInResult=> [UNWRAP_IN_RESULT]); -impl<'tcx> LateLintPass<'tcx> for UnwrapInResult { - fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { - if let ImplItemKind::Fn(ref _signature, _) = impl_item.kind - // first check if it's a method or function - // checking if its return type is `result` or `option` - && (is_type_diagnostic_item(cx, return_ty(cx, impl_item.owner_id), sym::Result) - || is_type_diagnostic_item(cx, return_ty(cx, impl_item.owner_id), sym::Option)) - { - lint_impl_body(cx, impl_item.span, impl_item); +#[derive(Clone, Copy, Eq, PartialEq)] +enum OptionOrResult { + Option, + Result, +} + +impl OptionOrResult { + fn with_article(self) -> &'static str { + match self { + Self::Option => "an `Option`", + Self::Result => "a `Result`", } } } -fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tcx hir::ImplItem<'_>) { - if let ImplItemKind::Fn(_, body_id) = impl_item.kind { - let body = cx.tcx.hir_body(body_id); - let typeck = cx.tcx.typeck(impl_item.owner_id.def_id); - let mut result = Vec::new(); - let _: Option = for_each_expr(cx, body.value, |e| { - // check for `expect` - if let Some(arglists) = method_chain_args(e, &[sym::expect]) { - let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs(); - if is_type_diagnostic_item(cx, receiver_ty, sym::Option) - || is_type_diagnostic_item(cx, receiver_ty, sym::Result) - { - result.push(e.span); - } - } +struct OptionOrResultFn { + kind: OptionOrResult, + return_ty_span: Option, +} - // check for `unwrap` - if let Some(arglists) = method_chain_args(e, &[sym::unwrap]) { - let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs(); - if is_type_diagnostic_item(cx, receiver_ty, sym::Option) - || is_type_diagnostic_item(cx, receiver_ty, sym::Result) - { - result.push(e.span); - } - } +#[derive(Default)] +pub struct UnwrapInResult { + fn_stack: Vec>, + current_fn: Option, +} - ControlFlow::Continue(()) +impl UnwrapInResult { + fn enter_item(&mut self, cx: &LateContext<'_>, fn_def_id: OwnerId, sig: &FnSig<'_>) { + self.fn_stack.push(self.current_fn.take()); + self.current_fn = is_option_or_result(cx, return_ty(cx, fn_def_id)).map(|kind| OptionOrResultFn { + kind, + return_ty_span: Some(sig.decl.output.span()), }); + } - // if we've found one, lint - if !result.is_empty() { + fn leave_item(&mut self) { + self.current_fn = self.fn_stack.pop().unwrap(); + } +} + +impl<'tcx> LateLintPass<'tcx> for UnwrapInResult { + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) { + if let ImplItemKind::Fn(sig, _) = &impl_item.kind { + self.enter_item(cx, impl_item.owner_id, sig); + } + } + + fn check_impl_item_post(&mut self, _: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'tcx>) { + if let ImplItemKind::Fn(..) = impl_item.kind { + self.leave_item(); + } + } + + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if let ItemKind::Fn { + has_body: true, sig, .. + } = &item.kind + { + self.enter_item(cx, item.owner_id, sig); + } + } + + fn check_item_post(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if let ItemKind::Fn { has_body: true, .. } = item.kind { + self.leave_item(); + } + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { + if expr.span.from_expansion() { + return; + } + + if let Some(OptionOrResultFn { + kind, + ref mut return_ty_span, + }) = self.current_fn + && let Some((oor, fn_name)) = is_unwrap_or_expect_call(cx, expr) + && oor == kind + { span_lint_and_then( cx, UNWRAP_IN_RESULT, - impl_span, - "used unwrap or expect in a function that returns result or option", - move |diag| { - diag.help("unwrap and expect should not be used in a function that returns result or option"); - diag.span_note(result, "potential non-recoverable error(s)"); + expr.span, + format!("`{fn_name}` used in a function that returns {}", kind.with_article()), + |diag| { + // Issue the note and help only once per function + if let Some(span) = return_ty_span.take() { + diag.span_note(span, "in this function signature"); + let complement = if kind == OptionOrResult::Result { + " or calling the `.map_err()` method" + } else { + "" + }; + diag.help(format!("consider using the `?` operator{complement}")); + } }, ); } } + + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { + let body_def_id = cx.tcx.hir_body_owner_def_id(body.id()); + if !matches!(cx.tcx.hir_body_owner_kind(body_def_id), BodyOwnerKind::Fn) { + // When entering a body which is not a function, mask the potential surrounding + // function to not apply the lint. + self.fn_stack.push(self.current_fn.take()); + } + } + + fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { + let body_def_id = cx.tcx.hir_body_owner_def_id(body.id()); + if !matches!(cx.tcx.hir_body_owner_kind(body_def_id), BodyOwnerKind::Fn) { + // Unmask the potential surrounding function. + self.current_fn = self.fn_stack.pop().unwrap(); + } + } +} + +fn is_option_or_result(cx: &LateContext<'_>, ty: Ty<'_>) -> Option { + match ty.ty_adt_def().and_then(|def| cx.tcx.get_diagnostic_name(def.did())) { + Some(sym::Option) => Some(OptionOrResult::Option), + Some(sym::Result) => Some(OptionOrResult::Result), + _ => None, + } +} + +fn is_unwrap_or_expect_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<(OptionOrResult, Symbol)> { + if let ExprKind::Call(func, _) = expr.kind + && let ExprKind::Path(QPath::TypeRelative( + hir_ty, + PathSegment { + ident: + Ident { + name: name @ (sym::unwrap | sym::expect), + .. + }, + .. + }, + )) = func.kind + { + is_option_or_result(cx, cx.typeck_results().node_type(hir_ty.hir_id)).map(|oor| (oor, *name)) + } else if let ExprKind::MethodCall( + PathSegment { + ident: Ident { + name: name @ (sym::unwrap | sym::expect), + .. + }, + .. + }, + recv, + _, + _, + ) = expr.kind + { + is_option_or_result(cx, cx.typeck_results().expr_ty_adjusted(recv)).map(|oor| (oor, *name)) + } else { + None + } } diff --git a/tests/ui/unwrap_in_result.rs b/tests/ui/unwrap_in_result.rs index 4e872c67b423..70c28fe54f37 100644 --- a/tests/ui/unwrap_in_result.rs +++ b/tests/ui/unwrap_in_result.rs @@ -1,4 +1,5 @@ #![warn(clippy::unwrap_in_result)] +#![allow(clippy::ok_expect)] struct A; @@ -20,10 +21,9 @@ impl A { // should be detected fn bad_divisible_by_3(i_str: String) -> Result { - //~^ unwrap_in_result - // checks whether a string represents a number divisible by 3 let i = i_str.parse::().unwrap(); + //~^ unwrap_in_result if i % 3 == 0 { Ok(true) } else { @@ -32,9 +32,8 @@ impl A { } fn example_option_expect(i_str: String) -> Option { + let i = i_str.parse::().ok().expect("not a number"); //~^ unwrap_in_result - - let i = i_str.parse::().expect("not a number"); if i % 3 == 0 { return Some(true); } @@ -42,13 +41,66 @@ impl A { } fn in_closure(a: Option) -> Option { - //~^ unwrap_in_result + // No lint inside a closure let c = || a.unwrap(); - Some(c()) + + // But lint outside + let a = c().then_some(true); + let _ = a.unwrap(); + //~^ unwrap_in_result + + None + } + + const fn in_const_inside_fn() -> bool { + const A: bool = { + const fn inner(b: Option) -> Option { + Some(b.unwrap()) + //~^ unwrap_in_result + } + + // No lint inside `const` + inner(Some(true)).unwrap() + }; + A + } + + fn in_static_inside_fn() -> bool { + static A: bool = { + const fn inner(b: Option) -> Option { + Some(b.unwrap()) + //~^ unwrap_in_result + } + + // No lint inside `static` + inner(Some(true)).unwrap() + }; + A } } -fn main() { - A::bad_divisible_by_3("3".to_string()); - A::good_divisible_by_3("3".to_string()); +macro_rules! mac { + () => { + Option::unwrap(Some(3)) + }; +} + +fn type_relative_unwrap() -> Option<()> { + _ = Option::unwrap(Some(3)); + //~^ unwrap_in_result + + // Do not lint macro output + _ = mac!(); + + None +} + +fn main() -> Result<(), ()> { + A::bad_divisible_by_3("3".to_string()).unwrap(); + //~^ unwrap_in_result + A::good_divisible_by_3("3".to_string()).unwrap(); + //~^ unwrap_in_result + Result::unwrap(A::good_divisible_by_3("3".to_string())); + //~^ unwrap_in_result + Ok(()) } diff --git a/tests/ui/unwrap_in_result.stderr b/tests/ui/unwrap_in_result.stderr index 5e3eab813e07..804b44246dc7 100644 --- a/tests/ui/unwrap_in_result.stderr +++ b/tests/ui/unwrap_in_result.stderr @@ -1,55 +1,107 @@ -error: used unwrap or expect in a function that returns result or option - --> tests/ui/unwrap_in_result.rs:22:5 - | -LL | / fn bad_divisible_by_3(i_str: String) -> Result { -... | -LL | | } - | |_____^ - | - = help: unwrap and expect should not be used in a function that returns result or option -note: potential non-recoverable error(s) - --> tests/ui/unwrap_in_result.rs:26:17 +error: `unwrap` used in a function that returns a `Result` + --> tests/ui/unwrap_in_result.rs:25:17 | LL | let i = i_str.parse::().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: in this function signature + --> tests/ui/unwrap_in_result.rs:23:45 + | +LL | fn bad_divisible_by_3(i_str: String) -> Result { + | ^^^^^^^^^^^^^^^^^^^^ + = help: consider using the `?` operator or calling the `.map_err()` method = note: `-D clippy::unwrap-in-result` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unwrap_in_result)]` -error: used unwrap or expect in a function that returns result or option - --> tests/ui/unwrap_in_result.rs:34:5 +error: `expect` used in a function that returns an `Option` + --> tests/ui/unwrap_in_result.rs:35:17 | -LL | / fn example_option_expect(i_str: String) -> Option { -LL | | -LL | | -LL | | let i = i_str.parse::().expect("not a number"); -... | -LL | | None -LL | | } - | |_____^ +LL | let i = i_str.parse::().ok().expect("not a number"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: unwrap and expect should not be used in a function that returns result or option -note: potential non-recoverable error(s) - --> tests/ui/unwrap_in_result.rs:37:17 +note: in this function signature + --> tests/ui/unwrap_in_result.rs:34:48 | -LL | let i = i_str.parse::().expect("not a number"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | fn example_option_expect(i_str: String) -> Option { + | ^^^^^^^^^^^^ + = help: consider using the `?` operator -error: used unwrap or expect in a function that returns result or option - --> tests/ui/unwrap_in_result.rs:44:5 +error: `unwrap` used in a function that returns an `Option` + --> tests/ui/unwrap_in_result.rs:49:17 | -LL | / fn in_closure(a: Option) -> Option { -LL | | -LL | | let c = || a.unwrap(); -LL | | Some(c()) -LL | | } - | |_____^ +LL | let _ = a.unwrap(); + | ^^^^^^^^^^ | - = help: unwrap and expect should not be used in a function that returns result or option -note: potential non-recoverable error(s) - --> tests/ui/unwrap_in_result.rs:46:20 +note: in this function signature + --> tests/ui/unwrap_in_result.rs:43:39 | -LL | let c = || a.unwrap(); - | ^^^^^^^^^^ +LL | fn in_closure(a: Option) -> Option { + | ^^^^^^^^^^^^ + = help: consider using the `?` operator -error: aborting due to 3 previous errors +error: `unwrap` used in a function that returns an `Option` + --> tests/ui/unwrap_in_result.rs:58:22 + | +LL | Some(b.unwrap()) + | ^^^^^^^^^^ + | +note: in this function signature + --> tests/ui/unwrap_in_result.rs:57:48 + | +LL | const fn inner(b: Option) -> Option { + | ^^^^^^^^^^^^ + = help: consider using the `?` operator + +error: `unwrap` used in a function that returns an `Option` + --> tests/ui/unwrap_in_result.rs:71:22 + | +LL | Some(b.unwrap()) + | ^^^^^^^^^^ + | +note: in this function signature + --> tests/ui/unwrap_in_result.rs:70:48 + | +LL | const fn inner(b: Option) -> Option { + | ^^^^^^^^^^^^ + = help: consider using the `?` operator + +error: `unwrap` used in a function that returns an `Option` + --> tests/ui/unwrap_in_result.rs:89:9 + | +LL | _ = Option::unwrap(Some(3)); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +note: in this function signature + --> tests/ui/unwrap_in_result.rs:88:30 + | +LL | fn type_relative_unwrap() -> Option<()> { + | ^^^^^^^^^^ + = help: consider using the `?` operator + +error: `unwrap` used in a function that returns a `Result` + --> tests/ui/unwrap_in_result.rs:99:5 + | +LL | A::bad_divisible_by_3("3".to_string()).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: in this function signature + --> tests/ui/unwrap_in_result.rs:98:14 + | +LL | fn main() -> Result<(), ()> { + | ^^^^^^^^^^^^^^ + = help: consider using the `?` operator or calling the `.map_err()` method + +error: `unwrap` used in a function that returns a `Result` + --> tests/ui/unwrap_in_result.rs:101:5 + | +LL | A::good_divisible_by_3("3".to_string()).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `unwrap` used in a function that returns a `Result` + --> tests/ui/unwrap_in_result.rs:103:5 + | +LL | Result::unwrap(A::good_divisible_by_3("3".to_string())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 9 previous errors From 6a4ab6837790ac8fe74cbd269c3644b9ec83245b Mon Sep 17 00:00:00 2001 From: usamoi Date: Sat, 14 Jun 2025 18:45:05 +0800 Subject: [PATCH 085/116] check f16 and f128 in float_equality_without_abs --- .../operators/float_equality_without_abs.rs | 6 +++--- tests/ui/float_equality_without_abs.rs | 14 ++++++++++++-- tests/ui/float_equality_without_abs.stderr | 18 +++++++++++++++++- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/operators/float_equality_without_abs.rs b/clippy_lints/src/operators/float_equality_without_abs.rs index b5f0d7197bbc..17fa8017c978 100644 --- a/clippy_lints/src/operators/float_equality_without_abs.rs +++ b/clippy_lints/src/operators/float_equality_without_abs.rs @@ -34,11 +34,11 @@ pub(crate) fn check<'tcx>( val_r, ) = lhs.kind - // right hand side matches either f32::EPSILON or f64::EPSILON + // right hand side matches _::EPSILON && let ExprKind::Path(ref epsilon_path) = rhs.kind && let Res::Def(DefKind::AssocConst, def_id) = cx.qpath_res(epsilon_path, rhs.hir_id) - && let Some(epsilon) = cx.tcx.get_diagnostic_name(def_id) - && matches!(epsilon, sym::f32_epsilon| sym::f64_epsilon) + && let Some(sym) = cx.tcx.get_diagnostic_name(def_id) + && matches!(sym, sym::f16_epsilon | sym::f32_epsilon | sym::f64_epsilon | sym::f128_epsilon) // values of the subtractions on the left hand side are of the type float && let t_val_l = cx.typeck_results().expr_ty(val_l) diff --git a/tests/ui/float_equality_without_abs.rs b/tests/ui/float_equality_without_abs.rs index a1548db6710d..e1dd79026839 100644 --- a/tests/ui/float_equality_without_abs.rs +++ b/tests/ui/float_equality_without_abs.rs @@ -1,8 +1,8 @@ +#![feature(f128)] +#![feature(f16)] #![warn(clippy::float_equality_without_abs)] //@no-rustfix: suggestions cause type ambiguity -// FIXME(f16_f128): add tests for these types when abs is available - pub fn is_roughly_equal(a: f32, b: f32) -> bool { (a - b) < f32::EPSILON //~^ float_equality_without_abs @@ -44,10 +44,20 @@ pub fn main() { let _ = f32::EPSILON > 1.0 - 2.0; //~^ float_equality_without_abs + let _ = (a as f16 - b as f16) < f16::EPSILON; + //~^ float_equality_without_abs + + let _ = (a as f128 - b as f128) < f128::EPSILON; + //~^ float_equality_without_abs + // those are correct + let _ = (a as f16 - b as f16).abs() < f16::EPSILON; let _ = (a - b).abs() < f32::EPSILON; let _ = (a as f64 - b as f64).abs() < f64::EPSILON; + let _ = (a as f128 - b as f128).abs() < f128::EPSILON; + let _ = f16::EPSILON > (a as f16 - b as f16).abs(); let _ = f32::EPSILON > (a - b).abs(); let _ = f64::EPSILON > (a as f64 - b as f64).abs(); + let _ = f128::EPSILON > (a as f128 - b as f128).abs(); } diff --git a/tests/ui/float_equality_without_abs.stderr b/tests/ui/float_equality_without_abs.stderr index d4c89ce72ba9..55a150dead5a 100644 --- a/tests/ui/float_equality_without_abs.stderr +++ b/tests/ui/float_equality_without_abs.stderr @@ -89,5 +89,21 @@ LL | let _ = f32::EPSILON > 1.0 - 2.0; | | | help: add `.abs()`: `(1.0 - 2.0).abs()` -error: aborting due to 11 previous errors +error: float equality check without `.abs()` + --> tests/ui/float_equality_without_abs.rs:47:13 + | +LL | let _ = (a as f16 - b as f16) < f16::EPSILON; + | ---------------------^^^^^^^^^^^^^^^ + | | + | help: add `.abs()`: `(a as f16 - b as f16).abs()` + +error: float equality check without `.abs()` + --> tests/ui/float_equality_without_abs.rs:50:13 + | +LL | let _ = (a as f128 - b as f128) < f128::EPSILON; + | -----------------------^^^^^^^^^^^^^^^^ + | | + | help: add `.abs()`: `(a as f128 - b as f128).abs()` + +error: aborting due to 13 previous errors From 852e552be4753043644294ed5cc71daec0f8eedd Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 23 Aug 2025 13:54:22 +0200 Subject: [PATCH 086/116] get rid of confusing nested `map_or`s Their default branches are even the same, which means that one of the `map_or`s could've been replaced with an `and_then`, but since we have access to let-chains, why not use that Additionally: - use `with_source_text` to avoid constructing a `SourceText` object - use `BytePos::from_usize` to avoid `allow`ing the lint --- clippy_lints/src/unused_unit.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/unused_unit.rs b/clippy_lints/src/unused_unit.rs index 3811f0fe6b55..68d1bbe6b850 100644 --- a/clippy_lints/src/unused_unit.rs +++ b/clippy_lints/src/unused_unit.rs @@ -12,7 +12,7 @@ use rustc_hir::{ use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::edition::Edition; -use rustc_span::{BytePos, Span, sym}; +use rustc_span::{BytePos, Pos as _, Span, sym}; declare_clippy_lint! { /// ### What it does @@ -160,17 +160,15 @@ fn get_def(span: Span) -> Option { fn lint_unneeded_unit_return(cx: &LateContext<'_>, ty_span: Span, span: Span) { let (ret_span, appl) = - span.with_hi(ty_span.hi()) - .get_source_text(cx) - .map_or((ty_span, Applicability::MaybeIncorrect), |src| { - position_before_rarrow(&src).map_or((ty_span, Applicability::MaybeIncorrect), |rpos| { - ( - #[expect(clippy::cast_possible_truncation)] - ty_span.with_lo(BytePos(span.lo().0 + rpos as u32)), - Applicability::MachineApplicable, - ) - }) - }); + if let Some(Some(rpos)) = span.with_hi(ty_span.hi()).with_source_text(cx, position_before_rarrow) { + ( + ty_span.with_lo(span.lo() + BytePos::from_usize(rpos)), + Applicability::MachineApplicable, + ) + } else { + (ty_span, Applicability::MaybeIncorrect) + }; + span_lint_and_sugg( cx, UNUSED_UNIT, From e185efe1315b897d21977e4dfbe4da5a63b569a2 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 23 Aug 2025 13:57:35 +0200 Subject: [PATCH 087/116] pattern-match on slices Avoids needless bounds checks (which almost definitely get optimized away, but still). And saves some characters --- clippy_lints/src/unused_unit.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/unused_unit.rs b/clippy_lints/src/unused_unit.rs index 68d1bbe6b850..3652bb68dea9 100644 --- a/clippy_lints/src/unused_unit.rs +++ b/clippy_lints/src/unused_unit.rs @@ -97,16 +97,13 @@ impl<'tcx> LateLintPass<'tcx> for UnusedUnit { } fn check_poly_trait_ref(&mut self, cx: &LateContext<'tcx>, poly: &'tcx PolyTraitRef<'tcx>) { - let segments = &poly.trait_ref.path.segments; - - if segments.len() == 1 - && matches!(segments[0].ident.name, sym::Fn | sym::FnMut | sym::FnOnce) - && let Some(args) = segments[0].args + if let [segment] = &poly.trait_ref.path.segments + && matches!(segment.ident.name, sym::Fn | sym::FnMut | sym::FnOnce) + && let Some(args) = segment.args && args.parenthesized == GenericArgsParentheses::ParenSugar - && let constraints = &args.constraints - && constraints.len() == 1 - && constraints[0].ident.name == sym::Output - && let AssocItemConstraintKind::Equality { term: Term::Ty(hir_ty) } = constraints[0].kind + && let [constraint] = &args.constraints + && constraint.ident.name == sym::Output + && let AssocItemConstraintKind::Equality { term: Term::Ty(hir_ty) } = constraint.kind && args.span_ext.hi() != poly.span.hi() && !hir_ty.span.from_expansion() && args.span_ext.hi() != hir_ty.span.hi() From 94d3352c082ea0a48ee07db8205753407102ec3f Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 23 Aug 2025 14:28:08 +0200 Subject: [PATCH 088/116] `unused_unit`: don't lint on closure return types --- clippy_lints/src/unused_unit.rs | 21 ++++++++++-------- tests/ui/unused_unit.edition2021.fixed | 27 ++++++++++++++++++----- tests/ui/unused_unit.edition2021.stderr | 6 ++--- tests/ui/unused_unit.edition2024.fixed | 29 ++++++++++++++++++++----- tests/ui/unused_unit.rs | 29 ++++++++++++++++++++----- 5 files changed, 83 insertions(+), 29 deletions(-) diff --git a/clippy_lints/src/unused_unit.rs b/clippy_lints/src/unused_unit.rs index 3652bb68dea9..d503bd3379b0 100644 --- a/clippy_lints/src/unused_unit.rs +++ b/clippy_lints/src/unused_unit.rs @@ -6,8 +6,8 @@ use rustc_errors::Applicability; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::FnKind; use rustc_hir::{ - AssocItemConstraintKind, Body, Expr, ExprKind, FnDecl, FnRetTy, GenericArgsParentheses, Node, PolyTraitRef, Term, - Ty, TyKind, + AssocItemConstraintKind, Body, Expr, ExprKind, FnDecl, FnRetTy, GenericArgsParentheses, PolyTraitRef, Term, Ty, + TyKind, }; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -49,19 +49,22 @@ impl<'tcx> LateLintPass<'tcx> for UnusedUnit { decl: &'tcx FnDecl<'tcx>, body: &'tcx Body<'tcx>, span: Span, - def_id: LocalDefId, + _def_id: LocalDefId, ) { if let FnRetTy::Return(hir_ty) = decl.output && is_unit_ty(hir_ty) && !hir_ty.span.from_expansion() && get_def(span) == get_def(hir_ty.span) { - // implicit types in closure signatures are forbidden when `for<...>` is present - if let FnKind::Closure = kind - && let Node::Expr(expr) = cx.tcx.hir_node_by_def_id(def_id) - && let ExprKind::Closure(closure) = expr.kind - && !closure.bound_generic_params.is_empty() - { + // The explicit `-> ()` in the closure signature might be necessary for multiple reasons: + // - Implicit types in closure signatures are forbidden when `for<...>` is present + // - If the closure body ends with a function call, and that function's return type is generic, the + // `-> ()` could be required for it to be inferred + // + // There could be more reasons to have it, and, in general, we shouldn't discourage the users from + // writing more type annotations than strictly necessary, because it can help readability and + // maintainability + if let FnKind::Closure = kind { return; } diff --git a/tests/ui/unused_unit.edition2021.fixed b/tests/ui/unused_unit.edition2021.fixed index def8ef86e3c5..8e12bd2c8c7b 100644 --- a/tests/ui/unused_unit.edition2021.fixed +++ b/tests/ui/unused_unit.edition2021.fixed @@ -127,14 +127,10 @@ mod issue14577 { trait Unit {} impl Unit for () {} - fn run(f: impl FnOnce() -> R) { - f(); - } - #[allow(dependency_on_unit_never_type_fallback)] fn bar() { - run(|| { todo!() }); //~[edition2021]^ unused_unit + panic!() } struct UnitStruct; @@ -150,3 +146,24 @@ mod pr14962 { type UnusedParensButNoUnit = Box; } + +mod issue15035 { + + trait Convert { + fn from(value: T) -> Self; + } + + impl Convert for () { + fn from(_value: u64) -> Self {} + } + + fn handle>(value: u64) -> T { + Convert::from(value) + } + + pub fn f() -> Option { + let result: Result = Err(42); + // the `-> ()` is required for the inference of `handle`'s return type + result.map_err(|err| -> () { handle(err) }).ok() + } +} diff --git a/tests/ui/unused_unit.edition2021.stderr b/tests/ui/unused_unit.edition2021.stderr index 13cc20d4d7ad..9ad3c2df915e 100644 --- a/tests/ui/unused_unit.edition2021.stderr +++ b/tests/ui/unused_unit.edition2021.stderr @@ -119,10 +119,10 @@ LL | fn test3()-> (){} | ^^^^^ help: remove the `-> ()` error: unneeded unit return type - --> tests/ui/unused_unit.rs:136:15 + --> tests/ui/unused_unit.rs:131:13 | -LL | run(|| -> () { todo!() }); - | ^^^^^^ help: remove the `-> ()` +LL | fn bar() -> () { + | ^^^^^^ help: remove the `-> ()` error: aborting due to 20 previous errors diff --git a/tests/ui/unused_unit.edition2024.fixed b/tests/ui/unused_unit.edition2024.fixed index f908b958b191..688d2fe9afa2 100644 --- a/tests/ui/unused_unit.edition2024.fixed +++ b/tests/ui/unused_unit.edition2024.fixed @@ -127,14 +127,10 @@ mod issue14577 { trait Unit {} impl Unit for () {} - fn run(f: impl FnOnce() -> R) { - f(); - } - #[allow(dependency_on_unit_never_type_fallback)] - fn bar() { - run(|| -> () { todo!() }); + fn bar() -> () { //~[edition2021]^ unused_unit + panic!() } struct UnitStruct; @@ -150,3 +146,24 @@ mod pr14962 { type UnusedParensButNoUnit = Box; } + +mod issue15035 { + + trait Convert { + fn from(value: T) -> Self; + } + + impl Convert for () { + fn from(_value: u64) -> Self {} + } + + fn handle>(value: u64) -> T { + Convert::from(value) + } + + pub fn f() -> Option { + let result: Result = Err(42); + // the `-> ()` is required for the inference of `handle`'s return type + result.map_err(|err| -> () { handle(err) }).ok() + } +} diff --git a/tests/ui/unused_unit.rs b/tests/ui/unused_unit.rs index 7298ec40cc28..31e980f2655c 100644 --- a/tests/ui/unused_unit.rs +++ b/tests/ui/unused_unit.rs @@ -127,14 +127,10 @@ mod issue14577 { trait Unit {} impl Unit for () {} - fn run(f: impl FnOnce() -> R) { - f(); - } - #[allow(dependency_on_unit_never_type_fallback)] - fn bar() { - run(|| -> () { todo!() }); + fn bar() -> () { //~[edition2021]^ unused_unit + panic!() } struct UnitStruct; @@ -150,3 +146,24 @@ mod pr14962 { type UnusedParensButNoUnit = Box; } + +mod issue15035 { + + trait Convert { + fn from(value: T) -> Self; + } + + impl Convert for () { + fn from(_value: u64) -> Self {} + } + + fn handle>(value: u64) -> T { + Convert::from(value) + } + + pub fn f() -> Option { + let result: Result = Err(42); + // the `-> ()` is required for the inference of `handle`'s return type + result.map_err(|err| -> () { handle(err) }).ok() + } +} From 0cdb47d4b59c5ebaf82e8342f9c67af73e502024 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 13 Aug 2025 17:08:14 +0200 Subject: [PATCH 089/116] move `ptr_as_ptr` to under `if` allows reusing numerous variables --- clippy_lints/src/casts/mod.rs | 2 +- clippy_lints/src/casts/ptr_as_ptr.rs | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 94940b32f27f..d2e62ee56e43 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -875,6 +875,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts { cast_slice_from_raw_parts::check(cx, expr, cast_from_expr, cast_to, self.msrv); cast_ptr_alignment::check(cx, expr, cast_from, cast_to); ptr_cast_constness::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv); + ptr_as_ptr::check(cx, expr, cast_from_expr, cast_from, cast_to_hir, cast_to, self.msrv); as_ptr_cast_mut::check(cx, expr, cast_from_expr, cast_to); fn_to_numeric_cast_any::check(cx, expr, cast_from_expr, cast_from, cast_to); confusing_method_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to); @@ -916,7 +917,6 @@ impl<'tcx> LateLintPass<'tcx> for Casts { cast_slice_from_raw_parts::check_implicit_cast(cx, expr); } cast_ptr_alignment::check_cast_method(cx, expr); - ptr_as_ptr::check(cx, expr, self.msrv); cast_slice_different_sizes::check(cx, expr, self.msrv); ptr_cast_constness::check_null_ptr_cast_method(cx, expr); } diff --git a/clippy_lints/src/casts/ptr_as_ptr.rs b/clippy_lints/src/casts/ptr_as_ptr.rs index 863a0278bf38..fda849135d3d 100644 --- a/clippy_lints/src/casts/ptr_as_ptr.rs +++ b/clippy_lints/src/casts/ptr_as_ptr.rs @@ -4,9 +4,9 @@ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, Mutability, QPath, TyKind}; +use rustc_hir::{self as hir, Expr, ExprKind, Mutability, QPath, TyKind}; use rustc_lint::LateContext; -use rustc_middle::ty; +use rustc_middle::ty::{self, Ty}; use rustc_span::{Span, sym}; use super::PTR_AS_PTR; @@ -26,10 +26,16 @@ impl OmitFollowedCastReason<'_> { } } -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) { - if let ExprKind::Cast(cast_expr, cast_to_hir_ty) = expr.kind - && let (cast_from, cast_to) = (cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(expr)) - && let ty::RawPtr(_, from_mutbl) = cast_from.kind() +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &Expr<'tcx>, + cast_from_expr: &Expr<'_>, + cast_from: Ty<'_>, + cast_to_hir: &hir::Ty<'_>, + cast_to: Ty<'tcx>, + msrv: Msrv, +) { + if let ty::RawPtr(_, from_mutbl) = cast_from.kind() && let ty::RawPtr(to_pointee_ty, to_mutbl) = cast_to.kind() && matches!((from_mutbl, to_mutbl), (Mutability::Not, Mutability::Not) | (Mutability::Mut, Mutability::Mut)) @@ -40,7 +46,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) && !is_from_proc_macro(cx, expr) { let mut app = Applicability::MachineApplicable; - let turbofish = match &cast_to_hir_ty.kind { + let turbofish = match &cast_to_hir.kind { TyKind::Infer(()) => String::new(), TyKind::Ptr(mut_ty) => { if matches!(mut_ty.ty.kind, TyKind::Infer(())) { @@ -58,7 +64,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) // following `cast` does not compile because it fails to infer what type is expected // as type argument to `std::ptr::ptr_null` or `std::ptr::ptr_null_mut`, so // we omit following `cast`: - let omit_cast = if let ExprKind::Call(func, []) = cast_expr.kind + let omit_cast = if let ExprKind::Call(func, []) = cast_from_expr.kind && let ExprKind::Path(ref qpath @ QPath::Resolved(None, path)) = func.kind && let Some(method_defid) = path.res.opt_def_id() { @@ -76,7 +82,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) let method = snippet_with_applicability(cx, qpath_span_without_turbofish(method), "..", &mut app); ("try call directly", format!("{method}{turbofish}()")) } else { - let cast_expr_sugg = Sugg::hir_with_context(cx, cast_expr, expr.span.ctxt(), "_", &mut app); + let cast_expr_sugg = Sugg::hir_with_context(cx, cast_from_expr, expr.span.ctxt(), "_", &mut app); ( "try `pointer::cast`, a safer alternative", From d4a60619ac4b3f63147ed95d39f8c41bd982b86b Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 13 Aug 2025 17:09:29 +0200 Subject: [PATCH 090/116] misc: replace `match` with an equality check that's all it was doing --- clippy_lints/src/casts/ptr_as_ptr.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/casts/ptr_as_ptr.rs b/clippy_lints/src/casts/ptr_as_ptr.rs index fda849135d3d..d012380cb762 100644 --- a/clippy_lints/src/casts/ptr_as_ptr.rs +++ b/clippy_lints/src/casts/ptr_as_ptr.rs @@ -4,7 +4,7 @@ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use rustc_errors::Applicability; -use rustc_hir::{self as hir, Expr, ExprKind, Mutability, QPath, TyKind}; +use rustc_hir::{self as hir, Expr, ExprKind, QPath, TyKind}; use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty}; use rustc_span::{Span, sym}; @@ -37,8 +37,7 @@ pub(super) fn check<'tcx>( ) { if let ty::RawPtr(_, from_mutbl) = cast_from.kind() && let ty::RawPtr(to_pointee_ty, to_mutbl) = cast_to.kind() - && matches!((from_mutbl, to_mutbl), - (Mutability::Not, Mutability::Not) | (Mutability::Mut, Mutability::Mut)) + && from_mutbl == to_mutbl // The `U` in `pointer::cast` have to be `Sized` // as explained here: https://github.com/rust-lang/rust/issues/60602. && to_pointee_ty.is_sized(cx.tcx, cx.typing_env()) From a7ff2b012698235f14841e0f47c965a3fe75a501 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Fri, 6 Jun 2025 11:43:28 +0200 Subject: [PATCH 091/116] Suggest naming types before using explicit type names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `missing_transmute_annotations` will suggest naming the origin and destination types if they do not have explicit names already. Co-authored-by: Alejandra González --- .../missing_transmute_annotations.rs | 55 ++++++++++++++++--- clippy_lints/src/transmute/mod.rs | 2 +- ...missing_transmute_annotations_unfixable.rs | 29 ++++++++++ ...ing_transmute_annotations_unfixable.stderr | 36 ++++++++++++ 4 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 tests/ui/missing_transmute_annotations_unfixable.rs create mode 100644 tests/ui/missing_transmute_annotations_unfixable.stderr diff --git a/clippy_lints/src/transmute/missing_transmute_annotations.rs b/clippy_lints/src/transmute/missing_transmute_annotations.rs index 08f36a2ed5d2..543f3c45e146 100644 --- a/clippy_lints/src/transmute/missing_transmute_annotations.rs +++ b/clippy_lints/src/transmute/missing_transmute_annotations.rs @@ -1,8 +1,12 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use std::borrow::Cow; + +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::{HasSession, SpanRangeExt as _}; use rustc_errors::Applicability; -use rustc_hir::{GenericArg, HirId, LetStmt, Node, Path, TyKind}; +use rustc_hir::{Expr, GenericArg, HirId, LetStmt, Node, Path, TyKind}; use rustc_lint::LateContext; -use rustc_middle::ty::Ty; +use rustc_middle::ty::{self, Ty}; +use rustc_span::Span; use crate::transmute::MISSING_TRANSMUTE_ANNOTATIONS; @@ -38,6 +42,7 @@ fn is_function_block(cx: &LateContext<'_>, expr_hir_id: HirId) -> bool { pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, path: &Path<'tcx>, + arg: &Expr<'tcx>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>, expr_hir_id: HirId, @@ -68,14 +73,48 @@ pub(super) fn check<'tcx>( } else if is_function_block(cx, expr_hir_id) { return false; } - span_lint_and_sugg( + let span = last.ident.span.with_hi(path.span.hi()); + span_lint_and_then( cx, MISSING_TRANSMUTE_ANNOTATIONS, - last.ident.span.with_hi(path.span.hi()), + span, "transmute used without annotations", - "consider adding missing annotations", - format!("{}::<{from_ty}, {to_ty}>", last.ident), - Applicability::MaybeIncorrect, + |diag| { + let from_ty_no_name = ty_cannot_be_named(from_ty); + let to_ty_no_name = ty_cannot_be_named(to_ty); + if from_ty_no_name || to_ty_no_name { + let to_name = match (from_ty_no_name, to_ty_no_name) { + (true, false) => maybe_name_by_expr(cx, arg.span, "the origin type"), + (false, true) => "the destination type".into(), + _ => "the source and destination types".into(), + }; + diag.help(format!( + "consider giving {to_name} a name, and adding missing type annotations" + )); + } else { + diag.span_suggestion( + span, + "consider adding missing annotations", + format!("{}::<{from_ty}, {to_ty}>", last.ident), + Applicability::MaybeIncorrect, + ); + } + }, ); true } + +fn ty_cannot_be_named(ty: Ty<'_>) -> bool { + matches!( + ty.kind(), + ty::Alias(ty::AliasTyKind::Opaque | ty::AliasTyKind::Inherent, _) + ) +} + +fn maybe_name_by_expr<'a>(sess: &impl HasSession, span: Span, default: &'a str) -> Cow<'a, str> { + span.with_source_text(sess, |name| { + (name.len() + 9 < default.len()).then_some(format!("`{name}`'s type").into()) + }) + .flatten() + .unwrap_or(default.into()) +} diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index d5112e2c3f97..284728b8ebbd 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -520,7 +520,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute { | transmuting_null::check(cx, e, arg, to_ty) | transmute_null_to_fn::check(cx, e, arg, to_ty) | transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, path, self.msrv) - | missing_transmute_annotations::check(cx, path, from_ty, to_ty, e.hir_id) + | missing_transmute_annotations::check(cx, path, arg, from_ty, to_ty, e.hir_id) | transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context) | transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg, self.msrv) | transmute_int_to_bool::check(cx, e, from_ty, to_ty, arg) diff --git a/tests/ui/missing_transmute_annotations_unfixable.rs b/tests/ui/missing_transmute_annotations_unfixable.rs new file mode 100644 index 000000000000..08ba3b791ee7 --- /dev/null +++ b/tests/ui/missing_transmute_annotations_unfixable.rs @@ -0,0 +1,29 @@ +//@no-rustfix + +fn issue14984() { + async fn e() {} + async fn x() -> u32 { + 0 + } + async fn y() -> f32 { + 0.0 + }; + let mut yy = unsafe { std::ptr::read(&y()) }; + yy = unsafe { std::mem::transmute(std::ptr::read(&x())) }; + //~^ missing_transmute_annotations + + let mut zz = 0u8; + zz = unsafe { std::mem::transmute(std::ptr::read(&x())) }; + //~^ missing_transmute_annotations + + yy = unsafe { std::mem::transmute(zz) }; + //~^ missing_transmute_annotations + + fn a() -> impl Sized { + 0u32 + } + + let mut b: f32 = 0.0; + b = unsafe { std::mem::transmute(a()) }; + //~^ missing_transmute_annotations +} diff --git a/tests/ui/missing_transmute_annotations_unfixable.stderr b/tests/ui/missing_transmute_annotations_unfixable.stderr new file mode 100644 index 000000000000..83efdce13f7e --- /dev/null +++ b/tests/ui/missing_transmute_annotations_unfixable.stderr @@ -0,0 +1,36 @@ +error: transmute used without annotations + --> tests/ui/missing_transmute_annotations_unfixable.rs:12:29 + | +LL | yy = unsafe { std::mem::transmute(std::ptr::read(&x())) }; + | ^^^^^^^^^ + | + = help: consider giving the source and destination types a name, and adding missing type annotations + = note: `-D clippy::missing-transmute-annotations` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::missing_transmute_annotations)]` + +error: transmute used without annotations + --> tests/ui/missing_transmute_annotations_unfixable.rs:16:29 + | +LL | zz = unsafe { std::mem::transmute(std::ptr::read(&x())) }; + | ^^^^^^^^^ + | + = help: consider giving the origin type a name, and adding missing type annotations + +error: transmute used without annotations + --> tests/ui/missing_transmute_annotations_unfixable.rs:19:29 + | +LL | yy = unsafe { std::mem::transmute(zz) }; + | ^^^^^^^^^ + | + = help: consider giving the destination type a name, and adding missing type annotations + +error: transmute used without annotations + --> tests/ui/missing_transmute_annotations_unfixable.rs:27:28 + | +LL | b = unsafe { std::mem::transmute(a()) }; + | ^^^^^^^^^ + | + = help: consider giving `a()`'s type a name, and adding missing type annotations + +error: aborting due to 4 previous errors + From 597d5a58ce0a3bf84f79f89ea2cdd4f09b11177a Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 8 Aug 2025 10:08:35 +0200 Subject: [PATCH 092/116] `unnecessary_mut_passed`: add structured suggestion --- clippy_lints/src/mut_reference.rs | 13 ++- tests/ui/mut_reference.fixed | 170 ++++++++++++++++++++++++++++++ tests/ui/mut_reference.rs | 152 ++++++++++++++++++++++---- tests/ui/mut_reference.stderr | 76 +++++++++++-- 4 files changed, 376 insertions(+), 35 deletions(-) create mode 100644 tests/ui/mut_reference.fixed diff --git a/clippy_lints/src/mut_reference.rs b/clippy_lints/src/mut_reference.rs index 31f51b457540..ec93ef97cfaf 100644 --- a/clippy_lints/src/mut_reference.rs +++ b/clippy_lints/src/mut_reference.rs @@ -1,4 +1,6 @@ -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; @@ -83,13 +85,18 @@ fn check_arguments<'tcx>( let parameters = type_definition.fn_sig(cx.tcx).skip_binder().inputs(); for (argument, parameter) in iter::zip(arguments, parameters) { if let ty::Ref(_, _, Mutability::Not) | ty::RawPtr(_, Mutability::Not) = parameter.kind() - && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) = argument.kind + && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, arg) = argument.kind { - span_lint( + let mut applicability = Applicability::MachineApplicable; + let sugg = Sugg::hir_with_applicability(cx, arg, "_", &mut applicability).addr(); + span_lint_and_sugg( cx, UNNECESSARY_MUT_PASSED, argument.span, format!("the {fn_kind} `{name}` doesn't need a mutable reference"), + "remove this `mut`", + sugg.to_string(), + applicability, ); } } diff --git a/tests/ui/mut_reference.fixed b/tests/ui/mut_reference.fixed new file mode 100644 index 000000000000..03d854099e64 --- /dev/null +++ b/tests/ui/mut_reference.fixed @@ -0,0 +1,170 @@ +#![allow(clippy::mut_mut)] + +fn takes_ref(a: &i32) {} +fn takes_refmut(a: &mut i32) {} +fn takes_ref_ref(a: &&i32) {} +fn takes_refmut_ref(a: &mut &i32) {} +fn takes_ref_refmut(a: &&mut i32) {} +fn takes_refmut_refmut(a: &mut &mut i32) {} +fn takes_raw_const(a: *const i32) {} +fn takes_raw_mut(a: *mut i32) {} + +mod issue11268 { + macro_rules! x { + (1 $f:expr) => { + $f(&mut 1); + }; + (2 $f:expr) => { + $f(&mut &1) + }; + (3 $f:expr) => { + $f(&mut &mut 1) + }; + (4 $f:expr) => { + let mut a = 1; + $f(&raw mut a) + }; + } + + fn f() { + x!(1 super::takes_ref); + x!(1 super::takes_refmut); + x!(2 super::takes_refmut_ref); + x!(3 super::takes_ref_refmut); + x!(3 super::takes_refmut_refmut); + x!(4 super::takes_raw_const); + x!(4 super::takes_raw_mut); + } +} + +struct MyStruct; + +impl MyStruct { + fn takes_ref(&self, a: &i32) {} + fn takes_refmut(&self, a: &mut i32) {} + fn takes_ref_ref(&self, a: &&i32) {} + fn takes_refmut_ref(&self, a: &mut &i32) {} + fn takes_ref_refmut(&self, a: &&mut i32) {} + fn takes_refmut_refmut(&self, a: &mut &mut i32) {} + fn takes_raw_const(&self, a: *const i32) {} + fn takes_raw_mut(&self, a: *mut i32) {} +} + +#[warn(clippy::unnecessary_mut_passed)] +fn main() { + // Functions + takes_ref(&42); + //~^ unnecessary_mut_passed + takes_ref_ref(&&42); + //~^ unnecessary_mut_passed + takes_ref_refmut(&&mut 42); + //~^ unnecessary_mut_passed + takes_raw_const(&42); + //~^ unnecessary_mut_passed + + let as_ptr: fn(&i32) = takes_ref; + as_ptr(&42); + //~^ unnecessary_mut_passed + let as_ptr: fn(&&i32) = takes_ref_ref; + as_ptr(&&42); + //~^ unnecessary_mut_passed + let as_ptr: fn(&&mut i32) = takes_ref_refmut; + as_ptr(&&mut 42); + //~^ unnecessary_mut_passed + let as_ptr: fn(*const i32) = takes_raw_const; + as_ptr(&42); + //~^ unnecessary_mut_passed + + // Methods + let my_struct = MyStruct; + my_struct.takes_ref(&42); + //~^ unnecessary_mut_passed + my_struct.takes_ref_ref(&&42); + //~^ unnecessary_mut_passed + my_struct.takes_ref_refmut(&&mut 42); + //~^ unnecessary_mut_passed + my_struct.takes_raw_const(&42); + //~^ unnecessary_mut_passed + + // No error + + // Functions + takes_ref(&42); + let as_ptr: fn(&i32) = takes_ref; + as_ptr(&42); + + takes_refmut(&mut 42); + let as_ptr: fn(&mut i32) = takes_refmut; + as_ptr(&mut 42); + + takes_ref_ref(&&42); + let as_ptr: fn(&&i32) = takes_ref_ref; + as_ptr(&&42); + + takes_refmut_ref(&mut &42); + let as_ptr: fn(&mut &i32) = takes_refmut_ref; + as_ptr(&mut &42); + + takes_ref_refmut(&&mut 42); + let as_ptr: fn(&&mut i32) = takes_ref_refmut; + as_ptr(&&mut 42); + + takes_refmut_refmut(&mut &mut 42); + let as_ptr: fn(&mut &mut i32) = takes_refmut_refmut; + as_ptr(&mut &mut 42); + + takes_raw_const(&42); + let as_ptr: fn(*const i32) = takes_raw_const; + as_ptr(&42); + + takes_raw_mut(&mut 42); + let as_ptr: fn(*mut i32) = takes_raw_mut; + as_ptr(&mut 42); + + let a = &mut 42; + let b = &mut &42; + let c = &mut &mut 42; + takes_ref(a); + takes_ref_ref(b); + takes_ref_refmut(c); + takes_raw_const(a); + + // Methods + my_struct.takes_ref(&42); + my_struct.takes_refmut(&mut 42); + my_struct.takes_ref_ref(&&42); + my_struct.takes_refmut_ref(&mut &42); + my_struct.takes_ref_refmut(&&mut 42); + my_struct.takes_refmut_refmut(&mut &mut 42); + my_struct.takes_raw_const(&42); + my_struct.takes_raw_mut(&mut 42); + my_struct.takes_ref(a); + my_struct.takes_ref_ref(b); + my_struct.takes_ref_refmut(c); + my_struct.takes_raw_const(a); + my_struct.takes_raw_mut(a); +} + +// not supported currently +fn raw_ptrs(my_struct: MyStruct) { + let mut n = 42; + + takes_raw_const(&raw mut n); + + let as_ptr: fn(*const i32) = takes_raw_const; + as_ptr(&raw mut n); + + my_struct.takes_raw_const(&raw mut n); + + // No error + + takes_raw_const(&raw const n); + takes_raw_mut(&raw mut n); + + let a = &raw mut n; + takes_raw_const(a); + + my_struct.takes_raw_const(&raw const n); + my_struct.takes_raw_mut(&raw mut n); + my_struct.takes_raw_const(a); +} diff --git a/tests/ui/mut_reference.rs b/tests/ui/mut_reference.rs index f664c373cdc3..80e3f5069277 100644 --- a/tests/ui/mut_reference.rs +++ b/tests/ui/mut_reference.rs @@ -1,60 +1,170 @@ -#![allow(unused_variables, dead_code)] -//@no-rustfix -fn takes_an_immutable_reference(a: &i32) {} -fn takes_a_mutable_reference(a: &mut i32) {} +#![allow(clippy::mut_mut)] + +fn takes_ref(a: &i32) {} +fn takes_refmut(a: &mut i32) {} +fn takes_ref_ref(a: &&i32) {} +fn takes_refmut_ref(a: &mut &i32) {} +fn takes_ref_refmut(a: &&mut i32) {} +fn takes_refmut_refmut(a: &mut &mut i32) {} +fn takes_raw_const(a: *const i32) {} +fn takes_raw_mut(a: *mut i32) {} mod issue11268 { macro_rules! x { - ($f:expr) => { + (1 $f:expr) => { $f(&mut 1); }; + (2 $f:expr) => { + $f(&mut &1) + }; + (3 $f:expr) => { + $f(&mut &mut 1) + }; + (4 $f:expr) => { + let mut a = 1; + $f(&raw mut a) + }; } fn f() { - x!(super::takes_an_immutable_reference); - x!(super::takes_a_mutable_reference); + x!(1 super::takes_ref); + x!(1 super::takes_refmut); + x!(2 super::takes_refmut_ref); + x!(3 super::takes_ref_refmut); + x!(3 super::takes_refmut_refmut); + x!(4 super::takes_raw_const); + x!(4 super::takes_raw_mut); } } struct MyStruct; impl MyStruct { - fn takes_an_immutable_reference(&self, a: &i32) {} - - fn takes_a_mutable_reference(&self, a: &mut i32) {} + fn takes_ref(&self, a: &i32) {} + fn takes_refmut(&self, a: &mut i32) {} + fn takes_ref_ref(&self, a: &&i32) {} + fn takes_refmut_ref(&self, a: &mut &i32) {} + fn takes_ref_refmut(&self, a: &&mut i32) {} + fn takes_refmut_refmut(&self, a: &mut &mut i32) {} + fn takes_raw_const(&self, a: *const i32) {} + fn takes_raw_mut(&self, a: *mut i32) {} } #[warn(clippy::unnecessary_mut_passed)] fn main() { // Functions - takes_an_immutable_reference(&mut 42); + takes_ref(&mut 42); + //~^ unnecessary_mut_passed + takes_ref_ref(&mut &42); + //~^ unnecessary_mut_passed + takes_ref_refmut(&mut &mut 42); + //~^ unnecessary_mut_passed + takes_raw_const(&mut 42); //~^ unnecessary_mut_passed - let as_ptr: fn(&i32) = takes_an_immutable_reference; + let as_ptr: fn(&i32) = takes_ref; + as_ptr(&mut 42); + //~^ unnecessary_mut_passed + let as_ptr: fn(&&i32) = takes_ref_ref; + as_ptr(&mut &42); + //~^ unnecessary_mut_passed + let as_ptr: fn(&&mut i32) = takes_ref_refmut; + as_ptr(&mut &mut 42); + //~^ unnecessary_mut_passed + let as_ptr: fn(*const i32) = takes_raw_const; as_ptr(&mut 42); //~^ unnecessary_mut_passed // Methods let my_struct = MyStruct; - my_struct.takes_an_immutable_reference(&mut 42); + my_struct.takes_ref(&mut 42); + //~^ unnecessary_mut_passed + my_struct.takes_ref_ref(&mut &42); + //~^ unnecessary_mut_passed + my_struct.takes_ref_refmut(&mut &mut 42); + //~^ unnecessary_mut_passed + my_struct.takes_raw_const(&mut 42); //~^ unnecessary_mut_passed // No error // Functions - takes_an_immutable_reference(&42); - let as_ptr: fn(&i32) = takes_an_immutable_reference; + takes_ref(&42); + let as_ptr: fn(&i32) = takes_ref; as_ptr(&42); - takes_a_mutable_reference(&mut 42); - let as_ptr: fn(&mut i32) = takes_a_mutable_reference; + takes_refmut(&mut 42); + let as_ptr: fn(&mut i32) = takes_refmut; + as_ptr(&mut 42); + + takes_ref_ref(&&42); + let as_ptr: fn(&&i32) = takes_ref_ref; + as_ptr(&&42); + + takes_refmut_ref(&mut &42); + let as_ptr: fn(&mut &i32) = takes_refmut_ref; + as_ptr(&mut &42); + + takes_ref_refmut(&&mut 42); + let as_ptr: fn(&&mut i32) = takes_ref_refmut; + as_ptr(&&mut 42); + + takes_refmut_refmut(&mut &mut 42); + let as_ptr: fn(&mut &mut i32) = takes_refmut_refmut; + as_ptr(&mut &mut 42); + + takes_raw_const(&42); + let as_ptr: fn(*const i32) = takes_raw_const; + as_ptr(&42); + + takes_raw_mut(&mut 42); + let as_ptr: fn(*mut i32) = takes_raw_mut; as_ptr(&mut 42); let a = &mut 42; - takes_an_immutable_reference(a); + let b = &mut &42; + let c = &mut &mut 42; + takes_ref(a); + takes_ref_ref(b); + takes_ref_refmut(c); + takes_raw_const(a); // Methods - my_struct.takes_an_immutable_reference(&42); - my_struct.takes_a_mutable_reference(&mut 42); - my_struct.takes_an_immutable_reference(a); + my_struct.takes_ref(&42); + my_struct.takes_refmut(&mut 42); + my_struct.takes_ref_ref(&&42); + my_struct.takes_refmut_ref(&mut &42); + my_struct.takes_ref_refmut(&&mut 42); + my_struct.takes_refmut_refmut(&mut &mut 42); + my_struct.takes_raw_const(&42); + my_struct.takes_raw_mut(&mut 42); + my_struct.takes_ref(a); + my_struct.takes_ref_ref(b); + my_struct.takes_ref_refmut(c); + my_struct.takes_raw_const(a); + my_struct.takes_raw_mut(a); +} + +// not supported currently +fn raw_ptrs(my_struct: MyStruct) { + let mut n = 42; + + takes_raw_const(&raw mut n); + + let as_ptr: fn(*const i32) = takes_raw_const; + as_ptr(&raw mut n); + + my_struct.takes_raw_const(&raw mut n); + + // No error + + takes_raw_const(&raw const n); + takes_raw_mut(&raw mut n); + + let a = &raw mut n; + takes_raw_const(a); + + my_struct.takes_raw_const(&raw const n); + my_struct.takes_raw_mut(&raw mut n); + my_struct.takes_raw_const(a); } diff --git a/tests/ui/mut_reference.stderr b/tests/ui/mut_reference.stderr index 474221329c25..5ecfaa37416b 100644 --- a/tests/ui/mut_reference.stderr +++ b/tests/ui/mut_reference.stderr @@ -1,23 +1,77 @@ -error: the function `takes_an_immutable_reference` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:30:34 +error: the function `takes_ref` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:56:15 | -LL | takes_an_immutable_reference(&mut 42); - | ^^^^^^^ +LL | takes_ref(&mut 42); + | ^^^^^^^ help: remove this `mut`: `&42` | = note: `-D clippy::unnecessary-mut-passed` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_mut_passed)]` +error: the function `takes_ref_ref` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:58:19 + | +LL | takes_ref_ref(&mut &42); + | ^^^^^^^^ help: remove this `mut`: `&&42` + +error: the function `takes_ref_refmut` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:60:22 + | +LL | takes_ref_refmut(&mut &mut 42); + | ^^^^^^^^^^^^ help: remove this `mut`: `&&mut 42` + +error: the function `takes_raw_const` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:62:21 + | +LL | takes_raw_const(&mut 42); + | ^^^^^^^ help: remove this `mut`: `&42` + error: the function `as_ptr` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:34:12 + --> tests/ui/mut_reference.rs:66:12 | LL | as_ptr(&mut 42); - | ^^^^^^^ + | ^^^^^^^ help: remove this `mut`: `&42` -error: the method `takes_an_immutable_reference` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:39:44 +error: the function `as_ptr` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:69:12 | -LL | my_struct.takes_an_immutable_reference(&mut 42); - | ^^^^^^^ +LL | as_ptr(&mut &42); + | ^^^^^^^^ help: remove this `mut`: `&&42` -error: aborting due to 3 previous errors +error: the function `as_ptr` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:72:12 + | +LL | as_ptr(&mut &mut 42); + | ^^^^^^^^^^^^ help: remove this `mut`: `&&mut 42` + +error: the function `as_ptr` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:75:12 + | +LL | as_ptr(&mut 42); + | ^^^^^^^ help: remove this `mut`: `&42` + +error: the method `takes_ref` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:80:25 + | +LL | my_struct.takes_ref(&mut 42); + | ^^^^^^^ help: remove this `mut`: `&42` + +error: the method `takes_ref_ref` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:82:29 + | +LL | my_struct.takes_ref_ref(&mut &42); + | ^^^^^^^^ help: remove this `mut`: `&&42` + +error: the method `takes_ref_refmut` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:84:32 + | +LL | my_struct.takes_ref_refmut(&mut &mut 42); + | ^^^^^^^^^^^^ help: remove this `mut`: `&&mut 42` + +error: the method `takes_raw_const` doesn't need a mutable reference + --> tests/ui/mut_reference.rs:86:31 + | +LL | my_struct.takes_raw_const(&mut 42); + | ^^^^^^^ help: remove this `mut`: `&42` + +error: aborting due to 12 previous errors From 01fefaad0294493380c25a304e38003ca50d0b05 Mon Sep 17 00:00:00 2001 From: Valdemar Erk Date: Sat, 23 Aug 2025 11:37:53 +0200 Subject: [PATCH 093/116] add span to struct pattern rest (..) --- clippy_lints/src/equatable_if_let.rs | 2 +- clippy_lints/src/manual_let_else.rs | 4 ++-- clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs | 2 +- clippy_lints/src/utils/author.rs | 3 ++- clippy_utils/src/lib.rs | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/equatable_if_let.rs b/clippy_lints/src/equatable_if_let.rs index 72f5eaf8a4bc..c3fc09343dbf 100644 --- a/clippy_lints/src/equatable_if_let.rs +++ b/clippy_lints/src/equatable_if_let.rs @@ -53,7 +53,7 @@ fn unary_pattern(pat: &Pat<'_>) -> bool { | PatKind::Never | PatKind::Or(_) | PatKind::Err(_) => false, - PatKind::Struct(_, a, etc) => !etc && a.iter().all(|x| unary_pattern(x.pat)), + PatKind::Struct(_, a, etc) => etc.is_none() && a.iter().all(|x| unary_pattern(x.pat)), PatKind::Tuple(a, etc) | PatKind::TupleStruct(_, a, etc) => etc.as_opt_usize().is_none() && array_rec(a), PatKind::Ref(x, _) | PatKind::Box(x) | PatKind::Deref(x) | PatKind::Guard(x, _) => unary_pattern(x), PatKind::Expr(_) => true, diff --git a/clippy_lints/src/manual_let_else.rs b/clippy_lints/src/manual_let_else.rs index 5a7967bbf946..2705ef20b795 100644 --- a/clippy_lints/src/manual_let_else.rs +++ b/clippy_lints/src/manual_let_else.rs @@ -287,7 +287,7 @@ fn replace_in_pattern( } return or_pat; }, - PatKind::Struct(path, fields, has_dot_dot) => { + PatKind::Struct(path, fields, dot_dot) => { let fields = fields .iter() .map(|fld| { @@ -311,7 +311,7 @@ fn replace_in_pattern( .collect::>(); let fields_string = fields.join(", "); - let dot_dot_str = if has_dot_dot { " .." } else { "" }; + let dot_dot_str = if dot_dot.is_some() { " .." } else { "" }; let (sn_pth, _) = snippet_with_context(cx, path.span(), span.ctxt(), "", app); return format!("{sn_pth} {{ {fields_string}{dot_dot_str} }}"); }, diff --git a/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs b/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs index 2154cd5b24a5..ae09c2e87d6b 100644 --- a/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs +++ b/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs @@ -7,7 +7,7 @@ use super::REST_PAT_IN_FULLY_BOUND_STRUCTS; pub(crate) fn check(cx: &LateContext<'_>, pat: &Pat<'_>) { if !pat.span.from_expansion() - && let PatKind::Struct(QPath::Resolved(_, path), fields, true) = pat.kind + && let PatKind::Struct(QPath::Resolved(_, path), fields, Some(_)) = pat.kind && let Some(def_id) = path.res.opt_def_id() && let ty = cx.tcx.type_of(def_id).instantiate_identity() && let ty::Adt(def, _) = ty.kind() diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 2113cb92137e..ece29362a39f 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -754,7 +754,8 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { self.ident(name); sub.if_some(|p| self.pat(p)); }, - PatKind::Struct(ref qpath, fields, ignore) => { + PatKind::Struct(ref qpath, fields, etc) => { + let ignore = etc.is_some(); bind!(self, qpath, fields); kind!("Struct(ref {qpath}, {fields}, {ignore})"); self.qpath(qpath, pat); diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 8533fa855419..011c9b2f931a 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -2011,7 +2011,7 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr< false } }, - (PatKind::Struct(pat_ident, field_pats, false), ExprKind::Struct(ident, fields, hir::StructTailExpr::None)) + (PatKind::Struct(pat_ident, field_pats, None), ExprKind::Struct(ident, fields, hir::StructTailExpr::None)) if field_pats.len() == fields.len() => { // check ident From 137feef30804ae1657de6120ade81c2f70e5d00a Mon Sep 17 00:00:00 2001 From: yanglsh Date: Mon, 25 Aug 2025 08:01:37 +0800 Subject: [PATCH 094/116] fix: `async_yields_async` wrongly unmangled macros --- clippy_lints/src/async_yields_async.rs | 60 +++++++++++++++----------- clippy_utils/src/lib.rs | 20 +++++++-- tests/ui/async_yields_async.fixed | 39 +++++++++++++++++ tests/ui/async_yields_async.rs | 39 +++++++++++++++++ tests/ui/async_yields_async.stderr | 47 +++++++++++++++++++- 5 files changed, 176 insertions(+), 29 deletions(-) diff --git a/clippy_lints/src/async_yields_async.rs b/clippy_lints/src/async_yields_async.rs index 013819b0da8a..1a10db291cde 100644 --- a/clippy_lints/src/async_yields_async.rs +++ b/clippy_lints/src/async_yields_async.rs @@ -1,8 +1,12 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::source::snippet; +use clippy_utils::is_expr_async_block; +use clippy_utils::source::walk_span_to_context; +use clippy_utils::sugg::Sugg; use clippy_utils::ty::implements_trait; use rustc_errors::Applicability; -use rustc_hir::{Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, QPath}; +use rustc_hir::{ + Block, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, QPath, +}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -87,31 +91,37 @@ impl<'tcx> LateLintPass<'tcx> for AsyncYieldsAsync { let expr_ty = typeck_results.expr_ty(body_expr); if implements_trait(cx, expr_ty, future_trait_def_id, &[]) { - let return_expr_span = match &body_expr.kind { - // XXXkhuey there has to be a better way. - ExprKind::Block(block, _) => block.expr.map(|e| e.span), - ExprKind::Path(QPath::Resolved(_, path)) => Some(path.span), - _ => None, + let (return_expr, return_expr_span) = match &body_expr.kind { + ExprKind::Block(Block { expr: Some(e), .. }, _) => (*e, e.span), + ExprKind::Path(QPath::Resolved(_, path)) => (body_expr, path.span), + _ => return, }; - if let Some(return_expr_span) = return_expr_span { - span_lint_hir_and_then( - cx, - ASYNC_YIELDS_ASYNC, - body_expr.hir_id, - return_expr_span, - "an async construct yields a type which is itself awaitable", - |db| { - db.span_label(body_expr.span, "outer async construct"); - db.span_label(return_expr_span, "awaitable value not awaited"); - db.span_suggestion( - return_expr_span, - "consider awaiting this value", - format!("{}.await", snippet(cx, return_expr_span, "..")), - Applicability::MaybeIncorrect, - ); - }, - ); + + let return_expr_span = walk_span_to_context(return_expr_span, expr.span.ctxt()).unwrap_or(return_expr_span); + let mut applicability = Applicability::MaybeIncorrect; + let mut return_expr_snip = + Sugg::hir_with_context(cx, return_expr, expr.span.ctxt(), "..", &mut applicability); + if !is_expr_async_block(return_expr) { + return_expr_snip = return_expr_snip.maybe_paren(); } + + span_lint_hir_and_then( + cx, + ASYNC_YIELDS_ASYNC, + body_expr.hir_id, + return_expr_span, + "an async construct yields a type which is itself awaitable", + |db| { + db.span_label(body_expr.span, "outer async construct"); + db.span_label(return_expr_span, "awaitable value not awaited"); + db.span_suggestion( + return_expr_span, + "consider awaiting this value", + format!("{return_expr_snip}.await"), + applicability, + ); + }, + ); } } } diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 1e0833001cf7..771742aa43d8 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -102,9 +102,9 @@ use rustc_hir::hir_id::{HirIdMap, HirIdSet}; use rustc_hir::intravisit::{FnKind, Visitor, walk_expr}; use rustc_hir::{ self as hir, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstArgKind, CoroutineDesugaring, - CoroutineKind, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs, HirId, Impl, - ImplItem, ImplItemKind, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode, - Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem, + CoroutineKind, CoroutineSource, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs, + HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, + OwnerNode, Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp, def, find_attr, }; use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize}; @@ -3632,3 +3632,17 @@ pub fn expr_adjustment_requires_coercion(cx: &LateContext<'_>, expr: &Expr<'_>) ) }) } + +/// Checks if the expression is an async block (i.e., `async { ... }`). +pub fn is_expr_async_block(expr: &Expr<'_>) -> bool { + matches!( + expr.kind, + ExprKind::Closure(Closure { + kind: hir::ClosureKind::Coroutine(CoroutineKind::Desugared( + CoroutineDesugaring::Async, + CoroutineSource::Block + )), + .. + }) + ) +} diff --git a/tests/ui/async_yields_async.fixed b/tests/ui/async_yields_async.fixed index 93c573d30863..bd1b31f74ee9 100644 --- a/tests/ui/async_yields_async.fixed +++ b/tests/ui/async_yields_async.fixed @@ -80,3 +80,42 @@ fn check_expect_suppression() { } }; } + +#[allow(clippy::let_underscore_future)] +fn issue15552() { + async fn bar(i: i32) {} + + macro_rules! call_bar { + () => { + async { bar(5).await } + }; + ($e:expr) => { + bar($e) + }; + } + let x = async { call_bar!(5).await }; + //~^ async_yields_async + let y = async { call_bar!().await }; + //~^ async_yields_async + //~| async_yields_async + + use std::future::{Future, Ready}; + use std::ops::Add; + use std::pin::Pin; + use std::task::{Context, Poll}; + struct CustomFutureType; + impl Add for CustomFutureType { + type Output = Self; + fn add(self, other: Self) -> Self { + self + } + } + impl Future for CustomFutureType { + type Output = (); + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + Poll::Ready(()) + } + } + let _ = async { (CustomFutureType + CustomFutureType).await }; + //~^ async_yields_async +} diff --git a/tests/ui/async_yields_async.rs b/tests/ui/async_yields_async.rs index 166d522e1c04..605d2734157c 100644 --- a/tests/ui/async_yields_async.rs +++ b/tests/ui/async_yields_async.rs @@ -80,3 +80,42 @@ fn check_expect_suppression() { } }; } + +#[allow(clippy::let_underscore_future)] +fn issue15552() { + async fn bar(i: i32) {} + + macro_rules! call_bar { + () => { + async { bar(5) } + }; + ($e:expr) => { + bar($e) + }; + } + let x = async { call_bar!(5) }; + //~^ async_yields_async + let y = async { call_bar!() }; + //~^ async_yields_async + //~| async_yields_async + + use std::future::{Future, Ready}; + use std::ops::Add; + use std::pin::Pin; + use std::task::{Context, Poll}; + struct CustomFutureType; + impl Add for CustomFutureType { + type Output = Self; + fn add(self, other: Self) -> Self { + self + } + } + impl Future for CustomFutureType { + type Output = (); + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + Poll::Ready(()) + } + } + let _ = async { CustomFutureType + CustomFutureType }; + //~^ async_yields_async +} diff --git a/tests/ui/async_yields_async.stderr b/tests/ui/async_yields_async.stderr index 17a35076fa3f..3041cf657893 100644 --- a/tests/ui/async_yields_async.stderr +++ b/tests/ui/async_yields_async.stderr @@ -89,5 +89,50 @@ LL | | CustomFutureType LL | | }; | |_____- outer async construct -error: aborting due to 6 previous errors +error: an async construct yields a type which is itself awaitable + --> tests/ui/async_yields_async.rs:96:21 + | +LL | let x = async { call_bar!(5) }; + | --^^^^^^^^^^^^-- + | | | + | | awaitable value not awaited + | | help: consider awaiting this value: `call_bar!(5).await` + | outer async construct + +error: an async construct yields a type which is itself awaitable + --> tests/ui/async_yields_async.rs:98:21 + | +LL | let y = async { call_bar!() }; + | --^^^^^^^^^^^-- + | | | + | | awaitable value not awaited + | | help: consider awaiting this value: `call_bar!().await` + | outer async construct + +error: an async construct yields a type which is itself awaitable + --> tests/ui/async_yields_async.rs:90:21 + | +LL | async { bar(5) } + | --^^^^^^-- + | | | + | | awaitable value not awaited + | | help: consider awaiting this value: `bar(5).await` + | outer async construct +... +LL | let y = async { call_bar!() }; + | ----------- in this macro invocation + | + = note: this error originates in the macro `call_bar` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: an async construct yields a type which is itself awaitable + --> tests/ui/async_yields_async.rs:119:21 + | +LL | let _ = async { CustomFutureType + CustomFutureType }; + | --^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-- + | | | + | | awaitable value not awaited + | | help: consider awaiting this value: `(CustomFutureType + CustomFutureType).await` + | outer async construct + +error: aborting due to 10 previous errors From 54ced9fd1c9a25f00498efd04c7be1e6893e02a9 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Tue, 26 Aug 2025 06:11:54 +0800 Subject: [PATCH 095/116] `doc_markdown`: add InfiniBand and RoCE to whitelist --- book/src/lint_configuration.md | 2 +- clippy_config/src/conf.rs | 1 + tests/ui/doc/doc-fixable.fixed | 1 + tests/ui/doc/doc-fixable.rs | 1 + tests/ui/doc/doc-fixable.stderr | 44 ++++++++++++++++----------------- 5 files changed, 26 insertions(+), 23 deletions(-) diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 87405f28a656..d51467cbfae5 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -555,7 +555,7 @@ default configuration of Clippy. By default, any configuration will replace the * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`. * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. -**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "PowerPC", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` +**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "InfiniBand", "RoCE", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "PowerPC", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` --- **Affected lints:** diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 8dd27b2265cb..365d664d75fe 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -33,6 +33,7 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[ "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", + "InfiniBand", "RoCE", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "PowerPC", "WebAssembly", "NaN", "NaNs", diff --git a/tests/ui/doc/doc-fixable.fixed b/tests/ui/doc/doc-fixable.fixed index 423a73734daa..46695dc929ab 100644 --- a/tests/ui/doc/doc-fixable.fixed +++ b/tests/ui/doc/doc-fixable.fixed @@ -73,6 +73,7 @@ fn test_units() { /// GPLv2 GPLv3 /// GitHub GitLab /// IPv4 IPv6 +/// InfiniBand RoCE /// ClojureScript CoffeeScript JavaScript PostScript PureScript TypeScript /// PowerPC WebAssembly /// NaN NaNs diff --git a/tests/ui/doc/doc-fixable.rs b/tests/ui/doc/doc-fixable.rs index 8deffb4210e4..4082fa5b56f4 100644 --- a/tests/ui/doc/doc-fixable.rs +++ b/tests/ui/doc/doc-fixable.rs @@ -73,6 +73,7 @@ fn test_units() { /// GPLv2 GPLv3 /// GitHub GitLab /// IPv4 IPv6 +/// InfiniBand RoCE /// ClojureScript CoffeeScript JavaScript PostScript PureScript TypeScript /// PowerPC WebAssembly /// NaN NaNs diff --git a/tests/ui/doc/doc-fixable.stderr b/tests/ui/doc/doc-fixable.stderr index 98c26e6bec2e..2a94a8f31658 100644 --- a/tests/ui/doc/doc-fixable.stderr +++ b/tests/ui/doc/doc-fixable.stderr @@ -145,7 +145,7 @@ LL + /// `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:90:5 + --> tests/ui/doc/doc-fixable.rs:91:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -157,7 +157,7 @@ LL + /// `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:108:5 + --> tests/ui/doc/doc-fixable.rs:109:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -169,7 +169,7 @@ LL + /// `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:117:8 + --> tests/ui/doc/doc-fixable.rs:118:8 | LL | /// ## CamelCaseThing | ^^^^^^^^^^^^^^ @@ -181,7 +181,7 @@ LL + /// ## `CamelCaseThing` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:121:7 + --> tests/ui/doc/doc-fixable.rs:122:7 | LL | /// # CamelCaseThing | ^^^^^^^^^^^^^^ @@ -193,7 +193,7 @@ LL + /// # `CamelCaseThing` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:124:22 + --> tests/ui/doc/doc-fixable.rs:125:22 | LL | /// Not a title #897 CamelCaseThing | ^^^^^^^^^^^^^^ @@ -205,7 +205,7 @@ LL + /// Not a title #897 `CamelCaseThing` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:126:5 + --> tests/ui/doc/doc-fixable.rs:127:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -217,7 +217,7 @@ LL + /// `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:134:5 + --> tests/ui/doc/doc-fixable.rs:135:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -229,7 +229,7 @@ LL + /// `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:148:5 + --> tests/ui/doc/doc-fixable.rs:149:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -241,7 +241,7 @@ LL + /// `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:160:43 + --> tests/ui/doc/doc-fixable.rs:161:43 | LL | /** E.g., serialization of an empty list: FooBar | ^^^^^^ @@ -253,7 +253,7 @@ LL + /** E.g., serialization of an empty list: `FooBar` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:165:5 + --> tests/ui/doc/doc-fixable.rs:166:5 | LL | And BarQuz too. | ^^^^^^ @@ -265,7 +265,7 @@ LL + And `BarQuz` too. | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:166:1 + --> tests/ui/doc/doc-fixable.rs:167:1 | LL | be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -277,7 +277,7 @@ LL + `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:174:43 + --> tests/ui/doc/doc-fixable.rs:175:43 | LL | /** E.g., serialization of an empty list: FooBar | ^^^^^^ @@ -289,7 +289,7 @@ LL + /** E.g., serialization of an empty list: `FooBar` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:179:5 + --> tests/ui/doc/doc-fixable.rs:180:5 | LL | And BarQuz too. | ^^^^^^ @@ -301,7 +301,7 @@ LL + And `BarQuz` too. | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:180:1 + --> tests/ui/doc/doc-fixable.rs:181:1 | LL | be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -313,7 +313,7 @@ LL + `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:194:5 + --> tests/ui/doc/doc-fixable.rs:195:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -325,7 +325,7 @@ LL + /// `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:214:22 + --> tests/ui/doc/doc-fixable.rs:215:22 | LL | /// An iterator over mycrate::Collection's values. | ^^^^^^^^^^^^^^^^^^^ @@ -337,7 +337,7 @@ LL + /// An iterator over `mycrate::Collection`'s values. | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:239:34 + --> tests/ui/doc/doc-fixable.rs:240:34 | LL | /// Foo \[bar\] \[baz\] \[qux\]. DocMarkdownLint | ^^^^^^^^^^^^^^^ @@ -349,7 +349,7 @@ LL + /// Foo \[bar\] \[baz\] \[qux\]. `DocMarkdownLint` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:263:22 + --> tests/ui/doc/doc-fixable.rs:264:22 | LL | /// There is no try (do() or do_not()). | ^^^^ @@ -361,7 +361,7 @@ LL + /// There is no try (`do()` or do_not()). | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:263:30 + --> tests/ui/doc/doc-fixable.rs:264:30 | LL | /// There is no try (do() or do_not()). | ^^^^^^^^ @@ -373,7 +373,7 @@ LL + /// There is no try (do() or `do_not()`). | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:268:5 + --> tests/ui/doc/doc-fixable.rs:269:5 | LL | /// ABes | ^^^^ @@ -385,7 +385,7 @@ LL + /// `ABes` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:275:9 + --> tests/ui/doc/doc-fixable.rs:276:9 | LL | /// foo() | ^^^^^ @@ -397,7 +397,7 @@ LL + /// `foo()` | error: you should put bare URLs between `<`/`>` or make a proper Markdown link - --> tests/ui/doc/doc-fixable.rs:280:5 + --> tests/ui/doc/doc-fixable.rs:281:5 | LL | /// https://github.com/rust-lang/rust-clippy/pull/12836 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `` From 1de71c8086e1a2aac8da1eed15e1152e8dad7c0f Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 22 Aug 2025 16:57:41 -0700 Subject: [PATCH 096/116] ptr_cast_constness: avoid suggesting unresolvable method call --- clippy_lints/src/casts/ptr_cast_constness.rs | 13 +++++++++++-- tests/ui/ptr_cast_constness.fixed | 6 ++++++ tests/ui/ptr_cast_constness.rs | 6 ++++++ tests/ui/ptr_cast_constness.stderr | 8 +++++++- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/casts/ptr_cast_constness.rs b/clippy_lints/src/casts/ptr_cast_constness.rs index 5ab399152519..c0f13f5e5782 100644 --- a/clippy_lints/src/casts/ptr_cast_constness.rs +++ b/clippy_lints/src/casts/ptr_cast_constness.rs @@ -4,7 +4,7 @@ use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use clippy_utils::{std_or_core, sym}; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, QPath}; +use rustc_hir::{self as hir, Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty, TypeVisitableExt}; @@ -51,7 +51,16 @@ pub(super) fn check<'tcx>( if msrv.meets(cx, msrvs::POINTER_CAST_CONSTNESS) { let mut app = Applicability::MachineApplicable; - let sugg = Sugg::hir_with_context(cx, cast_from_expr, expr.span.ctxt(), "_", &mut app); + let sugg = if let ExprKind::Cast(nested_from, nested_hir_ty) = cast_from_expr.kind + && let hir::TyKind::Ptr(ptr_ty) = nested_hir_ty.kind + && let hir::TyKind::Infer(()) = ptr_ty.ty.kind + { + // `(foo as *const _).cast_mut()` fails method name resolution + // avoid this by `as`-ing the full type + Sugg::hir_with_context(cx, nested_from, expr.span.ctxt(), "_", &mut app).as_ty(cast_from) + } else { + Sugg::hir_with_context(cx, cast_from_expr, expr.span.ctxt(), "_", &mut app) + }; let constness = to_mutbl.ptr_str(); span_lint_and_sugg( diff --git a/tests/ui/ptr_cast_constness.fixed b/tests/ui/ptr_cast_constness.fixed index 79bfae1f7ebb..cf57de53d9f3 100644 --- a/tests/ui/ptr_cast_constness.fixed +++ b/tests/ui/ptr_cast_constness.fixed @@ -106,3 +106,9 @@ fn issue14621() { let _ = std::ptr::addr_of_mut!(local).cast_const(); //~^ ptr_cast_constness } + +fn issue11317() { + let r = &0_u32; + let _ptr: *mut u32 = (r as *const u32).cast_mut(); + //~^ ptr_cast_constness +} diff --git a/tests/ui/ptr_cast_constness.rs b/tests/ui/ptr_cast_constness.rs index f6590dabd5b8..ea53a0fa8c50 100644 --- a/tests/ui/ptr_cast_constness.rs +++ b/tests/ui/ptr_cast_constness.rs @@ -106,3 +106,9 @@ fn issue14621() { let _ = std::ptr::addr_of_mut!(local) as *const _; //~^ ptr_cast_constness } + +fn issue11317() { + let r = &0_u32; + let _ptr: *mut u32 = r as *const _ as *mut _; + //~^ ptr_cast_constness +} diff --git a/tests/ui/ptr_cast_constness.stderr b/tests/ui/ptr_cast_constness.stderr index 0b1644168ff5..4adb5cc5ad7f 100644 --- a/tests/ui/ptr_cast_constness.stderr +++ b/tests/ui/ptr_cast_constness.stderr @@ -89,5 +89,11 @@ error: `as` casting between raw pointers while changing only its constness LL | let _ = std::ptr::addr_of_mut!(local) as *const _; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast_const`, a safer alternative: `std::ptr::addr_of_mut!(local).cast_const()` -error: aborting due to 14 previous errors +error: `as` casting between raw pointers while changing only its constness + --> tests/ui/ptr_cast_constness.rs:112:26 + | +LL | let _ptr: *mut u32 = r as *const _ as *mut _; + | ^^^^^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast_mut`, a safer alternative: `(r as *const u32).cast_mut()` + +error: aborting due to 15 previous errors From cfc9b8fad0629e3cef6d958bd4cc9f3fe135ce43 Mon Sep 17 00:00:00 2001 From: Boot0x7c00 Date: Tue, 26 Aug 2025 03:15:08 +0000 Subject: [PATCH 097/116] using full path in suggestion of --- clippy_lints/src/methods/clone_on_ref_ptr.rs | 7 ++++--- tests/ui/unnecessary_clone.stderr | 12 ++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/methods/clone_on_ref_ptr.rs b/clippy_lints/src/methods/clone_on_ref_ptr.rs index 96e2de0dc1cb..65583c6a9811 100644 --- a/clippy_lints/src/methods/clone_on_ref_ptr.rs +++ b/clippy_lints/src/methods/clone_on_ref_ptr.rs @@ -24,9 +24,10 @@ pub(super) fn check( && let Some(name) = cx.tcx.get_diagnostic_name(adt.did()) { let caller_type = match name { - sym::Rc => "Rc", - sym::Arc => "Arc", - sym::RcWeak | sym::ArcWeak => "Weak", + sym::Rc => "std::rc::Rc", + sym::Arc => "std::sync::Arc", + sym::RcWeak => "std::rc::Weak", + sym::ArcWeak => "std::sync::Weak", _ => return, }; span_lint_and_then( diff --git a/tests/ui/unnecessary_clone.stderr b/tests/ui/unnecessary_clone.stderr index a4fdf09cd5dc..17518e123d12 100644 --- a/tests/ui/unnecessary_clone.stderr +++ b/tests/ui/unnecessary_clone.stderr @@ -2,7 +2,7 @@ error: using `.clone()` on a ref-counted pointer --> tests/ui/unnecessary_clone.rs:23:5 | LL | rc.clone(); - | ^^^^^^^^^^ help: try: `Rc::::clone(&rc)` + | ^^^^^^^^^^ help: try: `std::rc::Rc::::clone(&rc)` | = note: `-D clippy::clone-on-ref-ptr` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::clone_on_ref_ptr)]` @@ -11,25 +11,25 @@ error: using `.clone()` on a ref-counted pointer --> tests/ui/unnecessary_clone.rs:28:5 | LL | arc.clone(); - | ^^^^^^^^^^^ help: try: `Arc::::clone(&arc)` + | ^^^^^^^^^^^ help: try: `std::sync::Arc::::clone(&arc)` error: using `.clone()` on a ref-counted pointer --> tests/ui/unnecessary_clone.rs:33:5 | LL | rcweak.clone(); - | ^^^^^^^^^^^^^^ help: try: `Weak::::clone(&rcweak)` + | ^^^^^^^^^^^^^^ help: try: `std::rc::Weak::::clone(&rcweak)` error: using `.clone()` on a ref-counted pointer --> tests/ui/unnecessary_clone.rs:38:5 | LL | arc_weak.clone(); - | ^^^^^^^^^^^^^^^^ help: try: `Weak::::clone(&arc_weak)` + | ^^^^^^^^^^^^^^^^ help: try: `std::sync::Weak::::clone(&arc_weak)` error: using `.clone()` on a ref-counted pointer --> tests/ui/unnecessary_clone.rs:44:33 | LL | let _: Arc = x.clone(); - | ^^^^^^^^^ help: try: `Arc::::clone(&x)` + | ^^^^^^^^^ help: try: `std::sync::Arc::::clone(&x)` error: using `clone` on type `T` which implements the `Copy` trait --> tests/ui/unnecessary_clone.rs:49:5 @@ -56,7 +56,7 @@ error: using `.clone()` on a ref-counted pointer --> tests/ui/unnecessary_clone.rs:108:14 | LL | Some(try_opt!(Some(rc)).clone()) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Rc::::clone(&try_opt!(Some(rc)))` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::rc::Rc::::clone(&try_opt!(Some(rc)))` error: aborting due to 9 previous errors From 79f3ae4b724fe531fb17f440dcca47ccb4e95b09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 25 Aug 2025 18:52:57 +0200 Subject: [PATCH 098/116] add a flag to codegen fn attrs for foreign items --- clippy_lints/src/missing_inline.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index b16924babd1c..d02952eb4870 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -190,5 +190,5 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { /// and a rustc warning would be triggered, see #15301 fn fn_is_externally_exported(cx: &LateContext<'_>, def_id: DefId) -> bool { let attrs = cx.tcx.codegen_fn_attrs(def_id); - attrs.contains_extern_indicator(cx.tcx, def_id) + attrs.contains_extern_indicator() } From a55391ce26fc7582f899975cf8ad2eaf7f6f5f36 Mon Sep 17 00:00:00 2001 From: Daniel Szoke <7881302+szokeasaurusrex@users.noreply.github.com> Date: Tue, 26 Aug 2025 14:22:16 +0200 Subject: [PATCH 099/116] docs(cognitive_complexity): Correct lint group The docs say this lint is included in `nursery`, but it is now in `restriction`. --- clippy_lints/src/cognitive_complexity.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs index 8f95c63a854f..7646aa48b772 100644 --- a/clippy_lints/src/cognitive_complexity.rs +++ b/clippy_lints/src/cognitive_complexity.rs @@ -23,8 +23,8 @@ declare_clippy_lint! { /// /// ### Known problems /// The true Cognitive Complexity of a method is not something we can - /// calculate using modern technology. This lint has been left in the - /// `nursery` so as to not mislead users into using this lint as a + /// calculate using modern technology. This lint has been left in + /// `restriction` so as to not mislead users into using this lint as a /// measurement tool. /// /// For more detailed information, see [rust-clippy#3793](https://github.com/rust-lang/rust-clippy/issues/3793) From e54afd3d7dc1f62944e5b6750ceecfdd17e1c3a3 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Tue, 26 Aug 2025 17:49:15 +0300 Subject: [PATCH 100/116] Enable clippy::panic in const contexts --- clippy_lints/src/panic_unimplemented.rs | 6 ++-- tests/ui/panicking_macros.rs | 15 ++++++++ tests/ui/panicking_macros.stderr | 46 +++++++++++++++++-------- 3 files changed, 50 insertions(+), 17 deletions(-) diff --git a/clippy_lints/src/panic_unimplemented.rs b/clippy_lints/src/panic_unimplemented.rs index 449d3da76394..43db0085f2e3 100644 --- a/clippy_lints/src/panic_unimplemented.rs +++ b/clippy_lints/src/panic_unimplemented.rs @@ -1,7 +1,7 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_in_test; use clippy_utils::macros::{is_panic, root_macro_call_first_node}; +use clippy_utils::{is_in_test, is_inside_always_const_context}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; @@ -99,7 +99,7 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let Some(macro_call) = root_macro_call_first_node(cx, expr) { if is_panic(cx, macro_call.def_id) { - if cx.tcx.hir_is_inside_const_context(expr.hir_id) + if is_inside_always_const_context(cx.tcx, expr.hir_id) || self.allow_panic_in_tests && is_in_test(cx.tcx, expr.hir_id) { return; @@ -140,7 +140,7 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented { && let Res::Def(DefKind::Fn, def_id) = expr_path.res && cx.tcx.is_diagnostic_item(sym::panic_any, def_id) { - if cx.tcx.hir_is_inside_const_context(expr.hir_id) + if is_inside_always_const_context(cx.tcx, expr.hir_id) || self.allow_panic_in_tests && is_in_test(cx.tcx, expr.hir_id) { return; diff --git a/tests/ui/panicking_macros.rs b/tests/ui/panicking_macros.rs index 65854d7eb4bf..b044be7d54ac 100644 --- a/tests/ui/panicking_macros.rs +++ b/tests/ui/panicking_macros.rs @@ -31,6 +31,20 @@ fn panic() { let b = a + 2; } +const fn panic_const() { + let a = 2; + panic!(); + //~^ panic + + panic!("message"); + //~^ panic + + panic!("{} {}", "panic with", "multiple arguments"); + //~^ panic + + let b = a + 2; +} + fn todo() { let a = 2; todo!(); @@ -114,6 +128,7 @@ fn debug_assert_msg() { fn main() { panic(); + panic_const(); todo(); unimplemented(); unreachable(); diff --git a/tests/ui/panicking_macros.stderr b/tests/ui/panicking_macros.stderr index 03e459e4ec6e..7b767ae0f65b 100644 --- a/tests/ui/panicking_macros.stderr +++ b/tests/ui/panicking_macros.stderr @@ -19,9 +19,27 @@ error: `panic` should not be present in production code LL | panic!("{} {}", "panic with", "multiple arguments"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: `todo` should not be present in production code +error: `panic` should not be present in production code --> tests/ui/panicking_macros.rs:36:5 | +LL | panic!(); + | ^^^^^^^^ + +error: `panic` should not be present in production code + --> tests/ui/panicking_macros.rs:39:5 + | +LL | panic!("message"); + | ^^^^^^^^^^^^^^^^^ + +error: `panic` should not be present in production code + --> tests/ui/panicking_macros.rs:42:5 + | +LL | panic!("{} {}", "panic with", "multiple arguments"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `todo` should not be present in production code + --> tests/ui/panicking_macros.rs:50:5 + | LL | todo!(); | ^^^^^^^ | @@ -29,19 +47,19 @@ LL | todo!(); = help: to override `-D warnings` add `#[allow(clippy::todo)]` error: `todo` should not be present in production code - --> tests/ui/panicking_macros.rs:39:5 + --> tests/ui/panicking_macros.rs:53:5 | LL | todo!("message"); | ^^^^^^^^^^^^^^^^ error: `todo` should not be present in production code - --> tests/ui/panicking_macros.rs:42:5 + --> tests/ui/panicking_macros.rs:56:5 | LL | todo!("{} {}", "panic with", "multiple arguments"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `unimplemented` should not be present in production code - --> tests/ui/panicking_macros.rs:50:5 + --> tests/ui/panicking_macros.rs:64:5 | LL | unimplemented!(); | ^^^^^^^^^^^^^^^^ @@ -50,19 +68,19 @@ LL | unimplemented!(); = help: to override `-D warnings` add `#[allow(clippy::unimplemented)]` error: `unimplemented` should not be present in production code - --> tests/ui/panicking_macros.rs:53:5 + --> tests/ui/panicking_macros.rs:67:5 | LL | unimplemented!("message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: `unimplemented` should not be present in production code - --> tests/ui/panicking_macros.rs:56:5 + --> tests/ui/panicking_macros.rs:70:5 | LL | unimplemented!("{} {}", "panic with", "multiple arguments"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: usage of the `unreachable!` macro - --> tests/ui/panicking_macros.rs:64:5 + --> tests/ui/panicking_macros.rs:78:5 | LL | unreachable!(); | ^^^^^^^^^^^^^^ @@ -71,40 +89,40 @@ LL | unreachable!(); = help: to override `-D warnings` add `#[allow(clippy::unreachable)]` error: usage of the `unreachable!` macro - --> tests/ui/panicking_macros.rs:67:5 + --> tests/ui/panicking_macros.rs:81:5 | LL | unreachable!("message"); | ^^^^^^^^^^^^^^^^^^^^^^^ error: usage of the `unreachable!` macro - --> tests/ui/panicking_macros.rs:70:5 + --> tests/ui/panicking_macros.rs:84:5 | LL | unreachable!("{} {}", "panic with", "multiple arguments"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `panic` should not be present in production code - --> tests/ui/panicking_macros.rs:78:5 + --> tests/ui/panicking_macros.rs:92:5 | LL | panic!(); | ^^^^^^^^ error: `todo` should not be present in production code - --> tests/ui/panicking_macros.rs:81:5 + --> tests/ui/panicking_macros.rs:95:5 | LL | todo!(); | ^^^^^^^ error: `unimplemented` should not be present in production code - --> tests/ui/panicking_macros.rs:84:5 + --> tests/ui/panicking_macros.rs:98:5 | LL | unimplemented!(); | ^^^^^^^^^^^^^^^^ error: usage of the `unreachable!` macro - --> tests/ui/panicking_macros.rs:87:5 + --> tests/ui/panicking_macros.rs:101:5 | LL | unreachable!(); | ^^^^^^^^^^^^^^ -error: aborting due to 16 previous errors +error: aborting due to 19 previous errors From a16500853434c8f38c14d6204457dc922a3c77ac Mon Sep 17 00:00:00 2001 From: Lieselotte <52315535+she3py@users.noreply.github.com> Date: Tue, 26 Aug 2025 18:42:26 +0200 Subject: [PATCH 101/116] Allow `--print=crate-root-lint-levels` --- src/driver.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/driver.rs b/src/driver.rs index c4076cbaa77b..6bddcbfd94ce 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -330,7 +330,8 @@ pub fn main() { // Do not run Clippy for Cargo's info queries so that invalid CLIPPY_ARGS are not cached // https://github.com/rust-lang/cargo/issues/14385 - let info_query = has_arg(&orig_args, "-vV") || has_arg(&orig_args, "--print"); + let info_query = has_arg(&orig_args, "-vV") + || arg_value(&orig_args, "--print", |val| val != "crate-root-lint-levels").is_some(); let clippy_enabled = !cap_lints_allow && relevant_package && !info_query; if clippy_enabled { From a527e9f67f4fa9cb0748b7c0073d91a07d392ac6 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Tue, 26 Aug 2025 22:33:18 +0200 Subject: [PATCH 102/116] Preserve `unsafe` blocks in `option_map_unit` suggestion --- clippy_lints/src/map_unit_fn.rs | 21 +++++++++++++-------- tests/ui/option_map_unit_fn_fixable.fixed | 9 +++++++++ tests/ui/option_map_unit_fn_fixable.rs | 9 +++++++++ tests/ui/option_map_unit_fn_fixable.stderr | 18 +++++++++++++++++- 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/map_unit_fn.rs b/clippy_lints/src/map_unit_fn.rs index af6a1b07a492..39e5289c62ae 100644 --- a/clippy_lints/src/map_unit_fn.rs +++ b/clippy_lints/src/map_unit_fn.rs @@ -116,8 +116,10 @@ fn is_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { /// The expression inside a closure may or may not have surrounding braces and /// semicolons, which causes problems when generating a suggestion. Given an /// expression that evaluates to '()' or '!', recursively remove useless braces -/// and semi-colons until is suitable for including in the suggestion template -fn reduce_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option { +/// and semi-colons until is suitable for including in the suggestion template. +/// The `bool` is `true` when the resulting `span` needs to be enclosed in an +/// `unsafe` block. +fn reduce_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<(Span, bool)> { if !is_unit_expression(cx, expr) { return None; } @@ -125,22 +127,24 @@ fn reduce_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option< match expr.kind { hir::ExprKind::Call(_, _) | hir::ExprKind::MethodCall(..) => { // Calls can't be reduced any more - Some(expr.span) + Some((expr.span, false)) }, hir::ExprKind::Block(block, _) => { + let is_unsafe = matches!(block.rules, hir::BlockCheckMode::UnsafeBlock(_)); match (block.stmts, block.expr.as_ref()) { ([], Some(inner_expr)) => { // If block only contains an expression, // reduce `{ X }` to `X` reduce_unit_expression(cx, inner_expr) + .map(|(span, inner_is_unsafe)| (span, inner_is_unsafe || is_unsafe)) }, ([inner_stmt], None) => { // If block only contains statements, // reduce `{ X; }` to `X` or `X;` match inner_stmt.kind { - hir::StmtKind::Let(local) => Some(local.span), - hir::StmtKind::Expr(e) => Some(e.span), - hir::StmtKind::Semi(..) => Some(inner_stmt.span), + hir::StmtKind::Let(local) => Some((local.span, is_unsafe)), + hir::StmtKind::Expr(e) => Some((e.span, is_unsafe)), + hir::StmtKind::Semi(..) => Some((inner_stmt.span, is_unsafe)), hir::StmtKind::Item(..) => None, } }, @@ -228,10 +232,11 @@ fn lint_map_unit_fn( let msg = suggestion_msg("closure", map_type); span_lint_and_then(cx, lint, expr.span, msg, |diag| { - if let Some(reduced_expr_span) = reduce_unit_expression(cx, closure_expr) { + if let Some((reduced_expr_span, is_unsafe)) = reduce_unit_expression(cx, closure_expr) { let mut applicability = Applicability::MachineApplicable; + let (prefix_is_unsafe, suffix_is_unsafe) = if is_unsafe { ("unsafe { ", " }") } else { ("", "") }; let suggestion = format!( - "if let {0}({1}) = {2} {{ {3} }}", + "if let {0}({1}) = {2} {{ {prefix_is_unsafe}{3}{suffix_is_unsafe} }}", variant, snippet_with_applicability(cx, binding.pat.span, "_", &mut applicability), snippet_with_applicability(cx, var_arg.span, "_", &mut applicability), diff --git a/tests/ui/option_map_unit_fn_fixable.fixed b/tests/ui/option_map_unit_fn_fixable.fixed index 55c1b8f110ce..340be7c7e932 100644 --- a/tests/ui/option_map_unit_fn_fixable.fixed +++ b/tests/ui/option_map_unit_fn_fixable.fixed @@ -102,4 +102,13 @@ fn option_map_unit_fn() { //~^ option_map_unit_fn } +fn issue15568() { + unsafe fn f(_: u32) {} + let x = Some(3); + if let Some(x) = x { unsafe { f(x) } } + //~^ option_map_unit_fn + if let Some(x) = x { unsafe { f(x) } } + //~^ option_map_unit_fn +} + fn main() {} diff --git a/tests/ui/option_map_unit_fn_fixable.rs b/tests/ui/option_map_unit_fn_fixable.rs index 5ed47e4c60b5..d902c87379b7 100644 --- a/tests/ui/option_map_unit_fn_fixable.rs +++ b/tests/ui/option_map_unit_fn_fixable.rs @@ -102,4 +102,13 @@ fn option_map_unit_fn() { //~^ option_map_unit_fn } +fn issue15568() { + unsafe fn f(_: u32) {} + let x = Some(3); + x.map(|x| unsafe { f(x) }); + //~^ option_map_unit_fn + x.map(|x| unsafe { { f(x) } }); + //~^ option_map_unit_fn +} + fn main() {} diff --git a/tests/ui/option_map_unit_fn_fixable.stderr b/tests/ui/option_map_unit_fn_fixable.stderr index 3f7abae34eea..2405aa9a7ccf 100644 --- a/tests/ui/option_map_unit_fn_fixable.stderr +++ b/tests/ui/option_map_unit_fn_fixable.stderr @@ -153,5 +153,21 @@ LL | option().map(|value| println!("{:?}", value)); | | | help: try: `if let Some(value) = option() { println!("{:?}", value) }` -error: aborting due to 19 previous errors +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> tests/ui/option_map_unit_fn_fixable.rs:108:5 + | +LL | x.map(|x| unsafe { f(x) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try: `if let Some(x) = x { unsafe { f(x) } }` + +error: called `map(f)` on an `Option` value where `f` is a closure that returns the unit type `()` + --> tests/ui/option_map_unit_fn_fixable.rs:110:5 + | +LL | x.map(|x| unsafe { { f(x) } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- + | | + | help: try: `if let Some(x) = x { unsafe { f(x) } }` + +error: aborting due to 21 previous errors From 27d005a1710e7ccd81020baca635475856485f20 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Wed, 27 Aug 2025 00:34:53 +0200 Subject: [PATCH 103/116] Document `is_unit_expr()` --- clippy_utils/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index e038a33dca39..df47b9f7e33b 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -309,6 +309,7 @@ pub fn is_lang_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: LangItem) -> cx.tcx.lang_items().get(item) == Some(did) } +/// Checks if `expr` is an empty block or an empty tuple. pub fn is_unit_expr(expr: &Expr<'_>) -> bool { matches!( expr.kind, From bb8ba698f54421b0b1d7bee5b91f48f16182650f Mon Sep 17 00:00:00 2001 From: Zihan Date: Mon, 25 Aug 2025 23:59:17 -0400 Subject: [PATCH 104/116] `unit_cmp`: don't lint on explicitly written unit expr changelog: [`unit_cmp`]: don't lint on explicitly written unit expr Signed-off-by: Zihan --- clippy_lints/src/unit_types/unit_cmp.rs | 9 ++++++--- tests/ui/unit_cmp.rs | 17 +++++++++++++++++ tests/ui/unit_cmp.stderr | 20 +++++++++++++++++++- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/unit_types/unit_cmp.rs b/clippy_lints/src/unit_types/unit_cmp.rs index 48b532968cb5..38716519e23b 100644 --- a/clippy_lints/src/unit_types/unit_cmp.rs +++ b/clippy_lints/src/unit_types/unit_cmp.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node}; -use clippy_utils::sym; +use clippy_utils::{is_unit_expr, sym}; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; @@ -16,10 +16,13 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { sym::assert_ne_macro | sym::debug_assert_ne_macro => "fail", _ => return, }; - let Some((left, _, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else { + let Some((lhs, rhs, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else { return; }; - if !cx.typeck_results().expr_ty(left).is_unit() { + if is_unit_expr(lhs) || is_unit_expr(rhs) { + return; + } + if !cx.typeck_results().expr_ty(lhs).is_unit() { return; } span_lint( diff --git a/tests/ui/unit_cmp.rs b/tests/ui/unit_cmp.rs index 93f5b87c3d2a..0a0fe3a1990b 100644 --- a/tests/ui/unit_cmp.rs +++ b/tests/ui/unit_cmp.rs @@ -68,3 +68,20 @@ fn main() { } ); } + +fn issue15559() { + fn foo() {} + assert_eq!( + //~^ unit_cmp + { + 1; + }, + foo() + ); + assert_eq!(foo(), foo()); + //~^ unit_cmp + + // don't lint on explicitly written unit expr + assert_eq!(foo(), ()); + assert_ne!((), ContainsUnit(()).0); +} diff --git a/tests/ui/unit_cmp.stderr b/tests/ui/unit_cmp.stderr index 8f26749fd860..21aa9dce1510 100644 --- a/tests/ui/unit_cmp.stderr +++ b/tests/ui/unit_cmp.stderr @@ -71,5 +71,23 @@ LL | | true; LL | | ); | |_____^ -error: aborting due to 6 previous errors +error: `assert_eq` of unit values detected. This will always succeed + --> tests/ui/unit_cmp.rs:74:5 + | +LL | / assert_eq!( +LL | | +LL | | { +LL | | 1; +LL | | }, +LL | | foo() +LL | | ); + | |_____^ + +error: `assert_eq` of unit values detected. This will always succeed + --> tests/ui/unit_cmp.rs:81:5 + | +LL | assert_eq!(foo(), foo()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 8 previous errors From 61ce0c5dc25aeb34cae36d2ea4587d6dbbfb5586 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 13 Jul 2025 16:29:39 +0200 Subject: [PATCH 105/116] `map_identity`: suggest making the variable mutable when necessary --- clippy_lints/src/methods/map_identity.rs | 96 ++++++++++++++++++------ tests/ui/map_identity.fixed | 20 ++++- tests/ui/map_identity.rs | 16 +++- tests/ui/map_identity.stderr | 66 ++++++++++------ 4 files changed, 148 insertions(+), 50 deletions(-) diff --git a/clippy_lints/src/methods/map_identity.rs b/clippy_lints/src/methods/map_identity.rs index 98def66ca149..a98cfff8bfbd 100644 --- a/clippy_lints/src/methods/map_identity.rs +++ b/clippy_lints/src/methods/map_identity.rs @@ -1,14 +1,16 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_expr_untyped_identity_function, is_trait_method, path_to_local}; -use rustc_ast::BindingMode; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::{is_copy, is_type_diagnostic_item}; +use clippy_utils::{is_expr_untyped_identity_function, is_mutable, is_trait_method, path_to_local_with_projections}; use rustc_errors::Applicability; -use rustc_hir::{self as hir, Node, PatKind}; -use rustc_lint::LateContext; +use rustc_hir::{self as hir, ExprKind, Node, PatKind}; +use rustc_lint::{LateContext, LintContext}; use rustc_span::{Span, Symbol, sym}; use super::MAP_IDENTITY; +const MSG: &str = "unnecessary map of the identity function"; + pub(super) fn check( cx: &LateContext<'_>, expr: &hir::Expr<'_>, @@ -23,26 +25,70 @@ pub(super) fn check( || is_type_diagnostic_item(cx, caller_ty, sym::Result) || is_type_diagnostic_item(cx, caller_ty, sym::Option)) && is_expr_untyped_identity_function(cx, map_arg) - && let Some(sugg_span) = expr.span.trim_start(caller.span) + && let Some(call_span) = expr.span.trim_start(caller.span) { - // If the result of `.map(identity)` is used as a mutable reference, - // the caller must not be an immutable binding. - if cx.typeck_results().expr_ty_adjusted(expr).is_mutable_ptr() - && let Some(hir_id) = path_to_local(caller) - && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) - && !matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..)) - { - return; - } + let main_sugg = (call_span, String::new()); + let mut app = if is_copy(cx, caller_ty) { + // there is technically a behavioral change here for `Copy` iterators, where + // `iter.map(|x| x).next()` would mutate a temporary copy of the iterator and + // changing it to `iter.next()` mutates iter directly + Applicability::Unspecified + } else { + Applicability::MachineApplicable + }; - span_lint_and_sugg( - cx, - MAP_IDENTITY, - sugg_span, - "unnecessary map of the identity function", - format!("remove the call to `{name}`"), - String::new(), - Applicability::MachineApplicable, - ); + let needs_to_be_mutable = cx.typeck_results().expr_ty_adjusted(expr).is_mutable_ptr(); + if needs_to_be_mutable && !is_mutable(cx, caller) { + if let Some(hir_id) = path_to_local_with_projections(caller) + && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) + && let PatKind::Binding(_, _, ident, _) = pat.kind + { + // We can reach the binding -- suggest making it mutable + let suggs = vec![main_sugg, (ident.span.shrink_to_lo(), String::from("mut "))]; + + let ident = snippet_with_applicability(cx.sess(), ident.span, "_", &mut app); + + span_lint_and_then(cx, MAP_IDENTITY, call_span, MSG, |diag| { + diag.multipart_suggestion( + format!("remove the call to `{name}`, and make `{ident}` mutable"), + suggs, + app, + ); + }); + } else { + // If we can't make the binding mutable, prevent the suggestion from being automatically applied, + // and add a complementary help message. + app = Applicability::Unspecified; + + let method_requiring_mut = if let Node::Expr(expr) = cx.tcx.parent_hir_node(expr.hir_id) + && let ExprKind::MethodCall(method, ..) = expr.kind + { + Some(method.ident) + } else { + None + }; + + span_lint_and_then(cx, MAP_IDENTITY, call_span, MSG, |diag| { + diag.span_suggestion(main_sugg.0, format!("remove the call to `{name}`"), main_sugg.1, app); + + let note = if let Some(method_requiring_mut) = method_requiring_mut { + format!("this must be made mutable to use `{method_requiring_mut}`") + } else { + "this must be made mutable".to_string() + }; + diag.span_note(caller.span, note); + }); + } + } else { + span_lint_and_sugg( + cx, + MAP_IDENTITY, + main_sugg.0, + MSG, + format!("remove the call to `{name}`"), + main_sugg.1, + app, + ); + } } } diff --git a/tests/ui/map_identity.fixed b/tests/ui/map_identity.fixed index 6c971ba63384..a10aae8cc121 100644 --- a/tests/ui/map_identity.fixed +++ b/tests/ui/map_identity.fixed @@ -1,3 +1,4 @@ +//@require-annotations-for-level: ERROR #![warn(clippy::map_identity)] #![allow(clippy::needless_return, clippy::disallowed_names)] @@ -72,20 +73,33 @@ fn issue11764() { } fn issue13904() { - // don't lint: `it.next()` would not be legal as `it` is immutable - let it = [1, 2, 3].into_iter(); - let _ = it.map(|x| x).next(); + // lint, but there's a catch: + // when we remove the `.map()`, `it.next()` would require `it` to be mutable + // therefore, include that in the suggestion as well + let mut it = [1, 2, 3].into_iter(); + let _ = it.next(); + //~^ map_identity + //~| HELP: remove the call to `map`, and make `it` mutable + + // lint + let mut index = [1, 2, 3].into_iter(); + let mut subindex = (index.by_ref().take(3), 42); + let _ = subindex.0.next(); + //~^ map_identity + //~| HELP: remove the call to `map`, and make `subindex` mutable // lint #[allow(unused_mut)] let mut it = [1, 2, 3].into_iter(); let _ = it.next(); //~^ map_identity + //~| HELP: remove the call to `map` // lint let it = [1, 2, 3].into_iter(); let _ = { it }.next(); //~^ map_identity + //~| HELP: remove the call to `map` } // same as `issue11764`, but for arrays diff --git a/tests/ui/map_identity.rs b/tests/ui/map_identity.rs index 59dcfcda3b6e..fc6e018f9248 100644 --- a/tests/ui/map_identity.rs +++ b/tests/ui/map_identity.rs @@ -1,3 +1,4 @@ +//@require-annotations-for-level: ERROR #![warn(clippy::map_identity)] #![allow(clippy::needless_return, clippy::disallowed_names)] @@ -78,20 +79,33 @@ fn issue11764() { } fn issue13904() { - // don't lint: `it.next()` would not be legal as `it` is immutable + // lint, but there's a catch: + // when we remove the `.map()`, `it.next()` would require `it` to be mutable + // therefore, include that in the suggestion as well let it = [1, 2, 3].into_iter(); let _ = it.map(|x| x).next(); + //~^ map_identity + //~| HELP: remove the call to `map`, and make `it` mutable + + // lint + let mut index = [1, 2, 3].into_iter(); + let subindex = (index.by_ref().take(3), 42); + let _ = subindex.0.map(|n| n).next(); + //~^ map_identity + //~| HELP: remove the call to `map`, and make `subindex` mutable // lint #[allow(unused_mut)] let mut it = [1, 2, 3].into_iter(); let _ = it.map(|x| x).next(); //~^ map_identity + //~| HELP: remove the call to `map` // lint let it = [1, 2, 3].into_iter(); let _ = { it }.map(|x| x).next(); //~^ map_identity + //~| HELP: remove the call to `map` } // same as `issue11764`, but for arrays diff --git a/tests/ui/map_identity.stderr b/tests/ui/map_identity.stderr index a50c0d6b87b5..8d19d62cdc64 100644 --- a/tests/ui/map_identity.stderr +++ b/tests/ui/map_identity.stderr @@ -1,5 +1,5 @@ error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:7:47 + --> tests/ui/map_identity.rs:8:47 | LL | let _: Vec<_> = x.iter().map(not_identity).map(|x| return x).collect(); | ^^^^^^^^^^^^^^^^^^ help: remove the call to `map` @@ -8,25 +8,25 @@ LL | let _: Vec<_> = x.iter().map(not_identity).map(|x| return x).collect(); = help: to override `-D warnings` add `#[allow(clippy::map_identity)]` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:9:57 + --> tests/ui/map_identity.rs:10:57 | LL | let _: Vec<_> = x.iter().map(std::convert::identity).map(|y| y).collect(); | ^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:9:29 + --> tests/ui/map_identity.rs:10:29 | LL | let _: Vec<_> = x.iter().map(std::convert::identity).map(|y| y).collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:12:32 + --> tests/ui/map_identity.rs:13:32 | LL | let _: Option = Some(3).map(|x| x); | ^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:14:36 + --> tests/ui/map_identity.rs:15:36 | LL | let _: Result = Ok(-3).map(|x| { | ____________________________________^ @@ -36,19 +36,19 @@ LL | | }); | |______^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:25:36 + --> tests/ui/map_identity.rs:26:36 | LL | let _: Result = Ok(1).map_err(|a| a); | ^^^^^^^^^^^^^^^ help: remove the call to `map_err` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:36:22 + --> tests/ui/map_identity.rs:37:22 | LL | let _ = x.clone().map(|(x, y)| (x, y)); | ^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:38:22 + --> tests/ui/map_identity.rs:39:22 | LL | let _ = x.clone().map(|(x, y)| { | ______________________^ @@ -58,76 +58,100 @@ LL | | }); | |______^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:42:22 + --> tests/ui/map_identity.rs:43:22 | LL | let _ = x.clone().map(|(x, y)| return (x, y)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:46:22 + --> tests/ui/map_identity.rs:47:22 | LL | let _ = y.clone().map(|(x, y, (z, (w,)))| (x, y, (z, (w,)))); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:76:30 + --> tests/ui/map_identity.rs:77:30 | LL | let _ = x.iter().copied().map(|(x, y)| (x, y)); | ^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:88:15 + --> tests/ui/map_identity.rs:86:15 + | +LL | let _ = it.map(|x| x).next(); + | ^^^^^^^^^^^ + | +help: remove the call to `map`, and make `it` mutable + | +LL ~ let mut it = [1, 2, 3].into_iter(); +LL ~ let _ = it.next(); + | + +error: unnecessary map of the identity function + --> tests/ui/map_identity.rs:93:23 + | +LL | let _ = subindex.0.map(|n| n).next(); + | ^^^^^^^^^^^ + | +help: remove the call to `map`, and make `subindex` mutable + | +LL ~ let mut subindex = (index.by_ref().take(3), 42); +LL ~ let _ = subindex.0.next(); + | + +error: unnecessary map of the identity function + --> tests/ui/map_identity.rs:100:15 | LL | let _ = it.map(|x| x).next(); | ^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:93:19 + --> tests/ui/map_identity.rs:106:19 | LL | let _ = { it }.map(|x| x).next(); | ^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:105:30 + --> tests/ui/map_identity.rs:119:30 | LL | let _ = x.iter().copied().map(|[x, y]| [x, y]); | ^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:131:26 + --> tests/ui/map_identity.rs:145:26 | LL | let _ = x.into_iter().map(|Foo { foo, bar }| Foo { foo, bar }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:135:26 + --> tests/ui/map_identity.rs:149:26 | LL | let _ = x.into_iter().map(|Foo { foo, bar }| foo::Foo { foo, bar }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:143:26 + --> tests/ui/map_identity.rs:157:26 | LL | let _ = x.into_iter().map(|Foo { foo, bar }| Foo { foo: foo, bar: bar }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:147:26 + --> tests/ui/map_identity.rs:161:26 | LL | let _ = x.into_iter().map(|Foo { foo, bar }| Foo { bar, foo }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:157:26 + --> tests/ui/map_identity.rs:171:26 | LL | let _ = x.into_iter().map(|Foo2(foo, bar)| Foo2(foo, bar)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` error: unnecessary map of the identity function - --> tests/ui/map_identity.rs:161:26 + --> tests/ui/map_identity.rs:175:26 | LL | let _ = x.into_iter().map(|Foo2(foo, bar)| foo::Foo2(foo, bar)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` -error: aborting due to 20 previous errors +error: aborting due to 22 previous errors From 991021785eae51e4687b368047e50955ee4adab4 Mon Sep 17 00:00:00 2001 From: Vishruth-Thimmaiah Date: Fri, 14 Feb 2025 19:15:38 +0530 Subject: [PATCH 106/116] fix: `collapsible_match` suggests ref/derefs when needed --- clippy_lints/src/matches/collapsible_match.rs | 49 +++++++++++-- clippy_lints/src/matches/mod.rs | 11 ++- clippy_utils/src/lib.rs | 18 +++++ tests/ui/collapsible_match.rs | 41 +++++++++++ tests/ui/collapsible_match.stderr | 71 ++++++++++++++++++- tests/ui/collapsible_match2.stderr | 2 + 6 files changed, 184 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/matches/collapsible_match.rs b/clippy_lints/src/matches/collapsible_match.rs index 5b50efad3e44..aaf559fc4439 100644 --- a/clippy_lints/src/matches/collapsible_match.rs +++ b/clippy_lints/src/matches/collapsible_match.rs @@ -4,20 +4,22 @@ use clippy_utils::msrvs::Msrv; use clippy_utils::source::snippet; use clippy_utils::visitors::is_local_used; use clippy_utils::{ - SpanlessEq, is_res_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, peel_ref_operators, + SpanlessEq, get_ref_operators, is_res_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, + peel_ref_operators, }; +use rustc_ast::BorrowKind; use rustc_errors::MultiSpan; use rustc_hir::LangItem::OptionNone; -use rustc_hir::{Arm, Expr, HirId, Pat, PatExpr, PatExprKind, PatKind}; +use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatExpr, PatExprKind, PatKind}; use rustc_lint::LateContext; use rustc_span::Span; use super::{COLLAPSIBLE_MATCH, pat_contains_disallowed_or}; -pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], msrv: Msrv) { +pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>], msrv: Msrv) { if let Some(els_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) { for arm in arms { - check_arm(cx, true, arm.pat, arm.body, arm.guard, Some(els_arm.body), msrv); + check_arm(cx, true, arm.pat, expr, arm.body, arm.guard, Some(els_arm.body), msrv); } } } @@ -27,15 +29,18 @@ pub(super) fn check_if_let<'tcx>( pat: &'tcx Pat<'_>, body: &'tcx Expr<'_>, else_expr: Option<&'tcx Expr<'_>>, + let_expr: &'tcx Expr<'_>, msrv: Msrv, ) { - check_arm(cx, false, pat, body, None, else_expr, msrv); + check_arm(cx, false, pat, let_expr, body, None, else_expr, msrv); } +#[allow(clippy::too_many_arguments)] fn check_arm<'tcx>( cx: &LateContext<'tcx>, outer_is_match: bool, outer_pat: &'tcx Pat<'tcx>, + outer_cond: &'tcx Expr<'tcx>, outer_then_body: &'tcx Expr<'tcx>, outer_guard: Option<&'tcx Expr<'tcx>>, outer_else_body: Option<&'tcx Expr<'tcx>>, @@ -82,6 +87,9 @@ fn check_arm<'tcx>( }, IfLetOrMatch::Match(_, arms, ..) => !arms.iter().any(|arm| is_local_used(cx, arm, binding_id)), } + // Check if the inner expression contains any borrows/dereferences + && let ref_types = get_ref_operators(cx, inner_scrutinee) + && let Some(method) = build_ref_method_chain(ref_types) { let msg = format!( "this `{}` can be collapsed into the outer `{}`", @@ -103,6 +111,10 @@ fn check_arm<'tcx>( let mut help_span = MultiSpan::from_spans(vec![binding_span, inner_then_pat.span]); help_span.push_span_label(binding_span, "replace this binding"); help_span.push_span_label(inner_then_pat.span, format!("with this pattern{replace_msg}")); + if !method.is_empty() { + let outer_cond_msg = format!("use: `{}{}`", snippet(cx, outer_cond.span, ".."), method); + help_span.push_span_label(outer_cond.span, outer_cond_msg); + } diag.span_help( help_span, "the outer pattern can be modified to include the inner pattern", @@ -148,3 +160,30 @@ fn find_pat_binding_and_is_innermost_parent_pat_struct(pat: &Pat<'_>, hir_id: Hi }); (span, is_innermost_parent_pat_struct) } + +/// Builds a chain of reference-manipulation method calls (e.g., `.as_ref()`, `.as_mut()`, +/// `.copied()`) based on reference operators +fn build_ref_method_chain(expr: Vec<&Expr<'_>>) -> Option { + let mut req_method_calls = String::new(); + + for ref_operator in expr { + match ref_operator.kind { + ExprKind::AddrOf(BorrowKind::Raw, _, _) => { + return None; + }, + ExprKind::AddrOf(_, m, _) if m.is_mut() => { + req_method_calls.push_str(".as_mut()"); + }, + ExprKind::AddrOf(_, _, _) => { + req_method_calls.push_str(".as_ref()"); + }, + // Deref operator is the only operator that this function should have received + ExprKind::Unary(_, _) => { + req_method_calls.push_str(".copied()"); + }, + _ => (), + } + } + + Some(req_method_calls) +} diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index c128fc40b733..6f49c5524118 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1073,7 +1073,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { significant_drop_in_scrutinee::check_match(cx, expr, ex, arms, source); } - collapsible_match::check_match(cx, arms, self.msrv); + collapsible_match::check_match(cx, ex, arms, self.msrv); if !from_expansion { // These don't depend on a relationship between multiple arms match_wild_err_arm::check(cx, ex, arms); @@ -1137,7 +1137,14 @@ impl<'tcx> LateLintPass<'tcx> for Matches { match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr); } } else if let Some(if_let) = higher::IfLet::hir(cx, expr) { - collapsible_match::check_if_let(cx, if_let.let_pat, if_let.if_then, if_let.if_else, self.msrv); + collapsible_match::check_if_let( + cx, + if_let.let_pat, + if_let.if_then, + if_let.if_else, + if_let.let_expr, + self.msrv, + ); significant_drop_in_scrutinee::check_if_let(cx, expr, if_let.let_expr, if_let.if_then, if_let.if_else); if !from_expansion { if let Some(else_expr) = if_let.if_else { diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index df47b9f7e33b..0bb19dab1f7a 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -2404,6 +2404,24 @@ pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir> expr } +/// Returns a `Vec` of `Expr`s containing `AddrOf` operators (`&`) or deref operators (`*`) of a +/// given expression. +pub fn get_ref_operators<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>) -> Vec<&'hir Expr<'hir>> { + let mut operators = Vec::new(); + peel_hir_expr_while(expr, |expr| match expr.kind { + ExprKind::AddrOf(_, _, e) => { + operators.push(expr); + Some(e) + }, + ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() => { + operators.push(expr); + Some(e) + }, + _ => None, + }); + operators +} + pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind && let Res::Def(_, def_id) = path.res diff --git a/tests/ui/collapsible_match.rs b/tests/ui/collapsible_match.rs index 71b82040ff62..8931a3aa09c6 100644 --- a/tests/ui/collapsible_match.rs +++ b/tests/ui/collapsible_match.rs @@ -316,6 +316,47 @@ fn lint_emitted_at_right_node(opt: Option>) { }; } +pub fn issue_14155() { + let mut arr = ["a", "b", "c"]; + if let Some(last) = arr.last() { + match *last { + //~^ collapsible_match + "a" | "b" => { + unimplemented!() + }, + _ => (), + } + } + + if let Some(last) = arr.last() { + match &last { + //~^ collapsible_match + &&"a" | &&"b" => { + unimplemented!() + }, + _ => (), + } + } + + if let Some(mut last) = arr.last_mut() { + match &mut last { + //~^ collapsible_match + &mut &mut "a" | &mut &mut "b" => { + unimplemented!() + }, + _ => (), + } + } + + const NULL_PTR: *const &'static str = std::ptr::null(); + if let Some(last) = arr.last() { + match &raw const *last { + NULL_PTR => unimplemented!(), + _ => (), + } + } +} + fn make() -> T { unimplemented!() } diff --git a/tests/ui/collapsible_match.stderr b/tests/ui/collapsible_match.stderr index c290d84ec297..14b1c1b187e4 100644 --- a/tests/ui/collapsible_match.stderr +++ b/tests/ui/collapsible_match.stderr @@ -250,5 +250,74 @@ LL | if let Issue9647::A { a: Some(a), .. } = x { LL | if let Some(u) = a { | ^^^^^^^ with this pattern -error: aborting due to 13 previous errors +error: this `match` can be collapsed into the outer `if let` + --> tests/ui/collapsible_match.rs:322:9 + | +LL | / match *last { +LL | | +LL | | "a" | "b" => { +LL | | unimplemented!() +LL | | }, +LL | | _ => (), +LL | | } + | |_________^ + | +help: the outer pattern can be modified to include the inner pattern + --> tests/ui/collapsible_match.rs:321:17 + | +LL | if let Some(last) = arr.last() { + | ^^^^ ---------- use: `arr.last().copied()` + | | + | replace this binding +... +LL | "a" | "b" => { + | ^^^^^^^^^ with this pattern + +error: this `match` can be collapsed into the outer `if let` + --> tests/ui/collapsible_match.rs:332:9 + | +LL | / match &last { +LL | | +LL | | &&"a" | &&"b" => { +LL | | unimplemented!() +LL | | }, +LL | | _ => (), +LL | | } + | |_________^ + | +help: the outer pattern can be modified to include the inner pattern + --> tests/ui/collapsible_match.rs:331:17 + | +LL | if let Some(last) = arr.last() { + | ^^^^ ---------- use: `arr.last().as_ref()` + | | + | replace this binding +... +LL | &&"a" | &&"b" => { + | ^^^^^^^^^^^^^ with this pattern + +error: this `match` can be collapsed into the outer `if let` + --> tests/ui/collapsible_match.rs:342:9 + | +LL | / match &mut last { +LL | | +LL | | &mut &mut "a" | &mut &mut "b" => { +LL | | unimplemented!() +LL | | }, +LL | | _ => (), +LL | | } + | |_________^ + | +help: the outer pattern can be modified to include the inner pattern + --> tests/ui/collapsible_match.rs:341:17 + | +LL | if let Some(mut last) = arr.last_mut() { + | ^^^^^^^^ -------------- use: `arr.last_mut().as_mut()` + | | + | replace this binding +... +LL | &mut &mut "a" | &mut &mut "b" => { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ with this pattern + +error: aborting due to 16 previous errors diff --git a/tests/ui/collapsible_match2.stderr b/tests/ui/collapsible_match2.stderr index 7b2730637528..259706709990 100644 --- a/tests/ui/collapsible_match2.stderr +++ b/tests/ui/collapsible_match2.stderr @@ -77,6 +77,8 @@ LL | | }, help: the outer pattern can be modified to include the inner pattern --> tests/ui/collapsible_match2.rs:54:14 | +LL | match Some(&[1]) { + | ---------- use: `Some(&[1]).copied()` LL | Some(s) => match *s { | ^ replace this binding LL | From 7f1dfe08964b5671d4110efa73833ba40669e935 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Wed, 27 Aug 2025 22:49:45 +0200 Subject: [PATCH 107/116] triagebot: Amend a review to include a link to what was changed since --- triagebot.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/triagebot.toml b/triagebot.toml index 79b6c2b792ff..b2fb50918f58 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -52,6 +52,9 @@ labels = ["S-waiting-on-concerns"] # Show differences when a PR is rebased [range-diff] +# Amend a review to include a link to what was changed since the review +[review-changes-since] + [assign] contributing_url = "https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md" users_on_vacation = [ From b616ba8496d85b19f47ca98d3f6793326780c1e0 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Thu, 28 Aug 2025 10:15:50 +0800 Subject: [PATCH 108/116] fix: `print_literal` suggests wrongly for inline literal following a numbered arg --- clippy_lints/src/write.rs | 13 ++++-------- tests/ui/print_literal.fixed | 13 ++++++++++++ tests/ui/print_literal.rs | 14 +++++++++++++ tests/ui/print_literal.stderr | 38 ++++++++++++++++++++++++++++++++++- 4 files changed, 68 insertions(+), 10 deletions(-) diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index a15e4e42e71d..c55c5ec2f51a 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -537,7 +537,7 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { sug_span = Some(sug_span.unwrap_or(arg.expr.span).to(arg.expr.span)); - if let Some((_, index)) = positional_arg_piece_span(piece) { + if let Some((_, index)) = format_arg_piece_span(piece) { replaced_position.push(index); } @@ -569,16 +569,11 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { } } -/// Extract Span and its index from the given `piece`, if it's positional argument. -fn positional_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> { +/// Extract Span and its index from the given `piece` +fn format_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> { match piece { FormatArgsPiece::Placeholder(FormatPlaceholder { - argument: - FormatArgPosition { - index: Ok(index), - kind: FormatArgPositionKind::Number, - .. - }, + argument: FormatArgPosition { index: Ok(index), .. }, span: Some(span), .. }) => Some((*span, *index)), diff --git a/tests/ui/print_literal.fixed b/tests/ui/print_literal.fixed index ebfe19c700ee..26139f9b6710 100644 --- a/tests/ui/print_literal.fixed +++ b/tests/ui/print_literal.fixed @@ -105,3 +105,16 @@ fn issue_14930() { println!("Hello x is {0:2$.1$}", 0.01, 2, 3); //~^ print_literal } + +fn issue_15576() { + println!("Hello x is {1:.*}", 5, 0.01); + //~^ print_literal + + println!("Hello x is {:.p$}", 0.01, p = 5); + //~^ print_literal + + println!( + "Hello name: x is {1:.*} (which {1} with {0} places)", 5, 0.01 + ); + //~^^ print_literal +} diff --git a/tests/ui/print_literal.rs b/tests/ui/print_literal.rs index 8f3d9be06985..7c4cf028e84c 100644 --- a/tests/ui/print_literal.rs +++ b/tests/ui/print_literal.rs @@ -106,3 +106,17 @@ fn issue_14930() { println!("Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3); //~^ print_literal } + +fn issue_15576() { + println!("Hello {} is {2:.*}", "x", 5, 0.01); + //~^ print_literal + + println!("Hello {} is {:.p$}", "x", 0.01, p = 5); + //~^ print_literal + + println!( + "Hello {}: {2} is {3:.*} (which {3} with {1} places)", + "name", 5, "x", 0.01 + ); + //~^^ print_literal +} diff --git a/tests/ui/print_literal.stderr b/tests/ui/print_literal.stderr index 1c378017d151..c136f52800f6 100644 --- a/tests/ui/print_literal.stderr +++ b/tests/ui/print_literal.stderr @@ -277,5 +277,41 @@ LL - println!("Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3); LL + println!("Hello x is {0:2$.1$}", 0.01, 2, 3); | -error: aborting due to 22 previous errors +error: literal with an empty format string + --> tests/ui/print_literal.rs:111:36 + | +LL | println!("Hello {} is {2:.*}", "x", 5, 0.01); + | ^^^ + | +help: try + | +LL - println!("Hello {} is {2:.*}", "x", 5, 0.01); +LL + println!("Hello x is {1:.*}", 5, 0.01); + | + +error: literal with an empty format string + --> tests/ui/print_literal.rs:114:36 + | +LL | println!("Hello {} is {:.p$}", "x", 0.01, p = 5); + | ^^^ + | +help: try + | +LL - println!("Hello {} is {:.p$}", "x", 0.01, p = 5); +LL + println!("Hello x is {:.p$}", 0.01, p = 5); + | + +error: literal with an empty format string + --> tests/ui/print_literal.rs:119:9 + | +LL | "name", 5, "x", 0.01 + | ^^^^^^^^^^^^^^ + | +help: try + | +LL - "Hello {}: {2} is {3:.*} (which {3} with {1} places)", +LL + "Hello name: x is {1:.*} (which {1} with {0} places)", 5, 0.01 + | + +error: aborting due to 25 previous errors From 05f62e0210ad9debd64f244b1edc18ed9285664b Mon Sep 17 00:00:00 2001 From: relaxcn Date: Thu, 14 Aug 2025 21:54:56 +0800 Subject: [PATCH 109/116] Fix false positive of `needless_range_loop` when meeting multidimensional array --- clippy_lints/src/loops/needless_range_loop.rs | 33 ++++++++++++++++--- tests/ui/needless_range_loop.rs | 32 ++++++++++++++++++ tests/ui/needless_range_loop.stderr | 26 ++++++++++++++- 3 files changed, 86 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/loops/needless_range_loop.rs b/clippy_lints/src/loops/needless_range_loop.rs index 7bb684d65bb4..11edb929d70b 100644 --- a/clippy_lints/src/loops/needless_range_loop.rs +++ b/clippy_lints/src/loops/needless_range_loop.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use clippy_utils::ty::has_iter_method; use clippy_utils::visitors::is_local_used; -use clippy_utils::{SpanlessEq, contains_name, higher, is_integer_const, sugg}; +use clippy_utils::{SpanlessEq, contains_name, higher, is_integer_const, peel_hir_expr_while, sugg}; use rustc_ast::ast; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap}; use rustc_errors::Applicability; @@ -253,12 +253,38 @@ struct VarVisitor<'a, 'tcx> { impl<'tcx> VarVisitor<'_, 'tcx> { fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool { - let index_used_directly = matches!(idx.kind, ExprKind::Path(_)); + let mut used_cnt = 0; + // It is `true` if all indices are direct + let mut index_used_directly = true; + + // Handle initial index + if is_local_used(self.cx, idx, self.var) { + used_cnt += 1; + index_used_directly &= matches!(idx.kind, ExprKind::Path(_)); + } + // Handle nested indices + let seqexpr = peel_hir_expr_while(seqexpr, |e| { + if let ExprKind::Index(e, idx, _) = e.kind { + if is_local_used(self.cx, idx, self.var) { + used_cnt += 1; + index_used_directly &= matches!(idx.kind, ExprKind::Path(_)); + } + Some(e) + } else { + None + } + }); + + match used_cnt { + 0 => return true, + n if n > 1 => self.nonindex = true, // Optimize code like `a[i][i]` + _ => {}, + } + if let ExprKind::Path(ref seqpath) = seqexpr.kind // the indexed container is referenced by a name && let QPath::Resolved(None, seqvar) = *seqpath && seqvar.segments.len() == 1 - && is_local_used(self.cx, idx, self.var) { if self.prefer_mutable { self.indexed_mut.insert(seqvar.segments[0].ident.name); @@ -312,7 +338,6 @@ impl<'tcx> VarVisitor<'_, 'tcx> { impl<'tcx> Visitor<'tcx> for VarVisitor<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { if let ExprKind::MethodCall(meth, args_0, [args_1, ..], _) = &expr.kind - // a range index op && let Some(trait_id) = self .cx .typeck_results() diff --git a/tests/ui/needless_range_loop.rs b/tests/ui/needless_range_loop.rs index 70cf9fa7369f..ea4591d8b71a 100644 --- a/tests/ui/needless_range_loop.rs +++ b/tests/ui/needless_range_loop.rs @@ -210,3 +210,35 @@ fn needless_loop() { black_box([1, 2, 3, 4, 5, 6, 7, 8][i]); } } + +fn issue_15068() { + let a = vec![vec![0u8; MAX_LEN]; MAX_LEN]; + let b = vec![0u8; MAX_LEN]; + + for i in 0..MAX_LEN { + // no error + let _ = a[0][i]; + let _ = b[i]; + } + + for i in 0..MAX_LEN { + // no error + let _ = a[i][0]; + let _ = b[i]; + } + + for i in 0..MAX_LEN { + // no error + let _ = a[i][b[i] as usize]; + } + + for i in 0..MAX_LEN { + //~^ needless_range_loop + let _ = a[i][i]; + } + + for i in 0..MAX_LEN { + //~^ needless_range_loop + let _ = a[0][i]; + } +} diff --git a/tests/ui/needless_range_loop.stderr b/tests/ui/needless_range_loop.stderr index 23c085f9d3b2..33a519d8a80d 100644 --- a/tests/ui/needless_range_loop.stderr +++ b/tests/ui/needless_range_loop.stderr @@ -168,5 +168,29 @@ LL - for i in 0..vec.len() { LL + for (i, ) in vec.iter_mut().enumerate() { | -error: aborting due to 14 previous errors +error: the loop variable `i` is used to index `a` + --> tests/ui/needless_range_loop.rs:235:14 + | +LL | for i in 0..MAX_LEN { + | ^^^^^^^^^^ + | +help: consider using an iterator and enumerate() + | +LL - for i in 0..MAX_LEN { +LL + for (i, ) in a.iter().enumerate().take(MAX_LEN) { + | + +error: the loop variable `i` is only used to index `a` + --> tests/ui/needless_range_loop.rs:240:14 + | +LL | for i in 0..MAX_LEN { + | ^^^^^^^^^^ + | +help: consider using an iterator + | +LL - for i in 0..MAX_LEN { +LL + for in a.iter().take(MAX_LEN) { + | + +error: aborting due to 16 previous errors From f4e68b25f9c53d4968edfccf7a975135adb8cbcf Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 30 Aug 2025 16:50:43 +0200 Subject: [PATCH 110/116] fix: `assertions_on_result_states` avoid changing return type in more cases previously, we added a trailing semicolon only if the expr was a trailing expr of a block --- .../src/assertions_on_result_states.rs | 27 ++++++++++--------- tests/ui/assertions_on_result_states.fixed | 11 +++++++- tests/ui/assertions_on_result_states.rs | 11 +++++++- tests/ui/assertions_on_result_states.stderr | 10 +++++-- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/clippy_lints/src/assertions_on_result_states.rs b/clippy_lints/src/assertions_on_result_states.rs index 6f2a6a36a38b..08253b0c4995 100644 --- a/clippy_lints/src/assertions_on_result_states.rs +++ b/clippy_lints/src/assertions_on_result_states.rs @@ -3,10 +3,10 @@ use clippy_utils::macros::{PanicExpn, find_assert_args, root_macro_call_first_no use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{has_debug_impl, is_copy, is_type_diagnostic_item}; use clippy_utils::usage::local_used_after_expr; -use clippy_utils::{is_expr_final_block_expr, path_res, sym}; +use clippy_utils::{path_res, sym}; use rustc_errors::Applicability; use rustc_hir::def::Res; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::{Expr, ExprKind, Node}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; use rustc_session::declare_lint_pass; @@ -77,17 +77,20 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnResultStates { _ => return, }; span_lint_and_then(cx, ASSERTIONS_ON_RESULT_STATES, macro_call.span, message, |diag| { - let semicolon = if is_expr_final_block_expr(cx.tcx, e) { ";" } else { "" }; let mut app = Applicability::MachineApplicable; - diag.span_suggestion( - macro_call.span, - "replace with", - format!( - "{}.{replacement}(){semicolon}", - snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0 - ), - app, - ); + let recv = snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0; + + // `assert!` doesn't return anything, but `Result::unwrap(_err)` does, so we might need to add a + // semicolon to the suggestion to avoid leaking the type + let sugg = match cx.tcx.parent_hir_node(e.hir_id) { + // trailing expr of a block + Node::Block(..) => format!("{recv}.{replacement}();"), + // already has a trailing semicolon + Node::Stmt(..) => format!("{recv}.{replacement}()"), + // this is the last-resort option, because it's rather verbose + _ => format!("{{ {recv}.{replacement}(); }}"), + }; + diag.span_suggestion(macro_call.span, "replace with", sugg, app); }); } } diff --git a/tests/ui/assertions_on_result_states.fixed b/tests/ui/assertions_on_result_states.fixed index 09c1a8d0ed1d..b2b7318c113f 100644 --- a/tests/ui/assertions_on_result_states.fixed +++ b/tests/ui/assertions_on_result_states.fixed @@ -82,9 +82,18 @@ fn main() { assert!(r.is_err()); } -#[allow(dead_code)] fn issue9450() { let res: Result = Ok(1); res.unwrap_err(); //~^ assertions_on_result_states } + +fn issue9916(res: Result) { + let a = 0; + match a { + 0 => {}, + 1 => { res.unwrap(); }, + //~^ assertions_on_result_states + _ => todo!(), + } +} diff --git a/tests/ui/assertions_on_result_states.rs b/tests/ui/assertions_on_result_states.rs index c63c2502b537..33f1485326b3 100644 --- a/tests/ui/assertions_on_result_states.rs +++ b/tests/ui/assertions_on_result_states.rs @@ -82,9 +82,18 @@ fn main() { assert!(r.is_err()); } -#[allow(dead_code)] fn issue9450() { let res: Result = Ok(1); assert!(res.is_err()) //~^ assertions_on_result_states } + +fn issue9916(res: Result) { + let a = 0; + match a { + 0 => {}, + 1 => assert!(res.is_ok()), + //~^ assertions_on_result_states + _ => todo!(), + } +} diff --git a/tests/ui/assertions_on_result_states.stderr b/tests/ui/assertions_on_result_states.stderr index 3bf811588c69..826f049555ca 100644 --- a/tests/ui/assertions_on_result_states.stderr +++ b/tests/ui/assertions_on_result_states.stderr @@ -38,10 +38,16 @@ LL | assert!(r.is_err()); | ^^^^^^^^^^^^^^^^^^^ help: replace with: `r.unwrap_err()` error: called `assert!` with `Result::is_err` - --> tests/ui/assertions_on_result_states.rs:88:5 + --> tests/ui/assertions_on_result_states.rs:87:5 | LL | assert!(res.is_err()) | ^^^^^^^^^^^^^^^^^^^^^ help: replace with: `res.unwrap_err();` -error: aborting due to 7 previous errors +error: called `assert!` with `Result::is_ok` + --> tests/ui/assertions_on_result_states.rs:95:14 + | +LL | 1 => assert!(res.is_ok()), + | ^^^^^^^^^^^^^^^^^^^^ help: replace with: `{ res.unwrap(); }` + +error: aborting due to 8 previous errors From 7f125af963ed5b44b6d7e47be62ed5026b96c6e6 Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Sat, 30 Aug 2025 12:13:04 -0400 Subject: [PATCH 111/116] Commas to semicolons in clippy.toml reasons --- clippy.toml | 6 +++--- tests/ui-internal/disallow_span_lint.stderr | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clippy.toml b/clippy.toml index 77573105d86a..d9bcfd17606e 100644 --- a/clippy.toml +++ b/clippy.toml @@ -6,12 +6,12 @@ lint-commented-code = true [[disallowed-methods]] path = "rustc_lint::context::LintContext::lint" -reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead" +reason = "this function does not add a link to our documentation; please use the `clippy_utils::diagnostics::span_lint*` functions instead" [[disallowed-methods]] path = "rustc_lint::context::LintContext::span_lint" -reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead" +reason = "this function does not add a link to our documentation; please use the `clippy_utils::diagnostics::span_lint*` functions instead" [[disallowed-methods]] path = "rustc_middle::ty::context::TyCtxt::node_span_lint" -reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead" +reason = "this function does not add a link to our documentation; please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead" diff --git a/tests/ui-internal/disallow_span_lint.stderr b/tests/ui-internal/disallow_span_lint.stderr index f03a745963e0..e9d53c64dd9b 100644 --- a/tests/ui-internal/disallow_span_lint.stderr +++ b/tests/ui-internal/disallow_span_lint.stderr @@ -4,7 +4,7 @@ error: use of a disallowed method `rustc_lint::context::LintContext::span_lint` LL | cx.span_lint(lint, span, |lint| { | ^^^^^^^^^ | - = note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead + = note: this function does not add a link to our documentation; please use the `clippy_utils::diagnostics::span_lint*` functions instead note: the lint level is defined here --> tests/ui-internal/disallow_span_lint.rs:2:9 | @@ -17,7 +17,7 @@ error: use of a disallowed method `rustc_middle::ty::context::TyCtxt::node_span_ LL | tcx.node_span_lint(lint, hir_id, span, |lint| { | ^^^^^^^^^^^^^^ | - = note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead + = note: this function does not add a link to our documentation; please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead error: aborting due to 2 previous errors From 04c6fb7a96f07d455c28be39a4854953a0c1ce6e Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 30 Aug 2025 19:47:00 +0200 Subject: [PATCH 112/116] fix: `needless_for_each` suggests wrongly with explicit closure input types --- clippy_lints/src/needless_for_each.rs | 36 ++++++++++++------- tests/ui/needless_for_each_unfixable.rs | 19 +++++++++-- tests/ui/needless_for_each_unfixable.stderr | 38 +++++++++++++++++++-- 3 files changed, 76 insertions(+), 17 deletions(-) diff --git a/clippy_lints/src/needless_for_each.rs b/clippy_lints/src/needless_for_each.rs index a67545e419ce..3a6ccc2bca99 100644 --- a/clippy_lints/src/needless_for_each.rs +++ b/clippy_lints/src/needless_for_each.rs @@ -1,6 +1,6 @@ use rustc_errors::Applicability; use rustc_hir::intravisit::{Visitor, walk_expr}; -use rustc_hir::{Block, BlockCheckMode, Closure, Expr, ExprKind, Stmt, StmtKind}; +use rustc_hir::{Block, BlockCheckMode, Closure, Expr, ExprKind, Stmt, StmtKind, TyKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::Span; @@ -70,12 +70,24 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessForEach { && has_iter_method(cx, cx.typeck_results().expr_ty(iter_recv)).is_some() // Skip the lint if the body is not block because this is simpler than `for` loop. // e.g. `v.iter().for_each(f)` is simpler and clearer than using `for` loop. - && let ExprKind::Closure(&Closure { body, .. }) = for_each_arg.kind + && let ExprKind::Closure(&Closure { body, fn_decl, .. }) = for_each_arg.kind && let body = cx.tcx.hir_body(body) // Skip the lint if the body is not safe, so as not to suggest `for … in … unsafe {}` // and suggesting `for … in … { unsafe { } }` is a little ugly. && !matches!(body.value.kind, ExprKind::Block(Block { rules: BlockCheckMode::UnsafeBlock(_), .. }, ..)) { + let mut applicability = Applicability::MachineApplicable; + + // If any closure parameter has an explicit type specified, applying the lint would necessarily + // remove that specification, possibly breaking type inference + if fn_decl + .inputs + .iter() + .any(|input| matches!(input.kind, TyKind::Infer(..))) + { + applicability = Applicability::MaybeIncorrect; + } + let mut ret_collector = RetCollector::default(); ret_collector.visit_expr(body.value); @@ -84,18 +96,16 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessForEach { return; } - let (mut applicability, ret_suggs) = if ret_collector.spans.is_empty() { - (Applicability::MachineApplicable, None) + let ret_suggs = if ret_collector.spans.is_empty() { + None } else { - ( - Applicability::MaybeIncorrect, - Some( - ret_collector - .spans - .into_iter() - .map(|span| (span, "continue".to_string())) - .collect(), - ), + applicability = Applicability::MaybeIncorrect; + Some( + ret_collector + .spans + .into_iter() + .map(|span| (span, "continue".to_string())) + .collect(), ) }; diff --git a/tests/ui/needless_for_each_unfixable.rs b/tests/ui/needless_for_each_unfixable.rs index 159cbe49828f..24ce5786b910 100644 --- a/tests/ui/needless_for_each_unfixable.rs +++ b/tests/ui/needless_for_each_unfixable.rs @@ -1,6 +1,6 @@ //@no-rustfix: overlapping suggestions #![warn(clippy::needless_for_each)] -#![allow(clippy::needless_return, clippy::uninlined_format_args)] +#![allow(clippy::needless_return)] fn main() { let v: Vec = Vec::new(); @@ -11,7 +11,22 @@ fn main() { if *v == 10 { return; } else { - println!("{}", v); + println!("{v}"); } }); } + +fn issue9912() { + let mut i = 0; + // Changing this to a `for` loop would break type inference + [].iter().for_each(move |_: &i32| { + //~^ needless_for_each + i += 1; + }); + + // Changing this would actually be okay, but we still suggest `MaybeIncorrect`ly + [1i32].iter().for_each(move |_: &i32| { + //~^ needless_for_each + i += 1; + }); +} diff --git a/tests/ui/needless_for_each_unfixable.stderr b/tests/ui/needless_for_each_unfixable.stderr index 3a3a240c5a3a..6bc56db68a0e 100644 --- a/tests/ui/needless_for_each_unfixable.stderr +++ b/tests/ui/needless_for_each_unfixable.stderr @@ -19,7 +19,7 @@ LL + LL + if *v == 10 { LL + return; LL + } else { -LL + println!("{}", v); +LL + println!("{v}"); LL + } LL + } | @@ -29,5 +29,39 @@ LL - return; LL + continue; | -error: aborting due to 1 previous error +error: needless use of `for_each` + --> tests/ui/needless_for_each_unfixable.rs:22:5 + | +LL | / [].iter().for_each(move |_: &i32| { +LL | | +LL | | i += 1; +LL | | }); + | |_______^ + | +help: try + | +LL ~ for _ in [].iter() { +LL + +LL + i += 1; +LL + } + | + +error: needless use of `for_each` + --> tests/ui/needless_for_each_unfixable.rs:28:5 + | +LL | / [1i32].iter().for_each(move |_: &i32| { +LL | | +LL | | i += 1; +LL | | }); + | |_______^ + | +help: try + | +LL ~ for _ in [1i32].iter() { +LL + +LL + i += 1; +LL + } + | + +error: aborting due to 3 previous errors From fc3959044f628745e4c6899cb0fed6452e63905c Mon Sep 17 00:00:00 2001 From: Teodoro Freund Date: Tue, 1 Jul 2025 11:15:17 +0100 Subject: [PATCH 113/116] Supress excessive_precision when constants are overly precise Added const associated items matching and static Added configurable threshold Addressed comments Renamed lint config field --- CHANGELOG.md | 1 + book/src/lint_configuration.md | 10 +++ clippy_config/src/conf.rs | 3 + clippy_lints/src/float_literal.rs | 45 +++++++++- clippy_lints/src/lib.rs | 2 +- tests/ui-toml/excessive_precision/clippy.toml | 1 + .../excessive_precision.fixed | 38 +++++++++ .../excessive_precision.rs | 38 +++++++++ .../excessive_precision.stderr | 38 +++++++++ .../toml_unknown_key/conf_unknown_key.stderr | 3 + tests/ui/excessive_precision.fixed | 45 +++++++++- tests/ui/excessive_precision.rs | 45 +++++++++- tests/ui/excessive_precision.stderr | 85 +++++++++++++++---- 13 files changed, 330 insertions(+), 24 deletions(-) create mode 100644 tests/ui-toml/excessive_precision/clippy.toml create mode 100644 tests/ui-toml/excessive_precision/excessive_precision.fixed create mode 100644 tests/ui-toml/excessive_precision/excessive_precision.rs create mode 100644 tests/ui-toml/excessive_precision/excessive_precision.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 874002237d0c..eb2a76a81836 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6712,6 +6712,7 @@ Released 2018-09-13 [`check-inconsistent-struct-field-initializers`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-inconsistent-struct-field-initializers [`check-private-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-private-items [`cognitive-complexity-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#cognitive-complexity-threshold +[`const-literal-digits-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#const-literal-digits-threshold [`disallowed-macros`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-macros [`disallowed-methods`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-methods [`disallowed-names`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-names diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index d51467cbfae5..c2d080cd96a1 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -485,6 +485,16 @@ The maximum cognitive complexity a function can have * [`cognitive_complexity`](https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity) +## `const-literal-digits-threshold` +The minimum digits a const float literal must have to supress the `excessive_precicion` lint + +**Default Value:** `30` + +--- +**Affected lints:** +* [`excessive_precision`](https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision) + + ## `disallowed-macros` The list of disallowed macros, written as fully qualified paths. diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 365d664d75fe..2f28f6175ad8 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -570,6 +570,9 @@ define_Conf! { /// The maximum cognitive complexity a function can have #[lints(cognitive_complexity)] cognitive_complexity_threshold: u64 = 25, + /// The minimum digits a const float literal must have to supress the `excessive_precicion` lint + #[lints(excessive_precision)] + const_literal_digits_threshold: usize = 30, /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. /// /// Use the Cognitive Complexity lint instead. diff --git a/clippy_lints/src/float_literal.rs b/clippy_lints/src/float_literal.rs index ccaf38aee4d8..6178addfff12 100644 --- a/clippy_lints/src/float_literal.rs +++ b/clippy_lints/src/float_literal.rs @@ -1,11 +1,12 @@ +use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::numeric_literal; +use clippy_utils::{ExprUseNode, expr_use_ctxt, numeric_literal}; use rustc_ast::ast::{LitFloatType, LitKind}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, FloatTy}; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; use std::fmt; declare_clippy_lint! { @@ -13,6 +14,8 @@ declare_clippy_lint! { /// Checks for float literals with a precision greater /// than that supported by the underlying type. /// + /// The lint is suppressed for literals with over `const_literal_digits_threshold` digits. + /// /// ### Why is this bad? /// Rust will truncate the literal silently. /// @@ -58,7 +61,21 @@ declare_clippy_lint! { "lossy whole number float literals" } -declare_lint_pass!(FloatLiteral => [EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL]); +pub struct FloatLiteral { + const_literal_digits_threshold: usize, +} + +impl_lint_pass!(FloatLiteral => [ + EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL +]); + +impl FloatLiteral { + pub fn new(conf: &'static Conf) -> Self { + Self { + const_literal_digits_threshold: conf.const_literal_digits_threshold, + } + } +} impl<'tcx> LateLintPass<'tcx> for FloatLiteral { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { @@ -126,13 +143,25 @@ impl<'tcx> LateLintPass<'tcx> for FloatLiteral { }, ); } - } else if digits > max as usize && count_digits(&float_str) < count_digits(sym_str) { + } else if digits > max as usize && count_digits(&float_str) < digits { + if digits >= self.const_literal_digits_threshold + && matches!(expr_use_ctxt(cx, expr).use_node(cx), ExprUseNode::ConstStatic(_)) + { + // If a big enough number of digits is specified and it's a constant + // we assume the user is definining a constant, and excessive precision is ok + return; + } span_lint_and_then( cx, EXCESSIVE_PRECISION, expr.span, "float has excessive precision", |diag| { + if digits >= self.const_literal_digits_threshold + && let Some(let_stmt) = maybe_let_stmt(cx, expr) + { + diag.span_note(let_stmt.span, "consider making it a `const` item"); + } diag.span_suggestion_verbose( expr.span, "consider changing the type or truncating it to", @@ -196,3 +225,11 @@ impl FloatFormat { } } } + +fn maybe_let_stmt<'a>(cx: &LateContext<'a>, expr: &hir::Expr<'_>) -> Option<&'a hir::LetStmt<'a>> { + let parent = cx.tcx.parent_hir_node(expr.hir_id); + match parent { + hir::Node::LetStmt(let_stmt) => Some(let_stmt), + _ => None, + } +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 08b224367d75..0d7931aae765 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -475,7 +475,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(move |_| Box::new(types::Types::new(conf))); store.register_late_pass(move |_| Box::new(booleans::NonminimalBool::new(conf))); store.register_late_pass(|_| Box::new(enum_clike::UnportableVariant)); - store.register_late_pass(|_| Box::new(float_literal::FloatLiteral)); + store.register_late_pass(move |_| Box::new(float_literal::FloatLiteral::new(conf))); store.register_late_pass(|_| Box::new(ptr::Ptr)); store.register_late_pass(|_| Box::new(needless_bool::NeedlessBool)); store.register_late_pass(|_| Box::new(bool_comparison::BoolComparison)); diff --git a/tests/ui-toml/excessive_precision/clippy.toml b/tests/ui-toml/excessive_precision/clippy.toml new file mode 100644 index 000000000000..c7fc230dcb31 --- /dev/null +++ b/tests/ui-toml/excessive_precision/clippy.toml @@ -0,0 +1 @@ +const-literal-digits-threshold = 20 diff --git a/tests/ui-toml/excessive_precision/excessive_precision.fixed b/tests/ui-toml/excessive_precision/excessive_precision.fixed new file mode 100644 index 000000000000..577bbff2957f --- /dev/null +++ b/tests/ui-toml/excessive_precision/excessive_precision.fixed @@ -0,0 +1,38 @@ +#![warn(clippy::excessive_precision)] +#![allow( + dead_code, + overflowing_literals, + unused_variables, + clippy::print_literal, + clippy::useless_vec +)] + +fn main() { + // Overly specified constants + let _: f32 = 1.012_345_7; + //~^ excessive_precision + let _: f64 = 1.012_345_678_901_234_6; + //~^ excessive_precision + const _: f32 = 1.012345678901234567890; + const _: f64 = 1.012345678901234567890; + + static STATIC1: f32 = 1.012345678901234567890; + static STATIC2: f64 = 1.012345678901234567890; + + static mut STATIC_MUT1: f32 = 1.012345678901234567890; + static mut STATIC_MUT2: f64 = 1.012345678901234567890; +} + +trait ExcessivelyPreciseTrait { + // Overly specified constants + const GOOD1: f32 = 1.012345678901234567890; + const GOOD2: f64 = 1.012345678901234567890; +} + +struct ExcessivelyPreciseStruct; + +impl ExcessivelyPreciseStruct { + // Overly specified constants + const GOOD1: f32 = 1.012345678901234567890; + const GOOD2: f64 = 1.012345678901234567890; +} diff --git a/tests/ui-toml/excessive_precision/excessive_precision.rs b/tests/ui-toml/excessive_precision/excessive_precision.rs new file mode 100644 index 000000000000..121448ed540d --- /dev/null +++ b/tests/ui-toml/excessive_precision/excessive_precision.rs @@ -0,0 +1,38 @@ +#![warn(clippy::excessive_precision)] +#![allow( + dead_code, + overflowing_literals, + unused_variables, + clippy::print_literal, + clippy::useless_vec +)] + +fn main() { + // Overly specified constants + let _: f32 = 1.012345678901234567890; + //~^ excessive_precision + let _: f64 = 1.012345678901234567890; + //~^ excessive_precision + const _: f32 = 1.012345678901234567890; + const _: f64 = 1.012345678901234567890; + + static STATIC1: f32 = 1.012345678901234567890; + static STATIC2: f64 = 1.012345678901234567890; + + static mut STATIC_MUT1: f32 = 1.012345678901234567890; + static mut STATIC_MUT2: f64 = 1.012345678901234567890; +} + +trait ExcessivelyPreciseTrait { + // Overly specified constants + const GOOD1: f32 = 1.012345678901234567890; + const GOOD2: f64 = 1.012345678901234567890; +} + +struct ExcessivelyPreciseStruct; + +impl ExcessivelyPreciseStruct { + // Overly specified constants + const GOOD1: f32 = 1.012345678901234567890; + const GOOD2: f64 = 1.012345678901234567890; +} diff --git a/tests/ui-toml/excessive_precision/excessive_precision.stderr b/tests/ui-toml/excessive_precision/excessive_precision.stderr new file mode 100644 index 000000000000..65d33eddef17 --- /dev/null +++ b/tests/ui-toml/excessive_precision/excessive_precision.stderr @@ -0,0 +1,38 @@ +error: float has excessive precision + --> tests/ui-toml/excessive_precision/excessive_precision.rs:12:18 + | +LL | let _: f32 = 1.012345678901234567890; + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +note: consider making it a `const` item + --> tests/ui-toml/excessive_precision/excessive_precision.rs:12:5 + | +LL | let _: f32 = 1.012345678901234567890; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `-D clippy::excessive-precision` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::excessive_precision)]` +help: consider changing the type or truncating it to + | +LL - let _: f32 = 1.012345678901234567890; +LL + let _: f32 = 1.012_345_7; + | + +error: float has excessive precision + --> tests/ui-toml/excessive_precision/excessive_precision.rs:14:18 + | +LL | let _: f64 = 1.012345678901234567890; + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +note: consider making it a `const` item + --> tests/ui-toml/excessive_precision/excessive_precision.rs:14:5 + | +LL | let _: f64 = 1.012345678901234567890; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: consider changing the type or truncating it to + | +LL - let _: f64 = 1.012345678901234567890; +LL + let _: f64 = 1.012_345_678_901_234_6; + | + +error: aborting due to 2 previous errors + diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 6ee77ebd8ece..20aeb4bb8498 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -35,6 +35,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect check-inconsistent-struct-field-initializers check-private-items cognitive-complexity-threshold + const-literal-digits-threshold disallowed-macros disallowed-methods disallowed-names @@ -129,6 +130,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect check-inconsistent-struct-field-initializers check-private-items cognitive-complexity-threshold + const-literal-digits-threshold disallowed-macros disallowed-methods disallowed-names @@ -223,6 +225,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni check-inconsistent-struct-field-initializers check-private-items cognitive-complexity-threshold + const-literal-digits-threshold disallowed-macros disallowed-methods disallowed-names diff --git a/tests/ui/excessive_precision.fixed b/tests/ui/excessive_precision.fixed index 8a8c2e1939c8..8158d4b332ac 100644 --- a/tests/ui/excessive_precision.fixed +++ b/tests/ui/excessive_precision.fixed @@ -4,9 +4,16 @@ overflowing_literals, unused_variables, clippy::print_literal, - clippy::useless_vec + clippy::useless_vec, + clippy::approx_constant )] +macro_rules! make_pi { + ($i:ident : $t:ty) => { + const $i: $t = 3.14159265358979323846264338327950288419716939937510582097494459230781640628; + }; +} + fn main() { // Consts const GOOD32: f32 = 0.123_456; @@ -101,4 +108,40 @@ fn main() { const _: f64 = 3.0; //~^ excessive_precision const _: f64 = 3.0000000000000000; + + // Overly specified constants + let _: f32 = 1.012_345_7; + //~^ excessive_precision + let _: f64 = 1.012_345_678_901_234_6; + //~^ excessive_precision + const _: f32 = 1.01234567890123456789012345678901234567890; + const _: f64 = 1.01234567890123456789012345678901234567890; + + static STATIC1: f32 = 1.01234567890123456789012345678901234567890; + static STATIC2: f64 = 1.01234567890123456789012345678901234567890; + + static mut STATIC_MUT1: f32 = 1.01234567890123456789012345678901234567890; + static mut STATIC_MUT2: f64 = 1.01234567890123456789012345678901234567890; + + // From issue #13855 + let gamma = 0.577_215_664_901_532_9; + //~^ excessive_precision + const GAMMA: f64 = 0.5772156649015328606065120900824024310421; + + make_pi!(P32: f32); + make_pi!(P64: f64); +} + +trait ExcessivelyPreciseTrait { + // Overly specified constants + const GOOD1: f32 = 1.01234567890123456789012345678901234567890; + const GOOD2: f64 = 1.01234567890123456789012345678901234567890; +} + +struct ExcessivelyPreciseStruct; + +impl ExcessivelyPreciseStruct { + // Overly specified constants + const GOOD1: f32 = 1.01234567890123456789012345678901234567890; + const GOOD2: f64 = 1.01234567890123456789012345678901234567890; } diff --git a/tests/ui/excessive_precision.rs b/tests/ui/excessive_precision.rs index 5dcf55cb9273..7ee6247ee5ac 100644 --- a/tests/ui/excessive_precision.rs +++ b/tests/ui/excessive_precision.rs @@ -4,9 +4,16 @@ overflowing_literals, unused_variables, clippy::print_literal, - clippy::useless_vec + clippy::useless_vec, + clippy::approx_constant )] +macro_rules! make_pi { + ($i:ident : $t:ty) => { + const $i: $t = 3.14159265358979323846264338327950288419716939937510582097494459230781640628; + }; +} + fn main() { // Consts const GOOD32: f32 = 0.123_456; @@ -101,4 +108,40 @@ fn main() { const _: f64 = 3.0000000000000000e+00; //~^ excessive_precision const _: f64 = 3.0000000000000000; + + // Overly specified constants + let _: f32 = 1.01234567890123456789012345678901234567890; + //~^ excessive_precision + let _: f64 = 1.01234567890123456789012345678901234567890; + //~^ excessive_precision + const _: f32 = 1.01234567890123456789012345678901234567890; + const _: f64 = 1.01234567890123456789012345678901234567890; + + static STATIC1: f32 = 1.01234567890123456789012345678901234567890; + static STATIC2: f64 = 1.01234567890123456789012345678901234567890; + + static mut STATIC_MUT1: f32 = 1.01234567890123456789012345678901234567890; + static mut STATIC_MUT2: f64 = 1.01234567890123456789012345678901234567890; + + // From issue #13855 + let gamma = 0.5772156649015328606065120900824024310421; + //~^ excessive_precision + const GAMMA: f64 = 0.5772156649015328606065120900824024310421; + + make_pi!(P32: f32); + make_pi!(P64: f64); +} + +trait ExcessivelyPreciseTrait { + // Overly specified constants + const GOOD1: f32 = 1.01234567890123456789012345678901234567890; + const GOOD2: f64 = 1.01234567890123456789012345678901234567890; +} + +struct ExcessivelyPreciseStruct; + +impl ExcessivelyPreciseStruct { + // Overly specified constants + const GOOD1: f32 = 1.01234567890123456789012345678901234567890; + const GOOD2: f64 = 1.01234567890123456789012345678901234567890; } diff --git a/tests/ui/excessive_precision.stderr b/tests/ui/excessive_precision.stderr index f5eeadf0c8cb..40806d67487f 100644 --- a/tests/ui/excessive_precision.stderr +++ b/tests/ui/excessive_precision.stderr @@ -1,5 +1,5 @@ error: float has excessive precision - --> tests/ui/excessive_precision.rs:20:26 + --> tests/ui/excessive_precision.rs:27:26 | LL | const BAD32_1: f32 = 0.123_456_789_f32; | ^^^^^^^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL + const BAD32_1: f32 = 0.123_456_79_f32; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:22:26 + --> tests/ui/excessive_precision.rs:29:26 | LL | const BAD32_2: f32 = 0.123_456_789; | ^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL + const BAD32_2: f32 = 0.123_456_79; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:24:26 + --> tests/ui/excessive_precision.rs:31:26 | LL | const BAD32_3: f32 = 0.100_000_000_000_1; | ^^^^^^^^^^^^^^^^^^^ @@ -37,7 +37,7 @@ LL + const BAD32_3: f32 = 0.1; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:26:29 + --> tests/ui/excessive_precision.rs:33:29 | LL | const BAD32_EDGE: f32 = 1.000_000_9; | ^^^^^^^^^^^ @@ -49,7 +49,7 @@ LL + const BAD32_EDGE: f32 = 1.000_001; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:31:26 + --> tests/ui/excessive_precision.rs:38:26 | LL | const BAD64_3: f64 = 0.100_000_000_000_000_000_1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -61,7 +61,7 @@ LL + const BAD64_3: f64 = 0.1; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:35:22 + --> tests/ui/excessive_precision.rs:42:22 | LL | println!("{:?}", 8.888_888_888_888_888_888_888); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -73,7 +73,7 @@ LL + println!("{:?}", 8.888_888_888_888_89); | error: float has excessive precision - --> tests/ui/excessive_precision.rs:47:22 + --> tests/ui/excessive_precision.rs:54:22 | LL | let bad32: f32 = 1.123_456_789; | ^^^^^^^^^^^^^ @@ -85,7 +85,7 @@ LL + let bad32: f32 = 1.123_456_8; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:49:26 + --> tests/ui/excessive_precision.rs:56:26 | LL | let bad32_suf: f32 = 1.123_456_789_f32; | ^^^^^^^^^^^^^^^^^ @@ -97,7 +97,7 @@ LL + let bad32_suf: f32 = 1.123_456_8_f32; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:51:21 + --> tests/ui/excessive_precision.rs:58:21 | LL | let bad32_inf = 1.123_456_789_f32; | ^^^^^^^^^^^^^^^^^ @@ -109,7 +109,7 @@ LL + let bad32_inf = 1.123_456_8_f32; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:62:36 + --> tests/ui/excessive_precision.rs:69:36 | LL | let bad_vec32: Vec = vec![0.123_456_789]; | ^^^^^^^^^^^^^ @@ -121,7 +121,7 @@ LL + let bad_vec32: Vec = vec![0.123_456_79]; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:64:36 + --> tests/ui/excessive_precision.rs:71:36 | LL | let bad_vec64: Vec = vec![0.123_456_789_123_456_789]; | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -133,7 +133,7 @@ LL + let bad_vec64: Vec = vec![0.123_456_789_123_456_78]; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:69:24 + --> tests/ui/excessive_precision.rs:76:24 | LL | let bad_e32: f32 = 1.123_456_788_888e-10; | ^^^^^^^^^^^^^^^^^^^^^ @@ -145,7 +145,7 @@ LL + let bad_e32: f32 = 1.123_456_8e-10; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:73:27 + --> tests/ui/excessive_precision.rs:80:27 | LL | let bad_bige32: f32 = 1.123_456_788_888E-10; | ^^^^^^^^^^^^^^^^^^^^^ @@ -157,7 +157,7 @@ LL + let bad_bige32: f32 = 1.123_456_8E-10; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:86:13 + --> tests/ui/excessive_precision.rs:93:13 | LL | let _ = 2.225_073_858_507_201_1e-308_f64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -169,7 +169,7 @@ LL + let _ = 2.225_073_858_507_201e-308_f64; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:90:13 + --> tests/ui/excessive_precision.rs:97:13 | LL | let _ = 1.000_000_000_000_001e-324_f64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -181,7 +181,7 @@ LL + let _ = 0_f64; | error: float has excessive precision - --> tests/ui/excessive_precision.rs:101:20 + --> tests/ui/excessive_precision.rs:108:20 | LL | const _: f64 = 3.0000000000000000e+00; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -192,5 +192,56 @@ LL - const _: f64 = 3.0000000000000000e+00; LL + const _: f64 = 3.0; | -error: aborting due to 16 previous errors +error: float has excessive precision + --> tests/ui/excessive_precision.rs:113:18 + | +LL | let _: f32 = 1.01234567890123456789012345678901234567890; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: consider making it a `const` item + --> tests/ui/excessive_precision.rs:113:5 + | +LL | let _: f32 = 1.01234567890123456789012345678901234567890; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: consider changing the type or truncating it to + | +LL - let _: f32 = 1.01234567890123456789012345678901234567890; +LL + let _: f32 = 1.012_345_7; + | + +error: float has excessive precision + --> tests/ui/excessive_precision.rs:115:18 + | +LL | let _: f64 = 1.01234567890123456789012345678901234567890; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: consider making it a `const` item + --> tests/ui/excessive_precision.rs:115:5 + | +LL | let _: f64 = 1.01234567890123456789012345678901234567890; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: consider changing the type or truncating it to + | +LL - let _: f64 = 1.01234567890123456789012345678901234567890; +LL + let _: f64 = 1.012_345_678_901_234_6; + | + +error: float has excessive precision + --> tests/ui/excessive_precision.rs:127:17 + | +LL | let gamma = 0.5772156649015328606065120900824024310421; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: consider making it a `const` item + --> tests/ui/excessive_precision.rs:127:5 + | +LL | let gamma = 0.5772156649015328606065120900824024310421; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: consider changing the type or truncating it to + | +LL - let gamma = 0.5772156649015328606065120900824024310421; +LL + let gamma = 0.577_215_664_901_532_9; + | + +error: aborting due to 19 previous errors From 8f3c6acb228f40c4bb81b75e90bb84d7579432ad Mon Sep 17 00:00:00 2001 From: yanglsh Date: Thu, 28 Aug 2025 05:59:36 +0800 Subject: [PATCH 114/116] fix: `alloc_instead_of_core` FP when `alloc` is an alias --- clippy_lints/src/std_instead_of_core.rs | 2 ++ tests/ui/std_instead_of_core.fixed | 7 +++++++ tests/ui/std_instead_of_core.rs | 7 +++++++ 3 files changed, 16 insertions(+) diff --git a/clippy_lints/src/std_instead_of_core.rs b/clippy_lints/src/std_instead_of_core.rs index e9534bc63a69..8c4a50041e67 100644 --- a/clippy_lints/src/std_instead_of_core.rs +++ b/clippy_lints/src/std_instead_of_core.rs @@ -126,6 +126,8 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports { && !is_from_proc_macro(cx, &first_segment.ident) && !matches!(def_kind, DefKind::Macro(_)) && let Some(last_segment) = path.segments.last() + && let Res::Def(DefKind::Mod, crate_def_id) = first_segment.res + && crate_def_id.is_crate_root() { let (lint, used_mod, replace_with) = match first_segment.ident.name { sym::std => match cx.tcx.crate_name(def_id.krate) { diff --git a/tests/ui/std_instead_of_core.fixed b/tests/ui/std_instead_of_core.fixed index 603ab0accb0b..c27cec558242 100644 --- a/tests/ui/std_instead_of_core.fixed +++ b/tests/ui/std_instead_of_core.fixed @@ -89,3 +89,10 @@ fn msrv_1_76(_: std::net::IpAddr) {} #[clippy::msrv = "1.77"] fn msrv_1_77(_: core::net::IpAddr) {} //~^ std_instead_of_core + +#[warn(clippy::alloc_instead_of_core)] +fn issue15579() { + use std::alloc; + + let layout = alloc::Layout::new::(); +} diff --git a/tests/ui/std_instead_of_core.rs b/tests/ui/std_instead_of_core.rs index b6d4abad9f8f..7d53f7fc3072 100644 --- a/tests/ui/std_instead_of_core.rs +++ b/tests/ui/std_instead_of_core.rs @@ -89,3 +89,10 @@ fn msrv_1_76(_: std::net::IpAddr) {} #[clippy::msrv = "1.77"] fn msrv_1_77(_: std::net::IpAddr) {} //~^ std_instead_of_core + +#[warn(clippy::alloc_instead_of_core)] +fn issue15579() { + use std::alloc; + + let layout = alloc::Layout::new::(); +} From 06e4ed62db6ebaaa394624f868e76e1bd8b42b5c Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 4 Sep 2025 18:01:10 -0400 Subject: [PATCH 115/116] Set flip1995 to on-vacation --- triagebot.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/triagebot.toml b/triagebot.toml index b2fb50918f58..7b19f8658c08 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -60,6 +60,7 @@ contributing_url = "https://github.com/rust-lang/rust-clippy/blob/master/CONTRIB users_on_vacation = [ "matthiaskrgr", "Manishearth", + "flip1995", ] [assign.owners] From e9810e578a187537d02e33121f004ed5face3375 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 4 Sep 2025 18:18:39 -0400 Subject: [PATCH 116/116] Bump nightly version -> 2025-09-04 --- clippy_utils/README.md | 2 +- rust-toolchain.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_utils/README.md b/clippy_utils/README.md index 2dfe28953d0c..e01f563c49e7 100644 --- a/clippy_utils/README.md +++ b/clippy_utils/README.md @@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain: ``` -nightly-2025-08-22 +nightly-2025-09-04 ``` diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 5497e77e8ad1..ec2f24a0a6d8 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2025-08-22" +channel = "nightly-2025-09-04" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal"