From ea2ea62fd1577cc67dc61d6b1b40e5f7427ed704 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Wed, 4 Jun 2025 13:40:50 +0200 Subject: [PATCH 01/93] Note that using `enumerate()` will swap the arguments The autofix now: - includes the swapping of index and element in the closure in which the content will be consumed; - notes, with applicability `MaybeIncorrect` (because it will be), that the element and the index will be swapped. --- .../src/methods/range_zip_with_len.rs | 96 +++++++++++++++++-- clippy_utils/src/sym.rs | 4 + tests/ui/range.fixed | 16 +++- tests/ui/range.rs | 16 +++- tests/ui/range.stderr | 29 +++++- tests/ui/range_unfixable.rs | 14 +++ tests/ui/range_unfixable.stderr | 12 +++ 7 files changed, 172 insertions(+), 15 deletions(-) create mode 100644 tests/ui/range_unfixable.rs create mode 100644 tests/ui/range_unfixable.stderr diff --git a/clippy_lints/src/methods/range_zip_with_len.rs b/clippy_lints/src/methods/range_zip_with_len.rs index 3a5e321720865..e13df18333e46 100644 --- a/clippy_lints/src/methods/range_zip_with_len.rs +++ b/clippy_lints/src/methods/range_zip_with_len.rs @@ -1,10 +1,9 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet; -use clippy_utils::{SpanlessEq, higher, is_integer_const, is_trait_method}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::{SpanRangeExt as _, snippet_with_applicability}; +use clippy_utils::{SpanlessEq, get_parent_expr, higher, is_integer_const, is_trait_method, sym}; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, QPath}; +use rustc_hir::{Expr, ExprKind, Node, Pat, PatKind, QPath}; use rustc_lint::LateContext; -use rustc_span::sym; use super::RANGE_ZIP_WITH_LEN; @@ -21,14 +20,93 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &' && let ExprKind::Path(QPath::Resolved(_, len_path)) = len_recv.kind && SpanlessEq::new(cx).eq_path_segments(iter_path.segments, len_path.segments) { - span_lint_and_sugg( + span_lint_and_then( cx, RANGE_ZIP_WITH_LEN, expr.span, "using `.zip()` with a range and `.len()`", - "try", - format!("{}.iter().enumerate()", snippet(cx, recv.span, "_")), - Applicability::MachineApplicable, + |diag| { + // If the iterator content is consumed by a pattern with exactly two elements, swap + // the order of those elements. Otherwise, the suggestion will be marked as + // `Applicability::MaybeIncorrect` (because it will be), and a note will be added + // to the diagnostic to underline the swapping of the index and the content. + let pat = methods_pattern(cx, expr).or_else(|| for_loop_pattern(cx, expr)); + let invert_bindings = if let Some(pat) = pat + && pat.span.eq_ctxt(expr.span) + && let PatKind::Tuple([first, second], _) = pat.kind + { + Some((first.span, second.span)) + } else { + None + }; + let mut app = Applicability::MachineApplicable; + let mut suggestions = vec![( + expr.span, + format!( + "{}.iter().enumerate()", + snippet_with_applicability(cx, recv.span, "_", &mut app) + ), + )]; + if let Some((left, right)) = invert_bindings + && let Some(snip_left) = left.get_source_text(cx) + && let Some(snip_right) = right.get_source_text(cx) + { + suggestions.extend([(left, snip_right.to_string()), (right, snip_left.to_string())]); + } else { + app = Applicability::MaybeIncorrect; + } + diag.multipart_suggestion("use", suggestions, app); + if app != Applicability::MachineApplicable { + diag.note("the order of the element and the index will be swapped"); + } + }, ); } } + +/// If `expr` is the argument of a `for` loop, return the loop pattern. +fn for_loop_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Pat<'tcx>> { + cx.tcx.hir_parent_iter(expr.hir_id).find_map(|(_, node)| { + if let Node::Expr(ancestor_expr) = node + && let Some(for_loop) = higher::ForLoop::hir(ancestor_expr) + && for_loop.arg.hir_id == expr.hir_id + { + Some(for_loop.pat) + } else { + None + } + }) +} + +/// If `expr` is the receiver of an `Iterator` method which consumes the iterator elements and feed +/// them to a closure, return the pattern of the closure. +fn methods_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Pat<'tcx>> { + if let Some(parent_expr) = get_parent_expr(cx, expr) + && is_trait_method(cx, expr, sym::Iterator) + && let ExprKind::MethodCall(method, recv, [arg], _) = parent_expr.kind + && recv.hir_id == expr.hir_id + && matches!( + method.ident.name, + sym::all + | sym::any + | sym::filter_map + | sym::find_map + | sym::flat_map + | sym::for_each + | sym::is_partitioned + | sym::is_sorted_by_key + | sym::map + | sym::map_while + | sym::position + | sym::rposition + | sym::try_for_each + ) + && let ExprKind::Closure(closure) = arg.kind + && let body = cx.tcx.hir_body(closure.body) + && let [param] = body.params + { + Some(param.pat) + } else { + None + } +} diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index a544954931bad..1fe9e62fae30c 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -191,8 +191,10 @@ generate! { is_none, is_none_or, is_ok, + is_partitioned, is_some, is_some_and, + is_sorted_by_key, isqrt, itertools, join, @@ -210,6 +212,7 @@ generate! { map_continue, map_or, map_or_else, + map_while, match_indices, matches, max, @@ -349,6 +352,7 @@ generate! { trim_start, trim_start_matches, truncate, + try_for_each, unreachable_pub, unsafe_removed_from_name, unused, diff --git a/tests/ui/range.fixed b/tests/ui/range.fixed index 0e951d88091b7..c8e654600045e 100644 --- a/tests/ui/range.fixed +++ b/tests/ui/range.fixed @@ -1,11 +1,23 @@ #![allow(clippy::useless_vec)] #[warn(clippy::range_zip_with_len)] fn main() { - let v1 = vec![1, 2, 3]; - let v2 = vec![4, 5]; + let v1: Vec = vec![1, 2, 3]; + let v2: Vec = vec![4, 5]; let _x = v1.iter().enumerate(); //~^ range_zip_with_len + //~v range_zip_with_len + for (i, e) in v1.iter().enumerate() { + let _: &u64 = e; + let _: usize = i; + } + + //~v range_zip_with_len + v1.iter().enumerate().for_each(|(i, e)| { + let _: &u64 = e; + let _: usize = i; + }); + let _y = v1.iter().zip(0..v2.len()); // No error } diff --git a/tests/ui/range.rs b/tests/ui/range.rs index 5343801647434..352d517eabdd7 100644 --- a/tests/ui/range.rs +++ b/tests/ui/range.rs @@ -1,11 +1,23 @@ #![allow(clippy::useless_vec)] #[warn(clippy::range_zip_with_len)] fn main() { - let v1 = vec![1, 2, 3]; - let v2 = vec![4, 5]; + let v1: Vec = vec![1, 2, 3]; + let v2: Vec = vec![4, 5]; let _x = v1.iter().zip(0..v1.len()); //~^ range_zip_with_len + //~v range_zip_with_len + for (e, i) in v1.iter().zip(0..v1.len()) { + let _: &u64 = e; + let _: usize = i; + } + + //~v range_zip_with_len + v1.iter().zip(0..v1.len()).for_each(|(e, i)| { + let _: &u64 = e; + let _: usize = i; + }); + let _y = v1.iter().zip(0..v2.len()); // No error } diff --git a/tests/ui/range.stderr b/tests/ui/range.stderr index 798ce1842d8b5..b0a852a69fc5a 100644 --- a/tests/ui/range.stderr +++ b/tests/ui/range.stderr @@ -2,10 +2,35 @@ error: using `.zip()` with a range and `.len()` --> tests/ui/range.rs:6:14 | LL | let _x = v1.iter().zip(0..v1.len()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v1.iter().enumerate()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `v1.iter().enumerate()` | + = note: the order of the element and the index will be swapped = note: `-D clippy::range-zip-with-len` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::range_zip_with_len)]` -error: aborting due to 1 previous error +error: using `.zip()` with a range and `.len()` + --> tests/ui/range.rs:10:19 + | +LL | for (e, i) in v1.iter().zip(0..v1.len()) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - for (e, i) in v1.iter().zip(0..v1.len()) { +LL + for (i, e) in v1.iter().enumerate() { + | + +error: using `.zip()` with a range and `.len()` + --> tests/ui/range.rs:16:5 + | +LL | v1.iter().zip(0..v1.len()).for_each(|(e, i)| { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - v1.iter().zip(0..v1.len()).for_each(|(e, i)| { +LL + v1.iter().enumerate().for_each(|(i, e)| { + | + +error: aborting due to 3 previous errors diff --git a/tests/ui/range_unfixable.rs b/tests/ui/range_unfixable.rs new file mode 100644 index 0000000000000..259be23fa1e5a --- /dev/null +++ b/tests/ui/range_unfixable.rs @@ -0,0 +1,14 @@ +//@no-rustfix +#![allow(clippy::useless_vec)] +#[warn(clippy::range_zip_with_len)] +fn main() { + let v1: Vec = vec![1, 2, 3]; + let v2: Vec = vec![4, 5]; + + // Do not autofix, `filter()` would not consume the iterator. + //~v range_zip_with_len + v1.iter().zip(0..v1.len()).filter(|(_, i)| *i < 2).for_each(|(e, i)| { + let _: &u64 = e; + let _: usize = i; + }); +} diff --git a/tests/ui/range_unfixable.stderr b/tests/ui/range_unfixable.stderr new file mode 100644 index 0000000000000..fb03ea2c05f6b --- /dev/null +++ b/tests/ui/range_unfixable.stderr @@ -0,0 +1,12 @@ +error: using `.zip()` with a range and `.len()` + --> tests/ui/range_unfixable.rs:10:5 + | +LL | v1.iter().zip(0..v1.len()).filter(|(_, i)| *i < 2).for_each(|(e, i)| { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `v1.iter().enumerate()` + | + = note: the order of the element and the index will be swapped + = note: `-D clippy::range-zip-with-len` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::range_zip_with_len)]` + +error: aborting due to 1 previous error + From d43c55be3487277783f9a5445ceeaa01fa558b11 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Tue, 26 Aug 2025 15:54:59 +0200 Subject: [PATCH 02/93] Bless clippy tests. --- tests/ui/author/macro_in_closure.stdout | 32 +++++++++++++++---------- tests/ui/author/macro_in_loop.stdout | 28 ++++++++++++++-------- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/tests/ui/author/macro_in_closure.stdout b/tests/ui/author/macro_in_closure.stdout index 49595e2fec257..786c61e0c018e 100644 --- a/tests/ui/author/macro_in_closure.stdout +++ b/tests/ui/author/macro_in_closure.stdout @@ -10,34 +10,42 @@ if let StmtKind::Let(local) = stmt.kind && paths::STD_IO_STDIO__PRINT.matches_path(cx, func) // Add the path to `clippy_utils::paths` if needed && args.len() == 1 && let ExprKind::Block(block1, None) = args[0].kind - && block1.stmts.len() == 1 + && block1.stmts.len() == 2 && let StmtKind::Let(local1) = block1.stmts[0].kind && let Some(init1) = local1.init - && let ExprKind::Array(elements) = init1.kind + && let ExprKind::Tup(elements) = init1.kind && elements.len() == 1 - && let ExprKind::Call(func1, args1) = elements[0].kind - && paths::CORE_FMT_RT_ARGUMENT_NEW_DISPLAY.matches_path(cx, func1) // Add the path to `clippy_utils::paths` if needed - && args1.len() == 1 - && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = args1[0].kind + && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = elements[0].kind && let PatKind::Binding(BindingMode::NONE, _, name, None) = local1.pat.kind && name.as_str() == "args" + && let StmtKind::Let(local2) = block1.stmts[1].kind + && let Some(init2) = local2.init + && let ExprKind::Array(elements1) = init2.kind + && elements1.len() == 1 + && let ExprKind::Call(func1, args1) = elements1[0].kind + && paths::CORE_FMT_RT_ARGUMENT_NEW_DISPLAY.matches_path(cx, func1) // Add the path to `clippy_utils::paths` if needed + && args1.len() == 1 + && let ExprKind::Field(object, field_name) = args1[0].kind + && field_name.as_str() == "0" + && let PatKind::Binding(BindingMode::NONE, _, name1, None) = local2.pat.kind + && name1.as_str() == "args" && let Some(trailing_expr) = block1.expr && let ExprKind::Call(func2, args2) = trailing_expr.kind && paths::CORE_FMT_RT_NEW_V1.matches_path(cx, func2) // Add the path to `clippy_utils::paths` if needed && args2.len() == 2 && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner1) = args2[0].kind - && let ExprKind::Array(elements1) = inner1.kind - && elements1.len() == 2 - && let ExprKind::Lit(ref lit) = elements1[0].kind + && let ExprKind::Array(elements2) = inner1.kind + && elements2.len() == 2 + && let ExprKind::Lit(ref lit) = elements2[0].kind && let LitKind::Str(s, _) = lit.node && s.as_str() == "" - && let ExprKind::Lit(ref lit1) = elements1[1].kind + && let ExprKind::Lit(ref lit1) = elements2[1].kind && let LitKind::Str(s1, _) = lit1.node && s1.as_str() == "\n" && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner2) = args2[1].kind && block.expr.is_none() - && let PatKind::Binding(BindingMode::NONE, _, name1, None) = local.pat.kind - && name1.as_str() == "print_text" + && let PatKind::Binding(BindingMode::NONE, _, name2, None) = local.pat.kind + && name2.as_str() == "print_text" { // report your lint here } diff --git a/tests/ui/author/macro_in_loop.stdout b/tests/ui/author/macro_in_loop.stdout index 4fc7b49464db3..80717900b5255 100644 --- a/tests/ui/author/macro_in_loop.stdout +++ b/tests/ui/author/macro_in_loop.stdout @@ -20,28 +20,36 @@ if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::Fo && paths::STD_IO_STDIO__PRINT.matches_path(cx, func) // Add the path to `clippy_utils::paths` if needed && args.len() == 1 && let ExprKind::Block(block2, None) = args[0].kind - && block2.stmts.len() == 1 + && block2.stmts.len() == 2 && let StmtKind::Let(local) = block2.stmts[0].kind && let Some(init) = local.init - && let ExprKind::Array(elements) = init.kind + && let ExprKind::Tup(elements) = init.kind && elements.len() == 1 - && let ExprKind::Call(func1, args1) = elements[0].kind - && paths::CORE_FMT_RT_ARGUMENT_NEW_DISPLAY.matches_path(cx, func1) // Add the path to `clippy_utils::paths` if needed - && args1.len() == 1 - && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = args1[0].kind + && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = elements[0].kind && let PatKind::Binding(BindingMode::NONE, _, name1, None) = local.pat.kind && name1.as_str() == "args" + && let StmtKind::Let(local1) = block2.stmts[1].kind + && let Some(init1) = local1.init + && let ExprKind::Array(elements1) = init1.kind + && elements1.len() == 1 + && let ExprKind::Call(func1, args1) = elements1[0].kind + && paths::CORE_FMT_RT_ARGUMENT_NEW_DISPLAY.matches_path(cx, func1) // Add the path to `clippy_utils::paths` if needed + && args1.len() == 1 + && let ExprKind::Field(object, field_name) = args1[0].kind + && field_name.as_str() == "0" + && let PatKind::Binding(BindingMode::NONE, _, name2, None) = local1.pat.kind + && name2.as_str() == "args" && let Some(trailing_expr) = block2.expr && let ExprKind::Call(func2, args2) = trailing_expr.kind && paths::CORE_FMT_RT_NEW_V1.matches_path(cx, func2) // Add the path to `clippy_utils::paths` if needed && args2.len() == 2 && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner1) = args2[0].kind - && let ExprKind::Array(elements1) = inner1.kind - && elements1.len() == 2 - && let ExprKind::Lit(ref lit2) = elements1[0].kind + && let ExprKind::Array(elements2) = inner1.kind + && elements2.len() == 2 + && let ExprKind::Lit(ref lit2) = elements2[0].kind && let LitKind::Str(s, _) = lit2.node && s.as_str() == "" - && let ExprKind::Lit(ref lit3) = elements1[1].kind + && let ExprKind::Lit(ref lit3) = elements2[1].kind && let LitKind::Str(s1, _) = lit3.node && s1.as_str() == "\n" && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner2) = args2[1].kind From e513ecc939b61f9020c511e4fc4daff892725cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Justus=20Fl=C3=BCgel?= Date: Sun, 7 Sep 2025 15:18:47 +0200 Subject: [PATCH 03/93] Fix `explicit_deref_methods` triggering from within proc macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Justus Flügel --- clippy_lints/src/dereference.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 9337816c80d9d..f82ea9844ed62 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -3,7 +3,8 @@ use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::ty::{adjust_derefs_manually_drop, implements_trait, is_manually_drop, peel_and_count_ty_refs}; use clippy_utils::{ - DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, + DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_from_proc_macro, is_lint_allowed, + path_to_local, }; use rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::fx::FxIndexMap; @@ -260,6 +261,13 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { }; self.skip_expr = skip_expr; + if is_from_proc_macro(cx, expr) { + if let Some((state, data)) = self.state.take() { + report(cx, expr, state, data, cx.typeck_results()); + } + return; + } + match (self.state.take(), kind) { (None, kind) => { let expr_ty = typeck.expr_ty(expr); From b92330b7b87ba86abccd6b4f4298652a9cbed606 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 30 Aug 2025 18:08:50 +0200 Subject: [PATCH 04/93] fix `nonstandard_macro_braces`: suggest trailing semicolon when needed --- clippy_lints/src/nonstandard_macro_braces.rs | 91 +++++++++++++------ .../conf_nonstandard_macro_braces.fixed | 8 ++ .../conf_nonstandard_macro_braces.rs | 8 ++ .../conf_nonstandard_macro_braces.stderr | 8 +- 4 files changed, 87 insertions(+), 28 deletions(-) diff --git a/clippy_lints/src/nonstandard_macro_braces.rs b/clippy_lints/src/nonstandard_macro_braces.rs index 83f7d9319697b..3a8a4dd0c7137 100644 --- a/clippy_lints/src/nonstandard_macro_braces.rs +++ b/clippy_lints/src/nonstandard_macro_braces.rs @@ -16,8 +16,8 @@ declare_clippy_lint! { /// Checks that common macros are used with consistent bracing. /// /// ### Why is this bad? - /// This is mostly a consistency lint although using () or [] - /// doesn't give you a semicolon in item position, which can be unexpected. + /// Having non-conventional braces on well-stablished macros can be confusing + /// when debugging, and they bring incosistencies with the rest of the ecosystem. /// /// ### Example /// ```no_run @@ -33,8 +33,12 @@ declare_clippy_lint! { "check consistent use of braces in macro" } -/// The (callsite span, (open brace, close brace), source snippet) -type MacroInfo = (Span, (char, char), SourceText); +struct MacroInfo { + callsite_span: Span, + callsite_snippet: SourceText, + old_open_brace: char, + braces: (char, char), +} pub struct MacroBraces { macro_braces: FxHashMap, @@ -54,30 +58,58 @@ impl_lint_pass!(MacroBraces => [NONSTANDARD_MACRO_BRACES]); impl EarlyLintPass for MacroBraces { fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { - if let Some((span, braces, snip)) = is_offending_macro(cx, item.span, self) { - emit_help(cx, &snip, braces, span); - self.done.insert(span); + if let Some(MacroInfo { + callsite_span, + callsite_snippet, + braces, + .. + }) = is_offending_macro(cx, item.span, self) + { + emit_help(cx, &callsite_snippet, braces, callsite_span, false); + self.done.insert(callsite_span); } } fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) { - if let Some((span, braces, snip)) = is_offending_macro(cx, stmt.span, self) { - emit_help(cx, &snip, braces, span); - self.done.insert(span); + if let Some(MacroInfo { + callsite_span, + callsite_snippet, + braces, + old_open_brace, + }) = is_offending_macro(cx, stmt.span, self) + { + // if we turn `macro!{}` into `macro!()`/`macro![]`, we'll no longer get the implicit + // trailing semicolon, see #9913 + // NOTE: `stmt.kind != StmtKind::MacCall` because `EarlyLintPass` happens after macro expansion + let add_semi = matches!(stmt.kind, ast::StmtKind::Expr(..)) && old_open_brace == '{'; + emit_help(cx, &callsite_snippet, braces, callsite_span, add_semi); + self.done.insert(callsite_span); } } fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) { - if let Some((span, braces, snip)) = is_offending_macro(cx, expr.span, self) { - emit_help(cx, &snip, braces, span); - self.done.insert(span); + if let Some(MacroInfo { + callsite_span, + callsite_snippet, + braces, + .. + }) = is_offending_macro(cx, expr.span, self) + { + emit_help(cx, &callsite_snippet, braces, callsite_span, false); + self.done.insert(callsite_span); } } fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) { - if let Some((span, braces, snip)) = is_offending_macro(cx, ty.span, self) { - emit_help(cx, &snip, braces, span); - self.done.insert(span); + if let Some(MacroInfo { + callsite_span, + braces, + callsite_snippet, + .. + }) = is_offending_macro(cx, ty.span, self) + { + emit_help(cx, &callsite_snippet, braces, callsite_span, false); + self.done.insert(callsite_span); } } } @@ -90,39 +122,44 @@ fn is_offending_macro(cx: &EarlyContext<'_>, span: Span, mac_braces: &MacroBrace .last() .is_some_and(|e| e.macro_def_id.is_some_and(DefId::is_local)) }; - let span_call_site = span.ctxt().outer_expn_data().call_site; + let callsite_span = span.ctxt().outer_expn_data().call_site; if let ExpnKind::Macro(MacroKind::Bang, mac_name) = span.ctxt().outer_expn_data().kind && let name = mac_name.as_str() && let Some(&braces) = mac_braces.macro_braces.get(name) - && let Some(snip) = span_call_site.get_source_text(cx) + && let Some(snip) = callsite_span.get_source_text(cx) // we must check only invocation sites // https://github.com/rust-lang/rust-clippy/issues/7422 - && snip.starts_with(&format!("{name}!")) + && let Some(macro_args_str) = snip.strip_prefix(name).and_then(|snip| snip.strip_prefix('!')) + && let Some(old_open_brace @ ('{' | '(' | '[')) = macro_args_str.trim_start().chars().next() + && old_open_brace != braces.0 && unnested_or_local() - // make formatting consistent - && let c = snip.replace(' ', "") - && !c.starts_with(&format!("{name}!{}", braces.0)) - && !mac_braces.done.contains(&span_call_site) + && !mac_braces.done.contains(&callsite_span) { - Some((span_call_site, braces, snip)) + Some(MacroInfo { + callsite_span, + callsite_snippet: snip, + old_open_brace, + braces, + }) } else { None } } -fn emit_help(cx: &EarlyContext<'_>, snip: &str, (open, close): (char, char), span: Span) { +fn emit_help(cx: &EarlyContext<'_>, snip: &str, (open, close): (char, char), span: Span, add_semi: bool) { + let semi = if add_semi { ";" } else { "" }; if let Some((macro_name, macro_args_str)) = snip.split_once('!') { let mut macro_args = macro_args_str.trim().to_string(); // now remove the wrong braces - macro_args.remove(0); macro_args.pop(); + macro_args.remove(0); span_lint_and_sugg( cx, NONSTANDARD_MACRO_BRACES, span, format!("use of irregular braces for `{macro_name}!` macro"), "consider writing", - format!("{macro_name}!{open}{macro_args}{close}"), + format!("{macro_name}!{open}{macro_args}{close}{semi}"), Applicability::MachineApplicable, ); } diff --git a/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.fixed b/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.fixed index 8da607ec65842..419e62f92f469 100644 --- a/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.fixed +++ b/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.fixed @@ -67,3 +67,11 @@ fn main() { printlnfoo!["test if printlnfoo is triggered by println"]; } + +#[rustfmt::skip] +#[expect(clippy::no_effect)] +fn issue9913() { + println!("hello world"); + [0]; // separate statement, not indexing into the result of println. + //~^^ nonstandard_macro_braces +} diff --git a/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs b/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs index e35844a209fa5..b0bbced4ea3c2 100644 --- a/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs +++ b/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs @@ -67,3 +67,11 @@ fn main() { printlnfoo!["test if printlnfoo is triggered by println"]; } + +#[rustfmt::skip] +#[expect(clippy::no_effect)] +fn issue9913() { + println! {"hello world"} + [0]; // separate statement, not indexing into the result of println. + //~^^ nonstandard_macro_braces +} diff --git a/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.stderr b/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.stderr index fda6addc7aa35..87325f05c9bcd 100644 --- a/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.stderr +++ b/tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.stderr @@ -54,5 +54,11 @@ error: use of irregular braces for `eprint!` macro LL | eprint!("test if user config overrides defaults"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `eprint!["test if user config overrides defaults"]` -error: aborting due to 8 previous errors +error: use of irregular braces for `println!` macro + --> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:74:5 + | +LL | println! {"hello world"} + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `println!("hello world");` + +error: aborting due to 9 previous errors From 05156f4e8b1c0e0ef6eedcf99cfedf142c8f56fb Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 8 Sep 2025 21:59:03 +0200 Subject: [PATCH 05/93] clean-up `single_match` a bit --- clippy_lints/src/matches/single_match.rs | 27 +++++++++++++----------- tests/ui/single_match.fixed | 5 +++-- tests/ui/single_match.rs | 5 +++-- tests/ui/single_match.stderr | 20 +++++++++--------- 4 files changed, 31 insertions(+), 26 deletions(-) diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index bcf079b700703..0470b5443d189 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -24,17 +24,16 @@ use super::{MATCH_BOOL, SINGLE_MATCH, SINGLE_MATCH_ELSE}; /// span, e.g. a string literal `"//"`, but we know that this isn't the case for empty /// match arms. fn empty_arm_has_comment(cx: &LateContext<'_>, span: Span) -> bool { - if let Some(ff) = span.get_source_range(cx) - && let Some(text) = ff.as_str() - { - text.as_bytes().windows(2).any(|w| w == b"//" || w == b"/*") - } else { - false - } + span.check_source_text(cx, |text| text.as_bytes().windows(2).any(|w| w == b"//" || w == b"/*")) } -#[rustfmt::skip] -pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>], expr: &'tcx Expr<'_>, contains_comments: bool) { +pub(crate) fn check<'tcx>( + cx: &LateContext<'tcx>, + ex: &'tcx Expr<'_>, + arms: &'tcx [Arm<'_>], + expr: &'tcx Expr<'_>, + contains_comments: bool, +) { if let [arm1, arm2] = arms && !arms.iter().any(|arm| arm.guard.is_some() || arm.pat.span.from_expansion()) && !expr.span.from_expansion() @@ -226,13 +225,13 @@ enum PatState<'a> { Wild, /// A std enum we know won't be extended. Tracks the states of each variant separately. /// - /// This is not used for `Option` since it uses the current pattern to track it's state. + /// This is not used for `Option` since it uses the current pattern to track its state. StdEnum(&'a mut [PatState<'a>]), /// Either the initial state for a pattern or a non-std enum. There is currently no need to /// distinguish these cases. /// /// For non-std enums there's no need to track the state of sub-patterns as the state of just - /// this pattern on it's own is enough for linting. Consider two cases: + /// this pattern on its own is enough for linting. Consider two cases: /// * This enum has no wild match. This case alone is enough to determine we can lint. /// * This enum has a wild match and therefore all sub-patterns also have a wild match. /// @@ -380,7 +379,11 @@ impl<'a> PatState<'a> { self.add_pat(cx, pat) }, PatKind::Tuple([sub_pat], pos) - if pos.as_opt_usize().is_none() || cx.typeck.pat_ty(pat).tuple_fields().len() == 1 => + // `pat` looks like `(sub_pat)`, without a `..` -- has only one sub-pattern + if pos.as_opt_usize().is_none() + // `pat` looks like `(sub_pat, ..)` or `(.., sub_pat)`, but its type is a unary tuple, + // so it still only has one sub-pattern + || cx.typeck.pat_ty(pat).tuple_fields().len() == 1 => { self.add_pat(cx, sub_pat) }, diff --git a/tests/ui/single_match.fixed b/tests/ui/single_match.fixed index db5107600ee6d..03982b069e675 100644 --- a/tests/ui/single_match.fixed +++ b/tests/ui/single_match.fixed @@ -218,7 +218,7 @@ fn main() { }; } -fn issue_10808(bar: Option) { +fn issue10808(bar: Option) { if let Some(v) = bar { unsafe { let r = &v as *const i32; println!("{}", *r); @@ -330,6 +330,7 @@ pub struct Data([u8; 4]); const DATA: Data = Data([1, 2, 3, 4]); const CONST_I32: i32 = 1; +// https://github.com/rust-lang/rust-clippy/issues/13012 fn irrefutable_match() { println!(); //~^^^^ single_match @@ -367,7 +368,7 @@ fn irrefutable_match() { //~| NOTE: you might want to preserve the comments from inside the `match` } -fn issue_14493() { +fn issue14493() { macro_rules! mac { (some) => { Some(42) diff --git a/tests/ui/single_match.rs b/tests/ui/single_match.rs index a367b94c4ca6b..e28128e35adab 100644 --- a/tests/ui/single_match.rs +++ b/tests/ui/single_match.rs @@ -269,7 +269,7 @@ fn main() { }; } -fn issue_10808(bar: Option) { +fn issue10808(bar: Option) { match bar { Some(v) => unsafe { let r = &v as *const i32; @@ -397,6 +397,7 @@ pub struct Data([u8; 4]); const DATA: Data = Data([1, 2, 3, 4]); const CONST_I32: i32 = 1; +// https://github.com/rust-lang/rust-clippy/issues/13012 fn irrefutable_match() { match DATA { DATA => println!(), @@ -462,7 +463,7 @@ fn irrefutable_match() { //~| NOTE: you might want to preserve the comments from inside the `match` } -fn issue_14493() { +fn issue14493() { macro_rules! mac { (some) => { Some(42) diff --git a/tests/ui/single_match.stderr b/tests/ui/single_match.stderr index 1a4edc45c928d..ba8bc5af5a608 100644 --- a/tests/ui/single_match.stderr +++ b/tests/ui/single_match.stderr @@ -225,7 +225,7 @@ LL | | } | |_____^ help: try: `if &s[0..3] == b"foo" { println!() }` error: this pattern is irrefutable, `match` is useless - --> tests/ui/single_match.rs:401:5 + --> tests/ui/single_match.rs:402:5 | LL | / match DATA { LL | | DATA => println!(), @@ -234,7 +234,7 @@ LL | | } | |_____^ help: try: `println!();` error: this pattern is irrefutable, `match` is useless - --> tests/ui/single_match.rs:407:5 + --> tests/ui/single_match.rs:408:5 | LL | / match CONST_I32 { LL | | CONST_I32 => println!(), @@ -243,7 +243,7 @@ LL | | } | |_____^ help: try: `println!();` error: this pattern is irrefutable, `match` is useless - --> tests/ui/single_match.rs:414:5 + --> tests/ui/single_match.rs:415:5 | LL | / match i { LL | | i => { @@ -263,7 +263,7 @@ LL + } | error: this pattern is irrefutable, `match` is useless - --> tests/ui/single_match.rs:423:5 + --> tests/ui/single_match.rs:424:5 | LL | / match i { LL | | i => {}, @@ -272,7 +272,7 @@ LL | | } | |_____^ help: `match` expression can be removed error: this pattern is irrefutable, `match` is useless - --> tests/ui/single_match.rs:429:5 + --> tests/ui/single_match.rs:430:5 | LL | / match i { LL | | i => (), @@ -281,7 +281,7 @@ LL | | } | |_____^ help: `match` expression can be removed error: this pattern is irrefutable, `match` is useless - --> tests/ui/single_match.rs:435:5 + --> tests/ui/single_match.rs:436:5 | LL | / match CONST_I32 { LL | | CONST_I32 => println!(), @@ -290,7 +290,7 @@ LL | | } | |_____^ help: try: `println!();` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> tests/ui/single_match.rs:443:5 + --> tests/ui/single_match.rs:444:5 | LL | / match x.pop() { LL | | // bla @@ -302,7 +302,7 @@ LL | | } = note: you might want to preserve the comments from inside the `match` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> tests/ui/single_match.rs:452:5 + --> tests/ui/single_match.rs:453:5 | LL | / match x.pop() { LL | | // bla @@ -322,7 +322,7 @@ LL + } | error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> tests/ui/single_match.rs:478:5 + --> tests/ui/single_match.rs:479:5 | LL | / match mac!(some) { LL | | Some(u) => println!("{u}"), @@ -331,7 +331,7 @@ LL | | } | |_____^ help: try: `if let Some(u) = mac!(some) { println!("{u}") }` error: you seem to be trying to use `match` for an equality check. Consider using `if` - --> tests/ui/single_match.rs:486:5 + --> tests/ui/single_match.rs:487:5 | LL | / match mac!(str) { LL | | "foo" => println!("eq"), From 593a9c98aef3819f727f2fc9fa8abe58ea784d07 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 9 Sep 2025 22:45:29 +0200 Subject: [PATCH 06/93] fix(collapsible{,_else}_if): respect `#[expect]` on inner `if` --- clippy_lints/src/collapsible_if.rs | 73 +++++++++++++------ clippy_utils/src/sym.rs | 2 + .../collapsible_if/collapsible_else_if.fixed | 19 ++++- .../collapsible_if/collapsible_else_if.rs | 19 ++++- tests/ui/collapsible_else_if.fixed | 27 +++++++ tests/ui/collapsible_else_if.rs | 27 +++++++ tests/ui/collapsible_else_if.stderr | 2 +- tests/ui/collapsible_else_if_unfixable.rs | 22 ++++++ tests/ui/collapsible_else_if_unfixable.stderr | 17 +++++ tests/ui/collapsible_if.fixed | 18 +++++ tests/ui/collapsible_if.rs | 18 +++++ tests/ui/collapsible_if.stderr | 2 +- tests/ui/collapsible_if_unfixable.rs | 20 +++++ tests/ui/collapsible_if_unfixable.stderr | 17 +++++ 14 files changed, 256 insertions(+), 27 deletions(-) create mode 100644 tests/ui/collapsible_else_if_unfixable.rs create mode 100644 tests/ui/collapsible_else_if_unfixable.stderr create mode 100644 tests/ui/collapsible_if_unfixable.rs create mode 100644 tests/ui/collapsible_if_unfixable.stderr diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index ad610fbd8d2c3..b239c20ab4d89 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -1,16 +1,16 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block_with_applicability}; -use clippy_utils::{span_contains_non_whitespace, tokenize_with_text}; -use rustc_ast::BinOpKind; +use clippy_utils::{span_contains_non_whitespace, sym, tokenize_with_text}; +use rustc_ast::{BinOpKind, MetaItemInner}; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind, StmtKind}; use rustc_lexer::TokenKind; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{LateContext, LateLintPass, Level}; use rustc_session::impl_lint_pass; use rustc_span::source_map::SourceMap; -use rustc_span::{BytePos, Span}; +use rustc_span::{BytePos, Span, Symbol}; declare_clippy_lint! { /// ### What it does @@ -95,14 +95,14 @@ impl CollapsibleIf { fn check_collapsible_else_if(&self, cx: &LateContext<'_>, then_span: Span, else_block: &Block<'_>) { if let Some(else_) = expr_block(else_block) - && cx.tcx.hir_attrs(else_.hir_id).is_empty() && !else_.span.from_expansion() && let ExprKind::If(else_if_cond, ..) = else_.kind - && !block_starts_with_significant_tokens(cx, else_block, else_, self.lint_commented_code) + && self.check_significant_tokens_and_expect_attrs(cx, else_block, else_, sym::collapsible_else_if) { - span_lint_and_then( + span_lint_hir_and_then( cx, COLLAPSIBLE_ELSE_IF, + else_.hir_id, else_block.span, "this `else { if .. }` block can be collapsed", |diag| { @@ -166,15 +166,15 @@ impl CollapsibleIf { fn check_collapsible_if_if(&self, cx: &LateContext<'_>, expr: &Expr<'_>, check: &Expr<'_>, then: &Block<'_>) { if let Some(inner) = expr_block(then) - && cx.tcx.hir_attrs(inner.hir_id).is_empty() && let ExprKind::If(check_inner, _, None) = &inner.kind && self.eligible_condition(cx, check_inner) && expr.span.eq_ctxt(inner.span) - && !block_starts_with_significant_tokens(cx, then, inner, self.lint_commented_code) + && self.check_significant_tokens_and_expect_attrs(cx, then, inner, sym::collapsible_if) { - span_lint_and_then( + span_lint_hir_and_then( cx, COLLAPSIBLE_IF, + inner.hir_id, expr.span, "this `if` statement can be collapsed", |diag| { @@ -219,6 +219,45 @@ impl CollapsibleIf { !matches!(cond.kind, ExprKind::Let(..)) || (cx.tcx.sess.edition().at_least_rust_2024() && self.msrv.meets(cx, msrvs::LET_CHAINS)) } + + // Check that nothing significant can be found between the initial `{` of `inner_if` and + // the beginning of `inner_if_expr`... + // + // Unless it's only an `#[expect(clippy::collapsible{,_else}_if)]` attribute, in which case we + // _do_ need to lint, in order to actually fulfill its expectation (#13365) + fn check_significant_tokens_and_expect_attrs( + &self, + cx: &LateContext<'_>, + inner_if: &Block<'_>, + inner_if_expr: &Expr<'_>, + expected_lint_name: Symbol, + ) -> bool { + match cx.tcx.hir_attrs(inner_if_expr.hir_id) { + [] => { + // There aren't any attributes, so just check for significant tokens + let span = inner_if.span.split_at(1).1.until(inner_if_expr.span); + !span_contains_non_whitespace(cx, span, self.lint_commented_code) + }, + + [attr] + if matches!(Level::from_attr(attr), Some((Level::Expect, _))) + && let Some(metas) = attr.meta_item_list() + && let Some(MetaItemInner::MetaItem(meta_item)) = metas.first() + && let [tool, lint_name] = meta_item.path.segments.as_slice() + && tool.ident.name == sym::clippy + && [expected_lint_name, sym::style, sym::all].contains(&lint_name.ident.name) => + { + // There is an `expect` attribute -- check that there is no _other_ significant text + let span_before_attr = inner_if.span.split_at(1).1.until(attr.span()); + let span_after_attr = attr.span().between(inner_if_expr.span); + !span_contains_non_whitespace(cx, span_before_attr, self.lint_commented_code) + && !span_contains_non_whitespace(cx, span_after_attr, self.lint_commented_code) + }, + + // There are other attributes, which are significant tokens -- check failed + _ => false, + } + } } impl_lint_pass!(CollapsibleIf => [COLLAPSIBLE_IF, COLLAPSIBLE_ELSE_IF]); @@ -242,18 +281,6 @@ impl LateLintPass<'_> for CollapsibleIf { } } -// Check that nothing significant can be found but whitespaces between the initial `{` of `block` -// and the beginning of `stop_at`. -fn block_starts_with_significant_tokens( - cx: &LateContext<'_>, - block: &Block<'_>, - stop_at: &Expr<'_>, - lint_commented_code: bool, -) -> bool { - let span = block.span.split_at(1).1.until(stop_at.span); - span_contains_non_whitespace(cx, span, lint_commented_code) -} - /// 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>> { diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index 278101ac27f96..463711195bb0c 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -111,6 +111,8 @@ generate! { clone_into, cloned, cognitive_complexity, + collapsible_else_if, + collapsible_if, collect, const_ptr, contains, diff --git a/tests/ui-toml/collapsible_if/collapsible_else_if.fixed b/tests/ui-toml/collapsible_if/collapsible_else_if.fixed index 0dc0fc230c8de..ec45dfd2033ad 100644 --- a/tests/ui-toml/collapsible_if/collapsible_else_if.fixed +++ b/tests/ui-toml/collapsible_if/collapsible_else_if.fixed @@ -1,7 +1,7 @@ #![allow(clippy::eq_op, clippy::nonminimal_bool)] +#![warn(clippy::collapsible_if)] #[rustfmt::skip] -#[warn(clippy::collapsible_if)] fn main() { let (x, y) = ("hello", "world"); @@ -48,3 +48,20 @@ fn main() { } //~^^^^^^ collapsible_else_if } + +fn issue_13365() { + // the comments don't stop us from linting, so the the `expect` *will* be fulfilled + if true { + } else { + // some other text before + #[expect(clippy::collapsible_else_if)] + if false {} + } + + if true { + } else { + #[expect(clippy::collapsible_else_if)] + // some other text after + if false {} + } +} diff --git a/tests/ui-toml/collapsible_if/collapsible_else_if.rs b/tests/ui-toml/collapsible_if/collapsible_else_if.rs index 8344c122f16c8..54315a3c32bf9 100644 --- a/tests/ui-toml/collapsible_if/collapsible_else_if.rs +++ b/tests/ui-toml/collapsible_if/collapsible_else_if.rs @@ -1,7 +1,7 @@ #![allow(clippy::eq_op, clippy::nonminimal_bool)] +#![warn(clippy::collapsible_if)] #[rustfmt::skip] -#[warn(clippy::collapsible_if)] fn main() { let (x, y) = ("hello", "world"); @@ -53,3 +53,20 @@ fn main() { } //~^^^^^^ collapsible_else_if } + +fn issue_13365() { + // the comments don't stop us from linting, so the the `expect` *will* be fulfilled + if true { + } else { + // some other text before + #[expect(clippy::collapsible_else_if)] + if false {} + } + + if true { + } else { + #[expect(clippy::collapsible_else_if)] + // some other text after + if false {} + } +} diff --git a/tests/ui/collapsible_else_if.fixed b/tests/ui/collapsible_else_if.fixed index 3d709fe9b8e0e..da958f76a5ca0 100644 --- a/tests/ui/collapsible_else_if.fixed +++ b/tests/ui/collapsible_else_if.fixed @@ -87,6 +87,33 @@ fn issue_7318() { //~^^^ collapsible_else_if } +fn issue_13365() { + // all the `expect`s that we should fulfill + if true { + } else { + #[expect(clippy::collapsible_else_if)] + if false {} + } + + if true { + } else { + #[expect(clippy::style)] + if false {} + } + + if true { + } else { + #[expect(clippy::all)] + if false {} + } + + if true { + } else { + #[expect(warnings)] + if false {} + } +} + fn issue14799() { use std::ops::ControlFlow; diff --git a/tests/ui/collapsible_else_if.rs b/tests/ui/collapsible_else_if.rs index 51868e039086f..06af49f2f6f3b 100644 --- a/tests/ui/collapsible_else_if.rs +++ b/tests/ui/collapsible_else_if.rs @@ -103,6 +103,33 @@ fn issue_7318() { //~^^^ collapsible_else_if } +fn issue_13365() { + // all the `expect`s that we should fulfill + if true { + } else { + #[expect(clippy::collapsible_else_if)] + if false {} + } + + if true { + } else { + #[expect(clippy::style)] + if false {} + } + + if true { + } else { + #[expect(clippy::all)] + if false {} + } + + if true { + } else { + #[expect(warnings)] + if false {} + } +} + fn issue14799() { use std::ops::ControlFlow; diff --git a/tests/ui/collapsible_else_if.stderr b/tests/ui/collapsible_else_if.stderr index 1a7bcec7fd5df..ce1da593a8e93 100644 --- a/tests/ui/collapsible_else_if.stderr +++ b/tests/ui/collapsible_else_if.stderr @@ -151,7 +151,7 @@ LL | | } | |_____^ help: collapse nested if block: `if false {}` error: this `else { if .. }` block can be collapsed - --> tests/ui/collapsible_else_if.rs:130:12 + --> tests/ui/collapsible_else_if.rs:157:12 | LL | } else { | ____________^ diff --git a/tests/ui/collapsible_else_if_unfixable.rs b/tests/ui/collapsible_else_if_unfixable.rs new file mode 100644 index 0000000000000..e5c18cf3ba901 --- /dev/null +++ b/tests/ui/collapsible_else_if_unfixable.rs @@ -0,0 +1,22 @@ +//@no-rustfix +#![warn(clippy::collapsible_else_if)] + +fn issue_13365() { + // in the following examples, we won't lint because of the comments, + // so the the `expect` will be unfulfilled + if true { + } else { + // some other text before + #[expect(clippy::collapsible_else_if)] + if false {} + } + //~^^^ ERROR: this lint expectation is unfulfilled + + if true { + } else { + #[expect(clippy::collapsible_else_if)] + // some other text after + if false {} + } + //~^^^^ ERROR: this lint expectation is unfulfilled +} diff --git a/tests/ui/collapsible_else_if_unfixable.stderr b/tests/ui/collapsible_else_if_unfixable.stderr new file mode 100644 index 0000000000000..b461ceba6de7f --- /dev/null +++ b/tests/ui/collapsible_else_if_unfixable.stderr @@ -0,0 +1,17 @@ +error: this lint expectation is unfulfilled + --> tests/ui/collapsible_else_if_unfixable.rs:10:18 + | +LL | #[expect(clippy::collapsible_else_if)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D unfulfilled-lint-expectations` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unfulfilled_lint_expectations)]` + +error: this lint expectation is unfulfilled + --> tests/ui/collapsible_else_if_unfixable.rs:17:18 + | +LL | #[expect(clippy::collapsible_else_if)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/collapsible_if.fixed b/tests/ui/collapsible_if.fixed index 78354c2d7cf88..ca9d02ff2d4f8 100644 --- a/tests/ui/collapsible_if.fixed +++ b/tests/ui/collapsible_if.fixed @@ -144,6 +144,24 @@ fn layout_check() -> u32 { //~^^^^^ collapsible_if } +fn issue13365() { + // all the `expect`s that we should fulfill + if true { + #[expect(clippy::collapsible_if)] + if true {} + } + + if true { + #[expect(clippy::style)] + if true {} + } + + if true { + #[expect(clippy::all)] + if true {} + } +} + fn issue14722() { let x = if true { Some(1) diff --git a/tests/ui/collapsible_if.rs b/tests/ui/collapsible_if.rs index 5d9afa109569d..9ac68ecd4cac0 100644 --- a/tests/ui/collapsible_if.rs +++ b/tests/ui/collapsible_if.rs @@ -154,6 +154,24 @@ fn layout_check() -> u32 { //~^^^^^ collapsible_if } +fn issue13365() { + // all the `expect`s that we should fulfill + if true { + #[expect(clippy::collapsible_if)] + if true {} + } + + if true { + #[expect(clippy::style)] + if true {} + } + + if true { + #[expect(clippy::all)] + if true {} + } +} + fn issue14722() { let x = if true { Some(1) diff --git a/tests/ui/collapsible_if.stderr b/tests/ui/collapsible_if.stderr index a685cc2e9291d..b1f26630f529b 100644 --- a/tests/ui/collapsible_if.stderr +++ b/tests/ui/collapsible_if.stderr @@ -191,7 +191,7 @@ LL ~ ; 3 | error: this `if` statement can be collapsed - --> tests/ui/collapsible_if.rs:178:5 + --> tests/ui/collapsible_if.rs:196:5 | LL | / if true { LL | | (if true { diff --git a/tests/ui/collapsible_if_unfixable.rs b/tests/ui/collapsible_if_unfixable.rs new file mode 100644 index 0000000000000..643520ac0f5d9 --- /dev/null +++ b/tests/ui/collapsible_if_unfixable.rs @@ -0,0 +1,20 @@ +//@ no-rustfix +#![warn(clippy::collapsible_if)] + +fn issue13365() { + // in the following examples, we won't lint because of the comments, + // so the the `expect` will be unfulfilled + if true { + // don't collapsible because of this comment + #[expect(clippy::collapsible_if)] + if true {} + } + //~^^^ ERROR: this lint expectation is unfulfilled + + if true { + #[expect(clippy::collapsible_if)] + // don't collapsible because of this comment + if true {} + } + //~^^^^ ERROR: this lint expectation is unfulfilled +} diff --git a/tests/ui/collapsible_if_unfixable.stderr b/tests/ui/collapsible_if_unfixable.stderr new file mode 100644 index 0000000000000..64c8fb8da2b44 --- /dev/null +++ b/tests/ui/collapsible_if_unfixable.stderr @@ -0,0 +1,17 @@ +error: this lint expectation is unfulfilled + --> tests/ui/collapsible_if_unfixable.rs:9:18 + | +LL | #[expect(clippy::collapsible_if)] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D unfulfilled-lint-expectations` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unfulfilled_lint_expectations)]` + +error: this lint expectation is unfulfilled + --> tests/ui/collapsible_if_unfixable.rs:15:18 + | +LL | #[expect(clippy::collapsible_if)] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + From 4d931e643af306eeabc417957975271879b68c50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Wed, 17 Sep 2025 04:16:47 +0200 Subject: [PATCH 07/93] Remove `DynKind` --- clippy_utils/src/qualify_min_const_fn.rs | 2 +- clippy_utils/src/ty/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 68f0b5ea25589..ac87e21b82f15 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -86,7 +86,7 @@ fn check_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, msrv: Msrv) ty::FnPtr(..) => { return Err((span, "function pointers in const fn are unstable".into())); }, - ty::Dynamic(preds, _, _) => { + ty::Dynamic(preds, _) => { for pred in *preds { match pred.skip_binder() { ty::ExistentialPredicate::AutoTrait(_) | ty::ExistentialPredicate::Projection(_) => { diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index 9f77a1c4d9b62..3e41bce1dc4ce 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -349,7 +349,7 @@ pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { } false }, - ty::Dynamic(binder, _, _) => { + ty::Dynamic(binder, _) => { for predicate in *binder { if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() && find_attr!(cx.tcx.get_all_attrs(trait_ref.def_id), AttributeKind::MustUse { .. }) @@ -673,7 +673,7 @@ pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option Some(ExprFnSig::Sig(sig_tys.with(hdr), None)), - ty::Dynamic(bounds, _, _) => { + ty::Dynamic(bounds, _) => { let lang_items = cx.tcx.lang_items(); match bounds.principal() { Some(bound) From 2972ae5e539162778f83529599040d5bbc4adca4 Mon Sep 17 00:00:00 2001 From: Emmanuel Thompson Date: Sat, 28 Jun 2025 19:39:43 -0400 Subject: [PATCH 08/93] rename_lint: unchecked_duration_subtraction to unchecked_time_subtraction --- CHANGELOG.md | 1 + book/src/lint_configuration.md | 2 +- clippy_config/src/conf.rs | 2 +- clippy_lints/src/declared_lints.rs | 2 +- clippy_lints/src/deprecated_lints.rs | 2 + clippy_lints/src/instant_subtraction.rs | 6 +- tests/ui/manual_instant_elapsed.fixed | 2 +- tests/ui/manual_instant_elapsed.rs | 2 +- tests/ui/rename.fixed | 2 + tests/ui/rename.rs | 2 + tests/ui/rename.stderr | 154 +++++++++--------- ...fixed => unchecked_time_subtraction.fixed} | 10 +- ...ction.rs => unchecked_time_subtraction.rs} | 10 +- ...derr => unchecked_time_subtraction.stderr} | 12 +- 14 files changed, 111 insertions(+), 98 deletions(-) rename tests/ui/{unchecked_duration_subtraction.fixed => unchecked_time_subtraction.fixed} (64%) rename tests/ui/{unchecked_duration_subtraction.rs => unchecked_time_subtraction.rs} (58%) rename tests/ui/{unchecked_duration_subtraction.stderr => unchecked_time_subtraction.stderr} (75%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a9be02143722..2144d68ae1527 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6702,6 +6702,7 @@ Released 2018-09-13 [`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds [`unbuffered_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#unbuffered_bytes [`unchecked_duration_subtraction`]: https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction +[`unchecked_time_subtraction`]: https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_time_subtraction [`unconditional_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#unconditional_recursion [`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks [`undropped_manually_drops`]: https://rust-lang.github.io/rust-clippy/master/index.html#undropped_manually_drops diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index c2d080cd96a1d..5502e6ff5e833 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -894,7 +894,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`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) * [`type_repetition_in_bounds`](https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds) -* [`unchecked_duration_subtraction`](https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction) +* [`unchecked_time_subtraction`](https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_time_subtraction) * [`uninlined_format_args`](https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args) * [`unnecessary_lazy_evaluations`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations) * [`unnested_or_patterns`](https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns) diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 2f28f6175ad82..ae994815f63a5 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -790,7 +790,7 @@ define_Conf! { transmute_ptr_to_ref, tuple_array_conversions, type_repetition_in_bounds, - unchecked_duration_subtraction, + unchecked_time_subtraction, uninlined_format_args, unnecessary_lazy_evaluations, unnested_or_patterns, diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 1b19a8851edd4..7734f2266308f 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -227,7 +227,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::init_numbered_fields::INIT_NUMBERED_FIELDS_INFO, crate::inline_fn_without_body::INLINE_FN_WITHOUT_BODY_INFO, crate::instant_subtraction::MANUAL_INSTANT_ELAPSED_INFO, - crate::instant_subtraction::UNCHECKED_DURATION_SUBTRACTION_INFO, + crate::instant_subtraction::UNCHECKED_TIME_SUBTRACTION_INFO, crate::int_plus_one::INT_PLUS_ONE_INFO, crate::integer_division_remainder_used::INTEGER_DIVISION_REMAINDER_USED_INFO, crate::invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS_INFO, diff --git a/clippy_lints/src/deprecated_lints.rs b/clippy_lints/src/deprecated_lints.rs index 88aebc3e6a168..80b74f50223a4 100644 --- a/clippy_lints/src/deprecated_lints.rs +++ b/clippy_lints/src/deprecated_lints.rs @@ -184,6 +184,8 @@ declare_with_version! { RENAMED(RENAMED_VERSION) = [ ("clippy::transmute_int_to_float", "unnecessary_transmutes"), #[clippy::version = "1.88.0"] ("clippy::transmute_num_to_bytes", "unnecessary_transmutes"), + #[clippy::version = "1.90.0"] + ("clippy::unchecked_duration_subtraction", "clippy::unchecked_time_subtraction"), #[clippy::version = ""] ("clippy::undropped_manually_drops", "undropped_manually_drops"), #[clippy::version = ""] diff --git a/clippy_lints/src/instant_subtraction.rs b/clippy_lints/src/instant_subtraction.rs index 13117f60abd52..5fe26e3e3e0c3 100644 --- a/clippy_lints/src/instant_subtraction.rs +++ b/clippy_lints/src/instant_subtraction.rs @@ -59,7 +59,7 @@ declare_clippy_lint! { /// let time_passed = Instant::now().checked_sub(Duration::from_secs(5)); /// ``` #[clippy::version = "1.67.0"] - pub UNCHECKED_DURATION_SUBTRACTION, + pub UNCHECKED_TIME_SUBTRACTION, pedantic, "finds unchecked subtraction of a 'Duration' from an 'Instant'" } @@ -74,7 +74,7 @@ impl InstantSubtraction { } } -impl_lint_pass!(InstantSubtraction => [MANUAL_INSTANT_ELAPSED, UNCHECKED_DURATION_SUBTRACTION]); +impl_lint_pass!(InstantSubtraction => [MANUAL_INSTANT_ELAPSED, UNCHECKED_TIME_SUBTRACTION]); impl LateLintPass<'_> for InstantSubtraction { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { @@ -141,7 +141,7 @@ fn print_unchecked_duration_subtraction_sugg( span_lint_and_sugg( cx, - UNCHECKED_DURATION_SUBTRACTION, + UNCHECKED_TIME_SUBTRACTION, expr.span, "unchecked subtraction of a 'Duration' from an 'Instant'", "try", diff --git a/tests/ui/manual_instant_elapsed.fixed b/tests/ui/manual_instant_elapsed.fixed index 187802bb76c9e..a04c601e08c10 100644 --- a/tests/ui/manual_instant_elapsed.fixed +++ b/tests/ui/manual_instant_elapsed.fixed @@ -1,6 +1,6 @@ #![warn(clippy::manual_instant_elapsed)] #![allow(clippy::unnecessary_operation)] -#![allow(clippy::unchecked_duration_subtraction)] +#![allow(clippy::unchecked_time_subtraction)] #![allow(unused_variables)] #![allow(unused_must_use)] diff --git a/tests/ui/manual_instant_elapsed.rs b/tests/ui/manual_instant_elapsed.rs index 61e14e5a3d9d3..7c67f6acf85de 100644 --- a/tests/ui/manual_instant_elapsed.rs +++ b/tests/ui/manual_instant_elapsed.rs @@ -1,6 +1,6 @@ #![warn(clippy::manual_instant_elapsed)] #![allow(clippy::unnecessary_operation)] -#![allow(clippy::unchecked_duration_subtraction)] +#![allow(clippy::unchecked_time_subtraction)] #![allow(unused_variables)] #![allow(unused_must_use)] diff --git a/tests/ui/rename.fixed b/tests/ui/rename.fixed index ff81c64260274..fdd851414746b 100644 --- a/tests/ui/rename.fixed +++ b/tests/ui/rename.fixed @@ -58,6 +58,7 @@ #![allow(clippy::missing_const_for_thread_local)] #![allow(clippy::recursive_format_impl)] #![allow(unnecessary_transmutes)] +#![allow(clippy::unchecked_time_subtraction)] #![allow(undropped_manually_drops)] #![allow(unknown_lints)] #![allow(unused_labels)] @@ -131,6 +132,7 @@ #![warn(unnecessary_transmutes)] //~ ERROR: lint `clippy::transmute_int_to_char` #![warn(unnecessary_transmutes)] //~ ERROR: lint `clippy::transmute_int_to_float` #![warn(unnecessary_transmutes)] //~ ERROR: lint `clippy::transmute_num_to_bytes` +#![warn(clippy::unchecked_time_subtraction)] //~ ERROR: lint `clippy::unchecked_duration_subtraction` #![warn(undropped_manually_drops)] //~ ERROR: lint `clippy::undropped_manually_drops` #![warn(unknown_lints)] //~ ERROR: lint `clippy::unknown_clippy_lints` #![warn(unused_labels)] //~ ERROR: lint `clippy::unused_label` diff --git a/tests/ui/rename.rs b/tests/ui/rename.rs index b5d5d07e639a0..591c8ca53ac22 100644 --- a/tests/ui/rename.rs +++ b/tests/ui/rename.rs @@ -58,6 +58,7 @@ #![allow(clippy::missing_const_for_thread_local)] #![allow(clippy::recursive_format_impl)] #![allow(unnecessary_transmutes)] +#![allow(clippy::unchecked_time_subtraction)] #![allow(undropped_manually_drops)] #![allow(unknown_lints)] #![allow(unused_labels)] @@ -131,6 +132,7 @@ #![warn(clippy::transmute_int_to_char)] //~ ERROR: lint `clippy::transmute_int_to_char` #![warn(clippy::transmute_int_to_float)] //~ ERROR: lint `clippy::transmute_int_to_float` #![warn(clippy::transmute_num_to_bytes)] //~ ERROR: lint `clippy::transmute_num_to_bytes` +#![warn(clippy::unchecked_duration_subtraction)] //~ ERROR: lint `clippy::unchecked_duration_subtraction` #![warn(clippy::undropped_manually_drops)] //~ ERROR: lint `clippy::undropped_manually_drops` #![warn(clippy::unknown_clippy_lints)] //~ ERROR: lint `clippy::unknown_clippy_lints` #![warn(clippy::unused_label)] //~ ERROR: lint `clippy::unused_label` diff --git a/tests/ui/rename.stderr b/tests/ui/rename.stderr index 2487dfc8eba44..b54fec8c57949 100644 --- a/tests/ui/rename.stderr +++ b/tests/ui/rename.stderr @@ -1,5 +1,5 @@ error: lint `clippy::almost_complete_letter_range` has been renamed to `clippy::almost_complete_range` - --> tests/ui/rename.rs:67:9 + --> tests/ui/rename.rs:68:9 | LL | #![warn(clippy::almost_complete_letter_range)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::almost_complete_range` @@ -8,436 +8,442 @@ LL | #![warn(clippy::almost_complete_letter_range)] = help: to override `-D warnings` add `#[allow(renamed_and_removed_lints)]` error: lint `clippy::blacklisted_name` has been renamed to `clippy::disallowed_names` - --> tests/ui/rename.rs:68:9 + --> tests/ui/rename.rs:69:9 | LL | #![warn(clippy::blacklisted_name)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_names` error: lint `clippy::block_in_if_condition_expr` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:69:9 + --> tests/ui/rename.rs:70:9 | LL | #![warn(clippy::block_in_if_condition_expr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::block_in_if_condition_stmt` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:70:9 + --> tests/ui/rename.rs:71:9 | LL | #![warn(clippy::block_in_if_condition_stmt)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::blocks_in_if_conditions` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:71:9 + --> tests/ui/rename.rs:72:9 | LL | #![warn(clippy::blocks_in_if_conditions)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::box_vec` has been renamed to `clippy::box_collection` - --> tests/ui/rename.rs:72:9 + --> tests/ui/rename.rs:73:9 | LL | #![warn(clippy::box_vec)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::box_collection` error: lint `clippy::cast_ref_to_mut` has been renamed to `invalid_reference_casting` - --> tests/ui/rename.rs:73:9 + --> tests/ui/rename.rs:74:9 | LL | #![warn(clippy::cast_ref_to_mut)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_reference_casting` error: lint `clippy::clone_double_ref` has been renamed to `suspicious_double_ref_op` - --> tests/ui/rename.rs:74:9 + --> tests/ui/rename.rs:75:9 | LL | #![warn(clippy::clone_double_ref)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `suspicious_double_ref_op` error: lint `clippy::cmp_nan` has been renamed to `invalid_nan_comparisons` - --> tests/ui/rename.rs:75:9 + --> tests/ui/rename.rs:76:9 | LL | #![warn(clippy::cmp_nan)] | ^^^^^^^^^^^^^^^ help: use the new name: `invalid_nan_comparisons` error: lint `clippy::const_static_lifetime` has been renamed to `clippy::redundant_static_lifetimes` - --> tests/ui/rename.rs:76:9 + --> tests/ui/rename.rs:77:9 | LL | #![warn(clippy::const_static_lifetime)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_static_lifetimes` error: lint `clippy::cyclomatic_complexity` has been renamed to `clippy::cognitive_complexity` - --> tests/ui/rename.rs:77:9 + --> tests/ui/rename.rs:78:9 | LL | #![warn(clippy::cyclomatic_complexity)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::cognitive_complexity` error: lint `clippy::derive_hash_xor_eq` has been renamed to `clippy::derived_hash_with_manual_eq` - --> tests/ui/rename.rs:78:9 + --> tests/ui/rename.rs:79:9 | LL | #![warn(clippy::derive_hash_xor_eq)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::derived_hash_with_manual_eq` error: lint `clippy::disallowed_method` has been renamed to `clippy::disallowed_methods` - --> tests/ui/rename.rs:79:9 + --> tests/ui/rename.rs:80:9 | LL | #![warn(clippy::disallowed_method)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_methods` error: lint `clippy::disallowed_type` has been renamed to `clippy::disallowed_types` - --> tests/ui/rename.rs:80:9 + --> tests/ui/rename.rs:81:9 | LL | #![warn(clippy::disallowed_type)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_types` error: lint `clippy::double_neg` has been renamed to `double_negations` - --> tests/ui/rename.rs:81:9 + --> tests/ui/rename.rs:82:9 | LL | #![warn(clippy::double_neg)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `double_negations` error: lint `clippy::drop_bounds` has been renamed to `drop_bounds` - --> tests/ui/rename.rs:82:9 + --> tests/ui/rename.rs:83:9 | LL | #![warn(clippy::drop_bounds)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `drop_bounds` error: lint `clippy::drop_copy` has been renamed to `dropping_copy_types` - --> tests/ui/rename.rs:83:9 + --> tests/ui/rename.rs:84:9 | LL | #![warn(clippy::drop_copy)] | ^^^^^^^^^^^^^^^^^ help: use the new name: `dropping_copy_types` error: lint `clippy::drop_ref` has been renamed to `dropping_references` - --> tests/ui/rename.rs:84:9 + --> tests/ui/rename.rs:85:9 | LL | #![warn(clippy::drop_ref)] | ^^^^^^^^^^^^^^^^ help: use the new name: `dropping_references` error: lint `clippy::eval_order_dependence` has been renamed to `clippy::mixed_read_write_in_expression` - --> tests/ui/rename.rs:85:9 + --> tests/ui/rename.rs:86:9 | LL | #![warn(clippy::eval_order_dependence)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::mixed_read_write_in_expression` error: lint `clippy::filter_map` has been renamed to `clippy::manual_filter_map` - --> tests/ui/rename.rs:86:9 + --> tests/ui/rename.rs:87:9 | LL | #![warn(clippy::filter_map)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::manual_filter_map` error: lint `clippy::find_map` has been renamed to `clippy::manual_find_map` - --> tests/ui/rename.rs:87:9 + --> tests/ui/rename.rs:88:9 | LL | #![warn(clippy::find_map)] | ^^^^^^^^^^^^^^^^ help: use the new name: `clippy::manual_find_map` error: lint `clippy::fn_address_comparisons` has been renamed to `unpredictable_function_pointer_comparisons` - --> tests/ui/rename.rs:88:9 + --> tests/ui/rename.rs:89:9 | LL | #![warn(clippy::fn_address_comparisons)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unpredictable_function_pointer_comparisons` error: lint `clippy::fn_null_check` has been renamed to `useless_ptr_null_checks` - --> tests/ui/rename.rs:89:9 + --> tests/ui/rename.rs:90:9 | LL | #![warn(clippy::fn_null_check)] | ^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `useless_ptr_null_checks` error: lint `clippy::for_loop_over_option` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:90:9 + --> tests/ui/rename.rs:91:9 | LL | #![warn(clippy::for_loop_over_option)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::for_loop_over_result` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:91:9 + --> tests/ui/rename.rs:92:9 | LL | #![warn(clippy::for_loop_over_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::for_loops_over_fallibles` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:92:9 + --> tests/ui/rename.rs:93:9 | LL | #![warn(clippy::for_loops_over_fallibles)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::forget_copy` has been renamed to `forgetting_copy_types` - --> tests/ui/rename.rs:93:9 + --> tests/ui/rename.rs:94:9 | LL | #![warn(clippy::forget_copy)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_copy_types` error: lint `clippy::forget_ref` has been renamed to `forgetting_references` - --> tests/ui/rename.rs:94:9 + --> tests/ui/rename.rs:95:9 | LL | #![warn(clippy::forget_ref)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_references` error: lint `clippy::identity_conversion` has been renamed to `clippy::useless_conversion` - --> tests/ui/rename.rs:95:9 + --> tests/ui/rename.rs:96:9 | LL | #![warn(clippy::identity_conversion)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::useless_conversion` error: lint `clippy::if_let_redundant_pattern_matching` has been renamed to `clippy::redundant_pattern_matching` - --> tests/ui/rename.rs:96:9 + --> tests/ui/rename.rs:97:9 | LL | #![warn(clippy::if_let_redundant_pattern_matching)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_pattern_matching` error: lint `clippy::if_let_some_result` has been renamed to `clippy::match_result_ok` - --> tests/ui/rename.rs:97:9 + --> tests/ui/rename.rs:98:9 | LL | #![warn(clippy::if_let_some_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::match_result_ok` error: lint `clippy::incorrect_clone_impl_on_copy_type` has been renamed to `clippy::non_canonical_clone_impl` - --> tests/ui/rename.rs:98:9 + --> tests/ui/rename.rs:99:9 | LL | #![warn(clippy::incorrect_clone_impl_on_copy_type)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::non_canonical_clone_impl` error: lint `clippy::incorrect_partial_ord_impl_on_ord_type` has been renamed to `clippy::non_canonical_partial_ord_impl` - --> tests/ui/rename.rs:99:9 + --> tests/ui/rename.rs:100:9 | LL | #![warn(clippy::incorrect_partial_ord_impl_on_ord_type)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::non_canonical_partial_ord_impl` error: lint `clippy::integer_arithmetic` has been renamed to `clippy::arithmetic_side_effects` - --> tests/ui/rename.rs:100:9 + --> tests/ui/rename.rs:101:9 | LL | #![warn(clippy::integer_arithmetic)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::arithmetic_side_effects` error: lint `clippy::into_iter_on_array` has been renamed to `array_into_iter` - --> tests/ui/rename.rs:101:9 + --> tests/ui/rename.rs:102:9 | LL | #![warn(clippy::into_iter_on_array)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `array_into_iter` error: lint `clippy::invalid_atomic_ordering` has been renamed to `invalid_atomic_ordering` - --> tests/ui/rename.rs:102:9 + --> tests/ui/rename.rs:103:9 | LL | #![warn(clippy::invalid_atomic_ordering)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_atomic_ordering` error: lint `clippy::invalid_null_ptr_usage` has been renamed to `invalid_null_arguments` - --> tests/ui/rename.rs:103:9 + --> tests/ui/rename.rs:104:9 | LL | #![warn(clippy::invalid_null_ptr_usage)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_null_arguments` error: lint `clippy::invalid_ref` has been renamed to `invalid_value` - --> tests/ui/rename.rs:104:9 + --> tests/ui/rename.rs:105:9 | LL | #![warn(clippy::invalid_ref)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_value` error: lint `clippy::invalid_utf8_in_unchecked` has been renamed to `invalid_from_utf8_unchecked` - --> tests/ui/rename.rs:105:9 + --> tests/ui/rename.rs:106:9 | LL | #![warn(clippy::invalid_utf8_in_unchecked)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_from_utf8_unchecked` error: lint `clippy::let_underscore_drop` has been renamed to `let_underscore_drop` - --> tests/ui/rename.rs:106:9 + --> tests/ui/rename.rs:107:9 | LL | #![warn(clippy::let_underscore_drop)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `let_underscore_drop` error: lint `clippy::logic_bug` has been renamed to `clippy::overly_complex_bool_expr` - --> tests/ui/rename.rs:107:9 + --> tests/ui/rename.rs:108:9 | LL | #![warn(clippy::logic_bug)] | ^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::overly_complex_bool_expr` error: lint `clippy::maybe_misused_cfg` has been renamed to `unexpected_cfgs` - --> tests/ui/rename.rs:108:9 + --> tests/ui/rename.rs:109:9 | LL | #![warn(clippy::maybe_misused_cfg)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unexpected_cfgs` error: lint `clippy::mem_discriminant_non_enum` has been renamed to `enum_intrinsics_non_enums` - --> tests/ui/rename.rs:109:9 + --> tests/ui/rename.rs:110:9 | LL | #![warn(clippy::mem_discriminant_non_enum)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `enum_intrinsics_non_enums` error: lint `clippy::mismatched_target_os` has been renamed to `unexpected_cfgs` - --> tests/ui/rename.rs:110:9 + --> tests/ui/rename.rs:111:9 | LL | #![warn(clippy::mismatched_target_os)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unexpected_cfgs` error: lint `clippy::new_without_default_derive` has been renamed to `clippy::new_without_default` - --> tests/ui/rename.rs:111:9 + --> tests/ui/rename.rs:112:9 | LL | #![warn(clippy::new_without_default_derive)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::new_without_default` error: lint `clippy::option_and_then_some` has been renamed to `clippy::bind_instead_of_map` - --> tests/ui/rename.rs:112:9 + --> tests/ui/rename.rs:113:9 | LL | #![warn(clippy::option_and_then_some)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::bind_instead_of_map` error: lint `clippy::option_expect_used` has been renamed to `clippy::expect_used` - --> tests/ui/rename.rs:113:9 + --> tests/ui/rename.rs:114:9 | LL | #![warn(clippy::option_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::option_map_unwrap_or` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:114:9 + --> tests/ui/rename.rs:115:9 | LL | #![warn(clippy::option_map_unwrap_or)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:115:9 + --> tests/ui/rename.rs:116:9 | LL | #![warn(clippy::option_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_unwrap_used` has been renamed to `clippy::unwrap_used` - --> tests/ui/rename.rs:116:9 + --> tests/ui/rename.rs:117:9 | LL | #![warn(clippy::option_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::overflow_check_conditional` has been renamed to `clippy::panicking_overflow_checks` - --> tests/ui/rename.rs:117:9 + --> tests/ui/rename.rs:118:9 | LL | #![warn(clippy::overflow_check_conditional)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::panicking_overflow_checks` error: lint `clippy::panic_params` has been renamed to `non_fmt_panics` - --> tests/ui/rename.rs:118:9 + --> tests/ui/rename.rs:119:9 | LL | #![warn(clippy::panic_params)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `non_fmt_panics` error: lint `clippy::positional_named_format_parameters` has been renamed to `named_arguments_used_positionally` - --> tests/ui/rename.rs:119:9 + --> tests/ui/rename.rs:120:9 | LL | #![warn(clippy::positional_named_format_parameters)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `named_arguments_used_positionally` error: lint `clippy::ref_in_deref` has been renamed to `clippy::needless_borrow` - --> tests/ui/rename.rs:120:9 + --> tests/ui/rename.rs:121:9 | LL | #![warn(clippy::ref_in_deref)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::needless_borrow` error: lint `clippy::result_expect_used` has been renamed to `clippy::expect_used` - --> tests/ui/rename.rs:121:9 + --> tests/ui/rename.rs:122:9 | LL | #![warn(clippy::result_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::result_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:122:9 + --> tests/ui/rename.rs:123:9 | LL | #![warn(clippy::result_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::result_unwrap_used` has been renamed to `clippy::unwrap_used` - --> tests/ui/rename.rs:123:9 + --> tests/ui/rename.rs:124:9 | LL | #![warn(clippy::result_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::reverse_range_loop` has been renamed to `clippy::reversed_empty_ranges` - --> tests/ui/rename.rs:124:9 + --> tests/ui/rename.rs:125:9 | LL | #![warn(clippy::reverse_range_loop)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::reversed_empty_ranges` error: lint `clippy::single_char_push_str` has been renamed to `clippy::single_char_add_str` - --> tests/ui/rename.rs:125:9 + --> tests/ui/rename.rs:126:9 | LL | #![warn(clippy::single_char_push_str)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::single_char_add_str` error: lint `clippy::stutter` has been renamed to `clippy::module_name_repetitions` - --> tests/ui/rename.rs:126:9 + --> tests/ui/rename.rs:127:9 | LL | #![warn(clippy::stutter)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::module_name_repetitions` error: lint `clippy::temporary_cstring_as_ptr` has been renamed to `dangling_pointers_from_temporaries` - --> tests/ui/rename.rs:127:9 + --> tests/ui/rename.rs:128:9 | LL | #![warn(clippy::temporary_cstring_as_ptr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `dangling_pointers_from_temporaries` error: lint `clippy::thread_local_initializer_can_be_made_const` has been renamed to `clippy::missing_const_for_thread_local` - --> tests/ui/rename.rs:128:9 + --> tests/ui/rename.rs:129:9 | LL | #![warn(clippy::thread_local_initializer_can_be_made_const)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::missing_const_for_thread_local` error: lint `clippy::to_string_in_display` has been renamed to `clippy::recursive_format_impl` - --> tests/ui/rename.rs:129:9 + --> tests/ui/rename.rs:130:9 | LL | #![warn(clippy::to_string_in_display)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::recursive_format_impl` error: lint `clippy::transmute_float_to_int` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:130:9 + --> tests/ui/rename.rs:131:9 | LL | #![warn(clippy::transmute_float_to_int)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` error: lint `clippy::transmute_int_to_char` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:131:9 + --> tests/ui/rename.rs:132:9 | LL | #![warn(clippy::transmute_int_to_char)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` error: lint `clippy::transmute_int_to_float` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:132:9 + --> tests/ui/rename.rs:133:9 | LL | #![warn(clippy::transmute_int_to_float)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` error: lint `clippy::transmute_num_to_bytes` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:133:9 + --> tests/ui/rename.rs:134:9 | LL | #![warn(clippy::transmute_num_to_bytes)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` +error: lint `clippy::unchecked_duration_subtraction` has been renamed to `clippy::unchecked_time_subtraction` + --> tests/ui/rename.rs:135:9 + | +LL | #![warn(clippy::unchecked_duration_subtraction)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unchecked_time_subtraction` + error: lint `clippy::undropped_manually_drops` has been renamed to `undropped_manually_drops` - --> tests/ui/rename.rs:134:9 + --> tests/ui/rename.rs:136:9 | LL | #![warn(clippy::undropped_manually_drops)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `undropped_manually_drops` error: lint `clippy::unknown_clippy_lints` has been renamed to `unknown_lints` - --> tests/ui/rename.rs:135:9 + --> tests/ui/rename.rs:137:9 | LL | #![warn(clippy::unknown_clippy_lints)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unknown_lints` error: lint `clippy::unused_label` has been renamed to `unused_labels` - --> tests/ui/rename.rs:136:9 + --> tests/ui/rename.rs:138:9 | LL | #![warn(clippy::unused_label)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unused_labels` error: lint `clippy::unwrap_or_else_default` has been renamed to `clippy::unwrap_or_default` - --> tests/ui/rename.rs:137:9 + --> tests/ui/rename.rs:139:9 | LL | #![warn(clippy::unwrap_or_else_default)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_or_default` error: lint `clippy::vtable_address_comparisons` has been renamed to `ambiguous_wide_pointer_comparisons` - --> tests/ui/rename.rs:138:9 + --> tests/ui/rename.rs:140:9 | LL | #![warn(clippy::vtable_address_comparisons)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `ambiguous_wide_pointer_comparisons` error: lint `clippy::zero_width_space` has been renamed to `clippy::invisible_characters` - --> tests/ui/rename.rs:139:9 + --> tests/ui/rename.rs:141:9 | LL | #![warn(clippy::zero_width_space)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::invisible_characters` -error: aborting due to 73 previous errors +error: aborting due to 74 previous errors diff --git a/tests/ui/unchecked_duration_subtraction.fixed b/tests/ui/unchecked_time_subtraction.fixed similarity index 64% rename from tests/ui/unchecked_duration_subtraction.fixed rename to tests/ui/unchecked_time_subtraction.fixed index bddffe44ac4d0..0850226eeea74 100644 --- a/tests/ui/unchecked_duration_subtraction.fixed +++ b/tests/ui/unchecked_time_subtraction.fixed @@ -1,4 +1,4 @@ -#![warn(clippy::unchecked_duration_subtraction)] +#![warn(clippy::unchecked_time_subtraction)] use std::time::{Duration, Instant}; @@ -7,14 +7,14 @@ fn main() { let second = Duration::from_secs(3); let _ = _first.checked_sub(second).unwrap(); - //~^ unchecked_duration_subtraction + //~^ unchecked_time_subtraction let _ = Instant::now().checked_sub(Duration::from_secs(5)).unwrap(); - //~^ unchecked_duration_subtraction + //~^ unchecked_time_subtraction let _ = _first.checked_sub(Duration::from_secs(5)).unwrap(); - //~^ unchecked_duration_subtraction + //~^ unchecked_time_subtraction let _ = Instant::now().checked_sub(second).unwrap(); - //~^ unchecked_duration_subtraction + //~^ unchecked_time_subtraction } diff --git a/tests/ui/unchecked_duration_subtraction.rs b/tests/ui/unchecked_time_subtraction.rs similarity index 58% rename from tests/ui/unchecked_duration_subtraction.rs rename to tests/ui/unchecked_time_subtraction.rs index bb0f712396424..a2dbfd1c1fadf 100644 --- a/tests/ui/unchecked_duration_subtraction.rs +++ b/tests/ui/unchecked_time_subtraction.rs @@ -1,4 +1,4 @@ -#![warn(clippy::unchecked_duration_subtraction)] +#![warn(clippy::unchecked_time_subtraction)] use std::time::{Duration, Instant}; @@ -7,14 +7,14 @@ fn main() { let second = Duration::from_secs(3); let _ = _first - second; - //~^ unchecked_duration_subtraction + //~^ unchecked_time_subtraction let _ = Instant::now() - Duration::from_secs(5); - //~^ unchecked_duration_subtraction + //~^ unchecked_time_subtraction let _ = _first - Duration::from_secs(5); - //~^ unchecked_duration_subtraction + //~^ unchecked_time_subtraction let _ = Instant::now() - second; - //~^ unchecked_duration_subtraction + //~^ unchecked_time_subtraction } diff --git a/tests/ui/unchecked_duration_subtraction.stderr b/tests/ui/unchecked_time_subtraction.stderr similarity index 75% rename from tests/ui/unchecked_duration_subtraction.stderr rename to tests/ui/unchecked_time_subtraction.stderr index be291c320e689..da0d51dbaef16 100644 --- a/tests/ui/unchecked_duration_subtraction.stderr +++ b/tests/ui/unchecked_time_subtraction.stderr @@ -1,26 +1,26 @@ error: unchecked subtraction of a 'Duration' from an 'Instant' - --> tests/ui/unchecked_duration_subtraction.rs:9:13 + --> tests/ui/unchecked_time_subtraction.rs:9:13 | LL | let _ = _first - second; | ^^^^^^^^^^^^^^^ help: try: `_first.checked_sub(second).unwrap()` | - = note: `-D clippy::unchecked-duration-subtraction` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::unchecked_duration_subtraction)]` + = note: `-D clippy::unchecked-time-subtraction` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unchecked_time_subtraction)]` error: unchecked subtraction of a 'Duration' from an 'Instant' - --> tests/ui/unchecked_duration_subtraction.rs:12:13 + --> tests/ui/unchecked_time_subtraction.rs:12:13 | LL | let _ = Instant::now() - Duration::from_secs(5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Instant::now().checked_sub(Duration::from_secs(5)).unwrap()` error: unchecked subtraction of a 'Duration' from an 'Instant' - --> tests/ui/unchecked_duration_subtraction.rs:15:13 + --> tests/ui/unchecked_time_subtraction.rs:15:13 | LL | let _ = _first - Duration::from_secs(5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `_first.checked_sub(Duration::from_secs(5)).unwrap()` error: unchecked subtraction of a 'Duration' from an 'Instant' - --> tests/ui/unchecked_duration_subtraction.rs:18:13 + --> tests/ui/unchecked_time_subtraction.rs:18:13 | LL | let _ = Instant::now() - second; | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Instant::now().checked_sub(second).unwrap()` From b0566b949b1e68319ed9852e637c5a77aae45186 Mon Sep 17 00:00:00 2001 From: Emmanuel Thompson Date: Sat, 28 Jun 2025 19:41:55 -0400 Subject: [PATCH 09/93] rename module: instant_substraction to time_substraction --- clippy_lints/src/declared_lints.rs | 4 ++-- clippy_lints/src/lib.rs | 4 ++-- .../src/{instant_subtraction.rs => time_subtraction.rs} | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename clippy_lints/src/{instant_subtraction.rs => time_subtraction.rs} (100%) diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 7734f2266308f..6a2833e5f86eb 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -226,8 +226,8 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY_INFO, crate::init_numbered_fields::INIT_NUMBERED_FIELDS_INFO, crate::inline_fn_without_body::INLINE_FN_WITHOUT_BODY_INFO, - crate::instant_subtraction::MANUAL_INSTANT_ELAPSED_INFO, - crate::instant_subtraction::UNCHECKED_TIME_SUBTRACTION_INFO, + crate::time_subtraction::MANUAL_INSTANT_ELAPSED_INFO, + crate::time_subtraction::UNCHECKED_TIME_SUBTRACTION_INFO, crate::int_plus_one::INT_PLUS_ONE_INFO, crate::integer_division_remainder_used::INTEGER_DIVISION_REMAINDER_USED_INFO, crate::invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c56fa257b0689..1ab6eef5e57d2 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -176,7 +176,6 @@ mod inherent_impl; mod inherent_to_string; mod init_numbered_fields; mod inline_fn_without_body; -mod instant_subtraction; mod int_plus_one; mod integer_division_remainder_used; mod invalid_upcast_comparisons; @@ -357,6 +356,7 @@ mod swap_ptr_to_ref; mod tabs_in_doc_comments; mod temporary_assignment; mod tests_outside_test_module; +mod time_subtraction; mod to_digit_is_some; mod to_string_trait_impl; mod toplevel_ref_arg; @@ -718,7 +718,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(move |_| Box::new(manual_rotate::ManualRotate)); store.register_late_pass(move |_| Box::new(operators::Operators::new(conf))); store.register_late_pass(move |_| Box::new(std_instead_of_core::StdReexports::new(conf))); - store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(conf))); + store.register_late_pass(move |_| Box::new(time_subtraction::InstantSubtraction::new(conf))); store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone)); store.register_late_pass(move |_| Box::new(manual_abs_diff::ManualAbsDiff::new(conf))); store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(conf))); diff --git a/clippy_lints/src/instant_subtraction.rs b/clippy_lints/src/time_subtraction.rs similarity index 100% rename from clippy_lints/src/instant_subtraction.rs rename to clippy_lints/src/time_subtraction.rs From f296de4665cec2f1200496a0432667a150322f9b Mon Sep 17 00:00:00 2001 From: Emmanuel Thompson Date: Sat, 28 Jun 2025 19:47:24 -0400 Subject: [PATCH 10/93] Implement Duration - Duration Renames InstantSubtraction to UncheckedTimeSubtraction --- clippy_lints/src/declared_lints.rs | 4 +- clippy_lints/src/lib.rs | 2 +- clippy_lints/src/time_subtraction.rs | 111 ++++++++++++++---- tests/ui/unchecked_time_subtraction.fixed | 17 +++ tests/ui/unchecked_time_subtraction.rs | 17 +++ tests/ui/unchecked_time_subtraction.stderr | 26 +++- .../unchecked_time_subtraction_unfixable.rs | 22 ++++ ...nchecked_time_subtraction_unfixable.stderr | 29 +++++ 8 files changed, 201 insertions(+), 27 deletions(-) create mode 100644 tests/ui/unchecked_time_subtraction_unfixable.rs create mode 100644 tests/ui/unchecked_time_subtraction_unfixable.stderr diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 6a2833e5f86eb..e1b6ee41967a3 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -226,8 +226,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY_INFO, crate::init_numbered_fields::INIT_NUMBERED_FIELDS_INFO, crate::inline_fn_without_body::INLINE_FN_WITHOUT_BODY_INFO, - crate::time_subtraction::MANUAL_INSTANT_ELAPSED_INFO, - crate::time_subtraction::UNCHECKED_TIME_SUBTRACTION_INFO, crate::int_plus_one::INT_PLUS_ONE_INFO, crate::integer_division_remainder_used::INTEGER_DIVISION_REMAINDER_USED_INFO, crate::invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS_INFO, @@ -703,6 +701,8 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::tabs_in_doc_comments::TABS_IN_DOC_COMMENTS_INFO, crate::temporary_assignment::TEMPORARY_ASSIGNMENT_INFO, crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO, + crate::time_subtraction::MANUAL_INSTANT_ELAPSED_INFO, + crate::time_subtraction::UNCHECKED_TIME_SUBTRACTION_INFO, crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO, crate::to_string_trait_impl::TO_STRING_TRAIT_IMPL_INFO, crate::toplevel_ref_arg::TOPLEVEL_REF_ARG_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 1ab6eef5e57d2..65c9a947b578d 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -718,7 +718,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(move |_| Box::new(manual_rotate::ManualRotate)); store.register_late_pass(move |_| Box::new(operators::Operators::new(conf))); store.register_late_pass(move |_| Box::new(std_instead_of_core::StdReexports::new(conf))); - store.register_late_pass(move |_| Box::new(time_subtraction::InstantSubtraction::new(conf))); + store.register_late_pass(move |_| Box::new(time_subtraction::UncheckedTimeSubtraction::new(conf))); store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone)); store.register_late_pass(move |_| Box::new(manual_abs_diff::ManualAbsDiff::new(conf))); store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(conf))); diff --git a/clippy_lints/src/time_subtraction.rs b/clippy_lints/src/time_subtraction.rs index 5fe26e3e3e0c3..fde8c3d9a9a76 100644 --- a/clippy_lints/src/time_subtraction.rs +++ b/clippy_lints/src/time_subtraction.rs @@ -1,12 +1,12 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::{span_lint, 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::{is_path_diagnostic_item, ty}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::Ty; use rustc_session::impl_lint_pass; use rustc_span::source_map::Spanned; use rustc_span::sym; @@ -41,7 +41,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Lints subtraction between an `Instant` and a `Duration`. + /// Lints subtraction between an `Instant` and a `Duration`, or between two `Duration` values. /// /// ### Why is this bad? /// Unchecked subtraction could cause underflow on certain platforms, leading to @@ -51,32 +51,38 @@ declare_clippy_lint! { /// ```no_run /// # use std::time::{Instant, Duration}; /// let time_passed = Instant::now() - Duration::from_secs(5); + /// let dur1 = Duration::from_secs(3); + /// let dur2 = Duration::from_secs(5); + /// let diff = dur1 - dur2; /// ``` /// /// Use instead: /// ```no_run /// # use std::time::{Instant, Duration}; /// let time_passed = Instant::now().checked_sub(Duration::from_secs(5)); + /// let dur1 = Duration::from_secs(3); + /// let dur2 = Duration::from_secs(5); + /// let diff = dur1.checked_sub(dur2); /// ``` #[clippy::version = "1.67.0"] pub UNCHECKED_TIME_SUBTRACTION, pedantic, - "finds unchecked subtraction of a 'Duration' from an 'Instant'" + "finds unchecked subtraction involving 'Duration' or 'Instant'" } -pub struct InstantSubtraction { +pub struct UncheckedTimeSubtraction { msrv: Msrv, } -impl InstantSubtraction { +impl UncheckedTimeSubtraction { pub fn new(conf: &'static Conf) -> Self { Self { msrv: conf.msrv } } } -impl_lint_pass!(InstantSubtraction => [MANUAL_INSTANT_ELAPSED, UNCHECKED_TIME_SUBTRACTION]); +impl_lint_pass!(UncheckedTimeSubtraction => [MANUAL_INSTANT_ELAPSED, UNCHECKED_TIME_SUBTRACTION]); -impl LateLintPass<'_> for InstantSubtraction { +impl LateLintPass<'_> for UncheckedTimeSubtraction { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { if let ExprKind::Binary( Spanned { @@ -85,21 +91,54 @@ impl LateLintPass<'_> for InstantSubtraction { lhs, rhs, ) = expr.kind - && let typeck = cx.typeck_results() - && ty::is_type_diagnostic_item(cx, typeck.expr_ty(lhs), sym::Instant) { + let typeck = cx.typeck_results(); + let lhs_ty = typeck.expr_ty(lhs); let rhs_ty = typeck.expr_ty(rhs); - if is_instant_now_call(cx, lhs) - && ty::is_type_diagnostic_item(cx, rhs_ty, sym::Instant) - && let Some(sugg) = Sugg::hir_opt(cx, rhs) - { - print_manual_instant_elapsed_sugg(cx, expr, sugg); - } else if ty::is_type_diagnostic_item(cx, rhs_ty, sym::Duration) + if ty::is_type_diagnostic_item(cx, lhs_ty, sym::Instant) { + // Instant::now() - instant + if is_instant_now_call(cx, lhs) + && ty::is_type_diagnostic_item(cx, rhs_ty, sym::Instant) + && let Some(sugg) = Sugg::hir_opt(cx, rhs) + { + print_manual_instant_elapsed_sugg(cx, expr, sugg); + } + // instant - duration + else if ty::is_type_diagnostic_item(cx, rhs_ty, sym::Duration) + && !expr.span.from_expansion() + && self.msrv.meets(cx, msrvs::TRY_FROM) + { + // For chained subtraction like (instant - dur1) - dur2, avoid suggestions + if is_chained_time_subtraction(cx, lhs) { + span_lint( + cx, + UNCHECKED_TIME_SUBTRACTION, + expr.span, + "unchecked subtraction of a 'Duration' from an 'Instant'", + ); + } else { + // instant - duration + print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr); + } + } + } else if ty::is_type_diagnostic_item(cx, lhs_ty, sym::Duration) + && ty::is_type_diagnostic_item(cx, rhs_ty, sym::Duration) && !expr.span.from_expansion() && self.msrv.meets(cx, msrvs::TRY_FROM) { - print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr); + // For chained subtraction like (dur1 - dur2) - dur3, avoid suggestions + if is_chained_time_subtraction(cx, lhs) { + span_lint( + cx, + UNCHECKED_TIME_SUBTRACTION, + expr.span, + "unchecked subtraction between 'Duration' values", + ); + } else { + // duration - duration + print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr); + } } } } @@ -115,6 +154,25 @@ fn is_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool { } } +/// Returns true if this subtraction is part of a chain like `(a - b) - c` +fn is_chained_time_subtraction(cx: &LateContext<'_>, lhs: &Expr<'_>) -> bool { + if let ExprKind::Binary(op, inner_lhs, inner_rhs) = &lhs.kind + && matches!(op.node, BinOpKind::Sub) + { + let typeck = cx.typeck_results(); + let left_ty = typeck.expr_ty(inner_lhs); + let right_ty = typeck.expr_ty(inner_rhs); + is_time_type(cx, left_ty) && is_time_type(cx, right_ty) + } else { + false + } +} + +/// Returns true if the type is Duration or Instant +fn is_time_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { + ty::is_type_diagnostic_item(cx, ty, sym::Duration) || ty::is_type_diagnostic_item(cx, ty, sym::Instant) +} + fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg: Sugg<'_>) { span_lint_and_sugg( cx, @@ -133,19 +191,26 @@ fn print_unchecked_duration_subtraction_sugg( right_expr: &Expr<'_>, expr: &Expr<'_>, ) { - let mut applicability = Applicability::MachineApplicable; + let typeck = cx.typeck_results(); + let left_ty = typeck.expr_ty(left_expr); - let ctxt = expr.span.ctxt(); - let left_expr = snippet_with_context(cx, left_expr.span, ctxt, "", &mut applicability).0; - let right_expr = snippet_with_context(cx, right_expr.span, ctxt, "", &mut applicability).0; + let lint_msg = if ty::is_type_diagnostic_item(cx, left_ty, sym::Instant) { + "unchecked subtraction of a 'Duration' from an 'Instant'" + } else { + "unchecked subtraction between 'Duration' values" + }; + + let mut applicability = Applicability::MachineApplicable; + let left_sugg = Sugg::hir_with_applicability(cx, left_expr, "", &mut applicability); + let right_sugg = Sugg::hir_with_applicability(cx, right_expr, "", &mut applicability); span_lint_and_sugg( cx, UNCHECKED_TIME_SUBTRACTION, expr.span, - "unchecked subtraction of a 'Duration' from an 'Instant'", + lint_msg, "try", - format!("{left_expr}.checked_sub({right_expr}).unwrap()"), + format!("{}.checked_sub({}).unwrap()", left_sugg.maybe_paren(), right_sugg), applicability, ); } diff --git a/tests/ui/unchecked_time_subtraction.fixed b/tests/ui/unchecked_time_subtraction.fixed index 0850226eeea74..2f923fef4c25d 100644 --- a/tests/ui/unchecked_time_subtraction.fixed +++ b/tests/ui/unchecked_time_subtraction.fixed @@ -17,4 +17,21 @@ fn main() { let _ = Instant::now().checked_sub(second).unwrap(); //~^ unchecked_time_subtraction + + // Duration - Duration cases + let dur1 = Duration::from_secs(5); + let dur2 = Duration::from_secs(3); + + let _ = dur1.checked_sub(dur2).unwrap(); + //~^ unchecked_time_subtraction + + let _ = Duration::from_secs(10).checked_sub(Duration::from_secs(5)).unwrap(); + //~^ unchecked_time_subtraction + + let _ = second.checked_sub(dur1).unwrap(); + //~^ unchecked_time_subtraction + + // Duration multiplication and subtraction + let _ = (2 * dur1).checked_sub(dur2).unwrap(); + //~^ unchecked_time_subtraction } diff --git a/tests/ui/unchecked_time_subtraction.rs b/tests/ui/unchecked_time_subtraction.rs index a2dbfd1c1fadf..cf727f62aafa7 100644 --- a/tests/ui/unchecked_time_subtraction.rs +++ b/tests/ui/unchecked_time_subtraction.rs @@ -17,4 +17,21 @@ fn main() { let _ = Instant::now() - second; //~^ unchecked_time_subtraction + + // Duration - Duration cases + let dur1 = Duration::from_secs(5); + let dur2 = Duration::from_secs(3); + + let _ = dur1 - dur2; + //~^ unchecked_time_subtraction + + let _ = Duration::from_secs(10) - Duration::from_secs(5); + //~^ unchecked_time_subtraction + + let _ = second - dur1; + //~^ unchecked_time_subtraction + + // Duration multiplication and subtraction + let _ = 2 * dur1 - dur2; + //~^ unchecked_time_subtraction } diff --git a/tests/ui/unchecked_time_subtraction.stderr b/tests/ui/unchecked_time_subtraction.stderr index da0d51dbaef16..7a39712269cf7 100644 --- a/tests/ui/unchecked_time_subtraction.stderr +++ b/tests/ui/unchecked_time_subtraction.stderr @@ -25,5 +25,29 @@ error: unchecked subtraction of a 'Duration' from an 'Instant' LL | let _ = Instant::now() - second; | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Instant::now().checked_sub(second).unwrap()` -error: aborting due to 4 previous errors +error: unchecked subtraction between 'Duration' values + --> tests/ui/unchecked_time_subtraction.rs:25:13 + | +LL | let _ = dur1 - dur2; + | ^^^^^^^^^^^ help: try: `dur1.checked_sub(dur2).unwrap()` + +error: unchecked subtraction between 'Duration' values + --> tests/ui/unchecked_time_subtraction.rs:28:13 + | +LL | let _ = Duration::from_secs(10) - Duration::from_secs(5); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Duration::from_secs(10).checked_sub(Duration::from_secs(5)).unwrap()` + +error: unchecked subtraction between 'Duration' values + --> tests/ui/unchecked_time_subtraction.rs:31:13 + | +LL | let _ = second - dur1; + | ^^^^^^^^^^^^^ help: try: `second.checked_sub(dur1).unwrap()` + +error: unchecked subtraction between 'Duration' values + --> tests/ui/unchecked_time_subtraction.rs:35:13 + | +LL | let _ = 2 * dur1 - dur2; + | ^^^^^^^^^^^^^^^ help: try: `(2 * dur1).checked_sub(dur2).unwrap()` + +error: aborting due to 8 previous errors diff --git a/tests/ui/unchecked_time_subtraction_unfixable.rs b/tests/ui/unchecked_time_subtraction_unfixable.rs new file mode 100644 index 0000000000000..4b6a5ca156209 --- /dev/null +++ b/tests/ui/unchecked_time_subtraction_unfixable.rs @@ -0,0 +1,22 @@ +#![warn(clippy::unchecked_time_subtraction)] +//@no-rustfix + +use std::time::{Duration, Instant}; + +fn main() { + let dur1 = Duration::from_secs(5); + let dur2 = Duration::from_secs(3); + let dur3 = Duration::from_secs(1); + + // Chained Duration subtraction - should lint without suggestion due to complexity + let _ = dur1 - dur2 - dur3; + //~^ unchecked_time_subtraction + //~| unchecked_time_subtraction + + // Chained Instant - Duration subtraction - should lint without suggestion due to complexity + let instant1 = Instant::now(); + + let _ = instant1 - dur2 - dur3; + //~^ unchecked_time_subtraction + //~| unchecked_time_subtraction +} diff --git a/tests/ui/unchecked_time_subtraction_unfixable.stderr b/tests/ui/unchecked_time_subtraction_unfixable.stderr new file mode 100644 index 0000000000000..c25c112b06ce0 --- /dev/null +++ b/tests/ui/unchecked_time_subtraction_unfixable.stderr @@ -0,0 +1,29 @@ +error: unchecked subtraction between 'Duration' values + --> tests/ui/unchecked_time_subtraction_unfixable.rs:12:13 + | +LL | let _ = dur1 - dur2 - dur3; + | ^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unchecked-time-subtraction` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unchecked_time_subtraction)]` + +error: unchecked subtraction between 'Duration' values + --> tests/ui/unchecked_time_subtraction_unfixable.rs:12:13 + | +LL | let _ = dur1 - dur2 - dur3; + | ^^^^^^^^^^^ help: try: `dur1.checked_sub(dur2).unwrap()` + +error: unchecked subtraction of a 'Duration' from an 'Instant' + --> tests/ui/unchecked_time_subtraction_unfixable.rs:19:13 + | +LL | let _ = instant1 - dur2 - dur3; + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: unchecked subtraction of a 'Duration' from an 'Instant' + --> tests/ui/unchecked_time_subtraction_unfixable.rs:19:13 + | +LL | let _ = instant1 - dur2 - dur3; + | ^^^^^^^^^^^^^^^ help: try: `instant1.checked_sub(dur2).unwrap()` + +error: aborting due to 4 previous errors + From 26cf3eee16d982ca66da4d103513f08da82f7ca5 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 8 May 2025 02:53:00 -0400 Subject: [PATCH 11/93] Rework `module_inception` * Don't check for repetition if the previous module crosses a body boundary. * Don't take a snippet of the entire item. * Check each item's visibility once. * Use `is_from_proc_macro` before linting --- clippy_lints/src/item_name_repetitions.rs | 180 ++++++++++++++-------- tests/ui/module_inception.rs | 9 ++ 2 files changed, 121 insertions(+), 68 deletions(-) diff --git a/clippy_lints/src/item_name_repetitions.rs b/clippy_lints/src/item_name_repetitions.rs index 945bb84708f85..76f5fdfaa8dcf 100644 --- a/clippy_lints/src/item_name_repetitions.rs +++ b/clippy_lints/src/item_name_repetitions.rs @@ -1,11 +1,9 @@ use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_hir}; -use clippy_utils::is_bool; -use clippy_utils::macros::span_is_local; -use clippy_utils::source::is_present_in_source; use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start, to_camel_case, to_snake_case}; +use clippy_utils::{is_bool, is_from_proc_macro}; use rustc_data_structures::fx::FxHashSet; -use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, QPath, TyKind, Variant, VariantData}; +use rustc_hir::{Body, EnumDef, FieldDef, Item, ItemKind, QPath, TyKind, UseKind, Variant, VariantData}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; use rustc_span::symbol::Symbol; @@ -158,7 +156,8 @@ declare_clippy_lint! { } pub struct ItemNameRepetitions { - modules: Vec<(Symbol, String, OwnerId)>, + /// The module path the lint pass is in. + modules: Vec, enum_threshold: u64, struct_threshold: u64, avoid_breaking_exported_api: bool, @@ -167,6 +166,17 @@ pub struct ItemNameRepetitions { allowed_prefixes: FxHashSet, } +struct ModInfo { + name: Symbol, + name_camel: String, + /// Does this module have the `pub` visibility modifier. + is_public: bool, + /// How many bodies are between this module and the current lint pass position. + /// + /// Only the most recently seen module is updated when entering/exiting a body. + in_body_count: u32, +} + impl ItemNameRepetitions { pub fn new(conf: &'static Conf) -> Self { Self { @@ -458,71 +468,109 @@ fn check_enum_tuple_path_match(variant_name: &str, variant_data: VariantData<'_> } impl LateLintPass<'_> for ItemNameRepetitions { - fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) { - let Some(_ident) = item.kind.ident() else { return }; - - let last = self.modules.pop(); - assert!(last.is_some()); + fn check_item_post(&mut self, _: &LateContext<'_>, item: &Item<'_>) { + if matches!(item.kind, ItemKind::Mod(..)) { + let prev = self.modules.pop(); + debug_assert!(prev.is_some()); + } } - fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { - let Some(ident) = item.kind.ident() else { return }; - - let item_name = ident.name.as_str(); - let item_camel = to_camel_case(item_name); - if !item.span.from_expansion() && is_present_in_source(cx, item.span) - && let [.., (mod_name, mod_camel, mod_owner_id)] = &*self.modules - // constants don't have surrounding modules - && !mod_camel.is_empty() - { - if mod_name == &ident.name - && let ItemKind::Mod(..) = item.kind - && (!self.allow_private_module_inception || cx.tcx.visibility(mod_owner_id.def_id).is_public()) - { - span_lint( - cx, - MODULE_INCEPTION, - item.span, - "module has the same name as its containing module", - ); - } + fn check_body(&mut self, _: &LateContext<'_>, _: &Body<'_>) { + if let [.., last] = &mut *self.modules { + last.in_body_count += 1; + } + } - // The `module_name_repetitions` lint should only trigger if the item has the module in its - // name. Having the same name is only accepted if `allow_exact_repetition` is set to `true`. + fn check_body_post(&mut self, _: &LateContext<'_>, _: &Body<'_>) { + if let [.., last] = &mut *self.modules { + last.in_body_count -= 1; + } + } - let both_are_public = - cx.tcx.visibility(item.owner_id).is_public() && cx.tcx.visibility(mod_owner_id.def_id).is_public(); + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + let ident = match item.kind { + ItemKind::Mod(ident, _) => { + if let [.., prev] = &*self.modules + && prev.name == ident.name + && prev.in_body_count == 0 + && (!self.allow_private_module_inception || prev.is_public) + && !item.span.from_expansion() + && !is_from_proc_macro(cx, item) + { + span_lint( + cx, + MODULE_INCEPTION, + item.span, + "module has the same name as its containing module", + ); + } + ident + }, - if both_are_public && !self.allow_exact_repetitions && item_camel == *mod_camel { - span_lint( - cx, - MODULE_NAME_REPETITIONS, - ident.span, - "item name is the same as its containing module's name", - ); - } + ItemKind::Enum(ident, _, def) => { + if !ident.span.in_external_macro(cx.tcx.sess.source_map()) { + self.check_variants(cx, item, &def); + } + ident + }, + ItemKind::Struct(ident, _, data) => { + if let VariantData::Struct { fields, .. } = data + && !ident.span.in_external_macro(cx.tcx.sess.source_map()) + { + self.check_fields(cx, item, fields); + } + ident + }, - let is_macro = matches!(item.kind, ItemKind::Macro(_, _, _)); - if both_are_public && item_camel.len() > mod_camel.len() && !is_macro { - let matching = count_match_start(mod_camel, &item_camel); - let rmatching = count_match_end(mod_camel, &item_camel); - let nchars = mod_camel.chars().count(); + ItemKind::Const(ident, ..) + | ItemKind::ExternCrate(_, ident) + | ItemKind::Fn { ident, .. } + | ItemKind::Macro(ident, ..) + | ItemKind::Static(_, ident, ..) + | ItemKind::Trait(_, _, _, ident, ..) + | ItemKind::TraitAlias(ident, ..) + | ItemKind::TyAlias(ident, ..) + | ItemKind::Union(ident, ..) + | ItemKind::Use(_, UseKind::Single(ident)) => ident, + + ItemKind::ForeignMod { .. } | ItemKind::GlobalAsm { .. } | ItemKind::Impl(_) | ItemKind::Use(..) => return, + }; - let is_word_beginning = |c: char| c == '_' || c.is_uppercase() || c.is_numeric(); + let item_name = ident.name.as_str(); + let item_camel = to_camel_case(item_name); - if matching.char_count == nchars { - match item_camel.chars().nth(nchars) { - Some(c) if is_word_beginning(c) => span_lint( + if let [.., prev] = &*self.modules + && prev.is_public + && prev.in_body_count == 0 + && !item.span.from_expansion() + && !matches!(item.kind, ItemKind::Macro(..)) + && cx.tcx.visibility(item.owner_id).is_public() + { + if !self.allow_exact_repetitions && item_camel == prev.name_camel { + if !is_from_proc_macro(cx, item) { + span_lint( + cx, + MODULE_NAME_REPETITIONS, + ident.span, + "item name is the same as its containing module's name", + ); + } + } else if item_camel.len() > prev.name_camel.len() { + if let Some(s) = item_camel.strip_prefix(&prev.name_camel) + && let Some(c) = s.chars().next() + && (c == '_' || c.is_uppercase() || c.is_numeric()) + { + if !is_from_proc_macro(cx, item) { + span_lint( cx, MODULE_NAME_REPETITIONS, ident.span, "item name starts with its containing module's name", - ), - _ => (), + ); } - } - if rmatching.char_count == nchars - && !self.is_allowed_prefix(&item_camel[..item_camel.len() - rmatching.byte_count]) + } else if let Some(s) = item_camel.strip_suffix(&prev.name_camel) + && !self.is_allowed_prefix(s) + && !is_from_proc_macro(cx, item) { span_lint( cx, @@ -534,17 +582,13 @@ impl LateLintPass<'_> for ItemNameRepetitions { } } - if span_is_local(item.span) { - match item.kind { - ItemKind::Enum(_, _, def) => { - self.check_variants(cx, item, &def); - }, - ItemKind::Struct(_, _, VariantData::Struct { fields, .. }) => { - self.check_fields(cx, item, fields); - }, - _ => (), - } + if matches!(item.kind, ItemKind::Mod(..)) { + self.modules.push(ModInfo { + name: ident.name, + name_camel: item_camel, + is_public: cx.tcx.visibility(item.owner_id).is_public(), + in_body_count: 0, + }); } - self.modules.push((ident.name, item_camel, item.owner_id)); } } diff --git a/tests/ui/module_inception.rs b/tests/ui/module_inception.rs index 15b7fb8777700..5735dd5867d55 100644 --- a/tests/ui/module_inception.rs +++ b/tests/ui/module_inception.rs @@ -38,4 +38,13 @@ mod bar { mod bar {} } +mod with_inner_impl { + struct S; + impl S { + fn f() { + mod with_inner_impl {} + } + } +} + fn main() {} From b3e056f19882e1d60a2a67ad9485546890108b44 Mon Sep 17 00:00:00 2001 From: Zihan Date: Mon, 11 Aug 2025 11:06:28 -0400 Subject: [PATCH 12/93] `module_style`: refactor and don't lint mod tagged with path attr refactored with `check_item` and `check_item_post`. this refactoring also fixed a few bugs related to mod path with shared segment name caused by preivous approach. changelog: [`self_named_module_files`]: don't lint mod tagged with path attribute changelog: [`mod_module_files`]: don't lint mod tagged with path attribute Signed-off-by: Zihan --- clippy_lints/src/lib.rs | 2 +- clippy_lints/src/module_style.rs | 155 +++++++++--------- .../duplicated_mod_names_14697/Cargo.stderr | 11 ++ .../duplicated_mod_names_14697/Cargo.toml | 11 ++ .../duplicated_mod_names_14697/src/foo.rs | 1 + .../duplicated_mod_names_14697/src/foo/bar.rs | 1 + .../duplicated_mod_names_14697/src/lib.rs | 4 + .../src/other/foo/mod.rs | 1 + .../src/other/mod.rs | 1 + .../module_style/fail_mod/Cargo.stderr | 16 +- .../Cargo.toml | 10 ++ .../foo/bar/Cargo.toml | 5 + .../foo/bar/src/foo.rs | 1 + .../foo/bar/src/lib.rs | 1 + .../src/lib.rs | 1 + .../with_path_attr_mod/Cargo.toml | 10 ++ .../with_path_attr_mod/src/bar/mod.rs | 1 + .../with_path_attr_mod/src/lib.rs | 4 + .../with_path_attr_no_mod/Cargo.toml | 10 ++ .../with_path_attr_no_mod/src/bar.rs | 1 + .../with_path_attr_no_mod/src/foo.rs | 2 + .../with_path_attr_no_mod/src/lib.rs | 3 + 22 files changed, 167 insertions(+), 85 deletions(-) create mode 100644 tests/ui-cargo/module_style/duplicated_mod_names_14697/Cargo.stderr create mode 100644 tests/ui-cargo/module_style/duplicated_mod_names_14697/Cargo.toml create mode 100644 tests/ui-cargo/module_style/duplicated_mod_names_14697/src/foo.rs create mode 100644 tests/ui-cargo/module_style/duplicated_mod_names_14697/src/foo/bar.rs create mode 100644 tests/ui-cargo/module_style/duplicated_mod_names_14697/src/lib.rs create mode 100644 tests/ui-cargo/module_style/duplicated_mod_names_14697/src/other/foo/mod.rs create mode 100644 tests/ui-cargo/module_style/duplicated_mod_names_14697/src/other/mod.rs create mode 100644 tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/Cargo.toml create mode 100644 tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/Cargo.toml create mode 100644 tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/src/foo.rs create mode 100644 tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/src/lib.rs create mode 100644 tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/src/lib.rs create mode 100644 tests/ui-cargo/module_style/with_path_attr_mod/Cargo.toml create mode 100644 tests/ui-cargo/module_style/with_path_attr_mod/src/bar/mod.rs create mode 100644 tests/ui-cargo/module_style/with_path_attr_mod/src/lib.rs create mode 100644 tests/ui-cargo/module_style/with_path_attr_no_mod/Cargo.toml create mode 100644 tests/ui-cargo/module_style/with_path_attr_no_mod/src/bar.rs create mode 100644 tests/ui-cargo/module_style/with_path_attr_no_mod/src/foo.rs create mode 100644 tests/ui-cargo/module_style/with_path_attr_no_mod/src/lib.rs diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c56fa257b0689..2b8f09d33405c 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -671,7 +671,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(from_str_radix_10::FromStrRadix10)); store.register_late_pass(move |_| Box::new(if_then_some_else_none::IfThenSomeElseNone::new(conf))); store.register_late_pass(|_| Box::new(bool_assert_comparison::BoolAssertComparison)); - store.register_early_pass(move || Box::new(module_style::ModStyle)); + store.register_early_pass(move || Box::new(module_style::ModStyle::default())); store.register_late_pass(|_| Box::::default()); store.register_late_pass(move |tcx| Box::new(disallowed_types::DisallowedTypes::new(tcx, conf))); store.register_late_pass(move |tcx| Box::new(missing_enforced_import_rename::ImportRename::new(tcx, conf))); diff --git a/clippy_lints/src/module_style.rs b/clippy_lints/src/module_style.rs index 98614baffcea6..f132b90ac4f2e 100644 --- a/clippy_lints/src/module_style.rs +++ b/clippy_lints/src/module_style.rs @@ -1,12 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_then; -use rustc_ast::ast; -use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; +use rustc_ast::ast::{self, Inline, ItemKind, ModKind}; use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext}; use rustc_session::impl_lint_pass; use rustc_span::def_id::LOCAL_CRATE; -use rustc_span::{FileName, SourceFile, Span, SyntaxContext}; -use std::ffi::OsStr; -use std::path::{Component, Path}; +use rustc_span::{FileName, SourceFile, Span, SyntaxContext, sym}; +use std::path::{Path, PathBuf}; +use std::sync::Arc; declare_clippy_lint! { /// ### What it does @@ -60,107 +59,97 @@ declare_clippy_lint! { /// mod.rs /// lib.rs /// ``` - #[clippy::version = "1.57.0"] pub SELF_NAMED_MODULE_FILES, restriction, "checks that module layout is consistent" } -pub struct ModStyle; - impl_lint_pass!(ModStyle => [MOD_MODULE_FILES, SELF_NAMED_MODULE_FILES]); +pub struct ModState { + contains_external: bool, + has_path_attr: bool, + mod_file: Arc, +} + +#[derive(Default)] +pub struct ModStyle { + working_dir: Option, + module_stack: Vec, +} + impl EarlyLintPass for ModStyle { fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) { + self.working_dir = cx.sess().opts.working_dir.local_path().map(Path::to_path_buf); + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { if cx.builder.lint_level(MOD_MODULE_FILES).level == Level::Allow && cx.builder.lint_level(SELF_NAMED_MODULE_FILES).level == Level::Allow { return; } + if let ItemKind::Mod(.., ModKind::Loaded(_, Inline::No { .. }, mod_spans, ..)) = &item.kind { + let has_path_attr = item.attrs.iter().any(|attr| attr.has_name(sym::path)); + if !has_path_attr && let Some(current) = self.module_stack.last_mut() { + current.contains_external = true; + } + let mod_file = cx.sess().source_map().lookup_source_file(mod_spans.inner_span.lo()); + self.module_stack.push(ModState { + contains_external: false, + has_path_attr, + mod_file, + }); + } + } - let files = cx.sess().source_map().files(); - - let Some(trim_to_src) = cx.sess().opts.working_dir.local_path() else { + fn check_item_post(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { + if cx.builder.lint_level(MOD_MODULE_FILES).level == Level::Allow + && cx.builder.lint_level(SELF_NAMED_MODULE_FILES).level == Level::Allow + { return; - }; - - // `folder_segments` is all unique folder path segments `path/to/foo.rs` gives - // `[path, to]` but not foo - let mut folder_segments = FxIndexSet::default(); - // `mod_folders` is all the unique folder names that contain a mod.rs file - let mut mod_folders = FxHashSet::default(); - // `file_map` maps file names to the full path including the file name - // `{ foo => path/to/foo.rs, .. } - let mut file_map = FxHashMap::default(); - for file in files.iter() { - if let FileName::Real(name) = &file.name - && let Some(lp) = name.local_path() - && file.cnum == LOCAL_CRATE - { - // [#8887](https://github.com/rust-lang/rust-clippy/issues/8887) - // Only check files in the current crate. - // Fix false positive that crate dependency in workspace sub directory - // is checked unintentionally. - let path = if lp.is_relative() { - lp - } else if let Ok(relative) = lp.strip_prefix(trim_to_src) { - relative - } else { - continue; - }; - - if let Some(stem) = path.file_stem() { - file_map.insert(stem, (file, path)); - } - process_paths_for_mod_files(path, &mut folder_segments, &mut mod_folders); - check_self_named_mod_exists(cx, path, file); - } } - for folder in &folder_segments { - if !mod_folders.contains(folder) - && let Some((file, path)) = file_map.get(folder) - { - span_lint_and_then( - cx, - SELF_NAMED_MODULE_FILES, - Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None), - format!("`mod.rs` files are required, found `{}`", path.display()), - |diag| { - let mut correct = path.to_path_buf(); - correct.pop(); - correct.push(folder); - correct.push("mod.rs"); - diag.help(format!("move `{}` to `{}`", path.display(), correct.display(),)); - }, - ); + if let ItemKind::Mod(.., ModKind::Loaded(_, Inline::No { .. }, ..)) = &item.kind + && let Some(current) = self.module_stack.pop() + && !current.has_path_attr + { + let Some(path) = self + .working_dir + .as_ref() + .and_then(|src| try_trim_file_path_prefix(¤t.mod_file, src)) + else { + return; + }; + if current.contains_external { + check_self_named_module(cx, path, ¤t.mod_file); } + check_mod_module(cx, path, ¤t.mod_file); } } } -/// For each `path` we add each folder component to `folder_segments` and if the file name -/// is `mod.rs` we add it's parent folder to `mod_folders`. -fn process_paths_for_mod_files<'a>( - path: &'a Path, - folder_segments: &mut FxIndexSet<&'a OsStr>, - mod_folders: &mut FxHashSet<&'a OsStr>, -) { - let mut comp = path.components().rev().peekable(); - let _: Option<_> = comp.next(); - if path.ends_with("mod.rs") { - mod_folders.insert(comp.peek().map(|c| c.as_os_str()).unwrap_or_default()); +fn check_self_named_module(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) { + if !path.ends_with("mod.rs") { + let mut mod_folder = path.with_extension(""); + span_lint_and_then( + cx, + SELF_NAMED_MODULE_FILES, + Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None), + format!("`mod.rs` files are required, found `{}`", path.display()), + |diag| { + mod_folder.push("mod.rs"); + diag.help(format!("move `{}` to `{}`", path.display(), mod_folder.display())); + }, + ); } - let folders = comp.filter_map(|c| if let Component::Normal(s) = c { Some(s) } else { None }); - folder_segments.extend(folders); } -/// Checks every path for the presence of `mod.rs` files and emits the lint if found. /// We should not emit a lint for test modules in the presence of `mod.rs`. /// Using `mod.rs` in integration tests is a [common pattern](https://doc.rust-lang.org/book/ch11-03-test-organization.html#submodules-in-integration-test) /// for code-sharing between tests. -fn check_self_named_mod_exists(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) { +fn check_mod_module(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) { if path.ends_with("mod.rs") && !path.starts_with("tests") { span_lint_and_then( cx, @@ -177,3 +166,17 @@ fn check_self_named_mod_exists(cx: &EarlyContext<'_>, path: &Path, file: &Source ); } } + +fn try_trim_file_path_prefix<'a>(file: &'a SourceFile, prefix: &'a Path) -> Option<&'a Path> { + if let FileName::Real(name) = &file.name + && let Some(mut path) = name.local_path() + && file.cnum == LOCAL_CRATE + { + if !path.is_relative() { + path = path.strip_prefix(prefix).ok()?; + } + Some(path) + } else { + None + } +} diff --git a/tests/ui-cargo/module_style/duplicated_mod_names_14697/Cargo.stderr b/tests/ui-cargo/module_style/duplicated_mod_names_14697/Cargo.stderr new file mode 100644 index 0000000000000..c7490c5da027b --- /dev/null +++ b/tests/ui-cargo/module_style/duplicated_mod_names_14697/Cargo.stderr @@ -0,0 +1,11 @@ +error: `mod.rs` files are required, found `src/foo.rs` + --> src/foo.rs:1:1 + | +1 | pub mod bar; + | ^ + | + = help: move `src/foo.rs` to `src/foo/mod.rs` + = note: `-D clippy::self-named-module-files` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::self_named_module_files)]` + +error: could not compile `duplicated-mod-names-14697` (lib) due to 1 previous error diff --git a/tests/ui-cargo/module_style/duplicated_mod_names_14697/Cargo.toml b/tests/ui-cargo/module_style/duplicated_mod_names_14697/Cargo.toml new file mode 100644 index 0000000000000..569082f2f6599 --- /dev/null +++ b/tests/ui-cargo/module_style/duplicated_mod_names_14697/Cargo.toml @@ -0,0 +1,11 @@ +# Should trigger when multiple mods with the same name exist and not all of them follow self-named convention. +# See issue #14697. +[package] +name = "duplicated-mod-names-14697" +version = "0.1.0" +edition = "2024" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/foo.rs b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/foo.rs new file mode 100644 index 0000000000000..46f285ca47d69 --- /dev/null +++ b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/foo.rs @@ -0,0 +1 @@ +pub mod bar; diff --git a/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/foo/bar.rs b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/foo/bar.rs new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/foo/bar.rs @@ -0,0 +1 @@ + diff --git a/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/lib.rs b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/lib.rs new file mode 100644 index 0000000000000..a85dae5748165 --- /dev/null +++ b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/lib.rs @@ -0,0 +1,4 @@ +#![warn(clippy::self_named_module_files)] + +pub mod foo; +pub mod other; diff --git a/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/other/foo/mod.rs b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/other/foo/mod.rs new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/other/foo/mod.rs @@ -0,0 +1 @@ + diff --git a/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/other/mod.rs b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/other/mod.rs new file mode 100644 index 0000000000000..b52703b257400 --- /dev/null +++ b/tests/ui-cargo/module_style/duplicated_mod_names_14697/src/other/mod.rs @@ -0,0 +1 @@ +pub mod foo; diff --git a/tests/ui-cargo/module_style/fail_mod/Cargo.stderr b/tests/ui-cargo/module_style/fail_mod/Cargo.stderr index 902330e178530..f134943e69bfc 100644 --- a/tests/ui-cargo/module_style/fail_mod/Cargo.stderr +++ b/tests/ui-cargo/module_style/fail_mod/Cargo.stderr @@ -1,19 +1,19 @@ -error: `mod.rs` files are required, found `src/bad/inner.rs` - --> src/bad/inner.rs:1:1 +error: `mod.rs` files are required, found `src/bad/inner/stuff.rs` + --> src/bad/inner/stuff.rs:1:1 | -1 | pub mod stuff; +1 | pub mod most; | ^ | - = help: move `src/bad/inner.rs` to `src/bad/inner/mod.rs` + = help: move `src/bad/inner/stuff.rs` to `src/bad/inner/stuff/mod.rs` = note: `-D clippy::self-named-module-files` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::self_named_module_files)]` -error: `mod.rs` files are required, found `src/bad/inner/stuff.rs` - --> src/bad/inner/stuff.rs:1:1 +error: `mod.rs` files are required, found `src/bad/inner.rs` + --> src/bad/inner.rs:1:1 | -1 | pub mod most; +1 | pub mod stuff; | ^ | - = help: move `src/bad/inner/stuff.rs` to `src/bad/inner/stuff/mod.rs` + = help: move `src/bad/inner.rs` to `src/bad/inner/mod.rs` error: could not compile `fail-mod` (bin "fail-mod") due to 2 previous errors diff --git a/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/Cargo.toml b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/Cargo.toml new file mode 100644 index 0000000000000..5c2fabd2283d3 --- /dev/null +++ b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/Cargo.toml @@ -0,0 +1,10 @@ +# Should not produce FP when irrelavant path segment shares the same name with module. +# See issue #10271 and #11916. +[package] +name = "segment-with-mod-name-10271-11916" +version = "0.1.0" +edition = "2024" +publish = false + +[workspace] +members = ["foo/bar"] \ No newline at end of file diff --git a/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/Cargo.toml b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/Cargo.toml new file mode 100644 index 0000000000000..1f68c0dccac54 --- /dev/null +++ b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "bar" +version = "0.1.0" +edition = "2024" +publish = false diff --git a/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/src/foo.rs b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/src/foo.rs new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/src/foo.rs @@ -0,0 +1 @@ + diff --git a/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/src/lib.rs b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/src/lib.rs new file mode 100644 index 0000000000000..b52703b257400 --- /dev/null +++ b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/foo/bar/src/lib.rs @@ -0,0 +1 @@ +pub mod foo; diff --git a/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/src/lib.rs b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/src/lib.rs new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/tests/ui-cargo/module_style/segment_with_mod_name_10271_11916/src/lib.rs @@ -0,0 +1 @@ + diff --git a/tests/ui-cargo/module_style/with_path_attr_mod/Cargo.toml b/tests/ui-cargo/module_style/with_path_attr_mod/Cargo.toml new file mode 100644 index 0000000000000..d867377545e15 --- /dev/null +++ b/tests/ui-cargo/module_style/with_path_attr_mod/Cargo.toml @@ -0,0 +1,10 @@ +# Should not lint mod tagged with `#[path = ...]` +[package] +name = "with-path-attr-mod" +version = "0.1.0" +edition = "2024" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/module_style/with_path_attr_mod/src/bar/mod.rs b/tests/ui-cargo/module_style/with_path_attr_mod/src/bar/mod.rs new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/tests/ui-cargo/module_style/with_path_attr_mod/src/bar/mod.rs @@ -0,0 +1 @@ + diff --git a/tests/ui-cargo/module_style/with_path_attr_mod/src/lib.rs b/tests/ui-cargo/module_style/with_path_attr_mod/src/lib.rs new file mode 100644 index 0000000000000..a5c2109ece7db --- /dev/null +++ b/tests/ui-cargo/module_style/with_path_attr_mod/src/lib.rs @@ -0,0 +1,4 @@ +#![warn(clippy::mod_module_files)] + +#[path = "bar/mod.rs"] +pub mod foo; diff --git a/tests/ui-cargo/module_style/with_path_attr_no_mod/Cargo.toml b/tests/ui-cargo/module_style/with_path_attr_no_mod/Cargo.toml new file mode 100644 index 0000000000000..ddf2ac394cddc --- /dev/null +++ b/tests/ui-cargo/module_style/with_path_attr_no_mod/Cargo.toml @@ -0,0 +1,10 @@ +# Should not lint mod tagged with `#[path = ...]` +[package] +name = "with-path-attr-no-mod" +version = "0.1.0" +edition = "2024" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/module_style/with_path_attr_no_mod/src/bar.rs b/tests/ui-cargo/module_style/with_path_attr_no_mod/src/bar.rs new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/tests/ui-cargo/module_style/with_path_attr_no_mod/src/bar.rs @@ -0,0 +1 @@ + diff --git a/tests/ui-cargo/module_style/with_path_attr_no_mod/src/foo.rs b/tests/ui-cargo/module_style/with_path_attr_no_mod/src/foo.rs new file mode 100644 index 0000000000000..3b12aefa3d5fe --- /dev/null +++ b/tests/ui-cargo/module_style/with_path_attr_no_mod/src/foo.rs @@ -0,0 +1,2 @@ +#[path = "bar.rs"] +mod bar; diff --git a/tests/ui-cargo/module_style/with_path_attr_no_mod/src/lib.rs b/tests/ui-cargo/module_style/with_path_attr_no_mod/src/lib.rs new file mode 100644 index 0000000000000..bf2a5d0193350 --- /dev/null +++ b/tests/ui-cargo/module_style/with_path_attr_no_mod/src/lib.rs @@ -0,0 +1,3 @@ +#![warn(clippy::self_named_module_files)] + +pub mod foo; From 427774af0719cdf2214825cfa0a634e550839169 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Thu, 18 Sep 2025 07:46:17 -0700 Subject: [PATCH 13/93] Changelog: fix heading level in 1.90 section --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a9be02143722..fb7cbbed6542d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ Current stable, released 2025-09-18 Note: This Clippy release does not introduce many new lints and is focused entirely on bug fixes — see [#15086](https://github.com/rust-lang/rust-clippy/issues/15086) for more details. -## New Lints +### New Lints * Added [`manual_is_multiple_of`] to `complexity` [#14292](https://github.com/rust-lang/rust-clippy/pull/14292) * Added [`doc_broken_link`] to `pedantic` [#13696](https://github.com/rust-lang/rust-clippy/pull/13696) From 47bcee4ee26cc03aa19df2331559686d6a719bd1 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 18 Sep 2025 17:21:44 +0200 Subject: [PATCH 14/93] Merge commit '20ce69b9a63bcd2756cd906fe0964d1e901e042a' into clippy-subtree-update --- .github/ISSUE_TEMPLATE/new_lint.yml | 4 +- .github/PULL_REQUEST_TEMPLATE.md | 4 - .github/workflows/feature_freeze.yml | 45 -- .gitignore | 3 + CHANGELOG.md | 148 ++++- Cargo.toml | 2 +- book/src/README.md | 4 - book/src/SUMMARY.md | 1 - book/src/development/adding_lints.md | 3 - book/src/development/feature_freeze.md | 55 -- clippy_config/Cargo.toml | 2 +- clippy_lints/Cargo.toml | 2 +- clippy_lints/src/attrs/useless_attribute.rs | 1 + clippy_lints/src/casts/as_underscore.rs | 8 +- clippy_lints/src/casts/cast_possible_wrap.rs | 30 +- clippy_lints/src/casts/cast_sign_loss.rs | 29 +- clippy_lints/src/casts/mod.rs | 4 +- clippy_lints/src/casts/utils.rs | 15 + clippy_lints/src/declared_lints.rs | 5 +- clippy_lints/src/dereference.rs | 7 +- clippy_lints/src/derive.rs | 527 ------------------ .../src/derive/derive_ord_xor_partial_ord.rs | 50 ++ .../derive/derive_partial_eq_without_eq.rs | 87 +++ .../src/derive/derived_hash_with_manual_eq.rs | 49 ++ .../src/derive/expl_impl_clone_on_copy.rs | 65 +++ clippy_lints/src/derive/mod.rs | 215 +++++++ .../src/derive/unsafe_derive_deserialize.rs | 93 ++++ clippy_lints/src/disallowed_macros.rs | 4 +- clippy_lints/src/eta_reduction.rs | 27 +- .../src/functions/not_unsafe_ptr_arg_deref.rs | 4 +- clippy_lints/src/functions/ref_option.rs | 48 +- clippy_lints/src/future_not_send.rs | 109 ++-- .../src/invalid_upcast_comparisons.rs | 13 +- clippy_lints/src/len_zero.rs | 6 +- clippy_lints/src/let_with_type_underscore.rs | 16 +- clippy_lints/src/lib.rs | 6 +- clippy_lints/src/lifetimes.rs | 109 +++- clippy_lints/src/loops/explicit_iter_loop.rs | 6 +- clippy_lints/src/loops/never_loop.rs | 59 +- clippy_lints/src/manual_abs_diff.rs | 6 +- clippy_lints/src/manual_option_as_slice.rs | 6 +- .../src/manual_slice_size_calculation.rs | 3 +- clippy_lints/src/matches/manual_ok_err.rs | 16 +- clippy_lints/src/matches/manual_utils.rs | 9 +- clippy_lints/src/matches/needless_match.rs | 6 +- .../matches/rest_pat_in_fully_bound_struct.rs | 10 +- clippy_lints/src/matches/single_match.rs | 8 +- clippy_lints/src/methods/implicit_clone.rs | 6 +- .../src/methods/inefficient_to_string.rs | 4 +- .../src/methods/iter_overeager_cloned.rs | 5 +- clippy_lints/src/methods/mod.rs | 75 ++- clippy_lints/src/methods/mut_mutex_lock.rs | 5 +- clippy_lints/src/methods/or_fun_call.rs | 7 + .../src/methods/ptr_offset_with_cast.rs | 82 +++ .../src/methods/unnecessary_to_owned.rs | 13 +- clippy_lints/src/methods/useless_asref.rs | 6 +- clippy_lints/src/min_ident_chars.rs | 6 +- clippy_lints/src/misc.rs | 118 +--- clippy_lints/src/missing_doc.rs | 2 +- .../src/multiple_unsafe_ops_per_block.rs | 8 + clippy_lints/src/only_used_in_recursion.rs | 4 +- clippy_lints/src/operators/erasing_op.rs | 4 +- clippy_lints/src/ptr_offset_with_cast.rs | 151 ----- clippy_lints/src/question_mark.rs | 21 +- clippy_lints/src/read_zero_byte_vec.rs | 27 +- clippy_lints/src/redundant_clone.rs | 4 +- clippy_lints/src/redundant_slicing.rs | 8 +- clippy_lints/src/returns.rs | 513 ----------------- clippy_lints/src/returns/let_and_return.rs | 86 +++ clippy_lints/src/returns/mod.rs | 140 +++++ clippy_lints/src/returns/needless_return.rs | 269 +++++++++ .../needless_return_with_question_mark.rs | 60 ++ clippy_lints/src/semicolon_block.rs | 14 + clippy_lints/src/size_of_ref.rs | 5 +- clippy_lints/src/toplevel_ref_arg.rs | 119 ++++ .../src/transmute/transmute_ptr_to_ref.rs | 44 +- .../src/transmute/transmute_ref_to_ref.rs | 4 +- clippy_lints/src/uninit_vec.rs | 11 +- clippy_lints/src/unused_peekable.rs | 6 +- clippy_lints/src/unwrap.rs | 1 + clippy_lints/src/use_self.rs | 81 +-- clippy_lints/src/useless_conversion.rs | 14 +- clippy_utils/Cargo.toml | 2 +- clippy_utils/README.md | 2 +- clippy_utils/src/check_proc_macro.rs | 146 ++++- clippy_utils/src/lib.rs | 29 +- clippy_utils/src/msrvs.rs | 4 +- clippy_utils/src/source.rs | 5 + clippy_utils/src/sym.rs | 1 + clippy_utils/src/ty/mod.rs | 88 ++- declare_clippy_lint/Cargo.toml | 2 +- rust-toolchain.toml | 2 +- tests/no-profile-in-cargo-toml.rs | 3 + .../ref_option/all/clippy.toml | 0 .../ref_option/private/clippy.toml | 0 tests/ui-toml/ref_option/ref_option.all.fixed | 114 ++++ .../ref_option/ref_option.all.stderr | 68 +-- .../ref_option/ref_option.private.fixed | 114 ++++ .../ref_option/ref_option.private.stderr | 44 +- tests/ui-toml/ref_option/ref_option.rs | 114 ++++ .../ref_option/ref_option_traits.all.stderr | 8 +- .../ref_option_traits.private.stderr | 4 +- tests/ui-toml/ref_option/ref_option_traits.rs | 71 +++ tests/ui/as_underscore_unfixable.rs | 14 + tests/ui/as_underscore_unfixable.stderr | 20 + tests/ui/cast.rs | 13 + tests/ui/cast.stderr | 96 ++-- .../ui/checked_unwrap/simple_conditionals.rs | 22 + .../checked_unwrap/simple_conditionals.stderr | 36 +- tests/ui/crashes/ice-15657.rs | 11 + tests/ui/crashes/ice-15666.fixed | 6 + tests/ui/crashes/ice-15666.rs | 6 + tests/ui/crashes/ice-15666.stderr | 16 + tests/ui/elidable_lifetime_names.fixed | 82 +++ tests/ui/elidable_lifetime_names.rs | 82 +++ tests/ui/elidable_lifetime_names.stderr | 146 ++++- tests/ui/eta.fixed | 8 + tests/ui/eta.rs | 8 + tests/ui/invalid_upcast_comparisons.rs | 12 + tests/ui/invalid_upcast_comparisons.stderr | 8 +- tests/ui/iter_overeager_cloned.fixed | 12 +- tests/ui/iter_overeager_cloned.rs | 12 +- tests/ui/iter_overeager_cloned.stderr | 4 +- tests/ui/match_as_ref.fixed | 30 + tests/ui/match_as_ref.rs | 30 + tests/ui/multiple_unsafe_ops_per_block.rs | 119 ++-- tests/ui/multiple_unsafe_ops_per_block.stderr | 143 +++-- tests/ui/needless_return.fixed | 7 + tests/ui/needless_return.rs | 7 + tests/ui/never_loop.rs | 24 + tests/ui/never_loop.stderr | 97 +++- tests/ui/option_if_let_else.fixed | 10 + tests/ui/option_if_let_else.rs | 16 + tests/ui/option_if_let_else.stderr | 50 +- tests/ui/or_fun_call.fixed | 16 + tests/ui/or_fun_call.rs | 22 +- tests/ui/or_fun_call.stderr | 88 +-- tests/ui/ptr_offset_with_cast.fixed | 22 +- tests/ui/ptr_offset_with_cast.rs | 22 +- tests/ui/ptr_offset_with_cast.stderr | 41 +- tests/ui/question_mark.fixed | 14 +- tests/ui/question_mark.rs | 26 +- tests/ui/question_mark.stderr | 92 +-- tests/ui/read_zero_byte_vec.rs | 27 + tests/ui/read_zero_byte_vec.stderr | 65 ++- tests/ui/ref_option/ref_option.all.fixed | 79 --- tests/ui/ref_option/ref_option.private.fixed | 79 --- tests/ui/ref_option/ref_option.rs | 79 --- tests/ui/ref_option/ref_option_traits.rs | 40 -- .../ui/rest_pat_in_fully_bound_structs.fixed | 61 ++ .../ui/rest_pat_in_fully_bound_structs.stderr | 18 +- tests/ui/semicolon_inside_block.fixed | 17 +- tests/ui/semicolon_inside_block.rs | 17 +- tests/ui/semicolon_inside_block.stderr | 8 +- tests/ui/track-diagnostics-clippy.stderr | 2 +- tests/ui/transmute_ptr_to_ref.fixed | 39 +- tests/ui/transmute_ptr_to_ref.rs | 39 +- tests/ui/transmute_ptr_to_ref.stderr | 54 +- tests/ui/use_self.fixed | 18 +- tests/ui/use_self.rs | 16 +- tests/ui/use_self.stderr | 8 +- tests/ui/useless_attribute.fixed | 10 + tests/ui/useless_attribute.rs | 10 + 163 files changed, 4375 insertions(+), 2474 deletions(-) delete mode 100644 .github/workflows/feature_freeze.yml delete mode 100644 book/src/development/feature_freeze.md delete mode 100644 clippy_lints/src/derive.rs create mode 100644 clippy_lints/src/derive/derive_ord_xor_partial_ord.rs create mode 100644 clippy_lints/src/derive/derive_partial_eq_without_eq.rs create mode 100644 clippy_lints/src/derive/derived_hash_with_manual_eq.rs create mode 100644 clippy_lints/src/derive/expl_impl_clone_on_copy.rs create mode 100644 clippy_lints/src/derive/mod.rs create mode 100644 clippy_lints/src/derive/unsafe_derive_deserialize.rs create mode 100644 clippy_lints/src/methods/ptr_offset_with_cast.rs delete mode 100644 clippy_lints/src/ptr_offset_with_cast.rs delete mode 100644 clippy_lints/src/returns.rs create mode 100644 clippy_lints/src/returns/let_and_return.rs create mode 100644 clippy_lints/src/returns/mod.rs create mode 100644 clippy_lints/src/returns/needless_return.rs create mode 100644 clippy_lints/src/returns/needless_return_with_question_mark.rs create mode 100644 clippy_lints/src/toplevel_ref_arg.rs rename tests/{ui => ui-toml}/ref_option/all/clippy.toml (100%) rename tests/{ui => ui-toml}/ref_option/private/clippy.toml (100%) create mode 100644 tests/ui-toml/ref_option/ref_option.all.fixed rename tests/{ui => ui-toml}/ref_option/ref_option.all.stderr (62%) create mode 100644 tests/ui-toml/ref_option/ref_option.private.fixed rename tests/{ui => ui-toml}/ref_option/ref_option.private.stderr (63%) create mode 100644 tests/ui-toml/ref_option/ref_option.rs rename tests/{ui => ui-toml}/ref_option/ref_option_traits.all.stderr (85%) rename tests/{ui => ui-toml}/ref_option/ref_option_traits.private.stderr (86%) create mode 100644 tests/ui-toml/ref_option/ref_option_traits.rs create mode 100644 tests/ui/as_underscore_unfixable.rs create mode 100644 tests/ui/as_underscore_unfixable.stderr create mode 100644 tests/ui/crashes/ice-15657.rs create mode 100644 tests/ui/crashes/ice-15666.fixed create mode 100644 tests/ui/crashes/ice-15666.rs create mode 100644 tests/ui/crashes/ice-15666.stderr delete mode 100644 tests/ui/ref_option/ref_option.all.fixed delete mode 100644 tests/ui/ref_option/ref_option.private.fixed delete mode 100644 tests/ui/ref_option/ref_option.rs delete mode 100644 tests/ui/ref_option/ref_option_traits.rs create mode 100644 tests/ui/rest_pat_in_fully_bound_structs.fixed diff --git a/.github/ISSUE_TEMPLATE/new_lint.yml b/.github/ISSUE_TEMPLATE/new_lint.yml index a8202f6378fd3..6ad16aead601d 100644 --- a/.github/ISSUE_TEMPLATE/new_lint.yml +++ b/.github/ISSUE_TEMPLATE/new_lint.yml @@ -1,7 +1,5 @@ name: New lint suggestion -description: | - Suggest a new Clippy lint (currently not accepting new lints) - Check out the Clippy book for more information about the feature freeze. +description: Suggest a new Clippy lint. labels: ["A-lint"] body: - type: markdown diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 83bfd8e9c6865..9e49f60892d26 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -32,10 +32,6 @@ order to get feedback. Delete this line and everything above before opening your PR. -Note that we are currently not taking in new PRs that add new lints. We are in a -feature freeze. Check out the book for more information. If you open a -feature-adding pull request, its review will be delayed. - --- *Please write a short comment explaining your change (or "none" for internal only changes)* diff --git a/.github/workflows/feature_freeze.yml b/.github/workflows/feature_freeze.yml deleted file mode 100644 index 5b139e767007b..0000000000000 --- a/.github/workflows/feature_freeze.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Feature freeze check - -on: - pull_request_target: - types: - - opened - branches: - - master - paths: - - 'clippy_lints/src/declared_lints.rs' - -jobs: - auto-comment: - runs-on: ubuntu-latest - - permissions: - pull-requests: write - - # 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: - - name: Add freeze warning comment - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_REPOSITORY: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - COMMENT=$(echo "**Seems that you are trying to add a new lint!**\n\ - \n\ - We are currently in a [feature freeze](https://doc.rust-lang.org/nightly/clippy/development/feature_freeze.html), so we are delaying all lint-adding PRs to September 18 and [focusing on bugfixes](https://github.com/rust-lang/rust-clippy/issues/15086).\n\ - \n\ - Thanks a lot for your contribution, and sorry for the inconvenience.\n\ - \n\ - With ❤ from the Clippy team.\n\ - \n\ - @rustbot note Feature-freeze\n\ - @rustbot blocked\n\ - @rustbot label +A-lint" - ) - curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Content-Type: application/vnd.github.raw+json" \ - -X POST \ - --data "{\"body\":\"${COMMENT}\"}" \ - "https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" diff --git a/.gitignore b/.gitignore index 36a4cdc1c3528..666c4ceac4dbb 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,6 @@ helper.txt # mdbook generated output /book/book + +# Remove jujutsu directory from search tools +.jj diff --git a/CHANGELOG.md b/CHANGELOG.md index eb2a76a818369..3f26b9470e826 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,152 @@ document. ## Unreleased / Beta / In Rust Nightly -[4ef75291...master](https://github.com/rust-lang/rust-clippy/compare/4ef75291...master) +[e9b7045...master](https://github.com/rust-lang/rust-clippy/compare/e9b7045...master) + +## Rust 1.90 + +Current stable, released 2025-09-18 + +[View all 118 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2025-06-13T15%3A55%3A04Z..2025-07-25T13%3A24%3A00Z+base%3Amaster) + +Note: This Clippy release does not introduce many new lints and is focused entirely on bug fixes — see +[#15086](https://github.com/rust-lang/rust-clippy/issues/15086) for more details. + +## New Lints + +* Added [`manual_is_multiple_of`] to `complexity` [#14292](https://github.com/rust-lang/rust-clippy/pull/14292) +* Added [`doc_broken_link`] to `pedantic` [#13696](https://github.com/rust-lang/rust-clippy/pull/13696) + +### Moves and Deprecations + +* Move [`uninlined_format_args`] to `pedantic` (from `style`, now allow-by-default) [#15287](https://github.com/rust-lang/rust-clippy/pull/15287) + +### Enhancements + +* [`or_fun_call`] now lints `Option::get_or_insert`, `Result::map_or`, `Option/Result::and` methods + [#15071](https://github.com/rust-lang/rust-clippy/pull/15071) + [#15073](https://github.com/rust-lang/rust-clippy/pull/15073) + [#15074](https://github.com/rust-lang/rust-clippy/pull/15074) +* [`incompatible_msrv`] now recognizes types exceeding MSRV + [#15296](https://github.com/rust-lang/rust-clippy/pull/15296) +* [`incompatible_msrv`] now checks the right MSRV when in a `const` context + [#15297](https://github.com/rust-lang/rust-clippy/pull/15297) +* [`zero_ptr`] now lints in `const` context as well + [#15152](https://github.com/rust-lang/rust-clippy/pull/15152) +* [`map_identity`],[`flat_map_identity`] now recognizes `|[x, y]| [x, y]` as an identity function + [#15229](https://github.com/rust-lang/rust-clippy/pull/15229) +* [`exit`] no longer fails on the main function when using `--test` or `--all-targets` flag + [#15222](https://github.com/rust-lang/rust-clippy/pull/15222) + +### False Positive Fixes + +* [`if_then_some_else_none`] fixed FP when require type coercion + [#15267](https://github.com/rust-lang/rust-clippy/pull/15267) +* [`module_name_repetitions`] fixed FP on exported macros + [#15319](https://github.com/rust-lang/rust-clippy/pull/15319) +* [`unused_async`] fixed FP on function with `todo!` + [#15308](https://github.com/rust-lang/rust-clippy/pull/15308) +* [`useless_attribute`] fixed FP when using `#[expect(redundant_imports)]` and similar lint attributes + on `use` statements + [#15318](https://github.com/rust-lang/rust-clippy/pull/15318) +* [`pattern_type_mismatch`] fixed FP in external macro + [#15306](https://github.com/rust-lang/rust-clippy/pull/15306) +* [`large_enum_variant`] fixed FP by not suggesting `Box` in `no_std` mode + [#15241](https://github.com/rust-lang/rust-clippy/pull/15241) +* [`missing_inline_in_public_items`] fixed FP on functions with `extern` + [#15313](https://github.com/rust-lang/rust-clippy/pull/15313) +* [`needless_range_loop`] fixed FP on array literals + [#15314](https://github.com/rust-lang/rust-clippy/pull/15314) +* [`ptr_as_ptr`] fixed wrong suggestions with turbo fish + [#15289](https://github.com/rust-lang/rust-clippy/pull/15289) +* [`range_plus_one`], [`range_minus_one`] fixed FP by restricting lint to cases where it is safe + to switch the range type + [#14432](https://github.com/rust-lang/rust-clippy/pull/14432) +* [`ptr_arg`] fixed FP with underscore binding to `&T` or `&mut T` argument + [#15105](https://github.com/rust-lang/rust-clippy/pull/15105) +* [`unsafe_derive_deserialize`] fixed FP caused by the standard library's `pin!()` macro + [#15137](https://github.com/rust-lang/rust-clippy/pull/15137) +* [`unused_trait_names`] fixed FP in macros + [#14947](https://github.com/rust-lang/rust-clippy/pull/14947) +* [`expect_fun_call`] fixed invalid suggestions + [#15122](https://github.com/rust-lang/rust-clippy/pull/15122) +* [`manual_is_multiple_of`] fixed various false positives + [#15205](https://github.com/rust-lang/rust-clippy/pull/15205) +* [`manual_assert`] fixed wrong suggestions for macros + [#15264](https://github.com/rust-lang/rust-clippy/pull/15264) +* [`expect_used`] fixed false negative when meeting `Option::ok().expect` + [#15253](https://github.com/rust-lang/rust-clippy/pull/15253) +* [`manual_abs_diff`] fixed wrong suggestions behind refs + [#15265](https://github.com/rust-lang/rust-clippy/pull/15265) +* [`approx_constant`] fixed FP with overly precise literals and non trivially formatted numerals + [#15236](https://github.com/rust-lang/rust-clippy/pull/15236) +* [`arithmetic_side_effects`] fixed FP on `NonZeroU*.get() - 1` + [#15238](https://github.com/rust-lang/rust-clippy/pull/15238) +* [`legacy_numeric_constants`] fixed suggestion when call is inside parentheses + [#15191](https://github.com/rust-lang/rust-clippy/pull/15191) +* [`manual_is_variant_and`] fixed inverted suggestions that could lead to code with different semantics + [#15206](https://github.com/rust-lang/rust-clippy/pull/15206) +* [`unnecessary_map_or`] fixed FP by not proposing to replace `map_or` call when types wouldn't be correct + [#15181](https://github.com/rust-lang/rust-clippy/pull/15181) +* [`return_and_then`] fixed FP in case of a partially used expression + [#15115](https://github.com/rust-lang/rust-clippy/pull/15115) +* [`manual_let_else`] fixed FP with binding subpattern with unused name + [#15118](https://github.com/rust-lang/rust-clippy/pull/15118) +* [`suboptimal_flops`] fixed FP with ambiguous float types in mul_add suggestions + [#15133](https://github.com/rust-lang/rust-clippy/pull/15133) +* [`borrow_as_ptr`] fixed FP by not removing cast to trait object pointer + [#15145](https://github.com/rust-lang/rust-clippy/pull/15145) +* [`unnecessary_operation`] fixed FP by not removing casts if they are useful to type the expression + [#15182](https://github.com/rust-lang/rust-clippy/pull/15182) +* [`empty_loop`] fixed FP on intrinsic function declaration + [#15201](https://github.com/rust-lang/rust-clippy/pull/15201) +* [`doc_nested_refdefs`] fixed FP where task lists are reported as refdefs + [#15146](https://github.com/rust-lang/rust-clippy/pull/15146) +* [`std_instead_of_core`] fixed FP when not all items come from the new crate + [#15165](https://github.com/rust-lang/rust-clippy/pull/15165) +* [`swap_with_temporary`] fixed FP leading to different semantics being suggested + [#15172](https://github.com/rust-lang/rust-clippy/pull/15172) +* [`cast_possible_truncation`] fixed FP by not giving suggestions inside const context + [#15164](https://github.com/rust-lang/rust-clippy/pull/15164) +* [`missing_panics_doc`] fixed FP by allowing unwrap() and expect()s in const-only contexts + [#15170](https://github.com/rust-lang/rust-clippy/pull/15170) +* [`disallowed_script_idents`] fixed FP on identifiers with `_` + [#15123](https://github.com/rust-lang/rust-clippy/pull/15123) +* [`wildcard_enum_match_arm`] fixed wrong suggestions with raw identifiers + [#15093](https://github.com/rust-lang/rust-clippy/pull/15093) +* [`borrow_deref_ref`] fixed FP by not proposing replacing a reborrow when the reborrow is itself mutably borrowed + [#14967](https://github.com/rust-lang/rust-clippy/pull/14967) +* [`manual_ok_err`] fixed wrong suggestions with references + [#15053](https://github.com/rust-lang/rust-clippy/pull/15053) +* [`identity_op`] fixed FP when encountering `Default::default()` + [#15028](https://github.com/rust-lang/rust-clippy/pull/15028) +* [`exhaustive_structs`] fixed FP on structs with default valued field + [#15022](https://github.com/rust-lang/rust-clippy/pull/15022) +* [`collapsible_else_if`] fixed FP on conditionally compiled stmt + [#14906](https://github.com/rust-lang/rust-clippy/pull/14906) +* [`missing_const_for_fn`] fixed FP by checking MSRV before emitting lint on function containing + non-`Sized` trait bounds + [#15080](https://github.com/rust-lang/rust-clippy/pull/15080) +* [`question_mark`] fixed FP when else branch of let-else contains `#[cfg]` + [#15082](https://github.com/rust-lang/rust-clippy/pull/15082) + +### ICE Fixes + +* [`single_match`] fixed ICE with deref patterns and string literals + [#15124](https://github.com/rust-lang/rust-clippy/pull/15124) +* [`needless_doctest_main`] fixed panic when doctest is invalid + [#15052](https://github.com/rust-lang/rust-clippy/pull/15052) +* [`zero_sized_map_values`] do not attempt to compute size of a type with escaping lifetimes + [#15434](https://github.com/rust-lang/rust-clippy/pull/15434) + +### Documentation Improvements + +* [`manual_is_variant_and`] improved documentation to include equality comparison patterns + [#15239](https://github.com/rust-lang/rust-clippy/pull/15239) +* [`uninlined_format_args`] improved documentation with example of how to fix a `{:?}` parameter + [#15228](https://github.com/rust-lang/rust-clippy/pull/15228) +* [`undocumented_unsafe_blocks`] improved documentation wording + [#15213](https://github.com/rust-lang/rust-clippy/pull/15213) ## Rust 1.89 @@ -6408,6 +6553,7 @@ Released 2018-09-13 [`redundant_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_feature_names [`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names [`redundant_guards`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_guards +[`redundant_iter_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_iter_cloned [`redundant_locals`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_locals [`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern [`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching diff --git a/Cargo.toml b/Cargo.toml index b3618932ded7d..e06383499893a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy" -version = "0.1.91" +version = "0.1.92" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/book/src/README.md b/book/src/README.md index db73b49ecc24e..5d2c3972b060a 100644 --- a/book/src/README.md +++ b/book/src/README.md @@ -1,9 +1,5 @@ # Clippy -[### IMPORTANT NOTE FOR CONTRIBUTORS ================](development/feature_freeze.md) - ----- - [![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](https://github.com/rust-lang/rust-clippy#license) A collection of lints to catch common mistakes and improve your diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index b66c3481e4930..39fe7358ed87a 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -13,7 +13,6 @@ - [GitLab CI](continuous_integration/gitlab.md) - [Travis CI](continuous_integration/travis.md) - [Development](development/README.md) - - [IMPORTANT: FEATURE FREEZE](development/feature_freeze.md) - [Basics](development/basics.md) - [Adding Lints](development/adding_lints.md) - [Defining Lints](development/defining_lints.md) diff --git a/book/src/development/adding_lints.md b/book/src/development/adding_lints.md index a42a298374465..2b89e94cf8f4f 100644 --- a/book/src/development/adding_lints.md +++ b/book/src/development/adding_lints.md @@ -1,8 +1,5 @@ # Adding a new lint -[### IMPORTANT NOTE FOR CONTRIBUTORS ================](feature_freeze.md) - - You are probably here because you want to add a new lint to Clippy. If this is the first time you're contributing to Clippy, this document guides you through creating an example lint from scratch. diff --git a/book/src/development/feature_freeze.md b/book/src/development/feature_freeze.md deleted file mode 100644 index 260cb136cc075..0000000000000 --- a/book/src/development/feature_freeze.md +++ /dev/null @@ -1,55 +0,0 @@ -# IMPORTANT: FEATURE FREEZE - -This is a temporary notice. - -From the 26th of June until the 18th of September we will perform a feature freeze. Only bugfix PRs will be reviewed -except already open ones. Every feature-adding PR opened in between those dates will be moved into a -milestone to be reviewed separately at another time. - -We do this because of the long backlog of bugs that need to be addressed -in order to continue being the state-of-the-art linter that Clippy has become known for being. - -## For contributors - -If you are a contributor or are planning to become one, **please do not open a lint-adding PR**, we have lots of open -bugs of all levels of difficulty that you can address instead! - -We currently have about 800 lints, each one posing a maintainability challenge that needs to account to every possible -use case of the whole ecosystem. Bugs are natural in every software, but the Clippy team considers that Clippy needs a -refinement period. - -If you open a PR at this time, we will not review it but push it into a milestone until the refinement period ends, -adding additional load into our reviewing schedules. - -## I want to help, what can I do - -Thanks a lot to everyone who wants to help Clippy become better software in this feature freeze period! -If you'd like to help, making a bugfix, making sure that it works, and opening a PR is a great step! - -To find things to fix, go to the [tracking issue][tracking_issue], find an issue that you like, go there and claim that -issue with `@rustbot claim`. - -As a general metric and always taking into account your skill and knowledge level, you can use this guide: - -- 🟥 [ICEs][search_ice], these are compiler errors that causes Clippy to panic and crash. Usually involves high-level -debugging, sometimes interacting directly with the upstream compiler. Difficult to fix but a great challenge that -improves a lot developer workflows! - -- 🟧 [Suggestion causes bug][sugg_causes_bug], Clippy suggested code that changed logic in some silent way. -Unacceptable, as this may have disastrous consequences. Easier to fix than ICEs - -- 🟨 [Suggestion causes error][sugg_causes_error], Clippy suggested code snippet that caused a compiler error -when applied. We need to make sure that Clippy doesn't suggest using a variable twice at the same time or similar -easy-to-happen occurrences. - -- 🟩 [False positives][false_positive], a lint should not have fired, the easiest of them all, as this is "just" -identifying the root of a false positive and making an exception for those cases. - -Note that false negatives do not have priority unless the case is very clear, as they are a feature-request in a -trench coat. - -[search_ice]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc+state%3Aopen+label%3A%22I-ICE%22 -[sugg_causes_bug]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc%20state%3Aopen%20label%3AI-suggestion-causes-bug -[sugg_causes_error]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc%20state%3Aopen%20label%3AI-suggestion-causes-error%20 -[false_positive]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc%20state%3Aopen%20label%3AI-false-positive -[tracking_issue]: https://github.com/rust-lang/rust-clippy/issues/15086 diff --git a/clippy_config/Cargo.toml b/clippy_config/Cargo.toml index 6ad2cf0d0b104..f8c748290e418 100644 --- a/clippy_config/Cargo.toml +++ b/clippy_config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_config" -version = "0.1.91" +version = "0.1.92" edition = "2024" publish = false diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 70184ee2ca596..51e59ae205076 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_lints" -version = "0.1.91" +version = "0.1.92" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/clippy_lints/src/attrs/useless_attribute.rs b/clippy_lints/src/attrs/useless_attribute.rs index b9b5cedb5aa76..1cebc18edc90a 100644 --- a/clippy_lints/src/attrs/useless_attribute.rs +++ b/clippy_lints/src/attrs/useless_attribute.rs @@ -30,6 +30,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) { sym::ambiguous_glob_reexports | sym::dead_code | sym::deprecated + | sym::deprecated_in_future | sym::hidden_glob_reexports | sym::unreachable_pub | sym::unused diff --git a/clippy_lints/src/casts/as_underscore.rs b/clippy_lints/src/casts/as_underscore.rs index 3ac486dd63fbb..a73e48e5fd5d1 100644 --- a/clippy_lints/src/casts/as_underscore.rs +++ b/clippy_lints/src/casts/as_underscore.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use rustc_errors::Applicability; use rustc_hir::{Expr, Ty, TyKind}; use rustc_lint::LateContext; -use rustc_middle::ty; +use rustc_middle::ty::IsSuggestable; use super::AS_UNDERSCORE; @@ -10,15 +10,15 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ty: &'tc if matches!(ty.kind, TyKind::Infer(())) { span_lint_and_then(cx, AS_UNDERSCORE, expr.span, "using `as _` conversion", |diag| { let ty_resolved = cx.typeck_results().expr_ty(expr); - if let ty::Error(_) = ty_resolved.kind() { - diag.help("consider giving the type explicitly"); - } else { + if ty_resolved.is_suggestable(cx.tcx, true) { diag.span_suggestion( ty.span, "consider giving the type explicitly", ty_resolved, Applicability::MachineApplicable, ); + } else { + diag.help("consider giving the type explicitly"); } }); } diff --git a/clippy_lints/src/casts/cast_possible_wrap.rs b/clippy_lints/src/casts/cast_possible_wrap.rs index e26c03ccda933..9eaa6e4cf26e4 100644 --- a/clippy_lints/src/casts/cast_possible_wrap.rs +++ b/clippy_lints/src/casts/cast_possible_wrap.rs @@ -1,4 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::sugg::Sugg; +use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty::Ty; @@ -16,7 +19,14 @@ enum EmitState { LintOnPtrSize(u64), } -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { +pub(super) fn check( + cx: &LateContext<'_>, + expr: &Expr<'_>, + cast_op: &Expr<'_>, + cast_from: Ty<'_>, + cast_to: Ty<'_>, + msrv: Msrv, +) { let (Some(from_nbits), Some(to_nbits)) = ( utils::int_ty_to_nbits(cx.tcx, cast_from), utils::int_ty_to_nbits(cx.tcx, cast_to), @@ -85,5 +95,23 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca .note("`usize` and `isize` may be as small as 16 bits on some platforms") .note("for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types"); } + + if msrv.meets(cx, msrvs::INTEGER_SIGN_CAST) + && let Some(cast) = utils::is_signedness_cast(cast_from, cast_to) + { + let method = match cast { + utils::CastTo::Signed => "cast_signed()", + utils::CastTo::Unsigned => "cast_unsigned()", + }; + let mut app = Applicability::MaybeIncorrect; + let sugg = Sugg::hir_with_context(cx, cast_op, expr.span.ctxt(), "..", &mut app); + + diag.span_suggestion( + expr.span, + format!("if this is intentional, use `{method}` instead"), + format!("{}.{method}", sugg.maybe_paren()), + app, + ); + } }); } diff --git a/clippy_lints/src/casts/cast_sign_loss.rs b/clippy_lints/src/casts/cast_sign_loss.rs index a70bd88619195..f870d27b796e8 100644 --- a/clippy_lints/src/casts/cast_sign_loss.rs +++ b/clippy_lints/src/casts/cast_sign_loss.rs @@ -2,15 +2,18 @@ use std::convert::Infallible; use std::ops::ControlFlow; use clippy_utils::consts::{ConstEvalCtxt, Constant}; -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::sugg::Sugg; use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; use clippy_utils::{method_chain_args, sext, sym}; +use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty}; use rustc_span::Symbol; -use super::CAST_SIGN_LOSS; +use super::{CAST_SIGN_LOSS, utils}; /// A list of methods that can never return a negative value. /// Includes methods that panic rather than returning a negative value. @@ -42,13 +45,33 @@ pub(super) fn check<'cx>( cast_op: &Expr<'_>, cast_from: Ty<'cx>, cast_to: Ty<'_>, + msrv: Msrv, ) { if should_lint(cx, cast_op, cast_from, cast_to) { - span_lint( + span_lint_and_then( cx, CAST_SIGN_LOSS, expr.span, format!("casting `{cast_from}` to `{cast_to}` may lose the sign of the value"), + |diag| { + if msrv.meets(cx, msrvs::INTEGER_SIGN_CAST) + && let Some(cast) = utils::is_signedness_cast(cast_from, cast_to) + { + let method = match cast { + utils::CastTo::Signed => "cast_signed()", + utils::CastTo::Unsigned => "cast_unsigned()", + }; + let mut app = Applicability::MaybeIncorrect; + let sugg = Sugg::hir_with_context(cx, cast_op, expr.span.ctxt(), "..", &mut app); + + diag.span_suggestion( + expr.span, + format!("if this is intentional, use `{method}` instead"), + format!("{}.{method}", sugg.maybe_paren()), + app, + ); + } + }, ); } } diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index d2e62ee56e43b..47cc1da0a6e9f 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -890,9 +890,9 @@ impl<'tcx> LateLintPass<'tcx> for Casts { if cast_to.is_numeric() { cast_possible_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir.span); if cast_from.is_numeric() { - cast_possible_wrap::check(cx, expr, cast_from, cast_to); + cast_possible_wrap::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv); cast_precision_loss::check(cx, expr, cast_from, cast_to); - cast_sign_loss::check(cx, expr, cast_from_expr, cast_from, cast_to); + cast_sign_loss::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv); cast_abs_to_unsigned::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv); cast_nan_to_int::check(cx, expr, cast_from_expr, cast_from, cast_to); } diff --git a/clippy_lints/src/casts/utils.rs b/clippy_lints/src/casts/utils.rs index d846d78b9ee78..707fc2a8eed35 100644 --- a/clippy_lints/src/casts/utils.rs +++ b/clippy_lints/src/casts/utils.rs @@ -60,3 +60,18 @@ pub(super) fn enum_ty_to_nbits(adt: AdtDef<'_>, tcx: TyCtxt<'_>) -> u64 { neg_bits.max(pos_bits).into() } } + +pub(super) enum CastTo { + Signed, + Unsigned, +} +/// Returns `Some` if the type cast is between 2 integral types that differ +/// only in signedness, otherwise `None`. The value of `Some` is which +/// signedness is casted to. +pub(super) fn is_signedness_cast(cast_from: Ty<'_>, cast_to: Ty<'_>) -> Option { + match (cast_from.kind(), cast_to.kind()) { + (ty::Int(from), ty::Uint(to)) if from.to_unsigned() == *to => Some(CastTo::Unsigned), + (ty::Uint(from), ty::Int(to)) if *from == to.to_unsigned() => Some(CastTo::Signed), + _ => None, + } +} diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index d0c7443a4a4b9..2a4bedc184552 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -448,10 +448,12 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::methods::OR_THEN_UNWRAP_INFO, crate::methods::PATH_BUF_PUSH_OVERWRITE_INFO, crate::methods::PATH_ENDS_WITH_EXT_INFO, + crate::methods::PTR_OFFSET_WITH_CAST_INFO, crate::methods::RANGE_ZIP_WITH_LEN_INFO, crate::methods::READONLY_WRITE_LOCK_INFO, crate::methods::READ_LINE_WITHOUT_TRIM_INFO, crate::methods::REDUNDANT_AS_STR_INFO, + crate::methods::REDUNDANT_ITER_CLONED_INFO, crate::methods::REPEAT_ONCE_INFO, crate::methods::RESULT_FILTER_MAP_INFO, crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO, @@ -503,7 +505,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::min_ident_chars::MIN_IDENT_CHARS_INFO, crate::minmax::MIN_MAX_INFO, crate::misc::SHORT_CIRCUIT_STATEMENT_INFO, - crate::misc::TOPLEVEL_REF_ARG_INFO, crate::misc::USED_UNDERSCORE_BINDING_INFO, crate::misc::USED_UNDERSCORE_ITEMS_INFO, crate::misc_early::BUILTIN_TYPE_SHADOW_INFO, @@ -625,7 +626,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::ptr::MUT_FROM_REF_INFO, crate::ptr::PTR_ARG_INFO, crate::ptr::PTR_EQ_INFO, - crate::ptr_offset_with_cast::PTR_OFFSET_WITH_CAST_INFO, crate::pub_underscore_fields::PUB_UNDERSCORE_FIELDS_INFO, crate::pub_use::PUB_USE_INFO, crate::question_mark::QUESTION_MARK_INFO, @@ -706,6 +706,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO, crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO, crate::to_string_trait_impl::TO_STRING_TRAIT_IMPL_INFO, + crate::toplevel_ref_arg::TOPLEVEL_REF_ARG_INFO, crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO, crate::trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS_INFO, crate::trait_bounds::TYPE_REPETITION_IN_BOUNDS_INFO, diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 9645a26a68ae1..a70105db19490 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -1,10 +1,9 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; -use clippy_utils::ty::{adjust_derefs_manually_drop, implements_trait, is_manually_drop}; +use clippy_utils::ty::{adjust_derefs_manually_drop, implements_trait, is_manually_drop, peel_and_count_ty_refs}; 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 rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::fx::FxIndexMap; @@ -942,7 +941,7 @@ fn report<'tcx>( let (expr_str, expr_is_macro_call) = snippet_with_context(cx, expr.span, data.first_expr.span.ctxt(), "..", &mut app); let ty = typeck.expr_ty(expr); - let (_, ref_count) = peel_middle_ty_refs(ty); + let (_, ref_count, _) = peel_and_count_ty_refs(ty); let deref_str = if ty_changed_count >= ref_count && ref_count != 0 { // a deref call changing &T -> &U requires two deref operators the first time // this occurs. One to remove the reference, a second to call the deref impl. @@ -1045,7 +1044,7 @@ fn report<'tcx>( if let ty::Ref(_, dst, _) = data.adjusted_ty.kind() && dst.is_slice() { - let (src, n_src_refs) = peel_middle_ty_refs(ty); + let (src, n_src_refs, _) = peel_and_count_ty_refs(ty); if n_src_refs >= 2 && src.is_array() { return; } diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs deleted file mode 100644 index c53a957f6a8b7..0000000000000 --- a/clippy_lints/src/derive.rs +++ /dev/null @@ -1,527 +0,0 @@ -use std::ops::ControlFlow; - -use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then, span_lint_hir_and_then}; -use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy}; -use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, paths}; -use rustc_errors::Applicability; -use rustc_hir::def_id::DefId; -use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn, walk_item}; -use rustc_hir::{self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Impl, Item, ItemKind, UnsafeSource}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::hir::nested_filter; -use rustc_middle::ty::{ - self, ClauseKind, GenericArgKind, GenericParamDefKind, ParamEnv, TraitPredicate, Ty, TyCtxt, Upcast, -}; -use rustc_session::declare_lint_pass; -use rustc_span::def_id::LocalDefId; -use rustc_span::{Span, sym}; - -declare_clippy_lint! { - /// ### What it does - /// Lints against manual `PartialEq` implementations for types with a derived `Hash` - /// implementation. - /// - /// ### Why is this bad? - /// The implementation of these traits must agree (for - /// example for use with `HashMap`) so it’s probably a bad idea to use a - /// default-generated `Hash` implementation with an explicitly defined - /// `PartialEq`. In particular, the following must hold for any type: - /// - /// ```text - /// k1 == k2 ⇒ hash(k1) == hash(k2) - /// ``` - /// - /// ### Example - /// ```ignore - /// #[derive(Hash)] - /// struct Foo; - /// - /// impl PartialEq for Foo { - /// ... - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub DERIVED_HASH_WITH_MANUAL_EQ, - correctness, - "deriving `Hash` but implementing `PartialEq` explicitly" -} - -declare_clippy_lint! { - /// ### What it does - /// Lints against manual `PartialOrd` and `Ord` implementations for types with a derived `Ord` - /// or `PartialOrd` implementation. - /// - /// ### Why is this bad? - /// The implementation of these traits must agree (for - /// example for use with `sort`) so it’s probably a bad idea to use a - /// default-generated `Ord` implementation with an explicitly defined - /// `PartialOrd`. In particular, the following must hold for any type - /// implementing `Ord`: - /// - /// ```text - /// k1.cmp(&k2) == k1.partial_cmp(&k2).unwrap() - /// ``` - /// - /// ### Example - /// ```rust,ignore - /// #[derive(Ord, PartialEq, Eq)] - /// struct Foo; - /// - /// impl PartialOrd for Foo { - /// ... - /// } - /// ``` - /// Use instead: - /// ```rust,ignore - /// #[derive(PartialEq, Eq)] - /// struct Foo; - /// - /// impl PartialOrd for Foo { - /// fn partial_cmp(&self, other: &Foo) -> Option { - /// Some(self.cmp(other)) - /// } - /// } - /// - /// impl Ord for Foo { - /// ... - /// } - /// ``` - /// or, if you don't need a custom ordering: - /// ```rust,ignore - /// #[derive(Ord, PartialOrd, PartialEq, Eq)] - /// struct Foo; - /// ``` - #[clippy::version = "1.47.0"] - pub DERIVE_ORD_XOR_PARTIAL_ORD, - correctness, - "deriving `Ord` but implementing `PartialOrd` explicitly" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for explicit `Clone` implementations for `Copy` - /// types. - /// - /// ### Why is this bad? - /// To avoid surprising behavior, these traits should - /// agree and the behavior of `Copy` cannot be overridden. In almost all - /// situations a `Copy` type should have a `Clone` implementation that does - /// nothing more than copy the object, which is what `#[derive(Copy, Clone)]` - /// gets you. - /// - /// ### Example - /// ```rust,ignore - /// #[derive(Copy)] - /// struct Foo; - /// - /// impl Clone for Foo { - /// // .. - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub EXPL_IMPL_CLONE_ON_COPY, - pedantic, - "implementing `Clone` explicitly on `Copy` types" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for deriving `serde::Deserialize` on a type that - /// has methods using `unsafe`. - /// - /// ### Why is this bad? - /// Deriving `serde::Deserialize` will create a constructor - /// that may violate invariants held by another constructor. - /// - /// ### Example - /// ```rust,ignore - /// use serde::Deserialize; - /// - /// #[derive(Deserialize)] - /// pub struct Foo { - /// // .. - /// } - /// - /// impl Foo { - /// pub fn new() -> Self { - /// // setup here .. - /// } - /// - /// pub unsafe fn parts() -> (&str, &str) { - /// // assumes invariants hold - /// } - /// } - /// ``` - #[clippy::version = "1.45.0"] - pub UNSAFE_DERIVE_DESERIALIZE, - pedantic, - "deriving `serde::Deserialize` on a type that has methods using `unsafe`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for types that derive `PartialEq` and could implement `Eq`. - /// - /// ### Why is this bad? - /// If a type `T` derives `PartialEq` and all of its members implement `Eq`, - /// then `T` can always implement `Eq`. Implementing `Eq` allows `T` to be used - /// in APIs that require `Eq` types. It also allows structs containing `T` to derive - /// `Eq` themselves. - /// - /// ### Example - /// ```no_run - /// #[derive(PartialEq)] - /// struct Foo { - /// i_am_eq: i32, - /// i_am_eq_too: Vec, - /// } - /// ``` - /// Use instead: - /// ```no_run - /// #[derive(PartialEq, Eq)] - /// struct Foo { - /// i_am_eq: i32, - /// i_am_eq_too: Vec, - /// } - /// ``` - #[clippy::version = "1.63.0"] - pub DERIVE_PARTIAL_EQ_WITHOUT_EQ, - nursery, - "deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`" -} - -declare_lint_pass!(Derive => [ - EXPL_IMPL_CLONE_ON_COPY, - DERIVED_HASH_WITH_MANUAL_EQ, - DERIVE_ORD_XOR_PARTIAL_ORD, - UNSAFE_DERIVE_DESERIALIZE, - DERIVE_PARTIAL_EQ_WITHOUT_EQ -]); - -impl<'tcx> LateLintPass<'tcx> for Derive { - fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - if let ItemKind::Impl(Impl { - of_trait: Some(of_trait), - .. - }) = item.kind - { - let trait_ref = &of_trait.trait_ref; - let ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); - let is_automatically_derived = cx.tcx.is_automatically_derived(item.owner_id.to_def_id()); - - check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived); - check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived); - - if is_automatically_derived { - check_unsafe_derive_deserialize(cx, item, trait_ref, ty); - check_partial_eq_without_eq(cx, item.span, trait_ref, ty); - } else { - check_copy_clone(cx, item, trait_ref, ty); - } - } - } -} - -/// Implementation of the `DERIVED_HASH_WITH_MANUAL_EQ` lint. -fn check_hash_peq<'tcx>( - cx: &LateContext<'tcx>, - span: Span, - trait_ref: &hir::TraitRef<'_>, - ty: Ty<'tcx>, - hash_is_automatically_derived: bool, -) { - if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait() - && let Some(def_id) = trait_ref.trait_def_id() - && cx.tcx.is_diagnostic_item(sym::Hash, def_id) - { - // Look for the PartialEq implementations for `ty` - cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| { - let peq_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id); - - if !hash_is_automatically_derived || peq_is_automatically_derived { - return; - } - - let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation"); - - // Only care about `impl PartialEq for Foo` - // For `impl PartialEq for A, input_types is [A, B] - if trait_ref.instantiate_identity().args.type_at(1) == ty { - span_lint_and_then( - cx, - DERIVED_HASH_WITH_MANUAL_EQ, - span, - "you are deriving `Hash` but have implemented `PartialEq` explicitly", - |diag| { - if let Some(local_def_id) = impl_id.as_local() { - let hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id); - diag.span_note(cx.tcx.hir_span(hir_id), "`PartialEq` implemented here"); - } - }, - ); - } - }); - } -} - -/// Implementation of the `DERIVE_ORD_XOR_PARTIAL_ORD` lint. -fn check_ord_partial_ord<'tcx>( - cx: &LateContext<'tcx>, - span: Span, - trait_ref: &hir::TraitRef<'_>, - ty: Ty<'tcx>, - ord_is_automatically_derived: bool, -) { - if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord) - && let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait() - && let Some(def_id) = &trait_ref.trait_def_id() - && *def_id == ord_trait_def_id - { - // Look for the PartialOrd implementations for `ty` - cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| { - let partial_ord_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id); - - if partial_ord_is_automatically_derived == ord_is_automatically_derived { - return; - } - - let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation"); - - // Only care about `impl PartialOrd for Foo` - // For `impl PartialOrd for A, input_types is [A, B] - if trait_ref.instantiate_identity().args.type_at(1) == ty { - let mess = if partial_ord_is_automatically_derived { - "you are implementing `Ord` explicitly but have derived `PartialOrd`" - } else { - "you are deriving `Ord` but have implemented `PartialOrd` explicitly" - }; - - span_lint_and_then(cx, DERIVE_ORD_XOR_PARTIAL_ORD, span, mess, |diag| { - if let Some(local_def_id) = impl_id.as_local() { - let hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id); - diag.span_note(cx.tcx.hir_span(hir_id), "`PartialOrd` implemented here"); - } - }); - } - }); - } -} - -/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint. -fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { - let clone_id = match cx.tcx.lang_items().clone_trait() { - Some(id) if trait_ref.trait_def_id() == Some(id) => id, - _ => return, - }; - let Some(copy_id) = cx.tcx.lang_items().copy_trait() else { - return; - }; - let (ty_adt, ty_subs) = match *ty.kind() { - // Unions can't derive clone. - ty::Adt(adt, subs) if !adt.is_union() => (adt, subs), - _ => return, - }; - // If the current self type doesn't implement Copy (due to generic constraints), search to see if - // there's a Copy impl for any instance of the adt. - if !is_copy(cx, ty) { - if ty_subs.non_erasable_generics().next().is_some() { - let has_copy_impl = cx.tcx.local_trait_impls(copy_id).iter().any(|&id| { - matches!(cx.tcx.type_of(id).instantiate_identity().kind(), ty::Adt(adt, _) - if ty_adt.did() == adt.did()) - }); - if !has_copy_impl { - return; - } - } else { - return; - } - } - // Derive constrains all generic types to requiring Clone. Check if any type is not constrained for - // this impl. - if ty_subs.types().any(|ty| !implements_trait(cx, ty, clone_id, &[])) { - return; - } - // `#[repr(packed)]` structs with type/const parameters can't derive `Clone`. - // https://github.com/rust-lang/rust-clippy/issues/10188 - if ty_adt.repr().packed() - && ty_subs - .iter() - .any(|arg| matches!(arg.kind(), GenericArgKind::Type(_) | GenericArgKind::Const(_))) - { - return; - } - // The presence of `unsafe` fields prevents deriving `Clone` automatically - if ty_adt.all_fields().any(|f| f.safety.is_unsafe()) { - return; - } - - span_lint_and_note( - cx, - EXPL_IMPL_CLONE_ON_COPY, - item.span, - "you are implementing `Clone` explicitly on a `Copy` type", - Some(item.span), - "consider deriving `Clone` or removing `Copy`", - ); -} - -/// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint. -fn check_unsafe_derive_deserialize<'tcx>( - cx: &LateContext<'tcx>, - item: &Item<'_>, - trait_ref: &hir::TraitRef<'_>, - ty: Ty<'tcx>, -) { - fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool { - let mut visitor = UnsafeVisitor { cx }; - walk_item(&mut visitor, item).is_break() - } - - if let Some(trait_def_id) = trait_ref.trait_def_id() - && paths::SERDE_DESERIALIZE.matches(cx, trait_def_id) - && let ty::Adt(def, _) = ty.kind() - && let Some(local_def_id) = def.did().as_local() - && let adt_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id) - && !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id) - && cx - .tcx - .inherent_impls(def.did()) - .iter() - .map(|imp_did| cx.tcx.hir_expect_item(imp_did.expect_local())) - .any(|imp| has_unsafe(cx, imp)) - { - span_lint_hir_and_then( - cx, - UNSAFE_DERIVE_DESERIALIZE, - adt_hir_id, - item.span, - "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`", - |diag| { - diag.help( - "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html", - ); - }, - ); - } -} - -struct UnsafeVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, -} - -impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { - type Result = ControlFlow<()>; - type NestedFilter = nested_filter::All; - - fn visit_fn( - &mut self, - kind: FnKind<'tcx>, - decl: &'tcx FnDecl<'_>, - body_id: BodyId, - _: Span, - id: LocalDefId, - ) -> Self::Result { - if let Some(header) = kind.header() - && header.is_unsafe() - { - ControlFlow::Break(()) - } else { - walk_fn(self, kind, decl, body_id, id) - } - } - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> Self::Result { - if let ExprKind::Block(block, _) = expr.kind - && block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) - && block - .span - .source_callee() - .and_then(|expr| expr.macro_def_id) - .is_none_or(|did| !self.cx.tcx.is_diagnostic_item(sym::pin_macro, did)) - { - return ControlFlow::Break(()); - } - - walk_expr(self, expr) - } - - fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { - self.cx.tcx - } -} - -/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint. -fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { - if let ty::Adt(adt, args) = ty.kind() - && cx.tcx.visibility(adt.did()).is_public() - && let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq) - && let Some(def_id) = trait_ref.trait_def_id() - && cx.tcx.is_diagnostic_item(sym::PartialEq, def_id) - && !has_non_exhaustive_attr(cx.tcx, *adt) - && !ty_implements_eq_trait(cx.tcx, ty, eq_trait_def_id) - && let typing_env = typing_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id) - && let Some(local_def_id) = adt.did().as_local() - // If all of our fields implement `Eq`, we can implement `Eq` too - && adt - .all_fields() - .map(|f| f.ty(cx.tcx, args)) - .all(|ty| implements_trait_with_env(cx.tcx, typing_env, ty, eq_trait_def_id, None, &[])) - { - span_lint_hir_and_then( - cx, - DERIVE_PARTIAL_EQ_WITHOUT_EQ, - cx.tcx.local_def_id_to_hir_id(local_def_id), - span.ctxt().outer_expn_data().call_site, - "you are deriving `PartialEq` and can implement `Eq`", - |diag| { - diag.span_suggestion( - span.ctxt().outer_expn_data().call_site, - "consider deriving `Eq` as well", - "PartialEq, Eq", - Applicability::MachineApplicable, - ); - }, - ); - } -} - -fn ty_implements_eq_trait<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, eq_trait_id: DefId) -> bool { - tcx.non_blanket_impls_for_ty(eq_trait_id, ty).next().is_some() -} - -/// Creates the `ParamEnv` used for the given type's derived `Eq` impl. -fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ty::TypingEnv<'_> { - // Initial map from generic index to param def. - // Vec<(param_def, needs_eq)> - let mut params = tcx - .generics_of(did) - .own_params - .iter() - .map(|p| (p, matches!(p.kind, GenericParamDefKind::Type { .. }))) - .collect::>(); - - let ty_predicates = tcx.predicates_of(did).predicates; - for (p, _) in ty_predicates { - if let ClauseKind::Trait(p) = p.kind().skip_binder() - && p.trait_ref.def_id == eq_trait_id - && let ty::Param(self_ty) = p.trait_ref.self_ty().kind() - { - // Flag types which already have an `Eq` bound. - params[self_ty.index as usize].1 = false; - } - } - - let param_env = ParamEnv::new(tcx.mk_clauses_from_iter(ty_predicates.iter().map(|&(p, _)| p).chain( - params.iter().filter(|&&(_, needs_eq)| needs_eq).map(|&(param, _)| { - ClauseKind::Trait(TraitPredicate { - trait_ref: ty::TraitRef::new(tcx, eq_trait_id, [tcx.mk_param_from_def(param)]), - polarity: ty::PredicatePolarity::Positive, - }) - .upcast(tcx) - }), - ))); - ty::TypingEnv { - typing_mode: ty::TypingMode::non_body_analysis(), - param_env, - } -} diff --git a/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs b/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs new file mode 100644 index 0000000000000..cbbcb2f7a3bab --- /dev/null +++ b/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs @@ -0,0 +1,50 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty::Ty; +use rustc_span::{Span, sym}; + +use super::DERIVE_ORD_XOR_PARTIAL_ORD; + +/// Implementation of the `DERIVE_ORD_XOR_PARTIAL_ORD` lint. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + span: Span, + trait_ref: &hir::TraitRef<'_>, + ty: Ty<'tcx>, + ord_is_automatically_derived: bool, +) { + if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord) + && let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait() + && let Some(def_id) = &trait_ref.trait_def_id() + && *def_id == ord_trait_def_id + { + // Look for the PartialOrd implementations for `ty` + cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| { + let partial_ord_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id); + + if partial_ord_is_automatically_derived == ord_is_automatically_derived { + return; + } + + let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation"); + + // Only care about `impl PartialOrd for Foo` + // For `impl PartialOrd for A, input_types is [A, B] + if trait_ref.instantiate_identity().args.type_at(1) == ty { + let mess = if partial_ord_is_automatically_derived { + "you are implementing `Ord` explicitly but have derived `PartialOrd`" + } else { + "you are deriving `Ord` but have implemented `PartialOrd` explicitly" + }; + + span_lint_and_then(cx, DERIVE_ORD_XOR_PARTIAL_ORD, span, mess, |diag| { + if let Some(local_def_id) = impl_id.as_local() { + let hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id); + diag.span_note(cx.tcx.hir_span(hir_id), "`PartialOrd` implemented here"); + } + }); + } + }); + } +} diff --git a/clippy_lints/src/derive/derive_partial_eq_without_eq.rs b/clippy_lints/src/derive/derive_partial_eq_without_eq.rs new file mode 100644 index 0000000000000..ed7881c461ffc --- /dev/null +++ b/clippy_lints/src/derive/derive_partial_eq_without_eq.rs @@ -0,0 +1,87 @@ +use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::has_non_exhaustive_attr; +use clippy_utils::ty::implements_trait_with_env; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::def_id::DefId; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, ClauseKind, GenericParamDefKind, ParamEnv, TraitPredicate, Ty, TyCtxt, Upcast}; +use rustc_span::{Span, sym}; + +use super::DERIVE_PARTIAL_EQ_WITHOUT_EQ; + +/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint. +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { + if let ty::Adt(adt, args) = ty.kind() + && cx.tcx.visibility(adt.did()).is_public() + && let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq) + && let Some(def_id) = trait_ref.trait_def_id() + && cx.tcx.is_diagnostic_item(sym::PartialEq, def_id) + && !has_non_exhaustive_attr(cx.tcx, *adt) + && !ty_implements_eq_trait(cx.tcx, ty, eq_trait_def_id) + && let typing_env = typing_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id) + && let Some(local_def_id) = adt.did().as_local() + // If all of our fields implement `Eq`, we can implement `Eq` too + && adt + .all_fields() + .map(|f| f.ty(cx.tcx, args)) + .all(|ty| implements_trait_with_env(cx.tcx, typing_env, ty, eq_trait_def_id, None, &[])) + { + span_lint_hir_and_then( + cx, + DERIVE_PARTIAL_EQ_WITHOUT_EQ, + cx.tcx.local_def_id_to_hir_id(local_def_id), + span.ctxt().outer_expn_data().call_site, + "you are deriving `PartialEq` and can implement `Eq`", + |diag| { + diag.span_suggestion( + span.ctxt().outer_expn_data().call_site, + "consider deriving `Eq` as well", + "PartialEq, Eq", + Applicability::MachineApplicable, + ); + }, + ); + } +} + +fn ty_implements_eq_trait<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, eq_trait_id: DefId) -> bool { + tcx.non_blanket_impls_for_ty(eq_trait_id, ty).next().is_some() +} + +/// Creates the `ParamEnv` used for the given type's derived `Eq` impl. +fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ty::TypingEnv<'_> { + // Initial map from generic index to param def. + // Vec<(param_def, needs_eq)> + let mut params = tcx + .generics_of(did) + .own_params + .iter() + .map(|p| (p, matches!(p.kind, GenericParamDefKind::Type { .. }))) + .collect::>(); + + let ty_predicates = tcx.predicates_of(did).predicates; + for (p, _) in ty_predicates { + if let ClauseKind::Trait(p) = p.kind().skip_binder() + && p.trait_ref.def_id == eq_trait_id + && let ty::Param(self_ty) = p.trait_ref.self_ty().kind() + { + // Flag types which already have an `Eq` bound. + params[self_ty.index as usize].1 = false; + } + } + + let param_env = ParamEnv::new(tcx.mk_clauses_from_iter(ty_predicates.iter().map(|&(p, _)| p).chain( + params.iter().filter(|&&(_, needs_eq)| needs_eq).map(|&(param, _)| { + ClauseKind::Trait(TraitPredicate { + trait_ref: ty::TraitRef::new(tcx, eq_trait_id, [tcx.mk_param_from_def(param)]), + polarity: ty::PredicatePolarity::Positive, + }) + .upcast(tcx) + }), + ))); + ty::TypingEnv { + typing_mode: ty::TypingMode::non_body_analysis(), + param_env, + } +} diff --git a/clippy_lints/src/derive/derived_hash_with_manual_eq.rs b/clippy_lints/src/derive/derived_hash_with_manual_eq.rs new file mode 100644 index 0000000000000..6f36a58025a2b --- /dev/null +++ b/clippy_lints/src/derive/derived_hash_with_manual_eq.rs @@ -0,0 +1,49 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty::Ty; +use rustc_span::{Span, sym}; + +use super::DERIVED_HASH_WITH_MANUAL_EQ; + +/// Implementation of the `DERIVED_HASH_WITH_MANUAL_EQ` lint. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + span: Span, + trait_ref: &hir::TraitRef<'_>, + ty: Ty<'tcx>, + hash_is_automatically_derived: bool, +) { + if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait() + && let Some(def_id) = trait_ref.trait_def_id() + && cx.tcx.is_diagnostic_item(sym::Hash, def_id) + { + // Look for the PartialEq implementations for `ty` + cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| { + let peq_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id); + + if !hash_is_automatically_derived || peq_is_automatically_derived { + return; + } + + let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation"); + + // Only care about `impl PartialEq for Foo` + // For `impl PartialEq for A, input_types is [A, B] + if trait_ref.instantiate_identity().args.type_at(1) == ty { + span_lint_and_then( + cx, + DERIVED_HASH_WITH_MANUAL_EQ, + span, + "you are deriving `Hash` but have implemented `PartialEq` explicitly", + |diag| { + if let Some(local_def_id) = impl_id.as_local() { + let hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id); + diag.span_note(cx.tcx.hir_span(hir_id), "`PartialEq` implemented here"); + } + }, + ); + } + }); + } +} diff --git a/clippy_lints/src/derive/expl_impl_clone_on_copy.rs b/clippy_lints/src/derive/expl_impl_clone_on_copy.rs new file mode 100644 index 0000000000000..6b97b4bd6b4d3 --- /dev/null +++ b/clippy_lints/src/derive/expl_impl_clone_on_copy.rs @@ -0,0 +1,65 @@ +use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::ty::{implements_trait, is_copy}; +use rustc_hir::{self as hir, Item}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, GenericArgKind, Ty}; + +use super::EXPL_IMPL_CLONE_ON_COPY; + +/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint. +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { + let clone_id = match cx.tcx.lang_items().clone_trait() { + Some(id) if trait_ref.trait_def_id() == Some(id) => id, + _ => return, + }; + let Some(copy_id) = cx.tcx.lang_items().copy_trait() else { + return; + }; + let (ty_adt, ty_subs) = match *ty.kind() { + // Unions can't derive clone. + ty::Adt(adt, subs) if !adt.is_union() => (adt, subs), + _ => return, + }; + // If the current self type doesn't implement Copy (due to generic constraints), search to see if + // there's a Copy impl for any instance of the adt. + if !is_copy(cx, ty) { + if ty_subs.non_erasable_generics().next().is_some() { + let has_copy_impl = cx.tcx.local_trait_impls(copy_id).iter().any(|&id| { + matches!(cx.tcx.type_of(id).instantiate_identity().kind(), ty::Adt(adt, _) + if ty_adt.did() == adt.did()) + }); + if !has_copy_impl { + return; + } + } else { + return; + } + } + // Derive constrains all generic types to requiring Clone. Check if any type is not constrained for + // this impl. + if ty_subs.types().any(|ty| !implements_trait(cx, ty, clone_id, &[])) { + return; + } + // `#[repr(packed)]` structs with type/const parameters can't derive `Clone`. + // https://github.com/rust-lang/rust-clippy/issues/10188 + if ty_adt.repr().packed() + && ty_subs + .iter() + .any(|arg| matches!(arg.kind(), GenericArgKind::Type(_) | GenericArgKind::Const(_))) + { + return; + } + // The presence of `unsafe` fields prevents deriving `Clone` automatically + if ty_adt.all_fields().any(|f| f.safety.is_unsafe()) { + return; + } + + span_lint_and_note( + cx, + EXPL_IMPL_CLONE_ON_COPY, + item.span, + "you are implementing `Clone` explicitly on a `Copy` type", + Some(item.span), + "consider deriving `Clone` or removing `Copy`", + ); +} diff --git a/clippy_lints/src/derive/mod.rs b/clippy_lints/src/derive/mod.rs new file mode 100644 index 0000000000000..1d63394ce37d4 --- /dev/null +++ b/clippy_lints/src/derive/mod.rs @@ -0,0 +1,215 @@ +use rustc_hir::{Impl, Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; + +mod derive_ord_xor_partial_ord; +mod derive_partial_eq_without_eq; +mod derived_hash_with_manual_eq; +mod expl_impl_clone_on_copy; +mod unsafe_derive_deserialize; + +declare_clippy_lint! { + /// ### What it does + /// Lints against manual `PartialEq` implementations for types with a derived `Hash` + /// implementation. + /// + /// ### Why is this bad? + /// The implementation of these traits must agree (for + /// example for use with `HashMap`) so it’s probably a bad idea to use a + /// default-generated `Hash` implementation with an explicitly defined + /// `PartialEq`. In particular, the following must hold for any type: + /// + /// ```text + /// k1 == k2 ⇒ hash(k1) == hash(k2) + /// ``` + /// + /// ### Example + /// ```ignore + /// #[derive(Hash)] + /// struct Foo; + /// + /// impl PartialEq for Foo { + /// ... + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub DERIVED_HASH_WITH_MANUAL_EQ, + correctness, + "deriving `Hash` but implementing `PartialEq` explicitly" +} + +declare_clippy_lint! { + /// ### What it does + /// Lints against manual `PartialOrd` and `Ord` implementations for types with a derived `Ord` + /// or `PartialOrd` implementation. + /// + /// ### Why is this bad? + /// The implementation of these traits must agree (for + /// example for use with `sort`) so it’s probably a bad idea to use a + /// default-generated `Ord` implementation with an explicitly defined + /// `PartialOrd`. In particular, the following must hold for any type + /// implementing `Ord`: + /// + /// ```text + /// k1.cmp(&k2) == k1.partial_cmp(&k2).unwrap() + /// ``` + /// + /// ### Example + /// ```rust,ignore + /// #[derive(Ord, PartialEq, Eq)] + /// struct Foo; + /// + /// impl PartialOrd for Foo { + /// ... + /// } + /// ``` + /// Use instead: + /// ```rust,ignore + /// #[derive(PartialEq, Eq)] + /// struct Foo; + /// + /// impl PartialOrd for Foo { + /// fn partial_cmp(&self, other: &Foo) -> Option { + /// Some(self.cmp(other)) + /// } + /// } + /// + /// impl Ord for Foo { + /// ... + /// } + /// ``` + /// or, if you don't need a custom ordering: + /// ```rust,ignore + /// #[derive(Ord, PartialOrd, PartialEq, Eq)] + /// struct Foo; + /// ``` + #[clippy::version = "1.47.0"] + pub DERIVE_ORD_XOR_PARTIAL_ORD, + correctness, + "deriving `Ord` but implementing `PartialOrd` explicitly" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for explicit `Clone` implementations for `Copy` + /// types. + /// + /// ### Why is this bad? + /// To avoid surprising behavior, these traits should + /// agree and the behavior of `Copy` cannot be overridden. In almost all + /// situations a `Copy` type should have a `Clone` implementation that does + /// nothing more than copy the object, which is what `#[derive(Copy, Clone)]` + /// gets you. + /// + /// ### Example + /// ```rust,ignore + /// #[derive(Copy)] + /// struct Foo; + /// + /// impl Clone for Foo { + /// // .. + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub EXPL_IMPL_CLONE_ON_COPY, + pedantic, + "implementing `Clone` explicitly on `Copy` types" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for deriving `serde::Deserialize` on a type that + /// has methods using `unsafe`. + /// + /// ### Why is this bad? + /// Deriving `serde::Deserialize` will create a constructor + /// that may violate invariants held by another constructor. + /// + /// ### Example + /// ```rust,ignore + /// use serde::Deserialize; + /// + /// #[derive(Deserialize)] + /// pub struct Foo { + /// // .. + /// } + /// + /// impl Foo { + /// pub fn new() -> Self { + /// // setup here .. + /// } + /// + /// pub unsafe fn parts() -> (&str, &str) { + /// // assumes invariants hold + /// } + /// } + /// ``` + #[clippy::version = "1.45.0"] + pub UNSAFE_DERIVE_DESERIALIZE, + pedantic, + "deriving `serde::Deserialize` on a type that has methods using `unsafe`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for types that derive `PartialEq` and could implement `Eq`. + /// + /// ### Why is this bad? + /// If a type `T` derives `PartialEq` and all of its members implement `Eq`, + /// then `T` can always implement `Eq`. Implementing `Eq` allows `T` to be used + /// in APIs that require `Eq` types. It also allows structs containing `T` to derive + /// `Eq` themselves. + /// + /// ### Example + /// ```no_run + /// #[derive(PartialEq)] + /// struct Foo { + /// i_am_eq: i32, + /// i_am_eq_too: Vec, + /// } + /// ``` + /// Use instead: + /// ```no_run + /// #[derive(PartialEq, Eq)] + /// struct Foo { + /// i_am_eq: i32, + /// i_am_eq_too: Vec, + /// } + /// ``` + #[clippy::version = "1.63.0"] + pub DERIVE_PARTIAL_EQ_WITHOUT_EQ, + nursery, + "deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`" +} + +declare_lint_pass!(Derive => [ + EXPL_IMPL_CLONE_ON_COPY, + DERIVED_HASH_WITH_MANUAL_EQ, + DERIVE_ORD_XOR_PARTIAL_ORD, + UNSAFE_DERIVE_DESERIALIZE, + DERIVE_PARTIAL_EQ_WITHOUT_EQ +]); + +impl<'tcx> LateLintPass<'tcx> for Derive { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + if let ItemKind::Impl(Impl { + of_trait: Some(of_trait), + .. + }) = item.kind + { + let trait_ref = &of_trait.trait_ref; + let ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); + let is_automatically_derived = cx.tcx.is_automatically_derived(item.owner_id.to_def_id()); + + derived_hash_with_manual_eq::check(cx, item.span, trait_ref, ty, is_automatically_derived); + derive_ord_xor_partial_ord::check(cx, item.span, trait_ref, ty, is_automatically_derived); + + if is_automatically_derived { + unsafe_derive_deserialize::check(cx, item, trait_ref, ty); + derive_partial_eq_without_eq::check(cx, item.span, trait_ref, ty); + } else { + expl_impl_clone_on_copy::check(cx, item, trait_ref, ty); + } + } + } +} diff --git a/clippy_lints/src/derive/unsafe_derive_deserialize.rs b/clippy_lints/src/derive/unsafe_derive_deserialize.rs new file mode 100644 index 0000000000000..c391e7b62289f --- /dev/null +++ b/clippy_lints/src/derive/unsafe_derive_deserialize.rs @@ -0,0 +1,93 @@ +use std::ops::ControlFlow; + +use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::{is_lint_allowed, paths}; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn, walk_item}; +use rustc_hir::{self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Item, UnsafeSource}; +use rustc_lint::LateContext; +use rustc_middle::hir::nested_filter; +use rustc_middle::ty::{self, Ty}; +use rustc_span::{Span, sym}; + +use super::UNSAFE_DERIVE_DESERIALIZE; + +/// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint. +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { + fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool { + let mut visitor = UnsafeVisitor { cx }; + walk_item(&mut visitor, item).is_break() + } + + if let Some(trait_def_id) = trait_ref.trait_def_id() + && paths::SERDE_DESERIALIZE.matches(cx, trait_def_id) + && let ty::Adt(def, _) = ty.kind() + && let Some(local_def_id) = def.did().as_local() + && let adt_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id) + && !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id) + && cx + .tcx + .inherent_impls(def.did()) + .iter() + .map(|imp_did| cx.tcx.hir_expect_item(imp_did.expect_local())) + .any(|imp| has_unsafe(cx, imp)) + { + span_lint_hir_and_then( + cx, + UNSAFE_DERIVE_DESERIALIZE, + adt_hir_id, + item.span, + "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`", + |diag| { + diag.help( + "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html", + ); + }, + ); + } +} + +struct UnsafeVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, +} + +impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { + type Result = ControlFlow<()>; + type NestedFilter = nested_filter::All; + + fn visit_fn( + &mut self, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body_id: BodyId, + _: Span, + id: LocalDefId, + ) -> Self::Result { + if let Some(header) = kind.header() + && header.is_unsafe() + { + ControlFlow::Break(()) + } else { + walk_fn(self, kind, decl, body_id, id) + } + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> Self::Result { + if let ExprKind::Block(block, _) = expr.kind + && block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) + && block + .span + .source_callee() + .and_then(|expr| expr.macro_def_id) + .is_none_or(|did| !self.cx.tcx.is_diagnostic_item(sym::pin_macro, did)) + { + return ControlFlow::Break(()); + } + + walk_expr(self, expr) + } + + fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { + self.cx.tcx + } +} diff --git a/clippy_lints/src/disallowed_macros.rs b/clippy_lints/src/disallowed_macros.rs index 49cd2671dc003..1c9c971730f6e 100644 --- a/clippy_lints/src/disallowed_macros.rs +++ b/clippy_lints/src/disallowed_macros.rs @@ -1,4 +1,3 @@ - use clippy_config::Conf; use clippy_config::types::{DisallowedPath, create_disallowed_map}; use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; @@ -8,7 +7,8 @@ use rustc_data_structures::fx::FxHashSet; use rustc_hir::def::DefKind; use rustc_hir::def_id::DefIdMap; use rustc_hir::{ - AmbigArg, Expr, ExprKind, ForeignItem, HirId, ImplItem, ImplItemImplKind, Item, ItemKind, OwnerId, Pat, Path, Stmt, TraitItem, Ty, + AmbigArg, Expr, ExprKind, ForeignItem, HirId, ImplItem, ImplItemImplKind, Item, ItemKind, OwnerId, Pat, Path, Stmt, + TraitItem, Ty, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::TyCtxt; diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 2da1c2bad1174..752f39b4e6dc1 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -197,6 +197,18 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx // in a type which is `'static`. // For now ignore all callee types which reference a type parameter. && !generic_args.types().any(|t| matches!(t.kind(), ty::Param(_))) + // Rule out `AsyncFn*`s, because while they can be called as `|x| f(x)`, + // they can't be passed directly into a place expecting an `Fn*` (#13892) + && let Ok((closure_kind, _)) = cx + .tcx + .infer_ctxt() + .build(cx.typing_mode()) + .err_ctxt() + .type_implements_fn_trait( + cx.param_env, + Binder::bind_with_vars(callee_ty_adjusted, List::empty()), + ty::PredicatePolarity::Positive, + ) { span_lint_hir_and_then( cx, @@ -213,19 +225,10 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx // 'cuz currently nothing changes after deleting this check. local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr) }) { - match cx - .tcx - .infer_ctxt() - .build(cx.typing_mode()) - .err_ctxt() - .type_implements_fn_trait( - cx.param_env, - Binder::bind_with_vars(callee_ty_adjusted, List::empty()), - ty::PredicatePolarity::Positive, - ) { + match closure_kind { // Mutable closure is used after current expr; we cannot consume it. - Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"), - Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => { + ClosureKind::FnMut => snippet = format!("&mut {snippet}"), + ClosureKind::Fn if !callee_ty_raw.is_ref() => { snippet = format!("&{snippet}"); }, _ => (), diff --git a/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs b/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs index 906bbd006d463..72f8795304055 100644 --- a/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs +++ b/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs @@ -4,7 +4,7 @@ use rustc_middle::ty; use rustc_span::def_id::LocalDefId; use clippy_utils::diagnostics::span_lint; -use clippy_utils::ty::type_is_unsafe_function; +use clippy_utils::ty::is_unsafe_fn; use clippy_utils::visitors::for_each_expr; use clippy_utils::{iter_input_pats, path_to_local}; @@ -51,7 +51,7 @@ fn check_raw_ptr<'tcx>( let typeck = cx.tcx.typeck_body(body.id()); let _: Option = for_each_expr(cx, body.value, |e| { match e.kind { - hir::ExprKind::Call(f, args) if type_is_unsafe_function(cx, typeck.expr_ty(f)) => { + hir::ExprKind::Call(f, args) if is_unsafe_fn(cx, typeck.expr_ty(f)) => { for arg in args { check_arg(cx, &raw_ptrs, arg); } diff --git a/clippy_lints/src/functions/ref_option.rs b/clippy_lints/src/functions/ref_option.rs index 106202d00d403..5dc1b7269b761 100644 --- a/clippy_lints/src/functions/ref_option.rs +++ b/clippy_lints/src/functions/ref_option.rs @@ -1,23 +1,20 @@ use crate::functions::REF_OPTION; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::is_trait_impl_item; use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::ty::option_arg_ty; +use clippy_utils::{is_from_proc_macro, is_trait_impl_item}; use rustc_errors::Applicability; -use rustc_hir as hir; use rustc_hir::intravisit::FnKind; -use rustc_hir::{FnDecl, HirId}; -use rustc_lint::LateContext; -use rustc_middle::ty::{self, GenericArgKind, Mutability, Ty}; +use rustc_hir::{self as hir, FnDecl, HirId}; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::ty::{self, Mutability, Ty}; +use rustc_span::Span; use rustc_span::def_id::LocalDefId; -use rustc_span::{Span, sym}; -fn check_ty<'a>(cx: &LateContext<'a>, param: &rustc_hir::Ty<'a>, param_ty: Ty<'a>, fixes: &mut Vec<(Span, String)>) { - if let ty::Ref(_, opt_ty, Mutability::Not) = param_ty.kind() - && is_type_diagnostic_item(cx, *opt_ty, sym::Option) - && let ty::Adt(_, opt_gen_args) = opt_ty.kind() - && let [gen_arg] = opt_gen_args.as_slice() - && let GenericArgKind::Type(gen_ty) = gen_arg.kind() +fn check_ty<'a>(cx: &LateContext<'a>, param: &hir::Ty<'a>, param_ty: Ty<'a>, fixes: &mut Vec<(Span, String)>) { + if !param.span.in_external_macro(cx.sess().source_map()) + && let ty::Ref(_, opt_ty, Mutability::Not) = param_ty.kind() + && let Some(gen_ty) = option_arg_ty(cx, *opt_ty) && !gen_ty.is_ref() // Need to gen the original spans, so first parsing mid, and hir parsing afterward && let hir::TyKind::Ref(lifetime, hir::MutTy { ty, .. }) = param.kind @@ -27,6 +24,7 @@ fn check_ty<'a>(cx: &LateContext<'a>, param: &rustc_hir::Ty<'a>, param_ty: Ty<'a args: [hir::GenericArg::Type(opt_ty)], .. }) = last.args + && !is_from_proc_macro(cx, param) { let lifetime = snippet(cx, lifetime.ident.span, ".."); fixes.push(( @@ -67,21 +65,24 @@ fn check_fn_sig<'a>(cx: &LateContext<'a>, decl: &FnDecl<'a>, span: Span, sig: ty #[allow(clippy::too_many_arguments)] pub(crate) fn check_fn<'a>( cx: &LateContext<'a>, - kind: FnKind<'_>, + kind: FnKind<'a>, decl: &FnDecl<'a>, span: Span, hir_id: HirId, def_id: LocalDefId, - body: &hir::Body<'_>, + body: &hir::Body<'a>, avoid_breaking_exported_api: bool, ) { if avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) { return; } + if span.in_external_macro(cx.sess().source_map()) { + return; + } if let FnKind::Closure = kind { // Compute the span of the closure parameters + return type if set - let span = if let hir::FnRetTy::Return(out_ty) = &decl.output { + let inputs_output_span = if let hir::FnRetTy::Return(out_ty) = &decl.output { if decl.inputs.is_empty() { out_ty.span } else { @@ -100,9 +101,18 @@ pub(crate) fn check_fn<'a>( }; let sig = args.as_closure().sig().skip_binder(); - check_fn_sig(cx, decl, span, sig); + if is_from_proc_macro(cx, &(&kind, body, hir_id, span)) { + return; + } + + check_fn_sig(cx, decl, inputs_output_span, sig); } else if !is_trait_impl_item(cx, hir_id) { let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder(); + + if is_from_proc_macro(cx, &(&kind, body, hir_id, span)) { + return; + } + check_fn_sig(cx, decl, span, sig); } } @@ -112,8 +122,10 @@ pub(super) fn check_trait_item<'a>( trait_item: &hir::TraitItem<'a>, avoid_breaking_exported_api: bool, ) { - if let hir::TraitItemKind::Fn(ref sig, _) = trait_item.kind + if !trait_item.span.in_external_macro(cx.sess().source_map()) + && let hir::TraitItemKind::Fn(ref sig, _) = trait_item.kind && !(avoid_breaking_exported_api && cx.effective_visibilities.is_exported(trait_item.owner_id.def_id)) + && !is_from_proc_macro(cx, trait_item) { let def_id = trait_item.owner_id.def_id; let ty_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder(); diff --git a/clippy_lints/src/future_not_send.rs b/clippy_lints/src/future_not_send.rs index 3ccfa51ab70b7..596047977a9b1 100644 --- a/clippy_lints/src/future_not_send.rs +++ b/clippy_lints/src/future_not_send.rs @@ -78,66 +78,65 @@ impl<'tcx> LateLintPass<'tcx> for FutureNotSend { if let ty::Alias(ty::Opaque, AliasTy { def_id, args, .. }) = *ret_ty.kind() && let Some(future_trait) = cx.tcx.lang_items().future_trait() && let Some(send_trait) = cx.tcx.get_diagnostic_item(sym::Send) + && let preds = cx.tcx.explicit_item_self_bounds(def_id) + // If is a Future + && preds + .iter_instantiated_copied(cx.tcx, args) + .filter_map(|(p, _)| p.as_trait_clause()) + .any(|trait_pred| trait_pred.skip_binder().trait_ref.def_id == future_trait) { - let preds = cx.tcx.explicit_item_self_bounds(def_id); - let is_future = preds.iter_instantiated_copied(cx.tcx, args).any(|(p, _)| { - p.as_trait_clause() - .is_some_and(|trait_pred| trait_pred.skip_binder().trait_ref.def_id == future_trait) - }); - if is_future { - let span = decl.output.span(); - let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode()); - let ocx = ObligationCtxt::new_with_diagnostics(&infcx); - let cause = traits::ObligationCause::misc(span, fn_def_id); - ocx.register_bound(cause, cx.param_env, ret_ty, send_trait); - let send_errors = ocx.select_all_or_error(); + let span = decl.output.span(); + let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode()); + let ocx = ObligationCtxt::new_with_diagnostics(&infcx); + let cause = traits::ObligationCause::misc(span, fn_def_id); + ocx.register_bound(cause, cx.param_env, ret_ty, send_trait); + let send_errors = ocx.select_all_or_error(); - // Allow errors that try to prove `Send` for types that "mention" a generic parameter at the "top - // level". - // For example, allow errors that `T: Send` can't be proven, but reject `Rc: Send` errors, - // which is always unconditionally `!Send` for any possible type `T`. - // - // We also allow associated type projections if the self type is either itself a projection or a - // type parameter. - // This is to prevent emitting warnings for e.g. holding a `::Output` across await - // points, where `Fut` is a type parameter. + // Allow errors that try to prove `Send` for types that "mention" a generic parameter at the "top + // level". + // For example, allow errors that `T: Send` can't be proven, but reject `Rc: Send` errors, + // which is always unconditionally `!Send` for any possible type `T`. + // + // We also allow associated type projections if the self type is either itself a projection or a + // type parameter. + // This is to prevent emitting warnings for e.g. holding a `::Output` across await + // points, where `Fut` is a type parameter. - let is_send = send_errors.iter().all(|err| { - err.obligation - .predicate - .as_trait_clause() - .map(Binder::skip_binder) - .is_some_and(|pred| { - pred.def_id() == send_trait - && pred.self_ty().has_param() - && TyParamAtTopLevelVisitor.visit_ty(pred.self_ty()) == ControlFlow::Break(true) - }) - }); + let is_send = send_errors.iter().all(|err| { + err.obligation + .predicate + .as_trait_clause() + .map(Binder::skip_binder) + .is_some_and(|pred| { + pred.def_id() == send_trait + && pred.self_ty().has_param() + && TyParamAtTopLevelVisitor.visit_ty(pred.self_ty()) == ControlFlow::Break(true) + }) + }); - if !is_send { - span_lint_and_then( - cx, - FUTURE_NOT_SEND, - span, - "future cannot be sent between threads safely", - |db| { - for FulfillmentError { obligation, .. } in send_errors { - infcx - .err_ctxt() - .maybe_note_obligation_cause_for_async_await(db, &obligation); - if let PredicateKind::Clause(ClauseKind::Trait(trait_pred)) = - obligation.predicate.kind().skip_binder() - { - db.note(format!( - "`{}` doesn't implement `{}`", - trait_pred.self_ty(), - trait_pred.trait_ref.print_only_trait_path(), - )); - } + if !is_send { + span_lint_and_then( + cx, + FUTURE_NOT_SEND, + span, + "future cannot be sent between threads safely", + |db| { + for FulfillmentError { obligation, .. } in send_errors { + infcx + .err_ctxt() + .maybe_note_obligation_cause_for_async_await(db, &obligation); + if let PredicateKind::Clause(ClauseKind::Trait(trait_pred)) = + obligation.predicate.kind().skip_binder() + { + db.note(format!( + "`{}` doesn't implement `{}`", + trait_pred.self_ty(), + trait_pred.trait_ref.print_only_trait_path(), + )); } - }, - ); - } + } + }, + ); } } } diff --git a/clippy_lints/src/invalid_upcast_comparisons.rs b/clippy_lints/src/invalid_upcast_comparisons.rs index b0ecc5d52ddb8..1666e8e5ae324 100644 --- a/clippy_lints/src/invalid_upcast_comparisons.rs +++ b/clippy_lints/src/invalid_upcast_comparisons.rs @@ -1,3 +1,4 @@ +use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::layout::LayoutOf; @@ -9,7 +10,7 @@ use clippy_utils::comparisons; use clippy_utils::comparisons::Rel; use clippy_utils::consts::{ConstEvalCtxt, FullInt}; use clippy_utils::diagnostics::span_lint; -use clippy_utils::source::snippet; +use clippy_utils::source::snippet_with_context; declare_clippy_lint! { /// ### What it does @@ -69,13 +70,21 @@ fn numeric_cast_precast_bounds(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option< fn err_upcast_comparison(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, always: bool) { if let ExprKind::Cast(cast_val, _) = expr.kind { + let mut applicability = Applicability::MachineApplicable; + let (cast_val_snip, _) = snippet_with_context( + cx, + cast_val.span, + expr.span.ctxt(), + "the expression", + &mut applicability, + ); span_lint( cx, INVALID_UPCAST_COMPARISONS, span, format!( "because of the numeric bounds on `{}` prior to casting, this expression is always {}", - snippet(cx, cast_val.span, "the expression"), + cast_val_snip, if always { "true" } else { "false" }, ), ); diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index f44a5fdf715e5..28a0fbc051158 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) 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())), + 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, diff --git a/clippy_lints/src/let_with_type_underscore.rs b/clippy_lints/src/let_with_type_underscore.rs index 5b0f95ffc377f..24a4c321bdabd 100644 --- a/clippy_lints/src/let_with_type_underscore.rs +++ b/clippy_lints/src/let_with_type_underscore.rs @@ -1,9 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_from_proc_macro; use clippy_utils::source::{IntoSpan, SpanRangeExt}; +use rustc_ast::{Local, TyKind}; use rustc_errors::Applicability; -use rustc_hir::{LetStmt, TyKind}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use rustc_session::declare_lint_pass; declare_clippy_lint! { @@ -26,14 +26,14 @@ declare_clippy_lint! { } declare_lint_pass!(UnderscoreTyped => [LET_WITH_TYPE_UNDERSCORE]); -impl<'tcx> LateLintPass<'tcx> for UnderscoreTyped { - fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) { - if let Some(ty) = local.ty // Ensure that it has a type defined - && let TyKind::Infer(()) = &ty.kind // that type is '_' +impl EarlyLintPass for UnderscoreTyped { + fn check_local(&mut self, cx: &EarlyContext<'_>, local: &Local) { + if let Some(ty) = &local.ty // Ensure that it has a type defined + && let TyKind::Infer = ty.kind // that type is '_' && local.span.eq_ctxt(ty.span) - && let sm = cx.tcx.sess.source_map() + && let sm = cx.sess().source_map() && !local.span.in_external_macro(sm) - && !is_from_proc_macro(cx, ty) + && !is_from_proc_macro(cx, &**ty) { let span_to_remove = sm .span_extend_to_prev_char_before(ty.span, ':', true) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index a89cf3fdc1eee..c56fa257b0689 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -302,7 +302,6 @@ mod permissions_set_readonly_false; mod pointers_in_nomem_asm_block; mod precedence; mod ptr; -mod ptr_offset_with_cast; mod pub_underscore_fields; mod pub_use; mod question_mark; @@ -360,6 +359,7 @@ mod temporary_assignment; mod tests_outside_test_module; mod to_digit_is_some; mod to_string_trait_impl; +mod toplevel_ref_arg; mod trailing_empty_array; mod trait_bounds; mod transmute; @@ -592,7 +592,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(unwrap::Unwrap)); store.register_late_pass(move |_| Box::new(indexing_slicing::IndexingSlicing::new(conf))); store.register_late_pass(move |tcx| Box::new(non_copy_const::NonCopyConst::new(tcx, conf))); - store.register_late_pass(|_| Box::new(ptr_offset_with_cast::PtrOffsetWithCast)); store.register_late_pass(|_| Box::new(redundant_clone::RedundantClone)); store.register_late_pass(|_| Box::new(slow_vector_initialization::SlowVectorInit)); store.register_late_pass(move |_| Box::new(unnecessary_wraps::UnnecessaryWraps::new(conf))); @@ -744,7 +743,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(missing_assert_message::MissingAssertMessage)); store.register_late_pass(|_| Box::new(needless_maybe_sized::NeedlessMaybeSized)); store.register_late_pass(|_| Box::new(redundant_async_block::RedundantAsyncBlock)); - store.register_late_pass(|_| Box::new(let_with_type_underscore::UnderscoreTyped)); + store.register_early_pass(|| Box::new(let_with_type_underscore::UnderscoreTyped)); store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(conf))); store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct)); store.register_late_pass(move |_| Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new(conf))); @@ -831,5 +830,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf))); store.register_late_pass(|_| Box::new(infallible_try_from::InfallibleTryFrom)); store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny)); + store.register_late_pass(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 149ae5e710c1f..d8b186b6787d1 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -745,7 +745,7 @@ fn report_elidable_impl_lifetimes<'tcx>( impl_: &'tcx Impl<'_>, map: &FxIndexMap>, ) { - let single_usages = map + let (elidable_lts, usages): (Vec<_>, Vec<_>) = map .iter() .filter_map(|(def_id, usages)| { if let [ @@ -762,14 +762,12 @@ fn report_elidable_impl_lifetimes<'tcx>( None } }) - .collect::>(); + .unzip(); - if single_usages.is_empty() { + if elidable_lts.is_empty() { return; } - let (elidable_lts, usages): (Vec<_>, Vec<_>) = single_usages.into_iter().unzip(); - report_elidable_lifetimes(cx, impl_.generics, &elidable_lts, &usages, true); } @@ -795,9 +793,7 @@ fn report_elidable_lifetimes( // In principle, the result of the call to `Node::ident` could be `unwrap`ped, as `DefId` should refer to a // `Node::GenericParam`. .filter_map(|&def_id| cx.tcx.hir_node_by_def_id(def_id).ident()) - .map(|ident| ident.to_string()) - .collect::>() - .join(", "); + .format(", "); let elidable_usages: Vec = usages .iter() @@ -860,36 +856,89 @@ fn elision_suggestions( .filter(|param| !param.is_elided_lifetime() && !param.is_impl_trait()) .collect::>(); - let mut suggestions = if elidable_lts.len() == explicit_params.len() { + if !elidable_lts + .iter() + .all(|lt| explicit_params.iter().any(|param| param.def_id == *lt)) + { + return None; + } + + let mut suggestions = if elidable_lts.is_empty() { + vec![] + } else if elidable_lts.len() == explicit_params.len() { // if all the params are elided remove the whole generic block // // fn x<'a>() {} // ^^^^ vec![(generics.span, String::new())] } else { - elidable_lts - .iter() - .map(|&id| { - let pos = explicit_params.iter().position(|param| param.def_id == id)?; - let param = explicit_params.get(pos)?; - - let span = if let Some(next) = explicit_params.get(pos + 1) { - // fn x<'prev, 'a, 'next>() {} - // ^^^^ - param.span.until(next.span) + match &explicit_params[..] { + // no params, nothing to elide + [] => unreachable!("handled by `elidable_lts.is_empty()`"), + [param] => { + if elidable_lts.contains(¶m.def_id) { + unreachable!("handled by `elidable_lts.len() == explicit_params.len()`") } else { - // `pos` should be at least 1 here, because the param in position 0 would either have a `next` - // param or would have taken the `elidable_lts.len() == explicit_params.len()` branch. - let prev = explicit_params.get(pos - 1)?; - - // fn x<'prev, 'a>() {} - // ^^^^ - param.span.with_lo(prev.span.hi()) + unreachable!("handled by `elidable_lts.is_empty()`") + } + }, + [_, _, ..] => { + // Given a list like `<'a, 'b, 'c, 'd, ..>`, + // + // If there is a cluster of elidable lifetimes at the beginning, say `'a` and `'b`, we should + // suggest removing them _and_ the trailing comma. The span for that is `a.span.until(c.span)`: + // <'a, 'b, 'c, 'd, ..> => <'a, 'b, 'c, 'd, ..> + // ^^ ^^ ^^^^^^^^ + // + // And since we know that `'c` isn't elidable--otherwise it would've been in the cluster--we can go + // over all the lifetimes after it, and for each elidable one, add a suggestion spanning the + // lifetime itself and the comma before, because each individual suggestion is guaranteed to leave + // the list valid: + // <.., 'c, 'd, 'e, 'f, 'g, ..> => <.., 'c, 'd, 'e, 'f, 'g, ..> + // ^^ ^^ ^^ ^^^^ ^^^^^^^^ + // + // In case there is no such starting cluster, we only need to do the second part of the algorithm: + // <'a, 'b, 'c, 'd, 'e, 'f, 'g, ..> => <'a, 'b , 'c, 'd, 'e, 'f, 'g, ..> + // ^^ ^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^ + + // Split off the starting cluster + // TODO: use `slice::split_once` once stabilized (github.com/rust-lang/rust/issues/112811): + // ``` + // let Some(split) = explicit_params.split_once(|param| !elidable_lts.contains(¶m.def_id)) else { + // // there were no lifetime param that couldn't be elided + // unreachable!("handled by `elidable_lts.len() == explicit_params.len()`") + // }; + // match split { /* .. */ } + // ``` + let Some(split_pos) = explicit_params + .iter() + .position(|param| !elidable_lts.contains(¶m.def_id)) + else { + // there were no lifetime param that couldn't be elided + unreachable!("handled by `elidable_lts.len() == explicit_params.len()`") }; - - Some((span, String::new())) - }) - .collect::>>()? + let split = explicit_params + .split_at_checked(split_pos) + .expect("got `split_pos` from `position` on the same Vec"); + + match split { + ([..], []) => unreachable!("handled by `elidable_lts.len() == explicit_params.len()`"), + ([], [_]) => unreachable!("handled by `explicit_params.len() == 1`"), + (cluster, rest @ [rest_first, ..]) => { + // the span for the cluster + (cluster.first().map(|fw| fw.span.until(rest_first.span)).into_iter()) + // the span for the remaining lifetimes (calculations independent of the cluster) + .chain( + rest.array_windows() + .filter(|[_, curr]| elidable_lts.contains(&curr.def_id)) + .map(|[prev, curr]| curr.span.with_lo(prev.span.hi())), + ) + .map(|sp| (sp, String::new())) + .collect() + }, + } + }, + } }; suggestions.extend(usages.iter().map(|&usage| { diff --git a/clippy_lints/src/loops/explicit_iter_loop.rs b/clippy_lints/src/loops/explicit_iter_loop.rs index 6ce7f0b1f0bc9..af475c40586fe 100644 --- a/clippy_lints/src/loops/explicit_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_iter_loop.rs @@ -138,9 +138,9 @@ fn is_ref_iterable<'tcx>( return Some((AdjustKind::None, self_ty)); } - let res_ty = cx - .tcx - .erase_and_anonymize_regions(EarlyBinder::bind(req_res_ty).instantiate(cx.tcx, typeck.node_args(call_expr.hir_id))); + let res_ty = cx.tcx.erase_and_anonymize_regions( + EarlyBinder::bind(req_res_ty).instantiate(cx.tcx, typeck.node_args(call_expr.hir_id)), + ); let mutbl = if let ty::Ref(_, _, mutbl) = *req_self_ty.kind() { Some(mutbl) } else { diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index 2ccff76809767..544c3c34d029c 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -22,7 +22,10 @@ pub(super) fn check<'tcx>( for_loop: Option<&ForLoop<'_>>, ) { match never_loop_block(cx, block, &mut Vec::new(), loop_id) { - NeverLoopResult::Diverging { ref break_spans } => { + NeverLoopResult::Diverging { + ref break_spans, + ref never_spans, + } => { span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| { if let Some(ForLoop { arg: iterator, @@ -34,12 +37,16 @@ pub(super) fn check<'tcx>( { // If the block contains a break or continue, or if the loop has a label, `MachineApplicable` is not // appropriate. - let app = if !contains_any_break_or_continue(block) && label.is_none() { + let mut app = if !contains_any_break_or_continue(block) && label.is_none() { Applicability::MachineApplicable } else { Applicability::Unspecified }; + if !never_spans.is_empty() { + app = Applicability::HasPlaceholders; + } + let mut suggestions = vec![( for_span.with_hi(iterator.span.hi()), for_to_if_let_sugg(cx, iterator, pat), @@ -51,6 +58,13 @@ pub(super) fn check<'tcx>( suggestions, app, ); + + for span in never_spans { + diag.span_help( + *span, + "this code is unreachable. Consider moving the reachable parts out", + ); + } } }); }, @@ -77,13 +91,16 @@ fn contains_any_break_or_continue(block: &Block<'_>) -> bool { /// The first two bits of information are in this enum, and the last part is in the /// `local_labels` variable, which contains a list of `(block_id, reachable)` pairs ordered by /// scope. -#[derive(Clone)] +#[derive(Clone, Debug)] enum NeverLoopResult { /// A continue may occur for the main loop. MayContinueMainLoop, /// We have not encountered any main loop continue, /// but we are diverging (subsequent control flow is not reachable) - Diverging { break_spans: Vec }, + Diverging { + break_spans: Vec, + never_spans: Vec, + }, /// We have not encountered any main loop continue, /// and subsequent control flow is (possibly) reachable Normal, @@ -128,14 +145,18 @@ fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult ( NeverLoopResult::Diverging { break_spans: mut break_spans1, + never_spans: mut never_spans1, }, NeverLoopResult::Diverging { break_spans: mut break_spans2, + never_spans: mut never_spans2, }, ) => { break_spans1.append(&mut break_spans2); + never_spans1.append(&mut never_spans2); NeverLoopResult::Diverging { break_spans: break_spans1, + never_spans: never_spans1, } }, } @@ -207,6 +228,8 @@ fn all_spans_after_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> Vec { } return vec![stmt.span]; + } else if let Node::Block(_) = cx.tcx.parent_hir_node(expr.hir_id) { + return vec![expr.span]; } vec![] @@ -270,10 +293,13 @@ fn never_loop_expr<'tcx>( ExprKind::Match(e, arms, _) => { let e = never_loop_expr(cx, e, local_labels, main_loop_id); combine_seq(e, || { - arms.iter() - .fold(NeverLoopResult::Diverging { break_spans: vec![] }, |a, b| { - combine_branches(a, never_loop_expr(cx, b.body, local_labels, main_loop_id)) - }) + arms.iter().fold( + NeverLoopResult::Diverging { + break_spans: vec![], + never_spans: vec![], + }, + |a, b| combine_branches(a, never_loop_expr(cx, b.body, local_labels, main_loop_id)), + ) }) }, ExprKind::Block(b, _) => { @@ -296,6 +322,7 @@ fn never_loop_expr<'tcx>( } else { NeverLoopResult::Diverging { break_spans: all_spans_after_expr(cx, expr), + never_spans: vec![], } } }, @@ -306,7 +333,10 @@ fn never_loop_expr<'tcx>( combine_seq(first, || { // checks if break targets a block instead of a loop mark_block_as_reachable(expr, local_labels); - NeverLoopResult::Diverging { break_spans: vec![] } + NeverLoopResult::Diverging { + break_spans: vec![], + never_spans: vec![], + } }) }, ExprKind::Break(dest, e) => { @@ -322,11 +352,15 @@ fn never_loop_expr<'tcx>( } else { all_spans_after_expr(cx, expr) }, + never_spans: vec![], } }) }, ExprKind::Become(e) => combine_seq(never_loop_expr(cx, e, local_labels, main_loop_id), || { - NeverLoopResult::Diverging { break_spans: vec![] } + NeverLoopResult::Diverging { + break_spans: vec![], + never_spans: vec![], + } }), ExprKind::InlineAsm(asm) => combine_seq_many(asm.operands.iter().map(|(o, _)| match o { InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => { @@ -356,7 +390,10 @@ fn never_loop_expr<'tcx>( }; let result = combine_seq(result, || { if cx.typeck_results().expr_ty(expr).is_never() { - NeverLoopResult::Diverging { break_spans: vec![] } + NeverLoopResult::Diverging { + break_spans: vec![], + never_spans: all_spans_after_expr(cx, expr), + } } else { NeverLoopResult::Normal } diff --git a/clippy_lints/src/manual_abs_diff.rs b/clippy_lints/src/manual_abs_diff.rs index 288f27db8ca2c..5814b6815a1ea 100644 --- a/clippy_lints/src/manual_abs_diff.rs +++ b/clippy_lints/src/manual_abs_diff.rs @@ -4,8 +4,8 @@ use clippy_utils::higher::If; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::HasSession as _; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{eq_expr_value, peel_blocks, peel_middle_ty_refs, span_contains_comment}; +use clippy_utils::ty::{is_type_diagnostic_item, peel_and_count_ty_refs}; +use clippy_utils::{eq_expr_value, peel_blocks, span_contains_comment}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -107,7 +107,7 @@ impl ManualAbsDiff { |ty| is_type_diagnostic_item(cx, ty, sym::Duration) && self.msrv.meets(cx, msrvs::DURATION_ABS_DIFF); let a_ty = cx.typeck_results().expr_ty(a).peel_refs(); - let (b_ty, b_n_refs) = peel_middle_ty_refs(cx.typeck_results().expr_ty(b)); + let (b_ty, b_n_refs, _) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(b)); (a_ty == b_ty && (is_int(a_ty) || is_duration(a_ty))).then_some((a_ty, b_n_refs)) } diff --git a/clippy_lints/src/manual_option_as_slice.rs b/clippy_lints/src/manual_option_as_slice.rs index 922db174e3d49..b036e78cdedc5 100644 --- a/clippy_lints/src/manual_option_as_slice.rs +++ b/clippy_lints/src/manual_option_as_slice.rs @@ -1,7 +1,7 @@ use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; use clippy_utils::msrvs::Msrv; -use clippy_utils::{is_none_arm, msrvs, peel_hir_expr_refs, sym}; +use clippy_utils::{is_none_pattern, msrvs, peel_hir_expr_refs, sym}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Arm, Expr, ExprKind, LangItem, Pat, PatKind, QPath, is_range_literal}; @@ -60,8 +60,8 @@ impl LateLintPass<'_> for ManualOptionAsSlice { } match expr.kind { ExprKind::Match(scrutinee, [arm1, arm2], _) => { - if is_none_arm(cx, arm2) && check_arms(cx, arm2, arm1) - || is_none_arm(cx, arm1) && check_arms(cx, arm1, arm2) + if is_none_pattern(cx, arm2.pat) && check_arms(cx, arm2, arm1) + || is_none_pattern(cx, arm1.pat) && check_arms(cx, arm1, arm2) { check_as_ref(cx, scrutinee, span, self.msrv); } diff --git a/clippy_lints/src/manual_slice_size_calculation.rs b/clippy_lints/src/manual_slice_size_calculation.rs index 0c09a47c96518..de12fa29d02cd 100644 --- a/clippy_lints/src/manual_slice_size_calculation.rs +++ b/clippy_lints/src/manual_slice_size_calculation.rs @@ -2,6 +2,7 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_context; +use clippy_utils::ty::peel_and_count_ty_refs; use clippy_utils::{expr_or_init, is_in_const_context, std_or_core}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind}; @@ -102,7 +103,7 @@ fn simplify_half<'tcx>( && let ExprKind::MethodCall(method_path, receiver, [], _) = expr1.kind && method_path.ident.name == sym::len && let receiver_ty = cx.typeck_results().expr_ty(receiver) - && let (receiver_ty, refs_count) = clippy_utils::ty::walk_ptrs_ty_depth(receiver_ty) + && let (receiver_ty, refs_count, _) = peel_and_count_ty_refs(receiver_ty) && let ty::Slice(ty1) = receiver_ty.kind() // expr2 is `size_of::()`? && let ExprKind::Call(func, []) = expr2.kind diff --git a/clippy_lints/src/matches/manual_ok_err.rs b/clippy_lints/src/matches/manual_ok_err.rs index edbb556fd9766..a8490d6aa7d82 100644 --- a/clippy_lints/src/matches/manual_ok_err.rs +++ b/clippy_lints/src/matches/manual_ok_err.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::{indent_of, reindent_multiline}; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::{option_arg_ty, peel_mid_ty_refs_is_mutable}; +use clippy_utils::ty::{option_arg_ty, peel_and_count_ty_refs}; use clippy_utils::{get_parent_expr, is_res_lang_ctor, path_res, peel_blocks, span_contains_comment}; use rustc_ast::{BindingMode, Mutability}; use rustc_errors::Applicability; @@ -135,15 +135,11 @@ fn apply_lint(cx: &LateContext<'_>, expr: &Expr<'_>, scrutinee: &Expr<'_>, is_ok let scrut = Sugg::hir_with_applicability(cx, scrutinee, "..", &mut app).maybe_paren(); let scrutinee_ty = cx.typeck_results().expr_ty(scrutinee); - let (_, n_ref, mutability) = peel_mid_ty_refs_is_mutable(scrutinee_ty); - let prefix = if n_ref > 0 { - if mutability == Mutability::Mut { - ".as_mut()" - } else { - ".as_ref()" - } - } else { - "" + let (_, _, mutability) = peel_and_count_ty_refs(scrutinee_ty); + let prefix = match mutability { + Some(Mutability::Mut) => ".as_mut()", + Some(Mutability::Not) => ".as_ref()", + None => "", }; let sugg = format!("{scrut}{prefix}.{method}()"); diff --git a/clippy_lints/src/matches/manual_utils.rs b/clippy_lints/src/matches/manual_utils.rs index dbae71bbb1b06..d4bfdb7e440d8 100644 --- a/clippy_lints/src/matches/manual_utils.rs +++ b/clippy_lints/src/matches/manual_utils.rs @@ -2,7 +2,7 @@ use crate::map_unit_fn::OPTION_MAP_UNIT_FN; use crate::matches::MATCH_AS_REF; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::{is_copy, is_type_diagnostic_item, peel_mid_ty_refs_is_mutable, type_is_unsafe_function}; +use clippy_utils::ty::{is_copy, is_type_diagnostic_item, is_unsafe_fn, peel_and_count_ty_refs}; use clippy_utils::{ CaptureKind, can_move_expr_to_closure, expr_requires_coercion, is_else_clause, is_lint_allowed, is_res_lang_ctor, path_res, path_to_local_id, peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, @@ -30,8 +30,9 @@ pub(super) fn check_with<'tcx, F>( where F: Fn(&LateContext<'tcx>, &'tcx Pat<'_>, &'tcx Expr<'_>, SyntaxContext) -> Option>, { - let (scrutinee_ty, ty_ref_count, ty_mutability) = - peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee)); + let (scrutinee_ty, ty_ref_count, ty_mutability) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(scrutinee)); + let ty_mutability = ty_mutability.unwrap_or(Mutability::Mut); + if !(is_type_diagnostic_item(cx, scrutinee_ty, sym::Option) && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Option)) { @@ -191,7 +192,7 @@ fn can_pass_as_func<'tcx>(cx: &LateContext<'tcx>, binding: HirId, expr: &'tcx Ex ExprKind::Call(func, [arg]) if path_to_local_id(arg, binding) && cx.typeck_results().expr_adjustments(arg).is_empty() - && !type_is_unsafe_function(cx, cx.typeck_results().expr_ty(func).peel_refs()) => + && !is_unsafe_fn(cx, cx.typeck_results().expr_ty(func).peel_refs()) => { Some(func) }, diff --git a/clippy_lints/src/matches/needless_match.rs b/clippy_lints/src/matches/needless_match.rs index b04db03f8d2e7..3a2097c3df261 100644 --- a/clippy_lints/src/matches/needless_match.rs +++ b/clippy_lints/src/matches/needless_match.rs @@ -1,7 +1,7 @@ use super::NEEDLESS_MATCH; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts}; +use clippy_utils::ty::{is_type_diagnostic_item, same_type_modulo_regions}; use clippy_utils::{ SpanlessEq, eq_expr_value, get_parent_expr_for_hir, higher, is_else_clause, is_res_lang_ctor, over, path_res, peel_blocks_with_stmt, @@ -122,7 +122,7 @@ fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_> // Compare match_expr ty with local in `let local = match match_expr {..}` Node::LetStmt(local) => { let results = cx.typeck_results(); - return same_type_and_consts(results.node_type(local.hir_id), results.expr_ty(expr)); + return same_type_modulo_regions(results.node_type(local.hir_id), results.expr_ty(expr)); }, // compare match_expr ty with RetTy in `fn foo() -> RetTy` Node::Item(item) => { @@ -133,7 +133,7 @@ fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_> .instantiate_identity() .output() .skip_binder(); - return same_type_and_consts(output, cx.typeck_results().expr_ty(expr)); + return same_type_modulo_regions(output, cx.typeck_results().expr_ty(expr)); } }, // check the parent expr for this whole block `{ match match_expr {..} }` 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 ae09c2e87d6b7..1130d82ab78f9 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, Some(_)) = pat.kind + && let PatKind::Struct(QPath::Resolved(_, path), fields, Some(dotdot)) = 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() @@ -15,14 +15,18 @@ pub(crate) fn check(cx: &LateContext<'_>, pat: &Pat<'_>) { && fields.len() == def.non_enum_variant().fields.len() && !def.non_enum_variant().is_field_list_non_exhaustive() { - #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then( cx, REST_PAT_IN_FULLY_BOUND_STRUCTS, pat.span, "unnecessary use of `..` pattern in struct binding. All fields were already bound", |diag| { - diag.help("consider removing `..` from this binding"); + diag.span_suggestion_verbose( + dotdot, + "consider removing `..` from this binding", + "", + rustc_errors::Applicability::MachineApplicable, + ); }, ); } diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index bcf079b700703..83939d325794e 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -2,10 +2,8 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{ SpanRangeExt, expr_block, snippet, snippet_block_with_context, snippet_with_applicability, snippet_with_context, }; -use clippy_utils::ty::implements_trait; -use clippy_utils::{ - is_lint_allowed, is_unit_expr, peel_blocks, peel_hir_pat_refs, peel_middle_ty_refs, peel_n_hir_expr_refs, -}; +use clippy_utils::ty::{implements_trait, peel_and_count_ty_refs}; +use clippy_utils::{is_lint_allowed, is_unit_expr, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs}; use core::ops::ControlFlow; use rustc_arena::DroplessArena; use rustc_errors::{Applicability, Diag}; @@ -133,7 +131,7 @@ fn report_single_pattern( let (pat, pat_ref_count) = peel_hir_pat_refs(arm.pat); let (msg, sugg) = if let PatKind::Expr(_) = pat.kind - && let (ty, ty_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(ex)) + && let (ty, ty_ref_count, _) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(ex)) && let Some(spe_trait_id) = cx.tcx.lang_items().structural_peq_trait() && let Some(pe_trait_id) = cx.tcx.lang_items().eq_trait() && (ty.is_integral() diff --git a/clippy_lints/src/methods/implicit_clone.rs b/clippy_lints/src/methods/implicit_clone.rs index 8a976d1b4dc00..0ba84919395cc 100644 --- a/clippy_lints/src/methods/implicit_clone.rs +++ b/clippy_lints/src/methods/implicit_clone.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_context; -use clippy_utils::ty::implements_trait; -use clippy_utils::{is_diag_item_method, is_diag_trait_item, peel_middle_ty_refs, sym}; +use clippy_utils::ty::{implements_trait, peel_and_count_ty_refs}; +use clippy_utils::{is_diag_item_method, is_diag_trait_item, sym}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; @@ -14,7 +14,7 @@ pub fn check(cx: &LateContext<'_>, method_name: Symbol, expr: &hir::Expr<'_>, re && is_clone_like(cx, method_name, method_def_id) && let return_type = cx.typeck_results().expr_ty(expr) && let input_type = cx.typeck_results().expr_ty(recv) - && let (input_type, ref_count) = peel_middle_ty_refs(input_type) + && let (input_type, ref_count, _) = peel_and_count_ty_refs(input_type) && !(ref_count > 0 && is_diag_trait_item(cx, method_def_id, sym::ToOwned)) && let Some(ty_name) = input_type.ty_adt_def().map(|adt_def| cx.tcx.item_name(adt_def.did())) && return_type == input_type diff --git a/clippy_lints/src/methods/inefficient_to_string.rs b/clippy_lints/src/methods/inefficient_to_string.rs index 4ed7de81ea3d0..47195fdd65f5f 100644 --- a/clippy_lints/src/methods/inefficient_to_string.rs +++ b/clippy_lints/src/methods/inefficient_to_string.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_lang_item, walk_ptrs_ty_depth}; +use clippy_utils::ty::{is_type_lang_item, peel_and_count_ty_refs}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; @@ -24,7 +24,7 @@ pub fn check( && let Some(args) = cx.typeck_results().node_args_opt(expr.hir_id) && let arg_ty = cx.typeck_results().expr_ty_adjusted(receiver) && let self_ty = args.type_at(0) - && let (deref_self_ty, deref_count) = walk_ptrs_ty_depth(self_ty) + && let (deref_self_ty, deref_count, _) = peel_and_count_ty_refs(self_ty) && deref_count >= 1 && specializes_tostring(cx, deref_self_ty) { diff --git a/clippy_lints/src/methods/iter_overeager_cloned.rs b/clippy_lints/src/methods/iter_overeager_cloned.rs index f851ebe91f37f..d43dc23a86b23 100644 --- a/clippy_lints/src/methods/iter_overeager_cloned.rs +++ b/clippy_lints/src/methods/iter_overeager_cloned.rs @@ -10,8 +10,7 @@ use rustc_middle::mir::{FakeReadCause, Mutability}; use rustc_middle::ty::{self, BorrowKind}; use rustc_span::{Symbol, sym}; -use super::ITER_OVEREAGER_CLONED; -use crate::redundant_clone::REDUNDANT_CLONE; +use super::{ITER_OVEREAGER_CLONED, REDUNDANT_ITER_CLONED}; #[derive(Clone, Copy)] pub(super) enum Op<'a> { @@ -96,7 +95,7 @@ pub(super) fn check<'tcx>( } let (lint, msg, trailing_clone) = match op { - Op::RmCloned | Op::NeedlessMove(_) => (REDUNDANT_CLONE, "unneeded cloning of iterator items", ""), + Op::RmCloned | Op::NeedlessMove(_) => (REDUNDANT_ITER_CLONED, "unneeded cloning of iterator items", ""), Op::LaterCloned | Op::FixClosure(_, _) => ( ITER_OVEREAGER_CLONED, "unnecessarily eager cloning of iterator items", diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 49ca81dafc280..8679689c8ad4d 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -91,6 +91,7 @@ mod or_fun_call; mod or_then_unwrap; mod path_buf_push_overwrite; mod path_ends_with_ext; +mod ptr_offset_with_cast; mod range_zip_with_len; mod read_line_without_trim; mod readonly_write_lock; @@ -1725,6 +1726,43 @@ declare_clippy_lint! { "Check for offset calculations on raw pointers to zero-sized types" } +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of the `offset` pointer method with a `usize` casted to an + /// `isize`. + /// + /// ### Why is this bad? + /// If we’re always increasing the pointer address, we can avoid the numeric + /// cast by using the `add` method instead. + /// + /// ### Example + /// ```no_run + /// let vec = vec![b'a', b'b', b'c']; + /// let ptr = vec.as_ptr(); + /// let offset = 1_usize; + /// + /// unsafe { + /// ptr.offset(offset as isize); + /// } + /// ``` + /// + /// Could be written: + /// + /// ```no_run + /// let vec = vec![b'a', b'b', b'c']; + /// let ptr = vec.as_ptr(); + /// let offset = 1_usize; + /// + /// unsafe { + /// ptr.add(offset); + /// } + /// ``` + #[clippy::version = "1.30.0"] + pub PTR_OFFSET_WITH_CAST, + complexity, + "unneeded pointer offset cast" +} + declare_clippy_lint! { /// ### What it does /// Checks for `FileType::is_file()`. @@ -4576,6 +4614,31 @@ declare_clippy_lint! { "hardcoded localhost IP address" } +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `Iterator::cloned` where the original value could be used + /// instead. + /// + /// ### Why is this bad? + /// It is not always possible for the compiler to eliminate useless allocations and + /// deallocations generated by redundant `clone()`s. + /// + /// ### Example + /// ```no_run + /// let x = vec![String::new()]; + /// let _ = x.iter().cloned().map(|x| x.len()); + /// ``` + /// Use instead: + /// ```no_run + /// let x = vec![String::new()]; + /// let _ = x.iter().map(|x| x.len()); + /// ``` + #[clippy::version = "1.90.0"] + pub REDUNDANT_ITER_CLONED, + perf, + "detects redundant calls to `Iterator::cloned`" +} + #[expect(clippy::struct_excessive_bools)] pub struct Methods { avoid_breaking_exported_api: bool, @@ -4665,6 +4728,7 @@ impl_lint_pass!(Methods => [ UNINIT_ASSUMED_INIT, MANUAL_SATURATING_ARITHMETIC, ZST_OFFSET, + PTR_OFFSET_WITH_CAST, FILETYPE_IS_FILE, OPTION_AS_REF_DEREF, UNNECESSARY_LAZY_EVALUATIONS, @@ -4755,6 +4819,7 @@ impl_lint_pass!(Methods => [ IO_OTHER_ERROR, SWAP_WITH_TEMPORARY, IP_CONSTANT, + REDUNDANT_ITER_CLONED, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4960,10 +5025,7 @@ impl Methods { // Handle method calls whose receiver and arguments may not come from expansion if let Some((name, recv, args, span, call_span)) = method_call(expr) { match (name, args) { - ( - sym::add | sym::offset | sym::sub | sym::wrapping_offset | sym::wrapping_add | sym::wrapping_sub, - [_arg], - ) => { + (sym::add | sym::sub | sym::wrapping_add | sym::wrapping_sub, [_arg]) => { zst_offset::check(cx, expr, recv); }, (sym::all, [arg]) => { @@ -5334,6 +5396,11 @@ impl Methods { }, _ => iter_nth_zero::check(cx, expr, recv, n_arg), }, + (sym::offset | sym::wrapping_offset, [arg]) => { + zst_offset::check(cx, expr, recv); + + ptr_offset_with_cast::check(cx, name, expr, recv, arg, self.msrv); + }, (sym::ok_or_else, [arg]) => { unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"); }, diff --git a/clippy_lints/src/methods/mut_mutex_lock.rs b/clippy_lints/src/methods/mut_mutex_lock.rs index 4235af882b0c7..9d2c5e6232d65 100644 --- a/clippy_lints/src/methods/mut_mutex_lock.rs +++ b/clippy_lints/src/methods/mut_mutex_lock.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::expr_custom_deref_adjustment; -use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable}; +use clippy_utils::ty::{is_type_diagnostic_item, peel_and_count_ty_refs}; use rustc_errors::Applicability; use rustc_hir::{Expr, Mutability}; use rustc_lint::LateContext; @@ -10,8 +10,7 @@ use super::MUT_MUTEX_LOCK; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>, recv: &'tcx Expr<'tcx>, name_span: Span) { if matches!(expr_custom_deref_adjustment(cx, recv), None | Some(Mutability::Mut)) - && let (_, ref_depth, Mutability::Mut) = peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(recv)) - && ref_depth >= 1 + && let (_, _, Some(Mutability::Mut)) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(recv)) && let Some(method_id) = cx.typeck_results().type_dependent_def_id(ex.hir_id) && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).instantiate_identity(), sym::Mutex) diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index 04f0e3c0479e0..71b2f251eded6 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -2,6 +2,7 @@ use std::ops::ControlFlow; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::eager_or_lazy::switch_to_lazy_eval; +use clippy_utils::higher::VecArgs; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{expr_type_is_certain, implements_trait, is_type_diagnostic_item}; use clippy_utils::visitors::for_each_expr; @@ -97,6 +98,12 @@ pub(super) fn check<'tcx>( return false; } + // `.unwrap_or(vec![])` is as readable as `.unwrap_or_default()`. And if the expression is a + // non-empty `Vec`, then it will not be a default value anyway. Bail out in all cases. + if call_expr.and_then(|call_expr| VecArgs::hir(cx, call_expr)).is_some() { + return false; + } + // needs to target Default::default in particular or be *::new and have a Default impl // available if (is_new(fun) && output_type_implements_default(fun)) diff --git a/clippy_lints/src/methods/ptr_offset_with_cast.rs b/clippy_lints/src/methods/ptr_offset_with_cast.rs new file mode 100644 index 0000000000000..d19d3b8eb89d9 --- /dev/null +++ b/clippy_lints/src/methods/ptr_offset_with_cast.rs @@ -0,0 +1,82 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::sym; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::Symbol; +use std::fmt; + +use super::PTR_OFFSET_WITH_CAST; + +pub(super) fn check( + cx: &LateContext<'_>, + method: Symbol, + expr: &Expr<'_>, + recv: &Expr<'_>, + arg: &Expr<'_>, + msrv: Msrv, +) { + // `pointer::add` and `pointer::wrapping_add` are only stable since 1.26.0. These functions + // became const-stable in 1.61.0, the same version that `pointer::offset` became const-stable. + if !msrv.meets(cx, msrvs::POINTER_ADD_SUB_METHODS) { + return; + } + + let method = match method { + sym::offset => Method::Offset, + sym::wrapping_offset => Method::WrappingOffset, + _ => return, + }; + + if !cx.typeck_results().expr_ty_adjusted(recv).is_raw_ptr() { + return; + } + + // Check if the argument to the method call is a cast from usize. + let cast_lhs_expr = match arg.kind { + ExprKind::Cast(lhs, _) if cx.typeck_results().expr_ty(lhs).is_usize() => lhs, + _ => return, + }; + + let ExprKind::MethodCall(method_name, _, _, _) = expr.kind else { + return; + }; + + let msg = format!("use of `{method}` with a `usize` casted to an `isize`"); + span_lint_and_then(cx, PTR_OFFSET_WITH_CAST, expr.span, msg, |diag| { + diag.multipart_suggestion( + format!("use `{}` instead", method.suggestion()), + vec![ + (method_name.ident.span, method.suggestion().to_string()), + (arg.span.with_lo(cast_lhs_expr.span.hi()), String::new()), + ], + Applicability::MachineApplicable, + ); + }); +} + +#[derive(Copy, Clone)] +enum Method { + Offset, + WrappingOffset, +} + +impl Method { + #[must_use] + fn suggestion(self) -> &'static str { + match self { + Self::Offset => "add", + Self::WrappingOffset => "wrapping_add", + } + } +} + +impl fmt::Display for Method { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Offset => write!(f, "offset"), + Self::WrappingOffset => write!(f, "wrapping_offset"), + } + } +} diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index c1f4904af7c48..640931a828998 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -3,11 +3,12 @@ use super::unnecessary_iter_cloned::{self, is_into_iter}; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::{SpanRangeExt, snippet}; -use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, is_type_lang_item}; +use clippy_utils::ty::{ + get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, is_type_lang_item, peel_and_count_ty_refs, +}; use clippy_utils::visitors::find_all_ret_expressions; use clippy_utils::{ - fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, is_expr_temporary_value, peel_middle_ty_refs, - return_ty, sym, + fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, is_expr_temporary_value, return_ty, sym, }; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; @@ -119,8 +120,8 @@ fn check_addr_of_expr( }, ] = adjustments[..] && let receiver_ty = cx.typeck_results().expr_ty(receiver) - && let (target_ty, n_target_refs) = peel_middle_ty_refs(*target_ty) - && let (receiver_ty, n_receiver_refs) = peel_middle_ty_refs(receiver_ty) + && let (target_ty, n_target_refs, _) = peel_and_count_ty_refs(*target_ty) + && let (receiver_ty, n_receiver_refs, _) = peel_and_count_ty_refs(receiver_ty) // Only flag cases satisfying at least one of the following three conditions: // * the referent and receiver types are distinct // * the referent/receiver type is a copyable array @@ -385,7 +386,7 @@ fn check_other_call_arg<'tcx>( && let fn_sig = cx.tcx.fn_sig(callee_def_id).instantiate_identity().skip_binder() && let Some(i) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == maybe_arg.hir_id) && let Some(input) = fn_sig.inputs().get(i) - && let (input, n_refs) = peel_middle_ty_refs(*input) + && let (input, n_refs, _) = peel_and_count_ty_refs(*input) && let (trait_predicates, _) = get_input_traits_and_projections(cx, callee_def_id, input) && let Some(sized_def_id) = cx.tcx.lang_items().sized_trait() && let Some(meta_sized_def_id) = cx.tcx.lang_items().meta_sized_trait() diff --git a/clippy_lints/src/methods/useless_asref.rs b/clippy_lints/src/methods/useless_asref.rs index 38fad239f6799..e56f4b80d0172 100644 --- a/clippy_lints/src/methods/useless_asref.rs +++ b/clippy_lints/src/methods/useless_asref.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::{implements_trait, should_call_clone_as_function, walk_ptrs_ty_depth}; +use clippy_utils::ty::{implements_trait, peel_and_count_ty_refs, should_call_clone_as_function}; use clippy_utils::{get_parent_expr, is_diag_trait_item, path_to_local_id, peel_blocks, strip_pat_refs}; use rustc_errors::Applicability; use rustc_hir::{self as hir, LangItem}; @@ -50,8 +50,8 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: Symbo // check if the type after `as_ref` or `as_mut` is the same as before let rcv_ty = cx.typeck_results().expr_ty(recvr); let res_ty = cx.typeck_results().expr_ty(expr); - let (base_res_ty, res_depth) = walk_ptrs_ty_depth(res_ty); - let (base_rcv_ty, rcv_depth) = walk_ptrs_ty_depth(rcv_ty); + let (base_res_ty, res_depth, _) = peel_and_count_ty_refs(res_ty); + let (base_rcv_ty, rcv_depth, _) = peel_and_count_ty_refs(rcv_ty); if base_rcv_ty == base_res_ty && rcv_depth >= res_depth { if let Some(parent) = get_parent_expr(cx, expr) { // allow the `as_ref` or `as_mut` if it is followed by another method call diff --git a/clippy_lints/src/min_ident_chars.rs b/clippy_lints/src/min_ident_chars.rs index 6258e408217fc..1ceee836732ae 100644 --- a/clippy_lints/src/min_ident_chars.rs +++ b/clippy_lints/src/min_ident_chars.rs @@ -256,7 +256,11 @@ fn is_not_in_trait_impl(cx: &LateContext<'_>, pat: &Pat<'_>, ident: Ident) -> bo } fn get_param_name(impl_item: &ImplItem<'_>, cx: &LateContext<'_>, ident: Ident) -> Option { - if let ImplItemImplKind::Trait { trait_item_def_id: Ok(trait_item_def_id), .. } = impl_item.impl_kind { + if let ImplItemImplKind::Trait { + trait_item_def_id: Ok(trait_item_def_id), + .. + } = impl_item.impl_kind + { let trait_param_names = cx.tcx.fn_arg_idents(trait_item_def_id); let ImplItemKind::Fn(_, body_id) = impl_item.kind else { diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index 09ee6f7037c64..19e9910dfe9dd 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -1,57 +1,11 @@ -use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir, span_lint_hir_and_then}; -use clippy_utils::source::{snippet, snippet_with_context}; +use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::sugg::Sugg; -use clippy_utils::{ - SpanlessEq, fulfill_or_allowed, get_parent_expr, in_automatically_derived, is_lint_allowed, iter_input_pats, - last_path_segment, -}; +use clippy_utils::{SpanlessEq, fulfill_or_allowed, get_parent_expr, in_automatically_derived, last_path_segment}; use rustc_errors::Applicability; use rustc_hir::def::Res; -use rustc_hir::intravisit::FnKind; -use rustc_hir::{ - BinOpKind, BindingMode, Body, ByRef, Expr, ExprKind, FnDecl, Mutability, PatKind, QPath, Stmt, StmtKind, -}; +use rustc_hir::{BinOpKind, Expr, ExprKind, QPath, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::declare_lint_pass; -use rustc_span::Span; -use rustc_span::def_id::LocalDefId; - -use crate::ref_patterns::REF_PATTERNS; - -declare_clippy_lint! { - /// ### What it does - /// Checks for function arguments and let bindings denoted as - /// `ref`. - /// - /// ### Why is this bad? - /// The `ref` declaration makes the function take an owned - /// value, but turns the argument into a reference (which means that the value - /// is destroyed when exiting the function). This adds not much value: either - /// take a reference type, or take an owned value and create references in the - /// body. - /// - /// For let bindings, `let x = &foo;` is preferred over `let ref x = foo`. The - /// type of `x` is more obvious with the former. - /// - /// ### Known problems - /// If the argument is dereferenced within the function, - /// removing the `ref` will lead to errors. This can be fixed by removing the - /// dereferences, e.g., changing `*x` to `x` within the function. - /// - /// ### Example - /// ```no_run - /// fn foo(ref _x: u8) {} - /// ``` - /// - /// Use instead: - /// ```no_run - /// fn foo(_x: &u8) {} - /// ``` - #[clippy::version = "pre 1.29.0"] - pub TOPLEVEL_REF_ARG, - style, - "an entire binding declared as `ref`, in a function argument or a `let` statement" -} declare_clippy_lint! { /// ### What it does @@ -140,79 +94,13 @@ declare_clippy_lint! { } declare_lint_pass!(LintPass => [ - TOPLEVEL_REF_ARG, USED_UNDERSCORE_BINDING, USED_UNDERSCORE_ITEMS, SHORT_CIRCUIT_STATEMENT, ]); impl<'tcx> LateLintPass<'tcx> for LintPass { - fn check_fn( - &mut self, - cx: &LateContext<'tcx>, - k: FnKind<'tcx>, - decl: &'tcx FnDecl<'_>, - body: &'tcx Body<'_>, - _: Span, - _: LocalDefId, - ) { - if !matches!(k, FnKind::Closure) { - for arg in iter_input_pats(decl, body) { - if let PatKind::Binding(BindingMode(ByRef::Yes(_), _), ..) = arg.pat.kind - && is_lint_allowed(cx, REF_PATTERNS, arg.pat.hir_id) - && !arg.span.in_external_macro(cx.tcx.sess.source_map()) - { - span_lint_hir( - cx, - TOPLEVEL_REF_ARG, - arg.hir_id, - arg.pat.span, - "`ref` directly on a function parameter does not prevent taking ownership of the passed argument. \ - Consider using a reference type instead", - ); - } - } - } - } - fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { - if let StmtKind::Let(local) = stmt.kind - && let PatKind::Binding(BindingMode(ByRef::Yes(mutabl), _), .., name, None) = local.pat.kind - && let Some(init) = local.init - // Do not emit if clippy::ref_patterns is not allowed to avoid having two lints for the same issue. - && is_lint_allowed(cx, REF_PATTERNS, local.pat.hir_id) - && !stmt.span.in_external_macro(cx.tcx.sess.source_map()) - { - let ctxt = local.span.ctxt(); - let mut app = Applicability::MachineApplicable; - let sugg_init = Sugg::hir_with_context(cx, init, ctxt, "..", &mut app); - let (mutopt, initref) = if mutabl == Mutability::Mut { - ("mut ", sugg_init.mut_addr()) - } else { - ("", sugg_init.addr()) - }; - let tyopt = if let Some(ty) = local.ty { - let ty_snip = snippet_with_context(cx, ty.span, ctxt, "_", &mut app).0; - format!(": &{mutopt}{ty_snip}") - } else { - String::new() - }; - span_lint_hir_and_then( - cx, - TOPLEVEL_REF_ARG, - init.hir_id, - local.pat.span, - "`ref` on an entire `let` pattern is discouraged, take a reference with `&` instead", - |diag| { - diag.span_suggestion( - stmt.span, - "try", - format!("let {name}{tyopt} = {initref};", name = snippet(cx, name.span, ".."),), - app, - ); - }, - ); - } if let StmtKind::Semi(expr) = stmt.kind && let ExprKind::Binary(binop, a, b) = &expr.kind && matches!(binop.node, BinOpKind::And | BinOpKind::Or) diff --git a/clippy_lints/src/missing_doc.rs b/clippy_lints/src/missing_doc.rs index 39b5964bd87b1..1c62caa1c8274 100644 --- a/clippy_lints/src/missing_doc.rs +++ b/clippy_lints/src/missing_doc.rs @@ -250,7 +250,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { AssocContainer::Trait | AssocContainer::TraitImpl(_) => { note_prev_span_then_ret!(self.prev_span, impl_item.span); }, - AssocContainer::InherentImpl => {} + AssocContainer::InherentImpl => {}, } let (article, desc) = cx.tcx.article_and_description(impl_item.owner_id.to_def_id()); diff --git a/clippy_lints/src/multiple_unsafe_ops_per_block.rs b/clippy_lints/src/multiple_unsafe_ops_per_block.rs index c6c27e22b90e5..bc5e72270f4e3 100644 --- a/clippy_lints/src/multiple_unsafe_ops_per_block.rs +++ b/clippy_lints/src/multiple_unsafe_ops_per_block.rs @@ -1,3 +1,4 @@ +use clippy_utils::desugar_await; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::visitors::{Descend, Visitable, for_each_expr}; use core::ops::ControlFlow::Continue; @@ -97,6 +98,13 @@ fn collect_unsafe_exprs<'tcx>( ) { for_each_expr(cx, node, |expr| { match expr.kind { + // The `await` itself will desugar to two unsafe calls, but we should ignore those. + // Instead, check the expression that is `await`ed + _ if let Some(e) = desugar_await(expr) => { + collect_unsafe_exprs(cx, e, unsafe_ops); + return Continue(Descend::No); + }, + ExprKind::InlineAsm(_) => unsafe_ops.push(("inline assembly used here", expr.span)), ExprKind::Field(e, _) => { diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs index c4cad592e3675..a21c361356e83 100644 --- a/clippy_lints/src/only_used_in_recursion.rs +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -252,7 +252,9 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion { { ( trait_item_id, - FnKind::ImplTraitFn(std::ptr::from_ref(cx.tcx.erase_and_anonymize_regions(trait_ref.args)) as usize), + FnKind::ImplTraitFn( + std::ptr::from_ref(cx.tcx.erase_and_anonymize_regions(trait_ref.args)) as usize + ), usize::from(sig.decl.implicit_self.has_implicit_self()), ) } else { diff --git a/clippy_lints/src/operators/erasing_op.rs b/clippy_lints/src/operators/erasing_op.rs index e3fc8d8fea7d1..8f5ee390f7222 100644 --- a/clippy_lints/src/operators/erasing_op.rs +++ b/clippy_lints/src/operators/erasing_op.rs @@ -1,6 +1,6 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint; -use clippy_utils::ty::same_type_and_consts; +use clippy_utils::ty::same_type_modulo_regions; use rustc_hir::{BinOpKind, Expr}; use rustc_lint::LateContext; @@ -29,7 +29,7 @@ pub(super) fn check<'tcx>( fn different_types(tck: &TypeckResults<'_>, input: &Expr<'_>, output: &Expr<'_>) -> bool { let input_ty = tck.expr_ty(input).peel_refs(); let output_ty = tck.expr_ty(output).peel_refs(); - !same_type_and_consts(input_ty, output_ty) + !same_type_modulo_regions(input_ty, output_ty) } fn check_op<'tcx>( diff --git a/clippy_lints/src/ptr_offset_with_cast.rs b/clippy_lints/src/ptr_offset_with_cast.rs deleted file mode 100644 index d8d813f9846d5..0000000000000 --- a/clippy_lints/src/ptr_offset_with_cast.rs +++ /dev/null @@ -1,151 +0,0 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; -use clippy_utils::source::SpanRangeExt; -use clippy_utils::sym; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; -use std::fmt; - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of the `offset` pointer method with a `usize` casted to an - /// `isize`. - /// - /// ### Why is this bad? - /// If we’re always increasing the pointer address, we can avoid the numeric - /// cast by using the `add` method instead. - /// - /// ### Example - /// ```no_run - /// let vec = vec![b'a', b'b', b'c']; - /// let ptr = vec.as_ptr(); - /// let offset = 1_usize; - /// - /// unsafe { - /// ptr.offset(offset as isize); - /// } - /// ``` - /// - /// Could be written: - /// - /// ```no_run - /// let vec = vec![b'a', b'b', b'c']; - /// let ptr = vec.as_ptr(); - /// let offset = 1_usize; - /// - /// unsafe { - /// ptr.add(offset); - /// } - /// ``` - #[clippy::version = "1.30.0"] - pub PTR_OFFSET_WITH_CAST, - complexity, - "unneeded pointer offset cast" -} - -declare_lint_pass!(PtrOffsetWithCast => [PTR_OFFSET_WITH_CAST]); - -impl<'tcx> LateLintPass<'tcx> for PtrOffsetWithCast { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - // Check if the expressions is a ptr.offset or ptr.wrapping_offset method call - let Some((receiver_expr, arg_expr, method)) = expr_as_ptr_offset_call(cx, expr) else { - return; - }; - - // Check if the argument to the method call is a cast from usize - let Some(cast_lhs_expr) = expr_as_cast_from_usize(cx, arg_expr) else { - return; - }; - - let msg = format!("use of `{method}` with a `usize` casted to an `isize`"); - if let Some(sugg) = build_suggestion(cx, method, receiver_expr, cast_lhs_expr) { - span_lint_and_sugg( - cx, - PTR_OFFSET_WITH_CAST, - expr.span, - msg, - "try", - sugg, - Applicability::MachineApplicable, - ); - } else { - span_lint(cx, PTR_OFFSET_WITH_CAST, expr.span, msg); - } - } -} - -// If the given expression is a cast from a usize, return the lhs of the cast -fn expr_as_cast_from_usize<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { - if let ExprKind::Cast(cast_lhs_expr, _) = expr.kind - && is_expr_ty_usize(cx, cast_lhs_expr) - { - return Some(cast_lhs_expr); - } - None -} - -// If the given expression is a ptr::offset or ptr::wrapping_offset method call, return the -// receiver, the arg of the method call, and the method. -fn expr_as_ptr_offset_call<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx Expr<'_>, -) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Method)> { - if let ExprKind::MethodCall(path_segment, arg_0, [arg_1], _) = &expr.kind - && is_expr_ty_raw_ptr(cx, arg_0) - { - if path_segment.ident.name == sym::offset { - return Some((arg_0, arg_1, Method::Offset)); - } - if path_segment.ident.name == sym::wrapping_offset { - return Some((arg_0, arg_1, Method::WrappingOffset)); - } - } - None -} - -// Is the type of the expression a usize? -fn is_expr_ty_usize(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - cx.typeck_results().expr_ty(expr) == cx.tcx.types.usize -} - -// Is the type of the expression a raw pointer? -fn is_expr_ty_raw_ptr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - cx.typeck_results().expr_ty(expr).is_raw_ptr() -} - -fn build_suggestion( - cx: &LateContext<'_>, - method: Method, - receiver_expr: &Expr<'_>, - cast_lhs_expr: &Expr<'_>, -) -> Option { - let receiver = receiver_expr.span.get_source_text(cx)?; - let cast_lhs = cast_lhs_expr.span.get_source_text(cx)?; - Some(format!("{receiver}.{}({cast_lhs})", method.suggestion())) -} - -#[derive(Copy, Clone)] -enum Method { - Offset, - WrappingOffset, -} - -impl Method { - #[must_use] - fn suggestion(self) -> &'static str { - match self { - Self::Offset => "add", - Self::WrappingOffset => "wrapping_add", - } - } -} - -impl fmt::Display for Method { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Offset => write!(f, "offset"), - Self::WrappingOffset => write!(f, "wrapping_offset"), - } - } -} diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index de12a25b03dff..4aa100a50e053 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -8,9 +8,9 @@ use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use clippy_utils::{ - eq_expr_value, higher, is_else_clause, is_in_const_context, is_lint_allowed, is_path_lang_item, is_res_lang_ctor, - pat_and_expr_can_be_question_mark, path_res, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt, - span_contains_cfg, span_contains_comment, sym, + eq_expr_value, fn_def_id_with_node_args, higher, is_else_clause, is_in_const_context, is_lint_allowed, + is_path_lang_item, is_res_lang_ctor, pat_and_expr_can_be_question_mark, path_res, path_to_local, path_to_local_id, + peel_blocks, peel_blocks_with_stmt, span_contains_cfg, span_contains_comment, sym, }; use rustc_errors::Applicability; use rustc_hir::LangItem::{self, OptionNone, OptionSome, ResultErr, ResultOk}; @@ -393,8 +393,8 @@ fn check_arm_is_none_or_err<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm: &A && let ExprKind::Ret(Some(wrapped_ret_expr)) = arm_body.kind && let ExprKind::Call(ok_ctor, [ret_expr]) = wrapped_ret_expr.kind && is_res_lang_ctor(cx, path_res(cx, ok_ctor), ResultErr) - // check `...` is `val` from binding - && path_to_local_id(ret_expr, ok_val) + // check if `...` is `val` from binding or `val.into()` + && is_local_or_local_into(cx, ret_expr, ok_val) { true } else { @@ -417,6 +417,17 @@ fn check_arm_is_none_or_err<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm: &A } } +/// Check if `expr` is `val` or `val.into()` +fn is_local_or_local_into(cx: &LateContext<'_>, expr: &Expr<'_>, val: HirId) -> bool { + let is_into_call = fn_def_id_with_node_args(cx, expr) + .and_then(|(fn_def_id, _)| cx.tcx.trait_of_assoc(fn_def_id)) + .is_some_and(|trait_def_id| cx.tcx.is_diagnostic_item(sym::Into, trait_def_id)); + match expr.kind { + ExprKind::MethodCall(_, recv, [], _) | ExprKind::Call(_, [recv]) => is_into_call && path_to_local_id(recv, val), + _ => path_to_local_id(expr, val), + } +} + fn check_arms_are_try<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm1: &Arm<'tcx>, arm2: &Arm<'tcx>) -> bool { (check_arm_is_some_or_ok(cx, mode, arm1) && check_arm_is_none_or_err(cx, mode, arm2)) || (check_arm_is_some_or_ok(cx, mode, arm2) && check_arm_is_none_or_err(cx, mode, arm1)) diff --git a/clippy_lints/src/read_zero_byte_vec.rs b/clippy_lints/src/read_zero_byte_vec.rs index acd840401c6bb..b8d4e7c4651d2 100644 --- a/clippy_lints/src/read_zero_byte_vec.rs +++ b/clippy_lints/src/read_zero_byte_vec.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; use clippy_utils::higher::{VecInitKind, get_vec_init_kind}; -use clippy_utils::source::snippet; +use clippy_utils::source::{indent_of, snippet}; use clippy_utils::{get_enclosing_block, sym}; use rustc_errors::Applicability; @@ -83,10 +83,12 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec { expr.span, "reading zero byte data to `Vec`", |diag| { + let span = first_stmt_containing_expr(cx, expr).map_or(expr.span, |stmt| stmt.span); + let indent = indent_of(cx, span).unwrap_or(0); diag.span_suggestion( - expr.span, + span.shrink_to_lo(), "try", - format!("{}.resize({len}, 0); {}", ident, snippet(cx, expr.span, "..")), + format!("{ident}.resize({len}, 0);\n{}", " ".repeat(indent)), applicability, ); }, @@ -100,14 +102,15 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec { expr.span, "reading zero byte data to `Vec`", |diag| { + let span = first_stmt_containing_expr(cx, expr).map_or(expr.span, |stmt| stmt.span); + let indent = indent_of(cx, span).unwrap_or(0); diag.span_suggestion( - expr.span, + span.shrink_to_lo(), "try", format!( - "{}.resize({}, 0); {}", - ident, + "{ident}.resize({}, 0);\n{}", snippet(cx, e.span, ".."), - snippet(cx, expr.span, "..") + " ".repeat(indent) ), applicability, ); @@ -130,6 +133,16 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec { } } +fn first_stmt_containing_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx hir::Stmt<'tcx>> { + cx.tcx.hir_parent_iter(expr.hir_id).find_map(|(_, node)| { + if let hir::Node::Stmt(stmt) = node { + Some(stmt) + } else { + None + } + }) +} + struct ReadVecVisitor<'tcx> { local_id: HirId, read_zero_expr: Option<&'tcx Expr<'tcx>>, diff --git a/clippy_lints/src/redundant_clone.rs b/clippy_lints/src/redundant_clone.rs index 1d58cdd26d88d..de6766cbe94aa 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_lang_item, walk_ptrs_ty_depth}; +use clippy_utils::ty::{has_drop, is_copy, is_type_lang_item, peel_and_count_ty_refs}; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{Body, FnDecl, LangItem, def_id}; @@ -263,7 +263,7 @@ fn is_call_with_ref_arg<'tcx>( && args.len() == 1 && let mir::Operand::Move(mir::Place { local, .. }) = &args[0].node && let ty::FnDef(def_id, _) = *func.ty(mir, cx.tcx).kind() - && let (inner_ty, 1) = walk_ptrs_ty_depth(args[0].node.ty(mir, cx.tcx)) + && let (inner_ty, 1, _) = peel_and_count_ty_refs(args[0].node.ty(mir, cx.tcx)) && !is_copy(cx, inner_ty) { Some((def_id, *local, inner_ty, destination.as_local()?)) diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index 324a05cdcc0c8..a358eff2ce554 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::get_parent_expr; use clippy_utils::source::snippet_with_context; -use clippy_utils::ty::is_type_lang_item; -use clippy_utils::{get_parent_expr, peel_middle_ty_refs}; +use clippy_utils::ty::{is_type_lang_item, peel_and_count_ty_refs}; use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, LangItem, Mutability}; @@ -82,8 +82,8 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { && let ExprKind::Index(indexed, range, _) = addressee.kind && is_type_lang_item(cx, cx.typeck_results().expr_ty_adjusted(range), LangItem::RangeFull) { - let (expr_ty, expr_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(expr)); - let (indexed_ty, indexed_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(indexed)); + let (expr_ty, expr_ref_count, _) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(expr)); + let (indexed_ty, indexed_ref_count, _) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(indexed)); let parent_expr = get_parent_expr(cx, expr); let needs_parens_for_prefix = parent_expr.is_some_and(|parent| cx.precedence(parent) > ExprPrecedence::Prefix); diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs deleted file mode 100644 index e0c93153a77a8..0000000000000 --- a/clippy_lints/src/returns.rs +++ /dev/null @@ -1,513 +0,0 @@ -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; -use clippy_utils::source::{SpanRangeExt, snippet_with_context}; -use clippy_utils::sugg::has_enclosing_paren; -use clippy_utils::visitors::for_each_expr; -use clippy_utils::{ - binary_expr_needs_parentheses, fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, - leaks_droppable_temporary_with_limited_lifetime, path_res, path_to_local_id, span_contains_cfg, - span_find_starting_semi, sym, -}; -use core::ops::ControlFlow; -use rustc_ast::MetaItemInner; -use rustc_errors::Applicability; -use rustc_hir::LangItem::ResultErr; -use rustc_hir::intravisit::FnKind; -use rustc_hir::{ - Block, Body, Expr, ExprKind, FnDecl, HirId, ItemKind, LangItem, MatchSource, Node, OwnerNode, PatKind, QPath, Stmt, - StmtKind, -}; -use rustc_lint::{LateContext, LateLintPass, Level, LintContext}; -use rustc_middle::ty::adjustment::Adjust; -use rustc_middle::ty::{self, GenericArgKind, Ty}; -use rustc_session::declare_lint_pass; -use rustc_span::def_id::LocalDefId; -use rustc_span::edition::Edition; -use rustc_span::{BytePos, Pos, Span}; -use std::borrow::Cow; -use std::fmt::Display; - -declare_clippy_lint! { - /// ### What it does - /// Checks for `let`-bindings, which are subsequently - /// returned. - /// - /// ### Why is this bad? - /// It is just extraneous code. Remove it to make your code - /// more rusty. - /// - /// ### Known problems - /// In the case of some temporaries, e.g. locks, eliding the variable binding could lead - /// to deadlocks. See [this issue](https://github.com/rust-lang/rust/issues/37612). - /// This could become relevant if the code is later changed to use the code that would have been - /// bound without first assigning it to a let-binding. - /// - /// ### Example - /// ```no_run - /// fn foo() -> String { - /// let x = String::new(); - /// x - /// } - /// ``` - /// instead, use - /// ```no_run - /// fn foo() -> String { - /// String::new() - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub LET_AND_RETURN, - style, - "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for return statements at the end of a block. - /// - /// ### Why is this bad? - /// Removing the `return` and semicolon will make the code - /// more rusty. - /// - /// ### Example - /// ```no_run - /// fn foo(x: usize) -> usize { - /// return x; - /// } - /// ``` - /// simplify to - /// ```no_run - /// fn foo(x: usize) -> usize { - /// x - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub NEEDLESS_RETURN, - // This lint requires some special handling in `check_final_expr` for `#[expect]`. - // This handling needs to be updated if the group gets changed. This should also - // be caught by tests. - style, - "using a return statement like `return expr;` where an expression would suffice" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for return statements on `Err` paired with the `?` operator. - /// - /// ### Why is this bad? - /// The `return` is unnecessary. - /// - /// Returns may be used to add attributes to the return expression. Return - /// statements with attributes are therefore be accepted by this lint. - /// - /// ### Example - /// ```rust,ignore - /// fn foo(x: usize) -> Result<(), Box> { - /// if x == 0 { - /// return Err(...)?; - /// } - /// Ok(()) - /// } - /// ``` - /// simplify to - /// ```rust,ignore - /// fn foo(x: usize) -> Result<(), Box> { - /// if x == 0 { - /// Err(...)?; - /// } - /// Ok(()) - /// } - /// ``` - /// if paired with `try_err`, use instead: - /// ```rust,ignore - /// fn foo(x: usize) -> Result<(), Box> { - /// if x == 0 { - /// return Err(...); - /// } - /// Ok(()) - /// } - /// ``` - #[clippy::version = "1.73.0"] - pub NEEDLESS_RETURN_WITH_QUESTION_MARK, - style, - "using a return statement like `return Err(expr)?;` where removing it would suffice" -} - -#[derive(PartialEq, Eq)] -enum RetReplacement<'tcx> { - Empty, - Block, - Unit, - NeedsPar(Cow<'tcx, str>, Applicability), - Expr(Cow<'tcx, str>, Applicability), -} - -impl RetReplacement<'_> { - fn sugg_help(&self) -> &'static str { - match self { - Self::Empty | Self::Expr(..) => "remove `return`", - Self::Block => "replace `return` with an empty block", - Self::Unit => "replace `return` with a unit value", - Self::NeedsPar(..) => "remove `return` and wrap the sequence with parentheses", - } - } - - fn applicability(&self) -> Applicability { - match self { - Self::Expr(_, ap) | Self::NeedsPar(_, ap) => *ap, - _ => Applicability::MachineApplicable, - } - } -} - -impl Display for RetReplacement<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Empty => write!(f, ""), - Self::Block => write!(f, "{{}}"), - Self::Unit => write!(f, "()"), - Self::NeedsPar(inner, _) => write!(f, "({inner})"), - Self::Expr(inner, _) => write!(f, "{inner}"), - } - } -} - -declare_lint_pass!(Return => [LET_AND_RETURN, NEEDLESS_RETURN, NEEDLESS_RETURN_WITH_QUESTION_MARK]); - -/// Checks if a return statement is "needed" in the middle of a block, or if it can be removed. This -/// is the case when the enclosing block expression is coerced to some other type, which only works -/// because of the never-ness of `return` expressions -fn stmt_needs_never_type(cx: &LateContext<'_>, stmt_hir_id: HirId) -> bool { - cx.tcx - .hir_parent_iter(stmt_hir_id) - .find_map(|(_, node)| if let Node::Expr(expr) = node { Some(expr) } else { None }) - .is_some_and(|e| { - cx.typeck_results() - .expr_adjustments(e) - .iter() - .any(|adjust| adjust.target != cx.tcx.types.unit && matches!(adjust.kind, Adjust::NeverToAny)) - }) -} - -impl<'tcx> LateLintPass<'tcx> for Return { - fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { - if !stmt.span.in_external_macro(cx.sess().source_map()) - && let StmtKind::Semi(expr) = stmt.kind - && let ExprKind::Ret(Some(ret)) = expr.kind - // return Err(...)? desugars to a match - // over a Err(...).branch() - // which breaks down to a branch call, with the callee being - // the constructor of the Err variant - && let ExprKind::Match(maybe_cons, _, MatchSource::TryDesugar(_)) = ret.kind - && let ExprKind::Call(_, [maybe_result_err]) = maybe_cons.kind - && let ExprKind::Call(maybe_constr, _) = maybe_result_err.kind - && is_res_lang_ctor(cx, path_res(cx, maybe_constr), ResultErr) - - // Ensure this is not the final stmt, otherwise removing it would cause a compile error - && let OwnerNode::Item(item) = cx.tcx.hir_owner_node(cx.tcx.hir_get_parent_item(expr.hir_id)) - && let ItemKind::Fn { body, .. } = item.kind - && let block = cx.tcx.hir_body(body).value - && let ExprKind::Block(block, _) = block.kind - && !is_inside_let_else(cx.tcx, expr) - && let [.., final_stmt] = block.stmts - && final_stmt.hir_id != stmt.hir_id - && !is_from_proc_macro(cx, expr) - && !stmt_needs_never_type(cx, stmt.hir_id) - { - span_lint_and_sugg( - cx, - NEEDLESS_RETURN_WITH_QUESTION_MARK, - expr.span.until(ret.span), - "unneeded `return` statement with `?` operator", - "remove it", - String::new(), - Applicability::MachineApplicable, - ); - } - } - - fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { - // we need both a let-binding stmt and an expr - if let Some(retexpr) = block.expr - && let Some(stmt) = block.stmts.iter().last() - && let StmtKind::Let(local) = &stmt.kind - && local.ty.is_none() - && cx.tcx.hir_attrs(local.hir_id).is_empty() - && let Some(initexpr) = &local.init - && let PatKind::Binding(_, local_id, _, _) = local.pat.kind - && path_to_local_id(retexpr, local_id) - && (cx.sess().edition() >= Edition::Edition2024 || !last_statement_borrows(cx, initexpr)) - && !initexpr.span.in_external_macro(cx.sess().source_map()) - && !retexpr.span.in_external_macro(cx.sess().source_map()) - && !local.span.from_expansion() - && !span_contains_cfg(cx, stmt.span.between(retexpr.span)) - { - span_lint_hir_and_then( - cx, - LET_AND_RETURN, - retexpr.hir_id, - retexpr.span, - "returning the result of a `let` binding from a block", - |err| { - err.span_label(local.span, "unnecessary `let` binding"); - - if let Some(src) = initexpr.span.get_source_text(cx) { - let sugg = if binary_expr_needs_parentheses(initexpr) { - if has_enclosing_paren(&src) { - src.to_owned() - } else { - format!("({src})") - } - } else if !cx.typeck_results().expr_adjustments(retexpr).is_empty() { - if has_enclosing_paren(&src) { - format!("{src} as _") - } else { - format!("({src}) as _") - } - } else { - src.to_owned() - }; - err.multipart_suggestion( - "return the expression directly", - vec![(local.span, String::new()), (retexpr.span, sugg)], - Applicability::MachineApplicable, - ); - } else { - err.span_help(initexpr.span, "this expression can be directly returned"); - } - }, - ); - } - } - - fn check_fn( - &mut self, - cx: &LateContext<'tcx>, - kind: FnKind<'tcx>, - _: &'tcx FnDecl<'tcx>, - body: &'tcx Body<'tcx>, - sp: Span, - _: LocalDefId, - ) { - if sp.from_expansion() { - return; - } - - match kind { - FnKind::Closure => { - // when returning without value in closure, replace this `return` - // with an empty block to prevent invalid suggestion (see #6501) - let replacement = if let ExprKind::Ret(None) = &body.value.kind { - RetReplacement::Block - } else { - RetReplacement::Empty - }; - check_final_expr(cx, body.value, vec![], replacement, None); - }, - FnKind::ItemFn(..) | FnKind::Method(..) => { - check_block_return(cx, &body.value.kind, sp, vec![]); - }, - } - } -} - -// if `expr` is a block, check if there are needless returns in it -fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, sp: Span, mut semi_spans: Vec) { - if let ExprKind::Block(block, _) = expr_kind { - if let Some(block_expr) = block.expr { - check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty, None); - } else if let Some(stmt) = block.stmts.iter().last() { - match stmt.kind { - StmtKind::Expr(expr) => { - check_final_expr(cx, expr, semi_spans, RetReplacement::Empty, None); - }, - StmtKind::Semi(semi_expr) => { - // Remove ending semicolons and any whitespace ' ' in between. - // Without `return`, the suggestion might not compile if the semicolon is retained - if let Some(semi_span) = stmt.span.trim_start(semi_expr.span) { - let semi_span_to_remove = - span_find_starting_semi(cx.sess().source_map(), semi_span.with_hi(sp.hi())); - semi_spans.push(semi_span_to_remove); - } - check_final_expr(cx, semi_expr, semi_spans, RetReplacement::Empty, None); - }, - _ => (), - } - } - } -} - -fn check_final_expr<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx Expr<'tcx>, - semi_spans: Vec, /* containing all the places where we would need to remove semicolons if finding an - * needless return */ - replacement: RetReplacement<'tcx>, - match_ty_opt: Option>, -) { - let peeled_drop_expr = expr.peel_drop_temps(); - match &peeled_drop_expr.kind { - // simple return is always "bad" - ExprKind::Ret(inner) => { - // check if expr return nothing - let ret_span = if inner.is_none() && replacement == RetReplacement::Empty { - extend_span_to_previous_non_ws(cx, peeled_drop_expr.span) - } else { - peeled_drop_expr.span - }; - - let replacement = if let Some(inner_expr) = inner { - // if desugar of `do yeet`, don't lint - if let ExprKind::Call(path_expr, [_]) = inner_expr.kind - && let ExprKind::Path(QPath::LangItem(LangItem::TryTraitFromYeet, ..)) = path_expr.kind - { - return; - } - - let mut applicability = Applicability::MachineApplicable; - let (snippet, _) = snippet_with_context(cx, inner_expr.span, ret_span.ctxt(), "..", &mut applicability); - if binary_expr_needs_parentheses(inner_expr) { - RetReplacement::NeedsPar(snippet, applicability) - } else { - RetReplacement::Expr(snippet, applicability) - } - } else { - match match_ty_opt { - Some(match_ty) => { - match match_ty.kind() { - // If the code got till here with - // tuple not getting detected before it, - // then we are sure it's going to be Unit - // type - ty::Tuple(_) => RetReplacement::Unit, - // We don't want to anything in this case - // cause we can't predict what the user would - // want here - _ => return, - } - }, - None => replacement, - } - }; - - if inner.is_some_and(|inner| leaks_droppable_temporary_with_limited_lifetime(cx, inner)) { - return; - } - - if ret_span.from_expansion() || is_from_proc_macro(cx, expr) { - return; - } - - // Returns may be used to turn an expression into a statement in rustc's AST. - // This allows the addition of attributes, like `#[allow]` (See: clippy#9361) - // `#[expect(clippy::needless_return)]` needs to be handled separately to - // actually fulfill the expectation (clippy::#12998) - match cx.tcx.hir_attrs(expr.hir_id) { - [] => {}, - [attr] => { - if matches!(Level::from_attr(attr), Some((Level::Expect, _))) - && let metas = attr.meta_item_list() - && let Some(lst) = metas - && let [MetaItemInner::MetaItem(meta_item), ..] = lst.as_slice() - && let [tool, lint_name] = meta_item.path.segments.as_slice() - && tool.ident.name == sym::clippy - && matches!( - lint_name.ident.name, - sym::needless_return | sym::style | sym::all | sym::warnings - ) - { - // This is an expectation of the `needless_return` lint - } else { - return; - } - }, - _ => return, - } - - emit_return_lint( - cx, - peeled_drop_expr.span, - ret_span, - semi_spans, - &replacement, - expr.hir_id, - ); - }, - ExprKind::If(_, then, else_clause_opt) => { - check_block_return(cx, &then.kind, peeled_drop_expr.span, semi_spans.clone()); - if let Some(else_clause) = else_clause_opt { - // The `RetReplacement` won't be used there as `else_clause` will be either a block or - // a `if` expression. - check_final_expr(cx, else_clause, semi_spans, RetReplacement::Empty, match_ty_opt); - } - }, - // a match expr, check all arms - // an if/if let expr, check both exprs - // note, if without else is going to be a type checking error anyways - // (except for unit type functions) so we don't match it - ExprKind::Match(_, arms, MatchSource::Normal) => { - let match_ty = cx.typeck_results().expr_ty(peeled_drop_expr); - for arm in *arms { - check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit, Some(match_ty)); - } - }, - // if it's a whole block, check it - other_expr_kind => check_block_return(cx, other_expr_kind, peeled_drop_expr.span, semi_spans), - } -} - -fn emit_return_lint( - cx: &LateContext<'_>, - lint_span: Span, - ret_span: Span, - semi_spans: Vec, - replacement: &RetReplacement<'_>, - at: HirId, -) { - span_lint_hir_and_then( - cx, - NEEDLESS_RETURN, - at, - lint_span, - "unneeded `return` statement", - |diag| { - let suggestions = std::iter::once((ret_span, replacement.to_string())) - .chain(semi_spans.into_iter().map(|span| (span, String::new()))) - .collect(); - - diag.multipart_suggestion_verbose(replacement.sugg_help(), suggestions, replacement.applicability()); - }, - ); -} - -fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { - for_each_expr(cx, expr, |e| { - if let Some(def_id) = fn_def_id(cx, e) - && cx - .tcx - .fn_sig(def_id) - .instantiate_identity() - .skip_binder() - .output() - .walk() - .any(|arg| matches!(arg.kind(), GenericArgKind::Lifetime(re) if !re.is_static())) - { - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) - } - }) - .is_some() -} - -// Go backwards while encountering whitespace and extend the given Span to that point. -fn extend_span_to_previous_non_ws(cx: &LateContext<'_>, sp: Span) -> Span { - if let Ok(prev_source) = cx.sess().source_map().span_to_prev_source(sp) { - let ws = [b' ', b'\t', b'\n']; - if let Some(non_ws_pos) = prev_source.bytes().rposition(|c| !ws.contains(&c)) { - let len = prev_source.len() - non_ws_pos - 1; - return sp.with_lo(sp.lo() - BytePos::from_usize(len)); - } - } - - sp -} diff --git a/clippy_lints/src/returns/let_and_return.rs b/clippy_lints/src/returns/let_and_return.rs new file mode 100644 index 0000000000000..e2002fb36e5a6 --- /dev/null +++ b/clippy_lints/src/returns/let_and_return.rs @@ -0,0 +1,86 @@ +use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::sugg::has_enclosing_paren; +use clippy_utils::visitors::for_each_expr; +use clippy_utils::{binary_expr_needs_parentheses, fn_def_id, path_to_local_id, span_contains_cfg}; +use core::ops::ControlFlow; +use rustc_errors::Applicability; +use rustc_hir::{Block, Expr, PatKind, StmtKind}; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::ty::GenericArgKind; +use rustc_span::edition::Edition; + +use super::LET_AND_RETURN; + +pub(super) fn check_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { + // we need both a let-binding stmt and an expr + if let Some(retexpr) = block.expr + && let Some(stmt) = block.stmts.last() + && let StmtKind::Let(local) = &stmt.kind + && local.ty.is_none() + && cx.tcx.hir_attrs(local.hir_id).is_empty() + && let Some(initexpr) = &local.init + && let PatKind::Binding(_, local_id, _, _) = local.pat.kind + && path_to_local_id(retexpr, local_id) + && (cx.sess().edition() >= Edition::Edition2024 || !last_statement_borrows(cx, initexpr)) + && !initexpr.span.in_external_macro(cx.sess().source_map()) + && !retexpr.span.in_external_macro(cx.sess().source_map()) + && !local.span.from_expansion() + && !span_contains_cfg(cx, stmt.span.between(retexpr.span)) + { + span_lint_hir_and_then( + cx, + LET_AND_RETURN, + retexpr.hir_id, + retexpr.span, + "returning the result of a `let` binding from a block", + |err| { + err.span_label(local.span, "unnecessary `let` binding"); + + if let Some(src) = initexpr.span.get_source_text(cx) { + let sugg = if binary_expr_needs_parentheses(initexpr) { + if has_enclosing_paren(&src) { + src.to_owned() + } else { + format!("({src})") + } + } else if !cx.typeck_results().expr_adjustments(retexpr).is_empty() { + if has_enclosing_paren(&src) { + format!("{src} as _") + } else { + format!("({src}) as _") + } + } else { + src.to_owned() + }; + err.multipart_suggestion( + "return the expression directly", + vec![(local.span, String::new()), (retexpr.span, sugg)], + Applicability::MachineApplicable, + ); + } else { + err.span_help(initexpr.span, "this expression can be directly returned"); + } + }, + ); + } +} +fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + for_each_expr(cx, expr, |e| { + if let Some(def_id) = fn_def_id(cx, e) + && cx + .tcx + .fn_sig(def_id) + .instantiate_identity() + .skip_binder() + .output() + .walk() + .any(|arg| matches!(arg.kind(), GenericArgKind::Lifetime(re) if !re.is_static())) + { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_some() +} diff --git a/clippy_lints/src/returns/mod.rs b/clippy_lints/src/returns/mod.rs new file mode 100644 index 0000000000000..47c6332b9b81d --- /dev/null +++ b/clippy_lints/src/returns/mod.rs @@ -0,0 +1,140 @@ +use rustc_hir::intravisit::FnKind; +use rustc_hir::{Block, Body, FnDecl, Stmt}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; +use rustc_span::Span; +use rustc_span::def_id::LocalDefId; + +mod let_and_return; +mod needless_return; +mod needless_return_with_question_mark; + +declare_clippy_lint! { + /// ### What it does + /// Checks for `let`-bindings, which are subsequently + /// returned. + /// + /// ### Why is this bad? + /// It is just extraneous code. Remove it to make your code + /// more rusty. + /// + /// ### Known problems + /// In the case of some temporaries, e.g. locks, eliding the variable binding could lead + /// to deadlocks. See [this issue](https://github.com/rust-lang/rust/issues/37612). + /// This could become relevant if the code is later changed to use the code that would have been + /// bound without first assigning it to a let-binding. + /// + /// ### Example + /// ```no_run + /// fn foo() -> String { + /// let x = String::new(); + /// x + /// } + /// ``` + /// instead, use + /// ```no_run + /// fn foo() -> String { + /// String::new() + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub LET_AND_RETURN, + style, + "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for return statements at the end of a block. + /// + /// ### Why is this bad? + /// Removing the `return` and semicolon will make the code + /// more rusty. + /// + /// ### Example + /// ```no_run + /// fn foo(x: usize) -> usize { + /// return x; + /// } + /// ``` + /// simplify to + /// ```no_run + /// fn foo(x: usize) -> usize { + /// x + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub NEEDLESS_RETURN, + // This lint requires some special handling in `check_final_expr` for `#[expect]`. + // This handling needs to be updated if the group gets changed. This should also + // be caught by tests. + style, + "using a return statement like `return expr;` where an expression would suffice" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for return statements on `Err` paired with the `?` operator. + /// + /// ### Why is this bad? + /// The `return` is unnecessary. + /// + /// Returns may be used to add attributes to the return expression. Return + /// statements with attributes are therefore be accepted by this lint. + /// + /// ### Example + /// ```rust,ignore + /// fn foo(x: usize) -> Result<(), Box> { + /// if x == 0 { + /// return Err(...)?; + /// } + /// Ok(()) + /// } + /// ``` + /// simplify to + /// ```rust,ignore + /// fn foo(x: usize) -> Result<(), Box> { + /// if x == 0 { + /// Err(...)?; + /// } + /// Ok(()) + /// } + /// ``` + /// if paired with `try_err`, use instead: + /// ```rust,ignore + /// fn foo(x: usize) -> Result<(), Box> { + /// if x == 0 { + /// return Err(...); + /// } + /// Ok(()) + /// } + /// ``` + #[clippy::version = "1.73.0"] + pub NEEDLESS_RETURN_WITH_QUESTION_MARK, + style, + "using a return statement like `return Err(expr)?;` where removing it would suffice" +} + +declare_lint_pass!(Return => [LET_AND_RETURN, NEEDLESS_RETURN, NEEDLESS_RETURN_WITH_QUESTION_MARK]); + +impl<'tcx> LateLintPass<'tcx> for Return { + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { + needless_return_with_question_mark::check_stmt(cx, stmt); + } + + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { + let_and_return::check_block(cx, block); + } + + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: FnKind<'tcx>, + _: &'tcx FnDecl<'tcx>, + body: &'tcx Body<'tcx>, + sp: Span, + _: LocalDefId, + ) { + needless_return::check_fn(cx, kind, body, sp); + } +} diff --git a/clippy_lints/src/returns/needless_return.rs b/clippy_lints/src/returns/needless_return.rs new file mode 100644 index 0000000000000..04739fc1b22ad --- /dev/null +++ b/clippy_lints/src/returns/needless_return.rs @@ -0,0 +1,269 @@ +use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::source::snippet_with_context; +use clippy_utils::{ + binary_expr_needs_parentheses, is_from_proc_macro, leaks_droppable_temporary_with_limited_lifetime, + span_contains_cfg, span_find_starting_semi, sym, +}; +use rustc_ast::MetaItemInner; +use rustc_errors::Applicability; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{Body, Expr, ExprKind, HirId, LangItem, MatchSource, QPath, StmtKind}; +use rustc_lint::{LateContext, Level, LintContext}; +use rustc_middle::ty::{self, Ty}; +use rustc_span::{BytePos, Pos, Span}; +use std::borrow::Cow; +use std::fmt::Display; + +use super::NEEDLESS_RETURN; + +#[derive(PartialEq, Eq)] +enum RetReplacement<'tcx> { + Empty, + Block, + Unit, + NeedsPar(Cow<'tcx, str>, Applicability), + Expr(Cow<'tcx, str>, Applicability), +} + +impl RetReplacement<'_> { + fn sugg_help(&self) -> &'static str { + match self { + Self::Empty | Self::Expr(..) => "remove `return`", + Self::Block => "replace `return` with an empty block", + Self::Unit => "replace `return` with a unit value", + Self::NeedsPar(..) => "remove `return` and wrap the sequence with parentheses", + } + } + + fn applicability(&self) -> Applicability { + match self { + Self::Expr(_, ap) | Self::NeedsPar(_, ap) => *ap, + _ => Applicability::MachineApplicable, + } + } +} + +impl Display for RetReplacement<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Block => f.write_str("{}"), + Self::Unit => f.write_str("()"), + Self::NeedsPar(inner, _) => write!(f, "({inner})"), + Self::Expr(inner, _) => write!(f, "{inner}"), + } + } +} + +pub(super) fn check_fn<'tcx>(cx: &LateContext<'tcx>, kind: FnKind<'tcx>, body: &'tcx Body<'tcx>, sp: Span) { + if sp.from_expansion() { + return; + } + + match kind { + FnKind::Closure => { + // when returning without value in closure, replace this `return` + // with an empty block to prevent invalid suggestion (see #6501) + let replacement = if let ExprKind::Ret(None) = &body.value.kind { + RetReplacement::Block + } else { + RetReplacement::Empty + }; + check_final_expr(cx, body.value, vec![], replacement, None); + }, + FnKind::ItemFn(..) | FnKind::Method(..) => { + check_block_return(cx, &body.value.kind, sp, vec![]); + }, + } +} + +// if `expr` is a block, check if there are needless returns in it +fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, sp: Span, mut semi_spans: Vec) { + if let ExprKind::Block(block, _) = expr_kind { + if let Some(block_expr) = block.expr { + check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty, None); + } else if let Some(stmt) = block.stmts.last() { + if span_contains_cfg( + cx, + Span::between( + stmt.span, + cx.sess().source_map().end_point(block.span), // the closing brace of the block + ), + ) { + return; + } + match stmt.kind { + StmtKind::Expr(expr) => { + check_final_expr(cx, expr, semi_spans, RetReplacement::Empty, None); + }, + StmtKind::Semi(semi_expr) => { + // Remove ending semicolons and any whitespace ' ' in between. + // Without `return`, the suggestion might not compile if the semicolon is retained + if let Some(semi_span) = stmt.span.trim_start(semi_expr.span) { + let semi_span_to_remove = + span_find_starting_semi(cx.sess().source_map(), semi_span.with_hi(sp.hi())); + semi_spans.push(semi_span_to_remove); + } + check_final_expr(cx, semi_expr, semi_spans, RetReplacement::Empty, None); + }, + _ => (), + } + } + } +} + +fn check_final_expr<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + semi_spans: Vec, /* containing all the places where we would need to remove semicolons if finding an + * needless return */ + replacement: RetReplacement<'tcx>, + match_ty_opt: Option>, +) { + let peeled_drop_expr = expr.peel_drop_temps(); + match &peeled_drop_expr.kind { + // simple return is always "bad" + ExprKind::Ret(inner) => { + // check if expr return nothing + let ret_span = if inner.is_none() && replacement == RetReplacement::Empty { + extend_span_to_previous_non_ws(cx, peeled_drop_expr.span) + } else { + peeled_drop_expr.span + }; + + let replacement = if let Some(inner_expr) = inner { + // if desugar of `do yeet`, don't lint + if let ExprKind::Call(path_expr, [_]) = inner_expr.kind + && let ExprKind::Path(QPath::LangItem(LangItem::TryTraitFromYeet, ..)) = path_expr.kind + { + return; + } + + let mut applicability = Applicability::MachineApplicable; + let (snippet, _) = snippet_with_context(cx, inner_expr.span, ret_span.ctxt(), "..", &mut applicability); + if binary_expr_needs_parentheses(inner_expr) { + RetReplacement::NeedsPar(snippet, applicability) + } else { + RetReplacement::Expr(snippet, applicability) + } + } else { + match match_ty_opt { + Some(match_ty) => { + match match_ty.kind() { + // If the code got till here with + // tuple not getting detected before it, + // then we are sure it's going to be Unit + // type + ty::Tuple(_) => RetReplacement::Unit, + // We don't want to anything in this case + // cause we can't predict what the user would + // want here + _ => return, + } + }, + None => replacement, + } + }; + + if inner.is_some_and(|inner| leaks_droppable_temporary_with_limited_lifetime(cx, inner)) { + return; + } + + if ret_span.from_expansion() || is_from_proc_macro(cx, expr) { + return; + } + + // Returns may be used to turn an expression into a statement in rustc's AST. + // This allows the addition of attributes, like `#[allow]` (See: clippy#9361) + // `#[expect(clippy::needless_return)]` needs to be handled separately to + // actually fulfill the expectation (clippy::#12998) + match cx.tcx.hir_attrs(expr.hir_id) { + [] => {}, + [attr] => { + if matches!(Level::from_attr(attr), Some((Level::Expect, _))) + && let metas = attr.meta_item_list() + && let Some(lst) = metas + && let [MetaItemInner::MetaItem(meta_item), ..] = lst.as_slice() + && let [tool, lint_name] = meta_item.path.segments.as_slice() + && tool.ident.name == sym::clippy + && matches!( + lint_name.ident.name, + sym::needless_return | sym::style | sym::all | sym::warnings + ) + { + // This is an expectation of the `needless_return` lint + } else { + return; + } + }, + _ => return, + } + + emit_return_lint( + cx, + peeled_drop_expr.span, + ret_span, + semi_spans, + &replacement, + expr.hir_id, + ); + }, + ExprKind::If(_, then, else_clause_opt) => { + check_block_return(cx, &then.kind, peeled_drop_expr.span, semi_spans.clone()); + if let Some(else_clause) = else_clause_opt { + // The `RetReplacement` won't be used there as `else_clause` will be either a block or + // a `if` expression. + check_final_expr(cx, else_clause, semi_spans, RetReplacement::Empty, match_ty_opt); + } + }, + // a match expr, check all arms + // an if/if let expr, check both exprs + // note, if without else is going to be a type checking error anyways + // (except for unit type functions) so we don't match it + ExprKind::Match(_, arms, MatchSource::Normal) => { + let match_ty = cx.typeck_results().expr_ty(peeled_drop_expr); + for arm in *arms { + check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit, Some(match_ty)); + } + }, + // if it's a whole block, check it + other_expr_kind => check_block_return(cx, other_expr_kind, peeled_drop_expr.span, semi_spans), + } +} + +fn emit_return_lint( + cx: &LateContext<'_>, + lint_span: Span, + ret_span: Span, + semi_spans: Vec, + replacement: &RetReplacement<'_>, + at: HirId, +) { + span_lint_hir_and_then( + cx, + NEEDLESS_RETURN, + at, + lint_span, + "unneeded `return` statement", + |diag| { + let suggestions = std::iter::once((ret_span, replacement.to_string())) + .chain(semi_spans.into_iter().map(|span| (span, String::new()))) + .collect(); + + diag.multipart_suggestion_verbose(replacement.sugg_help(), suggestions, replacement.applicability()); + }, + ); +} + +// Go backwards while encountering whitespace and extend the given Span to that point. +fn extend_span_to_previous_non_ws(cx: &LateContext<'_>, sp: Span) -> Span { + if let Ok(prev_source) = cx.sess().source_map().span_to_prev_source(sp) { + let ws = [b' ', b'\t', b'\n']; + if let Some(non_ws_pos) = prev_source.bytes().rposition(|c| !ws.contains(&c)) { + let len = prev_source.len() - non_ws_pos - 1; + return sp.with_lo(sp.lo() - BytePos::from_usize(len)); + } + } + + sp +} diff --git a/clippy_lints/src/returns/needless_return_with_question_mark.rs b/clippy_lints/src/returns/needless_return_with_question_mark.rs new file mode 100644 index 0000000000000..c05038cd1e50e --- /dev/null +++ b/clippy_lints/src/returns/needless_return_with_question_mark.rs @@ -0,0 +1,60 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::{is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res}; +use rustc_errors::Applicability; +use rustc_hir::LangItem::ResultErr; +use rustc_hir::{ExprKind, HirId, ItemKind, MatchSource, Node, OwnerNode, Stmt, StmtKind}; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::ty::adjustment::Adjust; + +use super::NEEDLESS_RETURN_WITH_QUESTION_MARK; + +pub(super) fn check_stmt<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { + if !stmt.span.in_external_macro(cx.sess().source_map()) + && let StmtKind::Semi(expr) = stmt.kind + && let ExprKind::Ret(Some(ret)) = expr.kind + // return Err(...)? desugars to a match + // over a Err(...).branch() + // which breaks down to a branch call, with the callee being + // the constructor of the Err variant + && let ExprKind::Match(maybe_cons, _, MatchSource::TryDesugar(_)) = ret.kind + && let ExprKind::Call(_, [maybe_result_err]) = maybe_cons.kind + && let ExprKind::Call(maybe_constr, _) = maybe_result_err.kind + && is_res_lang_ctor(cx, path_res(cx, maybe_constr), ResultErr) + + // Ensure this is not the final stmt, otherwise removing it would cause a compile error + && let OwnerNode::Item(item) = cx.tcx.hir_owner_node(cx.tcx.hir_get_parent_item(expr.hir_id)) + && let ItemKind::Fn { body, .. } = item.kind + && let block = cx.tcx.hir_body(body).value + && let ExprKind::Block(block, _) = block.kind + && !is_inside_let_else(cx.tcx, expr) + && let [.., final_stmt] = block.stmts + && final_stmt.hir_id != stmt.hir_id + && !is_from_proc_macro(cx, expr) + && !stmt_needs_never_type(cx, stmt.hir_id) + { + span_lint_and_sugg( + cx, + NEEDLESS_RETURN_WITH_QUESTION_MARK, + expr.span.until(ret.span), + "unneeded `return` statement with `?` operator", + "remove it", + String::new(), + Applicability::MachineApplicable, + ); + } +} + +/// Checks if a return statement is "needed" in the middle of a block, or if it can be removed. +/// This is the case when the enclosing block expression is coerced to some other type, +/// which only works because of the never-ness of `return` expressions +fn stmt_needs_never_type(cx: &LateContext<'_>, stmt_hir_id: HirId) -> bool { + cx.tcx + .hir_parent_iter(stmt_hir_id) + .find_map(|(_, node)| if let Node::Expr(expr) = node { Some(expr) } else { None }) + .is_some_and(|e| { + cx.typeck_results() + .expr_adjustments(e) + .iter() + .any(|adjust| adjust.target != cx.tcx.types.unit && matches!(adjust.kind, Adjust::NeverToAny)) + }) +} diff --git a/clippy_lints/src/semicolon_block.rs b/clippy_lints/src/semicolon_block.rs index 1dea8f17c34be..371d62a068492 100644 --- a/clippy_lints/src/semicolon_block.rs +++ b/clippy_lints/src/semicolon_block.rs @@ -1,5 +1,6 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::SpanRangeExt; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -82,6 +83,19 @@ impl SemicolonBlock { let insert_span = tail.span.source_callsite().shrink_to_hi(); let remove_span = semi_span.with_lo(block.span.hi()); + // If the block is surrounded by parens (`({ 0 });`), the author probably knows what + // they're doing and why, so don't get in their way. + // + // This has the additional benefit of stopping the block being parsed as a function call: + // ``` + // fn foo() { + // ({ 0 }); // if we remove this `;`, this will parse as a `({ 0 })(5);` function call + // (5); + // } + if remove_span.check_source_text(cx, |src| src.contains(')')) { + return; + } + if self.semicolon_inside_block_ignore_singleline && get_line(cx, remove_span) == get_line(cx, insert_span) { return; } diff --git a/clippy_lints/src/size_of_ref.rs b/clippy_lints/src/size_of_ref.rs index 60d923bcd77e7..606e852aae9e3 100644 --- a/clippy_lints/src/size_of_ref.rs +++ b/clippy_lints/src/size_of_ref.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::{path_def_id, peel_middle_ty_refs}; +use clippy_utils::path_def_id; +use clippy_utils::ty::peel_and_count_ty_refs; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -59,7 +60,7 @@ impl LateLintPass<'_> for SizeOfRef { && let Some(def_id) = path_def_id(cx, path) && cx.tcx.is_diagnostic_item(sym::mem_size_of_val, def_id) && let arg_ty = cx.typeck_results().expr_ty(arg) - && peel_middle_ty_refs(arg_ty).1 > 1 + && peel_and_count_ty_refs(arg_ty).1 > 1 { span_lint_and_help( cx, diff --git a/clippy_lints/src/toplevel_ref_arg.rs b/clippy_lints/src/toplevel_ref_arg.rs new file mode 100644 index 0000000000000..074b79263d377 --- /dev/null +++ b/clippy_lints/src/toplevel_ref_arg.rs @@ -0,0 +1,119 @@ +use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; +use clippy_utils::source::{snippet, snippet_with_context}; +use clippy_utils::sugg::Sugg; +use clippy_utils::{is_lint_allowed, iter_input_pats}; +use rustc_errors::Applicability; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{BindingMode, Body, ByRef, FnDecl, Mutability, PatKind, Stmt, StmtKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; +use rustc_span::Span; +use rustc_span::def_id::LocalDefId; + +use crate::ref_patterns::REF_PATTERNS; + +declare_clippy_lint! { + /// ### What it does + /// Checks for function arguments and let bindings denoted as + /// `ref`. + /// + /// ### Why is this bad? + /// The `ref` declaration makes the function take an owned + /// value, but turns the argument into a reference (which means that the value + /// is destroyed when exiting the function). This adds not much value: either + /// take a reference type, or take an owned value and create references in the + /// body. + /// + /// For let bindings, `let x = &foo;` is preferred over `let ref x = foo`. The + /// type of `x` is more obvious with the former. + /// + /// ### Known problems + /// If the argument is dereferenced within the function, + /// removing the `ref` will lead to errors. This can be fixed by removing the + /// dereferences, e.g., changing `*x` to `x` within the function. + /// + /// ### Example + /// ```no_run + /// fn foo(ref _x: u8) {} + /// ``` + /// + /// Use instead: + /// ```no_run + /// fn foo(_x: &u8) {} + /// ``` + #[clippy::version = "pre 1.29.0"] + pub TOPLEVEL_REF_ARG, + style, + "an entire binding declared as `ref`, in a function argument or a `let` statement" +} + +declare_lint_pass!(ToplevelRefArg => [TOPLEVEL_REF_ARG]); + +impl<'tcx> LateLintPass<'tcx> for ToplevelRefArg { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + k: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body: &'tcx Body<'_>, + _: Span, + _: LocalDefId, + ) { + if !matches!(k, FnKind::Closure) { + for arg in iter_input_pats(decl, body) { + if let PatKind::Binding(BindingMode(ByRef::Yes(_), _), ..) = arg.pat.kind + && is_lint_allowed(cx, REF_PATTERNS, arg.pat.hir_id) + && !arg.span.in_external_macro(cx.tcx.sess.source_map()) + { + span_lint_hir( + cx, + TOPLEVEL_REF_ARG, + arg.hir_id, + arg.pat.span, + "`ref` directly on a function parameter does not prevent taking ownership of the passed argument. \ + Consider using a reference type instead", + ); + } + } + } + } + + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { + if let StmtKind::Let(local) = stmt.kind + && let PatKind::Binding(BindingMode(ByRef::Yes(mutabl), _), .., name, None) = local.pat.kind + && let Some(init) = local.init + // Do not emit if clippy::ref_patterns is not allowed to avoid having two lints for the same issue. + && is_lint_allowed(cx, REF_PATTERNS, local.pat.hir_id) + && !stmt.span.in_external_macro(cx.tcx.sess.source_map()) + { + let ctxt = local.span.ctxt(); + let mut app = Applicability::MachineApplicable; + let sugg_init = Sugg::hir_with_context(cx, init, ctxt, "..", &mut app); + let (mutopt, initref) = match mutabl { + Mutability::Mut => ("mut ", sugg_init.mut_addr()), + Mutability::Not => ("", sugg_init.addr()), + }; + let tyopt = if let Some(ty) = local.ty { + let ty_snip = snippet_with_context(cx, ty.span, ctxt, "_", &mut app).0; + format!(": &{mutopt}{ty_snip}") + } else { + String::new() + }; + span_lint_hir_and_then( + cx, + TOPLEVEL_REF_ARG, + init.hir_id, + local.pat.span, + "`ref` on an entire `let` pattern is discouraged, take a reference with `&` instead", + |diag| { + diag.span_suggestion( + stmt.span, + "try", + format!("let {name}{tyopt} = {initref};", name = snippet(cx, name.span, ".."),), + app, + ); + }, + ); + } + } +} diff --git a/clippy_lints/src/transmute/transmute_ptr_to_ref.rs b/clippy_lints/src/transmute/transmute_ptr_to_ref.rs index e58212fae15cf..e67ab6a73d269 100644 --- a/clippy_lints/src/transmute/transmute_ptr_to_ref.rs +++ b/clippy_lints/src/transmute/transmute_ptr_to_ref.rs @@ -28,16 +28,27 @@ pub(super) fn check<'tcx>( format!("transmute from a pointer type (`{from_ty}`) to a reference type (`{to_ty}`)"), |diag| { let arg = sugg::Sugg::hir(cx, arg, ".."); - let (deref, cast) = if *mutbl == Mutability::Mut { - ("&mut *", "*mut") - } else { - ("&*", "*const") + let (deref, cast) = match mutbl { + Mutability::Mut => ("&mut *", "*mut"), + Mutability::Not => ("&*", "*const"), }; let mut app = Applicability::MachineApplicable; let sugg = if let Some(ty) = get_explicit_type(path) { let ty_snip = snippet_with_applicability(cx, ty.span, "..", &mut app); - if msrv.meets(cx, msrvs::POINTER_CAST) { + if !to_ref_ty.is_sized(cx.tcx, cx.typing_env()) { + // We can't suggest `.cast()`, because that requires `to_ref_ty` to be Sized. + if from_ptr_ty.has_erased_regions() { + // We can't suggest `as *mut/const () as *mut/const to_ref_ty`, because the former is a + // thin pointer, whereas the latter is a wide pointer, due of its pointee, `to_ref_ty`, + // being !Sized. + // + // The only remaining option is be to skip `*mut/const ()`, but that might not be safe + // to do because of the erased regions in `from_ptr_ty`, so reduce the applicability. + app = Applicability::MaybeIncorrect; + } + sugg::make_unop(deref, arg.as_ty(format!("{cast} {ty_snip}"))).to_string() + } else if msrv.meets(cx, msrvs::POINTER_CAST) { format!("{deref}{}.cast::<{ty_snip}>()", arg.maybe_paren()) } else if from_ptr_ty.has_erased_regions() { sugg::make_unop(deref, arg.as_ty(format!("{cast} () as {cast} {ty_snip}"))).to_string() @@ -45,15 +56,22 @@ pub(super) fn check<'tcx>( sugg::make_unop(deref, arg.as_ty(format!("{cast} {ty_snip}"))).to_string() } } else if *from_ptr_ty == *to_ref_ty { - if from_ptr_ty.has_erased_regions() { - if msrv.meets(cx, msrvs::POINTER_CAST) { - format!("{deref}{}.cast::<{to_ref_ty}>()", arg.maybe_paren()) - } else { - sugg::make_unop(deref, arg.as_ty(format!("{cast} () as {cast} {to_ref_ty}"))) - .to_string() - } - } else { + if !from_ptr_ty.has_erased_regions() { sugg::make_unop(deref, arg).to_string() + } else if !to_ref_ty.is_sized(cx.tcx, cx.typing_env()) { + // 1. We can't suggest `.cast()`, because that requires `to_ref_ty` to be Sized. + // 2. We can't suggest `as *mut/const () as *mut/const to_ref_ty`, because the former is a + // thin pointer, whereas the latter is a wide pointer, due of its pointee, `to_ref_ty`, + // being !Sized. + // + // The only remaining option is be to skip `*mut/const ()`, but that might not be safe to do + // because of the erased regions in `from_ptr_ty`, so reduce the applicability. + app = Applicability::MaybeIncorrect; + sugg::make_unop(deref, arg.as_ty(format!("{cast} {to_ref_ty}"))).to_string() + } else if msrv.meets(cx, msrvs::POINTER_CAST) { + format!("{deref}{}.cast::<{to_ref_ty}>()", arg.maybe_paren()) + } else { + sugg::make_unop(deref, arg.as_ty(format!("{cast} () as {cast} {to_ref_ty}"))).to_string() } } else { sugg::make_unop(deref, arg.as_ty(format!("{cast} {to_ref_ty}"))).to_string() diff --git a/clippy_lints/src/transmute/transmute_ref_to_ref.rs b/clippy_lints/src/transmute/transmute_ref_to_ref.rs index 6aeb22d41a7d7..70c2a73ce6ef2 100644 --- a/clippy_lints/src/transmute/transmute_ref_to_ref.rs +++ b/clippy_lints/src/transmute/transmute_ref_to_ref.rs @@ -45,7 +45,9 @@ pub(super) fn check<'tcx>( Applicability::MaybeIncorrect, ); triggered = true; - } else if (cx.tcx.erase_and_anonymize_regions(from_ty) != cx.tcx.erase_and_anonymize_regions(to_ty)) && !const_context { + } else if (cx.tcx.erase_and_anonymize_regions(from_ty) != cx.tcx.erase_and_anonymize_regions(to_ty)) + && !const_context + { span_lint_and_then( cx, TRANSMUTE_PTR_TO_PTR, diff --git a/clippy_lints/src/uninit_vec.rs b/clippy_lints/src/uninit_vec.rs index cee4a53f03cbe..51116b5eba9e1 100644 --- a/clippy_lints/src/uninit_vec.rs +++ b/clippy_lints/src/uninit_vec.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; +use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; use clippy_utils::higher::{VecInitKind, get_vec_init_kind}; use clippy_utils::ty::{is_type_diagnostic_item, is_uninit_value_valid_for_ty}; use clippy_utils::{SpanlessEq, is_integer_literal, is_lint_allowed, path_to_local_id, peel_hir_expr_while, sym}; @@ -95,16 +95,13 @@ fn handle_uninit_vec_pair<'tcx>( // Check T of Vec if !is_uninit_value_valid_for_ty(cx, args.type_at(0)) { - // FIXME: #7698, false positive of the internal lints - #[expect(clippy::collapsible_span_lint_calls)] - span_lint_and_then( + span_lint_and_help( cx, UNINIT_VEC, vec![call_span, maybe_init_or_reserve.span], "calling `set_len()` immediately after reserving a buffer creates uninitialized values", - |diag| { - diag.help("initialize the buffer or wrap the content in `MaybeUninit`"); - }, + None, + "initialize the buffer or wrap the content in `MaybeUninit`", ); } } else { diff --git a/clippy_lints/src/unused_peekable.rs b/clippy_lints/src/unused_peekable.rs index 1f5351e32aacf..5224b62e9fc71 100644 --- a/clippy_lints/src/unused_peekable.rs +++ b/clippy_lints/src/unused_peekable.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable}; +use clippy_utils::ty::{is_type_diagnostic_item, peel_and_count_ty_refs}; use clippy_utils::{fn_def_id, is_trait_method, path_to_local_id, peel_ref_operators, sym}; use rustc_ast::Mutability; use rustc_hir::intravisit::{Visitor, walk_expr}; @@ -61,7 +61,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedPeekable { && let Some(init) = local.init && !init.span.from_expansion() && let Some(ty) = cx.typeck_results().expr_ty_opt(init) - && let (ty, _, Mutability::Mut) = peel_mid_ty_refs_is_mutable(ty) + && let (ty, _, None | Some(Mutability::Mut)) = peel_and_count_ty_refs(ty) && is_type_diagnostic_item(cx, ty, sym::IterPeekable) { let mut vis = PeekableVisitor::new(cx, binding); @@ -211,7 +211,7 @@ impl<'tcx> Visitor<'tcx> for PeekableVisitor<'_, 'tcx> { fn arg_is_mut_peekable(cx: &LateContext<'_>, arg: &Expr<'_>) -> bool { if let Some(ty) = cx.typeck_results().expr_ty_opt(arg) - && let (ty, _, Mutability::Mut) = peel_mid_ty_refs_is_mutable(ty) + && let (ty, _, None | Some(Mutability::Mut)) = peel_and_count_ty_refs(ty) && is_type_diagnostic_item(cx, ty, sym::IterPeekable) { true diff --git a/clippy_lints/src/unwrap.rs b/clippy_lints/src/unwrap.rs index 490da4f1e037a..34dfe5b6546fa 100644 --- a/clippy_lints/src/unwrap.rs +++ b/clippy_lints/src/unwrap.rs @@ -292,6 +292,7 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { // Shouldn't lint when `expr` is in macro. if expr.span.in_external_macro(self.cx.tcx.sess.source_map()) { + walk_expr(self, expr); return; } // Skip checking inside closures since they are visited through `Unwrap::check_fn()` already. diff --git a/clippy_lints/src/use_self.rs b/clippy_lints/src/use_self.rs index 8252e6d48694b..9d5be922f43f8 100644 --- a/clippy_lints/src/use_self.rs +++ b/clippy_lints/src/use_self.rs @@ -2,20 +2,21 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::is_from_proc_macro; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::ty::{same_type_and_consts, ty_from_hir_ty}; +use clippy_utils::ty::{same_type_modulo_regions, ty_from_hir_ty}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::def::{CtorOf, DefKind, Res}; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::{InferKind, Visitor, VisitorExt, walk_ty}; use rustc_hir::{ - self as hir, AmbigArg, Expr, ExprKind, FnRetTy, FnSig, GenericArgsParentheses, GenericParam, GenericParamKind, - HirId, Impl, ImplItemKind, Item, ItemKind, Pat, PatExpr, PatExprKind, PatKind, Path, QPath, Ty, TyKind, + self as hir, AmbigArg, Expr, ExprKind, FnRetTy, FnSig, GenericArgsParentheses, GenericParamKind, HirId, Impl, + ImplItemKind, Item, ItemKind, Pat, PatExpr, PatExprKind, PatKind, Path, QPath, Ty, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::Ty as MiddleTy; use rustc_session::impl_lint_pass; use rustc_span::Span; +use std::iter; declare_clippy_lint! { /// ### What it does @@ -101,17 +102,11 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { let types_to_skip = generics .params .iter() - .filter_map(|param| match param { - GenericParam { - kind: - GenericParamKind::Const { - ty: Ty { hir_id, .. }, .. - }, - .. - } => Some(*hir_id), + .filter_map(|param| match param.kind { + GenericParamKind::Const { ty, .. } => Some(ty.hir_id), _ => None, }) - .chain(std::iter::once(self_ty.hir_id)) + .chain([self_ty.hir_id]) .collect(); StackItem::Check { impl_id: item.owner_id.def_id, @@ -209,11 +204,11 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { && !types_to_skip.contains(&hir_ty.hir_id) && let ty = ty_from_hir_ty(cx, hir_ty.as_unambig_ty()) && let impl_ty = cx.tcx.type_of(impl_id).instantiate_identity() - && same_type_and_consts(ty, impl_ty) + && same_type_modulo_regions(ty, impl_ty) // Ensure the type we encounter and the one from the impl have the same lifetime parameters. It may be that - // the lifetime parameters of `ty` are elided (`impl<'a> Foo<'a> { fn new() -> Self { Foo{..} } }`, in + // the lifetime parameters of `ty` are elided (`impl<'a> Foo<'a> { fn new() -> Self { Foo{..} } }`), in // which case we must still trigger the lint. - && (has_no_lifetime(ty) || same_lifetimes(ty, impl_ty)) + && same_lifetimes(ty, impl_ty) && self.msrv.meets(cx, msrvs::TYPE_ALIAS_ENUM_VARIANTS) { span_lint(cx, hir_ty.span); @@ -226,18 +221,16 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { && cx.typeck_results().expr_ty(expr) == cx.tcx.type_of(impl_id).instantiate_identity() && self.msrv.meets(cx, msrvs::TYPE_ALIAS_ENUM_VARIANTS) { - } else { - return; - } - match expr.kind { - ExprKind::Struct(QPath::Resolved(_, path), ..) => check_path(cx, path), - ExprKind::Call(fun, _) => { - if let ExprKind::Path(QPath::Resolved(_, path)) = fun.kind { - check_path(cx, path); - } - }, - ExprKind::Path(QPath::Resolved(_, path)) => check_path(cx, path), - _ => (), + match expr.kind { + ExprKind::Struct(QPath::Resolved(_, path), ..) => check_path(cx, path), + ExprKind::Call(fun, _) => { + if let ExprKind::Path(QPath::Resolved(_, path)) = fun.kind { + check_path(cx, path); + } + }, + ExprKind::Path(QPath::Resolved(_, path)) => check_path(cx, path), + _ => (), + } } } @@ -307,36 +300,20 @@ fn lint_path_to_variant(cx: &LateContext<'_>, path: &Path<'_>) { } } -/// Returns `true` if types `a` and `b` have the same lifetime parameters, otherwise returns -/// `false`. +/// Checks whether types `a` and `b` have the same lifetime parameters. /// /// This function does not check that types `a` and `b` are the same types. fn same_lifetimes<'tcx>(a: MiddleTy<'tcx>, b: MiddleTy<'tcx>) -> bool { use rustc_middle::ty::{Adt, GenericArgKind}; - match (&a.kind(), &b.kind()) { - (&Adt(_, args_a), &Adt(_, args_b)) => { - args_a - .iter() - .zip(args_b.iter()) - .all(|(arg_a, arg_b)| match (arg_a.kind(), arg_b.kind()) { - // TODO: Handle inferred lifetimes - (GenericArgKind::Lifetime(inner_a), GenericArgKind::Lifetime(inner_b)) => inner_a == inner_b, - (GenericArgKind::Type(type_a), GenericArgKind::Type(type_b)) => same_lifetimes(type_a, type_b), - _ => true, - }) + match (a.kind(), b.kind()) { + (Adt(_, args_a), Adt(_, args_b)) => { + iter::zip(*args_a, *args_b).all(|(arg_a, arg_b)| match (arg_a.kind(), arg_b.kind()) { + // TODO: Handle inferred lifetimes + (GenericArgKind::Lifetime(inner_a), GenericArgKind::Lifetime(inner_b)) => inner_a == inner_b, + (GenericArgKind::Type(type_a), GenericArgKind::Type(type_b)) => same_lifetimes(type_a, type_b), + _ => true, + }) }, _ => a == b, } } - -/// Returns `true` if `ty` has no lifetime parameter, otherwise returns `false`. -fn has_no_lifetime(ty: MiddleTy<'_>) -> bool { - use rustc_middle::ty::{Adt, GenericArgKind}; - match ty.kind() { - &Adt(_, args) => !args - .iter() - // TODO: Handle inferred lifetimes - .any(|arg| matches!(arg.kind(), GenericArgKind::Lifetime(..))), - _ => true, - } -} diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index e45f884cfcbcf..1b137017ecb46 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::{snippet, snippet_with_context}; use clippy_utils::sugg::{DiagExt as _, Sugg}; -use clippy_utils::ty::{get_type_diagnostic_name, is_copy, is_type_diagnostic_item, same_type_and_consts}; +use clippy_utils::ty::{get_type_diagnostic_name, is_copy, is_type_diagnostic_item, same_type_modulo_regions}; use clippy_utils::{ get_parent_expr, is_inherent_method_call, is_trait_item, is_trait_method, is_ty_alias, path_to_local, sym, }; @@ -184,7 +184,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { && (is_trait_item(cx, arg, sym::Into) || is_trait_item(cx, arg, sym::From)) && let ty::FnDef(_, args) = cx.typeck_results().expr_ty(arg).kind() && let &[from_ty, to_ty] = args.into_type_list(cx.tcx).as_slice() - && same_type_and_consts(from_ty, to_ty) + && same_type_modulo_regions(from_ty, to_ty) { span_lint_and_then( cx, @@ -207,7 +207,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { if is_trait_method(cx, e, sym::Into) && name.ident.name == sym::into { let a = cx.typeck_results().expr_ty(e); let b = cx.typeck_results().expr_ty(recv); - if same_type_and_consts(a, b) { + if same_type_modulo_regions(a, b) { let mut app = Applicability::MachineApplicable; let sugg = snippet_with_context(cx, recv.span, e.span.ctxt(), "", &mut app).0; span_lint_and_sugg( @@ -324,7 +324,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { // If the types are identical then .into_iter() can be removed, unless the type // implements Copy, in which case .into_iter() returns a copy of the receiver and // cannot be safely omitted. - if same_type_and_consts(a, b) && !is_copy(cx, b) { + if same_type_modulo_regions(a, b) && !is_copy(cx, b) { // Below we check if the parent method call meets the following conditions: // 1. First parameter is `&mut self` (requires mutable reference) // 2. Second parameter implements the `FnMut` trait (e.g., Iterator::any) @@ -371,7 +371,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { && is_type_diagnostic_item(cx, a, sym::Result) && let ty::Adt(_, args) = a.kind() && let Some(a_type) = args.types().next() - && same_type_and_consts(a_type, b) + && same_type_modulo_regions(a_type, b) { span_lint_and_help( cx, @@ -396,7 +396,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { && is_type_diagnostic_item(cx, a, sym::Result) && let ty::Adt(_, args) = a.kind() && let Some(a_type) = args.types().next() - && same_type_and_consts(a_type, b) + && same_type_modulo_regions(a_type, b) { let hint = format!("consider removing `{}()`", snippet(cx, path.span, "TryFrom::try_from")); span_lint_and_help( @@ -407,7 +407,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { None, hint, ); - } else if name == sym::from_fn && same_type_and_consts(a, b) { + } else if name == sym::from_fn && same_type_modulo_regions(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")); diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml index bdf7431f29f29..d58b47bf6deb3 100644 --- a/clippy_utils/Cargo.toml +++ b/clippy_utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_utils" -version = "0.1.91" +version = "0.1.92" edition = "2024" description = "Helpful tools for writing lints, provided as they are used in Clippy" repository = "https://github.com/rust-lang/rust-clippy" diff --git a/clippy_utils/README.md b/clippy_utils/README.md index e01f563c49e73..2c66fdc73f539 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-09-04 +nightly-2025-09-18 ``` diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs index 1a25c90d735e4..948a7203402d5 100644 --- a/clippy_utils/src/check_proc_macro.rs +++ b/clippy_utils/src/check_proc_macro.rs @@ -13,20 +13,24 @@ //! if the span is not from a `macro_rules` based macro. use rustc_abi::ExternAbi; +use rustc_ast as ast; use rustc_ast::AttrStyle; -use rustc_ast::ast::{AttrKind, Attribute, IntTy, LitIntType, LitKind, StrStyle, TraitObjectSyntax, UintTy}; +use rustc_ast::ast::{ + AttrKind, Attribute, GenericArgs, IntTy, LitIntType, LitKind, StrStyle, TraitObjectSyntax, UintTy, +}; 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, ImplItemImplKind, ImplItemKind, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, Path, QPath, Safety, - TraitImplHeader, TraitItem, TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, YieldSource, + ImplItem, ImplItemImplKind, 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; use rustc_session::Session; use rustc_span::symbol::{Ident, kw}; -use rustc_span::{Span, Symbol}; +use rustc_span::{Span, Symbol, sym}; /// The search pattern to look for. Used by `span_matches_pat` #[derive(Clone)] @@ -289,7 +293,7 @@ fn impl_item_search_pat(item: &ImplItem<'_>) -> (Pat, Pat) { && !vis_span.is_empty() { start_pat = Pat::Str("pub"); - }; + } (start_pat, end_pat) } @@ -321,14 +325,17 @@ fn fn_kind_pat(tcx: TyCtxt<'_>, kind: &FnKind<'_>, body: &Body<'_>, hir_id: HirI }; match tcx.hir_node(hir_id) { Node::Item(Item { vis_span, .. }) - | Node::ImplItem(ImplItem { impl_kind: ImplItemImplKind::Inherent { vis_span, .. }, .. }) => { + | Node::ImplItem(ImplItem { + impl_kind: ImplItemImplKind::Inherent { vis_span, .. }, + .. + }) => { if !vis_span.is_empty() { - start_pat = Pat::Str("pub") + start_pat = Pat::Str("pub"); } }, Node::ImplItem(_) | Node::TraitItem(_) => {}, _ => start_pat = Pat::Str(""), - }; + } (start_pat, end_pat) } @@ -403,6 +410,7 @@ fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) { TyKind::OpaqueDef(..) => (Pat::Str("impl"), Pat::Str("")), TyKind::Path(qpath) => qpath_search_pat(&qpath), TyKind::Infer(()) => (Pat::Str("_"), Pat::Str("_")), + TyKind::UnsafeBinder(binder_ty) => (Pat::Str("unsafe"), ty_search_pat(binder_ty.inner_ty).1), TyKind::TraitObject(_, tagged_ptr) if let TraitObjectSyntax::Dyn = tagged_ptr.tag() => { (Pat::Str("dyn"), Pat::Str("")) }, @@ -411,6 +419,127 @@ fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) { } } +fn ast_ty_search_pat(ty: &ast::Ty) -> (Pat, Pat) { + use ast::{Extern, FnRetTy, MutTy, Safety, TraitObjectSyntax, TyKind}; + + match &ty.kind { + TyKind::Slice(..) | TyKind::Array(..) => (Pat::Str("["), Pat::Str("]")), + TyKind::Ptr(MutTy { ty, .. }) => (Pat::Str("*"), ast_ty_search_pat(ty).1), + TyKind::Ref(_, MutTy { ty, .. }) | TyKind::PinnedRef(_, MutTy { ty, .. }) => { + (Pat::Str("&"), ast_ty_search_pat(ty).1) + }, + TyKind::FnPtr(fn_ptr) => ( + if let Safety::Unsafe(_) = fn_ptr.safety { + Pat::Str("unsafe") + } else if let Extern::Explicit(strlit, _) = fn_ptr.ext + && strlit.symbol == sym::rust + { + Pat::MultiStr(&["fn", "extern"]) + } else { + Pat::Str("extern") + }, + match &fn_ptr.decl.output { + FnRetTy::Default(_) => { + if let [.., param] = &*fn_ptr.decl.inputs { + ast_ty_search_pat(¶m.ty).1 + } else { + Pat::Str("(") + } + }, + FnRetTy::Ty(ty) => ast_ty_search_pat(ty).1, + }, + ), + TyKind::Never => (Pat::Str("!"), Pat::Str("!")), + // Parenthesis are trimmed from the text before the search patterns are matched. + // See: `span_matches_pat` + TyKind::Tup(tup) => match &**tup { + [] => (Pat::Str(")"), Pat::Str("(")), + [ty] => ast_ty_search_pat(ty), + [head, .., tail] => (ast_ty_search_pat(head).0, ast_ty_search_pat(tail).1), + }, + TyKind::ImplTrait(..) => (Pat::Str("impl"), Pat::Str("")), + TyKind::Path(qself_path, path) => { + let start = if qself_path.is_some() { + Pat::Str("<") + } else if let Some(first) = path.segments.first() { + ident_search_pat(first.ident).0 + } else { + // this shouldn't be possible, but sure + Pat::Str("") + }; + let end = if let Some(last) = path.segments.last() { + match last.args.as_deref() { + // last `>` in `std::foo::Bar` + Some(GenericArgs::AngleBracketed(_)) => Pat::Str(">"), + Some(GenericArgs::Parenthesized(par_args)) => match &par_args.output { + FnRetTy::Default(_) => { + if let Some(last) = par_args.inputs.last() { + // `B` in `(A, B)` -- `)` gets stripped + ast_ty_search_pat(last).1 + } else { + // `(` in `()` -- `)` gets stripped + Pat::Str("(") + } + }, + // `C` in `(A, B) -> C` + FnRetTy::Ty(ty) => ast_ty_search_pat(ty).1, + }, + // last `..` in `(..)` -- `)` gets stripped + Some(GenericArgs::ParenthesizedElided(_)) => Pat::Str(".."), + // `bar` in `std::foo::bar` + None => ident_search_pat(last.ident).1, + } + } else { + // this shouldn't be possible, but sure + #[allow( + clippy::collapsible_else_if, + reason = "we want to keep these cases together, since they are both impossible" + )] + if qself_path.is_some() { + // last `>` in `` + Pat::Str(">") + } else { + Pat::Str("") + } + }; + (start, end) + }, + TyKind::Infer => (Pat::Str("_"), Pat::Str("_")), + TyKind::Paren(ty) => ast_ty_search_pat(ty), + TyKind::UnsafeBinder(binder_ty) => (Pat::Str("unsafe"), ast_ty_search_pat(&binder_ty.inner_ty).1), + TyKind::TraitObject(_, trait_obj_syntax) => { + if let TraitObjectSyntax::Dyn = trait_obj_syntax { + (Pat::Str("dyn"), Pat::Str("")) + } else { + // NOTE: `TraitObject` is incomplete. It will always return true then. + (Pat::Str(""), Pat::Str("")) + } + }, + TyKind::MacCall(mac_call) => { + let start = if let Some(first) = mac_call.path.segments.first() { + ident_search_pat(first.ident).0 + } else { + Pat::Str("") + }; + (start, Pat::Str("")) + }, + + // implicit, so has no contents to match against + TyKind::ImplicitSelf + + // experimental + |TyKind::Pat(..) + + // unused + | TyKind::CVarArgs + | TyKind::Typeof(_) + + // placeholder + | TyKind::Dummy + | TyKind::Err(_) => (Pat::Str(""), Pat::Str("")), + } +} + fn ident_search_pat(ident: Ident) -> (Pat, Pat) { (Pat::Sym(ident.name), Pat::Sym(ident.name)) } @@ -445,6 +574,7 @@ impl_with_search_pat!((_cx: LateContext<'tcx>, self: Lit) => lit_search_pat(&sel impl_with_search_pat!((_cx: LateContext<'tcx>, self: Path<'_>) => path_search_pat(self)); impl_with_search_pat!((_cx: EarlyContext<'tcx>, self: Attribute) => attr_search_pat(self)); +impl_with_search_pat!((_cx: EarlyContext<'tcx>, self: ast::Ty) => ast_ty_search_pat(self)); impl<'cx> WithSearchPat<'cx> for (&FnKind<'cx>, &Body<'cx>, HirId, Span) { type Context = LateContext<'cx>; diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 120ab2e717db8..feadc0ecf659a 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -329,13 +329,17 @@ pub fn is_wild(pat: &Pat<'_>) -> bool { matches!(pat.kind, PatKind::Wild) } -// Checks if arm has the form `None => None` -pub fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { - matches!( - arm.pat.kind, +/// Checks if the `pat` is `None`. +pub fn is_none_pattern(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool { + matches!(pat.kind, PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), .. }) - if is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), OptionNone) - ) + if is_res_lang_ctor(cx, cx.qpath_res(qpath, pat.hir_id), OptionNone)) +} + +/// Checks if `arm` has the form `None => None`. +pub fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { + is_none_pattern(cx, arm.pat) + && matches!(peel_blocks(arm.body).kind, ExprKind::Path(qpath) if is_res_lang_ctor(cx, cx.qpath_res(&qpath, arm.body.hir_id), OptionNone)) } /// Checks if the given `QPath` belongs to a type alias. @@ -2374,15 +2378,12 @@ pub fn peel_hir_ty_refs<'a>(mut ty: &'a hir::Ty<'a>) -> (&'a hir::Ty<'a>, usize) } } -/// Peels off all references on the type. Returns the underlying type and the number of references -/// removed. -pub fn peel_middle_ty_refs(mut ty: Ty<'_>) -> (Ty<'_>, usize) { - let mut count = 0; - while let rustc_ty::Ref(_, dest_ty, _) = ty.kind() { - ty = *dest_ty; - count += 1; +/// Returns the base type for HIR references and pointers. +pub fn peel_hir_ty_refs_and_ptrs<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> { + match &ty.kind { + TyKind::Ptr(mut_ty) | TyKind::Ref(_, mut_ty) => peel_hir_ty_refs_and_ptrs(mut_ty.ty), + _ => ty, } - (ty, count) } /// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index 896d607fbcdd9..6e07ed9ffcc4d 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -24,7 +24,7 @@ macro_rules! msrv_aliases { // names may refer to stabilized feature flags or library items msrv_aliases! { 1,88,0 { LET_CHAINS } - 1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT, UNSIGNED_IS_MULTIPLE_OF } + 1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT, UNSIGNED_IS_MULTIPLE_OF, INTEGER_SIGN_CAST } 1,85,0 { UINT_FLOAT_MIDPOINT, CONST_SIZE_OF_VAL } 1,84,0 { CONST_OPTION_AS_SLICE, MANUAL_DANGLING_PTR } 1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP } @@ -73,7 +73,7 @@ msrv_aliases! { 1,29,0 { ITER_FLATTEN } 1,28,0 { FROM_BOOL, REPEAT_WITH, SLICE_FROM_REF } 1,27,0 { ITERATOR_TRY_FOLD } - 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN } + 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN, POINTER_ADD_SUB_METHODS } 1,24,0 { IS_ASCII_DIGIT, PTR_NULL } 1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN } 1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR } diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index e675291b6f3a7..638d329031231 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -215,6 +215,11 @@ impl fmt::Display for SourceText { self.as_str().fmt(f) } } +impl fmt::Debug for SourceText { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_str().fmt(f) + } +} fn get_source_range(sm: &SourceMap, sp: Range) -> Option { let start = sm.lookup_byte_offset(sp.start); diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index 8033b74a8d25c..7530d3bc7157d 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -125,6 +125,7 @@ generate! { cycle, cyclomatic_complexity, de, + deprecated_in_future, diagnostics, disallowed_types, drain, diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index 3e41bce1dc4ce..e4bc3b7682942 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -11,7 +11,7 @@ use rustc_hir as hir; use rustc_hir::attrs::AttributeKind; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; use rustc_hir::def_id::DefId; -use rustc_hir::{Expr, FnDecl, LangItem, TyKind, find_attr}; +use rustc_hir::{Expr, FnDecl, LangItem, find_attr}; use rustc_hir_analysis::lower_ty; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; @@ -43,13 +43,8 @@ pub use type_certainty::expr_type_is_certain; /// Lower a [`hir::Ty`] to a [`rustc_middle::ty::Ty`]. pub fn ty_from_hir_ty<'tcx>(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'tcx>) -> Ty<'tcx> { cx.maybe_typeck_results() - .and_then(|results| { - if results.hir_owner == hir_ty.hir_id.owner { - results.node_type_opt(hir_ty.hir_id) - } else { - None - } - }) + .filter(|results| results.hir_owner == hir_ty.hir_id.owner) + .and_then(|results| results.node_type_opt(hir_ty.hir_id)) .unwrap_or_else(|| lower_ty(cx.tcx, hir_ty)) } @@ -475,63 +470,50 @@ pub fn needs_ordered_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { needs_ordered_drop_inner(cx, ty, &mut FxHashSet::default()) } -/// Peels off all references on the type. Returns the underlying type, the number of references -/// removed, and whether the pointer is ultimately mutable or not. -pub fn peel_mid_ty_refs_is_mutable(ty: Ty<'_>) -> (Ty<'_>, usize, Mutability) { - fn f(ty: Ty<'_>, count: usize, mutability: Mutability) -> (Ty<'_>, usize, Mutability) { - match ty.kind() { - ty::Ref(_, ty, Mutability::Mut) => f(*ty, count + 1, mutability), - ty::Ref(_, ty, Mutability::Not) => f(*ty, count + 1, Mutability::Not), - _ => (ty, count, mutability), - } - } - f(ty, 0, Mutability::Mut) -} - -/// Returns `true` if the given type is an `unsafe` function. -pub fn type_is_unsafe_function<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { +/// Returns `true` if `ty` denotes an `unsafe fn`. +pub fn is_unsafe_fn<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { ty.is_fn() && ty.fn_sig(cx.tcx).safety().is_unsafe() } -/// Returns the base type for HIR references and pointers. -pub fn walk_ptrs_hir_ty<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> { - match ty.kind { - TyKind::Ptr(ref mut_ty) | TyKind::Ref(_, ref mut_ty) => walk_ptrs_hir_ty(mut_ty.ty), - _ => ty, - } -} - -/// Returns the base type for references and raw pointers, and count reference -/// depth. -pub fn walk_ptrs_ty_depth(ty: Ty<'_>) -> (Ty<'_>, usize) { - fn inner(ty: Ty<'_>, depth: usize) -> (Ty<'_>, usize) { - match ty.kind() { - ty::Ref(_, ty, _) => inner(*ty, depth + 1), - _ => (ty, depth), - } +/// Peels off all references on the type. Returns the underlying type, the number of references +/// removed, and, if there were any such references, whether the pointer is ultimately mutable or +/// not. +pub fn peel_and_count_ty_refs(mut ty: Ty<'_>) -> (Ty<'_>, usize, Option) { + let mut count = 0; + let mut mutbl = None; + while let ty::Ref(_, dest_ty, m) = ty.kind() { + ty = *dest_ty; + count += 1; + mutbl.replace(mutbl.map_or(*m, |mutbl: Mutability| mutbl.min(*m))); } - inner(ty, 0) + (ty, count, mutbl) } -/// Returns `true` if types `a` and `b` are same types having same `Const` generic args, -/// otherwise returns `false` -pub fn same_type_and_consts<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool { +/// Checks whether `a` and `b` are same types having same `Const` generic args, but ignores +/// lifetimes. +/// +/// For example, the function would return `true` for +/// - `u32` and `u32` +/// - `[u8; N]` and `[u8; M]`, if `N=M` +/// - `Option` and `Option`, if `same_type_modulo_regions(T, U)` holds +/// - `&'a str` and `&'b str` +/// +/// and `false` for: +/// - `Result` and `Result` +pub fn same_type_modulo_regions<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool { match (&a.kind(), &b.kind()) { (&ty::Adt(did_a, args_a), &ty::Adt(did_b, args_b)) => { if did_a != did_b { return false; } - args_a - .iter() - .zip(args_b.iter()) - .all(|(arg_a, arg_b)| match (arg_a.kind(), arg_b.kind()) { - (GenericArgKind::Const(inner_a), GenericArgKind::Const(inner_b)) => inner_a == inner_b, - (GenericArgKind::Type(type_a), GenericArgKind::Type(type_b)) => { - same_type_and_consts(type_a, type_b) - }, - _ => true, - }) + iter::zip(*args_a, *args_b).all(|(arg_a, arg_b)| match (arg_a.kind(), arg_b.kind()) { + (GenericArgKind::Const(inner_a), GenericArgKind::Const(inner_b)) => inner_a == inner_b, + (GenericArgKind::Type(type_a), GenericArgKind::Type(type_b)) => { + same_type_modulo_regions(type_a, type_b) + }, + _ => true, + }) }, _ => a == b, } diff --git a/declare_clippy_lint/Cargo.toml b/declare_clippy_lint/Cargo.toml index ec0e59e705495..4de7b5fb5924d 100644 --- a/declare_clippy_lint/Cargo.toml +++ b/declare_clippy_lint/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "declare_clippy_lint" -version = "0.1.91" +version = "0.1.92" edition = "2024" repository = "https://github.com/rust-lang/rust-clippy" license = "MIT OR Apache-2.0" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ec2f24a0a6d84..9c102de448200 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2025-09-04" +channel = "nightly-2025-09-18" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/tests/no-profile-in-cargo-toml.rs b/tests/no-profile-in-cargo-toml.rs index 2ad9bfb75dee8..1f8c4fae9b31f 100644 --- a/tests/no-profile-in-cargo-toml.rs +++ b/tests/no-profile-in-cargo-toml.rs @@ -17,6 +17,9 @@ fn no_profile_in_cargo_toml() { // keep it fast and simple. for entry in WalkDir::new(".") .into_iter() + // Do not recurse into `target` as lintcheck might put some sources (and their + // `Cargo.toml`) there. + .filter_entry(|e| e.file_name() != "target") .filter_map(Result::ok) .filter(|e| e.file_name().to_str() == Some("Cargo.toml")) { diff --git a/tests/ui/ref_option/all/clippy.toml b/tests/ui-toml/ref_option/all/clippy.toml similarity index 100% rename from tests/ui/ref_option/all/clippy.toml rename to tests/ui-toml/ref_option/all/clippy.toml diff --git a/tests/ui/ref_option/private/clippy.toml b/tests/ui-toml/ref_option/private/clippy.toml similarity index 100% rename from tests/ui/ref_option/private/clippy.toml rename to tests/ui-toml/ref_option/private/clippy.toml diff --git a/tests/ui-toml/ref_option/ref_option.all.fixed b/tests/ui-toml/ref_option/ref_option.all.fixed new file mode 100644 index 0000000000000..f8f097e9a75e3 --- /dev/null +++ b/tests/ui-toml/ref_option/ref_option.all.fixed @@ -0,0 +1,114 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: private all +//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/private +//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/all + +#![allow(unused, clippy::needless_lifetimes, clippy::borrowed_box)] +#![warn(clippy::ref_option)] + +fn opt_u8(a: Option<&u8>) {} +//~^ ref_option +fn opt_gen(a: Option<&T>) {} +//~^ ref_option +fn opt_string(a: std::option::Option<&String>) {} +//~^ ref_option +fn ret_u8<'a>(p: &'a str) -> Option<&'a u8> { + //~^ ref_option + panic!() +} +fn ret_u8_static() -> Option<&'static u8> { + //~^ ref_option + panic!() +} +fn mult_string(a: Option<&String>, b: Option<&Vec>) {} +//~^ ref_option +fn ret_box<'a>() -> Option<&'a Box> { + //~^ ref_option + panic!() +} + +pub fn pub_opt_string(a: Option<&String>) {} +//~[all]^ ref_option +pub fn pub_mult_string(a: Option<&String>, b: Option<&Vec>) {} +//~[all]^ ref_option + +pub struct PubStruct; + +impl PubStruct { + pub fn pub_opt_params(&self, a: Option<&()>) {} + //~[all]^ ref_option + pub fn pub_opt_ret(&self) -> Option<&String> { + //~[all]^ ref_option + panic!() + } + + fn private_opt_params(&self, a: Option<&()>) {} + //~^ ref_option + fn private_opt_ret(&self) -> Option<&String> { + //~^ ref_option + panic!() + } +} + +// valid, don't change +fn mut_u8(a: &mut Option) {} +pub fn pub_mut_u8(a: &mut Option) {} + +// might be good to catch in the future +fn mut_u8_ref(a: &mut &Option) {} +pub fn pub_mut_u8_ref(a: &mut &Option) {} +fn lambdas() { + // Not handled for now, not sure if we should + let x = |a: &Option| {}; + let x = |a: &Option| -> &Option { panic!() }; +} + +pub mod external { + proc_macros::external!( + fn opt_u8(a: &Option) {} + fn ret_u8<'a>(p: &'a str) -> &'a Option { + panic!() + } + pub fn pub_opt_u8(a: &Option) {} + + pub struct PubStruct; + impl PubStruct { + pub fn pub_opt_params(&self, a: &Option<()>) {} + pub fn pub_opt_ret(&self) -> &Option { + panic!() + } + + fn private_opt_params(&self, a: &Option<()>) {} + fn private_opt_ret(&self) -> &Option { + panic!() + } + } + ); +} + +pub mod proc_macros { + proc_macros::with_span!( + span + + fn opt_u8(a: &Option) {} + fn ret_u8<'a>(p: &'a str) -> &'a Option { + panic!() + } + pub fn pub_opt_u8(a: &Option) {} + + pub struct PubStruct; + impl PubStruct { + pub fn pub_opt_params(&self, a: &Option<()>) {} + pub fn pub_opt_ret(&self) -> &Option { + panic!() + } + + fn private_opt_params(&self, a: &Option<()>) {} + fn private_opt_ret(&self) -> &Option { + panic!() + } + } + ); +} + +fn main() {} diff --git a/tests/ui/ref_option/ref_option.all.stderr b/tests/ui-toml/ref_option/ref_option.all.stderr similarity index 62% rename from tests/ui/ref_option/ref_option.all.stderr rename to tests/ui-toml/ref_option/ref_option.all.stderr index bd43c28336eb3..45ce105e03084 100644 --- a/tests/ui/ref_option/ref_option.all.stderr +++ b/tests/ui-toml/ref_option/ref_option.all.stderr @@ -1,5 +1,5 @@ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:8:1 + --> tests/ui-toml/ref_option/ref_option.rs:9:1 | LL | fn opt_u8(a: &Option) {} | ^^^^^^^^^^^^^-----------^^^^ @@ -10,7 +10,7 @@ LL | fn opt_u8(a: &Option) {} = help: to override `-D warnings` add `#[allow(clippy::ref_option)]` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:10:1 + --> tests/ui-toml/ref_option/ref_option.rs:11:1 | LL | fn opt_gen(a: &Option) {} | ^^^^^^^^^^^^^^^^^----------^^^^ @@ -18,7 +18,7 @@ LL | fn opt_gen(a: &Option) {} | help: change this to: `Option<&T>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:12:1 + --> tests/ui-toml/ref_option/ref_option.rs:13:1 | LL | fn opt_string(a: &std::option::Option) {} | ^^^^^^^^^^^^^^^^^----------------------------^^^^ @@ -26,10 +26,10 @@ LL | fn opt_string(a: &std::option::Option) {} | help: change this to: `std::option::Option<&String>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:14:1 + --> tests/ui-toml/ref_option/ref_option.rs:15:1 | -LL | fn ret_string<'a>(p: &'a str) -> &'a Option { - | ^ -------------- help: change this to: `Option<&'a u8>` +LL | fn ret_u8<'a>(p: &'a str) -> &'a Option { + | ^ -------------- help: change this to: `Option<&'a u8>` | _| | | LL | | @@ -38,10 +38,10 @@ LL | | } | |_^ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:18:1 + --> tests/ui-toml/ref_option/ref_option.rs:19:1 | -LL | fn ret_string_static() -> &'static Option { - | ^ ------------------- help: change this to: `Option<&'static u8>` +LL | fn ret_u8_static() -> &'static Option { + | ^ ------------------- help: change this to: `Option<&'static u8>` | _| | | LL | | @@ -50,7 +50,7 @@ LL | | } | |_^ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:22:1 + --> tests/ui-toml/ref_option/ref_option.rs:23:1 | LL | fn mult_string(a: &Option, b: &Option>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -62,7 +62,7 @@ LL + fn mult_string(a: Option<&String>, b: Option<&Vec>) {} | error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:24:1 + --> tests/ui-toml/ref_option/ref_option.rs:25:1 | LL | fn ret_box<'a>() -> &'a Option> { | ^ ------------------- help: change this to: `Option<&'a Box>` @@ -74,7 +74,7 @@ LL | | } | |_^ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:29:1 + --> tests/ui-toml/ref_option/ref_option.rs:30:1 | LL | pub fn pub_opt_string(a: &Option) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^---------------^^^^ @@ -82,7 +82,7 @@ LL | pub fn pub_opt_string(a: &Option) {} | help: change this to: `Option<&String>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:31:1 + --> tests/ui-toml/ref_option/ref_option.rs:32:1 | LL | pub fn pub_mult_string(a: &Option, b: &Option>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -94,39 +94,7 @@ LL + pub fn pub_mult_string(a: Option<&String>, b: Option<&Vec>) {} | error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:35:5 - | -LL | fn pub_trait_opt(&self, a: &Option>); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^----------------^^ - | | - | help: change this to: `Option<&Vec>` - -error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:37:5 - | -LL | fn pub_trait_ret(&self) -> &Option>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^----------------^ - | | - | help: change this to: `Option<&Vec>` - -error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:42:5 - | -LL | fn trait_opt(&self, a: &Option); - | ^^^^^^^^^^^^^^^^^^^^^^^---------------^^ - | | - | help: change this to: `Option<&String>` - -error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:44:5 - | -LL | fn trait_ret(&self) -> &Option; - | ^^^^^^^^^^^^^^^^^^^^^^^---------------^ - | | - | help: change this to: `Option<&String>` - -error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:51:5 + --> tests/ui-toml/ref_option/ref_option.rs:38:5 | LL | pub fn pub_opt_params(&self, a: &Option<()>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------^^^^ @@ -134,7 +102,7 @@ LL | pub fn pub_opt_params(&self, a: &Option<()>) {} | help: change this to: `Option<&()>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:53:5 + --> tests/ui-toml/ref_option/ref_option.rs:40:5 | LL | pub fn pub_opt_ret(&self) -> &Option { | ^ --------------- help: change this to: `Option<&String>` @@ -146,7 +114,7 @@ LL | | } | |_____^ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:58:5 + --> tests/ui-toml/ref_option/ref_option.rs:45:5 | LL | fn private_opt_params(&self, a: &Option<()>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------^^^^ @@ -154,7 +122,7 @@ LL | fn private_opt_params(&self, a: &Option<()>) {} | help: change this to: `Option<&()>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:60:5 + --> tests/ui-toml/ref_option/ref_option.rs:47:5 | LL | fn private_opt_ret(&self) -> &Option { | ^ --------------- help: change this to: `Option<&String>` @@ -165,5 +133,5 @@ LL | | panic!() LL | | } | |_____^ -error: aborting due to 17 previous errors +error: aborting due to 13 previous errors diff --git a/tests/ui-toml/ref_option/ref_option.private.fixed b/tests/ui-toml/ref_option/ref_option.private.fixed new file mode 100644 index 0000000000000..4dd14a822067b --- /dev/null +++ b/tests/ui-toml/ref_option/ref_option.private.fixed @@ -0,0 +1,114 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: private all +//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/private +//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/all + +#![allow(unused, clippy::needless_lifetimes, clippy::borrowed_box)] +#![warn(clippy::ref_option)] + +fn opt_u8(a: Option<&u8>) {} +//~^ ref_option +fn opt_gen(a: Option<&T>) {} +//~^ ref_option +fn opt_string(a: std::option::Option<&String>) {} +//~^ ref_option +fn ret_u8<'a>(p: &'a str) -> Option<&'a u8> { + //~^ ref_option + panic!() +} +fn ret_u8_static() -> Option<&'static u8> { + //~^ ref_option + panic!() +} +fn mult_string(a: Option<&String>, b: Option<&Vec>) {} +//~^ ref_option +fn ret_box<'a>() -> Option<&'a Box> { + //~^ ref_option + panic!() +} + +pub fn pub_opt_string(a: &Option) {} +//~[all]^ ref_option +pub fn pub_mult_string(a: &Option, b: &Option>) {} +//~[all]^ ref_option + +pub struct PubStruct; + +impl PubStruct { + pub fn pub_opt_params(&self, a: &Option<()>) {} + //~[all]^ ref_option + pub fn pub_opt_ret(&self) -> &Option { + //~[all]^ ref_option + panic!() + } + + fn private_opt_params(&self, a: Option<&()>) {} + //~^ ref_option + fn private_opt_ret(&self) -> Option<&String> { + //~^ ref_option + panic!() + } +} + +// valid, don't change +fn mut_u8(a: &mut Option) {} +pub fn pub_mut_u8(a: &mut Option) {} + +// might be good to catch in the future +fn mut_u8_ref(a: &mut &Option) {} +pub fn pub_mut_u8_ref(a: &mut &Option) {} +fn lambdas() { + // Not handled for now, not sure if we should + let x = |a: &Option| {}; + let x = |a: &Option| -> &Option { panic!() }; +} + +pub mod external { + proc_macros::external!( + fn opt_u8(a: &Option) {} + fn ret_u8<'a>(p: &'a str) -> &'a Option { + panic!() + } + pub fn pub_opt_u8(a: &Option) {} + + pub struct PubStruct; + impl PubStruct { + pub fn pub_opt_params(&self, a: &Option<()>) {} + pub fn pub_opt_ret(&self) -> &Option { + panic!() + } + + fn private_opt_params(&self, a: &Option<()>) {} + fn private_opt_ret(&self) -> &Option { + panic!() + } + } + ); +} + +pub mod proc_macros { + proc_macros::with_span!( + span + + fn opt_u8(a: &Option) {} + fn ret_u8<'a>(p: &'a str) -> &'a Option { + panic!() + } + pub fn pub_opt_u8(a: &Option) {} + + pub struct PubStruct; + impl PubStruct { + pub fn pub_opt_params(&self, a: &Option<()>) {} + pub fn pub_opt_ret(&self) -> &Option { + panic!() + } + + fn private_opt_params(&self, a: &Option<()>) {} + fn private_opt_ret(&self) -> &Option { + panic!() + } + } + ); +} + +fn main() {} diff --git a/tests/ui/ref_option/ref_option.private.stderr b/tests/ui-toml/ref_option/ref_option.private.stderr similarity index 63% rename from tests/ui/ref_option/ref_option.private.stderr rename to tests/ui-toml/ref_option/ref_option.private.stderr index 88c65e429d870..a63efd60a0368 100644 --- a/tests/ui/ref_option/ref_option.private.stderr +++ b/tests/ui-toml/ref_option/ref_option.private.stderr @@ -1,5 +1,5 @@ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:8:1 + --> tests/ui-toml/ref_option/ref_option.rs:9:1 | LL | fn opt_u8(a: &Option) {} | ^^^^^^^^^^^^^-----------^^^^ @@ -10,7 +10,7 @@ LL | fn opt_u8(a: &Option) {} = help: to override `-D warnings` add `#[allow(clippy::ref_option)]` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:10:1 + --> tests/ui-toml/ref_option/ref_option.rs:11:1 | LL | fn opt_gen(a: &Option) {} | ^^^^^^^^^^^^^^^^^----------^^^^ @@ -18,7 +18,7 @@ LL | fn opt_gen(a: &Option) {} | help: change this to: `Option<&T>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:12:1 + --> tests/ui-toml/ref_option/ref_option.rs:13:1 | LL | fn opt_string(a: &std::option::Option) {} | ^^^^^^^^^^^^^^^^^----------------------------^^^^ @@ -26,10 +26,10 @@ LL | fn opt_string(a: &std::option::Option) {} | help: change this to: `std::option::Option<&String>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:14:1 + --> tests/ui-toml/ref_option/ref_option.rs:15:1 | -LL | fn ret_string<'a>(p: &'a str) -> &'a Option { - | ^ -------------- help: change this to: `Option<&'a u8>` +LL | fn ret_u8<'a>(p: &'a str) -> &'a Option { + | ^ -------------- help: change this to: `Option<&'a u8>` | _| | | LL | | @@ -38,10 +38,10 @@ LL | | } | |_^ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:18:1 + --> tests/ui-toml/ref_option/ref_option.rs:19:1 | -LL | fn ret_string_static() -> &'static Option { - | ^ ------------------- help: change this to: `Option<&'static u8>` +LL | fn ret_u8_static() -> &'static Option { + | ^ ------------------- help: change this to: `Option<&'static u8>` | _| | | LL | | @@ -50,7 +50,7 @@ LL | | } | |_^ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:22:1 + --> tests/ui-toml/ref_option/ref_option.rs:23:1 | LL | fn mult_string(a: &Option, b: &Option>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -62,7 +62,7 @@ LL + fn mult_string(a: Option<&String>, b: Option<&Vec>) {} | error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:24:1 + --> tests/ui-toml/ref_option/ref_option.rs:25:1 | LL | fn ret_box<'a>() -> &'a Option> { | ^ ------------------- help: change this to: `Option<&'a Box>` @@ -74,23 +74,7 @@ LL | | } | |_^ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:42:5 - | -LL | fn trait_opt(&self, a: &Option); - | ^^^^^^^^^^^^^^^^^^^^^^^---------------^^ - | | - | help: change this to: `Option<&String>` - -error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:44:5 - | -LL | fn trait_ret(&self) -> &Option; - | ^^^^^^^^^^^^^^^^^^^^^^^---------------^ - | | - | help: change this to: `Option<&String>` - -error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:58:5 + --> tests/ui-toml/ref_option/ref_option.rs:45:5 | LL | fn private_opt_params(&self, a: &Option<()>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------^^^^ @@ -98,7 +82,7 @@ LL | fn private_opt_params(&self, a: &Option<()>) {} | help: change this to: `Option<&()>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option.rs:60:5 + --> tests/ui-toml/ref_option/ref_option.rs:47:5 | LL | fn private_opt_ret(&self) -> &Option { | ^ --------------- help: change this to: `Option<&String>` @@ -109,5 +93,5 @@ LL | | panic!() LL | | } | |_____^ -error: aborting due to 11 previous errors +error: aborting due to 9 previous errors diff --git a/tests/ui-toml/ref_option/ref_option.rs b/tests/ui-toml/ref_option/ref_option.rs new file mode 100644 index 0000000000000..8397c2213d172 --- /dev/null +++ b/tests/ui-toml/ref_option/ref_option.rs @@ -0,0 +1,114 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: private all +//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/private +//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/all + +#![allow(unused, clippy::needless_lifetimes, clippy::borrowed_box)] +#![warn(clippy::ref_option)] + +fn opt_u8(a: &Option) {} +//~^ ref_option +fn opt_gen(a: &Option) {} +//~^ ref_option +fn opt_string(a: &std::option::Option) {} +//~^ ref_option +fn ret_u8<'a>(p: &'a str) -> &'a Option { + //~^ ref_option + panic!() +} +fn ret_u8_static() -> &'static Option { + //~^ ref_option + panic!() +} +fn mult_string(a: &Option, b: &Option>) {} +//~^ ref_option +fn ret_box<'a>() -> &'a Option> { + //~^ ref_option + panic!() +} + +pub fn pub_opt_string(a: &Option) {} +//~[all]^ ref_option +pub fn pub_mult_string(a: &Option, b: &Option>) {} +//~[all]^ ref_option + +pub struct PubStruct; + +impl PubStruct { + pub fn pub_opt_params(&self, a: &Option<()>) {} + //~[all]^ ref_option + pub fn pub_opt_ret(&self) -> &Option { + //~[all]^ ref_option + panic!() + } + + fn private_opt_params(&self, a: &Option<()>) {} + //~^ ref_option + fn private_opt_ret(&self) -> &Option { + //~^ ref_option + panic!() + } +} + +// valid, don't change +fn mut_u8(a: &mut Option) {} +pub fn pub_mut_u8(a: &mut Option) {} + +// might be good to catch in the future +fn mut_u8_ref(a: &mut &Option) {} +pub fn pub_mut_u8_ref(a: &mut &Option) {} +fn lambdas() { + // Not handled for now, not sure if we should + let x = |a: &Option| {}; + let x = |a: &Option| -> &Option { panic!() }; +} + +pub mod external { + proc_macros::external!( + fn opt_u8(a: &Option) {} + fn ret_u8<'a>(p: &'a str) -> &'a Option { + panic!() + } + pub fn pub_opt_u8(a: &Option) {} + + pub struct PubStruct; + impl PubStruct { + pub fn pub_opt_params(&self, a: &Option<()>) {} + pub fn pub_opt_ret(&self) -> &Option { + panic!() + } + + fn private_opt_params(&self, a: &Option<()>) {} + fn private_opt_ret(&self) -> &Option { + panic!() + } + } + ); +} + +pub mod proc_macros { + proc_macros::with_span!( + span + + fn opt_u8(a: &Option) {} + fn ret_u8<'a>(p: &'a str) -> &'a Option { + panic!() + } + pub fn pub_opt_u8(a: &Option) {} + + pub struct PubStruct; + impl PubStruct { + pub fn pub_opt_params(&self, a: &Option<()>) {} + pub fn pub_opt_ret(&self) -> &Option { + panic!() + } + + fn private_opt_params(&self, a: &Option<()>) {} + fn private_opt_ret(&self) -> &Option { + panic!() + } + } + ); +} + +fn main() {} diff --git a/tests/ui/ref_option/ref_option_traits.all.stderr b/tests/ui-toml/ref_option/ref_option_traits.all.stderr similarity index 85% rename from tests/ui/ref_option/ref_option_traits.all.stderr rename to tests/ui-toml/ref_option/ref_option_traits.all.stderr index 886bf2b034989..602e148be6012 100644 --- a/tests/ui/ref_option/ref_option_traits.all.stderr +++ b/tests/ui-toml/ref_option/ref_option_traits.all.stderr @@ -1,5 +1,5 @@ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option_traits.rs:9:5 + --> tests/ui-toml/ref_option/ref_option_traits.rs:10:5 | LL | fn pub_trait_opt(&self, a: &Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^----------------^^ @@ -10,7 +10,7 @@ LL | fn pub_trait_opt(&self, a: &Option>); = help: to override `-D warnings` add `#[allow(clippy::ref_option)]` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option_traits.rs:11:5 + --> tests/ui-toml/ref_option/ref_option_traits.rs:12:5 | LL | fn pub_trait_ret(&self) -> &Option>; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^----------------^ @@ -18,7 +18,7 @@ LL | fn pub_trait_ret(&self) -> &Option>; | help: change this to: `Option<&Vec>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option_traits.rs:16:5 + --> tests/ui-toml/ref_option/ref_option_traits.rs:17:5 | LL | fn trait_opt(&self, a: &Option); | ^^^^^^^^^^^^^^^^^^^^^^^---------------^^ @@ -26,7 +26,7 @@ LL | fn trait_opt(&self, a: &Option); | help: change this to: `Option<&String>` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option_traits.rs:18:5 + --> tests/ui-toml/ref_option/ref_option_traits.rs:19:5 | LL | fn trait_ret(&self) -> &Option; | ^^^^^^^^^^^^^^^^^^^^^^^---------------^ diff --git a/tests/ui/ref_option/ref_option_traits.private.stderr b/tests/ui-toml/ref_option/ref_option_traits.private.stderr similarity index 86% rename from tests/ui/ref_option/ref_option_traits.private.stderr rename to tests/ui-toml/ref_option/ref_option_traits.private.stderr index cfab7fa5734c3..20bea400edfe6 100644 --- a/tests/ui/ref_option/ref_option_traits.private.stderr +++ b/tests/ui-toml/ref_option/ref_option_traits.private.stderr @@ -1,5 +1,5 @@ error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option_traits.rs:16:5 + --> tests/ui-toml/ref_option/ref_option_traits.rs:17:5 | LL | fn trait_opt(&self, a: &Option); | ^^^^^^^^^^^^^^^^^^^^^^^---------------^^ @@ -10,7 +10,7 @@ LL | fn trait_opt(&self, a: &Option); = help: to override `-D warnings` add `#[allow(clippy::ref_option)]` error: it is more idiomatic to use `Option<&T>` instead of `&Option` - --> tests/ui/ref_option/ref_option_traits.rs:18:5 + --> tests/ui-toml/ref_option/ref_option_traits.rs:19:5 | LL | fn trait_ret(&self) -> &Option; | ^^^^^^^^^^^^^^^^^^^^^^^---------------^ diff --git a/tests/ui-toml/ref_option/ref_option_traits.rs b/tests/ui-toml/ref_option/ref_option_traits.rs new file mode 100644 index 0000000000000..e1477d7f8463d --- /dev/null +++ b/tests/ui-toml/ref_option/ref_option_traits.rs @@ -0,0 +1,71 @@ +//@no-rustfix: fixes are only done to traits, not the impls +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: private all +//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/private +//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/all + +#![warn(clippy::ref_option)] + +pub trait PubTrait { + fn pub_trait_opt(&self, a: &Option>); + //~[all]^ ref_option + fn pub_trait_ret(&self) -> &Option>; + //~[all]^ ref_option +} + +trait PrivateTrait { + fn trait_opt(&self, a: &Option); + //~^ ref_option + fn trait_ret(&self) -> &Option; + //~^ ref_option +} + +pub struct PubStruct; + +impl PubTrait for PubStruct { + fn pub_trait_opt(&self, a: &Option>) {} + fn pub_trait_ret(&self) -> &Option> { + panic!() + } +} + +struct PrivateStruct; + +impl PrivateTrait for PrivateStruct { + fn trait_opt(&self, a: &Option) {} + fn trait_ret(&self) -> &Option { + panic!() + } +} + +pub mod external { + proc_macros::external!( + pub trait PubTrait { + fn pub_trait_opt(&self, a: &Option>); + fn pub_trait_ret(&self) -> &Option>; + } + + trait PrivateTrait { + fn trait_opt(&self, a: &Option); + fn trait_ret(&self) -> &Option; + } + ); +} + +pub mod proc_macros { + proc_macros::with_span!( + span + + pub trait PubTrait { + fn pub_trait_opt(&self, a: &Option>); + fn pub_trait_ret(&self) -> &Option>; + } + + trait PrivateTrait { + fn trait_opt(&self, a: &Option); + fn trait_ret(&self) -> &Option; + } + ); +} + +fn main() {} diff --git a/tests/ui/as_underscore_unfixable.rs b/tests/ui/as_underscore_unfixable.rs new file mode 100644 index 0000000000000..854feca7174fb --- /dev/null +++ b/tests/ui/as_underscore_unfixable.rs @@ -0,0 +1,14 @@ +//@no-rustfix + +#![warn(clippy::as_underscore)] + +fn main() { + // From issue #15282 + let f = async || (); + let _: Box _> = Box::new(f) as _; + //~^ as_underscore + + let barr = || (|| ()); + let _: Box _> = Box::new(barr) as _; + //~^ as_underscore +} diff --git a/tests/ui/as_underscore_unfixable.stderr b/tests/ui/as_underscore_unfixable.stderr new file mode 100644 index 0000000000000..7385bea5c6501 --- /dev/null +++ b/tests/ui/as_underscore_unfixable.stderr @@ -0,0 +1,20 @@ +error: using `as _` conversion + --> tests/ui/as_underscore_unfixable.rs:8:37 + | +LL | let _: Box _> = Box::new(f) as _; + | ^^^^^^^^^^^^^^^^ + | + = help: consider giving the type explicitly + = note: `-D clippy::as-underscore` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::as_underscore)]` + +error: using `as _` conversion + --> tests/ui/as_underscore_unfixable.rs:12:33 + | +LL | let _: Box _> = Box::new(barr) as _; + | ^^^^^^^^^^^^^^^^^^^ + | + = help: consider giving the type explicitly + +error: aborting due to 2 previous errors + diff --git a/tests/ui/cast.rs b/tests/ui/cast.rs index 525be8216500a..fab02bf7b24e4 100644 --- a/tests/ui/cast.rs +++ b/tests/ui/cast.rs @@ -569,3 +569,16 @@ fn issue12721() { (255 % 999999u64) as u8; //~^ cast_possible_truncation } + +mod issue14150 { + #[clippy::msrv = "1.87"] + fn msrv_supports_cast_signed() { + _ = 1u8 as i8; + //~^ cast_possible_wrap + } + #[clippy::msrv = "1.86"] + fn msrv_doesnt_supports_cast_signed() { + _ = 1u8 as i8; + //~^ cast_possible_wrap + } +} diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index 1cb30d956679a..8c48855123f93 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -194,7 +194,7 @@ error: casting `u8` to `i8` may wrap around the value --> tests/ui/cast.rs:88:5 | LL | 1u8 as i8; - | ^^^^^^^^^ + | ^^^^^^^^^ help: if this is intentional, use `cast_signed()` instead: `1u8.cast_signed()` | = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_possible_wrap)]` @@ -203,25 +203,25 @@ error: casting `u16` to `i16` may wrap around the value --> tests/ui/cast.rs:91:5 | LL | 1u16 as i16; - | ^^^^^^^^^^^ + | ^^^^^^^^^^^ help: if this is intentional, use `cast_signed()` instead: `1u16.cast_signed()` error: casting `u32` to `i32` may wrap around the value --> tests/ui/cast.rs:94:5 | LL | 1u32 as i32; - | ^^^^^^^^^^^ + | ^^^^^^^^^^^ help: if this is intentional, use `cast_signed()` instead: `1u32.cast_signed()` error: casting `u64` to `i64` may wrap around the value --> tests/ui/cast.rs:97:5 | LL | 1u64 as i64; - | ^^^^^^^^^^^ + | ^^^^^^^^^^^ help: if this is intentional, use `cast_signed()` instead: `1u64.cast_signed()` error: casting `usize` to `isize` may wrap around the value --> tests/ui/cast.rs:100:5 | LL | 1usize as isize; - | ^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_signed()` instead: `1usize.cast_signed()` error: casting `usize` to `i8` may truncate the value --> tests/ui/cast.rs:104:5 @@ -321,43 +321,43 @@ error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:138:5 | LL | -1i32 as u32; - | ^^^^^^^^^^^^ + | ^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(-1i32).cast_unsigned()` error: casting `isize` to `usize` may lose the sign of the value --> tests/ui/cast.rs:142:5 | LL | -1isize as usize; - | ^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(-1isize).cast_unsigned()` error: casting `i8` to `u8` may lose the sign of the value --> tests/ui/cast.rs:154:5 | LL | (i8::MIN).abs() as u8; - | ^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(i8::MIN).abs().cast_unsigned()` error: casting `i64` to `u64` may lose the sign of the value --> tests/ui/cast.rs:159:5 | LL | (-1i64).abs() as u64; - | ^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(-1i64).abs().cast_unsigned()` error: casting `isize` to `usize` may lose the sign of the value --> tests/ui/cast.rs:161:5 | LL | (-1isize).abs() as usize; - | ^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(-1isize).abs().cast_unsigned()` error: casting `i64` to `u64` may lose the sign of the value --> tests/ui/cast.rs:169:5 | LL | (unsafe { (-1i64).checked_abs().unwrap_unchecked() }) as u64; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(unsafe { (-1i64).checked_abs().unwrap_unchecked() }).cast_unsigned()` error: casting `i64` to `u64` may lose the sign of the value --> tests/ui/cast.rs:185:5 | LL | (unsafe { (-1i64).checked_isqrt().unwrap_unchecked() }) as u64; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(unsafe { (-1i64).checked_isqrt().unwrap_unchecked() }).cast_unsigned()` error: casting `i64` to `i8` may truncate the value --> tests/ui/cast.rs:237:5 @@ -495,79 +495,79 @@ error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:438:9 | LL | (x * x) as u32; - | ^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(x * x).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:444:32 | LL | let _a = |x: i32| -> u32 { (x * x * x * x) as u32 }; - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(x * x * x * x).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:447:5 | LL | (2_i32).checked_pow(3).unwrap() as u32; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(2_i32).checked_pow(3).unwrap().cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:449:5 | LL | (-2_i32).pow(3) as u32; - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(-2_i32).pow(3).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:454:5 | LL | (-5_i32 % 2) as u32; - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(-5_i32 % 2).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:457:5 | LL | (-5_i32 % -2) as u32; - | ^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(-5_i32 % -2).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:461:5 | LL | (-2_i32 >> 1) as u32; - | ^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(-2_i32 >> 1).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:465:5 | LL | (x * x) as u32; - | ^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(x * x).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:467:5 | LL | (x * x * x) as u32; - | ^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(x * x * x).cast_unsigned()` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:471:5 | LL | (y * y * y * y * -2) as u16; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(y * y * y * y * -2).cast_unsigned()` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:474:5 | LL | (y * y * y / y * 2) as u16; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(y * y * y / y * 2).cast_unsigned()` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:476:5 | LL | (y * y / y * 2) as u16; - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(y * y / y * 2).cast_unsigned()` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:479:5 | LL | (y / y * y * -2) as u16; - | ^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(y / y * y * -2).cast_unsigned()` error: equal expressions as operands to `/` --> tests/ui/cast.rs:479:6 @@ -581,97 +581,97 @@ error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:483:5 | LL | (y + y + y + -2) as u16; - | ^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(y + y + y + -2).cast_unsigned()` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:486:5 | LL | (y + y + y + 2) as u16; - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(y + y + y + 2).cast_unsigned()` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:490:5 | LL | (z + -2) as u16; - | ^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(z + -2).cast_unsigned()` error: casting `i16` to `u16` may lose the sign of the value --> tests/ui/cast.rs:493:5 | LL | (z + z + 2) as u16; - | ^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(z + z + 2).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:497:9 | LL | (a * a * b * b * c * c) as u32; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a * a * b * b * c * c).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:499:9 | LL | (a * b * c) as u32; - | ^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a * b * c).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:502:9 | LL | (a * -b * c) as u32; - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a * -b * c).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:505:9 | LL | (a * b * c * c) as u32; - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a * b * c * c).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:507:9 | LL | (a * -2) as u32; - | ^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a * -2).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:510:9 | LL | (a * b * c * -2) as u32; - | ^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a * b * c * -2).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:513:9 | LL | (a / b) as u32; - | ^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a / b).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:515:9 | LL | (a / b * c) as u32; - | ^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a / b * c).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:518:9 | LL | (a / b + b * c) as u32; - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a / b + b * c).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:521:9 | LL | a.saturating_pow(3) as u32; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `a.saturating_pow(3).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:524:9 | LL | (a.abs() * b.pow(2) / c.abs()) as u32 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `(a.abs() * b.pow(2) / c.abs()).cast_unsigned()` error: casting `i32` to `u32` may lose the sign of the value --> tests/ui/cast.rs:532:21 | LL | let _ = i32::MIN as u32; // cast_sign_loss - | ^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ help: if this is intentional, use `cast_unsigned()` instead: `i32::MIN.cast_unsigned()` ... LL | m!(); | ---- in this macro invocation @@ -752,5 +752,17 @@ LL - (255 % 999999u64) as u8; LL + u8::try_from(255 % 999999u64); | -error: aborting due to 92 previous errors +error: casting `u8` to `i8` may wrap around the value + --> tests/ui/cast.rs:576:13 + | +LL | _ = 1u8 as i8; + | ^^^^^^^^^ help: if this is intentional, use `cast_signed()` instead: `1u8.cast_signed()` + +error: casting `u8` to `i8` may wrap around the value + --> tests/ui/cast.rs:581:13 + | +LL | _ = 1u8 as i8; + | ^^^^^^^^^ + +error: aborting due to 94 previous errors diff --git a/tests/ui/checked_unwrap/simple_conditionals.rs b/tests/ui/checked_unwrap/simple_conditionals.rs index 785b2473c0530..bba264080b406 100644 --- a/tests/ui/checked_unwrap/simple_conditionals.rs +++ b/tests/ui/checked_unwrap/simple_conditionals.rs @@ -273,6 +273,28 @@ const ISSUE14763: fn(Option) = |x| { } }; +fn issue12295() { + let option = Some(()); + + if option.is_some() { + println!("{:?}", option.unwrap()); + //~^ unnecessary_unwrap + } else { + println!("{:?}", option.unwrap()); + //~^ panicking_unwrap + } + + let result = Ok::<(), ()>(()); + + if result.is_ok() { + println!("{:?}", result.unwrap()); + //~^ unnecessary_unwrap + } else { + println!("{:?}", result.unwrap()); + //~^ panicking_unwrap + } +} + fn check_expect() { let x = Some(()); if x.is_some() { diff --git a/tests/ui/checked_unwrap/simple_conditionals.stderr b/tests/ui/checked_unwrap/simple_conditionals.stderr index 939b509d85c91..2007a85954137 100644 --- a/tests/ui/checked_unwrap/simple_conditionals.stderr +++ b/tests/ui/checked_unwrap/simple_conditionals.stderr @@ -322,6 +322,40 @@ LL | if x.is_some() { LL | _ = x.unwrap(); | ^^^^^^^^^^ +error: called `unwrap` on `option` after checking its variant with `is_some` + --> tests/ui/checked_unwrap/simple_conditionals.rs:280:26 + | +LL | if option.is_some() { + | ------------------- help: try: `if let Some() = option` +LL | println!("{:?}", option.unwrap()); + | ^^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> tests/ui/checked_unwrap/simple_conditionals.rs:283:26 + | +LL | if option.is_some() { + | ---------------- because of this check +... +LL | println!("{:?}", option.unwrap()); + | ^^^^^^^^^^^^^^^ + +error: called `unwrap` on `result` after checking its variant with `is_ok` + --> tests/ui/checked_unwrap/simple_conditionals.rs:290:26 + | +LL | if result.is_ok() { + | ----------------- help: try: `if let Ok() = result` +LL | println!("{:?}", result.unwrap()); + | ^^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> tests/ui/checked_unwrap/simple_conditionals.rs:293:26 + | +LL | if result.is_ok() { + | -------------- because of this check +... +LL | println!("{:?}", result.unwrap()); + | ^^^^^^^^^^^^^^^ + error: creating a shared reference to mutable static --> tests/ui/checked_unwrap/simple_conditionals.rs:183:12 | @@ -332,5 +366,5 @@ LL | if X.is_some() { = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives = note: `#[deny(static_mut_refs)]` (part of `#[deny(rust_2024_compatibility)]`) on by default -error: aborting due to 36 previous errors +error: aborting due to 40 previous errors diff --git a/tests/ui/crashes/ice-15657.rs b/tests/ui/crashes/ice-15657.rs new file mode 100644 index 0000000000000..c6f6506cd8d03 --- /dev/null +++ b/tests/ui/crashes/ice-15657.rs @@ -0,0 +1,11 @@ +//@check-pass +#![warn(clippy::len_zero)] + +pub struct S1; +pub struct S2; + +impl S1 { + pub fn len(&self) -> S2 { + S2 + } +} diff --git a/tests/ui/crashes/ice-15666.fixed b/tests/ui/crashes/ice-15666.fixed new file mode 100644 index 0000000000000..53b618765c0f0 --- /dev/null +++ b/tests/ui/crashes/ice-15666.fixed @@ -0,0 +1,6 @@ +#![warn(clippy::elidable_lifetime_names)] + +struct UnitVariantAccess<'a, 'b, 's>(&'a &'b &'s ()); +trait Trait<'de> {} +impl<'de> Trait<'de> for UnitVariantAccess<'_, 'de, '_> {} +//~^ elidable_lifetime_names diff --git a/tests/ui/crashes/ice-15666.rs b/tests/ui/crashes/ice-15666.rs new file mode 100644 index 0000000000000..1414b3d2035e4 --- /dev/null +++ b/tests/ui/crashes/ice-15666.rs @@ -0,0 +1,6 @@ +#![warn(clippy::elidable_lifetime_names)] + +struct UnitVariantAccess<'a, 'b, 's>(&'a &'b &'s ()); +trait Trait<'de> {} +impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {} +//~^ elidable_lifetime_names diff --git a/tests/ui/crashes/ice-15666.stderr b/tests/ui/crashes/ice-15666.stderr new file mode 100644 index 0000000000000..b417c09b5c65a --- /dev/null +++ b/tests/ui/crashes/ice-15666.stderr @@ -0,0 +1,16 @@ +error: the following explicit lifetimes could be elided: 'a, 's + --> tests/ui/crashes/ice-15666.rs:5:11 + | +LL | impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {} + | ^^ ^^ ^^ ^^ + | + = note: `-D clippy::elidable-lifetime-names` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::elidable_lifetime_names)]` +help: elide the lifetimes + | +LL - impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {} +LL + impl<'de> Trait<'de> for UnitVariantAccess<'_, 'de, '_> {} + | + +error: aborting due to 1 previous error + diff --git a/tests/ui/elidable_lifetime_names.fixed b/tests/ui/elidable_lifetime_names.fixed index abeee5c4cef34..a6c4cb7a36a8d 100644 --- a/tests/ui/elidable_lifetime_names.fixed +++ b/tests/ui/elidable_lifetime_names.fixed @@ -192,3 +192,85 @@ mod issue13923 { x.b } } + +fn issue15666_original() { + struct UnitVariantAccess<'a, 'b, 's>(&'a &'b &'s ()); + + trait Trait<'de> {} + + //~v elidable_lifetime_names + impl<'de> Trait<'de> for UnitVariantAccess<'_, 'de, '_> {} + // ^^ ^^ ^^ ^^ +} + +#[allow(clippy::upper_case_acronyms)] +fn issue15666() { + struct S1<'a>(&'a ()); + struct S2<'a, 'b>(&'a &'b ()); + struct S3<'a, 'b, 'c>(&'a &'b &'c ()); + + trait T {} + trait TA<'a> {} + trait TB<'b> {} + trait TC<'c> {} + trait TAB<'a, 'b> {} + trait TAC<'a, 'c> {} + trait TBC<'b, 'c> {} + trait TABC<'a, 'b, 'c> {} + + // 1 lifetime + + impl<'a> TA<'a> for S1<'a> {} + + //~v elidable_lifetime_names + impl T for S1<'_> {} + // ^^ + + // 2 lifetimes + + impl<'a, 'b> TAB<'a, 'b> for S2<'a, 'b> {} + + //~v elidable_lifetime_names + impl<'a> TA<'a> for S2<'a, '_> {} + // ^^ + + //~v elidable_lifetime_names + impl<'b> TB<'b> for S2<'_, 'b> {} + // ^^ + + //~v elidable_lifetime_names + impl T for S2<'_, '_> {} + // ^^ ^^ + + // 3 lifetimes + + impl<'a, 'b, 'c> TABC<'a, 'b, 'c> for S3<'a, 'b, 'c> {} + + //~v elidable_lifetime_names + impl<'a, 'b> TAB<'a, 'b> for S3<'a, 'b, '_> {} + // ^^ + + //~v elidable_lifetime_names + impl<'a, 'c> TAC<'a, 'c> for S3<'a, '_, 'c> {} + // ^^ + + //~v elidable_lifetime_names + impl<'a> TA<'a> for S3<'a, '_, '_> {} + // ^^ ^^ + + //~v elidable_lifetime_names + impl<'b, 'c> TBC<'b, 'c> for S3<'_, 'b, 'c> {} + // ^^ + + //~v elidable_lifetime_names + impl<'b> TB<'b> for S3<'_, 'b, '_> {} + // ^^ ^^ + + //~v elidable_lifetime_names + impl<'c> TC<'c> for S3<'_, '_, 'c> {} + // ^^ ^^ + + //~v elidable_lifetime_names + impl T for S3<'_, '_, '_> {} + // ^^ ^^ ^^ +} diff --git a/tests/ui/elidable_lifetime_names.rs b/tests/ui/elidable_lifetime_names.rs index fae3577a8e960..e08056b2fb56b 100644 --- a/tests/ui/elidable_lifetime_names.rs +++ b/tests/ui/elidable_lifetime_names.rs @@ -192,3 +192,85 @@ mod issue13923 { x.b } } + +fn issue15666_original() { + struct UnitVariantAccess<'a, 'b, 's>(&'a &'b &'s ()); + + trait Trait<'de> {} + + //~v elidable_lifetime_names + impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {} + // ^^ ^^ ^^ ^^ +} + +#[allow(clippy::upper_case_acronyms)] +fn issue15666() { + struct S1<'a>(&'a ()); + struct S2<'a, 'b>(&'a &'b ()); + struct S3<'a, 'b, 'c>(&'a &'b &'c ()); + + trait T {} + trait TA<'a> {} + trait TB<'b> {} + trait TC<'c> {} + trait TAB<'a, 'b> {} + trait TAC<'a, 'c> {} + trait TBC<'b, 'c> {} + trait TABC<'a, 'b, 'c> {} + + // 1 lifetime + + impl<'a> TA<'a> for S1<'a> {} + + //~v elidable_lifetime_names + impl<'a> T for S1<'a> {} + // ^^ + + // 2 lifetimes + + impl<'a, 'b> TAB<'a, 'b> for S2<'a, 'b> {} + + //~v elidable_lifetime_names + impl<'a, 'b> TA<'a> for S2<'a, 'b> {} + // ^^ + + //~v elidable_lifetime_names + impl<'a, 'b> TB<'b> for S2<'a, 'b> {} + // ^^ + + //~v elidable_lifetime_names + impl<'a, 'b> T for S2<'a, 'b> {} + // ^^ ^^ + + // 3 lifetimes + + impl<'a, 'b, 'c> TABC<'a, 'b, 'c> for S3<'a, 'b, 'c> {} + + //~v elidable_lifetime_names + impl<'a, 'b, 'c> TAB<'a, 'b> for S3<'a, 'b, 'c> {} + // ^^ + + //~v elidable_lifetime_names + impl<'a, 'b, 'c> TAC<'a, 'c> for S3<'a, 'b, 'c> {} + // ^^ + + //~v elidable_lifetime_names + impl<'a, 'b, 'c> TA<'a> for S3<'a, 'b, 'c> {} + // ^^ ^^ + + //~v elidable_lifetime_names + impl<'a, 'b, 'c> TBC<'b, 'c> for S3<'a, 'b, 'c> {} + // ^^ + + //~v elidable_lifetime_names + impl<'a, 'b, 'c> TB<'b> for S3<'a, 'b, 'c> {} + // ^^ ^^ + + //~v elidable_lifetime_names + impl<'a, 'b, 'c> TC<'c> for S3<'a, 'b, 'c> {} + // ^^ ^^ + + //~v elidable_lifetime_names + impl<'a, 'b, 'c> T for S3<'a, 'b, 'c> {} + // ^^ ^^ ^^ +} diff --git a/tests/ui/elidable_lifetime_names.stderr b/tests/ui/elidable_lifetime_names.stderr index a60dfc697564e..03fe383b8f67f 100644 --- a/tests/ui/elidable_lifetime_names.stderr +++ b/tests/ui/elidable_lifetime_names.stderr @@ -158,5 +158,149 @@ LL | o: &'t str, LL ~ ) -> Content<'t, '_> { | -error: aborting due to 12 previous errors +error: the following explicit lifetimes could be elided: 'a, 's + --> tests/ui/elidable_lifetime_names.rs:202:15 + | +LL | impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {} + | ^^ ^^ ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {} +LL + impl<'de> Trait<'de> for UnitVariantAccess<'_, 'de, '_> {} + | + +error: the following explicit lifetimes could be elided: 'a + --> tests/ui/elidable_lifetime_names.rs:226:10 + | +LL | impl<'a> T for S1<'a> {} + | ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a> T for S1<'a> {} +LL + impl T for S1<'_> {} + | + +error: the following explicit lifetimes could be elided: 'b + --> tests/ui/elidable_lifetime_names.rs:234:14 + | +LL | impl<'a, 'b> TA<'a> for S2<'a, 'b> {} + | ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b> TA<'a> for S2<'a, 'b> {} +LL + impl<'a> TA<'a> for S2<'a, '_> {} + | + +error: the following explicit lifetimes could be elided: 'a + --> tests/ui/elidable_lifetime_names.rs:238:10 + | +LL | impl<'a, 'b> TB<'b> for S2<'a, 'b> {} + | ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b> TB<'b> for S2<'a, 'b> {} +LL + impl<'b> TB<'b> for S2<'_, 'b> {} + | + +error: the following explicit lifetimes could be elided: 'a, 'b + --> tests/ui/elidable_lifetime_names.rs:242:10 + | +LL | impl<'a, 'b> T for S2<'a, 'b> {} + | ^^ ^^ ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b> T for S2<'a, 'b> {} +LL + impl T for S2<'_, '_> {} + | + +error: the following explicit lifetimes could be elided: 'c + --> tests/ui/elidable_lifetime_names.rs:250:18 + | +LL | impl<'a, 'b, 'c> TAB<'a, 'b> for S3<'a, 'b, 'c> {} + | ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b, 'c> TAB<'a, 'b> for S3<'a, 'b, 'c> {} +LL + impl<'a, 'b> TAB<'a, 'b> for S3<'a, 'b, '_> {} + | + +error: the following explicit lifetimes could be elided: 'b + --> tests/ui/elidable_lifetime_names.rs:254:14 + | +LL | impl<'a, 'b, 'c> TAC<'a, 'c> for S3<'a, 'b, 'c> {} + | ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b, 'c> TAC<'a, 'c> for S3<'a, 'b, 'c> {} +LL + impl<'a, 'c> TAC<'a, 'c> for S3<'a, '_, 'c> {} + | + +error: the following explicit lifetimes could be elided: 'b, 'c + --> tests/ui/elidable_lifetime_names.rs:258:14 + | +LL | impl<'a, 'b, 'c> TA<'a> for S3<'a, 'b, 'c> {} + | ^^ ^^ ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b, 'c> TA<'a> for S3<'a, 'b, 'c> {} +LL + impl<'a> TA<'a> for S3<'a, '_, '_> {} + | + +error: the following explicit lifetimes could be elided: 'a + --> tests/ui/elidable_lifetime_names.rs:262:10 + | +LL | impl<'a, 'b, 'c> TBC<'b, 'c> for S3<'a, 'b, 'c> {} + | ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b, 'c> TBC<'b, 'c> for S3<'a, 'b, 'c> {} +LL + impl<'b, 'c> TBC<'b, 'c> for S3<'_, 'b, 'c> {} + | + +error: the following explicit lifetimes could be elided: 'a, 'c + --> tests/ui/elidable_lifetime_names.rs:266:10 + | +LL | impl<'a, 'b, 'c> TB<'b> for S3<'a, 'b, 'c> {} + | ^^ ^^ ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b, 'c> TB<'b> for S3<'a, 'b, 'c> {} +LL + impl<'b> TB<'b> for S3<'_, 'b, '_> {} + | + +error: the following explicit lifetimes could be elided: 'a, 'b + --> tests/ui/elidable_lifetime_names.rs:270:10 + | +LL | impl<'a, 'b, 'c> TC<'c> for S3<'a, 'b, 'c> {} + | ^^ ^^ ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b, 'c> TC<'c> for S3<'a, 'b, 'c> {} +LL + impl<'c> TC<'c> for S3<'_, '_, 'c> {} + | + +error: the following explicit lifetimes could be elided: 'a, 'b, 'c + --> tests/ui/elidable_lifetime_names.rs:274:10 + | +LL | impl<'a, 'b, 'c> T for S3<'a, 'b, 'c> {} + | ^^ ^^ ^^ ^^ ^^ ^^ + | +help: elide the lifetimes + | +LL - impl<'a, 'b, 'c> T for S3<'a, 'b, 'c> {} +LL + impl T for S3<'_, '_, '_> {} + | + +error: aborting due to 24 previous errors diff --git a/tests/ui/eta.fixed b/tests/ui/eta.fixed index 6944a979c05e6..107318e5323d0 100644 --- a/tests/ui/eta.fixed +++ b/tests/ui/eta.fixed @@ -635,3 +635,11 @@ fn issue8817() { //~| HELP: replace the closure with the tuple variant itself .unwrap(); // just for nicer formatting } + +async fn issue13892<'a, T, F>(maybe: Option<&'a T>, visitor: F) +where + F: AsyncFn(&'a T), + T: 'a, +{ + maybe.map(|x| visitor(x)); +} diff --git a/tests/ui/eta.rs b/tests/ui/eta.rs index 5bcc1cb26fd78..b85e8e75153a1 100644 --- a/tests/ui/eta.rs +++ b/tests/ui/eta.rs @@ -635,3 +635,11 @@ fn issue8817() { //~| HELP: replace the closure with the tuple variant itself .unwrap(); // just for nicer formatting } + +async fn issue13892<'a, T, F>(maybe: Option<&'a T>, visitor: F) +where + F: AsyncFn(&'a T), + T: 'a, +{ + maybe.map(|x| visitor(x)); +} diff --git a/tests/ui/invalid_upcast_comparisons.rs b/tests/ui/invalid_upcast_comparisons.rs index 4f3194869f430..88ecca65e1509 100644 --- a/tests/ui/invalid_upcast_comparisons.rs +++ b/tests/ui/invalid_upcast_comparisons.rs @@ -133,3 +133,15 @@ fn main() { -5 == (u32 as i32); } + +fn issue15662() { + macro_rules! add_one { + ($x:expr) => { + $x + 1 + }; + } + + let x: u8 = 1; + (add_one!(x) as u32) > 300; + //~^ invalid_upcast_comparisons +} diff --git a/tests/ui/invalid_upcast_comparisons.stderr b/tests/ui/invalid_upcast_comparisons.stderr index ef36f18eabc9a..cc042a7c4b01e 100644 --- a/tests/ui/invalid_upcast_comparisons.stderr +++ b/tests/ui/invalid_upcast_comparisons.stderr @@ -163,5 +163,11 @@ error: because of the numeric bounds on `u8` prior to casting, this expression i LL | -5 >= (u8 as i32); | ^^^^^^^^^^^^^^^^^ -error: aborting due to 27 previous errors +error: because of the numeric bounds on `add_one!(x)` prior to casting, this expression is always false + --> tests/ui/invalid_upcast_comparisons.rs:145:5 + | +LL | (add_one!(x) as u32) > 300; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 28 previous errors diff --git a/tests/ui/iter_overeager_cloned.fixed b/tests/ui/iter_overeager_cloned.fixed index b0e548f179093..4171f19469a4d 100644 --- a/tests/ui/iter_overeager_cloned.fixed +++ b/tests/ui/iter_overeager_cloned.fixed @@ -1,4 +1,4 @@ -#![warn(clippy::iter_overeager_cloned, clippy::redundant_clone, clippy::filter_next)] +#![warn(clippy::iter_overeager_cloned, clippy::redundant_iter_cloned, clippy::filter_next)] #![allow( dead_code, clippy::let_unit_value, @@ -16,7 +16,7 @@ fn main() { //~^ iter_overeager_cloned let _: usize = vec.iter().filter(|x| x == &"2").count(); - //~^ redundant_clone + //~^ redundant_iter_cloned let _: Vec<_> = vec.iter().take(2).cloned().collect(); //~^ iter_overeager_cloned @@ -77,19 +77,19 @@ fn main() { } let _ = vec.iter().map(|x| x.len()); - //~^ redundant_clone + //~^ redundant_iter_cloned // This would fail if changed. let _ = vec.iter().cloned().map(|x| x + "2"); let _ = vec.iter().for_each(|x| assert!(!x.is_empty())); - //~^ redundant_clone + //~^ redundant_iter_cloned let _ = vec.iter().all(|x| x.len() == 1); - //~^ redundant_clone + //~^ redundant_iter_cloned let _ = vec.iter().any(|x| x.len() == 1); - //~^ redundant_clone + //~^ redundant_iter_cloned // Should probably stay as it is. let _ = [0, 1, 2, 3, 4].iter().cloned().take(10); diff --git a/tests/ui/iter_overeager_cloned.rs b/tests/ui/iter_overeager_cloned.rs index cedf62a6b4730..fe6aba24dd3e2 100644 --- a/tests/ui/iter_overeager_cloned.rs +++ b/tests/ui/iter_overeager_cloned.rs @@ -1,4 +1,4 @@ -#![warn(clippy::iter_overeager_cloned, clippy::redundant_clone, clippy::filter_next)] +#![warn(clippy::iter_overeager_cloned, clippy::redundant_iter_cloned, clippy::filter_next)] #![allow( dead_code, clippy::let_unit_value, @@ -16,7 +16,7 @@ fn main() { //~^ iter_overeager_cloned let _: usize = vec.iter().filter(|x| x == &"2").cloned().count(); - //~^ redundant_clone + //~^ redundant_iter_cloned let _: Vec<_> = vec.iter().cloned().take(2).collect(); //~^ iter_overeager_cloned @@ -78,19 +78,19 @@ fn main() { } let _ = vec.iter().cloned().map(|x| x.len()); - //~^ redundant_clone + //~^ redundant_iter_cloned // This would fail if changed. let _ = vec.iter().cloned().map(|x| x + "2"); let _ = vec.iter().cloned().for_each(|x| assert!(!x.is_empty())); - //~^ redundant_clone + //~^ redundant_iter_cloned let _ = vec.iter().cloned().all(|x| x.len() == 1); - //~^ redundant_clone + //~^ redundant_iter_cloned let _ = vec.iter().cloned().any(|x| x.len() == 1); - //~^ redundant_clone + //~^ redundant_iter_cloned // Should probably stay as it is. let _ = [0, 1, 2, 3, 4].iter().cloned().take(10); diff --git a/tests/ui/iter_overeager_cloned.stderr b/tests/ui/iter_overeager_cloned.stderr index 1616dec95b792..f234d19e4aaaa 100644 --- a/tests/ui/iter_overeager_cloned.stderr +++ b/tests/ui/iter_overeager_cloned.stderr @@ -25,8 +25,8 @@ LL | let _: usize = vec.iter().filter(|x| x == &"2").cloned().count(); | | | help: try: `.count()` | - = note: `-D clippy::redundant-clone` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::redundant_clone)]` + = note: `-D clippy::redundant-iter-cloned` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::redundant_iter_cloned)]` error: unnecessarily eager cloning of iterator items --> tests/ui/iter_overeager_cloned.rs:21:21 diff --git a/tests/ui/match_as_ref.fixed b/tests/ui/match_as_ref.fixed index 8c07076af4a49..a39f0c9299bd5 100644 --- a/tests/ui/match_as_ref.fixed +++ b/tests/ui/match_as_ref.fixed @@ -41,3 +41,33 @@ fn main() { None => None, }; } + +mod issue15691 { + use std::ops::{Deref, DerefMut}; + + struct A(B); + struct B; + + impl Deref for A { + type Target = B; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl DerefMut for A { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + fn func() { + let mut a = Some(A(B)); + let mut b = Some(B); + // Do not lint, we don't have `None => None` + let _ = match b { + Some(ref mut x) => Some(x), + None => a.as_deref_mut(), + }; + } +} diff --git a/tests/ui/match_as_ref.rs b/tests/ui/match_as_ref.rs index 3a5b1227331e7..049928167901a 100644 --- a/tests/ui/match_as_ref.rs +++ b/tests/ui/match_as_ref.rs @@ -53,3 +53,33 @@ fn main() { None => None, }; } + +mod issue15691 { + use std::ops::{Deref, DerefMut}; + + struct A(B); + struct B; + + impl Deref for A { + type Target = B; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl DerefMut for A { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + fn func() { + let mut a = Some(A(B)); + let mut b = Some(B); + // Do not lint, we don't have `None => None` + let _ = match b { + Some(ref mut x) => Some(x), + None => a.as_deref_mut(), + }; + } +} diff --git a/tests/ui/multiple_unsafe_ops_per_block.rs b/tests/ui/multiple_unsafe_ops_per_block.rs index 016fd89a7b7ac..132673d5164ab 100644 --- a/tests/ui/multiple_unsafe_ops_per_block.rs +++ b/tests/ui/multiple_unsafe_ops_per_block.rs @@ -1,10 +1,10 @@ //@needs-asm-support //@aux-build:proc_macros.rs -#![allow(unused)] -#![allow(deref_nullptr)] -#![allow(clippy::unnecessary_operation)] -#![allow(dropping_copy_types)] -#![allow(clippy::assign_op_pattern)] +#![expect( + dropping_copy_types, + clippy::unnecessary_operation, + clippy::unnecessary_literal_unwrap +)] #![warn(clippy::multiple_unsafe_ops_per_block)] extern crate proc_macros; @@ -105,17 +105,17 @@ fn correct3() { } } -// tests from the issue (https://github.com/rust-lang/rust-clippy/issues/10064) - -unsafe fn read_char_bad(ptr: *const u8) -> char { - unsafe { char::from_u32_unchecked(*ptr.cast::()) } - //~^ multiple_unsafe_ops_per_block -} +fn issue10064() { + unsafe fn read_char_bad(ptr: *const u8) -> char { + unsafe { char::from_u32_unchecked(*ptr.cast::()) } + //~^ multiple_unsafe_ops_per_block + } -// no lint -unsafe fn read_char_good(ptr: *const u8) -> char { - let int_value = unsafe { *ptr.cast::() }; - unsafe { core::char::from_u32_unchecked(int_value) } + // no lint + unsafe fn read_char_good(ptr: *const u8) -> char { + let int_value = unsafe { *ptr.cast::() }; + unsafe { core::char::from_u32_unchecked(int_value) } + } } // no lint @@ -126,42 +126,87 @@ fn issue10259() { }); } -fn _fn_ptr(x: unsafe fn()) { - unsafe { - //~^ multiple_unsafe_ops_per_block - x(); - x(); +fn issue10367() { + fn fn_ptr(x: unsafe fn()) { + unsafe { + //~^ multiple_unsafe_ops_per_block + x(); + x(); + } } -} -fn _assoc_const() { - trait X { - const X: unsafe fn(); + fn assoc_const() { + trait X { + const X: unsafe fn(); + } + fn _f() { + unsafe { + //~^ multiple_unsafe_ops_per_block + T::X(); + T::X(); + } + } } - fn _f() { + + fn field_fn_ptr(x: unsafe fn()) { + struct X(unsafe fn()); + let x = X(x); unsafe { //~^ multiple_unsafe_ops_per_block - T::X(); - T::X(); + x.0(); + x.0(); } } } -fn _field_fn_ptr(x: unsafe fn()) { - struct X(unsafe fn()); - let x = X(x); +// await expands to an unsafe block with several operations, but this is fine. +async fn issue11312() { + async fn helper() {} + + helper().await; +} + +async fn issue13879() { + async fn foo() {} + + // no lint: nothing unsafe beyond the `await` which we ignore + unsafe { + foo().await; + } + + // no lint: only one unsafe call beyond the `await` + unsafe { + not_very_safe(); + foo().await; + } + + // lint: two unsafe calls beyond the `await` unsafe { //~^ multiple_unsafe_ops_per_block - x.0(); - x.0(); + not_very_safe(); + STATIC += 1; + foo().await; } -} -// await expands to an unsafe block with several operations, but this is fine.: #11312 -async fn await_desugaring_silent() { - async fn helper() {} + async unsafe fn foo_unchecked() {} - helper().await; + // no lint: only one unsafe call in the `await`ed expr + unsafe { + foo_unchecked().await; + } + + // lint: one unsafe call in the `await`ed expr, and one outside + unsafe { + //~^ multiple_unsafe_ops_per_block + not_very_safe(); + foo_unchecked().await; + } + + // lint: two unsafe calls in the `await`ed expr + unsafe { + //~^ multiple_unsafe_ops_per_block + Some(foo_unchecked()).unwrap_unchecked().await; + } } fn main() {} diff --git a/tests/ui/multiple_unsafe_ops_per_block.stderr b/tests/ui/multiple_unsafe_ops_per_block.stderr index 3130cecc25250..922a464c6b6ef 100644 --- a/tests/ui/multiple_unsafe_ops_per_block.stderr +++ b/tests/ui/multiple_unsafe_ops_per_block.stderr @@ -113,84 +113,147 @@ LL | asm!("nop"); | ^^^^^^^^^^^ error: this `unsafe` block contains 2 unsafe operations, expected only one - --> tests/ui/multiple_unsafe_ops_per_block.rs:111:5 + --> tests/ui/multiple_unsafe_ops_per_block.rs:110:9 | -LL | unsafe { char::from_u32_unchecked(*ptr.cast::()) } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { char::from_u32_unchecked(*ptr.cast::()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: unsafe function call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:111:14 + --> tests/ui/multiple_unsafe_ops_per_block.rs:110:18 | -LL | unsafe { char::from_u32_unchecked(*ptr.cast::()) } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { char::from_u32_unchecked(*ptr.cast::()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: raw pointer dereference occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:111:39 + --> tests/ui/multiple_unsafe_ops_per_block.rs:110:43 | -LL | unsafe { char::from_u32_unchecked(*ptr.cast::()) } - | ^^^^^^^^^^^^^^^^^^ +LL | unsafe { char::from_u32_unchecked(*ptr.cast::()) } + | ^^^^^^^^^^^^^^^^^^ error: this `unsafe` block contains 2 unsafe operations, expected only one - --> tests/ui/multiple_unsafe_ops_per_block.rs:130:5 + --> tests/ui/multiple_unsafe_ops_per_block.rs:131:9 | -LL | / unsafe { +LL | / unsafe { LL | | -LL | | x(); -LL | | x(); -LL | | } - | |_____^ +LL | | x(); +LL | | x(); +LL | | } + | |_________^ | note: unsafe function call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:132:9 + --> tests/ui/multiple_unsafe_ops_per_block.rs:133:13 | -LL | x(); - | ^^^ +LL | x(); + | ^^^ note: unsafe function call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:133:9 + --> tests/ui/multiple_unsafe_ops_per_block.rs:134:13 | -LL | x(); - | ^^^ +LL | x(); + | ^^^ error: this `unsafe` block contains 2 unsafe operations, expected only one - --> tests/ui/multiple_unsafe_ops_per_block.rs:142:9 + --> tests/ui/multiple_unsafe_ops_per_block.rs:143:13 + | +LL | / unsafe { +LL | | +LL | | T::X(); +LL | | T::X(); +LL | | } + | |_____________^ + | +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:145:17 + | +LL | T::X(); + | ^^^^^^ +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:146:17 + | +LL | T::X(); + | ^^^^^^ + +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> tests/ui/multiple_unsafe_ops_per_block.rs:154:9 | LL | / unsafe { LL | | -LL | | T::X(); -LL | | T::X(); +LL | | x.0(); +LL | | x.0(); LL | | } | |_________^ | note: unsafe function call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:144:13 + --> tests/ui/multiple_unsafe_ops_per_block.rs:156:13 | -LL | T::X(); - | ^^^^^^ +LL | x.0(); + | ^^^^^ note: unsafe function call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:145:13 + --> tests/ui/multiple_unsafe_ops_per_block.rs:157:13 | -LL | T::X(); - | ^^^^^^ +LL | x.0(); + | ^^^^^ error: this `unsafe` block contains 2 unsafe operations, expected only one - --> tests/ui/multiple_unsafe_ops_per_block.rs:153:5 + --> tests/ui/multiple_unsafe_ops_per_block.rs:184:5 | LL | / unsafe { LL | | -LL | | x.0(); -LL | | x.0(); +LL | | not_very_safe(); +LL | | STATIC += 1; +LL | | foo().await; LL | | } | |_____^ | note: unsafe function call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:155:9 + --> tests/ui/multiple_unsafe_ops_per_block.rs:186:9 + | +LL | not_very_safe(); + | ^^^^^^^^^^^^^^^ +note: modification of a mutable static occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:187:9 + | +LL | STATIC += 1; + | ^^^^^^^^^^^ + +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> tests/ui/multiple_unsafe_ops_per_block.rs:199:5 + | +LL | / unsafe { +LL | | +LL | | not_very_safe(); +LL | | foo_unchecked().await; +LL | | } + | |_____^ + | +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:201:9 + | +LL | not_very_safe(); + | ^^^^^^^^^^^^^^^ +note: unsafe function call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:202:9 + | +LL | foo_unchecked().await; + | ^^^^^^^^^^^^^^^ + +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> tests/ui/multiple_unsafe_ops_per_block.rs:206:5 + | +LL | / unsafe { +LL | | +LL | | Some(foo_unchecked()).unwrap_unchecked().await; +LL | | } + | |_____^ + | +note: unsafe method call occurs here + --> tests/ui/multiple_unsafe_ops_per_block.rs:208:9 | -LL | x.0(); - | ^^^^^ +LL | Some(foo_unchecked()).unwrap_unchecked().await; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: unsafe function call occurs here - --> tests/ui/multiple_unsafe_ops_per_block.rs:156:9 + --> tests/ui/multiple_unsafe_ops_per_block.rs:208:14 | -LL | x.0(); - | ^^^^^ +LL | Some(foo_unchecked()).unwrap_unchecked().await; + | ^^^^^^^^^^^^^^^ -error: aborting due to 8 previous errors +error: aborting due to 11 previous errors diff --git a/tests/ui/needless_return.fixed b/tests/ui/needless_return.fixed index d571b97f51946..f5f8bb21e815b 100644 --- a/tests/ui/needless_return.fixed +++ b/tests/ui/needless_return.fixed @@ -517,3 +517,10 @@ mod else_ifs { } } } + +fn issue14474() -> u64 { + return 456; + + #[cfg(false)] + 123 +} diff --git a/tests/ui/needless_return.rs b/tests/ui/needless_return.rs index 2e4348ea338c0..495516c1c2e5d 100644 --- a/tests/ui/needless_return.rs +++ b/tests/ui/needless_return.rs @@ -526,3 +526,10 @@ mod else_ifs { } } } + +fn issue14474() -> u64 { + return 456; + + #[cfg(false)] + 123 +} diff --git a/tests/ui/never_loop.rs b/tests/ui/never_loop.rs index 48d4b8ad15196..01db64a446c0f 100644 --- a/tests/ui/never_loop.rs +++ b/tests/ui/never_loop.rs @@ -498,3 +498,27 @@ fn issue15059() { () } } + +fn issue15350() { + 'bar: for _ in 0..100 { + //~^ never_loop + loop { + //~^ never_loop + println!("This will still run"); + break 'bar; + } + } + + 'foo: for _ in 0..100 { + //~^ never_loop + loop { + //~^ never_loop + println!("This will still run"); + loop { + //~^ never_loop + println!("This will still run"); + break 'foo; + } + } + } +} diff --git a/tests/ui/never_loop.stderr b/tests/ui/never_loop.stderr index 54b463266a3a5..4fda06cff4ab9 100644 --- a/tests/ui/never_loop.stderr +++ b/tests/ui/never_loop.stderr @@ -193,6 +193,19 @@ LL | | return; LL | | } | |_____^ | +help: this code is unreachable. Consider moving the reachable parts out + --> tests/ui/never_loop.rs:436:9 + | +LL | / loop { +LL | | +LL | | break 'outer; +LL | | } + | |_________^ +help: this code is unreachable. Consider moving the reachable parts out + --> tests/ui/never_loop.rs:440:9 + | +LL | return; + | ^^^^^^^ help: if you need the first element of the iterator, try writing | LL - 'outer: for v in 0..10 { @@ -297,5 +310,87 @@ LL ~ LL ~ | -error: aborting due to 24 previous errors +error: this loop never actually loops + --> tests/ui/never_loop.rs:503:5 + | +LL | / 'bar: for _ in 0..100 { +LL | | +LL | | loop { +... | +LL | | } + | |_____^ + | +help: this code is unreachable. Consider moving the reachable parts out + --> tests/ui/never_loop.rs:505:9 + | +LL | / loop { +LL | | +LL | | println!("This will still run"); +LL | | break 'bar; +LL | | } + | |_________^ +help: if you need the first element of the iterator, try writing + | +LL - 'bar: for _ in 0..100 { +LL + if let Some(_) = (0..100).next() { + | + +error: this loop never actually loops + --> tests/ui/never_loop.rs:505:9 + | +LL | / loop { +LL | | +LL | | println!("This will still run"); +LL | | break 'bar; +LL | | } + | |_________^ + +error: this loop never actually loops + --> tests/ui/never_loop.rs:512:5 + | +LL | / 'foo: for _ in 0..100 { +LL | | +LL | | loop { +... | +LL | | } + | |_____^ + | +help: this code is unreachable. Consider moving the reachable parts out + --> tests/ui/never_loop.rs:514:9 + | +LL | / loop { +LL | | +LL | | println!("This will still run"); +LL | | loop { +... | +LL | | } + | |_________^ +help: if you need the first element of the iterator, try writing + | +LL - 'foo: for _ in 0..100 { +LL + if let Some(_) = (0..100).next() { + | + +error: this loop never actually loops + --> tests/ui/never_loop.rs:514:9 + | +LL | / loop { +LL | | +LL | | println!("This will still run"); +LL | | loop { +... | +LL | | } + | |_________^ + +error: this loop never actually loops + --> tests/ui/never_loop.rs:517:13 + | +LL | / loop { +LL | | +LL | | println!("This will still run"); +LL | | break 'foo; +LL | | } + | |_____________^ + +error: aborting due to 29 previous errors diff --git a/tests/ui/option_if_let_else.fixed b/tests/ui/option_if_let_else.fixed index 0f86de5646cdb..6ce067f5c2462 100644 --- a/tests/ui/option_if_let_else.fixed +++ b/tests/ui/option_if_let_else.fixed @@ -125,6 +125,16 @@ fn complex_subpat() -> DummyEnum { DummyEnum::Two } +// #10335 +pub fn test_result_err_ignored_1(r: Result<&[u8], &[u8]>) -> Vec { + r.map_or_else(|_| Vec::new(), |s| s.to_owned()) +} + +// #10335 +pub fn test_result_err_ignored_2(r: Result<&[u8], &[u8]>) -> Vec { + r.map_or_else(|_| Vec::new(), |s| s.to_owned()) +} + fn main() { let optional = Some(5); let _ = optional.map_or(5, |x| x + 2); diff --git a/tests/ui/option_if_let_else.rs b/tests/ui/option_if_let_else.rs index 7aabd778f87e7..096d3aabf28db 100644 --- a/tests/ui/option_if_let_else.rs +++ b/tests/ui/option_if_let_else.rs @@ -152,6 +152,22 @@ fn complex_subpat() -> DummyEnum { DummyEnum::Two } +// #10335 +pub fn test_result_err_ignored_1(r: Result<&[u8], &[u8]>) -> Vec { + match r { + //~^ option_if_let_else + Ok(s) => s.to_owned(), + Err(_) => Vec::new(), + } +} + +// #10335 +pub fn test_result_err_ignored_2(r: Result<&[u8], &[u8]>) -> Vec { + if let Ok(s) = r { s.to_owned() } + //~^ option_if_let_else + else { Vec::new() } +} + fn main() { let optional = Some(5); let _ = if let Some(x) = optional { x + 2 } else { 5 }; diff --git a/tests/ui/option_if_let_else.stderr b/tests/ui/option_if_let_else.stderr index 2e2fe6f20492b..21a80ae038d8c 100644 --- a/tests/ui/option_if_let_else.stderr +++ b/tests/ui/option_if_let_else.stderr @@ -188,14 +188,32 @@ LL + true LL + }) | +error: use Option::map_or_else instead of an if let/else + --> tests/ui/option_if_let_else.rs:157:5 + | +LL | / match r { +LL | | +LL | | Ok(s) => s.to_owned(), +LL | | Err(_) => Vec::new(), +LL | | } + | |_____^ help: try: `r.map_or_else(|_| Vec::new(), |s| s.to_owned())` + +error: use Option::map_or_else instead of an if let/else + --> tests/ui/option_if_let_else.rs:166:5 + | +LL | / if let Ok(s) = r { s.to_owned() } +LL | | +LL | | else { Vec::new() } + | |_______________________^ help: try: `r.map_or_else(|_| Vec::new(), |s| s.to_owned())` + error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:157:13 + --> tests/ui/option_if_let_else.rs:173:13 | LL | let _ = if let Some(x) = optional { x + 2 } else { 5 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `optional.map_or(5, |x| x + 2)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:168:13 + --> tests/ui/option_if_let_else.rs:184:13 | LL | let _ = if let Some(x) = Some(0) { | _____________^ @@ -217,13 +235,13 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:197:13 + --> tests/ui/option_if_let_else.rs:213:13 | LL | let _ = if let Some(x) = Some(0) { s.len() + x } else { s.len() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(0).map_or(s.len(), |x| s.len() + x)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:202:13 + --> tests/ui/option_if_let_else.rs:218:13 | LL | let _ = if let Some(x) = Some(0) { | _____________^ @@ -245,7 +263,7 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:242:13 + --> tests/ui/option_if_let_else.rs:258:13 | LL | let _ = match s { | _____________^ @@ -256,7 +274,7 @@ LL | | }; | |_____^ help: try: `s.map_or(1, |string| string.len())` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:247:13 + --> tests/ui/option_if_let_else.rs:263:13 | LL | let _ = match Some(10) { | _____________^ @@ -267,7 +285,7 @@ LL | | }; | |_____^ help: try: `Some(10).map_or(5, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:254:13 + --> tests/ui/option_if_let_else.rs:270:13 | LL | let _ = match res { | _____________^ @@ -278,7 +296,7 @@ LL | | }; | |_____^ help: try: `res.map_or(1, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:259:13 + --> tests/ui/option_if_let_else.rs:275:13 | LL | let _ = match res { | _____________^ @@ -289,13 +307,13 @@ LL | | }; | |_____^ help: try: `res.map_or(1, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:264:13 + --> tests/ui/option_if_let_else.rs:280:13 | LL | let _ = if let Ok(a) = res { a + 1 } else { 5 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `res.map_or(5, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:282:17 + --> tests/ui/option_if_let_else.rs:298:17 | LL | let _ = match initial { | _________________^ @@ -306,7 +324,7 @@ LL | | }; | |_________^ help: try: `initial.as_ref().map_or(42, |value| do_something(value))` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:290:17 + --> tests/ui/option_if_let_else.rs:306:17 | LL | let _ = match initial { | _________________^ @@ -317,7 +335,7 @@ LL | | }; | |_________^ help: try: `initial.as_mut().map_or(42, |value| do_something2(value))` error: use Option::map_or_else instead of an if let/else - --> tests/ui/option_if_let_else.rs:314:24 + --> tests/ui/option_if_let_else.rs:330:24 | LL | let mut _hashmap = if let Some(hm) = &opt { | ________________________^ @@ -329,19 +347,19 @@ LL | | }; | |_____^ help: try: `opt.as_ref().map_or_else(HashMap::new, |hm| hm.clone())` error: use Option::map_or_else instead of an if let/else - --> tests/ui/option_if_let_else.rs:321:19 + --> tests/ui/option_if_let_else.rs:337:19 | LL | let mut _hm = if let Some(hm) = &opt { hm.clone() } else { new_map!() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.as_ref().map_or_else(|| new_map!(), |hm| hm.clone())` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:372:22 + --> tests/ui/option_if_let_else.rs:388:22 | LL | let _ = unsafe { if let Some(o) = *opt_raw_ptr { o } else { 1 } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(*opt_raw_ptr).map_or(1, |o| o)` error: use Option::map_or_else instead of an if let/else - --> tests/ui/option_if_let_else.rs:378:13 + --> tests/ui/option_if_let_else.rs:394:13 | LL | let _ = match res { | _____________^ @@ -351,5 +369,5 @@ LL | | Err(_) => String::new(), LL | | }; | |_____^ help: try: `res.map_or_else(|_| String::new(), |s| s.clone())` -error: aborting due to 27 previous errors +error: aborting due to 29 previous errors diff --git a/tests/ui/or_fun_call.fixed b/tests/ui/or_fun_call.fixed index 0a8525a12f5e0..7a0be97017ebc 100644 --- a/tests/ui/or_fun_call.fixed +++ b/tests/ui/or_fun_call.fixed @@ -77,6 +77,22 @@ fn or_fun_call() { with_default_type.unwrap_or_default(); //~^ unwrap_or_default + let with_default_literal = Some(1); + with_default_literal.unwrap_or(0); + // Do not lint because `.unwrap_or_default()` wouldn't be simpler + + let with_default_literal = Some(1.0); + with_default_literal.unwrap_or(0.0); + // Do not lint because `.unwrap_or_default()` wouldn't be simpler + + let with_default_literal = Some("foo"); + with_default_literal.unwrap_or(""); + // Do not lint because `.unwrap_or_default()` wouldn't be simpler + + let with_default_vec_macro = Some(vec![1, 2, 3]); + with_default_vec_macro.unwrap_or(vec![]); + // Do not lint because `.unwrap_or_default()` wouldn't be simpler + let self_default = None::; self_default.unwrap_or_else(::default); //~^ or_fun_call diff --git a/tests/ui/or_fun_call.rs b/tests/ui/or_fun_call.rs index b4f9b950a7fe0..724af606de9cf 100644 --- a/tests/ui/or_fun_call.rs +++ b/tests/ui/or_fun_call.rs @@ -77,6 +77,22 @@ fn or_fun_call() { with_default_type.unwrap_or(u64::default()); //~^ unwrap_or_default + let with_default_literal = Some(1); + with_default_literal.unwrap_or(0); + // Do not lint because `.unwrap_or_default()` wouldn't be simpler + + let with_default_literal = Some(1.0); + with_default_literal.unwrap_or(0.0); + // Do not lint because `.unwrap_or_default()` wouldn't be simpler + + let with_default_literal = Some("foo"); + with_default_literal.unwrap_or(""); + // Do not lint because `.unwrap_or_default()` wouldn't be simpler + + let with_default_vec_macro = Some(vec![1, 2, 3]); + with_default_vec_macro.unwrap_or(vec![]); + // Do not lint because `.unwrap_or_default()` wouldn't be simpler + let self_default = None::; self_default.unwrap_or(::default()); //~^ or_fun_call @@ -86,7 +102,7 @@ fn or_fun_call() { //~^ unwrap_or_default let with_vec = Some(vec![1]); - with_vec.unwrap_or(vec![]); + with_vec.unwrap_or(Vec::new()); //~^ unwrap_or_default let without_default = Some(Foo); @@ -98,7 +114,7 @@ fn or_fun_call() { //~^ unwrap_or_default let mut map_vec = HashMap::>::new(); - map_vec.entry(42).or_insert(vec![]); + map_vec.entry(42).or_insert(Vec::new()); //~^ unwrap_or_default let mut btree = BTreeMap::::new(); @@ -106,7 +122,7 @@ fn or_fun_call() { //~^ unwrap_or_default let mut btree_vec = BTreeMap::>::new(); - btree_vec.entry(42).or_insert(vec![]); + btree_vec.entry(42).or_insert(Vec::new()); //~^ unwrap_or_default let stringy = Some(String::new()); diff --git a/tests/ui/or_fun_call.stderr b/tests/ui/or_fun_call.stderr index 3e4df772668d7..40b25f91154dd 100644 --- a/tests/ui/or_fun_call.stderr +++ b/tests/ui/or_fun_call.stderr @@ -47,175 +47,175 @@ LL | with_default_type.unwrap_or(u64::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:81:18 + --> tests/ui/or_fun_call.rs:97:18 | LL | self_default.unwrap_or(::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(::default)` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:85:18 + --> tests/ui/or_fun_call.rs:101:18 | LL | real_default.unwrap_or(::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:89:14 + --> tests/ui/or_fun_call.rs:105:14 | -LL | with_vec.unwrap_or(vec![]); - | ^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` +LL | with_vec.unwrap_or(Vec::new()); + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:93:21 + --> tests/ui/or_fun_call.rs:109:21 | LL | without_default.unwrap_or(Foo::new()); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(Foo::new)` error: use of `or_insert` to construct default value - --> tests/ui/or_fun_call.rs:97:19 + --> tests/ui/or_fun_call.rs:113:19 | LL | map.entry(42).or_insert(String::new()); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `or_insert` to construct default value - --> tests/ui/or_fun_call.rs:101:23 + --> tests/ui/or_fun_call.rs:117:23 | -LL | map_vec.entry(42).or_insert(vec![]); - | ^^^^^^^^^^^^^^^^^ help: try: `or_default()` +LL | map_vec.entry(42).or_insert(Vec::new()); + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `or_insert` to construct default value - --> tests/ui/or_fun_call.rs:105:21 + --> tests/ui/or_fun_call.rs:121:21 | LL | btree.entry(42).or_insert(String::new()); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `or_insert` to construct default value - --> tests/ui/or_fun_call.rs:109:25 + --> tests/ui/or_fun_call.rs:125:25 | -LL | btree_vec.entry(42).or_insert(vec![]); - | ^^^^^^^^^^^^^^^^^ help: try: `or_default()` +LL | btree_vec.entry(42).or_insert(Vec::new()); + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:113:21 + --> tests/ui/or_fun_call.rs:129:21 | LL | let _ = stringy.unwrap_or(String::new()); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: function call inside of `ok_or` - --> tests/ui/or_fun_call.rs:118:17 + --> tests/ui/or_fun_call.rs:134:17 | LL | let _ = opt.ok_or(format!("{} world.", hello)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ok_or_else(|| format!("{} world.", hello))` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:123:21 + --> tests/ui/or_fun_call.rs:139:21 | LL | let _ = Some(1).unwrap_or(map[&1]); | ^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| map[&1])` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:126:21 + --> tests/ui/or_fun_call.rs:142:21 | LL | let _ = Some(1).unwrap_or(map[&1]); | ^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| map[&1])` error: function call inside of `or` - --> tests/ui/or_fun_call.rs:151:35 + --> tests/ui/or_fun_call.rs:167:35 | LL | let _ = Some("a".to_string()).or(Some("b".to_string())); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_else(|| Some("b".to_string()))` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:194:18 + --> tests/ui/or_fun_call.rs:210:18 | LL | None.unwrap_or(ptr_to_ref(s)); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| ptr_to_ref(s))` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:202:14 + --> tests/ui/or_fun_call.rs:218:14 | LL | None.unwrap_or(unsafe { ptr_to_ref(s) }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| unsafe { ptr_to_ref(s) })` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:205:14 + --> tests/ui/or_fun_call.rs:221:14 | LL | None.unwrap_or( unsafe { ptr_to_ref(s) } ); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| unsafe { ptr_to_ref(s) })` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:281:25 + --> tests/ui/or_fun_call.rs:297:25 | LL | let _ = Some(4).map_or(g(), |v| v); | ^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(g, |v| v)` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:283:25 + --> tests/ui/or_fun_call.rs:299:25 | LL | let _ = Some(4).map_or(g(), f); | ^^^^^^^^^^^^^^ help: try: `map_or_else(g, f)` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:286:25 + --> tests/ui/or_fun_call.rs:302:25 | LL | let _ = Some(4).map_or("asd".to_string().len() as i32, f); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(|| "asd".to_string().len() as i32, f)` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:317:18 + --> tests/ui/or_fun_call.rs:333:18 | LL | with_new.unwrap_or_else(Vec::new); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:321:28 + --> tests/ui/or_fun_call.rs:337:28 | LL | with_default_trait.unwrap_or_else(Default::default); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:325:27 + --> tests/ui/or_fun_call.rs:341:27 | LL | with_default_type.unwrap_or_else(u64::default); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:329:22 + --> tests/ui/or_fun_call.rs:345:22 | LL | real_default.unwrap_or_else(::default); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `or_insert_with` to construct default value - --> tests/ui/or_fun_call.rs:333:23 + --> tests/ui/or_fun_call.rs:349:23 | LL | map.entry(42).or_insert_with(String::new); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `or_insert_with` to construct default value - --> tests/ui/or_fun_call.rs:337:25 + --> tests/ui/or_fun_call.rs:353:25 | LL | btree.entry(42).or_insert_with(String::new); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:341:25 + --> tests/ui/or_fun_call.rs:357:25 | LL | let _ = stringy.unwrap_or_else(String::new); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:383:17 + --> tests/ui/or_fun_call.rs:399:17 | LL | let _ = opt.unwrap_or({ f() }); // suggest `.unwrap_or_else(f)` | ^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(f)` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:388:17 + --> tests/ui/or_fun_call.rs:404:17 | LL | let _ = opt.unwrap_or(f() + 1); // suggest `.unwrap_or_else(|| f() + 1)` | ^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| f() + 1)` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:393:17 + --> tests/ui/or_fun_call.rs:409:17 | LL | let _ = opt.unwrap_or({ | _________________^ @@ -235,55 +235,55 @@ LL ~ }); | error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:399:17 + --> tests/ui/or_fun_call.rs:415:17 | LL | let _ = opt.map_or(f() + 1, |v| v); // suggest `.map_or_else(|| f() + 1, |v| v)` | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(|| f() + 1, |v| v)` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:404:17 + --> tests/ui/or_fun_call.rs:420:17 | LL | let _ = opt.unwrap_or({ i32::default() }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:411:21 + --> tests/ui/or_fun_call.rs:427:21 | LL | let _ = opt_foo.unwrap_or(Foo { val: String::default() }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| Foo { val: String::default() })` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:426:19 + --> tests/ui/or_fun_call.rs:442:19 | LL | let _ = x.map_or(g(), |v| v); | ^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(|_| g(), |v| v)` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:428:19 + --> tests/ui/or_fun_call.rs:444:19 | LL | let _ = x.map_or(g(), f); | ^^^^^^^^^^^^^^ help: try: `map_or_else(|_| g(), f)` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:431:19 + --> tests/ui/or_fun_call.rs:447:19 | LL | let _ = x.map_or("asd".to_string().len() as i32, f); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(|_| "asd".to_string().len() as i32, f)` error: function call inside of `get_or_insert` - --> tests/ui/or_fun_call.rs:442:15 + --> tests/ui/or_fun_call.rs:458:15 | LL | let _ = x.get_or_insert(g()); | ^^^^^^^^^^^^^^^^^^ help: try: `get_or_insert_with(g)` error: function call inside of `and` - --> tests/ui/or_fun_call.rs:452:15 + --> tests/ui/or_fun_call.rs:468:15 | LL | let _ = x.and(g()); | ^^^^^^^^ help: try: `and_then(|_| g())` error: function call inside of `and` - --> tests/ui/or_fun_call.rs:462:15 + --> tests/ui/or_fun_call.rs:478:15 | LL | let _ = x.and(g()); | ^^^^^^^^ help: try: `and_then(|_| g())` diff --git a/tests/ui/ptr_offset_with_cast.fixed b/tests/ui/ptr_offset_with_cast.fixed index 4fe9dcf46c357..42d1abeaa05a6 100644 --- a/tests/ui/ptr_offset_with_cast.fixed +++ b/tests/ui/ptr_offset_with_cast.fixed @@ -1,4 +1,4 @@ -#![allow(clippy::unnecessary_cast, clippy::useless_vec)] +#![expect(clippy::unnecessary_cast, clippy::useless_vec, clippy::needless_borrow)] fn main() { let vec = vec![b'a', b'b', b'c']; @@ -18,5 +18,25 @@ fn main() { //~^ ptr_offset_with_cast let _ = ptr.wrapping_offset(offset_isize as isize); let _ = ptr.wrapping_offset(offset_u8 as isize); + + let _ = S.offset(offset_usize as isize); + let _ = S.wrapping_offset(offset_usize as isize); + + let _ = (&ptr).add(offset_usize); + //~^ ptr_offset_with_cast + let _ = (&ptr).wrapping_add(offset_usize); + //~^ ptr_offset_with_cast + } +} + +#[derive(Clone, Copy)] +struct S; + +impl S { + fn offset(self, _: isize) -> Self { + self + } + fn wrapping_offset(self, _: isize) -> Self { + self } } diff --git a/tests/ui/ptr_offset_with_cast.rs b/tests/ui/ptr_offset_with_cast.rs index a1fb892733d35..6d06a6af1fa2d 100644 --- a/tests/ui/ptr_offset_with_cast.rs +++ b/tests/ui/ptr_offset_with_cast.rs @@ -1,4 +1,4 @@ -#![allow(clippy::unnecessary_cast, clippy::useless_vec)] +#![expect(clippy::unnecessary_cast, clippy::useless_vec, clippy::needless_borrow)] fn main() { let vec = vec![b'a', b'b', b'c']; @@ -18,5 +18,25 @@ fn main() { //~^ ptr_offset_with_cast let _ = ptr.wrapping_offset(offset_isize as isize); let _ = ptr.wrapping_offset(offset_u8 as isize); + + let _ = S.offset(offset_usize as isize); + let _ = S.wrapping_offset(offset_usize as isize); + + let _ = (&ptr).offset(offset_usize as isize); + //~^ ptr_offset_with_cast + let _ = (&ptr).wrapping_offset(offset_usize as isize); + //~^ ptr_offset_with_cast + } +} + +#[derive(Clone, Copy)] +struct S; + +impl S { + fn offset(self, _: isize) -> Self { + self + } + fn wrapping_offset(self, _: isize) -> Self { + self } } diff --git a/tests/ui/ptr_offset_with_cast.stderr b/tests/ui/ptr_offset_with_cast.stderr index dcd5e027d1829..022b3286c93b7 100644 --- a/tests/ui/ptr_offset_with_cast.stderr +++ b/tests/ui/ptr_offset_with_cast.stderr @@ -2,16 +2,51 @@ error: use of `offset` with a `usize` casted to an `isize` --> tests/ui/ptr_offset_with_cast.rs:12:17 | LL | let _ = ptr.offset(offset_usize as isize); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr.add(offset_usize)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::ptr-offset-with-cast` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::ptr_offset_with_cast)]` +help: use `add` instead + | +LL - let _ = ptr.offset(offset_usize as isize); +LL + let _ = ptr.add(offset_usize); + | error: use of `wrapping_offset` with a `usize` casted to an `isize` --> tests/ui/ptr_offset_with_cast.rs:17:17 | LL | let _ = ptr.wrapping_offset(offset_usize as isize); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr.wrapping_add(offset_usize)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `wrapping_add` instead + | +LL - let _ = ptr.wrapping_offset(offset_usize as isize); +LL + let _ = ptr.wrapping_add(offset_usize); + | + +error: use of `offset` with a `usize` casted to an `isize` + --> tests/ui/ptr_offset_with_cast.rs:25:17 + | +LL | let _ = (&ptr).offset(offset_usize as isize); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `add` instead + | +LL - let _ = (&ptr).offset(offset_usize as isize); +LL + let _ = (&ptr).add(offset_usize); + | + +error: use of `wrapping_offset` with a `usize` casted to an `isize` + --> tests/ui/ptr_offset_with_cast.rs:27:17 + | +LL | let _ = (&ptr).wrapping_offset(offset_usize as isize); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `wrapping_add` instead + | +LL - let _ = (&ptr).wrapping_offset(offset_usize as isize); +LL + let _ = (&ptr).wrapping_add(offset_usize); + | -error: aborting due to 2 previous errors +error: aborting due to 4 previous errors diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index 8d6f5fbadca56..ac81b324c2049 100644 --- a/tests/ui/question_mark.fixed +++ b/tests/ui/question_mark.fixed @@ -1,6 +1,4 @@ #![feature(try_blocks)] -#![allow(unreachable_code)] -#![allow(dead_code)] #![allow(clippy::unnecessary_wraps)] use std::sync::MutexGuard; @@ -465,3 +463,15 @@ fn issue_13642(x: Option) -> Option<()> { None } + +fn issue_15679() -> Result { + let some_result: Result = todo!(); + + some_result?; + + some_result?; + + some_result?; + + Ok(0) +} diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs index f13eee29c113c..b5866dac6b8f6 100644 --- a/tests/ui/question_mark.rs +++ b/tests/ui/question_mark.rs @@ -1,6 +1,4 @@ #![feature(try_blocks)] -#![allow(unreachable_code)] -#![allow(dead_code)] #![allow(clippy::unnecessary_wraps)] use std::sync::MutexGuard; @@ -561,3 +559,27 @@ fn issue_13642(x: Option) -> Option<()> { None } + +fn issue_15679() -> Result { + let some_result: Result = todo!(); + + match some_result { + //~^ question_mark + Ok(val) => val, + Err(err) => return Err(err.into()), + }; + + match some_result { + //~^ question_mark + Ok(val) => val, + Err(err) => return Err(Into::into(err)), + }; + + match some_result { + //~^ question_mark + Ok(val) => val, + Err(err) => return Err(<&str as Into>::into(err)), + }; + + Ok(0) +} diff --git a/tests/ui/question_mark.stderr b/tests/ui/question_mark.stderr index d8ce4420aeeb6..1ecd936292e55 100644 --- a/tests/ui/question_mark.stderr +++ b/tests/ui/question_mark.stderr @@ -1,5 +1,5 @@ error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:9:5 + --> tests/ui/question_mark.rs:7:5 | LL | / if a.is_none() { LL | | @@ -11,7 +11,7 @@ LL | | } = help: to override `-D warnings` add `#[allow(clippy::question_mark)]` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:55:9 + --> tests/ui/question_mark.rs:53:9 | LL | / if (self.opt).is_none() { LL | | @@ -20,7 +20,7 @@ LL | | } | |_________^ help: replace it with: `(self.opt)?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:60:9 + --> tests/ui/question_mark.rs:58:9 | LL | / if self.opt.is_none() { LL | | @@ -29,7 +29,7 @@ LL | | } | |_________^ help: replace it with: `self.opt?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:65:17 + --> tests/ui/question_mark.rs:63:17 | LL | let _ = if self.opt.is_none() { | _________________^ @@ -41,7 +41,7 @@ LL | | }; | |_________^ help: replace it with: `Some(self.opt?)` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:72:17 + --> tests/ui/question_mark.rs:70:17 | LL | let _ = if let Some(x) = self.opt { | _________________^ @@ -53,7 +53,7 @@ LL | | }; | |_________^ help: replace it with: `self.opt?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:90:9 + --> tests/ui/question_mark.rs:88:9 | LL | / if self.opt.is_none() { LL | | @@ -62,7 +62,7 @@ LL | | } | |_________^ help: replace it with: `self.opt.as_ref()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:99:9 + --> tests/ui/question_mark.rs:97:9 | LL | / if self.opt.is_none() { LL | | @@ -71,7 +71,7 @@ LL | | } | |_________^ help: replace it with: `self.opt.as_ref()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:108:9 + --> tests/ui/question_mark.rs:106:9 | LL | / if self.opt.is_none() { LL | | @@ -80,7 +80,7 @@ LL | | } | |_________^ help: replace it with: `self.opt.as_ref()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:116:26 + --> tests/ui/question_mark.rs:114:26 | LL | let v: &Vec<_> = if let Some(ref v) = self.opt { | __________________________^ @@ -92,7 +92,7 @@ LL | | }; | |_________^ help: replace it with: `self.opt.as_ref()?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:127:17 + --> tests/ui/question_mark.rs:125:17 | LL | let v = if let Some(v) = self.opt { | _________________^ @@ -104,7 +104,7 @@ LL | | }; | |_________^ help: replace it with: `self.opt?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:149:5 + --> tests/ui/question_mark.rs:147:5 | LL | / if f().is_none() { LL | | @@ -113,7 +113,7 @@ LL | | } | |_____^ help: replace it with: `f()?;` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:154:16 + --> tests/ui/question_mark.rs:152:16 | LL | let _val = match f() { | ________________^ @@ -124,7 +124,7 @@ LL | | }; | |_____^ help: try instead: `f()?` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:165:5 + --> tests/ui/question_mark.rs:163:5 | LL | / match f() { LL | | @@ -134,7 +134,7 @@ LL | | }; | |_____^ help: try instead: `f()?` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:171:5 + --> tests/ui/question_mark.rs:169:5 | LL | / match opt_none!() { LL | | @@ -144,13 +144,13 @@ LL | | }; | |_____^ help: try instead: `opt_none!()?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:198:13 + --> tests/ui/question_mark.rs:196:13 | LL | let _ = if let Ok(x) = x { x } else { return x }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:201:5 + --> tests/ui/question_mark.rs:199:5 | LL | / if x.is_err() { LL | | @@ -159,7 +159,7 @@ LL | | } | |_____^ help: replace it with: `x?;` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:206:16 + --> tests/ui/question_mark.rs:204:16 | LL | let _val = match func_returning_result() { | ________________^ @@ -170,7 +170,7 @@ LL | | }; | |_____^ help: try instead: `func_returning_result()?` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:212:5 + --> tests/ui/question_mark.rs:210:5 | LL | / match func_returning_result() { LL | | @@ -180,7 +180,7 @@ LL | | }; | |_____^ help: try instead: `func_returning_result()?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:304:5 + --> tests/ui/question_mark.rs:302:5 | LL | / if let Err(err) = func_returning_result() { LL | | @@ -189,7 +189,7 @@ LL | | } | |_____^ help: replace it with: `func_returning_result()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:312:5 + --> tests/ui/question_mark.rs:310:5 | LL | / if let Err(err) = func_returning_result() { LL | | @@ -198,7 +198,7 @@ LL | | } | |_____^ help: replace it with: `func_returning_result()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:395:13 + --> tests/ui/question_mark.rs:393:13 | LL | / if a.is_none() { LL | | @@ -208,7 +208,7 @@ LL | | } | |_____________^ help: replace it with: `a?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:456:5 + --> tests/ui/question_mark.rs:454:5 | LL | / let Some(v) = bar.foo.owned.clone() else { LL | | return None; @@ -216,7 +216,7 @@ LL | | }; | |______^ help: replace it with: `let v = bar.foo.owned.clone()?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:471:5 + --> tests/ui/question_mark.rs:469:5 | LL | / let Some(ref x) = foo.opt_x else { LL | | return None; @@ -224,7 +224,7 @@ LL | | }; | |______^ help: replace it with: `let x = foo.opt_x.as_ref()?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:481:5 + --> tests/ui/question_mark.rs:479:5 | LL | / let Some(ref mut x) = foo.opt_x else { LL | | return None; @@ -232,7 +232,7 @@ LL | | }; | |______^ help: replace it with: `let x = foo.opt_x.as_mut()?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:492:5 + --> tests/ui/question_mark.rs:490:5 | LL | / let Some(ref x @ ref y) = foo.opt_x else { LL | | return None; @@ -240,7 +240,7 @@ LL | | }; | |______^ help: replace it with: `let x @ y = foo.opt_x.as_ref()?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:496:5 + --> tests/ui/question_mark.rs:494:5 | LL | / let Some(ref x @ WrapperStructWithString(_)) = bar else { LL | | return None; @@ -248,7 +248,7 @@ LL | | }; | |______^ help: replace it with: `let x @ &WrapperStructWithString(_) = bar.as_ref()?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:500:5 + --> tests/ui/question_mark.rs:498:5 | LL | / let Some(ref mut x @ WrapperStructWithString(_)) = bar else { LL | | return None; @@ -256,7 +256,7 @@ LL | | }; | |______^ help: replace it with: `let x @ &mut WrapperStructWithString(_) = bar.as_mut()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:522:5 + --> tests/ui/question_mark.rs:520:5 | LL | / if arg.is_none() { LL | | @@ -265,7 +265,7 @@ LL | | } | |_____^ help: replace it with: `arg?;` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:526:15 + --> tests/ui/question_mark.rs:524:15 | LL | let val = match arg { | _______________^ @@ -276,12 +276,42 @@ LL | | }; | |_____^ help: try instead: `arg?` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:536:5 + --> tests/ui/question_mark.rs:534:5 | LL | / let Some(a) = *a else { LL | | return None; LL | | }; | |______^ help: replace it with: `let a = (*a)?;` -error: aborting due to 30 previous errors +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:566:5 + | +LL | / match some_result { +LL | | +LL | | Ok(val) => val, +LL | | Err(err) => return Err(err.into()), +LL | | }; + | |_____^ help: try instead: `some_result?` + +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:572:5 + | +LL | / match some_result { +LL | | +LL | | Ok(val) => val, +LL | | Err(err) => return Err(Into::into(err)), +LL | | }; + | |_____^ help: try instead: `some_result?` + +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:578:5 + | +LL | / match some_result { +LL | | +LL | | Ok(val) => val, +LL | | Err(err) => return Err(<&str as Into>::into(err)), +LL | | }; + | |_____^ help: try instead: `some_result?` + +error: aborting due to 33 previous errors diff --git a/tests/ui/read_zero_byte_vec.rs b/tests/ui/read_zero_byte_vec.rs index 938d61b68607c..720276cb55488 100644 --- a/tests/ui/read_zero_byte_vec.rs +++ b/tests/ui/read_zero_byte_vec.rs @@ -120,3 +120,30 @@ fn allow_works(mut f: F) { } fn main() {} + +fn issue15575() -> usize { + use std::io::Read; + use std::net::TcpListener; + + let listener = TcpListener::bind("127.0.0.1:9010").unwrap(); + let mut stream_and_addr = listener.accept().unwrap(); + let mut buf = Vec::with_capacity(32); + let num_bytes_received = stream_and_addr.0.read(&mut buf).unwrap(); + //~^ read_zero_byte_vec + + let cap = 1000; + let mut buf = Vec::with_capacity(cap); + let num_bytes_received = stream_and_addr.0.read(&mut buf).unwrap(); + //~^ read_zero_byte_vec + + let cap = 1000; + let mut buf = Vec::with_capacity(cap); + let num_bytes_received = { stream_and_addr.0.read(&mut buf) }.unwrap(); + //~^ read_zero_byte_vec + + use std::fs::File; + let mut f = File::open("foo.txt").unwrap(); + let mut data = Vec::with_capacity(100); + f.read(&mut data).unwrap() + //~^ read_zero_byte_vec +} diff --git a/tests/ui/read_zero_byte_vec.stderr b/tests/ui/read_zero_byte_vec.stderr index 8f255bc87ab82..8dd74592e4c15 100644 --- a/tests/ui/read_zero_byte_vec.stderr +++ b/tests/ui/read_zero_byte_vec.stderr @@ -2,16 +2,27 @@ error: reading zero byte data to `Vec` --> tests/ui/read_zero_byte_vec.rs:22:5 | LL | f.read_exact(&mut data).unwrap(); - | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `data.resize(20, 0); f.read_exact(&mut data)` + | ^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::read-zero-byte-vec` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::read_zero_byte_vec)]` +help: try + | +LL ~ data.resize(20, 0); +LL ~ f.read_exact(&mut data).unwrap(); + | error: reading zero byte data to `Vec` --> tests/ui/read_zero_byte_vec.rs:28:5 | LL | f.read_exact(&mut data2)?; - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `data2.resize(cap, 0); f.read_exact(&mut data2)` + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ data2.resize(cap, 0); +LL ~ f.read_exact(&mut data2)?; + | error: reading zero byte data to `Vec` --> tests/ui/read_zero_byte_vec.rs:33:5 @@ -67,5 +78,53 @@ error: reading zero byte data to `Vec` LL | r.read_exact(&mut data2).await.unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 11 previous errors +error: reading zero byte data to `Vec` + --> tests/ui/read_zero_byte_vec.rs:131:30 + | +LL | let num_bytes_received = stream_and_addr.0.read(&mut buf).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ buf.resize(32, 0); +LL ~ let num_bytes_received = stream_and_addr.0.read(&mut buf).unwrap(); + | + +error: reading zero byte data to `Vec` + --> tests/ui/read_zero_byte_vec.rs:136:30 + | +LL | let num_bytes_received = stream_and_addr.0.read(&mut buf).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ buf.resize(cap, 0); +LL ~ let num_bytes_received = stream_and_addr.0.read(&mut buf).unwrap(); + | + +error: reading zero byte data to `Vec` + --> tests/ui/read_zero_byte_vec.rs:141:32 + | +LL | let num_bytes_received = { stream_and_addr.0.read(&mut buf) }.unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ buf.resize(cap, 0); +LL ~ let num_bytes_received = { stream_and_addr.0.read(&mut buf) }.unwrap(); + | + +error: reading zero byte data to `Vec` + --> tests/ui/read_zero_byte_vec.rs:147:5 + | +LL | f.read(&mut data).unwrap() + | ^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ data.resize(100, 0); +LL ~ f.read(&mut data).unwrap() + | + +error: aborting due to 15 previous errors diff --git a/tests/ui/ref_option/ref_option.all.fixed b/tests/ui/ref_option/ref_option.all.fixed deleted file mode 100644 index 4159e916f1995..0000000000000 --- a/tests/ui/ref_option/ref_option.all.fixed +++ /dev/null @@ -1,79 +0,0 @@ -//@revisions: private all -//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/private -//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/all - -#![allow(unused, clippy::needless_lifetimes, clippy::borrowed_box)] -#![warn(clippy::ref_option)] - -fn opt_u8(a: Option<&u8>) {} -//~^ ref_option -fn opt_gen(a: Option<&T>) {} -//~^ ref_option -fn opt_string(a: std::option::Option<&String>) {} -//~^ ref_option -fn ret_string<'a>(p: &'a str) -> Option<&'a u8> { - //~^ ref_option - panic!() -} -fn ret_string_static() -> Option<&'static u8> { - //~^ ref_option - panic!() -} -fn mult_string(a: Option<&String>, b: Option<&Vec>) {} -//~^ ref_option -fn ret_box<'a>() -> Option<&'a Box> { - //~^ ref_option - panic!() -} - -pub fn pub_opt_string(a: Option<&String>) {} -//~[all]^ ref_option -pub fn pub_mult_string(a: Option<&String>, b: Option<&Vec>) {} -//~[all]^ ref_option - -pub trait PubTrait { - fn pub_trait_opt(&self, a: Option<&Vec>); - //~[all]^ ref_option - fn pub_trait_ret(&self) -> Option<&Vec>; - //~[all]^ ref_option -} - -trait PrivateTrait { - fn trait_opt(&self, a: Option<&String>); - //~^ ref_option - fn trait_ret(&self) -> Option<&String>; - //~^ ref_option -} - -pub struct PubStruct; - -impl PubStruct { - pub fn pub_opt_params(&self, a: Option<&()>) {} - //~[all]^ ref_option - pub fn pub_opt_ret(&self) -> Option<&String> { - //~[all]^ ref_option - panic!() - } - - fn private_opt_params(&self, a: Option<&()>) {} - //~^ ref_option - fn private_opt_ret(&self) -> Option<&String> { - //~^ ref_option - panic!() - } -} - -// valid, don't change -fn mut_u8(a: &mut Option) {} -pub fn pub_mut_u8(a: &mut Option) {} - -// might be good to catch in the future -fn mut_u8_ref(a: &mut &Option) {} -pub fn pub_mut_u8_ref(a: &mut &Option) {} -fn lambdas() { - // Not handled for now, not sure if we should - let x = |a: &Option| {}; - let x = |a: &Option| -> &Option { panic!() }; -} - -fn main() {} diff --git a/tests/ui/ref_option/ref_option.private.fixed b/tests/ui/ref_option/ref_option.private.fixed deleted file mode 100644 index 3b158befb926e..0000000000000 --- a/tests/ui/ref_option/ref_option.private.fixed +++ /dev/null @@ -1,79 +0,0 @@ -//@revisions: private all -//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/private -//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/all - -#![allow(unused, clippy::needless_lifetimes, clippy::borrowed_box)] -#![warn(clippy::ref_option)] - -fn opt_u8(a: Option<&u8>) {} -//~^ ref_option -fn opt_gen(a: Option<&T>) {} -//~^ ref_option -fn opt_string(a: std::option::Option<&String>) {} -//~^ ref_option -fn ret_string<'a>(p: &'a str) -> Option<&'a u8> { - //~^ ref_option - panic!() -} -fn ret_string_static() -> Option<&'static u8> { - //~^ ref_option - panic!() -} -fn mult_string(a: Option<&String>, b: Option<&Vec>) {} -//~^ ref_option -fn ret_box<'a>() -> Option<&'a Box> { - //~^ ref_option - panic!() -} - -pub fn pub_opt_string(a: &Option) {} -//~[all]^ ref_option -pub fn pub_mult_string(a: &Option, b: &Option>) {} -//~[all]^ ref_option - -pub trait PubTrait { - fn pub_trait_opt(&self, a: &Option>); - //~[all]^ ref_option - fn pub_trait_ret(&self) -> &Option>; - //~[all]^ ref_option -} - -trait PrivateTrait { - fn trait_opt(&self, a: Option<&String>); - //~^ ref_option - fn trait_ret(&self) -> Option<&String>; - //~^ ref_option -} - -pub struct PubStruct; - -impl PubStruct { - pub fn pub_opt_params(&self, a: &Option<()>) {} - //~[all]^ ref_option - pub fn pub_opt_ret(&self) -> &Option { - //~[all]^ ref_option - panic!() - } - - fn private_opt_params(&self, a: Option<&()>) {} - //~^ ref_option - fn private_opt_ret(&self) -> Option<&String> { - //~^ ref_option - panic!() - } -} - -// valid, don't change -fn mut_u8(a: &mut Option) {} -pub fn pub_mut_u8(a: &mut Option) {} - -// might be good to catch in the future -fn mut_u8_ref(a: &mut &Option) {} -pub fn pub_mut_u8_ref(a: &mut &Option) {} -fn lambdas() { - // Not handled for now, not sure if we should - let x = |a: &Option| {}; - let x = |a: &Option| -> &Option { panic!() }; -} - -fn main() {} diff --git a/tests/ui/ref_option/ref_option.rs b/tests/ui/ref_option/ref_option.rs deleted file mode 100644 index 35cd94174f8bc..0000000000000 --- a/tests/ui/ref_option/ref_option.rs +++ /dev/null @@ -1,79 +0,0 @@ -//@revisions: private all -//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/private -//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/all - -#![allow(unused, clippy::needless_lifetimes, clippy::borrowed_box)] -#![warn(clippy::ref_option)] - -fn opt_u8(a: &Option) {} -//~^ ref_option -fn opt_gen(a: &Option) {} -//~^ ref_option -fn opt_string(a: &std::option::Option) {} -//~^ ref_option -fn ret_string<'a>(p: &'a str) -> &'a Option { - //~^ ref_option - panic!() -} -fn ret_string_static() -> &'static Option { - //~^ ref_option - panic!() -} -fn mult_string(a: &Option, b: &Option>) {} -//~^ ref_option -fn ret_box<'a>() -> &'a Option> { - //~^ ref_option - panic!() -} - -pub fn pub_opt_string(a: &Option) {} -//~[all]^ ref_option -pub fn pub_mult_string(a: &Option, b: &Option>) {} -//~[all]^ ref_option - -pub trait PubTrait { - fn pub_trait_opt(&self, a: &Option>); - //~[all]^ ref_option - fn pub_trait_ret(&self) -> &Option>; - //~[all]^ ref_option -} - -trait PrivateTrait { - fn trait_opt(&self, a: &Option); - //~^ ref_option - fn trait_ret(&self) -> &Option; - //~^ ref_option -} - -pub struct PubStruct; - -impl PubStruct { - pub fn pub_opt_params(&self, a: &Option<()>) {} - //~[all]^ ref_option - pub fn pub_opt_ret(&self) -> &Option { - //~[all]^ ref_option - panic!() - } - - fn private_opt_params(&self, a: &Option<()>) {} - //~^ ref_option - fn private_opt_ret(&self) -> &Option { - //~^ ref_option - panic!() - } -} - -// valid, don't change -fn mut_u8(a: &mut Option) {} -pub fn pub_mut_u8(a: &mut Option) {} - -// might be good to catch in the future -fn mut_u8_ref(a: &mut &Option) {} -pub fn pub_mut_u8_ref(a: &mut &Option) {} -fn lambdas() { - // Not handled for now, not sure if we should - let x = |a: &Option| {}; - let x = |a: &Option| -> &Option { panic!() }; -} - -fn main() {} diff --git a/tests/ui/ref_option/ref_option_traits.rs b/tests/ui/ref_option/ref_option_traits.rs deleted file mode 100644 index 4c773e84f8da8..0000000000000 --- a/tests/ui/ref_option/ref_option_traits.rs +++ /dev/null @@ -1,40 +0,0 @@ -//@no-rustfix: fixes are only done to traits, not the impls -//@revisions: private all -//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/private -//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/all - -#![warn(clippy::ref_option)] - -pub trait PubTrait { - fn pub_trait_opt(&self, a: &Option>); - //~[all]^ ref_option - fn pub_trait_ret(&self) -> &Option>; - //~[all]^ ref_option -} - -trait PrivateTrait { - fn trait_opt(&self, a: &Option); - //~^ ref_option - fn trait_ret(&self) -> &Option; - //~^ ref_option -} - -pub struct PubStruct; - -impl PubTrait for PubStruct { - fn pub_trait_opt(&self, a: &Option>) {} - fn pub_trait_ret(&self) -> &Option> { - panic!() - } -} - -struct PrivateStruct; - -impl PrivateTrait for PrivateStruct { - fn trait_opt(&self, a: &Option) {} - fn trait_ret(&self) -> &Option { - panic!() - } -} - -fn main() {} diff --git a/tests/ui/rest_pat_in_fully_bound_structs.fixed b/tests/ui/rest_pat_in_fully_bound_structs.fixed new file mode 100644 index 0000000000000..ac200b3b19b74 --- /dev/null +++ b/tests/ui/rest_pat_in_fully_bound_structs.fixed @@ -0,0 +1,61 @@ +#![warn(clippy::rest_pat_in_fully_bound_structs)] +#![allow(clippy::struct_field_names)] + +struct A { + a: i32, + b: i64, + c: &'static str, +} + +macro_rules! foo { + ($param:expr) => { + match $param { + A { a: 0, b: 0, c: "", .. } => {}, + _ => {}, + } + }; +} + +fn main() { + let a_struct = A { a: 5, b: 42, c: "A" }; + + match a_struct { + A { a: 5, b: 42, c: "", } => {}, // Lint + //~^ rest_pat_in_fully_bound_structs + A { a: 0, b: 0, c: "", } => {}, // Lint + //~^ rest_pat_in_fully_bound_structs + _ => {}, + } + + match a_struct { + A { a: 5, b: 42, .. } => {}, + A { a: 0, b: 0, c: "", } => {}, // Lint + //~^ rest_pat_in_fully_bound_structs + _ => {}, + } + + // No lint + match a_struct { + A { a: 5, .. } => {}, + A { a: 0, b: 0, .. } => {}, + _ => {}, + } + + // No lint + foo!(a_struct); + + #[non_exhaustive] + struct B { + a: u32, + b: u32, + c: u64, + } + + let b_struct = B { a: 5, b: 42, c: 342 }; + + match b_struct { + B { a: 5, b: 42, .. } => {}, + B { a: 0, b: 0, c: 128, .. } => {}, // No Lint + _ => {}, + } +} diff --git a/tests/ui/rest_pat_in_fully_bound_structs.stderr b/tests/ui/rest_pat_in_fully_bound_structs.stderr index d048933ddb7bc..8a2da302b9ef2 100644 --- a/tests/ui/rest_pat_in_fully_bound_structs.stderr +++ b/tests/ui/rest_pat_in_fully_bound_structs.stderr @@ -4,9 +4,13 @@ error: unnecessary use of `..` pattern in struct binding. All fields were alread LL | A { a: 5, b: 42, c: "", .. } => {}, // Lint | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: consider removing `..` from this binding = note: `-D clippy::rest-pat-in-fully-bound-structs` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::rest_pat_in_fully_bound_structs)]` +help: consider removing `..` from this binding + | +LL - A { a: 5, b: 42, c: "", .. } => {}, // Lint +LL + A { a: 5, b: 42, c: "", } => {}, // Lint + | error: unnecessary use of `..` pattern in struct binding. All fields were already bound --> tests/ui/rest_pat_in_fully_bound_structs.rs:25:9 @@ -14,7 +18,11 @@ error: unnecessary use of `..` pattern in struct binding. All fields were alread LL | A { a: 0, b: 0, c: "", .. } => {}, // Lint | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: consider removing `..` from this binding +help: consider removing `..` from this binding + | +LL - A { a: 0, b: 0, c: "", .. } => {}, // Lint +LL + A { a: 0, b: 0, c: "", } => {}, // Lint + | error: unnecessary use of `..` pattern in struct binding. All fields were already bound --> tests/ui/rest_pat_in_fully_bound_structs.rs:32:9 @@ -22,7 +30,11 @@ error: unnecessary use of `..` pattern in struct binding. All fields were alread LL | A { a: 0, b: 0, c: "", .. } => {}, // Lint | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: consider removing `..` from this binding +help: consider removing `..` from this binding + | +LL - A { a: 0, b: 0, c: "", .. } => {}, // Lint +LL + A { a: 0, b: 0, c: "", } => {}, // Lint + | error: aborting due to 3 previous errors diff --git a/tests/ui/semicolon_inside_block.fixed b/tests/ui/semicolon_inside_block.fixed index 7308e78aae263..468f0a5b1e472 100644 --- a/tests/ui/semicolon_inside_block.fixed +++ b/tests/ui/semicolon_inside_block.fixed @@ -3,7 +3,8 @@ clippy::unused_unit, clippy::unnecessary_operation, clippy::no_effect, - clippy::single_element_loop + clippy::single_element_loop, + clippy::double_parens )] #![warn(clippy::semicolon_inside_block)] @@ -87,6 +88,20 @@ fn main() { unit_fn_block() } +#[rustfmt::skip] +fn issue15380() { + ( {0;0}); + + ({ + 0; + 0 + }); + + (({ 0 })) ; + + ( ( { 0 } ) ) ; +} + pub fn issue15388() { #[rustfmt::skip] {0; 0}; diff --git a/tests/ui/semicolon_inside_block.rs b/tests/ui/semicolon_inside_block.rs index 467bf4d779f2a..101374af26471 100644 --- a/tests/ui/semicolon_inside_block.rs +++ b/tests/ui/semicolon_inside_block.rs @@ -3,7 +3,8 @@ clippy::unused_unit, clippy::unnecessary_operation, clippy::no_effect, - clippy::single_element_loop + clippy::single_element_loop, + clippy::double_parens )] #![warn(clippy::semicolon_inside_block)] @@ -87,6 +88,20 @@ fn main() { unit_fn_block() } +#[rustfmt::skip] +fn issue15380() { + ( {0;0}); + + ({ + 0; + 0 + }); + + (({ 0 })) ; + + ( ( { 0 } ) ) ; +} + pub fn issue15388() { #[rustfmt::skip] {0; 0}; diff --git a/tests/ui/semicolon_inside_block.stderr b/tests/ui/semicolon_inside_block.stderr index 23433f4e7ef90..2046dd1c36be7 100644 --- a/tests/ui/semicolon_inside_block.stderr +++ b/tests/ui/semicolon_inside_block.stderr @@ -1,5 +1,5 @@ error: consider moving the `;` inside the block for consistent formatting - --> tests/ui/semicolon_inside_block.rs:38:5 + --> tests/ui/semicolon_inside_block.rs:39:5 | LL | { unit_fn_block() }; | ^^^^^^^^^^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL + { unit_fn_block(); } | error: consider moving the `;` inside the block for consistent formatting - --> tests/ui/semicolon_inside_block.rs:40:5 + --> tests/ui/semicolon_inside_block.rs:41:5 | LL | unsafe { unit_fn_block() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL + unsafe { unit_fn_block(); } | error: consider moving the `;` inside the block for consistent formatting - --> tests/ui/semicolon_inside_block.rs:49:5 + --> tests/ui/semicolon_inside_block.rs:50:5 | LL | / { LL | | @@ -41,7 +41,7 @@ LL ~ } | error: consider moving the `;` inside the block for consistent formatting - --> tests/ui/semicolon_inside_block.rs:63:5 + --> tests/ui/semicolon_inside_block.rs:64:5 | LL | { m!(()) }; | ^^^^^^^^^^^ diff --git a/tests/ui/track-diagnostics-clippy.stderr b/tests/ui/track-diagnostics-clippy.stderr index d5533877b4515..3ceb501463b77 100644 --- a/tests/ui/track-diagnostics-clippy.stderr +++ b/tests/ui/track-diagnostics-clippy.stderr @@ -16,7 +16,7 @@ LL | let d = 42; LL | d | ^ | - = note: -Ztrack-diagnostics: created at clippy_lints/src/returns.rs:LL:CC + = note: -Ztrack-diagnostics: created at clippy_lints/src/returns/let_and_return.rs:LL:CC = note: `-D clippy::let-and-return` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::let_and_return)]` help: return the expression directly diff --git a/tests/ui/transmute_ptr_to_ref.fixed b/tests/ui/transmute_ptr_to_ref.fixed index 61e3ac2fe88e3..c130575df9607 100644 --- a/tests/ui/transmute_ptr_to_ref.fixed +++ b/tests/ui/transmute_ptr_to_ref.fixed @@ -5,7 +5,7 @@ clippy::missing_transmute_annotations )] -unsafe fn _ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { +fn ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { unsafe { let _: &T = &*p; //~^ transmute_ptr_to_ref @@ -37,7 +37,7 @@ unsafe fn _ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { } } -fn _issue1231() { +fn issue1231() { struct Foo<'a, T> { bar: &'a T, } @@ -55,7 +55,7 @@ fn _issue1231() { //~^ transmute_ptr_to_ref } -unsafe fn _issue8924<'a, 'b, 'c>(x: *const &'a u32, y: *const &'b u32) -> &'c &'b u32 { +fn issue8924<'a, 'b, 'c>(x: *const &'a u32, y: *const &'b u32) -> &'c &'b u32 { unsafe { match 0 { 0 => &*x.cast::<&u32>(), @@ -71,7 +71,7 @@ unsafe fn _issue8924<'a, 'b, 'c>(x: *const &'a u32, y: *const &'b u32) -> &'c &' } #[clippy::msrv = "1.38"] -unsafe fn _meets_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { +fn meets_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { unsafe { let a = 0u32; let a = &a as *const u32; @@ -89,7 +89,7 @@ unsafe fn _meets_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { } #[clippy::msrv = "1.37"] -unsafe fn _under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { +fn under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { unsafe { let a = 0u32; let a = &a as *const u32; @@ -106,4 +106,33 @@ unsafe fn _under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { } } +// handle DSTs +fn issue13357(ptr: *const [i32], s_ptr: *const &str, a_s_ptr: *const [&str]) { + unsafe { + // different types, without erased regions + let _ = &*(ptr as *const [u32]); + //~^ transmute_ptr_to_ref + let _: &[u32] = &*(ptr as *const [u32]); + //~^ transmute_ptr_to_ref + + // different types, with erased regions + let _ = &*(a_s_ptr as *const [&[u8]]); + //~^ transmute_ptr_to_ref + let _: &[&[u8]] = &*(a_s_ptr as *const [&[u8]]); + //~^ transmute_ptr_to_ref + + // same type, without erased regions + let _ = &*(ptr as *const [i32]); + //~^ transmute_ptr_to_ref + let _: &[i32] = &*ptr; + //~^ transmute_ptr_to_ref + + // same type, with erased regions + let _ = &*(a_s_ptr as *const [&str]); + //~^ transmute_ptr_to_ref + let _: &[&str] = &*(a_s_ptr as *const [&str]); + //~^ transmute_ptr_to_ref + } +} + fn main() {} diff --git a/tests/ui/transmute_ptr_to_ref.rs b/tests/ui/transmute_ptr_to_ref.rs index 48e2f527b554c..f79d54234a2c9 100644 --- a/tests/ui/transmute_ptr_to_ref.rs +++ b/tests/ui/transmute_ptr_to_ref.rs @@ -5,7 +5,7 @@ clippy::missing_transmute_annotations )] -unsafe fn _ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { +fn ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { unsafe { let _: &T = std::mem::transmute(p); //~^ transmute_ptr_to_ref @@ -37,7 +37,7 @@ unsafe fn _ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { } } -fn _issue1231() { +fn issue1231() { struct Foo<'a, T> { bar: &'a T, } @@ -55,7 +55,7 @@ fn _issue1231() { //~^ transmute_ptr_to_ref } -unsafe fn _issue8924<'a, 'b, 'c>(x: *const &'a u32, y: *const &'b u32) -> &'c &'b u32 { +fn issue8924<'a, 'b, 'c>(x: *const &'a u32, y: *const &'b u32) -> &'c &'b u32 { unsafe { match 0 { 0 => std::mem::transmute(x), @@ -71,7 +71,7 @@ unsafe fn _issue8924<'a, 'b, 'c>(x: *const &'a u32, y: *const &'b u32) -> &'c &' } #[clippy::msrv = "1.38"] -unsafe fn _meets_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { +fn meets_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { unsafe { let a = 0u32; let a = &a as *const u32; @@ -89,7 +89,7 @@ unsafe fn _meets_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { } #[clippy::msrv = "1.37"] -unsafe fn _under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { +fn under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { unsafe { let a = 0u32; let a = &a as *const u32; @@ -106,4 +106,33 @@ unsafe fn _under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { } } +// handle DSTs +fn issue13357(ptr: *const [i32], s_ptr: *const &str, a_s_ptr: *const [&str]) { + unsafe { + // different types, without erased regions + let _ = core::mem::transmute::<_, &[u32]>(ptr); + //~^ transmute_ptr_to_ref + let _: &[u32] = core::mem::transmute(ptr); + //~^ transmute_ptr_to_ref + + // different types, with erased regions + let _ = core::mem::transmute::<_, &[&[u8]]>(a_s_ptr); + //~^ transmute_ptr_to_ref + let _: &[&[u8]] = core::mem::transmute(a_s_ptr); + //~^ transmute_ptr_to_ref + + // same type, without erased regions + let _ = core::mem::transmute::<_, &[i32]>(ptr); + //~^ transmute_ptr_to_ref + let _: &[i32] = core::mem::transmute(ptr); + //~^ transmute_ptr_to_ref + + // same type, with erased regions + let _ = core::mem::transmute::<_, &[&str]>(a_s_ptr); + //~^ transmute_ptr_to_ref + let _: &[&str] = core::mem::transmute(a_s_ptr); + //~^ transmute_ptr_to_ref + } +} + fn main() {} diff --git a/tests/ui/transmute_ptr_to_ref.stderr b/tests/ui/transmute_ptr_to_ref.stderr index 7685c345c8619..3f404d295fef0 100644 --- a/tests/ui/transmute_ptr_to_ref.stderr +++ b/tests/ui/transmute_ptr_to_ref.stderr @@ -43,13 +43,13 @@ error: transmute from a pointer type (`*mut U`) to a reference type (`&T`) LL | let _: &T = std::mem::transmute(om); | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(om as *const T)` -error: transmute from a pointer type (`*const i32`) to a reference type (`&_issue1231::Foo<'_, u8>`) +error: transmute from a pointer type (`*const i32`) to a reference type (`&issue1231::Foo<'_, u8>`) --> tests/ui/transmute_ptr_to_ref.rs:46:32 | LL | let _: &Foo = unsafe { std::mem::transmute::<_, &Foo<_>>(raw) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*raw.cast::>()` -error: transmute from a pointer type (`*const i32`) to a reference type (`&_issue1231::Foo<'_, &u8>`) +error: transmute from a pointer type (`*const i32`) to a reference type (`&issue1231::Foo<'_, &u8>`) --> tests/ui/transmute_ptr_to_ref.rs:49:33 | LL | let _: &Foo<&u8> = unsafe { std::mem::transmute::<_, &Foo<&_>>(raw) }; @@ -133,5 +133,53 @@ error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32 LL | _ => std::mem::transmute::<_, &&'b u32>(x), | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(x as *const () as *const &'b u32)` -error: aborting due to 22 previous errors +error: transmute from a pointer type (`*const [i32]`) to a reference type (`&[u32]`) + --> tests/ui/transmute_ptr_to_ref.rs:113:17 + | +LL | let _ = core::mem::transmute::<_, &[u32]>(ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(ptr as *const [u32])` + +error: transmute from a pointer type (`*const [i32]`) to a reference type (`&[u32]`) + --> tests/ui/transmute_ptr_to_ref.rs:115:25 + | +LL | let _: &[u32] = core::mem::transmute(ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(ptr as *const [u32])` + +error: transmute from a pointer type (`*const [&str]`) to a reference type (`&[&[u8]]`) + --> tests/ui/transmute_ptr_to_ref.rs:119:17 + | +LL | let _ = core::mem::transmute::<_, &[&[u8]]>(a_s_ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(a_s_ptr as *const [&[u8]])` + +error: transmute from a pointer type (`*const [&str]`) to a reference type (`&[&[u8]]`) + --> tests/ui/transmute_ptr_to_ref.rs:121:27 + | +LL | let _: &[&[u8]] = core::mem::transmute(a_s_ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(a_s_ptr as *const [&[u8]])` + +error: transmute from a pointer type (`*const [i32]`) to a reference type (`&[i32]`) + --> tests/ui/transmute_ptr_to_ref.rs:125:17 + | +LL | let _ = core::mem::transmute::<_, &[i32]>(ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(ptr as *const [i32])` + +error: transmute from a pointer type (`*const [i32]`) to a reference type (`&[i32]`) + --> tests/ui/transmute_ptr_to_ref.rs:127:25 + | +LL | let _: &[i32] = core::mem::transmute(ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*ptr` + +error: transmute from a pointer type (`*const [&str]`) to a reference type (`&[&str]`) + --> tests/ui/transmute_ptr_to_ref.rs:131:17 + | +LL | let _ = core::mem::transmute::<_, &[&str]>(a_s_ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(a_s_ptr as *const [&str])` + +error: transmute from a pointer type (`*const [&str]`) to a reference type (`&[&str]`) + --> tests/ui/transmute_ptr_to_ref.rs:133:26 + | +LL | let _: &[&str] = core::mem::transmute(a_s_ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(a_s_ptr as *const [&str])` + +error: aborting due to 30 previous errors diff --git a/tests/ui/use_self.fixed b/tests/ui/use_self.fixed index cccb6bffabb71..075e31d202b06 100644 --- a/tests/ui/use_self.fixed +++ b/tests/ui/use_self.fixed @@ -530,8 +530,8 @@ mod issue7206 { impl<'a> S2> { fn new_again() -> Self { - Self::new() - //~^ use_self + S2::new() + // FIXME: ^Broken by PR #15611 } } } @@ -755,3 +755,17 @@ mod crash_check_13128 { } } } + +mod issue_13277 { + trait Foo { + type Item<'foo>; + } + struct Bar<'b> { + content: &'b str, + } + impl<'b> Foo for Option> { + // when checking whether `Option>` has a lifetime, check not only the outer + // `Option`, but also the inner `Bar<'foo>` + type Item<'foo> = Option>; + } +} diff --git a/tests/ui/use_self.rs b/tests/ui/use_self.rs index 09288677aa715..6fbba0bbc550b 100644 --- a/tests/ui/use_self.rs +++ b/tests/ui/use_self.rs @@ -531,7 +531,7 @@ mod issue7206 { impl<'a> S2> { fn new_again() -> Self { S2::new() - //~^ use_self + // FIXME: ^Broken by PR #15611 } } } @@ -755,3 +755,17 @@ mod crash_check_13128 { } } } + +mod issue_13277 { + trait Foo { + type Item<'foo>; + } + struct Bar<'b> { + content: &'b str, + } + impl<'b> Foo for Option> { + // when checking whether `Option>` has a lifetime, check not only the outer + // `Option`, but also the inner `Bar<'foo>` + type Item<'foo> = Option>; + } +} diff --git a/tests/ui/use_self.stderr b/tests/ui/use_self.stderr index 781327696ac19..5f65c53ea25c3 100644 --- a/tests/ui/use_self.stderr +++ b/tests/ui/use_self.stderr @@ -169,12 +169,6 @@ error: unnecessary structure name repetition LL | A::new::(submod::B {}) | ^ help: use the applicable keyword: `Self` -error: unnecessary structure name repetition - --> tests/ui/use_self.rs:533:13 - | -LL | S2::new() - | ^^ help: use the applicable keyword: `Self` - error: unnecessary structure name repetition --> tests/ui/use_self.rs:571:17 | @@ -259,5 +253,5 @@ error: unnecessary structure name repetition LL | E::A => {}, | ^ help: use the applicable keyword: `Self` -error: aborting due to 43 previous errors +error: aborting due to 42 previous errors diff --git a/tests/ui/useless_attribute.fixed b/tests/ui/useless_attribute.fixed index be4fb55ddfb86..15070dd9c2ca6 100644 --- a/tests/ui/useless_attribute.fixed +++ b/tests/ui/useless_attribute.fixed @@ -158,3 +158,13 @@ pub mod redundant_imports_issue { empty!(); } + +pub mod issue15636 { + pub mod f { + #[deprecated(since = "TBD")] + pub mod deprec {} + } + + #[allow(deprecated_in_future)] + pub use f::deprec; +} diff --git a/tests/ui/useless_attribute.rs b/tests/ui/useless_attribute.rs index 5a1bcf97a5b46..3f530de7fd8e3 100644 --- a/tests/ui/useless_attribute.rs +++ b/tests/ui/useless_attribute.rs @@ -158,3 +158,13 @@ pub mod redundant_imports_issue { empty!(); } + +pub mod issue15636 { + pub mod f { + #[deprecated(since = "TBD")] + pub mod deprec {} + } + + #[allow(deprecated_in_future)] + pub use f::deprec; +} From 63a55083aa03e94dc380af31d51e3ffd944cb2b4 Mon Sep 17 00:00:00 2001 From: pommicket Date: Mon, 12 May 2025 10:32:21 -0400 Subject: [PATCH 15/93] Add clippy::self_only_used_in_recursion lint and use it instead of clippy::only_used_in_recursion when the parameter in question is self. --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/only_used_in_recursion.rs | 153 ++++++++++++++++----- tests/ui/only_used_in_recursion.rs | 3 +- tests/ui/only_used_in_recursion.stderr | 70 +++++----- 5 files changed, 162 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a9be02143722..f4e4cbfe37509 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6597,6 +6597,7 @@ Released 2018-09-13 [`self_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_assignment [`self_named_constructors`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_constructors [`self_named_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_module_files +[`self_only_used_in_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_only_used_in_recursion [`semicolon_if_nothing_returned`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_if_nothing_returned [`semicolon_inside_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_inside_block [`semicolon_outside_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_outside_block diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 1b19a8851edd4..c67026e6f266d 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -575,6 +575,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES_INFO, crate::octal_escapes::OCTAL_ESCAPES_INFO, crate::only_used_in_recursion::ONLY_USED_IN_RECURSION_INFO, + crate::only_used_in_recursion::SELF_ONLY_USED_IN_RECURSION_INFO, crate::operators::ABSURD_EXTREME_COMPARISONS_INFO, crate::operators::ARITHMETIC_SIDE_EFFECTS_INFO, crate::operators::ASSIGN_OP_PATTERN_INFO, diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs index a42763172f565..bd2fb9d1aa72a 100644 --- a/clippy_lints/src/only_used_in_recursion.rs +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -24,6 +24,33 @@ declare_clippy_lint! { /// the calculations have no side-effects (function calls or mutating dereference) /// and the assigned variables are also only in recursion, it is useless. /// + /// ### Example + /// ```no_run + /// fn f(a: usize, b: usize) -> usize { + /// if a == 0 { + /// 1 + /// } else { + /// f(a - 1, b + 1) + /// } + /// } + /// # fn main() { + /// # print!("{}", f(1, 1)); + /// # } + /// ``` + /// Use instead: + /// ```no_run + /// fn f(a: usize) -> usize { + /// if a == 0 { + /// 1 + /// } else { + /// f(a - 1) + /// } + /// } + /// # fn main() { + /// # print!("{}", f(1)); + /// # } + /// ``` + /// /// ### Known problems /// Too many code paths in the linting code are currently untested and prone to produce false /// positives or are prone to have performance implications. @@ -51,39 +78,90 @@ declare_clippy_lint! { /// - struct pattern binding /// /// Also, when you recurse the function name with path segments, it is not possible to detect. + #[clippy::version = "1.61.0"] + pub ONLY_USED_IN_RECURSION, + complexity, + "arguments that is only used in recursion can be removed" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `self` receiver that is only used in recursion with no side-effects. + /// + /// ### Why is this bad? + /// + /// It may be possible to remove the `self` argument, allowing the function to be + /// used without an object of type `Self`. /// /// ### Example /// ```no_run - /// fn f(a: usize, b: usize) -> usize { - /// if a == 0 { - /// 1 - /// } else { - /// f(a - 1, b + 1) + /// struct Foo; + /// impl Foo { + /// fn f(&self, n: u32) -> u32 { + /// if n == 0 { + /// 1 + /// } else { + /// n * self.f(n - 1) + /// } /// } /// } /// # fn main() { - /// # print!("{}", f(1, 1)); + /// # print!("{}", Foo.f(10)); /// # } /// ``` /// Use instead: /// ```no_run - /// fn f(a: usize) -> usize { - /// if a == 0 { - /// 1 - /// } else { - /// f(a - 1) + /// struct Foo; + /// impl Foo { + /// fn f(n: u32) -> u32 { + /// if n == 0 { + /// 1 + /// } else { + /// n * Self::f(n - 1) + /// } /// } /// } /// # fn main() { - /// # print!("{}", f(1)); + /// # print!("{}", Foo::f(10)); /// # } /// ``` - #[clippy::version = "1.61.0"] - pub ONLY_USED_IN_RECURSION, - complexity, - "arguments that is only used in recursion can be removed" + /// + /// ### Known problems + /// Too many code paths in the linting code are currently untested and prone to produce false + /// positives or are prone to have performance implications. + /// + /// In some cases, this would not catch all useless arguments. + /// + /// ```no_run + /// struct Foo; + /// impl Foo { + /// fn foo(&self, a: usize) -> usize { + /// let f = |x| x; + /// + /// if a == 0 { + /// 1 + /// } else { + /// f(self).foo(a) + /// } + /// } + /// } + /// ``` + /// + /// For example, here `self` is only used in recursion, but the lint would not catch it. + /// + /// List of some examples that can not be caught: + /// - binary operation of non-primitive types + /// - closure usage + /// - some `break` relative operations + /// - struct pattern binding + /// + /// Also, when you recurse the function name with path segments, it is not possible to detect. + #[clippy::version = "1.92.0"] + pub SELF_ONLY_USED_IN_RECURSION, + pedantic, + "self receiver only used to recursively call method can be removed" } -impl_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION]); +impl_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION, SELF_ONLY_USED_IN_RECURSION]); #[derive(Clone, Copy)] enum FnKind { @@ -355,26 +433,39 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion { self.params.flag_for_linting(); for param in &self.params.params { if param.apply_lint.get() { - span_lint_and_then( - cx, - ONLY_USED_IN_RECURSION, - param.ident.span, - "parameter is only used in recursion", - |diag| { - if param.ident.name != kw::SelfLower { + if param.ident.name == kw::SelfLower { + span_lint_and_then( + cx, + SELF_ONLY_USED_IN_RECURSION, + param.ident.span, + "`self` is only used in recursion", + |diag| { + diag.span_note( + param.uses.iter().map(|x| x.span).collect::>(), + "`self` used here", + ); + }, + ); + } else { + span_lint_and_then( + cx, + ONLY_USED_IN_RECURSION, + param.ident.span, + "parameter is only used in recursion", + |diag| { diag.span_suggestion( param.ident.span, "if this is intentional, prefix it with an underscore", format!("_{}", param.ident.name), Applicability::MaybeIncorrect, ); - } - diag.span_note( - param.uses.iter().map(|x| x.span).collect::>(), - "parameter used here", - ); - }, - ); + diag.span_note( + param.uses.iter().map(|x| x.span).collect::>(), + "parameter used here", + ); + }, + ); + } } } self.params.clear(); diff --git a/tests/ui/only_used_in_recursion.rs b/tests/ui/only_used_in_recursion.rs index 7d6075ba9ea47..d58635969238b 100644 --- a/tests/ui/only_used_in_recursion.rs +++ b/tests/ui/only_used_in_recursion.rs @@ -1,4 +1,5 @@ #![warn(clippy::only_used_in_recursion)] +#![warn(clippy::self_only_used_in_recursion)] //@no-rustfix fn _simple(x: u32) -> u32 { x @@ -74,7 +75,7 @@ impl A { } fn _method_self(&self, flag: usize, a: usize) -> usize { - //~^ only_used_in_recursion + //~^ self_only_used_in_recursion //~| only_used_in_recursion if flag == 0 { 0 } else { self._method_self(flag - 1, a) } diff --git a/tests/ui/only_used_in_recursion.stderr b/tests/ui/only_used_in_recursion.stderr index ca08319e11200..5d1e3e9c50fd9 100644 --- a/tests/ui/only_used_in_recursion.stderr +++ b/tests/ui/only_used_in_recursion.stderr @@ -1,11 +1,11 @@ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:11:27 + --> tests/ui/only_used_in_recursion.rs:12:27 | LL | fn _one_unused(flag: u32, a: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:14:53 + --> tests/ui/only_used_in_recursion.rs:15:53 | LL | if flag == 0 { 0 } else { _one_unused(flag - 1, a) } | ^ @@ -13,181 +13,183 @@ LL | if flag == 0 { 0 } else { _one_unused(flag - 1, a) } = help: to override `-D warnings` add `#[allow(clippy::only_used_in_recursion)]` error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:17:27 + --> tests/ui/only_used_in_recursion.rs:18:27 | LL | fn _two_unused(flag: u32, a: u32, b: i32) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:21:53 + --> tests/ui/only_used_in_recursion.rs:22:53 | LL | if flag == 0 { 0 } else { _two_unused(flag - 1, a, b) } | ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:17:35 + --> tests/ui/only_used_in_recursion.rs:18:35 | LL | fn _two_unused(flag: u32, a: u32, b: i32) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:21:56 + --> tests/ui/only_used_in_recursion.rs:22:56 | LL | if flag == 0 { 0 } else { _two_unused(flag - 1, a, b) } | ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:24:26 + --> tests/ui/only_used_in_recursion.rs:25:26 | LL | fn _with_calc(flag: u32, a: i64) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:30:32 + --> tests/ui/only_used_in_recursion.rs:31:32 | LL | _with_calc(flag - 1, (-a + 10) * 5) | ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:39:33 + --> tests/ui/only_used_in_recursion.rs:40:33 | LL | fn _used_with_unused(flag: u32, a: i32, b: i32) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:46:38 + --> tests/ui/only_used_in_recursion.rs:47:38 | LL | _used_with_unused(flag - 1, -a, a + b) | ^ ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:39:41 + --> tests/ui/only_used_in_recursion.rs:40:41 | LL | fn _used_with_unused(flag: u32, a: i32, b: i32) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:46:45 + --> tests/ui/only_used_in_recursion.rs:47:45 | LL | _used_with_unused(flag - 1, -a, a + b) | ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:50:35 + --> tests/ui/only_used_in_recursion.rs:51:35 | LL | fn _codependent_unused(flag: u32, a: i32, b: i32) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:57:39 + --> tests/ui/only_used_in_recursion.rs:58:39 | LL | _codependent_unused(flag - 1, a * b, a + b) | ^ ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:50:43 + --> tests/ui/only_used_in_recursion.rs:51:43 | LL | fn _codependent_unused(flag: u32, a: i32, b: i32) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:57:43 + --> tests/ui/only_used_in_recursion.rs:58:43 | LL | _codependent_unused(flag - 1, a * b, a + b) | ^ ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:61:30 + --> tests/ui/only_used_in_recursion.rs:62:30 | LL | fn _not_primitive(flag: u32, b: String) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:64:56 + --> tests/ui/only_used_in_recursion.rs:65:56 | LL | if flag == 0 { 0 } else { _not_primitive(flag - 1, b) } | ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:70:29 + --> tests/ui/only_used_in_recursion.rs:71:29 | LL | fn _method(flag: usize, a: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:73:59 + --> tests/ui/only_used_in_recursion.rs:74:59 | LL | if flag == 0 { 0 } else { Self::_method(flag - 1, a) } | ^ -error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:76:22 +error: `self` is only used in recursion + --> tests/ui/only_used_in_recursion.rs:77:22 | LL | fn _method_self(&self, flag: usize, a: usize) -> usize { | ^^^^ | -note: parameter used here - --> tests/ui/only_used_in_recursion.rs:80:35 +note: `self` used here + --> tests/ui/only_used_in_recursion.rs:81:35 | LL | if flag == 0 { 0 } else { self._method_self(flag - 1, a) } | ^^^^ + = note: `-D clippy::self-only-used-in-recursion` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::self_only_used_in_recursion)]` error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:76:41 + --> tests/ui/only_used_in_recursion.rs:77:41 | LL | fn _method_self(&self, flag: usize, a: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:80:63 + --> tests/ui/only_used_in_recursion.rs:81:63 | LL | if flag == 0 { 0 } else { self._method_self(flag - 1, a) } | ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:90:26 + --> tests/ui/only_used_in_recursion.rs:91:26 | LL | fn method(flag: u32, a: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:93:58 + --> tests/ui/only_used_in_recursion.rs:94:58 | LL | if flag == 0 { 0 } else { Self::method(flag - 1, a) } | ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:96:38 + --> tests/ui/only_used_in_recursion.rs:97:38 | LL | fn method_self(&self, flag: u32, a: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:99:62 + --> tests/ui/only_used_in_recursion.rs:100:62 | LL | if flag == 0 { 0 } else { self.method_self(flag - 1, a) } | ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:124:26 + --> tests/ui/only_used_in_recursion.rs:125:26 | LL | fn method(flag: u32, a: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:127:58 + --> tests/ui/only_used_in_recursion.rs:128:58 | LL | if flag == 0 { 0 } else { Self::method(flag - 1, a) } | ^ error: parameter is only used in recursion - --> tests/ui/only_used_in_recursion.rs:130:38 + --> tests/ui/only_used_in_recursion.rs:131:38 | LL | fn method_self(&self, flag: u32, a: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: parameter used here - --> tests/ui/only_used_in_recursion.rs:133:62 + --> tests/ui/only_used_in_recursion.rs:134:62 | LL | if flag == 0 { 0 } else { self.method_self(flag - 1, a) } | ^ From 4a909bc6b6d39f3596e12ebd77f1dc02f039af3d Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Fri, 19 Sep 2025 13:24:16 +0500 Subject: [PATCH 16/93] Fix versions lints from 1.90.0 --- clippy_lints/src/doc/mod.rs | 2 +- clippy_lints/src/operators/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index eca3bc390d778..f8ae770b3a4db 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -315,7 +315,7 @@ declare_clippy_lint! { /// /// [example of a good link](https://github.com/rust-lang/rust-clippy/) /// pub fn do_something() {} /// ``` - #[clippy::version = "1.84.0"] + #[clippy::version = "1.90.0"] pub DOC_BROKEN_LINK, pedantic, "broken document link" diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index bdbbb3475cd5f..aaea4ff11fc37 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -854,7 +854,7 @@ declare_clippy_lint! { /// println!("{a} is divisible by {b}"); /// } /// ``` - #[clippy::version = "1.89.0"] + #[clippy::version = "1.90.0"] pub MANUAL_IS_MULTIPLE_OF, complexity, "manual implementation of `.is_multiple_of()`" From fb7e89cc7268f7b02d39e2c61ab4bd2df458a586 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Tue, 9 Sep 2025 23:30:24 +0800 Subject: [PATCH 17/93] fix: `question_mark` FP on variables used after --- clippy_lints/src/question_mark.rs | 10 +++++++++- tests/ui/question_mark.fixed | 25 ++++++++++++++++++++++++ tests/ui/question_mark.rs | 32 +++++++++++++++++++++++++++++++ tests/ui/question_mark.stderr | 22 ++++++++++++++++++++- 4 files changed, 87 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 4aa100a50e053..d3a5a5dddfbeb 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -6,7 +6,8 @@ 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::ty::{implements_trait, is_type_diagnostic_item}; +use clippy_utils::ty::{implements_trait, is_copy, is_type_diagnostic_item}; +use clippy_utils::usage::local_used_after_expr; use clippy_utils::{ eq_expr_value, fn_def_id_with_node_args, higher, is_else_clause, is_in_const_context, is_lint_allowed, is_path_lang_item, is_res_lang_ctor, pat_and_expr_can_be_question_mark, path_res, path_to_local, path_to_local_id, @@ -483,6 +484,13 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: .filter(|e| *e) .is_none() { + if !is_copy(cx, caller_ty) + && let Some(hir_id) = path_to_local(let_expr) + && local_used_after_expr(cx, hir_id, expr) + { + return; + } + let mut applicability = Applicability::MachineApplicable; let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability); let requires_semi = matches!(cx.tcx.parent_hir_node(expr.hir_id), Node::Stmt(_)); diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index ac81b324c2049..2c5ee02450389 100644 --- a/tests/ui/question_mark.fixed +++ b/tests/ui/question_mark.fixed @@ -475,3 +475,28 @@ fn issue_15679() -> Result { Ok(0) } + +mod issue14894 { + fn use_after_question_mark(do_something_else: impl Fn() -> Result) -> Result<(), ()> { + let result = do_something_else(); + if let Err(reason) = result { + return Err(reason); + } + drop(result); + + let result = do_something_else(); + let x = result?; + drop(x); + + Ok(()) + } + + #[expect(dropping_copy_types)] + fn use_after_question_mark_but_is_copy(do_something_else: impl Fn() -> Result) -> Result<(), ()> { + let result = do_something_else(); + result?; + drop(result); + + Ok(()) + } +} diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs index b5866dac6b8f6..b9ff9d1565b2f 100644 --- a/tests/ui/question_mark.rs +++ b/tests/ui/question_mark.rs @@ -583,3 +583,35 @@ fn issue_15679() -> Result { Ok(0) } + +mod issue14894 { + fn use_after_question_mark(do_something_else: impl Fn() -> Result) -> Result<(), ()> { + let result = do_something_else(); + if let Err(reason) = result { + return Err(reason); + } + drop(result); + + let result = do_something_else(); + let x = match result { + //~^ question_mark + Ok(v) => v, + Err(e) => return Err(e), + }; + drop(x); + + Ok(()) + } + + #[expect(dropping_copy_types)] + fn use_after_question_mark_but_is_copy(do_something_else: impl Fn() -> Result) -> Result<(), ()> { + let result = do_something_else(); + if let Err(reason) = result { + //~^ question_mark + return Err(reason); + } + drop(result); + + Ok(()) + } +} diff --git a/tests/ui/question_mark.stderr b/tests/ui/question_mark.stderr index 1ecd936292e55..9b2896328e665 100644 --- a/tests/ui/question_mark.stderr +++ b/tests/ui/question_mark.stderr @@ -313,5 +313,25 @@ LL | | Err(err) => return Err(<&str as Into>::into(err)), LL | | }; | |_____^ help: try instead: `some_result?` -error: aborting due to 33 previous errors +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:596:17 + | +LL | let x = match result { + | _________________^ +LL | | +LL | | Ok(v) => v, +LL | | Err(e) => return Err(e), +LL | | }; + | |_________^ help: try instead: `result?` + +error: this block may be rewritten with the `?` operator + --> tests/ui/question_mark.rs:609:9 + | +LL | / if let Err(reason) = result { +LL | | +LL | | return Err(reason); +LL | | } + | |_________^ help: replace it with: `result?;` + +error: aborting due to 35 previous errors From 3e65620bef7d86a8f914a2a408dd91ca66b3499d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Justus=20Fl=C3=BCgel?= Date: Sun, 7 Sep 2025 15:33:11 +0200 Subject: [PATCH 18/93] Add tests for `explicit_deref_methods` lints from within proc macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Justus Flügel --- tests/ui/explicit_deref_methods.fixed | 15 +++++++++++++++ tests/ui/explicit_deref_methods.rs | 15 +++++++++++++++ tests/ui/explicit_deref_methods.stderr | 26 +++++++++++++------------- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/tests/ui/explicit_deref_methods.fixed b/tests/ui/explicit_deref_methods.fixed index 52c4d1b1f301a..97e8e0bafe4fd 100644 --- a/tests/ui/explicit_deref_methods.fixed +++ b/tests/ui/explicit_deref_methods.fixed @@ -1,3 +1,4 @@ +//@aux-build:proc_macros.rs #![warn(clippy::explicit_deref_methods)] #![allow(unused_variables, unused_must_use)] #![allow( @@ -14,6 +15,8 @@ use std::ops::{Deref, DerefMut}; +extern crate proc_macros; + fn concat(deref_str: &str) -> String { format!("{}bar", deref_str) } @@ -121,6 +124,18 @@ fn main() { let b: &str = expr_deref!(&*a); //~^ explicit_deref_methods + proc_macros::external! { + let a: &mut String = &mut String::from("foo"); + let b: &str = a.deref(); + } + + // Issue #15168 + proc_macros::with_span! { + span + let a: &mut String = &mut String::from("foo"); + let b: &str = a.deref(); + } + // The struct does not implement Deref trait #[derive(Copy, Clone)] struct NoLint(u32); diff --git a/tests/ui/explicit_deref_methods.rs b/tests/ui/explicit_deref_methods.rs index 706d6cb2b79a6..b689649d49dd7 100644 --- a/tests/ui/explicit_deref_methods.rs +++ b/tests/ui/explicit_deref_methods.rs @@ -1,3 +1,4 @@ +//@aux-build:proc_macros.rs #![warn(clippy::explicit_deref_methods)] #![allow(unused_variables, unused_must_use)] #![allow( @@ -14,6 +15,8 @@ use std::ops::{Deref, DerefMut}; +extern crate proc_macros; + fn concat(deref_str: &str) -> String { format!("{}bar", deref_str) } @@ -121,6 +124,18 @@ fn main() { let b: &str = expr_deref!(a.deref()); //~^ explicit_deref_methods + proc_macros::external! { + let a: &mut String = &mut String::from("foo"); + let b: &str = a.deref(); + } + + // Issue #15168 + proc_macros::with_span! { + span + let a: &mut String = &mut String::from("foo"); + let b: &str = a.deref(); + } + // The struct does not implement Deref trait #[derive(Copy, Clone)] struct NoLint(u32); diff --git a/tests/ui/explicit_deref_methods.stderr b/tests/ui/explicit_deref_methods.stderr index 5036884366cfc..e2f2e68720b17 100644 --- a/tests/ui/explicit_deref_methods.stderr +++ b/tests/ui/explicit_deref_methods.stderr @@ -1,5 +1,5 @@ error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:55:19 + --> tests/ui/explicit_deref_methods.rs:58:19 | LL | let b: &str = a.deref(); | ^^^^^^^^^ help: try: `&*a` @@ -8,73 +8,73 @@ LL | let b: &str = a.deref(); = help: to override `-D warnings` add `#[allow(clippy::explicit_deref_methods)]` error: explicit `deref_mut` method call - --> tests/ui/explicit_deref_methods.rs:58:23 + --> tests/ui/explicit_deref_methods.rs:61:23 | LL | let b: &mut str = a.deref_mut(); | ^^^^^^^^^^^^^ help: try: `&mut **a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:62:39 + --> tests/ui/explicit_deref_methods.rs:65:39 | LL | let b: String = format!("{}, {}", a.deref(), a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:62:50 + --> tests/ui/explicit_deref_methods.rs:65:50 | LL | let b: String = format!("{}, {}", a.deref(), a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:66:20 + --> tests/ui/explicit_deref_methods.rs:69:20 | LL | println!("{}", a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:70:11 + --> tests/ui/explicit_deref_methods.rs:73:11 | LL | match a.deref() { | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:75:28 + --> tests/ui/explicit_deref_methods.rs:78:28 | LL | let b: String = concat(a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:78:13 + --> tests/ui/explicit_deref_methods.rs:81:13 | LL | let b = just_return(a).deref(); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `just_return(a)` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:81:28 + --> tests/ui/explicit_deref_methods.rs:84:28 | LL | let b: String = concat(just_return(a).deref()); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `just_return(a)` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:121:31 + --> tests/ui/explicit_deref_methods.rs:124:31 | LL | let b: &str = expr_deref!(a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:139:14 + --> tests/ui/explicit_deref_methods.rs:154:14 | LL | let _ = &Deref::deref(&"foo"); | ^^^^^^^^^^^^^^^^^^^^ help: try: `*&"foo"` error: explicit `deref_mut` method call - --> tests/ui/explicit_deref_methods.rs:141:14 + --> tests/ui/explicit_deref_methods.rs:156:14 | LL | let _ = &DerefMut::deref_mut(&mut x); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut **&mut x` error: explicit `deref_mut` method call - --> tests/ui/explicit_deref_methods.rs:142:14 + --> tests/ui/explicit_deref_methods.rs:157:14 | LL | let _ = &DerefMut::deref_mut((&mut &mut x).deref_mut()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut ***(&mut &mut x)` From 2f6503d57406b17d2c56ce77339df5547589c1f6 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Sat, 20 Sep 2025 19:45:28 +0500 Subject: [PATCH 19/93] Update actions/download-artifact to v5 --- .github/workflows/clippy_mq.yml | 2 +- .github/workflows/lintcheck.yml | 2 +- .github/workflows/lintcheck_summary.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/clippy_mq.yml b/.github/workflows/clippy_mq.yml index 0bcb713593593..9d099137449ef 100644 --- a/.github/workflows/clippy_mq.yml +++ b/.github/workflows/clippy_mq.yml @@ -181,7 +181,7 @@ jobs: # Download - name: Download target dir - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: binaries path: target/debug diff --git a/.github/workflows/lintcheck.yml b/.github/workflows/lintcheck.yml index 390d6a0f74758..45fd10ae76149 100644 --- a/.github/workflows/lintcheck.yml +++ b/.github/workflows/lintcheck.yml @@ -126,7 +126,7 @@ jobs: fail-on-cache-miss: true - name: Download JSON - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 - name: Store PR number run: echo ${{ github.event.pull_request.number }} > pr.txt diff --git a/.github/workflows/lintcheck_summary.yml b/.github/workflows/lintcheck_summary.yml index 52f52e155a07b..183258a964379 100644 --- a/.github/workflows/lintcheck_summary.yml +++ b/.github/workflows/lintcheck_summary.yml @@ -27,7 +27,7 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: summary path: untrusted From 77f44f2f0760867aef315fb8429fa1c495622fb7 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 21 Sep 2025 11:05:22 +0200 Subject: [PATCH 20/93] `default_constructed_unit_structs`: use `check_source_text` --- clippy_lints/src/default_constructed_unit_structs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/default_constructed_unit_structs.rs b/clippy_lints/src/default_constructed_unit_structs.rs index f8a9037fc8047..641f8ae03b72a 100644 --- a/clippy_lints/src/default_constructed_unit_structs.rs +++ b/clippy_lints/src/default_constructed_unit_structs.rs @@ -75,7 +75,7 @@ impl LateLintPass<'_> for DefaultConstructedUnitStructs { && !base.is_suggestable_infer_ty() { let mut removals = vec![(expr.span.with_lo(qpath.qself_span().hi()), String::new())]; - if expr.span.with_source_text(cx, |s| s.starts_with('<')) == Some(true) { + if expr.span.check_source_text(cx, |s| s.starts_with('<')) { // Remove `<`, '>` has already been removed by the existing removal expression. removals.push((expr.span.with_hi(qpath.qself_span().lo()), String::new())); } From f76664ca83b816c08370e806b61f5230fd980d18 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 21 Sep 2025 08:57:36 +0200 Subject: [PATCH 21/93] clean-up `match_same_arms` a bit --- clippy_lints/src/matches/match_same_arms.rs | 54 ++++++++------------- 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index ae277da089fd2..818e504245549 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::SpanRangeExt; -use clippy_utils::{SpanlessEq, SpanlessHash, fulfill_or_allowed, is_lint_allowed, path_to_local, search_same}; +use clippy_utils::{SpanlessEq, fulfill_or_allowed, hash_expr, is_lint_allowed, path_to_local, search_same}; use core::cmp::Ordering; use core::{iter, slice}; use itertools::Itertools; @@ -18,11 +18,7 @@ use super::MATCH_SAME_ARMS; #[expect(clippy::too_many_lines)] pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { - let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 { - let mut h = SpanlessHash::new(cx); - h.hash_expr(arm.body); - h.finish() - }; + let hash = |&(_, arm): &(_, &Arm<'_>)| hash_expr(cx, arm.body); let arena = DroplessArena::default(); let normalized_pats: Vec<_> = arms @@ -35,9 +31,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { .iter() .enumerate() .map(|(i, pat)| { - normalized_pats[i + 1..] - .iter() - .enumerate() + (normalized_pats[i + 1..].iter().enumerate()) .find_map(|(j, other)| pat.has_overlapping_values(other).then_some(i + 1 + j)) .unwrap_or(normalized_pats.len()) }) @@ -48,16 +42,15 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { .iter() .enumerate() .map(|(i, pat)| { - normalized_pats[..i] - .iter() - .enumerate() - .rev() - .zip(forwards_blocking_idxs[..i].iter().copied().rev()) - .skip_while(|&(_, forward_block)| forward_block > i) - .find_map(|((j, other), forward_block)| { - (forward_block == i || pat.has_overlapping_values(other)).then_some(j) - }) - .unwrap_or(0) + iter::zip( + normalized_pats[..i].iter().enumerate().rev(), + forwards_blocking_idxs[..i].iter().copied().rev(), + ) + .skip_while(|&(_, forward_block)| forward_block > i) + .find_map(|((j, other), forward_block)| { + (forward_block == i || pat.has_overlapping_values(other)).then_some(j) + }) + .unwrap_or(0) }) .collect(); @@ -158,12 +151,12 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { .map(|(_, arm)| arm.pat.span.get_source_text(cx)) .collect::>>() { - let mut suggs = src + let suggs = src .iter() .map(|(_, arm)| (adjusted_arm_span(cx, arm.span), String::new())) + .chain([(dest.pat.span, pat_snippets.iter().join(" | "))]) .collect_vec(); - suggs.push((dest.pat.span, pat_snippets.iter().join(" | "))); diag.multipart_suggestion_verbose( "otherwise merge the patterns into a single arm", suggs, @@ -396,10 +389,7 @@ impl<'a> NormalizedPat<'a> { if lpath != rpath { return false; } - lpats - .iter() - .zip(rpats.iter()) - .all(|(lpat, rpat)| lpat.has_overlapping_values(rpat)) + iter::zip(lpats, rpats).all(|(lpat, rpat)| lpat.has_overlapping_values(rpat)) }, (Self::Path(x), Self::Path(y)) => x == y, (Self::LitStr(x), Self::LitStr(y)) => x == y, @@ -409,7 +399,7 @@ impl<'a> NormalizedPat<'a> { (Self::Range(ref x), Self::Range(ref y)) => x.overlaps(y), (Self::Range(ref range), Self::LitInt(x)) | (Self::LitInt(x), Self::Range(ref range)) => range.contains(x), (Self::Slice(lpats, None), Self::Slice(rpats, None)) => { - lpats.len() == rpats.len() && lpats.iter().zip(rpats.iter()).all(|(x, y)| x.has_overlapping_values(y)) + lpats.len() == rpats.len() && iter::zip(lpats, rpats).all(|(x, y)| x.has_overlapping_values(y)) }, (Self::Slice(pats, None), Self::Slice(front, Some(back))) | (Self::Slice(front, Some(back)), Self::Slice(pats, None)) => { @@ -418,16 +408,12 @@ impl<'a> NormalizedPat<'a> { if pats.len() < front.len() + back.len() { return false; } - pats[..front.len()] - .iter() - .zip(front.iter()) - .chain(pats[pats.len() - back.len()..].iter().zip(back.iter())) + iter::zip(&pats[..front.len()], front) + .chain(iter::zip(&pats[pats.len() - back.len()..], back)) .all(|(x, y)| x.has_overlapping_values(y)) }, - (Self::Slice(lfront, Some(lback)), Self::Slice(rfront, Some(rback))) => lfront - .iter() - .zip(rfront.iter()) - .chain(lback.iter().rev().zip(rback.iter().rev())) + (Self::Slice(lfront, Some(lback)), Self::Slice(rfront, Some(rback))) => iter::zip(lfront, rfront) + .chain(iter::zip(lback.iter().rev(), rback.iter().rev())) .all(|(x, y)| x.has_overlapping_values(y)), // Enums can mix unit variants with tuple/struct variants. These can never overlap. From b6c4b3068ba7398c3e82b54675dcade446f276bf Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 19 Sep 2025 22:51:17 +0200 Subject: [PATCH 22/93] move `copies.rs` to `ifs/mod.rs` all the lints work with `if`-expressions --- clippy_lints/src/declared_lints.rs | 8 ++++---- clippy_lints/src/{copies.rs => ifs/mod.rs} | 0 clippy_lints/src/lib.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename clippy_lints/src/{copies.rs => ifs/mod.rs} (100%) diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 5563b8094f01b..a468cc19004cf 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -84,10 +84,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::collapsible_if::COLLAPSIBLE_IF_INFO, crate::collection_is_never_read::COLLECTION_IS_NEVER_READ_INFO, crate::comparison_chain::COMPARISON_CHAIN_INFO, - crate::copies::BRANCHES_SHARING_CODE_INFO, - crate::copies::IFS_SAME_COND_INFO, - crate::copies::IF_SAME_THEN_ELSE_INFO, - crate::copies::SAME_FUNCTIONS_IN_IF_CONDITION_INFO, crate::copy_iterator::COPY_ITERATOR_INFO, crate::crate_in_macro_def::CRATE_IN_MACRO_DEF_INFO, crate::create_dir::CREATE_DIR_INFO, @@ -204,6 +200,10 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::if_let_mutex::IF_LET_MUTEX_INFO, crate::if_not_else::IF_NOT_ELSE_INFO, crate::if_then_some_else_none::IF_THEN_SOME_ELSE_NONE_INFO, + crate::ifs::BRANCHES_SHARING_CODE_INFO, + crate::ifs::IFS_SAME_COND_INFO, + crate::ifs::IF_SAME_THEN_ELSE_INFO, + crate::ifs::SAME_FUNCTIONS_IN_IF_CONDITION_INFO, crate::ignored_unit_patterns::IGNORED_UNIT_PATTERNS_INFO, crate::impl_hash_with_borrow_str_and_bytes::IMPL_HASH_BORROW_WITH_STR_AND_BYTES_INFO, crate::implicit_hasher::IMPLICIT_HASHER_INFO, diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/ifs/mod.rs similarity index 100% rename from clippy_lints/src/copies.rs rename to clippy_lints/src/ifs/mod.rs diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c56fa257b0689..1cae377f3fbd3 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -100,7 +100,6 @@ mod cognitive_complexity; mod collapsible_if; mod collection_is_never_read; mod comparison_chain; -mod copies; mod copy_iterator; mod crate_in_macro_def; mod create_dir; @@ -158,6 +157,7 @@ mod future_not_send; mod if_let_mutex; mod if_not_else; mod if_then_some_else_none; +mod ifs; mod ignored_unit_patterns; mod impl_hash_with_borrow_str_and_bytes; mod implicit_hasher; @@ -549,7 +549,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(empty_enum::EmptyEnum)); store.register_late_pass(|_| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons)); store.register_late_pass(|_| Box::::default()); - store.register_late_pass(move |tcx| Box::new(copies::CopyAndPaste::new(tcx, conf))); + store.register_late_pass(move |tcx| Box::new(ifs::CopyAndPaste::new(tcx, conf))); store.register_late_pass(|_| Box::new(copy_iterator::CopyIterator)); let format_args = format_args_storage.clone(); store.register_late_pass(move |_| Box::new(format::UselessFormat::new(format_args.clone()))); From 95f5a00d8628fba223c802251af3c42547927c5a Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 21 Sep 2025 11:19:14 +0200 Subject: [PATCH 23/93] extract each lint into its own module --- clippy_lints/src/ifs/branches_sharing_code.rs | 407 ++++++++++++++ clippy_lints/src/ifs/if_same_then_else.rs | 29 + clippy_lints/src/ifs/ifs_same_cond.rs | 46 ++ clippy_lints/src/ifs/mod.rs | 508 +----------------- .../src/ifs/same_functions_in_if_cond.rs | 31 ++ 5 files changed, 525 insertions(+), 496 deletions(-) create mode 100644 clippy_lints/src/ifs/branches_sharing_code.rs create mode 100644 clippy_lints/src/ifs/if_same_then_else.rs create mode 100644 clippy_lints/src/ifs/ifs_same_cond.rs create mode 100644 clippy_lints/src/ifs/same_functions_in_if_cond.rs diff --git a/clippy_lints/src/ifs/branches_sharing_code.rs b/clippy_lints/src/ifs/branches_sharing_code.rs new file mode 100644 index 0000000000000..eb1025f71498e --- /dev/null +++ b/clippy_lints/src/ifs/branches_sharing_code.rs @@ -0,0 +1,407 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::{IntoSpan, SpanRangeExt, first_line_of_span, indent_of, reindent_multiline, snippet}; +use clippy_utils::ty::needs_ordered_drop; +use clippy_utils::visitors::for_each_expr_without_closures; +use clippy_utils::{ + ContainsName, HirEqInterExpr, SpanlessEq, capture_local_usage, get_enclosing_block, hash_expr, hash_stmt, + path_to_local, +}; +use core::iter; +use core::ops::ControlFlow; +use rustc_errors::Applicability; +use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, LetStmt, Node, Stmt, StmtKind, intravisit}; +use rustc_lint::LateContext; +use rustc_span::hygiene::walk_chain; +use rustc_span::source_map::SourceMap; +use rustc_span::{Span, Symbol}; + +use super::BRANCHES_SHARING_CODE; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + conds: &[&'tcx Expr<'_>], + blocks: &[&'tcx Block<'_>], + expr: &'tcx Expr<'_>, +) { + // We only lint ifs with multiple blocks + let &[first_block, ref blocks @ ..] = blocks else { + return; + }; + let &[.., last_block] = blocks else { + return; + }; + + let res = scan_block_for_eq(cx, conds, first_block, blocks); + let sm = cx.tcx.sess.source_map(); + let start_suggestion = res.start_span(first_block, sm).map(|span| { + let first_line_span = first_line_of_span(cx, expr.span); + let replace_span = first_line_span.with_hi(span.hi()); + let cond_span = first_line_span.until(first_block.span); + let cond_snippet = reindent_multiline(&snippet(cx, cond_span, "_"), false, None); + let cond_indent = indent_of(cx, cond_span); + let moved_snippet = reindent_multiline(&snippet(cx, span, "_"), true, None); + let suggestion = moved_snippet + "\n" + &cond_snippet + "{"; + let suggestion = reindent_multiline(&suggestion, true, cond_indent); + (replace_span, suggestion) + }); + let end_suggestion = res.end_span(last_block, sm).map(|span| { + let moved_snipped = reindent_multiline(&snippet(cx, span, "_"), true, None); + let indent = indent_of(cx, expr.span.shrink_to_hi()); + let suggestion = "}\n".to_string() + &moved_snipped; + let suggestion = reindent_multiline(&suggestion, true, indent); + + let span = span.with_hi(last_block.span.hi()); + // Improve formatting if the inner block has indentation (i.e. normal Rust formatting) + let span = span + .map_range(cx, |_, src, range| { + (range.start > 4 && src.get(range.start - 4..range.start)? == " ") + .then_some(range.start - 4..range.end) + }) + .map_or(span, |range| range.with_ctxt(span.ctxt())); + (span, suggestion.clone()) + }); + + let (span, msg, end_span) = match (&start_suggestion, &end_suggestion) { + (&Some((span, _)), &Some((end_span, _))) => ( + span, + "all if blocks contain the same code at both the start and the end", + Some(end_span), + ), + (&Some((span, _)), None) => (span, "all if blocks contain the same code at the start", None), + (None, &Some((span, _))) => (span, "all if blocks contain the same code at the end", None), + (None, None) => return, + }; + span_lint_and_then(cx, BRANCHES_SHARING_CODE, span, msg, |diag| { + if let Some(span) = end_span { + diag.span_note(span, "this code is shared at the end"); + } + if let Some((span, sugg)) = start_suggestion { + diag.span_suggestion( + span, + "consider moving these statements before the if", + sugg, + Applicability::Unspecified, + ); + } + if let Some((span, sugg)) = end_suggestion { + diag.span_suggestion( + span, + "consider moving these statements after the if", + sugg, + Applicability::Unspecified, + ); + if is_expr_parent_assignment(cx, expr) || !cx.typeck_results().expr_ty(expr).is_unit() { + diag.note("the end suggestion probably needs some adjustments to use the expression result correctly"); + } + } + if check_for_warn_of_moved_symbol(cx, &res.moved_locals, expr) { + diag.warn("some moved values might need to be renamed to avoid wrong references"); + } + }); +} + +struct BlockEq { + /// The end of the range of equal stmts at the start. + start_end_eq: usize, + /// The start of the range of equal stmts at the end. + end_begin_eq: Option, + /// The name and id of every local which can be moved at the beginning and the end. + moved_locals: Vec<(HirId, Symbol)>, +} +impl BlockEq { + fn start_span(&self, b: &Block<'_>, sm: &SourceMap) -> Option { + match &b.stmts[..self.start_end_eq] { + [first, .., last] => Some(sm.stmt_span(first.span, b.span).to(sm.stmt_span(last.span, b.span))), + [s] => Some(sm.stmt_span(s.span, b.span)), + [] => None, + } + } + + fn end_span(&self, b: &Block<'_>, sm: &SourceMap) -> Option { + match (&b.stmts[b.stmts.len() - self.end_begin_eq?..], b.expr) { + ([first, .., last], None) => Some(sm.stmt_span(first.span, b.span).to(sm.stmt_span(last.span, b.span))), + ([first, ..], Some(last)) => Some(sm.stmt_span(first.span, b.span).to(sm.stmt_span(last.span, b.span))), + ([s], None) => Some(sm.stmt_span(s.span, b.span)), + ([], Some(e)) => Some(walk_chain(e.span, b.span.ctxt())), + ([], None) => None, + } + } +} + +/// If the statement is a local, checks if the bound names match the expected list of names. +fn eq_binding_names(s: &Stmt<'_>, names: &[(HirId, Symbol)]) -> bool { + if let StmtKind::Let(l) = s.kind { + let mut i = 0usize; + let mut res = true; + l.pat.each_binding_or_first(&mut |_, _, _, name| { + if names.get(i).is_some_and(|&(_, n)| n == name.name) { + i += 1; + } else { + res = false; + } + }); + res && i == names.len() + } else { + false + } +} + +/// Checks if the statement modifies or moves any of the given locals. +fn modifies_any_local<'tcx>(cx: &LateContext<'tcx>, s: &'tcx Stmt<'_>, locals: &HirIdSet) -> bool { + for_each_expr_without_closures(s, |e| { + if let Some(id) = path_to_local(e) + && locals.contains(&id) + && !capture_local_usage(cx, e).is_imm_ref() + { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_some() +} + +/// Checks if the given statement should be considered equal to the statement in the same +/// position for each block. +fn eq_stmts( + stmt: &Stmt<'_>, + blocks: &[&Block<'_>], + get_stmt: impl for<'a> Fn(&'a Block<'a>) -> Option<&'a Stmt<'a>>, + eq: &mut HirEqInterExpr<'_, '_, '_>, + moved_bindings: &mut Vec<(HirId, Symbol)>, +) -> bool { + (if let StmtKind::Let(l) = stmt.kind { + let old_count = moved_bindings.len(); + l.pat.each_binding_or_first(&mut |_, id, _, name| { + moved_bindings.push((id, name.name)); + }); + let new_bindings = &moved_bindings[old_count..]; + blocks + .iter() + .all(|b| get_stmt(b).is_some_and(|s| eq_binding_names(s, new_bindings))) + } else { + true + }) && blocks.iter().all(|b| get_stmt(b).is_some_and(|s| eq.eq_stmt(s, stmt))) +} + +#[expect(clippy::too_many_lines)] +fn scan_block_for_eq<'tcx>( + cx: &LateContext<'tcx>, + conds: &[&'tcx Expr<'_>], + block: &'tcx Block<'_>, + blocks: &[&'tcx Block<'_>], +) -> BlockEq { + let mut eq = SpanlessEq::new(cx); + let mut eq = eq.inter_expr(); + let mut moved_locals = Vec::new(); + + let mut cond_locals = HirIdSet::default(); + for &cond in conds { + let _: Option = for_each_expr_without_closures(cond, |e| { + if let Some(id) = path_to_local(e) { + cond_locals.insert(id); + } + ControlFlow::Continue(()) + }); + } + + let mut local_needs_ordered_drop = false; + let start_end_eq = block + .stmts + .iter() + .enumerate() + .find(|&(i, stmt)| { + if let StmtKind::Let(l) = stmt.kind + && needs_ordered_drop(cx, cx.typeck_results().node_type(l.hir_id)) + { + local_needs_ordered_drop = true; + return true; + } + modifies_any_local(cx, stmt, &cond_locals) + || !eq_stmts(stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals) + }) + .map_or(block.stmts.len(), |(i, stmt)| { + adjust_by_closest_callsite(i, stmt, block.stmts[..i].iter().enumerate().rev()) + }); + + if local_needs_ordered_drop { + return BlockEq { + start_end_eq, + end_begin_eq: None, + moved_locals, + }; + } + + // Walk backwards through the final expression/statements so long as their hashes are equal. Note + // `SpanlessHash` treats all local references as equal allowing locals declared earlier in the block + // to match those in other blocks. e.g. If each block ends with the following the hash value will be + // the same even though each `x` binding will have a different `HirId`: + // let x = foo(); + // x + 50 + let expr_hash_eq = if let Some(e) = block.expr { + let hash = hash_expr(cx, e); + blocks.iter().all(|b| b.expr.is_some_and(|e| hash_expr(cx, e) == hash)) + } else { + blocks.iter().all(|b| b.expr.is_none()) + }; + if !expr_hash_eq { + return BlockEq { + start_end_eq, + end_begin_eq: None, + moved_locals, + }; + } + let end_search_start = block.stmts[start_end_eq..] + .iter() + .rev() + .enumerate() + .find(|&(offset, stmt)| { + let hash = hash_stmt(cx, stmt); + blocks.iter().any(|b| { + b.stmts + // the bounds check will catch the underflow + .get(b.stmts.len().wrapping_sub(offset + 1)) + .is_none_or(|s| hash != hash_stmt(cx, s)) + }) + }) + .map_or(block.stmts.len() - start_end_eq, |(i, stmt)| { + adjust_by_closest_callsite(i, stmt, (0..i).rev().zip(block.stmts[(block.stmts.len() - i)..].iter())) + }); + + let moved_locals_at_start = moved_locals.len(); + let mut i = end_search_start; + let end_begin_eq = block.stmts[block.stmts.len() - end_search_start..] + .iter() + .zip(iter::repeat_with(move || { + let x = i; + i -= 1; + x + })) + .fold(end_search_start, |init, (stmt, offset)| { + if eq_stmts( + stmt, + blocks, + |b| b.stmts.get(b.stmts.len() - offset), + &mut eq, + &mut moved_locals, + ) { + init + } else { + // Clear out all locals seen at the end so far. None of them can be moved. + let stmts = &blocks[0].stmts; + for stmt in &stmts[stmts.len() - init..=stmts.len() - offset] { + if let StmtKind::Let(l) = stmt.kind { + l.pat.each_binding_or_first(&mut |_, id, _, _| { + // FIXME(rust/#120456) - is `swap_remove` correct? + eq.locals.swap_remove(&id); + }); + } + } + moved_locals.truncate(moved_locals_at_start); + offset - 1 + } + }); + if let Some(e) = block.expr { + for block in blocks { + if block.expr.is_some_and(|expr| !eq.eq_expr(expr, e)) { + moved_locals.truncate(moved_locals_at_start); + return BlockEq { + start_end_eq, + end_begin_eq: None, + moved_locals, + }; + } + } + } + + BlockEq { + start_end_eq, + end_begin_eq: Some(end_begin_eq), + moved_locals, + } +} + +/// Adjusts the index for which the statements begin to differ to the closest macro callsite. +/// This avoids giving suggestions that requires splitting a macro call in half, when only a +/// part of the macro expansion is equal. +/// +/// For example, for the following macro: +/// ```rust,ignore +/// macro_rules! foo { +/// ($x:expr) => { +/// let y = 42; +/// $x; +/// }; +/// } +/// ``` +/// If the macro is called like this: +/// ```rust,ignore +/// if false { +/// let z = 42; +/// foo!(println!("Hello")); +/// } else { +/// let z = 42; +/// foo!(println!("World")); +/// } +/// ``` +/// Although the expanded `let y = 42;` is equal, the macro call should not be included in the +/// suggestion. +fn adjust_by_closest_callsite<'tcx>( + i: usize, + stmt: &'tcx Stmt<'tcx>, + mut iter: impl Iterator)>, +) -> usize { + let Some((_, first)) = iter.next() else { + return 0; + }; + + // If it is already at the boundary of a macro call, then just return. + if first.span.source_callsite() != stmt.span.source_callsite() { + return i; + } + + iter.find(|(_, stmt)| stmt.span.source_callsite() != first.span.source_callsite()) + .map_or(0, |(i, _)| i + 1) +} + +fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbol)], if_expr: &Expr<'_>) -> bool { + get_enclosing_block(cx, if_expr.hir_id).is_some_and(|block| { + let ignore_span = block.span.shrink_to_lo().to(if_expr.span); + + symbols + .iter() + .filter(|&&(_, name)| !name.as_str().starts_with('_')) + .any(|&(_, name)| { + let mut walker = ContainsName { name, cx }; + + // Scan block + let mut res = block + .stmts + .iter() + .filter(|stmt| !ignore_span.overlaps(stmt.span)) + .try_for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt)); + + if let Some(expr) = block.expr + && res.is_continue() + { + res = intravisit::walk_expr(&mut walker, expr); + } + + res.is_break() + }) + }) +} + +fn is_expr_parent_assignment(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let parent = cx.tcx.parent_hir_node(expr.hir_id); + if let Node::LetStmt(LetStmt { init: Some(e), .. }) + | Node::Expr(Expr { + kind: ExprKind::Assign(_, e, _), + .. + }) = parent + { + return e.hir_id == expr.hir_id; + } + + false +} diff --git a/clippy_lints/src/ifs/if_same_then_else.rs b/clippy_lints/src/ifs/if_same_then_else.rs new file mode 100644 index 0000000000000..69402ec890768 --- /dev/null +++ b/clippy_lints/src/ifs/if_same_then_else.rs @@ -0,0 +1,29 @@ +use clippy_utils::SpanlessEq; +use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::higher::has_let_expr; +use rustc_hir::{Block, Expr}; +use rustc_lint::LateContext; + +use super::IF_SAME_THEN_ELSE; + +pub(super) fn check(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[&Block<'_>]) -> bool { + let mut eq = SpanlessEq::new(cx); + blocks + .array_windows::<2>() + .enumerate() + .fold(true, |all_eq, (i, &[lhs, rhs])| { + if eq.eq_block(lhs, rhs) && !has_let_expr(conds[i]) && conds.get(i + 1).is_none_or(|e| !has_let_expr(e)) { + span_lint_and_note( + cx, + IF_SAME_THEN_ELSE, + lhs.span, + "this `if` has identical blocks", + Some(rhs.span), + "same as this", + ); + all_eq + } else { + false + } + }) +} diff --git a/clippy_lints/src/ifs/ifs_same_cond.rs b/clippy_lints/src/ifs/ifs_same_cond.rs new file mode 100644 index 0000000000000..ca76fc2587db9 --- /dev/null +++ b/clippy_lints/src/ifs/ifs_same_cond.rs @@ -0,0 +1,46 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::ty::InteriorMut; +use clippy_utils::{SpanlessEq, eq_expr_value, find_binding_init, hash_expr, path_to_local, search_same}; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; + +use super::IFS_SAME_COND; + +fn method_caller_is_mutable<'tcx>( + cx: &LateContext<'tcx>, + caller_expr: &Expr<'_>, + interior_mut: &mut InteriorMut<'tcx>, +) -> bool { + let caller_ty = cx.typeck_results().expr_ty(caller_expr); + + interior_mut.is_interior_mut_ty(cx, caller_ty) + || caller_ty.is_mutable_ptr() + // `find_binding_init` will return the binding iff its not mutable + || path_to_local(caller_expr) + .and_then(|hid| find_binding_init(cx, hid)) + .is_none() +} + +/// Implementation of `IFS_SAME_COND`. +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, conds: &[&Expr<'_>], interior_mut: &mut InteriorMut<'tcx>) { + for group in search_same( + conds, + |e| hash_expr(cx, e), + |lhs, rhs| { + // Ignore eq_expr side effects iff one of the expression kind is a method call + // and the caller is not a mutable, including inner mutable type. + if let ExprKind::MethodCall(_, caller, _, _) = lhs.kind { + if method_caller_is_mutable(cx, caller, interior_mut) { + false + } else { + SpanlessEq::new(cx).eq_expr(lhs, rhs) + } + } else { + eq_expr_value(cx, lhs, rhs) + } + }, + ) { + let spans: Vec<_> = group.into_iter().map(|expr| expr.span).collect(); + span_lint(cx, IFS_SAME_COND, spans, "these `if` branches have the same condition"); + } +} diff --git a/clippy_lints/src/ifs/mod.rs b/clippy_lints/src/ifs/mod.rs index 4fdb497950f8d..739f2fc917293 100644 --- a/clippy_lints/src/ifs/mod.rs +++ b/clippy_lints/src/ifs/mod.rs @@ -1,24 +1,15 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::{span_lint, span_lint_and_note, span_lint_and_then}; -use clippy_utils::higher::has_let_expr; -use clippy_utils::source::{IntoSpan, SpanRangeExt, first_line_of_span, indent_of, reindent_multiline, snippet}; -use clippy_utils::ty::{InteriorMut, needs_ordered_drop}; -use clippy_utils::visitors::for_each_expr_without_closures; -use clippy_utils::{ - ContainsName, HirEqInterExpr, SpanlessEq, capture_local_usage, eq_expr_value, find_binding_init, - get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause, is_lint_allowed, path_to_local, - search_same, -}; -use core::iter; -use core::ops::ControlFlow; -use rustc_errors::Applicability; -use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, LetStmt, Node, Stmt, StmtKind, intravisit}; +use clippy_utils::ty::InteriorMut; +use clippy_utils::{if_sequence, is_else_clause, is_lint_allowed}; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::TyCtxt; use rustc_session::impl_lint_pass; -use rustc_span::hygiene::walk_chain; -use rustc_span::source_map::SourceMap; -use rustc_span::{Span, Symbol}; + +mod branches_sharing_code; +mod if_same_then_else; +mod ifs_same_cond; +mod same_functions_in_if_cond; declare_clippy_lint! { /// ### What it does @@ -179,488 +170,13 @@ impl<'tcx> LateLintPass<'tcx> for CopyAndPaste<'tcx> { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if !expr.span.from_expansion() && matches!(expr.kind, ExprKind::If(..)) && !is_else_clause(cx.tcx, expr) { let (conds, blocks) = if_sequence(expr); - lint_same_cond(cx, &conds, &mut self.interior_mut); - lint_same_fns_in_if_cond(cx, &conds); + ifs_same_cond::check(cx, &conds, &mut self.interior_mut); + same_functions_in_if_cond::check(cx, &conds); let all_same = - !is_lint_allowed(cx, IF_SAME_THEN_ELSE, expr.hir_id) && lint_if_same_then_else(cx, &conds, &blocks); + !is_lint_allowed(cx, IF_SAME_THEN_ELSE, expr.hir_id) && if_same_then_else::check(cx, &conds, &blocks); if !all_same && conds.len() != blocks.len() { - lint_branches_sharing_code(cx, &conds, &blocks, expr); - } - } - } -} - -fn lint_if_same_then_else(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[&Block<'_>]) -> bool { - let mut eq = SpanlessEq::new(cx); - blocks - .array_windows::<2>() - .enumerate() - .fold(true, |all_eq, (i, &[lhs, rhs])| { - if eq.eq_block(lhs, rhs) && !has_let_expr(conds[i]) && conds.get(i + 1).is_none_or(|e| !has_let_expr(e)) { - span_lint_and_note( - cx, - IF_SAME_THEN_ELSE, - lhs.span, - "this `if` has identical blocks", - Some(rhs.span), - "same as this", - ); - all_eq - } else { - false - } - }) -} - -fn lint_branches_sharing_code<'tcx>( - cx: &LateContext<'tcx>, - conds: &[&'tcx Expr<'_>], - blocks: &[&'tcx Block<'_>], - expr: &'tcx Expr<'_>, -) { - // We only lint ifs with multiple blocks - let &[first_block, ref blocks @ ..] = blocks else { - return; - }; - let &[.., last_block] = blocks else { - return; - }; - - let res = scan_block_for_eq(cx, conds, first_block, blocks); - let sm = cx.tcx.sess.source_map(); - let start_suggestion = res.start_span(first_block, sm).map(|span| { - let first_line_span = first_line_of_span(cx, expr.span); - let replace_span = first_line_span.with_hi(span.hi()); - let cond_span = first_line_span.until(first_block.span); - let cond_snippet = reindent_multiline(&snippet(cx, cond_span, "_"), false, None); - let cond_indent = indent_of(cx, cond_span); - let moved_snippet = reindent_multiline(&snippet(cx, span, "_"), true, None); - let suggestion = moved_snippet + "\n" + &cond_snippet + "{"; - let suggestion = reindent_multiline(&suggestion, true, cond_indent); - (replace_span, suggestion) - }); - let end_suggestion = res.end_span(last_block, sm).map(|span| { - let moved_snipped = reindent_multiline(&snippet(cx, span, "_"), true, None); - let indent = indent_of(cx, expr.span.shrink_to_hi()); - let suggestion = "}\n".to_string() + &moved_snipped; - let suggestion = reindent_multiline(&suggestion, true, indent); - - let span = span.with_hi(last_block.span.hi()); - // Improve formatting if the inner block has indentation (i.e. normal Rust formatting) - let span = span - .map_range(cx, |_, src, range| { - (range.start > 4 && src.get(range.start - 4..range.start)? == " ") - .then_some(range.start - 4..range.end) - }) - .map_or(span, |range| range.with_ctxt(span.ctxt())); - (span, suggestion.clone()) - }); - - let (span, msg, end_span) = match (&start_suggestion, &end_suggestion) { - (&Some((span, _)), &Some((end_span, _))) => ( - span, - "all if blocks contain the same code at both the start and the end", - Some(end_span), - ), - (&Some((span, _)), None) => (span, "all if blocks contain the same code at the start", None), - (None, &Some((span, _))) => (span, "all if blocks contain the same code at the end", None), - (None, None) => return, - }; - span_lint_and_then(cx, BRANCHES_SHARING_CODE, span, msg, |diag| { - if let Some(span) = end_span { - diag.span_note(span, "this code is shared at the end"); - } - if let Some((span, sugg)) = start_suggestion { - diag.span_suggestion( - span, - "consider moving these statements before the if", - sugg, - Applicability::Unspecified, - ); - } - if let Some((span, sugg)) = end_suggestion { - diag.span_suggestion( - span, - "consider moving these statements after the if", - sugg, - Applicability::Unspecified, - ); - if is_expr_parent_assignment(cx, expr) || !cx.typeck_results().expr_ty(expr).is_unit() { - diag.note("the end suggestion probably needs some adjustments to use the expression result correctly"); + branches_sharing_code::check(cx, &conds, &blocks, expr); } } - if check_for_warn_of_moved_symbol(cx, &res.moved_locals, expr) { - diag.warn("some moved values might need to be renamed to avoid wrong references"); - } - }); -} - -struct BlockEq { - /// The end of the range of equal stmts at the start. - start_end_eq: usize, - /// The start of the range of equal stmts at the end. - end_begin_eq: Option, - /// The name and id of every local which can be moved at the beginning and the end. - moved_locals: Vec<(HirId, Symbol)>, -} -impl BlockEq { - fn start_span(&self, b: &Block<'_>, sm: &SourceMap) -> Option { - match &b.stmts[..self.start_end_eq] { - [first, .., last] => Some(sm.stmt_span(first.span, b.span).to(sm.stmt_span(last.span, b.span))), - [s] => Some(sm.stmt_span(s.span, b.span)), - [] => None, - } } - - fn end_span(&self, b: &Block<'_>, sm: &SourceMap) -> Option { - match (&b.stmts[b.stmts.len() - self.end_begin_eq?..], b.expr) { - ([first, .., last], None) => Some(sm.stmt_span(first.span, b.span).to(sm.stmt_span(last.span, b.span))), - ([first, ..], Some(last)) => Some(sm.stmt_span(first.span, b.span).to(sm.stmt_span(last.span, b.span))), - ([s], None) => Some(sm.stmt_span(s.span, b.span)), - ([], Some(e)) => Some(walk_chain(e.span, b.span.ctxt())), - ([], None) => None, - } - } -} - -/// If the statement is a local, checks if the bound names match the expected list of names. -fn eq_binding_names(s: &Stmt<'_>, names: &[(HirId, Symbol)]) -> bool { - if let StmtKind::Let(l) = s.kind { - let mut i = 0usize; - let mut res = true; - l.pat.each_binding_or_first(&mut |_, _, _, name| { - if names.get(i).is_some_and(|&(_, n)| n == name.name) { - i += 1; - } else { - res = false; - } - }); - res && i == names.len() - } else { - false - } -} - -/// Checks if the statement modifies or moves any of the given locals. -fn modifies_any_local<'tcx>(cx: &LateContext<'tcx>, s: &'tcx Stmt<'_>, locals: &HirIdSet) -> bool { - for_each_expr_without_closures(s, |e| { - if let Some(id) = path_to_local(e) - && locals.contains(&id) - && !capture_local_usage(cx, e).is_imm_ref() - { - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) - } - }) - .is_some() -} - -/// Checks if the given statement should be considered equal to the statement in the same position -/// for each block. -fn eq_stmts( - stmt: &Stmt<'_>, - blocks: &[&Block<'_>], - get_stmt: impl for<'a> Fn(&'a Block<'a>) -> Option<&'a Stmt<'a>>, - eq: &mut HirEqInterExpr<'_, '_, '_>, - moved_bindings: &mut Vec<(HirId, Symbol)>, -) -> bool { - (if let StmtKind::Let(l) = stmt.kind { - let old_count = moved_bindings.len(); - l.pat.each_binding_or_first(&mut |_, id, _, name| { - moved_bindings.push((id, name.name)); - }); - let new_bindings = &moved_bindings[old_count..]; - blocks - .iter() - .all(|b| get_stmt(b).is_some_and(|s| eq_binding_names(s, new_bindings))) - } else { - true - }) && blocks.iter().all(|b| get_stmt(b).is_some_and(|s| eq.eq_stmt(s, stmt))) -} - -#[expect(clippy::too_many_lines)] -fn scan_block_for_eq<'tcx>( - cx: &LateContext<'tcx>, - conds: &[&'tcx Expr<'_>], - block: &'tcx Block<'_>, - blocks: &[&'tcx Block<'_>], -) -> BlockEq { - let mut eq = SpanlessEq::new(cx); - let mut eq = eq.inter_expr(); - let mut moved_locals = Vec::new(); - - let mut cond_locals = HirIdSet::default(); - for &cond in conds { - let _: Option = for_each_expr_without_closures(cond, |e| { - if let Some(id) = path_to_local(e) { - cond_locals.insert(id); - } - ControlFlow::Continue(()) - }); - } - - let mut local_needs_ordered_drop = false; - let start_end_eq = block - .stmts - .iter() - .enumerate() - .find(|&(i, stmt)| { - if let StmtKind::Let(l) = stmt.kind - && needs_ordered_drop(cx, cx.typeck_results().node_type(l.hir_id)) - { - local_needs_ordered_drop = true; - return true; - } - modifies_any_local(cx, stmt, &cond_locals) - || !eq_stmts(stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals) - }) - .map_or(block.stmts.len(), |(i, stmt)| { - adjust_by_closest_callsite(i, stmt, block.stmts[..i].iter().enumerate().rev()) - }); - - if local_needs_ordered_drop { - return BlockEq { - start_end_eq, - end_begin_eq: None, - moved_locals, - }; - } - - // Walk backwards through the final expression/statements so long as their hashes are equal. Note - // `SpanlessHash` treats all local references as equal allowing locals declared earlier in the block - // to match those in other blocks. e.g. If each block ends with the following the hash value will be - // the same even though each `x` binding will have a different `HirId`: - // let x = foo(); - // x + 50 - let expr_hash_eq = if let Some(e) = block.expr { - let hash = hash_expr(cx, e); - blocks.iter().all(|b| b.expr.is_some_and(|e| hash_expr(cx, e) == hash)) - } else { - blocks.iter().all(|b| b.expr.is_none()) - }; - if !expr_hash_eq { - return BlockEq { - start_end_eq, - end_begin_eq: None, - moved_locals, - }; - } - let end_search_start = block.stmts[start_end_eq..] - .iter() - .rev() - .enumerate() - .find(|&(offset, stmt)| { - let hash = hash_stmt(cx, stmt); - blocks.iter().any(|b| { - b.stmts - // the bounds check will catch the underflow - .get(b.stmts.len().wrapping_sub(offset + 1)) - .is_none_or(|s| hash != hash_stmt(cx, s)) - }) - }) - .map_or(block.stmts.len() - start_end_eq, |(i, stmt)| { - adjust_by_closest_callsite(i, stmt, (0..i).rev().zip(block.stmts[(block.stmts.len() - i)..].iter())) - }); - - let moved_locals_at_start = moved_locals.len(); - let mut i = end_search_start; - let end_begin_eq = block.stmts[block.stmts.len() - end_search_start..] - .iter() - .zip(iter::repeat_with(move || { - let x = i; - i -= 1; - x - })) - .fold(end_search_start, |init, (stmt, offset)| { - if eq_stmts( - stmt, - blocks, - |b| b.stmts.get(b.stmts.len() - offset), - &mut eq, - &mut moved_locals, - ) { - init - } else { - // Clear out all locals seen at the end so far. None of them can be moved. - let stmts = &blocks[0].stmts; - for stmt in &stmts[stmts.len() - init..=stmts.len() - offset] { - if let StmtKind::Let(l) = stmt.kind { - l.pat.each_binding_or_first(&mut |_, id, _, _| { - // FIXME(rust/#120456) - is `swap_remove` correct? - eq.locals.swap_remove(&id); - }); - } - } - moved_locals.truncate(moved_locals_at_start); - offset - 1 - } - }); - if let Some(e) = block.expr { - for block in blocks { - if block.expr.is_some_and(|expr| !eq.eq_expr(expr, e)) { - moved_locals.truncate(moved_locals_at_start); - return BlockEq { - start_end_eq, - end_begin_eq: None, - moved_locals, - }; - } - } - } - - BlockEq { - start_end_eq, - end_begin_eq: Some(end_begin_eq), - moved_locals, - } -} - -/// Adjusts the index for which the statements begin to differ to the closest macro callsite. This -/// avoids giving suggestions that requires splitting a macro call in half, when only a part of the -/// macro expansion is equal. -/// -/// For example, for the following macro: -/// ```rust,ignore -/// macro_rules! foo { -/// ($x:expr) => { -/// let y = 42; -/// $x; -/// }; -/// } -/// ``` -/// If the macro is called like this: -/// ```rust,ignore -/// if false { -/// let z = 42; -/// foo!(println!("Hello")); -/// } else { -/// let z = 42; -/// foo!(println!("World")); -/// } -/// ``` -/// Although the expanded `let y = 42;` is equal, the macro call should not be included in the -/// suggestion. -fn adjust_by_closest_callsite<'tcx>( - i: usize, - stmt: &'tcx Stmt<'tcx>, - mut iter: impl Iterator)>, -) -> usize { - let Some((_, first)) = iter.next() else { - return 0; - }; - - // If it is already at the boundary of a macro call, then just return. - if first.span.source_callsite() != stmt.span.source_callsite() { - return i; - } - - iter.find(|(_, stmt)| stmt.span.source_callsite() != first.span.source_callsite()) - .map_or(0, |(i, _)| i + 1) -} - -fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbol)], if_expr: &Expr<'_>) -> bool { - get_enclosing_block(cx, if_expr.hir_id).is_some_and(|block| { - let ignore_span = block.span.shrink_to_lo().to(if_expr.span); - - symbols - .iter() - .filter(|&&(_, name)| !name.as_str().starts_with('_')) - .any(|&(_, name)| { - let mut walker = ContainsName { name, cx }; - - // Scan block - let mut res = block - .stmts - .iter() - .filter(|stmt| !ignore_span.overlaps(stmt.span)) - .try_for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt)); - - if let Some(expr) = block.expr - && res.is_continue() - { - res = intravisit::walk_expr(&mut walker, expr); - } - - res.is_break() - }) - }) -} - -fn method_caller_is_mutable<'tcx>( - cx: &LateContext<'tcx>, - caller_expr: &Expr<'_>, - interior_mut: &mut InteriorMut<'tcx>, -) -> bool { - let caller_ty = cx.typeck_results().expr_ty(caller_expr); - - interior_mut.is_interior_mut_ty(cx, caller_ty) - || caller_ty.is_mutable_ptr() - // `find_binding_init` will return the binding iff its not mutable - || path_to_local(caller_expr) - .and_then(|hid| find_binding_init(cx, hid)) - .is_none() -} - -/// Implementation of `IFS_SAME_COND`. -fn lint_same_cond<'tcx>(cx: &LateContext<'tcx>, conds: &[&Expr<'_>], interior_mut: &mut InteriorMut<'tcx>) { - for group in search_same( - conds, - |e| hash_expr(cx, e), - |lhs, rhs| { - // Ignore eq_expr side effects iff one of the expression kind is a method call - // and the caller is not a mutable, including inner mutable type. - if let ExprKind::MethodCall(_, caller, _, _) = lhs.kind { - if method_caller_is_mutable(cx, caller, interior_mut) { - false - } else { - SpanlessEq::new(cx).eq_expr(lhs, rhs) - } - } else { - eq_expr_value(cx, lhs, rhs) - } - }, - ) { - let spans: Vec<_> = group.into_iter().map(|expr| expr.span).collect(); - span_lint(cx, IFS_SAME_COND, spans, "these `if` branches have the same condition"); - } -} - -/// Implementation of `SAME_FUNCTIONS_IN_IF_CONDITION`. -fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) { - let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool { - // Do not lint if any expr originates from a macro - if lhs.span.from_expansion() || rhs.span.from_expansion() { - return false; - } - // Do not spawn warning if `IFS_SAME_COND` already produced it. - if eq_expr_value(cx, lhs, rhs) { - return false; - } - SpanlessEq::new(cx).eq_expr(lhs, rhs) - }; - - for group in search_same(conds, |e| hash_expr(cx, e), eq) { - let spans: Vec<_> = group.into_iter().map(|expr| expr.span).collect(); - span_lint( - cx, - SAME_FUNCTIONS_IN_IF_CONDITION, - spans, - "these `if` branches have the same function call", - ); - } -} - -fn is_expr_parent_assignment(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - let parent = cx.tcx.parent_hir_node(expr.hir_id); - if let Node::LetStmt(LetStmt { init: Some(e), .. }) - | Node::Expr(Expr { - kind: ExprKind::Assign(_, e, _), - .. - }) = parent - { - return e.hir_id == expr.hir_id; - } - - false } diff --git a/clippy_lints/src/ifs/same_functions_in_if_cond.rs b/clippy_lints/src/ifs/same_functions_in_if_cond.rs new file mode 100644 index 0000000000000..1f6bf04e22e7d --- /dev/null +++ b/clippy_lints/src/ifs/same_functions_in_if_cond.rs @@ -0,0 +1,31 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::{SpanlessEq, eq_expr_value, hash_expr, search_same}; +use rustc_hir::Expr; +use rustc_lint::LateContext; + +use super::SAME_FUNCTIONS_IN_IF_CONDITION; + +/// Implementation of `SAME_FUNCTIONS_IN_IF_CONDITION`. +pub(super) fn check(cx: &LateContext<'_>, conds: &[&Expr<'_>]) { + let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool { + // Do not lint if any expr originates from a macro + if lhs.span.from_expansion() || rhs.span.from_expansion() { + return false; + } + // Do not spawn warning if `IFS_SAME_COND` already produced it. + if eq_expr_value(cx, lhs, rhs) { + return false; + } + SpanlessEq::new(cx).eq_expr(lhs, rhs) + }; + + for group in search_same(conds, |e| hash_expr(cx, e), eq) { + let spans: Vec<_> = group.into_iter().map(|expr| expr.span).collect(); + span_lint( + cx, + SAME_FUNCTIONS_IN_IF_CONDITION, + spans, + "these `if` branches have the same function call", + ); + } +} From 7d80c15fa049a808e3b7cd7ff88447f9a42c0ceb Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 21 Sep 2025 10:20:56 -0400 Subject: [PATCH 24/93] Port #[macro_export] to the new attribute parsing infrastructure Co-authored-by: Anne Stijns --- clippy_lints/src/macro_metavars_in_unsafe.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/macro_metavars_in_unsafe.rs b/clippy_lints/src/macro_metavars_in_unsafe.rs index 9071c9c95f9d7..c5acaf0999332 100644 --- a/clippy_lints/src/macro_metavars_in_unsafe.rs +++ b/clippy_lints/src/macro_metavars_in_unsafe.rs @@ -5,10 +5,12 @@ use itertools::Itertools; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_stmt}; use rustc_hir::{BlockCheckMode, Expr, ExprKind, HirId, Stmt, UnsafeSource}; +use rustc_hir::attrs::AttributeKind; +use rustc_hir::find_attr; use rustc_lint::{LateContext, LateLintPass, Level, LintContext}; use rustc_middle::lint::LevelAndSource; use rustc_session::impl_lint_pass; -use rustc_span::{Span, SyntaxContext, sym}; +use rustc_span::{Span, SyntaxContext}; use std::collections::BTreeMap; use std::collections::btree_map::Entry; @@ -146,7 +148,8 @@ struct BodyVisitor<'a, 'tcx> { } fn is_public_macro(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { - (cx.effective_visibilities.is_exported(def_id) || cx.tcx.has_attr(def_id, sym::macro_export)) + ( cx.effective_visibilities.is_exported(def_id) || + find_attr!(cx.tcx.get_all_attrs(def_id), AttributeKind::MacroExport{..}) ) && !cx.tcx.is_doc_hidden(def_id) } From d84505b2ca9d5c79c3700eb2098f8d0dc9aabb2e Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 18 Sep 2025 22:30:21 +0200 Subject: [PATCH 25/93] Post lint on the type HIR node Out of the 5 lints in the `derive` directory, only two of them (`unsafe_derive_deserialize` and `derive_partial_eq_without_eq`) posted the lint on the ADT node. This fixes the situation for the three other lints: - `derive_hash_with_manual_eq` - `derive_ord_xor_partial_ord` - `expl_impl_clone_on_copy` This allows `#[expect]` to be used on the ADT to silence the lint. Also, this makes `expl_impl_clone_on_copy` properly use an "help" message instead of a "note" one when suggesting a fix. --- .../src/derive/derive_ord_xor_partial_ord.rs | 7 ++++--- .../derive/derive_partial_eq_without_eq.rs | 13 ++++++++---- .../src/derive/derived_hash_with_manual_eq.rs | 10 ++++++---- .../src/derive/expl_impl_clone_on_copy.rs | 20 +++++++++++++------ clippy_lints/src/derive/mod.rs | 16 ++++++++++----- .../src/derive/unsafe_derive_deserialize.rs | 12 +++++++---- tests/ui/derive.rs | 13 ++++++++++++ tests/ui/derive.stderr | 10 +++++----- tests/ui/derive_ord_xor_partial_ord.rs | 15 ++++++++++++++ tests/ui/derived_hash_with_manual_eq.rs | 16 +++++++++++++++ 10 files changed, 101 insertions(+), 31 deletions(-) diff --git a/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs b/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs index cbbcb2f7a3bab..274c699ff9d24 100644 --- a/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs +++ b/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs @@ -1,5 +1,5 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use rustc_hir as hir; +use clippy_utils::diagnostics::span_lint_hir_and_then; +use rustc_hir::{self as hir, HirId}; use rustc_lint::LateContext; use rustc_middle::ty::Ty; use rustc_span::{Span, sym}; @@ -12,6 +12,7 @@ pub(super) fn check<'tcx>( span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>, + adt_hir_id: HirId, ord_is_automatically_derived: bool, ) { if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord) @@ -38,7 +39,7 @@ pub(super) fn check<'tcx>( "you are deriving `Ord` but have implemented `PartialOrd` explicitly" }; - span_lint_and_then(cx, DERIVE_ORD_XOR_PARTIAL_ORD, span, mess, |diag| { + span_lint_hir_and_then(cx, DERIVE_ORD_XOR_PARTIAL_ORD, adt_hir_id, span, mess, |diag| { if let Some(local_def_id) = impl_id.as_local() { let hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id); diag.span_note(cx.tcx.hir_span(hir_id), "`PartialOrd` implemented here"); diff --git a/clippy_lints/src/derive/derive_partial_eq_without_eq.rs b/clippy_lints/src/derive/derive_partial_eq_without_eq.rs index ed7881c461ffc..fbace0bd73acf 100644 --- a/clippy_lints/src/derive/derive_partial_eq_without_eq.rs +++ b/clippy_lints/src/derive/derive_partial_eq_without_eq.rs @@ -2,8 +2,8 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::has_non_exhaustive_attr; use clippy_utils::ty::implements_trait_with_env; use rustc_errors::Applicability; -use rustc_hir as hir; use rustc_hir::def_id::DefId; +use rustc_hir::{self as hir, HirId}; use rustc_lint::LateContext; use rustc_middle::ty::{self, ClauseKind, GenericParamDefKind, ParamEnv, TraitPredicate, Ty, TyCtxt, Upcast}; use rustc_span::{Span, sym}; @@ -11,7 +11,13 @@ use rustc_span::{Span, sym}; use super::DERIVE_PARTIAL_EQ_WITHOUT_EQ; /// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint. -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + span: Span, + trait_ref: &hir::TraitRef<'_>, + ty: Ty<'tcx>, + adt_hir_id: HirId, +) { if let ty::Adt(adt, args) = ty.kind() && cx.tcx.visibility(adt.did()).is_public() && let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq) @@ -20,7 +26,6 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::T && !has_non_exhaustive_attr(cx.tcx, *adt) && !ty_implements_eq_trait(cx.tcx, ty, eq_trait_def_id) && let typing_env = typing_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id) - && let Some(local_def_id) = adt.did().as_local() // If all of our fields implement `Eq`, we can implement `Eq` too && adt .all_fields() @@ -30,7 +35,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::T span_lint_hir_and_then( cx, DERIVE_PARTIAL_EQ_WITHOUT_EQ, - cx.tcx.local_def_id_to_hir_id(local_def_id), + adt_hir_id, span.ctxt().outer_expn_data().call_site, "you are deriving `PartialEq` and can implement `Eq`", |diag| { diff --git a/clippy_lints/src/derive/derived_hash_with_manual_eq.rs b/clippy_lints/src/derive/derived_hash_with_manual_eq.rs index 6f36a58025a2b..afc02ce32d48e 100644 --- a/clippy_lints/src/derive/derived_hash_with_manual_eq.rs +++ b/clippy_lints/src/derive/derived_hash_with_manual_eq.rs @@ -1,5 +1,5 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use rustc_hir as hir; +use clippy_utils::diagnostics::span_lint_hir_and_then; +use rustc_hir::{HirId, TraitRef}; use rustc_lint::LateContext; use rustc_middle::ty::Ty; use rustc_span::{Span, sym}; @@ -10,8 +10,9 @@ use super::DERIVED_HASH_WITH_MANUAL_EQ; pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, span: Span, - trait_ref: &hir::TraitRef<'_>, + trait_ref: &TraitRef<'_>, ty: Ty<'tcx>, + adt_hir_id: HirId, hash_is_automatically_derived: bool, ) { if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait() @@ -31,9 +32,10 @@ pub(super) fn check<'tcx>( // Only care about `impl PartialEq for Foo` // For `impl PartialEq for A, input_types is [A, B] if trait_ref.instantiate_identity().args.type_at(1) == ty { - span_lint_and_then( + span_lint_hir_and_then( cx, DERIVED_HASH_WITH_MANUAL_EQ, + adt_hir_id, span, "you are deriving `Hash` but have implemented `PartialEq` explicitly", |diag| { diff --git a/clippy_lints/src/derive/expl_impl_clone_on_copy.rs b/clippy_lints/src/derive/expl_impl_clone_on_copy.rs index 6b97b4bd6b4d3..dfb723b86eb93 100644 --- a/clippy_lints/src/derive/expl_impl_clone_on_copy.rs +++ b/clippy_lints/src/derive/expl_impl_clone_on_copy.rs @@ -1,13 +1,19 @@ -use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::ty::{implements_trait, is_copy}; -use rustc_hir::{self as hir, Item}; +use rustc_hir::{self as hir, HirId, Item}; use rustc_lint::LateContext; use rustc_middle::ty::{self, GenericArgKind, Ty}; use super::EXPL_IMPL_CLONE_ON_COPY; /// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint. -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + item: &Item<'_>, + trait_ref: &hir::TraitRef<'_>, + ty: Ty<'tcx>, + adt_hir_id: HirId, +) { let clone_id = match cx.tcx.lang_items().clone_trait() { Some(id) if trait_ref.trait_def_id() == Some(id) => id, _ => return, @@ -54,12 +60,14 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &h return; } - span_lint_and_note( + span_lint_hir_and_then( cx, EXPL_IMPL_CLONE_ON_COPY, + adt_hir_id, item.span, "you are implementing `Clone` explicitly on a `Copy` type", - Some(item.span), - "consider deriving `Clone` or removing `Copy`", + |diag| { + diag.span_help(item.span, "consider deriving `Clone` or removing `Copy`"); + }, ); } diff --git a/clippy_lints/src/derive/mod.rs b/clippy_lints/src/derive/mod.rs index 1d63394ce37d4..06efc2709faa6 100644 --- a/clippy_lints/src/derive/mod.rs +++ b/clippy_lints/src/derive/mod.rs @@ -1,3 +1,5 @@ +use clippy_utils::path_res; +use rustc_hir::def::Res; use rustc_hir::{Impl, Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -194,21 +196,25 @@ impl<'tcx> LateLintPass<'tcx> for Derive { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Impl(Impl { of_trait: Some(of_trait), + self_ty, .. }) = item.kind + && let Res::Def(_, def_id) = path_res(cx, self_ty) + && let Some(local_def_id) = def_id.as_local() { + let adt_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id); let trait_ref = &of_trait.trait_ref; let ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); let is_automatically_derived = cx.tcx.is_automatically_derived(item.owner_id.to_def_id()); - derived_hash_with_manual_eq::check(cx, item.span, trait_ref, ty, is_automatically_derived); - derive_ord_xor_partial_ord::check(cx, item.span, trait_ref, ty, is_automatically_derived); + derived_hash_with_manual_eq::check(cx, item.span, trait_ref, ty, adt_hir_id, is_automatically_derived); + derive_ord_xor_partial_ord::check(cx, item.span, trait_ref, ty, adt_hir_id, is_automatically_derived); if is_automatically_derived { - unsafe_derive_deserialize::check(cx, item, trait_ref, ty); - derive_partial_eq_without_eq::check(cx, item.span, trait_ref, ty); + unsafe_derive_deserialize::check(cx, item, trait_ref, ty, adt_hir_id); + derive_partial_eq_without_eq::check(cx, item.span, trait_ref, ty, adt_hir_id); } else { - expl_impl_clone_on_copy::check(cx, item, trait_ref, ty); + expl_impl_clone_on_copy::check(cx, item, trait_ref, ty, adt_hir_id); } } } diff --git a/clippy_lints/src/derive/unsafe_derive_deserialize.rs b/clippy_lints/src/derive/unsafe_derive_deserialize.rs index c391e7b62289f..38f3251fd3891 100644 --- a/clippy_lints/src/derive/unsafe_derive_deserialize.rs +++ b/clippy_lints/src/derive/unsafe_derive_deserialize.rs @@ -4,7 +4,7 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::{is_lint_allowed, paths}; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn, walk_item}; -use rustc_hir::{self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Item, UnsafeSource}; +use rustc_hir::{self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Item, UnsafeSource}; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, Ty}; @@ -13,7 +13,13 @@ use rustc_span::{Span, sym}; use super::UNSAFE_DERIVE_DESERIALIZE; /// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint. -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + item: &Item<'_>, + trait_ref: &hir::TraitRef<'_>, + ty: Ty<'tcx>, + adt_hir_id: HirId, +) { fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool { let mut visitor = UnsafeVisitor { cx }; walk_item(&mut visitor, item).is_break() @@ -22,8 +28,6 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &h if let Some(trait_def_id) = trait_ref.trait_def_id() && paths::SERDE_DESERIALIZE.matches(cx, trait_def_id) && let ty::Adt(def, _) = ty.kind() - && let Some(local_def_id) = def.did().as_local() - && let adt_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id) && !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id) && cx .tcx diff --git a/tests/ui/derive.rs b/tests/ui/derive.rs index 2c5fa0be9755c..305f73c92cd5c 100644 --- a/tests/ui/derive.rs +++ b/tests/ui/derive.rs @@ -130,3 +130,16 @@ fn issue14558() { } fn main() {} + +mod issue15708 { + // Check that the lint posts on the type definition node + #[expect(clippy::expl_impl_clone_on_copy)] + #[derive(Copy)] + struct S; + + impl Clone for S { + fn clone(&self) -> Self { + S + } + } +} diff --git a/tests/ui/derive.stderr b/tests/ui/derive.stderr index ff2c24ff48ee7..48e55fc7469e4 100644 --- a/tests/ui/derive.stderr +++ b/tests/ui/derive.stderr @@ -9,7 +9,7 @@ LL | | fn clone(&self) -> Self { LL | | } | |_^ | -note: consider deriving `Clone` or removing `Copy` +help: consider deriving `Clone` or removing `Copy` --> tests/ui/derive.rs:15:1 | LL | / impl Clone for Qux { @@ -33,7 +33,7 @@ LL | | fn clone(&self) -> Self { LL | | } | |_^ | -note: consider deriving `Clone` or removing `Copy` +help: consider deriving `Clone` or removing `Copy` --> tests/ui/derive.rs:41:1 | LL | / impl<'a> Clone for Lt<'a> { @@ -55,7 +55,7 @@ LL | | fn clone(&self) -> Self { LL | | } | |_^ | -note: consider deriving `Clone` or removing `Copy` +help: consider deriving `Clone` or removing `Copy` --> tests/ui/derive.rs:54:1 | LL | / impl Clone for BigArray { @@ -77,7 +77,7 @@ LL | | fn clone(&self) -> Self { LL | | } | |_^ | -note: consider deriving `Clone` or removing `Copy` +help: consider deriving `Clone` or removing `Copy` --> tests/ui/derive.rs:67:1 | LL | / impl Clone for FnPtr { @@ -99,7 +99,7 @@ LL | | fn clone(&self) -> Self { LL | | } | |_^ | -note: consider deriving `Clone` or removing `Copy` +help: consider deriving `Clone` or removing `Copy` --> tests/ui/derive.rs:89:1 | LL | / impl Clone for Generic2 { diff --git a/tests/ui/derive_ord_xor_partial_ord.rs b/tests/ui/derive_ord_xor_partial_ord.rs index 3ef4ee9463dca..b4bb24b0d2fe7 100644 --- a/tests/ui/derive_ord_xor_partial_ord.rs +++ b/tests/ui/derive_ord_xor_partial_ord.rs @@ -76,3 +76,18 @@ mod use_ord { } fn main() {} + +mod issue15708 { + use std::cmp::{Ord, Ordering}; + + // Check that the lint posts on the type definition node + #[expect(clippy::derive_ord_xor_partial_ord)] + #[derive(PartialOrd, PartialEq, Eq)] + struct DerivePartialOrdInUseOrd; + + impl Ord for DerivePartialOrdInUseOrd { + fn cmp(&self, other: &Self) -> Ordering { + Ordering::Less + } + } +} diff --git a/tests/ui/derived_hash_with_manual_eq.rs b/tests/ui/derived_hash_with_manual_eq.rs index 88b574add3f27..9f5c85d7fbc38 100644 --- a/tests/ui/derived_hash_with_manual_eq.rs +++ b/tests/ui/derived_hash_with_manual_eq.rs @@ -41,3 +41,19 @@ impl std::hash::Hash for Bah { } fn main() {} + +mod issue15708 { + // Check that the lint posts on the type definition node + #[expect(clippy::derived_hash_with_manual_eq)] + #[derive(Debug, Clone, Copy, Eq, PartialOrd, Ord, Hash)] + pub struct Span { + start: usize, + end: usize, + } + + impl PartialEq for Span { + fn eq(&self, other: &Self) -> bool { + self.start.cmp(&other.start).then(self.end.cmp(&other.end)).is_eq() + } + } +} From 27cad5a61602e0435967d2c5c34f0d94178ab1be Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 13 Aug 2025 20:30:34 +0200 Subject: [PATCH 26/93] fix `unnecessary_semicolon`: FN on `#[feature(stmt_expr_attributes)]` --- clippy_lints/src/unnecessary_semicolon.rs | 3 ++- ...ary_semicolon_feature_stmt_expr_attributes.fixed | 13 +++++++++++++ ...essary_semicolon_feature_stmt_expr_attributes.rs | 13 +++++++++++++ ...ry_semicolon_feature_stmt_expr_attributes.stderr | 11 +++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.fixed create mode 100644 tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.rs create mode 100644 tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.stderr diff --git a/clippy_lints/src/unnecessary_semicolon.rs b/clippy_lints/src/unnecessary_semicolon.rs index 76e24b6bf8058..e1e450a52fdf0 100644 --- a/clippy_lints/src/unnecessary_semicolon.rs +++ b/clippy_lints/src/unnecessary_semicolon.rs @@ -88,7 +88,8 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessarySemicolon { ) && 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() + // -- unless the corresponding feature is enabled + && (cx.tcx.hir_attrs(stmt.hir_id).is_empty() || cx.tcx.features().stmt_expr_attributes()) { 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/tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.fixed b/tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.fixed new file mode 100644 index 0000000000000..b90ae1365bbf2 --- /dev/null +++ b/tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.fixed @@ -0,0 +1,13 @@ +#![warn(clippy::unnecessary_semicolon)] +#![feature(stmt_expr_attributes)] + +fn main() { + // removing the `;` would turn the stmt into an expr, but attrs aren't allowed on exprs + // -- unless the feature `stmt_expr_attributes` is enabled + #[rustfmt::skip] + match 0 { + 0b00 => {} 0b01 => {} + 0b11 => {} _ => {} + } + //~^ unnecessary_semicolon +} diff --git a/tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.rs b/tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.rs new file mode 100644 index 0000000000000..606c901c20d35 --- /dev/null +++ b/tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.rs @@ -0,0 +1,13 @@ +#![warn(clippy::unnecessary_semicolon)] +#![feature(stmt_expr_attributes)] + +fn main() { + // removing the `;` would turn the stmt into an expr, but attrs aren't allowed on exprs + // -- unless the feature `stmt_expr_attributes` is enabled + #[rustfmt::skip] + match 0 { + 0b00 => {} 0b01 => {} + 0b11 => {} _ => {} + }; + //~^ unnecessary_semicolon +} diff --git a/tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.stderr b/tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.stderr new file mode 100644 index 0000000000000..3e98a92ef299c --- /dev/null +++ b/tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.stderr @@ -0,0 +1,11 @@ +error: unnecessary semicolon + --> tests/ui/unnecessary_semicolon_feature_stmt_expr_attributes.rs:11:6 + | +LL | }; + | ^ help: remove + | + = note: `-D clippy::unnecessary-semicolon` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unnecessary_semicolon)]` + +error: aborting due to 1 previous error + From 0ed31beba681fab11b8137b81c981c57cab06308 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 21 Sep 2025 23:27:21 +0200 Subject: [PATCH 27/93] rename `mut_reference` to `unnecessary_mut_passed` The lint was probably renamed at some point, but the files weren't. This made it annoying to search for the lint. --- clippy_lints/src/declared_lints.rs | 2 +- clippy_lints/src/lib.rs | 4 ++-- ...reference.rs => unnecessary_mut_passed.rs} | 0 ...nce.fixed => unnecessary_mut_passed.fixed} | 0 ...reference.rs => unnecessary_mut_passed.rs} | 0 ...e.stderr => unnecessary_mut_passed.stderr} | 24 +++++++++---------- 6 files changed, 15 insertions(+), 15 deletions(-) rename clippy_lints/src/{mut_reference.rs => unnecessary_mut_passed.rs} (100%) rename tests/ui/{mut_reference.fixed => unnecessary_mut_passed.fixed} (100%) rename tests/ui/{mut_reference.rs => unnecessary_mut_passed.rs} (100%) rename tests/ui/{mut_reference.stderr => unnecessary_mut_passed.stderr} (80%) diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 5563b8094f01b..89178290e1a2f 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -535,7 +535,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::multiple_unsafe_ops_per_block::MULTIPLE_UNSAFE_OPS_PER_BLOCK_INFO, crate::mut_key::MUTABLE_KEY_TYPE_INFO, crate::mut_mut::MUT_MUT_INFO, - crate::mut_reference::UNNECESSARY_MUT_PASSED_INFO, crate::mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL_INFO, crate::mutex_atomic::MUTEX_ATOMIC_INFO, crate::mutex_atomic::MUTEX_INTEGER_INFO, @@ -752,6 +751,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::unnecessary_box_returns::UNNECESSARY_BOX_RETURNS_INFO, crate::unnecessary_literal_bound::UNNECESSARY_LITERAL_BOUND_INFO, crate::unnecessary_map_on_constructor::UNNECESSARY_MAP_ON_CONSTRUCTOR_INFO, + crate::unnecessary_mut_passed::UNNECESSARY_MUT_PASSED_INFO, crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO, crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO, crate::unnecessary_semicolon::UNNECESSARY_SEMICOLON_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c56fa257b0689..1d6c836691547 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -253,7 +253,6 @@ mod multiple_bound_locations; mod multiple_unsafe_ops_per_block; mod mut_key; mod mut_mut; -mod mut_reference; mod mutable_debug_assertion; mod mutex_atomic; mod needless_arbitrary_self_type; @@ -375,6 +374,7 @@ mod unit_types; mod unnecessary_box_returns; mod unnecessary_literal_bound; mod unnecessary_map_on_constructor; +mod unnecessary_mut_passed; mod unnecessary_owned_empty_strings; mod unnecessary_self_imports; mod unnecessary_semicolon; @@ -483,7 +483,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(misc::LintPass)); store.register_late_pass(|_| Box::new(eta_reduction::EtaReduction)); store.register_late_pass(|_| Box::new(mut_mut::MutMut)); - store.register_late_pass(|_| Box::new(mut_reference::UnnecessaryMutPassed)); + store.register_late_pass(|_| Box::new(unnecessary_mut_passed::UnnecessaryMutPassed)); store.register_late_pass(|_| Box::>::default()); store.register_late_pass(|_| Box::new(len_zero::LenZero)); store.register_late_pass(move |_| Box::new(attrs::Attributes::new(conf))); diff --git a/clippy_lints/src/mut_reference.rs b/clippy_lints/src/unnecessary_mut_passed.rs similarity index 100% rename from clippy_lints/src/mut_reference.rs rename to clippy_lints/src/unnecessary_mut_passed.rs diff --git a/tests/ui/mut_reference.fixed b/tests/ui/unnecessary_mut_passed.fixed similarity index 100% rename from tests/ui/mut_reference.fixed rename to tests/ui/unnecessary_mut_passed.fixed diff --git a/tests/ui/mut_reference.rs b/tests/ui/unnecessary_mut_passed.rs similarity index 100% rename from tests/ui/mut_reference.rs rename to tests/ui/unnecessary_mut_passed.rs diff --git a/tests/ui/mut_reference.stderr b/tests/ui/unnecessary_mut_passed.stderr similarity index 80% rename from tests/ui/mut_reference.stderr rename to tests/ui/unnecessary_mut_passed.stderr index 5ecfaa37416ba..c69a637bf4088 100644 --- a/tests/ui/mut_reference.stderr +++ b/tests/ui/unnecessary_mut_passed.stderr @@ -1,5 +1,5 @@ error: the function `takes_ref` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:56:15 + --> tests/ui/unnecessary_mut_passed.rs:56:15 | LL | takes_ref(&mut 42); | ^^^^^^^ help: remove this `mut`: `&42` @@ -8,67 +8,67 @@ LL | takes_ref(&mut 42); = 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 + --> tests/ui/unnecessary_mut_passed.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 + --> tests/ui/unnecessary_mut_passed.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 + --> tests/ui/unnecessary_mut_passed.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:66:12 + --> tests/ui/unnecessary_mut_passed.rs:66:12 | LL | as_ptr(&mut 42); | ^^^^^^^ help: remove this `mut`: `&42` error: the function `as_ptr` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:69:12 + --> tests/ui/unnecessary_mut_passed.rs:69:12 | LL | as_ptr(&mut &42); | ^^^^^^^^ help: remove this `mut`: `&&42` error: the function `as_ptr` doesn't need a mutable reference - --> tests/ui/mut_reference.rs:72:12 + --> tests/ui/unnecessary_mut_passed.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 + --> tests/ui/unnecessary_mut_passed.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 + --> tests/ui/unnecessary_mut_passed.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 + --> tests/ui/unnecessary_mut_passed.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 + --> tests/ui/unnecessary_mut_passed.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 + --> tests/ui/unnecessary_mut_passed.rs:86:31 | LL | my_struct.takes_raw_const(&mut 42); | ^^^^^^^ help: remove this `mut`: `&42` From a23d4e16d82953c493c75acc568a2c75a368828c Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Mon, 22 Sep 2025 15:56:27 +0200 Subject: [PATCH 28/93] flip1995 is back from vacation --- triagebot.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/triagebot.toml b/triagebot.toml index 7b19f8658c088..b2fb50918f583 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -60,7 +60,6 @@ contributing_url = "https://github.com/rust-lang/rust-clippy/blob/master/CONTRIB users_on_vacation = [ "matthiaskrgr", "Manishearth", - "flip1995", ] [assign.owners] From 526d794d807ca0e54d3411fed4c508b1c6f0f0e5 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 01:34:05 +0200 Subject: [PATCH 29/93] create let-chain --- clippy_lints/src/non_canonical_impls.rs | 222 +++++++++++------------- 1 file changed, 106 insertions(+), 116 deletions(-) diff --git a/clippy_lints/src/non_canonical_impls.rs b/clippy_lints/src/non_canonical_impls.rs index ba67dc62abbda..c479f40707e98 100644 --- a/clippy_lints/src/non_canonical_impls.rs +++ b/clippy_lints/src/non_canonical_impls.rs @@ -114,136 +114,126 @@ declare_lint_pass!(NonCanonicalImpls => [NON_CANONICAL_CLONE_IMPL, NON_CANONICAL impl LateLintPass<'_> for NonCanonicalImpls { #[expect(clippy::too_many_lines)] fn check_impl_item<'tcx>(&mut self, cx: &LateContext<'tcx>, impl_item: &ImplItem<'tcx>) { - let Node::Item(item) = cx.tcx.parent_hir_node(impl_item.hir_id()) else { - return; - }; - let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder) else { - return; - }; - if cx.tcx.is_automatically_derived(item.owner_id.to_def_id()) { - return; - } - let ImplItemKind::Fn(_, impl_item_id) = cx.tcx.hir_impl_item(impl_item.impl_item_id()).kind else { - return; - }; - let body = cx.tcx.hir_body(impl_item_id); - let ExprKind::Block(block, ..) = body.value.kind else { - return; - }; - if block.span.in_external_macro(cx.sess().source_map()) || is_from_proc_macro(cx, impl_item) { - return; - } - - 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, &[]) + if let Node::Item(item) = cx.tcx.parent_hir_node(impl_item.hir_id()) + && let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder) + && !cx.tcx.is_automatically_derived(item.owner_id.to_def_id()) + && let ImplItemKind::Fn(_, impl_item_id) = cx.tcx.hir_impl_item(impl_item.impl_item_id()).kind + && let body = cx.tcx.hir_body(impl_item_id) + && let ExprKind::Block(block, ..) = body.value.kind + && !block.span.in_external_macro(cx.sess().source_map()) + && !is_from_proc_macro(cx, impl_item) { - if impl_item.ident.name == sym::clone { - if block.stmts.is_empty() - && let Some(expr) = block.expr - && let ExprKind::Unary(UnOp::Deref, deref) = expr.kind - && let ExprKind::Path(qpath) = deref.kind - && last_path_segment(&qpath).ident.name == kw::SelfLower - { - } else { + 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, &[]) + { + if impl_item.ident.name == sym::clone { + if block.stmts.is_empty() + && let Some(expr) = block.expr + && let ExprKind::Unary(UnOp::Deref, deref) = expr.kind + && let ExprKind::Path(qpath) = deref.kind + && last_path_segment(&qpath).ident.name == kw::SelfLower + { + } else { + span_lint_and_sugg( + cx, + NON_CANONICAL_CLONE_IMPL, + block.span, + "non-canonical implementation of `clone` on a `Copy` type", + "change this to", + "{ *self }".to_owned(), + Applicability::MaybeIncorrect, + ); + + return; + } + } + + if impl_item.ident.name == sym::clone_from { span_lint_and_sugg( cx, NON_CANONICAL_CLONE_IMPL, - block.span, - "non-canonical implementation of `clone` on a `Copy` type", - "change this to", - "{ *self }".to_owned(), + impl_item.span, + "unnecessary implementation of `clone_from` on a `Copy` type", + "remove it", + String::new(), Applicability::MaybeIncorrect, ); + } + } 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, &[]) + { + // If the `cmp` call likely needs to be fully qualified in the suggestion + // (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't + // access `cmp_expr` in the suggestion without major changes, as we lint in `else`. + let mut needs_fully_qualified = false; + if block.stmts.is_empty() + && let Some(expr) = block.expr + && expr_is_cmp(cx, expr, impl_item, &mut needs_fully_qualified) + { + return; + } + // Fix #12683, allow [`needless_return`] here + else if block.expr.is_none() + && let Some(stmt) = block.stmts.first() + && let rustc_hir::StmtKind::Semi(Expr { + kind: ExprKind::Ret(Some(ret)), + .. + }) = stmt.kind + && expr_is_cmp(cx, ret, impl_item, &mut needs_fully_qualified) + { + return; + } + // If `Self` and `Rhs` are not the same type, bail. This makes creating a valid + // suggestion tons more complex. + else if let [lhs, rhs, ..] = trait_impl.args.as_slice() + && lhs != rhs + { return; } - } - if impl_item.ident.name == sym::clone_from { - span_lint_and_sugg( + span_lint_and_then( cx, - NON_CANONICAL_CLONE_IMPL, - impl_item.span, - "unnecessary implementation of `clone_from` on a `Copy` type", - "remove it", - String::new(), - Applicability::MaybeIncorrect, - ); - } - } 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, &[]) - { - // If the `cmp` call likely needs to be fully qualified in the suggestion - // (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't - // access `cmp_expr` in the suggestion without major changes, as we lint in `else`. - let mut needs_fully_qualified = false; - - if block.stmts.is_empty() - && let Some(expr) = block.expr - && expr_is_cmp(cx, expr, impl_item, &mut needs_fully_qualified) - { - return; - } - // Fix #12683, allow [`needless_return`] here - else if block.expr.is_none() - && let Some(stmt) = block.stmts.first() - && let rustc_hir::StmtKind::Semi(Expr { - kind: ExprKind::Ret(Some(ret)), - .. - }) = stmt.kind - && expr_is_cmp(cx, ret, impl_item, &mut needs_fully_qualified) - { - return; - } - // If `Self` and `Rhs` are not the same type, bail. This makes creating a valid - // suggestion tons more complex. - else if let [lhs, rhs, ..] = trait_impl.args.as_slice() - && lhs != rhs - { - return; - } - - span_lint_and_then( - cx, - NON_CANONICAL_PARTIAL_ORD_IMPL, - item.span, - "non-canonical implementation of `partial_cmp` on an `Ord` type", - |diag| { - let [_, other] = body.params else { - return; - }; - let Some(std_or_core) = std_or_core(cx) else { - return; - }; + NON_CANONICAL_PARTIAL_ORD_IMPL, + item.span, + "non-canonical implementation of `partial_cmp` on an `Ord` type", + |diag| { + let [_, other] = body.params else { + return; + }; + let Some(std_or_core) = std_or_core(cx) else { + return; + }; - let suggs = match (other.pat.simple_ident(), needs_fully_qualified) { - (Some(other_ident), true) => vec![( - block.span, - format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}", other_ident.name), - )], - (Some(other_ident), false) => { - vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))] - }, - (None, true) => vec![ - ( + let suggs = match (other.pat.simple_ident(), needs_fully_qualified) { + (Some(other_ident), true) => vec![( block.span, - format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}"), - ), - (other.pat.span, "other".to_owned()), - ], - (None, false) => vec![ - (block.span, "{ Some(self.cmp(other)) }".to_owned()), - (other.pat.span, "other".to_owned()), - ], - }; + format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}", other_ident.name), + )], + (Some(other_ident), false) => { + vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))] + }, + (None, true) => vec![ + ( + block.span, + format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}"), + ), + (other.pat.span, "other".to_owned()), + ], + (None, false) => vec![ + (block.span, "{ Some(self.cmp(other)) }".to_owned()), + (other.pat.span, "other".to_owned()), + ], + }; - diag.multipart_suggestion("change this to", suggs, Applicability::Unspecified); - }, - ); + diag.multipart_suggestion("change this to", suggs, Applicability::Unspecified); + }, + ); + } } } } From 54e28471d707c86476f7519571eea74c0016d3c8 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 01:31:18 +0200 Subject: [PATCH 30/93] extract helper functions too many lines no more! --- clippy_lints/src/non_canonical_impls.rs | 214 +++++++++++++----------- 1 file changed, 113 insertions(+), 101 deletions(-) diff --git a/clippy_lints/src/non_canonical_impls.rs b/clippy_lints/src/non_canonical_impls.rs index c479f40707e98..5f2c388ff678f 100644 --- a/clippy_lints/src/non_canonical_impls.rs +++ b/clippy_lints/src/non_canonical_impls.rs @@ -5,9 +5,9 @@ use clippy_utils::{ }; use rustc_errors::Applicability; use rustc_hir::def_id::LocalDefId; -use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, LangItem, Node, UnOp}; +use rustc_hir::{Block, Body, Expr, ExprKind, ImplItem, ImplItemKind, Item, LangItem, Node, UnOp}; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::ty::EarlyBinder; +use rustc_middle::ty::{EarlyBinder, TraitRef}; use rustc_session::declare_lint_pass; use rustc_span::sym; use rustc_span::symbol::kw; @@ -112,7 +112,6 @@ declare_clippy_lint! { declare_lint_pass!(NonCanonicalImpls => [NON_CANONICAL_CLONE_IMPL, NON_CANONICAL_PARTIAL_ORD_IMPL]); impl LateLintPass<'_> for NonCanonicalImpls { - #[expect(clippy::too_many_lines)] fn check_impl_item<'tcx>(&mut self, cx: &LateContext<'tcx>, impl_item: &ImplItem<'tcx>) { if let Node::Item(item) = cx.tcx.parent_hir_node(impl_item.hir_id()) && let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder) @@ -128,114 +127,127 @@ impl LateLintPass<'_> for NonCanonicalImpls { && let Some(copy_def_id) = cx.tcx.get_diagnostic_item(sym::Copy) && implements_trait(cx, trait_impl.self_ty(), copy_def_id, &[]) { - if impl_item.ident.name == sym::clone { - if block.stmts.is_empty() - && let Some(expr) = block.expr - && let ExprKind::Unary(UnOp::Deref, deref) = expr.kind - && let ExprKind::Path(qpath) = deref.kind - && last_path_segment(&qpath).ident.name == kw::SelfLower - { - } else { - span_lint_and_sugg( - cx, - NON_CANONICAL_CLONE_IMPL, - block.span, - "non-canonical implementation of `clone` on a `Copy` type", - "change this to", - "{ *self }".to_owned(), - Applicability::MaybeIncorrect, - ); - - return; - } - } - - if impl_item.ident.name == sym::clone_from { - span_lint_and_sugg( - cx, - NON_CANONICAL_CLONE_IMPL, - impl_item.span, - "unnecessary implementation of `clone_from` on a `Copy` type", - "remove it", - String::new(), - Applicability::MaybeIncorrect, - ); - } + check_clone_on_copy(cx, impl_item, block); } 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, &[]) { - // If the `cmp` call likely needs to be fully qualified in the suggestion - // (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't - // access `cmp_expr` in the suggestion without major changes, as we lint in `else`. - let mut needs_fully_qualified = false; + check_partial_ord_on_ord(cx, impl_item, item, &trait_impl, body, block); + } + } + } +} - if block.stmts.is_empty() - && let Some(expr) = block.expr - && expr_is_cmp(cx, expr, impl_item, &mut needs_fully_qualified) - { - return; - } - // Fix #12683, allow [`needless_return`] here - else if block.expr.is_none() - && let Some(stmt) = block.stmts.first() - && let rustc_hir::StmtKind::Semi(Expr { - kind: ExprKind::Ret(Some(ret)), - .. - }) = stmt.kind - && expr_is_cmp(cx, ret, impl_item, &mut needs_fully_qualified) - { - return; - } - // If `Self` and `Rhs` are not the same type, bail. This makes creating a valid - // suggestion tons more complex. - else if let [lhs, rhs, ..] = trait_impl.args.as_slice() - && lhs != rhs - { - return; - } +fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &Block<'_>) { + if impl_item.ident.name == sym::clone { + if block.stmts.is_empty() + && let Some(expr) = block.expr + && let ExprKind::Unary(UnOp::Deref, deref) = expr.kind + && let ExprKind::Path(qpath) = deref.kind + && last_path_segment(&qpath).ident.name == kw::SelfLower + { + } else { + span_lint_and_sugg( + cx, + NON_CANONICAL_CLONE_IMPL, + block.span, + "non-canonical implementation of `clone` on a `Copy` type", + "change this to", + "{ *self }".to_owned(), + Applicability::MaybeIncorrect, + ); + } + } - span_lint_and_then( - cx, - NON_CANONICAL_PARTIAL_ORD_IMPL, - item.span, - "non-canonical implementation of `partial_cmp` on an `Ord` type", - |diag| { - let [_, other] = body.params else { - return; - }; - let Some(std_or_core) = std_or_core(cx) else { - return; - }; + if impl_item.ident.name == sym::clone_from { + span_lint_and_sugg( + cx, + NON_CANONICAL_CLONE_IMPL, + impl_item.span, + "unnecessary implementation of `clone_from` on a `Copy` type", + "remove it", + String::new(), + Applicability::MaybeIncorrect, + ); + } +} - let suggs = match (other.pat.simple_ident(), needs_fully_qualified) { - (Some(other_ident), true) => vec![( - block.span, - format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}", other_ident.name), - )], - (Some(other_ident), false) => { - vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))] - }, - (None, true) => vec![ - ( - block.span, - format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}"), - ), - (other.pat.span, "other".to_owned()), - ], - (None, false) => vec![ - (block.span, "{ Some(self.cmp(other)) }".to_owned()), - (other.pat.span, "other".to_owned()), - ], - }; +fn check_partial_ord_on_ord<'tcx>( + cx: &LateContext<'tcx>, + impl_item: &ImplItem<'_>, + item: &Item<'_>, + trait_impl: &TraitRef<'_>, + body: &Body<'_>, + block: &Block<'tcx>, +) { + // If the `cmp` call likely needs to be fully qualified in the suggestion + // (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't + // access `cmp_expr` in the suggestion without major changes, as we lint in `else`. - diag.multipart_suggestion("change this to", suggs, Applicability::Unspecified); - }, - ); - } - } + let mut needs_fully_qualified = false; + if block.stmts.is_empty() + && let Some(expr) = block.expr + && expr_is_cmp(cx, expr, impl_item, &mut needs_fully_qualified) + { + return; + } + // Fix #12683, allow [`needless_return`] here + else if block.expr.is_none() + && let Some(stmt) = block.stmts.first() + && let rustc_hir::StmtKind::Semi(Expr { + kind: ExprKind::Ret(Some(ret)), + .. + }) = stmt.kind + && expr_is_cmp(cx, ret, impl_item, &mut needs_fully_qualified) + { + return; + } + // If `Self` and `Rhs` are not the same type, bail. This makes creating a valid + // suggestion tons more complex. + else if let [lhs, rhs, ..] = trait_impl.args.as_slice() + && lhs != rhs + { + return; } + + span_lint_and_then( + cx, + NON_CANONICAL_PARTIAL_ORD_IMPL, + item.span, + "non-canonical implementation of `partial_cmp` on an `Ord` type", + |diag| { + let [_, other] = body.params else { + return; + }; + let Some(std_or_core) = std_or_core(cx) else { + return; + }; + + let suggs = match (other.pat.simple_ident(), needs_fully_qualified) { + (Some(other_ident), true) => vec![( + block.span, + format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}", other_ident.name), + )], + (Some(other_ident), false) => { + vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))] + }, + (None, true) => vec![ + ( + block.span, + format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}"), + ), + (other.pat.span, "other".to_owned()), + ], + (None, false) => vec![ + (block.span, "{ Some(self.cmp(other)) }".to_owned()), + (other.pat.span, "other".to_owned()), + ], + }; + + diag.multipart_suggestion("change this to", suggs, Applicability::Unspecified); + }, + ); } /// Return true if `expr_kind` is a `cmp` call. From 34d9a2a084b42533d508cbfe3207b91b2f014eba Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 20 Aug 2025 23:17:32 +0200 Subject: [PATCH 31/93] use `match` imo it's a bit more clear than checking for the same thing (`expr.kind`) in multiple branches --- clippy_lints/src/non_canonical_impls.rs | 37 +++++++++++++------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/clippy_lints/src/non_canonical_impls.rs b/clippy_lints/src/non_canonical_impls.rs index 5f2c388ff678f..7dd5157340305 100644 --- a/clippy_lints/src/non_canonical_impls.rs +++ b/clippy_lints/src/non_canonical_impls.rs @@ -258,26 +258,27 @@ fn expr_is_cmp<'tcx>( needs_fully_qualified: &mut bool, ) -> bool { let impl_item_did = impl_item.owner_id.def_id; - if let ExprKind::Call( - Expr { - kind: ExprKind::Path(some_path), - hir_id: some_hir_id, - .. - }, - [cmp_expr], - ) = expr.kind - { - is_res_lang_ctor(cx, cx.qpath_res(some_path, *some_hir_id), LangItem::OptionSome) + match expr.kind { + ExprKind::Call( + Expr { + kind: ExprKind::Path(some_path), + hir_id: some_hir_id, + .. + }, + [cmp_expr], + ) => { + is_res_lang_ctor(cx, cx.qpath_res(some_path, *some_hir_id), LangItem::OptionSome) // Fix #11178, allow `Self::cmp(self, ..)` too && self_cmp_call(cx, cmp_expr, impl_item_did, needs_fully_qualified) - } else if let ExprKind::MethodCall(_, recv, [], _) = expr.kind { - cx.tcx - .typeck(impl_item_did) - .type_dependent_def_id(expr.hir_id) - .is_some_and(|def_id| is_diag_trait_item(cx, def_id, sym::Into)) - && self_cmp_call(cx, recv, impl_item_did, needs_fully_qualified) - } else { - false + }, + ExprKind::MethodCall(_, recv, [], _) => { + cx.tcx + .typeck(impl_item_did) + .type_dependent_def_id(expr.hir_id) + .is_some_and(|def_id| is_diag_trait_item(cx, def_id, sym::Into)) + && self_cmp_call(cx, recv, impl_item_did, needs_fully_qualified) + }, + _ => false, } } From 21a5948ca17787961c6dcae8964086ddadb4f1b8 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 21 Sep 2025 22:04:25 +0200 Subject: [PATCH 32/93] incorporate review feedback --- clippy_lints/src/non_canonical_impls.rs | 30 ++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/non_canonical_impls.rs b/clippy_lints/src/non_canonical_impls.rs index 7dd5157340305..e531f797272d2 100644 --- a/clippy_lints/src/non_canonical_impls.rs +++ b/clippy_lints/src/non_canonical_impls.rs @@ -113,16 +113,18 @@ declare_lint_pass!(NonCanonicalImpls => [NON_CANONICAL_CLONE_IMPL, NON_CANONICAL impl LateLintPass<'_> for NonCanonicalImpls { fn check_impl_item<'tcx>(&mut self, cx: &LateContext<'tcx>, impl_item: &ImplItem<'tcx>) { - if let Node::Item(item) = cx.tcx.parent_hir_node(impl_item.hir_id()) + if let ImplItemKind::Fn(_, impl_item_id) = impl_item.kind + && let Node::Item(item) = cx.tcx.parent_hir_node(impl_item.hir_id()) && let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder) + && let trait_name = cx.tcx.get_diagnostic_name(trait_impl.def_id) + // NOTE: check this early to avoid expensive checks that come after this one + && matches!(trait_name, Some(sym::Clone | sym::PartialOrd)) && !cx.tcx.is_automatically_derived(item.owner_id.to_def_id()) - && let ImplItemKind::Fn(_, impl_item_id) = cx.tcx.hir_impl_item(impl_item.impl_item_id()).kind && let body = cx.tcx.hir_body(impl_item_id) && let ExprKind::Block(block, ..) = body.value.kind && !block.span.in_external_macro(cx.sess().source_map()) && !is_from_proc_macro(cx, impl_item) { - 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, &[]) @@ -147,17 +149,19 @@ fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &B && let ExprKind::Path(qpath) = deref.kind && last_path_segment(&qpath).ident.name == kw::SelfLower { - } else { - span_lint_and_sugg( - cx, - NON_CANONICAL_CLONE_IMPL, - block.span, - "non-canonical implementation of `clone` on a `Copy` type", - "change this to", - "{ *self }".to_owned(), - Applicability::MaybeIncorrect, - ); + // this is the canonical implementation, `fn clone(&self) -> Self { *self }` + return; } + + span_lint_and_sugg( + cx, + NON_CANONICAL_CLONE_IMPL, + block.span, + "non-canonical implementation of `clone` on a `Copy` type", + "change this to", + "{ *self }".to_owned(), + Applicability::MaybeIncorrect, + ); } if impl_item.ident.name == sym::clone_from { From 220e137089943b3977cdac4755212646bded08dc Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 23 Sep 2025 14:34:38 +0200 Subject: [PATCH 33/93] split `unnecessary_clone` test into `clone_on_ref_ptr` and `clone_on_copy` --- tests/ui/clone_on_copy.fixed | 65 +++++++++++++---- tests/ui/clone_on_copy.rs | 65 +++++++++++++---- tests/ui/clone_on_copy.stderr | 26 ++++--- tests/ui/clone_on_ref_ptr.fixed | 51 ++++++++++++++ tests/ui/clone_on_ref_ptr.rs | 51 ++++++++++++++ tests/ui/clone_on_ref_ptr.stderr | 41 +++++++++++ tests/ui/unnecessary_clone.rs | 111 ------------------------------ tests/ui/unnecessary_clone.stderr | 62 ----------------- 8 files changed, 261 insertions(+), 211 deletions(-) create mode 100644 tests/ui/clone_on_ref_ptr.fixed create mode 100644 tests/ui/clone_on_ref_ptr.rs create mode 100644 tests/ui/clone_on_ref_ptr.stderr delete mode 100644 tests/ui/unnecessary_clone.rs delete mode 100644 tests/ui/unnecessary_clone.stderr diff --git a/tests/ui/clone_on_copy.fixed b/tests/ui/clone_on_copy.fixed index 2dd8af1525150..469121bbd740f 100644 --- a/tests/ui/clone_on_copy.fixed +++ b/tests/ui/clone_on_copy.fixed @@ -1,25 +1,16 @@ +#![warn(clippy::clone_on_copy)] #![allow( - unused, clippy::redundant_clone, clippy::deref_addrof, clippy::no_effect, clippy::unnecessary_operation, - clippy::vec_init_then_push, clippy::toplevel_ref_arg, clippy::needless_borrow )] use std::cell::RefCell; -use std::rc::{self, Rc}; -use std::sync::{self, Arc}; -fn main() {} - -fn is_ascii(ch: char) -> bool { - ch.is_ascii() -} - -fn clone_on_copy() -> Option<(i32)> { +fn main() { 42; //~^ clone_on_copy @@ -65,20 +56,66 @@ fn clone_on_copy() -> Option<(i32)> { let x = 42; let ref y = x.clone(); // ok, binds by reference let ref mut y = x.clone(); // ok, binds by reference +} + +mod issue3052 { + struct A; + struct B; + struct C; + struct D; + #[derive(Copy, Clone)] + struct E; + + macro_rules! impl_deref { + ($src:ident, $dst:ident) => { + impl std::ops::Deref for $src { + type Target = $dst; + fn deref(&self) -> &Self::Target { + &$dst + } + } + }; + } + + impl_deref!(A, B); + impl_deref!(B, C); + impl_deref!(C, D); + impl std::ops::Deref for D { + type Target = &'static E; + fn deref(&self) -> &Self::Target { + &&E + } + } + + fn go1() { + let a = A; + let _: E = *****a; + //~^ clone_on_copy + + let _: E = *****a; + } +} + +fn issue4348() { + fn is_ascii(ch: char) -> bool { + ch.is_ascii() + } - // Issue #4348 let mut x = 43; let _ = &x.clone(); // ok, getting a ref 'a'.clone().make_ascii_uppercase(); // ok, clone and then mutate is_ascii('z'); //~^ clone_on_copy +} - // Issue #5436 +#[expect(clippy::vec_init_then_push)] +fn issue5436() { let mut vec = Vec::new(); vec.push(42); //~^ clone_on_copy +} - // Issue #9277 +fn issue9277() -> Option { let opt: &Option = &None; let value = (*opt)?; // operator precedence needed (*opt)? // diff --git a/tests/ui/clone_on_copy.rs b/tests/ui/clone_on_copy.rs index a371afb04a78f..b05f1d3aa35e9 100644 --- a/tests/ui/clone_on_copy.rs +++ b/tests/ui/clone_on_copy.rs @@ -1,25 +1,16 @@ +#![warn(clippy::clone_on_copy)] #![allow( - unused, clippy::redundant_clone, clippy::deref_addrof, clippy::no_effect, clippy::unnecessary_operation, - clippy::vec_init_then_push, clippy::toplevel_ref_arg, clippy::needless_borrow )] use std::cell::RefCell; -use std::rc::{self, Rc}; -use std::sync::{self, Arc}; -fn main() {} - -fn is_ascii(ch: char) -> bool { - ch.is_ascii() -} - -fn clone_on_copy() -> Option<(i32)> { +fn main() { 42.clone(); //~^ clone_on_copy @@ -65,20 +56,66 @@ fn clone_on_copy() -> Option<(i32)> { let x = 42; let ref y = x.clone(); // ok, binds by reference let ref mut y = x.clone(); // ok, binds by reference +} + +mod issue3052 { + struct A; + struct B; + struct C; + struct D; + #[derive(Copy, Clone)] + struct E; + + macro_rules! impl_deref { + ($src:ident, $dst:ident) => { + impl std::ops::Deref for $src { + type Target = $dst; + fn deref(&self) -> &Self::Target { + &$dst + } + } + }; + } + + impl_deref!(A, B); + impl_deref!(B, C); + impl_deref!(C, D); + impl std::ops::Deref for D { + type Target = &'static E; + fn deref(&self) -> &Self::Target { + &&E + } + } + + fn go1() { + let a = A; + let _: E = a.clone(); + //~^ clone_on_copy + + let _: E = *****a; + } +} + +fn issue4348() { + fn is_ascii(ch: char) -> bool { + ch.is_ascii() + } - // Issue #4348 let mut x = 43; let _ = &x.clone(); // ok, getting a ref 'a'.clone().make_ascii_uppercase(); // ok, clone and then mutate is_ascii('z'.clone()); //~^ clone_on_copy +} - // Issue #5436 +#[expect(clippy::vec_init_then_push)] +fn issue5436() { let mut vec = Vec::new(); vec.push(42.clone()); //~^ clone_on_copy +} - // Issue #9277 +fn issue9277() -> Option { let opt: &Option = &None; let value = opt.clone()?; // operator precedence needed (*opt)? // diff --git a/tests/ui/clone_on_copy.stderr b/tests/ui/clone_on_copy.stderr index 92cdd635d20a8..c87d1d488dde5 100644 --- a/tests/ui/clone_on_copy.stderr +++ b/tests/ui/clone_on_copy.stderr @@ -1,5 +1,5 @@ error: using `clone` on type `i32` which implements the `Copy` trait - --> tests/ui/clone_on_copy.rs:23:5 + --> tests/ui/clone_on_copy.rs:14:5 | LL | 42.clone(); | ^^^^^^^^^^ help: try removing the `clone` call: `42` @@ -8,52 +8,58 @@ LL | 42.clone(); = help: to override `-D warnings` add `#[allow(clippy::clone_on_copy)]` error: using `clone` on type `i32` which implements the `Copy` trait - --> tests/ui/clone_on_copy.rs:28:5 + --> tests/ui/clone_on_copy.rs:19:5 | LL | (&42).clone(); | ^^^^^^^^^^^^^ help: try dereferencing it: `*(&42)` error: using `clone` on type `i32` which implements the `Copy` trait - --> tests/ui/clone_on_copy.rs:32:5 + --> tests/ui/clone_on_copy.rs:23:5 | LL | rc.borrow().clone(); | ^^^^^^^^^^^^^^^^^^^ help: try dereferencing it: `*rc.borrow()` error: using `clone` on type `u32` which implements the `Copy` trait - --> tests/ui/clone_on_copy.rs:36:5 + --> tests/ui/clone_on_copy.rs:27:5 | LL | x.clone().rotate_left(1); | ^^^^^^^^^ help: try removing the `clone` call: `x` error: using `clone` on type `i32` which implements the `Copy` trait - --> tests/ui/clone_on_copy.rs:51:5 + --> tests/ui/clone_on_copy.rs:42:5 | LL | m!(42).clone(); | ^^^^^^^^^^^^^^ help: try removing the `clone` call: `m!(42)` error: using `clone` on type `[u32; 2]` which implements the `Copy` trait - --> tests/ui/clone_on_copy.rs:62:5 + --> tests/ui/clone_on_copy.rs:53:5 | LL | x.clone()[0]; | ^^^^^^^^^ help: try dereferencing it: `(*x)` +error: using `clone` on type `E` which implements the `Copy` trait + --> tests/ui/clone_on_copy.rs:92:20 + | +LL | let _: E = a.clone(); + | ^^^^^^^^^ help: try dereferencing it: `*****a` + error: using `clone` on type `char` which implements the `Copy` trait - --> tests/ui/clone_on_copy.rs:73:14 + --> tests/ui/clone_on_copy.rs:107:14 | LL | is_ascii('z'.clone()); | ^^^^^^^^^^^ help: try removing the `clone` call: `'z'` error: using `clone` on type `i32` which implements the `Copy` trait - --> tests/ui/clone_on_copy.rs:78:14 + --> tests/ui/clone_on_copy.rs:114:14 | LL | vec.push(42.clone()); | ^^^^^^^^^^ help: try removing the `clone` call: `42` error: using `clone` on type `Option` which implements the `Copy` trait - --> tests/ui/clone_on_copy.rs:83:17 + --> tests/ui/clone_on_copy.rs:120:17 | LL | let value = opt.clone()?; // operator precedence needed (*opt)? | ^^^^^^^^^^^ help: try dereferencing it: `(*opt)` -error: aborting due to 9 previous errors +error: aborting due to 10 previous errors diff --git a/tests/ui/clone_on_ref_ptr.fixed b/tests/ui/clone_on_ref_ptr.fixed new file mode 100644 index 0000000000000..8ef4b36566363 --- /dev/null +++ b/tests/ui/clone_on_ref_ptr.fixed @@ -0,0 +1,51 @@ +#![warn(clippy::clone_on_ref_ptr)] + +use std::rc::{Rc, Weak as RcWeak}; +use std::sync::{Arc, Weak as ArcWeak}; + +fn main() {} + +fn clone_on_ref_ptr(rc: Rc, rc_weak: RcWeak, arc: Arc, arc_weak: ArcWeak) { + std::rc::Rc::::clone(&rc); + //~^ clone_on_ref_ptr + std::rc::Weak::::clone(&rc_weak); + //~^ clone_on_ref_ptr + std::sync::Arc::::clone(&arc); + //~^ clone_on_ref_ptr + std::sync::Weak::::clone(&arc_weak); + //~^ clone_on_ref_ptr + + Rc::clone(&rc); + Arc::clone(&arc); + RcWeak::clone(&rc_weak); + ArcWeak::clone(&arc_weak); +} + +trait SomeTrait {} +struct SomeImpl; +impl SomeTrait for SomeImpl {} + +fn trait_object() { + let x = Arc::new(SomeImpl); + let _: Arc = std::sync::Arc::::clone(&x); + //~^ clone_on_ref_ptr +} + +mod issue2076 { + use std::rc::Rc; + + macro_rules! try_opt { + ($expr: expr) => { + match $expr { + Some(value) => value, + None => return None, + } + }; + } + + fn func() -> Option> { + let rc = Rc::new(42); + Some(std::rc::Rc::::clone(&try_opt!(Some(rc)))) + //~^ clone_on_ref_ptr + } +} diff --git a/tests/ui/clone_on_ref_ptr.rs b/tests/ui/clone_on_ref_ptr.rs new file mode 100644 index 0000000000000..fbd787099aee3 --- /dev/null +++ b/tests/ui/clone_on_ref_ptr.rs @@ -0,0 +1,51 @@ +#![warn(clippy::clone_on_ref_ptr)] + +use std::rc::{Rc, Weak as RcWeak}; +use std::sync::{Arc, Weak as ArcWeak}; + +fn main() {} + +fn clone_on_ref_ptr(rc: Rc, rc_weak: RcWeak, arc: Arc, arc_weak: ArcWeak) { + rc.clone(); + //~^ clone_on_ref_ptr + rc_weak.clone(); + //~^ clone_on_ref_ptr + arc.clone(); + //~^ clone_on_ref_ptr + arc_weak.clone(); + //~^ clone_on_ref_ptr + + Rc::clone(&rc); + Arc::clone(&arc); + RcWeak::clone(&rc_weak); + ArcWeak::clone(&arc_weak); +} + +trait SomeTrait {} +struct SomeImpl; +impl SomeTrait for SomeImpl {} + +fn trait_object() { + let x = Arc::new(SomeImpl); + let _: Arc = x.clone(); + //~^ clone_on_ref_ptr +} + +mod issue2076 { + use std::rc::Rc; + + macro_rules! try_opt { + ($expr: expr) => { + match $expr { + Some(value) => value, + None => return None, + } + }; + } + + fn func() -> Option> { + let rc = Rc::new(42); + Some(try_opt!(Some(rc)).clone()) + //~^ clone_on_ref_ptr + } +} diff --git a/tests/ui/clone_on_ref_ptr.stderr b/tests/ui/clone_on_ref_ptr.stderr new file mode 100644 index 0000000000000..b15f0e803a352 --- /dev/null +++ b/tests/ui/clone_on_ref_ptr.stderr @@ -0,0 +1,41 @@ +error: using `.clone()` on a ref-counted pointer + --> tests/ui/clone_on_ref_ptr.rs:9:5 + | +LL | rc.clone(); + | ^^^^^^^^^^ 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)]` + +error: using `.clone()` on a ref-counted pointer + --> tests/ui/clone_on_ref_ptr.rs:11:5 + | +LL | rc_weak.clone(); + | ^^^^^^^^^^^^^^^ help: try: `std::rc::Weak::::clone(&rc_weak)` + +error: using `.clone()` on a ref-counted pointer + --> tests/ui/clone_on_ref_ptr.rs:13:5 + | +LL | arc.clone(); + | ^^^^^^^^^^^ help: try: `std::sync::Arc::::clone(&arc)` + +error: using `.clone()` on a ref-counted pointer + --> tests/ui/clone_on_ref_ptr.rs:15:5 + | +LL | arc_weak.clone(); + | ^^^^^^^^^^^^^^^^ help: try: `std::sync::Weak::::clone(&arc_weak)` + +error: using `.clone()` on a ref-counted pointer + --> tests/ui/clone_on_ref_ptr.rs:30:33 + | +LL | let _: Arc = x.clone(); + | ^^^^^^^^^ help: try: `std::sync::Arc::::clone(&x)` + +error: using `.clone()` on a ref-counted pointer + --> tests/ui/clone_on_ref_ptr.rs:48:14 + | +LL | Some(try_opt!(Some(rc)).clone()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::rc::Rc::::clone(&try_opt!(Some(rc)))` + +error: aborting due to 6 previous errors + diff --git a/tests/ui/unnecessary_clone.rs b/tests/ui/unnecessary_clone.rs deleted file mode 100644 index 7335b6f9f0390..0000000000000 --- a/tests/ui/unnecessary_clone.rs +++ /dev/null @@ -1,111 +0,0 @@ -// does not test any rustfixable lints -#![warn(clippy::clone_on_ref_ptr)] -#![allow(unused)] -#![allow(clippy::redundant_clone, clippy::uninlined_format_args, clippy::unnecessary_wraps)] -//@no-rustfix -use std::cell::RefCell; -use std::rc::{self, Rc}; -use std::sync::{self, Arc}; - -trait SomeTrait {} -struct SomeImpl; -impl SomeTrait for SomeImpl {} - -fn main() {} - -fn clone_on_ref_ptr() { - let rc = Rc::new(true); - let arc = Arc::new(true); - - let rcweak = Rc::downgrade(&rc); - let arc_weak = Arc::downgrade(&arc); - - rc.clone(); - //~^ clone_on_ref_ptr - - Rc::clone(&rc); - - arc.clone(); - //~^ clone_on_ref_ptr - - Arc::clone(&arc); - - rcweak.clone(); - //~^ clone_on_ref_ptr - - rc::Weak::clone(&rcweak); - - arc_weak.clone(); - //~^ clone_on_ref_ptr - - sync::Weak::clone(&arc_weak); - - let x = Arc::new(SomeImpl); - let _: Arc = x.clone(); - //~^ clone_on_ref_ptr -} - -fn clone_on_copy_generic(t: T) { - t.clone(); - //~^ clone_on_copy - - Some(t).clone(); - //~^ clone_on_copy -} - -mod many_derefs { - struct A; - struct B; - struct C; - struct D; - #[derive(Copy, Clone)] - struct E; - - macro_rules! impl_deref { - ($src:ident, $dst:ident) => { - impl std::ops::Deref for $src { - type Target = $dst; - fn deref(&self) -> &Self::Target { - &$dst - } - } - }; - } - - impl_deref!(A, B); - impl_deref!(B, C); - impl_deref!(C, D); - impl std::ops::Deref for D { - type Target = &'static E; - fn deref(&self) -> &Self::Target { - &&E - } - } - - fn go1() { - let a = A; - let _: E = a.clone(); - //~^ clone_on_copy - - let _: E = *****a; - } -} - -mod issue2076 { - use std::rc::Rc; - - macro_rules! try_opt { - ($expr: expr) => { - match $expr { - Some(value) => value, - None => return None, - } - }; - } - - fn func() -> Option> { - let rc = Rc::new(42); - Some(try_opt!(Some(rc)).clone()) - //~^ clone_on_ref_ptr - } -} diff --git a/tests/ui/unnecessary_clone.stderr b/tests/ui/unnecessary_clone.stderr deleted file mode 100644 index 17518e123d12e..0000000000000 --- a/tests/ui/unnecessary_clone.stderr +++ /dev/null @@ -1,62 +0,0 @@ -error: using `.clone()` on a ref-counted pointer - --> tests/ui/unnecessary_clone.rs:23:5 - | -LL | rc.clone(); - | ^^^^^^^^^^ 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)]` - -error: using `.clone()` on a ref-counted pointer - --> tests/ui/unnecessary_clone.rs:28:5 - | -LL | arc.clone(); - | ^^^^^^^^^^^ 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: `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: `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: `std::sync::Arc::::clone(&x)` - -error: using `clone` on type `T` which implements the `Copy` trait - --> tests/ui/unnecessary_clone.rs:49:5 - | -LL | t.clone(); - | ^^^^^^^^^ help: try removing the `clone` call: `t` - | - = note: `-D clippy::clone-on-copy` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::clone_on_copy)]` - -error: using `clone` on type `Option` which implements the `Copy` trait - --> tests/ui/unnecessary_clone.rs:52:5 - | -LL | Some(t).clone(); - | ^^^^^^^^^^^^^^^ help: try removing the `clone` call: `Some(t)` - -error: using `clone` on type `E` which implements the `Copy` trait - --> tests/ui/unnecessary_clone.rs:87:20 - | -LL | let _: E = a.clone(); - | ^^^^^^^^^ help: try dereferencing it: `*****a` - -error: using `.clone()` on a ref-counted pointer - --> tests/ui/unnecessary_clone.rs:108:14 - | -LL | Some(try_opt!(Some(rc)).clone()) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::rc::Rc::::clone(&try_opt!(Some(rc)))` - -error: aborting due to 9 previous errors - From 38a2829c97e07e7a403d59424403561de4f91a3a Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 23 Sep 2025 16:58:45 +0200 Subject: [PATCH 34/93] doc(trait_checking): use `is_some_and` --- book/src/development/trait_checking.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/book/src/development/trait_checking.md b/book/src/development/trait_checking.md index 6d01496eebe07..c6f6f6bd2f99e 100644 --- a/book/src/development/trait_checking.md +++ b/book/src/development/trait_checking.md @@ -24,12 +24,11 @@ use rustc_span::symbol::sym; impl LateLintPass<'_> for CheckIteratorTraitLint { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - let implements_iterator = cx.tcx.get_diagnostic_item(sym::Iterator).map_or(false, |id| { - implements_trait(cx, cx.typeck_results().expr_ty(expr), id, &[]) - }); - if implements_iterator { - // [...] - } + let implements_iterator = (cx.tcx.get_diagnostic_item(sym::Iterator)) + .is_some_and(|id| implements_trait(cx, cx.typeck_results().expr_ty(expr), id, &[])); + if implements_iterator { + // [...] + } } } From ace2e274db8165fc5bfeba1faf83e15095723229 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Tue, 23 Sep 2025 23:02:59 +0200 Subject: [PATCH 35/93] Cleanup: group code for `single_char_add_str` lint in one file A lot of code was duplicated instead of being shared. --- clippy_lints/src/methods/mod.rs | 2 - .../src/methods/single_char_add_str.rs | 80 +++++++++++++++++-- .../src/methods/single_char_insert_string.rs | 67 ---------------- .../src/methods/single_char_push_string.rs | 65 --------------- 4 files changed, 73 insertions(+), 141 deletions(-) delete mode 100644 clippy_lints/src/methods/single_char_insert_string.rs delete mode 100644 clippy_lints/src/methods/single_char_push_string.rs diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 8679689c8ad4d..3755ec37fb967 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -103,8 +103,6 @@ mod search_is_some; mod seek_from_current; mod seek_to_start_instead_of_rewind; mod single_char_add_str; -mod single_char_insert_string; -mod single_char_push_string; mod skip_while_next; mod sliced_string_as_bytes; mod stable_sort_primitive; diff --git a/clippy_lints/src/methods/single_char_add_str.rs b/clippy_lints/src/methods/single_char_add_str.rs index ef3d7acdc01ef..1248d1658d77e 100644 --- a/clippy_lints/src/methods/single_char_add_str.rs +++ b/clippy_lints/src/methods/single_char_add_str.rs @@ -1,14 +1,80 @@ -use crate::methods::{single_char_insert_string, single_char_push_string}; -use rustc_hir as hir; +use super::SINGLE_CHAR_ADD_STR; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal}; +use rustc_ast::BorrowKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; +use rustc_middle::ty; use rustc_span::sym; -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { - 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), - _ => {}, + let mut applicability = Applicability::MachineApplicable; + let (short_name, arg, extra) = match cx.tcx.get_diagnostic_name(fn_def_id) { + Some(sym::string_insert_str) => ( + "insert", + &args[1], + Some(|applicability| { + format!( + "{}, ", + snippet_with_applicability(cx, args[0].span, "..", applicability) + ) + }), + ), + Some(sym::string_push_str) => ("push", &args[0], None), + _ => return, + }; + + if let Some(extension_string) = str_literal_to_char_literal(cx, arg, &mut applicability, false) { + let base_string_snippet = + snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability); + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + format!("calling `{short_name}_str()` using a single-character string literal"), + format!("consider using `{short_name}` with a character literal"), + format!( + "{base_string_snippet}.{short_name}({}{extension_string})", + extra.map_or(String::new(), |f| f(&mut applicability)) + ), + applicability, + ); + } else if let ExprKind::AddrOf(BorrowKind::Ref, _, inner) = arg.kind + && let ExprKind::MethodCall(path_segment, method_arg, [], _) = inner.kind + && path_segment.ident.name == sym::to_string + && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) + { + let base_string_snippet = + snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability); + let extension_string = match ( + snippet_with_applicability(cx, method_arg.span.source_callsite(), "_", &mut applicability), + is_ref_char(cx, method_arg), + ) { + (snippet, false) => snippet, + (snippet, true) => format!("*{snippet}").into(), + }; + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + format!("calling `{short_name}_str()` using a single-character converted to string"), + format!("consider using `{short_name}` without `to_string()`"), + format!( + "{base_string_snippet}.{short_name}({}{extension_string})", + extra.map_or(String::new(), |f| f(&mut applicability)) + ), + applicability, + ); } } } + +fn is_ref_char(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + matches!(cx.typeck_results().expr_ty(expr).kind(), ty::Ref(_, ty, _) if ty.is_char()) +} + +fn is_char(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + cx.typeck_results().expr_ty(expr).is_char() +} diff --git a/clippy_lints/src/methods/single_char_insert_string.rs b/clippy_lints/src/methods/single_char_insert_string.rs deleted file mode 100644 index 4a1d25deade90..0000000000000 --- a/clippy_lints/src/methods/single_char_insert_string.rs +++ /dev/null @@ -1,67 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal}; -use rustc_ast::BorrowKind; -use rustc_errors::Applicability; -use rustc_hir::{self as hir, ExprKind}; -use rustc_lint::LateContext; - -use super::SINGLE_CHAR_ADD_STR; - -/// lint for length-1 `str`s as argument for `insert_str` -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let mut applicability = Applicability::MachineApplicable; - if let Some(extension_string) = str_literal_to_char_literal(cx, &args[1], &mut applicability, false) { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability); - let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability); - let sugg = format!("{base_string_snippet}.insert({pos_arg}, {extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `insert_str()` using a single-character string literal", - "consider using `insert` with a character literal", - sugg, - applicability, - ); - } - - if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[1].kind - && let ExprKind::MethodCall(path_segment, method_arg, [], _) = &arg.kind - && path_segment.ident.name == rustc_span::sym::to_string - && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) - { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); - let extension_string = - snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability); - let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability); - let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" }; - - let sugg = format!("{base_string_snippet}.insert({pos_arg}, {deref_string}{extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `insert_str()` using a single-character converted to string", - "consider using `insert` without `to_string()`", - sugg, - applicability, - ); - } -} - -fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - if cx.typeck_results().expr_ty(expr).is_ref() - && let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind() - && ty.is_char() - { - return true; - } - - false -} - -fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - cx.typeck_results().expr_ty(expr).is_char() -} diff --git a/clippy_lints/src/methods/single_char_push_string.rs b/clippy_lints/src/methods/single_char_push_string.rs deleted file mode 100644 index bc271d593925e..0000000000000 --- a/clippy_lints/src/methods/single_char_push_string.rs +++ /dev/null @@ -1,65 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal}; -use rustc_ast::BorrowKind; -use rustc_errors::Applicability; -use rustc_hir::{self as hir, ExprKind}; -use rustc_lint::LateContext; - -use super::SINGLE_CHAR_ADD_STR; - -/// lint for length-1 `str`s as argument for `push_str` -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let mut applicability = Applicability::MachineApplicable; - if let Some(extension_string) = str_literal_to_char_literal(cx, &args[0], &mut applicability, false) { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); - let sugg = format!("{base_string_snippet}.push({extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `push_str()` using a single-character string literal", - "consider using `push` with a character literal", - sugg, - applicability, - ); - } - - if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[0].kind - && let ExprKind::MethodCall(path_segment, method_arg, [], _) = &arg.kind - && path_segment.ident.name == rustc_span::sym::to_string - && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) - { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); - let extension_string = - snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability); - let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" }; - - let sugg = format!("{base_string_snippet}.push({deref_string}{extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `push_str()` using a single-character converted to string", - "consider using `push` without `to_string()`", - sugg, - applicability, - ); - } -} - -fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - if cx.typeck_results().expr_ty(expr).is_ref() - && let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind() - && ty.is_char() - { - return true; - } - - false -} - -fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - cx.typeck_results().expr_ty(expr).is_char() -} From 5317b7be529eb1274dd255cbb2fab94e74475e12 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 24 Sep 2025 09:15:38 +0200 Subject: [PATCH 36/93] clean-up --- clippy_lints/src/methods/mod.rs | 2 +- clippy_lints/src/methods/or_fun_call.rs | 356 ++++++++++++------------ tests/ui/or_fun_call.fixed | 1 - tests/ui/or_fun_call.rs | 1 - tests/ui/or_fun_call.stderr | 90 +++--- 5 files changed, 223 insertions(+), 227 deletions(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 3755ec37fb967..be1fcf136e92d 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -4460,7 +4460,7 @@ declare_clippy_lint! { /// Checks for calls to `Read::bytes` on types which don't implement `BufRead`. /// /// ### Why is this bad? - /// The default implementation calls `read` for each byte, which can be very inefficient for data that’s not in memory, such as `File`. + /// The default implementation calls `read` for each byte, which can be very inefficient for data that's not in memory, such as `File`. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index 71b2f251eded6..1433c5c1307b2 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -18,7 +18,6 @@ use {rustc_ast as ast, rustc_hir as hir}; use super::{OR_FUN_CALL, UNWRAP_OR_DEFAULT}; /// Checks for the `OR_FUN_CALL` lint. -#[expect(clippy::too_many_lines)] pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, @@ -27,184 +26,6 @@ pub(super) fn check<'tcx>( receiver: &'tcx hir::Expr<'_>, args: &'tcx [hir::Expr<'_>], ) { - /// Checks for `unwrap_or(T::new())`, `unwrap_or(T::default())`, - /// `or_insert(T::new())` or `or_insert(T::default())`. - /// Similarly checks for `unwrap_or_else(T::new)`, `unwrap_or_else(T::default)`, - /// `or_insert_with(T::new)` or `or_insert_with(T::default)`. - fn check_unwrap_or_default( - cx: &LateContext<'_>, - name: Symbol, - receiver: &hir::Expr<'_>, - fun: &hir::Expr<'_>, - call_expr: Option<&hir::Expr<'_>>, - span: Span, - method_span: Span, - ) -> bool { - if !expr_type_is_certain(cx, receiver) { - return false; - } - - let is_new = |fun: &hir::Expr<'_>| { - if let hir::ExprKind::Path(ref qpath) = fun.kind { - let path = last_path_segment(qpath).ident.name; - matches!(path, sym::new) - } else { - false - } - }; - - let output_type_implements_default = |fun| { - let fun_ty = cx.typeck_results().expr_ty(fun); - if let ty::FnDef(def_id, args) = fun_ty.kind() { - let output_ty = cx.tcx.fn_sig(def_id).instantiate(cx.tcx, args).skip_binder().output(); - cx.tcx - .get_diagnostic_item(sym::Default) - .is_some_and(|default_trait_id| implements_trait(cx, output_ty, default_trait_id, &[])) - } else { - false - } - }; - - let sugg = match (name, call_expr.is_some()) { - (sym::unwrap_or, true) | (sym::unwrap_or_else, false) => sym::unwrap_or_default, - (sym::or_insert, true) | (sym::or_insert_with, false) => sym::or_default, - _ => return false, - }; - - let receiver_ty = cx.typeck_results().expr_ty_adjusted(receiver).peel_refs(); - let Some(suggested_method_def_id) = receiver_ty.ty_adt_def().and_then(|adt_def| { - cx.tcx - .inherent_impls(adt_def.did()) - .iter() - .flat_map(|impl_id| cx.tcx.associated_items(impl_id).filter_by_name_unhygienic(sugg)) - .find_map(|assoc| { - if assoc.is_method() && cx.tcx.fn_sig(assoc.def_id).skip_binder().inputs().skip_binder().len() == 1 - { - Some(assoc.def_id) - } else { - None - } - }) - }) else { - return false; - }; - let in_sugg_method_implementation = { - matches!( - suggested_method_def_id.as_local(), - Some(local_def_id) if local_def_id == cx.tcx.hir_get_parent_item(receiver.hir_id).def_id - ) - }; - if in_sugg_method_implementation { - return false; - } - - // `.unwrap_or(vec![])` is as readable as `.unwrap_or_default()`. And if the expression is a - // non-empty `Vec`, then it will not be a default value anyway. Bail out in all cases. - if call_expr.and_then(|call_expr| VecArgs::hir(cx, call_expr)).is_some() { - return false; - } - - // needs to target Default::default in particular or be *::new and have a Default impl - // available - if (is_new(fun) && output_type_implements_default(fun)) - || match call_expr { - Some(call_expr) => is_default_equivalent(cx, call_expr), - None => is_default_equivalent_call(cx, fun, None) || closure_body_returns_empty_to_string(cx, fun), - } - { - span_lint_and_sugg( - cx, - UNWRAP_OR_DEFAULT, - method_span.with_hi(span.hi()), - format!("use of `{name}` to construct default value"), - "try", - format!("{sugg}()"), - Applicability::MachineApplicable, - ); - - true - } else { - false - } - } - - /// Checks for `*or(foo())`. - #[expect(clippy::too_many_arguments)] - fn check_or_fn_call<'tcx>( - cx: &LateContext<'tcx>, - name: Symbol, - method_span: Span, - self_expr: &hir::Expr<'_>, - arg: &'tcx hir::Expr<'_>, - // `Some` if fn has second argument - second_arg: Option<&hir::Expr<'_>>, - span: Span, - // None if lambda is required - fun_span: Option, - ) -> bool { - // (path, fn_has_argument, methods, suffix) - const KNOW_TYPES: [(Symbol, bool, &[Symbol], &str); 7] = [ - (sym::BTreeEntry, false, &[sym::or_insert], "with"), - (sym::HashMapEntry, false, &[sym::or_insert], "with"), - ( - sym::Option, - false, - &[sym::map_or, sym::ok_or, sym::or, sym::unwrap_or], - "else", - ), - (sym::Option, false, &[sym::get_or_insert], "with"), - (sym::Option, true, &[sym::and], "then"), - (sym::Result, true, &[sym::map_or, sym::or, sym::unwrap_or], "else"), - (sym::Result, true, &[sym::and], "then"), - ]; - - if KNOW_TYPES.iter().any(|k| k.2.contains(&name)) - && switch_to_lazy_eval(cx, arg) - && !contains_return(arg) - && let self_ty = cx.typeck_results().expr_ty(self_expr) - && let Some(&(_, fn_has_arguments, _, suffix)) = KNOW_TYPES - .iter() - .find(|&&i| is_type_diagnostic_item(cx, self_ty, i.0) && i.2.contains(&name)) - { - let ctxt = span.ctxt(); - let mut app = Applicability::HasPlaceholders; - let sugg = { - let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) { - (false, Some(fun_span)) => (fun_span, false), - _ => (arg.span, true), - }; - - let snip = snippet_with_context(cx, snippet_span, ctxt, "..", &mut app).0; - let snip = if use_lambda { - let l_arg = if fn_has_arguments { "_" } else { "" }; - format!("|{l_arg}| {snip}") - } else { - snip.into_owned() - }; - - if let Some(f) = second_arg { - let f = snippet_with_context(cx, f.span, ctxt, "..", &mut app).0; - format!("{snip}, {f}") - } else { - snip - } - }; - let span_replace_word = method_span.with_hi(span.hi()); - span_lint_and_sugg( - cx, - OR_FUN_CALL, - span_replace_word, - format!("function call inside of `{name}`"), - "try", - format!("{name}_{suffix}({sugg})"), - app, - ); - true - } else { - false - } - } - if let [arg] = args { let inner_arg = peel_blocks(arg); for_each_expr(cx, inner_arg, |ex| { @@ -272,6 +93,183 @@ pub(super) fn check<'tcx>( } } +/// Checks for `unwrap_or(T::new())`, `unwrap_or(T::default())`, +/// `or_insert(T::new())` or `or_insert(T::default())`. +/// Similarly checks for `unwrap_or_else(T::new)`, `unwrap_or_else(T::default)`, +/// `or_insert_with(T::new)` or `or_insert_with(T::default)`. +fn check_unwrap_or_default( + cx: &LateContext<'_>, + name: Symbol, + receiver: &hir::Expr<'_>, + fun: &hir::Expr<'_>, + call_expr: Option<&hir::Expr<'_>>, + span: Span, + method_span: Span, +) -> bool { + if !expr_type_is_certain(cx, receiver) { + return false; + } + + let is_new = |fun: &hir::Expr<'_>| { + if let hir::ExprKind::Path(ref qpath) = fun.kind { + let path = last_path_segment(qpath).ident.name; + matches!(path, sym::new) + } else { + false + } + }; + + let output_type_implements_default = |fun| { + let fun_ty = cx.typeck_results().expr_ty(fun); + if let ty::FnDef(def_id, args) = fun_ty.kind() { + let output_ty = cx.tcx.fn_sig(def_id).instantiate(cx.tcx, args).skip_binder().output(); + cx.tcx + .get_diagnostic_item(sym::Default) + .is_some_and(|default_trait_id| implements_trait(cx, output_ty, default_trait_id, &[])) + } else { + false + } + }; + + let sugg = match (name, call_expr.is_some()) { + (sym::unwrap_or, true) | (sym::unwrap_or_else, false) => sym::unwrap_or_default, + (sym::or_insert, true) | (sym::or_insert_with, false) => sym::or_default, + _ => return false, + }; + + let receiver_ty = cx.typeck_results().expr_ty_adjusted(receiver).peel_refs(); + let Some(suggested_method_def_id) = receiver_ty.ty_adt_def().and_then(|adt_def| { + cx.tcx + .inherent_impls(adt_def.did()) + .iter() + .flat_map(|impl_id| cx.tcx.associated_items(impl_id).filter_by_name_unhygienic(sugg)) + .find_map(|assoc| { + if assoc.is_method() && cx.tcx.fn_sig(assoc.def_id).skip_binder().inputs().skip_binder().len() == 1 { + Some(assoc.def_id) + } else { + None + } + }) + }) else { + return false; + }; + let in_sugg_method_implementation = { + matches!( + suggested_method_def_id.as_local(), + Some(local_def_id) if local_def_id == cx.tcx.hir_get_parent_item(receiver.hir_id).def_id + ) + }; + if in_sugg_method_implementation { + return false; + } + + // `.unwrap_or(vec![])` is as readable as `.unwrap_or_default()`. And if the expression is a + // non-empty `Vec`, then it will not be a default value anyway. Bail out in all cases. + if call_expr.and_then(|call_expr| VecArgs::hir(cx, call_expr)).is_some() { + return false; + } + + // needs to target Default::default in particular or be *::new and have a Default impl + // available + if (is_new(fun) && output_type_implements_default(fun)) + || match call_expr { + Some(call_expr) => is_default_equivalent(cx, call_expr), + None => is_default_equivalent_call(cx, fun, None) || closure_body_returns_empty_to_string(cx, fun), + } + { + span_lint_and_sugg( + cx, + UNWRAP_OR_DEFAULT, + method_span.with_hi(span.hi()), + format!("use of `{name}` to construct default value"), + "try", + format!("{sugg}()"), + Applicability::MachineApplicable, + ); + + true + } else { + false + } +} + +/// Checks for `*or(foo())`. +#[expect(clippy::too_many_arguments)] +fn check_or_fn_call<'tcx>( + cx: &LateContext<'tcx>, + name: Symbol, + method_span: Span, + self_expr: &hir::Expr<'_>, + arg: &'tcx hir::Expr<'_>, + // `Some` if fn has second argument + second_arg: Option<&hir::Expr<'_>>, + span: Span, + // None if lambda is required + fun_span: Option, +) -> bool { + // (path, fn_has_argument, methods, suffix) + const KNOW_TYPES: [(Symbol, bool, &[Symbol], &str); 7] = [ + (sym::BTreeEntry, false, &[sym::or_insert], "with"), + (sym::HashMapEntry, false, &[sym::or_insert], "with"), + ( + sym::Option, + false, + &[sym::map_or, sym::ok_or, sym::or, sym::unwrap_or], + "else", + ), + (sym::Option, false, &[sym::get_or_insert], "with"), + (sym::Option, true, &[sym::and], "then"), + (sym::Result, true, &[sym::map_or, sym::or, sym::unwrap_or], "else"), + (sym::Result, true, &[sym::and], "then"), + ]; + + if KNOW_TYPES.iter().any(|k| k.2.contains(&name)) + && switch_to_lazy_eval(cx, arg) + && !contains_return(arg) + && let self_ty = cx.typeck_results().expr_ty(self_expr) + && let Some(&(_, fn_has_arguments, _, suffix)) = KNOW_TYPES + .iter() + .find(|&&i| is_type_diagnostic_item(cx, self_ty, i.0) && i.2.contains(&name)) + { + let ctxt = span.ctxt(); + let mut app = Applicability::HasPlaceholders; + let sugg = { + let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) { + (false, Some(fun_span)) => (fun_span, false), + _ => (arg.span, true), + }; + + let snip = snippet_with_context(cx, snippet_span, ctxt, "..", &mut app).0; + let snip = if use_lambda { + let l_arg = if fn_has_arguments { "_" } else { "" }; + format!("|{l_arg}| {snip}") + } else { + snip.into_owned() + }; + + if let Some(f) = second_arg { + let f = snippet_with_context(cx, f.span, ctxt, "..", &mut app).0; + format!("{snip}, {f}") + } else { + snip + } + }; + let span_replace_word = method_span.with_hi(span.hi()); + span_lint_and_sugg( + cx, + OR_FUN_CALL, + span_replace_word, + format!("function call inside of `{name}`"), + "try", + format!("{name}_{suffix}({sugg})"), + app, + ); + true + } else { + false + } +} + fn closure_body_returns_empty_to_string(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> bool { if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = e.kind { let body = cx.tcx.hir_body(body); diff --git a/tests/ui/or_fun_call.fixed b/tests/ui/or_fun_call.fixed index 7a0be97017ebc..f42e637328a11 100644 --- a/tests/ui/or_fun_call.fixed +++ b/tests/ui/or_fun_call.fixed @@ -1,5 +1,4 @@ #![warn(clippy::or_fun_call)] -#![allow(dead_code)] #![allow( clippy::borrow_as_ptr, clippy::uninlined_format_args, diff --git a/tests/ui/or_fun_call.rs b/tests/ui/or_fun_call.rs index 724af606de9cf..7d2bd32ca0b3b 100644 --- a/tests/ui/or_fun_call.rs +++ b/tests/ui/or_fun_call.rs @@ -1,5 +1,4 @@ #![warn(clippy::or_fun_call)] -#![allow(dead_code)] #![allow( clippy::borrow_as_ptr, clippy::uninlined_format_args, diff --git a/tests/ui/or_fun_call.stderr b/tests/ui/or_fun_call.stderr index 40b25f91154dd..551ca8f3b8016 100644 --- a/tests/ui/or_fun_call.stderr +++ b/tests/ui/or_fun_call.stderr @@ -1,5 +1,5 @@ error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:53:22 + --> tests/ui/or_fun_call.rs:52:22 | LL | with_constructor.unwrap_or(make()); | ^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(make)` @@ -8,7 +8,7 @@ LL | with_constructor.unwrap_or(make()); = help: to override `-D warnings` add `#[allow(clippy::or_fun_call)]` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:57:14 + --> tests/ui/or_fun_call.rs:56:14 | LL | with_new.unwrap_or(Vec::new()); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` @@ -17,205 +17,205 @@ LL | with_new.unwrap_or(Vec::new()); = help: to override `-D warnings` add `#[allow(clippy::unwrap_or_default)]` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:61:21 + --> tests/ui/or_fun_call.rs:60:21 | LL | with_const_args.unwrap_or(Vec::with_capacity(12)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| Vec::with_capacity(12))` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:65:14 + --> tests/ui/or_fun_call.rs:64:14 | LL | with_err.unwrap_or(make()); | ^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|_| make())` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:69:19 + --> tests/ui/or_fun_call.rs:68:19 | LL | with_err_args.unwrap_or(Vec::with_capacity(12)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|_| Vec::with_capacity(12))` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:73:24 + --> tests/ui/or_fun_call.rs:72:24 | LL | with_default_trait.unwrap_or(Default::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:77:23 + --> tests/ui/or_fun_call.rs:76:23 | LL | with_default_type.unwrap_or(u64::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:97:18 + --> tests/ui/or_fun_call.rs:96:18 | LL | self_default.unwrap_or(::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(::default)` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:101:18 + --> tests/ui/or_fun_call.rs:100:18 | LL | real_default.unwrap_or(::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:105:14 + --> tests/ui/or_fun_call.rs:104:14 | LL | with_vec.unwrap_or(Vec::new()); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:109:21 + --> tests/ui/or_fun_call.rs:108:21 | LL | without_default.unwrap_or(Foo::new()); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(Foo::new)` error: use of `or_insert` to construct default value - --> tests/ui/or_fun_call.rs:113:19 + --> tests/ui/or_fun_call.rs:112:19 | LL | map.entry(42).or_insert(String::new()); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `or_insert` to construct default value - --> tests/ui/or_fun_call.rs:117:23 + --> tests/ui/or_fun_call.rs:116:23 | LL | map_vec.entry(42).or_insert(Vec::new()); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `or_insert` to construct default value - --> tests/ui/or_fun_call.rs:121:21 + --> tests/ui/or_fun_call.rs:120:21 | LL | btree.entry(42).or_insert(String::new()); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `or_insert` to construct default value - --> tests/ui/or_fun_call.rs:125:25 + --> tests/ui/or_fun_call.rs:124:25 | LL | btree_vec.entry(42).or_insert(Vec::new()); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:129:21 + --> tests/ui/or_fun_call.rs:128:21 | LL | let _ = stringy.unwrap_or(String::new()); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: function call inside of `ok_or` - --> tests/ui/or_fun_call.rs:134:17 + --> tests/ui/or_fun_call.rs:133:17 | LL | let _ = opt.ok_or(format!("{} world.", hello)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ok_or_else(|| format!("{} world.", hello))` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:139:21 + --> tests/ui/or_fun_call.rs:138:21 | LL | let _ = Some(1).unwrap_or(map[&1]); | ^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| map[&1])` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:142:21 + --> tests/ui/or_fun_call.rs:141:21 | LL | let _ = Some(1).unwrap_or(map[&1]); | ^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| map[&1])` error: function call inside of `or` - --> tests/ui/or_fun_call.rs:167:35 + --> tests/ui/or_fun_call.rs:166:35 | LL | let _ = Some("a".to_string()).or(Some("b".to_string())); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_else(|| Some("b".to_string()))` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:210:18 + --> tests/ui/or_fun_call.rs:209:18 | LL | None.unwrap_or(ptr_to_ref(s)); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| ptr_to_ref(s))` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:218:14 + --> tests/ui/or_fun_call.rs:217:14 | LL | None.unwrap_or(unsafe { ptr_to_ref(s) }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| unsafe { ptr_to_ref(s) })` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:221:14 + --> tests/ui/or_fun_call.rs:220:14 | LL | None.unwrap_or( unsafe { ptr_to_ref(s) } ); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| unsafe { ptr_to_ref(s) })` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:297:25 + --> tests/ui/or_fun_call.rs:296:25 | LL | let _ = Some(4).map_or(g(), |v| v); | ^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(g, |v| v)` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:299:25 + --> tests/ui/or_fun_call.rs:298:25 | LL | let _ = Some(4).map_or(g(), f); | ^^^^^^^^^^^^^^ help: try: `map_or_else(g, f)` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:302:25 + --> tests/ui/or_fun_call.rs:301:25 | LL | let _ = Some(4).map_or("asd".to_string().len() as i32, f); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(|| "asd".to_string().len() as i32, f)` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:333:18 + --> tests/ui/or_fun_call.rs:332:18 | LL | with_new.unwrap_or_else(Vec::new); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:337:28 + --> tests/ui/or_fun_call.rs:336:28 | LL | with_default_trait.unwrap_or_else(Default::default); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:341:27 + --> tests/ui/or_fun_call.rs:340:27 | LL | with_default_type.unwrap_or_else(u64::default); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:345:22 + --> tests/ui/or_fun_call.rs:344:22 | LL | real_default.unwrap_or_else(::default); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: use of `or_insert_with` to construct default value - --> tests/ui/or_fun_call.rs:349:23 + --> tests/ui/or_fun_call.rs:348:23 | LL | map.entry(42).or_insert_with(String::new); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `or_insert_with` to construct default value - --> tests/ui/or_fun_call.rs:353:25 + --> tests/ui/or_fun_call.rs:352:25 | LL | btree.entry(42).or_insert_with(String::new); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` error: use of `unwrap_or_else` to construct default value - --> tests/ui/or_fun_call.rs:357:25 + --> tests/ui/or_fun_call.rs:356:25 | LL | let _ = stringy.unwrap_or_else(String::new); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:399:17 + --> tests/ui/or_fun_call.rs:398:17 | LL | let _ = opt.unwrap_or({ f() }); // suggest `.unwrap_or_else(f)` | ^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(f)` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:404:17 + --> tests/ui/or_fun_call.rs:403:17 | LL | let _ = opt.unwrap_or(f() + 1); // suggest `.unwrap_or_else(|| f() + 1)` | ^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| f() + 1)` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:409:17 + --> tests/ui/or_fun_call.rs:408:17 | LL | let _ = opt.unwrap_or({ | _________________^ @@ -235,55 +235,55 @@ LL ~ }); | error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:415:17 + --> tests/ui/or_fun_call.rs:414:17 | LL | let _ = opt.map_or(f() + 1, |v| v); // suggest `.map_or_else(|| f() + 1, |v| v)` | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(|| f() + 1, |v| v)` error: use of `unwrap_or` to construct default value - --> tests/ui/or_fun_call.rs:420:17 + --> tests/ui/or_fun_call.rs:419:17 | LL | let _ = opt.unwrap_or({ i32::default() }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` error: function call inside of `unwrap_or` - --> tests/ui/or_fun_call.rs:427:21 + --> tests/ui/or_fun_call.rs:426:21 | LL | let _ = opt_foo.unwrap_or(Foo { val: String::default() }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| Foo { val: String::default() })` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:442:19 + --> tests/ui/or_fun_call.rs:441:19 | LL | let _ = x.map_or(g(), |v| v); | ^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(|_| g(), |v| v)` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:444:19 + --> tests/ui/or_fun_call.rs:443:19 | LL | let _ = x.map_or(g(), f); | ^^^^^^^^^^^^^^ help: try: `map_or_else(|_| g(), f)` error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:447:19 + --> tests/ui/or_fun_call.rs:446:19 | LL | let _ = x.map_or("asd".to_string().len() as i32, f); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map_or_else(|_| "asd".to_string().len() as i32, f)` error: function call inside of `get_or_insert` - --> tests/ui/or_fun_call.rs:458:15 + --> tests/ui/or_fun_call.rs:457:15 | LL | let _ = x.get_or_insert(g()); | ^^^^^^^^^^^^^^^^^^ help: try: `get_or_insert_with(g)` error: function call inside of `and` - --> tests/ui/or_fun_call.rs:468:15 + --> tests/ui/or_fun_call.rs:467:15 | LL | let _ = x.and(g()); | ^^^^^^^^ help: try: `and_then(|_| g())` error: function call inside of `and` - --> tests/ui/or_fun_call.rs:478:15 + --> tests/ui/or_fun_call.rs:477:15 | LL | let _ = x.and(g()); | ^^^^^^^^ help: try: `and_then(|_| g())` From 7449d63ab7dc12de7e311f1bf56de51051bb68dc Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 24 Sep 2025 09:40:45 +0200 Subject: [PATCH 37/93] fix(or_fun_call): respect MSRV for `Result::unwrap_or_default` suggestion --- book/src/lint_configuration.md | 1 + clippy_config/src/conf.rs | 1 + clippy_lints/src/methods/mod.rs | 2 +- clippy_lints/src/methods/or_fun_call.rs | 16 ++++++++++++--- clippy_utils/src/msrvs.rs | 2 +- tests/ui/or_fun_call.fixed | 15 ++++++++++++++ tests/ui/or_fun_call.rs | 15 ++++++++++++++ tests/ui/or_fun_call.stderr | 26 ++++++++++++++++++++++++- 8 files changed, 72 insertions(+), 6 deletions(-) diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index c2d080cd96a1d..f8263bb8852ab 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -883,6 +883,7 @@ 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) +* [`or_fun_call`](https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call) * [`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) diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 2f28f6175ad82..e14e15a022f71 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -779,6 +779,7 @@ define_Conf! { needless_borrow, non_std_lazy_statics, option_as_ref_deref, + or_fun_call, ptr_as_ptr, question_mark, redundant_field_names, diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index be1fcf136e92d..c18f20c17e2e2 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -4854,7 +4854,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { }, ExprKind::MethodCall(method_call, receiver, args, _) => { let method_span = method_call.ident.span; - or_fun_call::check(cx, expr, method_span, method_call.ident.name, receiver, args); + or_fun_call::check(cx, expr, method_span, method_call.ident.name, receiver, args, self.msrv); expect_fun_call::check( cx, &self.format_args, diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index 1433c5c1307b2..04e4503e4097f 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -3,6 +3,7 @@ use std::ops::ControlFlow; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::eager_or_lazy::switch_to_lazy_eval; use clippy_utils::higher::VecArgs; +use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{expr_type_is_certain, implements_trait, is_type_diagnostic_item}; use clippy_utils::visitors::for_each_expr; @@ -25,6 +26,7 @@ pub(super) fn check<'tcx>( name: Symbol, receiver: &'tcx hir::Expr<'_>, args: &'tcx [hir::Expr<'_>], + msrv: Msrv, ) { if let [arg] = args { let inner_arg = peel_blocks(arg); @@ -45,11 +47,11 @@ pub(super) fn check<'tcx>( }; (!inner_fun_has_args && !is_nested_expr - && check_unwrap_or_default(cx, name, receiver, fun, Some(ex), expr.span, method_span)) + && check_unwrap_or_default(cx, name, receiver, fun, Some(ex), expr.span, method_span, msrv)) || check_or_fn_call(cx, name, method_span, receiver, arg, None, expr.span, fun_span) }, hir::ExprKind::Path(..) | hir::ExprKind::Closure(..) if !is_nested_expr => { - check_unwrap_or_default(cx, name, receiver, ex, None, expr.span, method_span) + check_unwrap_or_default(cx, name, receiver, ex, None, expr.span, method_span, msrv) }, hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => { check_or_fn_call(cx, name, method_span, receiver, arg, None, expr.span, None) @@ -97,6 +99,7 @@ pub(super) fn check<'tcx>( /// `or_insert(T::new())` or `or_insert(T::default())`. /// Similarly checks for `unwrap_or_else(T::new)`, `unwrap_or_else(T::default)`, /// `or_insert_with(T::new)` or `or_insert_with(T::default)`. +#[expect(clippy::too_many_arguments)] fn check_unwrap_or_default( cx: &LateContext<'_>, name: Symbol, @@ -105,7 +108,15 @@ fn check_unwrap_or_default( call_expr: Option<&hir::Expr<'_>>, span: Span, method_span: Span, + msrv: Msrv, ) -> bool { + let receiver_ty = cx.typeck_results().expr_ty_adjusted(receiver).peel_refs(); + + // Check MSRV, but only for `Result::unwrap_or_default` + if is_type_diagnostic_item(cx, receiver_ty, sym::Result) && !msrv.meets(cx, msrvs::RESULT_UNWRAP_OR_DEFAULT) { + return false; + } + if !expr_type_is_certain(cx, receiver) { return false; } @@ -137,7 +148,6 @@ fn check_unwrap_or_default( _ => return false, }; - let receiver_ty = cx.typeck_results().expr_ty_adjusted(receiver).peel_refs(); let Some(suggested_method_def_id) = receiver_ty.ty_adt_def().and_then(|adt_def| { cx.tcx .inherent_impls(adt_def.did()) diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index 6e07ed9ffcc4d..3cccce3975449 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -77,7 +77,7 @@ msrv_aliases! { 1,24,0 { IS_ASCII_DIGIT, PTR_NULL } 1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN } 1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR } - 1,16,0 { STR_REPEAT } + 1,16,0 { STR_REPEAT, RESULT_UNWRAP_OR_DEFAULT } 1,15,0 { MAYBE_BOUND_IN_WHERE } 1,13,0 { QUESTION_MARK_OPERATOR } } diff --git a/tests/ui/or_fun_call.fixed b/tests/ui/or_fun_call.fixed index f42e637328a11..386351aa39f51 100644 --- a/tests/ui/or_fun_call.fixed +++ b/tests/ui/or_fun_call.fixed @@ -478,4 +478,19 @@ fn test_result_and() { //~^ or_fun_call } +#[clippy::msrv = "1.15"] +fn below_msrv(opt: Option, res: Result) { + let _ = opt.unwrap_or_default(); + //~^ unwrap_or_default + let _ = res.unwrap_or_else(|_| Default::default()); + //~^ or_fun_call +} + +#[clippy::msrv = "1.16"] +fn above_msrv(opt: Option, res: Result) { + let _ = opt.unwrap_or_default(); + //~^ unwrap_or_default + let _ = res.unwrap_or_default(); + //~^ unwrap_or_default +} fn main() {} diff --git a/tests/ui/or_fun_call.rs b/tests/ui/or_fun_call.rs index 7d2bd32ca0b3b..e27f9aa65c37a 100644 --- a/tests/ui/or_fun_call.rs +++ b/tests/ui/or_fun_call.rs @@ -478,4 +478,19 @@ fn test_result_and() { //~^ or_fun_call } +#[clippy::msrv = "1.15"] +fn below_msrv(opt: Option, res: Result) { + let _ = opt.unwrap_or(Default::default()); + //~^ unwrap_or_default + let _ = res.unwrap_or(Default::default()); + //~^ or_fun_call +} + +#[clippy::msrv = "1.16"] +fn above_msrv(opt: Option, res: Result) { + let _ = opt.unwrap_or(Default::default()); + //~^ unwrap_or_default + let _ = res.unwrap_or(Default::default()); + //~^ unwrap_or_default +} fn main() {} diff --git a/tests/ui/or_fun_call.stderr b/tests/ui/or_fun_call.stderr index 551ca8f3b8016..6bce06ab20eb6 100644 --- a/tests/ui/or_fun_call.stderr +++ b/tests/ui/or_fun_call.stderr @@ -288,5 +288,29 @@ error: function call inside of `and` LL | let _ = x.and(g()); | ^^^^^^^^ help: try: `and_then(|_| g())` -error: aborting due to 45 previous errors +error: use of `unwrap_or` to construct default value + --> tests/ui/or_fun_call.rs:483:17 + | +LL | let _ = opt.unwrap_or(Default::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: function call inside of `unwrap_or` + --> tests/ui/or_fun_call.rs:485:17 + | +LL | let _ = res.unwrap_or(Default::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|_| Default::default())` + +error: use of `unwrap_or` to construct default value + --> tests/ui/or_fun_call.rs:491:17 + | +LL | let _ = opt.unwrap_or(Default::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: use of `unwrap_or` to construct default value + --> tests/ui/or_fun_call.rs:493:17 + | +LL | let _ = res.unwrap_or(Default::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` + +error: aborting due to 49 previous errors From 9cb82d7ac849adf1bffb2d7e30f114db8d9a9208 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 21 Sep 2025 23:22:31 +0200 Subject: [PATCH 38/93] fix(unnecessary_mut_passed): retain parens around the arguments --- clippy_lints/src/unnecessary_mut_passed.rs | 33 +++- tests/ui/unnecessary_mut_passed.fixed | 20 +++ tests/ui/unnecessary_mut_passed.rs | 20 +++ tests/ui/unnecessary_mut_passed.stderr | 193 ++++++++++++++++++--- 4 files changed, 233 insertions(+), 33 deletions(-) diff --git a/clippy_lints/src/unnecessary_mut_passed.rs b/clippy_lints/src/unnecessary_mut_passed.rs index ec93ef97cfaf7..eb2d7639e91f7 100644 --- a/clippy_lints/src/unnecessary_mut_passed.rs +++ b/clippy_lints/src/unnecessary_mut_passed.rs @@ -1,5 +1,5 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::sugg::Sugg; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::SpanRangeExt; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability}; use rustc_lint::{LateContext, LateLintPass}; @@ -87,16 +87,33 @@ fn check_arguments<'tcx>( if let ty::Ref(_, _, Mutability::Not) | ty::RawPtr(_, Mutability::Not) = parameter.kind() && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, arg) = argument.kind { - let mut applicability = Applicability::MachineApplicable; - let sugg = Sugg::hir_with_applicability(cx, arg, "_", &mut applicability).addr(); - span_lint_and_sugg( + let applicability = Applicability::MachineApplicable; + + let span_to_remove = { + let span_until_arg = argument.span.until(arg.span); + if let Some(Some(ref_pos)) = span_until_arg.with_source_text(cx, |src| { + src + // we don't use `strip_prefix` here, because `argument` might be enclosed in parens, in + // which case `&` is no longer the prefix + .find('&') + // just a sanity check, in case some proc-macro messes up the spans + .filter(|ref_pos| src[*ref_pos..].contains("mut")) + }) && let Ok(lo) = u32::try_from(ref_pos + '&'.len_utf8()) + { + span_until_arg.split_at(lo).1 + } else { + return; + } + }; + + span_lint_and_then( cx, UNNECESSARY_MUT_PASSED, argument.span, format!("the {fn_kind} `{name}` doesn't need a mutable reference"), - "remove this `mut`", - sugg.to_string(), - applicability, + |diag| { + diag.span_suggestion_verbose(span_to_remove, "remove this `mut`", String::new(), applicability); + }, ); } } diff --git a/tests/ui/unnecessary_mut_passed.fixed b/tests/ui/unnecessary_mut_passed.fixed index 03d854099e644..63bbadb01dcb2 100644 --- a/tests/ui/unnecessary_mut_passed.fixed +++ b/tests/ui/unnecessary_mut_passed.fixed @@ -40,6 +40,7 @@ mod issue11268 { struct MyStruct; impl MyStruct { + fn takes_nothing(&self) {} fn takes_ref(&self, a: &i32) {} fn takes_refmut(&self, a: &mut i32) {} fn takes_ref_ref(&self, a: &&i32) {} @@ -168,3 +169,22 @@ fn raw_ptrs(my_struct: MyStruct) { my_struct.takes_raw_mut(&raw mut n); my_struct.takes_raw_const(a); } + +#[expect(clippy::needless_borrow)] +fn issue15722(mut my_struct: MyStruct) { + (&my_struct).takes_nothing(); + //~^ unnecessary_mut_passed + (&my_struct).takes_nothing(); + + // Only put parens around the argument that used to have it + (&my_struct).takes_ref(&42); + //~^ unnecessary_mut_passed + //~| unnecessary_mut_passed + #[expect(clippy::double_parens)] + (&my_struct).takes_ref((&42)); + //~^ unnecessary_mut_passed + //~| unnecessary_mut_passed + #[expect(clippy::double_parens)] + my_struct.takes_ref((&42)); + //~^ unnecessary_mut_passed +} diff --git a/tests/ui/unnecessary_mut_passed.rs b/tests/ui/unnecessary_mut_passed.rs index 80e3f50692770..b719ca1871b29 100644 --- a/tests/ui/unnecessary_mut_passed.rs +++ b/tests/ui/unnecessary_mut_passed.rs @@ -40,6 +40,7 @@ mod issue11268 { struct MyStruct; impl MyStruct { + fn takes_nothing(&self) {} fn takes_ref(&self, a: &i32) {} fn takes_refmut(&self, a: &mut i32) {} fn takes_ref_ref(&self, a: &&i32) {} @@ -168,3 +169,22 @@ fn raw_ptrs(my_struct: MyStruct) { my_struct.takes_raw_mut(&raw mut n); my_struct.takes_raw_const(a); } + +#[expect(clippy::needless_borrow)] +fn issue15722(mut my_struct: MyStruct) { + (&mut my_struct).takes_nothing(); + //~^ unnecessary_mut_passed + (&my_struct).takes_nothing(); + + // Only put parens around the argument that used to have it + (&mut my_struct).takes_ref(&mut 42); + //~^ unnecessary_mut_passed + //~| unnecessary_mut_passed + #[expect(clippy::double_parens)] + (&mut my_struct).takes_ref((&mut 42)); + //~^ unnecessary_mut_passed + //~| unnecessary_mut_passed + #[expect(clippy::double_parens)] + my_struct.takes_ref((&mut 42)); + //~^ unnecessary_mut_passed +} diff --git a/tests/ui/unnecessary_mut_passed.stderr b/tests/ui/unnecessary_mut_passed.stderr index c69a637bf4088..ace11027e3e25 100644 --- a/tests/ui/unnecessary_mut_passed.stderr +++ b/tests/ui/unnecessary_mut_passed.stderr @@ -1,77 +1,220 @@ error: the function `takes_ref` doesn't need a mutable reference - --> tests/ui/unnecessary_mut_passed.rs:56:15 + --> tests/ui/unnecessary_mut_passed.rs:57:15 | 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)]` +help: remove this `mut` + | +LL - takes_ref(&mut 42); +LL + takes_ref(&42); + | error: the function `takes_ref_ref` doesn't need a mutable reference - --> tests/ui/unnecessary_mut_passed.rs:58:19 + --> tests/ui/unnecessary_mut_passed.rs:59:19 | LL | takes_ref_ref(&mut &42); - | ^^^^^^^^ help: remove this `mut`: `&&42` + | ^^^^^^^^ + | +help: remove this `mut` + | +LL - takes_ref_ref(&mut &42); +LL + takes_ref_ref(&&42); + | error: the function `takes_ref_refmut` doesn't need a mutable reference - --> tests/ui/unnecessary_mut_passed.rs:60:22 + --> tests/ui/unnecessary_mut_passed.rs:61:22 | LL | takes_ref_refmut(&mut &mut 42); - | ^^^^^^^^^^^^ help: remove this `mut`: `&&mut 42` + | ^^^^^^^^^^^^ + | +help: remove this `mut` + | +LL - takes_ref_refmut(&mut &mut 42); +LL + takes_ref_refmut(&&mut 42); + | error: the function `takes_raw_const` doesn't need a mutable reference - --> tests/ui/unnecessary_mut_passed.rs:62:21 + --> tests/ui/unnecessary_mut_passed.rs:63:21 | LL | takes_raw_const(&mut 42); - | ^^^^^^^ help: remove this `mut`: `&42` + | ^^^^^^^ + | +help: remove this `mut` + | +LL - takes_raw_const(&mut 42); +LL + takes_raw_const(&42); + | error: the function `as_ptr` doesn't need a mutable reference - --> tests/ui/unnecessary_mut_passed.rs:66:12 + --> tests/ui/unnecessary_mut_passed.rs:67:12 | LL | as_ptr(&mut 42); - | ^^^^^^^ help: remove this `mut`: `&42` + | ^^^^^^^ + | +help: remove this `mut` + | +LL - as_ptr(&mut 42); +LL + as_ptr(&42); + | error: the function `as_ptr` doesn't need a mutable reference - --> tests/ui/unnecessary_mut_passed.rs:69:12 + --> tests/ui/unnecessary_mut_passed.rs:70:12 | LL | as_ptr(&mut &42); - | ^^^^^^^^ help: remove this `mut`: `&&42` + | ^^^^^^^^ + | +help: remove this `mut` + | +LL - as_ptr(&mut &42); +LL + as_ptr(&&42); + | error: the function `as_ptr` doesn't need a mutable reference - --> tests/ui/unnecessary_mut_passed.rs:72:12 + --> tests/ui/unnecessary_mut_passed.rs:73:12 | LL | as_ptr(&mut &mut 42); - | ^^^^^^^^^^^^ help: remove this `mut`: `&&mut 42` + | ^^^^^^^^^^^^ + | +help: remove this `mut` + | +LL - as_ptr(&mut &mut 42); +LL + as_ptr(&&mut 42); + | error: the function `as_ptr` doesn't need a mutable reference - --> tests/ui/unnecessary_mut_passed.rs:75:12 + --> tests/ui/unnecessary_mut_passed.rs:76:12 | LL | as_ptr(&mut 42); - | ^^^^^^^ help: remove this `mut`: `&42` + | ^^^^^^^ + | +help: remove this `mut` + | +LL - as_ptr(&mut 42); +LL + as_ptr(&42); + | error: the method `takes_ref` doesn't need a mutable reference - --> tests/ui/unnecessary_mut_passed.rs:80:25 + --> tests/ui/unnecessary_mut_passed.rs:81:25 | LL | my_struct.takes_ref(&mut 42); - | ^^^^^^^ help: remove this `mut`: `&42` + | ^^^^^^^ + | +help: remove this `mut` + | +LL - my_struct.takes_ref(&mut 42); +LL + my_struct.takes_ref(&42); + | error: the method `takes_ref_ref` doesn't need a mutable reference - --> tests/ui/unnecessary_mut_passed.rs:82:29 + --> tests/ui/unnecessary_mut_passed.rs:83:29 | LL | my_struct.takes_ref_ref(&mut &42); - | ^^^^^^^^ help: remove this `mut`: `&&42` + | ^^^^^^^^ + | +help: remove this `mut` + | +LL - my_struct.takes_ref_ref(&mut &42); +LL + my_struct.takes_ref_ref(&&42); + | error: the method `takes_ref_refmut` doesn't need a mutable reference - --> tests/ui/unnecessary_mut_passed.rs:84:32 + --> tests/ui/unnecessary_mut_passed.rs:85:32 | LL | my_struct.takes_ref_refmut(&mut &mut 42); - | ^^^^^^^^^^^^ help: remove this `mut`: `&&mut 42` + | ^^^^^^^^^^^^ + | +help: remove this `mut` + | +LL - my_struct.takes_ref_refmut(&mut &mut 42); +LL + my_struct.takes_ref_refmut(&&mut 42); + | error: the method `takes_raw_const` doesn't need a mutable reference - --> tests/ui/unnecessary_mut_passed.rs:86:31 + --> tests/ui/unnecessary_mut_passed.rs:87:31 | LL | my_struct.takes_raw_const(&mut 42); - | ^^^^^^^ help: remove this `mut`: `&42` + | ^^^^^^^ + | +help: remove this `mut` + | +LL - my_struct.takes_raw_const(&mut 42); +LL + my_struct.takes_raw_const(&42); + | + +error: the method `takes_nothing` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:175:5 + | +LL | (&mut my_struct).takes_nothing(); + | ^^^^^^^^^^^^^^^^ + | +help: remove this `mut` + | +LL - (&mut my_struct).takes_nothing(); +LL + (&my_struct).takes_nothing(); + | + +error: the method `takes_ref` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:180:5 + | +LL | (&mut my_struct).takes_ref(&mut 42); + | ^^^^^^^^^^^^^^^^ + | +help: remove this `mut` + | +LL - (&mut my_struct).takes_ref(&mut 42); +LL + (&my_struct).takes_ref(&mut 42); + | + +error: the method `takes_ref` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:180:32 + | +LL | (&mut my_struct).takes_ref(&mut 42); + | ^^^^^^^ + | +help: remove this `mut` + | +LL - (&mut my_struct).takes_ref(&mut 42); +LL + (&mut my_struct).takes_ref(&42); + | + +error: the method `takes_ref` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:184:5 + | +LL | (&mut my_struct).takes_ref((&mut 42)); + | ^^^^^^^^^^^^^^^^ + | +help: remove this `mut` + | +LL - (&mut my_struct).takes_ref((&mut 42)); +LL + (&my_struct).takes_ref((&mut 42)); + | + +error: the method `takes_ref` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:184:32 + | +LL | (&mut my_struct).takes_ref((&mut 42)); + | ^^^^^^^^^ + | +help: remove this `mut` + | +LL - (&mut my_struct).takes_ref((&mut 42)); +LL + (&mut my_struct).takes_ref((&42)); + | + +error: the method `takes_ref` doesn't need a mutable reference + --> tests/ui/unnecessary_mut_passed.rs:188:25 + | +LL | my_struct.takes_ref((&mut 42)); + | ^^^^^^^^^ + | +help: remove this `mut` + | +LL - my_struct.takes_ref((&mut 42)); +LL + my_struct.takes_ref((&42)); + | -error: aborting due to 12 previous errors +error: aborting due to 18 previous errors From d63a7ccc7cd9eab72a0ddca84625589d6ba0aec8 Mon Sep 17 00:00:00 2001 From: alexey semenyuk Date: Wed, 24 Sep 2025 23:41:07 +0500 Subject: [PATCH 39/93] Update actions/github-script to v8 --- .github/workflows/lintcheck_summary.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lintcheck_summary.yml b/.github/workflows/lintcheck_summary.yml index 183258a964379..6768cd65701ac 100644 --- a/.github/workflows/lintcheck_summary.yml +++ b/.github/workflows/lintcheck_summary.yml @@ -35,7 +35,7 @@ jobs: github-token: ${{ github.token }} - name: Format comment - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const fs = require("fs"); From 56194654c6b9f2ba5eb077c0e535969f34c12e21 Mon Sep 17 00:00:00 2001 From: Zihan Date: Tue, 23 Sep 2025 17:02:09 -0400 Subject: [PATCH 40/93] `filter_next`: check for `filter().next_back()` changelog: [`filter_next`]: suggest replacing `filter().next_back()` with `rfind()` for `DoubleEndedIterator` Signed-off-by: Zihan --- clippy_lints/src/methods/filter_next.rs | 91 +++++++++++++++---------- clippy_lints/src/methods/mod.rs | 12 +++- clippy_utils/src/msrvs.rs | 2 +- tests/ui/auxiliary/option_helpers.rs | 4 ++ tests/ui/methods.rs | 20 ++++++ tests/ui/methods.stderr | 13 +++- tests/ui/methods_fixable.fixed | 14 ++++ tests/ui/methods_fixable.rs | 14 ++++ tests/ui/methods_fixable.stderr | 14 +++- 9 files changed, 143 insertions(+), 41 deletions(-) diff --git a/clippy_lints/src/methods/filter_next.rs b/clippy_lints/src/methods/filter_next.rs index 72f83b245a0cb..e1a9a79e20eea 100644 --- a/clippy_lints/src/methods/filter_next.rs +++ b/clippy_lints/src/methods/filter_next.rs @@ -10,51 +10,68 @@ use rustc_span::sym; use super::FILTER_NEXT; -/// lint use of `filter().next()` for `Iterators` +#[derive(Copy, Clone)] +pub(super) enum Direction { + Forward, + Backward, +} + +/// lint use of `filter().next()` for `Iterator` and `filter().next_back()` for +/// `DoubleEndedIterator` pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, recv: &'tcx hir::Expr<'_>, filter_arg: &'tcx hir::Expr<'_>, + direction: Direction, ) { - // lint if caller of `.filter().next()` is an Iterator - let recv_impls_iterator = cx + // lint if caller of `.filter().next()` is an Iterator or `.filter().next_back()` is a + // DoubleEndedIterator + let (required_trait, next_method, find_method) = match direction { + Direction::Forward => (sym::Iterator, "next", "find"), + Direction::Backward => (sym::DoubleEndedIterator, "next_back", "rfind"), + }; + if !cx .tcx - .get_diagnostic_item(sym::Iterator) - .is_some_and(|id| implements_trait(cx, cx.typeck_results().expr_ty(recv), id, &[])); - if recv_impls_iterator { - let msg = "called `filter(..).next()` on an `Iterator`. This is more succinctly expressed by calling \ - `.find(..)` instead"; - let filter_snippet = snippet(cx, filter_arg.span, ".."); - if filter_snippet.lines().count() <= 1 { - 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_with_projections(recv) - && let hir::Node::Pat(pat) = cx.tcx.hir_node(id) - && let hir::PatKind::Binding(BindingMode(_, Mutability::Not), _, ident, _) = pat.kind - { - (Applicability::Unspecified, Some((pat.span, ident))) - } else { - (Applicability::MachineApplicable, None) - }; + .get_diagnostic_item(required_trait) + .is_some_and(|id| implements_trait(cx, cx.typeck_results().expr_ty(recv), id, &[])) + { + return; + } + let msg = format!( + "called `filter(..).{next_method}()` on an `{}`. This is more succinctly expressed by calling \ + `.{find_method}(..)` instead", + required_trait.as_str() + ); + let filter_snippet = snippet(cx, filter_arg.span, ".."); + if filter_snippet.lines().count() <= 1 { + 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_with_projections(recv) + && let hir::Node::Pat(pat) = cx.tcx.hir_node(id) + && let hir::PatKind::Binding(BindingMode(_, Mutability::Not), _, ident, _) = pat.kind + { + (Applicability::Unspecified, Some((pat.span, ident))) + } else { + (Applicability::MachineApplicable, None) + }; - diag.span_suggestion( - expr.span, - "try", - format!("{iter_snippet}.find({filter_snippet})"), - applicability, - ); + diag.span_suggestion( + expr.span, + "try", + format!("{iter_snippet}.{find_method}({filter_snippet})"), + applicability, + ); - if let Some((pat_span, ident)) = pat { - diag.span_help( - pat_span, - format!("you will also need to make `{ident}` mutable, because `find` takes `&mut self`"), - ); - } - }); - } else { - span_lint(cx, FILTER_NEXT, expr.span, msg); - } + if let Some((pat_span, ident)) = pat { + diag.span_help( + pat_span, + format!("you will also need to make `{ident}` mutable, because `{find_method}` takes `&mut self`"), + ); + } + }); + } else { + span_lint(cx, FILTER_NEXT, expr.span, msg); } } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 8679689c8ad4d..9b27a6c19e1fb 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -5369,7 +5369,9 @@ impl Methods { iter_overeager_cloned::Op::LaterCloned, false, ), - (sym::filter, [arg]) => filter_next::check(cx, expr, recv2, arg), + (sym::filter, [arg]) => { + filter_next::check(cx, expr, recv2, arg, filter_next::Direction::Forward); + }, (sym::filter_map, [arg]) => filter_map_next::check(cx, expr, recv2, arg, self.msrv), (sym::iter, []) => iter_next_slice::check(cx, expr, recv2), (sym::skip, [arg]) => iter_skip_next::check(cx, expr, recv2, arg), @@ -5379,6 +5381,14 @@ impl Methods { } } }, + (sym::next_back, []) => { + if let Some((name2, recv2, args2, _, _)) = method_call(recv) + && let (sym::filter, [arg]) = (name2, args2) + && self.msrv.meets(cx, msrvs::DOUBLE_ENDED_ITERATOR_RFIND) + { + filter_next::check(cx, expr, recv2, arg, filter_next::Direction::Backward); + } + }, (sym::nth, [n_arg]) => match method_call(recv) { Some((sym::bytes, recv2, [], _, _)) => bytes_nth::check(cx, expr, recv2, n_arg), Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index 6e07ed9ffcc4d..ad3d52193bf6e 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -72,7 +72,7 @@ msrv_aliases! { 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES } 1,29,0 { ITER_FLATTEN } 1,28,0 { FROM_BOOL, REPEAT_WITH, SLICE_FROM_REF } - 1,27,0 { ITERATOR_TRY_FOLD } + 1,27,0 { ITERATOR_TRY_FOLD, DOUBLE_ENDED_ITERATOR_RFIND } 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN, POINTER_ADD_SUB_METHODS } 1,24,0 { IS_ASCII_DIGIT, PTR_NULL } 1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN } diff --git a/tests/ui/auxiliary/option_helpers.rs b/tests/ui/auxiliary/option_helpers.rs index f9bc9436b0792..796d4dabf04d2 100644 --- a/tests/ui/auxiliary/option_helpers.rs +++ b/tests/ui/auxiliary/option_helpers.rs @@ -25,6 +25,10 @@ impl IteratorFalsePositives { self } + pub fn next_back(self) -> IteratorFalsePositives { + self + } + pub fn find(self) -> Option { Some(self.foo) } diff --git a/tests/ui/methods.rs b/tests/ui/methods.rs index f73fe288b0f8c..9595888b99f83 100644 --- a/tests/ui/methods.rs +++ b/tests/ui/methods.rs @@ -135,6 +135,26 @@ fn filter_next() { let _ = foo.filter(42).next(); } +#[rustfmt::skip] +fn filter_next_back() { + let v = vec![3, 2, 1, 0, -1, -2, -3]; + + // Multi-line case. + let _ = v.iter().filter(|&x| { + //~^ filter_next + *x < 0 + } + ).next_back(); + + // Check that we don't lint if the caller is not an `Iterator`. + let foo = IteratorFalsePositives { foo: 0 }; + let _ = foo.filter().next_back(); + + let foo = IteratorMethodFalsePositives {}; + let _ = foo.filter(42).next_back(); +} + fn main() { filter_next(); + filter_next_back(); } diff --git a/tests/ui/methods.stderr b/tests/ui/methods.stderr index b226ce7c65dab..45efea4ee01cd 100644 --- a/tests/ui/methods.stderr +++ b/tests/ui/methods.stderr @@ -24,5 +24,16 @@ LL | | ).next(); = note: `-D clippy::filter-next` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::filter_next)]` -error: aborting due to 2 previous errors +error: called `filter(..).next_back()` on an `DoubleEndedIterator`. This is more succinctly expressed by calling `.rfind(..)` instead + --> tests/ui/methods.rs:143:13 + | +LL | let _ = v.iter().filter(|&x| { + | _____________^ +LL | | +LL | | *x < 0 +LL | | } +LL | | ).next_back(); + | |________________________________^ + +error: aborting due to 3 previous errors diff --git a/tests/ui/methods_fixable.fixed b/tests/ui/methods_fixable.fixed index 49730d8115584..287d8d881ec28 100644 --- a/tests/ui/methods_fixable.fixed +++ b/tests/ui/methods_fixable.fixed @@ -8,4 +8,18 @@ fn main() { // Single-line case. let _ = v.iter().find(|&x| *x < 0); //~^ filter_next + + let _ = v.iter().rfind(|&x| *x < 0); + //~^ filter_next +} + +#[clippy::msrv = "1.27"] +fn msrv_1_27() { + let _ = vec![1].into_iter().rfind(|&x| x < 0); + //~^ filter_next +} + +#[clippy::msrv = "1.26"] +fn msrv_1_26() { + let _ = vec![1].into_iter().filter(|&x| x < 0).next_back(); } diff --git a/tests/ui/methods_fixable.rs b/tests/ui/methods_fixable.rs index a499b63b6f5bd..11ce1b63560db 100644 --- a/tests/ui/methods_fixable.rs +++ b/tests/ui/methods_fixable.rs @@ -8,4 +8,18 @@ fn main() { // Single-line case. let _ = v.iter().filter(|&x| *x < 0).next(); //~^ filter_next + + let _ = v.iter().filter(|&x| *x < 0).next_back(); + //~^ filter_next +} + +#[clippy::msrv = "1.27"] +fn msrv_1_27() { + let _ = vec![1].into_iter().filter(|&x| x < 0).next_back(); + //~^ filter_next +} + +#[clippy::msrv = "1.26"] +fn msrv_1_26() { + let _ = vec![1].into_iter().filter(|&x| x < 0).next_back(); } diff --git a/tests/ui/methods_fixable.stderr b/tests/ui/methods_fixable.stderr index 852e7a224a6e3..d26b5e9ac271e 100644 --- a/tests/ui/methods_fixable.stderr +++ b/tests/ui/methods_fixable.stderr @@ -7,5 +7,17 @@ LL | let _ = v.iter().filter(|&x| *x < 0).next(); = note: `-D clippy::filter-next` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::filter_next)]` -error: aborting due to 1 previous error +error: called `filter(..).next_back()` on an `DoubleEndedIterator`. This is more succinctly expressed by calling `.rfind(..)` instead + --> tests/ui/methods_fixable.rs:12:13 + | +LL | let _ = v.iter().filter(|&x| *x < 0).next_back(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v.iter().rfind(|&x| *x < 0)` + +error: called `filter(..).next_back()` on an `DoubleEndedIterator`. This is more succinctly expressed by calling `.rfind(..)` instead + --> tests/ui/methods_fixable.rs:18:13 + | +LL | let _ = vec![1].into_iter().filter(|&x| x < 0).next_back(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec![1].into_iter().rfind(|&x| x < 0)` + +error: aborting due to 3 previous errors From 283aad06e9a9145b963119b2b061db92ada9d680 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Fri, 1 Aug 2025 21:55:11 +0300 Subject: [PATCH 41/93] resolve: Do not finalize shadowed bindings I.e. do not mark them as used, or non-speculative loaded, or similar. Previously they were sometimes finalized during early resolution, causing issues like https://github.com/rust-lang/rust/pull/144793#issuecomment-3168108005. --- tests/ui/useless_attribute.fixed | 2 +- tests/ui/useless_attribute.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ui/useless_attribute.fixed b/tests/ui/useless_attribute.fixed index 15070dd9c2ca6..e0bc23e0788c6 100644 --- a/tests/ui/useless_attribute.fixed +++ b/tests/ui/useless_attribute.fixed @@ -153,7 +153,7 @@ pub mod redundant_imports_issue { () => {}; } - #[expect(redundant_imports)] + #[expect(unused_imports)] pub(crate) use empty; empty!(); diff --git a/tests/ui/useless_attribute.rs b/tests/ui/useless_attribute.rs index 3f530de7fd8e3..30a4c354b238a 100644 --- a/tests/ui/useless_attribute.rs +++ b/tests/ui/useless_attribute.rs @@ -153,7 +153,7 @@ pub mod redundant_imports_issue { () => {}; } - #[expect(redundant_imports)] + #[expect(unused_imports)] pub(crate) use empty; empty!(); From bd39ea463f30fd51be0b4587191c67cd53bf6658 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 25 Sep 2025 00:17:03 +0200 Subject: [PATCH 42/93] inline `clippy_utils::ptr` into `needless_pass_by_value` ..and make it less generic, thanks to all the callers being known --- clippy_lints/src/needless_pass_by_value.rs | 63 +++++++++++++++++----- clippy_utils/src/lib.rs | 1 - clippy_utils/src/ptr.rs | 52 ------------------ 3 files changed, 51 insertions(+), 65 deletions(-) delete mode 100644 clippy_utils/src/ptr.rs diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index 32ded96c1236f..455d1426aa8cb 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -1,16 +1,16 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::ptr::get_spans; use clippy_utils::source::{SpanRangeExt, snippet}; use clippy_utils::ty::{ implements_trait, implements_trait_with_env_from_iter, is_copy, is_type_diagnostic_item, is_type_lang_item, }; -use clippy_utils::{is_self, peel_hir_ty_options}; +use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; +use clippy_utils::{is_self, path_to_local_id, peel_hir_ty_options, strip_pat_refs, sym}; use rustc_abi::ExternAbi; use rustc_errors::{Applicability, Diag}; use rustc_hir::intravisit::FnKind; use rustc_hir::{ - Attribute, BindingMode, Body, FnDecl, GenericArg, HirId, HirIdSet, Impl, ItemKind, LangItem, Mutability, Node, - PatKind, QPath, TyKind, + Attribute, BindingMode, Body, ExprKind, FnDecl, GenericArg, HirId, HirIdSet, Impl, ItemKind, LangItem, Mutability, + Node, PatKind, QPath, TyKind, }; use rustc_hir_typeck::expr_use_visitor as euv; use rustc_lint::{LateContext, LateLintPass}; @@ -19,10 +19,13 @@ use rustc_middle::ty::{self, Ty, TypeVisitableExt}; use rustc_session::declare_lint_pass; use rustc_span::def_id::LocalDefId; use rustc_span::symbol::kw; -use rustc_span::{Span, sym}; +use rustc_span::{Span, Symbol}; use rustc_trait_selection::traits; use rustc_trait_selection::traits::misc::type_allowed_to_implement_copy; +use std::borrow::Cow; +use std::ops::ControlFlow; + declare_clippy_lint! { /// ### What it does /// Checks for functions taking arguments by value, but not @@ -217,7 +220,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { } if is_type_diagnostic_item(cx, ty, sym::Vec) - && let Some(clone_spans) = get_spans(cx, Some(body.id()), idx, &[(sym::clone, ".to_owned()")]) + && let Some(clone_spans) = get_spans(cx, body, idx, &[(sym::clone, ".to_owned()")]) && let TyKind::Path(QPath::Resolved(_, path)) = input.kind && let Some(elem_ty) = path .segments @@ -260,12 +263,8 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { } if is_type_lang_item(cx, ty, LangItem::String) - && let Some(clone_spans) = get_spans( - cx, - Some(body.id()), - idx, - &[(sym::clone, ".to_string()"), (sym::as_str, "")], - ) + && let Some(clone_spans) = + get_spans(cx, body, idx, &[(sym::clone, ".to_string()"), (sym::as_str, "")]) { diag.span_suggestion( input.span, @@ -340,3 +339,43 @@ impl<'tcx> euv::Delegate<'tcx> for MovedVariablesCtxt { fn fake_read(&mut self, _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {} } + +fn get_spans<'tcx>( + cx: &LateContext<'tcx>, + body: &'tcx Body<'_>, + idx: usize, + replacements: &[(Symbol, &'static str)], +) -> Option)>> { + if let PatKind::Binding(_, binding_id, _, _) = strip_pat_refs(body.params[idx].pat).kind { + extract_clone_suggestions(cx, binding_id, replacements, body) + } else { + Some(vec![]) + } +} + +fn extract_clone_suggestions<'tcx>( + cx: &LateContext<'tcx>, + id: HirId, + replace: &[(Symbol, &'static str)], + body: &'tcx Body<'_>, +) -> Option)>> { + let mut spans = Vec::new(); + for_each_expr_without_closures(body, |e| { + if let ExprKind::MethodCall(seg, recv, [], _) = e.kind + && path_to_local_id(recv, id) + { + if seg.ident.name == sym::capacity { + return ControlFlow::Break(()); + } + for &(fn_name, suffix) in replace { + if seg.ident.name == fn_name { + spans.push((e.span, snippet(cx, recv.span, "_") + suffix)); + return ControlFlow::Continue(Descend::No); + } + } + } + ControlFlow::Continue(Descend::Yes) + }) + .is_none() + .then_some(spans) +} diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index feadc0ecf659a..d6586625a1e5e 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -63,7 +63,6 @@ pub mod mir; pub mod msrvs; pub mod numeric_literal; pub mod paths; -pub mod ptr; pub mod qualify_min_const_fn; pub mod source; pub mod str_utils; diff --git a/clippy_utils/src/ptr.rs b/clippy_utils/src/ptr.rs deleted file mode 100644 index 5847e916e340f..0000000000000 --- a/clippy_utils/src/ptr.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::source::snippet; -use crate::visitors::{Descend, for_each_expr_without_closures}; -use crate::{path_to_local_id, strip_pat_refs, sym}; -use core::ops::ControlFlow; -use rustc_hir::{Body, BodyId, ExprKind, HirId, PatKind}; -use rustc_lint::LateContext; -use rustc_span::{Span, Symbol}; -use std::borrow::Cow; - -pub fn get_spans( - cx: &LateContext<'_>, - opt_body_id: Option, - idx: usize, - replacements: &[(Symbol, &'static str)], -) -> Option)>> { - if let Some(body) = opt_body_id.map(|id| cx.tcx.hir_body(id)) { - if let PatKind::Binding(_, binding_id, _, _) = strip_pat_refs(body.params[idx].pat).kind { - extract_clone_suggestions(cx, binding_id, replacements, body) - } else { - Some(vec![]) - } - } else { - Some(vec![]) - } -} - -fn extract_clone_suggestions<'tcx>( - cx: &LateContext<'tcx>, - id: HirId, - replace: &[(Symbol, &'static str)], - body: &'tcx Body<'_>, -) -> Option)>> { - let mut spans = Vec::new(); - for_each_expr_without_closures(body, |e| { - if let ExprKind::MethodCall(seg, recv, [], _) = e.kind - && path_to_local_id(recv, id) - { - if seg.ident.name == sym::capacity { - return ControlFlow::Break(()); - } - for &(fn_name, suffix) in replace { - if seg.ident.name == fn_name { - spans.push((e.span, snippet(cx, recv.span, "_") + suffix)); - return ControlFlow::Continue(Descend::No); - } - } - } - ControlFlow::Continue(Descend::Yes) - }) - .is_none() - .then_some(spans) -} From 36d6327fa46a949d217b691fc7fb978359ca636b Mon Sep 17 00:00:00 2001 From: beepster4096 <19316085+beepster4096@users.noreply.github.com> Date: Fri, 26 Sep 2025 12:17:06 -0700 Subject: [PATCH 43/93] castkind::subtype in clippy --- clippy_utils/src/qualify_min_const_fn.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 2bda6d50373cd..cc98fac45c7cb 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -141,7 +141,8 @@ fn check_rvalue<'tcx>( | CastKind::FloatToFloat | CastKind::FnPtrToPtr | CastKind::PtrToPtr - | CastKind::PointerCoercion(PointerCoercion::MutToConstPointer | PointerCoercion::ArrayToPointer, _), + | CastKind::PointerCoercion(PointerCoercion::MutToConstPointer | PointerCoercion::ArrayToPointer, _) + | CastKind::Subtype, operand, _, ) => check_operand(cx, operand, span, body, msrv), @@ -312,7 +313,6 @@ fn check_place<'tcx>( | ProjectionElem::OpaqueCast(..) | ProjectionElem::Downcast(..) | ProjectionElem::Subslice { .. } - | ProjectionElem::Subtype(_) | ProjectionElem::Index(_) | ProjectionElem::UnwrapUnsafeBinder(_) => {}, } From 714fdacdf8f2c07950e3f3ddd1999cba10b828af Mon Sep 17 00:00:00 2001 From: Mark Rousskov Date: Mon, 15 Sep 2025 22:01:36 -0400 Subject: [PATCH 44/93] Apply cfg(bootstrap) replacement --- clippy_lints/src/lib.rs | 1 - tests/missing-test-files.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c56fa257b0689..b0083b99f175f 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -7,7 +7,6 @@ #![feature(iter_intersperse)] #![feature(iter_partition_in_place)] #![feature(never_type)] -#![cfg_attr(bootstrap, feature(round_char_boundary))] #![feature(rustc_private)] #![feature(stmt_expr_attributes)] #![feature(unwrap_infallible)] diff --git a/tests/missing-test-files.rs b/tests/missing-test-files.rs index 63f960c92fa32..9fff3132498d8 100644 --- a/tests/missing-test-files.rs +++ b/tests/missing-test-files.rs @@ -1,6 +1,5 @@ #![warn(rust_2018_idioms, unused_lifetimes)] #![allow(clippy::assertions_on_constants)] -#![cfg_attr(bootstrap, feature(path_file_prefix))] use std::cmp::Ordering; use std::ffi::OsStr; From 77c96c031b40298a013befa2d3ebf11f459f9275 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 5 Aug 2025 23:07:11 +0200 Subject: [PATCH 45/93] clean-up --- tests/ui/double_parens.rs | 9 +-------- tests/ui/double_parens.stderr | 10 +++++----- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/tests/ui/double_parens.rs b/tests/ui/double_parens.rs index 7c976015b4e79..1e03033615950 100644 --- a/tests/ui/double_parens.rs +++ b/tests/ui/double_parens.rs @@ -1,5 +1,5 @@ #![warn(clippy::double_parens)] -#![allow(dead_code, clippy::eq_op)] +#![expect(clippy::eq_op)] #![feature(custom_inner_attributes)] #![rustfmt::skip] @@ -14,32 +14,26 @@ impl DummyStruct { fn simple_double_parens() -> i32 { ((0)) //~^ double_parens - - } fn fn_double_parens() { dummy_fn((0)); //~^ double_parens - } fn method_double_parens(x: DummyStruct) { x.dummy_method((0)); //~^ double_parens - } fn tuple_double_parens() -> (i32, i32) { ((1, 2)) //~^ double_parens - } fn unit_double_parens() { (()) //~^ double_parens - } fn fn_tuple_ok() { @@ -63,7 +57,6 @@ fn inside_macro() { assert_eq!((1, 2), (1, 2), "Error"); assert_eq!(((1, 2)), (1, 2), "Error"); //~^ double_parens - } fn main() {} diff --git a/tests/ui/double_parens.stderr b/tests/ui/double_parens.stderr index e119f54949b1f..da48219c97e10 100644 --- a/tests/ui/double_parens.stderr +++ b/tests/ui/double_parens.stderr @@ -8,31 +8,31 @@ LL | ((0)) = help: to override `-D warnings` add `#[allow(clippy::double_parens)]` error: consider removing unnecessary double parentheses - --> tests/ui/double_parens.rs:22:14 + --> tests/ui/double_parens.rs:20:14 | LL | dummy_fn((0)); | ^^^ error: consider removing unnecessary double parentheses - --> tests/ui/double_parens.rs:28:20 + --> tests/ui/double_parens.rs:25:20 | LL | x.dummy_method((0)); | ^^^ error: consider removing unnecessary double parentheses - --> tests/ui/double_parens.rs:34:5 + --> tests/ui/double_parens.rs:30:5 | LL | ((1, 2)) | ^^^^^^^^ error: consider removing unnecessary double parentheses - --> tests/ui/double_parens.rs:40:5 + --> tests/ui/double_parens.rs:35:5 | LL | (()) | ^^^^ error: consider removing unnecessary double parentheses - --> tests/ui/double_parens.rs:64:16 + --> tests/ui/double_parens.rs:58:16 | LL | assert_eq!(((1, 2)), (1, 2), "Error"); | ^^^^^^^^ From dbef783309a6692bd0e6573055f3e6252d444708 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 22 Aug 2025 18:13:08 +0200 Subject: [PATCH 46/93] call args "args" in the `Call` case as well since that's what they are --- clippy_lints/src/double_parens.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/double_parens.rs b/clippy_lints/src/double_parens.rs index 4dd8f01ee7090..73078547c695d 100644 --- a/clippy_lints/src/double_parens.rs +++ b/clippy_lints/src/double_parens.rs @@ -42,11 +42,11 @@ impl EarlyLintPass for DoubleParens { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { let span = match &expr.kind { ExprKind::Paren(in_paren) if matches!(in_paren.kind, ExprKind::Paren(_) | ExprKind::Tup(_)) => expr.span, - ExprKind::Call(_, params) - if let [param] = &**params - && let ExprKind::Paren(_) = param.kind => + ExprKind::Call(_, args) + if let [args] = &**args + && let ExprKind::Paren(_) = args.kind => { - param.span + args.span }, ExprKind::MethodCall(call) if let [arg] = &*call.args From 6a6b4a3ba8ef0793f275e9eb8351f8acea72e8f1 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 22 Aug 2025 18:14:04 +0200 Subject: [PATCH 47/93] combine the `Call` and `MethodCall` cases --- clippy_lints/src/double_parens.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/double_parens.rs b/clippy_lints/src/double_parens.rs index 73078547c695d..d014efcde48dd 100644 --- a/clippy_lints/src/double_parens.rs +++ b/clippy_lints/src/double_parens.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint; -use rustc_ast::ast::{Expr, ExprKind}; +use rustc_ast::ast::{Expr, ExprKind, MethodCall}; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::declare_lint_pass; @@ -42,18 +42,12 @@ impl EarlyLintPass for DoubleParens { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { let span = match &expr.kind { ExprKind::Paren(in_paren) if matches!(in_paren.kind, ExprKind::Paren(_) | ExprKind::Tup(_)) => expr.span, - ExprKind::Call(_, args) + ExprKind::Call(_, args) | ExprKind::MethodCall(box MethodCall { args, .. }) if let [args] = &**args && let ExprKind::Paren(_) = args.kind => { args.span }, - ExprKind::MethodCall(call) - if let [arg] = &*call.args - && let ExprKind::Paren(_) = arg.kind => - { - arg.span - }, _ => return, }; if !expr.span.from_expansion() { From 82b2eb13f8ed8777c97cdceb94137a4ed73d43f6 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 22 Aug 2025 18:18:03 +0200 Subject: [PATCH 48/93] add structured suggestions --- clippy_lints/src/double_parens.rs | 62 ++++++++++++++++++++++-------- tests/ui/double_parens.fixed | 63 +++++++++++++++++++++++++++++++ tests/ui/double_parens.rs | 1 + tests/ui/double_parens.stderr | 28 +++++++------- 4 files changed, 124 insertions(+), 30 deletions(-) create mode 100644 tests/ui/double_parens.fixed diff --git a/clippy_lints/src/double_parens.rs b/clippy_lints/src/double_parens.rs index d014efcde48dd..ab6e477121207 100644 --- a/clippy_lints/src/double_parens.rs +++ b/clippy_lints/src/double_parens.rs @@ -1,5 +1,7 @@ -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::{HasSession, snippet_with_applicability}; use rustc_ast::ast::{Expr, ExprKind, MethodCall}; +use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::declare_lint_pass; @@ -24,7 +26,7 @@ declare_clippy_lint! { /// Use instead: /// ```no_run /// fn simple_no_parens() -> i32 { - /// 0 + /// (0) /// } /// /// # fn foo(bar: usize) {} @@ -40,23 +42,51 @@ declare_lint_pass!(DoubleParens => [DOUBLE_PARENS]); impl EarlyLintPass for DoubleParens { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { - let span = match &expr.kind { - ExprKind::Paren(in_paren) if matches!(in_paren.kind, ExprKind::Paren(_) | ExprKind::Tup(_)) => expr.span, + if expr.span.from_expansion() { + return; + } + + match &expr.kind { + // ((..)) + // ^^^^^^ expr + // ^^^^ inner + ExprKind::Paren(inner) if matches!(inner.kind, ExprKind::Paren(_) | ExprKind::Tup(_)) => { + // suggest removing the outer parens + let mut applicability = Applicability::MachineApplicable; + let sugg = snippet_with_applicability(cx.sess(), inner.span, "_", &mut applicability); + span_lint_and_sugg( + cx, + DOUBLE_PARENS, + expr.span, + "unnecessary parentheses", + "remove them", + sugg.to_string(), + applicability, + ); + }, + + // func((n)) + // ^^^^^^^^^ expr + // ^^^ arg + // ^ inner ExprKind::Call(_, args) | ExprKind::MethodCall(box MethodCall { args, .. }) - if let [args] = &**args - && let ExprKind::Paren(_) = args.kind => + if let [arg] = &**args + && let ExprKind::Paren(inner) = &arg.kind => { - args.span + // suggest removing the inner parens + let mut applicability = Applicability::MachineApplicable; + let sugg = snippet_with_applicability(cx.sess(), inner.span, "_", &mut applicability); + span_lint_and_sugg( + cx, + DOUBLE_PARENS, + arg.span, + "unnecessary parentheses", + "remove them", + sugg.to_string(), + applicability, + ); }, - _ => return, - }; - if !expr.span.from_expansion() { - span_lint( - cx, - DOUBLE_PARENS, - span, - "consider removing unnecessary double parentheses", - ); + _ => {}, } } } diff --git a/tests/ui/double_parens.fixed b/tests/ui/double_parens.fixed new file mode 100644 index 0000000000000..7fa116942fe85 --- /dev/null +++ b/tests/ui/double_parens.fixed @@ -0,0 +1,63 @@ +#![warn(clippy::double_parens)] +#![expect(clippy::eq_op)] +#![feature(custom_inner_attributes)] +#![rustfmt::skip] + +fn dummy_fn(_: T) {} + +struct DummyStruct; + +impl DummyStruct { + fn dummy_method(self, _: T) {} +} + +fn simple_double_parens() -> i32 { + (0) + //~^ double_parens +} + +fn fn_double_parens() { + dummy_fn(0); + //~^ double_parens +} + +fn method_double_parens(x: DummyStruct) { + x.dummy_method(0); + //~^ double_parens +} + +fn tuple_double_parens() -> (i32, i32) { + (1, 2) + //~^ double_parens +} + +#[allow(clippy::unused_unit)] +fn unit_double_parens() { + () + //~^ double_parens +} + +fn fn_tuple_ok() { + dummy_fn((1, 2)); +} + +fn method_tuple_ok(x: DummyStruct) { + x.dummy_method((1, 2)); +} + +fn fn_unit_ok() { + dummy_fn(()); +} + +fn method_unit_ok(x: DummyStruct) { + x.dummy_method(()); +} + +// Issue #3206 +fn inside_macro() { + assert_eq!((1, 2), (1, 2), "Error"); + assert_eq!((1, 2), (1, 2), "Error"); + //~^ double_parens +} + +fn main() {} diff --git a/tests/ui/double_parens.rs b/tests/ui/double_parens.rs index 1e03033615950..8730b4b124689 100644 --- a/tests/ui/double_parens.rs +++ b/tests/ui/double_parens.rs @@ -31,6 +31,7 @@ fn tuple_double_parens() -> (i32, i32) { //~^ double_parens } +#[allow(clippy::unused_unit)] fn unit_double_parens() { (()) //~^ double_parens diff --git a/tests/ui/double_parens.stderr b/tests/ui/double_parens.stderr index da48219c97e10..2b37877ccdd86 100644 --- a/tests/ui/double_parens.stderr +++ b/tests/ui/double_parens.stderr @@ -1,41 +1,41 @@ -error: consider removing unnecessary double parentheses +error: unnecessary parentheses --> tests/ui/double_parens.rs:15:5 | LL | ((0)) - | ^^^^^ + | ^^^^^ help: remove them: `(0)` | = note: `-D clippy::double-parens` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::double_parens)]` -error: consider removing unnecessary double parentheses +error: unnecessary parentheses --> tests/ui/double_parens.rs:20:14 | LL | dummy_fn((0)); - | ^^^ + | ^^^ help: remove them: `0` -error: consider removing unnecessary double parentheses +error: unnecessary parentheses --> tests/ui/double_parens.rs:25:20 | LL | x.dummy_method((0)); - | ^^^ + | ^^^ help: remove them: `0` -error: consider removing unnecessary double parentheses +error: unnecessary parentheses --> tests/ui/double_parens.rs:30:5 | LL | ((1, 2)) - | ^^^^^^^^ + | ^^^^^^^^ help: remove them: `(1, 2)` -error: consider removing unnecessary double parentheses - --> tests/ui/double_parens.rs:35:5 +error: unnecessary parentheses + --> tests/ui/double_parens.rs:36:5 | LL | (()) - | ^^^^ + | ^^^^ help: remove them: `()` -error: consider removing unnecessary double parentheses - --> tests/ui/double_parens.rs:58:16 +error: unnecessary parentheses + --> tests/ui/double_parens.rs:59:16 | LL | assert_eq!(((1, 2)), (1, 2), "Error"); - | ^^^^^^^^ + | ^^^^^^^^ help: remove them: `(1, 2)` error: aborting due to 6 previous errors From b613be223dfc2151bc7aea15019b5ee04607621c Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 22 Aug 2025 18:19:10 +0200 Subject: [PATCH 49/93] fix a bug while we're at it --- clippy_lints/src/double_parens.rs | 57 ++++++++++++++++--------------- tests/ui/double_parens.fixed | 40 ++++++++++++++++++++-- tests/ui/double_parens.rs | 40 ++++++++++++++++++++-- tests/ui/double_parens.stderr | 31 ++++++++++++++++- 4 files changed, 136 insertions(+), 32 deletions(-) diff --git a/clippy_lints/src/double_parens.rs b/clippy_lints/src/double_parens.rs index ab6e477121207..bddf4702fb340 100644 --- a/clippy_lints/src/double_parens.rs +++ b/clippy_lints/src/double_parens.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{HasSession, snippet_with_applicability}; +use clippy_utils::source::{HasSession, snippet_with_applicability, snippet_with_context}; use rustc_ast::ast::{Expr, ExprKind, MethodCall}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; @@ -42,27 +42,28 @@ declare_lint_pass!(DoubleParens => [DOUBLE_PARENS]); impl EarlyLintPass for DoubleParens { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { - if expr.span.from_expansion() { - return; - } - match &expr.kind { // ((..)) // ^^^^^^ expr // ^^^^ inner ExprKind::Paren(inner) if matches!(inner.kind, ExprKind::Paren(_) | ExprKind::Tup(_)) => { // suggest removing the outer parens - let mut applicability = Applicability::MachineApplicable; - let sugg = snippet_with_applicability(cx.sess(), inner.span, "_", &mut applicability); - span_lint_and_sugg( - cx, - DOUBLE_PARENS, - expr.span, - "unnecessary parentheses", - "remove them", - sugg.to_string(), - applicability, - ); + if expr.span.eq_ctxt(inner.span) { + let mut applicability = Applicability::MachineApplicable; + // We don't need to use `snippet_with_context` here, because: + // - if `inner`'s `ctxt` is from macro, we don't lint in the first place (see the check above) + // - otherwise, calling `snippet_with_applicability` on a not-from-macro span is fine + let sugg = snippet_with_applicability(cx.sess(), inner.span, "_", &mut applicability); + span_lint_and_sugg( + cx, + DOUBLE_PARENS, + expr.span, + "unnecessary parentheses", + "remove them", + sugg.to_string(), + applicability, + ); + } }, // func((n)) @@ -74,17 +75,19 @@ impl EarlyLintPass for DoubleParens { && let ExprKind::Paren(inner) = &arg.kind => { // suggest removing the inner parens - let mut applicability = Applicability::MachineApplicable; - let sugg = snippet_with_applicability(cx.sess(), inner.span, "_", &mut applicability); - span_lint_and_sugg( - cx, - DOUBLE_PARENS, - arg.span, - "unnecessary parentheses", - "remove them", - sugg.to_string(), - applicability, - ); + if expr.span.eq_ctxt(arg.span) { + let mut applicability = Applicability::MachineApplicable; + let sugg = snippet_with_context(cx.sess(), inner.span, arg.span.ctxt(), "_", &mut applicability).0; + span_lint_and_sugg( + cx, + DOUBLE_PARENS, + arg.span, + "unnecessary parentheses", + "remove them", + sugg.to_string(), + applicability, + ); + } }, _ => {}, } diff --git a/tests/ui/double_parens.fixed b/tests/ui/double_parens.fixed index 7fa116942fe85..dedc513438d11 100644 --- a/tests/ui/double_parens.fixed +++ b/tests/ui/double_parens.fixed @@ -1,5 +1,5 @@ #![warn(clippy::double_parens)] -#![expect(clippy::eq_op)] +#![expect(clippy::eq_op, clippy::no_effect)] #![feature(custom_inner_attributes)] #![rustfmt::skip] @@ -8,7 +8,7 @@ fn dummy_fn(_: T) {} struct DummyStruct; impl DummyStruct { - fn dummy_method(self, _: T) {} + fn dummy_method(&self, _: T) {} } fn simple_double_parens() -> i32 { @@ -60,4 +60,40 @@ fn inside_macro() { //~^ double_parens } +fn issue9000(x: DummyStruct) { + macro_rules! foo { + () => {(100)} + } + // don't lint: the inner paren comes from the macro expansion + (foo!()); + dummy_fn(foo!()); + x.dummy_method(foo!()); + + macro_rules! baz { + ($n:literal) => {($n)} + } + // don't lint: don't get confused by the expression inside the inner paren + // having the same `ctxt` as the overall expression + // (this is a bug that happened during the development of the fix) + (baz!(100)); + dummy_fn(baz!(100)); + x.dummy_method(baz!(100)); + + // should lint: both parens are from inside the macro + macro_rules! bar { + () => {(100)} + //~^ double_parens + } + bar!(); + + // should lint: both parens are from outside the macro; + // make sure to suggest the macro unexpanded + (vec![1, 2]); + //~^ double_parens + dummy_fn(vec![1, 2]); + //~^ double_parens + x.dummy_method(vec![1, 2]); + //~^ double_parens +} + fn main() {} diff --git a/tests/ui/double_parens.rs b/tests/ui/double_parens.rs index 8730b4b124689..27f252485b714 100644 --- a/tests/ui/double_parens.rs +++ b/tests/ui/double_parens.rs @@ -1,5 +1,5 @@ #![warn(clippy::double_parens)] -#![expect(clippy::eq_op)] +#![expect(clippy::eq_op, clippy::no_effect)] #![feature(custom_inner_attributes)] #![rustfmt::skip] @@ -8,7 +8,7 @@ fn dummy_fn(_: T) {} struct DummyStruct; impl DummyStruct { - fn dummy_method(self, _: T) {} + fn dummy_method(&self, _: T) {} } fn simple_double_parens() -> i32 { @@ -60,4 +60,40 @@ fn inside_macro() { //~^ double_parens } +fn issue9000(x: DummyStruct) { + macro_rules! foo { + () => {(100)} + } + // don't lint: the inner paren comes from the macro expansion + (foo!()); + dummy_fn(foo!()); + x.dummy_method(foo!()); + + macro_rules! baz { + ($n:literal) => {($n)} + } + // don't lint: don't get confused by the expression inside the inner paren + // having the same `ctxt` as the overall expression + // (this is a bug that happened during the development of the fix) + (baz!(100)); + dummy_fn(baz!(100)); + x.dummy_method(baz!(100)); + + // should lint: both parens are from inside the macro + macro_rules! bar { + () => {((100))} + //~^ double_parens + } + bar!(); + + // should lint: both parens are from outside the macro; + // make sure to suggest the macro unexpanded + ((vec![1, 2])); + //~^ double_parens + dummy_fn((vec![1, 2])); + //~^ double_parens + x.dummy_method((vec![1, 2])); + //~^ double_parens +} + fn main() {} diff --git a/tests/ui/double_parens.stderr b/tests/ui/double_parens.stderr index 2b37877ccdd86..3a740e44cacf3 100644 --- a/tests/ui/double_parens.stderr +++ b/tests/ui/double_parens.stderr @@ -37,5 +37,34 @@ error: unnecessary parentheses LL | assert_eq!(((1, 2)), (1, 2), "Error"); | ^^^^^^^^ help: remove them: `(1, 2)` -error: aborting due to 6 previous errors +error: unnecessary parentheses + --> tests/ui/double_parens.rs:84:16 + | +LL | () => {((100))} + | ^^^^^^^ help: remove them: `(100)` +... +LL | bar!(); + | ------ in this macro invocation + | + = note: this error originates in the macro `bar` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unnecessary parentheses + --> tests/ui/double_parens.rs:91:5 + | +LL | ((vec![1, 2])); + | ^^^^^^^^^^^^^^ help: remove them: `(vec![1, 2])` + +error: unnecessary parentheses + --> tests/ui/double_parens.rs:93:14 + | +LL | dummy_fn((vec![1, 2])); + | ^^^^^^^^^^^^ help: remove them: `vec![1, 2]` + +error: unnecessary parentheses + --> tests/ui/double_parens.rs:95:20 + | +LL | x.dummy_method((vec![1, 2])); + | ^^^^^^^^^^^^ help: remove them: `vec![1, 2]` + +error: aborting due to 10 previous errors From bb1081e5a9a85081d944c885939809a08db84dd3 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 5 Aug 2025 11:58:48 +0200 Subject: [PATCH 50/93] move unfixable stuff to a separate file --- tests/ui/mut_mut.rs | 9 +++----- tests/ui/mut_mut.stderr | 18 ++++++++-------- tests/ui/mut_mut_unfixable.rs | 34 ++++++++++++++++++++++++++++++ tests/ui/mut_mut_unfixable.stderr | 35 +++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 15 deletions(-) create mode 100644 tests/ui/mut_mut_unfixable.rs create mode 100644 tests/ui/mut_mut_unfixable.stderr diff --git a/tests/ui/mut_mut.rs b/tests/ui/mut_mut.rs index bbcdbc89b6a4b..d006578b3d284 100644 --- a/tests/ui/mut_mut.rs +++ b/tests/ui/mut_mut.rs @@ -12,9 +12,8 @@ extern crate proc_macros; use proc_macros::{external, inline_macros}; -fn fun(x: &mut &mut u32) -> bool { +fn fun(x: &mut &mut u32) { //~^ mut_mut - **x > 0 } fn less_fun(x: *mut *mut u32) { @@ -37,19 +36,17 @@ fn main() { //~^ mut_mut } - if fun(x) { + { let y: &mut &mut u32 = &mut &mut 2; //~^ mut_mut //~| mut_mut - **y + **x; } - if fun(x) { + { let y: &mut &mut &mut u32 = &mut &mut &mut 2; //~^ mut_mut //~| mut_mut //~| mut_mut - ***y + **x; } let mut z = inline!(&mut $(&mut 3u32)); diff --git a/tests/ui/mut_mut.stderr b/tests/ui/mut_mut.stderr index 74b0c9ba145a9..8517c4e5ad89f 100644 --- a/tests/ui/mut_mut.stderr +++ b/tests/ui/mut_mut.stderr @@ -1,20 +1,20 @@ error: generally you want to avoid `&mut &mut _` if possible --> tests/ui/mut_mut.rs:15:11 | -LL | fn fun(x: &mut &mut u32) -> bool { +LL | fn fun(x: &mut &mut u32) { | ^^^^^^^^^^^^^ | = note: `-D clippy::mut-mut` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::mut_mut)]` error: generally you want to avoid `&mut &mut _` if possible - --> tests/ui/mut_mut.rs:33:17 + --> tests/ui/mut_mut.rs:32:17 | LL | let mut x = &mut &mut 1u32; | ^^^^^^^^^^^^^^ error: generally you want to avoid `&mut &mut _` if possible - --> tests/ui/mut_mut.rs:55:25 + --> tests/ui/mut_mut.rs:52:25 | LL | let mut z = inline!(&mut $(&mut 3u32)); | ^ @@ -22,37 +22,37 @@ LL | let mut z = inline!(&mut $(&mut 3u32)); = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) error: this expression mutably borrows a mutable reference. Consider reborrowing - --> tests/ui/mut_mut.rs:36:21 + --> tests/ui/mut_mut.rs:35:21 | LL | let mut y = &mut x; | ^^^^^^ error: generally you want to avoid `&mut &mut _` if possible - --> tests/ui/mut_mut.rs:41:32 + --> tests/ui/mut_mut.rs:40:32 | LL | let y: &mut &mut u32 = &mut &mut 2; | ^^^^^^^^^^^ error: generally you want to avoid `&mut &mut _` if possible - --> tests/ui/mut_mut.rs:41:16 + --> tests/ui/mut_mut.rs:40:16 | LL | let y: &mut &mut u32 = &mut &mut 2; | ^^^^^^^^^^^^^ error: generally you want to avoid `&mut &mut _` if possible - --> tests/ui/mut_mut.rs:48:37 + --> tests/ui/mut_mut.rs:46:37 | LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; | ^^^^^^^^^^^^^^^^ error: generally you want to avoid `&mut &mut _` if possible - --> tests/ui/mut_mut.rs:48:16 + --> tests/ui/mut_mut.rs:46:16 | LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; | ^^^^^^^^^^^^^^^^^^ error: generally you want to avoid `&mut &mut _` if possible - --> tests/ui/mut_mut.rs:48:21 + --> tests/ui/mut_mut.rs:46:21 | LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; | ^^^^^^^^^^^^^ diff --git a/tests/ui/mut_mut_unfixable.rs b/tests/ui/mut_mut_unfixable.rs new file mode 100644 index 0000000000000..db0552a86ec9b --- /dev/null +++ b/tests/ui/mut_mut_unfixable.rs @@ -0,0 +1,34 @@ +//@no-rustfix + +#![warn(clippy::mut_mut)] +#![allow(unused)] +#![expect(clippy::no_effect)] + +//! removing the extra `&mut`s will break the derefs + +fn fun(x: &mut &mut u32) -> bool { + //~^ mut_mut + **x > 0 +} + +fn main() { + let mut x = &mut &mut 1u32; + //~^ mut_mut + { + let mut y = &mut x; + //~^ mut_mut + ***y + **x; + } + + if fun(x) { + let y = &mut &mut 2; + //~^ mut_mut + **y + **x; + } + + if fun(x) { + let y = &mut &mut &mut 2; + //~^ mut_mut + ***y + **x; + } +} diff --git a/tests/ui/mut_mut_unfixable.stderr b/tests/ui/mut_mut_unfixable.stderr new file mode 100644 index 0000000000000..bb0363ef7ed9c --- /dev/null +++ b/tests/ui/mut_mut_unfixable.stderr @@ -0,0 +1,35 @@ +error: generally you want to avoid `&mut &mut _` if possible + --> tests/ui/mut_mut_unfixable.rs:9:11 + | +LL | fn fun(x: &mut &mut u32) -> bool { + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::mut-mut` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::mut_mut)]` + +error: generally you want to avoid `&mut &mut _` if possible + --> tests/ui/mut_mut_unfixable.rs:15:17 + | +LL | let mut x = &mut &mut 1u32; + | ^^^^^^^^^^^^^^ + +error: this expression mutably borrows a mutable reference. Consider reborrowing + --> tests/ui/mut_mut_unfixable.rs:18:21 + | +LL | let mut y = &mut x; + | ^^^^^^ + +error: generally you want to avoid `&mut &mut _` if possible + --> tests/ui/mut_mut_unfixable.rs:24:17 + | +LL | let y = &mut &mut 2; + | ^^^^^^^^^^^ + +error: generally you want to avoid `&mut &mut _` if possible + --> tests/ui/mut_mut_unfixable.rs:30:17 + | +LL | let y = &mut &mut &mut 2; + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors + From a4fba850a90a3a7f9a33d872de09795d9801743f Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 3 Aug 2025 23:32:07 +0200 Subject: [PATCH 51/93] sugg on expr 1 --- clippy_lints/src/mut_mut.rs | 13 ++++- tests/ui/mut_mut.fixed | 94 +++++++++++++++++++++++++++++++ tests/ui/mut_mut.stderr | 4 +- tests/ui/mut_mut_unfixable.stderr | 4 +- 4 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 tests/ui/mut_mut.fixed diff --git a/clippy_lints/src/mut_mut.rs b/clippy_lints/src/mut_mut.rs index d98c70e7f5a85..c8b4ad3f7722c 100644 --- a/clippy_lints/src/mut_mut.rs +++ b/clippy_lints/src/mut_mut.rs @@ -1,5 +1,7 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_hir}; +use clippy_utils::diagnostics::{span_lint, span_lint_hir, span_lint_hir_and_then}; use clippy_utils::higher; +use clippy_utils::sugg::Sugg; +use rustc_errors::Applicability; use rustc_hir::{self as hir, AmbigArg, intravisit}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty; @@ -80,12 +82,17 @@ impl<'tcx> intravisit::Visitor<'tcx> for MutVisitor<'_, 'tcx> { } else if let ty::Ref(_, ty, hir::Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind() && ty.peel_refs().is_sized(self.cx.tcx, self.cx.typing_env()) { - span_lint_hir( + let mut applicability = Applicability::MaybeIncorrect; + let sugg = Sugg::hir_with_applicability(self.cx, e, "..", &mut applicability).mut_addr_deref(); + span_lint_hir_and_then( self.cx, MUT_MUT, expr.hir_id, expr.span, - "this expression mutably borrows a mutable reference. Consider reborrowing", + "this expression mutably borrows a mutable reference", + |diag| { + diag.span_suggestion(expr.span, "reborrow instead", sugg, applicability); + }, ); } } diff --git a/tests/ui/mut_mut.fixed b/tests/ui/mut_mut.fixed new file mode 100644 index 0000000000000..cbce826b6e85d --- /dev/null +++ b/tests/ui/mut_mut.fixed @@ -0,0 +1,94 @@ +//@aux-build:proc_macros.rs + +#![warn(clippy::mut_mut)] +#![allow(unused)] +#![allow( + clippy::no_effect, + clippy::uninlined_format_args, + clippy::unnecessary_operation, + clippy::needless_pass_by_ref_mut +)] + +extern crate proc_macros; +use proc_macros::{external, inline_macros}; + +fn fun(x: &mut &mut u32) { + //~^ mut_mut +} + +fn less_fun(x: *mut *mut u32) { + let y = x; +} + +macro_rules! mut_ptr { + ($p:expr) => { + &mut $p + }; +} + +#[allow(unused_mut, unused_variables)] +#[inline_macros] +fn main() { + let mut x = &mut &mut 1u32; + //~^ mut_mut + { + let mut y = &mut *x; + //~^ mut_mut + } + + { + let y: &mut &mut u32 = &mut &mut 2; + //~^ mut_mut + //~| mut_mut + } + + { + let y: &mut &mut &mut u32 = &mut &mut &mut 2; + //~^ mut_mut + //~| mut_mut + //~| mut_mut + } + + let mut z = inline!(&mut $(&mut 3u32)); + //~^ mut_mut +} + +fn issue939() { + let array = [5, 6, 7, 8, 9]; + let mut args = array.iter().skip(2); + for &arg in &mut args { + println!("{}", arg); + } + + let args = &mut args; + for arg in args { + println!(":{}", arg); + } +} + +fn issue6922() { + // do not lint from an external macro + external!(let mut_mut_ty: &mut &mut u32 = &mut &mut 1u32;); +} + +mod issue9035 { + use std::fmt::Display; + + struct Foo<'a> { + inner: &'a mut dyn Display, + } + + impl Foo<'_> { + fn foo(&mut self) { + let hlp = &mut self.inner; + bar(hlp); + } + } + + fn bar(_: &mut impl Display) {} +} + +fn allow_works() { + #[allow(clippy::mut_mut)] + let _ = &mut &mut 1; +} diff --git a/tests/ui/mut_mut.stderr b/tests/ui/mut_mut.stderr index 8517c4e5ad89f..aa4731e8fe041 100644 --- a/tests/ui/mut_mut.stderr +++ b/tests/ui/mut_mut.stderr @@ -21,11 +21,11 @@ LL | let mut z = inline!(&mut $(&mut 3u32)); | = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) -error: this expression mutably borrows a mutable reference. Consider reborrowing +error: this expression mutably borrows a mutable reference --> tests/ui/mut_mut.rs:35:21 | LL | let mut y = &mut x; - | ^^^^^^ + | ^^^^^^ help: reborrow instead: `&mut *x` error: generally you want to avoid `&mut &mut _` if possible --> tests/ui/mut_mut.rs:40:32 diff --git a/tests/ui/mut_mut_unfixable.stderr b/tests/ui/mut_mut_unfixable.stderr index bb0363ef7ed9c..835b272258d39 100644 --- a/tests/ui/mut_mut_unfixable.stderr +++ b/tests/ui/mut_mut_unfixable.stderr @@ -13,11 +13,11 @@ error: generally you want to avoid `&mut &mut _` if possible LL | let mut x = &mut &mut 1u32; | ^^^^^^^^^^^^^^ -error: this expression mutably borrows a mutable reference. Consider reborrowing +error: this expression mutably borrows a mutable reference --> tests/ui/mut_mut_unfixable.rs:18:21 | LL | let mut y = &mut x; - | ^^^^^^ + | ^^^^^^ help: reborrow instead: `&mut *x` error: generally you want to avoid `&mut &mut _` if possible --> tests/ui/mut_mut_unfixable.rs:24:17 From 70be286cefb2dae0a708e0351c09a6a1170cbd54 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 5 Aug 2025 12:14:09 +0200 Subject: [PATCH 52/93] sugg on expr 2 --- clippy_lints/src/mut_mut.rs | 11 ++++++++--- tests/ui/mut_mut.fixed | 8 ++++---- tests/ui/mut_mut.stderr | 16 ++++++++-------- tests/ui/mut_mut_unfixable.stderr | 12 ++++++------ 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/clippy_lints/src/mut_mut.rs b/clippy_lints/src/mut_mut.rs index c8b4ad3f7722c..0feed460d2ed7 100644 --- a/clippy_lints/src/mut_mut.rs +++ b/clippy_lints/src/mut_mut.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_hir, span_lint_hir_and_then}; +use clippy_utils::diagnostics::{span_lint, span_lint_hir_and_then}; use clippy_utils::higher; use clippy_utils::sugg::Sugg; use rustc_errors::Applicability; @@ -72,12 +72,17 @@ impl<'tcx> intravisit::Visitor<'tcx> for MutVisitor<'_, 'tcx> { intravisit::walk_expr(self, body); } else if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, e) = expr.kind { if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, _) = e.kind { - span_lint_hir( + let mut applicability = Applicability::MaybeIncorrect; + let sugg = Sugg::hir_with_applicability(self.cx, e, "..", &mut applicability); + span_lint_hir_and_then( self.cx, MUT_MUT, expr.hir_id, expr.span, - "generally you want to avoid `&mut &mut _` if possible", + "an expression of form `&mut &mut _`", + |diag| { + diag.span_suggestion(expr.span, "remove the extra `&mut`", sugg, applicability); + }, ); } else if let ty::Ref(_, ty, hir::Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind() && ty.peel_refs().is_sized(self.cx.tcx, self.cx.typing_env()) diff --git a/tests/ui/mut_mut.fixed b/tests/ui/mut_mut.fixed index cbce826b6e85d..c6b318d765104 100644 --- a/tests/ui/mut_mut.fixed +++ b/tests/ui/mut_mut.fixed @@ -29,7 +29,7 @@ macro_rules! mut_ptr { #[allow(unused_mut, unused_variables)] #[inline_macros] fn main() { - let mut x = &mut &mut 1u32; + let mut x = &mut 1u32; //~^ mut_mut { let mut y = &mut *x; @@ -37,19 +37,19 @@ fn main() { } { - let y: &mut &mut u32 = &mut &mut 2; + let y: &mut &mut u32 = &mut 2; //~^ mut_mut //~| mut_mut } { - let y: &mut &mut &mut u32 = &mut &mut &mut 2; + let y: &mut &mut &mut u32 = &mut &mut 2; //~^ mut_mut //~| mut_mut //~| mut_mut } - let mut z = inline!(&mut $(&mut 3u32)); + let mut z = inline!(&mut 3u32mut $(&mut 3u32)); //~^ mut_mut } diff --git a/tests/ui/mut_mut.stderr b/tests/ui/mut_mut.stderr index aa4731e8fe041..f21de359e64f7 100644 --- a/tests/ui/mut_mut.stderr +++ b/tests/ui/mut_mut.stderr @@ -7,17 +7,17 @@ LL | fn fun(x: &mut &mut u32) { = note: `-D clippy::mut-mut` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::mut_mut)]` -error: generally you want to avoid `&mut &mut _` if possible +error: an expression of form `&mut &mut _` --> tests/ui/mut_mut.rs:32:17 | LL | let mut x = &mut &mut 1u32; - | ^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut 1u32` -error: generally you want to avoid `&mut &mut _` if possible +error: an expression of form `&mut &mut _` --> tests/ui/mut_mut.rs:52:25 | LL | let mut z = inline!(&mut $(&mut 3u32)); - | ^ + | ^ help: remove the extra `&mut`: `&mut 3u32` | = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -27,11 +27,11 @@ error: this expression mutably borrows a mutable reference LL | let mut y = &mut x; | ^^^^^^ help: reborrow instead: `&mut *x` -error: generally you want to avoid `&mut &mut _` if possible +error: an expression of form `&mut &mut _` --> tests/ui/mut_mut.rs:40:32 | LL | let y: &mut &mut u32 = &mut &mut 2; - | ^^^^^^^^^^^ + | ^^^^^^^^^^^ help: remove the extra `&mut`: `&mut 2` error: generally you want to avoid `&mut &mut _` if possible --> tests/ui/mut_mut.rs:40:16 @@ -39,11 +39,11 @@ error: generally you want to avoid `&mut &mut _` if possible LL | let y: &mut &mut u32 = &mut &mut 2; | ^^^^^^^^^^^^^ -error: generally you want to avoid `&mut &mut _` if possible +error: an expression of form `&mut &mut _` --> tests/ui/mut_mut.rs:46:37 | LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; - | ^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut &mut 2` error: generally you want to avoid `&mut &mut _` if possible --> tests/ui/mut_mut.rs:46:16 diff --git a/tests/ui/mut_mut_unfixable.stderr b/tests/ui/mut_mut_unfixable.stderr index 835b272258d39..129e5b24387e1 100644 --- a/tests/ui/mut_mut_unfixable.stderr +++ b/tests/ui/mut_mut_unfixable.stderr @@ -7,11 +7,11 @@ LL | fn fun(x: &mut &mut u32) -> bool { = note: `-D clippy::mut-mut` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::mut_mut)]` -error: generally you want to avoid `&mut &mut _` if possible +error: an expression of form `&mut &mut _` --> tests/ui/mut_mut_unfixable.rs:15:17 | LL | let mut x = &mut &mut 1u32; - | ^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut 1u32` error: this expression mutably borrows a mutable reference --> tests/ui/mut_mut_unfixable.rs:18:21 @@ -19,17 +19,17 @@ error: this expression mutably borrows a mutable reference LL | let mut y = &mut x; | ^^^^^^ help: reborrow instead: `&mut *x` -error: generally you want to avoid `&mut &mut _` if possible +error: an expression of form `&mut &mut _` --> tests/ui/mut_mut_unfixable.rs:24:17 | LL | let y = &mut &mut 2; - | ^^^^^^^^^^^ + | ^^^^^^^^^^^ help: remove the extra `&mut`: `&mut 2` -error: generally you want to avoid `&mut &mut _` if possible +error: an expression of form `&mut &mut _` --> tests/ui/mut_mut_unfixable.rs:30:17 | LL | let y = &mut &mut &mut 2; - | ^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut &mut 2` error: aborting due to 5 previous errors From 269870a5f9b48117ba9bac65e8b9549df421e9c2 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 4 Aug 2025 20:21:19 +0200 Subject: [PATCH 53/93] fix: don't lint in macro calls --- clippy_lints/src/mut_mut.rs | 3 +++ tests/ui/mut_mut.fixed | 3 +-- tests/ui/mut_mut.rs | 1 - tests/ui/mut_mut.stderr | 10 +--------- 4 files changed, 5 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/mut_mut.rs b/clippy_lints/src/mut_mut.rs index 0feed460d2ed7..376d7214c267d 100644 --- a/clippy_lints/src/mut_mut.rs +++ b/clippy_lints/src/mut_mut.rs @@ -72,6 +72,9 @@ impl<'tcx> intravisit::Visitor<'tcx> for MutVisitor<'_, 'tcx> { intravisit::walk_expr(self, body); } else if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, e) = expr.kind { if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, _) = e.kind { + if !expr.span.eq_ctxt(e.span) { + return; + } let mut applicability = Applicability::MaybeIncorrect; let sugg = Sugg::hir_with_applicability(self.cx, e, "..", &mut applicability); span_lint_hir_and_then( diff --git a/tests/ui/mut_mut.fixed b/tests/ui/mut_mut.fixed index c6b318d765104..137306064b439 100644 --- a/tests/ui/mut_mut.fixed +++ b/tests/ui/mut_mut.fixed @@ -49,8 +49,7 @@ fn main() { //~| mut_mut } - let mut z = inline!(&mut 3u32mut $(&mut 3u32)); - //~^ mut_mut + let mut z = inline!(&mut $(&mut 3u32)); } fn issue939() { diff --git a/tests/ui/mut_mut.rs b/tests/ui/mut_mut.rs index d006578b3d284..92ecfbfc02d12 100644 --- a/tests/ui/mut_mut.rs +++ b/tests/ui/mut_mut.rs @@ -50,7 +50,6 @@ fn main() { } let mut z = inline!(&mut $(&mut 3u32)); - //~^ mut_mut } fn issue939() { diff --git a/tests/ui/mut_mut.stderr b/tests/ui/mut_mut.stderr index f21de359e64f7..5f7c9b4fe23f0 100644 --- a/tests/ui/mut_mut.stderr +++ b/tests/ui/mut_mut.stderr @@ -13,14 +13,6 @@ error: an expression of form `&mut &mut _` LL | let mut x = &mut &mut 1u32; | ^^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut 1u32` -error: an expression of form `&mut &mut _` - --> tests/ui/mut_mut.rs:52:25 - | -LL | let mut z = inline!(&mut $(&mut 3u32)); - | ^ help: remove the extra `&mut`: `&mut 3u32` - | - = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) - error: this expression mutably borrows a mutable reference --> tests/ui/mut_mut.rs:35:21 | @@ -57,5 +49,5 @@ error: generally you want to avoid `&mut &mut _` if possible LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; | ^^^^^^^^^^^^^ -error: aborting due to 9 previous errors +error: aborting due to 8 previous errors From 729d92cf0405ef8ee06e76b9e59b4892caa173f0 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 4 Aug 2025 00:12:47 +0200 Subject: [PATCH 54/93] sugg on ty --- clippy_lints/src/mut_mut.rs | 16 +++++++++++----- tests/ui/mut_mut.stderr | 16 ++++++++-------- tests/ui/mut_mut_unfixable.stderr | 4 ++-- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/clippy_lints/src/mut_mut.rs b/clippy_lints/src/mut_mut.rs index 376d7214c267d..7d7d4fa542c19 100644 --- a/clippy_lints/src/mut_mut.rs +++ b/clippy_lints/src/mut_mut.rs @@ -1,5 +1,6 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_hir_and_then}; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::higher; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use rustc_errors::Applicability; use rustc_hir::{self as hir, AmbigArg, intravisit}; @@ -37,15 +38,20 @@ impl<'tcx> LateLintPass<'tcx> for MutMut { fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'_, AmbigArg>) { if let hir::TyKind::Ref(_, mty) = ty.kind && mty.mutbl == hir::Mutability::Mut - && let hir::TyKind::Ref(_, mty) = mty.ty.kind - && mty.mutbl == hir::Mutability::Mut + && let hir::TyKind::Ref(_, mty2) = mty.ty.kind + && mty2.mutbl == hir::Mutability::Mut && !ty.span.in_external_macro(cx.sess().source_map()) { - span_lint( + let mut applicability = Applicability::MaybeIncorrect; + let sugg = snippet_with_applicability(cx.sess(), mty.ty.span, "..", &mut applicability); + span_lint_and_sugg( cx, MUT_MUT, ty.span, - "generally you want to avoid `&mut &mut _` if possible", + "a type of form `&mut &mut _`", + "remove the extra `&mut`", + sugg.to_string(), + applicability, ); } } diff --git a/tests/ui/mut_mut.stderr b/tests/ui/mut_mut.stderr index 5f7c9b4fe23f0..341ac643417c7 100644 --- a/tests/ui/mut_mut.stderr +++ b/tests/ui/mut_mut.stderr @@ -1,8 +1,8 @@ -error: generally you want to avoid `&mut &mut _` if possible +error: a type of form `&mut &mut _` --> tests/ui/mut_mut.rs:15:11 | LL | fn fun(x: &mut &mut u32) { - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut u32` | = note: `-D clippy::mut-mut` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::mut_mut)]` @@ -25,11 +25,11 @@ error: an expression of form `&mut &mut _` LL | let y: &mut &mut u32 = &mut &mut 2; | ^^^^^^^^^^^ help: remove the extra `&mut`: `&mut 2` -error: generally you want to avoid `&mut &mut _` if possible +error: a type of form `&mut &mut _` --> tests/ui/mut_mut.rs:40:16 | LL | let y: &mut &mut u32 = &mut &mut 2; - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut u32` error: an expression of form `&mut &mut _` --> tests/ui/mut_mut.rs:46:37 @@ -37,17 +37,17 @@ error: an expression of form `&mut &mut _` LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; | ^^^^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut &mut 2` -error: generally you want to avoid `&mut &mut _` if possible +error: a type of form `&mut &mut _` --> tests/ui/mut_mut.rs:46:16 | LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; - | ^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut &mut u32` -error: generally you want to avoid `&mut &mut _` if possible +error: a type of form `&mut &mut _` --> tests/ui/mut_mut.rs:46:21 | LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut u32` error: aborting due to 8 previous errors diff --git a/tests/ui/mut_mut_unfixable.stderr b/tests/ui/mut_mut_unfixable.stderr index 129e5b24387e1..da820f766d027 100644 --- a/tests/ui/mut_mut_unfixable.stderr +++ b/tests/ui/mut_mut_unfixable.stderr @@ -1,8 +1,8 @@ -error: generally you want to avoid `&mut &mut _` if possible +error: a type of form `&mut &mut _` --> tests/ui/mut_mut_unfixable.rs:9:11 | LL | fn fun(x: &mut &mut u32) -> bool { - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut u32` | = note: `-D clippy::mut-mut` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::mut_mut)]` From c6971c22c1450abaa9f4913db60725df85632e21 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 5 Aug 2025 12:37:20 +0200 Subject: [PATCH 55/93] only lint once on 3+ `&mut`s for some reason this seems to already be the case with exprs, so only do it for tys --- clippy_lints/src/lib.rs | 2 +- clippy_lints/src/mut_mut.rs | 20 +++++++++++++++++--- tests/ui/mut_mut.fixed | 7 +++---- tests/ui/mut_mut.rs | 1 - tests/ui/mut_mut.stderr | 8 +------- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 51dabee78e9fb..733113fe0d332 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -482,7 +482,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co 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)); - store.register_late_pass(|_| Box::new(mut_mut::MutMut)); + store.register_late_pass(|_| Box::new(mut_mut::MutMut::default())); store.register_late_pass(|_| Box::new(unnecessary_mut_passed::UnnecessaryMutPassed)); store.register_late_pass(|_| Box::>::default()); store.register_late_pass(|_| Box::new(len_zero::LenZero)); diff --git a/clippy_lints/src/mut_mut.rs b/clippy_lints/src/mut_mut.rs index 7d7d4fa542c19..968d99143c4b1 100644 --- a/clippy_lints/src/mut_mut.rs +++ b/clippy_lints/src/mut_mut.rs @@ -2,11 +2,12 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::higher; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; +use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir::{self as hir, AmbigArg, intravisit}; +use rustc_hir::{self as hir, AmbigArg, HirId, intravisit}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; declare_clippy_lint! { /// ### What it does @@ -28,7 +29,12 @@ declare_clippy_lint! { "usage of double-mut refs, e.g., `&mut &mut ...`" } -declare_lint_pass!(MutMut => [MUT_MUT]); +impl_lint_pass!(MutMut => [MUT_MUT]); + +#[derive(Default)] +pub(crate) struct MutMut { + seen_tys: FxHashSet, +} impl<'tcx> LateLintPass<'tcx> for MutMut { fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { @@ -42,6 +48,14 @@ impl<'tcx> LateLintPass<'tcx> for MutMut { && mty2.mutbl == hir::Mutability::Mut && !ty.span.in_external_macro(cx.sess().source_map()) { + if self.seen_tys.contains(&ty.hir_id) { + // we have 2+ `&mut`s, e.g., `&mut &mut &mut x` + // and we have already flagged on the outermost `&mut &mut (&mut x)`, + // so don't flag the inner `&mut &mut (x)` + return; + } + self.seen_tys.insert(mty.ty.hir_id); + let mut applicability = Applicability::MaybeIncorrect; let sugg = snippet_with_applicability(cx.sess(), mty.ty.span, "..", &mut applicability); span_lint_and_sugg( diff --git a/tests/ui/mut_mut.fixed b/tests/ui/mut_mut.fixed index 137306064b439..d89129e70b004 100644 --- a/tests/ui/mut_mut.fixed +++ b/tests/ui/mut_mut.fixed @@ -12,7 +12,7 @@ extern crate proc_macros; use proc_macros::{external, inline_macros}; -fn fun(x: &mut &mut u32) { +fn fun(x: &mut u32) { //~^ mut_mut } @@ -37,16 +37,15 @@ fn main() { } { - let y: &mut &mut u32 = &mut 2; + let y: &mut u32 = &mut 2; //~^ mut_mut //~| mut_mut } { - let y: &mut &mut &mut u32 = &mut &mut 2; + let y: &mut &mut u32 = &mut &mut 2; //~^ mut_mut //~| mut_mut - //~| mut_mut } let mut z = inline!(&mut $(&mut 3u32)); diff --git a/tests/ui/mut_mut.rs b/tests/ui/mut_mut.rs index 92ecfbfc02d12..bbec48011a17e 100644 --- a/tests/ui/mut_mut.rs +++ b/tests/ui/mut_mut.rs @@ -46,7 +46,6 @@ fn main() { let y: &mut &mut &mut u32 = &mut &mut &mut 2; //~^ mut_mut //~| mut_mut - //~| mut_mut } let mut z = inline!(&mut $(&mut 3u32)); diff --git a/tests/ui/mut_mut.stderr b/tests/ui/mut_mut.stderr index 341ac643417c7..b30a64d8fe74c 100644 --- a/tests/ui/mut_mut.stderr +++ b/tests/ui/mut_mut.stderr @@ -43,11 +43,5 @@ error: a type of form `&mut &mut _` LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; | ^^^^^^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut &mut u32` -error: a type of form `&mut &mut _` - --> tests/ui/mut_mut.rs:46:21 - | -LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; - | ^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut u32` - -error: aborting due to 8 previous errors +error: aborting due to 7 previous errors From 4053c446541940e878b7a9b4a92cb2c4696b3340 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 5 Aug 2025 13:31:04 +0200 Subject: [PATCH 56/93] misc: import more from `hir` --- clippy_lints/src/mut_mut.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/mut_mut.rs b/clippy_lints/src/mut_mut.rs index 968d99143c4b1..574c0c8f8de0b 100644 --- a/clippy_lints/src/mut_mut.rs +++ b/clippy_lints/src/mut_mut.rs @@ -4,7 +4,7 @@ use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir::{self as hir, AmbigArg, HirId, intravisit}; +use rustc_hir::{self as hir, AmbigArg, BorrowKind, Expr, ExprKind, HirId, Mutability, TyKind, intravisit}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty; use rustc_session::impl_lint_pass; @@ -42,10 +42,10 @@ impl<'tcx> LateLintPass<'tcx> for MutMut { } fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'_, AmbigArg>) { - if let hir::TyKind::Ref(_, mty) = ty.kind - && mty.mutbl == hir::Mutability::Mut - && let hir::TyKind::Ref(_, mty2) = mty.ty.kind - && mty2.mutbl == hir::Mutability::Mut + if let TyKind::Ref(_, mty) = ty.kind + && mty.mutbl == Mutability::Mut + && let TyKind::Ref(_, mty2) = mty.ty.kind + && mty2.mutbl == Mutability::Mut && !ty.span.in_external_macro(cx.sess().source_map()) { if self.seen_tys.contains(&ty.hir_id) { @@ -76,7 +76,7 @@ pub struct MutVisitor<'a, 'tcx> { } impl<'tcx> intravisit::Visitor<'tcx> for MutVisitor<'_, 'tcx> { - fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { if expr.span.in_external_macro(self.cx.sess().source_map()) { return; } @@ -90,8 +90,8 @@ impl<'tcx> intravisit::Visitor<'tcx> for MutVisitor<'_, 'tcx> { // Let's ignore the generated code. intravisit::walk_expr(self, arg); intravisit::walk_expr(self, body); - } else if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, e) = expr.kind { - if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, _) = e.kind { + } else if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e) = expr.kind { + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) = e.kind { if !expr.span.eq_ctxt(e.span) { return; } @@ -107,7 +107,7 @@ impl<'tcx> intravisit::Visitor<'tcx> for MutVisitor<'_, 'tcx> { diag.span_suggestion(expr.span, "remove the extra `&mut`", sugg, applicability); }, ); - } else if let ty::Ref(_, ty, hir::Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind() + } else if let ty::Ref(_, ty, Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind() && ty.peel_refs().is_sized(self.cx.tcx, self.cx.typing_env()) { let mut applicability = Applicability::MaybeIncorrect; From 9a1b2bcc9f82f14e8a5215a2a7b9229c7f1df390 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 5 Aug 2025 13:49:43 +0200 Subject: [PATCH 57/93] peel _all_ extra `&mut`s see https://rust-lang.zulipchat.com/#narrow/channel/257328-clippy/topic/problem.20when.20suggestion.20only.20removes.20one.20.22level.22.20of.20a.20lint/near/532889973 --- clippy_lints/src/mut_mut.rs | 47 ++++++++++++++++++++++++++++--- tests/ui/mut_mut.fixed | 2 +- tests/ui/mut_mut.stderr | 4 +-- tests/ui/mut_mut_unfixable.stderr | 2 +- 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/mut_mut.rs b/clippy_lints/src/mut_mut.rs index 574c0c8f8de0b..d66daf0c785f8 100644 --- a/clippy_lints/src/mut_mut.rs +++ b/clippy_lints/src/mut_mut.rs @@ -56,14 +56,30 @@ impl<'tcx> LateLintPass<'tcx> for MutMut { } self.seen_tys.insert(mty.ty.hir_id); + // if there is an even longer chain, like `&mut &mut &mut x`, suggest peeling off + // all extra ones at once + let (mut t, mut t2) = (mty.ty, mty2.ty); + let mut many_muts = false; + loop { + if let TyKind::Ref(_, next) = t2.kind + && next.mutbl == Mutability::Mut + { + (t, t2) = (t2, next.ty); + many_muts = true; + } else { + break; + } + } + let mut applicability = Applicability::MaybeIncorrect; - let sugg = snippet_with_applicability(cx.sess(), mty.ty.span, "..", &mut applicability); + let sugg = snippet_with_applicability(cx.sess(), t.span, "..", &mut applicability); + let suffix = if many_muts { "s" } else { "" }; span_lint_and_sugg( cx, MUT_MUT, ty.span, "a type of form `&mut &mut _`", - "remove the extra `&mut`", + format!("remove the extra `&mut`{suffix}"), sugg.to_string(), applicability, ); @@ -91,12 +107,30 @@ impl<'tcx> intravisit::Visitor<'tcx> for MutVisitor<'_, 'tcx> { intravisit::walk_expr(self, arg); intravisit::walk_expr(self, body); } else if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e) = expr.kind { - if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) = e.kind { + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e2) = e.kind { if !expr.span.eq_ctxt(e.span) { return; } + + // if there is an even longer chain, like `&mut &mut &mut x`, suggest peeling off + // all extra ones at once + let (mut e, mut e2) = (e, e2); + let mut many_muts = false; + loop { + if !e.span.eq_ctxt(e2.span) { + return; + } + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, next) = e2.kind { + (e, e2) = (e2, next); + many_muts = true; + } else { + break; + } + } + let mut applicability = Applicability::MaybeIncorrect; let sugg = Sugg::hir_with_applicability(self.cx, e, "..", &mut applicability); + let suffix = if many_muts { "s" } else { "" }; span_lint_hir_and_then( self.cx, MUT_MUT, @@ -104,7 +138,12 @@ impl<'tcx> intravisit::Visitor<'tcx> for MutVisitor<'_, 'tcx> { expr.span, "an expression of form `&mut &mut _`", |diag| { - diag.span_suggestion(expr.span, "remove the extra `&mut`", sugg, applicability); + diag.span_suggestion( + expr.span, + format!("remove the extra `&mut`{suffix}"), + sugg, + applicability, + ); }, ); } else if let ty::Ref(_, ty, Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind() diff --git a/tests/ui/mut_mut.fixed b/tests/ui/mut_mut.fixed index d89129e70b004..f9a7f5dcb5a15 100644 --- a/tests/ui/mut_mut.fixed +++ b/tests/ui/mut_mut.fixed @@ -43,7 +43,7 @@ fn main() { } { - let y: &mut &mut u32 = &mut &mut 2; + let y: &mut u32 = &mut 2; //~^ mut_mut //~| mut_mut } diff --git a/tests/ui/mut_mut.stderr b/tests/ui/mut_mut.stderr index b30a64d8fe74c..85e9c5649b89a 100644 --- a/tests/ui/mut_mut.stderr +++ b/tests/ui/mut_mut.stderr @@ -35,13 +35,13 @@ error: an expression of form `&mut &mut _` --> tests/ui/mut_mut.rs:46:37 | LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; - | ^^^^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut &mut 2` + | ^^^^^^^^^^^^^^^^ help: remove the extra `&mut`s: `&mut 2` error: a type of form `&mut &mut _` --> tests/ui/mut_mut.rs:46:16 | LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2; - | ^^^^^^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut &mut u32` + | ^^^^^^^^^^^^^^^^^^ help: remove the extra `&mut`s: `&mut u32` error: aborting due to 7 previous errors diff --git a/tests/ui/mut_mut_unfixable.stderr b/tests/ui/mut_mut_unfixable.stderr index da820f766d027..e967ed3537256 100644 --- a/tests/ui/mut_mut_unfixable.stderr +++ b/tests/ui/mut_mut_unfixable.stderr @@ -29,7 +29,7 @@ error: an expression of form `&mut &mut _` --> tests/ui/mut_mut_unfixable.rs:30:17 | LL | let y = &mut &mut &mut 2; - | ^^^^^^^^^^^^^^^^ help: remove the extra `&mut`: `&mut &mut 2` + | ^^^^^^^^^^^^^^^^ help: remove the extra `&mut`s: `&mut 2` error: aborting due to 5 previous errors From 554680683f69f142e97d502b941c0ce8797ad0c6 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 5 Aug 2025 14:06:50 +0200 Subject: [PATCH 58/93] remember all the nested types while we loop see https://rust-lang.zulipchat.com/#narrow/channel/25732 --- clippy_lints/src/mut_mut.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/mut_mut.rs b/clippy_lints/src/mut_mut.rs index d66daf0c785f8..75b8aa9c90cc1 100644 --- a/clippy_lints/src/mut_mut.rs +++ b/clippy_lints/src/mut_mut.rs @@ -54,13 +54,15 @@ impl<'tcx> LateLintPass<'tcx> for MutMut { // so don't flag the inner `&mut &mut (x)` return; } - self.seen_tys.insert(mty.ty.hir_id); // if there is an even longer chain, like `&mut &mut &mut x`, suggest peeling off // all extra ones at once let (mut t, mut t2) = (mty.ty, mty2.ty); let mut many_muts = false; loop { + // this should allow us to remember all the nested types, so that the `contains` + // above fails faster + self.seen_tys.insert(t.hir_id); if let TyKind::Ref(_, next) = t2.kind && next.mutbl == Mutability::Mut { From 75b68606bc11529c2dd0e0f12dd317b74d40409c Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 5 Aug 2025 14:12:41 +0200 Subject: [PATCH 59/93] add an unfixable case as recommended in https://rust-lang.zulipchat.com/#narrow/channel/257328-clippy/topic/problem.20when.20suggestion.20only.20removes.20one.20.22level.22.20of.20a.20lint/near/532894687 --- tests/ui/mut_mut_unfixable.rs | 8 ++++++++ tests/ui/mut_mut_unfixable.stderr | 8 +++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/ui/mut_mut_unfixable.rs b/tests/ui/mut_mut_unfixable.rs index db0552a86ec9b..271cb7b968898 100644 --- a/tests/ui/mut_mut_unfixable.rs +++ b/tests/ui/mut_mut_unfixable.rs @@ -31,4 +31,12 @@ fn main() { //~^ mut_mut ***y + **x; } + + if fun(x) { + // The lint will remove the extra `&mut`, but the result will still be a `&mut` of an expr + // of type `&mut _` (x), so the lint will fire again. That's because we've decided that + // doing both fixes in one run is not worth it, given how improbable code like this is. + let y = &mut &mut x; + //~^ mut_mut + } } diff --git a/tests/ui/mut_mut_unfixable.stderr b/tests/ui/mut_mut_unfixable.stderr index e967ed3537256..cf66eb2ed1eca 100644 --- a/tests/ui/mut_mut_unfixable.stderr +++ b/tests/ui/mut_mut_unfixable.stderr @@ -31,5 +31,11 @@ error: an expression of form `&mut &mut _` LL | let y = &mut &mut &mut 2; | ^^^^^^^^^^^^^^^^ help: remove the extra `&mut`s: `&mut 2` -error: aborting due to 5 previous errors +error: an expression of form `&mut &mut _` + --> tests/ui/mut_mut_unfixable.rs:39:17 + | +LL | let y = &mut &mut x; + | ^^^^^^^^^^^ help: remove the extra `&mut`: `&mut x` + +error: aborting due to 6 previous errors From dc534c49b3ce818ade236527149a94a51ea91a34 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 5 Aug 2025 14:14:41 +0200 Subject: [PATCH 60/93] doc: add more examples, and a "Use instead" section, don't be condescending --- clippy_lints/src/mut_mut.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/mut_mut.rs b/clippy_lints/src/mut_mut.rs index 75b8aa9c90cc1..588afd85afb02 100644 --- a/clippy_lints/src/mut_mut.rs +++ b/clippy_lints/src/mut_mut.rs @@ -14,19 +14,30 @@ declare_clippy_lint! { /// Checks for instances of `mut mut` references. /// /// ### Why is this bad? - /// Multiple `mut`s don't add anything meaningful to the - /// source. This is either a copy'n'paste error, or it shows a fundamental - /// misunderstanding of references. + /// This is usually just a typo or a misunderstanding of how references work. /// /// ### Example /// ```no_run - /// # let mut y = 1; - /// let x = &mut &mut y; + /// let x = &mut &mut 1; + /// + /// let mut x = &mut 1; + /// let y = &mut x; + /// + /// fn foo(x: &mut &mut u32) {} + /// ``` + /// Use instead + /// ```no_run + /// let x = &mut 1; + /// + /// let mut x = &mut 1; + /// let y = &mut *x; // reborrow + /// + /// fn foo(x: &mut u32) {} /// ``` #[clippy::version = "pre 1.29.0"] pub MUT_MUT, pedantic, - "usage of double-mut refs, e.g., `&mut &mut ...`" + "usage of double mut-refs, e.g., `&mut &mut ...`" } impl_lint_pass!(MutMut => [MUT_MUT]); From 6b4febeeb7be931c9317bf987604bbd94967cd94 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 28 Sep 2025 15:39:57 +0200 Subject: [PATCH 61/93] simplify and inline `is_async_fn` --- clippy_lints/src/cognitive_complexity.rs | 4 ++-- clippy_lints/src/implicit_return.rs | 4 ++-- clippy_utils/src/lib.rs | 11 +---------- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs index 7646aa48b7726..c0f30e456d8db 100644 --- a/clippy_lints/src/cognitive_complexity.rs +++ b/clippy_lints/src/cognitive_complexity.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::source::{IntoSpan, SpanRangeExt}; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::visitors::for_each_expr_without_closures; -use clippy_utils::{LimitStack, get_async_fn_body, is_async_fn, sym}; +use clippy_utils::{LimitStack, get_async_fn_body, sym}; use core::ops::ControlFlow; use rustc_hir::intravisit::FnKind; use rustc_hir::{Attribute, Body, Expr, ExprKind, FnDecl}; @@ -147,7 +147,7 @@ impl<'tcx> LateLintPass<'tcx> for CognitiveComplexity { def_id: LocalDefId, ) { if !cx.tcx.has_attr(def_id, sym::test) { - let expr = if is_async_fn(kind) { + let expr = if kind.asyncness().is_async() { match get_async_fn_body(cx.tcx, body) { Some(b) => b, None => { diff --git a/clippy_lints/src/implicit_return.rs b/clippy_lints/src/implicit_return.rs index 076017a247b4b..6ed478b2708a3 100644 --- a/clippy_lints/src/implicit_return.rs +++ b/clippy_lints/src/implicit_return.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context}; use clippy_utils::visitors::for_each_expr_without_closures; -use clippy_utils::{desugar_await, get_async_closure_expr, get_async_fn_body, is_async_fn, is_from_proc_macro}; +use clippy_utils::{desugar_await, get_async_closure_expr, get_async_fn_body, is_from_proc_macro}; use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; @@ -240,7 +240,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitReturn { return; } - let expr = if is_async_fn(kind) { + let expr = if kind.asyncness().is_async() { match get_async_fn_body(cx.tcx, body) { Some(e) => e, None => return, diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index d6586625a1e5e..708491df77079 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -98,7 +98,7 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId}; use rustc_hir::definitions::{DefPath, DefPathData}; use rustc_hir::hir_id::{HirIdMap, HirIdSet}; -use rustc_hir::intravisit::{FnKind, Visitor, walk_expr}; +use rustc_hir::intravisit::{Visitor, walk_expr}; use rustc_hir::{ self as hir, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstArgKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs, @@ -1854,15 +1854,6 @@ pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>, (conds, blocks) } -/// Checks if the given function kind is an async function. -pub fn is_async_fn(kind: FnKind<'_>) -> bool { - match kind { - FnKind::ItemFn(_, _, header) => header.asyncness.is_async(), - FnKind::Method(_, sig) => sig.header.asyncness.is_async(), - FnKind::Closure => false, - } -} - /// Peels away all the compiler generated code surrounding the body of an async closure. pub fn get_async_closure_expr<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> { if let ExprKind::Closure(&Closure { From 30f284d15befc66143fee59918ffac209a0ae657 Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Sun, 28 Sep 2025 14:40:39 -0700 Subject: [PATCH 62/93] remove explicit deref of AbiAlign for most methods Much of the compiler calls functions on Align projected from AbiAlign. AbiAlign impls Deref to its inner Align, so we can simplify these away. Also, it will minimize disruption when AbiAlign is removed. For now, preserve usages that might resolve to PartialOrd or PartialEq, as those have odd inference. --- clippy_lints/src/casts/cast_ptr_alignment.rs | 4 ++-- clippy_lints/src/casts/manual_dangling_ptr.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/casts/cast_ptr_alignment.rs b/clippy_lints/src/casts/cast_ptr_alignment.rs index d78da9396faf3..7d14ba7fcf132 100644 --- a/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -43,8 +43,8 @@ fn lint_cast_ptr_alignment<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, cast_f expr.span, format!( "casting from `{cast_from}` to a more-strictly-aligned pointer (`{cast_to}`) ({} < {} bytes)", - from_layout.align.abi.bytes(), - to_layout.align.abi.bytes(), + from_layout.align.bytes(), + to_layout.align.bytes(), ), ); } diff --git a/clippy_lints/src/casts/manual_dangling_ptr.rs b/clippy_lints/src/casts/manual_dangling_ptr.rs index 92910cf8adf5d..ff5320719aa28 100644 --- a/clippy_lints/src/casts/manual_dangling_ptr.rs +++ b/clippy_lints/src/casts/manual_dangling_ptr.rs @@ -72,7 +72,7 @@ fn is_literal_aligned(cx: &LateContext<'_>, lit: &Spanned, to: &Ty<'_>) cx.tcx .layout_of(cx.typing_env().as_query_input(to_mid_ty)) .is_ok_and(|layout| { - let align = u128::from(layout.align.abi.bytes()); + let align = u128::from(layout.align.bytes()); u128::from(val) <= align }) } From f6e223cdb5b848f2eca1ce06aac9f9f1b764abf2 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 29 Sep 2025 00:21:27 +0200 Subject: [PATCH 63/93] clean-up --- clippy_lints/src/methods/mod.rs | 181 ++++++++---------- .../src/methods/wrong_self_convention.rs | 36 ++-- 2 files changed, 97 insertions(+), 120 deletions(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index a1cdab9cc491b..195d91c4e66b1 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -149,13 +149,12 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; use clippy_utils::macros::FormatArgsStorage; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy, is_type_diagnostic_item}; +use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy}; use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty, sym}; pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES; use rustc_abi::ExternAbi; use rustc_data_structures::fx::FxHashSet; -use rustc_hir as hir; -use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind}; +use rustc_hir::{self as hir, Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::{self, TraitRef, Ty}; use rustc_session::impl_lint_pass; @@ -4889,13 +4888,13 @@ impl<'tcx> LateLintPass<'tcx> for Methods { if impl_item.span.in_external_macro(cx.sess().source_map()) { return; } - let name = impl_item.ident.name; - let parent = cx.tcx.hir_get_parent_item(impl_item.hir_id()).def_id; - let item = cx.tcx.hir_expect_item(parent); - let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); - - let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. })); if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind { + let name = impl_item.ident.name; + let parent = cx.tcx.hir_get_parent_item(impl_item.hir_id()).def_id; + let item = cx.tcx.hir_expect_item(parent); + let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); + + let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. })); let method_sig = cx.tcx.fn_sig(impl_item.owner_id).instantiate_identity(); let method_sig = cx.tcx.instantiate_bound_regions_with_erased(method_sig); let first_arg_ty_opt = method_sig.inputs().iter().next().copied(); @@ -4908,9 +4907,8 @@ impl<'tcx> LateLintPass<'tcx> for Methods { && method_config.output_type.matches(&sig.decl.output) // in case there is no first arg, since we already have checked the number of arguments // it's should be always true - && first_arg_ty_opt.is_none_or(|first_arg_ty| method_config - .self_kind.matches(cx, self_ty, first_arg_ty) - ) + && first_arg_ty_opt + .is_none_or(|first_arg_ty| method_config.self_kind.matches(cx, self_ty, first_arg_ty)) && fn_header_equals(method_config.fn_header, sig.header) && method_config.lifetime_param_cond(impl_item) { @@ -4948,21 +4946,14 @@ impl<'tcx> LateLintPass<'tcx> for Methods { false, ); } - } - - // if this impl block implements a trait, lint in trait definition instead - if implements_trait { - return; - } - if let hir::ImplItemKind::Fn(_, _) = impl_item.kind { - let ret_ty = return_ty(cx, impl_item.owner_id); - - if contains_ty_adt_constructor_opaque(cx, ret_ty, self_ty) { - return; - } - - if name == sym::new && ret_ty != self_ty { + // if this impl block implements a trait, lint in trait definition instead + if !implements_trait + && impl_item.ident.name == sym::new + && let ret_ty = return_ty(cx, impl_item.owner_id) + && ret_ty != self_ty + && !contains_ty_adt_constructor_opaque(cx, ret_ty, self_ty) + { span_lint( cx, NEW_RET_NO_SELF, @@ -4978,41 +4969,41 @@ impl<'tcx> LateLintPass<'tcx> for Methods { return; } - if let TraitItemKind::Fn(ref sig, _) = item.kind - && sig.decl.implicit_self.has_implicit_self() - && let Some(first_arg_hir_ty) = sig.decl.inputs.first() - && let Some(&first_arg_ty) = cx - .tcx - .fn_sig(item.owner_id) - .instantiate_identity() - .inputs() - .skip_binder() - .first() - { - let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty(); - wrong_self_convention::check( - cx, - item.ident.name, - self_ty, - first_arg_ty, - first_arg_hir_ty.span, - false, - true, - ); - } + if let TraitItemKind::Fn(ref sig, _) = item.kind { + if sig.decl.implicit_self.has_implicit_self() + && let Some(first_arg_hir_ty) = sig.decl.inputs.first() + && let Some(&first_arg_ty) = cx + .tcx + .fn_sig(item.owner_id) + .instantiate_identity() + .inputs() + .skip_binder() + .first() + { + let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty(); + wrong_self_convention::check( + cx, + item.ident.name, + self_ty, + first_arg_ty, + first_arg_hir_ty.span, + false, + true, + ); + } - if item.ident.name == sym::new - && let TraitItemKind::Fn(_, _) = item.kind - && let ret_ty = return_ty(cx, item.owner_id) - && let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty() - && !ret_ty.contains(self_ty) - { - span_lint( - cx, - NEW_RET_NO_SELF, - item.span, - "methods called `new` usually return `Self`", - ); + if item.ident.name == sym::new + && let ret_ty = return_ty(cx, item.owner_id) + && let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty() + && !ret_ty.contains(self_ty) + { + span_lint( + cx, + NEW_RET_NO_SELF, + item.span, + "methods called `new` usually return `Self`", + ); + } } } } @@ -5776,36 +5767,36 @@ impl ShouldImplTraitCase { #[rustfmt::skip] const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [ - ShouldImplTraitCase::new("std::ops::Add", sym::add, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::convert::AsMut", sym::as_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), - ShouldImplTraitCase::new("std::convert::AsRef", sym::as_ref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true), - ShouldImplTraitCase::new("std::ops::BitAnd", sym::bitand, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::BitOr", sym::bitor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::BitXor", sym::bitxor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::borrow::Borrow", sym::borrow, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true), - ShouldImplTraitCase::new("std::borrow::BorrowMut", sym::borrow_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), - ShouldImplTraitCase::new("std::clone::Clone", sym::clone, 1, FN_HEADER, SelfKind::Ref, OutType::Any, true), - ShouldImplTraitCase::new("std::cmp::Ord", sym::cmp, 2, FN_HEADER, SelfKind::Ref, OutType::Any, true), - ShouldImplTraitCase::new("std::default::Default", kw::Default, 0, FN_HEADER, SelfKind::No, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Deref", sym::deref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true), - ShouldImplTraitCase::new("std::ops::DerefMut", sym::deref_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), - ShouldImplTraitCase::new("std::ops::Div", sym::div, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Drop", sym::drop, 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true), - ShouldImplTraitCase::new("std::cmp::PartialEq", sym::eq, 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true), - ShouldImplTraitCase::new("std::iter::FromIterator", sym::from_iter, 1, FN_HEADER, SelfKind::No, OutType::Any, true), - ShouldImplTraitCase::new("std::str::FromStr", sym::from_str, 1, FN_HEADER, SelfKind::No, OutType::Any, true), - ShouldImplTraitCase::new("std::hash::Hash", sym::hash, 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true), - ShouldImplTraitCase::new("std::ops::Index", sym::index, 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true), - ShouldImplTraitCase::new("std::ops::IndexMut", sym::index_mut, 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), - ShouldImplTraitCase::new("std::iter::IntoIterator", sym::into_iter, 1, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Mul", sym::mul, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Neg", sym::neg, 1, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::iter::Iterator", sym::next, 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false), - ShouldImplTraitCase::new("std::ops::Not", sym::not, 1, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Rem", sym::rem, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Shl", sym::shl, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Shr", sym::shr, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Sub", sym::sub, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::Add", sym::add, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::convert::AsMut", sym::as_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true ), + ShouldImplTraitCase::new("std::convert::AsRef", sym::as_ref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true ), + ShouldImplTraitCase::new("std::ops::BitAnd", sym::bitand, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::BitOr", sym::bitor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::BitXor", sym::bitxor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::borrow::Borrow", sym::borrow, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true ), + ShouldImplTraitCase::new("std::borrow::BorrowMut", sym::borrow_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true ), + ShouldImplTraitCase::new("std::clone::Clone", sym::clone, 1, FN_HEADER, SelfKind::Ref, OutType::Any, true ), + ShouldImplTraitCase::new("std::cmp::Ord", sym::cmp, 2, FN_HEADER, SelfKind::Ref, OutType::Any, true ), + ShouldImplTraitCase::new("std::default::Default", kw::Default, 0, FN_HEADER, SelfKind::No, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::Deref", sym::deref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true ), + ShouldImplTraitCase::new("std::ops::DerefMut", sym::deref_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true ), + ShouldImplTraitCase::new("std::ops::Div", sym::div, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::Drop", sym::drop, 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true ), + ShouldImplTraitCase::new("std::cmp::PartialEq", sym::eq, 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true ), + ShouldImplTraitCase::new("std::iter::FromIterator", sym::from_iter, 1, FN_HEADER, SelfKind::No, OutType::Any, true ), + ShouldImplTraitCase::new("std::str::FromStr", sym::from_str, 1, FN_HEADER, SelfKind::No, OutType::Any, true ), + ShouldImplTraitCase::new("std::hash::Hash", sym::hash, 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true ), + ShouldImplTraitCase::new("std::ops::Index", sym::index, 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true ), + ShouldImplTraitCase::new("std::ops::IndexMut", sym::index_mut, 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true ), + ShouldImplTraitCase::new("std::iter::IntoIterator", sym::into_iter, 1, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::Mul", sym::mul, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::Neg", sym::neg, 1, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::iter::Iterator", sym::next, 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false), + ShouldImplTraitCase::new("std::ops::Not", sym::not, 1, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::Rem", sym::rem, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::Shl", sym::shl, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::Shr", sym::shr, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::Sub", sym::sub, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), ]; #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -5823,12 +5814,10 @@ impl SelfKind { true } else if let Some(boxed_ty) = ty.boxed_ty() { boxed_ty == parent_ty - } else if is_type_diagnostic_item(cx, ty, sym::Rc) || is_type_diagnostic_item(cx, ty, sym::Arc) { - if let ty::Adt(_, args) = ty.kind() { - args.types().next() == Some(parent_ty) - } else { - false - } + } else if let ty::Adt(adt, args) = ty.kind() + && matches!(cx.tcx.get_diagnostic_name(adt.did()), Some(sym::Rc | sym::Arc)) + { + args.types().next() == Some(parent_ty) } else { false } diff --git a/clippy_lints/src/methods/wrong_self_convention.rs b/clippy_lints/src/methods/wrong_self_convention.rs index ad9b3c3645425..249119d549bac 100644 --- a/clippy_lints/src/methods/wrong_self_convention.rs +++ b/clippy_lints/src/methods/wrong_self_convention.rs @@ -1,6 +1,7 @@ use crate::methods::SelfKind; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::ty::is_copy; +use itertools::Itertools; use rustc_lint::LateContext; use rustc_middle::ty::Ty; use rustc_span::{Span, Symbol}; @@ -61,20 +62,20 @@ impl Convention { impl fmt::Display for Convention { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match *self { - Self::Eq(this) => format!("`{this}`").fmt(f), - Self::StartsWith(this) => format!("`{this}*`").fmt(f), - Self::EndsWith(this) => format!("`*{this}`").fmt(f), - Self::NotEndsWith(this) => format!("`~{this}`").fmt(f), + Self::Eq(this) => write!(f, "`{this}`"), + Self::StartsWith(this) => write!(f, "`{this}*`"), + Self::EndsWith(this) => write!(f, "`*{this}`"), + Self::NotEndsWith(this) => write!(f, "`~{this}`"), Self::IsSelfTypeCopy(is_true) => { - format!("`self` type is{} `Copy`", if is_true { "" } else { " not" }).fmt(f) + write!(f, "`self` type is{} `Copy`", if is_true { "" } else { " not" }) }, Self::ImplementsTrait(is_true) => { let (negation, s_suffix) = if is_true { ("", "s") } else { (" does not", "") }; - format!("method{negation} implement{s_suffix} a trait").fmt(f) + write!(f, "method{negation} implement{s_suffix} a trait") }, Self::IsTraitItem(is_true) => { let suffix = if is_true { " is" } else { " is not" }; - format!("method{suffix} a trait item").fmt(f) + write!(f, "method{suffix} a trait item") }, } } @@ -115,18 +116,9 @@ pub(super) fn check<'tcx>( let s = conventions .iter() - .filter_map(|conv| { - if (cut_ends_with_conv && matches!(conv, Convention::NotEndsWith(_))) - || matches!(conv, Convention::ImplementsTrait(_)) - || matches!(conv, Convention::IsTraitItem(_)) - { - None - } else { - Some(conv.to_string()) - } - }) - .collect::>() - .join(" and "); + .filter(|conv| !(cut_ends_with_conv && matches!(conv, Convention::NotEndsWith(_)))) + .filter(|conv| !matches!(conv, Convention::ImplementsTrait(_) | Convention::IsTraitItem(_))) + .format(" and "); format!("methods with the following characteristics: ({s})") } else { @@ -140,11 +132,7 @@ pub(super) fn check<'tcx>( first_arg_span, format!( "{suggestion} usually take {}", - &self_kinds - .iter() - .map(|k| k.description()) - .collect::>() - .join(" or ") + self_kinds.iter().map(|k| k.description()).format(" or ") ), None, "consider choosing a less ambiguous name", From 26a9a371ee3fa8731815cbff5b397d66457afc4c Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 28 Sep 2025 23:46:30 +0200 Subject: [PATCH 64/93] clean-up: extract `should_implement_trait` Also put `SelfKind` into `lib.rs`, since it's shared with `wrong_self_convention` -- not `utils.rs`, because it's not really a utility function --- clippy_lints/src/methods/lib.rs | 70 ++++++ clippy_lints/src/methods/mod.rs | 229 +----------------- .../src/methods/should_implement_trait.rs | 165 +++++++++++++ .../src/methods/wrong_self_convention.rs | 2 +- 4 files changed, 247 insertions(+), 219 deletions(-) create mode 100644 clippy_lints/src/methods/lib.rs create mode 100644 clippy_lints/src/methods/should_implement_trait.rs diff --git a/clippy_lints/src/methods/lib.rs b/clippy_lints/src/methods/lib.rs new file mode 100644 index 0000000000000..84038283bcf8f --- /dev/null +++ b/clippy_lints/src/methods/lib.rs @@ -0,0 +1,70 @@ +use clippy_utils::sym; +use clippy_utils::ty::{implements_trait, is_copy}; +use rustc_hir::Mutability; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(super) enum SelfKind { + Value, + Ref, + RefMut, + No, // When we want the first argument type to be different than `Self` +} + +impl SelfKind { + pub(super) fn matches<'a>(self, cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { + fn matches_value<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { + if ty == parent_ty { + true + } else if let Some(boxed_ty) = ty.boxed_ty() { + boxed_ty == parent_ty + } else if let ty::Adt(adt_def, args) = ty.kind() + && matches!(cx.tcx.get_diagnostic_name(adt_def.did()), Some(sym::Rc | sym::Arc)) + { + args.types().next() == Some(parent_ty) + } else { + false + } + } + + fn matches_ref<'a>(cx: &LateContext<'a>, mutability: Mutability, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { + if let ty::Ref(_, t, m) = *ty.kind() { + return m == mutability && t == parent_ty; + } + + let trait_sym = match mutability { + Mutability::Not => sym::AsRef, + Mutability::Mut => sym::AsMut, + }; + + let Some(trait_def_id) = cx.tcx.get_diagnostic_item(trait_sym) else { + return false; + }; + implements_trait(cx, ty, trait_def_id, &[parent_ty.into()]) + } + + fn matches_none<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { + !matches_value(cx, parent_ty, ty) + && !matches_ref(cx, Mutability::Not, parent_ty, ty) + && !matches_ref(cx, Mutability::Mut, parent_ty, ty) + } + + match self { + Self::Value => matches_value(cx, parent_ty, ty), + Self::Ref => matches_ref(cx, Mutability::Not, parent_ty, ty) || ty == parent_ty && is_copy(cx, ty), + Self::RefMut => matches_ref(cx, Mutability::Mut, parent_ty, ty), + Self::No => matches_none(cx, parent_ty, ty), + } + } + + #[must_use] + pub(super) fn description(self) -> &'static str { + match self { + Self::Value => "`self` by value", + Self::Ref => "`self` by reference", + Self::RefMut => "`self` by mutable reference", + Self::No => "no `self`", + } + } +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 195d91c4e66b1..8c2948e4a74e6 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -55,6 +55,7 @@ mod iter_skip_zero; mod iter_with_drain; mod iterator_step_by_zero; mod join_absolute_paths; +mod lib; mod manual_c_str_literals; mod manual_contains; mod manual_inspect; @@ -102,6 +103,7 @@ mod return_and_then; mod search_is_some; mod seek_from_current; mod seek_to_start_instead_of_rewind; +mod should_implement_trait; mod single_char_add_str; mod skip_while_next; mod sliced_string_as_bytes; @@ -146,19 +148,18 @@ mod zst_offset; use clippy_config::Conf; use clippy_utils::consts::{ConstEvalCtxt, Constant}; -use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; +use clippy_utils::diagnostics::span_lint; use clippy_utils::macros::FormatArgsStorage; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy}; -use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty, sym}; +use clippy_utils::ty::contains_ty_adt_constructor_opaque; +use clippy_utils::{contains_return, is_trait_method, iter_input_pats, peel_blocks, return_ty, sym}; pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES; -use rustc_abi::ExternAbi; use rustc_data_structures::fx::FxHashSet; use rustc_hir::{self as hir, Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::ty::{self, TraitRef, Ty}; +use rustc_middle::ty::TraitRef; use rustc_session::impl_lint_pass; -use rustc_span::{Span, Symbol, kw}; +use rustc_span::{Span, Symbol}; declare_clippy_lint! { /// ### What it does @@ -4888,47 +4889,17 @@ impl<'tcx> LateLintPass<'tcx> for Methods { if impl_item.span.in_external_macro(cx.sess().source_map()) { return; } + if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind { - let name = impl_item.ident.name; let parent = cx.tcx.hir_get_parent_item(impl_item.hir_id()).def_id; let item = cx.tcx.hir_expect_item(parent); let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); - let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. })); + let method_sig = cx.tcx.fn_sig(impl_item.owner_id).instantiate_identity(); let method_sig = cx.tcx.instantiate_bound_regions_with_erased(method_sig); let first_arg_ty_opt = method_sig.inputs().iter().next().copied(); - // if this impl block implements a trait, lint in trait definition instead - if !implements_trait && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) { - // check missing trait implementations - for method_config in &TRAIT_METHODS { - if name == method_config.method_name - && sig.decl.inputs.len() == method_config.param_count - && method_config.output_type.matches(&sig.decl.output) - // in case there is no first arg, since we already have checked the number of arguments - // it's should be always true - && first_arg_ty_opt - .is_none_or(|first_arg_ty| method_config.self_kind.matches(cx, self_ty, first_arg_ty)) - && fn_header_equals(method_config.fn_header, sig.header) - && method_config.lifetime_param_cond(impl_item) - { - span_lint_and_help( - cx, - SHOULD_IMPLEMENT_TRAIT, - impl_item.span, - format!( - "method `{}` can be confused for the standard trait method `{}::{}`", - method_config.method_name, method_config.trait_name, method_config.method_name - ), - None, - format!( - "consider implementing the trait `{}` or choosing a less ambiguous method name", - method_config.trait_name - ), - ); - } - } - } + should_implement_trait::check_impl_item(cx, impl_item, self_ty, implements_trait, first_arg_ty_opt, sig); if sig.decl.implicit_self.has_implicit_self() && !(self.avoid_breaking_exported_api @@ -4938,7 +4909,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { { wrong_self_convention::check( cx, - name, + impl_item.ident.name, self_ty, first_arg_ty, first_arg.pat.span, @@ -5711,181 +5682,3 @@ fn lint_binary_expr_with_method_call(cx: &LateContext<'_>, info: &mut BinaryExpr lint_with_both_lhs_and_rhs!(chars_next_cmp_with_unwrap::check, cx, info); lint_with_both_lhs_and_rhs!(chars_last_cmp_with_unwrap::check, cx, info); } - -const FN_HEADER: hir::FnHeader = hir::FnHeader { - safety: hir::HeaderSafety::Normal(hir::Safety::Safe), - constness: hir::Constness::NotConst, - asyncness: hir::IsAsync::NotAsync, - abi: ExternAbi::Rust, -}; - -struct ShouldImplTraitCase { - trait_name: &'static str, - method_name: Symbol, - param_count: usize, - fn_header: hir::FnHeader, - // implicit self kind expected (none, self, &self, ...) - self_kind: SelfKind, - // checks against the output type - output_type: OutType, - // certain methods with explicit lifetimes can't implement the equivalent trait method - lint_explicit_lifetime: bool, -} -impl ShouldImplTraitCase { - const fn new( - trait_name: &'static str, - method_name: Symbol, - param_count: usize, - fn_header: hir::FnHeader, - self_kind: SelfKind, - output_type: OutType, - lint_explicit_lifetime: bool, - ) -> ShouldImplTraitCase { - ShouldImplTraitCase { - trait_name, - method_name, - param_count, - fn_header, - self_kind, - output_type, - lint_explicit_lifetime, - } - } - - fn lifetime_param_cond(&self, impl_item: &hir::ImplItem<'_>) -> bool { - self.lint_explicit_lifetime - || !impl_item.generics.params.iter().any(|p| { - matches!( - p.kind, - hir::GenericParamKind::Lifetime { - kind: hir::LifetimeParamKind::Explicit - } - ) - }) - } -} - -#[rustfmt::skip] -const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [ - ShouldImplTraitCase::new("std::ops::Add", sym::add, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::convert::AsMut", sym::as_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true ), - ShouldImplTraitCase::new("std::convert::AsRef", sym::as_ref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true ), - ShouldImplTraitCase::new("std::ops::BitAnd", sym::bitand, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::BitOr", sym::bitor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::BitXor", sym::bitxor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::borrow::Borrow", sym::borrow, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true ), - ShouldImplTraitCase::new("std::borrow::BorrowMut", sym::borrow_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true ), - ShouldImplTraitCase::new("std::clone::Clone", sym::clone, 1, FN_HEADER, SelfKind::Ref, OutType::Any, true ), - ShouldImplTraitCase::new("std::cmp::Ord", sym::cmp, 2, FN_HEADER, SelfKind::Ref, OutType::Any, true ), - ShouldImplTraitCase::new("std::default::Default", kw::Default, 0, FN_HEADER, SelfKind::No, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::Deref", sym::deref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true ), - ShouldImplTraitCase::new("std::ops::DerefMut", sym::deref_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true ), - ShouldImplTraitCase::new("std::ops::Div", sym::div, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::Drop", sym::drop, 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true ), - ShouldImplTraitCase::new("std::cmp::PartialEq", sym::eq, 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true ), - ShouldImplTraitCase::new("std::iter::FromIterator", sym::from_iter, 1, FN_HEADER, SelfKind::No, OutType::Any, true ), - ShouldImplTraitCase::new("std::str::FromStr", sym::from_str, 1, FN_HEADER, SelfKind::No, OutType::Any, true ), - ShouldImplTraitCase::new("std::hash::Hash", sym::hash, 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true ), - ShouldImplTraitCase::new("std::ops::Index", sym::index, 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true ), - ShouldImplTraitCase::new("std::ops::IndexMut", sym::index_mut, 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true ), - ShouldImplTraitCase::new("std::iter::IntoIterator", sym::into_iter, 1, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::Mul", sym::mul, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::Neg", sym::neg, 1, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::iter::Iterator", sym::next, 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false), - ShouldImplTraitCase::new("std::ops::Not", sym::not, 1, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::Rem", sym::rem, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::Shl", sym::shl, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::Shr", sym::shr, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::Sub", sym::sub, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), -]; - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum SelfKind { - Value, - Ref, - RefMut, - No, // When we want the first argument type to be different than `Self` -} - -impl SelfKind { - fn matches<'a>(self, cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { - fn matches_value<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { - if ty == parent_ty { - true - } else if let Some(boxed_ty) = ty.boxed_ty() { - boxed_ty == parent_ty - } else if let ty::Adt(adt, args) = ty.kind() - && matches!(cx.tcx.get_diagnostic_name(adt.did()), Some(sym::Rc | sym::Arc)) - { - args.types().next() == Some(parent_ty) - } else { - false - } - } - - fn matches_ref<'a>(cx: &LateContext<'a>, mutability: hir::Mutability, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { - if let ty::Ref(_, t, m) = *ty.kind() { - return m == mutability && t == parent_ty; - } - - let trait_sym = match mutability { - hir::Mutability::Not => sym::AsRef, - hir::Mutability::Mut => sym::AsMut, - }; - - let Some(trait_def_id) = cx.tcx.get_diagnostic_item(trait_sym) else { - return false; - }; - implements_trait(cx, ty, trait_def_id, &[parent_ty.into()]) - } - - fn matches_none<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { - !matches_value(cx, parent_ty, ty) - && !matches_ref(cx, hir::Mutability::Not, parent_ty, ty) - && !matches_ref(cx, hir::Mutability::Mut, parent_ty, ty) - } - - match self { - Self::Value => matches_value(cx, parent_ty, ty), - Self::Ref => matches_ref(cx, hir::Mutability::Not, parent_ty, ty) || ty == parent_ty && is_copy(cx, ty), - Self::RefMut => matches_ref(cx, hir::Mutability::Mut, parent_ty, ty), - Self::No => matches_none(cx, parent_ty, ty), - } - } - - #[must_use] - fn description(self) -> &'static str { - match self { - Self::Value => "`self` by value", - Self::Ref => "`self` by reference", - Self::RefMut => "`self` by mutable reference", - Self::No => "no `self`", - } - } -} - -#[derive(Clone, Copy)] -enum OutType { - Unit, - Bool, - Any, - Ref, -} - -impl OutType { - fn matches(self, ty: &hir::FnRetTy<'_>) -> bool { - let is_unit = |ty: &hir::Ty<'_>| matches!(ty.kind, hir::TyKind::Tup(&[])); - match (self, ty) { - (Self::Unit, &hir::FnRetTy::DefaultReturn(_)) => true, - (Self::Unit, &hir::FnRetTy::Return(ty)) if is_unit(ty) => true, - (Self::Bool, &hir::FnRetTy::Return(ty)) if is_bool(ty) => true, - (Self::Any, &hir::FnRetTy::Return(ty)) if !is_unit(ty) => true, - (Self::Ref, &hir::FnRetTy::Return(ty)) => matches!(ty.kind, hir::TyKind::Ref(_, _)), - _ => false, - } - } -} - -fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool { - expected.constness == actual.constness && expected.safety == actual.safety && expected.asyncness == actual.asyncness -} diff --git a/clippy_lints/src/methods/should_implement_trait.rs b/clippy_lints/src/methods/should_implement_trait.rs new file mode 100644 index 0000000000000..3f6600159a2f0 --- /dev/null +++ b/clippy_lints/src/methods/should_implement_trait.rs @@ -0,0 +1,165 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::{is_bool, sym}; +use rustc_abi::ExternAbi; +use rustc_hir as hir; +use rustc_hir::{FnSig, ImplItem}; +use rustc_lint::LateContext; +use rustc_middle::ty::Ty; +use rustc_span::{Symbol, kw}; + +use super::SHOULD_IMPLEMENT_TRAIT; +use super::lib::SelfKind; + +pub(super) fn check_impl_item<'tcx>( + cx: &LateContext<'tcx>, + impl_item: &'tcx ImplItem<'_>, + self_ty: Ty<'tcx>, + impl_implements_trait: bool, + first_arg_ty_opt: Option>, + sig: &FnSig<'_>, +) { + // if this impl block implements a trait, lint in trait definition instead + if !impl_implements_trait && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) { + // check missing trait implementations + for method_config in &TRAIT_METHODS { + if impl_item.ident.name == method_config.method_name + && sig.decl.inputs.len() == method_config.param_count + && method_config.output_type.matches(&sig.decl.output) + // in case there is no first arg, since we already have checked the number of arguments + // it's should be always true + && first_arg_ty_opt + .is_none_or(|first_arg_ty| method_config.self_kind.matches(cx, self_ty, first_arg_ty)) + && fn_header_equals(method_config.fn_header, sig.header) + && method_config.lifetime_param_cond(impl_item) + { + span_lint_and_help( + cx, + SHOULD_IMPLEMENT_TRAIT, + impl_item.span, + format!( + "method `{}` can be confused for the standard trait method `{}::{}`", + method_config.method_name, method_config.trait_name, method_config.method_name + ), + None, + format!( + "consider implementing the trait `{}` or choosing a less ambiguous method name", + method_config.trait_name + ), + ); + } + } + } +} + +const FN_HEADER: hir::FnHeader = hir::FnHeader { + safety: hir::HeaderSafety::Normal(hir::Safety::Safe), + constness: hir::Constness::NotConst, + asyncness: hir::IsAsync::NotAsync, + abi: ExternAbi::Rust, +}; + +struct ShouldImplTraitCase { + trait_name: &'static str, + method_name: Symbol, + param_count: usize, + fn_header: hir::FnHeader, + // implicit self kind expected (none, self, &self, ...) + self_kind: SelfKind, + // checks against the output type + output_type: OutType, + // certain methods with explicit lifetimes can't implement the equivalent trait method + lint_explicit_lifetime: bool, +} +impl ShouldImplTraitCase { + const fn new( + trait_name: &'static str, + method_name: Symbol, + param_count: usize, + fn_header: hir::FnHeader, + self_kind: SelfKind, + output_type: OutType, + lint_explicit_lifetime: bool, + ) -> ShouldImplTraitCase { + ShouldImplTraitCase { + trait_name, + method_name, + param_count, + fn_header, + self_kind, + output_type, + lint_explicit_lifetime, + } + } + + fn lifetime_param_cond(&self, impl_item: &ImplItem<'_>) -> bool { + self.lint_explicit_lifetime + || !impl_item.generics.params.iter().any(|p| { + matches!( + p.kind, + hir::GenericParamKind::Lifetime { + kind: hir::LifetimeParamKind::Explicit + } + ) + }) + } +} + +#[rustfmt::skip] +const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [ + ShouldImplTraitCase::new("std::ops::Add", sym::add, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::convert::AsMut", sym::as_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true ), + ShouldImplTraitCase::new("std::convert::AsRef", sym::as_ref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true ), + ShouldImplTraitCase::new("std::ops::BitAnd", sym::bitand, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::BitOr", sym::bitor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::BitXor", sym::bitxor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::borrow::Borrow", sym::borrow, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true ), + ShouldImplTraitCase::new("std::borrow::BorrowMut", sym::borrow_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true ), + ShouldImplTraitCase::new("std::clone::Clone", sym::clone, 1, FN_HEADER, SelfKind::Ref, OutType::Any, true ), + ShouldImplTraitCase::new("std::cmp::Ord", sym::cmp, 2, FN_HEADER, SelfKind::Ref, OutType::Any, true ), + ShouldImplTraitCase::new("std::default::Default", kw::Default, 0, FN_HEADER, SelfKind::No, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::Deref", sym::deref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true ), + ShouldImplTraitCase::new("std::ops::DerefMut", sym::deref_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true ), + ShouldImplTraitCase::new("std::ops::Div", sym::div, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::Drop", sym::drop, 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true ), + ShouldImplTraitCase::new("std::cmp::PartialEq", sym::eq, 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true ), + ShouldImplTraitCase::new("std::iter::FromIterator", sym::from_iter, 1, FN_HEADER, SelfKind::No, OutType::Any, true ), + ShouldImplTraitCase::new("std::str::FromStr", sym::from_str, 1, FN_HEADER, SelfKind::No, OutType::Any, true ), + ShouldImplTraitCase::new("std::hash::Hash", sym::hash, 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true ), + ShouldImplTraitCase::new("std::ops::Index", sym::index, 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true ), + ShouldImplTraitCase::new("std::ops::IndexMut", sym::index_mut, 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true ), + ShouldImplTraitCase::new("std::iter::IntoIterator", sym::into_iter, 1, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::Mul", sym::mul, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::Neg", sym::neg, 1, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::iter::Iterator", sym::next, 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false), + ShouldImplTraitCase::new("std::ops::Not", sym::not, 1, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::Rem", sym::rem, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::Shl", sym::shl, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::Shr", sym::shr, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::Sub", sym::sub, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), +]; + +#[derive(Clone, Copy)] +enum OutType { + Unit, + Bool, + Any, + Ref, +} + +impl OutType { + fn matches(self, ty: &hir::FnRetTy<'_>) -> bool { + let is_unit = |ty: &hir::Ty<'_>| matches!(ty.kind, hir::TyKind::Tup(&[])); + match (self, ty) { + (Self::Unit, &hir::FnRetTy::DefaultReturn(_)) => true, + (Self::Unit, &hir::FnRetTy::Return(ty)) if is_unit(ty) => true, + (Self::Bool, &hir::FnRetTy::Return(ty)) if is_bool(ty) => true, + (Self::Any, &hir::FnRetTy::Return(ty)) if !is_unit(ty) => true, + (Self::Ref, &hir::FnRetTy::Return(ty)) => matches!(ty.kind, hir::TyKind::Ref(_, _)), + _ => false, + } + } +} + +fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool { + expected.constness == actual.constness && expected.safety == actual.safety && expected.asyncness == actual.asyncness +} diff --git a/clippy_lints/src/methods/wrong_self_convention.rs b/clippy_lints/src/methods/wrong_self_convention.rs index 249119d549bac..74b297c13621e 100644 --- a/clippy_lints/src/methods/wrong_self_convention.rs +++ b/clippy_lints/src/methods/wrong_self_convention.rs @@ -1,4 +1,3 @@ -use crate::methods::SelfKind; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::ty::is_copy; use itertools::Itertools; @@ -8,6 +7,7 @@ use rustc_span::{Span, Symbol}; use std::fmt; use super::WRONG_SELF_CONVENTION; +use super::lib::SelfKind; #[rustfmt::skip] const CONVENTIONS: [(&[Convention], &[SelfKind]); 9] = [ From 2d2d143a18249dbdf2709991d95e29e378f0c66c Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 29 Sep 2025 01:45:13 +0200 Subject: [PATCH 65/93] fix: respect crate's edition --- .../src/methods/should_implement_trait.rs | 66 ++++--- ...tderr => method_list_1.edition2015.stderr} | 30 +-- .../method_list_1.edition2021.stderr | 184 ++++++++++++++++++ tests/ui/should_impl_trait/method_list_1.rs | 3 + .../method_list_2.edition2015.stderr | 172 ++++++++++++++++ .../method_list_2.edition2021.stderr | 184 ++++++++++++++++++ tests/ui/should_impl_trait/method_list_2.rs | 5 +- 7 files changed, 598 insertions(+), 46 deletions(-) rename tests/ui/should_impl_trait/{method_list_1.stderr => method_list_1.edition2015.stderr} (86%) create mode 100644 tests/ui/should_impl_trait/method_list_1.edition2021.stderr create mode 100644 tests/ui/should_impl_trait/method_list_2.edition2015.stderr create mode 100644 tests/ui/should_impl_trait/method_list_2.edition2021.stderr diff --git a/clippy_lints/src/methods/should_implement_trait.rs b/clippy_lints/src/methods/should_implement_trait.rs index 3f6600159a2f0..5f13b8c7e91df 100644 --- a/clippy_lints/src/methods/should_implement_trait.rs +++ b/clippy_lints/src/methods/should_implement_trait.rs @@ -5,6 +5,7 @@ use rustc_hir as hir; use rustc_hir::{FnSig, ImplItem}; use rustc_lint::LateContext; use rustc_middle::ty::Ty; +use rustc_span::edition::Edition::{self, Edition2015, Edition2021}; use rustc_span::{Symbol, kw}; use super::SHOULD_IMPLEMENT_TRAIT; @@ -31,6 +32,7 @@ pub(super) fn check_impl_item<'tcx>( .is_none_or(|first_arg_ty| method_config.self_kind.matches(cx, self_ty, first_arg_ty)) && fn_header_equals(method_config.fn_header, sig.header) && method_config.lifetime_param_cond(impl_item) + && method_config.in_prelude_since <= cx.tcx.sess.edition() { span_lint_and_help( cx, @@ -69,8 +71,10 @@ struct ShouldImplTraitCase { output_type: OutType, // certain methods with explicit lifetimes can't implement the equivalent trait method lint_explicit_lifetime: bool, + in_prelude_since: Edition, } impl ShouldImplTraitCase { + #[expect(clippy::too_many_arguments)] const fn new( trait_name: &'static str, method_name: Symbol, @@ -79,6 +83,7 @@ impl ShouldImplTraitCase { self_kind: SelfKind, output_type: OutType, lint_explicit_lifetime: bool, + in_prelude_since: Edition, ) -> ShouldImplTraitCase { ShouldImplTraitCase { trait_name, @@ -88,6 +93,7 @@ impl ShouldImplTraitCase { self_kind, output_type, lint_explicit_lifetime, + in_prelude_since, } } @@ -106,36 +112,36 @@ impl ShouldImplTraitCase { #[rustfmt::skip] const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [ - ShouldImplTraitCase::new("std::ops::Add", sym::add, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::convert::AsMut", sym::as_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true ), - ShouldImplTraitCase::new("std::convert::AsRef", sym::as_ref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true ), - ShouldImplTraitCase::new("std::ops::BitAnd", sym::bitand, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::BitOr", sym::bitor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::BitXor", sym::bitxor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::borrow::Borrow", sym::borrow, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true ), - ShouldImplTraitCase::new("std::borrow::BorrowMut", sym::borrow_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true ), - ShouldImplTraitCase::new("std::clone::Clone", sym::clone, 1, FN_HEADER, SelfKind::Ref, OutType::Any, true ), - ShouldImplTraitCase::new("std::cmp::Ord", sym::cmp, 2, FN_HEADER, SelfKind::Ref, OutType::Any, true ), - ShouldImplTraitCase::new("std::default::Default", kw::Default, 0, FN_HEADER, SelfKind::No, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::Deref", sym::deref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true ), - ShouldImplTraitCase::new("std::ops::DerefMut", sym::deref_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true ), - ShouldImplTraitCase::new("std::ops::Div", sym::div, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::Drop", sym::drop, 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true ), - ShouldImplTraitCase::new("std::cmp::PartialEq", sym::eq, 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true ), - ShouldImplTraitCase::new("std::iter::FromIterator", sym::from_iter, 1, FN_HEADER, SelfKind::No, OutType::Any, true ), - ShouldImplTraitCase::new("std::str::FromStr", sym::from_str, 1, FN_HEADER, SelfKind::No, OutType::Any, true ), - ShouldImplTraitCase::new("std::hash::Hash", sym::hash, 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true ), - ShouldImplTraitCase::new("std::ops::Index", sym::index, 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true ), - ShouldImplTraitCase::new("std::ops::IndexMut", sym::index_mut, 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true ), - ShouldImplTraitCase::new("std::iter::IntoIterator", sym::into_iter, 1, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::Mul", sym::mul, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::Neg", sym::neg, 1, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::iter::Iterator", sym::next, 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false), - ShouldImplTraitCase::new("std::ops::Not", sym::not, 1, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::Rem", sym::rem, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::Shl", sym::shl, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::Shr", sym::shr, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), - ShouldImplTraitCase::new("std::ops::Sub", sym::sub, 2, FN_HEADER, SelfKind::Value, OutType::Any, true ), + ShouldImplTraitCase::new("std::ops::Add", sym::add, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::convert::AsMut", sym::as_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::convert::AsRef", sym::as_ref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::ops::BitAnd", sym::bitand, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::BitOr", sym::bitor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::BitXor", sym::bitxor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::borrow::Borrow", sym::borrow, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::borrow::BorrowMut", sym::borrow_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::clone::Clone", sym::clone, 1, FN_HEADER, SelfKind::Ref, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::cmp::Ord", sym::cmp, 2, FN_HEADER, SelfKind::Ref, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::default::Default", kw::Default, 0, FN_HEADER, SelfKind::No, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Deref", sym::deref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::ops::DerefMut", sym::deref_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Div", sym::div, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Drop", sym::drop, 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true, Edition2015), + ShouldImplTraitCase::new("std::cmp::PartialEq", sym::eq, 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true, Edition2015), + ShouldImplTraitCase::new("std::iter::FromIterator", sym::from_iter, 1, FN_HEADER, SelfKind::No, OutType::Any, true, Edition2021), + ShouldImplTraitCase::new("std::str::FromStr", sym::from_str, 1, FN_HEADER, SelfKind::No, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::hash::Hash", sym::hash, 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Index", sym::index, 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::ops::IndexMut", sym::index_mut, 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::iter::IntoIterator", sym::into_iter, 1, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Mul", sym::mul, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Neg", sym::neg, 1, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::iter::Iterator", sym::next, 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false, Edition2015), + ShouldImplTraitCase::new("std::ops::Not", sym::not, 1, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Rem", sym::rem, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Shl", sym::shl, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Shr", sym::shr, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Sub", sym::sub, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), ]; #[derive(Clone, Copy)] diff --git a/tests/ui/should_impl_trait/method_list_1.stderr b/tests/ui/should_impl_trait/method_list_1.edition2015.stderr similarity index 86% rename from tests/ui/should_impl_trait/method_list_1.stderr rename to tests/ui/should_impl_trait/method_list_1.edition2015.stderr index 5609d6a21a360..0312fa8f04fae 100644 --- a/tests/ui/should_impl_trait/method_list_1.stderr +++ b/tests/ui/should_impl_trait/method_list_1.edition2015.stderr @@ -1,5 +1,5 @@ error: method `add` can be confused for the standard trait method `std::ops::Add::add` - --> tests/ui/should_impl_trait/method_list_1.rs:24:5 + --> tests/ui/should_impl_trait/method_list_1.rs:27:5 | LL | / pub fn add(self, other: T) -> T { LL | | @@ -13,7 +13,7 @@ LL | | } = help: to override `-D warnings` add `#[allow(clippy::should_implement_trait)]` error: method `as_mut` can be confused for the standard trait method `std::convert::AsMut::as_mut` - --> tests/ui/should_impl_trait/method_list_1.rs:30:5 + --> tests/ui/should_impl_trait/method_list_1.rs:33:5 | LL | / pub fn as_mut(&mut self) -> &mut T { LL | | @@ -25,7 +25,7 @@ LL | | } = help: consider implementing the trait `std::convert::AsMut` or choosing a less ambiguous method name error: method `as_ref` can be confused for the standard trait method `std::convert::AsRef::as_ref` - --> tests/ui/should_impl_trait/method_list_1.rs:36:5 + --> tests/ui/should_impl_trait/method_list_1.rs:39:5 | LL | / pub fn as_ref(&self) -> &T { LL | | @@ -37,7 +37,7 @@ LL | | } = help: consider implementing the trait `std::convert::AsRef` or choosing a less ambiguous method name error: method `bitand` can be confused for the standard trait method `std::ops::BitAnd::bitand` - --> tests/ui/should_impl_trait/method_list_1.rs:42:5 + --> tests/ui/should_impl_trait/method_list_1.rs:45:5 | LL | / pub fn bitand(self, rhs: T) -> T { LL | | @@ -49,7 +49,7 @@ LL | | } = help: consider implementing the trait `std::ops::BitAnd` or choosing a less ambiguous method name error: method `bitor` can be confused for the standard trait method `std::ops::BitOr::bitor` - --> tests/ui/should_impl_trait/method_list_1.rs:48:5 + --> tests/ui/should_impl_trait/method_list_1.rs:51:5 | LL | / pub fn bitor(self, rhs: Self) -> Self { LL | | @@ -61,7 +61,7 @@ LL | | } = help: consider implementing the trait `std::ops::BitOr` or choosing a less ambiguous method name error: method `bitxor` can be confused for the standard trait method `std::ops::BitXor::bitxor` - --> tests/ui/should_impl_trait/method_list_1.rs:54:5 + --> tests/ui/should_impl_trait/method_list_1.rs:57:5 | LL | / pub fn bitxor(self, rhs: Self) -> Self { LL | | @@ -73,7 +73,7 @@ LL | | } = help: consider implementing the trait `std::ops::BitXor` or choosing a less ambiguous method name error: method `borrow` can be confused for the standard trait method `std::borrow::Borrow::borrow` - --> tests/ui/should_impl_trait/method_list_1.rs:60:5 + --> tests/ui/should_impl_trait/method_list_1.rs:63:5 | LL | / pub fn borrow(&self) -> &str { LL | | @@ -85,7 +85,7 @@ LL | | } = help: consider implementing the trait `std::borrow::Borrow` or choosing a less ambiguous method name error: method `borrow_mut` can be confused for the standard trait method `std::borrow::BorrowMut::borrow_mut` - --> tests/ui/should_impl_trait/method_list_1.rs:66:5 + --> tests/ui/should_impl_trait/method_list_1.rs:69:5 | LL | / pub fn borrow_mut(&mut self) -> &mut str { LL | | @@ -97,7 +97,7 @@ LL | | } = help: consider implementing the trait `std::borrow::BorrowMut` or choosing a less ambiguous method name error: method `clone` can be confused for the standard trait method `std::clone::Clone::clone` - --> tests/ui/should_impl_trait/method_list_1.rs:72:5 + --> tests/ui/should_impl_trait/method_list_1.rs:75:5 | LL | / pub fn clone(&self) -> Self { LL | | @@ -109,7 +109,7 @@ LL | | } = help: consider implementing the trait `std::clone::Clone` or choosing a less ambiguous method name error: method `cmp` can be confused for the standard trait method `std::cmp::Ord::cmp` - --> tests/ui/should_impl_trait/method_list_1.rs:78:5 + --> tests/ui/should_impl_trait/method_list_1.rs:81:5 | LL | / pub fn cmp(&self, other: &Self) -> Self { LL | | @@ -121,7 +121,7 @@ LL | | } = help: consider implementing the trait `std::cmp::Ord` or choosing a less ambiguous method name error: method `default` can be confused for the standard trait method `std::default::Default::default` - --> tests/ui/should_impl_trait/method_list_1.rs:84:5 + --> tests/ui/should_impl_trait/method_list_1.rs:87:5 | LL | / pub fn default() -> Self { LL | | @@ -133,7 +133,7 @@ LL | | } = help: consider implementing the trait `std::default::Default` or choosing a less ambiguous method name error: method `deref` can be confused for the standard trait method `std::ops::Deref::deref` - --> tests/ui/should_impl_trait/method_list_1.rs:90:5 + --> tests/ui/should_impl_trait/method_list_1.rs:93:5 | LL | / pub fn deref(&self) -> &Self { LL | | @@ -145,7 +145,7 @@ LL | | } = help: consider implementing the trait `std::ops::Deref` or choosing a less ambiguous method name error: method `deref_mut` can be confused for the standard trait method `std::ops::DerefMut::deref_mut` - --> tests/ui/should_impl_trait/method_list_1.rs:96:5 + --> tests/ui/should_impl_trait/method_list_1.rs:99:5 | LL | / pub fn deref_mut(&mut self) -> &mut Self { LL | | @@ -157,7 +157,7 @@ LL | | } = help: consider implementing the trait `std::ops::DerefMut` or choosing a less ambiguous method name error: method `div` can be confused for the standard trait method `std::ops::Div::div` - --> tests/ui/should_impl_trait/method_list_1.rs:102:5 + --> tests/ui/should_impl_trait/method_list_1.rs:105:5 | LL | / pub fn div(self, rhs: Self) -> Self { LL | | @@ -169,7 +169,7 @@ LL | | } = help: consider implementing the trait `std::ops::Div` or choosing a less ambiguous method name error: method `drop` can be confused for the standard trait method `std::ops::Drop::drop` - --> tests/ui/should_impl_trait/method_list_1.rs:108:5 + --> tests/ui/should_impl_trait/method_list_1.rs:111:5 | LL | / pub fn drop(&mut self) { LL | | diff --git a/tests/ui/should_impl_trait/method_list_1.edition2021.stderr b/tests/ui/should_impl_trait/method_list_1.edition2021.stderr new file mode 100644 index 0000000000000..0312fa8f04fae --- /dev/null +++ b/tests/ui/should_impl_trait/method_list_1.edition2021.stderr @@ -0,0 +1,184 @@ +error: method `add` can be confused for the standard trait method `std::ops::Add::add` + --> tests/ui/should_impl_trait/method_list_1.rs:27:5 + | +LL | / pub fn add(self, other: T) -> T { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Add` or choosing a less ambiguous method name + = note: `-D clippy::should-implement-trait` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::should_implement_trait)]` + +error: method `as_mut` can be confused for the standard trait method `std::convert::AsMut::as_mut` + --> tests/ui/should_impl_trait/method_list_1.rs:33:5 + | +LL | / pub fn as_mut(&mut self) -> &mut T { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::convert::AsMut` or choosing a less ambiguous method name + +error: method `as_ref` can be confused for the standard trait method `std::convert::AsRef::as_ref` + --> tests/ui/should_impl_trait/method_list_1.rs:39:5 + | +LL | / pub fn as_ref(&self) -> &T { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::convert::AsRef` or choosing a less ambiguous method name + +error: method `bitand` can be confused for the standard trait method `std::ops::BitAnd::bitand` + --> tests/ui/should_impl_trait/method_list_1.rs:45:5 + | +LL | / pub fn bitand(self, rhs: T) -> T { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::BitAnd` or choosing a less ambiguous method name + +error: method `bitor` can be confused for the standard trait method `std::ops::BitOr::bitor` + --> tests/ui/should_impl_trait/method_list_1.rs:51:5 + | +LL | / pub fn bitor(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::BitOr` or choosing a less ambiguous method name + +error: method `bitxor` can be confused for the standard trait method `std::ops::BitXor::bitxor` + --> tests/ui/should_impl_trait/method_list_1.rs:57:5 + | +LL | / pub fn bitxor(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::BitXor` or choosing a less ambiguous method name + +error: method `borrow` can be confused for the standard trait method `std::borrow::Borrow::borrow` + --> tests/ui/should_impl_trait/method_list_1.rs:63:5 + | +LL | / pub fn borrow(&self) -> &str { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::borrow::Borrow` or choosing a less ambiguous method name + +error: method `borrow_mut` can be confused for the standard trait method `std::borrow::BorrowMut::borrow_mut` + --> tests/ui/should_impl_trait/method_list_1.rs:69:5 + | +LL | / pub fn borrow_mut(&mut self) -> &mut str { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::borrow::BorrowMut` or choosing a less ambiguous method name + +error: method `clone` can be confused for the standard trait method `std::clone::Clone::clone` + --> tests/ui/should_impl_trait/method_list_1.rs:75:5 + | +LL | / pub fn clone(&self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::clone::Clone` or choosing a less ambiguous method name + +error: method `cmp` can be confused for the standard trait method `std::cmp::Ord::cmp` + --> tests/ui/should_impl_trait/method_list_1.rs:81:5 + | +LL | / pub fn cmp(&self, other: &Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::cmp::Ord` or choosing a less ambiguous method name + +error: method `default` can be confused for the standard trait method `std::default::Default::default` + --> tests/ui/should_impl_trait/method_list_1.rs:87:5 + | +LL | / pub fn default() -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::default::Default` or choosing a less ambiguous method name + +error: method `deref` can be confused for the standard trait method `std::ops::Deref::deref` + --> tests/ui/should_impl_trait/method_list_1.rs:93:5 + | +LL | / pub fn deref(&self) -> &Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Deref` or choosing a less ambiguous method name + +error: method `deref_mut` can be confused for the standard trait method `std::ops::DerefMut::deref_mut` + --> tests/ui/should_impl_trait/method_list_1.rs:99:5 + | +LL | / pub fn deref_mut(&mut self) -> &mut Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::DerefMut` or choosing a less ambiguous method name + +error: method `div` can be confused for the standard trait method `std::ops::Div::div` + --> tests/ui/should_impl_trait/method_list_1.rs:105:5 + | +LL | / pub fn div(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Div` or choosing a less ambiguous method name + +error: method `drop` can be confused for the standard trait method `std::ops::Drop::drop` + --> tests/ui/should_impl_trait/method_list_1.rs:111:5 + | +LL | / pub fn drop(&mut self) { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Drop` or choosing a less ambiguous method name + +error: aborting due to 15 previous errors + diff --git a/tests/ui/should_impl_trait/method_list_1.rs b/tests/ui/should_impl_trait/method_list_1.rs index e8de0e04c0c4c..bbb04c0c5aa12 100644 --- a/tests/ui/should_impl_trait/method_list_1.rs +++ b/tests/ui/should_impl_trait/method_list_1.rs @@ -1,3 +1,6 @@ +//@revisions: edition2015 edition2021 +//@[edition2015] edition:2015 +//@[edition2021] edition:2021 #![allow( clippy::missing_errors_doc, clippy::needless_pass_by_value, diff --git a/tests/ui/should_impl_trait/method_list_2.edition2015.stderr b/tests/ui/should_impl_trait/method_list_2.edition2015.stderr new file mode 100644 index 0000000000000..259815908fee0 --- /dev/null +++ b/tests/ui/should_impl_trait/method_list_2.edition2015.stderr @@ -0,0 +1,172 @@ +error: method `eq` can be confused for the standard trait method `std::cmp::PartialEq::eq` + --> tests/ui/should_impl_trait/method_list_2.rs:28:5 + | +LL | / pub fn eq(&self, other: &Self) -> bool { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::cmp::PartialEq` or choosing a less ambiguous method name + = note: `-D clippy::should-implement-trait` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::should_implement_trait)]` + +error: method `from_str` can be confused for the standard trait method `std::str::FromStr::from_str` + --> tests/ui/should_impl_trait/method_list_2.rs:40:5 + | +LL | / pub fn from_str(s: &str) -> Result { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::str::FromStr` or choosing a less ambiguous method name + +error: method `hash` can be confused for the standard trait method `std::hash::Hash::hash` + --> tests/ui/should_impl_trait/method_list_2.rs:46:5 + | +LL | / pub fn hash(&self, state: &mut T) { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::hash::Hash` or choosing a less ambiguous method name + +error: method `index` can be confused for the standard trait method `std::ops::Index::index` + --> tests/ui/should_impl_trait/method_list_2.rs:52:5 + | +LL | / pub fn index(&self, index: usize) -> &Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Index` or choosing a less ambiguous method name + +error: method `index_mut` can be confused for the standard trait method `std::ops::IndexMut::index_mut` + --> tests/ui/should_impl_trait/method_list_2.rs:58:5 + | +LL | / pub fn index_mut(&mut self, index: usize) -> &mut Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::IndexMut` or choosing a less ambiguous method name + +error: method `into_iter` can be confused for the standard trait method `std::iter::IntoIterator::into_iter` + --> tests/ui/should_impl_trait/method_list_2.rs:64:5 + | +LL | / pub fn into_iter(self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::iter::IntoIterator` or choosing a less ambiguous method name + +error: method `mul` can be confused for the standard trait method `std::ops::Mul::mul` + --> tests/ui/should_impl_trait/method_list_2.rs:70:5 + | +LL | / pub fn mul(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Mul` or choosing a less ambiguous method name + +error: method `neg` can be confused for the standard trait method `std::ops::Neg::neg` + --> tests/ui/should_impl_trait/method_list_2.rs:76:5 + | +LL | / pub fn neg(self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Neg` or choosing a less ambiguous method name + +error: method `next` can be confused for the standard trait method `std::iter::Iterator::next` + --> tests/ui/should_impl_trait/method_list_2.rs:82:5 + | +LL | / pub fn next(&mut self) -> Option { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::iter::Iterator` or choosing a less ambiguous method name + +error: method `not` can be confused for the standard trait method `std::ops::Not::not` + --> tests/ui/should_impl_trait/method_list_2.rs:88:5 + | +LL | / pub fn not(self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Not` or choosing a less ambiguous method name + +error: method `rem` can be confused for the standard trait method `std::ops::Rem::rem` + --> tests/ui/should_impl_trait/method_list_2.rs:94:5 + | +LL | / pub fn rem(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Rem` or choosing a less ambiguous method name + +error: method `shl` can be confused for the standard trait method `std::ops::Shl::shl` + --> tests/ui/should_impl_trait/method_list_2.rs:100:5 + | +LL | / pub fn shl(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Shl` or choosing a less ambiguous method name + +error: method `shr` can be confused for the standard trait method `std::ops::Shr::shr` + --> tests/ui/should_impl_trait/method_list_2.rs:106:5 + | +LL | / pub fn shr(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Shr` or choosing a less ambiguous method name + +error: method `sub` can be confused for the standard trait method `std::ops::Sub::sub` + --> tests/ui/should_impl_trait/method_list_2.rs:112:5 + | +LL | / pub fn sub(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Sub` or choosing a less ambiguous method name + +error: aborting due to 14 previous errors + diff --git a/tests/ui/should_impl_trait/method_list_2.edition2021.stderr b/tests/ui/should_impl_trait/method_list_2.edition2021.stderr new file mode 100644 index 0000000000000..2f90b61e7a176 --- /dev/null +++ b/tests/ui/should_impl_trait/method_list_2.edition2021.stderr @@ -0,0 +1,184 @@ +error: method `eq` can be confused for the standard trait method `std::cmp::PartialEq::eq` + --> tests/ui/should_impl_trait/method_list_2.rs:28:5 + | +LL | / pub fn eq(&self, other: &Self) -> bool { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::cmp::PartialEq` or choosing a less ambiguous method name + = note: `-D clippy::should-implement-trait` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::should_implement_trait)]` + +error: method `from_iter` can be confused for the standard trait method `std::iter::FromIterator::from_iter` + --> tests/ui/should_impl_trait/method_list_2.rs:34:5 + | +LL | / pub fn from_iter(iter: T) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::iter::FromIterator` or choosing a less ambiguous method name + +error: method `from_str` can be confused for the standard trait method `std::str::FromStr::from_str` + --> tests/ui/should_impl_trait/method_list_2.rs:40:5 + | +LL | / pub fn from_str(s: &str) -> Result { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::str::FromStr` or choosing a less ambiguous method name + +error: method `hash` can be confused for the standard trait method `std::hash::Hash::hash` + --> tests/ui/should_impl_trait/method_list_2.rs:46:5 + | +LL | / pub fn hash(&self, state: &mut T) { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::hash::Hash` or choosing a less ambiguous method name + +error: method `index` can be confused for the standard trait method `std::ops::Index::index` + --> tests/ui/should_impl_trait/method_list_2.rs:52:5 + | +LL | / pub fn index(&self, index: usize) -> &Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Index` or choosing a less ambiguous method name + +error: method `index_mut` can be confused for the standard trait method `std::ops::IndexMut::index_mut` + --> tests/ui/should_impl_trait/method_list_2.rs:58:5 + | +LL | / pub fn index_mut(&mut self, index: usize) -> &mut Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::IndexMut` or choosing a less ambiguous method name + +error: method `into_iter` can be confused for the standard trait method `std::iter::IntoIterator::into_iter` + --> tests/ui/should_impl_trait/method_list_2.rs:64:5 + | +LL | / pub fn into_iter(self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::iter::IntoIterator` or choosing a less ambiguous method name + +error: method `mul` can be confused for the standard trait method `std::ops::Mul::mul` + --> tests/ui/should_impl_trait/method_list_2.rs:70:5 + | +LL | / pub fn mul(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Mul` or choosing a less ambiguous method name + +error: method `neg` can be confused for the standard trait method `std::ops::Neg::neg` + --> tests/ui/should_impl_trait/method_list_2.rs:76:5 + | +LL | / pub fn neg(self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Neg` or choosing a less ambiguous method name + +error: method `next` can be confused for the standard trait method `std::iter::Iterator::next` + --> tests/ui/should_impl_trait/method_list_2.rs:82:5 + | +LL | / pub fn next(&mut self) -> Option { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::iter::Iterator` or choosing a less ambiguous method name + +error: method `not` can be confused for the standard trait method `std::ops::Not::not` + --> tests/ui/should_impl_trait/method_list_2.rs:88:5 + | +LL | / pub fn not(self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Not` or choosing a less ambiguous method name + +error: method `rem` can be confused for the standard trait method `std::ops::Rem::rem` + --> tests/ui/should_impl_trait/method_list_2.rs:94:5 + | +LL | / pub fn rem(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Rem` or choosing a less ambiguous method name + +error: method `shl` can be confused for the standard trait method `std::ops::Shl::shl` + --> tests/ui/should_impl_trait/method_list_2.rs:100:5 + | +LL | / pub fn shl(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Shl` or choosing a less ambiguous method name + +error: method `shr` can be confused for the standard trait method `std::ops::Shr::shr` + --> tests/ui/should_impl_trait/method_list_2.rs:106:5 + | +LL | / pub fn shr(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Shr` or choosing a less ambiguous method name + +error: method `sub` can be confused for the standard trait method `std::ops::Sub::sub` + --> tests/ui/should_impl_trait/method_list_2.rs:112:5 + | +LL | / pub fn sub(self, rhs: Self) -> Self { +LL | | +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | + = help: consider implementing the trait `std::ops::Sub` or choosing a less ambiguous method name + +error: aborting due to 15 previous errors + diff --git a/tests/ui/should_impl_trait/method_list_2.rs b/tests/ui/should_impl_trait/method_list_2.rs index 1f25ab3938a3d..4dfbe7e0f9f4d 100644 --- a/tests/ui/should_impl_trait/method_list_2.rs +++ b/tests/ui/should_impl_trait/method_list_2.rs @@ -1,3 +1,6 @@ +//@revisions: edition2015 edition2021 +//@[edition2015] edition:2015 +//@[edition2021] edition:2021 #![allow( clippy::missing_errors_doc, clippy::needless_pass_by_value, @@ -29,7 +32,7 @@ impl T { } pub fn from_iter(iter: T) -> Self { - //~^ should_implement_trait + //~[edition2021]^ should_implement_trait unimplemented!() } From 17cecfd311848f10c60014fe4714b1475ee545d5 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Tue, 30 Sep 2025 02:00:06 +0800 Subject: [PATCH 66/93] fix: `new_without_default` FP on private type with trait impl --- clippy_lints/src/new_without_default.rs | 2 +- tests/ui/new_without_default.fixed | 20 ++++++++++++++++++++ tests/ui/new_without_default.rs | 20 ++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index b598a390005ba..ab7b09c48a7fe 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -94,7 +94,7 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { return; } if sig.decl.inputs.is_empty() - && cx.effective_visibilities.is_reachable(impl_item.owner_id.def_id) + && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) && let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity() && self_ty == return_ty(cx, impl_item.owner_id) && let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default) diff --git a/tests/ui/new_without_default.fixed b/tests/ui/new_without_default.fixed index 277c335cd8851..b5558cd3086cc 100644 --- a/tests/ui/new_without_default.fixed +++ b/tests/ui/new_without_default.fixed @@ -322,3 +322,23 @@ where Self { _kv: None } } } + +mod issue15778 { + pub struct Foo(Vec); + + impl Foo { + pub fn new() -> Self { + Self(Vec::new()) + } + } + + impl<'a> IntoIterator for &'a Foo { + type Item = &'a i32; + + type IntoIter = std::slice::Iter<'a, i32>; + + fn into_iter(self) -> Self::IntoIter { + self.0.as_slice().iter() + } + } +} diff --git a/tests/ui/new_without_default.rs b/tests/ui/new_without_default.rs index f2844897c93d9..be598c81718f5 100644 --- a/tests/ui/new_without_default.rs +++ b/tests/ui/new_without_default.rs @@ -265,3 +265,23 @@ where Self { _kv: None } } } + +mod issue15778 { + pub struct Foo(Vec); + + impl Foo { + pub fn new() -> Self { + Self(Vec::new()) + } + } + + impl<'a> IntoIterator for &'a Foo { + type Item = &'a i32; + + type IntoIter = std::slice::Iter<'a, i32>; + + fn into_iter(self) -> Self::IntoIter { + self.0.as_slice().iter() + } + } +} From 642feb7e562edcc5d95bbc2e6ca27438fcd3b35f Mon Sep 17 00:00:00 2001 From: yanglsh Date: Tue, 30 Sep 2025 02:28:54 +0800 Subject: [PATCH 67/93] fix: `if_then_some_else_none` FP when return exists in block expr --- clippy_lints/src/if_then_some_else_none.rs | 1 + tests/ui/if_then_some_else_none.fixed | 12 ++++++++++++ tests/ui/if_then_some_else_none.rs | 12 ++++++++++++ 3 files changed, 25 insertions(+) diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index b50d91f101463..f9fee292837ea 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -79,6 +79,7 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { && !is_in_const_context(cx) && self.msrv.meets(cx, msrvs::BOOL_THEN) && !contains_return(then_block.stmts) + && then_block.expr.is_none_or(|expr| !contains_return(expr)) { let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(cx, msrvs::BOOL_THEN_SOME) { sym::then_some diff --git a/tests/ui/if_then_some_else_none.fixed b/tests/ui/if_then_some_else_none.fixed index 0fd130609aee2..7da9401a308f7 100644 --- a/tests/ui/if_then_some_else_none.fixed +++ b/tests/ui/if_then_some_else_none.fixed @@ -206,3 +206,15 @@ fn dont_lint_inside_macros() { } let _: Option = mac!(true, 42); } + +mod issue15770 { + fn maybe_error() -> Result { + Err("error!") + } + + pub fn trying(b: bool) -> Result<(), &'static str> { + let _x: Option = if b { Some(maybe_error()?) } else { None }; + // Process _x locally + Ok(()) + } +} diff --git a/tests/ui/if_then_some_else_none.rs b/tests/ui/if_then_some_else_none.rs index 640828aa9bf6b..02962f83ce8aa 100644 --- a/tests/ui/if_then_some_else_none.rs +++ b/tests/ui/if_then_some_else_none.rs @@ -262,3 +262,15 @@ fn dont_lint_inside_macros() { } let _: Option = mac!(true, 42); } + +mod issue15770 { + fn maybe_error() -> Result { + Err("error!") + } + + pub fn trying(b: bool) -> Result<(), &'static str> { + let _x: Option = if b { Some(maybe_error()?) } else { None }; + // Process _x locally + Ok(()) + } +} From c3e4d2bb90c71040adc120e3322fa2e3696152ab Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 29 Sep 2025 19:21:21 +0200 Subject: [PATCH 68/93] clean-up --- clippy_lints/src/unit_types/let_unit_value.rs | 25 +++++++++++++++++-- tests/ui/let_unit.fixed | 15 +++++------ tests/ui/let_unit.rs | 15 +++++------ tests/ui/let_unit.stderr | 10 ++++---- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index d5b6c17585494..1f05617a746f3 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -89,15 +89,30 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, format_args: &FormatArgsStorag walk_body(&mut visitor, body); let mut has_in_format_capture = false; - suggestions.extend(visitor.spans.iter().filter_map(|span| match span { + suggestions.extend(visitor.spans.into_iter().filter_map(|span| match span { MaybeInFormatCapture::Yes => { has_in_format_capture = true; None }, - MaybeInFormatCapture::No(span) => Some((*span, "()".to_string())), + MaybeInFormatCapture::No(span) => Some((span, "()".to_string())), })); if has_in_format_capture { + // In a case like this: + // ``` + // let unit = returns_unit(); + // eprintln!("{unit}"); + // ``` + // we can't remove the `unit` binding and replace its uses with a `()`, + // because the `eprintln!` would break. + // + // So do the following instead: + // ``` + // let unit = (); + // returns_unit(); + // eprintln!("{unit}"); + // ``` + // TODO: find a less awkward way to do this suggestions.push(( init.span, format!("();\n{}", reindent_multiline(&snip, false, indent_of(cx, local.span))), @@ -132,6 +147,12 @@ struct UnitVariableCollector<'a, 'tcx> { macro_call: Option<&'a FormatArgs>, } +/// Whether the unit variable is captured in a `format!`: +/// +/// ```ignore +/// let unit = (); +/// eprintln!("{unit}"); +/// ``` enum MaybeInFormatCapture { Yes, No(Span), diff --git a/tests/ui/let_unit.fixed b/tests/ui/let_unit.fixed index 381d4cac4622b..d640c6a916d03 100644 --- a/tests/ui/let_unit.fixed +++ b/tests/ui/let_unit.fixed @@ -1,5 +1,5 @@ #![warn(clippy::let_unit_value)] -#![allow(unused, clippy::no_effect, clippy::needless_late_init, path_statements)] +#![allow(clippy::no_effect, clippy::needless_late_init, path_statements)] macro_rules! let_and_return { ($n:expr) => {{ @@ -15,12 +15,12 @@ fn main() { if true { // do not lint this, since () is explicit let _a = (); - let () = dummy(); + let () = returns_unit(); let () = (); - () = dummy(); + () = returns_unit(); () = (); let _a: () = (); - let _a: () = dummy(); + let _a: () = returns_unit(); } consume_units_with_for_loop(); // should be fine as well @@ -30,7 +30,7 @@ fn main() { let_and_return!(()) // should be fine } -fn dummy() {} +fn returns_unit() {} // Related to issue #1964 fn consume_units_with_for_loop() { @@ -181,8 +181,6 @@ async fn issue10433() { pub async fn issue11502(a: ()) {} pub fn issue12594() { - fn returns_unit() {} - fn returns_result(res: T) -> Result { Ok(res) } @@ -200,11 +198,10 @@ pub fn issue12594() { } fn issue15061() { - fn return_unit() {} fn do_something(x: ()) {} let res = (); - return_unit(); + returns_unit(); //~^ let_unit_value do_something(()); println!("{res:?}"); diff --git a/tests/ui/let_unit.rs b/tests/ui/let_unit.rs index cdfc74991c40d..d966d2ec0a5c1 100644 --- a/tests/ui/let_unit.rs +++ b/tests/ui/let_unit.rs @@ -1,5 +1,5 @@ #![warn(clippy::let_unit_value)] -#![allow(unused, clippy::no_effect, clippy::needless_late_init, path_statements)] +#![allow(clippy::no_effect, clippy::needless_late_init, path_statements)] macro_rules! let_and_return { ($n:expr) => {{ @@ -15,12 +15,12 @@ fn main() { if true { // do not lint this, since () is explicit let _a = (); - let () = dummy(); + let () = returns_unit(); let () = (); - () = dummy(); + () = returns_unit(); () = (); let _a: () = (); - let _a: () = dummy(); + let _a: () = returns_unit(); } consume_units_with_for_loop(); // should be fine as well @@ -30,7 +30,7 @@ fn main() { let_and_return!(()) // should be fine } -fn dummy() {} +fn returns_unit() {} // Related to issue #1964 fn consume_units_with_for_loop() { @@ -181,8 +181,6 @@ async fn issue10433() { pub async fn issue11502(a: ()) {} pub fn issue12594() { - fn returns_unit() {} - fn returns_result(res: T) -> Result { Ok(res) } @@ -200,10 +198,9 @@ pub fn issue12594() { } fn issue15061() { - fn return_unit() {} fn do_something(x: ()) {} - let res = return_unit(); + let res = returns_unit(); //~^ let_unit_value do_something(res); println!("{res:?}"); diff --git a/tests/ui/let_unit.stderr b/tests/ui/let_unit.stderr index 637c9ff686bdb..4d9bbbe310749 100644 --- a/tests/ui/let_unit.stderr +++ b/tests/ui/let_unit.stderr @@ -55,7 +55,7 @@ LL + }; | error: this let-binding has unit value - --> tests/ui/let_unit.rs:192:9 + --> tests/ui/let_unit.rs:190:9 | LL | let res = returns_unit(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -69,15 +69,15 @@ LL ~ returns_result(()).unwrap(); | error: this let-binding has unit value - --> tests/ui/let_unit.rs:206:5 + --> tests/ui/let_unit.rs:203:5 | -LL | let res = return_unit(); - | ^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let res = returns_unit(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ | help: replace variable usages with `()` | LL ~ let res = (); -LL ~ return_unit(); +LL ~ returns_unit(); LL | LL ~ do_something(()); | From 48ee4705ddf68c452525b931355cf546a1bba023 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 29 Sep 2025 21:16:15 +0200 Subject: [PATCH 69/93] fix: actually use the `Applicability` --- clippy_lints/src/unit_types/let_unit_value.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index 1f05617a746f3..80a99cfd0544b 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -117,11 +117,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, format_args: &FormatArgsStorag init.span, format!("();\n{}", reindent_multiline(&snip, false, indent_of(cx, local.span))), )); - diag.multipart_suggestion( - "replace variable usages with `()`", - suggestions, - Applicability::MachineApplicable, - ); + diag.multipart_suggestion("replace variable usages with `()`", suggestions, app); return; } } @@ -132,7 +128,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, format_args: &FormatArgsStorag } else { "omit the `let` binding and replace variable usages with `()`" }; - diag.multipart_suggestion(message, suggestions, Applicability::MachineApplicable); + diag.multipart_suggestion(message, suggestions, app); }, ); } From f6dd112a0ae3e017828dd49a5dc4e5edd648bca6 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 29 Sep 2025 19:20:47 +0200 Subject: [PATCH 70/93] fix: create the suggestion "differentially" --- clippy_lints/src/unit_types/let_unit_value.rs | 18 +++-- tests/ui/let_unit.fixed | 31 +++++++- tests/ui/let_unit.rs | 30 ++++++- tests/ui/let_unit.stderr | 78 +++++++++++++------ 4 files changed, 118 insertions(+), 39 deletions(-) diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index 80a99cfd0544b..424aa14cd6863 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::{FormatArgsStorage, find_format_arg_expr, is_format_macro, root_macro_call_first_node}; -use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_context}; +use clippy_utils::source::{snippet_indent, walk_span_to_context}; use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source}; use core::ops::ControlFlow; use rustc_ast::{FormatArgs, FormatArgumentKind}; @@ -74,10 +74,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, format_args: &FormatArgsStorag "this let-binding has unit value", |diag| { let mut suggestions = Vec::new(); + let init_new_span = walk_span_to_context(init.span, local.span.ctxt()).unwrap(); // Suggest omitting the `let` binding - let mut app = Applicability::MachineApplicable; - let snip = snippet_with_context(cx, init.span, local.span.ctxt(), "()", &mut app).0; + let app = Applicability::MachineApplicable; // If this is a binding pattern, we need to add suggestions to remove any usages // of the variable @@ -114,21 +114,23 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, format_args: &FormatArgsStorag // ``` // TODO: find a less awkward way to do this suggestions.push(( - init.span, - format!("();\n{}", reindent_multiline(&snip, false, indent_of(cx, local.span))), + init_new_span.shrink_to_lo(), + format!("();\n{}", snippet_indent(cx, local.span).as_deref().unwrap_or("")), )); - diag.multipart_suggestion("replace variable usages with `()`", suggestions, app); + diag.multipart_suggestion_verbose("replace variable usages with `()`", suggestions, app); return; } } - suggestions.push((local.span, format!("{snip};"))); + // let local = returns_unit(); + // ^^^^^^^^^^^^ remove this + suggestions.push((local.span.until(init_new_span), String::new())); let message = if suggestions.len() == 1 { "omit the `let` binding" } else { "omit the `let` binding and replace variable usages with `()`" }; - diag.multipart_suggestion(message, suggestions, app); + diag.multipart_suggestion_verbose(message, suggestions, app); }, ); } diff --git a/tests/ui/let_unit.fixed b/tests/ui/let_unit.fixed index d640c6a916d03..e8517f18e788b 100644 --- a/tests/ui/let_unit.fixed +++ b/tests/ui/let_unit.fixed @@ -1,5 +1,10 @@ #![warn(clippy::let_unit_value)] -#![allow(clippy::no_effect, clippy::needless_late_init, path_statements)] +#![allow( + clippy::no_effect, + clippy::needless_late_init, + path_statements, + clippy::match_single_binding +)] macro_rules! let_and_return { ($n:expr) => {{ @@ -197,12 +202,30 @@ pub fn issue12594() { } } -fn issue15061() { - fn do_something(x: ()) {} +fn takes_unit(x: ()) {} +fn issue15061() { let res = (); returns_unit(); //~^ let_unit_value - do_something(()); + takes_unit(()); + println!("{res:?}"); +} + +fn issue15771() { + match "Example String" { + _ => returns_unit(), + //~^ let_unit_value + } + + if true {} + //~^ let_unit_value +} + +fn issue_15784() { + let res = (); + eprintln!("I return unit"); + //~^ let_unit_value + takes_unit(()); println!("{res:?}"); } diff --git a/tests/ui/let_unit.rs b/tests/ui/let_unit.rs index d966d2ec0a5c1..3f6f0139b2fec 100644 --- a/tests/ui/let_unit.rs +++ b/tests/ui/let_unit.rs @@ -1,5 +1,10 @@ #![warn(clippy::let_unit_value)] -#![allow(clippy::no_effect, clippy::needless_late_init, path_statements)] +#![allow( + clippy::no_effect, + clippy::needless_late_init, + path_statements, + clippy::match_single_binding +)] macro_rules! let_and_return { ($n:expr) => {{ @@ -197,11 +202,28 @@ pub fn issue12594() { } } -fn issue15061() { - fn do_something(x: ()) {} +fn takes_unit(x: ()) {} +fn issue15061() { let res = returns_unit(); //~^ let_unit_value - do_something(res); + takes_unit(res); + println!("{res:?}"); +} + +fn issue15771() { + match "Example String" { + _ => _ = returns_unit(), + //~^ let_unit_value + } + + _ = if true {} + //~^ let_unit_value +} + +fn issue_15784() { + let res = eprintln!("I return unit"); + //~^ let_unit_value + takes_unit(res); println!("{res:?}"); } diff --git a/tests/ui/let_unit.stderr b/tests/ui/let_unit.stderr index 4d9bbbe310749..8ced32ab828f7 100644 --- a/tests/ui/let_unit.stderr +++ b/tests/ui/let_unit.stderr @@ -1,14 +1,19 @@ error: this let-binding has unit value - --> tests/ui/let_unit.rs:11:5 + --> tests/ui/let_unit.rs:16:5 | LL | let _x = println!("x"); - | ^^^^^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `println!("x");` + | ^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::let-unit-value` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::let_unit_value)]` +help: omit the `let` binding + | +LL - let _x = println!("x"); +LL + println!("x"); + | error: this let-binding has unit value - --> tests/ui/let_unit.rs:60:5 + --> tests/ui/let_unit.rs:65:5 | LL | / let _ = v LL | | @@ -21,18 +26,12 @@ LL | | .unwrap(); | help: omit the `let` binding | -LL ~ v -LL + -LL + .into_iter() -LL + .map(|i| i * 2) -LL + .filter(|i| i.is_multiple_of(2)) -LL + .map(|_| ()) -LL + .next() -LL + .unwrap(); +LL - let _ = v +LL + v | error: this let-binding has unit value - --> tests/ui/let_unit.rs:110:5 + --> tests/ui/let_unit.rs:115:5 | LL | / let x = match Some(0) { LL | | @@ -45,17 +44,12 @@ LL | | }; | help: omit the `let` binding | -LL ~ match Some(0) { -LL + -LL + None => f2(1), -LL + Some(0) => f(), -LL + Some(1) => f2(3), -LL + Some(_) => (), -LL + }; +LL - let x = match Some(0) { +LL + match Some(0) { | error: this let-binding has unit value - --> tests/ui/let_unit.rs:190:9 + --> tests/ui/let_unit.rs:195:9 | LL | let res = returns_unit(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -69,7 +63,7 @@ LL ~ returns_result(()).unwrap(); | error: this let-binding has unit value - --> tests/ui/let_unit.rs:203:5 + --> tests/ui/let_unit.rs:208:5 | LL | let res = returns_unit(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -79,8 +73,46 @@ help: replace variable usages with `()` LL ~ let res = (); LL ~ returns_unit(); LL | -LL ~ do_something(()); +LL ~ takes_unit(()); + | + +error: this let-binding has unit value + --> tests/ui/let_unit.rs:216:14 + | +LL | _ => _ = returns_unit(), + | ^^^^^^^^^^^^^^^^^^ + | +help: omit the `let` binding + | +LL - _ => _ = returns_unit(), +LL + _ => returns_unit(), + | + +error: this let-binding has unit value + --> tests/ui/let_unit.rs:220:5 + | +LL | _ = if true {} + | ^^^^^^^^^^^^^^ + | +help: omit the `let` binding + | +LL - _ = if true {} +LL + if true {} + | + +error: this let-binding has unit value + --> tests/ui/let_unit.rs:225:5 + | +LL | let res = eprintln!("I return unit"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace variable usages with `()` + | +LL ~ let res = (); +LL ~ eprintln!("I return unit"); +LL | +LL ~ takes_unit(()); | -error: aborting due to 5 previous errors +error: aborting due to 8 previous errors From 93355f1c7b279866718f76609450cfa1b8cc9973 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 19 Sep 2025 20:17:07 +0200 Subject: [PATCH 71/93] clean-up - reduce indentation - use `snippet_with_applicability` --- clippy_lints/src/new_without_default.rs | 170 ++++++++++++------------ tests/ui/new_without_default.fixed | 1 - tests/ui/new_without_default.rs | 1 - tests/ui/new_without_default.stderr | 18 +-- 4 files changed, 92 insertions(+), 98 deletions(-) diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index ab7b09c48a7fe..5a409a811c30a 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::return_ty; -use clippy_utils::source::snippet; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::DiagExt; use rustc_errors::Applicability; use rustc_hir as hir; @@ -58,103 +58,99 @@ impl_lint_pass!(NewWithoutDefault => [NEW_WITHOUT_DEFAULT]); impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { - if let hir::ItemKind::Impl(hir::Impl { + let hir::ItemKind::Impl(hir::Impl { of_trait: None, generics, self_ty: impl_self_ty, .. }) = item.kind + else { + return; + }; + + for assoc_item in cx + .tcx + .associated_items(item.owner_id.def_id) + .filter_by_name_unhygienic(sym::new) { - for assoc_item in cx - .tcx - .associated_items(item.owner_id.def_id) - .filter_by_name_unhygienic(sym::new) + if let AssocKind::Fn { has_self: false, .. } = assoc_item.kind + && let impl_item = cx + .tcx + .hir_node_by_def_id(assoc_item.def_id.expect_local()) + .expect_impl_item() + && !impl_item.span.in_external_macro(cx.sess().source_map()) + && let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind + && let id = impl_item.owner_id + // can't be implemented for unsafe new + && !sig.header.is_unsafe() + // shouldn't be implemented when it is hidden in docs + && !cx.tcx.is_doc_hidden(impl_item.owner_id.def_id) + // when the result of `new()` depends on a parameter we should not require + // an impl of `Default` + && impl_item.generics.params.is_empty() + && sig.decl.inputs.is_empty() + && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) + && let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity() + && self_ty == return_ty(cx, impl_item.owner_id) + && let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default) { - if let AssocKind::Fn { has_self: false, .. } = assoc_item.kind { - let impl_item = cx - .tcx - .hir_node_by_def_id(assoc_item.def_id.expect_local()) - .expect_impl_item(); - if impl_item.span.in_external_macro(cx.sess().source_map()) { - return; - } - if let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind { - let id = impl_item.owner_id; - if sig.header.is_unsafe() { - // can't be implemented for unsafe new - return; - } - if cx.tcx.is_doc_hidden(impl_item.owner_id.def_id) { - // shouldn't be implemented when it is hidden in docs - return; - } - if !impl_item.generics.params.is_empty() { - // when the result of `new()` depends on a parameter we should not require - // an impl of `Default` - return; - } - if sig.decl.inputs.is_empty() - && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) - && let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity() - && self_ty == return_ty(cx, impl_item.owner_id) - && let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default) + if self.impling_types.is_none() { + let mut impls = HirIdSet::default(); + for &d in cx.tcx.local_trait_impls(default_trait_id) { + let ty = cx.tcx.type_of(d).instantiate_identity(); + if let Some(ty_def) = ty.ty_adt_def() + && let Some(local_def_id) = ty_def.did().as_local() { - if self.impling_types.is_none() { - let mut impls = HirIdSet::default(); - for &d in cx.tcx.local_trait_impls(default_trait_id) { - let ty = cx.tcx.type_of(d).instantiate_identity(); - if let Some(ty_def) = ty.ty_adt_def() - && let Some(local_def_id) = ty_def.did().as_local() - { - impls.insert(cx.tcx.local_def_id_to_hir_id(local_def_id)); - } - } - self.impling_types = Some(impls); - } - - // Check if a Default implementation exists for the Self type, regardless of - // generics - if let Some(ref impling_types) = self.impling_types - && let self_def = cx.tcx.type_of(item.owner_id).instantiate_identity() - && let Some(self_def) = self_def.ty_adt_def() - && let Some(self_local_did) = self_def.did().as_local() - && let self_id = cx.tcx.local_def_id_to_hir_id(self_local_did) - && impling_types.contains(&self_id) - { - return; - } - - let generics_sugg = snippet(cx, generics.span, ""); - let where_clause_sugg = if generics.has_where_clause_predicates { - format!("\n{}\n", snippet(cx, generics.where_clause_span, "")) - } else { - String::new() - }; - let self_ty_fmt = self_ty.to_string(); - let self_type_snip = snippet(cx, impl_self_ty.span, &self_ty_fmt); - span_lint_hir_and_then( - cx, - NEW_WITHOUT_DEFAULT, - id.into(), - impl_item.span, - format!("you should consider adding a `Default` implementation for `{self_type_snip}`"), - |diag| { - diag.suggest_prepend_item( - cx, - item.span, - "try adding this", - &create_new_without_default_suggest_msg( - &self_type_snip, - &generics_sugg, - &where_clause_sugg, - ), - Applicability::MachineApplicable, - ); - }, - ); + impls.insert(cx.tcx.local_def_id_to_hir_id(local_def_id)); } } + self.impling_types = Some(impls); } + + // Check if a Default implementation exists for the Self type, regardless of + // generics + if let Some(ref impling_types) = self.impling_types + && let self_def = cx.tcx.type_of(item.owner_id).instantiate_identity() + && let Some(self_def) = self_def.ty_adt_def() + && let Some(self_local_did) = self_def.did().as_local() + && let self_id = cx.tcx.local_def_id_to_hir_id(self_local_did) + && impling_types.contains(&self_id) + { + return; + } + + let mut app = Applicability::MachineApplicable; + let generics_sugg = snippet_with_applicability(cx, generics.span, "", &mut app); + let where_clause_sugg = if generics.has_where_clause_predicates { + format!( + "\n{}\n", + snippet_with_applicability(cx, generics.where_clause_span, "", &mut app) + ) + } else { + String::new() + }; + let self_ty_fmt = self_ty.to_string(); + let self_type_snip = snippet_with_applicability(cx, impl_self_ty.span, &self_ty_fmt, &mut app); + span_lint_hir_and_then( + cx, + NEW_WITHOUT_DEFAULT, + id.into(), + impl_item.span, + format!("you should consider adding a `Default` implementation for `{self_type_snip}`"), + |diag| { + diag.suggest_prepend_item( + cx, + item.span, + "try adding this", + &create_new_without_default_suggest_msg( + &self_type_snip, + &generics_sugg, + &where_clause_sugg, + ), + app, + ); + }, + ); } } } diff --git a/tests/ui/new_without_default.fixed b/tests/ui/new_without_default.fixed index b5558cd3086cc..4eb2a9903b9a8 100644 --- a/tests/ui/new_without_default.fixed +++ b/tests/ui/new_without_default.fixed @@ -1,5 +1,4 @@ #![allow( - dead_code, clippy::missing_safety_doc, clippy::extra_unused_lifetimes, clippy::extra_unused_type_parameters, diff --git a/tests/ui/new_without_default.rs b/tests/ui/new_without_default.rs index be598c81718f5..cfcff1e3ac3d0 100644 --- a/tests/ui/new_without_default.rs +++ b/tests/ui/new_without_default.rs @@ -1,5 +1,4 @@ #![allow( - dead_code, clippy::missing_safety_doc, clippy::extra_unused_lifetimes, clippy::extra_unused_type_parameters, diff --git a/tests/ui/new_without_default.stderr b/tests/ui/new_without_default.stderr index 70a65aba464b8..a0b4d3704f56c 100644 --- a/tests/ui/new_without_default.stderr +++ b/tests/ui/new_without_default.stderr @@ -1,5 +1,5 @@ error: you should consider adding a `Default` implementation for `Foo` - --> tests/ui/new_without_default.rs:13:5 + --> tests/ui/new_without_default.rs:12:5 | LL | / pub fn new() -> Foo { LL | | @@ -20,7 +20,7 @@ LL + } | error: you should consider adding a `Default` implementation for `Bar` - --> tests/ui/new_without_default.rs:23:5 + --> tests/ui/new_without_default.rs:22:5 | LL | / pub fn new() -> Self { LL | | @@ -39,7 +39,7 @@ LL + } | error: you should consider adding a `Default` implementation for `LtKo<'c>` - --> tests/ui/new_without_default.rs:89:5 + --> tests/ui/new_without_default.rs:88:5 | LL | / pub fn new() -> LtKo<'c> { LL | | @@ -58,7 +58,7 @@ LL + } | error: you should consider adding a `Default` implementation for `Const` - --> tests/ui/new_without_default.rs:123:5 + --> tests/ui/new_without_default.rs:122:5 | LL | / pub const fn new() -> Const { LL | | @@ -76,7 +76,7 @@ LL + } | error: you should consider adding a `Default` implementation for `NewNotEqualToDerive` - --> tests/ui/new_without_default.rs:184:5 + --> tests/ui/new_without_default.rs:183:5 | LL | / pub fn new() -> Self { LL | | @@ -95,7 +95,7 @@ LL + } | error: you should consider adding a `Default` implementation for `FooGenerics` - --> tests/ui/new_without_default.rs:194:5 + --> tests/ui/new_without_default.rs:193:5 | LL | / pub fn new() -> Self { LL | | @@ -114,7 +114,7 @@ LL + } | error: you should consider adding a `Default` implementation for `BarGenerics` - --> tests/ui/new_without_default.rs:203:5 + --> tests/ui/new_without_default.rs:202:5 | LL | / pub fn new() -> Self { LL | | @@ -133,7 +133,7 @@ LL + } | error: you should consider adding a `Default` implementation for `Foo` - --> tests/ui/new_without_default.rs:216:9 + --> tests/ui/new_without_default.rs:215:9 | LL | / pub fn new() -> Self { LL | | @@ -154,7 +154,7 @@ LL ~ impl Foo { | error: you should consider adding a `Default` implementation for `MyStruct` - --> tests/ui/new_without_default.rs:263:5 + --> tests/ui/new_without_default.rs:262:5 | LL | / pub fn new() -> Self { LL | | From 7a04adae7d68f9a404e5843039479b954cfa0bb5 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 19 Sep 2025 22:21:16 +0200 Subject: [PATCH 72/93] fix(new_without_default): if `new` has `#[cfg]`, copy that onto `impl Default` --- clippy_lints/src/new_without_default.rs | 30 +++++++-- tests/ui/new_without_default.fixed | 68 +++++++++++++++++++++ tests/ui/new_without_default.rs | 40 ++++++++++++ tests/ui/new_without_default.stderr | 81 ++++++++++++++++++++++++- 4 files changed, 213 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index 5a409a811c30a..6fc034b6fc5d2 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -74,10 +74,8 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { .filter_by_name_unhygienic(sym::new) { if let AssocKind::Fn { has_self: false, .. } = assoc_item.kind - && let impl_item = cx - .tcx - .hir_node_by_def_id(assoc_item.def_id.expect_local()) - .expect_impl_item() + && let assoc_item_hir_id = cx.tcx.local_def_id_to_hir_id(assoc_item.def_id.expect_local()) + && let impl_item = cx.tcx.hir_node(assoc_item_hir_id).expect_impl_item() && !impl_item.span.in_external_macro(cx.sess().source_map()) && let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind && let id = impl_item.owner_id @@ -120,6 +118,26 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { } let mut app = Applicability::MachineApplicable; + let attrs_sugg = { + let mut sugg = String::new(); + for attr in cx.tcx.hir_attrs(assoc_item_hir_id) { + if !attr.has_name(sym::cfg_trace) { + // This might be some other attribute that the `impl Default` ought to inherit. + // But it could also be one of the many attributes that: + // - can't be put on an impl block -- like `#[inline]` + // - we can't even build a suggestion for, since `Attribute::span` may panic. + // + // Because of all that, remain on the safer side -- don't inherit this attr, and just + // reduce the applicability + app = Applicability::MaybeIncorrect; + continue; + } + + sugg.push_str(&snippet_with_applicability(cx.sess(), attr.span(), "_", &mut app)); + sugg.push('\n'); + } + sugg + }; let generics_sugg = snippet_with_applicability(cx, generics.span, "", &mut app); let where_clause_sugg = if generics.has_where_clause_predicates { format!( @@ -143,6 +161,7 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { item.span, "try adding this", &create_new_without_default_suggest_msg( + &attrs_sugg, &self_type_snip, &generics_sugg, &where_clause_sugg, @@ -157,13 +176,14 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { } fn create_new_without_default_suggest_msg( + attrs_sugg: &str, self_type_snip: &str, generics_sugg: &str, where_clause_sugg: &str, ) -> String { #[rustfmt::skip] format!( -"impl{generics_sugg} Default for {self_type_snip}{where_clause_sugg} {{ +"{attrs_sugg}impl{generics_sugg} Default for {self_type_snip}{where_clause_sugg} {{ fn default() -> Self {{ Self::new() }} diff --git a/tests/ui/new_without_default.fixed b/tests/ui/new_without_default.fixed index 4eb2a9903b9a8..9a5e90b48065c 100644 --- a/tests/ui/new_without_default.fixed +++ b/tests/ui/new_without_default.fixed @@ -322,6 +322,74 @@ where } } +// From issue #14552, but with `#[cfg]`s that are actually `true` in the uitest context + +pub struct NewWithCfg; +#[cfg(not(test))] +impl Default for NewWithCfg { + fn default() -> Self { + Self::new() + } +} + +impl NewWithCfg { + #[cfg(not(test))] + pub fn new() -> Self { + //~^ new_without_default + unimplemented!() + } +} + +pub struct NewWith2Cfgs; +#[cfg(not(test))] +#[cfg(panic = "unwind")] +impl Default for NewWith2Cfgs { + fn default() -> Self { + Self::new() + } +} + +impl NewWith2Cfgs { + #[cfg(not(test))] + #[cfg(panic = "unwind")] + pub fn new() -> Self { + //~^ new_without_default + unimplemented!() + } +} + +pub struct NewWithExtraneous; +impl Default for NewWithExtraneous { + fn default() -> Self { + Self::new() + } +} + +impl NewWithExtraneous { + #[inline] + pub fn new() -> Self { + //~^ new_without_default + unimplemented!() + } +} + +pub struct NewWithCfgAndExtraneous; +#[cfg(not(test))] +impl Default for NewWithCfgAndExtraneous { + fn default() -> Self { + Self::new() + } +} + +impl NewWithCfgAndExtraneous { + #[cfg(not(test))] + #[inline] + pub fn new() -> Self { + //~^ new_without_default + unimplemented!() + } +} + mod issue15778 { pub struct Foo(Vec); diff --git a/tests/ui/new_without_default.rs b/tests/ui/new_without_default.rs index cfcff1e3ac3d0..f7466aa321896 100644 --- a/tests/ui/new_without_default.rs +++ b/tests/ui/new_without_default.rs @@ -265,6 +265,46 @@ where } } +// From issue #14552, but with `#[cfg]`s that are actually `true` in the uitest context + +pub struct NewWithCfg; +impl NewWithCfg { + #[cfg(not(test))] + pub fn new() -> Self { + //~^ new_without_default + unimplemented!() + } +} + +pub struct NewWith2Cfgs; +impl NewWith2Cfgs { + #[cfg(not(test))] + #[cfg(panic = "unwind")] + pub fn new() -> Self { + //~^ new_without_default + unimplemented!() + } +} + +pub struct NewWithExtraneous; +impl NewWithExtraneous { + #[inline] + pub fn new() -> Self { + //~^ new_without_default + unimplemented!() + } +} + +pub struct NewWithCfgAndExtraneous; +impl NewWithCfgAndExtraneous { + #[cfg(not(test))] + #[inline] + pub fn new() -> Self { + //~^ new_without_default + unimplemented!() + } +} + mod issue15778 { pub struct Foo(Vec); diff --git a/tests/ui/new_without_default.stderr b/tests/ui/new_without_default.stderr index a0b4d3704f56c..1e0d5e2131994 100644 --- a/tests/ui/new_without_default.stderr +++ b/tests/ui/new_without_default.stderr @@ -174,5 +174,84 @@ LL + } LL + } | -error: aborting due to 9 previous errors +error: you should consider adding a `Default` implementation for `NewWithCfg` + --> tests/ui/new_without_default.rs:273:5 + | +LL | / pub fn new() -> Self { +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | +help: try adding this + | +LL + #[cfg(not(test))] +LL + impl Default for NewWithCfg { +LL + fn default() -> Self { +LL + Self::new() +LL + } +LL + } +LL | impl NewWithCfg { + | + +error: you should consider adding a `Default` implementation for `NewWith2Cfgs` + --> tests/ui/new_without_default.rs:283:5 + | +LL | / pub fn new() -> Self { +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | +help: try adding this + | +LL + #[cfg(not(test))] +LL + #[cfg(panic = "unwind")] +LL + impl Default for NewWith2Cfgs { +LL + fn default() -> Self { +LL + Self::new() +LL + } +LL + } +LL | impl NewWith2Cfgs { + | + +error: you should consider adding a `Default` implementation for `NewWithExtraneous` + --> tests/ui/new_without_default.rs:292:5 + | +LL | / pub fn new() -> Self { +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | +help: try adding this + | +LL + impl Default for NewWithExtraneous { +LL + fn default() -> Self { +LL + Self::new() +LL + } +LL + } + | + +error: you should consider adding a `Default` implementation for `NewWithCfgAndExtraneous` + --> tests/ui/new_without_default.rs:302:5 + | +LL | / pub fn new() -> Self { +LL | | +LL | | unimplemented!() +LL | | } + | |_____^ + | +help: try adding this + | +LL + #[cfg(not(test))] +LL + impl Default for NewWithCfgAndExtraneous { +LL + fn default() -> Self { +LL + Self::new() +LL + } +LL + } +LL | impl NewWithCfgAndExtraneous { + | + +error: aborting due to 13 previous errors From 1da0092218e4b7bbc25034c3e5b98cb85a01a619 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 30 Sep 2025 13:05:45 +0200 Subject: [PATCH 73/93] `redundant_pattern_match`: clean-up - cast `&[Arm]` to `&[Arm; 2]` early on to hopefully avoid bounds checks - use `if let [pattern] = X { .. }` instead of `if let patterns = X { let pattern = patterns[0]; .. }` - use `Symbol`s instead of `&str`s - reduce indentation --- .../src/matches/redundant_pattern_match.rs | 147 ++++++++---------- 1 file changed, 67 insertions(+), 80 deletions(-) diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index c936c96f9719a..9d0115791838c 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -269,66 +269,61 @@ fn find_method_sugg_for_if_let<'tcx>( } pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) { - if arms.len() == 2 { - let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind); - - if let Some((good_method, maybe_guard)) = found_good_method(cx, arms, node_pair) { - let span = is_expn_of(expr.span, sym::matches).unwrap_or(expr.span.to(op.span)); - let result_expr = match &op.kind { - ExprKind::AddrOf(_, _, borrowed) => borrowed, - _ => op, - }; - let mut app = Applicability::MachineApplicable; - let receiver_sugg = Sugg::hir_with_applicability(cx, result_expr, "_", &mut app).maybe_paren(); - let mut sugg = format!("{receiver_sugg}.{good_method}"); - - if let Some(guard) = maybe_guard { - // wow, the HIR for match guards in `PAT if let PAT = expr && expr => ...` is annoying! - // `guard` here is `Guard::If` with the let expression somewhere deep in the tree of exprs, - // counter to the intuition that it should be `Guard::IfLet`, so we need another check - // to see that there aren't any let chains anywhere in the guard, as that would break - // if we suggest `t.is_none() && (let X = y && z)` for: - // `match t { None if let X = y && z => true, _ => false }` - let has_nested_let_chain = for_each_expr_without_closures(guard, |expr| { - if matches!(expr.kind, ExprKind::Let(..)) { - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) - } - }) - .is_some(); - - if has_nested_let_chain { - return; + if let Ok(arms) = arms.try_into() // TODO: use `slice::as_array` once stabilized + && let Some((good_method, maybe_guard)) = found_good_method(cx, arms) + { + let span = is_expn_of(expr.span, sym::matches).unwrap_or(expr.span.to(op.span)); + let result_expr = match &op.kind { + ExprKind::AddrOf(_, _, borrowed) => borrowed, + _ => op, + }; + let mut app = Applicability::MachineApplicable; + let receiver_sugg = Sugg::hir_with_applicability(cx, result_expr, "_", &mut app).maybe_paren(); + let mut sugg = format!("{receiver_sugg}.{good_method}"); + + if let Some(guard) = maybe_guard { + // wow, the HIR for match guards in `PAT if let PAT = expr && expr => ...` is annoying! + // `guard` here is `Guard::If` with the let expression somewhere deep in the tree of exprs, + // counter to the intuition that it should be `Guard::IfLet`, so we need another check + // to see that there aren't any let chains anywhere in the guard, as that would break + // if we suggest `t.is_none() && (let X = y && z)` for: + // `match t { None if let X = y && z => true, _ => false }` + let has_nested_let_chain = for_each_expr_without_closures(guard, |expr| { + if matches!(expr.kind, ExprKind::Let(..)) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) } + }) + .is_some(); - let guard = Sugg::hir(cx, guard, ".."); - let _ = write!(sugg, " && {}", guard.maybe_paren()); + if has_nested_let_chain { + return; } - span_lint_and_sugg( - cx, - REDUNDANT_PATTERN_MATCHING, - span, - format!("redundant pattern matching, consider using `{good_method}`"), - "try", - sugg, - app, - ); + let guard = Sugg::hir(cx, guard, ".."); + let _ = write!(sugg, " && {}", guard.maybe_paren()); } + + span_lint_and_sugg( + cx, + REDUNDANT_PATTERN_MATCHING, + span, + format!("redundant pattern matching, consider using `{good_method}`"), + "try", + sugg, + app, + ); } } fn found_good_method<'tcx>( cx: &LateContext<'_>, - arms: &'tcx [Arm<'tcx>], - node: (&PatKind<'_>, &PatKind<'_>), + arms: &'tcx [Arm<'tcx>; 2], ) -> Option<(&'static str, Option<&'tcx Expr<'tcx>>)> { - match node { - (PatKind::TupleStruct(path_left, patterns_left, _), PatKind::TupleStruct(path_right, patterns_right, _)) - if patterns_left.len() == 1 && patterns_right.len() == 1 => - { - if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) { + match (&arms[0].pat.kind, &arms[1].pat.kind) { + (PatKind::TupleStruct(path_left, [pattern_left], _), PatKind::TupleStruct(path_right, [pattern_right], _)) => { + if let (PatKind::Wild, PatKind::Wild) = (&pattern_left.kind, &pattern_right.kind) { find_good_method_for_match( cx, arms, @@ -356,7 +351,7 @@ fn found_good_method<'tcx>( } }, ( - PatKind::TupleStruct(path_left, patterns, _), + PatKind::TupleStruct(path_left, [pattern], _), PatKind::Expr(PatExpr { kind: PatExprKind::Path(path_right), .. @@ -367,9 +362,9 @@ fn found_good_method<'tcx>( kind: PatExprKind::Path(path_left), .. }), - PatKind::TupleStruct(path_right, patterns, _), - ) if patterns.len() == 1 => { - if let PatKind::Wild = patterns[0].kind { + PatKind::TupleStruct(path_right, [pattern], _), + ) => { + if let PatKind::Wild = pattern.kind { find_good_method_for_match( cx, arms, @@ -396,8 +391,8 @@ fn found_good_method<'tcx>( None } }, - (PatKind::TupleStruct(path_left, patterns, _), PatKind::Wild) if patterns.len() == 1 => { - if let PatKind::Wild = patterns[0].kind { + (PatKind::TupleStruct(path_left, [pattern], _), PatKind::Wild) => { + if let PatKind::Wild = pattern.kind { get_good_method(cx, arms, path_left) } else { None @@ -426,31 +421,23 @@ fn get_ident(path: &QPath<'_>) -> Option { fn get_good_method<'tcx>( cx: &LateContext<'_>, - arms: &'tcx [Arm<'tcx>], + arms: &'tcx [Arm<'tcx>; 2], path_left: &QPath<'_>, ) -> Option<(&'static str, Option<&'tcx Expr<'tcx>>)> { - if let Some(name) = get_ident(path_left) { - let (expected_item_left, should_be_left, should_be_right) = match name.as_str() { - "Ok" => (Item::Lang(ResultOk), "is_ok()", "is_err()"), - "Err" => (Item::Lang(ResultErr), "is_err()", "is_ok()"), - "Some" => (Item::Lang(OptionSome), "is_some()", "is_none()"), - "None" => (Item::Lang(OptionNone), "is_none()", "is_some()"), - "Ready" => (Item::Lang(PollReady), "is_ready()", "is_pending()"), - "Pending" => (Item::Lang(PollPending), "is_pending()", "is_ready()"), - "V4" => (Item::Diag(sym::IpAddr, sym::V4), "is_ipv4()", "is_ipv6()"), - "V6" => (Item::Diag(sym::IpAddr, sym::V6), "is_ipv6()", "is_ipv4()"), - _ => return None, - }; - return find_good_method_for_matches_macro( - cx, - arms, - path_left, - expected_item_left, - should_be_left, - should_be_right, - ); - } - None + let ident = get_ident(path_left)?; + + let (expected_item_left, should_be_left, should_be_right) = match ident.name { + sym::Ok => (Item::Lang(ResultOk), "is_ok()", "is_err()"), + sym::Err => (Item::Lang(ResultErr), "is_err()", "is_ok()"), + sym::Some => (Item::Lang(OptionSome), "is_some()", "is_none()"), + sym::None => (Item::Lang(OptionNone), "is_none()", "is_some()"), + sym::Ready => (Item::Lang(PollReady), "is_ready()", "is_pending()"), + sym::Pending => (Item::Lang(PollPending), "is_pending()", "is_ready()"), + sym::V4 => (Item::Diag(sym::IpAddr, sym::V4), "is_ipv4()", "is_ipv6()"), + sym::V6 => (Item::Diag(sym::IpAddr, sym::V6), "is_ipv6()", "is_ipv4()"), + _ => return None, + }; + find_good_method_for_matches_macro(cx, arms, path_left, expected_item_left, should_be_left, should_be_right) } #[derive(Clone, Copy)] @@ -490,7 +477,7 @@ fn is_pat_variant(cx: &LateContext<'_>, pat: &Pat<'_>, path: &QPath<'_>, expecte #[expect(clippy::too_many_arguments)] fn find_good_method_for_match<'a, 'tcx>( cx: &LateContext<'_>, - arms: &'tcx [Arm<'tcx>], + arms: &'tcx [Arm<'tcx>; 2], path_left: &QPath<'_>, path_right: &QPath<'_>, expected_item_left: Item, @@ -525,7 +512,7 @@ fn find_good_method_for_match<'a, 'tcx>( fn find_good_method_for_matches_macro<'a, 'tcx>( cx: &LateContext<'_>, - arms: &'tcx [Arm<'tcx>], + arms: &'tcx [Arm<'tcx>; 2], path_left: &QPath<'_>, expected_item_left: Item, should_be_left: &'a str, From 089fbd35aa7c606bc61c4fef6bf3da747d507b75 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Tue, 30 Sep 2025 10:53:35 +0800 Subject: [PATCH 74/93] fix: `let_unit_value` suggests wrongly for field init shorthand --- clippy_lints/src/unit_types/let_unit_value.rs | 48 +++++++++++++------ tests/ui/let_unit.fixed | 10 ++++ tests/ui/let_unit.rs | 10 ++++ tests/ui/let_unit.stderr | 16 ++++++- 4 files changed, 69 insertions(+), 15 deletions(-) diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index 424aa14cd6863..2645e94358e11 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -90,11 +90,12 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, format_args: &FormatArgsStorag let mut has_in_format_capture = false; suggestions.extend(visitor.spans.into_iter().filter_map(|span| match span { - MaybeInFormatCapture::Yes => { + VariableUsage::FormatCapture => { has_in_format_capture = true; None }, - MaybeInFormatCapture::No(span) => Some((span, "()".to_string())), + VariableUsage::Normal(span) => Some((span, "()".to_string())), + VariableUsage::FieldShorthand(span) => Some((span.shrink_to_hi(), ": ()".to_string())), })); if has_in_format_capture { @@ -141,19 +142,30 @@ struct UnitVariableCollector<'a, 'tcx> { cx: &'a LateContext<'tcx>, format_args: &'a FormatArgsStorage, id: HirId, - spans: Vec, + spans: Vec, macro_call: Option<&'a FormatArgs>, } -/// Whether the unit variable is captured in a `format!`: -/// -/// ```ignore -/// let unit = (); -/// eprintln!("{unit}"); -/// ``` -enum MaybeInFormatCapture { - Yes, - No(Span), +/// How the unit variable is used +enum VariableUsage { + Normal(Span), + /// Captured in a `format!`: + /// + /// ```ignore + /// let unit = (); + /// eprintln!("{unit}"); + /// ``` + FormatCapture, + /// In a field shorthand init: + /// + /// ```ignore + /// struct Foo { + /// unit: (), + /// } + /// let unit = (); + /// Foo { unit }; + /// ``` + FieldShorthand(Span), } impl<'a, 'tcx> UnitVariableCollector<'a, 'tcx> { @@ -193,9 +205,17 @@ impl<'tcx> Visitor<'tcx> for UnitVariableCollector<'_, 'tcx> { matches!(arg.kind, FormatArgumentKind::Captured(_)) && find_format_arg_expr(ex, arg).is_some() }) { - self.spans.push(MaybeInFormatCapture::Yes); + self.spans.push(VariableUsage::FormatCapture); } else { - self.spans.push(MaybeInFormatCapture::No(path.span)); + let parent = self.cx.tcx.parent_hir_node(ex.hir_id); + match parent { + Node::ExprField(expr_field) if expr_field.is_shorthand => { + self.spans.push(VariableUsage::FieldShorthand(ex.span)); + }, + _ => { + self.spans.push(VariableUsage::Normal(path.span)); + }, + } } } diff --git a/tests/ui/let_unit.fixed b/tests/ui/let_unit.fixed index e8517f18e788b..6d984a495d2bd 100644 --- a/tests/ui/let_unit.fixed +++ b/tests/ui/let_unit.fixed @@ -229,3 +229,13 @@ fn issue_15784() { takes_unit(()); println!("{res:?}"); } + +fn issue15789() { + struct Foo { + value: (), + } + println!(); + //~^ let_unit_value + + Foo { value: () }; +} diff --git a/tests/ui/let_unit.rs b/tests/ui/let_unit.rs index 3f6f0139b2fec..a0e32f0b67a02 100644 --- a/tests/ui/let_unit.rs +++ b/tests/ui/let_unit.rs @@ -227,3 +227,13 @@ fn issue_15784() { takes_unit(res); println!("{res:?}"); } + +fn issue15789() { + struct Foo { + value: (), + } + let value = println!(); + //~^ let_unit_value + + Foo { value }; +} diff --git a/tests/ui/let_unit.stderr b/tests/ui/let_unit.stderr index 8ced32ab828f7..6e7b958df4d9a 100644 --- a/tests/ui/let_unit.stderr +++ b/tests/ui/let_unit.stderr @@ -114,5 +114,19 @@ LL | LL ~ takes_unit(()); | -error: aborting due to 8 previous errors +error: this let-binding has unit value + --> tests/ui/let_unit.rs:235:5 + | +LL | let value = println!(); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: omit the `let` binding and replace variable usages with `()` + | +LL ~ println!(); +LL | +LL | +LL ~ Foo { value: () }; + | + +error: aborting due to 9 previous errors From 195ff236c36df93fed8bee9106b3ecbfb4874df0 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 29 Sep 2025 01:03:16 +0200 Subject: [PATCH 75/93] `new_ret_no_self`: extract to a separate module --- clippy_lints/src/methods/mod.rs | 33 ++------------- clippy_lints/src/methods/new_ret_no_self.rs | 46 +++++++++++++++++++++ 2 files changed, 50 insertions(+), 29 deletions(-) create mode 100644 clippy_lints/src/methods/new_ret_no_self.rs diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 8c2948e4a74e6..157fafa1c6155 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -80,6 +80,7 @@ mod needless_character_iteration; mod needless_collect; mod needless_option_as_deref; mod needless_option_take; +mod new_ret_no_self; mod no_effect_replace; mod obfuscated_if_else; mod ok_expect; @@ -148,11 +149,9 @@ mod zst_offset; use clippy_config::Conf; use clippy_utils::consts::{ConstEvalCtxt, Constant}; -use clippy_utils::diagnostics::span_lint; use clippy_utils::macros::FormatArgsStorage; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::ty::contains_ty_adt_constructor_opaque; -use clippy_utils::{contains_return, is_trait_method, iter_input_pats, peel_blocks, return_ty, sym}; +use clippy_utils::{contains_return, is_trait_method, iter_input_pats, peel_blocks, sym}; pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES; use rustc_data_structures::fx::FxHashSet; use rustc_hir::{self as hir, Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind}; @@ -4918,20 +4917,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { ); } - // if this impl block implements a trait, lint in trait definition instead - if !implements_trait - && impl_item.ident.name == sym::new - && let ret_ty = return_ty(cx, impl_item.owner_id) - && ret_ty != self_ty - && !contains_ty_adt_constructor_opaque(cx, ret_ty, self_ty) - { - span_lint( - cx, - NEW_RET_NO_SELF, - impl_item.span, - "methods called `new` usually return `Self`", - ); - } + new_ret_no_self::check_impl_item(cx, impl_item, self_ty, implements_trait); } } @@ -4963,18 +4949,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { ); } - if item.ident.name == sym::new - && let ret_ty = return_ty(cx, item.owner_id) - && let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty() - && !ret_ty.contains(self_ty) - { - span_lint( - cx, - NEW_RET_NO_SELF, - item.span, - "methods called `new` usually return `Self`", - ); - } + new_ret_no_self::check_trait_item(cx, item); } } } diff --git a/clippy_lints/src/methods/new_ret_no_self.rs b/clippy_lints/src/methods/new_ret_no_self.rs new file mode 100644 index 0000000000000..2aa28fbb5f406 --- /dev/null +++ b/clippy_lints/src/methods/new_ret_no_self.rs @@ -0,0 +1,46 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::return_ty; +use clippy_utils::ty::contains_ty_adt_constructor_opaque; +use rustc_hir::{ImplItem, TraitItem}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_span::sym; + +use super::NEW_RET_NO_SELF; + +pub(super) fn check_impl_item<'tcx>( + cx: &LateContext<'tcx>, + impl_item: &'tcx ImplItem<'_>, + self_ty: Ty<'tcx>, + implements_trait: bool, +) { + // if this impl block implements a trait, lint in trait definition instead + if !implements_trait + && impl_item.ident.name == sym::new + && let ret_ty = return_ty(cx, impl_item.owner_id) + && ret_ty != self_ty + && !contains_ty_adt_constructor_opaque(cx, ret_ty, self_ty) + { + span_lint( + cx, + NEW_RET_NO_SELF, + impl_item.span, + "methods called `new` usually return `Self`", + ); + } +} + +pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, trait_item: &'tcx TraitItem<'tcx>) { + if trait_item.ident.name == sym::new + && let ret_ty = return_ty(cx, trait_item.owner_id) + && let self_ty = ty::TraitRef::identity(cx.tcx, trait_item.owner_id.to_def_id()).self_ty() + && !ret_ty.contains(self_ty) + { + span_lint( + cx, + NEW_RET_NO_SELF, + trait_item.span, + "methods called `new` usually return `Self`", + ); + } +} From b3219502d71776bd2ab5be92734f1a05c2b01eb2 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 30 Sep 2025 17:43:02 +0200 Subject: [PATCH 76/93] `map_identity`: simplify lint emission Instead of calling `span_lint_and_*` in multiple places, call `span_lint_and_then` once and then use the `Diag` methods. --- clippy_lints/src/methods/map_identity.rs | 80 +++++++++++------------- 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/clippy_lints/src/methods/map_identity.rs b/clippy_lints/src/methods/map_identity.rs index a98cfff8bfbd1..6190c43578e9b 100644 --- a/clippy_lints/src/methods/map_identity.rs +++ b/clippy_lints/src/methods/map_identity.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::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}; @@ -27,48 +27,46 @@ pub(super) fn check( && is_expr_untyped_identity_function(cx, map_arg) && let Some(call_span) = expr.span.trim_start(caller.span) { - 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_then(cx, MAP_IDENTITY, call_span, MSG, |diag| { + 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 + }; - 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 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); + 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 - }; + // 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 { @@ -77,18 +75,10 @@ pub(super) fn check( "this must be made mutable".to_string() }; diag.span_note(caller.span, note); - }); + } + } else { + diag.span_suggestion(main_sugg.0, format!("remove the call to `{name}`"), main_sugg.1, app); } - } else { - span_lint_and_sugg( - cx, - MAP_IDENTITY, - main_sugg.0, - MSG, - format!("remove the call to `{name}`"), - main_sugg.1, - app, - ); - } + }); } } From e9ede27a5d880e5b8075ffb98dd381f4d5ce09ca Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 24 Aug 2025 09:31:06 +0200 Subject: [PATCH 77/93] test: remove extraneous `#[allow(clippy::uninlined_format_args)]` These were probably added automatically to avoid code churn. I'd like to get rid of them bit by bit, so here's the first bit --- .../conf_deprecated_key.rs | 4 +- .../conf_deprecated_key.stderr | 2 +- .../index_refutable_slice.fixed | 1 - .../index_refutable_slice.rs | 1 - .../index_refutable_slice.stderr | 4 +- tests/ui/bind_instead_of_map.fixed | 1 - tests/ui/bind_instead_of_map.rs | 1 - tests/ui/bind_instead_of_map.stderr | 6 +- .../shared_at_top_and_bottom.rs | 5 +- .../shared_at_top_and_bottom.stderr | 24 ++-- tests/ui/cast_abs_to_unsigned.fixed | 4 +- tests/ui/cast_abs_to_unsigned.rs | 4 +- tests/ui/crashes/ice-4775.rs | 2 +- tests/ui/default_trait_access.fixed | 2 +- tests/ui/default_trait_access.rs | 2 +- tests/ui/default_trait_access.stderr | 16 +-- tests/ui/explicit_counter_loop.rs | 24 ++-- tests/ui/explicit_write.fixed | 5 +- tests/ui/explicit_write.rs | 5 +- tests/ui/explicit_write.stderr | 34 ++--- tests/ui/fallible_impl_from.rs | 3 +- tests/ui/fallible_impl_from.stderr | 20 +-- .../if_let_slice_binding.fixed | 14 +- .../if_let_slice_binding.rs | 14 +- tests/ui/infinite_iter.rs | 4 +- tests/ui/infinite_iter.stderr | 4 +- tests/ui/issue_2356.fixed | 3 +- tests/ui/issue_2356.rs | 3 +- tests/ui/issue_2356.stderr | 2 +- tests/ui/issue_4266.rs | 3 +- tests/ui/issue_4266.stderr | 6 +- tests/ui/match_single_binding.fixed | 37 +++-- tests/ui/match_single_binding.rs | 37 +++-- tests/ui/match_single_binding.stderr | 130 +++++++++--------- tests/ui/match_single_binding2.fixed | 3 +- tests/ui/match_single_binding2.rs | 3 +- tests/ui/match_single_binding2.stderr | 12 +- 37 files changed, 214 insertions(+), 231 deletions(-) diff --git a/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs b/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs index ecb43dc34a8a0..b28e46af0a467 100644 --- a/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs +++ b/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs @@ -1,5 +1,3 @@ -#![allow(clippy::uninlined_format_args)] - fn main() {} #[warn(clippy::cognitive_complexity)] @@ -8,7 +6,7 @@ fn cognitive_complexity() { let x = vec![1, 2, 3]; for i in x { if i == 1 { - println!("{}", i); + println!("{i}"); } } } diff --git a/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.stderr b/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.stderr index 627498dc175cd..95b0508189e9c 100644 --- a/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.stderr +++ b/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.stderr @@ -11,7 +11,7 @@ LL | blacklisted-names = [ "..", "wibble" ] | ^^^^^^^^^^^^^^^^^ error: the function has a cognitive complexity of (3/2) - --> tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs:6:4 + --> tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs:4:4 | LL | fn cognitive_complexity() { | ^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.fixed b/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.fixed index 2877871d0bf4c..36540bf1dcf73 100644 --- a/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.fixed +++ b/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.fixed @@ -1,4 +1,3 @@ -#![allow(clippy::uninlined_format_args)] #![deny(clippy::index_refutable_slice)] fn below_limit() { diff --git a/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs b/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs index f958b92a102a3..da76bb20fd961 100644 --- a/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs +++ b/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs @@ -1,4 +1,3 @@ -#![allow(clippy::uninlined_format_args)] #![deny(clippy::index_refutable_slice)] fn below_limit() { diff --git a/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.stderr b/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.stderr index e1a8941e102f5..022deb330e6e3 100644 --- a/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.stderr +++ b/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.stderr @@ -1,11 +1,11 @@ error: this binding can be a slice pattern to avoid indexing - --> tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs:6:17 + --> tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs:5:17 | LL | if let Some(slice) = slice { | ^^^^^ | note: the lint level is defined here - --> tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs:2:9 + --> tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs:1:9 | LL | #![deny(clippy::index_refutable_slice)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/bind_instead_of_map.fixed b/tests/ui/bind_instead_of_map.fixed index 80e010e2dfd74..fa35a01242d1d 100644 --- a/tests/ui/bind_instead_of_map.fixed +++ b/tests/ui/bind_instead_of_map.fixed @@ -1,5 +1,4 @@ #![deny(clippy::bind_instead_of_map)] -#![allow(clippy::uninlined_format_args)] // need a main anyway, use it get rid of unused warnings too pub fn main() { diff --git a/tests/ui/bind_instead_of_map.rs b/tests/ui/bind_instead_of_map.rs index 09aa8480cbd9e..403077e72ff98 100644 --- a/tests/ui/bind_instead_of_map.rs +++ b/tests/ui/bind_instead_of_map.rs @@ -1,5 +1,4 @@ #![deny(clippy::bind_instead_of_map)] -#![allow(clippy::uninlined_format_args)] // need a main anyway, use it get rid of unused warnings too pub fn main() { diff --git a/tests/ui/bind_instead_of_map.stderr b/tests/ui/bind_instead_of_map.stderr index 08f85fb58549c..3f8d631591e93 100644 --- a/tests/ui/bind_instead_of_map.stderr +++ b/tests/ui/bind_instead_of_map.stderr @@ -1,5 +1,5 @@ error: using `Option.and_then(Some)`, which is a no-op - --> tests/ui/bind_instead_of_map.rs:8:13 + --> tests/ui/bind_instead_of_map.rs:7:13 | LL | let _ = x.and_then(Some); | ^^^^^^^^^^^^^^^^ help: use the expression directly: `x` @@ -11,13 +11,13 @@ LL | #![deny(clippy::bind_instead_of_map)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)` - --> tests/ui/bind_instead_of_map.rs:10:13 + --> tests/ui/bind_instead_of_map.rs:9:13 | LL | let _ = x.and_then(|o| Some(o + 1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.map(|o| o + 1)` error: using `Result.and_then(Ok)`, which is a no-op - --> tests/ui/bind_instead_of_map.rs:17:13 + --> tests/ui/bind_instead_of_map.rs:16:13 | LL | let _ = x.and_then(Ok); | ^^^^^^^^^^^^^^ help: use the expression directly: `x` diff --git a/tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs b/tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs index e848f0601e32d..1646b5705b6dd 100644 --- a/tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs +++ b/tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs @@ -1,6 +1,5 @@ #![deny(clippy::branches_sharing_code, clippy::if_same_then_else)] #![allow(dead_code)] -#![allow(clippy::uninlined_format_args)] //@no-rustfix // branches_sharing_code at the top and bottom of the if blocks @@ -70,7 +69,7 @@ fn complexer_example() { let b = 0xffff00ff; let e_id = gen_id(a, b); - println!("From the a `{}` to the b `{}`", a, b); + println!("From the a `{a}` to the b `{b}`"); let pack = DataPack { id: e_id, @@ -83,7 +82,7 @@ fn complexer_example() { let b = 0xffff00ff; let e_id = gen_id(a, b); - println!("The new ID is '{}'", e_id); + println!("The new ID is '{e_id}'"); let pack = DataPack { id: e_id, diff --git a/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr b/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr index 40f3453edb9ae..f5c51f7888d04 100644 --- a/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr +++ b/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr @@ -1,5 +1,5 @@ error: all if blocks contain the same code at both the start and the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:17:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:16:5 | LL | / if x == 7 { LL | | @@ -10,7 +10,7 @@ LL | | let _overlap_end = 2 * t; | |_________________________________^ | note: this code is shared at the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:31:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:30:5 | LL | / let _u = 9; LL | | } @@ -34,7 +34,7 @@ LL + let _u = 9; | error: all if blocks contain the same code at both the start and the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:35:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:34:5 | LL | / if x == 99 { LL | | @@ -45,7 +45,7 @@ LL | | let _overlap_middle = r * r; | |____________________________________^ | note: this code is shared at the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:48:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:47:5 | LL | / let _overlap_end = r * r * r; LL | | let z = "end"; @@ -67,7 +67,7 @@ LL + let z = "end"; | error: all if blocks contain the same code at both the start and the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:66:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:65:5 | LL | / if (x > 7 && y < 13) || (x + y) % 2 == 1 { LL | | @@ -78,7 +78,7 @@ LL | | let e_id = gen_id(a, b); | |________________________________^ | note: this code is shared at the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:88:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:87:5 | LL | / let pack = DataPack { LL | | id: e_id, @@ -108,7 +108,7 @@ LL + process_data(pack); | error: all if blocks contain the same code at both the start and the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:101:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:100:5 | LL | / let _ = if x == 7 { ... | @@ -116,7 +116,7 @@ LL | | let _ = 19; | |___________________^ | note: this code is shared at the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:112:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:111:5 | LL | / x << 2 LL | | }; @@ -134,7 +134,7 @@ LL ~ x << 2; | error: all if blocks contain the same code at both the start and the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:115:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:114:5 | LL | / if x == 9 { ... | @@ -142,7 +142,7 @@ LL | | let _ = 17; | |___________________^ | note: this code is shared at the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:126:5 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:125:5 | LL | / x * 4 LL | | } @@ -160,7 +160,7 @@ LL + x * 4 | error: all if blocks contain the same code at both the start and the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:158:9 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:157:9 | LL | / if false { LL | | @@ -168,7 +168,7 @@ LL | | let x = 1; | |______________________^ | note: this code is shared at the end - --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:166:9 + --> tests/ui/branches_sharing_code/shared_at_top_and_bottom.rs:165:9 | LL | / let y = 1; LL | | } diff --git a/tests/ui/cast_abs_to_unsigned.fixed b/tests/ui/cast_abs_to_unsigned.fixed index b55c22f5ca83c..44cb66a8d1848 100644 --- a/tests/ui/cast_abs_to_unsigned.fixed +++ b/tests/ui/cast_abs_to_unsigned.fixed @@ -1,11 +1,11 @@ #![warn(clippy::cast_abs_to_unsigned)] -#![allow(clippy::uninlined_format_args, unused)] +#![allow(unused)] fn main() { let x: i32 = -42; let y: u32 = x.unsigned_abs(); //~^ cast_abs_to_unsigned - println!("The absolute value of {} is {}", x, y); + println!("The absolute value of {x} is {y}"); let a: i32 = -3; let _: usize = a.unsigned_abs() as usize; diff --git a/tests/ui/cast_abs_to_unsigned.rs b/tests/ui/cast_abs_to_unsigned.rs index 466aa6aeb1fbb..555b9090fe437 100644 --- a/tests/ui/cast_abs_to_unsigned.rs +++ b/tests/ui/cast_abs_to_unsigned.rs @@ -1,11 +1,11 @@ #![warn(clippy::cast_abs_to_unsigned)] -#![allow(clippy::uninlined_format_args, unused)] +#![allow(unused)] fn main() { let x: i32 = -42; let y: u32 = x.abs() as u32; //~^ cast_abs_to_unsigned - println!("The absolute value of {} is {}", x, y); + println!("The absolute value of {x} is {y}"); let a: i32 = -3; let _: usize = a.abs() as usize; diff --git a/tests/ui/crashes/ice-4775.rs b/tests/ui/crashes/ice-4775.rs index dd6c6b8de25af..e8c47b4f3a77b 100644 --- a/tests/ui/crashes/ice-4775.rs +++ b/tests/ui/crashes/ice-4775.rs @@ -7,7 +7,7 @@ pub struct ArrayWrapper([usize; N]); impl ArrayWrapper<{ N }> { pub fn ice(&self) { for i in self.0.iter() { - println!("{}", i); + println!("{i}"); } } } diff --git a/tests/ui/default_trait_access.fixed b/tests/ui/default_trait_access.fixed index d3fe09a052ef3..e3bf603da80f3 100644 --- a/tests/ui/default_trait_access.fixed +++ b/tests/ui/default_trait_access.fixed @@ -1,7 +1,6 @@ //@aux-build: proc_macros.rs #![deny(clippy::default_trait_access)] #![allow(dead_code, unused_imports)] -#![allow(clippy::uninlined_format_args)] extern crate proc_macros; @@ -63,6 +62,7 @@ fn main() { let _s21: String = with_span!(s Default::default()); + #[expect(clippy::uninlined_format_args)] println!( "[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}]", s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19, s20, diff --git a/tests/ui/default_trait_access.rs b/tests/ui/default_trait_access.rs index cdffb2a2ee8ca..8cc065e5bced2 100644 --- a/tests/ui/default_trait_access.rs +++ b/tests/ui/default_trait_access.rs @@ -1,7 +1,6 @@ //@aux-build: proc_macros.rs #![deny(clippy::default_trait_access)] #![allow(dead_code, unused_imports)] -#![allow(clippy::uninlined_format_args)] extern crate proc_macros; @@ -63,6 +62,7 @@ fn main() { let _s21: String = with_span!(s Default::default()); + #[expect(clippy::uninlined_format_args)] println!( "[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}]", s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19, s20, diff --git a/tests/ui/default_trait_access.stderr b/tests/ui/default_trait_access.stderr index aa7eb4f89558c..aa89516f175c4 100644 --- a/tests/ui/default_trait_access.stderr +++ b/tests/ui/default_trait_access.stderr @@ -1,5 +1,5 @@ error: calling `String::default()` is more clear than this expression - --> tests/ui/default_trait_access.rs:13:22 + --> tests/ui/default_trait_access.rs:12:22 | LL | let s1: String = Default::default(); | ^^^^^^^^^^^^^^^^^^ help: try: `String::default()` @@ -11,43 +11,43 @@ LL | #![deny(clippy::default_trait_access)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: calling `String::default()` is more clear than this expression - --> tests/ui/default_trait_access.rs:18:22 + --> tests/ui/default_trait_access.rs:17:22 | LL | let s3: String = D2::default(); | ^^^^^^^^^^^^^ help: try: `String::default()` error: calling `String::default()` is more clear than this expression - --> tests/ui/default_trait_access.rs:21:22 + --> tests/ui/default_trait_access.rs:20:22 | LL | let s4: String = std::default::Default::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `String::default()` error: calling `String::default()` is more clear than this expression - --> tests/ui/default_trait_access.rs:26:22 + --> tests/ui/default_trait_access.rs:25:22 | LL | let s6: String = default::Default::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `String::default()` error: calling `GenericDerivedDefault::default()` is more clear than this expression - --> tests/ui/default_trait_access.rs:37:46 + --> tests/ui/default_trait_access.rs:36:46 | LL | let s11: GenericDerivedDefault = Default::default(); | ^^^^^^^^^^^^^^^^^^ help: try: `GenericDerivedDefault::default()` error: calling `TupleDerivedDefault::default()` is more clear than this expression - --> tests/ui/default_trait_access.rs:44:36 + --> tests/ui/default_trait_access.rs:43:36 | LL | let s14: TupleDerivedDefault = Default::default(); | ^^^^^^^^^^^^^^^^^^ help: try: `TupleDerivedDefault::default()` error: calling `ArrayDerivedDefault::default()` is more clear than this expression - --> tests/ui/default_trait_access.rs:47:36 + --> tests/ui/default_trait_access.rs:46:36 | LL | let s15: ArrayDerivedDefault = Default::default(); | ^^^^^^^^^^^^^^^^^^ help: try: `ArrayDerivedDefault::default()` error: calling `TupleStructDerivedDefault::default()` is more clear than this expression - --> tests/ui/default_trait_access.rs:52:42 + --> tests/ui/default_trait_access.rs:51:42 | LL | let s17: TupleStructDerivedDefault = Default::default(); | ^^^^^^^^^^^^^^^^^^ help: try: `TupleStructDerivedDefault::default()` diff --git a/tests/ui/explicit_counter_loop.rs b/tests/ui/explicit_counter_loop.rs index 13934785d7b15..ec4cecf377667 100644 --- a/tests/ui/explicit_counter_loop.rs +++ b/tests/ui/explicit_counter_loop.rs @@ -1,5 +1,5 @@ #![warn(clippy::explicit_counter_loop)] -#![allow(clippy::uninlined_format_args, clippy::useless_vec)] +#![allow(clippy::useless_vec)] //@no-rustfix: suggestion does not remove the `+= 1` fn main() { let mut vec = vec![1, 2, 3, 4]; @@ -89,13 +89,13 @@ mod issue_1219 { for _v in &vec { index += 1 } - println!("index: {}", index); + println!("index: {index}"); // should not trigger the lint because the count is conditional #1219 let text = "banana"; let mut count = 0; for ch in text.chars() { - println!("{}", count); + println!("{count}"); if ch == 'a' { continue; } @@ -106,7 +106,7 @@ mod issue_1219 { let text = "banana"; let mut count = 0; for ch in text.chars() { - println!("{}", count); + println!("{count}"); if ch == 'a' { count += 1; } @@ -118,7 +118,7 @@ mod issue_1219 { for ch in text.chars() { //~^ explicit_counter_loop - println!("{}", count); + println!("{count}"); count += 1; if ch == 'a' { continue; @@ -131,7 +131,7 @@ mod issue_1219 { for ch in text.chars() { //~^ explicit_counter_loop - println!("{}", count); + println!("{count}"); count += 1; for i in 0..2 { let _ = 123; @@ -142,7 +142,7 @@ mod issue_1219 { let text = "banana"; let mut count = 0; for ch in text.chars() { - println!("{}", count); + println!("{count}"); count += 1; for i in 0..2 { count += 1; @@ -157,7 +157,7 @@ mod issue_3308 { let mut skips = 0; let erasures = vec![]; for i in 0..10 { - println!("{}", skips); + println!("{skips}"); while erasures.contains(&(i + skips)) { skips += 1; } @@ -166,7 +166,7 @@ mod issue_3308 { // should not trigger the lint because the count is incremented multiple times let mut skips = 0; for i in 0..10 { - println!("{}", skips); + println!("{skips}"); let mut j = 0; while j < 5 { skips += 1; @@ -177,7 +177,7 @@ mod issue_3308 { // should not trigger the lint because the count is incremented multiple times let mut skips = 0; for i in 0..10 { - println!("{}", skips); + println!("{skips}"); for j in 0..5 { skips += 1; } @@ -205,7 +205,7 @@ mod issue_4732 { for _v in slice { index += 1 } - let _closure = || println!("index: {}", index); + let _closure = || println!("index: {index}"); } } @@ -217,7 +217,7 @@ mod issue_4677 { let mut count = 0; for _i in slice { count += 1; - println!("{}", count); + println!("{count}"); } } } diff --git a/tests/ui/explicit_write.fixed b/tests/ui/explicit_write.fixed index 024999fc609ee..ab28c1ccd8e0d 100644 --- a/tests/ui/explicit_write.fixed +++ b/tests/ui/explicit_write.fixed @@ -1,6 +1,5 @@ #![warn(clippy::explicit_write)] #![allow(unused_imports)] -#![allow(clippy::uninlined_format_args)] fn stdout() -> String { String::new() @@ -40,7 +39,7 @@ fn main() { //~^ explicit_write let value = 1; - eprintln!("with {}", value); + eprintln!("with {value}"); //~^ explicit_write eprintln!("with {} {}", 2, value); //~^ explicit_write @@ -49,7 +48,7 @@ fn main() { eprintln!("macro arg {}", one!()); //~^ explicit_write let width = 2; - eprintln!("{:w$}", value, w = width); + eprintln!("{value:w$}", w = width); //~^ explicit_write } // these should not warn, different destination diff --git a/tests/ui/explicit_write.rs b/tests/ui/explicit_write.rs index c83c760d48c8b..975ee103b6278 100644 --- a/tests/ui/explicit_write.rs +++ b/tests/ui/explicit_write.rs @@ -1,6 +1,5 @@ #![warn(clippy::explicit_write)] #![allow(unused_imports)] -#![allow(clippy::uninlined_format_args)] fn stdout() -> String { String::new() @@ -40,7 +39,7 @@ fn main() { //~^ explicit_write let value = 1; - writeln!(std::io::stderr(), "with {}", value).unwrap(); + writeln!(std::io::stderr(), "with {value}").unwrap(); //~^ explicit_write writeln!(std::io::stderr(), "with {} {}", 2, value).unwrap(); //~^ explicit_write @@ -49,7 +48,7 @@ fn main() { writeln!(std::io::stderr(), "macro arg {}", one!()).unwrap(); //~^ explicit_write let width = 2; - writeln!(std::io::stderr(), "{:w$}", value, w = width).unwrap(); + writeln!(std::io::stderr(), "{value:w$}", w = width).unwrap(); //~^ explicit_write } // these should not warn, different destination diff --git a/tests/ui/explicit_write.stderr b/tests/ui/explicit_write.stderr index 670a0411b3107..ef4b2a049a6d9 100644 --- a/tests/ui/explicit_write.stderr +++ b/tests/ui/explicit_write.stderr @@ -1,5 +1,5 @@ error: use of `write!(stdout(), ...).unwrap()` - --> tests/ui/explicit_write.rs:23:9 + --> tests/ui/explicit_write.rs:22:9 | LL | write!(std::io::stdout(), "test").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `print!("test")` @@ -8,76 +8,76 @@ LL | write!(std::io::stdout(), "test").unwrap(); = help: to override `-D warnings` add `#[allow(clippy::explicit_write)]` error: use of `write!(stderr(), ...).unwrap()` - --> tests/ui/explicit_write.rs:25:9 + --> tests/ui/explicit_write.rs:24:9 | LL | write!(std::io::stderr(), "test").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprint!("test")` error: use of `writeln!(stdout(), ...).unwrap()` - --> tests/ui/explicit_write.rs:27:9 + --> tests/ui/explicit_write.rs:26:9 | LL | writeln!(std::io::stdout(), "test").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `println!("test")` error: use of `writeln!(stderr(), ...).unwrap()` - --> tests/ui/explicit_write.rs:29:9 + --> tests/ui/explicit_write.rs:28:9 | LL | writeln!(std::io::stderr(), "test").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprintln!("test")` error: use of `stdout().write_fmt(...).unwrap()` - --> tests/ui/explicit_write.rs:31:9 + --> tests/ui/explicit_write.rs:30:9 | LL | std::io::stdout().write_fmt(format_args!("test")).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `print!("test")` error: use of `stderr().write_fmt(...).unwrap()` - --> tests/ui/explicit_write.rs:33:9 + --> tests/ui/explicit_write.rs:32:9 | LL | std::io::stderr().write_fmt(format_args!("test")).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprint!("test")` error: use of `writeln!(stdout(), ...).unwrap()` - --> tests/ui/explicit_write.rs:37:9 + --> tests/ui/explicit_write.rs:36:9 | LL | writeln!(std::io::stdout(), "test\ntest").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `println!("test\ntest")` error: use of `writeln!(stderr(), ...).unwrap()` - --> tests/ui/explicit_write.rs:39:9 + --> tests/ui/explicit_write.rs:38:9 | LL | writeln!(std::io::stderr(), "test\ntest").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprintln!("test\ntest")` error: use of `writeln!(stderr(), ...).unwrap()` - --> tests/ui/explicit_write.rs:43:9 + --> tests/ui/explicit_write.rs:42:9 | -LL | writeln!(std::io::stderr(), "with {}", value).unwrap(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprintln!("with {}", value)` +LL | writeln!(std::io::stderr(), "with {value}").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprintln!("with {value}")` error: use of `writeln!(stderr(), ...).unwrap()` - --> tests/ui/explicit_write.rs:45:9 + --> tests/ui/explicit_write.rs:44:9 | LL | writeln!(std::io::stderr(), "with {} {}", 2, value).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprintln!("with {} {}", 2, value)` error: use of `writeln!(stderr(), ...).unwrap()` - --> tests/ui/explicit_write.rs:47:9 + --> tests/ui/explicit_write.rs:46:9 | LL | writeln!(std::io::stderr(), "with {value}").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprintln!("with {value}")` error: use of `writeln!(stderr(), ...).unwrap()` - --> tests/ui/explicit_write.rs:49:9 + --> tests/ui/explicit_write.rs:48:9 | LL | writeln!(std::io::stderr(), "macro arg {}", one!()).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprintln!("macro arg {}", one!())` error: use of `writeln!(stderr(), ...).unwrap()` - --> tests/ui/explicit_write.rs:52:9 + --> tests/ui/explicit_write.rs:51:9 | -LL | writeln!(std::io::stderr(), "{:w$}", value, w = width).unwrap(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprintln!("{:w$}", value, w = width)` +LL | writeln!(std::io::stderr(), "{value:w$}", w = width).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `eprintln!("{value:w$}", w = width)` error: aborting due to 13 previous errors diff --git a/tests/ui/fallible_impl_from.rs b/tests/ui/fallible_impl_from.rs index 1c62c1e937b63..28bb1157f6e52 100644 --- a/tests/ui/fallible_impl_from.rs +++ b/tests/ui/fallible_impl_from.rs @@ -1,5 +1,4 @@ #![deny(clippy::fallible_impl_from)] -#![allow(clippy::uninlined_format_args)] // docs example struct Foo(i32); @@ -62,7 +61,7 @@ impl<'a> From<&'a mut as ProjStrTrait>::ProjString> for Invalid { fn from(s: &'a mut as ProjStrTrait>::ProjString) -> Invalid { if s.parse::().ok().unwrap() != 42 { - panic!("{:?}", s); + panic!("{s:?}"); } Invalid } diff --git a/tests/ui/fallible_impl_from.stderr b/tests/ui/fallible_impl_from.stderr index 402494b39f30b..25ecc8b0a39a2 100644 --- a/tests/ui/fallible_impl_from.stderr +++ b/tests/ui/fallible_impl_from.stderr @@ -1,5 +1,5 @@ error: consider implementing `TryFrom` instead - --> tests/ui/fallible_impl_from.rs:6:1 + --> tests/ui/fallible_impl_from.rs:5:1 | LL | / impl From for Foo { LL | | @@ -11,7 +11,7 @@ LL | | } | = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail note: potential failure(s) - --> tests/ui/fallible_impl_from.rs:10:13 + --> tests/ui/fallible_impl_from.rs:9:13 | LL | Foo(s.parse().unwrap()) | ^^^^^^^^^^^^^^^^^^ @@ -22,7 +22,7 @@ LL | #![deny(clippy::fallible_impl_from)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: consider implementing `TryFrom` instead - --> tests/ui/fallible_impl_from.rs:29:1 + --> tests/ui/fallible_impl_from.rs:28:1 | LL | / impl From for Invalid { LL | | @@ -34,13 +34,13 @@ LL | | } | = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail note: potential failure(s) - --> tests/ui/fallible_impl_from.rs:34:13 + --> tests/ui/fallible_impl_from.rs:33:13 | LL | panic!(); | ^^^^^^^^ error: consider implementing `TryFrom` instead - --> tests/ui/fallible_impl_from.rs:40:1 + --> tests/ui/fallible_impl_from.rs:39:1 | LL | / impl From> for Invalid { LL | | @@ -52,7 +52,7 @@ LL | | } | = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail note: potential failure(s) - --> tests/ui/fallible_impl_from.rs:44:17 + --> tests/ui/fallible_impl_from.rs:43:17 | LL | let s = s.unwrap(); | ^^^^^^^^^^ @@ -65,7 +65,7 @@ LL | panic!("{:?}", s); | ^^^^^^^^^^^^^^^^^ error: consider implementing `TryFrom` instead - --> tests/ui/fallible_impl_from.rs:60:1 + --> tests/ui/fallible_impl_from.rs:59:1 | LL | / impl<'a> From<&'a mut as ProjStrTrait>::ProjString> for Invalid { LL | | @@ -77,12 +77,12 @@ LL | | } | = help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail note: potential failure(s) - --> tests/ui/fallible_impl_from.rs:64:12 + --> tests/ui/fallible_impl_from.rs:63:12 | LL | if s.parse::().ok().unwrap() != 42 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -LL | panic!("{:?}", s); - | ^^^^^^^^^^^^^^^^^ +LL | panic!("{s:?}"); + | ^^^^^^^^^^^^^^^ error: aborting due to 4 previous errors diff --git a/tests/ui/index_refutable_slice/if_let_slice_binding.fixed b/tests/ui/index_refutable_slice/if_let_slice_binding.fixed index 050cdfcba966e..dc7e09bbdc7d2 100644 --- a/tests/ui/index_refutable_slice/if_let_slice_binding.fixed +++ b/tests/ui/index_refutable_slice/if_let_slice_binding.fixed @@ -1,5 +1,5 @@ #![deny(clippy::index_refutable_slice)] -#![allow(clippy::uninlined_format_args, clippy::needless_lifetimes, clippy::collapsible_if)] +#![allow(clippy::needless_lifetimes, clippy::collapsible_if)] enum SomeEnum { One(T), @@ -60,7 +60,7 @@ fn lintable_examples() { println!("{:?}", slice_1); } - println!("{:?}", slice); + println!("{slice:?}"); // This should not suggest using the `ref` keyword as the scrutinee is already // a reference @@ -70,7 +70,7 @@ fn lintable_examples() { println!("{:?}", slice_0); } - println!("{:?}", slice); + println!("{slice:?}"); } fn slice_index_above_limit() { @@ -113,7 +113,7 @@ fn check_slice_as_arg() { println!("This is interesting {}", slice[0]); } } - println!("{:?}", slice_wrapped); + println!("{slice_wrapped:?}"); } fn check_slice_in_struct() { @@ -152,7 +152,7 @@ fn check_slice_in_struct() { println!("This is super awesome! {}", slice_0); } } - println!("Complete wrap: {:?}", wrap); + println!("Complete wrap: {wrap:?}"); } /// This would be a nice additional feature to have in the future, but adding it @@ -164,14 +164,14 @@ fn mutable_slice_index() { if let Some(ref mut slice) = slice { slice[0] = String::from("Mr. Penguin"); } - println!("Use after modification: {:?}", slice); + println!("Use after modification: {slice:?}"); // Mut access on reference let mut slice: Option<[String; 1]> = Some([String::from("Cat")]); if let Some(slice) = &mut slice { slice[0] = String::from("Lord Meow Meow"); } - println!("Use after modification: {:?}", slice); + println!("Use after modification: {slice:?}"); } /// The lint will ignore bindings with sub patterns as it would be hard diff --git a/tests/ui/index_refutable_slice/if_let_slice_binding.rs b/tests/ui/index_refutable_slice/if_let_slice_binding.rs index 91429bfea2762..f39ace101b45c 100644 --- a/tests/ui/index_refutable_slice/if_let_slice_binding.rs +++ b/tests/ui/index_refutable_slice/if_let_slice_binding.rs @@ -1,5 +1,5 @@ #![deny(clippy::index_refutable_slice)] -#![allow(clippy::uninlined_format_args, clippy::needless_lifetimes, clippy::collapsible_if)] +#![allow(clippy::needless_lifetimes, clippy::collapsible_if)] enum SomeEnum { One(T), @@ -60,7 +60,7 @@ fn lintable_examples() { println!("{:?}", slice[1]); } - println!("{:?}", slice); + println!("{slice:?}"); // This should not suggest using the `ref` keyword as the scrutinee is already // a reference @@ -70,7 +70,7 @@ fn lintable_examples() { println!("{:?}", slice[0]); } - println!("{:?}", slice); + println!("{slice:?}"); } fn slice_index_above_limit() { @@ -113,7 +113,7 @@ fn check_slice_as_arg() { println!("This is interesting {}", slice[0]); } } - println!("{:?}", slice_wrapped); + println!("{slice_wrapped:?}"); } fn check_slice_in_struct() { @@ -152,7 +152,7 @@ fn check_slice_in_struct() { println!("This is super awesome! {}", slice[0]); } } - println!("Complete wrap: {:?}", wrap); + println!("Complete wrap: {wrap:?}"); } /// This would be a nice additional feature to have in the future, but adding it @@ -164,14 +164,14 @@ fn mutable_slice_index() { if let Some(ref mut slice) = slice { slice[0] = String::from("Mr. Penguin"); } - println!("Use after modification: {:?}", slice); + println!("Use after modification: {slice:?}"); // Mut access on reference let mut slice: Option<[String; 1]> = Some([String::from("Cat")]); if let Some(slice) = &mut slice { slice[0] = String::from("Lord Meow Meow"); } - println!("Use after modification: {:?}", slice); + println!("Use after modification: {slice:?}"); } /// The lint will ignore bindings with sub patterns as it would be hard diff --git a/tests/ui/infinite_iter.rs b/tests/ui/infinite_iter.rs index 701a86534ba00..4e1668ed04fb7 100644 --- a/tests/ui/infinite_iter.rs +++ b/tests/ui/infinite_iter.rs @@ -1,4 +1,4 @@ -#![allow(clippy::uninlined_format_args, clippy::double_ended_iterator_last)] +#![allow(clippy::double_ended_iterator_last)] use std::iter::repeat; fn square_is_lower_64(x: &u32) -> bool { @@ -30,7 +30,7 @@ fn infinite_iters() { .rev() .cycle() .map(|x| x + 1_u32) - .for_each(|x| println!("{}", x)); + .for_each(|x| println!("{x}")); // infinite iter (0..3_u32).flat_map(|x| x..).sum::(); // infinite iter diff --git a/tests/ui/infinite_iter.stderr b/tests/ui/infinite_iter.stderr index b9e7c008f93e0..3db97313b621d 100644 --- a/tests/ui/infinite_iter.stderr +++ b/tests/ui/infinite_iter.stderr @@ -30,8 +30,8 @@ LL | | LL | | .rev() LL | | .cycle() LL | | .map(|x| x + 1_u32) -LL | | .for_each(|x| println!("{}", x)); - | |________________________________________^ +LL | | .for_each(|x| println!("{x}")); + | |______________________________________^ error: infinite iteration detected --> tests/ui/infinite_iter.rs:37:5 diff --git a/tests/ui/issue_2356.fixed b/tests/ui/issue_2356.fixed index 46ba653eba2cf..3e066df77bfb4 100644 --- a/tests/ui/issue_2356.fixed +++ b/tests/ui/issue_2356.fixed @@ -1,6 +1,5 @@ #![deny(clippy::while_let_on_iterator)] #![allow(unused_mut)] -#![allow(clippy::uninlined_format_args)] use std::iter::Iterator; @@ -16,7 +15,7 @@ impl Foo { fn foo2>(mut it: I) { for e in it { //~^ while_let_on_iterator - println!("{:?}", e); + println!("{e:?}"); } } } diff --git a/tests/ui/issue_2356.rs b/tests/ui/issue_2356.rs index defe2584a93e1..98600d17c6df7 100644 --- a/tests/ui/issue_2356.rs +++ b/tests/ui/issue_2356.rs @@ -1,6 +1,5 @@ #![deny(clippy::while_let_on_iterator)] #![allow(unused_mut)] -#![allow(clippy::uninlined_format_args)] use std::iter::Iterator; @@ -16,7 +15,7 @@ impl Foo { fn foo2>(mut it: I) { while let Some(e) = it.next() { //~^ while_let_on_iterator - println!("{:?}", e); + println!("{e:?}"); } } } diff --git a/tests/ui/issue_2356.stderr b/tests/ui/issue_2356.stderr index eae2ce97fc6b1..ddee91fcfcd52 100644 --- a/tests/ui/issue_2356.stderr +++ b/tests/ui/issue_2356.stderr @@ -1,5 +1,5 @@ error: this loop could be written as a `for` loop - --> tests/ui/issue_2356.rs:17:9 + --> tests/ui/issue_2356.rs:16:9 | LL | while let Some(e) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for e in it` diff --git a/tests/ui/issue_4266.rs b/tests/ui/issue_4266.rs index 664f0b84a2077..b2a01124995c5 100644 --- a/tests/ui/issue_4266.rs +++ b/tests/ui/issue_4266.rs @@ -1,5 +1,4 @@ #![allow(dead_code)] -#![allow(clippy::uninlined_format_args)] async fn sink1<'a>(_: &'a str) {} // lint //~^ needless_lifetimes @@ -39,7 +38,7 @@ impl Foo { // rust-lang/rust#61115 // ok async fn print(s: &str) { - println!("{}", s); + println!("{s}"); } fn main() {} diff --git a/tests/ui/issue_4266.stderr b/tests/ui/issue_4266.stderr index 0e181025430f3..b80a738a50bee 100644 --- a/tests/ui/issue_4266.stderr +++ b/tests/ui/issue_4266.stderr @@ -1,5 +1,5 @@ error: the following explicit lifetimes could be elided: 'a - --> tests/ui/issue_4266.rs:4:16 + --> tests/ui/issue_4266.rs:3:16 | LL | async fn sink1<'a>(_: &'a str) {} // lint | ^^ ^^ @@ -8,13 +8,13 @@ LL | async fn sink1<'a>(_: &'a str) {} // lint = help: to override `-D warnings` add `#[allow(clippy::needless_lifetimes)]` error: the following explicit lifetimes could be elided: 'a - --> tests/ui/issue_4266.rs:10:21 + --> tests/ui/issue_4266.rs:9:21 | LL | async fn one_to_one<'a>(s: &'a str) -> &'a str { | ^^ ^^ ^^ error: methods called `new` usually take no `self` - --> tests/ui/issue_4266.rs:32:22 + --> tests/ui/issue_4266.rs:31:22 | LL | pub async fn new(&mut self) -> Self { | ^^^^^^^^^ diff --git a/tests/ui/match_single_binding.fixed b/tests/ui/match_single_binding.fixed index e29fb87dbc300..7e899a4766661 100644 --- a/tests/ui/match_single_binding.fixed +++ b/tests/ui/match_single_binding.fixed @@ -4,7 +4,6 @@ clippy::let_unit_value, clippy::no_effect, clippy::toplevel_ref_arg, - clippy::uninlined_format_args, clippy::useless_vec )] @@ -32,11 +31,11 @@ fn main() { // Lint let (x, y, z) = (a, b, c); { - println!("{} {} {}", x, y, z); + println!("{x} {y} {z}"); } // Lint let (x, y, z) = (a, b, c); - println!("{} {} {}", x, y, z); + println!("{x} {y} {z}"); // Ok foo!(a); // Ok @@ -47,7 +46,7 @@ fn main() { // Ok let d = Some(5); match d { - Some(d) => println!("{}", d), + Some(d) => println!("{d}"), _ => println!("None"), } // Lint @@ -55,7 +54,7 @@ fn main() { // Lint { let x = 29; - println!("x has a value of {}", x); + println!("x has a value of {x}"); } // Lint { @@ -67,18 +66,18 @@ fn main() { // Lint let p = Point { x: 0, y: 7 }; let Point { x, y } = p; - println!("Coords: ({}, {})", x, y); + println!("Coords: ({x}, {y})"); // Lint let Point { x: x1, y: y1 } = p; - println!("Coords: ({}, {})", x1, y1); + println!("Coords: ({x1}, {y1})"); // Lint let x = 5; let ref r = x; - println!("Got a reference to {}", r); + println!("Got a reference to {r}"); // Lint let mut x = 5; let ref mut mr = x; - println!("Got a mutable reference to {}", mr); + println!("Got a mutable reference to {mr}"); // Lint let Point { x, y } = coords(); let product = x * y; @@ -122,7 +121,7 @@ fn issue_8723() { let (pre, suf) = val.split_at(idx); val = { - println!("{}", pre); + println!("{pre}"); suf }; @@ -210,20 +209,20 @@ mod issue15018 { let x = 1; { let (x, y, z) = (a, b, c); - println!("{} {} {}", x, y, z); + println!("{x} {y} {z}"); } println!("x = {x}"); } fn not_used_later(a: i32, b: i32, c: i32) { let (x, y, z) = (a, b, c); - println!("{} {} {}", x, y, z) + println!("{x} {y} {z}") } #[allow(irrefutable_let_patterns)] fn not_used_later_but_shadowed(a: i32, b: i32, c: i32) { let (x, y, z) = (a, b, c); - println!("{} {} {}", x, y, z); + println!("{x} {y} {z}"); let x = 1; println!("x = {x}"); } @@ -231,27 +230,27 @@ mod issue15018 { #[allow(irrefutable_let_patterns)] fn not_used_later_but_shadowed_nested(a: i32, b: i32, c: i32) { let (x, y, z) = (a, b, c); - println!("{} {} {}", x, y, z); + println!("{x} {x} {y}"); if let (x, y, z) = (a, b, c) { - println!("{} {} {}", x, y, z) + println!("{x} {y} {z}") } { let x: i32 = 1; { let (x, y, z) = (a, b, c); - println!("{} {} {}", x, y, z); + println!("{x} {y} {z}"); } if let (x, y, z) = (a, x, c) { - println!("{} {} {}", x, y, z) + println!("{x} {y} {z}") } } { let (x, y, z) = (a, b, c); - println!("{} {} {}", x, y, z); + println!("{x} {y} {z}"); let fn_ = |y| { - println!("{} {} {}", a, b, y); + println!("{a} {b} {y}"); }; fn_(c); } diff --git a/tests/ui/match_single_binding.rs b/tests/ui/match_single_binding.rs index ede1ab32beb5f..37a96f2287c85 100644 --- a/tests/ui/match_single_binding.rs +++ b/tests/ui/match_single_binding.rs @@ -4,7 +4,6 @@ clippy::let_unit_value, clippy::no_effect, clippy::toplevel_ref_arg, - clippy::uninlined_format_args, clippy::useless_vec )] @@ -33,13 +32,13 @@ fn main() { match (a, b, c) { //~^ match_single_binding (x, y, z) => { - println!("{} {} {}", x, y, z); + println!("{x} {y} {z}"); }, } // Lint match (a, b, c) { //~^ match_single_binding - (x, y, z) => println!("{} {} {}", x, y, z), + (x, y, z) => println!("{x} {y} {z}"), } // Ok foo!(a); @@ -51,7 +50,7 @@ fn main() { // Ok let d = Some(5); match d { - Some(d) => println!("{}", d), + Some(d) => println!("{d}"), _ => println!("None"), } // Lint @@ -64,7 +63,7 @@ fn main() { //~^ match_single_binding _ => { let x = 29; - println!("x has a value of {}", x); + println!("x has a value of {x}"); }, } // Lint @@ -81,24 +80,24 @@ fn main() { let p = Point { x: 0, y: 7 }; match p { //~^ match_single_binding - Point { x, y } => println!("Coords: ({}, {})", x, y), + Point { x, y } => println!("Coords: ({x}, {y})"), } // Lint match p { //~^ match_single_binding - Point { x: x1, y: y1 } => println!("Coords: ({}, {})", x1, y1), + Point { x: x1, y: y1 } => println!("Coords: ({x1}, {y1})"), } // Lint let x = 5; match x { //~^ match_single_binding - ref r => println!("Got a reference to {}", r), + ref r => println!("Got a reference to {r}"), } // Lint let mut x = 5; match x { //~^ match_single_binding - ref mut mr => println!("Got a mutable reference to {}", mr), + ref mut mr => println!("Got a mutable reference to {mr}"), } // Lint let product = match coords() { @@ -150,7 +149,7 @@ fn issue_8723() { val = match val.split_at(idx) { //~^ match_single_binding (pre, suf) => { - println!("{}", pre); + println!("{pre}"); suf }, }; @@ -273,7 +272,7 @@ mod issue15018 { let x = 1; match (a, b, c) { //~^ match_single_binding - (x, y, z) => println!("{} {} {}", x, y, z), + (x, y, z) => println!("{x} {y} {z}"), } println!("x = {x}"); } @@ -281,7 +280,7 @@ mod issue15018 { fn not_used_later(a: i32, b: i32, c: i32) { match (a, b, c) { //~^ match_single_binding - (x, y, z) => println!("{} {} {}", x, y, z), + (x, y, z) => println!("{x} {y} {z}"), } } @@ -289,7 +288,7 @@ mod issue15018 { fn not_used_later_but_shadowed(a: i32, b: i32, c: i32) { match (a, b, c) { //~^ match_single_binding - (x, y, z) => println!("{} {} {}", x, y, z), + (x, y, z) => println!("{x} {y} {z}"), } let x = 1; println!("x = {x}"); @@ -299,30 +298,30 @@ mod issue15018 { fn not_used_later_but_shadowed_nested(a: i32, b: i32, c: i32) { match (a, b, c) { //~^ match_single_binding - (x, y, z) => println!("{} {} {}", x, y, z), + (x, y, z) => println!("{x} {x} {y}"), } if let (x, y, z) = (a, b, c) { - println!("{} {} {}", x, y, z) + println!("{x} {y} {z}") } { let x: i32 = 1; match (a, b, c) { //~^ match_single_binding - (x, y, z) => println!("{} {} {}", x, y, z), + (x, y, z) => println!("{x} {y} {z}"), } if let (x, y, z) = (a, x, c) { - println!("{} {} {}", x, y, z) + println!("{x} {y} {z}") } } { match (a, b, c) { //~^ match_single_binding - (x, y, z) => println!("{} {} {}", x, y, z), + (x, y, z) => println!("{x} {y} {z}"), } let fn_ = |y| { - println!("{} {} {}", a, b, y); + println!("{a} {b} {y}"); }; fn_(c); } diff --git a/tests/ui/match_single_binding.stderr b/tests/ui/match_single_binding.stderr index eea71777890e5..82fc43aaa5eae 100644 --- a/tests/ui/match_single_binding.stderr +++ b/tests/ui/match_single_binding.stderr @@ -1,10 +1,10 @@ error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:33:5 + --> tests/ui/match_single_binding.rs:32:5 | LL | / match (a, b, c) { LL | | LL | | (x, y, z) => { -LL | | println!("{} {} {}", x, y, z); +LL | | println!("{x} {y} {z}"); LL | | }, LL | | } | |_____^ @@ -15,27 +15,27 @@ help: consider using a `let` statement | LL ~ let (x, y, z) = (a, b, c); LL + { -LL + println!("{} {} {}", x, y, z); +LL + println!("{x} {y} {z}"); LL + } | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:40:5 + --> tests/ui/match_single_binding.rs:39:5 | LL | / match (a, b, c) { LL | | -LL | | (x, y, z) => println!("{} {} {}", x, y, z), +LL | | (x, y, z) => println!("{x} {y} {z}"), LL | | } | |_____^ | help: consider using a `let` statement | LL ~ let (x, y, z) = (a, b, c); -LL + println!("{} {} {}", x, y, z); +LL + println!("{x} {y} {z}"); | error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:58:5 + --> tests/ui/match_single_binding.rs:57:5 | LL | / match a { LL | | @@ -44,13 +44,13 @@ LL | | } | |_____^ help: consider using the match body instead: `println!("whatever");` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:63:5 + --> tests/ui/match_single_binding.rs:62:5 | LL | / match a { LL | | LL | | _ => { LL | | let x = 29; -LL | | println!("x has a value of {}", x); +LL | | println!("x has a value of {x}"); LL | | }, LL | | } | |_____^ @@ -59,12 +59,12 @@ help: consider using the match body instead | LL ~ { LL + let x = 29; -LL + println!("x has a value of {}", x); +LL + println!("x has a value of {x}"); LL + } | error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:71:5 + --> tests/ui/match_single_binding.rs:70:5 | LL | / match a { LL | | @@ -86,67 +86,67 @@ LL + } | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:82:5 + --> tests/ui/match_single_binding.rs:81:5 | LL | / match p { LL | | -LL | | Point { x, y } => println!("Coords: ({}, {})", x, y), +LL | | Point { x, y } => println!("Coords: ({x}, {y})"), LL | | } | |_____^ | help: consider using a `let` statement | LL ~ let Point { x, y } = p; -LL + println!("Coords: ({}, {})", x, y); +LL + println!("Coords: ({x}, {y})"); | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:87:5 + --> tests/ui/match_single_binding.rs:86:5 | LL | / match p { LL | | -LL | | Point { x: x1, y: y1 } => println!("Coords: ({}, {})", x1, y1), +LL | | Point { x: x1, y: y1 } => println!("Coords: ({x1}, {y1})"), LL | | } | |_____^ | help: consider using a `let` statement | LL ~ let Point { x: x1, y: y1 } = p; -LL + println!("Coords: ({}, {})", x1, y1); +LL + println!("Coords: ({x1}, {y1})"); | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:93:5 + --> tests/ui/match_single_binding.rs:92:5 | LL | / match x { LL | | -LL | | ref r => println!("Got a reference to {}", r), +LL | | ref r => println!("Got a reference to {r}"), LL | | } | |_____^ | help: consider using a `let` statement | LL ~ let ref r = x; -LL + println!("Got a reference to {}", r); +LL + println!("Got a reference to {r}"); | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:99:5 + --> tests/ui/match_single_binding.rs:98:5 | LL | / match x { LL | | -LL | | ref mut mr => println!("Got a mutable reference to {}", mr), +LL | | ref mut mr => println!("Got a mutable reference to {mr}"), LL | | } | |_____^ | help: consider using a `let` statement | LL ~ let ref mut mr = x; -LL + println!("Got a mutable reference to {}", mr); +LL + println!("Got a mutable reference to {mr}"); | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:104:5 + --> tests/ui/match_single_binding.rs:103:5 | LL | / let product = match coords() { LL | | @@ -161,7 +161,7 @@ LL + let product = x * y; | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:113:18 + --> tests/ui/match_single_binding.rs:112:18 | LL | .map(|i| match i.unwrap() { | __________________^ @@ -179,7 +179,7 @@ LL ~ }) | error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:140:5 + --> tests/ui/match_single_binding.rs:139:5 | LL | / match x { LL | | @@ -189,12 +189,12 @@ LL | | } | |_____^ help: consider using the match body instead: `println!("Not an array index start")` error: this assignment could be simplified - --> tests/ui/match_single_binding.rs:150:5 + --> tests/ui/match_single_binding.rs:149:5 | LL | / val = match val.split_at(idx) { LL | | LL | | (pre, suf) => { -LL | | println!("{}", pre); +LL | | println!("{pre}"); LL | | suf LL | | }, LL | | }; @@ -204,13 +204,13 @@ help: consider removing the `match` expression | LL ~ let (pre, suf) = val.split_at(idx); LL + val = { -LL + println!("{}", pre); +LL + println!("{pre}"); LL + suf LL ~ }; | error: this match could be replaced by its scrutinee and body - --> tests/ui/match_single_binding.rs:164:16 + --> tests/ui/match_single_binding.rs:163:16 | LL | let _ = || match side_effects() { | ________________^ @@ -228,7 +228,7 @@ LL ~ }; | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:171:5 + --> tests/ui/match_single_binding.rs:170:5 | LL | / match r { LL | | @@ -253,7 +253,7 @@ LL ~ }; | error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:185:5 + --> tests/ui/match_single_binding.rs:184:5 | LL | / match 1 { LL | | @@ -262,7 +262,7 @@ LL | | } | |_____^ help: consider using the match body instead: `();` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:190:13 + --> tests/ui/match_single_binding.rs:189:13 | LL | let a = match 1 { | _____________^ @@ -272,7 +272,7 @@ LL | | }; | |_____^ help: consider using the match body instead: `()` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:195:5 + --> tests/ui/match_single_binding.rs:194:5 | LL | / match 1 { LL | | @@ -281,7 +281,7 @@ LL | | } | |_____^ help: consider using the match body instead: `side_effects();` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:200:13 + --> tests/ui/match_single_binding.rs:199:13 | LL | let b = match 1 { | _____________^ @@ -291,7 +291,7 @@ LL | | }; | |_____^ help: consider using the match body instead: `side_effects()` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:205:5 + --> tests/ui/match_single_binding.rs:204:5 | LL | / match 1 { LL | | @@ -300,7 +300,7 @@ LL | | } | |_____^ help: consider using the match body instead: `println!("1");` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:210:13 + --> tests/ui/match_single_binding.rs:209:13 | LL | let c = match 1 { | _____________^ @@ -310,7 +310,7 @@ LL | | }; | |_____^ help: consider using the match body instead: `println!("1")` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:216:9 + --> tests/ui/match_single_binding.rs:215:9 | LL | / match 1 { LL | | @@ -319,7 +319,7 @@ LL | | }, | |_________^ help: consider using the match body instead: `()` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:220:9 + --> tests/ui/match_single_binding.rs:219:9 | LL | / match 1 { LL | | @@ -328,7 +328,7 @@ LL | | }, | |_________^ help: consider using the match body instead: `side_effects()` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:224:9 + --> tests/ui/match_single_binding.rs:223:9 | LL | / match 1 { LL | | @@ -337,7 +337,7 @@ LL | | }, | |_________^ help: consider using the match body instead: `println!("1")` error: this match could be replaced by its scrutinee and body - --> tests/ui/match_single_binding.rs:239:5 + --> tests/ui/match_single_binding.rs:238:5 | LL | / match dbg!(3) { LL | | _ => println!("here"), @@ -351,7 +351,7 @@ LL + println!("here"); | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:243:5 + --> tests/ui/match_single_binding.rs:242:5 | LL | / match dbg!(3) { LL | | id!(a) => println!("found {a}"), @@ -365,7 +365,7 @@ LL + println!("found {a}"); | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:247:5 + --> tests/ui/match_single_binding.rs:246:5 | LL | / let id!(_a) = match dbg!(3) { LL | | id!(b) => dbg!(b + 1), @@ -379,7 +379,7 @@ LL + let id!(_a) = dbg!(b + 1); | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:255:21 + --> tests/ui/match_single_binding.rs:254:21 | LL | inner: [(); match 1 { | _____________________^ @@ -397,7 +397,7 @@ LL ~ }], | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:263:13 + --> tests/ui/match_single_binding.rs:262:13 | LL | / match 1 { LL | | @@ -412,11 +412,11 @@ LL + 42 | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:274:9 + --> tests/ui/match_single_binding.rs:273:9 | LL | / match (a, b, c) { LL | | -LL | | (x, y, z) => println!("{} {} {}", x, y, z), +LL | | (x, y, z) => println!("{x} {y} {z}"), LL | | } | |_________^ | @@ -424,61 +424,61 @@ help: consider using a `let` statement | LL ~ { LL + let (x, y, z) = (a, b, c); -LL + println!("{} {} {}", x, y, z); +LL + println!("{x} {y} {z}"); LL + } | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:282:9 + --> tests/ui/match_single_binding.rs:281:9 | LL | / match (a, b, c) { LL | | -LL | | (x, y, z) => println!("{} {} {}", x, y, z), +LL | | (x, y, z) => println!("{x} {y} {z}"), LL | | } | |_________^ | help: consider using a `let` statement | LL ~ let (x, y, z) = (a, b, c); -LL + println!("{} {} {}", x, y, z) +LL + println!("{x} {y} {z}") | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:290:9 + --> tests/ui/match_single_binding.rs:289:9 | LL | / match (a, b, c) { LL | | -LL | | (x, y, z) => println!("{} {} {}", x, y, z), +LL | | (x, y, z) => println!("{x} {y} {z}"), LL | | } | |_________^ | help: consider using a `let` statement | LL ~ let (x, y, z) = (a, b, c); -LL + println!("{} {} {}", x, y, z); +LL + println!("{x} {y} {z}"); | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:300:9 + --> tests/ui/match_single_binding.rs:299:9 | LL | / match (a, b, c) { LL | | -LL | | (x, y, z) => println!("{} {} {}", x, y, z), +LL | | (x, y, z) => println!("{x} {x} {y}"), LL | | } | |_________^ | help: consider using a `let` statement | LL ~ let (x, y, z) = (a, b, c); -LL + println!("{} {} {}", x, y, z); +LL + println!("{x} {x} {y}"); | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:310:13 + --> tests/ui/match_single_binding.rs:309:13 | LL | / match (a, b, c) { LL | | -LL | | (x, y, z) => println!("{} {} {}", x, y, z), +LL | | (x, y, z) => println!("{x} {y} {z}"), LL | | } | |_____________^ | @@ -486,27 +486,27 @@ help: consider using a `let` statement | LL ~ { LL + let (x, y, z) = (a, b, c); -LL + println!("{} {} {}", x, y, z); +LL + println!("{x} {y} {z}"); LL + } | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding.rs:320:13 + --> tests/ui/match_single_binding.rs:319:13 | LL | / match (a, b, c) { LL | | -LL | | (x, y, z) => println!("{} {} {}", x, y, z), +LL | | (x, y, z) => println!("{x} {y} {z}"), LL | | } | |_____________^ | help: consider using a `let` statement | LL ~ let (x, y, z) = (a, b, c); -LL + println!("{} {} {}", x, y, z); +LL + println!("{x} {y} {z}"); | error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:335:12 + --> tests/ui/match_single_binding.rs:334:12 | LL | && match b { | ____________^ @@ -516,7 +516,7 @@ LL | | }; | |_________^ help: consider using the match body instead: `b < c` error: this match could be replaced by its body itself - --> tests/ui/match_single_binding.rs:341:12 + --> tests/ui/match_single_binding.rs:340:12 | LL | && match (a, b) { | ____________^ diff --git a/tests/ui/match_single_binding2.fixed b/tests/ui/match_single_binding2.fixed index 988121f50d0fa..f00987470ae1b 100644 --- a/tests/ui/match_single_binding2.fixed +++ b/tests/ui/match_single_binding2.fixed @@ -1,6 +1,5 @@ #![warn(clippy::match_single_binding)] #![allow(unused_variables)] -#![allow(clippy::uninlined_format_args)] fn main() { // Lint (additional curly braces needed, see #6572) @@ -29,7 +28,7 @@ fn main() { #[rustfmt::skip] Some((first, _second)) => { let (a, b) = get_tup(); - println!("a {:?} and b {:?}", a, b) + println!("a {a:?} and b {b:?}") }, None => println!("nothing"), } diff --git a/tests/ui/match_single_binding2.rs b/tests/ui/match_single_binding2.rs index a4fb2bd6f3817..5416f647b4e6c 100644 --- a/tests/ui/match_single_binding2.rs +++ b/tests/ui/match_single_binding2.rs @@ -1,6 +1,5 @@ #![warn(clippy::match_single_binding)] #![allow(unused_variables)] -#![allow(clippy::uninlined_format_args)] fn main() { // Lint (additional curly braces needed, see #6572) @@ -30,7 +29,7 @@ fn main() { Some((first, _second)) => { match get_tup() { //~^ match_single_binding - (a, b) => println!("a {:?} and b {:?}", a, b), + (a, b) => println!("a {a:?} and b {b:?}"), } }, None => println!("nothing"), diff --git a/tests/ui/match_single_binding2.stderr b/tests/ui/match_single_binding2.stderr index a24cbe3eed766..65b8aa6acd5e1 100644 --- a/tests/ui/match_single_binding2.stderr +++ b/tests/ui/match_single_binding2.stderr @@ -1,5 +1,5 @@ error: this match could be written as a `let` statement - --> tests/ui/match_single_binding2.rs:17:36 + --> tests/ui/match_single_binding2.rs:16:36 | LL | Some((iter, _item)) => match iter.size_hint() { | ____________________________________^ @@ -19,22 +19,22 @@ LL ~ }, | error: this match could be written as a `let` statement - --> tests/ui/match_single_binding2.rs:31:13 + --> tests/ui/match_single_binding2.rs:30:13 | LL | / match get_tup() { LL | | -LL | | (a, b) => println!("a {:?} and b {:?}", a, b), +LL | | (a, b) => println!("a {a:?} and b {b:?}"), LL | | } | |_____________^ | help: consider using a `let` statement | LL ~ let (a, b) = get_tup(); -LL + println!("a {:?} and b {:?}", a, b) +LL + println!("a {a:?} and b {b:?}") | error: this match could be replaced by its scrutinee and body - --> tests/ui/match_single_binding2.rs:43:5 + --> tests/ui/match_single_binding2.rs:42:5 | LL | / match side_effects() { LL | | @@ -49,7 +49,7 @@ LL + println!("Side effects"); | error: this match could be replaced by its scrutinee and body - --> tests/ui/match_single_binding2.rs:51:5 + --> tests/ui/match_single_binding2.rs:50:5 | LL | / match match x { LL | | From f9852613a965f2f3daccec46d63b85fb0407960d Mon Sep 17 00:00:00 2001 From: yanglsh Date: Mon, 25 Aug 2025 08:32:42 +0800 Subject: [PATCH 78/93] fix: `mem_replace_with_default` wrongly unmangled macros --- clippy_lints/src/mem_replace.rs | 13 +++++-------- tests/ui/mem_replace.fixed | 6 ++++++ tests/ui/mem_replace.rs | 6 ++++++ tests/ui/mem_replace.stderr | 8 +++++++- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/mem_replace.rs b/clippy_lints/src/mem_replace.rs index e39916f733d59..95ecef5987007 100644 --- a/clippy_lints/src/mem_replace.rs +++ b/clippy_lints/src/mem_replace.rs @@ -1,7 +1,7 @@ use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::source::{snippet, snippet_with_applicability}; +use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_non_aggregate_primitive_type; use clippy_utils::{ @@ -269,14 +269,11 @@ fn check_replace_with_default( ), |diag| { if !expr.span.from_expansion() { - let suggestion = format!("{top_crate}::mem::take({})", snippet(cx, dest.span, "")); + let mut applicability = Applicability::MachineApplicable; + let (dest_snip, _) = snippet_with_context(cx, dest.span, expr.span.ctxt(), "", &mut applicability); + let suggestion = format!("{top_crate}::mem::take({dest_snip})"); - diag.span_suggestion( - expr.span, - "consider using", - suggestion, - Applicability::MachineApplicable, - ); + diag.span_suggestion(expr.span, "consider using", suggestion, applicability); } }, ); diff --git a/tests/ui/mem_replace.fixed b/tests/ui/mem_replace.fixed index 870ef23113a2f..94ad1aad3eb75 100644 --- a/tests/ui/mem_replace.fixed +++ b/tests/ui/mem_replace.fixed @@ -179,3 +179,9 @@ fn mem_replace_option_with_some_bad_msrv() { let mut an_option = Some(0); let replaced = mem::replace(&mut an_option, Some(1)); } + +fn issue15785() { + let mut text = String::from("foo"); + let replaced = std::mem::take(dbg!(&mut text)); + //~^ mem_replace_with_default +} diff --git a/tests/ui/mem_replace.rs b/tests/ui/mem_replace.rs index b4ed5eafea953..ac79660f0f1ee 100644 --- a/tests/ui/mem_replace.rs +++ b/tests/ui/mem_replace.rs @@ -179,3 +179,9 @@ fn mem_replace_option_with_some_bad_msrv() { let mut an_option = Some(0); let replaced = mem::replace(&mut an_option, Some(1)); } + +fn issue15785() { + let mut text = String::from("foo"); + let replaced = std::mem::replace(dbg!(&mut text), String::default()); + //~^ mem_replace_with_default +} diff --git a/tests/ui/mem_replace.stderr b/tests/ui/mem_replace.stderr index fb4a367266d30..104c985400282 100644 --- a/tests/ui/mem_replace.stderr +++ b/tests/ui/mem_replace.stderr @@ -181,5 +181,11 @@ error: replacing an `Option` with `Some(..)` LL | let replaced = mem::replace(if b { &mut opt1 } else { &mut opt2 }, Some(1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider `Option::replace()` instead: `(if b { &mut opt1 } else { &mut opt2 }).replace(1)` -error: aborting due to 29 previous errors +error: replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take` + --> tests/ui/mem_replace.rs:185:20 + | +LL | let replaced = std::mem::replace(dbg!(&mut text), String::default()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `std::mem::take(dbg!(&mut text))` + +error: aborting due to 30 previous errors From 1db4d8ebfdf14896c43fc714507e37b97db5d289 Mon Sep 17 00:00:00 2001 From: jackh726 Date: Sun, 28 Sep 2025 23:04:55 +0000 Subject: [PATCH 79/93] Split Bound into Canonical and Bound --- clippy_lints/src/pass_by_ref_or_value.rs | 4 ++-- clippy_utils/src/ty/mod.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/pass_by_ref_or_value.rs b/clippy_lints/src/pass_by_ref_or_value.rs index 1b1e77bbea8fb..6e9142b22e0ef 100644 --- a/clippy_lints/src/pass_by_ref_or_value.rs +++ b/clippy_lints/src/pass_by_ref_or_value.rs @@ -14,7 +14,7 @@ use rustc_hir::{BindingMode, Body, FnDecl, Impl, ItemKind, MutTy, Mutability, No use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, PointerCoercion}; use rustc_middle::ty::layout::LayoutOf; -use rustc_middle::ty::{self, RegionKind, TyCtxt}; +use rustc_middle::ty::{self, BoundVarIndexKind, RegionKind, TyCtxt}; use rustc_session::impl_lint_pass; use rustc_span::def_id::LocalDefId; use rustc_span::{Span, sym}; @@ -151,7 +151,7 @@ impl PassByRefOrValue { match *ty.skip_binder().kind() { ty::Ref(lt, ty, Mutability::Not) => { match lt.kind() { - RegionKind::ReBound(index, region) + RegionKind::ReBound(BoundVarIndexKind::Bound(index), region) if index.as_u32() == 0 && output_regions.contains(®ion) => { continue; diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index e4bc3b7682942..c03469c2b8851 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -21,7 +21,7 @@ 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, + self, AdtDef, AliasTy, AssocItem, AssocTag, Binder, BoundRegion, BoundVarIndexKind, FnSig, GenericArg, GenericArgKind, GenericArgsRef, GenericParamDefKind, IntTy, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr, }; @@ -826,7 +826,7 @@ pub fn for_each_top_level_late_bound_region( impl<'tcx, B, F: FnMut(BoundRegion) -> ControlFlow> TypeVisitor> for V { type Result = ControlFlow; fn visit_region(&mut self, r: Region<'tcx>) -> Self::Result { - if let RegionKind::ReBound(idx, bound) = r.kind() + if let RegionKind::ReBound(BoundVarIndexKind::Bound(idx), bound) = r.kind() && idx.as_u32() == self.index { (self.f)(bound) From 61b1f0e37fad6842c24c41b0de2cb7dbf90f658f Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sat, 23 Aug 2025 06:51:50 -0400 Subject: [PATCH 80/93] `assertions_on_constants`: Don't suggest removing assertions with non-local constants. --- clippy_lints/src/assertions_on_constants.rs | 102 +++++++++++--------- clippy_lints/src/lib.rs | 2 +- clippy_utils/src/msrvs.rs | 3 +- tests/ui/assertions_on_constants.rs | 29 +++++- tests/ui/assertions_on_constants.stderr | 88 +++++++++++------ 5 files changed, 145 insertions(+), 79 deletions(-) diff --git a/clippy_lints/src/assertions_on_constants.rs b/clippy_lints/src/assertions_on_constants.rs index b6684825835a9..a6518216aa820 100644 --- a/clippy_lints/src/assertions_on_constants.rs +++ b/clippy_lints/src/assertions_on_constants.rs @@ -1,10 +1,13 @@ +use clippy_config::Conf; use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::is_inside_always_const_context; -use clippy_utils::macros::{PanicExpn, find_assert_args, root_macro_call_first_node}; +use clippy_utils::macros::{find_assert_args, root_macro_call_first_node}; +use clippy_utils::msrvs::Msrv; +use clippy_utils::{is_inside_always_const_context, msrvs}; +use rustc_ast::LitKind; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; use rustc_span::sym; declare_clippy_lint! { @@ -28,56 +31,59 @@ declare_clippy_lint! { "`assert!(true)` / `assert!(false)` will be optimized out by the compiler, and should probably be replaced by a `panic!()` or `unreachable!()`" } -declare_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]); +impl_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]); +pub struct AssertionsOnConstants { + msrv: Msrv, +} +impl AssertionsOnConstants { + pub fn new(conf: &Conf) -> Self { + Self { msrv: conf.msrv } + } +} impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - let Some(macro_call) = root_macro_call_first_node(cx, e) else { - return; - }; - let is_debug = match cx.tcx.get_diagnostic_name(macro_call.def_id) { - Some(sym::debug_assert_macro) => true, - Some(sym::assert_macro) => false, - _ => return, - }; - let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) else { - return; - }; - let Some(Constant::Bool(val)) = ConstEvalCtxt::new(cx).eval(condition) else { - return; - }; + if let Some(macro_call) = root_macro_call_first_node(cx, e) + && let is_debug = match cx.tcx.get_diagnostic_name(macro_call.def_id) { + Some(sym::debug_assert_macro) => true, + Some(sym::assert_macro) => false, + _ => return, + } + && let Some((condition, _)) = find_assert_args(cx, e, macro_call.expn) + && let Some((Constant::Bool(assert_val), const_src)) = ConstEvalCtxt::new(cx).eval_with_source(condition) + && let in_const_context = is_inside_always_const_context(cx.tcx, e.hir_id) + && (const_src.is_local() || !in_const_context) + && !(is_debug && as_bool_lit(condition) == Some(false)) + { + let (msg, help) = if !const_src.is_local() { + let help = if self.msrv.meets(cx, msrvs::CONST_BLOCKS) { + "consider moving this into a const block: `const { assert!(..) }`" + } else if self.msrv.meets(cx, msrvs::CONST_PANIC) { + "consider moving this to an anonymous constant: `const _: () = { assert!(..); }`" + } else { + return; + }; + ("this assertion has a constant value", help) + } else if assert_val { + ("this assertion is always `true`", "remove the assertion") + } else { + ( + "this assertion is always `false`", + "replace this with `panic!()` or `unreachable!()`", + ) + }; - match condition.kind { - ExprKind::Path(..) | ExprKind::Lit(_) => {}, - _ if is_inside_always_const_context(cx.tcx, e.hir_id) => return, - _ => {}, + span_lint_and_help(cx, ASSERTIONS_ON_CONSTANTS, macro_call.span, msg, None, help); } + } +} - if val { - span_lint_and_help( - cx, - ASSERTIONS_ON_CONSTANTS, - macro_call.span, - format!( - "`{}!(true)` will be optimized out by the compiler", - cx.tcx.item_name(macro_call.def_id) - ), - None, - "remove it", - ); - } else if !is_debug { - let (assert_arg, panic_arg) = match panic_expn { - PanicExpn::Empty => ("", ""), - _ => (", ..", ".."), - }; - span_lint_and_help( - cx, - ASSERTIONS_ON_CONSTANTS, - macro_call.span, - format!("`assert!(false{assert_arg})` should probably be replaced"), - None, - format!("use `panic!({panic_arg})` or `unreachable!({panic_arg})`"), - ); - } +fn as_bool_lit(e: &Expr<'_>) -> Option { + if let ExprKind::Lit(l) = e.kind + && let LitKind::Bool(b) = l.node + { + Some(b) + } else { + None } } diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 51dabee78e9fb..0a955d2383194 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -595,7 +595,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(redundant_clone::RedundantClone)); store.register_late_pass(|_| Box::new(slow_vector_initialization::SlowVectorInit)); store.register_late_pass(move |_| Box::new(unnecessary_wraps::UnnecessaryWraps::new(conf))); - store.register_late_pass(|_| Box::new(assertions_on_constants::AssertionsOnConstants)); + store.register_late_pass(|_| Box::new(assertions_on_constants::AssertionsOnConstants::new(conf))); store.register_late_pass(|_| Box::new(assertions_on_result_states::AssertionsOnResultStates)); store.register_late_pass(|_| Box::new(inherent_to_string::InherentToString)); store.register_late_pass(move |_| Box::new(trait_bounds::TraitBounds::new(conf))); diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index 5ce28a582f4ab..995deba077788 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -31,6 +31,7 @@ msrv_aliases! { 1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP } 1,81,0 { LINT_REASONS_STABILIZATION, ERROR_IN_CORE, EXPLICIT_SELF_TYPE_ELISION, DURATION_ABS_DIFF } 1,80,0 { BOX_INTO_ITER, LAZY_CELL } + 1,79,0 { CONST_BLOCKS } 1,77,0 { C_STR_LITERALS } 1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT } 1,75,0 { OPTION_AS_SLICE } @@ -46,7 +47,7 @@ msrv_aliases! { 1,60,0 { ABS_DIFF } 1,59,0 { THREAD_LOCAL_CONST_INIT } 1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY, CONST_RAW_PTR_DEREF } - 1,57,0 { MAP_WHILE } + 1,57,0 { MAP_WHILE, CONST_PANIC } 1,56,0 { CONST_FN_UNION } 1,55,0 { SEEK_REWIND } 1,54,0 { INTO_KEYS } diff --git a/tests/ui/assertions_on_constants.rs b/tests/ui/assertions_on_constants.rs index c2516c5414753..b613892f206a6 100644 --- a/tests/ui/assertions_on_constants.rs +++ b/tests/ui/assertions_on_constants.rs @@ -42,7 +42,6 @@ fn main() { assert_const!(3); assert_const!(-1); - // Don't lint if based on `cfg!(..)`: assert!(cfg!(feature = "hey") || cfg!(not(feature = "asdf"))); let flag: bool = cfg!(not(feature = "asdf")); @@ -62,9 +61,37 @@ fn main() { const _: () = assert!(N.is_power_of_two()); } +const C: bool = true; + const _: () = { assert!(true); //~^ assertions_on_constants assert!(8 == (7 + 1)); + //~^ assertions_on_constants + + assert!(C); }; + +#[clippy::msrv = "1.57"] +fn _f1() { + assert!(C); + //~^ assertions_on_constants +} + +#[clippy::msrv = "1.56"] +fn _f2() { + assert!(C); +} + +#[clippy::msrv = "1.79"] +fn _f3() { + assert!(C); + //~^ assertions_on_constants +} + +#[clippy::msrv = "1.78"] +fn _f4() { + assert!(C); + //~^ assertions_on_constants +} diff --git a/tests/ui/assertions_on_constants.stderr b/tests/ui/assertions_on_constants.stderr index 8b7440ec4832c..9aed1405fa043 100644 --- a/tests/ui/assertions_on_constants.stderr +++ b/tests/ui/assertions_on_constants.stderr @@ -1,100 +1,132 @@ -error: `assert!(true)` will be optimized out by the compiler +error: this assertion is always `true` --> tests/ui/assertions_on_constants.rs:10:5 | LL | assert!(true); | ^^^^^^^^^^^^^ | - = help: remove it + = help: remove the assertion = note: `-D clippy::assertions-on-constants` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::assertions_on_constants)]` -error: `assert!(false)` should probably be replaced +error: this assertion is always `false` --> tests/ui/assertions_on_constants.rs:13:5 | LL | assert!(false); | ^^^^^^^^^^^^^^ | - = help: use `panic!()` or `unreachable!()` + = help: replace this with `panic!()` or `unreachable!()` -error: `assert!(true)` will be optimized out by the compiler +error: this assertion is always `true` --> tests/ui/assertions_on_constants.rs:16:5 | LL | assert!(true, "true message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: remove it + = help: remove the assertion -error: `assert!(false, ..)` should probably be replaced +error: this assertion is always `false` --> tests/ui/assertions_on_constants.rs:19:5 | LL | assert!(false, "false message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: use `panic!(..)` or `unreachable!(..)` + = help: replace this with `panic!()` or `unreachable!()` -error: `assert!(false, ..)` should probably be replaced +error: this assertion is always `false` --> tests/ui/assertions_on_constants.rs:23:5 | LL | assert!(false, "{}", msg.to_uppercase()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: use `panic!(..)` or `unreachable!(..)` + = help: replace this with `panic!()` or `unreachable!()` -error: `assert!(true)` will be optimized out by the compiler +error: this assertion has a constant value --> tests/ui/assertions_on_constants.rs:27:5 | LL | assert!(B); | ^^^^^^^^^^ | - = help: remove it + = help: consider moving this into a const block: `const { assert!(..) }` -error: `assert!(false)` should probably be replaced +error: this assertion has a constant value --> tests/ui/assertions_on_constants.rs:31:5 | LL | assert!(C); | ^^^^^^^^^^ | - = help: use `panic!()` or `unreachable!()` + = help: consider moving this into a const block: `const { assert!(..) }` -error: `assert!(false, ..)` should probably be replaced +error: this assertion has a constant value --> tests/ui/assertions_on_constants.rs:34:5 | LL | assert!(C, "C message"); | ^^^^^^^^^^^^^^^^^^^^^^^ | - = help: use `panic!(..)` or `unreachable!(..)` + = help: consider moving this into a const block: `const { assert!(..) }` -error: `debug_assert!(true)` will be optimized out by the compiler +error: this assertion is always `true` --> tests/ui/assertions_on_constants.rs:37:5 | LL | debug_assert!(true); | ^^^^^^^^^^^^^^^^^^^ | - = help: remove it + = help: remove the assertion -error: `assert!(true)` will be optimized out by the compiler - --> tests/ui/assertions_on_constants.rs:54:19 +error: this assertion is always `true` + --> tests/ui/assertions_on_constants.rs:53:19 | LL | const _: () = assert!(true); | ^^^^^^^^^^^^^ | - = help: remove it + = help: remove the assertion -error: `assert!(true)` will be optimized out by the compiler - --> tests/ui/assertions_on_constants.rs:57:5 +error: this assertion is always `true` + --> tests/ui/assertions_on_constants.rs:56:5 | LL | assert!(8 == (7 + 1)); | ^^^^^^^^^^^^^^^^^^^^^ | - = help: remove it + = help: remove the assertion -error: `assert!(true)` will be optimized out by the compiler - --> tests/ui/assertions_on_constants.rs:66:5 +error: this assertion is always `true` + --> tests/ui/assertions_on_constants.rs:67:5 | LL | assert!(true); | ^^^^^^^^^^^^^ | - = help: remove it + = help: remove the assertion -error: aborting due to 12 previous errors +error: this assertion is always `true` + --> tests/ui/assertions_on_constants.rs:70:5 + | +LL | assert!(8 == (7 + 1)); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove the assertion + +error: this assertion has a constant value + --> tests/ui/assertions_on_constants.rs:78:5 + | +LL | assert!(C); + | ^^^^^^^^^^ + | + = help: consider moving this to an anonymous constant: `const _: () = { assert!(..); }` + +error: this assertion has a constant value + --> tests/ui/assertions_on_constants.rs:89:5 + | +LL | assert!(C); + | ^^^^^^^^^^ + | + = help: consider moving this into a const block: `const { assert!(..) }` + +error: this assertion has a constant value + --> tests/ui/assertions_on_constants.rs:95:5 + | +LL | assert!(C); + | ^^^^^^^^^^ + | + = help: consider moving this to an anonymous constant: `const _: () = { assert!(..); }` + +error: aborting due to 16 previous errors From f1079915b46f159e822a3f943ecafed8b7db8c6f Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Tue, 23 Sep 2025 18:22:08 +0200 Subject: [PATCH 81/93] Do not suggest using a `if let` chain if it is not supported This might be due to a low edition (< 2024) or too low a MSRV. --- book/src/lint_configuration.md | 1 + clippy_config/src/conf.rs | 1 + clippy_lints/src/collapsible_if.rs | 7 ++- clippy_lints/src/lib.rs | 2 +- clippy_lints/src/unwrap.rs | 50 +++++++++++++------- clippy_utils/src/lib.rs | 6 +++ tests/ui/checked_unwrap/if_let_chains.rs | 24 ++++++++++ tests/ui/checked_unwrap/if_let_chains.stderr | 29 ++++++++++++ 8 files changed, 97 insertions(+), 23 deletions(-) create mode 100644 tests/ui/checked_unwrap/if_let_chains.rs create mode 100644 tests/ui/checked_unwrap/if_let_chains.stderr diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index f8263bb8852ab..e7e0670357b3c 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -898,6 +898,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`unchecked_duration_subtraction`](https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction) * [`uninlined_format_args`](https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args) * [`unnecessary_lazy_evaluations`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations) +* [`unnecessary_unwrap`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap) * [`unnested_or_patterns`](https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns) * [`unused_trait_names`](https://rust-lang.github.io/rust-clippy/master/index.html#unused_trait_names) * [`use_self`](https://rust-lang.github.io/rust-clippy/master/index.html#use_self) diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index e14e15a022f71..df8495dfe0ecf 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -794,6 +794,7 @@ define_Conf! { unchecked_duration_subtraction, uninlined_format_args, unnecessary_lazy_evaluations, + unnecessary_unwrap, unnested_or_patterns, unused_trait_names, use_self, diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index b239c20ab4d89..b13e307a3f9c8 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -1,8 +1,8 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::msrvs::Msrv; use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block_with_applicability}; -use clippy_utils::{span_contains_non_whitespace, sym, tokenize_with_text}; +use clippy_utils::{can_use_if_let_chains, span_contains_non_whitespace, sym, tokenize_with_text}; use rustc_ast::{BinOpKind, MetaItemInner}; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind, StmtKind}; @@ -216,8 +216,7 @@ impl CollapsibleIf { } fn eligible_condition(&self, cx: &LateContext<'_>, cond: &Expr<'_>) -> bool { - !matches!(cond.kind, ExprKind::Let(..)) - || (cx.tcx.sess.edition().at_least_rust_2024() && self.msrv.meets(cx, msrvs::LET_CHAINS)) + !matches!(cond.kind, ExprKind::Let(..)) || can_use_if_let_chains(cx, self.msrv) } // Check that nothing significant can be found between the initial `{` of `inner_if` and diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index c56fa257b0689..1a706df567e55 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -589,7 +589,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(map_unit_fn::MapUnit)); store.register_late_pass(|_| Box::new(inherent_impl::MultipleInherentImpl)); store.register_late_pass(|_| Box::new(neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd)); - store.register_late_pass(|_| Box::new(unwrap::Unwrap)); + store.register_late_pass(move |_| Box::new(unwrap::Unwrap::new(conf))); store.register_late_pass(move |_| Box::new(indexing_slicing::IndexingSlicing::new(conf))); store.register_late_pass(move |tcx| Box::new(non_copy_const::NonCopyConst::new(tcx, conf))); store.register_late_pass(|_| Box::new(redundant_clone::RedundantClone)); diff --git a/clippy_lints/src/unwrap.rs b/clippy_lints/src/unwrap.rs index 34dfe5b6546fa..aee8028a75de7 100644 --- a/clippy_lints/src/unwrap.rs +++ b/clippy_lints/src/unwrap.rs @@ -1,7 +1,9 @@ +use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::msrvs::Msrv; +use clippy_utils::ty::get_type_diagnostic_name; use clippy_utils::usage::is_potentially_local_place; -use clippy_utils::{higher, path_to_local, sym}; +use clippy_utils::{can_use_if_let_chains, higher, path_to_local, sym}; use rustc_errors::Applicability; use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn}; use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, Node, UnOp}; @@ -10,7 +12,7 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_middle::mir::FakeReadCause; use rustc_middle::ty::{self, Ty, TyCtxt}; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; use rustc_span::def_id::LocalDefId; use rustc_span::{Span, Symbol}; @@ -72,10 +74,21 @@ declare_clippy_lint! { "checks for calls of `unwrap[_err]()` that will always fail" } +pub(crate) struct Unwrap { + msrv: Msrv, +} + +impl Unwrap { + pub fn new(conf: &'static Conf) -> Self { + Self { msrv: conf.msrv } + } +} + /// Visitor that keeps track of which variables are unwrappable. struct UnwrappableVariablesVisitor<'a, 'tcx> { unwrappables: Vec>, cx: &'a LateContext<'tcx>, + msrv: Msrv, } /// What kind of unwrappable this is. @@ -133,12 +146,14 @@ fn collect_unwrap_info<'tcx>( invert: bool, is_entire_condition: bool, ) -> Vec> { - fn is_relevant_option_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool { - is_type_diagnostic_item(cx, ty, sym::Option) && matches!(method_name, sym::is_none | sym::is_some) - } - - fn is_relevant_result_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool { - is_type_diagnostic_item(cx, ty, sym::Result) && matches!(method_name, sym::is_err | sym::is_ok) + fn option_or_result_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> Option<(UnwrappableKind, bool)> { + match (get_type_diagnostic_name(cx, ty)?, method_name) { + (sym::Option, sym::is_some) => Some((UnwrappableKind::Option, true)), + (sym::Option, sym::is_none) => Some((UnwrappableKind::Option, false)), + (sym::Result, sym::is_ok) => Some((UnwrappableKind::Result, true)), + (sym::Result, sym::is_err) => Some((UnwrappableKind::Result, false)), + _ => None, + } } match expr.kind { @@ -157,15 +172,9 @@ fn collect_unwrap_info<'tcx>( 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 Some((kind, unwrappable)) = option_or_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 - }; vec![UnwrapInfo { local_id, @@ -357,7 +366,11 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> { ); } else { diag.span_label(unwrappable.check.span, "the check is happening here"); - diag.help("try using `if let` or `match`"); + if can_use_if_let_chains(self.cx, self.msrv) { + diag.help("try using `if let` or `match`"); + } else { + diag.help("try using `match`"); + } } }, ); @@ -383,7 +396,7 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> { } } -declare_lint_pass!(Unwrap => [PANICKING_UNWRAP, UNNECESSARY_UNWRAP]); +impl_lint_pass!(Unwrap => [PANICKING_UNWRAP, UNNECESSARY_UNWRAP]); impl<'tcx> LateLintPass<'tcx> for Unwrap { fn check_fn( @@ -402,6 +415,7 @@ impl<'tcx> LateLintPass<'tcx> for Unwrap { let mut v = UnwrappableVariablesVisitor { unwrappables: Vec::new(), cx, + msrv: self.msrv, }; walk_fn(&mut v, kind, decl, body.id(), fn_id); diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index feadc0ecf659a..1e0bd3a2c4dc8 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -129,6 +129,7 @@ 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::msrvs::Msrv; use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type}; use crate::visitors::for_each_expr_without_closures; @@ -3659,3 +3660,8 @@ pub fn is_expr_async_block(expr: &Expr<'_>) -> bool { }) ) } + +/// Checks if the chosen edition and `msrv` allows using `if let` chains. +pub fn can_use_if_let_chains(cx: &LateContext<'_>, msrv: Msrv) -> bool { + cx.tcx.sess.edition().at_least_rust_2024() && msrv.meets(cx, msrvs::LET_CHAINS) +} diff --git a/tests/ui/checked_unwrap/if_let_chains.rs b/tests/ui/checked_unwrap/if_let_chains.rs new file mode 100644 index 0000000000000..cfa7715965cdb --- /dev/null +++ b/tests/ui/checked_unwrap/if_let_chains.rs @@ -0,0 +1,24 @@ +//@require-annotations-for-level: ERROR +#![deny(clippy::unnecessary_unwrap)] + +#[clippy::msrv = "1.85"] +fn if_let_chains_unsupported(a: Option, b: Option) { + if a.is_none() || b.is_none() { + println!("a or b is not set"); + } else { + println!("the value of a is {}", a.unwrap()); + //~^ unnecessary_unwrap + //~| HELP: try using `match` + } +} + +#[clippy::msrv = "1.88"] +fn if_let_chains_supported(a: Option, b: Option) { + if a.is_none() || b.is_none() { + println!("a or b is not set"); + } else { + println!("the value of a is {}", a.unwrap()); + //~^ unnecessary_unwrap + //~| HELP: try using `if let` or `match` + } +} diff --git a/tests/ui/checked_unwrap/if_let_chains.stderr b/tests/ui/checked_unwrap/if_let_chains.stderr new file mode 100644 index 0000000000000..8a4137de37a3c --- /dev/null +++ b/tests/ui/checked_unwrap/if_let_chains.stderr @@ -0,0 +1,29 @@ +error: called `unwrap` on `a` after checking its variant with `is_none` + --> tests/ui/checked_unwrap/if_let_chains.rs:9:42 + | +LL | if a.is_none() || b.is_none() { + | ----------- the check is happening here +... +LL | println!("the value of a is {}", a.unwrap()); + | ^^^^^^^^^^ + | + = help: try using `match` +note: the lint level is defined here + --> tests/ui/checked_unwrap/if_let_chains.rs:2:9 + | +LL | #![deny(clippy::unnecessary_unwrap)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: called `unwrap` on `a` after checking its variant with `is_none` + --> tests/ui/checked_unwrap/if_let_chains.rs:20:42 + | +LL | if a.is_none() || b.is_none() { + | ----------- the check is happening here +... +LL | println!("the value of a is {}", a.unwrap()); + | ^^^^^^^^^^ + | + = help: try using `if let` or `match` + +error: aborting due to 2 previous errors + From 3e24d50407e29f750ae8dc10e84bfef142b715e2 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sat, 23 Aug 2025 04:33:46 -0400 Subject: [PATCH 82/93] Rename `eval_simple` to `eval_local` and take the `SyntaxContext` as an argument. --- clippy_lints/src/assertions_on_constants.rs | 3 +- clippy_lints/src/floating_point_arithmetic.rs | 13 ++--- clippy_lints/src/if_not_else.rs | 12 +++-- .../src/invalid_upcast_comparisons.rs | 2 +- clippy_lints/src/manual_rem_euclid.rs | 20 +++++--- clippy_lints/src/matches/manual_unwrap_or.rs | 2 +- .../src/methods/is_digit_ascii_radix.rs | 2 +- .../src/methods/unnecessary_min_or_max.rs | 5 +- clippy_lints/src/minmax.rs | 12 +++-- .../src/operators/arithmetic_side_effects.rs | 4 +- clippy_lints/src/operators/erasing_op.rs | 4 +- clippy_lints/src/operators/float_cmp.rs | 5 +- clippy_lints/src/operators/identity_op.rs | 32 ++++++------ .../src/operators/manual_is_multiple_of.rs | 9 ++-- .../src/operators/numeric_arithmetic.rs | 2 +- clippy_lints/src/zero_div_zero.rs | 5 +- clippy_utils/src/consts.rs | 49 +++++++++++++------ clippy_utils/src/higher.rs | 2 +- clippy_utils/src/hir_utils.rs | 8 +-- tests/ui/assertions_on_constants.rs | 1 + tests/ui/assertions_on_constants.stderr | 24 ++++++--- 21 files changed, 134 insertions(+), 82 deletions(-) diff --git a/clippy_lints/src/assertions_on_constants.rs b/clippy_lints/src/assertions_on_constants.rs index a6518216aa820..2586c89bc8680 100644 --- a/clippy_lints/src/assertions_on_constants.rs +++ b/clippy_lints/src/assertions_on_constants.rs @@ -50,7 +50,8 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants { _ => return, } && let Some((condition, _)) = find_assert_args(cx, e, macro_call.expn) - && let Some((Constant::Bool(assert_val), const_src)) = ConstEvalCtxt::new(cx).eval_with_source(condition) + && let Some((Constant::Bool(assert_val), const_src)) = + ConstEvalCtxt::new(cx).eval_with_source(condition, macro_call.span.ctxt()) && let in_const_context = is_inside_always_const_context(cx.tcx, e.hir_id) && (const_src.is_local() || !in_const_context) && !(is_debug && as_bool_lit(condition) == Some(false)) diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index 84d39dd81c919..5052bbb3ca033 100644 --- a/clippy_lints/src/floating_point_arithmetic.rs +++ b/clippy_lints/src/floating_point_arithmetic.rs @@ -11,6 +11,7 @@ use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::declare_lint_pass; +use rustc_span::SyntaxContext; use rustc_span::source_map::Spanned; use std::f32::consts as f32_consts; use std::f64::consts as f64_consts; @@ -517,8 +518,8 @@ fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) { fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool { if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind { match op { - BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right) && eq_expr_value(cx, left, test), - BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left) && eq_expr_value(cx, right, test), + BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test), + BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test), _ => false, } } else { @@ -530,8 +531,8 @@ fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) - fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool { if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind { match op { - BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left) && eq_expr_value(cx, right, test), - BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right) && eq_expr_value(cx, left, test), + BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test), + BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test), _ => false, } } else { @@ -540,8 +541,8 @@ fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) - } /// Returns true iff expr is some zero literal -fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - match ConstEvalCtxt::new(cx).eval_simple(expr) { +fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool { + match ConstEvalCtxt::new(cx).eval_local(expr, ctxt) { Some(Int(i)) => i == 0, Some(F32(f)) => f == 0.0, Some(F64(f)) => f == 0.0, diff --git a/clippy_lints/src/if_not_else.rs b/clippy_lints/src/if_not_else.rs index e8afa69b537ea..54e9538fcb993 100644 --- a/clippy_lints/src/if_not_else.rs +++ b/clippy_lints/src/if_not_else.rs @@ -60,10 +60,14 @@ impl LateLintPass<'_> for IfNotElse { ), // Don't lint on `… != 0`, as these are likely to be bit tests. // For example, `if foo & 0x0F00 != 0 { … } else { … }` is already in the "proper" order. - ExprKind::Binary(op, _, rhs) if op.node == BinOpKind::Ne && !is_zero_integer_const(cx, rhs) => ( - "unnecessary `!=` operation", - "change to `==` and swap the blocks of the `if`/`else`", - ), + ExprKind::Binary(op, _, rhs) + if op.node == BinOpKind::Ne && !is_zero_integer_const(cx, rhs, e.span.ctxt()) => + { + ( + "unnecessary `!=` operation", + "change to `==` and swap the blocks of the `if`/`else`", + ) + }, _ => return, }; diff --git a/clippy_lints/src/invalid_upcast_comparisons.rs b/clippy_lints/src/invalid_upcast_comparisons.rs index 1666e8e5ae324..885649074ab63 100644 --- a/clippy_lints/src/invalid_upcast_comparisons.rs +++ b/clippy_lints/src/invalid_upcast_comparisons.rs @@ -101,7 +101,7 @@ fn upcast_comparison_bounds_err<'tcx>( invert: bool, ) { if let Some((lb, ub)) = lhs_bounds - && let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs) + && let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs, span.ctxt()) { if rel == Rel::Eq || rel == Rel::Ne { if norm_rhs_val < lb || norm_rhs_val > ub { diff --git a/clippy_lints/src/manual_rem_euclid.rs b/clippy_lints/src/manual_rem_euclid.rs index 41e07e26bff0a..1e91a429fe45c 100644 --- a/clippy_lints/src/manual_rem_euclid.rs +++ b/clippy_lints/src/manual_rem_euclid.rs @@ -8,6 +8,7 @@ use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, Node, TyKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::impl_lint_pass; +use rustc_span::SyntaxContext; declare_clippy_lint! { /// ### What it does @@ -58,13 +59,13 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid { && add_lhs.span.ctxt() == ctxt && add_rhs.span.ctxt() == ctxt && !expr.span.in_external_macro(cx.sess().source_map()) - && let Some(const1) = check_for_unsigned_int_constant(cx, rem_rhs) - && let Some((const2, add_other)) = check_for_either_unsigned_int_constant(cx, add_lhs, add_rhs) + && let Some(const1) = check_for_unsigned_int_constant(cx, ctxt, rem_rhs) + && let Some((const2, add_other)) = check_for_either_unsigned_int_constant(cx, ctxt, add_lhs, add_rhs) && let ExprKind::Binary(rem2_op, rem2_lhs, rem2_rhs) = add_other.kind && rem2_op.node == BinOpKind::Rem && const1 == const2 && let Some(hir_id) = path_to_local(rem2_lhs) - && let Some(const3) = check_for_unsigned_int_constant(cx, rem2_rhs) + && let Some(const3) = check_for_unsigned_int_constant(cx, ctxt, rem2_rhs) // Also ensures the const is nonzero since zero can't be a divisor && const2 == const3 && rem2_lhs.span.ctxt() == ctxt @@ -103,16 +104,21 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid { // constant along with the other expression unchanged if so fn check_for_either_unsigned_int_constant<'a>( cx: &'a LateContext<'_>, + ctxt: SyntaxContext, left: &'a Expr<'_>, right: &'a Expr<'_>, ) -> Option<(u128, &'a Expr<'a>)> { - check_for_unsigned_int_constant(cx, left) + check_for_unsigned_int_constant(cx, ctxt, left) .map(|int_const| (int_const, right)) - .or_else(|| check_for_unsigned_int_constant(cx, right).map(|int_const| (int_const, left))) + .or_else(|| check_for_unsigned_int_constant(cx, ctxt, right).map(|int_const| (int_const, left))) } -fn check_for_unsigned_int_constant<'a>(cx: &'a LateContext<'_>, expr: &'a Expr<'_>) -> Option { - let int_const = ConstEvalCtxt::new(cx).eval_full_int(expr)?; +fn check_for_unsigned_int_constant<'a>( + cx: &'a LateContext<'_>, + ctxt: SyntaxContext, + expr: &'a Expr<'_>, +) -> Option { + let int_const = ConstEvalCtxt::new(cx).eval_full_int(expr, ctxt)?; match int_const { FullInt::S(s) => s.try_into().ok(), FullInt::U(u) => Some(u), diff --git a/clippy_lints/src/matches/manual_unwrap_or.rs b/clippy_lints/src/matches/manual_unwrap_or.rs index 8c3f52542d917..ac9e51890362b 100644 --- a/clippy_lints/src/matches/manual_unwrap_or.rs +++ b/clippy_lints/src/matches/manual_unwrap_or.rs @@ -155,7 +155,7 @@ fn handle( && cx.typeck_results().expr_adjustments(body_some).is_empty() && let Some(or_body_snippet) = peel_blocks(body_none).span.get_source_text(cx) && let Some(indent) = indent_of(cx, expr.span) - && ConstEvalCtxt::new(cx).eval_simple(body_none).is_some() + && ConstEvalCtxt::new(cx).eval_local(body_none, expr.span.ctxt()).is_some() { let reindented_or_body = reindent_multiline(&or_body_snippet, true, Some(indent)); let mut app = Applicability::MachineApplicable; diff --git a/clippy_lints/src/methods/is_digit_ascii_radix.rs b/clippy_lints/src/methods/is_digit_ascii_radix.rs index 9c32e9ac539d9..2aeb6f42d05cd 100644 --- a/clippy_lints/src/methods/is_digit_ascii_radix.rs +++ b/clippy_lints/src/methods/is_digit_ascii_radix.rs @@ -18,7 +18,7 @@ pub(super) fn check<'tcx>( return; } - if let Some(radix_val) = ConstEvalCtxt::new(cx).eval_full_int(radix) { + if let Some(radix_val) = ConstEvalCtxt::new(cx).eval_full_int(radix, expr.span.ctxt()) { let (num, replacement) = match radix_val { FullInt::S(10) | FullInt::U(10) => (10, "is_ascii_digit"), FullInt::S(16) | FullInt::U(16) => (16, "is_ascii_hexdigit"), diff --git a/clippy_lints/src/methods/unnecessary_min_or_max.rs b/clippy_lints/src/methods/unnecessary_min_or_max.rs index b87d81b710269..130e4970a7163 100644 --- a/clippy_lints/src/methods/unnecessary_min_or_max.rs +++ b/clippy_lints/src/methods/unnecessary_min_or_max.rs @@ -25,8 +25,9 @@ pub(super) fn check<'tcx>( && 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) + let ctxt = expr.span.ctxt(); + if let Some((left, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(recv, ctxt) + && let Some((right, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(arg, ctxt) { let Some(ord) = Constant::partial_cmp(cx.tcx, typeck_results.expr_ty(recv), &left, &right) else { return; diff --git a/clippy_lints/src/minmax.rs b/clippy_lints/src/minmax.rs index 64eafc0ebccdc..8f76e6c9048e1 100644 --- a/clippy_lints/src/minmax.rs +++ b/clippy_lints/src/minmax.rs @@ -4,6 +4,7 @@ use clippy_utils::{is_trait_method, sym}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; +use rustc_span::SyntaxContext; use std::cmp::Ordering::{Equal, Greater, Less}; declare_clippy_lint! { @@ -68,8 +69,8 @@ fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinM .qpath_res(qpath, path.hir_id) .opt_def_id() .and_then(|def_id| match cx.tcx.get_diagnostic_name(def_id) { - Some(sym::cmp_min) => fetch_const(cx, None, args, MinMax::Min), - Some(sym::cmp_max) => fetch_const(cx, None, args, MinMax::Max), + Some(sym::cmp_min) => fetch_const(cx, expr.span.ctxt(), None, args, MinMax::Min), + Some(sym::cmp_max) => fetch_const(cx, expr.span.ctxt(), None, args, MinMax::Max), _ => None, }) } else { @@ -79,8 +80,8 @@ fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinM ExprKind::MethodCall(path, receiver, args @ [_], _) => { if cx.typeck_results().expr_ty(receiver).is_floating_point() || is_trait_method(cx, expr, sym::Ord) { match path.ident.name { - sym::max => fetch_const(cx, Some(receiver), args, MinMax::Max), - sym::min => fetch_const(cx, Some(receiver), args, MinMax::Min), + sym::max => fetch_const(cx, expr.span.ctxt(), Some(receiver), args, MinMax::Max), + sym::min => fetch_const(cx, expr.span.ctxt(), Some(receiver), args, MinMax::Min), _ => None, } } else { @@ -93,6 +94,7 @@ fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinM fn fetch_const<'a, 'tcx>( cx: &LateContext<'tcx>, + ctxt: SyntaxContext, receiver: Option<&'a Expr<'a>>, args: &'a [Expr<'a>], m: MinMax, @@ -104,7 +106,7 @@ fn fetch_const<'a, 'tcx>( return None; } let ecx = ConstEvalCtxt::new(cx); - match (ecx.eval_simple(first_arg), ecx.eval_simple(second_arg)) { + match (ecx.eval_local(first_arg, ctxt), ecx.eval_local(second_arg, ctxt)) { (Some(c), None) => Some((m, c, second_arg)), (None, Some(c)) => Some((m, c, first_arg)), // otherwise ignore diff --git a/clippy_lints/src/operators/arithmetic_side_effects.rs b/clippy_lints/src/operators/arithmetic_side_effects.rs index ea5b81aec31ea..e062e55dad894 100644 --- a/clippy_lints/src/operators/arithmetic_side_effects.rs +++ b/clippy_lints/src/operators/arithmetic_side_effects.rs @@ -190,7 +190,7 @@ impl ArithmeticSideEffects { lhs: &'tcx hir::Expr<'_>, rhs: &'tcx hir::Expr<'_>, ) { - if ConstEvalCtxt::new(cx).eval_simple(expr).is_some() { + if ConstEvalCtxt::new(cx).eval_local(expr, expr.span.ctxt()).is_some() { return; } if !matches!( @@ -283,7 +283,7 @@ impl ArithmeticSideEffects { let Some(arg) = args.first() else { return; }; - if ConstEvalCtxt::new(cx).eval_simple(receiver).is_some() { + if ConstEvalCtxt::new(cx).eval_local(receiver, expr.span.ctxt()).is_some() { return; } let instance_ty = cx.typeck_results().expr_ty_adjusted(receiver); diff --git a/clippy_lints/src/operators/erasing_op.rs b/clippy_lints/src/operators/erasing_op.rs index 8f5ee390f7222..ae1a94a3e23f0 100644 --- a/clippy_lints/src/operators/erasing_op.rs +++ b/clippy_lints/src/operators/erasing_op.rs @@ -39,7 +39,9 @@ fn check_op<'tcx>( other: &Expr<'tcx>, parent: &Expr<'tcx>, ) { - if ConstEvalCtxt::with_env(cx.tcx, cx.typing_env(), tck).eval_simple(op) == Some(Constant::Int(0)) { + if ConstEvalCtxt::with_env(cx.tcx, cx.typing_env(), tck).eval_local(op, parent.span.ctxt()) + == Some(Constant::Int(0)) + { if different_types(tck, other, parent) { return; } diff --git a/clippy_lints/src/operators/float_cmp.rs b/clippy_lints/src/operators/float_cmp.rs index ded161c8576a1..03b2cf055d919 100644 --- a/clippy_lints/src/operators/float_cmp.rs +++ b/clippy_lints/src/operators/float_cmp.rs @@ -18,12 +18,13 @@ pub(crate) fn check<'tcx>( ) { if (op == BinOpKind::Eq || op == BinOpKind::Ne) && is_float(cx, left) { let ecx = ConstEvalCtxt::new(cx); - let left_is_local = match ecx.eval_with_source(left) { + let ctxt = expr.span.ctxt(); + let left_is_local = match ecx.eval_with_source(left, ctxt) { Some((c, s)) if !is_allowed(&c) => s.is_local(), Some(_) => return, None => true, }; - let right_is_local = match ecx.eval_with_source(right) { + let right_is_local = match ecx.eval_with_source(right, ctxt) { Some((c, s)) if !is_allowed(&c) => s.is_local(), Some(_) => return, None => true, diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index 3efbb8963587f..43c62e1e131a3 100644 --- a/clippy_lints/src/operators/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -7,7 +7,7 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::{BinOpKind, Expr, ExprKind, Node, Path, QPath}; use rustc_lint::LateContext; use rustc_middle::ty; -use rustc_span::{Span, kw}; +use rustc_span::{Span, SyntaxContext, kw}; use super::IDENTITY_OP; @@ -41,42 +41,43 @@ pub(crate) fn check<'tcx>( (span, is_coerced) }; + let ctxt = expr.span.ctxt(); match op { BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => { - if is_redundant_op(cx, left, 0) { + if is_redundant_op(cx, left, 0, ctxt) { let paren = needs_parenthesis(cx, expr, right); span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value); - } else if is_redundant_op(cx, right, 0) { + } else if is_redundant_op(cx, right, 0, ctxt) { let paren = needs_parenthesis(cx, expr, left); span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); } }, BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => { - if is_redundant_op(cx, right, 0) { + if is_redundant_op(cx, right, 0, ctxt) { let paren = needs_parenthesis(cx, expr, left); span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); } }, BinOpKind::Mul => { - if is_redundant_op(cx, left, 1) { + if is_redundant_op(cx, left, 1, ctxt) { let paren = needs_parenthesis(cx, expr, right); span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value); - } else if is_redundant_op(cx, right, 1) { + } else if is_redundant_op(cx, right, 1, ctxt) { let paren = needs_parenthesis(cx, expr, left); span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); } }, BinOpKind::Div => { - if is_redundant_op(cx, right, 1) { + if is_redundant_op(cx, right, 1, ctxt) { let paren = needs_parenthesis(cx, expr, left); span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); } }, BinOpKind::BitAnd => { - if is_redundant_op(cx, left, -1) { + if is_redundant_op(cx, left, -1, ctxt) { let paren = needs_parenthesis(cx, expr, right); span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value); - } else if is_redundant_op(cx, right, -1) { + } else if is_redundant_op(cx, right, -1, ctxt) { let paren = needs_parenthesis(cx, expr, left); span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); } @@ -184,14 +185,17 @@ fn is_allowed<'tcx>( // This lint applies to integers and their references cx.typeck_results().expr_ty(left).peel_refs().is_integral() - && cx.typeck_results().expr_ty(right).peel_refs().is_integral() + && cx.typeck_results().expr_ty(right).peel_refs().is_integral() // `1 << 0` is a common pattern in bit manipulation code - && !(cmp == BinOpKind::Shl && is_zero_integer_const(cx, right) && integer_const(cx, left) == Some(1)) + && !(cmp == BinOpKind::Shl + && is_zero_integer_const(cx, right, expr.span.ctxt()) + && integer_const(cx, left, expr.span.ctxt()) == Some(1)) } fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span: Span, arg: Span) { let ecx = ConstEvalCtxt::new(cx); - if match (ecx.eval_full_int(left), ecx.eval_full_int(right)) { + let ctxt = span.ctxt(); + if match (ecx.eval_full_int(left, ctxt), ecx.eval_full_int(right, ctxt)) { (Some(FullInt::S(lv)), Some(FullInt::S(rv))) => lv.abs() < rv.abs(), (Some(FullInt::U(lv)), Some(FullInt::U(rv))) => lv < rv, _ => return, @@ -200,8 +204,8 @@ fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span } } -fn is_redundant_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8) -> bool { - if let Some(Constant::Int(v)) = ConstEvalCtxt::new(cx).eval_simple(e).map(Constant::peel_refs) { +fn is_redundant_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, ctxt: SyntaxContext) -> bool { + if let Some(Constant::Int(v)) = ConstEvalCtxt::new(cx).eval_local(e, ctxt).map(Constant::peel_refs) { let check = match *cx.typeck_results().expr_ty(e).peel_refs().kind() { ty::Int(ity) => unsext(cx.tcx, -1_i128, ity), ty::Uint(uty) => clip(cx.tcx, !0, uty), diff --git a/clippy_lints/src/operators/manual_is_multiple_of.rs b/clippy_lints/src/operators/manual_is_multiple_of.rs index 55bb78cfce5fc..0b9bd4fb6d320 100644 --- a/clippy_lints/src/operators/manual_is_multiple_of.rs +++ b/clippy_lints/src/operators/manual_is_multiple_of.rs @@ -13,14 +13,14 @@ use super::MANUAL_IS_MULTIPLE_OF; pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, - expr: &Expr<'_>, + expr: &'tcx Expr<'tcx>, op: BinOpKind, lhs: &'tcx Expr<'tcx>, rhs: &'tcx Expr<'tcx>, msrv: Msrv, ) { if msrv.meets(cx, msrvs::UNSIGNED_IS_MULTIPLE_OF) - && let Some(operand) = uint_compare_to_zero(cx, op, lhs, rhs) + && let Some(operand) = uint_compare_to_zero(cx, expr, op, lhs, rhs) && let ExprKind::Binary(operand_op, operand_left, operand_right) = operand.kind && operand_op.node == BinOpKind::Rem && matches!( @@ -57,18 +57,19 @@ pub(super) fn check<'tcx>( // If we have a `x == 0`, `x != 0` or `x > 0` (or the reverted ones), return the non-zero operand fn uint_compare_to_zero<'tcx>( cx: &LateContext<'tcx>, + e: &'tcx Expr<'tcx>, op: BinOpKind, lhs: &'tcx Expr<'tcx>, rhs: &'tcx Expr<'tcx>, ) -> Option<&'tcx Expr<'tcx>> { let operand = if matches!(lhs.kind, ExprKind::Binary(..)) && matches!(op, BinOpKind::Eq | BinOpKind::Ne | BinOpKind::Gt) - && is_zero_integer_const(cx, rhs) + && is_zero_integer_const(cx, rhs, e.span.ctxt()) { lhs } else if matches!(rhs.kind, ExprKind::Binary(..)) && matches!(op, BinOpKind::Eq | BinOpKind::Ne | BinOpKind::Lt) - && is_zero_integer_const(cx, lhs) + && is_zero_integer_const(cx, lhs, e.span.ctxt()) { rhs } else { diff --git a/clippy_lints/src/operators/numeric_arithmetic.rs b/clippy_lints/src/operators/numeric_arithmetic.rs index 9b1b063c4737d..622f328f369a5 100644 --- a/clippy_lints/src/operators/numeric_arithmetic.rs +++ b/clippy_lints/src/operators/numeric_arithmetic.rs @@ -55,7 +55,7 @@ impl Context { return; } let ty = cx.typeck_results().expr_ty(arg); - if ConstEvalCtxt::new(cx).eval_simple(expr).is_none() && ty.is_floating_point() { + if ConstEvalCtxt::new(cx).eval_local(expr, expr.span.ctxt()).is_none() && ty.is_floating_point() { span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected"); self.expr_id = Some(expr.hir_id); } diff --git a/clippy_lints/src/zero_div_zero.rs b/clippy_lints/src/zero_div_zero.rs index 5eb207a0aedb0..bb0cab3a30757 100644 --- a/clippy_lints/src/zero_div_zero.rs +++ b/clippy_lints/src/zero_div_zero.rs @@ -37,8 +37,9 @@ impl<'tcx> LateLintPass<'tcx> for ZeroDiv { // That's probably fine for this lint - it's pretty unlikely that someone would // do something like 0.0/(2.0 - 2.0), but it would be nice to warn on that case too. && let ecx = ConstEvalCtxt::new(cx) - && let Some(lhs_value) = ecx.eval_simple(left) - && let Some(rhs_value) = ecx.eval_simple(right) + && let ctxt = expr.span.ctxt() + && let Some(lhs_value) = ecx.eval_local(left, ctxt) + && let Some(rhs_value) = ecx.eval_local(right, ctxt) // FIXME(f16_f128): add these types when eq is available on all platforms && (Constant::F32(0.0) == lhs_value || Constant::F64(0.0) == lhs_value) && (Constant::F32(0.0) == rhs_value || Constant::F64(0.0) == rhs_value) diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index ecd88daa6b39f..29f4988b4211b 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -387,6 +387,7 @@ pub struct ConstEvalCtxt<'tcx> { typing_env: ty::TypingEnv<'tcx>, typeck: &'tcx TypeckResults<'tcx>, source: Cell, + ctxt: Cell, } impl<'tcx> ConstEvalCtxt<'tcx> { @@ -398,6 +399,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> { typing_env: cx.typing_env(), typeck: cx.typeck_results(), source: Cell::new(ConstantSource::Local), + ctxt: Cell::new(SyntaxContext::root()), } } @@ -408,13 +410,15 @@ impl<'tcx> ConstEvalCtxt<'tcx> { typing_env, typeck, source: Cell::new(ConstantSource::Local), + ctxt: Cell::new(SyntaxContext::root()), } } /// Attempts to evaluate the expression and returns both the value and whether it's dependant on /// other items. - pub fn eval_with_source(&self, e: &Expr<'_>) -> Option<(Constant<'tcx>, ConstantSource)> { + pub fn eval_with_source(&self, e: &Expr<'_>, ctxt: SyntaxContext) -> Option<(Constant<'tcx>, ConstantSource)> { self.source.set(ConstantSource::Local); + self.ctxt.set(ctxt); self.expr(e).map(|c| (c, self.source.get())) } @@ -424,16 +428,16 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } /// Attempts to evaluate the expression without accessing other items. - pub fn eval_simple(&self, e: &Expr<'_>) -> Option> { - match self.eval_with_source(e) { + pub fn eval_local(&self, e: &Expr<'_>, ctxt: SyntaxContext) -> Option> { + match self.eval_with_source(e, ctxt) { Some((x, ConstantSource::Local)) => Some(x), _ => None, } } /// Attempts to evaluate the expression as an integer without accessing other items. - pub fn eval_full_int(&self, e: &Expr<'_>) -> Option { - match self.eval_with_source(e) { + pub fn eval_full_int(&self, e: &Expr<'_>, ctxt: SyntaxContext) -> Option { + match self.eval_with_source(e, ctxt) { Some((x, ConstantSource::Local)) => x.int_value(self.tcx, self.typeck.expr_ty(e)), _ => None, } @@ -455,6 +459,14 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } } + fn check_ctxt(&self, ctxt: SyntaxContext) { + self.source.set(if self.ctxt.get() != ctxt { + ConstantSource::Constant + } else { + self.source.get() + }); + } + fn qpath(&self, qpath: &QPath<'_>, hir_id: HirId) -> Option> { let is_core_crate = if let Some(def_id) = self.typeck.qpath_res(qpath, hir_id).opt_def_id() { self.tcx.crate_name(def_id.krate) == sym::core @@ -477,17 +489,18 @@ impl<'tcx> ConstEvalCtxt<'tcx> { /// Simple constant folding: Insert an expression, get a constant or none. fn expr(&self, e: &Expr<'_>) -> Option> { + self.check_ctxt(e.span.ctxt()); match e.kind { ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr(self.tcx.hir_body(body).value), ExprKind::DropTemps(e) => self.expr(e), ExprKind::Path(ref qpath) => self.qpath(qpath, e.hir_id), - ExprKind::Block(block, _) => self.block(block), + ExprKind::Block(block, _) => { + self.check_ctxt(block.span.ctxt()); + self.block(block) + }, ExprKind::Lit(lit) => { - if is_direct_expn_of(e.span, sym::cfg).is_some() { - None - } else { - Some(lit_to_mir_constant(&lit.node, self.typeck.expr_ty_opt(e))) - } + self.check_ctxt(lit.span.ctxt()); + Some(lit_to_mir_constant(&lit.node, self.typeck.expr_ty_opt(e))) }, ExprKind::Array(vec) => self.multi(vec).map(Constant::Vec), ExprKind::Tup(tup) => self.multi(tup).map(Constant::Tuple), @@ -504,7 +517,10 @@ impl<'tcx> ConstEvalCtxt<'tcx> { UnOp::Deref => Some(if let Constant::Ref(r) = o { *r } else { o }), }), ExprKind::If(cond, then, ref otherwise) => self.ifthenelse(cond, then, *otherwise), - ExprKind::Binary(op, left, right) => self.binop(op.node, left, right), + ExprKind::Binary(op, left, right) => { + self.check_ctxt(e.span.ctxt()); + self.binop(op.node, left, right) + }, ExprKind::Call(callee, []) => { // We only handle a few const functions for now. if let ExprKind::Path(qpath) = &callee.kind @@ -525,6 +541,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> { ExprKind::Index(arr, index, _) => self.index(arr, index), ExprKind::AddrOf(_, _, inner) => self.expr(inner).map(|r| Constant::Ref(Box::new(r))), ExprKind::Field(local_expr, ref field) => { + self.check_ctxt(field.span.ctxt()); let result = self.expr(local_expr); if let Some(Constant::Adt(constant)) = &self.expr(local_expr) && let ty::Adt(adt_def, _) = constant.ty().kind() @@ -958,8 +975,8 @@ fn field_of_struct<'tcx>( } /// If `expr` evaluates to an integer constant, return its value. -pub fn integer_const(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { - if let Some(Constant::Int(value)) = ConstEvalCtxt::new(cx).eval_simple(expr) { +pub fn integer_const(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> Option { + if let Some(Constant::Int(value)) = ConstEvalCtxt::new(cx).eval_local(expr, ctxt) { Some(value) } else { None @@ -968,6 +985,6 @@ pub fn integer_const(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { /// Check if `expr` evaluates to an integer constant of 0. #[inline] -pub fn is_zero_integer_const(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - integer_const(cx, expr) == Some(0) +pub fn is_zero_integer_const(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool { + integer_const(cx, expr, ctxt) == Some(0) } diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index bda28a663fb07..6f1bc28fbab81 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -454,7 +454,7 @@ pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) - return Some(VecInitKind::Default); } else if name.ident.name == sym::with_capacity { let arg = args.first()?; - return match ConstEvalCtxt::new(cx).eval_simple(arg) { + return match ConstEvalCtxt::new(cx).eval_local(arg, expr.span.ctxt()) { Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)), _ => Some(VecInitKind::WithExprCapacity(arg.hir_id)), }; diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index b79e15cd7170b..a6d821c99c1b5 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -290,8 +290,10 @@ impl HirEqInterExpr<'_, '_, '_> { if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results && typeck_lhs.expr_ty(left) == typeck_rhs.expr_ty(right) && let (Some(l), Some(r)) = ( - ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_lhs).eval_simple(left), - ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_rhs).eval_simple(right), + ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_lhs) + .eval_local(left, self.left_ctxt), + ConstEvalCtxt::with_env(self.inner.cx.tcx, self.inner.cx.typing_env(), typeck_rhs) + .eval_local(right, self.right_ctxt), ) && l == r { @@ -842,7 +844,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { #[expect(clippy::too_many_lines)] pub fn hash_expr(&mut self, e: &Expr<'_>) { let simple_const = self.maybe_typeck_results.and_then(|typeck_results| { - ConstEvalCtxt::with_env(self.cx.tcx, self.cx.typing_env(), typeck_results).eval_simple(e) + ConstEvalCtxt::with_env(self.cx.tcx, self.cx.typing_env(), typeck_results).eval_local(e, e.span.ctxt()) }); // const hashing may result in the same hash as some unrelated node, so add a sort of diff --git a/tests/ui/assertions_on_constants.rs b/tests/ui/assertions_on_constants.rs index b613892f206a6..f467d4966aef7 100644 --- a/tests/ui/assertions_on_constants.rs +++ b/tests/ui/assertions_on_constants.rs @@ -43,6 +43,7 @@ fn main() { assert_const!(-1); assert!(cfg!(feature = "hey") || cfg!(not(feature = "asdf"))); + //~^ assertions_on_constants let flag: bool = cfg!(not(feature = "asdf")); assert!(flag); diff --git a/tests/ui/assertions_on_constants.stderr b/tests/ui/assertions_on_constants.stderr index 9aed1405fa043..a996c41b69420 100644 --- a/tests/ui/assertions_on_constants.stderr +++ b/tests/ui/assertions_on_constants.stderr @@ -72,8 +72,16 @@ LL | debug_assert!(true); | = help: remove the assertion +error: this assertion has a constant value + --> tests/ui/assertions_on_constants.rs:45:5 + | +LL | assert!(cfg!(feature = "hey") || cfg!(not(feature = "asdf"))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider moving this into a const block: `const { assert!(..) }` + error: this assertion is always `true` - --> tests/ui/assertions_on_constants.rs:53:19 + --> tests/ui/assertions_on_constants.rs:54:19 | LL | const _: () = assert!(true); | ^^^^^^^^^^^^^ @@ -81,7 +89,7 @@ LL | const _: () = assert!(true); = help: remove the assertion error: this assertion is always `true` - --> tests/ui/assertions_on_constants.rs:56:5 + --> tests/ui/assertions_on_constants.rs:57:5 | LL | assert!(8 == (7 + 1)); | ^^^^^^^^^^^^^^^^^^^^^ @@ -89,7 +97,7 @@ LL | assert!(8 == (7 + 1)); = help: remove the assertion error: this assertion is always `true` - --> tests/ui/assertions_on_constants.rs:67:5 + --> tests/ui/assertions_on_constants.rs:68:5 | LL | assert!(true); | ^^^^^^^^^^^^^ @@ -97,7 +105,7 @@ LL | assert!(true); = help: remove the assertion error: this assertion is always `true` - --> tests/ui/assertions_on_constants.rs:70:5 + --> tests/ui/assertions_on_constants.rs:71:5 | LL | assert!(8 == (7 + 1)); | ^^^^^^^^^^^^^^^^^^^^^ @@ -105,7 +113,7 @@ LL | assert!(8 == (7 + 1)); = help: remove the assertion error: this assertion has a constant value - --> tests/ui/assertions_on_constants.rs:78:5 + --> tests/ui/assertions_on_constants.rs:79:5 | LL | assert!(C); | ^^^^^^^^^^ @@ -113,7 +121,7 @@ LL | assert!(C); = help: consider moving this to an anonymous constant: `const _: () = { assert!(..); }` error: this assertion has a constant value - --> tests/ui/assertions_on_constants.rs:89:5 + --> tests/ui/assertions_on_constants.rs:90:5 | LL | assert!(C); | ^^^^^^^^^^ @@ -121,12 +129,12 @@ LL | assert!(C); = help: consider moving this into a const block: `const { assert!(..) }` error: this assertion has a constant value - --> tests/ui/assertions_on_constants.rs:95:5 + --> tests/ui/assertions_on_constants.rs:96:5 | LL | assert!(C); | ^^^^^^^^^^ | = help: consider moving this to an anonymous constant: `const _: () = { assert!(..); }` -error: aborting due to 16 previous errors +error: aborting due to 17 previous errors From 951d35eeb6aa3bd0ba6fd399290cbdeceec5a97a Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sun, 21 Sep 2025 17:30:57 +0200 Subject: [PATCH 83/93] Do not trigger `inefficient_to_string` after Rust 1.82 Starting with Rust version 1.82.0, the compiler generates similar code with and without the `with_ref` cfg: ```rust fn f(x: impl IntoIterator) { for y in x { println!("{y}"); } } fn main() { #[cfg(with_ref)] let a = ["foo", "bar"].iter().map(|&s| s.to_string()); #[cfg(not(with_ref))] let a = ["foo", "bar"].iter().map(|s| s.to_string()); f(a); } ``` The generated code is strictly identical with `-O`, and identical modulo some minor reordering without. --- book/src/lint_configuration.md | 1 + clippy_config/src/conf.rs | 1 + clippy_lints/src/methods/inefficient_to_string.rs | 4 ++++ clippy_lints/src/methods/mod.rs | 11 +++++++---- clippy_utils/src/msrvs.rs | 2 +- tests/ui/inefficient_to_string.fixed | 8 ++++++++ tests/ui/inefficient_to_string.rs | 8 ++++++++ tests/ui/inefficient_to_string.stderr | 12 ++++++------ 8 files changed, 36 insertions(+), 11 deletions(-) diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index c2d080cd96a1d..5953e5048e4a6 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -845,6 +845,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`from_over_into`](https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into) * [`if_then_some_else_none`](https://rust-lang.github.io/rust-clippy/master/index.html#if_then_some_else_none) * [`index_refutable_slice`](https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice) +* [`inefficient_to_string`](https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_to_string) * [`io_other_error`](https://rust-lang.github.io/rust-clippy/master/index.html#io_other_error) * [`iter_kv_map`](https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map) * [`legacy_numeric_constants`](https://rust-lang.github.io/rust-clippy/master/index.html#legacy_numeric_constants) diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 2f28f6175ad82..6e8752c5f932d 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -741,6 +741,7 @@ define_Conf! { from_over_into, if_then_some_else_none, index_refutable_slice, + inefficient_to_string, io_other_error, iter_kv_map, legacy_numeric_constants, diff --git a/clippy_lints/src/methods/inefficient_to_string.rs b/clippy_lints/src/methods/inefficient_to_string.rs index 47195fdd65f5f..ab21515f47f78 100644 --- a/clippy_lints/src/methods/inefficient_to_string.rs +++ b/clippy_lints/src/methods/inefficient_to_string.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::{is_type_lang_item, peel_and_count_ty_refs}; use rustc_errors::Applicability; @@ -16,6 +17,7 @@ pub fn check( method_name: Symbol, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>], + msrv: Msrv, ) { if args.is_empty() && method_name == sym::to_string @@ -26,6 +28,8 @@ pub fn check( && let self_ty = args.type_at(0) && let (deref_self_ty, deref_count, _) = peel_and_count_ty_refs(self_ty) && deref_count >= 1 + // Since Rust 1.82, the specialized `ToString` is properly called + && !msrv.meets(cx, msrvs::SPECIALIZED_TO_STRING_FOR_REFS) && specializes_tostring(cx, deref_self_ty) { span_lint_and_then( diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 8679689c8ad4d..5448a266ab7d0 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -477,6 +477,9 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `ok().expect(..)`. /// + /// Note: This lint only triggers for code marked compatible + /// with versions of the compiler older than Rust 1.82.0. + /// /// ### Why is this bad? /// Because you usually call `expect()` on the `Result` /// directly to get a better error message. @@ -1080,9 +1083,9 @@ declare_clippy_lint! { /// `T` implements `ToString` directly (like `&&str` or `&&String`). /// /// ### Why is this bad? - /// This bypasses the specialized implementation of - /// `ToString` and instead goes through the more expensive string formatting - /// facilities. + /// In versions of the compiler before Rust 1.82.0, this bypasses the specialized + /// implementation of`ToString` and instead goes through the more expensive string + /// formatting facilities. /// /// ### Example /// ```no_run @@ -4868,7 +4871,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { ); clone_on_copy::check(cx, expr, method_call.ident.name, receiver, args); clone_on_ref_ptr::check(cx, expr, method_call.ident.name, receiver, args); - inefficient_to_string::check(cx, expr, method_call.ident.name, receiver, args); + inefficient_to_string::check(cx, expr, method_call.ident.name, receiver, args, self.msrv); single_char_add_str::check(cx, expr, receiver, args); into_iter_on_ref::check(cx, expr, method_span, method_call.ident.name, receiver); unnecessary_to_owned::check(cx, expr, method_call.ident.name, receiver, args, self.msrv); diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index 6e07ed9ffcc4d..43e9a8f505191 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -28,7 +28,7 @@ msrv_aliases! { 1,85,0 { UINT_FLOAT_MIDPOINT, CONST_SIZE_OF_VAL } 1,84,0 { CONST_OPTION_AS_SLICE, MANUAL_DANGLING_PTR } 1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP } - 1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP } + 1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP, SPECIALIZED_TO_STRING_FOR_REFS } 1,81,0 { LINT_REASONS_STABILIZATION, ERROR_IN_CORE, EXPLICIT_SELF_TYPE_ELISION, DURATION_ABS_DIFF } 1,80,0 { BOX_INTO_ITER, LAZY_CELL } 1,77,0 { C_STR_LITERALS } diff --git a/tests/ui/inefficient_to_string.fixed b/tests/ui/inefficient_to_string.fixed index a0d34e58a9255..29cf6de6ae5e8 100644 --- a/tests/ui/inefficient_to_string.fixed +++ b/tests/ui/inefficient_to_string.fixed @@ -2,6 +2,7 @@ use std::borrow::Cow; +#[clippy::msrv = "1.81"] fn main() { let rstr: &str = "hello"; let rrstr: &&str = &rstr; @@ -34,3 +35,10 @@ fn main() { let _: String = (**rrrcow).to_string(); //~^ inefficient_to_string } + +#[clippy::msrv = "1.82"] +fn sufficient_msrv() { + let rstr: &str = "hello"; + let rrstr: &&str = &rstr; + let _: String = rrstr.to_string(); +} diff --git a/tests/ui/inefficient_to_string.rs b/tests/ui/inefficient_to_string.rs index cbe90d4a125b0..724955c60f795 100644 --- a/tests/ui/inefficient_to_string.rs +++ b/tests/ui/inefficient_to_string.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; +#[clippy::msrv = "1.81"] fn main() { let rstr: &str = "hello"; let rrstr: &&str = &rstr; @@ -34,3 +35,10 @@ fn main() { let _: String = rrrcow.to_string(); //~^ inefficient_to_string } + +#[clippy::msrv = "1.82"] +fn sufficient_msrv() { + let rstr: &str = "hello"; + let rrstr: &&str = &rstr; + let _: String = rrstr.to_string(); +} diff --git a/tests/ui/inefficient_to_string.stderr b/tests/ui/inefficient_to_string.stderr index 8593c0addc5f8..ea3dd7e0ae2f8 100644 --- a/tests/ui/inefficient_to_string.stderr +++ b/tests/ui/inefficient_to_string.stderr @@ -1,5 +1,5 @@ error: calling `to_string` on `&&str` - --> tests/ui/inefficient_to_string.rs:10:21 + --> tests/ui/inefficient_to_string.rs:11:21 | LL | let _: String = rrstr.to_string(); | ^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(*rrstr).to_string()` @@ -12,7 +12,7 @@ LL | #![deny(clippy::inefficient_to_string)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: calling `to_string` on `&&&str` - --> tests/ui/inefficient_to_string.rs:12:21 + --> tests/ui/inefficient_to_string.rs:13:21 | LL | let _: String = rrrstr.to_string(); | ^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(**rrrstr).to_string()` @@ -20,7 +20,7 @@ LL | let _: String = rrrstr.to_string(); = help: `&&str` implements `ToString` through a slower blanket impl, but `str` has a fast specialization of `ToString` error: calling `to_string` on `&&std::string::String` - --> tests/ui/inefficient_to_string.rs:21:21 + --> tests/ui/inefficient_to_string.rs:22:21 | LL | let _: String = rrstring.to_string(); | ^^^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(*rrstring).to_string()` @@ -28,7 +28,7 @@ LL | let _: String = rrstring.to_string(); = help: `&std::string::String` implements `ToString` through a slower blanket impl, but `std::string::String` has a fast specialization of `ToString` error: calling `to_string` on `&&&std::string::String` - --> tests/ui/inefficient_to_string.rs:23:21 + --> tests/ui/inefficient_to_string.rs:24:21 | LL | let _: String = rrrstring.to_string(); | ^^^^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(**rrrstring).to_string()` @@ -36,7 +36,7 @@ LL | let _: String = rrrstring.to_string(); = help: `&&std::string::String` implements `ToString` through a slower blanket impl, but `std::string::String` has a fast specialization of `ToString` error: calling `to_string` on `&&std::borrow::Cow<'_, str>` - --> tests/ui/inefficient_to_string.rs:32:21 + --> tests/ui/inefficient_to_string.rs:33:21 | LL | let _: String = rrcow.to_string(); | ^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(*rrcow).to_string()` @@ -44,7 +44,7 @@ LL | let _: String = rrcow.to_string(); = help: `&std::borrow::Cow<'_, str>` implements `ToString` through a slower blanket impl, but `std::borrow::Cow<'_, str>` has a fast specialization of `ToString` error: calling `to_string` on `&&&std::borrow::Cow<'_, str>` - --> tests/ui/inefficient_to_string.rs:34:21 + --> tests/ui/inefficient_to_string.rs:35:21 | LL | let _: String = rrrcow.to_string(); | ^^^^^^^^^^^^^^^^^^ help: try dereferencing the receiver: `(**rrrcow).to_string()` From 3d2a8df077dcf6f84a15a76dec511af286435e84 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 2 Oct 2025 13:29:14 -0400 Subject: [PATCH 84/93] Upgrade `toml` to 0.9.7 and remove the `serde` feature. --- Cargo.toml | 7 +- clippy_lints/Cargo.toml | 7 +- .../src/cargo/lint_groups_priority.rs | 186 +++++++++--------- lintcheck/Cargo.toml | 2 +- tests/compile-test.rs | 22 ++- 5 files changed, 119 insertions(+), 105 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e06383499893a..bedcc300f8567 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,13 +38,18 @@ ui_test = "0.30.2" regex = "1.5.5" serde = { version = "1.0.145", features = ["derive"] } serde_json = "1.0.122" -toml = "0.7.3" walkdir = "2.3" filetime = "0.2.9" itertools = "0.12" pulldown-cmark = { version = "0.11", default-features = false, features = ["html"] } askama = { version = "0.14", default-features = false, features = ["alloc", "config", "derive"] } +[dev-dependencies.toml] +version = "0.9.7" +default-features = false +# preserve_order keeps diagnostic output in file order +features = ["parse", "preserve_order"] + [build-dependencies] rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" } diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 51e59ae205076..42486e182ee34 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -18,12 +18,17 @@ itertools = "0.12" quine-mc_cluskey = "0.2" regex-syntax = "0.8" serde = { version = "1.0", features = ["derive"] } -toml = "0.7.3" unicode-normalization = "0.1" unicode-script = { version = "0.5", default-features = false } semver = "1.0" url = "2.2" +[dependencies.toml] +version = "0.9.7" +default-features = false +# preserve_order keeps diagnostic output in file order +features = ["parse", "preserve_order"] + [dev-dependencies] walkdir = "2.3" diff --git a/clippy_lints/src/cargo/lint_groups_priority.rs b/clippy_lints/src/cargo/lint_groups_priority.rs index ffd6c520c9ae8..14c5e22fb9cd5 100644 --- a/clippy_lints/src/cargo/lint_groups_priority.rs +++ b/clippy_lints/src/cargo/lint_groups_priority.rs @@ -4,142 +4,103 @@ use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_lint::{LateContext, unerased_lint_store}; use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext}; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; use std::ops::Range; use std::path::Path; use toml::Spanned; +use toml::de::{DeTable, DeValue}; -#[derive(Deserialize, Serialize, Debug)] -struct LintConfigTable { - level: String, - priority: Option, +fn toml_span(range: Range, file: &SourceFile) -> Span { + Span::new( + file.start_pos + BytePos::from_usize(range.start), + file.start_pos + BytePos::from_usize(range.end), + SyntaxContext::root(), + None, + ) } -#[derive(Deserialize, Debug)] -#[serde(untagged)] -enum LintConfig { - Level(String), - Table(LintConfigTable), +struct LintConfig<'a> { + sp: Range, + level: &'a str, + priority: Option, } - -impl LintConfig { - fn level(&self) -> &str { - match self { - LintConfig::Level(level) => level, - LintConfig::Table(table) => &table.level, - } - } - +impl<'a> LintConfig<'a> { fn priority(&self) -> i64 { - match self { - LintConfig::Level(_) => 0, - LintConfig::Table(table) => table.priority.unwrap_or(0), - } + self.priority.unwrap_or(0) } fn is_implicit(&self) -> bool { - if let LintConfig::Table(table) = self { - table.priority.is_none() - } else { - true - } + self.priority.is_none() } -} - -type LintTable = BTreeMap, Spanned>; - -#[derive(Deserialize, Debug, Default)] -struct Lints { - #[serde(default)] - rust: LintTable, - #[serde(default)] - clippy: LintTable, -} - -#[derive(Deserialize, Debug, Default)] -struct Workspace { - #[serde(default)] - lints: Lints, -} -#[derive(Deserialize, Debug)] -struct CargoToml { - #[serde(default)] - lints: Lints, - #[serde(default)] - workspace: Workspace, -} - -fn toml_span(range: Range, file: &SourceFile) -> Span { - Span::new( - file.start_pos + BytePos::from_usize(range.start), - file.start_pos + BytePos::from_usize(range.end), - SyntaxContext::root(), - None, - ) + fn parse(value: &'a Spanned>) -> Option { + let sp = value.span(); + let (level, priority) = match value.get_ref() { + DeValue::String(level) => (&**level, None), + DeValue::Table(tbl) => { + let level = tbl.get("level")?.get_ref().as_str()?; + let priority = if let Some(priority) = tbl.get("priority") { + let priority = priority.get_ref().as_integer()?; + Some(i64::from_str_radix(priority.as_str(), priority.radix()).ok()?) + } else { + None + }; + (level, priority) + }, + _ => return None, + }; + Some(Self { sp, level, priority }) + } } -fn check_table(cx: &LateContext<'_>, table: LintTable, known_groups: &FxHashSet<&str>, file: &SourceFile) { +fn check_table(cx: &LateContext<'_>, table: &DeTable<'_>, known_groups: &FxHashSet<&str>, file: &SourceFile) { let mut lints = Vec::new(); let mut groups = Vec::new(); for (name, config) in table { - if name.get_ref() == "warnings" { - continue; - } - - if known_groups.contains(name.get_ref().as_str()) { - groups.push((name, config)); - } else { - lints.push((name, config.into_inner())); + if name.get_ref() != "warnings" + && let Some(config) = LintConfig::parse(config) + { + if known_groups.contains(&**name.get_ref()) { + groups.push((name, config)); + } else { + lints.push((name, config)); + } } } for (group, group_config) in groups { - let priority = group_config.get_ref().priority(); - let level = group_config.get_ref().level(); - if let Some((conflict, _)) = lints - .iter() - .rfind(|(_, lint_config)| lint_config.priority() == priority && lint_config.level() != level) - { + if let Some((conflict, _)) = lints.iter().rfind(|(_, lint_config)| { + lint_config.priority() == group_config.priority() && lint_config.level != group_config.level + }) { span_lint_and_then( cx, LINT_GROUPS_PRIORITY, toml_span(group.span(), file), format!( - "lint group `{}` has the same priority ({priority}) as a lint", - group.as_ref() + "lint group `{}` has the same priority ({}) as a lint", + group.as_ref(), + group_config.priority(), ), |diag| { - let config_span = toml_span(group_config.span(), file); + let config_span = toml_span(group_config.sp.clone(), file); - if group_config.as_ref().is_implicit() { + if group_config.is_implicit() { diag.span_label(config_span, "has an implicit priority of 0"); } diag.span_label(toml_span(conflict.span(), file), "has the same priority as this lint"); diag.note("the order of the lints in the table is ignored by Cargo"); - let mut suggestion = String::new(); let low_priority = lints .iter() - .map(|(_, config)| config.priority().saturating_sub(1)) + .map(|(_, lint_config)| lint_config.priority().saturating_sub(1)) .min() .unwrap_or(-1); - Serialize::serialize( - &LintConfigTable { - level: level.into(), - priority: Some(low_priority), - }, - toml::ser::ValueSerializer::new(&mut suggestion), - ) - .unwrap(); diag.span_suggestion_verbose( config_span, format!( "to have lints override the group set `{}` to a lower priority", group.as_ref() ), - suggestion, + format!("{{ level = {:?}, priority = {low_priority} }}", group_config.level,), Applicability::MaybeIncorrect, ); }, @@ -148,10 +109,29 @@ fn check_table(cx: &LateContext<'_>, table: LintTable, known_groups: &FxHashSet< } } +struct LintTbls<'a> { + rust: Option<&'a DeTable<'a>>, + clippy: Option<&'a DeTable<'a>>, +} +fn get_lint_tbls<'a>(tbl: &'a DeTable<'a>) -> LintTbls<'a> { + if let Some(lints) = tbl.get("lints") + && let Some(lints) = lints.get_ref().as_table() + { + let rust = lints.get("rust").and_then(|x| x.get_ref().as_table()); + let clippy = lints.get("clippy").and_then(|x| x.get_ref().as_table()); + LintTbls { rust, clippy } + } else { + LintTbls { + rust: None, + clippy: None, + } + } +} + pub fn check(cx: &LateContext<'_>) { if let Ok(file) = cx.tcx.sess.source_map().load_file(Path::new("Cargo.toml")) && let Some(src) = file.src.as_deref() - && let Ok(cargo_toml) = toml::from_str::(src) + && let Ok(cargo_toml) = DeTable::parse(src) { let mut rustc_groups = FxHashSet::default(); let mut clippy_groups = FxHashSet::default(); @@ -167,9 +147,23 @@ pub fn check(cx: &LateContext<'_>) { } } - check_table(cx, cargo_toml.lints.rust, &rustc_groups, &file); - check_table(cx, cargo_toml.lints.clippy, &clippy_groups, &file); - check_table(cx, cargo_toml.workspace.lints.rust, &rustc_groups, &file); - check_table(cx, cargo_toml.workspace.lints.clippy, &clippy_groups, &file); + let lints = get_lint_tbls(cargo_toml.get_ref()); + if let Some(lints) = lints.rust { + check_table(cx, lints, &rustc_groups, &file); + } + if let Some(lints) = lints.clippy { + check_table(cx, lints, &clippy_groups, &file); + } + if let Some(tbl) = cargo_toml.get_ref().get("workspace") + && let Some(tbl) = tbl.get_ref().as_table() + { + let lints = get_lint_tbls(tbl); + if let Some(lints) = lints.rust { + check_table(cx, lints, &rustc_groups, &file); + } + if let Some(lints) = lints.clippy { + check_table(cx, lints, &clippy_groups, &file); + } + } } } diff --git a/lintcheck/Cargo.toml b/lintcheck/Cargo.toml index 55e588f5ec736..0d0b80c309ddd 100644 --- a/lintcheck/Cargo.toml +++ b/lintcheck/Cargo.toml @@ -22,6 +22,6 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.85" strip-ansi-escapes = "0.2.0" tar = "0.4" -toml = "0.7.3" +toml = "0.9.7" ureq = { version = "2.2", features = ["json"] } walkdir = "2.3" diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 6b6dfd7b81eab..71cd8a6c03cc9 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -405,11 +405,18 @@ fn ui_cargo_toml_metadata() { continue; } - let toml = fs::read_to_string(path).unwrap().parse::().unwrap(); - - let package = toml.as_table().unwrap().get("package").unwrap().as_table().unwrap(); - - let name = package.get("name").unwrap().as_str().unwrap().replace('-', "_"); + let toml = fs::read_to_string(path).unwrap(); + let toml = toml::de::DeTable::parse(&toml).unwrap(); + + let package = toml.get_ref().get("package").unwrap().get_ref().as_table().unwrap(); + + let name = package + .get("name") + .unwrap() + .as_ref() + .as_str() + .unwrap() + .replace('-', "_"); assert!( path.parent() .unwrap() @@ -421,7 +428,10 @@ fn ui_cargo_toml_metadata() { path.display(), ); - let publish = package.get("publish").and_then(toml::Value::as_bool).unwrap_or(true); + let publish = package + .get("publish") + .and_then(|x| x.get_ref().as_bool()) + .unwrap_or(true); assert!( !publish || publish_exceptions.contains(&path.parent().unwrap().to_path_buf()), "`{}` lacks `publish = false`", From 87d2891e82707c8c9730997f2f4771d4cebdbb22 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Mon, 29 Sep 2025 22:31:41 +0200 Subject: [PATCH 85/93] Cleanup `should_implement_trait` lint code --- .../src/methods/should_implement_trait.rs | 153 ++++++++---------- 1 file changed, 69 insertions(+), 84 deletions(-) diff --git a/clippy_lints/src/methods/should_implement_trait.rs b/clippy_lints/src/methods/should_implement_trait.rs index 5f13b8c7e91df..599ff696f6aed 100644 --- a/clippy_lints/src/methods/should_implement_trait.rs +++ b/clippy_lints/src/methods/should_implement_trait.rs @@ -1,8 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::{is_bool, sym}; use rustc_abi::ExternAbi; -use rustc_hir as hir; -use rustc_hir::{FnSig, ImplItem}; +use rustc_hir::{self as hir, FnRetTy, FnSig, GenericParamKind, ImplItem, LifetimeParamKind}; use rustc_lint::LateContext; use rustc_middle::ty::Ty; use rustc_span::edition::Edition::{self, Edition2015, Edition2021}; @@ -20,51 +19,43 @@ pub(super) fn check_impl_item<'tcx>( sig: &FnSig<'_>, ) { // if this impl block implements a trait, lint in trait definition instead - if !impl_implements_trait && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) { + if !impl_implements_trait && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) // check missing trait implementations - for method_config in &TRAIT_METHODS { - if impl_item.ident.name == method_config.method_name - && sig.decl.inputs.len() == method_config.param_count - && method_config.output_type.matches(&sig.decl.output) - // in case there is no first arg, since we already have checked the number of arguments - // it's should be always true - && first_arg_ty_opt - .is_none_or(|first_arg_ty| method_config.self_kind.matches(cx, self_ty, first_arg_ty)) - && fn_header_equals(method_config.fn_header, sig.header) - && method_config.lifetime_param_cond(impl_item) - && method_config.in_prelude_since <= cx.tcx.sess.edition() - { - span_lint_and_help( - cx, - SHOULD_IMPLEMENT_TRAIT, - impl_item.span, - format!( - "method `{}` can be confused for the standard trait method `{}::{}`", - method_config.method_name, method_config.trait_name, method_config.method_name - ), - None, - format!( - "consider implementing the trait `{}` or choosing a less ambiguous method name", - method_config.trait_name - ), - ); - } - } + && let Some(method_config) = TRAIT_METHODS.iter().find(|case| case.method_name == impl_item.ident.name) + && sig.decl.inputs.len() == method_config.param_count + && method_config.output_type.matches(&sig.decl.output) + // in case there is no first arg, since we already have checked the number of arguments + // it's should be always true + && first_arg_ty_opt + .is_none_or(|first_arg_ty| method_config.self_kind.matches(cx, self_ty, first_arg_ty)) + && sig.header.is_safe() + && !sig.header.is_const() + && !sig.header.is_async() + && sig.header.abi == ExternAbi::Rust + && method_config.lifetime_param_cond(impl_item) + && method_config.in_prelude_since <= cx.tcx.sess.edition() + { + span_lint_and_help( + cx, + SHOULD_IMPLEMENT_TRAIT, + impl_item.span, + format!( + "method `{}` can be confused for the standard trait method `{}::{}`", + method_config.method_name, method_config.trait_name, method_config.method_name + ), + None, + format!( + "consider implementing the trait `{}` or choosing a less ambiguous method name", + method_config.trait_name + ), + ); } } -const FN_HEADER: hir::FnHeader = hir::FnHeader { - safety: hir::HeaderSafety::Normal(hir::Safety::Safe), - constness: hir::Constness::NotConst, - asyncness: hir::IsAsync::NotAsync, - abi: ExternAbi::Rust, -}; - struct ShouldImplTraitCase { trait_name: &'static str, method_name: Symbol, param_count: usize, - fn_header: hir::FnHeader, // implicit self kind expected (none, self, &self, ...) self_kind: SelfKind, // checks against the output type @@ -73,13 +64,12 @@ struct ShouldImplTraitCase { lint_explicit_lifetime: bool, in_prelude_since: Edition, } + impl ShouldImplTraitCase { - #[expect(clippy::too_many_arguments)] const fn new( trait_name: &'static str, method_name: Symbol, param_count: usize, - fn_header: hir::FnHeader, self_kind: SelfKind, output_type: OutType, lint_explicit_lifetime: bool, @@ -89,7 +79,6 @@ impl ShouldImplTraitCase { trait_name, method_name, param_count, - fn_header, self_kind, output_type, lint_explicit_lifetime, @@ -102,8 +91,8 @@ impl ShouldImplTraitCase { || !impl_item.generics.params.iter().any(|p| { matches!( p.kind, - hir::GenericParamKind::Lifetime { - kind: hir::LifetimeParamKind::Explicit + GenericParamKind::Lifetime { + kind: LifetimeParamKind::Explicit } ) }) @@ -112,36 +101,36 @@ impl ShouldImplTraitCase { #[rustfmt::skip] const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [ - ShouldImplTraitCase::new("std::ops::Add", sym::add, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), - ShouldImplTraitCase::new("std::convert::AsMut", sym::as_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true, Edition2015), - ShouldImplTraitCase::new("std::convert::AsRef", sym::as_ref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true, Edition2015), - ShouldImplTraitCase::new("std::ops::BitAnd", sym::bitand, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), - ShouldImplTraitCase::new("std::ops::BitOr", sym::bitor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), - ShouldImplTraitCase::new("std::ops::BitXor", sym::bitxor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), - ShouldImplTraitCase::new("std::borrow::Borrow", sym::borrow, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true, Edition2015), - ShouldImplTraitCase::new("std::borrow::BorrowMut", sym::borrow_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true, Edition2015), - ShouldImplTraitCase::new("std::clone::Clone", sym::clone, 1, FN_HEADER, SelfKind::Ref, OutType::Any, true, Edition2015), - ShouldImplTraitCase::new("std::cmp::Ord", sym::cmp, 2, FN_HEADER, SelfKind::Ref, OutType::Any, true, Edition2015), - ShouldImplTraitCase::new("std::default::Default", kw::Default, 0, FN_HEADER, SelfKind::No, OutType::Any, true, Edition2015), - ShouldImplTraitCase::new("std::ops::Deref", sym::deref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true, Edition2015), - ShouldImplTraitCase::new("std::ops::DerefMut", sym::deref_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true, Edition2015), - ShouldImplTraitCase::new("std::ops::Div", sym::div, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), - ShouldImplTraitCase::new("std::ops::Drop", sym::drop, 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true, Edition2015), - ShouldImplTraitCase::new("std::cmp::PartialEq", sym::eq, 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true, Edition2015), - ShouldImplTraitCase::new("std::iter::FromIterator", sym::from_iter, 1, FN_HEADER, SelfKind::No, OutType::Any, true, Edition2021), - ShouldImplTraitCase::new("std::str::FromStr", sym::from_str, 1, FN_HEADER, SelfKind::No, OutType::Any, true, Edition2015), - ShouldImplTraitCase::new("std::hash::Hash", sym::hash, 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true, Edition2015), - ShouldImplTraitCase::new("std::ops::Index", sym::index, 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true, Edition2015), - ShouldImplTraitCase::new("std::ops::IndexMut", sym::index_mut, 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true, Edition2015), - ShouldImplTraitCase::new("std::iter::IntoIterator", sym::into_iter, 1, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), - ShouldImplTraitCase::new("std::ops::Mul", sym::mul, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), - ShouldImplTraitCase::new("std::ops::Neg", sym::neg, 1, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), - ShouldImplTraitCase::new("std::iter::Iterator", sym::next, 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false, Edition2015), - ShouldImplTraitCase::new("std::ops::Not", sym::not, 1, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), - ShouldImplTraitCase::new("std::ops::Rem", sym::rem, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), - ShouldImplTraitCase::new("std::ops::Shl", sym::shl, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), - ShouldImplTraitCase::new("std::ops::Shr", sym::shr, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), - ShouldImplTraitCase::new("std::ops::Sub", sym::sub, 2, FN_HEADER, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Add", sym::add, 2, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::convert::AsMut", sym::as_mut, 1, SelfKind::RefMut, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::convert::AsRef", sym::as_ref, 1, SelfKind::Ref, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::ops::BitAnd", sym::bitand, 2, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::BitOr", sym::bitor, 2, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::BitXor", sym::bitxor, 2, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::borrow::Borrow", sym::borrow, 1, SelfKind::Ref, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::borrow::BorrowMut", sym::borrow_mut, 1, SelfKind::RefMut, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::clone::Clone", sym::clone, 1, SelfKind::Ref, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::cmp::Ord", sym::cmp, 2, SelfKind::Ref, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::default::Default", kw::Default, 0, SelfKind::No, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Deref", sym::deref, 1, SelfKind::Ref, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::ops::DerefMut", sym::deref_mut, 1, SelfKind::RefMut, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Div", sym::div, 2, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Drop", sym::drop, 1, SelfKind::RefMut, OutType::Unit, true, Edition2015), + ShouldImplTraitCase::new("std::cmp::PartialEq", sym::eq, 2, SelfKind::Ref, OutType::Bool, true, Edition2015), + ShouldImplTraitCase::new("std::iter::FromIterator", sym::from_iter, 1, SelfKind::No, OutType::Any, true, Edition2021), + ShouldImplTraitCase::new("std::str::FromStr", sym::from_str, 1, SelfKind::No, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::hash::Hash", sym::hash, 2, SelfKind::Ref, OutType::Unit, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Index", sym::index, 2, SelfKind::Ref, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::ops::IndexMut", sym::index_mut, 2, SelfKind::RefMut, OutType::Ref, true, Edition2015), + ShouldImplTraitCase::new("std::iter::IntoIterator", sym::into_iter, 1, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Mul", sym::mul, 2, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Neg", sym::neg, 1, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::iter::Iterator", sym::next, 1, SelfKind::RefMut, OutType::Any, false, Edition2015), + ShouldImplTraitCase::new("std::ops::Not", sym::not, 1, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Rem", sym::rem, 2, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Shl", sym::shl, 2, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Shr", sym::shr, 2, SelfKind::Value, OutType::Any, true, Edition2015), + ShouldImplTraitCase::new("std::ops::Sub", sym::sub, 2, SelfKind::Value, OutType::Any, true, Edition2015), ]; #[derive(Clone, Copy)] @@ -153,19 +142,15 @@ enum OutType { } impl OutType { - fn matches(self, ty: &hir::FnRetTy<'_>) -> bool { + fn matches(self, ty: &FnRetTy<'_>) -> bool { let is_unit = |ty: &hir::Ty<'_>| matches!(ty.kind, hir::TyKind::Tup(&[])); match (self, ty) { - (Self::Unit, &hir::FnRetTy::DefaultReturn(_)) => true, - (Self::Unit, &hir::FnRetTy::Return(ty)) if is_unit(ty) => true, - (Self::Bool, &hir::FnRetTy::Return(ty)) if is_bool(ty) => true, - (Self::Any, &hir::FnRetTy::Return(ty)) if !is_unit(ty) => true, - (Self::Ref, &hir::FnRetTy::Return(ty)) => matches!(ty.kind, hir::TyKind::Ref(_, _)), + (Self::Unit, &FnRetTy::DefaultReturn(_)) => true, + (Self::Unit, &FnRetTy::Return(ty)) if is_unit(ty) => true, + (Self::Bool, &FnRetTy::Return(ty)) if is_bool(ty) => true, + (Self::Any, &FnRetTy::Return(ty)) if !is_unit(ty) => true, + (Self::Ref, &FnRetTy::Return(ty)) => matches!(ty.kind, hir::TyKind::Ref(_, _)), _ => false, } } } - -fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool { - expected.constness == actual.constness && expected.safety == actual.safety && expected.asyncness == actual.asyncness -} From 24befed7279ae8be1c52e698172c1366e41e06b1 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 3 Oct 2025 22:56:05 +0200 Subject: [PATCH 86/93] clean-up - inline `array_span_lint` - use `match` instead of `if-let` - give a more descriptive help message --- clippy_lints/src/zero_repeat_side_effects.rs | 104 ++++++------------- tests/ui/zero_repeat_side_effects.fixed | 4 +- tests/ui/zero_repeat_side_effects.rs | 4 +- tests/ui/zero_repeat_side_effects.stderr | 89 ++++++++++++---- 4 files changed, 105 insertions(+), 96 deletions(-) diff --git a/clippy_lints/src/zero_repeat_side_effects.rs b/clippy_lints/src/zero_repeat_side_effects.rs index 30fdf22fdbb09..e51c4a6898164 100644 --- a/clippy_lints/src/zero_repeat_side_effects.rs +++ b/clippy_lints/src/zero_repeat_side_effects.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::VecArgs; use clippy_utils::source::snippet; use clippy_utils::visitors::for_each_expr_without_closures; @@ -7,9 +7,7 @@ use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; use rustc_hir::{ConstArgKind, ExprKind, Node}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::Ty; use rustc_session::declare_lint_pass; -use rustc_span::Span; declare_clippy_lint! { /// ### What it does @@ -85,77 +83,39 @@ fn inner_check(cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>, inner_expr: let parent_hir_node = cx.tcx.parent_hir_node(expr.hir_id); let return_type = cx.typeck_results().expr_ty(expr); - if let Node::LetStmt(l) = parent_hir_node { - array_span_lint( - cx, + let inner_expr = snippet(cx, inner_expr.span.source_callsite(), ".."); + let vec = if is_vec { "vec!" } else { "" }; + + let (span, sugg) = match parent_hir_node { + Node::LetStmt(l) => ( l.span, - inner_expr.span, - l.pat.span, - Some(return_type), - is_vec, - false, - ); - } else if let Node::Expr(x) = parent_hir_node - && let ExprKind::Assign(l, _, _) = x.kind - { - array_span_lint(cx, x.span, inner_expr.span, l.span, Some(return_type), is_vec, true); - } else { - span_lint_and_sugg( - cx, - ZERO_REPEAT_SIDE_EFFECTS, - expr.span.source_callsite(), - "function or method calls as the initial value in zero-sized array initializers may cause side effects", - "consider using", format!( - "{{ {}; {}[] as {return_type} }}", - snippet(cx, inner_expr.span.source_callsite(), ".."), - if is_vec { "vec!" } else { "" }, + "{inner_expr}; let {var_name}: {return_type} = {vec}[];", + var_name = snippet(cx, l.pat.span.source_callsite(), "..") ), - Applicability::Unspecified, - ); - } - } -} - -fn array_span_lint( - cx: &LateContext<'_>, - expr_span: Span, - func_call_span: Span, - variable_name_span: Span, - expr_ty: Option>, - is_vec: bool, - is_assign: bool, -) { - let has_ty = expr_ty.is_some(); - - span_lint_and_sugg( - cx, - ZERO_REPEAT_SIDE_EFFECTS, - expr_span.source_callsite(), - "function or method calls as the initial value in zero-sized array initializers may cause side effects", - "consider using", - format!( - "{}; {}{}{} = {}[]{}{}", - snippet(cx, func_call_span.source_callsite(), ".."), - if has_ty && !is_assign { "let " } else { "" }, - snippet(cx, variable_name_span.source_callsite(), ".."), - if let Some(ty) = expr_ty - && !is_assign - { - format!(": {ty}") - } else { - String::new() - }, - if is_vec { "vec!" } else { "" }, - if let Some(ty) = expr_ty - && is_assign - { - format!(" as {ty}") - } else { - String::new() + ), + Node::Expr(x) if let ExprKind::Assign(l, _, _) = x.kind => ( + x.span, + format!( + "{inner_expr}; {var_name} = {vec}[] as {return_type}", + var_name = snippet(cx, l.span.source_callsite(), "..") + ), + ), + _ => (expr.span, format!("{{ {inner_expr}; {vec}[] as {return_type} }}")), + }; + span_lint_and_then( + cx, + ZERO_REPEAT_SIDE_EFFECTS, + span.source_callsite(), + "function or method calls as the initial value in zero-sized array initializers may cause side effects", + |diag| { + diag.span_suggestion_verbose( + span.source_callsite(), + "consider performing the side effect separately", + sugg, + Applicability::Unspecified, + ); }, - if is_assign { "" } else { ";" } - ), - Applicability::Unspecified, - ); + ); + } } diff --git a/tests/ui/zero_repeat_side_effects.fixed b/tests/ui/zero_repeat_side_effects.fixed index fb9d7880a4a7f..5184249e942d9 100644 --- a/tests/ui/zero_repeat_side_effects.fixed +++ b/tests/ui/zero_repeat_side_effects.fixed @@ -1,7 +1,5 @@ #![warn(clippy::zero_repeat_side_effects)] -#![allow(clippy::unnecessary_operation)] -#![allow(clippy::useless_vec)] -#![allow(clippy::needless_late_init)] +#![expect(clippy::unnecessary_operation, clippy::useless_vec, clippy::needless_late_init)] fn f() -> i32 { println!("side effect"); diff --git a/tests/ui/zero_repeat_side_effects.rs b/tests/ui/zero_repeat_side_effects.rs index 8b22ff840244e..9c6e758f2e28c 100644 --- a/tests/ui/zero_repeat_side_effects.rs +++ b/tests/ui/zero_repeat_side_effects.rs @@ -1,7 +1,5 @@ #![warn(clippy::zero_repeat_side_effects)] -#![allow(clippy::unnecessary_operation)] -#![allow(clippy::useless_vec)] -#![allow(clippy::needless_late_init)] +#![expect(clippy::unnecessary_operation, clippy::useless_vec, clippy::needless_late_init)] fn f() -> i32 { println!("side effect"); diff --git a/tests/ui/zero_repeat_side_effects.stderr b/tests/ui/zero_repeat_side_effects.stderr index 2dba52e2112ec..dc99110932d89 100644 --- a/tests/ui/zero_repeat_side_effects.stderr +++ b/tests/ui/zero_repeat_side_effects.stderr @@ -1,59 +1,112 @@ error: function or method calls as the initial value in zero-sized array initializers may cause side effects - --> tests/ui/zero_repeat_side_effects.rs:18:5 + --> tests/ui/zero_repeat_side_effects.rs:16:5 | LL | let a = [f(); 0]; - | ^^^^^^^^^^^^^^^^^ help: consider using: `f(); let a: [i32; 0] = [];` + | ^^^^^^^^^^^^^^^^^ | = note: `-D clippy::zero-repeat-side-effects` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::zero_repeat_side_effects)]` +help: consider performing the side effect separately + | +LL - let a = [f(); 0]; +LL + f(); let a: [i32; 0] = []; + | error: function or method calls as the initial value in zero-sized array initializers may cause side effects - --> tests/ui/zero_repeat_side_effects.rs:21:5 + --> tests/ui/zero_repeat_side_effects.rs:19:5 | LL | b = [f(); 0]; - | ^^^^^^^^^^^^ help: consider using: `f(); b = [] as [i32; 0]` + | ^^^^^^^^^^^^ + | +help: consider performing the side effect separately + | +LL - b = [f(); 0]; +LL + f(); b = [] as [i32; 0]; + | error: function or method calls as the initial value in zero-sized array initializers may cause side effects - --> tests/ui/zero_repeat_side_effects.rs:26:5 + --> tests/ui/zero_repeat_side_effects.rs:24:5 | LL | let c = vec![f(); 0]; - | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `f(); let c: std::vec::Vec = vec![];` + | ^^^^^^^^^^^^^^^^^^^^^ + | +help: consider performing the side effect separately + | +LL - let c = vec![f(); 0]; +LL + f(); let c: std::vec::Vec = vec![]; + | error: function or method calls as the initial value in zero-sized array initializers may cause side effects - --> tests/ui/zero_repeat_side_effects.rs:29:5 + --> tests/ui/zero_repeat_side_effects.rs:27:5 | LL | d = vec![f(); 0]; - | ^^^^^^^^^^^^^^^^ help: consider using: `f(); d = vec![] as std::vec::Vec` + | ^^^^^^^^^^^^^^^^ + | +help: consider performing the side effect separately + | +LL - d = vec![f(); 0]; +LL + f(); d = vec![] as std::vec::Vec; + | error: function or method calls as the initial value in zero-sized array initializers may cause side effects - --> tests/ui/zero_repeat_side_effects.rs:33:5 + --> tests/ui/zero_repeat_side_effects.rs:31:5 | LL | let e = [println!("side effect"); 0]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `println!("side effect"); let e: [(); 0] = [];` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider performing the side effect separately + | +LL - let e = [println!("side effect"); 0]; +LL + println!("side effect"); let e: [(); 0] = []; + | error: function or method calls as the initial value in zero-sized array initializers may cause side effects - --> tests/ui/zero_repeat_side_effects.rs:37:5 + --> tests/ui/zero_repeat_side_effects.rs:35:5 | LL | let g = [{ f() }; 0]; - | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `{ f() }; let g: [i32; 0] = [];` + | ^^^^^^^^^^^^^^^^^^^^^ + | +help: consider performing the side effect separately + | +LL - let g = [{ f() }; 0]; +LL + { f() }; let g: [i32; 0] = []; + | error: function or method calls as the initial value in zero-sized array initializers may cause side effects - --> tests/ui/zero_repeat_side_effects.rs:41:10 + --> tests/ui/zero_repeat_side_effects.rs:39:10 | LL | drop(vec![f(); 0]); - | ^^^^^^^^^^^^ help: consider using: `{ f(); vec![] as std::vec::Vec }` + | ^^^^^^^^^^^^ + | +help: consider performing the side effect separately + | +LL - drop(vec![f(); 0]); +LL + drop({ f(); vec![] as std::vec::Vec }); + | error: function or method calls as the initial value in zero-sized array initializers may cause side effects - --> tests/ui/zero_repeat_side_effects.rs:45:5 + --> tests/ui/zero_repeat_side_effects.rs:43:5 | LL | vec![f(); 0]; - | ^^^^^^^^^^^^ help: consider using: `{ f(); vec![] as std::vec::Vec }` + | ^^^^^^^^^^^^ + | +help: consider performing the side effect separately + | +LL - vec![f(); 0]; +LL + { f(); vec![] as std::vec::Vec }; + | error: function or method calls as the initial value in zero-sized array initializers may cause side effects - --> tests/ui/zero_repeat_side_effects.rs:47:5 + --> tests/ui/zero_repeat_side_effects.rs:45:5 | LL | [f(); 0]; - | ^^^^^^^^ help: consider using: `{ f(); [] as [i32; 0] }` + | ^^^^^^^^ + | +help: consider performing the side effect separately + | +LL - [f(); 0]; +LL + { f(); [] as [i32; 0] }; + | error: aborting due to 9 previous errors From adff9baeb3d16c5d9de8146a6d67f3b276429762 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 4 Oct 2025 00:10:12 +0200 Subject: [PATCH 87/93] fix(zero_repeat_side_effects): better identify exprs with side effects --- clippy_lints/src/zero_repeat_side_effects.rs | 15 ++----- tests/ui/zero_repeat_side_effects.fixed | 24 +++++++++++ tests/ui/zero_repeat_side_effects.rs | 24 +++++++++++ tests/ui/zero_repeat_side_effects.stderr | 44 +++++++++++++++----- 4 files changed, 85 insertions(+), 22 deletions(-) diff --git a/clippy_lints/src/zero_repeat_side_effects.rs b/clippy_lints/src/zero_repeat_side_effects.rs index e51c4a6898164..cd6c11b512742 100644 --- a/clippy_lints/src/zero_repeat_side_effects.rs +++ b/clippy_lints/src/zero_repeat_side_effects.rs @@ -1,7 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::VecArgs; use clippy_utils::source::snippet; -use clippy_utils::visitors::for_each_expr_without_closures; use rustc_ast::LitKind; use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; @@ -11,7 +10,7 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does - /// Checks for array or vec initializations which call a function or method, + /// Checks for array or vec initializations which contain an expression with side effects, /// but which have a repeat count of zero. /// /// ### Why is this bad? @@ -71,15 +70,7 @@ impl LateLintPass<'_> for ZeroRepeatSideEffects { fn inner_check(cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>, inner_expr: &'_ rustc_hir::Expr<'_>, is_vec: bool) { // check if expr is a call or has a call inside it - if for_each_expr_without_closures(inner_expr, |x| { - if let ExprKind::Call(_, _) | ExprKind::MethodCall(_, _, _, _) = x.kind { - std::ops::ControlFlow::Break(()) - } else { - std::ops::ControlFlow::Continue(()) - } - }) - .is_some() - { + if inner_expr.can_have_side_effects() { let parent_hir_node = cx.tcx.parent_hir_node(expr.hir_id); let return_type = cx.typeck_results().expr_ty(expr); @@ -107,7 +98,7 @@ fn inner_check(cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>, inner_expr: cx, ZERO_REPEAT_SIDE_EFFECTS, span.source_callsite(), - "function or method calls as the initial value in zero-sized array initializers may cause side effects", + "expression with side effects as the initial value in a zero-sized array initializer", |diag| { diag.span_suggestion_verbose( span.source_callsite(), diff --git a/tests/ui/zero_repeat_side_effects.fixed b/tests/ui/zero_repeat_side_effects.fixed index 5184249e942d9..e6c451ce7399e 100644 --- a/tests/ui/zero_repeat_side_effects.fixed +++ b/tests/ui/zero_repeat_side_effects.fixed @@ -77,3 +77,27 @@ fn issue_13110() { const LENGTH: usize = LEN!(); let _data = [f(); LENGTH]; } + +// TODO: consider moving the defintion+impl inside `issue_14681` +// once https://github.com/rust-lang/rust/issues/146786 is fixed +#[derive(Clone, Copy)] +struct S; + +impl S { + fn new() -> Self { + println!("This is a side effect"); + S + } +} + +// should not trigger on non-function calls +fn issue_14681() { + fn foo(_s: &[Option]) {} + + foo(&[Some(0i64); 0]); + foo(&[Some(Some(0i64)); 0]); + foo(&{ Some(f()); [] as [std::option::Option; 0] }); + //~^ zero_repeat_side_effects + foo(&{ Some(Some(S::new())); [] as [std::option::Option>; 0] }); + //~^ zero_repeat_side_effects +} diff --git a/tests/ui/zero_repeat_side_effects.rs b/tests/ui/zero_repeat_side_effects.rs index 9c6e758f2e28c..f8a497976aa43 100644 --- a/tests/ui/zero_repeat_side_effects.rs +++ b/tests/ui/zero_repeat_side_effects.rs @@ -77,3 +77,27 @@ fn issue_13110() { const LENGTH: usize = LEN!(); let _data = [f(); LENGTH]; } + +// TODO: consider moving the defintion+impl inside `issue_14681` +// once https://github.com/rust-lang/rust/issues/146786 is fixed +#[derive(Clone, Copy)] +struct S; + +impl S { + fn new() -> Self { + println!("This is a side effect"); + S + } +} + +// should not trigger on non-function calls +fn issue_14681() { + fn foo(_s: &[Option]) {} + + foo(&[Some(0i64); 0]); + foo(&[Some(Some(0i64)); 0]); + foo(&[Some(f()); 0]); + //~^ zero_repeat_side_effects + foo(&[Some(Some(S::new())); 0]); + //~^ zero_repeat_side_effects +} diff --git a/tests/ui/zero_repeat_side_effects.stderr b/tests/ui/zero_repeat_side_effects.stderr index dc99110932d89..771b71c686ae4 100644 --- a/tests/ui/zero_repeat_side_effects.stderr +++ b/tests/ui/zero_repeat_side_effects.stderr @@ -1,4 +1,4 @@ -error: function or method calls as the initial value in zero-sized array initializers may cause side effects +error: expression with side effects as the initial value in a zero-sized array initializer --> tests/ui/zero_repeat_side_effects.rs:16:5 | LL | let a = [f(); 0]; @@ -12,7 +12,7 @@ LL - let a = [f(); 0]; LL + f(); let a: [i32; 0] = []; | -error: function or method calls as the initial value in zero-sized array initializers may cause side effects +error: expression with side effects as the initial value in a zero-sized array initializer --> tests/ui/zero_repeat_side_effects.rs:19:5 | LL | b = [f(); 0]; @@ -24,7 +24,7 @@ LL - b = [f(); 0]; LL + f(); b = [] as [i32; 0]; | -error: function or method calls as the initial value in zero-sized array initializers may cause side effects +error: expression with side effects as the initial value in a zero-sized array initializer --> tests/ui/zero_repeat_side_effects.rs:24:5 | LL | let c = vec![f(); 0]; @@ -36,7 +36,7 @@ LL - let c = vec![f(); 0]; LL + f(); let c: std::vec::Vec = vec![]; | -error: function or method calls as the initial value in zero-sized array initializers may cause side effects +error: expression with side effects as the initial value in a zero-sized array initializer --> tests/ui/zero_repeat_side_effects.rs:27:5 | LL | d = vec![f(); 0]; @@ -48,7 +48,7 @@ LL - d = vec![f(); 0]; LL + f(); d = vec![] as std::vec::Vec; | -error: function or method calls as the initial value in zero-sized array initializers may cause side effects +error: expression with side effects as the initial value in a zero-sized array initializer --> tests/ui/zero_repeat_side_effects.rs:31:5 | LL | let e = [println!("side effect"); 0]; @@ -60,7 +60,7 @@ LL - let e = [println!("side effect"); 0]; LL + println!("side effect"); let e: [(); 0] = []; | -error: function or method calls as the initial value in zero-sized array initializers may cause side effects +error: expression with side effects as the initial value in a zero-sized array initializer --> tests/ui/zero_repeat_side_effects.rs:35:5 | LL | let g = [{ f() }; 0]; @@ -72,7 +72,7 @@ LL - let g = [{ f() }; 0]; LL + { f() }; let g: [i32; 0] = []; | -error: function or method calls as the initial value in zero-sized array initializers may cause side effects +error: expression with side effects as the initial value in a zero-sized array initializer --> tests/ui/zero_repeat_side_effects.rs:39:10 | LL | drop(vec![f(); 0]); @@ -84,7 +84,7 @@ LL - drop(vec![f(); 0]); LL + drop({ f(); vec![] as std::vec::Vec }); | -error: function or method calls as the initial value in zero-sized array initializers may cause side effects +error: expression with side effects as the initial value in a zero-sized array initializer --> tests/ui/zero_repeat_side_effects.rs:43:5 | LL | vec![f(); 0]; @@ -96,7 +96,7 @@ LL - vec![f(); 0]; LL + { f(); vec![] as std::vec::Vec }; | -error: function or method calls as the initial value in zero-sized array initializers may cause side effects +error: expression with side effects as the initial value in a zero-sized array initializer --> tests/ui/zero_repeat_side_effects.rs:45:5 | LL | [f(); 0]; @@ -108,5 +108,29 @@ LL - [f(); 0]; LL + { f(); [] as [i32; 0] }; | -error: aborting due to 9 previous errors +error: expression with side effects as the initial value in a zero-sized array initializer + --> tests/ui/zero_repeat_side_effects.rs:99:10 + | +LL | foo(&[Some(f()); 0]); + | ^^^^^^^^^^^^^^ + | +help: consider performing the side effect separately + | +LL - foo(&[Some(f()); 0]); +LL + foo(&{ Some(f()); [] as [std::option::Option; 0] }); + | + +error: expression with side effects as the initial value in a zero-sized array initializer + --> tests/ui/zero_repeat_side_effects.rs:101:10 + | +LL | foo(&[Some(Some(S::new())); 0]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider performing the side effect separately + | +LL - foo(&[Some(Some(S::new())); 0]); +LL + foo(&{ Some(Some(S::new())); [] as [std::option::Option>; 0] }); + | + +error: aborting due to 11 previous errors From 3d351c839dcb045adcc91372f78f0f0cbaf81033 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sat, 23 Aug 2025 09:45:53 -0400 Subject: [PATCH 88/93] Const eval changes: * Remove `CoreConstant`. * Treat most constants from core as though they were inlined. * Don't evaluate `is_empty` for named constants. --- clippy_lints/src/enum_clike.rs | 8 +- clippy_lints/src/floating_point_arithmetic.rs | 2 +- clippy_lints/src/manual_float_methods.rs | 4 +- clippy_lints/src/matches/overlapping_arms.rs | 6 +- .../src/methods/unnecessary_min_or_max.rs | 6 +- clippy_lints/src/minmax.rs | 4 +- .../src/operators/const_comparisons.rs | 2 +- clippy_lints/src/operators/float_cmp.rs | 2 +- clippy_lints/src/ranges.rs | 6 +- clippy_utils/src/consts.rs | 485 ++++++++++++------ clippy_utils/src/lib.rs | 10 +- clippy_utils/src/paths.rs | 18 +- clippy_utils/src/sym.rs | 1 + tests/ui/const_comparisons.rs | 14 +- tests/ui/const_comparisons.stderr | 62 +-- tests/ui/const_is_empty.rs | 50 +- tests/ui/const_is_empty.stderr | 108 +--- 17 files changed, 395 insertions(+), 393 deletions(-) diff --git a/clippy_lints/src/enum_clike.rs b/clippy_lints/src/enum_clike.rs index c828fc57f7601..1a56c8f810ee7 100644 --- a/clippy_lints/src/enum_clike.rs +++ b/clippy_lints/src/enum_clike.rs @@ -43,12 +43,8 @@ impl<'tcx> LateLintPass<'tcx> for UnportableVariant { if let Some(anon_const) = &var.disr_expr { let def_id = cx.tcx.hir_body_owner_def_id(anon_const.body); let mut ty = cx.tcx.type_of(def_id.to_def_id()).instantiate_identity(); - let constant = cx - .tcx - .const_eval_poly(def_id.to_def_id()) - .ok() - .map(|val| rustc_middle::mir::Const::from_value(val, ty)); - if let Some(Constant::Int(val)) = constant.and_then(|c| mir_to_const(cx.tcx, c)) { + let constant = cx.tcx.const_eval_poly(def_id.to_def_id()).ok(); + if let Some(Constant::Int(val)) = constant.and_then(|c| mir_to_const(cx.tcx, c, ty)) { if let ty::Adt(adt, _) = ty.kind() && adt.is_enum() { diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index 5052bbb3ca033..3ecc232caff43 100644 --- a/clippy_lints/src/floating_point_arithmetic.rs +++ b/clippy_lints/src/floating_point_arithmetic.rs @@ -206,7 +206,7 @@ fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) { // ranges [-16777215, 16777216) for type f32 as whole number floats outside // this range are lossy and ambiguous. #[expect(clippy::cast_possible_truncation)] -fn get_integer_from_float_constant(value: &Constant<'_>) -> Option { +fn get_integer_from_float_constant(value: &Constant) -> Option { match value { F32(num) if num.fract() == 0.0 => { if (-16_777_215.0..16_777_216.0).contains(num) { diff --git a/clippy_lints/src/manual_float_methods.rs b/clippy_lints/src/manual_float_methods.rs index bd2785fea2709..7d6c30aa74ddd 100644 --- a/clippy_lints/src/manual_float_methods.rs +++ b/clippy_lints/src/manual_float_methods.rs @@ -202,7 +202,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods { } } -fn is_infinity(constant: &Constant<'_>) -> bool { +fn is_infinity(constant: &Constant) -> bool { match constant { // FIXME(f16_f128): add f16 and f128 when constants are available Constant::F32(float) => *float == f32::INFINITY, @@ -211,7 +211,7 @@ fn is_infinity(constant: &Constant<'_>) -> bool { } } -fn is_neg_infinity(constant: &Constant<'_>) -> bool { +fn is_neg_infinity(constant: &Constant) -> bool { match constant { // FIXME(f16_f128): add f16 and f128 when constants are available Constant::F32(float) => *float == f32::NEG_INFINITY, diff --git a/clippy_lints/src/matches/overlapping_arms.rs b/clippy_lints/src/matches/overlapping_arms.rs index d3136c89178e6..d76218e6305b0 100644 --- a/clippy_lints/src/matches/overlapping_arms.rs +++ b/clippy_lints/src/matches/overlapping_arms.rs @@ -1,4 +1,4 @@ -use clippy_utils::consts::{ConstEvalCtxt, FullInt, mir_to_const}; +use clippy_utils::consts::{ConstEvalCtxt, Constant, FullInt}; use clippy_utils::diagnostics::span_lint_and_note; use core::cmp::Ordering; use rustc_hir::{Arm, Expr, PatKind, RangeEnd}; @@ -35,12 +35,12 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) let lhs_const = if let Some(lhs) = lhs { ConstEvalCtxt::new(cx).eval_pat_expr(lhs)? } else { - mir_to_const(cx.tcx, ty.numeric_min_val(cx.tcx)?)? + Constant::new_numeric_min(cx.tcx, ty)? }; let rhs_const = if let Some(rhs) = rhs { ConstEvalCtxt::new(cx).eval_pat_expr(rhs)? } else { - mir_to_const(cx.tcx, ty.numeric_max_val(cx.tcx)?)? + Constant::new_numeric_max(cx.tcx, ty)? }; let lhs_val = lhs_const.int_value(cx.tcx, ty)?; let rhs_val = rhs_const.int_value(cx.tcx, ty)?; diff --git a/clippy_lints/src/methods/unnecessary_min_or_max.rs b/clippy_lints/src/methods/unnecessary_min_or_max.rs index 130e4970a7163..bf91a469e7f01 100644 --- a/clippy_lints/src/methods/unnecessary_min_or_max.rs +++ b/clippy_lints/src/methods/unnecessary_min_or_max.rs @@ -1,7 +1,7 @@ use std::cmp::Ordering; use super::UNNECESSARY_MIN_OR_MAX; -use clippy_utils::consts::{ConstEvalCtxt, Constant, ConstantSource, FullInt}; +use clippy_utils::consts::{ConstEvalCtxt, Constant, FullInt}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; @@ -26,8 +26,8 @@ pub(super) fn check<'tcx>( && matches!(fn_name, sym::cmp_ord_min | sym::cmp_ord_max) { let ctxt = expr.span.ctxt(); - if let Some((left, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(recv, ctxt) - && let Some((right, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(arg, ctxt) + if let Some(left) = ecx.eval_local(recv, ctxt) + && let Some(right) = ecx.eval_local(arg, ctxt) { let Some(ord) = Constant::partial_cmp(cx.tcx, typeck_results.expr_ty(recv), &left, &right) else { return; diff --git a/clippy_lints/src/minmax.rs b/clippy_lints/src/minmax.rs index 8f76e6c9048e1..35b6af0c20f7f 100644 --- a/clippy_lints/src/minmax.rs +++ b/clippy_lints/src/minmax.rs @@ -61,7 +61,7 @@ enum MinMax { Max, } -fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinMax, Constant<'tcx>, &'a Expr<'a>)> { +fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinMax, Constant, &'a Expr<'a>)> { match expr.kind { ExprKind::Call(path, args) => { if let ExprKind::Path(ref qpath) = path.kind { @@ -98,7 +98,7 @@ fn fetch_const<'a, 'tcx>( receiver: Option<&'a Expr<'a>>, args: &'a [Expr<'a>], m: MinMax, -) -> Option<(MinMax, Constant<'tcx>, &'a Expr<'a>)> { +) -> Option<(MinMax, Constant, &'a Expr<'a>)> { let mut args = receiver.into_iter().chain(args); let first_arg = args.next()?; let second_arg = args.next()?; diff --git a/clippy_lints/src/operators/const_comparisons.rs b/clippy_lints/src/operators/const_comparisons.rs index 10455d3b93a00..56001a185771a 100644 --- a/clippy_lints/src/operators/const_comparisons.rs +++ b/clippy_lints/src/operators/const_comparisons.rs @@ -22,7 +22,7 @@ fn comparison_to_const<'tcx>( cx: &LateContext<'tcx>, typeck: &'tcx TypeckResults<'tcx>, expr: &'tcx Expr<'tcx>, -) -> Option<(CmpOp, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Constant<'tcx>, Ty<'tcx>)> { +) -> Option<(CmpOp, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Constant, Ty<'tcx>)> { if let ExprKind::Binary(operator, left, right) = expr.kind && let Ok(cmp_op) = CmpOp::try_from(operator.node) { diff --git a/clippy_lints/src/operators/float_cmp.rs b/clippy_lints/src/operators/float_cmp.rs index 03b2cf055d919..eb2353cfd90bf 100644 --- a/clippy_lints/src/operators/float_cmp.rs +++ b/clippy_lints/src/operators/float_cmp.rs @@ -85,7 +85,7 @@ fn get_lint_and_message(is_local: bool, is_comparing_arrays: bool) -> (&'static } } -fn is_allowed(val: &Constant<'_>) -> bool { +fn is_allowed(val: &Constant) -> bool { match val { // FIXME(f16_f128): add when equality check is available on all platforms &Constant::F32(f) => f == 0.0 || f.is_infinite(), diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index 03d00ba849f3c..8a216afb82c9a 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -299,8 +299,8 @@ fn check_possible_range_contains( } } -struct RangeBounds<'a, 'tcx> { - val: Constant<'tcx>, +struct RangeBounds<'a> { + val: Constant, expr: &'a Expr<'a>, id: HirId, name_span: Span, @@ -312,7 +312,7 @@ struct RangeBounds<'a, 'tcx> { // Takes a binary expression such as x <= 2 as input // Breaks apart into various pieces, such as the value of the number, // hir id of the variable, and direction/inclusiveness of the operator -fn check_range_bounds<'a, 'tcx>(cx: &'a LateContext<'tcx>, ex: &'a Expr<'_>) -> Option> { +fn check_range_bounds<'a, 'tcx>(cx: &'a LateContext<'tcx>, ex: &'a Expr<'_>) -> Option> { if let ExprKind::Binary(ref op, l, r) = ex.kind { let (inclusive, ordering) = match op.node { BinOpKind::Gt => (false, Ordering::Greater), diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 29f4988b4211b..f5f3786164608 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -5,25 +5,21 @@ #![allow(clippy::float_cmp)] use crate::source::{SpanRangeExt, walk_span_to_context}; -use crate::{clip, is_direct_expn_of, sext, unsext}; +use crate::{clip, is_direct_expn_of, paths, sext, sym, unsext}; use rustc_abi::Size; use rustc_apfloat::Float; use rustc_apfloat::ieee::{Half, Quad}; use rustc_ast::ast::{LitFloatType, LitKind}; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{ - BinOpKind, Block, ConstBlock, Expr, ExprKind, HirId, Item, ItemKind, Node, PatExpr, PatExprKind, QPath, UnOp, -}; +use rustc_hir::{BinOpKind, Block, ConstBlock, Expr, ExprKind, HirId, PatExpr, PatExprKind, QPath, TyKind, UnOp}; use rustc_lexer::{FrontmatterAllowed, tokenize}; use rustc_lint::LateContext; use rustc_middle::mir::ConstValue; use rustc_middle::mir::interpret::{Scalar, alloc_range}; use rustc_middle::ty::{self, FloatTy, IntTy, ScalarInt, Ty, TyCtxt, TypeckResults, UintTy}; use rustc_middle::{bug, mir, span_bug}; -use rustc_span::def_id::DefId; -use rustc_span::symbol::Ident; -use rustc_span::{SyntaxContext, sym}; +use rustc_span::{Symbol, SyntaxContext}; use std::cell::Cell; use std::cmp::Ordering; use std::hash::{Hash, Hasher}; @@ -31,8 +27,8 @@ use std::iter; /// A `LitKind`-like enum to fold constant `Expr`s into. #[derive(Debug, Clone)] -pub enum Constant<'tcx> { - Adt(mir::Const<'tcx>), +pub enum Constant { + Adt(ConstValue), /// A `String` (e.g., "abc"). Str(String), /// A binary string (e.g., `b"abc"`). @@ -54,15 +50,15 @@ pub enum Constant<'tcx> { /// `true` or `false`. Bool(bool), /// An array of constants. - Vec(Vec>), + Vec(Vec), /// Also an array, but with only one constant, repeated N times. - Repeat(Box>, u64), + Repeat(Box, u64), /// A tuple of constants. - Tuple(Vec>), + Tuple(Vec), /// A raw pointer. RawPtr(u128), /// A reference - Ref(Box>), + Ref(Box), /// A literal with syntax error. Err, } @@ -124,7 +120,7 @@ impl IntTypeBounds for IntTy { } } -impl PartialEq for Constant<'_> { +impl PartialEq for Constant { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Str(ls), Self::Str(rs)) => ls == rs, @@ -135,13 +131,13 @@ impl PartialEq for Constant<'_> { // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have // `Fw32 == Fw64`, so don’t compare them. // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs. - l.to_bits() == r.to_bits() + l.to_bits() == r.to_bits() && !l.is_nan() }, (&Self::F32(l), &Self::F32(r)) => { // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have // `Fw32 == Fw64`, so don’t compare them. // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs. - f64::from(l).to_bits() == f64::from(r).to_bits() + l.to_bits() == r.to_bits() && !l.is_nan() }, (&Self::Bool(l), &Self::Bool(r)) => l == r, (&Self::Vec(ref l), &Self::Vec(ref r)) | (&Self::Tuple(ref l), &Self::Tuple(ref r)) => l == r, @@ -153,7 +149,7 @@ impl PartialEq for Constant<'_> { } } -impl Hash for Constant<'_> { +impl Hash for Constant { fn hash(&self, state: &mut H) where H: Hasher, @@ -209,7 +205,7 @@ impl Hash for Constant<'_> { } } -impl Constant<'_> { +impl Constant { pub fn partial_cmp(tcx: TyCtxt<'_>, cmp_type: Ty<'_>, left: &Self, right: &Self) -> Option { match (left, right) { (Self::Str(ls), Self::Str(rs)) => Some(ls.cmp(rs)), @@ -297,10 +293,129 @@ impl Constant<'_> { let f: Quad = s.parse().unwrap(); Self::F128(f.to_bits()) } + + pub fn new_numeric_min<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option { + match *ty.kind() { + ty::Uint(_) => Some(Self::Int(0)), + ty::Int(ty) => { + let val = match ty.normalize(tcx.sess.target.pointer_width) { + IntTy::I8 => i128::from(i8::MIN), + IntTy::I16 => i128::from(i16::MIN), + IntTy::I32 => i128::from(i32::MIN), + IntTy::I64 => i128::from(i64::MIN), + IntTy::I128 => i128::MIN, + IntTy::Isize => return None, + }; + Some(Self::Int(val.cast_unsigned())) + }, + ty::Char => Some(Self::Char(char::MIN)), + ty::Float(FloatTy::F32) => Some(Self::F32(f32::NEG_INFINITY)), + ty::Float(FloatTy::F64) => Some(Self::F64(f64::NEG_INFINITY)), + _ => None, + } + } + + pub fn new_numeric_max<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option { + match *ty.kind() { + ty::Uint(ty) => Some(Self::Int(match ty.normalize(tcx.sess.target.pointer_width) { + UintTy::U8 => u128::from(u8::MAX), + UintTy::U16 => u128::from(u16::MAX), + UintTy::U32 => u128::from(u32::MAX), + UintTy::U64 => u128::from(u64::MAX), + UintTy::U128 => u128::MAX, + UintTy::Usize => return None, + })), + ty::Int(ty) => { + let val = match ty.normalize(tcx.sess.target.pointer_width) { + IntTy::I8 => i128::from(i8::MAX), + IntTy::I16 => i128::from(i16::MAX), + IntTy::I32 => i128::from(i32::MAX), + IntTy::I64 => i128::from(i64::MAX), + IntTy::I128 => i128::MAX, + IntTy::Isize => return None, + }; + Some(Self::Int(val.cast_unsigned())) + }, + ty::Char => Some(Self::Char(char::MAX)), + ty::Float(FloatTy::F32) => Some(Self::F32(f32::INFINITY)), + ty::Float(FloatTy::F64) => Some(Self::F64(f64::INFINITY)), + _ => None, + } + } + + pub fn is_numeric_min<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { + match (self, ty.kind()) { + (&Self::Int(x), &ty::Uint(_)) => x == 0, + (&Self::Int(x), &ty::Int(ty)) => { + let limit = match ty.normalize(tcx.sess.target.pointer_width) { + IntTy::I8 => i128::from(i8::MIN), + IntTy::I16 => i128::from(i16::MIN), + IntTy::I32 => i128::from(i32::MIN), + IntTy::I64 => i128::from(i64::MIN), + IntTy::I128 => i128::MIN, + IntTy::Isize => return false, + }; + x.cast_signed() == limit + }, + (&Self::Char(x), &ty::Char) => x == char::MIN, + (&Self::F32(x), &ty::Float(FloatTy::F32)) => x == f32::NEG_INFINITY, + (&Self::F64(x), &ty::Float(FloatTy::F64)) => x == f64::NEG_INFINITY, + _ => false, + } + } + + pub fn is_numeric_max<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { + match (self, ty.kind()) { + (&Self::Int(x), &ty::Uint(ty)) => { + let limit = match ty.normalize(tcx.sess.target.pointer_width) { + UintTy::U8 => u128::from(u8::MAX), + UintTy::U16 => u128::from(u16::MAX), + UintTy::U32 => u128::from(u32::MAX), + UintTy::U64 => u128::from(u64::MAX), + UintTy::U128 => u128::MAX, + UintTy::Usize => return false, + }; + x == limit + }, + (&Self::Int(x), &ty::Int(ty)) => { + let limit = match ty.normalize(tcx.sess.target.pointer_width) { + IntTy::I8 => i128::from(i8::MAX), + IntTy::I16 => i128::from(i16::MAX), + IntTy::I32 => i128::from(i32::MAX), + IntTy::I64 => i128::from(i64::MAX), + IntTy::I128 => i128::MAX, + IntTy::Isize => return false, + }; + x.cast_signed() == limit + }, + (&Self::Char(x), &ty::Char) => x == char::MAX, + (&Self::F32(x), &ty::Float(FloatTy::F32)) => x == f32::INFINITY, + (&Self::F64(x), &ty::Float(FloatTy::F64)) => x == f64::INFINITY, + _ => false, + } + } + + pub fn is_pos_infinity(&self) -> bool { + match *self { + // FIXME(f16_f128): add f16 and f128 when constants are available + Constant::F32(x) => x == f32::INFINITY, + Constant::F64(x) => x == f64::INFINITY, + _ => false, + } + } + + pub fn is_neg_infinity(&self) -> bool { + match *self { + // FIXME(f16_f128): add f16 and f128 when constants are available + Constant::F32(x) => x == f32::NEG_INFINITY, + Constant::F64(x) => x == f64::NEG_INFINITY, + _ => false, + } + } } /// Parses a `LitKind` to a `Constant`. -pub fn lit_to_mir_constant<'tcx>(lit: &LitKind, ty: Option>) -> Constant<'tcx> { +pub fn lit_to_mir_constant(lit: &LitKind, ty: Option>) -> Constant { match *lit { LitKind::Str(ref is, _) => Constant::Str(is.to_string()), LitKind::Byte(b) => Constant::Int(u128::from(b)), @@ -331,10 +446,9 @@ pub fn lit_to_mir_constant<'tcx>(lit: &LitKind, ty: Option>) -> Constan pub enum ConstantSource { /// The value is determined solely from the expression. Local, - /// The value is dependent on a defined constant. - Constant, - /// The value is dependent on a constant defined in `core` crate. - CoreConstant, + /// The value is dependent on another definition that may change independently from the local + /// expression. + NonLocal, } impl ConstantSource { pub fn is_local(self) -> bool { @@ -416,19 +530,24 @@ impl<'tcx> ConstEvalCtxt<'tcx> { /// Attempts to evaluate the expression and returns both the value and whether it's dependant on /// other items. - pub fn eval_with_source(&self, e: &Expr<'_>, ctxt: SyntaxContext) -> Option<(Constant<'tcx>, ConstantSource)> { + pub fn eval_with_source(&self, e: &Expr<'_>, ctxt: SyntaxContext) -> Option<(Constant, ConstantSource)> { self.source.set(ConstantSource::Local); self.ctxt.set(ctxt); self.expr(e).map(|c| (c, self.source.get())) } /// Attempts to evaluate the expression. - pub fn eval(&self, e: &Expr<'_>) -> Option> { + pub fn eval(&self, e: &Expr<'_>) -> Option { self.expr(e) } /// Attempts to evaluate the expression without accessing other items. - pub fn eval_local(&self, e: &Expr<'_>, ctxt: SyntaxContext) -> Option> { + /// + /// The context argument is the context used to view the evaluated expression. e.g. when + /// evaluating the argument in `f(m!(1))` the context of the call expression should be used. + /// This is need so the const evaluator can see the `m` macro and marke the evaluation as + /// non-local independant of what the macro expands to. + pub fn eval_local(&self, e: &Expr<'_>, ctxt: SyntaxContext) -> Option { match self.eval_with_source(e, ctxt) { Some((x, ConstantSource::Local)) => Some(x), _ => None, @@ -436,6 +555,11 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } /// Attempts to evaluate the expression as an integer without accessing other items. + /// + /// The context argument is the context used to view the evaluated expression. e.g. when + /// evaluating the argument in `f(m!(1))` the context of the call expression should be used. + /// This is need so the const evaluator can see the `m` macro and marke the evaluation as + /// non-local independant of what the macro expands to. pub fn eval_full_int(&self, e: &Expr<'_>, ctxt: SyntaxContext) -> Option { match self.eval_with_source(e, ctxt) { Some((x, ConstantSource::Local)) => x.int_value(self.tcx, self.typeck.expr_ty(e)), @@ -443,7 +567,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } } - pub fn eval_pat_expr(&self, pat_expr: &PatExpr<'_>) -> Option> { + pub fn eval_pat_expr(&self, pat_expr: &PatExpr<'_>) -> Option { match &pat_expr.kind { PatExprKind::Lit { lit, negated } => { let ty = self.typeck.node_type_opt(pat_expr.hir_id); @@ -460,35 +584,18 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } fn check_ctxt(&self, ctxt: SyntaxContext) { - self.source.set(if self.ctxt.get() != ctxt { - ConstantSource::Constant - } else { - self.source.get() - }); + if self.ctxt.get() != ctxt { + self.source.set(ConstantSource::NonLocal); + } } - fn qpath(&self, qpath: &QPath<'_>, hir_id: HirId) -> Option> { - let is_core_crate = if let Some(def_id) = self.typeck.qpath_res(qpath, hir_id).opt_def_id() { - self.tcx.crate_name(def_id.krate) == sym::core - } else { - false - }; - self.fetch_path_and_apply(qpath, hir_id, self.typeck.node_type(hir_id), |self_, result| { - let result = mir_to_const(self_.tcx, result)?; - // If source is already Constant we wouldn't want to override it with CoreConstant - self_.source.set( - if is_core_crate && !matches!(self_.source.get(), ConstantSource::Constant) { - ConstantSource::CoreConstant - } else { - ConstantSource::Constant - }, - ); - Some(result) - }) + fn qpath(&self, qpath: &QPath<'_>, hir_id: HirId) -> Option { + self.fetch_path(qpath, hir_id) + .and_then(|c| mir_to_const(self.tcx, c, self.typeck.node_type(hir_id))) } /// Simple constant folding: Insert an expression, get a constant or none. - fn expr(&self, e: &Expr<'_>) -> Option> { + fn expr(&self, e: &Expr<'_>) -> Option { self.check_ctxt(e.span.ctxt()); match e.kind { ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr(self.tcx.hir_body(body).value), @@ -540,18 +647,20 @@ impl<'tcx> ConstEvalCtxt<'tcx> { }, ExprKind::Index(arr, index, _) => self.index(arr, index), ExprKind::AddrOf(_, _, inner) => self.expr(inner).map(|r| Constant::Ref(Box::new(r))), - ExprKind::Field(local_expr, ref field) => { - self.check_ctxt(field.span.ctxt()); - let result = self.expr(local_expr); - if let Some(Constant::Adt(constant)) = &self.expr(local_expr) - && let ty::Adt(adt_def, _) = constant.ty().kind() + ExprKind::Field(base, ref field) + if let base_ty = self.typeck.expr_ty(base) + && match self.typeck.expr_adjustments(base) { + [] => true, + [.., a] => a.target == base_ty, + } + && let Some(Constant::Adt(constant)) = self.expr(base) + && let ty::Adt(adt_def, _) = *base_ty.kind() && adt_def.is_struct() - && let Some(desired_field) = field_of_struct(*adt_def, self.tcx, *constant, field) - { - mir_to_const(self.tcx, desired_field) - } else { - result - } + && let Some((desired_field, ty)) = + field_of_struct(adt_def, self.tcx, constant, base_ty, field.name) => + { + self.check_ctxt(field.span.ctxt()); + mir_to_const(self.tcx, desired_field, ty) }, _ => None, } @@ -564,19 +673,6 @@ impl<'tcx> ConstEvalCtxt<'tcx> { match e.kind { ExprKind::ConstBlock(ConstBlock { body, .. }) => self.eval_is_empty(self.tcx.hir_body(body).value), ExprKind::DropTemps(e) => self.eval_is_empty(e), - ExprKind::Path(ref qpath) => { - if !self - .typeck - .qpath_res(qpath, e.hir_id) - .opt_def_id() - .is_some_and(DefId::is_local) - { - return None; - } - self.fetch_path_and_apply(qpath, e.hir_id, self.typeck.expr_ty(e), |self_, result| { - mir_is_empty(self_.tcx, result) - }) - }, ExprKind::Lit(lit) => { if is_direct_expn_of(e.span, sym::cfg).is_some() { None @@ -601,7 +697,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } #[expect(clippy::cast_possible_wrap)] - fn constant_not(&self, o: &Constant<'tcx>, ty: Ty<'_>) -> Option> { + fn constant_not(&self, o: &Constant, ty: Ty<'_>) -> Option { use self::Constant::{Bool, Int}; match *o { Bool(b) => Some(Bool(!b)), @@ -617,7 +713,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } } - fn constant_negate(&self, o: &Constant<'tcx>, ty: Ty<'_>) -> Option> { + fn constant_negate(&self, o: &Constant, ty: Ty<'_>) -> Option { use self::Constant::{F32, F64, Int}; match *o { Int(value) => { @@ -643,48 +739,128 @@ impl<'tcx> ConstEvalCtxt<'tcx> { /// Create `Some(Vec![..])` of all constants, unless there is any /// non-constant part. - fn multi(&self, vec: &[Expr<'_>]) -> Option>> { + fn multi(&self, vec: &[Expr<'_>]) -> Option> { vec.iter().map(|elem| self.expr(elem)).collect::>() } /// Lookup a possibly constant expression from an `ExprKind::Path` and apply a function on it. - fn fetch_path_and_apply(&self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>, f: F) -> Option - where - F: FnOnce(&Self, mir::Const<'tcx>) -> Option, - { - let res = self.typeck.qpath_res(qpath, id); - match res { - Res::Def(DefKind::Const | DefKind::AssocConst, def_id) => { - // Check if this constant is based on `cfg!(..)`, - // which is NOT constant for our purposes. - if let Some(node) = self.tcx.hir_get_if_local(def_id) - && let Node::Item(Item { - kind: ItemKind::Const(.., body_id), - .. - }) = node - && let Node::Expr(Expr { - kind: ExprKind::Lit(_), - span, - .. - }) = self.tcx.hir_node(body_id.hir_id) - && is_direct_expn_of(*span, sym::cfg).is_some() - { - return None; - } - - let args = self.typeck.node_args(id); - let result = self - .tcx - .const_eval_resolve(self.typing_env, mir::UnevaluatedConst::new(def_id, args), qpath.span()) - .ok() - .map(|val| mir::Const::from_value(val, ty))?; - f(self, result) + #[expect(clippy::too_many_lines)] + fn fetch_path(&self, qpath: &QPath<'_>, id: HirId) -> Option { + // Resolve the path to a constant and check if that constant is known to + // not change based on the target. + // + // This should be replaced with an attribute at some point. + let did = match *qpath { + QPath::Resolved(None, path) + if path.span.ctxt() == self.ctxt.get() + && path.segments.iter().all(|s| self.ctxt.get() == s.ident.span.ctxt()) + && let Res::Def(DefKind::Const, did) = path.res + && (matches!( + self.tcx.get_diagnostic_name(did), + Some( + sym::f32_legacy_const_digits + | sym::f32_legacy_const_epsilon + | sym::f32_legacy_const_infinity + | sym::f32_legacy_const_mantissa_dig + | sym::f32_legacy_const_max + | sym::f32_legacy_const_max_10_exp + | sym::f32_legacy_const_max_exp + | sym::f32_legacy_const_min + | sym::f32_legacy_const_min_10_exp + | sym::f32_legacy_const_min_exp + | sym::f32_legacy_const_min_positive + | sym::f32_legacy_const_nan + | sym::f32_legacy_const_neg_infinity + | sym::f32_legacy_const_radix + | sym::f64_legacy_const_digits + | sym::f64_legacy_const_epsilon + | sym::f64_legacy_const_infinity + | sym::f64_legacy_const_mantissa_dig + | sym::f64_legacy_const_max + | sym::f64_legacy_const_max_10_exp + | sym::f64_legacy_const_max_exp + | sym::f64_legacy_const_min + | sym::f64_legacy_const_min_10_exp + | sym::f64_legacy_const_min_exp + | sym::f64_legacy_const_min_positive + | sym::f64_legacy_const_nan + | sym::f64_legacy_const_neg_infinity + | sym::f64_legacy_const_radix + | sym::u8_legacy_const_min + | sym::u16_legacy_const_min + | sym::u32_legacy_const_min + | sym::u64_legacy_const_min + | sym::u128_legacy_const_min + | sym::usize_legacy_const_min + | sym::u8_legacy_const_max + | sym::u16_legacy_const_max + | sym::u32_legacy_const_max + | sym::u64_legacy_const_max + | sym::u128_legacy_const_max + | sym::i8_legacy_const_min + | sym::i16_legacy_const_min + | sym::i32_legacy_const_min + | sym::i64_legacy_const_min + | sym::i128_legacy_const_min + | sym::i8_legacy_const_max + | sym::i16_legacy_const_max + | sym::i32_legacy_const_max + | sym::i64_legacy_const_max + | sym::i128_legacy_const_max + ) + ) || self.tcx.opt_parent(did).is_some_and(|parent| { + paths::F16_CONSTS.matches(&self.tcx, parent) + || paths::F32_CONSTS.matches(&self.tcx, parent) + || paths::F64_CONSTS.matches(&self.tcx, parent) + || paths::F128_CONSTS.matches(&self.tcx, parent) + })) => + { + did }, - _ => None, - } + QPath::TypeRelative(ty, const_name) + if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind + && let [.., ty_name] = ty_path.segments + && (matches!( + ty_name.ident.name, + sym::i8 + | sym::i16 + | sym::i32 + | sym::i64 + | sym::i128 + | sym::u8 + | sym::u16 + | sym::u32 + | sym::u64 + | sym::u128 + | sym::f32 + | sym::f64 + | sym::char + ) || (ty_name.ident.name == sym::usize && const_name.ident.name == sym::MIN)) + && const_name.ident.span.ctxt() == self.ctxt.get() + && ty.span.ctxt() == self.ctxt.get() + && ty_name.ident.span.ctxt() == self.ctxt.get() + && matches!(ty_path.res, Res::PrimTy(_)) + && let Some((DefKind::AssocConst, did)) = self.typeck.type_dependent_def(id) => + { + did + }, + _ if let Res::Def(DefKind::Const | DefKind::AssocConst, did) = self.typeck.qpath_res(qpath, id) => { + self.source.set(ConstantSource::NonLocal); + did + }, + _ => return None, + }; + + self.tcx + .const_eval_resolve( + self.typing_env, + mir::UnevaluatedConst::new(did, self.typeck.node_args(id)), + qpath.span(), + ) + .ok() } - fn index(&self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option> { + fn index(&self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option { let lhs = self.expr(lhs); let index = self.expr(index); @@ -714,7 +890,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } /// A block can only yield a constant if it has exactly one constant expression. - fn block(&self, block: &Block<'_>) -> Option> { + fn block(&self, block: &Block<'_>) -> Option { if block.stmts.is_empty() && let Some(expr) = block.expr { @@ -733,11 +909,11 @@ impl<'tcx> ConstEvalCtxt<'tcx> { .filter(|t| !matches!(t, Whitespace | LineComment { .. } | BlockComment { .. } | Semi)) .eq([OpenBrace]) { - self.source.set(ConstantSource::Constant); + self.source.set(ConstantSource::NonLocal); } } else { // Unable to access the source. Assume a non-local dependency. - self.source.set(ConstantSource::Constant); + self.source.set(ConstantSource::NonLocal); } } @@ -747,7 +923,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } } - fn ifthenelse(&self, cond: &Expr<'_>, then: &Expr<'_>, otherwise: Option<&Expr<'_>>) -> Option> { + fn ifthenelse(&self, cond: &Expr<'_>, then: &Expr<'_>, otherwise: Option<&Expr<'_>>) -> Option { if let Some(Constant::Bool(b)) = self.expr(cond) { if b { self.expr(then) @@ -759,7 +935,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } } - fn binop(&self, op: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> Option> { + fn binop(&self, op: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> Option { let l = self.expr(left)?; let r = self.expr(right); match (l, r) { @@ -795,6 +971,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> { BinOpKind::BitXor => Some(zext(l ^ r)), BinOpKind::BitOr => Some(zext(l | r)), BinOpKind::BitAnd => Some(zext(l & r)), + // FIXME: f32/f64 currently consider `0.0` and `-0.0` as different. BinOpKind::Eq => Some(Constant::Bool(l == r)), BinOpKind::Ne => Some(Constant::Bool(l != r)), BinOpKind::Lt => Some(Constant::Bool(l < r)), @@ -873,14 +1050,10 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } } -pub fn mir_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::Const<'tcx>) -> Option> { - let mir::Const::Val(val, _) = result else { - // We only work on evaluated consts. - return None; - }; - match (val, result.ty().kind()) { - (ConstValue::Scalar(Scalar::Int(int)), _) => match result.ty().kind() { - ty::Adt(adt_def, _) if adt_def.is_struct() => Some(Constant::Adt(result)), +pub fn mir_to_const<'tcx>(tcx: TyCtxt<'tcx>, val: ConstValue, ty: Ty<'tcx>) -> Option { + match (val, ty.kind()) { + (_, &ty::Adt(adt_def, _)) if adt_def.is_struct() => Some(Constant::Adt(val)), + (ConstValue::Scalar(Scalar::Int(int)), _) => match ty.kind() { ty::Bool => Some(Constant::Bool(int == ScalarInt::TRUE)), ty::Uint(_) | ty::Int(_) => Some(Constant::Int(int.to_bits(int.size()))), ty::Float(FloatTy::F16) => Some(Constant::F16(int.into())), @@ -894,7 +1067,6 @@ pub fn mir_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::Const<'tcx>) -> Option let data = val.try_get_slice_bytes_for_diagnostics(tcx)?; String::from_utf8(data.to_owned()).ok().map(Constant::Str) }, - (_, ty::Adt(adt_def, _)) if adt_def.is_struct() => Some(Constant::Adt(result)), (ConstValue::Indirect { alloc_id, offset }, ty::Array(sub_type, len)) => { let alloc = tcx.global_alloc(alloc_id).unwrap_memory().inner(); let len = len.try_to_target_usize(tcx)?; @@ -919,62 +1091,30 @@ pub fn mir_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::Const<'tcx>) -> Option } } -fn mir_is_empty<'tcx>(tcx: TyCtxt<'tcx>, result: mir::Const<'tcx>) -> Option { - let mir::Const::Val(val, _) = result else { - // We only work on evaluated consts. - return None; - }; - match (val, result.ty().kind()) { - (_, ty::Ref(_, inner_ty, _)) => match inner_ty.kind() { - ty::Str | ty::Slice(_) => { - if let ConstValue::Indirect { alloc_id, offset } = val { - // Get the length from the slice, using the same formula as - // [`ConstValue::try_get_slice_bytes_for_diagnostics`]. - let a = tcx.global_alloc(alloc_id).unwrap_memory().inner(); - let ptr_size = tcx.data_layout.pointer_size(); - if a.size() < offset + 2 * ptr_size { - // (partially) dangling reference - return None; - } - let len = a - .read_scalar(&tcx, alloc_range(offset + ptr_size, ptr_size), false) - .ok()? - .to_target_usize(&tcx) - .discard_err()?; - Some(len == 0) - } else { - None - } - }, - ty::Array(_, len) => Some(len.try_to_target_usize(tcx)? == 0), - _ => None, - }, - (ConstValue::Indirect { .. }, ty::Array(_, len)) => Some(len.try_to_target_usize(tcx)? == 0), - (ConstValue::ZeroSized, _) => Some(true), - _ => None, - } -} - fn field_of_struct<'tcx>( adt_def: ty::AdtDef<'tcx>, tcx: TyCtxt<'tcx>, - result: mir::Const<'tcx>, - field: &Ident, -) -> Option> { - if let mir::Const::Val(result, ty) = result - && let Some(dc) = tcx.try_destructure_mir_constant_for_user_output(result, ty) + value: ConstValue, + ty: Ty<'tcx>, + field: Symbol, +) -> Option<(ConstValue, Ty<'tcx>)> { + if let Some(dc) = tcx.try_destructure_mir_constant_for_user_output(value, ty) && let Some(dc_variant) = dc.variant && let Some(variant) = adt_def.variants().get(dc_variant) - && let Some(field_idx) = variant.fields.iter().position(|el| el.name == field.name) - && let Some(&(val, ty)) = dc.fields.get(field_idx) + && let Some(field_idx) = variant.fields.iter().position(|el| el.name == field) { - Some(mir::Const::Val(val, ty)) + dc.fields.get(field_idx).copied() } else { None } } /// If `expr` evaluates to an integer constant, return its value. +/// +/// The context argument is the context used to view the evaluated expression. e.g. when evaluating +/// the argument in `f(m!(1))` the context of the call expression should be used. This is need so +/// the const evaluator can see the `m` macro and marke the evaluation as non-local independant of +/// what the macro expands to. pub fn integer_const(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> Option { if let Some(Constant::Int(value)) = ConstEvalCtxt::new(cx).eval_local(expr, ctxt) { Some(value) @@ -984,6 +1124,11 @@ pub fn integer_const(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) } /// Check if `expr` evaluates to an integer constant of 0. +/// +/// The context argument is the context used to view the evaluated expression. e.g. when evaluating +/// the argument in `f(m!(1))` the context of the call expression should be used. This is need so +/// the const evaluator can see the `m` macro and marke the evaluation as non-local independant of +/// what the macro expands to. #[inline] pub fn is_zero_integer_const(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool { integer_const(cx, expr, ctxt) == Some(0) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 708491df77079..a2c8ec34c6be2 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -126,7 +126,7 @@ 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::consts::{ConstEvalCtxt, Constant}; use crate::higher::Range; use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type}; use crate::visitors::for_each_expr_without_closures; @@ -1422,11 +1422,9 @@ pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Opti let start_is_none_or_min = start.is_none_or(|start| { if let rustc_ty::Adt(_, subst) = ty.kind() && let bnd_ty = subst.type_at(0) - && let Some(min_const) = bnd_ty.numeric_min_val(cx.tcx) - && let Some(min_const) = mir_to_const(cx.tcx, min_const) && let Some(start_const) = ConstEvalCtxt::new(cx).eval(start) { - start_const == min_const + start_const.is_numeric_min(cx.tcx, bnd_ty) } else { false } @@ -1435,11 +1433,9 @@ pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Opti RangeLimits::Closed => { if let rustc_ty::Adt(_, subst) = ty.kind() && let bnd_ty = subst.type_at(0) - && let Some(max_const) = bnd_ty.numeric_max_val(cx.tcx) - && let Some(max_const) = mir_to_const(cx.tcx, max_const) && let Some(end_const) = ConstEvalCtxt::new(cx).eval(end) { - end_const == max_const + end_const.is_numeric_max(cx.tcx, bnd_ty) } else { false } diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index ea8cfc59356a7..5ab8e16d88ed3 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -13,6 +13,7 @@ use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId}; use rustc_hir::{ItemKind, Node, UseKind}; use rustc_lint::LateContext; use rustc_middle::ty::fast_reject::SimplifiedType; +use rustc_middle::ty::layout::HasTyCtxt; use rustc_middle::ty::{FloatTy, IntTy, Ty, TyCtxt, UintTy}; use rustc_span::{Ident, STDLIB_STABLE_CRATES, Symbol}; use std::sync::OnceLock; @@ -74,8 +75,8 @@ impl PathLookup { } /// Returns the list of [`DefId`]s that the path resolves to - pub fn get(&self, cx: &LateContext<'_>) -> &[DefId] { - self.once.get_or_init(|| lookup_path(cx.tcx, self.ns, self.path)) + pub fn get<'tcx>(&self, tcx: &impl HasTyCtxt<'tcx>) -> &[DefId] { + self.once.get_or_init(|| lookup_path(tcx.tcx(), self.ns, self.path)) } /// Returns the single [`DefId`] that the path resolves to, this can only be used for paths into @@ -90,8 +91,8 @@ impl PathLookup { } /// Checks if the path resolves to the given `def_id` - pub fn matches(&self, cx: &LateContext<'_>, def_id: DefId) -> bool { - self.get(cx).contains(&def_id) + pub fn matches<'tcx>(&self, tcx: &impl HasTyCtxt<'tcx>, def_id: DefId) -> bool { + self.get(&tcx.tcx()).contains(&def_id) } /// Resolves `maybe_path` to a [`DefId`] and checks if the [`PathLookup`] matches it @@ -100,8 +101,8 @@ impl PathLookup { } /// Checks if the path resolves to `ty`'s definition, must be an `Adt` - pub fn matches_ty(&self, cx: &LateContext<'_>, ty: Ty<'_>) -> bool { - ty.ty_adt_def().is_some_and(|adt| self.matches(cx, adt.did())) + pub fn matches_ty<'tcx>(&self, tcx: &impl HasTyCtxt<'tcx>, ty: Ty<'_>) -> bool { + ty.ty_adt_def().is_some_and(|adt| self.matches(&tcx.tcx(), adt.did())) } } @@ -126,6 +127,11 @@ path_macros! { macro_path: PathNS::Macro, } +pub static F16_CONSTS: PathLookup = type_path!(core::f16::consts); +pub static F32_CONSTS: PathLookup = type_path!(core::f32::consts); +pub static F64_CONSTS: PathLookup = type_path!(core::f64::consts); +pub static F128_CONSTS: PathLookup = type_path!(core::f128::consts); + // Paths in external crates pub static FUTURES_IO_ASYNCREADEXT: PathLookup = type_path!(futures_util::AsyncReadExt); pub static FUTURES_IO_ASYNCWRITEEXT: PathLookup = type_path!(futures_util::AsyncWriteExt); diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index 4ba0e52572ddf..2d0d4a5319f3c 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -115,6 +115,7 @@ generate! { collapsible_if, collect, const_ptr, + consts, contains, copied, copy_from, diff --git a/tests/ui/const_comparisons.rs b/tests/ui/const_comparisons.rs index b732d7d142fc5..a2a1f0a7b4c09 100644 --- a/tests/ui/const_comparisons.rs +++ b/tests/ui/const_comparisons.rs @@ -1,9 +1,11 @@ -#![allow(unused)] -#![warn(clippy::impossible_comparisons)] -#![warn(clippy::redundant_comparisons)] -#![allow(clippy::no_effect)] -#![allow(clippy::short_circuit_statement)] -#![allow(clippy::manual_range_contains)] +#![allow( + unused, + clippy::identity_op, + clippy::manual_range_contains, + clippy::no_effect, + clippy::short_circuit_statement +)] +#![warn(clippy::impossible_comparisons, clippy::redundant_comparisons)] const STATUS_BAD_REQUEST: u16 = 400; const STATUS_SERVER_ERROR: u16 = 500; diff --git a/tests/ui/const_comparisons.stderr b/tests/ui/const_comparisons.stderr index 48a2c6e8d4879..1ce62c23ff293 100644 --- a/tests/ui/const_comparisons.stderr +++ b/tests/ui/const_comparisons.stderr @@ -1,5 +1,5 @@ error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:45:5 + --> tests/ui/const_comparisons.rs:47:5 | LL | status_code <= 400 && status_code > 500; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -9,7 +9,7 @@ LL | status_code <= 400 && status_code > 500; = help: to override `-D warnings` add `#[allow(clippy::impossible_comparisons)]` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:48:5 + --> tests/ui/const_comparisons.rs:50:5 | LL | status_code > 500 && status_code < 400; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -17,7 +17,7 @@ LL | status_code > 500 && status_code < 400; = note: since `500` > `400`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:51:5 + --> tests/ui/const_comparisons.rs:53:5 | LL | status_code < 500 && status_code > 500; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL | status_code < 500 && status_code > 500; = note: `status_code` cannot simultaneously be greater than and less than `500` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:55:5 + --> tests/ui/const_comparisons.rs:57:5 | LL | status_code < { 400 } && status_code > { 500 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -33,7 +33,7 @@ LL | status_code < { 400 } && status_code > { 500 }; = note: since `{ 400 }` < `{ 500 }`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:58:5 + --> tests/ui/const_comparisons.rs:60:5 | LL | status_code < STATUS_BAD_REQUEST && status_code > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -41,7 +41,7 @@ LL | status_code < STATUS_BAD_REQUEST && status_code > STATUS_SERVER_ERROR; = note: since `STATUS_BAD_REQUEST` < `STATUS_SERVER_ERROR`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:61:5 + --> tests/ui/const_comparisons.rs:63:5 | LL | status_code <= u16::MIN + 1 && status_code > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -49,7 +49,7 @@ LL | status_code <= u16::MIN + 1 && status_code > STATUS_SERVER_ERROR; = note: since `u16::MIN + 1` < `STATUS_SERVER_ERROR`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:64:5 + --> tests/ui/const_comparisons.rs:66:5 | LL | status_code < STATUS_SERVER_ERROR && status_code > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -57,7 +57,7 @@ LL | status_code < STATUS_SERVER_ERROR && status_code > STATUS_SERVER_ERROR; = note: `status_code` cannot simultaneously be greater than and less than `STATUS_SERVER_ERROR` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:68:5 + --> tests/ui/const_comparisons.rs:70:5 | LL | status < { 400 } && status > { 500 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -65,7 +65,7 @@ LL | status < { 400 } && status > { 500 }; = note: since `{ 400 }` < `{ 500 }`, the expression evaluates to false for any value of `status` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:71:5 + --> tests/ui/const_comparisons.rs:73:5 | LL | status < STATUS_BAD_REQUEST && status > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -73,7 +73,7 @@ LL | status < STATUS_BAD_REQUEST && status > STATUS_SERVER_ERROR; = note: since `STATUS_BAD_REQUEST` < `STATUS_SERVER_ERROR`, the expression evaluates to false for any value of `status` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:74:5 + --> tests/ui/const_comparisons.rs:76:5 | LL | status <= u16::MIN + 1 && status > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -81,7 +81,7 @@ LL | status <= u16::MIN + 1 && status > STATUS_SERVER_ERROR; = note: since `u16::MIN + 1` < `STATUS_SERVER_ERROR`, the expression evaluates to false for any value of `status` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:77:5 + --> tests/ui/const_comparisons.rs:79:5 | LL | status < STATUS_SERVER_ERROR && status > STATUS_SERVER_ERROR; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -89,7 +89,7 @@ LL | status < STATUS_SERVER_ERROR && status > STATUS_SERVER_ERROR; = note: `status` cannot simultaneously be greater than and less than `STATUS_SERVER_ERROR` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:86:5 + --> tests/ui/const_comparisons.rs:88:5 | LL | 500 >= status_code && 600 < status_code; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -97,7 +97,7 @@ LL | 500 >= status_code && 600 < status_code; = note: since `500` < `600`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:90:5 + --> tests/ui/const_comparisons.rs:92:5 | LL | 500 >= status_code && status_code > 600; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -105,7 +105,7 @@ LL | 500 >= status_code && status_code > 600; = note: since `500` < `600`, the expression evaluates to false for any value of `status_code` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:99:5 + --> tests/ui/const_comparisons.rs:101:5 | LL | 500 >= status && 600 < status; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -113,7 +113,7 @@ LL | 500 >= status && 600 < status; = note: since `500` < `600`, the expression evaluates to false for any value of `status` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:103:5 + --> tests/ui/const_comparisons.rs:105:5 | LL | 500 >= status && status > 600; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -121,13 +121,13 @@ LL | 500 >= status && status > 600; = note: since `500` < `600`, the expression evaluates to false for any value of `status` error: right-hand side of `&&` operator has no effect - --> tests/ui/const_comparisons.rs:107:5 + --> tests/ui/const_comparisons.rs:109:5 | LL | status_code < 200 && status_code <= 299; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code < 200` evaluates to true, status_code <= 299` will always evaluate to true as well - --> tests/ui/const_comparisons.rs:107:23 + --> tests/ui/const_comparisons.rs:109:23 | LL | status_code < 200 && status_code <= 299; | ^^^^^^^^^^^^^^^^^^^^^ @@ -135,67 +135,67 @@ LL | status_code < 200 && status_code <= 299; = help: to override `-D warnings` add `#[allow(clippy::redundant_comparisons)]` error: left-hand side of `&&` operator has no effect - --> tests/ui/const_comparisons.rs:110:5 + --> tests/ui/const_comparisons.rs:112:5 | LL | status_code > 200 && status_code >= 299; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code >= 299` evaluates to true, status_code > 200` will always evaluate to true as well - --> tests/ui/const_comparisons.rs:110:5 + --> tests/ui/const_comparisons.rs:112:5 | LL | status_code > 200 && status_code >= 299; | ^^^^^^^^^^^^^^^^^^^^^ error: left-hand side of `&&` operator has no effect - --> tests/ui/const_comparisons.rs:114:5 + --> tests/ui/const_comparisons.rs:116:5 | LL | status_code >= 500 && status_code > 500; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code > 500` evaluates to true, status_code >= 500` will always evaluate to true as well - --> tests/ui/const_comparisons.rs:114:5 + --> tests/ui/const_comparisons.rs:116:5 | LL | status_code >= 500 && status_code > 500; | ^^^^^^^^^^^^^^^^^^^^^^ error: right-hand side of `&&` operator has no effect - --> tests/ui/const_comparisons.rs:118:5 + --> tests/ui/const_comparisons.rs:120:5 | LL | status_code > 500 && status_code >= 500; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code > 500` evaluates to true, status_code >= 500` will always evaluate to true as well - --> tests/ui/const_comparisons.rs:118:23 + --> tests/ui/const_comparisons.rs:120:23 | LL | status_code > 500 && status_code >= 500; | ^^^^^^^^^^^^^^^^^^^^^ error: left-hand side of `&&` operator has no effect - --> tests/ui/const_comparisons.rs:122:5 + --> tests/ui/const_comparisons.rs:124:5 | LL | status_code <= 500 && status_code < 500; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code < 500` evaluates to true, status_code <= 500` will always evaluate to true as well - --> tests/ui/const_comparisons.rs:122:5 + --> tests/ui/const_comparisons.rs:124:5 | LL | status_code <= 500 && status_code < 500; | ^^^^^^^^^^^^^^^^^^^^^^ error: right-hand side of `&&` operator has no effect - --> tests/ui/const_comparisons.rs:126:5 + --> tests/ui/const_comparisons.rs:128:5 | LL | status_code < 500 && status_code <= 500; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: `if `status_code < 500` evaluates to true, status_code <= 500` will always evaluate to true as well - --> tests/ui/const_comparisons.rs:126:23 + --> tests/ui/const_comparisons.rs:128:23 | LL | status_code < 500 && status_code <= 500; | ^^^^^^^^^^^^^^^^^^^^^ error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:131:5 + --> tests/ui/const_comparisons.rs:133:5 | LL | name < "Jennifer" && name > "Shannon"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -203,7 +203,7 @@ LL | name < "Jennifer" && name > "Shannon"; = note: since `"Jennifer"` < `"Shannon"`, the expression evaluates to false for any value of `name` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:135:5 + --> tests/ui/const_comparisons.rs:137:5 | LL | numbers < [3, 4] && numbers > [5, 6]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -211,7 +211,7 @@ LL | numbers < [3, 4] && numbers > [5, 6]; = note: since `[3, 4]` < `[5, 6]`, the expression evaluates to false for any value of `numbers` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:139:5 + --> tests/ui/const_comparisons.rs:141:5 | LL | letter < 'b' && letter > 'c'; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -219,7 +219,7 @@ LL | letter < 'b' && letter > 'c'; = note: since `'b'` < `'c'`, the expression evaluates to false for any value of `letter` error: boolean expression will never evaluate to 'true' - --> tests/ui/const_comparisons.rs:143:5 + --> tests/ui/const_comparisons.rs:145:5 | LL | area < std::f32::consts::E && area > std::f32::consts::PI; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/const_is_empty.rs b/tests/ui/const_is_empty.rs index 8bb4f0e5d9750..2ad1b5276def8 100644 --- a/tests/ui/const_is_empty.rs +++ b/tests/ui/const_is_empty.rs @@ -55,53 +55,6 @@ const NON_EMPTY_ARRAY_REPEAT: [u32; 2] = [1; 2]; const EMPTY_REF_ARRAY: &[u32; 0] = &[]; const NON_EMPTY_REF_ARRAY: &[u32; 3] = &[1, 2, 3]; -fn test_from_const() { - let _ = EMPTY_STR.is_empty(); - //~^ const_is_empty - - let _ = NON_EMPTY_STR.is_empty(); - //~^ const_is_empty - - let _ = EMPTY_BSTR.is_empty(); - //~^ const_is_empty - - let _ = NON_EMPTY_BSTR.is_empty(); - //~^ const_is_empty - - let _ = EMPTY_ARRAY.is_empty(); - //~^ const_is_empty - - let _ = EMPTY_ARRAY_REPEAT.is_empty(); - //~^ const_is_empty - - let _ = EMPTY_U8_SLICE.is_empty(); - //~^ const_is_empty - - let _ = NON_EMPTY_U8_SLICE.is_empty(); - //~^ const_is_empty - - let _ = NON_EMPTY_ARRAY.is_empty(); - //~^ const_is_empty - - let _ = NON_EMPTY_ARRAY_REPEAT.is_empty(); - //~^ const_is_empty - - let _ = EMPTY_REF_ARRAY.is_empty(); - //~^ const_is_empty - - let _ = NON_EMPTY_REF_ARRAY.is_empty(); - //~^ const_is_empty - - let _ = EMPTY_SLICE.is_empty(); - //~^ const_is_empty - - let _ = NON_EMPTY_SLICE.is_empty(); - //~^ const_is_empty - - let _ = NON_EMPTY_SLICE_REPEAT.is_empty(); - //~^ const_is_empty -} - fn main() { let value = "foobar"; let _ = value.is_empty(); @@ -120,7 +73,7 @@ fn main() { fn str_from_arg(var: &str) { var.is_empty(); - // Do not lint, we know nothiny about var + // Do not lint, we know nothing about var } fn update_str() { @@ -200,6 +153,5 @@ fn issue_13106() { const { EMPTY_STR.is_empty(); - //~^ const_is_empty } } diff --git a/tests/ui/const_is_empty.stderr b/tests/ui/const_is_empty.stderr index 2ba189058e832..e1837695bc1c9 100644 --- a/tests/ui/const_is_empty.stderr +++ b/tests/ui/const_is_empty.stderr @@ -37,131 +37,35 @@ error: this expression always evaluates to false LL | if non_empty2.is_empty() { | ^^^^^^^^^^^^^^^^^^^^^ -error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:59:13 - | -LL | let _ = EMPTY_STR.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:62:13 - | -LL | let _ = NON_EMPTY_STR.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:65:13 - | -LL | let _ = EMPTY_BSTR.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:68:13 - | -LL | let _ = NON_EMPTY_BSTR.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:71:13 - | -LL | let _ = EMPTY_ARRAY.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:74:13 - | -LL | let _ = EMPTY_ARRAY_REPEAT.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:77:13 - | -LL | let _ = EMPTY_U8_SLICE.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:80:13 - | -LL | let _ = NON_EMPTY_U8_SLICE.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:83:13 - | -LL | let _ = NON_EMPTY_ARRAY.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:86:13 - | -LL | let _ = NON_EMPTY_ARRAY_REPEAT.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:89:13 - | -LL | let _ = EMPTY_REF_ARRAY.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:92:13 - | -LL | let _ = NON_EMPTY_REF_ARRAY.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:95:13 - | -LL | let _ = EMPTY_SLICE.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:98:13 - | -LL | let _ = NON_EMPTY_SLICE.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:101:13 - | -LL | let _ = NON_EMPTY_SLICE_REPEAT.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:107:13 + --> tests/ui/const_is_empty.rs:60:13 | LL | let _ = value.is_empty(); | ^^^^^^^^^^^^^^^^ error: this expression always evaluates to false - --> tests/ui/const_is_empty.rs:111:13 + --> tests/ui/const_is_empty.rs:64:13 | LL | let _ = x.is_empty(); | ^^^^^^^^^^^^ error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:114:13 + --> tests/ui/const_is_empty.rs:67:13 | LL | let _ = "".is_empty(); | ^^^^^^^^^^^^^ error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:117:13 + --> tests/ui/const_is_empty.rs:70:13 | LL | let _ = b"".is_empty(); | ^^^^^^^^^^^^^^ error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:171:13 + --> tests/ui/const_is_empty.rs:124:13 | LL | let _ = val.is_empty(); | ^^^^^^^^^^^^^^ -error: this expression always evaluates to true - --> tests/ui/const_is_empty.rs:202:9 - | -LL | EMPTY_STR.is_empty(); - | ^^^^^^^^^^^^^^^^^^^^ - -error: aborting due to 27 previous errors +error: aborting due to 11 previous errors From 47b0f903a3b0e2a863f34a6bd1c33196f851c796 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 24 Aug 2025 10:38:06 -0400 Subject: [PATCH 89/93] Use `eval_local` in more places. --- clippy_lints/src/floating_point_arithmetic.rs | 6 +- clippy_lints/src/implicit_saturating_add.rs | 5 +- clippy_lints/src/manual_float_methods.rs | 29 +--- clippy_lints/src/manual_rotate.rs | 2 +- clippy_lints/src/manual_strip.rs | 24 ++- clippy_lints/src/methods/ip_constant.rs | 4 +- clippy_lints/src/methods/iter_nth_zero.rs | 2 +- clippy_lints/src/methods/iter_skip_zero.rs | 16 +- clippy_lints/src/methods/repeat_once.rs | 2 +- clippy_lints/src/methods/str_splitn.rs | 2 +- clippy_lints/src/minmax.rs | 6 +- clippy_lints/src/operators/duration_subsec.rs | 2 +- .../src/operators/modulo_arithmetic.rs | 6 +- clippy_lints/src/ranges.rs | 2 +- clippy_utils/src/consts.rs | 8 +- tests/ui/duration_subsec.fixed | 3 +- tests/ui/duration_subsec.rs | 1 - tests/ui/duration_subsec.stderr | 8 +- tests/ui/floating_point_log.fixed | 6 +- tests/ui/floating_point_log.rs | 2 - tests/ui/floating_point_log.stderr | 60 +++---- tests/ui/ip_constant.fixed | 45 ++++-- tests/ui/ip_constant.rs | 9 -- tests/ui/ip_constant.stderr | 146 +----------------- tests/ui/manual_float_methods.rs | 15 +- tests/ui/manual_float_methods.stderr | 40 +---- tests/ui/manual_strip.stderr | 3 - tests/ui/repeat_once.fixed | 3 +- tests/ui/repeat_once.rs | 1 - tests/ui/repeat_once.stderr | 14 +- 30 files changed, 132 insertions(+), 340 deletions(-) diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index 3ecc232caff43..407a3f1306739 100644 --- a/clippy_lints/src/floating_point_arithmetic.rs +++ b/clippy_lints/src/floating_point_arithmetic.rs @@ -111,8 +111,8 @@ declare_lint_pass!(FloatingPointArithmetic => [ // Returns the specialized log method for a given base if base is constant // and is one of 2, 10 and e -fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>) -> Option<&'static str> { - if let Some(value) = ConstEvalCtxt::new(cx).eval(base) { +fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>, ctxt: SyntaxContext) -> Option<&'static str> { + if let Some(value) = ConstEvalCtxt::new(cx).eval_local(base, ctxt) { if F32(2.0) == value || F64(2.0) == value { return Some("log2"); } else if F32(10.0) == value || F64(10.0) == value { @@ -158,7 +158,7 @@ fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Su } fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { - if let Some(method) = get_specialized_log_method(cx, &args[0]) { + if let Some(method) = get_specialized_log_method(cx, &args[0], expr.span.ctxt()) { span_lint_and_sugg( cx, SUBOPTIMAL_FLOPS, diff --git a/clippy_lints/src/implicit_saturating_add.rs b/clippy_lints/src/implicit_saturating_add.rs index 0fdbf67973813..4bf3a390b050c 100644 --- a/clippy_lints/src/implicit_saturating_add.rs +++ b/clippy_lints/src/implicit_saturating_add.rs @@ -117,10 +117,11 @@ fn get_int_max(ty: Ty<'_>) -> Option { fn get_const<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<(u128, BinOpKind, &'tcx Expr<'tcx>)> { if let ExprKind::Binary(op, l, r) = expr.kind { let ecx = ConstEvalCtxt::new(cx); - if let Some(Constant::Int(c)) = ecx.eval(r) { + let ctxt = expr.span.ctxt(); + if let Some(Constant::Int(c)) = ecx.eval_local(r, ctxt) { return Some((c, op.node, l)); } - if let Some(Constant::Int(c)) = ecx.eval(l) { + if let Some(Constant::Int(c)) = ecx.eval_local(l, ctxt) { return Some((c, invert_op(op.node)?, r)); } } diff --git a/clippy_lints/src/manual_float_methods.rs b/clippy_lints/src/manual_float_methods.rs index 7d6c30aa74ddd..60782f445ab91 100644 --- a/clippy_lints/src/manual_float_methods.rs +++ b/clippy_lints/src/manual_float_methods.rs @@ -1,5 +1,5 @@ use clippy_config::Conf; -use clippy_utils::consts::{ConstEvalCtxt, Constant}; +use clippy_utils::consts::ConstEvalCtxt; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::SpanRangeExt; @@ -146,13 +146,14 @@ impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods { ) && let [first, second, const_1, const_2] = exprs && let ecx = ConstEvalCtxt::new(cx) - && let Some(const_1) = ecx.eval(const_1) - && let Some(const_2) = ecx.eval(const_2) + && let ctxt = expr.span.ctxt() + && let Some(const_1) = ecx.eval_local(const_1, ctxt) + && let Some(const_2) = ecx.eval_local(const_2, ctxt) && path_to_local(first).is_some_and(|f| path_to_local(second).is_some_and(|s| f == s)) // The actual infinity check, we also allow `NEG_INFINITY` before` INFINITY` just in // case somebody does that for some reason - && (is_infinity(&const_1) && is_neg_infinity(&const_2) - || is_neg_infinity(&const_1) && is_infinity(&const_2)) + && (const_1.is_pos_infinity() && const_2.is_neg_infinity() + || const_1.is_neg_infinity() && const_2.is_pos_infinity()) && let Some(local_snippet) = first.span.get_source_text(cx) { let variant = match (kind.node, lhs_kind.node, rhs_kind.node) { @@ -201,21 +202,3 @@ impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods { } } } - -fn is_infinity(constant: &Constant) -> bool { - match constant { - // FIXME(f16_f128): add f16 and f128 when constants are available - Constant::F32(float) => *float == f32::INFINITY, - Constant::F64(float) => *float == f64::INFINITY, - _ => false, - } -} - -fn is_neg_infinity(constant: &Constant) -> bool { - match constant { - // FIXME(f16_f128): add f16 and f128 when constants are available - Constant::F32(float) => *float == f32::NEG_INFINITY, - Constant::F64(float) => *float == f64::NEG_INFINITY, - _ => false, - } -} diff --git a/clippy_lints/src/manual_rotate.rs b/clippy_lints/src/manual_rotate.rs index 06ee00c2cef3c..22e3407303f07 100644 --- a/clippy_lints/src/manual_rotate.rs +++ b/clippy_lints/src/manual_rotate.rs @@ -66,7 +66,7 @@ fn parse_shift<'tcx>( BinOpKind::Shr => ShiftDirection::Right, _ => return None, }; - let const_expr = ConstEvalCtxt::new(cx).eval(r)?; + let const_expr = ConstEvalCtxt::new(cx).eval_local(r, expr.span.ctxt())?; if let Constant::Int(shift) = const_expr { return Some((dir, shift, l)); } diff --git a/clippy_lints/src/manual_strip.rs b/clippy_lints/src/manual_strip.rs index 07cce4046ca4f..f5d15310879a3 100644 --- a/clippy_lints/src/manual_strip.rs +++ b/clippy_lints/src/manual_strip.rs @@ -16,7 +16,7 @@ use rustc_lint::{LateContext, LateLintPass, LintContext as _}; use rustc_middle::ty; use rustc_session::impl_lint_pass; use rustc_span::source_map::Spanned; -use rustc_span::{Symbol, sym}; +use rustc_span::{Symbol, SyntaxContext, sym}; use std::iter; declare_clippy_lint! { @@ -92,7 +92,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip { return; } - let (strippings, bindings) = find_stripping(cx, strip_kind, target_res, pattern, then); + let (strippings, bindings) = find_stripping(cx, strip_kind, target_res, pattern, then, expr.span.ctxt()); if !strippings.is_empty() && self.msrv.meets(cx, msrvs::STR_STRIP_PREFIX) { let kind_word = match strip_kind { StripKind::Prefix => "prefix", @@ -166,8 +166,8 @@ fn len_arg<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx E } // Returns the length of the `expr` if it's a constant string or char. -fn constant_length(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { - let value = ConstEvalCtxt::new(cx).eval(expr)?; +fn constant_length(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> Option { + let value = ConstEvalCtxt::new(cx).eval_local(expr, ctxt)?; match value { Constant::Str(value) => Some(value.len() as u128), Constant::Char(value) => Some(value.len_utf8() as u128), @@ -176,13 +176,18 @@ fn constant_length(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { } // Tests if `expr` equals the length of the pattern. -fn eq_pattern_length<'tcx>(cx: &LateContext<'tcx>, pattern: &Expr<'_>, expr: &'tcx Expr<'_>) -> bool { +fn eq_pattern_length<'tcx>( + cx: &LateContext<'tcx>, + pattern: &Expr<'_>, + expr: &'tcx Expr<'_>, + ctxt: SyntaxContext, +) -> bool { if let ExprKind::Lit(Spanned { node: LitKind::Int(n, _), .. }) = expr.kind { - constant_length(cx, pattern).is_some_and(|length| n == length) + constant_length(cx, pattern, ctxt).is_some_and(|length| n == length) } else { len_arg(cx, expr).is_some_and(|arg| eq_expr_value(cx, pattern, arg)) } @@ -215,6 +220,7 @@ fn find_stripping<'tcx>( target: Res, pattern: &'tcx Expr<'_>, expr: &'tcx Expr<'tcx>, + ctxt: SyntaxContext, ) -> (Vec<&'tcx Expr<'tcx>>, FxHashMap) { struct StrippingFinder<'a, 'tcx> { cx: &'a LateContext<'tcx>, @@ -223,6 +229,7 @@ fn find_stripping<'tcx>( pattern: &'tcx Expr<'tcx>, results: Vec<&'tcx Expr<'tcx>>, bindings: FxHashMap, + ctxt: SyntaxContext, } impl<'tcx> Visitor<'tcx> for StrippingFinder<'_, 'tcx> { @@ -236,7 +243,7 @@ fn find_stripping<'tcx>( { match (self.strip_kind, start, end) { (StripKind::Prefix, Some(start), None) => { - if eq_pattern_length(self.cx, self.pattern, start) { + if eq_pattern_length(self.cx, self.pattern, start, self.ctxt) { self.results.push(ex); return; } @@ -252,7 +259,7 @@ fn find_stripping<'tcx>( && let Some(left_arg) = len_arg(self.cx, left) && let ExprKind::Path(left_path) = &left_arg.kind && self.cx.qpath_res(left_path, left_arg.hir_id) == self.target - && eq_pattern_length(self.cx, self.pattern, right) + && eq_pattern_length(self.cx, self.pattern, right, self.ctxt) { self.results.push(ex); return; @@ -280,6 +287,7 @@ fn find_stripping<'tcx>( pattern, results: vec![], bindings: FxHashMap::default(), + ctxt, }; walk_expr(&mut finder, expr); (finder.results, finder.bindings) diff --git a/clippy_lints/src/methods/ip_constant.rs b/clippy_lints/src/methods/ip_constant.rs index a2ac4e54334ee..bf602811009a2 100644 --- a/clippy_lints/src/methods/ip_constant.rs +++ b/clippy_lints/src/methods/ip_constant.rs @@ -17,10 +17,12 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args cx.tcx.get_diagnostic_name(func_def_id), Some(sym::Ipv4Addr | sym::Ipv6Addr) ) + && let ecx = ConstEvalCtxt::new(cx) + && let ctxt = expr.span.ctxt() && let Some(args) = args .iter() .map(|arg| { - if let Some(Constant::Int(constant @ (0 | 1 | 127 | 255))) = ConstEvalCtxt::new(cx).eval(arg) { + if let Some(Constant::Int(constant @ (0 | 1 | 127 | 255))) = ecx.eval_local(arg, ctxt) { u8::try_from(constant).ok() } else { None diff --git a/clippy_lints/src/methods/iter_nth_zero.rs b/clippy_lints/src/methods/iter_nth_zero.rs index 4bdf589f48762..0f8abd0172423 100644 --- a/clippy_lints/src/methods/iter_nth_zero.rs +++ b/clippy_lints/src/methods/iter_nth_zero.rs @@ -14,7 +14,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr if let OwnerNode::Item(item) = cx.tcx.hir_owner_node(cx.tcx.hir_get_parent_item(expr.hir_id)) && let def_id = item.owner_id.to_def_id() && is_trait_method(cx, expr, sym::Iterator) - && let Some(Constant::Int(0)) = ConstEvalCtxt::new(cx).eval(arg) + && let Some(Constant::Int(0)) = ConstEvalCtxt::new(cx).eval_local(arg, expr.span.ctxt()) && !is_lang_item_or_ctor(cx, def_id, LangItem::IteratorNext) { let mut app = Applicability::MachineApplicable; diff --git a/clippy_lints/src/methods/iter_skip_zero.rs b/clippy_lints/src/methods/iter_skip_zero.rs index 39e440e784f6d..663e34437a307 100644 --- a/clippy_lints/src/methods/iter_skip_zero.rs +++ b/clippy_lints/src/methods/iter_skip_zero.rs @@ -11,13 +11,15 @@ use super::ITER_SKIP_ZERO; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg_expr: &Expr<'_>) { if !expr.span.from_expansion() && is_trait_method(cx, expr, sym::Iterator) - && let Some(arg) = ConstEvalCtxt::new(cx).eval(arg_expr).and_then(|constant| { - if let Constant::Int(arg) = constant { - Some(arg) - } else { - None - } - }) + && let Some(arg) = ConstEvalCtxt::new(cx) + .eval_local(arg_expr, expr.span.ctxt()) + .and_then(|constant| { + if let Constant::Int(arg) = constant { + Some(arg) + } else { + None + } + }) && arg == 0 && !is_from_proc_macro(cx, expr) { diff --git a/clippy_lints/src/methods/repeat_once.rs b/clippy_lints/src/methods/repeat_once.rs index 7837517ed5d8a..9111604ef53b7 100644 --- a/clippy_lints/src/methods/repeat_once.rs +++ b/clippy_lints/src/methods/repeat_once.rs @@ -14,7 +14,7 @@ pub(super) fn check<'tcx>( recv: &'tcx Expr<'_>, repeat_arg: &'tcx Expr<'_>, ) { - if ConstEvalCtxt::new(cx).eval(repeat_arg) == Some(Constant::Int(1)) { + if ConstEvalCtxt::new(cx).eval_local(repeat_arg, expr.span.ctxt()) == Some(Constant::Int(1)) { let ty = cx.typeck_results().expr_ty(recv).peel_refs(); if ty.is_str() { span_lint_and_sugg( diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints/src/methods/str_splitn.rs index 51dd4ac313a6d..8daa5db887acf 100644 --- a/clippy_lints/src/methods/str_splitn.rs +++ b/clippy_lints/src/methods/str_splitn.rs @@ -304,7 +304,7 @@ fn parse_iter_usage<'tcx>( }; }, (sym::nth | sym::skip, [idx_expr]) if cx.tcx.trait_of_assoc(did) == Some(iter_id) => { - if let Some(Constant::Int(idx)) = ConstEvalCtxt::new(cx).eval(idx_expr) { + if let Some(Constant::Int(idx)) = ConstEvalCtxt::new(cx).eval_local(idx_expr, ctxt) { let span = if name.ident.as_str() == "nth" { e.span } else if let Some((_, Node::Expr(next_expr))) = iter.next() diff --git a/clippy_lints/src/minmax.rs b/clippy_lints/src/minmax.rs index 35b6af0c20f7f..f9a7c562c7a59 100644 --- a/clippy_lints/src/minmax.rs +++ b/clippy_lints/src/minmax.rs @@ -61,7 +61,7 @@ enum MinMax { Max, } -fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinMax, Constant, &'a Expr<'a>)> { +fn min_max<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(MinMax, Constant, &'a Expr<'a>)> { match expr.kind { ExprKind::Call(path, args) => { if let ExprKind::Path(ref qpath) = path.kind { @@ -92,8 +92,8 @@ fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinM } } -fn fetch_const<'a, 'tcx>( - cx: &LateContext<'tcx>, +fn fetch_const<'a>( + cx: &LateContext<'_>, ctxt: SyntaxContext, receiver: Option<&'a Expr<'a>>, args: &'a [Expr<'a>], diff --git a/clippy_lints/src/operators/duration_subsec.rs b/clippy_lints/src/operators/duration_subsec.rs index 6c9be7c5e90b3..d897b0e8dd918 100644 --- a/clippy_lints/src/operators/duration_subsec.rs +++ b/clippy_lints/src/operators/duration_subsec.rs @@ -19,7 +19,7 @@ pub(crate) fn check<'tcx>( if op == BinOpKind::Div && let ExprKind::MethodCall(method_path, self_arg, [], _) = left.kind && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_arg).peel_refs(), sym::Duration) - && let Some(Constant::Int(divisor)) = ConstEvalCtxt::new(cx).eval(right) + && let Some(Constant::Int(divisor)) = ConstEvalCtxt::new(cx).eval_local(right, expr.span.ctxt()) { let suggested_fn = match (method_path.ident.name, divisor) { (sym::subsec_micros, 1_000) | (sym::subsec_nanos, 1_000_000) => "subsec_millis", diff --git a/clippy_lints/src/operators/modulo_arithmetic.rs b/clippy_lints/src/operators/modulo_arithmetic.rs index b79461663d7bf..ffe91fc2cef6d 100644 --- a/clippy_lints/src/operators/modulo_arithmetic.rs +++ b/clippy_lints/src/operators/modulo_arithmetic.rs @@ -39,7 +39,9 @@ fn used_in_comparison_with_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { && let BinOpKind::Eq | BinOpKind::Ne = op.node { let ecx = ConstEvalCtxt::new(cx); - matches!(ecx.eval(lhs), Some(Constant::Int(0))) || matches!(ecx.eval(rhs), Some(Constant::Int(0))) + let ctxt = expr.span.ctxt(); + matches!(ecx.eval_local(lhs, ctxt), Some(Constant::Int(0))) + || matches!(ecx.eval_local(rhs, ctxt), Some(Constant::Int(0))) } else { false } @@ -55,7 +57,7 @@ fn analyze_operand(operand: &Expr<'_>, cx: &LateContext<'_>, expr: &Expr<'_>) -> match ConstEvalCtxt::new(cx).eval(operand)? { Constant::Int(v) => match *cx.typeck_results().expr_ty(expr).kind() { ty::Int(ity) => { - let value = sext(cx.tcx, v, ity); + let value: i128 = sext(cx.tcx, v, ity); Some(OperandInfo { string_representation: Some(value.to_string()), is_negative: value < 0, diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index 8a216afb82c9a..0b2313cb7eeb9 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -312,7 +312,7 @@ struct RangeBounds<'a> { // Takes a binary expression such as x <= 2 as input // Breaks apart into various pieces, such as the value of the number, // hir id of the variable, and direction/inclusiveness of the operator -fn check_range_bounds<'a, 'tcx>(cx: &'a LateContext<'tcx>, ex: &'a Expr<'_>) -> Option> { +fn check_range_bounds<'a>(cx: &'a LateContext<'_>, ex: &'a Expr<'_>) -> Option> { if let ExprKind::Binary(ref op, l, r) = ex.kind { let (inclusive, ordering) = match op.node { BinOpKind::Gt => (false, Ordering::Greater), diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index f5f3786164608..9ba796137cc3a 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -128,15 +128,11 @@ impl PartialEq for Constant { (&Self::Char(l), &Self::Char(r)) => l == r, (&Self::Int(l), &Self::Int(r)) => l == r, (&Self::F64(l), &Self::F64(r)) => { - // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have - // `Fw32 == Fw64`, so don’t compare them. - // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs. + // `to_bits` is required to catch non-matching `0.0` and `-0.0`. l.to_bits() == r.to_bits() && !l.is_nan() }, (&Self::F32(l), &Self::F32(r)) => { - // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have - // `Fw32 == Fw64`, so don’t compare them. - // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs. + // `to_bits` is required to catch non-matching `0.0` and `-0.0`. l.to_bits() == r.to_bits() && !l.is_nan() }, (&Self::Bool(l), &Self::Bool(r)) => l == r, diff --git a/tests/ui/duration_subsec.fixed b/tests/ui/duration_subsec.fixed index a8c2f78ca383a..b6b2d156c0e0f 100644 --- a/tests/ui/duration_subsec.fixed +++ b/tests/ui/duration_subsec.fixed @@ -25,8 +25,7 @@ fn main() { // Handle constants const NANOS_IN_MICRO: u32 = 1_000; - let _ = dur.subsec_micros(); - //~^ duration_subsec + let _ = dur.subsec_nanos() / NANOS_IN_MICRO; // Other literals aren't linted let _ = dur.subsec_nanos() / 699; diff --git a/tests/ui/duration_subsec.rs b/tests/ui/duration_subsec.rs index 582f4717de27f..1061e6003c35e 100644 --- a/tests/ui/duration_subsec.rs +++ b/tests/ui/duration_subsec.rs @@ -26,7 +26,6 @@ fn main() { // Handle constants const NANOS_IN_MICRO: u32 = 1_000; let _ = dur.subsec_nanos() / NANOS_IN_MICRO; - //~^ duration_subsec // Other literals aren't linted let _ = dur.subsec_nanos() / 699; diff --git a/tests/ui/duration_subsec.stderr b/tests/ui/duration_subsec.stderr index 1a41742e1fa6d..27756bc1c20f6 100644 --- a/tests/ui/duration_subsec.stderr +++ b/tests/ui/duration_subsec.stderr @@ -25,11 +25,5 @@ error: calling `subsec_micros()` is more concise than this calculation LL | let _ = (&dur).subsec_nanos() / 1_000; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(&dur).subsec_micros()` -error: calling `subsec_micros()` is more concise than this calculation - --> tests/ui/duration_subsec.rs:28:13 - | -LL | let _ = dur.subsec_nanos() / NANOS_IN_MICRO; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `dur.subsec_micros()` - -error: aborting due to 5 previous errors +error: aborting due to 4 previous errors diff --git a/tests/ui/floating_point_log.fixed b/tests/ui/floating_point_log.fixed index 275c9b4a3ab9a..e831e30a71d81 100644 --- a/tests/ui/floating_point_log.fixed +++ b/tests/ui/floating_point_log.fixed @@ -14,10 +14,8 @@ fn check_log_base() { //~^ suboptimal_flops let _ = x.ln(); //~^ suboptimal_flops - let _ = x.log2(); - //~^ suboptimal_flops - let _ = x.ln(); - //~^ suboptimal_flops + let _ = x.log(TWO); + let _ = x.log(E); let _ = (x as f32).log2(); //~^ suboptimal_flops diff --git a/tests/ui/floating_point_log.rs b/tests/ui/floating_point_log.rs index a372ccbb9fb03..06cb1c8d96039 100644 --- a/tests/ui/floating_point_log.rs +++ b/tests/ui/floating_point_log.rs @@ -15,9 +15,7 @@ fn check_log_base() { let _ = x.log(std::f32::consts::E); //~^ suboptimal_flops let _ = x.log(TWO); - //~^ suboptimal_flops let _ = x.log(E); - //~^ suboptimal_flops let _ = (x as f32).log(2f32); //~^ suboptimal_flops diff --git a/tests/ui/floating_point_log.stderr b/tests/ui/floating_point_log.stderr index e93b3af851cbe..3e141de626d94 100644 --- a/tests/ui/floating_point_log.stderr +++ b/tests/ui/floating_point_log.stderr @@ -19,44 +19,32 @@ error: logarithm for bases 2, 10 and e can be computed more accurately LL | let _ = x.log(std::f32::consts::E); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.ln()` -error: logarithm for bases 2, 10 and e can be computed more accurately - --> tests/ui/floating_point_log.rs:17:13 - | -LL | let _ = x.log(TWO); - | ^^^^^^^^^^ help: consider using: `x.log2()` - error: logarithm for bases 2, 10 and e can be computed more accurately --> tests/ui/floating_point_log.rs:19:13 | -LL | let _ = x.log(E); - | ^^^^^^^^ help: consider using: `x.ln()` - -error: logarithm for bases 2, 10 and e can be computed more accurately - --> tests/ui/floating_point_log.rs:21:13 - | LL | let _ = (x as f32).log(2f32); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x as f32).log2()` error: logarithm for bases 2, 10 and e can be computed more accurately - --> tests/ui/floating_point_log.rs:25:13 + --> tests/ui/floating_point_log.rs:23:13 | LL | let _ = x.log(2f64); | ^^^^^^^^^^^ help: consider using: `x.log2()` error: logarithm for bases 2, 10 and e can be computed more accurately - --> tests/ui/floating_point_log.rs:27:13 + --> tests/ui/floating_point_log.rs:25:13 | LL | let _ = x.log(10f64); | ^^^^^^^^^^^^ help: consider using: `x.log10()` error: logarithm for bases 2, 10 and e can be computed more accurately - --> tests/ui/floating_point_log.rs:29:13 + --> tests/ui/floating_point_log.rs:27:13 | LL | let _ = x.log(std::f64::consts::E); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.ln()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:35:13 + --> tests/ui/floating_point_log.rs:33:13 | LL | let _ = (1f32 + 2.).ln(); | ^^^^^^^^^^^^^^^^ help: consider using: `2.0f32.ln_1p()` @@ -65,118 +53,118 @@ LL | let _ = (1f32 + 2.).ln(); = help: to override `-D warnings` add `#[allow(clippy::imprecise_flops)]` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:37:13 + --> tests/ui/floating_point_log.rs:35:13 | LL | let _ = (1f32 + 2.0).ln(); | ^^^^^^^^^^^^^^^^^ help: consider using: `2.0f32.ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:39:13 + --> tests/ui/floating_point_log.rs:37:13 | LL | let _ = (1.0 + x).ln(); | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:41:13 + --> tests/ui/floating_point_log.rs:39:13 | LL | let _ = (1.0 + x / 2.0).ln(); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:43:13 + --> tests/ui/floating_point_log.rs:41:13 | LL | let _ = (1.0 + x.powi(3)).ln(); | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(3).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:45:13 + --> tests/ui/floating_point_log.rs:43:13 | LL | let _ = (1.0 + x.powi(3) / 2.0).ln(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x.powi(3) / 2.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:47:13 + --> tests/ui/floating_point_log.rs:45:13 | LL | let _ = (1.0 + (std::f32::consts::E - 1.0)).ln(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(std::f32::consts::E - 1.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:49:13 + --> tests/ui/floating_point_log.rs:47:13 | LL | let _ = (x + 1.0).ln(); | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:51:13 + --> tests/ui/floating_point_log.rs:49:13 | LL | let _ = (x.powi(3) + 1.0).ln(); | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(3).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:53:13 + --> tests/ui/floating_point_log.rs:51:13 | LL | let _ = (x + 2.0 + 1.0).ln(); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x + 2.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:55:13 + --> tests/ui/floating_point_log.rs:53:13 | LL | let _ = (x / 2.0 + 1.0).ln(); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:64:13 + --> tests/ui/floating_point_log.rs:62:13 | LL | let _ = (1f64 + 2.).ln(); | ^^^^^^^^^^^^^^^^ help: consider using: `2.0f64.ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:66:13 + --> tests/ui/floating_point_log.rs:64:13 | LL | let _ = (1f64 + 2.0).ln(); | ^^^^^^^^^^^^^^^^^ help: consider using: `2.0f64.ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:68:13 + --> tests/ui/floating_point_log.rs:66:13 | LL | let _ = (1.0 + x).ln(); | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:70:13 + --> tests/ui/floating_point_log.rs:68:13 | LL | let _ = (1.0 + x / 2.0).ln(); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:72:13 + --> tests/ui/floating_point_log.rs:70:13 | LL | let _ = (1.0 + x.powi(3)).ln(); | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(3).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:74:13 + --> tests/ui/floating_point_log.rs:72:13 | LL | let _ = (x + 1.0).ln(); | ^^^^^^^^^^^^^^ help: consider using: `x.ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:76:13 + --> tests/ui/floating_point_log.rs:74:13 | LL | let _ = (x.powi(3) + 1.0).ln(); | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.powi(3).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:78:13 + --> tests/ui/floating_point_log.rs:76:13 | LL | let _ = (x + 2.0 + 1.0).ln(); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x + 2.0).ln_1p()` error: ln(1 + x) can be computed more accurately - --> tests/ui/floating_point_log.rs:80:13 + --> tests/ui/floating_point_log.rs:78:13 | LL | let _ = (x / 2.0 + 1.0).ln(); | ^^^^^^^^^^^^^^^^^^^^ help: consider using: `(x / 2.0).ln_1p()` -error: aborting due to 29 previous errors +error: aborting due to 27 previous errors diff --git a/tests/ui/ip_constant.fixed b/tests/ui/ip_constant.fixed index c947968213948..afdf581bacf7a 100644 --- a/tests/ui/ip_constant.fixed +++ b/tests/ui/ip_constant.fixed @@ -72,33 +72,44 @@ const CONST_U16_1: u16 = 1; fn const_test1() { use std::net::Ipv4Addr; - let _ = Ipv4Addr::LOCALHOST; - //~^ ip_constant - let _ = Ipv4Addr::BROADCAST; - //~^ ip_constant - let _ = Ipv4Addr::UNSPECIFIED; - //~^ ip_constant + let _ = Ipv4Addr::new(CONST_U8_127, CONST_U8_0, CONST_U8_0, CONST_U8_1); + let _ = Ipv4Addr::new(CONST_U8_255, CONST_U8_255, CONST_U8_255, CONST_U8_255); + let _ = Ipv4Addr::new(CONST_U8_0, CONST_U8_0, CONST_U8_0, CONST_U8_0); use std::net::Ipv6Addr; - let _ = Ipv6Addr::LOCALHOST; - - let _ = Ipv6Addr::UNSPECIFIED; + let _ = Ipv6Addr::new( + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_1, + ); + + let _ = Ipv6Addr::new( + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + CONST_U16_0, + ); } fn const_test2() { use std::net::Ipv4Addr; let _ = Ipv4Addr::LOCALHOST; //~^ ip_constant - let _ = Ipv4Addr::BROADCAST; - //~^ ip_constant - let _ = Ipv4Addr::UNSPECIFIED; - //~^ ip_constant + let _ = Ipv4Addr::new(254 + CONST_U8_1, 255, { 255 - CONST_U8_0 }, CONST_U8_255); + let _ = Ipv4Addr::new(0, CONST_U8_255 - 255, 0, { 1 + 0 - 1 }); use std::net::Ipv6Addr; - let _ = Ipv6Addr::LOCALHOST; - //~^ ip_constant - let _ = Ipv6Addr::LOCALHOST; - //~^ ip_constant + let _ = Ipv6Addr::new(0 + CONST_U16_0, 0, 0, 0, 0, 0, 0, 1); + let _ = Ipv6Addr::new(0 + 0, 0, 0, 0, 0, { 2 - 1 - CONST_U16_1 }, 0, 1); } macro_rules! ipv4_new { diff --git a/tests/ui/ip_constant.rs b/tests/ui/ip_constant.rs index 69a5c3b4e9230..04fc2f0f6fdae 100644 --- a/tests/ui/ip_constant.rs +++ b/tests/ui/ip_constant.rs @@ -73,15 +73,11 @@ const CONST_U16_1: u16 = 1; fn const_test1() { use std::net::Ipv4Addr; let _ = Ipv4Addr::new(CONST_U8_127, CONST_U8_0, CONST_U8_0, CONST_U8_1); - //~^ ip_constant let _ = Ipv4Addr::new(CONST_U8_255, CONST_U8_255, CONST_U8_255, CONST_U8_255); - //~^ ip_constant let _ = Ipv4Addr::new(CONST_U8_0, CONST_U8_0, CONST_U8_0, CONST_U8_0); - //~^ ip_constant use std::net::Ipv6Addr; let _ = Ipv6Addr::new( - //~^ ip_constant CONST_U16_0, CONST_U16_0, CONST_U16_0, @@ -93,7 +89,6 @@ fn const_test1() { ); let _ = Ipv6Addr::new( - //~^ ip_constant CONST_U16_0, CONST_U16_0, CONST_U16_0, @@ -110,15 +105,11 @@ fn const_test2() { let _ = Ipv4Addr::new(126 + 1, 0, 0, 1); //~^ ip_constant let _ = Ipv4Addr::new(254 + CONST_U8_1, 255, { 255 - CONST_U8_0 }, CONST_U8_255); - //~^ ip_constant let _ = Ipv4Addr::new(0, CONST_U8_255 - 255, 0, { 1 + 0 - 1 }); - //~^ ip_constant use std::net::Ipv6Addr; let _ = Ipv6Addr::new(0 + CONST_U16_0, 0, 0, 0, 0, 0, 0, 1); - //~^ ip_constant let _ = Ipv6Addr::new(0 + 0, 0, 0, 0, 0, { 2 - 1 - CONST_U16_1 }, 0, 1); - //~^ ip_constant } macro_rules! ipv4_new { diff --git a/tests/ui/ip_constant.stderr b/tests/ui/ip_constant.stderr index 07d912b18a57b..44e3d6448dbd8 100644 --- a/tests/ui/ip_constant.stderr +++ b/tests/ui/ip_constant.stderr @@ -241,101 +241,7 @@ LL + let _ = std::net::Ipv6Addr::UNSPECIFIED; | error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:75:13 - | -LL | let _ = Ipv4Addr::new(CONST_U8_127, CONST_U8_0, CONST_U8_0, CONST_U8_1); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use - | -LL - let _ = Ipv4Addr::new(CONST_U8_127, CONST_U8_0, CONST_U8_0, CONST_U8_1); -LL + let _ = Ipv4Addr::LOCALHOST; - | - -error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:77:13 - | -LL | let _ = Ipv4Addr::new(CONST_U8_255, CONST_U8_255, CONST_U8_255, CONST_U8_255); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use - | -LL - let _ = Ipv4Addr::new(CONST_U8_255, CONST_U8_255, CONST_U8_255, CONST_U8_255); -LL + let _ = Ipv4Addr::BROADCAST; - | - -error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:79:13 - | -LL | let _ = Ipv4Addr::new(CONST_U8_0, CONST_U8_0, CONST_U8_0, CONST_U8_0); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use - | -LL - let _ = Ipv4Addr::new(CONST_U8_0, CONST_U8_0, CONST_U8_0, CONST_U8_0); -LL + let _ = Ipv4Addr::UNSPECIFIED; - | - -error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:83:13 - | -LL | let _ = Ipv6Addr::new( - | _____________^ -LL | | -LL | | CONST_U16_0, -LL | | CONST_U16_0, -... | -LL | | CONST_U16_1, -LL | | ); - | |_____^ - | -help: use - | -LL - let _ = Ipv6Addr::new( -LL - -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_1, -LL - ); -LL + let _ = Ipv6Addr::LOCALHOST; - | - -error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:95:13 - | -LL | let _ = Ipv6Addr::new( - | _____________^ -LL | | -LL | | CONST_U16_0, -LL | | CONST_U16_0, -... | -LL | | CONST_U16_0, -LL | | ); - | |_____^ - | -help: use - | -LL - let _ = Ipv6Addr::new( -LL - -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - CONST_U16_0, -LL - ); -LL + let _ = Ipv6Addr::UNSPECIFIED; - | - -error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:110:13 + --> tests/ui/ip_constant.rs:105:13 | LL | let _ = Ipv4Addr::new(126 + 1, 0, 0, 1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -346,53 +252,5 @@ LL - let _ = Ipv4Addr::new(126 + 1, 0, 0, 1); LL + let _ = Ipv4Addr::LOCALHOST; | -error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:112:13 - | -LL | let _ = Ipv4Addr::new(254 + CONST_U8_1, 255, { 255 - CONST_U8_0 }, CONST_U8_255); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use - | -LL - let _ = Ipv4Addr::new(254 + CONST_U8_1, 255, { 255 - CONST_U8_0 }, CONST_U8_255); -LL + let _ = Ipv4Addr::BROADCAST; - | - -error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:114:13 - | -LL | let _ = Ipv4Addr::new(0, CONST_U8_255 - 255, 0, { 1 + 0 - 1 }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use - | -LL - let _ = Ipv4Addr::new(0, CONST_U8_255 - 255, 0, { 1 + 0 - 1 }); -LL + let _ = Ipv4Addr::UNSPECIFIED; - | - -error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:118:13 - | -LL | let _ = Ipv6Addr::new(0 + CONST_U16_0, 0, 0, 0, 0, 0, 0, 1); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use - | -LL - let _ = Ipv6Addr::new(0 + CONST_U16_0, 0, 0, 0, 0, 0, 0, 1); -LL + let _ = Ipv6Addr::LOCALHOST; - | - -error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:120:13 - | -LL | let _ = Ipv6Addr::new(0 + 0, 0, 0, 0, 0, { 2 - 1 - CONST_U16_1 }, 0, 1); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use - | -LL - let _ = Ipv6Addr::new(0 + 0, 0, 0, 0, 0, { 2 - 1 - CONST_U16_1 }, 0, 1); -LL + let _ = Ipv6Addr::LOCALHOST; - | - -error: aborting due to 30 previous errors +error: aborting due to 21 previous errors diff --git a/tests/ui/manual_float_methods.rs b/tests/ui/manual_float_methods.rs index 62cdc1c141d0c..4b496a4932833 100644 --- a/tests/ui/manual_float_methods.rs +++ b/tests/ui/manual_float_methods.rs @@ -8,9 +8,6 @@ #[macro_use] extern crate proc_macros; -const INFINITE: f32 = f32::INFINITY; -const NEG_INFINITE: f32 = f32::NEG_INFINITY; - fn fn_test() -> f64 { f64::NEG_INFINITY } @@ -25,10 +22,6 @@ fn main() { //~^ manual_is_infinite if x != f32::INFINITY && x != f32::NEG_INFINITY {} //~^ manual_is_finite - if x == INFINITE || x == NEG_INFINITE {} - //~^ manual_is_infinite - if x != INFINITE && x != NEG_INFINITE {} - //~^ manual_is_finite let x = 1.0f64; if x == f64::INFINITY || x == f64::NEG_INFINITY {} //~^ manual_is_infinite @@ -64,4 +57,12 @@ fn main() { if x == f32::INFINITY || x == f32::NEG_INFINITY {} if x != f32::INFINITY && x != f32::NEG_INFINITY {} } + + { + let x = 1.0f32; + const X: f32 = f32::INFINITY; + const Y: f32 = f32::NEG_INFINITY; + if x == X || x == Y {} + if x != X && x != Y {} + } } diff --git a/tests/ui/manual_float_methods.stderr b/tests/ui/manual_float_methods.stderr index 352c879c87d73..0a27e0eac48b3 100644 --- a/tests/ui/manual_float_methods.stderr +++ b/tests/ui/manual_float_methods.stderr @@ -1,5 +1,5 @@ error: manually checking if a float is infinite - --> tests/ui/manual_float_methods.rs:24:8 + --> tests/ui/manual_float_methods.rs:21:8 | LL | if x == f32::INFINITY || x == f32::NEG_INFINITY {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the dedicated method instead: `x.is_infinite()` @@ -8,7 +8,7 @@ LL | if x == f32::INFINITY || x == f32::NEG_INFINITY {} = help: to override `-D warnings` add `#[allow(clippy::manual_is_infinite)]` error: manually checking if a float is finite - --> tests/ui/manual_float_methods.rs:26:8 + --> tests/ui/manual_float_methods.rs:23:8 | LL | if x != f32::INFINITY && x != f32::NEG_INFINITY {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -32,41 +32,13 @@ LL + if !x.is_infinite() {} | error: manually checking if a float is infinite - --> tests/ui/manual_float_methods.rs:28:8 - | -LL | if x == INFINITE || x == NEG_INFINITE {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the dedicated method instead: `x.is_infinite()` - -error: manually checking if a float is finite - --> tests/ui/manual_float_methods.rs:30:8 - | -LL | if x != INFINITE && x != NEG_INFINITE {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use the dedicated method instead - | -LL - if x != INFINITE && x != NEG_INFINITE {} -LL + if x.is_finite() {} - | -help: this will alter how it handles NaN; if that is a problem, use instead - | -LL - if x != INFINITE && x != NEG_INFINITE {} -LL + if x.is_finite() || x.is_nan() {} - | -help: or, for conciseness - | -LL - if x != INFINITE && x != NEG_INFINITE {} -LL + if !x.is_infinite() {} - | - -error: manually checking if a float is infinite - --> tests/ui/manual_float_methods.rs:33:8 + --> tests/ui/manual_float_methods.rs:26:8 | LL | if x == f64::INFINITY || x == f64::NEG_INFINITY {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the dedicated method instead: `x.is_infinite()` error: manually checking if a float is finite - --> tests/ui/manual_float_methods.rs:35:8 + --> tests/ui/manual_float_methods.rs:28:8 | LL | if x != f64::INFINITY && x != f64::NEG_INFINITY {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -88,10 +60,10 @@ LL + if !x.is_infinite() {} | error: manually checking if a float is infinite - --> tests/ui/manual_float_methods.rs:50:12 + --> tests/ui/manual_float_methods.rs:43:12 | LL | if x == f64::INFINITY || x == f64::NEG_INFINITY {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the dedicated method instead: `x.is_infinite()` -error: aborting due to 7 previous errors +error: aborting due to 5 previous errors diff --git a/tests/ui/manual_strip.stderr b/tests/ui/manual_strip.stderr index a323ef700e767..d147cdae1f3b1 100644 --- a/tests/ui/manual_strip.stderr +++ b/tests/ui/manual_strip.stderr @@ -97,9 +97,6 @@ LL | if s.starts_with(PREFIX) { help: try using the `strip_prefix` method | LL ~ if let Some() = s.strip_prefix(PREFIX) { -LL ~ str::to_string(); -LL | -LL | LL ~ str::to_string(); | diff --git a/tests/ui/repeat_once.fixed b/tests/ui/repeat_once.fixed index e739e176f0acd..c08d630a32f7f 100644 --- a/tests/ui/repeat_once.fixed +++ b/tests/ui/repeat_once.fixed @@ -10,8 +10,7 @@ fn main() { //~^ repeat_once let b = slice.to_vec(); //~^ repeat_once - let c = "hello".to_string(); - //~^ repeat_once + let c = "hello".repeat(N); let d = "hi".to_string(); //~^ repeat_once let e = s.to_string(); diff --git a/tests/ui/repeat_once.rs b/tests/ui/repeat_once.rs index 89ab94bbaee85..d967fdc466ed7 100644 --- a/tests/ui/repeat_once.rs +++ b/tests/ui/repeat_once.rs @@ -11,7 +11,6 @@ fn main() { let b = slice.repeat(1); //~^ repeat_once let c = "hello".repeat(N); - //~^ repeat_once let d = "hi".repeat(1); //~^ repeat_once let e = s.repeat(1); diff --git a/tests/ui/repeat_once.stderr b/tests/ui/repeat_once.stderr index 3db7a3568f8e6..62dbf7d233759 100644 --- a/tests/ui/repeat_once.stderr +++ b/tests/ui/repeat_once.stderr @@ -14,28 +14,22 @@ LL | let b = slice.repeat(1); | ^^^^^^^^^^^^^^^ help: consider using `.to_vec()` instead: `slice.to_vec()` error: calling `repeat(1)` on str - --> tests/ui/repeat_once.rs:13:13 - | -LL | let c = "hello".repeat(N); - | ^^^^^^^^^^^^^^^^^ help: consider using `.to_string()` instead: `"hello".to_string()` - -error: calling `repeat(1)` on str - --> tests/ui/repeat_once.rs:15:13 + --> tests/ui/repeat_once.rs:14:13 | LL | let d = "hi".repeat(1); | ^^^^^^^^^^^^^^ help: consider using `.to_string()` instead: `"hi".to_string()` error: calling `repeat(1)` on str - --> tests/ui/repeat_once.rs:17:13 + --> tests/ui/repeat_once.rs:16:13 | LL | let e = s.repeat(1); | ^^^^^^^^^^^ help: consider using `.to_string()` instead: `s.to_string()` error: calling `repeat(1)` on a string literal - --> tests/ui/repeat_once.rs:19:13 + --> tests/ui/repeat_once.rs:18:13 | LL | let f = string.repeat(1); | ^^^^^^^^^^^^^^^^ help: consider using `.clone()` instead: `string.clone()` -error: aborting due to 6 previous errors +error: aborting due to 5 previous errors From f4b4fb9b7f1af29c3b9f6e25229a2514d5db35c7 Mon Sep 17 00:00:00 2001 From: Teodoro Freund Date: Mon, 6 Oct 2025 09:35:46 -0300 Subject: [PATCH 90/93] Fixed some lint deprecated versions --- clippy_lints/src/deprecated_lints.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/deprecated_lints.rs b/clippy_lints/src/deprecated_lints.rs index 80b74f50223a4..2147f72889093 100644 --- a/clippy_lints/src/deprecated_lints.rs +++ b/clippy_lints/src/deprecated_lints.rs @@ -18,11 +18,11 @@ declare_with_version! { DEPRECATED(DEPRECATED_VERSION) = [ ("clippy::assign_ops", "compound operators are harmless and linting on them is not in scope for clippy"), #[clippy::version = "pre 1.29.0"] ("clippy::extend_from_slice", "`Vec::extend_from_slice` is no longer faster than `Vec::extend` due to specialization"), - #[clippy::version = "1.86.0"] + #[clippy::version = "1.88.0"] ("clippy::match_on_vec_items", "`clippy::indexing_slicing` covers indexing and slicing on `Vec<_>`"), #[clippy::version = "pre 1.29.0"] ("clippy::misaligned_transmute", "split into `clippy::cast_ptr_alignment` and `clippy::transmute_ptr_to_ptr`"), - #[clippy::version = "1.86.0"] + #[clippy::version = "1.87.0"] ("clippy::option_map_or_err_ok", "`clippy::manual_ok_or` covers this case"), #[clippy::version = "1.54.0"] ("clippy::pub_enum_variant_names", "`clippy::enum_variant_names` now covers this case via the `avoid-breaking-exported-api` config"), @@ -34,7 +34,7 @@ declare_with_version! { DEPRECATED(DEPRECATED_VERSION) = [ ("clippy::replace_consts", "`min_value` and `max_value` are now deprecated"), #[clippy::version = "pre 1.29.0"] ("clippy::should_assert_eq", "`assert!(a == b)` can now print the values the same way `assert_eq!(a, b) can"), - #[clippy::version = "1.90.0"] + #[clippy::version = "1.91.0"] ("clippy::string_to_string", "`clippy:implicit_clone` covers those cases"), #[clippy::version = "pre 1.29.0"] ("clippy::unsafe_vector_initialization", "the suggested alternative could be substantially slower"), From 085ddaaa97bf55914a33938462d133dd597c8f0b Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Mon, 6 Oct 2025 17:26:17 +0200 Subject: [PATCH 91/93] Bump nightly version -> 2025-10-06 --- 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 2c66fdc73f539..1f678a6a29f08 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-09-18 +nightly-2025-10-06 ``` diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 9c102de448200..e936f5dc3b7a5 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2025-09-18" +channel = "nightly-2025-10-06" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" From 8c4be66084f62b6de4dece52f517b455b802edca Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Mon, 6 Oct 2025 18:10:44 +0200 Subject: [PATCH 92/93] Update Cargo.lock --- Cargo.lock | 99 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2de9c84e3b031..92a9013ca2575 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -216,7 +216,7 @@ dependencies = [ "memchr", "serde", "serde_derive", - "winnow 0.7.12", + "winnow 0.7.13", ] [[package]] @@ -590,7 +590,7 @@ dependencies = [ "serde_json", "tempfile", "termize", - "toml 0.7.8", + "toml 0.9.7", "ui_test", "walkdir", ] @@ -632,7 +632,7 @@ dependencies = [ "regex-syntax 0.8.5", "semver", "serde", - "toml 0.7.8", + "toml 0.9.7", "unicode-normalization", "unicode-script", "url", @@ -1866,13 +1866,14 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" -version = "2.10.0" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", "hashbrown", "serde", + "serde_core", ] [[package]] @@ -5003,10 +5004,11 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -5031,11 +5033,20 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -5084,6 +5095,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" +dependencies = [ + "serde_core", +] + [[package]] name = "sha1" version = "0.10.6" @@ -5545,8 +5565,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_edit 0.19.15", ] @@ -5558,11 +5578,26 @@ checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "indexmap", "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_edit 0.22.27", ] +[[package]] +name = "toml" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.0.2", + "toml_datetime 0.7.2", + "toml_parser", + "toml_writer", + "winnow 0.7.13", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -5572,6 +5607,15 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.19.15" @@ -5580,8 +5624,8 @@ checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap", "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "winnow 0.5.40", ] @@ -5593,10 +5637,19 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_write", - "winnow 0.7.12", + "winnow 0.7.13", +] + +[[package]] +name = "toml_parser" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +dependencies = [ + "winnow 0.7.13", ] [[package]] @@ -5605,6 +5658,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "toml_writer" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" + [[package]] name = "tracing" version = "0.1.41" @@ -6569,9 +6628,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] From e652436f2adbb58f8754a12f8040b5093bd1e039 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Mon, 6 Oct 2025 18:18:33 +0200 Subject: [PATCH 93/93] Allow serde_core as a dependency --- src/bootstrap/src/utils/proc_macro_deps.rs | 1 + src/tools/tidy/src/deps.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/bootstrap/src/utils/proc_macro_deps.rs b/src/bootstrap/src/utils/proc_macro_deps.rs index db2369097d6ce..83e5580eaa913 100644 --- a/src/bootstrap/src/utils/proc_macro_deps.rs +++ b/src/bootstrap/src/utils/proc_macro_deps.rs @@ -43,6 +43,7 @@ pub static CRATES: &[&str] = &[ "rustc-hash", "self_cell", "serde", + "serde_core", "serde_derive_internals", "sha2", "smallvec", diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs index c76b46ec2bf26..3f40bef821c9d 100644 --- a/src/tools/tidy/src/deps.rs +++ b/src/tools/tidy/src/deps.rs @@ -458,6 +458,7 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[ "scopeguard", "self_cell", "serde", + "serde_core", "serde_derive", "serde_derive_internals", "serde_json",