From 8c6eea85a171bdd54811f5562884e8e706da34ce Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Sat, 8 Feb 2025 19:47:27 -0500 Subject: [PATCH 01/65] Extend `implicit_clone` to handle `to_string` calls --- clippy_lints/src/methods/implicit_clone.rs | 4 +--- clippy_lints/src/methods/mod.rs | 2 +- clippy_lints/src/methods/unnecessary_to_owned.rs | 4 ++-- tests/ui/implicit_clone.fixed | 6 ++++++ tests/ui/implicit_clone.rs | 6 ++++++ tests/ui/implicit_clone.stderr | 14 +++++++++++++- tests/ui/string_to_string.fixed | 8 ++++++++ tests/ui/string_to_string.rs | 4 ++-- tests/ui/string_to_string.stderr | 9 ++++----- 9 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 tests/ui/string_to_string.fixed diff --git a/clippy_lints/src/methods/implicit_clone.rs b/clippy_lints/src/methods/implicit_clone.rs index 9724463f0c08..adb2002feea9 100644 --- a/clippy_lints/src/methods/implicit_clone.rs +++ b/clippy_lints/src/methods/implicit_clone.rs @@ -40,14 +40,12 @@ pub fn check(cx: &LateContext<'_>, method_name: Symbol, expr: &hir::Expr<'_>, re } /// Returns true if the named method can be used to clone the receiver. -/// Note that `to_string` is not flagged by `implicit_clone`. So other lints that call -/// `is_clone_like` and that do flag `to_string` must handle it separately. See, e.g., -/// `is_to_owned_like` in `unnecessary_to_owned.rs`. pub fn is_clone_like(cx: &LateContext<'_>, method_name: Symbol, method_def_id: hir::def_id::DefId) -> bool { match method_name { sym::to_os_string => is_diag_item_method(cx, method_def_id, sym::OsStr), sym::to_owned => is_diag_trait_item(cx, method_def_id, sym::ToOwned), sym::to_path_buf => is_diag_item_method(cx, method_def_id, sym::Path), + sym::to_string => is_diag_trait_item(cx, method_def_id, sym::ToString), sym::to_vec => cx .tcx .impl_of_method(method_def_id) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index d2d59f0013c0..a79ccba19e15 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -5403,7 +5403,7 @@ impl Methods { implicit_clone::check(cx, name, expr, recv); } }, - (sym::to_os_string | sym::to_path_buf | sym::to_vec, []) => { + (sym::to_os_string | sym::to_path_buf | sym::to_string | sym::to_vec, []) => { implicit_clone::check(cx, name, expr, recv); }, (sym::type_id, []) => { diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index 29a0d2950bc6..74cb95359c59 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -619,8 +619,8 @@ fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_def_id: /// Returns true if the named method can be used to convert the receiver to its "owned" /// representation. fn is_to_owned_like<'a>(cx: &LateContext<'a>, call_expr: &Expr<'a>, method_name: Symbol, method_def_id: DefId) -> bool { - is_clone_like(cx, method_name, method_def_id) - || is_cow_into_owned(cx, method_name, method_def_id) + is_cow_into_owned(cx, method_name, method_def_id) + || (method_name != sym::to_string && is_clone_like(cx, method_name, method_def_id)) || is_to_string_on_string_like(cx, call_expr, method_name, method_def_id) } diff --git a/tests/ui/implicit_clone.fixed b/tests/ui/implicit_clone.fixed index d60d1cb0ec04..267514c5f3d3 100644 --- a/tests/ui/implicit_clone.fixed +++ b/tests/ui/implicit_clone.fixed @@ -135,4 +135,10 @@ fn main() { } let no_clone = &NoClone; let _ = no_clone.to_owned(); + + let s = String::from("foo"); + let _ = s.clone(); + //~^ implicit_clone + let _ = s.clone(); + //~^ implicit_clone } diff --git a/tests/ui/implicit_clone.rs b/tests/ui/implicit_clone.rs index b96828f28c82..fba954026e7f 100644 --- a/tests/ui/implicit_clone.rs +++ b/tests/ui/implicit_clone.rs @@ -135,4 +135,10 @@ fn main() { } let no_clone = &NoClone; let _ = no_clone.to_owned(); + + let s = String::from("foo"); + let _ = s.to_owned(); + //~^ implicit_clone + let _ = s.to_string(); + //~^ implicit_clone } diff --git a/tests/ui/implicit_clone.stderr b/tests/ui/implicit_clone.stderr index 1eb6ff1fe429..4cca9b0d0c07 100644 --- a/tests/ui/implicit_clone.stderr +++ b/tests/ui/implicit_clone.stderr @@ -67,5 +67,17 @@ error: implicitly cloning a `PathBuf` by calling `to_path_buf` on its dereferenc LL | let _ = pathbuf_ref.to_path_buf(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(**pathbuf_ref).clone()` -error: aborting due to 11 previous errors +error: implicitly cloning a `String` by calling `to_owned` on its dereferenced type + --> tests/ui/implicit_clone.rs:140:13 + | +LL | let _ = s.to_owned(); + | ^^^^^^^^^^^^ help: consider using: `s.clone()` + +error: implicitly cloning a `String` by calling `to_string` on its dereferenced type + --> tests/ui/implicit_clone.rs:142:13 + | +LL | let _ = s.to_string(); + | ^^^^^^^^^^^^^ help: consider using: `s.clone()` + +error: aborting due to 13 previous errors diff --git a/tests/ui/string_to_string.fixed b/tests/ui/string_to_string.fixed new file mode 100644 index 000000000000..354b6c7c9fb6 --- /dev/null +++ b/tests/ui/string_to_string.fixed @@ -0,0 +1,8 @@ +#![warn(clippy::implicit_clone)] +#![allow(clippy::redundant_clone)] + +fn main() { + let mut message = String::from("Hello"); + let mut v = message.clone(); + //~^ ERROR: implicitly cloning a `String` by calling `to_string` on its dereferenced type +} diff --git a/tests/ui/string_to_string.rs b/tests/ui/string_to_string.rs index 7c5bd8a897ba..43a1cc18cd06 100644 --- a/tests/ui/string_to_string.rs +++ b/tests/ui/string_to_string.rs @@ -1,10 +1,10 @@ -#![warn(clippy::string_to_string)] +#![warn(clippy::implicit_clone, clippy::string_to_string)] #![allow(clippy::redundant_clone, clippy::unnecessary_literal_unwrap)] fn main() { let mut message = String::from("Hello"); let mut v = message.to_string(); - //~^ string_to_string + //~^ ERROR: implicitly cloning a `String` by calling `to_string` on its dereferenced type let variable1 = String::new(); let v = &variable1; diff --git a/tests/ui/string_to_string.stderr b/tests/ui/string_to_string.stderr index 99eea06f18eb..0e33c6c1bf35 100644 --- a/tests/ui/string_to_string.stderr +++ b/tests/ui/string_to_string.stderr @@ -1,12 +1,11 @@ -error: `to_string()` called on a `String` +error: implicitly cloning a `String` by calling `to_string` on its dereferenced type --> tests/ui/string_to_string.rs:6:17 | LL | let mut v = message.to_string(); - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^ help: consider using: `message.clone()` | - = help: consider using `.clone()` - = note: `-D clippy::string-to-string` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::string_to_string)]` + = note: `-D clippy::implicit-clone` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::implicit_clone)]` error: `to_string()` called on a `String` --> tests/ui/string_to_string.rs:14:9 From 8a9adba8525e7bf40ca5f3b08d359742173f6fa7 Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Sat, 8 Feb 2025 19:29:02 -0500 Subject: [PATCH 02/65] Fix adjacent code --- clippy_lints/src/blocks_in_conditions.rs | 3 +-- clippy_lints/src/copies.rs | 6 +++--- clippy_lints/src/if_not_else.rs | 2 +- clippy_lints/src/manual_async_fn.rs | 2 +- clippy_lints/src/matches/match_single_binding.rs | 4 +--- clippy_lints/src/matches/single_match.rs | 8 +++----- clippy_lints/src/methods/unnecessary_sort_by.rs | 2 +- clippy_lints/src/redundant_else.rs | 2 +- lintcheck/src/input.rs | 2 +- lintcheck/src/output.rs | 2 +- 10 files changed, 14 insertions(+), 19 deletions(-) diff --git a/clippy_lints/src/blocks_in_conditions.rs b/clippy_lints/src/blocks_in_conditions.rs index 011962846cb3..10fe1a4174f8 100644 --- a/clippy_lints/src/blocks_in_conditions.rs +++ b/clippy_lints/src/blocks_in_conditions.rs @@ -100,8 +100,7 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInConditions { cond.span, BRACED_EXPR_MESSAGE, "try", - snippet_block_with_applicability(cx, ex.span, "..", Some(expr.span), &mut applicability) - .to_string(), + snippet_block_with_applicability(cx, ex.span, "..", Some(expr.span), &mut applicability), applicability, ); } diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 2467fc95fd05..6cffb508fe64 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -245,9 +245,9 @@ fn lint_branches_sharing_code<'tcx>( 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.to_string() + "\n" + &cond_snippet + "{"; + let suggestion = moved_snippet + "\n" + &cond_snippet + "{"; let suggestion = reindent_multiline(&suggestion, true, cond_indent); - (replace_span, suggestion.to_string()) + (replace_span, suggestion) }); let end_suggestion = res.end_span(last_block, sm).map(|span| { let moved_snipped = reindent_multiline(&snippet(cx, span, "_"), true, None); @@ -263,7 +263,7 @@ fn lint_branches_sharing_code<'tcx>( .then_some(range.start - 4..range.end) }) .map_or(span, |range| range.with_ctxt(span.ctxt())); - (span, suggestion.to_string()) + (span, suggestion.clone()) }); let (span, msg, end_span) = match (&start_suggestion, &end_suggestion) { diff --git a/clippy_lints/src/if_not_else.rs b/clippy_lints/src/if_not_else.rs index 45f9aa0a53e4..5e7eb26a4f90 100644 --- a/clippy_lints/src/if_not_else.rs +++ b/clippy_lints/src/if_not_else.rs @@ -89,7 +89,7 @@ impl LateLintPass<'_> for IfNotElse { e.span, msg, "try", - make_sugg(cx, &cond.kind, cond_inner.span, els.span, "..", Some(e.span)).to_string(), + make_sugg(cx, &cond.kind, cond_inner.span, els.span, "..", Some(e.span)), Applicability::MachineApplicable, ), _ => span_lint_and_help(cx, IF_NOT_ELSE, e.span, msg, None, help), diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs index abd1ac954cda..ba1ad599e116 100644 --- a/clippy_lints/src/manual_async_fn.rs +++ b/clippy_lints/src/manual_async_fn.rs @@ -83,7 +83,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn { format!("{} async {}", vis_snip, &header_snip[vis_snip.len() + 1..ret_pos]) }; - let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span)).to_string(); + let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span)); diag.multipart_suggestion( "make the function `async` and return the output of the future directly", diff --git a/clippy_lints/src/matches/match_single_binding.rs b/clippy_lints/src/matches/match_single_binding.rs index adda35869900..5c2837d6b1a0 100644 --- a/clippy_lints/src/matches/match_single_binding.rs +++ b/clippy_lints/src/matches/match_single_binding.rs @@ -25,9 +25,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e let match_body = peel_blocks(arms[0].body); let mut app = Applicability::MaybeIncorrect; let ctxt = expr.span.ctxt(); - let mut snippet_body = snippet_block_with_context(cx, match_body.span, ctxt, "..", Some(expr.span), &mut app) - .0 - .to_string(); + let mut snippet_body = snippet_block_with_context(cx, match_body.span, ctxt, "..", Some(expr.span), &mut app).0; // Do we need to add ';' to suggestion ? if let Node::Stmt(stmt) = cx.tcx.parent_hir_node(expr.hir_id) diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index 08c0caa4266c..7c0cd6ee0dea 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -112,9 +112,7 @@ fn report_single_pattern( let (sugg, help) = if is_unit_expr(arm.body) { (String::new(), "`match` expression can be removed") } else { - let mut sugg = snippet_block_with_context(cx, arm.body.span, ctxt, "..", Some(expr.span), &mut app) - .0 - .to_string(); + let mut sugg = snippet_block_with_context(cx, arm.body.span, ctxt, "..", Some(expr.span), &mut app).0; if let Node::Stmt(stmt) = cx.tcx.parent_hir_node(expr.hir_id) && let StmtKind::Expr(_) = stmt.kind && match arm.body.kind { @@ -127,7 +125,7 @@ fn report_single_pattern( (sugg, "try") }; span_lint_and_then(cx, lint, expr.span, msg, |diag| { - diag.span_suggestion(expr.span, help, sugg.to_string(), app); + diag.span_suggestion(expr.span, help, sugg, app); note(diag); }); return; @@ -183,7 +181,7 @@ fn report_single_pattern( }; span_lint_and_then(cx, lint, expr.span, msg, |diag| { - diag.span_suggestion(expr.span, "try", sugg.to_string(), app); + diag.span_suggestion(expr.span, "try", sugg, app); note(diag); }); } diff --git a/clippy_lints/src/methods/unnecessary_sort_by.rs b/clippy_lints/src/methods/unnecessary_sort_by.rs index 235fdac29433..437f353746f5 100644 --- a/clippy_lints/src/methods/unnecessary_sort_by.rs +++ b/clippy_lints/src/methods/unnecessary_sort_by.rs @@ -216,7 +216,7 @@ pub(super) fn check<'tcx>( { format!("{}::cmp::Reverse({})", std_or_core, trigger.closure_body) } else { - trigger.closure_body.to_string() + trigger.closure_body }, ), if trigger.reverse { diff --git a/clippy_lints/src/redundant_else.rs b/clippy_lints/src/redundant_else.rs index a3be16ed858e..79353dc9247e 100644 --- a/clippy_lints/src/redundant_else.rs +++ b/clippy_lints/src/redundant_else.rs @@ -97,7 +97,7 @@ impl EarlyLintPass for RedundantElse { els.span.with_lo(then.span.hi()), "redundant else block", "remove the `else` block and move the contents out", - make_sugg(cx, els.span, "..", Some(expr.span)).to_string(), + make_sugg(cx, els.span, "..", Some(expr.span)), app, ); } diff --git a/lintcheck/src/input.rs b/lintcheck/src/input.rs index 83eb0a577d6b..60182fc2293a 100644 --- a/lintcheck/src/input.rs +++ b/lintcheck/src/input.rs @@ -117,7 +117,7 @@ pub fn read_crates(toml_path: &Path) -> (Vec, RecursiveOptions) crate_sources.push(CrateWithSource { name: tk.name.clone(), source: CrateSource::CratesIo { - version: version.to_string(), + version: version.clone(), }, file_link: tk.file_link(DEFAULT_DOCS_LINK), options: tk.options.clone(), diff --git a/lintcheck/src/output.rs b/lintcheck/src/output.rs index dcc1ec339ef9..e38e21015702 100644 --- a/lintcheck/src/output.rs +++ b/lintcheck/src/output.rs @@ -220,7 +220,7 @@ fn print_stats(old_stats: HashMap, new_stats: HashMap<&String, us let same_in_both_hashmaps = old_stats .iter() .filter(|(old_key, old_val)| new_stats.get::<&String>(old_key) == Some(old_val)) - .map(|(k, v)| (k.to_string(), *v)) + .map(|(k, v)| (k.clone(), *v)) .collect::>(); let mut old_stats_deduped = old_stats; From 8e5b0cdae1aaffcee5455852cb0eec0c6b56b17f Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Thu, 20 Mar 2025 14:01:04 -0400 Subject: [PATCH 03/65] Account for changes from issue 14396 --- clippy_lints/src/strings.rs | 16 ++-------------- tests/ui/string_to_string.fixed | 17 +++++++++++++++-- tests/ui/string_to_string.rs | 4 ++-- tests/ui/string_to_string.stderr | 12 ++++-------- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 73a9fe71e001..9fcef84eeb34 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -494,21 +494,9 @@ impl<'tcx> LateLintPass<'tcx> for StringToString { if path.ident.name == sym::to_string && let ty = cx.typeck_results().expr_ty(self_arg) && is_type_lang_item(cx, ty.peel_refs(), LangItem::String) + && let Some(parent_span) = is_called_from_map_like(cx, expr) { - if let Some(parent_span) = is_called_from_map_like(cx, expr) { - suggest_cloned_string_to_string(cx, parent_span); - } else { - #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] - span_lint_and_then( - cx, - STRING_TO_STRING, - expr.span, - "`to_string()` called on a `String`", - |diag| { - diag.help("consider using `.clone()`"); - }, - ); - } + suggest_cloned_string_to_string(cx, parent_span); } }, ExprKind::Path(QPath::TypeRelative(ty, segment)) => { diff --git a/tests/ui/string_to_string.fixed b/tests/ui/string_to_string.fixed index 354b6c7c9fb6..751f451cb685 100644 --- a/tests/ui/string_to_string.fixed +++ b/tests/ui/string_to_string.fixed @@ -1,8 +1,21 @@ -#![warn(clippy::implicit_clone)] -#![allow(clippy::redundant_clone)] +#![warn(clippy::implicit_clone, clippy::string_to_string)] +#![allow(clippy::redundant_clone, clippy::unnecessary_literal_unwrap)] fn main() { let mut message = String::from("Hello"); let mut v = message.clone(); //~^ ERROR: implicitly cloning a `String` by calling `to_string` on its dereferenced type + + let variable1 = String::new(); + let v = &variable1; + let variable2 = Some(v); + let _ = variable2.map(|x| { + println!(); + x.clone() + //~^ ERROR: implicitly cloning a `String` by calling `to_string` on its dereferenced type + }); + + let x = Some(String::new()); + let _ = x.unwrap_or_else(|| v.clone()); + //~^ ERROR: implicitly cloning a `String` by calling `to_string` on its dereferenced type } diff --git a/tests/ui/string_to_string.rs b/tests/ui/string_to_string.rs index 43a1cc18cd06..d519677d59ec 100644 --- a/tests/ui/string_to_string.rs +++ b/tests/ui/string_to_string.rs @@ -12,10 +12,10 @@ fn main() { let _ = variable2.map(|x| { println!(); x.to_string() + //~^ ERROR: implicitly cloning a `String` by calling `to_string` on its dereferenced type }); - //~^^ string_to_string let x = Some(String::new()); let _ = x.unwrap_or_else(|| v.to_string()); - //~^ string_to_string + //~^ ERROR: implicitly cloning a `String` by calling `to_string` on its dereferenced type } diff --git a/tests/ui/string_to_string.stderr b/tests/ui/string_to_string.stderr index 0e33c6c1bf35..220fe3170c6e 100644 --- a/tests/ui/string_to_string.stderr +++ b/tests/ui/string_to_string.stderr @@ -7,21 +7,17 @@ LL | let mut v = message.to_string(); = note: `-D clippy::implicit-clone` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::implicit_clone)]` -error: `to_string()` called on a `String` +error: implicitly cloning a `String` by calling `to_string` on its dereferenced type --> tests/ui/string_to_string.rs:14:9 | LL | x.to_string() - | ^^^^^^^^^^^^^ - | - = help: consider using `.clone()` + | ^^^^^^^^^^^^^ help: consider using: `x.clone()` -error: `to_string()` called on a `String` +error: implicitly cloning a `String` by calling `to_string` on its dereferenced type --> tests/ui/string_to_string.rs:19:33 | LL | let _ = x.unwrap_or_else(|| v.to_string()); - | ^^^^^^^^^^^^^ - | - = help: consider using `.clone()` + | ^^^^^^^^^^^^^ help: consider using: `v.clone()` error: aborting due to 3 previous errors From 6cea9ccf0f256e00936baf1db0cfbb223a5e8812 Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Mon, 10 Mar 2025 22:55:21 +0000 Subject: [PATCH 04/65] Replace `string_to_string` with `char_lit_as_u8` in driver.sh --- .github/driver.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/driver.sh b/.github/driver.sh index 5a81b4112918..2874aaf2110c 100755 --- a/.github/driver.sh +++ b/.github/driver.sh @@ -47,9 +47,9 @@ unset CARGO_MANIFEST_DIR # Run a lint and make sure it produces the expected output. It's also expected to exit with code 1 # FIXME: How to match the clippy invocation in compile-test.rs? -./target/debug/clippy-driver -Dwarnings -Aunused -Zui-testing --emit metadata --crate-type bin tests/ui/string_to_string.rs 2>string_to_string.stderr && exit 1 -sed -e "/= help: for/d" string_to_string.stderr > normalized.stderr -diff -u normalized.stderr tests/ui/string_to_string.stderr +./target/debug/clippy-driver -Dwarnings -Aunused -Zui-testing --emit metadata --crate-type bin tests/ui/char_lit_as_u8.rs 2>char_lit_as_u8.stderr && exit 1 +sed -e "/= help: for/d" char_lit_as_u8.stderr > normalized.stderr +diff -u normalized.stderr tests/ui/char_lit_as_u8.stderr # make sure "clippy-driver --rustc --arg" and "rustc --arg" behave the same SYSROOT=$(rustc --print sysroot) From 9e9c9170a50ba34a3cb66540a2079884b92ed480 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Sun, 8 Jun 2025 00:49:00 +0800 Subject: [PATCH 05/65] fix: `iter_on_single_items` FP on function pointers --- .../iter_on_single_or_empty_collections.rs | 65 ++++++++++--------- clippy_utils/src/ty/mod.rs | 2 +- tests/ui/iter_on_single_items.fixed | 11 ++++ tests/ui/iter_on_single_items.rs | 11 ++++ 4 files changed, 56 insertions(+), 33 deletions(-) diff --git a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs index c0366765234f..bda74ab173b6 100644 --- a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs +++ b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs @@ -2,11 +2,11 @@ use std::iter::once; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; +use clippy_utils::ty::{ExprFnSig, expr_sig, ty_sig}; use clippy_utils::{get_expr_use_or_unification_node, is_res_lang_ctor, path_res, std_or_core, sym}; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome}; -use rustc_hir::def_id::DefId; use rustc_hir::hir_id::HirId; use rustc_hir::{Expr, ExprKind, Node}; use rustc_lint::LateContext; @@ -32,24 +32,34 @@ impl IterType { fn is_arg_ty_unified_in_fn<'tcx>( cx: &LateContext<'tcx>, - fn_id: DefId, + fn_sig: ExprFnSig<'tcx>, arg_id: HirId, args: impl IntoIterator>, + is_method: bool, ) -> bool { - let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity(); let arg_id_in_args = args.into_iter().position(|e| e.hir_id == arg_id).unwrap(); - let arg_ty_in_args = fn_sig.input(arg_id_in_args).skip_binder(); + let Some(arg_ty_in_args) = fn_sig.input(arg_id_in_args).map(|binder| binder.skip_binder()) else { + return false; + }; - cx.tcx.predicates_of(fn_id).predicates.iter().any(|(clause, _)| { - clause - .as_projection_clause() - .and_then(|p| p.map_bound(|p| p.term.as_type()).transpose()) - .is_some_and(|ty| ty.skip_binder() == arg_ty_in_args) - }) || fn_sig - .inputs() - .iter() - .enumerate() - .any(|(i, ty)| i != arg_id_in_args && ty.skip_binder().walk().any(|arg| arg.as_type() == Some(arg_ty_in_args))) + fn_sig + .predicates_id() + .map(|def_id| cx.tcx.predicates_of(def_id)) + .is_some_and(|generics| { + generics.predicates.iter().any(|(clause, _)| { + clause + .as_projection_clause() + .and_then(|p| p.map_bound(|p| p.term.as_type()).transpose()) + .is_some_and(|ty| ty.skip_binder() == arg_ty_in_args) + }) + }) + || (!is_method + && fn_sig.input(arg_id_in_args).is_some_and(|binder| { + binder + .skip_binder() + .walk() + .any(|arg| arg.as_type() == Some(arg_ty_in_args)) + })) } pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, recv: &'tcx Expr<'tcx>) { @@ -70,25 +80,16 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method let is_unified = match get_expr_use_or_unification_node(cx.tcx, expr) { Some((Node::Expr(parent), child_id)) => match parent.kind { ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id == child_id => false, - ExprKind::Call( - Expr { - kind: ExprKind::Path(path), - hir_id, - .. - }, - args, - ) => cx + ExprKind::Call(recv, args) => { + expr_sig(cx, recv).is_some_and(|fn_sig| is_arg_ty_unified_in_fn(cx, fn_sig, child_id, args, false)) + }, + ExprKind::MethodCall(_name, recv, args, _span) => cx .typeck_results() - .qpath_res(path, *hir_id) - .opt_def_id() - .filter(|fn_id| cx.tcx.def_kind(fn_id).is_fn_like()) - .is_some_and(|fn_id| is_arg_ty_unified_in_fn(cx, fn_id, child_id, args)), - ExprKind::MethodCall(_name, recv, args, _span) => is_arg_ty_unified_in_fn( - cx, - cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(), - child_id, - once(recv).chain(args.iter()), - ), + .type_dependent_def_id(parent.hir_id) + .and_then(|def_id| ty_sig(cx, cx.tcx.type_of(def_id).instantiate_identity())) + .is_some_and(|fn_sig| { + is_arg_ty_unified_in_fn(cx, fn_sig, child_id, once(recv).chain(args.iter()), true) + }), ExprKind::If(_, _, _) | ExprKind::Match(_, _, _) | ExprKind::Closure(_) diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index 1f0a0f2b02ac..c9100d97bcfc 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -581,7 +581,7 @@ pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator { Sig(Binder<'tcx, FnSig<'tcx>>, Option), Closure(Option<&'tcx FnDecl<'tcx>>, Binder<'tcx, FnSig<'tcx>>), diff --git a/tests/ui/iter_on_single_items.fixed b/tests/ui/iter_on_single_items.fixed index b43fad6449c1..bdab6121b1f6 100644 --- a/tests/ui/iter_on_single_items.fixed +++ b/tests/ui/iter_on_single_items.fixed @@ -66,3 +66,14 @@ fn main() { custom_option::custom_option(); in_macros!(); } + +fn issue14981() { + use std::option::IntoIter; + fn some_func(_: IntoIter) -> IntoIter { + todo!() + } + some_func(Some(5).into_iter()); + + const C: fn(IntoIter) -> IntoIter = as IntoIterator>::into_iter; + C(Some(5).into_iter()); +} diff --git a/tests/ui/iter_on_single_items.rs b/tests/ui/iter_on_single_items.rs index 625c96d3ef1f..40ed6d3ec1a6 100644 --- a/tests/ui/iter_on_single_items.rs +++ b/tests/ui/iter_on_single_items.rs @@ -66,3 +66,14 @@ fn main() { custom_option::custom_option(); in_macros!(); } + +fn issue14981() { + use std::option::IntoIter; + fn some_func(_: IntoIter) -> IntoIter { + todo!() + } + some_func(Some(5).into_iter()); + + const C: fn(IntoIter) -> IntoIter = as IntoIterator>::into_iter; + C(Some(5).into_iter()); +} From 20670f78fdd63fbf1597457fcb35a562a428e0e9 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Sun, 8 Jun 2025 01:13:18 +0800 Subject: [PATCH 06/65] fix: `iter_on_single_item` FP on let stmt with type annotation --- .../iter_on_single_or_empty_collections.rs | 6 +++-- tests/ui/iter_on_single_items.fixed | 27 ++++++++++++++----- tests/ui/iter_on_single_items.rs | 27 ++++++++++++++----- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs index bda74ab173b6..83e565562af0 100644 --- a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs +++ b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs @@ -10,6 +10,7 @@ use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::hir_id::HirId; use rustc_hir::{Expr, ExprKind, Node}; use rustc_lint::LateContext; +use rustc_middle::ty::Binder; use rustc_span::Symbol; use super::{ITER_ON_EMPTY_COLLECTIONS, ITER_ON_SINGLE_ITEMS}; @@ -38,7 +39,7 @@ fn is_arg_ty_unified_in_fn<'tcx>( is_method: bool, ) -> bool { let arg_id_in_args = args.into_iter().position(|e| e.hir_id == arg_id).unwrap(); - let Some(arg_ty_in_args) = fn_sig.input(arg_id_in_args).map(|binder| binder.skip_binder()) else { + let Some(arg_ty_in_args) = fn_sig.input(arg_id_in_args).map(Binder::skip_binder) else { return false; }; @@ -97,7 +98,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method | ExprKind::Break(_, _) => true, _ => false, }, - Some((Node::Stmt(_) | Node::LetStmt(_), _)) => false, + Some((Node::LetStmt(let_stmt), _)) => let_stmt.ty.is_some(), + Some((Node::Stmt(_), _)) => false, _ => true, }; diff --git a/tests/ui/iter_on_single_items.fixed b/tests/ui/iter_on_single_items.fixed index bdab6121b1f6..044037aac2e3 100644 --- a/tests/ui/iter_on_single_items.fixed +++ b/tests/ui/iter_on_single_items.fixed @@ -67,13 +67,26 @@ fn main() { in_macros!(); } -fn issue14981() { +mod issue14981 { use std::option::IntoIter; - fn some_func(_: IntoIter) -> IntoIter { - todo!() - } - some_func(Some(5).into_iter()); + fn takes_into_iter(_: impl IntoIterator) {} - const C: fn(IntoIter) -> IntoIter = as IntoIterator>::into_iter; - C(Some(5).into_iter()); + fn let_stmt() { + macro_rules! x { + ($e:expr) => { + let _: IntoIter = $e; + }; + } + x!(Some(5).into_iter()); + } + + fn fn_ptr() { + fn some_func(_: IntoIter) -> IntoIter { + todo!() + } + some_func(Some(5).into_iter()); + + const C: fn(IntoIter) -> IntoIter = as IntoIterator>::into_iter; + C(Some(5).into_iter()); + } } diff --git a/tests/ui/iter_on_single_items.rs b/tests/ui/iter_on_single_items.rs index 40ed6d3ec1a6..c925d0e480fa 100644 --- a/tests/ui/iter_on_single_items.rs +++ b/tests/ui/iter_on_single_items.rs @@ -67,13 +67,26 @@ fn main() { in_macros!(); } -fn issue14981() { +mod issue14981 { use std::option::IntoIter; - fn some_func(_: IntoIter) -> IntoIter { - todo!() - } - some_func(Some(5).into_iter()); + fn takes_into_iter(_: impl IntoIterator) {} - const C: fn(IntoIter) -> IntoIter = as IntoIterator>::into_iter; - C(Some(5).into_iter()); + fn let_stmt() { + macro_rules! x { + ($e:expr) => { + let _: IntoIter = $e; + }; + } + x!(Some(5).into_iter()); + } + + fn fn_ptr() { + fn some_func(_: IntoIter) -> IntoIter { + todo!() + } + some_func(Some(5).into_iter()); + + const C: fn(IntoIter) -> IntoIter = as IntoIterator>::into_iter; + C(Some(5).into_iter()); + } } From 31b9de6965d955bc95d107ed2e58a75a3d902488 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Fri, 20 Jun 2025 15:49:14 +0800 Subject: [PATCH 07/65] fix: `let_unit_value` suggests wrongly for format macros --- clippy_lints/src/lib.rs | 3 +- clippy_lints/src/unit_types/let_unit_value.rs | 109 +++++++++++++----- clippy_lints/src/unit_types/mod.rs | 17 ++- tests/ui/let_unit.fixed | 11 ++ tests/ui/let_unit.rs | 10 ++ tests/ui/let_unit.stderr | 16 ++- 6 files changed, 131 insertions(+), 35 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 96a6dee58852..792acec9e527 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -523,7 +523,8 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(same_name_method::SameNameMethod)); store.register_late_pass(move |_| Box::new(index_refutable_slice::IndexRefutableSlice::new(conf))); store.register_late_pass(|_| Box::::default()); - store.register_late_pass(|_| Box::new(unit_types::UnitTypes)); + let format_args = format_args_storage.clone(); + store.register_late_pass(move |_| Box::new(unit_types::UnitTypes::new(format_args.clone()))); store.register_late_pass(move |_| Box::new(loops::Loops::new(conf))); store.register_late_pass(|_| Box::::default()); store.register_late_pass(move |_| Box::new(lifetimes::Lifetimes::new(conf))); diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index 87f184e13ce1..d5b6c1758549 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -1,17 +1,20 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet_with_context; +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::visitors::{for_each_local_assignment, for_each_value_source}; use core::ops::ControlFlow; +use rustc_ast::{FormatArgs, FormatArgumentKind}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::intravisit::{Visitor, walk_body}; +use rustc_hir::intravisit::{Visitor, walk_body, walk_expr}; use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, LetStmt, MatchSource, Node, PatKind, QPath, TyKind}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty; +use rustc_span::Span; use super::LET_UNIT_VALUE; -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) { +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, format_args: &FormatArgsStorage, local: &'tcx LetStmt<'_>) { // skip `let () = { ... }` if let PatKind::Tuple(fields, ..) = local.pat.kind && fields.is_empty() @@ -73,11 +76,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) { let mut suggestions = Vec::new(); // Suggest omitting the `let` binding - if let Some(expr) = &local.init { - let mut app = Applicability::MachineApplicable; - let snip = snippet_with_context(cx, expr.span, local.span.ctxt(), "()", &mut app).0; - suggestions.push((local.span, format!("{snip};"))); - } + let mut app = Applicability::MachineApplicable; + let snip = snippet_with_context(cx, init.span, local.span.ctxt(), "()", &mut app).0; // If this is a binding pattern, we need to add suggestions to remove any usages // of the variable @@ -85,53 +85,102 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) { && let Some(body_id) = cx.enclosing_body.as_ref() { let body = cx.tcx.hir_body(*body_id); - - // Collect variable usages - let mut visitor = UnitVariableCollector::new(binding_hir_id); + let mut visitor = UnitVariableCollector::new(cx, format_args, binding_hir_id); walk_body(&mut visitor, body); - // Add suggestions for replacing variable usages - suggestions.extend(visitor.spans.into_iter().map(|span| (span, "()".to_string()))); + let mut has_in_format_capture = false; + suggestions.extend(visitor.spans.iter().filter_map(|span| match span { + MaybeInFormatCapture::Yes => { + has_in_format_capture = true; + None + }, + MaybeInFormatCapture::No(span) => Some((*span, "()".to_string())), + })); + + if has_in_format_capture { + suggestions.push(( + init.span, + format!("();\n{}", reindent_multiline(&snip, false, indent_of(cx, local.span))), + )); + diag.multipart_suggestion( + "replace variable usages with `()`", + suggestions, + Applicability::MachineApplicable, + ); + return; + } } - // Emit appropriate diagnostic based on whether there are usages of the let binding - if !suggestions.is_empty() { - 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, Applicability::MachineApplicable); - } + suggestions.push((local.span, format!("{snip};"))); + 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, Applicability::MachineApplicable); }, ); } } } -struct UnitVariableCollector { +struct UnitVariableCollector<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + format_args: &'a FormatArgsStorage, id: HirId, - spans: Vec, + spans: Vec, + macro_call: Option<&'a FormatArgs>, } -impl UnitVariableCollector { - fn new(id: HirId) -> Self { - Self { id, spans: vec![] } +enum MaybeInFormatCapture { + Yes, + No(Span), +} + +impl<'a, 'tcx> UnitVariableCollector<'a, 'tcx> { + fn new(cx: &'a LateContext<'tcx>, format_args: &'a FormatArgsStorage, id: HirId) -> Self { + Self { + cx, + format_args, + id, + spans: Vec::new(), + macro_call: None, + } } } /** * Collect all instances where a variable is used based on its `HirId`. */ -impl<'tcx> Visitor<'tcx> for UnitVariableCollector { +impl<'tcx> Visitor<'tcx> for UnitVariableCollector<'_, 'tcx> { fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) -> Self::Result { + if let Some(macro_call) = root_macro_call_first_node(self.cx, ex) + && is_format_macro(self.cx, macro_call.def_id) + && let Some(format_args) = self.format_args.get(self.cx, ex, macro_call.expn) + { + let parent_macro_call = self.macro_call; + self.macro_call = Some(format_args); + walk_expr(self, ex); + self.macro_call = parent_macro_call; + return; + } + if let ExprKind::Path(QPath::Resolved(None, path)) = ex.kind && let Res::Local(id) = path.res && id == self.id { - self.spans.push(path.span); + if let Some(macro_call) = self.macro_call + && macro_call.arguments.all_args().iter().any(|arg| { + matches!(arg.kind, FormatArgumentKind::Captured(_)) && find_format_arg_expr(ex, arg).is_some() + }) + { + self.spans.push(MaybeInFormatCapture::Yes); + } else { + self.spans.push(MaybeInFormatCapture::No(path.span)); + } } - rustc_hir::intravisit::walk_expr(self, ex); + + walk_expr(self, ex); } } diff --git a/clippy_lints/src/unit_types/mod.rs b/clippy_lints/src/unit_types/mod.rs index e016bd3434b1..4ffcc247acf6 100644 --- a/clippy_lints/src/unit_types/mod.rs +++ b/clippy_lints/src/unit_types/mod.rs @@ -3,9 +3,10 @@ mod unit_arg; mod unit_cmp; mod utils; +use clippy_utils::macros::FormatArgsStorage; use rustc_hir::{Expr, LetStmt}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; declare_clippy_lint! { /// ### What it does @@ -96,11 +97,21 @@ declare_clippy_lint! { "passing unit to a function" } -declare_lint_pass!(UnitTypes => [LET_UNIT_VALUE, UNIT_CMP, UNIT_ARG]); +pub struct UnitTypes { + format_args: FormatArgsStorage, +} + +impl_lint_pass!(UnitTypes => [LET_UNIT_VALUE, UNIT_CMP, UNIT_ARG]); + +impl UnitTypes { + pub fn new(format_args: FormatArgsStorage) -> Self { + Self { format_args } + } +} impl<'tcx> LateLintPass<'tcx> for UnitTypes { fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx LetStmt<'tcx>) { - let_unit_value::check(cx, local); + let_unit_value::check(cx, &self.format_args, local); } fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { diff --git a/tests/ui/let_unit.fixed b/tests/ui/let_unit.fixed index 5e7a2ad37a84..0484ab297306 100644 --- a/tests/ui/let_unit.fixed +++ b/tests/ui/let_unit.fixed @@ -198,3 +198,14 @@ pub fn issue12594() { returns_result(res).unwrap(); } } + +fn issue15061() { + fn return_unit() {} + fn do_something(x: ()) {} + + let res = (); + return_unit(); + //~^ let_unit_value + do_something(()); + println!("{res:?}"); +} diff --git a/tests/ui/let_unit.rs b/tests/ui/let_unit.rs index 7b06f6940121..a0afc995b6f9 100644 --- a/tests/ui/let_unit.rs +++ b/tests/ui/let_unit.rs @@ -198,3 +198,13 @@ pub fn issue12594() { returns_result(res).unwrap(); } } + +fn issue15061() { + fn return_unit() {} + fn do_something(x: ()) {} + + let res = return_unit(); + //~^ let_unit_value + do_something(res); + println!("{res:?}"); +} diff --git a/tests/ui/let_unit.stderr b/tests/ui/let_unit.stderr index d7d01d304cad..0bc4da8b9c60 100644 --- a/tests/ui/let_unit.stderr +++ b/tests/ui/let_unit.stderr @@ -68,5 +68,19 @@ LL ~ returns_result(()).unwrap(); LL ~ returns_result(()).unwrap(); | -error: aborting due to 4 previous errors +error: this let-binding has unit value + --> tests/ui/let_unit.rs:206:5 + | +LL | let res = return_unit(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace variable usages with `()` + | +LL ~ let res = (); +LL ~ return_unit(); +LL | +LL ~ do_something(()); + | + +error: aborting due to 5 previous errors From 0920486fa6c0a3c08e23a867b1e6ca6a5563849b Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sun, 29 Jun 2025 22:28:39 +0200 Subject: [PATCH 08/65] Do not autofix comments containing bare CR --- clippy_lints/src/four_forward_slashes.rs | 15 +++++++++++++++ tests/ui/four_forward_slashes_bare_cr.rs | 6 ++++++ tests/ui/four_forward_slashes_bare_cr.stderr | 14 ++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 tests/ui/four_forward_slashes_bare_cr.rs create mode 100644 tests/ui/four_forward_slashes_bare_cr.stderr diff --git a/clippy_lints/src/four_forward_slashes.rs b/clippy_lints/src/four_forward_slashes.rs index 8822b87f92f7..a7b0edeb7991 100644 --- a/clippy_lints/src/four_forward_slashes.rs +++ b/clippy_lints/src/four_forward_slashes.rs @@ -1,4 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::SpanRangeExt as _; +use itertools::Itertools; use rustc_errors::Applicability; use rustc_hir::Item; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -81,6 +83,14 @@ impl<'tcx> LateLintPass<'tcx> for FourForwardSlashes { "turn these into doc comments by removing one `/`" }; + // If the comment contains a bare CR (not followed by a LF), do not propose an auto-fix + // as bare CR are not allowed in doc comments. + if span.check_source_text(cx, contains_bare_cr) { + diag.help(msg) + .note("bare CR characters are not allowed in doc comments"); + return; + } + diag.multipart_suggestion( msg, bad_comments @@ -97,3 +107,8 @@ impl<'tcx> LateLintPass<'tcx> for FourForwardSlashes { } } } + +/// Checks if `text` contains any CR not followed by a LF +fn contains_bare_cr(text: &str) -> bool { + text.bytes().tuple_windows().any(|(a, b)| a == b'\r' && b != b'\n') +} diff --git a/tests/ui/four_forward_slashes_bare_cr.rs b/tests/ui/four_forward_slashes_bare_cr.rs new file mode 100644 index 000000000000..19123cd206e3 --- /dev/null +++ b/tests/ui/four_forward_slashes_bare_cr.rs @@ -0,0 +1,6 @@ +//@no-rustfix +#![warn(clippy::four_forward_slashes)] + +//~v four_forward_slashes +//// nondoc comment with bare CR: ' ' +fn main() {} diff --git a/tests/ui/four_forward_slashes_bare_cr.stderr b/tests/ui/four_forward_slashes_bare_cr.stderr new file mode 100644 index 000000000000..64e70b97db9a --- /dev/null +++ b/tests/ui/four_forward_slashes_bare_cr.stderr @@ -0,0 +1,14 @@ +error: this item has comments with 4 forward slashes (`////`). These look like doc comments, but they aren't + --> tests/ui/four_forward_slashes_bare_cr.rs:5:1 + | +LL | / //// nondoc comment with bare CR: '␍' +LL | | fn main() {} + | |_^ + | + = help: make this a doc comment by removing one `/` + = note: bare CR characters are not allowed in doc comments + = note: `-D clippy::four-forward-slashes` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::four_forward_slashes)]` + +error: aborting due to 1 previous error + From 222ebd0535bcaeda021702bef63e25cfcaa47e5b Mon Sep 17 00:00:00 2001 From: yanglsh Date: Wed, 25 Jun 2025 21:27:02 +0800 Subject: [PATCH 09/65] fix: `search_is_some` suggests wrongly inside macro --- clippy_utils/src/sugg.rs | 24 ++++++++++++++++++--- tests/ui/search_is_some.rs | 15 +++++++++++++ tests/ui/search_is_some.stderr | 17 ++++++++++++++- tests/ui/search_is_some_fixable_some.fixed | 7 ++++++ tests/ui/search_is_some_fixable_some.rs | 7 ++++++ tests/ui/search_is_some_fixable_some.stderr | 8 ++++++- 6 files changed, 73 insertions(+), 5 deletions(-) diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 7a24d07fa1df..88f313c5f7e5 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -6,9 +6,9 @@ use crate::ty::expr_sig; use crate::{get_parent_expr_for_hir, higher}; use rustc_ast::ast; use rustc_ast::util::parser::AssocOp; +use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_hir::{Closure, ExprKind, HirId, MutTy, TyKind}; +use rustc_hir::{self as hir, Closure, ExprKind, HirId, MutTy, Node, TyKind}; use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; use rustc_lint::{EarlyContext, LateContext, LintContext}; use rustc_middle::hir::place::ProjectionKind; @@ -753,8 +753,10 @@ pub fn deref_closure_args(cx: &LateContext<'_>, closure: &hir::Expr<'_>) -> Opti let mut visitor = DerefDelegate { cx, closure_span: closure.span, + closure_arg_id: closure_body.params[0].pat.hir_id, closure_arg_is_type_annotated_double_ref, next_pos: closure.span.lo(), + checked_borrows: FxHashSet::default(), suggestion_start: String::new(), applicability: Applicability::MachineApplicable, }; @@ -780,10 +782,15 @@ struct DerefDelegate<'a, 'tcx> { cx: &'a LateContext<'tcx>, /// The span of the input closure to adapt closure_span: Span, + /// The `hir_id` of the closure argument being checked + closure_arg_id: HirId, /// Indicates if the arg of the closure is a type annotated double reference closure_arg_is_type_annotated_double_ref: bool, /// last position of the span to gradually build the suggestion next_pos: BytePos, + /// `hir_id`s that has been checked. This is used to avoid checking the same `hir_id` multiple + /// times when inside macro expansions. + checked_borrows: FxHashSet, /// starting part of the gradually built suggestion suggestion_start: String, /// confidence on the built suggestion @@ -847,9 +854,15 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> { fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} + #[expect(clippy::too_many_lines)] fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) { if let PlaceBase::Local(id) = cmt.place.base { let span = self.cx.tcx.hir_span(cmt.hir_id); + if !self.checked_borrows.insert(cmt.hir_id) { + // already checked this span and hir_id, skip + return; + } + let start_span = Span::new(self.next_pos, span.lo(), span.ctxt(), None); let mut start_snip = snippet_with_applicability(self.cx, start_span, "..", &mut self.applicability); @@ -858,7 +871,12 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> { // full identifier that includes projection (i.e.: `fp.field`) let ident_str_with_proj = snippet(self.cx, span, "..").to_string(); - if cmt.place.projections.is_empty() { + // Make sure to get in all projections if we're on a `matches!` + if let Node::Pat(pat) = self.cx.tcx.hir_node(id) + && pat.hir_id != self.closure_arg_id + { + let _ = write!(self.suggestion_start, "{start_snip}{ident_str_with_proj}"); + } else if cmt.place.projections.is_empty() { // handle item without any projection, that needs an explicit borrowing // i.e.: suggest `&x` instead of `x` let _: fmt::Result = write!(self.suggestion_start, "{start_snip}&{ident_str}"); diff --git a/tests/ui/search_is_some.rs b/tests/ui/search_is_some.rs index 4143b8bfba58..802d27449abf 100644 --- a/tests/ui/search_is_some.rs +++ b/tests/ui/search_is_some.rs @@ -87,3 +87,18 @@ fn is_none() { let _ = (0..1).find(some_closure).is_none(); //~^ search_is_some } + +#[allow(clippy::match_like_matches_macro)] +fn issue15102() { + let values = [None, Some(3)]; + let has_even = values + //~^ search_is_some + .iter() + .find(|v| match v { + Some(x) if x % 2 == 0 => true, + _ => false, + }) + .is_some(); + + println!("{has_even}"); +} diff --git a/tests/ui/search_is_some.stderr b/tests/ui/search_is_some.stderr index d9a43c8915e8..d5412f901110 100644 --- a/tests/ui/search_is_some.stderr +++ b/tests/ui/search_is_some.stderr @@ -90,5 +90,20 @@ error: called `is_none()` after searching an `Iterator` with `find` LL | let _ = (0..1).find(some_closure).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!(0..1).any(some_closure)` -error: aborting due to 8 previous errors +error: called `is_some()` after searching an `Iterator` with `find` + --> tests/ui/search_is_some.rs:94:20 + | +LL | let has_even = values + | ____________________^ +LL | | +LL | | .iter() +LL | | .find(|v| match v { +... | +LL | | }) +LL | | .is_some(); + | |__________________^ + | + = help: this is more succinctly expressed by calling `any()` + +error: aborting due to 9 previous errors diff --git a/tests/ui/search_is_some_fixable_some.fixed b/tests/ui/search_is_some_fixable_some.fixed index 42b39b33b575..c7a4422f3737 100644 --- a/tests/ui/search_is_some_fixable_some.fixed +++ b/tests/ui/search_is_some_fixable_some.fixed @@ -289,3 +289,10 @@ mod issue9120 { //~^ search_is_some } } + +fn issue15102() { + let values = [None, Some(3)]; + let has_even = values.iter().any(|v| matches!(&v, Some(x) if x % 2 == 0)); + //~^ search_is_some + println!("{has_even}"); +} diff --git a/tests/ui/search_is_some_fixable_some.rs b/tests/ui/search_is_some_fixable_some.rs index ca4f4d941cb2..d6b1c67c9718 100644 --- a/tests/ui/search_is_some_fixable_some.rs +++ b/tests/ui/search_is_some_fixable_some.rs @@ -297,3 +297,10 @@ mod issue9120 { //~^ search_is_some } } + +fn issue15102() { + let values = [None, Some(3)]; + let has_even = values.iter().find(|v| matches!(v, Some(x) if x % 2 == 0)).is_some(); + //~^ search_is_some + println!("{has_even}"); +} diff --git a/tests/ui/search_is_some_fixable_some.stderr b/tests/ui/search_is_some_fixable_some.stderr index 8291f48d43c4..551a670d937f 100644 --- a/tests/ui/search_is_some_fixable_some.stderr +++ b/tests/ui/search_is_some_fixable_some.stderr @@ -289,5 +289,11 @@ error: called `is_some()` after searching an `Iterator` with `find` LL | let _ = v.iter().find(|x: &&u32| (*arg_no_deref_dyn)(x)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x: &u32| (*arg_no_deref_dyn)(&x))` -error: aborting due to 46 previous errors +error: called `is_some()` after searching an `Iterator` with `find` + --> tests/ui/search_is_some_fixable_some.rs:303:34 + | +LL | let has_even = values.iter().find(|v| matches!(v, Some(x) if x % 2 == 0)).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|v| matches!(&v, Some(x) if x % 2 == 0))` + +error: aborting due to 47 previous errors From ba6485d61c5dd6c106c5f93b6d581a88f630cbc2 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Sun, 15 Jun 2025 21:28:14 +0800 Subject: [PATCH 10/65] fix: `match_single_binding` wrongly handles scope --- .../src/matches/match_single_binding.rs | 222 +++++++++++++++--- clippy_utils/src/source.rs | 34 ++- tests/ui/match_single_binding.fixed | 53 +++++ tests/ui/match_single_binding.rs | 61 +++++ tests/ui/match_single_binding.stderr | 96 +++++++- 5 files changed, 429 insertions(+), 37 deletions(-) diff --git a/clippy_lints/src/matches/match_single_binding.rs b/clippy_lints/src/matches/match_single_binding.rs index 6a76c6cedd0d..9790e63821da 100644 --- a/clippy_lints/src/matches/match_single_binding.rs +++ b/clippy_lints/src/matches/match_single_binding.rs @@ -1,11 +1,18 @@ +use std::ops::ControlFlow; + use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::macros::HirNode; -use clippy_utils::source::{indent_of, snippet, snippet_block_with_context, snippet_with_context}; +use clippy_utils::source::{ + RelativeIndent, indent_of, reindent_multiline_relative, snippet, snippet_block_with_context, snippet_with_context, +}; use clippy_utils::{is_refutable, peel_blocks}; +use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind, StmtKind}; +use rustc_hir::def::Res; +use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_path, walk_stmt}; +use rustc_hir::{Arm, Block, Expr, ExprKind, HirId, Node, PatKind, Path, Stmt, StmtKind}; use rustc_lint::LateContext; -use rustc_span::Span; +use rustc_span::{Span, Symbol}; use super::MATCH_SINGLE_BINDING; @@ -50,10 +57,11 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e cx, (ex, expr), (bind_names, matched_vars), - &snippet_body, + snippet_body, &mut app, Some(span), true, + is_var_binding_used_later(cx, expr, &arms[0]), ); span_lint_and_sugg( @@ -83,10 +91,11 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e cx, (ex, expr), (bind_names, matched_vars), - &snippet_body, + snippet_body, &mut app, None, true, + is_var_binding_used_later(cx, expr, &arms[0]), ); (expr.span, sugg) }, @@ -108,10 +117,11 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e cx, (ex, expr), (bind_names, matched_vars), - &snippet_body, + snippet_body, &mut app, None, false, + true, ); span_lint_and_sugg( @@ -139,6 +149,125 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e } } +struct VarBindingVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + identifiers: FxHashSet, +} + +impl<'tcx> Visitor<'tcx> for VarBindingVisitor<'_, 'tcx> { + type Result = ControlFlow<()>; + + fn visit_path(&mut self, path: &Path<'tcx>, _: HirId) -> Self::Result { + if let Res::Local(_) = path.res + && let [segment] = path.segments + && self.identifiers.contains(&segment.ident.name) + { + return ControlFlow::Break(()); + } + + walk_path(self, path) + } + + fn visit_block(&mut self, block: &'tcx Block<'tcx>) -> Self::Result { + let before = self.identifiers.clone(); + walk_block(self, block)?; + self.identifiers = before; + ControlFlow::Continue(()) + } + + fn visit_stmt(&mut self, stmt: &'tcx Stmt<'tcx>) -> Self::Result { + if let StmtKind::Let(let_stmt) = stmt.kind { + if let Some(init) = let_stmt.init { + self.visit_expr(init)?; + } + + let_stmt.pat.each_binding(|_, _, _, ident| { + self.identifiers.remove(&ident.name); + }); + } + walk_stmt(self, stmt) + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) -> Self::Result { + match expr.kind { + ExprKind::If( + Expr { + kind: ExprKind::Let(let_expr), + .. + }, + then, + else_, + ) => { + self.visit_expr(let_expr.init)?; + let before = self.identifiers.clone(); + let_expr.pat.each_binding(|_, _, _, ident| { + self.identifiers.remove(&ident.name); + }); + + self.visit_expr(then)?; + self.identifiers = before; + if let Some(else_) = else_ { + self.visit_expr(else_)?; + } + ControlFlow::Continue(()) + }, + ExprKind::Closure(closure) => { + let body = self.cx.tcx.hir_body(closure.body); + let before = self.identifiers.clone(); + for param in body.params { + param.pat.each_binding(|_, _, _, ident| { + self.identifiers.remove(&ident.name); + }); + } + self.visit_expr(body.value)?; + self.identifiers = before; + ControlFlow::Continue(()) + }, + ExprKind::Match(expr, arms, _) => { + self.visit_expr(expr)?; + for arm in arms { + let before = self.identifiers.clone(); + arm.pat.each_binding(|_, _, _, ident| { + self.identifiers.remove(&ident.name); + }); + if let Some(guard) = arm.guard { + self.visit_expr(guard)?; + } + self.visit_expr(arm.body)?; + self.identifiers = before; + } + ControlFlow::Continue(()) + }, + _ => walk_expr(self, expr), + } + } +} + +fn is_var_binding_used_later(cx: &LateContext<'_>, expr: &Expr<'_>, arm: &Arm<'_>) -> bool { + let Node::Stmt(stmt) = cx.tcx.parent_hir_node(expr.hir_id) else { + return false; + }; + let Node::Block(block) = cx.tcx.parent_hir_node(stmt.hir_id) else { + return false; + }; + + let mut identifiers = FxHashSet::default(); + arm.pat.each_binding(|_, _, _, ident| { + identifiers.insert(ident.name); + }); + + let mut visitor = VarBindingVisitor { cx, identifiers }; + block + .stmts + .iter() + .skip_while(|s| s.hir_id != stmt.hir_id) + .skip(1) + .any(|stmt| matches!(visitor.visit_stmt(stmt), ControlFlow::Break(()))) + || block + .expr + .is_some_and(|expr| matches!(visitor.visit_expr(expr), ControlFlow::Break(()))) +} + /// Returns true if the `ex` match expression is in a local (`let`) or assign expression fn opt_parent_assign_span<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option { if let Node::Expr(parent_arm_expr) = cx.tcx.parent_hir_node(ex.hir_id) { @@ -161,47 +290,58 @@ fn opt_parent_assign_span<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option(cx: &LateContext<'a>, match_expr: &Expr<'a>) -> bool { +fn expr_in_nested_block(cx: &LateContext<'_>, match_expr: &Expr<'_>) -> bool { + if let Node::Block(block) = cx.tcx.parent_hir_node(match_expr.hir_id) { + return block + .expr + .map_or_else(|| matches!(block.stmts, [_]), |_| block.stmts.is_empty()); + } + false +} + +fn expr_must_have_curlies(cx: &LateContext<'_>, match_expr: &Expr<'_>) -> bool { let parent = cx.tcx.parent_hir_node(match_expr.hir_id); - matches!( - parent, - Node::Expr(Expr { - kind: ExprKind::Closure { .. }, - .. - }) | Node::AnonConst(..) + if let Node::Expr(Expr { + kind: ExprKind::Closure { .. }, + .. + }) + | Node::AnonConst(..) = parent + { + return true; + } + + if let Node::Arm(arm) = &cx.tcx.parent_hir_node(match_expr.hir_id) + && let ExprKind::Match(..) = arm.body.kind + { + return true; + } + + false +} + +fn reindent_snippet_if_in_block(snippet_body: &str, has_assignment: bool) -> String { + if has_assignment || !snippet_body.starts_with('{') { + return reindent_multiline_relative(snippet_body, true, RelativeIndent::Add(0)); + } + + reindent_multiline_relative( + snippet_body.trim_start_matches('{').trim_end_matches('}').trim(), + false, + RelativeIndent::Sub(4), ) } +#[expect(clippy::too_many_arguments)] fn sugg_with_curlies<'a>( cx: &LateContext<'a>, (ex, match_expr): (&Expr<'a>, &Expr<'a>), (bind_names, matched_vars): (Span, Span), - snippet_body: &str, + mut snippet_body: String, applicability: &mut Applicability, assignment: Option, needs_var_binding: bool, + is_var_binding_used_later: bool, ) -> String { - let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0)); - - let (mut cbrace_start, mut cbrace_end) = (String::new(), String::new()); - if expr_parent_requires_curlies(cx, match_expr) { - cbrace_end = format!("\n{indent}}}"); - // Fix body indent due to the closure - indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); - cbrace_start = format!("{{\n{indent}"); - } - - // If the parent is already an arm, and the body is another match statement, - // we need curly braces around suggestion - if let Node::Arm(arm) = &cx.tcx.parent_hir_node(match_expr.hir_id) - && let ExprKind::Match(..) = arm.body.kind - { - cbrace_end = format!("\n{indent}}}"); - // Fix body indent due to the match - indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); - cbrace_start = format!("{{\n{indent}"); - } - let assignment_str = assignment.map_or_else(String::new, |span| { let mut s = snippet(cx, span, "..").to_string(); s.push_str(" = "); @@ -221,5 +361,17 @@ fn sugg_with_curlies<'a>( .to_string() }; + let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0)); + let (mut cbrace_start, mut cbrace_end) = (String::new(), String::new()); + if !expr_in_nested_block(cx, match_expr) + && ((needs_var_binding && is_var_binding_used_later) || expr_must_have_curlies(cx, match_expr)) + { + cbrace_end = format!("\n{indent}}}"); + // Fix body indent due to the closure + indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); + cbrace_start = format!("{{\n{indent}"); + snippet_body = reindent_snippet_if_in_block(&snippet_body, !assignment_str.is_empty()); + } + format!("{cbrace_start}{scrutinee};\n{indent}{assignment_str}{snippet_body}{cbrace_end}") } diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index 7f2bf99daff2..b490902429f5 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -18,7 +18,7 @@ use rustc_span::{ }; use std::borrow::Cow; use std::fmt; -use std::ops::{Deref, Index, Range}; +use std::ops::{Add, Deref, Index, Range}; pub trait HasSession { fn sess(&self) -> &Session; @@ -476,6 +476,38 @@ pub fn position_before_rarrow(s: &str) -> Option { }) } +pub enum RelativeIndent { + Add(usize), + Sub(usize), +} + +impl Add for usize { + type Output = usize; + + fn add(self, rhs: RelativeIndent) -> Self::Output { + match rhs { + RelativeIndent::Add(n) => self + n, + RelativeIndent::Sub(n) => self.saturating_sub(n), + } + } +} + +/// Reindents a multiline string with possibility of ignoring the first line and relative +/// indentation. +pub fn reindent_multiline_relative(s: &str, ignore_first: bool, relative_indent: RelativeIndent) -> String { + fn indent_of_string(s: &str) -> usize { + s.find(|c: char| !c.is_whitespace()).unwrap_or(0) + } + + let mut indent = 0; + if let Some(line) = s.lines().nth(usize::from(ignore_first)) { + let line_indent = indent_of_string(line); + indent = line_indent + relative_indent; + } + + reindent_multiline(s, ignore_first, Some(indent)) +} + /// Reindent a multiline string with possibility of ignoring the first line. pub fn reindent_multiline(s: &str, ignore_first: bool, indent: Option) -> String { let s_space = reindent_multiline_inner(s, ignore_first, indent, ' '); diff --git a/tests/ui/match_single_binding.fixed b/tests/ui/match_single_binding.fixed index e11dea352049..d8d865ee44cf 100644 --- a/tests/ui/match_single_binding.fixed +++ b/tests/ui/match_single_binding.fixed @@ -204,3 +204,56 @@ mod issue14991 { }], } } + +mod issue15018 { + fn used_later(a: i32, b: i32, c: i32) { + let x = 1; + { + let (x, y, z) = (a, b, c); + 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) + } + + #[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); + let x = 1; + println!("x = {x}"); + } + + #[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); + if let (x, y, z) = (a, b, c) { + println!("{} {} {}", x, y, z) + } + + { + let x: i32 = 1; + { + let (x, y, z) = (a, b, c); + println!("{} {} {}", x, y, z); + } + if let (x, y, z) = (a, x, c) { + println!("{} {} {}", x, y, z) + } + } + + { + let (x, y, z) = (a, b, c); + println!("{} {} {}", x, y, z); + let fn_ = |y| { + println!("{} {} {}", a, b, y); + }; + fn_(c); + } + } +} diff --git a/tests/ui/match_single_binding.rs b/tests/ui/match_single_binding.rs index d498da30fc87..ecff945fb913 100644 --- a/tests/ui/match_single_binding.rs +++ b/tests/ui/match_single_binding.rs @@ -267,3 +267,64 @@ mod issue14991 { }], } } + +mod issue15018 { + fn used_later(a: i32, b: i32, c: i32) { + let x = 1; + match (a, b, c) { + //~^ match_single_binding + (x, y, z) => println!("{} {} {}", x, y, z), + } + println!("x = {x}"); + } + + fn not_used_later(a: i32, b: i32, c: i32) { + match (a, b, c) { + //~^ match_single_binding + (x, y, z) => println!("{} {} {}", x, y, z), + } + } + + #[allow(irrefutable_let_patterns)] + 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), + } + let x = 1; + println!("x = {x}"); + } + + #[allow(irrefutable_let_patterns)] + 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), + } + if let (x, y, z) = (a, b, c) { + println!("{} {} {}", x, y, z) + } + + { + let x: i32 = 1; + match (a, b, c) { + //~^ match_single_binding + (x, y, z) => println!("{} {} {}", x, y, z), + } + if let (x, y, z) = (a, x, c) { + println!("{} {} {}", x, y, z) + } + } + + { + match (a, b, c) { + //~^ match_single_binding + (x, y, z) => println!("{} {} {}", x, y, z), + } + let fn_ = |y| { + println!("{} {} {}", a, b, y); + }; + fn_(c); + } + } +} diff --git a/tests/ui/match_single_binding.stderr b/tests/ui/match_single_binding.stderr index f274f80c81da..789626de6035 100644 --- a/tests/ui/match_single_binding.stderr +++ b/tests/ui/match_single_binding.stderr @@ -411,5 +411,99 @@ LL ~ let _n = 1; LL + 42 | -error: aborting due to 29 previous errors +error: this match could be written as a `let` statement + --> tests/ui/match_single_binding.rs:274:9 + | +LL | / match (a, b, c) { +LL | | +LL | | (x, y, z) => println!("{} {} {}", x, y, z), +LL | | } + | |_________^ + | +help: consider using a `let` statement + | +LL ~ { +LL + let (x, y, z) = (a, b, c); +LL + println!("{} {} {}", x, y, z); +LL + } + | + +error: this match could be written as a `let` statement + --> tests/ui/match_single_binding.rs:282:9 + | +LL | / match (a, b, c) { +LL | | +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) + | + +error: this match could be written as a `let` statement + --> tests/ui/match_single_binding.rs:290:9 + | +LL | / match (a, b, c) { +LL | | +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); + | + +error: this match could be written as a `let` statement + --> tests/ui/match_single_binding.rs:300:9 + | +LL | / match (a, b, c) { +LL | | +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); + | + +error: this match could be written as a `let` statement + --> tests/ui/match_single_binding.rs:310:13 + | +LL | / match (a, b, c) { +LL | | +LL | | (x, y, z) => println!("{} {} {}", x, y, z), +LL | | } + | |_____________^ + | +help: consider using a `let` statement + | +LL ~ { +LL + let (x, y, z) = (a, b, c); +LL + println!("{} {} {}", x, y, z); +LL + } + | + +error: this match could be written as a `let` statement + --> tests/ui/match_single_binding.rs:320:13 + | +LL | / match (a, b, c) { +LL | | +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); + | + +error: aborting due to 35 previous errors From 48df86f37ddf2904b60f31744e52f56234e4d95c Mon Sep 17 00:00:00 2001 From: yanglsh Date: Tue, 15 Jul 2025 22:55:41 +0800 Subject: [PATCH 11/65] fix: `match_single_binding` suggests wrongly inside binary expr --- .../src/matches/match_single_binding.rs | 36 ++++++--- clippy_utils/src/lib.rs | 80 +++++++++++-------- clippy_utils/src/source.rs | 34 +------- tests/ui/match_single_binding.fixed | 9 +++ tests/ui/match_single_binding.rs | 15 ++++ tests/ui/match_single_binding.stderr | 22 ++++- 6 files changed, 119 insertions(+), 77 deletions(-) diff --git a/clippy_lints/src/matches/match_single_binding.rs b/clippy_lints/src/matches/match_single_binding.rs index 9790e63821da..9bbc6042fe1a 100644 --- a/clippy_lints/src/matches/match_single_binding.rs +++ b/clippy_lints/src/matches/match_single_binding.rs @@ -2,10 +2,8 @@ use std::ops::ControlFlow; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::macros::HirNode; -use clippy_utils::source::{ - RelativeIndent, indent_of, reindent_multiline_relative, snippet, snippet_block_with_context, snippet_with_context, -}; -use clippy_utils::{is_refutable, peel_blocks}; +use clippy_utils::source::{indent_of, reindent_multiline, snippet, snippet_block_with_context, snippet_with_context}; +use clippy_utils::{is_expr_identity_of_pat, is_refutable, peel_blocks}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::def::Res; @@ -86,6 +84,18 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e snippet_with_context(cx, pat_span, ctxt, "..", &mut app).0 ), ), + None if is_expr_identity_of_pat(cx, arms[0].pat, ex, false) => { + span_lint_and_sugg( + cx, + MATCH_SINGLE_BINDING, + expr.span, + "this match could be replaced by its body itself", + "consider using the match body instead", + snippet_body, + Applicability::MachineApplicable, + ); + return; + }, None => { let sugg = sugg_with_curlies( cx, @@ -302,7 +312,7 @@ fn expr_in_nested_block(cx: &LateContext<'_>, match_expr: &Expr<'_>) -> bool { fn expr_must_have_curlies(cx: &LateContext<'_>, match_expr: &Expr<'_>) -> bool { let parent = cx.tcx.parent_hir_node(match_expr.hir_id); if let Node::Expr(Expr { - kind: ExprKind::Closure { .. }, + kind: ExprKind::Closure(..) | ExprKind::Binary(..), .. }) | Node::AnonConst(..) = parent @@ -319,15 +329,23 @@ fn expr_must_have_curlies(cx: &LateContext<'_>, match_expr: &Expr<'_>) -> bool { false } +fn indent_of_nth_line(snippet: &str, nth: usize) -> Option { + snippet + .lines() + .nth(nth) + .and_then(|s| s.find(|c: char| !c.is_whitespace())) +} + fn reindent_snippet_if_in_block(snippet_body: &str, has_assignment: bool) -> String { if has_assignment || !snippet_body.starts_with('{') { - return reindent_multiline_relative(snippet_body, true, RelativeIndent::Add(0)); + return reindent_multiline(snippet_body, true, indent_of_nth_line(snippet_body, 1)); } - reindent_multiline_relative( - snippet_body.trim_start_matches('{').trim_end_matches('}').trim(), + let snippet_body = snippet_body.trim_start_matches('{').trim_end_matches('}').trim(); + reindent_multiline( + snippet_body, false, - RelativeIndent::Sub(4), + indent_of_nth_line(snippet_body, 0).map(|indent| indent.saturating_sub(4)), ) } diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 8b9cd6a54dd6..febc13d89598 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1900,39 +1900,6 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { /// /// Consider calling [`is_expr_untyped_identity_function`] or [`is_expr_identity_function`] instead. fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool { - fn check_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<'_>) -> bool { - if cx - .typeck_results() - .pat_binding_modes() - .get(pat.hir_id) - .is_some_and(|mode| matches!(mode.0, ByRef::Yes(_))) - { - // If the parameter is `(x, y)` of type `&(T, T)`, or `[x, y]` of type `&[T; 2]`, then - // due to match ergonomics, the inner patterns become references. Don't consider this - // the identity function as that changes types. - return false; - } - - match (pat.kind, expr.kind) { - (PatKind::Binding(_, id, _, _), _) => { - path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty() - }, - (PatKind::Tuple(pats, dotdot), ExprKind::Tup(tup)) - if dotdot.as_opt_usize().is_none() && pats.len() == tup.len() => - { - pats.iter().zip(tup).all(|(pat, expr)| check_pat(cx, pat, expr)) - }, - (PatKind::Slice(before, slice, after), ExprKind::Array(arr)) - if slice.is_none() && before.len() + after.len() == arr.len() => - { - (before.iter().chain(after)) - .zip(arr) - .all(|(pat, expr)| check_pat(cx, pat, expr)) - }, - _ => false, - } - } - let [param] = func.params else { return false; }; @@ -1965,11 +1932,56 @@ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool { return false; } }, - _ => return check_pat(cx, param.pat, expr), + _ => return is_expr_identity_of_pat(cx, param.pat, expr, true), } } } +/// Checks if the given expression is an identity representation of the given pattern: +/// * `x` is the identity representation of `x` +/// * `(x, y)` is the identity representation of `(x, y)` +/// * `[x, y]` is the identity representation of `[x, y]` +/// +/// Note that `by_hir` is used to determine bindings are checked by their `HirId` or by their name. +/// This can be useful when checking patterns in `let` bindings or `match` arms. +pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<'_>, by_hir: bool) -> bool { + if cx + .typeck_results() + .pat_binding_modes() + .get(pat.hir_id) + .is_some_and(|mode| matches!(mode.0, ByRef::Yes(_))) + { + // If the parameter is `(x, y)` of type `&(T, T)`, or `[x, y]` of type `&[T; 2]`, then + // due to match ergonomics, the inner patterns become references. Don't consider this + // the identity function as that changes types. + return false; + } + + match (pat.kind, expr.kind) { + (PatKind::Binding(_, id, _, _), _) if by_hir => { + path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty() + }, + (PatKind::Binding(_, _, ident, _), ExprKind::Path(QPath::Resolved(_, path))) => { + matches!(path.segments, [ segment] if segment.ident.name == ident.name) + }, + (PatKind::Tuple(pats, dotdot), ExprKind::Tup(tup)) + if dotdot.as_opt_usize().is_none() && pats.len() == tup.len() => + { + pats.iter() + .zip(tup) + .all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir)) + }, + (PatKind::Slice(before, slice, after), ExprKind::Array(arr)) + if slice.is_none() && before.len() + after.len() == arr.len() => + { + (before.iter().chain(after)) + .zip(arr) + .all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir)) + }, + _ => false, + } +} + /// This is the same as [`is_expr_identity_function`], but does not consider closures /// with type annotations for its bindings (or similar) as identity functions: /// * `|x: u8| x` diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index b490902429f5..7f2bf99daff2 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -18,7 +18,7 @@ use rustc_span::{ }; use std::borrow::Cow; use std::fmt; -use std::ops::{Add, Deref, Index, Range}; +use std::ops::{Deref, Index, Range}; pub trait HasSession { fn sess(&self) -> &Session; @@ -476,38 +476,6 @@ pub fn position_before_rarrow(s: &str) -> Option { }) } -pub enum RelativeIndent { - Add(usize), - Sub(usize), -} - -impl Add for usize { - type Output = usize; - - fn add(self, rhs: RelativeIndent) -> Self::Output { - match rhs { - RelativeIndent::Add(n) => self + n, - RelativeIndent::Sub(n) => self.saturating_sub(n), - } - } -} - -/// Reindents a multiline string with possibility of ignoring the first line and relative -/// indentation. -pub fn reindent_multiline_relative(s: &str, ignore_first: bool, relative_indent: RelativeIndent) -> String { - fn indent_of_string(s: &str) -> usize { - s.find(|c: char| !c.is_whitespace()).unwrap_or(0) - } - - let mut indent = 0; - if let Some(line) = s.lines().nth(usize::from(ignore_first)) { - let line_indent = indent_of_string(line); - indent = line_indent + relative_indent; - } - - reindent_multiline(s, ignore_first, Some(indent)) -} - /// Reindent a multiline string with possibility of ignoring the first line. pub fn reindent_multiline(s: &str, ignore_first: bool, indent: Option) -> String { let s_space = reindent_multiline_inner(s, ignore_first, indent, ' '); diff --git a/tests/ui/match_single_binding.fixed b/tests/ui/match_single_binding.fixed index d8d865ee44cf..e29fb87dbc30 100644 --- a/tests/ui/match_single_binding.fixed +++ b/tests/ui/match_single_binding.fixed @@ -257,3 +257,12 @@ mod issue15018 { } } } + +#[allow(clippy::short_circuit_statement)] +fn issue15269(a: usize, b: usize, c: usize) -> bool { + a < b + && b < c; + + a < b + && b < c +} diff --git a/tests/ui/match_single_binding.rs b/tests/ui/match_single_binding.rs index ecff945fb913..ede1ab32beb5 100644 --- a/tests/ui/match_single_binding.rs +++ b/tests/ui/match_single_binding.rs @@ -328,3 +328,18 @@ mod issue15018 { } } } + +#[allow(clippy::short_circuit_statement)] +fn issue15269(a: usize, b: usize, c: usize) -> bool { + a < b + && match b { + //~^ match_single_binding + b => b < c, + }; + + a < b + && match (a, b) { + //~^ match_single_binding + (a, b) => b < c, + } +} diff --git a/tests/ui/match_single_binding.stderr b/tests/ui/match_single_binding.stderr index 789626de6035..eea71777890e 100644 --- a/tests/ui/match_single_binding.stderr +++ b/tests/ui/match_single_binding.stderr @@ -505,5 +505,25 @@ LL ~ let (x, y, z) = (a, b, c); LL + println!("{} {} {}", x, y, z); | -error: aborting due to 35 previous errors +error: this match could be replaced by its body itself + --> tests/ui/match_single_binding.rs:335:12 + | +LL | && match b { + | ____________^ +LL | | +LL | | b => b < c, +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 + | +LL | && match (a, b) { + | ____________^ +LL | | +LL | | (a, b) => b < c, +LL | | } + | |_________^ help: consider using the match body instead: `b < c` + +error: aborting due to 37 previous errors From af19ff8cc89bb6a010faaf110eb63e1fbce49b1a Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 17 Jul 2025 19:16:39 +0200 Subject: [PATCH 12/65] fix `collapsable_if` when the inner `if` is in parens --- clippy_lints/src/collapsible_if.rs | 51 +++++++++++++++++++++++++++++- tests/ui/collapsible_if.fixed | 8 +++++ tests/ui/collapsible_if.rs | 9 ++++++ tests/ui/collapsible_if.stderr | 20 +++++++++++- 4 files changed, 86 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index 1854d86c53b2..da0f0b2f1be9 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -187,7 +187,8 @@ impl CollapsibleIf { .with_leading_whitespace(cx) .into_span() }; - let inner_if = inner.span.split_at(2).0; + let (paren_start, inner_if_span, paren_end) = peel_parens(cx.tcx.sess.source_map(), inner.span); + let inner_if = inner_if_span.split_at(2).0; let mut sugg = vec![ // Remove the outer then block `{` (then_open_bracket, String::new()), @@ -196,6 +197,17 @@ impl CollapsibleIf { // Replace inner `if` by `&&` (inner_if, String::from("&&")), ]; + + if !paren_start.is_empty() { + // Remove any leading parentheses '(' + sugg.push((paren_start, String::new())); + } + + if !paren_end.is_empty() { + // Remove any trailing parentheses ')' + sugg.push((paren_end, String::new())); + } + sugg.extend(parens_around(check)); sugg.extend(parens_around(check_inner)); @@ -285,3 +297,40 @@ fn span_extract_keyword(sm: &SourceMap, span: Span, keyword: &str) -> Option (Span, Span, Span) { + use crate::rustc_span::Pos; + use rustc_span::SpanData; + + let start = span.shrink_to_lo(); + let end = span.shrink_to_hi(); + + loop { + let data = span.data(); + let snippet = sm.span_to_snippet(span).unwrap(); + + let trim_start = snippet.len() - snippet.trim_start().len(); + let trim_end = snippet.len() - snippet.trim_end().len(); + + let trimmed = snippet.trim(); + + if trimmed.starts_with('(') && trimmed.ends_with(')') { + // Try to remove one layer of parens by adjusting the span + span = SpanData { + lo: data.lo + BytePos::from_usize(trim_start + 1), + hi: data.hi - BytePos::from_usize(trim_end + 1), + ctxt: data.ctxt, + parent: data.parent, + } + .span(); + + continue; + } + + break; + } + + (start.with_hi(span.lo()), + span, + end.with_lo(span.hi())) +} diff --git a/tests/ui/collapsible_if.fixed b/tests/ui/collapsible_if.fixed index 77bc791ea8e9..f7c840c4cc1e 100644 --- a/tests/ui/collapsible_if.fixed +++ b/tests/ui/collapsible_if.fixed @@ -163,3 +163,11 @@ fn issue14799() { if true {} }; } + +fn in_parens() { + if true + && true { + println!("In parens, linted"); + } + //~^^^^^ collapsible_if +} diff --git a/tests/ui/collapsible_if.rs b/tests/ui/collapsible_if.rs index d30df157d5eb..4faebcb0ca0d 100644 --- a/tests/ui/collapsible_if.rs +++ b/tests/ui/collapsible_if.rs @@ -173,3 +173,12 @@ fn issue14799() { if true {} }; } + +fn in_parens() { + if true { + (if true { + println!("In parens, linted"); + }) + } + //~^^^^^ collapsible_if +} diff --git a/tests/ui/collapsible_if.stderr b/tests/ui/collapsible_if.stderr index 32c6b0194030..a685cc2e9291 100644 --- a/tests/ui/collapsible_if.stderr +++ b/tests/ui/collapsible_if.stderr @@ -190,5 +190,23 @@ LL | // This is a comment, do not collapse code to it LL ~ ; 3 | -error: aborting due to 11 previous errors +error: this `if` statement can be collapsed + --> tests/ui/collapsible_if.rs:178:5 + | +LL | / if true { +LL | | (if true { +LL | | println!("In parens, linted"); +LL | | }) +LL | | } + | |_____^ + | +help: collapse nested if block + | +LL ~ if true +LL ~ && true { +LL | println!("In parens, linted"); +LL ~ } + | + +error: aborting due to 12 previous errors From 58c00f3b7139a689492a48ee45b45b951aeb2427 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 17 Jul 2025 19:41:40 +0200 Subject: [PATCH 13/65] fix `collapsable_else_if` when the inner `if` is in parens --- clippy_lints/src/collapsible_if.rs | 56 ++++++++++++++--------------- tests/ui/collapsible_else_if.fixed | 22 ++++++++++++ tests/ui/collapsible_else_if.rs | 24 +++++++++++++ tests/ui/collapsible_else_if.stderr | 11 +++++- tests/ui/collapsible_if.fixed | 10 ++++++ tests/ui/collapsible_if.rs | 10 ++++++ 6 files changed, 104 insertions(+), 29 deletions(-) diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index da0f0b2f1be9..e3103e2d3016 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -136,6 +136,9 @@ impl CollapsibleIf { return; } + // Peel off any parentheses. + let (_, else_block_span, _) = peel_parens(cx.tcx.sess.source_map(), else_.span); + // Prevent "elseif" // Check that the "else" is followed by whitespace let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() { @@ -152,7 +155,7 @@ impl CollapsibleIf { if requires_space { " " } else { "" }, snippet_block_with_applicability( cx, - else_.span, + else_block_span, "..", Some(else_block.span), &mut applicability @@ -298,39 +301,36 @@ fn span_extract_keyword(sm: &SourceMap, span: Span, keyword: &str) -> Option (Span, Span, Span) { use crate::rustc_span::Pos; - use rustc_span::SpanData; let start = span.shrink_to_lo(); let end = span.shrink_to_hi(); - loop { - let data = span.data(); - let snippet = sm.span_to_snippet(span).unwrap(); - - let trim_start = snippet.len() - snippet.trim_start().len(); - let trim_end = snippet.len() - snippet.trim_end().len(); - - let trimmed = snippet.trim(); - - if trimmed.starts_with('(') && trimmed.ends_with(')') { - // Try to remove one layer of parens by adjusting the span - span = SpanData { - lo: data.lo + BytePos::from_usize(trim_start + 1), - hi: data.hi - BytePos::from_usize(trim_end + 1), - ctxt: data.ctxt, - parent: data.parent, - } - .span(); - - continue; - } - - break; + let snippet = sm.span_to_snippet(span).unwrap(); + if let Some((trim_start, _, trim_end)) = peel_parens_str(&snippet) { + let mut data = span.data(); + data.lo = data.lo + BytePos::from_usize(trim_start); + data.hi = data.hi - BytePos::from_usize(trim_end); + span = data.span(); } - (start.with_hi(span.lo()), - span, - end.with_lo(span.hi())) + (start.with_hi(span.lo()), span, end.with_lo(span.hi())) +} + +fn peel_parens_str(snippet: &str) -> Option<(usize, &str, usize)> { + let trimmed = snippet.trim(); + if !(trimmed.starts_with('(') && trimmed.ends_with(')')) { + return None; + } + + let trim_start = (snippet.len() - snippet.trim_start().len()) + 1; + let trim_end = (snippet.len() - snippet.trim_end().len()) + 1; + + let inner = snippet.get(trim_start..snippet.len() - trim_end)?; + Some(match peel_parens_str(inner) { + None => (trim_start, inner, trim_end), + Some((start, inner, end)) => (trim_start + start, inner, trim_end + end), + }) } diff --git a/tests/ui/collapsible_else_if.fixed b/tests/ui/collapsible_else_if.fixed index fed75244c6f7..3d709fe9b8e0 100644 --- a/tests/ui/collapsible_else_if.fixed +++ b/tests/ui/collapsible_else_if.fixed @@ -104,3 +104,25 @@ fn issue14799() { } } } + +fn in_parens() { + let x = "hello"; + let y = "world"; + + if x == "hello" { + print!("Hello "); + } else if y == "world" { println!("world") } else { println!("!") } + //~^^^ collapsible_else_if +} + +fn in_brackets() { + let x = "hello"; + let y = "world"; + + // There is no lint when the inner `if` is in a block. + if x == "hello" { + print!("Hello "); + } else { + { if y == "world" { println!("world") } else { println!("!") } } + } +} diff --git a/tests/ui/collapsible_else_if.rs b/tests/ui/collapsible_else_if.rs index e50e781fb698..51868e039086 100644 --- a/tests/ui/collapsible_else_if.rs +++ b/tests/ui/collapsible_else_if.rs @@ -120,3 +120,27 @@ fn issue14799() { } } } + +fn in_parens() { + let x = "hello"; + let y = "world"; + + if x == "hello" { + print!("Hello "); + } else { + (if y == "world" { println!("world") } else { println!("!") }) + } + //~^^^ collapsible_else_if +} + +fn in_brackets() { + let x = "hello"; + let y = "world"; + + // There is no lint when the inner `if` is in a block. + if x == "hello" { + print!("Hello "); + } else { + { if y == "world" { println!("world") } else { println!("!") } } + } +} diff --git a/tests/ui/collapsible_else_if.stderr b/tests/ui/collapsible_else_if.stderr index 7d80894cadbb..1a7bcec7fd5d 100644 --- a/tests/ui/collapsible_else_if.stderr +++ b/tests/ui/collapsible_else_if.stderr @@ -150,5 +150,14 @@ LL | | if false {} LL | | } | |_____^ help: collapse nested if block: `if false {}` -error: aborting due to 8 previous errors +error: this `else { if .. }` block can be collapsed + --> tests/ui/collapsible_else_if.rs:130:12 + | +LL | } else { + | ____________^ +LL | | (if y == "world" { println!("world") } else { println!("!") }) +LL | | } + | |_____^ help: collapse nested if block: `if y == "world" { println!("world") } else { println!("!") }` + +error: aborting due to 9 previous errors diff --git a/tests/ui/collapsible_if.fixed b/tests/ui/collapsible_if.fixed index f7c840c4cc1e..78354c2d7cf8 100644 --- a/tests/ui/collapsible_if.fixed +++ b/tests/ui/collapsible_if.fixed @@ -171,3 +171,13 @@ fn in_parens() { } //~^^^^^ collapsible_if } + +fn in_brackets() { + if true { + { + if true { + println!("In brackets, not linted"); + } + } + } +} diff --git a/tests/ui/collapsible_if.rs b/tests/ui/collapsible_if.rs index 4faebcb0ca0d..5d9afa109569 100644 --- a/tests/ui/collapsible_if.rs +++ b/tests/ui/collapsible_if.rs @@ -182,3 +182,13 @@ fn in_parens() { } //~^^^^^ collapsible_if } + +fn in_brackets() { + if true { + { + if true { + println!("In brackets, not linted"); + } + } + } +} From 9e50049157eb783ad7bf40c3f4e0cff1f8f2d3bf Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 20 Jul 2025 09:43:45 -0400 Subject: [PATCH 14/65] Split `possible_missing_else` from `suspicious_else_formatting`. --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/formatting.rs | 28 +++++++++++++++++++++- tests/ui/suspicious_else_formatting.rs | 10 ++++---- tests/ui/suspicious_else_formatting.stderr | 6 +++-- 5 files changed, 38 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a92fbdc767bd..8e0f76ddf759 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6260,6 +6260,7 @@ Released 2018-09-13 [`pointers_in_nomem_asm_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#pointers_in_nomem_asm_block [`positional_named_format_parameters`]: https://rust-lang.github.io/rust-clippy/master/index.html#positional_named_format_parameters [`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma +[`possible_missing_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_else [`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence [`precedence_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence_bits [`print_in_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_in_format_impl diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index c3f8e02b4c06..c0e7ffc77ba9 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -178,6 +178,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::format_impl::RECURSIVE_FORMAT_IMPL_INFO, crate::format_push_string::FORMAT_PUSH_STRING_INFO, crate::formatting::POSSIBLE_MISSING_COMMA_INFO, + crate::formatting::POSSIBLE_MISSING_ELSE_INFO, crate::formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING_INFO, crate::formatting::SUSPICIOUS_ELSE_FORMATTING_INFO, crate::formatting::SUSPICIOUS_UNARY_OP_FORMATTING_INFO, diff --git a/clippy_lints/src/formatting.rs b/clippy_lints/src/formatting.rs index 4b482f7b233b..1c751643becb 100644 --- a/clippy_lints/src/formatting.rs +++ b/clippy_lints/src/formatting.rs @@ -91,6 +91,31 @@ declare_clippy_lint! { "suspicious formatting of `else`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for an `if` expression followed by either a block or another `if` that + /// looks like it should have an `else` between them. + /// + /// ### Why is this bad? + /// This is probably some refactoring remnant, even if the code is correct, it + /// might look confusing. + /// + /// ### Example + /// ```rust,ignore + /// if foo { + /// } { // looks like an `else` is missing here + /// } + /// + /// if foo { + /// } if bar { // looks like an `else` is missing here + /// } + /// ``` + #[clippy::version = "1.90.0"] + pub POSSIBLE_MISSING_ELSE, + suspicious, + "possibly missing `else`" +} + declare_clippy_lint! { /// ### What it does /// Checks for possible missing comma in an array. It lints if @@ -116,6 +141,7 @@ declare_lint_pass!(Formatting => [ SUSPICIOUS_ASSIGNMENT_FORMATTING, SUSPICIOUS_UNARY_OP_FORMATTING, SUSPICIOUS_ELSE_FORMATTING, + POSSIBLE_MISSING_ELSE, POSSIBLE_MISSING_COMMA ]); @@ -307,7 +333,7 @@ fn check_missing_else(cx: &EarlyContext<'_>, first: &Expr, second: &Expr) { span_lint_and_note( cx, - SUSPICIOUS_ELSE_FORMATTING, + POSSIBLE_MISSING_ELSE, else_span, format!("this looks like {looks_like} but the `else` is missing"), None, diff --git a/tests/ui/suspicious_else_formatting.rs b/tests/ui/suspicious_else_formatting.rs index 28a3b551116d..072e7b27b0d0 100644 --- a/tests/ui/suspicious_else_formatting.rs +++ b/tests/ui/suspicious_else_formatting.rs @@ -1,6 +1,6 @@ //@aux-build:proc_macro_suspicious_else_formatting.rs -#![warn(clippy::suspicious_else_formatting)] +#![warn(clippy::suspicious_else_formatting, clippy::possible_missing_else)] #![allow( clippy::if_same_then_else, clippy::let_unit_value, @@ -20,12 +20,12 @@ fn main() { // weird `else` formatting: if foo() { } { - //~^ suspicious_else_formatting + //~^ possible_missing_else } if foo() { } if foo() { - //~^ suspicious_else_formatting + //~^ possible_missing_else } let _ = { // if as the last expression @@ -33,7 +33,7 @@ fn main() { if foo() { } if foo() { - //~^ suspicious_else_formatting + //~^ possible_missing_else } else { } @@ -42,7 +42,7 @@ fn main() { let _ = { // if in the middle of a block if foo() { } if foo() { - //~^ suspicious_else_formatting + //~^ possible_missing_else } else { } diff --git a/tests/ui/suspicious_else_formatting.stderr b/tests/ui/suspicious_else_formatting.stderr index affd20b22d9c..04555c6edbd3 100644 --- a/tests/ui/suspicious_else_formatting.stderr +++ b/tests/ui/suspicious_else_formatting.stderr @@ -5,8 +5,8 @@ LL | } { | ^ | = note: to remove this lint, add the missing `else` or add a new line before the next block - = note: `-D clippy::suspicious-else-formatting` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::suspicious_else_formatting)]` + = note: `-D clippy::possible-missing-else` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::possible_missing_else)]` error: this looks like an `else if` but the `else` is missing --> tests/ui/suspicious_else_formatting.rs:27:6 @@ -41,6 +41,8 @@ LL | | { | |____^ | = note: to remove this lint, remove the `else` or remove the new line between `else` and `{..}` + = note: `-D clippy::suspicious-else-formatting` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::suspicious_else_formatting)]` error: this is an `else if` but the formatting might hide it --> tests/ui/suspicious_else_formatting.rs:67:6 From 6017fe65968cbe044e1f9a0638376e788829736b Mon Sep 17 00:00:00 2001 From: binarycat Date: Thu, 24 Jul 2025 12:27:00 -0500 Subject: [PATCH 15/65] expand the issue template for new lints When I first tried contributing to clippy, I encountered the problem that a lot of lint suggestions overlapped with existing lints, so I would spend a bunch of time implementing something only to figure out it wasn't actually needed. The "comparison with existing lints" field should hopefully reduce this somewhat, while not incresing the burden of requesting a new lint too much due to not being mandatory. --- .github/ISSUE_TEMPLATE/new_lint.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/new_lint.yml b/.github/ISSUE_TEMPLATE/new_lint.yml index 464740640e0c..49a616d94f81 100644 --- a/.github/ISSUE_TEMPLATE/new_lint.yml +++ b/.github/ISSUE_TEMPLATE/new_lint.yml @@ -48,3 +48,24 @@ body: ``` validations: required: true + - type: textarea + id: comparison + attributes: + label: Comparision with existing lints + description: | + What makes this lint different from any existing lints that are similar, and how are those differences useful? + + You can [use this playground template to see what existing lints are triggered by the bad code][playground] + (make sure to use "Tools > Clippy" and not "Build"). + You can also look through the list of [rustc's allowed-by-default lints][allowed-by-default], + as those won't show up in the playground above. + + [allowed-by-default](https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html) + + [playground]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&code=%23%21%5Bwarn%28clippy%3A%3Apedantic%29%5D%0A%23%21%5Bwarn%28clippy%3A%3Anursery%29%5D%0A%23%21%5Bwarn%28clippy%3A%3Arestriction%29%5D%0A%23%21%5Bwarn%28clippy%3A%3Aall%29%5D%0A%23%21%5Ballow%28clippy%3A%3Ablanket_clippy_restriction_lints%2C+reason+%3D+%22testing+to+see+if+any+restriction+lints+match+given+code%22%29%5D%0A%0A%2F%2F%21+Template+that+can+be+used+to+see+what+clippy+lints+a+given+piece+of+code+would+trigger + placeholder: Unlike `clippy::...`, the proposed lint would... + - type: textarea + id: context + attributes: + label: Additional Context + description: Any additional context that you believe may be relevant. From a19b46d745cc2ef4d6fbff32119a3a9331bda8e1 Mon Sep 17 00:00:00 2001 From: lolbinarycat Date: Thu, 24 Jul 2025 14:10:12 -0500 Subject: [PATCH 16/65] fix markdown Co-authored-by: Philipp Krones --- .github/ISSUE_TEMPLATE/new_lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/new_lint.yml b/.github/ISSUE_TEMPLATE/new_lint.yml index 49a616d94f81..4c9e18b2b913 100644 --- a/.github/ISSUE_TEMPLATE/new_lint.yml +++ b/.github/ISSUE_TEMPLATE/new_lint.yml @@ -60,7 +60,7 @@ body: You can also look through the list of [rustc's allowed-by-default lints][allowed-by-default], as those won't show up in the playground above. - [allowed-by-default](https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html) + [allowed-by-default]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html [playground]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&code=%23%21%5Bwarn%28clippy%3A%3Apedantic%29%5D%0A%23%21%5Bwarn%28clippy%3A%3Anursery%29%5D%0A%23%21%5Bwarn%28clippy%3A%3Arestriction%29%5D%0A%23%21%5Bwarn%28clippy%3A%3Aall%29%5D%0A%23%21%5Ballow%28clippy%3A%3Ablanket_clippy_restriction_lints%2C+reason+%3D+%22testing+to+see+if+any+restriction+lints+match+given+code%22%29%5D%0A%0A%2F%2F%21+Template+that+can+be+used+to+see+what+clippy+lints+a+given+piece+of+code+would+trigger placeholder: Unlike `clippy::...`, the proposed lint would... From 0aa92fdba699a3227d9d1f7ed8121785d7b3b502 Mon Sep 17 00:00:00 2001 From: Dan Johnson Date: Tue, 17 Jun 2025 17:21:44 -0700 Subject: [PATCH 17/65] fix ip_constant when call wrapped in extra parens --- clippy_lints/src/methods/ip_constant.rs | 25 ++++---- tests/ui/ip_constant.fixed | 14 +++++ tests/ui/ip_constant.rs | 14 +++++ tests/ui/ip_constant.stderr | 80 +++++++++++++++++++++---- 4 files changed, 109 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/methods/ip_constant.rs b/clippy_lints/src/methods/ip_constant.rs index 83803fba6a13..a2ac4e54334e 100644 --- a/clippy_lints/src/methods/ip_constant.rs +++ b/clippy_lints/src/methods/ip_constant.rs @@ -1,7 +1,7 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint_and_then; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, QPath, Ty, TyKind}; +use rustc_hir::{Expr, ExprKind, QPath, TyKind}; use rustc_lint::LateContext; use rustc_span::sym; use smallvec::SmallVec; @@ -9,13 +9,8 @@ use smallvec::SmallVec; use super::IP_CONSTANT; pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args: &[Expr<'_>]) { - if let ExprKind::Path(QPath::TypeRelative( - Ty { - kind: TyKind::Path(QPath::Resolved(_, func_path)), - .. - }, - p, - )) = func.kind + if let ExprKind::Path(QPath::TypeRelative(ty, p)) = func.kind + && let TyKind::Path(QPath::Resolved(_, func_path)) = ty.kind && p.ident.name == sym::new && let Some(func_def_id) = func_path.res.opt_def_id() && matches!( @@ -40,13 +35,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args _ => return, }; + let mut sugg = vec![(expr.span.with_lo(p.ident.span.lo()), constant_name.to_owned())]; + let before_span = expr.span.shrink_to_lo().until(ty.span); + if !before_span.is_empty() { + // Remove everything before the type name + sugg.push((before_span, String::new())); + } + span_lint_and_then(cx, IP_CONSTANT, expr.span, "hand-coded well-known IP address", |diag| { - diag.span_suggestion_verbose( - expr.span.with_lo(p.ident.span.lo()), - "use", - constant_name, - Applicability::MachineApplicable, - ); + diag.multipart_suggestion_verbose("use", sugg, Applicability::MachineApplicable); }); } } diff --git a/tests/ui/ip_constant.fixed b/tests/ui/ip_constant.fixed index 2e3389c11938..c94796821394 100644 --- a/tests/ui/ip_constant.fixed +++ b/tests/ui/ip_constant.fixed @@ -48,6 +48,20 @@ fn literal_test3() { //~^ ip_constant } +fn wrapped_in_parens() { + let _ = std::net::Ipv4Addr::LOCALHOST; + //~^ ip_constant + let _ = std::net::Ipv4Addr::BROADCAST; + //~^ ip_constant + let _ = std::net::Ipv4Addr::UNSPECIFIED; + //~^ ip_constant + + let _ = std::net::Ipv6Addr::LOCALHOST; + //~^ ip_constant + let _ = std::net::Ipv6Addr::UNSPECIFIED; + //~^ ip_constant +} + const CONST_U8_0: u8 = 0; const CONST_U8_1: u8 = 1; const CONST_U8_127: u8 = 127; diff --git a/tests/ui/ip_constant.rs b/tests/ui/ip_constant.rs index 15e0b0551bab..69a5c3b4e923 100644 --- a/tests/ui/ip_constant.rs +++ b/tests/ui/ip_constant.rs @@ -48,6 +48,20 @@ fn literal_test3() { //~^ ip_constant } +fn wrapped_in_parens() { + let _ = (std::net::Ipv4Addr::new(127, 0, 0, 1)); + //~^ ip_constant + let _ = (std::net::Ipv4Addr::new(255, 255, 255, 255)); + //~^ ip_constant + let _ = (std::net::Ipv4Addr::new(0, 0, 0, 0)); + //~^ ip_constant + + let _ = (std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)); + //~^ ip_constant + let _ = (std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)); + //~^ ip_constant +} + const CONST_U8_0: u8 = 0; const CONST_U8_1: u8 = 1; const CONST_U8_127: u8 = 127; diff --git a/tests/ui/ip_constant.stderr b/tests/ui/ip_constant.stderr index 3e984c6cb3bb..07d912b18a57 100644 --- a/tests/ui/ip_constant.stderr +++ b/tests/ui/ip_constant.stderr @@ -180,9 +180,69 @@ LL - let _ = std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); LL + let _ = std::net::Ipv6Addr::UNSPECIFIED; | +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:52:13 + | +LL | let _ = (std::net::Ipv4Addr::new(127, 0, 0, 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = (std::net::Ipv4Addr::new(127, 0, 0, 1)); +LL + let _ = std::net::Ipv4Addr::LOCALHOST; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:54:13 + | +LL | let _ = (std::net::Ipv4Addr::new(255, 255, 255, 255)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = (std::net::Ipv4Addr::new(255, 255, 255, 255)); +LL + let _ = std::net::Ipv4Addr::BROADCAST; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:56:13 + | +LL | let _ = (std::net::Ipv4Addr::new(0, 0, 0, 0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = (std::net::Ipv4Addr::new(0, 0, 0, 0)); +LL + let _ = std::net::Ipv4Addr::UNSPECIFIED; + | + +error: hand-coded well-known IP address + --> tests/ui/ip_constant.rs:59:13 + | +LL | let _ = (std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = (std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)); +LL + let _ = std::net::Ipv6Addr::LOCALHOST; + | + error: hand-coded well-known IP address --> tests/ui/ip_constant.rs:61:13 | +LL | let _ = (std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use + | +LL - let _ = (std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)); +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); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | @@ -193,7 +253,7 @@ LL + let _ = Ipv4Addr::LOCALHOST; | error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:63:13 + --> tests/ui/ip_constant.rs:77:13 | LL | let _ = Ipv4Addr::new(CONST_U8_255, CONST_U8_255, CONST_U8_255, CONST_U8_255); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -205,7 +265,7 @@ LL + let _ = Ipv4Addr::BROADCAST; | error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:65:13 + --> tests/ui/ip_constant.rs:79:13 | LL | let _ = Ipv4Addr::new(CONST_U8_0, CONST_U8_0, CONST_U8_0, CONST_U8_0); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -217,7 +277,7 @@ LL + let _ = Ipv4Addr::UNSPECIFIED; | error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:69:13 + --> tests/ui/ip_constant.rs:83:13 | LL | let _ = Ipv6Addr::new( | _____________^ @@ -246,7 +306,7 @@ LL + let _ = Ipv6Addr::LOCALHOST; | error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:81:13 + --> tests/ui/ip_constant.rs:95:13 | LL | let _ = Ipv6Addr::new( | _____________^ @@ -275,7 +335,7 @@ LL + let _ = Ipv6Addr::UNSPECIFIED; | error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:96:13 + --> tests/ui/ip_constant.rs:110:13 | LL | let _ = Ipv4Addr::new(126 + 1, 0, 0, 1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -287,7 +347,7 @@ LL + let _ = Ipv4Addr::LOCALHOST; | error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:98:13 + --> tests/ui/ip_constant.rs:112:13 | LL | let _ = Ipv4Addr::new(254 + CONST_U8_1, 255, { 255 - CONST_U8_0 }, CONST_U8_255); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -299,7 +359,7 @@ LL + let _ = Ipv4Addr::BROADCAST; | error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:100:13 + --> tests/ui/ip_constant.rs:114:13 | LL | let _ = Ipv4Addr::new(0, CONST_U8_255 - 255, 0, { 1 + 0 - 1 }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -311,7 +371,7 @@ LL + let _ = Ipv4Addr::UNSPECIFIED; | error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:104:13 + --> tests/ui/ip_constant.rs:118:13 | LL | let _ = Ipv6Addr::new(0 + CONST_U16_0, 0, 0, 0, 0, 0, 0, 1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -323,7 +383,7 @@ LL + let _ = Ipv6Addr::LOCALHOST; | error: hand-coded well-known IP address - --> tests/ui/ip_constant.rs:106:13 + --> tests/ui/ip_constant.rs:120:13 | LL | let _ = Ipv6Addr::new(0 + 0, 0, 0, 0, 0, { 2 - 1 - CONST_U16_1 }, 0, 1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -334,5 +394,5 @@ 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 25 previous errors +error: aborting due to 30 previous errors From da6618097f9dea0baf5950b588d38fefa0544a07 Mon Sep 17 00:00:00 2001 From: newt Date: Fri, 25 Jul 2025 00:34:01 +0100 Subject: [PATCH 18/65] Detect prefixed attributes as duplicated --- .../src/attrs/duplicated_attributes.rs | 44 ++++++++++-------- tests/ui/duplicated_attributes.rs | 2 +- tests/ui/duplicated_attributes.stderr | 23 ++++++++-- tests/ui/indexing_slicing_slice.rs | 1 - tests/ui/indexing_slicing_slice.stderr | 38 +++++++-------- tests/ui/needless_collect_indirect.rs | 3 +- tests/ui/needless_collect_indirect.stderr | 32 ++++++------- tests/ui/unnecessary_clippy_cfg.rs | 3 +- tests/ui/unnecessary_clippy_cfg.stderr | 46 ++----------------- 9 files changed, 89 insertions(+), 103 deletions(-) diff --git a/clippy_lints/src/attrs/duplicated_attributes.rs b/clippy_lints/src/attrs/duplicated_attributes.rs index c2406bcfb647..c956738edf0e 100644 --- a/clippy_lints/src/attrs/duplicated_attributes.rs +++ b/clippy_lints/src/attrs/duplicated_attributes.rs @@ -2,6 +2,7 @@ use super::DUPLICATED_ATTRIBUTES; use clippy_utils::diagnostics::span_lint_and_then; use itertools::Itertools; use rustc_ast::{Attribute, MetaItem}; +use rustc_ast_pretty::pprust::path_to_string; use rustc_data_structures::fx::FxHashMap; use rustc_lint::EarlyContext; use rustc_span::{Span, Symbol, sym}; @@ -35,31 +36,38 @@ fn check_duplicated_attr( if attr.span.from_expansion() { return; } - let Some(ident) = attr.ident() else { return }; - let name = ident.name; - if name == sym::doc || name == sym::cfg_attr_trace || name == sym::rustc_on_unimplemented || name == sym::reason { - // FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg - // conditions are the same. - // `#[rustc_on_unimplemented]` contains duplicated subattributes, that's expected. - return; - } - if let Some(direct_parent) = parent.last() - && *direct_parent == sym::cfg_trace - && [sym::all, sym::not, sym::any].contains(&name) - { - // FIXME: We don't correctly check `cfg`s for now, so if it's more complex than just a one - // level `cfg`, we leave. - return; + let attr_path = if let Some(ident) = attr.ident() { + ident.name + } else { + Symbol::intern(&path_to_string(&attr.path)) + }; + if let Some(ident) = attr.ident() { + let name = ident.name; + if name == sym::doc || name == sym::cfg_attr_trace || name == sym::rustc_on_unimplemented || name == sym::reason + { + // FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg + // conditions are the same. + // `#[rustc_on_unimplemented]` contains duplicated subattributes, that's expected. + return; + } + if let Some(direct_parent) = parent.last() + && *direct_parent == sym::cfg_trace + && [sym::all, sym::not, sym::any].contains(&name) + { + // FIXME: We don't correctly check `cfg`s for now, so if it's more complex than just a one + // level `cfg`, we leave. + return; + } } if let Some(value) = attr.value_str() { emit_if_duplicated( cx, attr, attr_paths, - format!("{}:{name}={value}", parent.iter().join(":")), + format!("{}:{attr_path}={value}", parent.iter().join(":")), ); } else if let Some(sub_attrs) = attr.meta_item_list() { - parent.push(name); + parent.push(attr_path); for sub_attr in sub_attrs { if let Some(meta) = sub_attr.meta_item() { check_duplicated_attr(cx, meta, attr_paths, parent); @@ -67,7 +75,7 @@ fn check_duplicated_attr( } parent.pop(); } else { - emit_if_duplicated(cx, attr, attr_paths, format!("{}:{name}", parent.iter().join(":"))); + emit_if_duplicated(cx, attr, attr_paths, format!("{}:{attr_path}", parent.iter().join(":"))); } } diff --git a/tests/ui/duplicated_attributes.rs b/tests/ui/duplicated_attributes.rs index 3ca91d6f1829..9a6714995059 100644 --- a/tests/ui/duplicated_attributes.rs +++ b/tests/ui/duplicated_attributes.rs @@ -1,6 +1,6 @@ //@aux-build:proc_macro_attr.rs +#![warn(clippy::duplicated_attributes, clippy::duplicated_attributes)] //~ ERROR: duplicated attribute #![feature(rustc_attrs)] -#![warn(clippy::duplicated_attributes)] #![cfg(any(unix, windows))] #![allow(dead_code)] #![allow(dead_code)] //~ ERROR: duplicated attribute diff --git a/tests/ui/duplicated_attributes.stderr b/tests/ui/duplicated_attributes.stderr index 0903617a8d1f..922939d60dd6 100644 --- a/tests/ui/duplicated_attributes.stderr +++ b/tests/ui/duplicated_attributes.stderr @@ -1,3 +1,22 @@ +error: duplicated attribute + --> tests/ui/duplicated_attributes.rs:2:40 + | +LL | #![warn(clippy::duplicated_attributes, clippy::duplicated_attributes)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: first defined here + --> tests/ui/duplicated_attributes.rs:2:9 + | +LL | #![warn(clippy::duplicated_attributes, clippy::duplicated_attributes)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: remove this attribute + --> tests/ui/duplicated_attributes.rs:2:40 + | +LL | #![warn(clippy::duplicated_attributes, clippy::duplicated_attributes)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `-D clippy::duplicated-attributes` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::duplicated_attributes)]` + error: duplicated attribute --> tests/ui/duplicated_attributes.rs:6:10 | @@ -14,8 +33,6 @@ help: remove this attribute | LL | #![allow(dead_code)] | ^^^^^^^^^ - = note: `-D clippy::duplicated-attributes` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::duplicated_attributes)]` error: duplicated attribute --> tests/ui/duplicated_attributes.rs:14:9 @@ -34,5 +51,5 @@ help: remove this attribute LL | #[allow(dead_code)] | ^^^^^^^^^ -error: aborting due to 2 previous errors +error: aborting due to 3 previous errors diff --git a/tests/ui/indexing_slicing_slice.rs b/tests/ui/indexing_slicing_slice.rs index cad77f56d03a..dea31530a0b6 100644 --- a/tests/ui/indexing_slicing_slice.rs +++ b/tests/ui/indexing_slicing_slice.rs @@ -1,6 +1,5 @@ //@aux-build: proc_macros.rs -#![warn(clippy::indexing_slicing)] // We also check the out_of_bounds_indexing lint here, because it lints similar things and // we want to avoid false positives. #![warn(clippy::out_of_bounds_indexing)] diff --git a/tests/ui/indexing_slicing_slice.stderr b/tests/ui/indexing_slicing_slice.stderr index e3ef89823e3d..e3d6086544de 100644 --- a/tests/ui/indexing_slicing_slice.stderr +++ b/tests/ui/indexing_slicing_slice.stderr @@ -1,5 +1,5 @@ error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:115:6 + --> tests/ui/indexing_slicing_slice.rs:114:6 | LL | &x[index..]; | ^^^^^^^^^^ @@ -9,7 +9,7 @@ LL | &x[index..]; = help: to override `-D warnings` add `#[allow(clippy::indexing_slicing)]` error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:117:6 + --> tests/ui/indexing_slicing_slice.rs:116:6 | LL | &x[..index]; | ^^^^^^^^^^ @@ -17,7 +17,7 @@ LL | &x[..index]; = help: consider using `.get(..n)`or `.get_mut(..n)` instead error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:119:6 + --> tests/ui/indexing_slicing_slice.rs:118:6 | LL | &x[index_from..index_to]; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL | &x[index_from..index_to]; = help: consider using `.get(n..m)` or `.get_mut(n..m)` instead error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:121:6 + --> tests/ui/indexing_slicing_slice.rs:120:6 | LL | &x[index_from..][..index_to]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -33,7 +33,7 @@ LL | &x[index_from..][..index_to]; = help: consider using `.get(..n)`or `.get_mut(..n)` instead error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:121:6 + --> tests/ui/indexing_slicing_slice.rs:120:6 | LL | &x[index_from..][..index_to]; | ^^^^^^^^^^^^^^^ @@ -41,7 +41,7 @@ LL | &x[index_from..][..index_to]; = help: consider using `.get(n..)` or .get_mut(n..)` instead error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:124:6 + --> tests/ui/indexing_slicing_slice.rs:123:6 | LL | &x[5..][..10]; | ^^^^^^^^^^^^ @@ -49,7 +49,7 @@ LL | &x[5..][..10]; = help: consider using `.get(..n)`or `.get_mut(..n)` instead error: range is out of bounds - --> tests/ui/indexing_slicing_slice.rs:124:8 + --> tests/ui/indexing_slicing_slice.rs:123:8 | LL | &x[5..][..10]; | ^ @@ -58,7 +58,7 @@ LL | &x[5..][..10]; = help: to override `-D warnings` add `#[allow(clippy::out_of_bounds_indexing)]` error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:127:6 + --> tests/ui/indexing_slicing_slice.rs:126:6 | LL | &x[0..][..3]; | ^^^^^^^^^^^ @@ -66,7 +66,7 @@ LL | &x[0..][..3]; = help: consider using `.get(..n)`or `.get_mut(..n)` instead error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:129:6 + --> tests/ui/indexing_slicing_slice.rs:128:6 | LL | &x[1..][..5]; | ^^^^^^^^^^^ @@ -74,19 +74,19 @@ LL | &x[1..][..5]; = help: consider using `.get(..n)`or `.get_mut(..n)` instead error: range is out of bounds - --> tests/ui/indexing_slicing_slice.rs:137:12 + --> tests/ui/indexing_slicing_slice.rs:136:12 | LL | &y[0..=4]; | ^ error: range is out of bounds - --> tests/ui/indexing_slicing_slice.rs:139:11 + --> tests/ui/indexing_slicing_slice.rs:138:11 | LL | &y[..=4]; | ^ error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:145:6 + --> tests/ui/indexing_slicing_slice.rs:144:6 | LL | &v[10..100]; | ^^^^^^^^^^ @@ -94,7 +94,7 @@ LL | &v[10..100]; = help: consider using `.get(n..m)` or `.get_mut(n..m)` instead error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:147:6 + --> tests/ui/indexing_slicing_slice.rs:146:6 | LL | &x[10..][..100]; | ^^^^^^^^^^^^^^ @@ -102,13 +102,13 @@ LL | &x[10..][..100]; = help: consider using `.get(..n)`or `.get_mut(..n)` instead error: range is out of bounds - --> tests/ui/indexing_slicing_slice.rs:147:8 + --> tests/ui/indexing_slicing_slice.rs:146:8 | LL | &x[10..][..100]; | ^^ error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:150:6 + --> tests/ui/indexing_slicing_slice.rs:149:6 | LL | &v[10..]; | ^^^^^^^ @@ -116,7 +116,7 @@ LL | &v[10..]; = help: consider using `.get(n..)` or .get_mut(n..)` instead error: slicing may panic - --> tests/ui/indexing_slicing_slice.rs:152:6 + --> tests/ui/indexing_slicing_slice.rs:151:6 | LL | &v[..100]; | ^^^^^^^^ @@ -124,7 +124,7 @@ LL | &v[..100]; = help: consider using `.get(..n)`or `.get_mut(..n)` instead error: indexing may panic - --> tests/ui/indexing_slicing_slice.rs:170:5 + --> tests/ui/indexing_slicing_slice.rs:169:5 | LL | map_with_get[true]; | ^^^^^^^^^^^^^^^^^^ @@ -132,7 +132,7 @@ LL | map_with_get[true]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: indexing may panic - --> tests/ui/indexing_slicing_slice.rs:174:5 + --> tests/ui/indexing_slicing_slice.rs:173:5 | LL | s[0]; | ^^^^ @@ -140,7 +140,7 @@ LL | s[0]; = help: consider using `.get(n)` or `.get_mut(n)` instead error: indexing may panic - --> tests/ui/indexing_slicing_slice.rs:178:5 + --> tests/ui/indexing_slicing_slice.rs:177:5 | LL | y[0]; | ^^^^ diff --git a/tests/ui/needless_collect_indirect.rs b/tests/ui/needless_collect_indirect.rs index 57d0f2b99480..fff6d2f34b8e 100644 --- a/tests/ui/needless_collect_indirect.rs +++ b/tests/ui/needless_collect_indirect.rs @@ -1,5 +1,4 @@ -#![allow(clippy::uninlined_format_args, clippy::useless_vec)] -#![allow(clippy::needless_if, clippy::uninlined_format_args)] +#![allow(clippy::uninlined_format_args, clippy::useless_vec, clippy::needless_if)] #![warn(clippy::needless_collect)] //@no-rustfix use std::collections::{BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}; diff --git a/tests/ui/needless_collect_indirect.stderr b/tests/ui/needless_collect_indirect.stderr index c7bf1b14df80..24523c9f97b0 100644 --- a/tests/ui/needless_collect_indirect.stderr +++ b/tests/ui/needless_collect_indirect.stderr @@ -1,5 +1,5 @@ error: avoid using `collect()` when not needed - --> tests/ui/needless_collect_indirect.rs:9:39 + --> tests/ui/needless_collect_indirect.rs:8:39 | LL | let indirect_iter = sample.iter().collect::>(); | ^^^^^^^ @@ -18,7 +18,7 @@ LL ~ sample.iter().map(|x| (x, x + 1)).collect::>(); | error: avoid using `collect()` when not needed - --> tests/ui/needless_collect_indirect.rs:13:38 + --> tests/ui/needless_collect_indirect.rs:12:38 | LL | let indirect_len = sample.iter().collect::>(); | ^^^^^^^ @@ -35,7 +35,7 @@ LL ~ sample.iter().count(); | error: avoid using `collect()` when not needed - --> tests/ui/needless_collect_indirect.rs:17:40 + --> tests/ui/needless_collect_indirect.rs:16:40 | LL | let indirect_empty = sample.iter().collect::>(); | ^^^^^^^ @@ -52,7 +52,7 @@ LL ~ sample.iter().next().is_none(); | error: avoid using `collect()` when not needed - --> tests/ui/needless_collect_indirect.rs:21:43 + --> tests/ui/needless_collect_indirect.rs:20:43 | LL | let indirect_contains = sample.iter().collect::>(); | ^^^^^^^ @@ -69,7 +69,7 @@ LL ~ sample.iter().any(|x| x == &5); | error: avoid using `collect()` when not needed - --> tests/ui/needless_collect_indirect.rs:35:48 + --> tests/ui/needless_collect_indirect.rs:34:48 | LL | let non_copy_contains = sample.into_iter().collect::>(); | ^^^^^^^ @@ -86,7 +86,7 @@ LL ~ sample.into_iter().any(|x| x == a); | error: avoid using `collect()` when not needed - --> tests/ui/needless_collect_indirect.rs:66:51 + --> tests/ui/needless_collect_indirect.rs:65:51 | LL | let buffer: Vec<&str> = string.split('/').collect(); | ^^^^^^^ @@ -103,7 +103,7 @@ LL ~ string.split('/').count() | error: avoid using `collect()` when not needed - --> tests/ui/needless_collect_indirect.rs:73:55 + --> tests/ui/needless_collect_indirect.rs:72:55 | LL | let indirect_len: VecDeque<_> = sample.iter().collect(); | ^^^^^^^ @@ -120,7 +120,7 @@ LL ~ sample.iter().count() | error: avoid using `collect()` when not needed - --> tests/ui/needless_collect_indirect.rs:80:57 + --> tests/ui/needless_collect_indirect.rs:79:57 | LL | let indirect_len: LinkedList<_> = sample.iter().collect(); | ^^^^^^^ @@ -137,7 +137,7 @@ LL ~ sample.iter().count() | error: avoid using `collect()` when not needed - --> tests/ui/needless_collect_indirect.rs:87:57 + --> tests/ui/needless_collect_indirect.rs:86:57 | LL | let indirect_len: BinaryHeap<_> = sample.iter().collect(); | ^^^^^^^ @@ -154,7 +154,7 @@ LL ~ sample.iter().count() | error: avoid using `collect()` when not needed - --> tests/ui/needless_collect_indirect.rs:149:59 + --> tests/ui/needless_collect_indirect.rs:148:59 | LL | let y: Vec = vec.iter().map(|k| k * k).collect(); | ^^^^^^^ @@ -172,7 +172,7 @@ LL ~ vec.iter().map(|k| k * k).any(|x| x == i); | error: avoid using `collect()` when not needed - --> tests/ui/needless_collect_indirect.rs:176:59 + --> tests/ui/needless_collect_indirect.rs:175:59 | LL | let y: Vec = vec.iter().map(|k| k * k).collect(); | ^^^^^^^ @@ -190,7 +190,7 @@ LL ~ vec.iter().map(|k| k * k).any(|x| x == n); | error: avoid using `collect()` when not needed - --> tests/ui/needless_collect_indirect.rs:207:63 + --> tests/ui/needless_collect_indirect.rs:206:63 | LL | let y: Vec = vec.iter().map(|k| k * k).collect(); | ^^^^^^^ @@ -208,7 +208,7 @@ LL ~ vec.iter().map(|k| k * k).any(|x| x == n); | error: avoid using `collect()` when not needed - --> tests/ui/needless_collect_indirect.rs:245:59 + --> tests/ui/needless_collect_indirect.rs:244:59 | LL | let y: Vec = vec.iter().map(|k| k * k).collect(); | ^^^^^^^ @@ -226,7 +226,7 @@ LL ~ vec.iter().map(|k| k * k).any(|x| x == n); | error: avoid using `collect()` when not needed - --> tests/ui/needless_collect_indirect.rs:272:26 + --> tests/ui/needless_collect_indirect.rs:271:26 | LL | let w = v.iter().collect::>(); | ^^^^^^^ @@ -244,7 +244,7 @@ LL ~ for _ in 0..v.iter().count() { | error: avoid using `collect()` when not needed - --> tests/ui/needless_collect_indirect.rs:296:30 + --> tests/ui/needless_collect_indirect.rs:295:30 | LL | let mut w = v.iter().collect::>(); | ^^^^^^^ @@ -262,7 +262,7 @@ LL ~ while 1 == v.iter().count() { | error: avoid using `collect()` when not needed - --> tests/ui/needless_collect_indirect.rs:320:30 + --> tests/ui/needless_collect_indirect.rs:319:30 | LL | let mut w = v.iter().collect::>(); | ^^^^^^^ diff --git a/tests/ui/unnecessary_clippy_cfg.rs b/tests/ui/unnecessary_clippy_cfg.rs index e7e01248dfbe..65f67df79131 100644 --- a/tests/ui/unnecessary_clippy_cfg.rs +++ b/tests/ui/unnecessary_clippy_cfg.rs @@ -1,5 +1,6 @@ //@no-rustfix +#![allow(clippy::duplicated_attributes)] #![warn(clippy::unnecessary_clippy_cfg)] #![cfg_attr(clippy, deny(clippy::non_minimal_cfg))] //~^ unnecessary_clippy_cfg @@ -7,7 +8,6 @@ //~^ unnecessary_clippy_cfg #![cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))] //~^ unnecessary_clippy_cfg -//~| duplicated_attributes #![cfg_attr(clippy, deny(clippy::non_minimal_cfg))] //~^ unnecessary_clippy_cfg @@ -17,7 +17,6 @@ //~^ unnecessary_clippy_cfg #[cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))] //~^ unnecessary_clippy_cfg -//~| duplicated_attributes #[cfg_attr(clippy, deny(clippy::non_minimal_cfg))] //~^ unnecessary_clippy_cfg diff --git a/tests/ui/unnecessary_clippy_cfg.stderr b/tests/ui/unnecessary_clippy_cfg.stderr index f66c68949548..4f638d5c513c 100644 --- a/tests/ui/unnecessary_clippy_cfg.stderr +++ b/tests/ui/unnecessary_clippy_cfg.stderr @@ -1,5 +1,5 @@ error: no need to put clippy lints behind a `clippy` cfg - --> tests/ui/unnecessary_clippy_cfg.rs:4:1 + --> tests/ui/unnecessary_clippy_cfg.rs:5:1 | LL | #![cfg_attr(clippy, deny(clippy::non_minimal_cfg))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `#![deny(clippy::non_minimal_cfg)]` @@ -8,7 +8,7 @@ LL | #![cfg_attr(clippy, deny(clippy::non_minimal_cfg))] = help: to override `-D warnings` add `#[allow(clippy::unnecessary_clippy_cfg)]` error: no need to put clippy lints behind a `clippy` cfg - --> tests/ui/unnecessary_clippy_cfg.rs:6:37 + --> tests/ui/unnecessary_clippy_cfg.rs:7:37 | LL | #![cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))] | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -16,7 +16,7 @@ LL | #![cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))] = note: write instead: `#![deny(clippy::non_minimal_cfg)]` error: no need to put clippy lints behind a `clippy` cfg - --> tests/ui/unnecessary_clippy_cfg.rs:8:37 + --> tests/ui/unnecessary_clippy_cfg.rs:9:37 | LL | #![cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))] | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -52,46 +52,10 @@ LL | #[cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))] = note: write instead: `#[deny(clippy::non_minimal_cfg)]` error: no need to put clippy lints behind a `clippy` cfg - --> tests/ui/unnecessary_clippy_cfg.rs:21:1 + --> tests/ui/unnecessary_clippy_cfg.rs:20:1 | LL | #[cfg_attr(clippy, deny(clippy::non_minimal_cfg))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `#[deny(clippy::non_minimal_cfg)]` -error: duplicated attribute - --> tests/ui/unnecessary_clippy_cfg.rs:8:26 - | -LL | #![cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))] - | ^^^^^^^^^ - | -note: first defined here - --> tests/ui/unnecessary_clippy_cfg.rs:6:26 - | -LL | #![cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))] - | ^^^^^^^^^ -help: remove this attribute - --> tests/ui/unnecessary_clippy_cfg.rs:8:26 - | -LL | #![cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))] - | ^^^^^^^^^ - = note: `-D clippy::duplicated-attributes` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::duplicated_attributes)]` - -error: duplicated attribute - --> tests/ui/unnecessary_clippy_cfg.rs:18:25 - | -LL | #[cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))] - | ^^^^^^^^^ - | -note: first defined here - --> tests/ui/unnecessary_clippy_cfg.rs:16:25 - | -LL | #[cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))] - | ^^^^^^^^^ -help: remove this attribute - --> tests/ui/unnecessary_clippy_cfg.rs:18:25 - | -LL | #[cfg_attr(clippy, deny(dead_code, clippy::non_minimal_cfg))] - | ^^^^^^^^^ - -error: aborting due to 10 previous errors +error: aborting due to 8 previous errors From 5c7418b38afeb0f7256917b7bbee481b854e3113 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 25 Jul 2025 15:54:22 +0200 Subject: [PATCH 19/65] Merge commit '1db89a1b1ca87f24bf22d0bad21d14b2d81b3e99' into clippy-subtree-update --- .github/workflows/feature_freeze.yml | 36 ++- .gitignore | 2 + clippy_dev/src/fmt.rs | 4 +- clippy_lints/src/approx_const.rs | 33 ++- .../src/arbitrary_source_item_ordering.rs | 15 +- clippy_lints/src/arc_with_non_send_sync.rs | 2 +- clippy_lints/src/attrs/useless_attribute.rs | 1 + .../casts/confusing_method_to_numeric_cast.rs | 5 +- clippy_lints/src/casts/fn_to_numeric_cast.rs | 33 +-- .../src/casts/fn_to_numeric_cast_any.rs | 9 +- .../fn_to_numeric_cast_with_truncation.rs | 33 +-- clippy_lints/src/casts/ptr_as_ptr.rs | 16 +- clippy_lints/src/cognitive_complexity.rs | 1 - clippy_lints/src/copies.rs | 16 +- clippy_lints/src/derive.rs | 5 + clippy_lints/src/disallowed_macros.rs | 8 +- clippy_lints/src/doc/mod.rs | 1 - clippy_lints/src/empty_with_brackets.rs | 12 +- clippy_lints/src/escape.rs | 10 +- clippy_lints/src/eta_reduction.rs | 6 - clippy_lints/src/fallible_impl_from.rs | 8 +- clippy_lints/src/format_args.rs | 12 +- clippy_lints/src/from_over_into.rs | 2 +- clippy_lints/src/functions/must_use.rs | 29 +- clippy_lints/src/if_then_some_else_none.rs | 7 +- clippy_lints/src/incompatible_msrv.rs | 151 +++++++--- clippy_lints/src/ineffective_open_options.rs | 100 +++---- clippy_lints/src/infallible_try_from.rs | 8 +- clippy_lints/src/item_name_repetitions.rs | 4 +- clippy_lints/src/iter_without_into_iter.rs | 10 +- clippy_lints/src/large_enum_variant.rs | 5 +- clippy_lints/src/legacy_numeric_constants.rs | 56 ++-- clippy_lints/src/len_zero.rs | 15 +- clippy_lints/src/loops/needless_range_loop.rs | 22 +- clippy_lints/src/loops/never_loop.rs | 139 +++++++-- clippy_lints/src/macro_use.rs | 4 +- clippy_lints/src/manual_abs_diff.rs | 13 +- clippy_lints/src/manual_assert.rs | 3 +- clippy_lints/src/matches/single_match.rs | 21 +- clippy_lints/src/methods/expect_fun_call.rs | 188 +++++-------- .../src/methods/filter_map_bool_then.rs | 10 +- clippy_lints/src/methods/manual_inspect.rs | 1 - clippy_lints/src/methods/mod.rs | 19 +- clippy_lints/src/methods/or_fun_call.rs | 26 +- clippy_lints/src/missing_fields_in_debug.rs | 4 +- clippy_lints/src/missing_inline.rs | 25 +- clippy_lints/src/missing_trait_methods.rs | 4 +- .../src/mixed_read_write_in_expression.rs | 13 +- clippy_lints/src/mut_reference.rs | 2 +- clippy_lints/src/needless_for_each.rs | 21 +- clippy_lints/src/needless_pass_by_value.rs | 12 +- clippy_lints/src/new_without_default.rs | 9 +- .../src/operators/arithmetic_side_effects.rs | 51 ++-- .../src/operators/manual_is_multiple_of.rs | 25 +- clippy_lints/src/pattern_type_mismatch.rs | 6 + clippy_lints/src/ptr.rs | 17 +- clippy_lints/src/ranges.rs | 264 ++++++++++++----- clippy_lints/src/same_name_method.rs | 17 +- clippy_lints/src/unused_async.rs | 29 +- clippy_lints/src/unused_self.rs | 20 +- clippy_lints/src/unused_trait_names.rs | 4 +- clippy_lints/src/useless_conversion.rs | 53 ++-- .../derive_deserialize_allowing_unknown.rs | 7 +- .../src/lint_without_lint_pass.rs | 7 +- clippy_lints_internal/src/msrv_attr_impl.rs | 5 +- clippy_utils/README.md | 2 +- clippy_utils/src/diagnostics.rs | 11 +- clippy_utils/src/lib.rs | 45 ++- clippy_utils/src/paths.rs | 9 +- clippy_utils/src/sym.rs | 3 + clippy_utils/src/ty/mod.rs | 5 +- clippy_utils/src/ty/type_certainty/mod.rs | 74 +++-- clippy_utils/src/usage.rs | 42 +++ rust-toolchain.toml | 2 +- rustc_tools_util/src/lib.rs | 5 +- tests/compile-test.rs | 9 +- ..._incompatible_msrv_in_tests.enabled.stderr | 2 + .../enum_variant_size.stderr | 2 +- tests/ui/approx_const.rs | 15 + tests/ui/approx_const.stderr | 34 ++- tests/ui/arc_with_non_send_sync.stderr | 6 +- tests/ui/arithmetic_side_effects.rs | 14 + tests/ui/arithmetic_side_effects.stderr | 4 +- tests/ui/assign_ops.fixed | 1 + tests/ui/assign_ops.rs | 1 + tests/ui/auxiliary/external_item.rs | 2 +- tests/ui/checked_conversions.fixed | 2 +- tests/ui/checked_conversions.rs | 4 +- tests/ui/checked_conversions.stderr | 4 +- tests/ui/expect.rs | 19 ++ tests/ui/expect.stderr | 34 ++- tests/ui/expect_fun_call.fixed | 34 ++- tests/ui/expect_fun_call.rs | 24 ++ tests/ui/expect_fun_call.stderr | 52 ++-- tests/ui/filter_map_bool_then.fixed | 21 ++ tests/ui/filter_map_bool_then.rs | 21 ++ tests/ui/filter_map_bool_then.stderr | 8 +- tests/ui/flat_map_identity.fixed | 13 + tests/ui/flat_map_identity.rs | 13 + tests/ui/flat_map_identity.stderr | 8 +- tests/ui/if_then_some_else_none.fixed | 43 +++ tests/ui/if_then_some_else_none.rs | 68 +++++ tests/ui/if_then_some_else_none.stderr | 60 +++- tests/ui/if_then_some_else_none_unfixable.rs | 35 +++ .../if_then_some_else_none_unfixable.stderr | 28 ++ tests/ui/incompatible_msrv.rs | 111 +++++++- tests/ui/incompatible_msrv.stderr | 96 +++++-- tests/ui/large_enum_variant.32bit.stderr | 36 +-- tests/ui/large_enum_variant.64bit.stderr | 40 +-- tests/ui/large_enum_variant_no_std.rs | 8 + tests/ui/large_enum_variant_no_std.stderr | 22 ++ tests/ui/legacy_numeric_constants.fixed | 22 ++ tests/ui/legacy_numeric_constants.rs | 22 ++ tests/ui/legacy_numeric_constants.stderr | 110 +++++++- tests/ui/manual_abs_diff.fixed | 4 + tests/ui/manual_abs_diff.rs | 9 + tests/ui/manual_abs_diff.stderr | 13 +- tests/ui/manual_assert.edition2018.stderr | 20 +- tests/ui/manual_assert.edition2021.stderr | 20 +- tests/ui/manual_assert.rs | 14 + tests/ui/manual_is_multiple_of.fixed | 78 +++++ tests/ui/manual_is_multiple_of.rs | 78 +++++ tests/ui/manual_is_multiple_of.stderr | 32 ++- tests/ui/map_identity.fixed | 12 + tests/ui/map_identity.rs | 12 + tests/ui/map_identity.stderr | 8 +- tests/ui/missing_inline.rs | 17 ++ tests/ui/module_name_repetitions.rs | 18 ++ tests/ui/must_use_candidates.fixed | 15 +- tests/ui/must_use_candidates.stderr | 49 +++- tests/ui/needless_for_each_fixable.fixed | 6 + tests/ui/needless_for_each_fixable.rs | 6 + tests/ui/needless_for_each_fixable.stderr | 8 +- tests/ui/needless_range_loop.rs | 25 ++ tests/ui/never_loop.rs | 32 +++ tests/ui/never_loop.stderr | 71 ++++- tests/ui/or_fun_call.fixed | 4 + tests/ui/or_fun_call.rs | 4 + tests/ui/or_fun_call.stderr | 50 ++-- .../auxiliary/external.rs | 13 + tests/ui/pattern_type_mismatch/syntax.rs | 9 + tests/ui/pattern_type_mismatch/syntax.stderr | 18 +- tests/ui/ptr_arg.rs | 140 +++++++-- tests/ui/ptr_arg.stderr | 64 ++++- tests/ui/ptr_as_ptr.fixed | 8 + tests/ui/ptr_as_ptr.rs | 8 + tests/ui/ptr_as_ptr.stderr | 8 +- tests/ui/range_plus_minus_one.fixed | 134 ++++++++- tests/ui/range_plus_minus_one.rs | 126 ++++++++- tests/ui/range_plus_minus_one.stderr | 94 ++++--- .../ui/single_match_else_deref_patterns.fixed | 53 ++++ tests/ui/single_match_else_deref_patterns.rs | 94 +++++++ .../single_match_else_deref_patterns.stderr | 188 +++++++++++++ tests/ui/unsafe_derive_deserialize.rs | 29 ++ tests/ui/unsafe_derive_deserialize.stderr | 11 +- tests/ui/unused_async.rs | 10 + tests/ui/unused_trait_names.fixed | 4 +- tests/ui/unused_trait_names.rs | 2 +- tests/ui/unused_trait_names.stderr | 13 +- tests/ui/used_underscore_items.rs | 4 +- tests/ui/useless_attribute.fixed | 12 + tests/ui/useless_attribute.rs | 12 + triagebot.toml | 1 + util/gh-pages/index_template.html | 266 +++++++++--------- util/gh-pages/script.js | 27 +- util/gh-pages/style.css | 88 +++--- 166 files changed, 3824 insertions(+), 1159 deletions(-) create mode 100644 tests/ui/if_then_some_else_none_unfixable.rs create mode 100644 tests/ui/if_then_some_else_none_unfixable.stderr create mode 100644 tests/ui/large_enum_variant_no_std.rs create mode 100644 tests/ui/large_enum_variant_no_std.stderr create mode 100644 tests/ui/pattern_type_mismatch/auxiliary/external.rs create mode 100644 tests/ui/single_match_else_deref_patterns.fixed create mode 100644 tests/ui/single_match_else_deref_patterns.rs create mode 100644 tests/ui/single_match_else_deref_patterns.stderr diff --git a/.github/workflows/feature_freeze.yml b/.github/workflows/feature_freeze.yml index 7ad58af77d4a..ec59be3e7f67 100644 --- a/.github/workflows/feature_freeze.yml +++ b/.github/workflows/feature_freeze.yml @@ -20,16 +20,26 @@ jobs: # of the pull request, as malicious code would be able to access the private # GitHub token. steps: - - name: Check PR Changes - id: pr-changes - run: echo "::set-output name=changes::${{ toJson(github.event.pull_request.changed_files) }}" - - - name: Create Comment - if: steps.pr-changes.outputs.changes != '[]' - run: | - # Use GitHub API to create a comment on the PR - PR_NUMBER=${{ github.event.pull_request.number }} - COMMENT="**Seems that you are trying to add a new lint!**\nWe 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.\nThanks a lot for your contribution, and sorry for the inconvenience.\nWith ❤ from the Clippy team\n\n@rustbot note Feature-freeze\n@rustbot blocked\n@rustbot label +A-lint\n" - GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} - COMMENT_URL="https://api.github.com/repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" - curl -s -H "Authorization: token ${GITHUB_TOKEN}" -X POST $COMMENT_URL -d "{\"body\":\"$COMMENT\"}" + - 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 a7c25b29021f..36a4cdc1c352 100644 --- a/.gitignore +++ b/.gitignore @@ -19,8 +19,10 @@ out # Generated by Cargo *Cargo.lock +!/clippy_test_deps/Cargo.lock /target /clippy_lints/target +/clippy_lints_internal/target /clippy_utils/target /clippy_dev/target /lintcheck/target diff --git a/clippy_dev/src/fmt.rs b/clippy_dev/src/fmt.rs index bd9e57c9f6da..2b2138d3108d 100644 --- a/clippy_dev/src/fmt.rs +++ b/clippy_dev/src/fmt.rs @@ -3,7 +3,7 @@ use crate::utils::{ walk_dir_no_dot_or_target, }; use itertools::Itertools; -use rustc_lexer::{TokenKind, tokenize}; +use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize}; use std::fmt::Write; use std::fs; use std::io::{self, Read}; @@ -92,7 +92,7 @@ fn fmt_conf(check: bool) -> Result<(), Error> { let mut fields = Vec::new(); let mut state = State::Start; - for (i, t) in tokenize(conf) + for (i, t) in tokenize(conf, FrontmatterAllowed::No) .map(|x| { let start = pos; pos += x.len; diff --git a/clippy_lints/src/approx_const.rs b/clippy_lints/src/approx_const.rs index 5ed4c82634aa..184fbbf77962 100644 --- a/clippy_lints/src/approx_const.rs +++ b/clippy_lints/src/approx_const.rs @@ -92,9 +92,11 @@ impl LateLintPass<'_> for ApproxConstant { impl ApproxConstant { fn check_known_consts(&self, cx: &LateContext<'_>, span: Span, s: symbol::Symbol, module: &str) { let s = s.as_str(); - if s.parse::().is_ok() { + if let Ok(maybe_constant) = s.parse::() { for &(constant, name, min_digits, msrv) in &KNOWN_CONSTS { - if is_approx_const(constant, s, min_digits) && msrv.is_none_or(|msrv| self.msrv.meets(cx, msrv)) { + if is_approx_const(constant, s, maybe_constant, min_digits) + && msrv.is_none_or(|msrv| self.msrv.meets(cx, msrv)) + { span_lint_and_help( cx, APPROX_CONSTANT, @@ -112,18 +114,35 @@ impl ApproxConstant { impl_lint_pass!(ApproxConstant => [APPROX_CONSTANT]); +fn count_digits_after_dot(input: &str) -> usize { + input + .char_indices() + .find(|(_, ch)| *ch == '.') + .map_or(0, |(i, _)| input.len() - i - 1) +} + /// Returns `false` if the number of significant figures in `value` are /// less than `min_digits`; otherwise, returns true if `value` is equal -/// to `constant`, rounded to the number of digits present in `value`. +/// to `constant`, rounded to the number of significant digits present in `value`. #[must_use] -fn is_approx_const(constant: f64, value: &str, min_digits: usize) -> bool { +fn is_approx_const(constant: f64, value: &str, f_value: f64, min_digits: usize) -> bool { if value.len() <= min_digits { + // The value is not precise enough false - } else if constant.to_string().starts_with(value) { - // The value is a truncated constant + } else if f_value.to_string().len() > min_digits && constant.to_string().starts_with(&f_value.to_string()) { + // The value represents the same value true } else { - let round_const = format!("{constant:.*}", value.len() - 2); + // The value is a truncated constant + + // Print constant with numeric formatting (`0`), with the length of `value` as minimum width + // (`value_len$`), and with the same precision as `value` (`.value_prec$`). + // See https://doc.rust-lang.org/std/fmt/index.html. + let round_const = format!( + "{constant:0value_len$.value_prec$}", + value_len = value.len(), + value_prec = count_digits_after_dot(value) + ); value == round_const } } diff --git a/clippy_lints/src/arbitrary_source_item_ordering.rs b/clippy_lints/src/arbitrary_source_item_ordering.rs index a9d3015ce5c4..7b4cf0336741 100644 --- a/clippy_lints/src/arbitrary_source_item_ordering.rs +++ b/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -8,11 +8,11 @@ use clippy_utils::diagnostics::span_lint_and_note; use clippy_utils::is_cfg_test; use rustc_attr_data_structures::AttributeKind; use rustc_hir::{ - Attribute, FieldDef, HirId, IsAuto, ImplItemId, Item, ItemKind, Mod, OwnerId, QPath, TraitItemId, TyKind, - Variant, VariantData, + Attribute, FieldDef, HirId, ImplItemId, IsAuto, Item, ItemKind, Mod, OwnerId, QPath, TraitItemId, TyKind, Variant, + VariantData, }; -use rustc_middle::ty::AssocKind; use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::ty::AssocKind; use rustc_session::impl_lint_pass; use rustc_span::Ident; @@ -469,13 +469,14 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering { /// This is implemented here because `rustc_hir` is not a dependency of /// `clippy_config`. fn convert_assoc_item_kind(cx: &LateContext<'_>, owner_id: OwnerId) -> SourceItemOrderingTraitAssocItemKind { - let kind = cx.tcx.associated_item(owner_id.def_id).kind; - #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. use SourceItemOrderingTraitAssocItemKind::*; + + let kind = cx.tcx.associated_item(owner_id.def_id).kind; + match kind { - AssocKind::Const{..} => Const, - AssocKind::Type {..}=> Type, + AssocKind::Const { .. } => Const, + AssocKind::Type { .. } => Type, AssocKind::Fn { .. } => Fn, } } diff --git a/clippy_lints/src/arc_with_non_send_sync.rs b/clippy_lints/src/arc_with_non_send_sync.rs index 9e09fb5bb439..085029a744be 100644 --- a/clippy_lints/src/arc_with_non_send_sync.rs +++ b/clippy_lints/src/arc_with_non_send_sync.rs @@ -73,7 +73,7 @@ impl<'tcx> LateLintPass<'tcx> for ArcWithNonSendSync { diag.note(format!( "`Arc<{arg_ty}>` is not `Send` and `Sync` as `{arg_ty}` is {reason}" )); - diag.help("if the `Arc` will not used be across threads replace it with an `Rc`"); + diag.help("if the `Arc` will not be used across threads replace it with an `Rc`"); diag.help(format!( "otherwise make `{arg_ty}` `Send` and `Sync` or consider a wrapper type such as `Mutex`" )); diff --git a/clippy_lints/src/attrs/useless_attribute.rs b/clippy_lints/src/attrs/useless_attribute.rs index 4059f9603c33..b9b5cedb5aa7 100644 --- a/clippy_lints/src/attrs/useless_attribute.rs +++ b/clippy_lints/src/attrs/useless_attribute.rs @@ -36,6 +36,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) { | sym::unused_braces | sym::unused_import_braces | sym::unused_imports + | sym::redundant_imports ) { return; diff --git a/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs b/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs index 769cc120c950..849de22cfbaa 100644 --- a/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs +++ b/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs @@ -59,9 +59,8 @@ fn get_const_name_and_ty_name( pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { // We allow casts from any function type to any function type. - match cast_to.kind() { - ty::FnDef(..) | ty::FnPtr(..) => return, - _ => { /* continue to checks */ }, + if cast_to.is_fn() { + return; } if let ty::FnDef(def_id, generics) = cast_from.kind() diff --git a/clippy_lints/src/casts/fn_to_numeric_cast.rs b/clippy_lints/src/casts/fn_to_numeric_cast.rs index 55e27a05f3c0..c5d9643f56a5 100644 --- a/clippy_lints/src/casts/fn_to_numeric_cast.rs +++ b/clippy_lints/src/casts/fn_to_numeric_cast.rs @@ -3,7 +3,7 @@ use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::Ty; use super::{FN_TO_NUMERIC_CAST, utils}; @@ -13,23 +13,20 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, return; }; - match cast_from.kind() { - ty::FnDef(..) | ty::FnPtr(..) => { - let mut applicability = Applicability::MaybeIncorrect; + if cast_from.is_fn() { + let mut applicability = Applicability::MaybeIncorrect; - if to_nbits >= cx.tcx.data_layout.pointer_size().bits() && !cast_to.is_usize() { - let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); - span_lint_and_sugg( - cx, - FN_TO_NUMERIC_CAST, - expr.span, - format!("casting function pointer `{from_snippet}` to `{cast_to}`"), - "try", - format!("{from_snippet} as usize"), - applicability, - ); - } - }, - _ => {}, + if to_nbits >= cx.tcx.data_layout.pointer_size().bits() && !cast_to.is_usize() { + let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); + span_lint_and_sugg( + cx, + FN_TO_NUMERIC_CAST, + expr.span, + format!("casting function pointer `{from_snippet}` to `{cast_to}`"), + "try", + format!("{from_snippet} as usize"), + applicability, + ); + } } } diff --git a/clippy_lints/src/casts/fn_to_numeric_cast_any.rs b/clippy_lints/src/casts/fn_to_numeric_cast_any.rs index b22e8f4ee891..43ee91af6e5a 100644 --- a/clippy_lints/src/casts/fn_to_numeric_cast_any.rs +++ b/clippy_lints/src/casts/fn_to_numeric_cast_any.rs @@ -3,18 +3,17 @@ use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::Ty; use super::FN_TO_NUMERIC_CAST_ANY; pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { // We allow casts from any function type to any function type. - match cast_to.kind() { - ty::FnDef(..) | ty::FnPtr(..) => return, - _ => { /* continue to checks */ }, + if cast_to.is_fn() { + return; } - if let ty::FnDef(..) | ty::FnPtr(..) = cast_from.kind() { + if cast_from.is_fn() { let mut applicability = Applicability::MaybeIncorrect; let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability); diff --git a/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs b/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs index 4da79205e208..9a2e44e07d4b 100644 --- a/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs +++ b/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs @@ -3,7 +3,7 @@ use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::Ty; use super::{FN_TO_NUMERIC_CAST_WITH_TRUNCATION, utils}; @@ -12,23 +12,20 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, let Some(to_nbits) = utils::int_ty_to_nbits(cx.tcx, cast_to) else { return; }; - match cast_from.kind() { - ty::FnDef(..) | ty::FnPtr(..) => { - let mut applicability = Applicability::MaybeIncorrect; - let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); + if cast_from.is_fn() { + let mut applicability = Applicability::MaybeIncorrect; + let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); - if to_nbits < cx.tcx.data_layout.pointer_size().bits() { - span_lint_and_sugg( - cx, - FN_TO_NUMERIC_CAST_WITH_TRUNCATION, - expr.span, - format!("casting function pointer `{from_snippet}` to `{cast_to}`, which truncates the value"), - "try", - format!("{from_snippet} as usize"), - applicability, - ); - } - }, - _ => {}, + if to_nbits < cx.tcx.data_layout.pointer_size().bits() { + span_lint_and_sugg( + cx, + FN_TO_NUMERIC_CAST_WITH_TRUNCATION, + expr.span, + format!("casting function pointer `{from_snippet}` to `{cast_to}`, which truncates the value"), + "try", + format!("{from_snippet} as usize"), + applicability, + ); + } } } diff --git a/clippy_lints/src/casts/ptr_as_ptr.rs b/clippy_lints/src/casts/ptr_as_ptr.rs index 6f944914b8fd..ee0f3fa81c6d 100644 --- a/clippy_lints/src/casts/ptr_as_ptr.rs +++ b/clippy_lints/src/casts/ptr_as_ptr.rs @@ -4,10 +4,9 @@ use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, Mutability, QPath, TyKind}; -use rustc_hir_pretty::qpath_to_string; use rustc_lint::LateContext; use rustc_middle::ty; -use rustc_span::sym; +use rustc_span::{Span, sym}; use super::PTR_AS_PTR; @@ -74,7 +73,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) { let (help, final_suggestion) = if let Some(method) = omit_cast.corresponding_item() { // don't force absolute path - let method = qpath_to_string(&cx.tcx, method); + let method = snippet_with_applicability(cx, qpath_span_without_turbofish(method), "..", &mut app); ("try call directly", format!("{method}{turbofish}()")) } else { let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app); @@ -96,3 +95,14 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) { ); } } + +fn qpath_span_without_turbofish(qpath: &QPath<'_>) -> Span { + if let QPath::Resolved(_, path) = qpath + && let [.., last_ident] = path.segments + && last_ident.args.is_some() + { + return qpath.span().shrink_to_lo().to(last_ident.ident.span); + } + + qpath.span() +} diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs index d5d937d91338..518535e8c8bf 100644 --- a/clippy_lints/src/cognitive_complexity.rs +++ b/clippy_lints/src/cognitive_complexity.rs @@ -110,7 +110,6 @@ impl CognitiveComplexity { FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) => ident.span, FnKind::Closure => { let header_span = body_span.with_hi(decl.output.span().lo()); - #[expect(clippy::range_plus_one)] if let Some(range) = header_span.map_range(cx, |_, src, range| { let mut idxs = src.get(range.clone())?.match_indices('|'); Some(range.start + idxs.next()?.0..range.start + idxs.next()?.0 + 1) diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 27918698cd6b..4bd34527d21f 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -1,5 +1,6 @@ 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; @@ -11,7 +12,7 @@ use clippy_utils::{ use core::iter; use core::ops::ControlFlow; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, HirIdSet, LetStmt, Node, Stmt, StmtKind, intravisit}; +use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, LetStmt, Node, Stmt, StmtKind, intravisit}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::TyCtxt; use rustc_session::impl_lint_pass; @@ -189,24 +190,13 @@ impl<'tcx> LateLintPass<'tcx> for CopyAndPaste<'tcx> { } } -/// Checks if the given expression is a let chain. -fn contains_let(e: &Expr<'_>) -> bool { - match e.kind { - ExprKind::Let(..) => true, - ExprKind::Binary(op, lhs, rhs) if op.node == BinOpKind::And => { - matches!(lhs.kind, ExprKind::Let(..)) || contains_let(rhs) - }, - _ => false, - } -} - 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) && !contains_let(conds[i]) && conds.get(i + 1).is_none_or(|e| !contains_let(e)) { + 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, diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 062f7cef3a72..49dd1bb09c61 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -432,6 +432,11 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { 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(()); } diff --git a/clippy_lints/src/disallowed_macros.rs b/clippy_lints/src/disallowed_macros.rs index d55aeae98ede..23e7c7251cf1 100644 --- a/clippy_lints/src/disallowed_macros.rs +++ b/clippy_lints/src/disallowed_macros.rs @@ -72,11 +72,11 @@ pub struct DisallowedMacros { // When a macro is disallowed in an early pass, it's stored // and emitted during the late pass. This happens for attributes. - earlies: AttrStorage, + early_macro_cache: AttrStorage, } impl DisallowedMacros { - pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, earlies: AttrStorage) -> Self { + pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, early_macro_cache: AttrStorage) -> Self { let (disallowed, _) = create_disallowed_map( tcx, &conf.disallowed_macros, @@ -89,7 +89,7 @@ impl DisallowedMacros { disallowed, seen: FxHashSet::default(), derive_src: None, - earlies, + early_macro_cache, } } @@ -130,7 +130,7 @@ impl_lint_pass!(DisallowedMacros => [DISALLOWED_MACROS]); impl LateLintPass<'_> for DisallowedMacros { fn check_crate(&mut self, cx: &LateContext<'_>) { // once we check a crate in the late pass we can emit the early pass lints - if let Some(attr_spans) = self.earlies.clone().0.get() { + if let Some(attr_spans) = self.early_macro_cache.clone().0.get() { for span in attr_spans { self.check(cx, *span, None); } diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 22b781b89294..ea0da0d24675 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -1232,7 +1232,6 @@ fn check_doc<'a, Events: Iterator, Range) -> Option> { if range.end < range.start { return None; diff --git a/clippy_lints/src/empty_with_brackets.rs b/clippy_lints/src/empty_with_brackets.rs index 4414aebbf9a3..f2757407ba57 100644 --- a/clippy_lints/src/empty_with_brackets.rs +++ b/clippy_lints/src/empty_with_brackets.rs @@ -92,8 +92,10 @@ impl_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM_VA impl LateLintPass<'_> for EmptyWithBrackets { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + // FIXME: handle `struct $name {}` if let ItemKind::Struct(ident, _, var_data) = &item.kind && !item.span.from_expansion() + && !ident.span.from_expansion() && has_brackets(var_data) && let span_after_ident = item.span.with_lo(ident.span.hi()) && has_no_fields(cx, var_data, span_after_ident) @@ -116,10 +118,12 @@ impl LateLintPass<'_> for EmptyWithBrackets { } fn check_variant(&mut self, cx: &LateContext<'_>, variant: &Variant<'_>) { - // the span of the parentheses/braces - let span_after_ident = variant.span.with_lo(variant.ident.span.hi()); - - if has_no_fields(cx, &variant.data, span_after_ident) { + // FIXME: handle `$name {}` + if !variant.span.from_expansion() + && !variant.ident.span.from_expansion() + && let span_after_ident = variant.span.with_lo(variant.ident.span.hi()) + && has_no_fields(cx, &variant.data, span_after_ident) + { match variant.data { VariantData::Struct { .. } => { // Empty struct variants can be linted immediately diff --git a/clippy_lints/src/escape.rs b/clippy_lints/src/escape.rs index db2fea1aae95..fc224fa5f924 100644 --- a/clippy_lints/src/escape.rs +++ b/clippy_lints/src/escape.rs @@ -1,8 +1,8 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_hir; use rustc_abi::ExternAbi; -use rustc_hir::{Body, FnDecl, HirId, HirIdSet, Node, Pat, PatKind, intravisit}; use rustc_hir::def::DefKind; +use rustc_hir::{Body, FnDecl, HirId, HirIdSet, Node, Pat, PatKind, intravisit}; use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::FakeReadCause; @@ -87,16 +87,14 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal { let mut trait_self_ty = None; match cx.tcx.def_kind(parent_id) { // If the method is an impl for a trait, don't warn. - DefKind::Impl { of_trait: true } => { - return - } + DefKind::Impl { of_trait: true } => return, // find `self` ty for this trait if relevant DefKind::Trait => { trait_self_ty = Some(TraitRef::identity(cx.tcx, parent_id.to_def_id()).self_ty()); - } + }, - _ => {} + _ => {}, } let mut v = EscapeDelegate { diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 0288747d6f3e..9b627678bd35 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -29,12 +29,6 @@ declare_clippy_lint! { /// Needlessly creating a closure adds code for no benefit /// and gives the optimizer more work. /// - /// ### Known problems - /// If creating the closure inside the closure has a side- - /// effect then moving the closure creation out will change when that side- - /// effect runs. - /// See [#1439](https://github.com/rust-lang/rust-clippy/issues/1439) for more details. - /// /// ### Example /// ```rust,ignore /// xs.map(|x| foo(x)) diff --git a/clippy_lints/src/fallible_impl_from.rs b/clippy_lints/src/fallible_impl_from.rs index 552cd721f4ef..fdfcbb540bce 100644 --- a/clippy_lints/src/fallible_impl_from.rs +++ b/clippy_lints/src/fallible_impl_from.rs @@ -64,8 +64,8 @@ impl<'tcx> LateLintPass<'tcx> for FallibleImplFrom { } fn lint_impl_body(cx: &LateContext<'_>, item_def_id: hir::OwnerId, impl_span: Span) { - use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::Expr; + use rustc_hir::intravisit::{self, Visitor}; struct FindPanicUnwrap<'a, 'tcx> { lcx: &'a LateContext<'tcx>, @@ -96,10 +96,12 @@ fn lint_impl_body(cx: &LateContext<'_>, item_def_id: hir::OwnerId, impl_span: Sp } } - for impl_item in cx.tcx.associated_items(item_def_id) + for impl_item in cx + .tcx + .associated_items(item_def_id) .filter_by_name_unhygienic_and_kind(sym::from, ty::AssocTag::Fn) { - let impl_item_def_id= impl_item.def_id.expect_local(); + let impl_item_def_id = impl_item.def_id.expect_local(); // check the body for `begin_panic` or `unwrap` let body = cx.tcx.hir_body_owned_by(impl_item_def_id); diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index 16c58ecb455e..a251f15ba3da 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -17,7 +17,7 @@ use rustc_ast::{ FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions, FormatPlaceholder, FormatTrait, }; -use rustc_attr_data_structures::RustcVersion; +use rustc_attr_data_structures::{AttributeKind, RustcVersion, find_attr}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; use rustc_errors::SuggestionStyle::{CompletelyHidden, ShowCode}; @@ -30,7 +30,6 @@ use rustc_span::edition::Edition::Edition2021; use rustc_span::{Span, Symbol, sym}; use rustc_trait_selection::infer::TyCtxtInferExt; use rustc_trait_selection::traits::{Obligation, ObligationCause, Selection, SelectionContext}; -use rustc_attr_data_structures::{AttributeKind, find_attr}; declare_clippy_lint! { /// ### What it does @@ -129,6 +128,7 @@ declare_clippy_lint! { /// # let width = 1; /// # let prec = 2; /// format!("{}", var); + /// format!("{:?}", var); /// format!("{v:?}", v = var); /// format!("{0} {0}", var); /// format!("{0:1$}", var, width); @@ -141,6 +141,7 @@ declare_clippy_lint! { /// # let prec = 2; /// format!("{var}"); /// format!("{var:?}"); + /// format!("{var:?}"); /// format!("{var} {var}"); /// format!("{var:width$}"); /// format!("{var:.prec$}"); @@ -164,7 +165,7 @@ declare_clippy_lint! { /// nothing will be suggested, e.g. `println!("{0}={1}", var, 1+2)`. #[clippy::version = "1.66.0"] pub UNINLINED_FORMAT_ARGS, - style, + pedantic, "using non-inlined variables in `format!` calls" } @@ -657,7 +658,10 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { }; let selection = SelectionContext::new(&infcx).select(&obligation); let derived = if let Ok(Some(Selection::UserDefined(data))) = selection { - find_attr!(tcx.get_all_attrs(data.impl_def_id), AttributeKind::AutomaticallyDerived(..)) + find_attr!( + tcx.get_all_attrs(data.impl_def_id), + AttributeKind::AutomaticallyDerived(..) + ) } else { false }; diff --git a/clippy_lints/src/from_over_into.rs b/clippy_lints/src/from_over_into.rs index 85b40ba7419b..1da6952eb64c 100644 --- a/clippy_lints/src/from_over_into.rs +++ b/clippy_lints/src/from_over_into.rs @@ -9,7 +9,7 @@ use clippy_utils::source::SpanRangeExt; use rustc_errors::Applicability; use rustc_hir::intravisit::{Visitor, walk_path}; use rustc_hir::{ - FnRetTy, GenericArg, GenericArgs, HirId, Impl, ImplItemKind, ImplItemId, Item, ItemKind, PatKind, Path, + FnRetTy, GenericArg, GenericArgs, HirId, Impl, ImplItemId, ImplItemKind, Item, ItemKind, PatKind, Path, PathSegment, Ty, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; diff --git a/clippy_lints/src/functions/must_use.rs b/clippy_lints/src/functions/must_use.rs index d959981a83ce..b8d0cec5aeb1 100644 --- a/clippy_lints/src/functions/must_use.rs +++ b/clippy_lints/src/functions/must_use.rs @@ -10,7 +10,7 @@ use rustc_span::{Span, sym}; use clippy_utils::attrs::is_proc_macro; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then}; -use clippy_utils::source::SpanRangeExt; +use clippy_utils::source::snippet_indent; use clippy_utils::ty::is_must_use_ty; use clippy_utils::visitors::for_each_expr_without_closures; use clippy_utils::{return_ty, trait_ref_of_method}; @@ -28,6 +28,7 @@ pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_> if let hir::ItemKind::Fn { ref sig, body: ref body_id, + ident, .. } = item.kind { @@ -51,8 +52,8 @@ pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_> sig.decl, cx.tcx.hir_body(*body_id), item.span, + ident.span, item.owner_id, - item.span.with_hi(sig.decl.output.span().hi()), "this function could have a `#[must_use]` attribute", ); } @@ -84,8 +85,8 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Imp sig.decl, cx.tcx.hir_body(*body_id), item.span, + item.ident.span, item.owner_id, - item.span.with_hi(sig.decl.output.span().hi()), "this method could have a `#[must_use]` attribute", ); } @@ -120,8 +121,8 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Tr sig.decl, body, item.span, + item.ident.span, item.owner_id, - item.span.with_hi(sig.decl.output.span().hi()), "this method could have a `#[must_use]` attribute", ); } @@ -198,8 +199,8 @@ fn check_must_use_candidate<'tcx>( decl: &'tcx hir::FnDecl<'_>, body: &'tcx hir::Body<'_>, item_span: Span, + ident_span: Span, item_id: hir::OwnerId, - fn_span: Span, msg: &'static str, ) { if has_mutable_arg(cx, body) @@ -208,18 +209,18 @@ fn check_must_use_candidate<'tcx>( || returns_unit(decl) || !cx.effective_visibilities.is_exported(item_id.def_id) || is_must_use_ty(cx, return_ty(cx, item_id)) + || item_span.from_expansion() { return; } - span_lint_and_then(cx, MUST_USE_CANDIDATE, fn_span, msg, |diag| { - if let Some(snippet) = fn_span.get_source_text(cx) { - diag.span_suggestion( - fn_span, - "add the attribute", - format!("#[must_use] {snippet}"), - Applicability::MachineApplicable, - ); - } + span_lint_and_then(cx, MUST_USE_CANDIDATE, ident_span, msg, |diag| { + let indent = snippet_indent(cx, item_span).unwrap_or_default(); + diag.span_suggestion( + item_span.shrink_to_lo(), + "add the attribute", + format!("#[must_use] \n{indent}"), + Applicability::MachineApplicable, + ); }); } diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index 9e94280fc074..7158f9419c1c 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -5,7 +5,8 @@ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::Sugg; use clippy_utils::{ - contains_return, higher, is_else_clause, is_in_const_context, is_res_lang_ctor, path_res, peel_blocks, + contains_return, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, is_res_lang_ctor, + path_res, peel_blocks, }; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome}; @@ -92,6 +93,10 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { expr.span, format!("this could be simplified with `bool::{method_name}`"), |diag| { + if expr_adjustment_requires_coercion(cx, then_arg) { + return; + } + let mut app = Applicability::MachineApplicable; let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app) .maybe_paren() diff --git a/clippy_lints/src/incompatible_msrv.rs b/clippy_lints/src/incompatible_msrv.rs index 5d0bd3e8ca30..116d63c3bb15 100644 --- a/clippy_lints/src/incompatible_msrv.rs +++ b/clippy_lints/src/incompatible_msrv.rs @@ -1,10 +1,11 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_in_test; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::Msrv; +use clippy_utils::{is_in_const_context, is_in_test}; use rustc_attr_data_structures::{RustcVersion, StabilityLevel, StableSince}; use rustc_data_structures::fx::FxHashMap; -use rustc_hir::{Expr, ExprKind, HirId, QPath}; +use rustc_hir::def::DefKind; +use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, HirId, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::TyCtxt; use rustc_session::impl_lint_pass; @@ -33,15 +34,54 @@ declare_clippy_lint! { /// /// To fix this problem, either increase your MSRV or use another item /// available in your current MSRV. + /// + /// You can also locally change the MSRV that should be checked by Clippy, + /// for example if a feature in your crate (e.g., `modern_compiler`) should + /// allow you to use an item: + /// + /// ```no_run + /// //! This crate has a MSRV of 1.3.0, but we also have an optional feature + /// //! `sleep_well` which requires at least Rust 1.4.0. + /// + /// // When the `sleep_well` feature is set, do not warn for functions available + /// // in Rust 1.4.0 and below. + /// #![cfg_attr(feature = "sleep_well", clippy::msrv = "1.4.0")] + /// + /// use std::time::Duration; + /// + /// #[cfg(feature = "sleep_well")] + /// fn sleep_for_some_time() { + /// std::thread::sleep(Duration::new(1, 0)); // Will not trigger the lint + /// } + /// ``` + /// + /// You can also increase the MSRV in tests, by using: + /// + /// ```no_run + /// // Use a much higher MSRV for tests while keeping the main one low + /// #![cfg_attr(test, clippy::msrv = "1.85.0")] + /// + /// #[test] + /// fn my_test() { + /// // The tests can use items introduced in Rust 1.85.0 and lower + /// // without triggering the `incompatible_msrv` lint. + /// } + /// ``` #[clippy::version = "1.78.0"] pub INCOMPATIBLE_MSRV, suspicious, "ensures that all items used in the crate are available for the current MSRV" } +#[derive(Clone, Copy)] +enum Availability { + FeatureEnabled, + Since(RustcVersion), +} + pub struct IncompatibleMsrv { msrv: Msrv, - is_above_msrv: FxHashMap, + availability_cache: FxHashMap<(DefId, bool), Availability>, check_in_tests: bool, } @@ -51,38 +91,50 @@ impl IncompatibleMsrv { pub fn new(conf: &'static Conf) -> Self { Self { msrv: conf.msrv, - is_above_msrv: FxHashMap::default(), + availability_cache: FxHashMap::default(), check_in_tests: conf.check_incompatible_msrv_in_tests, } } - fn get_def_id_version(&mut self, tcx: TyCtxt<'_>, def_id: DefId) -> RustcVersion { - if let Some(version) = self.is_above_msrv.get(&def_id) { - return *version; + /// Returns the availability of `def_id`, whether it is enabled through a feature or + /// available since a given version (the default being Rust 1.0.0). `needs_const` requires + /// the `const`-stability to be looked up instead of the stability in non-`const` contexts. + fn get_def_id_availability(&mut self, tcx: TyCtxt<'_>, def_id: DefId, needs_const: bool) -> Availability { + if let Some(availability) = self.availability_cache.get(&(def_id, needs_const)) { + return *availability; } - let version = if let Some(version) = tcx - .lookup_stability(def_id) - .and_then(|stability| match stability.level { - StabilityLevel::Stable { - since: StableSince::Version(version), - .. - } => Some(version), - _ => None, - }) { - version - } else if let Some(parent_def_id) = tcx.opt_parent(def_id) { - self.get_def_id_version(tcx, parent_def_id) + let (feature, stability_level) = if needs_const { + tcx.lookup_const_stability(def_id) + .map(|stability| (stability.feature, stability.level)) + .unzip() } else { - RustcVersion { + tcx.lookup_stability(def_id) + .map(|stability| (stability.feature, stability.level)) + .unzip() + }; + let version = if feature.is_some_and(|feature| tcx.features().enabled(feature)) { + Availability::FeatureEnabled + } else if let Some(StableSince::Version(version)) = + stability_level.as_ref().and_then(StabilityLevel::stable_since) + { + Availability::Since(version) + } else if needs_const { + // Fallback to regular stability + self.get_def_id_availability(tcx, def_id, false) + } else if let Some(parent_def_id) = tcx.opt_parent(def_id) { + self.get_def_id_availability(tcx, parent_def_id, needs_const) + } else { + Availability::Since(RustcVersion { major: 1, minor: 0, patch: 0, - } + }) }; - self.is_above_msrv.insert(def_id, version); + self.availability_cache.insert((def_id, needs_const), version); version } + /// Emit lint if `def_id`, associated with `node` and `span`, is below the current MSRV. fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, node: HirId, span: Span) { if def_id.is_local() { // We don't check local items since their MSRV is supposed to always be valid. @@ -108,18 +160,28 @@ impl IncompatibleMsrv { return; } + let needs_const = cx.enclosing_body.is_some() + && is_in_const_context(cx) + && matches!(cx.tcx.def_kind(def_id), DefKind::AssocFn | DefKind::Fn); + if (self.check_in_tests || !is_in_test(cx.tcx, node)) && let Some(current) = self.msrv.current(cx) - && let version = self.get_def_id_version(cx.tcx, def_id) + && let Availability::Since(version) = self.get_def_id_availability(cx.tcx, def_id, needs_const) && version > current { - span_lint( + span_lint_and_then( cx, INCOMPATIBLE_MSRV, span, format!( - "current MSRV (Minimum Supported Rust Version) is `{current}` but this item is stable since `{version}`" + "current MSRV (Minimum Supported Rust Version) is `{current}` but this item is stable{} since `{version}`", + if needs_const { " in a `const` context" } else { "" }, ), + |diag| { + if is_under_cfg_attribute(cx, node) { + diag.note_once("you may want to conditionally increase the MSRV considered by Clippy using the `clippy::msrv` attribute"); + } + }, ); } } @@ -133,17 +195,38 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv { self.emit_lint_if_under_msrv(cx, method_did, expr.hir_id, span); } }, - ExprKind::Call(call, _) => { - // Desugaring into function calls by the compiler will use `QPath::LangItem` variants. Those should - // not be linted as they will not be generated in older compilers if the function is not available, - // and the compiler is allowed to call unstable functions. - if let ExprKind::Path(qpath @ (QPath::Resolved(..) | QPath::TypeRelative(..))) = call.kind - && let Some(path_def_id) = cx.qpath_res(&qpath, call.hir_id).opt_def_id() - { - self.emit_lint_if_under_msrv(cx, path_def_id, expr.hir_id, call.span); + // Desugaring into function calls by the compiler will use `QPath::LangItem` variants. Those should + // not be linted as they will not be generated in older compilers if the function is not available, + // and the compiler is allowed to call unstable functions. + ExprKind::Path(qpath @ (QPath::Resolved(..) | QPath::TypeRelative(..))) => { + if let Some(path_def_id) = cx.qpath_res(&qpath, expr.hir_id).opt_def_id() { + self.emit_lint_if_under_msrv(cx, path_def_id, expr.hir_id, expr.span); } }, _ => {}, } } + + fn check_ty(&mut self, cx: &LateContext<'tcx>, hir_ty: &'tcx hir::Ty<'tcx, AmbigArg>) { + if let hir::TyKind::Path(qpath @ (QPath::Resolved(..) | QPath::TypeRelative(..))) = hir_ty.kind + && let Some(ty_def_id) = cx.qpath_res(&qpath, hir_ty.hir_id).opt_def_id() + // `CStr` and `CString` have been moved around but have been available since Rust 1.0.0 + && !matches!(cx.tcx.get_diagnostic_name(ty_def_id), Some(sym::cstr_type | sym::cstring_type)) + { + self.emit_lint_if_under_msrv(cx, ty_def_id, hir_ty.hir_id, hir_ty.span); + } + } +} + +/// Heuristic checking if the node `hir_id` is under a `#[cfg()]` or `#[cfg_attr()]` +/// attribute. +fn is_under_cfg_attribute(cx: &LateContext<'_>, hir_id: HirId) -> bool { + cx.tcx.hir_parent_id_iter(hir_id).any(|id| { + cx.tcx.hir_attrs(id).iter().any(|attr| { + matches!( + attr.ident().map(|ident| ident.name), + Some(sym::cfg_trace | sym::cfg_attr_trace) + ) + }) + }) } diff --git a/clippy_lints/src/ineffective_open_options.rs b/clippy_lints/src/ineffective_open_options.rs index 7a751514b647..a159f6157183 100644 --- a/clippy_lints/src/ineffective_open_options.rs +++ b/clippy_lints/src/ineffective_open_options.rs @@ -1,13 +1,12 @@ -use crate::methods::method_call; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::{peel_blocks, sym}; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{peel_blocks, peel_hir_expr_while, sym}; use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; use rustc_session::declare_lint_pass; -use rustc_span::{BytePos, Span}; declare_clippy_lint! { /// ### What it does @@ -43,53 +42,58 @@ declare_clippy_lint! { declare_lint_pass!(IneffectiveOpenOptions => [INEFFECTIVE_OPEN_OPTIONS]); -fn index_if_arg_is_boolean(args: &[Expr<'_>], call_span: Span) -> Option { - if let [arg] = args - && let ExprKind::Lit(lit) = peel_blocks(arg).kind - && lit.node == LitKind::Bool(true) - { - // The `.` is not included in the span so we cheat a little bit to include it as well. - Some(call_span.with_lo(call_span.lo() - BytePos(1))) - } else { - None - } -} - impl<'tcx> LateLintPass<'tcx> for IneffectiveOpenOptions { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let Some((sym::open, mut receiver, [_arg], _, _)) = method_call(expr) else { - return; - }; - let receiver_ty = cx.typeck_results().expr_ty(receiver); - match receiver_ty.peel_refs().kind() { - ty::Adt(adt, _) if cx.tcx.is_diagnostic_item(sym::FsOpenOptions, adt.did()) => {}, - _ => return, - } - - let mut append = None; - let mut write = None; - - while let Some((name, recv, args, _, span)) = method_call(receiver) { - if name == sym::append { - append = index_if_arg_is_boolean(args, span); - } else if name == sym::write { - write = index_if_arg_is_boolean(args, span); - } - receiver = recv; - } - - if let Some(write_span) = write - && append.is_some() + if let ExprKind::MethodCall(name, recv, [_], _) = expr.kind + && name.ident.name == sym::open + && !expr.span.from_expansion() + && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv).peel_refs(), sym::FsOpenOptions) { - span_lint_and_sugg( - cx, - INEFFECTIVE_OPEN_OPTIONS, - write_span, - "unnecessary use of `.write(true)` because there is `.append(true)`", - "remove `.write(true)`", - String::new(), - Applicability::MachineApplicable, - ); + let mut append = false; + let mut write = None; + peel_hir_expr_while(recv, |e| { + if let ExprKind::MethodCall(name, recv, args, call_span) = e.kind + && !e.span.from_expansion() + { + if let [arg] = args + && let ExprKind::Lit(lit) = peel_blocks(arg).kind + && matches!(lit.node, LitKind::Bool(true)) + && !arg.span.from_expansion() + && !lit.span.from_expansion() + { + match name.ident.name { + sym::append => append = true, + sym::write + if let Some(range) = call_span.map_range(cx, |_, text, range| { + if text.get(..range.start)?.ends_with('.') { + Some(range.start - 1..range.end) + } else { + None + } + }) => + { + write = Some(call_span.with_lo(range.start)); + }, + _ => {}, + } + } + Some(recv) + } else { + None + } + }); + + if append && let Some(write_span) = write { + span_lint_and_sugg( + cx, + INEFFECTIVE_OPEN_OPTIONS, + write_span, + "unnecessary use of `.write(true)` because there is `.append(true)`", + "remove `.write(true)`", + String::new(), + Applicability::MachineApplicable, + ); + } } } } diff --git a/clippy_lints/src/infallible_try_from.rs b/clippy_lints/src/infallible_try_from.rs index e79fcec6e6ac..f7cdf05359a3 100644 --- a/clippy_lints/src/infallible_try_from.rs +++ b/clippy_lints/src/infallible_try_from.rs @@ -52,13 +52,17 @@ impl<'tcx> LateLintPass<'tcx> for InfallibleTryFrom { if !cx.tcx.is_diagnostic_item(sym::TryFrom, trait_def_id) { return; } - for ii in cx.tcx.associated_items(item.owner_id.def_id) + for ii in cx + .tcx + .associated_items(item.owner_id.def_id) .filter_by_name_unhygienic_and_kind(sym::Error, AssocTag::Type) { let ii_ty = cx.tcx.type_of(ii.def_id).instantiate_identity(); if !ii_ty.is_inhabited_from(cx.tcx, ii.def_id, cx.typing_env()) { let mut span = MultiSpan::from_span(cx.tcx.def_span(item.owner_id.to_def_id())); - let ii_ty_span = cx.tcx.hir_node_by_def_id(ii.def_id.expect_local()) + let ii_ty_span = cx + .tcx + .hir_node_by_def_id(ii.def_id.expect_local()) .expect_impl_item() .expect_type() .span; diff --git a/clippy_lints/src/item_name_repetitions.rs b/clippy_lints/src/item_name_repetitions.rs index 9c91cf680851..95e16aae40f9 100644 --- a/clippy_lints/src/item_name_repetitions.rs +++ b/clippy_lints/src/item_name_repetitions.rs @@ -8,6 +8,7 @@ use rustc_data_structures::fx::FxHashSet; use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, QPath, TyKind, Variant, VariantData}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; +use rustc_span::MacroKind; use rustc_span::symbol::Symbol; declare_clippy_lint! { @@ -502,7 +503,8 @@ impl LateLintPass<'_> for ItemNameRepetitions { ); } - if both_are_public && item_camel.len() > mod_camel.len() { + let is_macro_rule = matches!(item.kind, ItemKind::Macro(_, _, MacroKind::Bang)); + if both_are_public && item_camel.len() > mod_camel.len() && !is_macro_rule { let matching = count_match_start(mod_camel, &item_camel); let rmatching = count_match_end(mod_camel, &item_camel); let nchars = mod_camel.chars().count(); diff --git a/clippy_lints/src/iter_without_into_iter.rs b/clippy_lints/src/iter_without_into_iter.rs index 03038f0ab49c..b89f91f7255f 100644 --- a/clippy_lints/src/iter_without_into_iter.rs +++ b/clippy_lints/src/iter_without_into_iter.rs @@ -139,11 +139,17 @@ impl LateLintPass<'_> for IterWithoutIntoIter { // We can't check inherent impls for slices, but we know that they have an `iter(_mut)` method ty.peel_refs().is_slice() || get_adt_inherent_method(cx, ty, expected_method_name).is_some() }) - && let Some(iter_assoc_span) = cx.tcx.associated_items(item.owner_id) + && let Some(iter_assoc_span) = cx + .tcx + .associated_items(item.owner_id) .filter_by_name_unhygienic_and_kind(sym::IntoIter, ty::AssocTag::Type) .next() .map(|assoc_item| { - cx.tcx.hir_node_by_def_id(assoc_item.def_id.expect_local()).expect_impl_item().expect_type().span + cx.tcx + .hir_node_by_def_id(assoc_item.def_id.expect_local()) + .expect_impl_item() + .expect_type() + .span }) && is_ty_exported(cx, ty) { diff --git a/clippy_lints/src/large_enum_variant.rs b/clippy_lints/src/large_enum_variant.rs index e85d779b4880..c2b739431060 100644 --- a/clippy_lints/src/large_enum_variant.rs +++ b/clippy_lints/src/large_enum_variant.rs @@ -1,5 +1,6 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_no_std_crate; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::{AdtVariantInfo, approx_ty_size, is_copy}; use rustc_errors::Applicability; @@ -83,7 +84,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant { let mut difference = variants_size[0].size - variants_size[1].size; if difference > self.maximum_size_difference_allowed { - let help_text = "consider boxing the large fields to reduce the total size of the enum"; + let help_text = "consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum"; span_lint_and_then( cx, LARGE_ENUM_VARIANT, @@ -117,7 +118,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant { ident.span, "boxing a variant would require the type no longer be `Copy`", ); - } else { + } else if !is_no_std_crate(cx) { let sugg: Vec<(Span, String)> = variants_size[0] .fields_size .iter() diff --git a/clippy_lints/src/legacy_numeric_constants.rs b/clippy_lints/src/legacy_numeric_constants.rs index b3c63f022d35..42c636505c01 100644 --- a/clippy_lints/src/legacy_numeric_constants.rs +++ b/clippy_lints/src/legacy_numeric_constants.rs @@ -1,7 +1,8 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_from_proc_macro; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::{get_parent_expr, is_from_proc_macro}; +use clippy_utils::source::SpanRangeExt; use hir::def_id::DefId; use rustc_errors::Applicability; use rustc_hir as hir; @@ -102,39 +103,45 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants { } fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) { - let ExprKind::Path(qpath) = &expr.kind else { - return; - }; - // `std::::` check - let (span, sugg, msg) = if let QPath::Resolved(None, path) = qpath + let (sugg, msg) = if let ExprKind::Path(qpath) = &expr.kind + && let QPath::Resolved(None, path) = qpath && let Some(def_id) = path.res.opt_def_id() && is_numeric_const(cx, def_id) - && let def_path = cx.get_def_path(def_id) - && let [.., mod_name, name] = &*def_path + && let [.., mod_name, name] = &*cx.get_def_path(def_id) // Skip linting if this usage looks identical to the associated constant, // since this would only require removing a `use` import (which is already linted). && !is_numeric_const_path_canonical(path, [*mod_name, *name]) { ( - expr.span, - format!("{mod_name}::{name}"), + vec![(expr.span, format!("{mod_name}::{name}"))], "usage of a legacy numeric constant", ) // `::xxx_value` check - } else if let QPath::TypeRelative(_, last_segment) = qpath - && let Some(def_id) = cx.qpath_res(qpath, expr.hir_id).opt_def_id() - && let Some(par_expr) = get_parent_expr(cx, expr) - && let ExprKind::Call(_, []) = par_expr.kind + } else if let ExprKind::Call(func, []) = &expr.kind + && let ExprKind::Path(qpath) = &func.kind + && let QPath::TypeRelative(ty, last_segment) = qpath + && let Some(def_id) = cx.qpath_res(qpath, func.hir_id).opt_def_id() && is_integer_method(cx, def_id) { - let name = last_segment.ident.name.as_str(); - - ( - last_segment.ident.span.with_hi(par_expr.span.hi()), - name[..=2].to_ascii_uppercase(), - "usage of a legacy numeric method", - ) + let mut sugg = vec![ + // Replace the function name up to the end by the constant name + ( + last_segment.ident.span.to(expr.span.shrink_to_hi()), + last_segment.ident.name.as_str()[..=2].to_ascii_uppercase(), + ), + ]; + let before_span = expr.span.shrink_to_lo().until(ty.span); + if !before_span.is_empty() { + // Remove everything before the type name + sugg.push((before_span, String::new())); + } + // Use `::` between the type name and the constant + let between_span = ty.span.shrink_to_hi().until(last_segment.ident.span); + if !between_span.check_source_text(cx, |s| s == "::") { + sugg.push((between_span, String::from("::"))); + } + (sugg, "usage of a legacy numeric method") } else { return; }; @@ -143,9 +150,8 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants { && self.msrv.meets(cx, msrvs::NUMERIC_ASSOCIATED_CONSTANTS) && !is_from_proc_macro(cx, expr) { - span_lint_hir_and_then(cx, LEGACY_NUMERIC_CONSTANTS, expr.hir_id, span, msg, |diag| { - diag.span_suggestion_verbose( - span, + span_lint_and_then(cx, LEGACY_NUMERIC_CONSTANTS, expr.span, msg, |diag| { + diag.multipart_suggestion_verbose( "use the associated constant instead", sugg, Applicability::MaybeIncorrect, diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 1bf03480c825..6beddc1be144 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -10,9 +10,9 @@ use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_hir::{ - BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, HirId, ImplItem, ImplItemKind, - ImplicitSelfKind, Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatExprKind, PatKind, PathSegment, PrimTy, - QPath, TraitItemId, TyKind, + BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, HirId, ImplItem, ImplItemKind, ImplicitSelfKind, + Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatExprKind, PatKind, PathSegment, PrimTy, QPath, TraitItemId, + TyKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, FnSig, Ty}; @@ -266,11 +266,14 @@ fn span_without_enclosing_paren(cx: &LateContext<'_>, span: Span) -> Span { } fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, ident: Ident, trait_items: &[TraitItemId]) { - fn is_named_self(cx: &LateContext<'_>, item: &TraitItemId, name: Symbol) -> bool { + fn is_named_self(cx: &LateContext<'_>, item: TraitItemId, name: Symbol) -> bool { cx.tcx.item_name(item.owner_id) == name && matches!( cx.tcx.fn_arg_idents(item.owner_id), - [Some(Ident { name: kw::SelfLower, .. })], + [Some(Ident { + name: kw::SelfLower, + .. + })], ) } @@ -284,7 +287,7 @@ fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, ident: Iden } if cx.effective_visibilities.is_exported(visited_trait.owner_id.def_id) - && trait_items.iter().any(|i| is_named_self(cx, i, sym::len)) + && trait_items.iter().any(|&i| is_named_self(cx, i, sym::len)) { let mut current_and_super_traits = DefIdSet::default(); fill_trait_set(visited_trait.owner_id.to_def_id(), &mut current_and_super_traits, cx); diff --git a/clippy_lints/src/loops/needless_range_loop.rs b/clippy_lints/src/loops/needless_range_loop.rs index 7837b18bcd36..972b0b110e0e 100644 --- a/clippy_lints/src/loops/needless_range_loop.rs +++ b/clippy_lints/src/loops/needless_range_loop.rs @@ -39,7 +39,9 @@ pub(super) fn check<'tcx>( var: canonical_id, indexed_mut: FxHashSet::default(), indexed_indirectly: FxHashMap::default(), + unnamed_indexed_indirectly: false, indexed_directly: FxIndexMap::default(), + unnamed_indexed_directly: false, referenced: FxHashSet::default(), nonindex: false, prefer_mutable: false, @@ -47,7 +49,11 @@ pub(super) fn check<'tcx>( walk_expr(&mut visitor, body); // linting condition: we only indexed one variable, and indexed it directly - if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 { + if visitor.indexed_indirectly.is_empty() + && !visitor.unnamed_indexed_indirectly + && !visitor.unnamed_indexed_directly + && visitor.indexed_directly.len() == 1 + { let (indexed, (indexed_extent, indexed_ty)) = visitor .indexed_directly .into_iter() @@ -217,6 +223,7 @@ fn is_end_eq_array_len<'tcx>( false } +#[expect(clippy::struct_excessive_bools)] struct VarVisitor<'a, 'tcx> { /// context reference cx: &'a LateContext<'tcx>, @@ -226,9 +233,13 @@ struct VarVisitor<'a, 'tcx> { indexed_mut: FxHashSet, /// indirectly indexed variables (`v[(i + 4) % N]`), the extend is `None` for global indexed_indirectly: FxHashMap>, + /// indirectly indexed literals, like `[1, 2, 3][(i + 4) % N]` + unnamed_indexed_indirectly: bool, /// subset of `indexed` of vars that are indexed directly: `v[i]` /// this will not contain cases like `v[calc_index(i)]` or `v[(i + 4) % N]` indexed_directly: FxIndexMap, Ty<'tcx>)>, + /// directly indexed literals, like `[1, 2, 3][i]` + unnamed_indexed_directly: bool, /// Any names that are used outside an index operation. /// Used to detect things like `&mut vec` used together with `vec[i]` referenced: FxHashSet, @@ -242,6 +253,7 @@ struct VarVisitor<'a, 'tcx> { impl<'tcx> VarVisitor<'_, 'tcx> { fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool { + let index_used_directly = matches!(idx.kind, ExprKind::Path(_)); if let ExprKind::Path(ref seqpath) = seqexpr.kind // the indexed container is referenced by a name && let QPath::Resolved(None, seqvar) = *seqpath @@ -251,7 +263,6 @@ impl<'tcx> VarVisitor<'_, 'tcx> { if self.prefer_mutable { self.indexed_mut.insert(seqvar.segments[0].ident.name); } - let index_used_directly = matches!(idx.kind, ExprKind::Path(_)); let res = self.cx.qpath_res(seqpath, seqexpr.hir_id); match res { Res::Local(hir_id) => { @@ -286,6 +297,13 @@ impl<'tcx> VarVisitor<'_, 'tcx> { }, _ => (), } + } else if let ExprKind::Repeat(..) | ExprKind::Array(..) = seqexpr.kind { + if index_used_directly { + self.unnamed_indexed_directly = true; + } else { + self.unnamed_indexed_indirectly = true; + } + return false; } true } diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index 69c84bc7038e..8a253ae5810f 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -6,9 +6,11 @@ use clippy_utils::macros::root_macro_call_first_node; use clippy_utils::source::snippet; use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; use rustc_errors::Applicability; -use rustc_hir::{Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind, StructTailExpr}; +use rustc_hir::{ + Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Node, Pat, Stmt, StmtKind, StructTailExpr, +}; use rustc_lint::LateContext; -use rustc_span::{Span, sym}; +use rustc_span::{BytePos, Span, sym}; use std::iter::once; use std::ops::ControlFlow; @@ -20,7 +22,7 @@ pub(super) fn check<'tcx>( for_loop: Option<&ForLoop<'_>>, ) { match never_loop_block(cx, block, &mut Vec::new(), loop_id) { - NeverLoopResult::Diverging => { + NeverLoopResult::Diverging { ref break_spans } => { span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| { if let Some(ForLoop { arg: iterator, @@ -38,10 +40,15 @@ pub(super) fn check<'tcx>( Applicability::Unspecified }; - diag.span_suggestion_verbose( + let mut suggestions = vec![( for_span.with_hi(iterator.span.hi()), - "if you need the first element of the iterator, try writing", for_to_if_let_sugg(cx, iterator, pat), + )]; + // Make sure to clear up the diverging sites when we remove a loopp. + suggestions.extend(break_spans.iter().map(|span| (*span, String::new()))); + diag.multipart_suggestion_verbose( + "if you need the first element of the iterator, try writing", + suggestions, app, ); } @@ -70,22 +77,22 @@ 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(Copy, Clone)] +#[derive(Clone)] 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, + Diverging { break_spans: Vec }, /// We have not encountered any main loop continue, /// and subsequent control flow is (possibly) reachable Normal, } #[must_use] -fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult { +fn absorb_break(arg: &NeverLoopResult) -> NeverLoopResult { match arg { - NeverLoopResult::Diverging | NeverLoopResult::Normal => NeverLoopResult::Normal, + NeverLoopResult::Diverging { .. } | NeverLoopResult::Normal => NeverLoopResult::Normal, NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop, } } @@ -94,7 +101,7 @@ fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult { #[must_use] fn combine_seq(first: NeverLoopResult, second: impl FnOnce() -> NeverLoopResult) -> NeverLoopResult { match first { - NeverLoopResult::Diverging | NeverLoopResult::MayContinueMainLoop => first, + NeverLoopResult::Diverging { .. } | NeverLoopResult::MayContinueMainLoop => first, NeverLoopResult::Normal => second(), } } @@ -103,7 +110,7 @@ fn combine_seq(first: NeverLoopResult, second: impl FnOnce() -> NeverLoopResult) #[must_use] fn combine_seq_many(iter: impl IntoIterator) -> NeverLoopResult { for e in iter { - if let NeverLoopResult::Diverging | NeverLoopResult::MayContinueMainLoop = e { + if let NeverLoopResult::Diverging { .. } | NeverLoopResult::MayContinueMainLoop = e { return e; } } @@ -118,7 +125,19 @@ fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult NeverLoopResult::MayContinueMainLoop }, (NeverLoopResult::Normal, _) | (_, NeverLoopResult::Normal) => NeverLoopResult::Normal, - (NeverLoopResult::Diverging, NeverLoopResult::Diverging) => NeverLoopResult::Diverging, + ( + NeverLoopResult::Diverging { + break_spans: mut break_spans1, + }, + NeverLoopResult::Diverging { + break_spans: mut break_spans2, + }, + ) => { + break_spans1.append(&mut break_spans2); + NeverLoopResult::Diverging { + break_spans: break_spans1, + } + }, } } @@ -136,7 +155,7 @@ fn never_loop_block<'tcx>( combine_seq_many(iter.map(|(e, els)| { let e = never_loop_expr(cx, e, local_labels, main_loop_id); // els is an else block in a let...else binding - els.map_or(e, |els| { + els.map_or(e.clone(), |els| { combine_seq(e, || match never_loop_block(cx, els, local_labels, main_loop_id) { // Returning MayContinueMainLoop here means that // we will not evaluate the rest of the body @@ -144,7 +163,7 @@ fn never_loop_block<'tcx>( // An else block always diverges, so the Normal case should not happen, // but the analysis is approximate so it might return Normal anyway. // Returning Normal here says that nothing more happens on the main path - NeverLoopResult::Diverging | NeverLoopResult::Normal => NeverLoopResult::Normal, + NeverLoopResult::Diverging { .. } | NeverLoopResult::Normal => NeverLoopResult::Normal, }) }) })) @@ -159,6 +178,45 @@ fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'t } } +fn stmt_source_span(stmt: &Stmt<'_>) -> Span { + let call_span = stmt.span.source_callsite(); + // if it is a macro call, the span will be missing the trailing semicolon + if stmt.span == call_span { + return call_span; + } + + // An expression without a trailing semi-colon (must have unit type). + if let StmtKind::Expr(..) = stmt.kind { + return call_span; + } + + call_span.with_hi(call_span.hi() + BytePos(1)) +} + +/// Returns a Vec of all the individual spans after the highlighted expression in a block +fn all_spans_after_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> Vec { + if let Node::Stmt(stmt) = cx.tcx.parent_hir_node(expr.hir_id) { + if let Node::Block(block) = cx.tcx.parent_hir_node(stmt.hir_id) { + return block + .stmts + .iter() + .skip_while(|inner| inner.hir_id != stmt.hir_id) + .map(stmt_source_span) + .chain(if let Some(e) = block.expr { vec![e.span] } else { vec![] }) + .collect(); + } + + return vec![stmt.span]; + } + + vec![] +} + +fn is_label_for_block(cx: &LateContext<'_>, dest: &Destination) -> bool { + dest.target_id + .is_ok_and(|hir_id| matches!(cx.tcx.hir_node(hir_id), Node::Block(_))) +} + #[allow(clippy::too_many_lines)] fn never_loop_expr<'tcx>( cx: &LateContext<'tcx>, @@ -197,7 +255,7 @@ fn never_loop_expr<'tcx>( ExprKind::Loop(b, _, _, _) => { // We don't attempt to track reachability after a loop, // just assume there may have been a break somewhere - absorb_break(never_loop_block(cx, b, local_labels, main_loop_id)) + absorb_break(&never_loop_block(cx, b, local_labels, main_loop_id)) }, ExprKind::If(e, e2, e3) => { let e1 = never_loop_expr(cx, e, local_labels, main_loop_id); @@ -212,9 +270,10 @@ 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, |a, b| { - combine_branches(a, never_loop_expr(cx, b.body, local_labels, main_loop_id)) - }) + arms.iter() + .fold(NeverLoopResult::Diverging { break_spans: vec![] }, |a, b| { + combine_branches(a, never_loop_expr(cx, b.body, local_labels, main_loop_id)) + }) }) }, ExprKind::Block(b, _) => { @@ -224,7 +283,7 @@ fn never_loop_expr<'tcx>( let ret = never_loop_block(cx, b, local_labels, main_loop_id); let jumped_to = b.targeted_by_break && local_labels.pop().unwrap().1; match ret { - NeverLoopResult::Diverging if jumped_to => NeverLoopResult::Normal, + NeverLoopResult::Diverging { .. } if jumped_to => NeverLoopResult::Normal, _ => ret, } }, @@ -235,25 +294,39 @@ fn never_loop_expr<'tcx>( if id == main_loop_id { NeverLoopResult::MayContinueMainLoop } else { - NeverLoopResult::Diverging + NeverLoopResult::Diverging { + break_spans: all_spans_after_expr(cx, expr), + } } }, - ExprKind::Break(_, e) | ExprKind::Ret(e) => { + ExprKind::Ret(e) => { let first = e.as_ref().map_or(NeverLoopResult::Normal, |e| { never_loop_expr(cx, e, local_labels, main_loop_id) }); combine_seq(first, || { // checks if break targets a block instead of a loop - if let ExprKind::Break(Destination { target_id: Ok(t), .. }, _) = expr.kind - && let Some((_, reachable)) = local_labels.iter_mut().find(|(label, _)| *label == t) - { - *reachable = true; + mark_block_as_reachable(expr, local_labels); + NeverLoopResult::Diverging { break_spans: vec![] } + }) + }, + ExprKind::Break(dest, e) => { + let first = e.as_ref().map_or(NeverLoopResult::Normal, |e| { + never_loop_expr(cx, e, local_labels, main_loop_id) + }); + combine_seq(first, || { + // checks if break targets a block instead of a loop + mark_block_as_reachable(expr, local_labels); + NeverLoopResult::Diverging { + break_spans: if is_label_for_block(cx, &dest) { + vec![] + } else { + all_spans_after_expr(cx, expr) + }, } - NeverLoopResult::Diverging }) }, ExprKind::Become(e) => combine_seq(never_loop_expr(cx, e, local_labels, main_loop_id), || { - NeverLoopResult::Diverging + NeverLoopResult::Diverging { break_spans: vec![] } }), ExprKind::InlineAsm(asm) => combine_seq_many(asm.operands.iter().map(|(o, _)| match o { InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => { @@ -283,12 +356,12 @@ fn never_loop_expr<'tcx>( }; let result = combine_seq(result, || { if cx.typeck_results().expr_ty(expr).is_never() { - NeverLoopResult::Diverging + NeverLoopResult::Diverging { break_spans: vec![] } } else { NeverLoopResult::Normal } }); - if let NeverLoopResult::Diverging = result + if let NeverLoopResult::Diverging { .. } = result && let Some(macro_call) = root_macro_call_first_node(cx, expr) && let Some(sym::todo_macro) = cx.tcx.get_diagnostic_name(macro_call.def_id) { @@ -316,3 +389,11 @@ fn for_to_if_let_sugg(cx: &LateContext<'_>, iterator: &Expr<'_>, pat: &Pat<'_>) format!("if let Some({pat_snippet}) = {iter_snippet}.next()") } + +fn mark_block_as_reachable(expr: &Expr<'_>, local_labels: &mut [(HirId, bool)]) { + if let ExprKind::Break(Destination { target_id: Ok(t), .. }, _) = expr.kind + && let Some((_, reachable)) = local_labels.iter_mut().find(|(label, _)| *label == t) + { + *reachable = true; + } +} diff --git a/clippy_lints/src/macro_use.rs b/clippy_lints/src/macro_use.rs index d1a54df988f5..3aa449f64118 100644 --- a/clippy_lints/src/macro_use.rs +++ b/clippy_lints/src/macro_use.rs @@ -1,15 +1,15 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::snippet; use hir::def::{DefKind, Res}; +use rustc_attr_data_structures::{AttributeKind, find_attr}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::{self as hir, AmbigArg}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::impl_lint_pass; +use rustc_span::Span; use rustc_span::edition::Edition; -use rustc_span::{Span}; use std::collections::BTreeMap; -use rustc_attr_data_structures::{AttributeKind, find_attr}; declare_clippy_lint! { /// ### What it does diff --git a/clippy_lints/src/manual_abs_diff.rs b/clippy_lints/src/manual_abs_diff.rs index bac4b3d32f2a..288f27db8ca2 100644 --- a/clippy_lints/src/manual_abs_diff.rs +++ b/clippy_lints/src/manual_abs_diff.rs @@ -5,7 +5,7 @@ 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, span_contains_comment}; +use clippy_utils::{eq_expr_value, peel_blocks, peel_middle_ty_refs, span_contains_comment}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -62,7 +62,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualAbsDiff { && let ExprKind::Binary(op, rhs, lhs) = if_expr.cond.kind && let (BinOpKind::Gt | BinOpKind::Ge, mut a, mut b) | (BinOpKind::Lt | BinOpKind::Le, mut b, mut a) = (op.node, rhs, lhs) - && let Some(ty) = self.are_ty_eligible(cx, a, b) + && let Some((ty, b_n_refs)) = self.are_ty_eligible(cx, a, b) && is_sub_expr(cx, if_expr.then, a, b, ty) && is_sub_expr(cx, r#else, b, a, ty) { @@ -86,8 +86,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualAbsDiff { } }; let sugg = format!( - "{}.abs_diff({})", + "{}.abs_diff({}{})", Sugg::hir(cx, a, "..").maybe_paren(), + "*".repeat(b_n_refs), Sugg::hir(cx, b, "..") ); diag.span_suggestion(expr.span, "replace with `abs_diff`", sugg, applicability); @@ -100,13 +101,15 @@ impl<'tcx> LateLintPass<'tcx> for ManualAbsDiff { impl ManualAbsDiff { /// Returns a type if `a` and `b` are both of it, and this lint can be applied to that /// type (currently, any primitive int, or a `Duration`) - fn are_ty_eligible<'tcx>(&self, cx: &LateContext<'tcx>, a: &Expr<'_>, b: &Expr<'_>) -> Option> { + fn are_ty_eligible<'tcx>(&self, cx: &LateContext<'tcx>, a: &Expr<'_>, b: &Expr<'_>) -> Option<(Ty<'tcx>, usize)> { let is_int = |ty: Ty<'_>| matches!(ty.kind(), ty::Uint(_) | ty::Int(_)) && self.msrv.meets(cx, msrvs::ABS_DIFF); let is_duration = |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(); - (a_ty == cx.typeck_results().expr_ty(b).peel_refs() && (is_int(a_ty) || is_duration(a_ty))).then_some(a_ty) + let (b_ty, b_n_refs) = peel_middle_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_assert.rs b/clippy_lints/src/manual_assert.rs index 8378e15c581c..ea6b01a053a3 100644 --- a/clippy_lints/src/manual_assert.rs +++ b/clippy_lints/src/manual_assert.rs @@ -60,7 +60,8 @@ impl<'tcx> LateLintPass<'tcx> for ManualAssert { ExprKind::Unary(UnOp::Not, e) => (e, ""), _ => (cond, "!"), }; - let cond_sugg = sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_paren(); + let cond_sugg = + sugg::Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "..", &mut applicability).maybe_paren(); let semicolon = if is_parent_stmt(cx, expr.hir_id) { ";" } else { "" }; let sugg = format!("assert!({not}{cond_sugg}, {format_args_snip}){semicolon}"); // we show to the user the suggestion without the comments, but when applying the fix, include the diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index 08c0caa4266c..7e530e98ac4a 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -152,21 +152,26 @@ fn report_single_pattern( }) if lit.node.is_str() || lit.node.is_bytestr() => pat_ref_count + 1, _ => pat_ref_count, }; - // References are only implicitly added to the pattern, so no overflow here. - // e.g. will work: match &Some(_) { Some(_) => () } - // will not: match Some(_) { &Some(_) => () } - let ref_count_diff = ty_ref_count - pat_ref_count; - // Try to remove address of expressions first. - let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff); - let ref_count_diff = ref_count_diff - removed; + // References are implicitly removed when `deref_patterns` are used. + // They are implicitly added when match ergonomics are used. + let (ex, ref_or_deref_adjust) = if ty_ref_count > pat_ref_count { + let ref_count_diff = ty_ref_count - pat_ref_count; + + // Try to remove address of expressions first. + let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff); + + (ex, String::from(if ref_count_diff == removed { "" } else { "&" })) + } else { + (ex, "*".repeat(pat_ref_count - ty_ref_count)) + }; let msg = "you seem to be trying to use `match` for an equality check. Consider using `if`"; let sugg = format!( "if {} == {}{} {}{els_str}", snippet_with_context(cx, ex.span, ctxt, "..", &mut app).0, // PartialEq for different reference counts may not exist. - "&".repeat(ref_count_diff), + ref_or_deref_adjust, snippet_with_applicability(cx, arm.pat.span, "..", &mut app), expr_block(cx, arm.body, ctxt, "..", Some(expr.span), &mut app), ); diff --git a/clippy_lints/src/methods/expect_fun_call.rs b/clippy_lints/src/methods/expect_fun_call.rs index 82e5a6d5a412..6e5da5bda8c9 100644 --- a/clippy_lints/src/methods/expect_fun_call.rs +++ b/clippy_lints/src/methods/expect_fun_call.rs @@ -2,13 +2,15 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::macros::{FormatArgsStorage, format_args_inputs_span, root_macro_call_first_node}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; +use clippy_utils::visitors::for_each_expr; +use clippy_utils::{contains_return, is_inside_always_const_context, peel_blocks}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; -use rustc_middle::ty; use rustc_span::symbol::sym; use rustc_span::{Span, Symbol}; use std::borrow::Cow; +use std::ops::ControlFlow; use super::EXPECT_FUN_CALL; @@ -23,10 +25,10 @@ pub(super) fn check<'tcx>( receiver: &'tcx hir::Expr<'tcx>, args: &'tcx [hir::Expr<'tcx>], ) { - // Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or + // Strip `{}`, `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or // `&str` fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { - let mut arg_root = arg; + let mut arg_root = peel_blocks(arg); loop { arg_root = match &arg_root.kind { hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr, @@ -47,124 +49,68 @@ pub(super) fn check<'tcx>( arg_root } - // Only `&'static str` or `String` can be used directly in the `panic!`. Other types should be - // converted to string. - fn requires_to_string(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool { - let arg_ty = cx.typeck_results().expr_ty(arg); - if is_type_lang_item(cx, arg_ty, hir::LangItem::String) { - return false; + fn contains_call<'a>(cx: &LateContext<'a>, arg: &'a hir::Expr<'a>) -> bool { + for_each_expr(cx, arg, |expr| { + if matches!(expr.kind, hir::ExprKind::MethodCall { .. } | hir::ExprKind::Call { .. }) + && !is_inside_always_const_context(cx.tcx, expr.hir_id) + { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_some() + } + + if name == sym::expect + && let [arg] = args + && let arg_root = get_arg_root(cx, arg) + && contains_call(cx, arg_root) + && !contains_return(arg_root) + { + let receiver_type = cx.typeck_results().expr_ty_adjusted(receiver); + let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::Option) { + "||" + } else if is_type_diagnostic_item(cx, receiver_type, sym::Result) { + "|_|" + } else { + return; + }; + + let span_replace_word = method_span.with_hi(expr.span.hi()); + + let mut applicability = Applicability::MachineApplicable; + + // Special handling for `format!` as arg_root + if let Some(macro_call) = root_macro_call_first_node(cx, arg_root) { + if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) + && let Some(format_args) = format_args_storage.get(cx, arg_root, macro_call.expn) + { + let span = format_args_inputs_span(format_args); + let sugg = snippet_with_applicability(cx, span, "..", &mut applicability); + span_lint_and_sugg( + cx, + EXPECT_FUN_CALL, + span_replace_word, + format!("function call inside of `{name}`"), + "try", + format!("unwrap_or_else({closure_args} panic!({sugg}))"), + applicability, + ); + } + return; } - if let ty::Ref(_, ty, ..) = arg_ty.kind() - && ty.is_str() - && can_be_static_str(cx, arg) - { - return false; - } - true + + let arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability); + + span_lint_and_sugg( + cx, + EXPECT_FUN_CALL, + span_replace_word, + format!("function call inside of `{name}`"), + "try", + format!("unwrap_or_else({closure_args} panic!(\"{{}}\", {arg_root_snippet}))"), + applicability, + ); } - - // Check if an expression could have type `&'static str`, knowing that it - // has type `&str` for some lifetime. - fn can_be_static_str(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool { - match arg.kind { - hir::ExprKind::Lit(_) => true, - hir::ExprKind::Call(fun, _) => { - if let hir::ExprKind::Path(ref p) = fun.kind { - match cx.qpath_res(p, fun.hir_id) { - hir::def::Res::Def(hir::def::DefKind::Fn | hir::def::DefKind::AssocFn, def_id) => matches!( - cx.tcx.fn_sig(def_id).instantiate_identity().output().skip_binder().kind(), - ty::Ref(re, ..) if re.is_static(), - ), - _ => false, - } - } else { - false - } - }, - hir::ExprKind::MethodCall(..) => { - cx.typeck_results() - .type_dependent_def_id(arg.hir_id) - .is_some_and(|method_id| { - matches!( - cx.tcx.fn_sig(method_id).instantiate_identity().output().skip_binder().kind(), - ty::Ref(re, ..) if re.is_static() - ) - }) - }, - hir::ExprKind::Path(ref p) => matches!( - cx.qpath_res(p, arg.hir_id), - hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static { .. }, _) - ), - _ => false, - } - } - - fn is_call(node: &hir::ExprKind<'_>) -> bool { - match node { - hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => { - is_call(&expr.kind) - }, - hir::ExprKind::Call(..) - | hir::ExprKind::MethodCall(..) - // These variants are debatable or require further examination - | hir::ExprKind::If(..) - | hir::ExprKind::Match(..) - | hir::ExprKind::Block{ .. } => true, - _ => false, - } - } - - if args.len() != 1 || name != sym::expect || !is_call(&args[0].kind) { - return; - } - - let receiver_type = cx.typeck_results().expr_ty_adjusted(receiver); - let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::Option) { - "||" - } else if is_type_diagnostic_item(cx, receiver_type, sym::Result) { - "|_|" - } else { - return; - }; - - let arg_root = get_arg_root(cx, &args[0]); - - let span_replace_word = method_span.with_hi(expr.span.hi()); - - let mut applicability = Applicability::MachineApplicable; - - // Special handling for `format!` as arg_root - if let Some(macro_call) = root_macro_call_first_node(cx, arg_root) { - if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) - && let Some(format_args) = format_args_storage.get(cx, arg_root, macro_call.expn) - { - let span = format_args_inputs_span(format_args); - let sugg = snippet_with_applicability(cx, span, "..", &mut applicability); - span_lint_and_sugg( - cx, - EXPECT_FUN_CALL, - span_replace_word, - format!("function call inside of `{name}`"), - "try", - format!("unwrap_or_else({closure_args} panic!({sugg}))"), - applicability, - ); - } - return; - } - - let mut arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability); - if requires_to_string(cx, arg_root) { - arg_root_snippet.to_mut().push_str(".to_string()"); - } - - span_lint_and_sugg( - cx, - EXPECT_FUN_CALL, - span_replace_word, - format!("function call inside of `{name}`"), - "try", - format!("unwrap_or_else({closure_args} {{ panic!(\"{{}}\", {arg_root_snippet}) }})"), - applicability, - ); } diff --git a/clippy_lints/src/methods/filter_map_bool_then.rs b/clippy_lints/src/methods/filter_map_bool_then.rs index 965993808f6b..94944bd9445b 100644 --- a/clippy_lints/src/methods/filter_map_bool_then.rs +++ b/clippy_lints/src/methods/filter_map_bool_then.rs @@ -1,6 +1,6 @@ use super::FILTER_MAP_BOOL_THEN; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::SpanRangeExt; +use clippy_utils::source::{SpanRangeExt, snippet_with_context}; use clippy_utils::ty::is_copy; use clippy_utils::{ CaptureKind, can_move_expr_to_closure, contains_return, is_from_proc_macro, is_trait_method, peel_blocks, @@ -45,9 +45,11 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: & .filter(|adj| matches!(adj.kind, Adjust::Deref(_))) .count() && let Some(param_snippet) = param.span.get_source_text(cx) - && let Some(filter) = recv.span.get_source_text(cx) - && let Some(map) = then_body.span.get_source_text(cx) { + let mut applicability = Applicability::MachineApplicable; + let (filter, _) = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut applicability); + let (map, _) = snippet_with_context(cx, then_body.span, expr.span.ctxt(), "..", &mut applicability); + span_lint_and_then( cx, FILTER_MAP_BOOL_THEN, @@ -62,7 +64,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: & "filter(|&{param_snippet}| {derefs}{filter}).map(|{param_snippet}| {map})", derefs = "*".repeat(needed_derefs) ), - Applicability::MachineApplicable, + applicability, ); } else { diag.help("consider using `filter` then `map` instead"); diff --git a/clippy_lints/src/methods/manual_inspect.rs b/clippy_lints/src/methods/manual_inspect.rs index 21f2ce8b7c90..bc96815944d5 100644 --- a/clippy_lints/src/methods/manual_inspect.rs +++ b/clippy_lints/src/methods/manual_inspect.rs @@ -100,7 +100,6 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: match x { UseKind::Return(s) => edits.push((s.with_leading_whitespace(cx).with_ctxt(s.ctxt()), String::new())), UseKind::Borrowed(s) => { - #[expect(clippy::range_plus_one)] let range = s.map_range(cx, |_, src, range| { let src = src.get(range.clone())?; let trimmed = src.trim_start_matches([' ', '\t', '\n', '\r', '(']); diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index f2dabdd34387..bcd54557331b 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -3859,6 +3859,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does /// Checks for usage of `option.map(f).unwrap_or_default()` and `result.map(f).unwrap_or_default()` where f is a function or closure that returns the `bool` type. + /// Also checks for equality comparisons like `option.map(f) == Some(true)` and `result.map(f) == Ok(true)`. /// /// ### Why is this bad? /// Readability. These can be written more concisely as `option.is_some_and(f)` and `result.is_ok_and(f)`. @@ -3869,6 +3870,11 @@ declare_clippy_lint! { /// # let result: Result = Ok(1); /// option.map(|a| a > 10).unwrap_or_default(); /// result.map(|a| a > 10).unwrap_or_default(); + /// + /// option.map(|a| a > 10) == Some(true); + /// result.map(|a| a > 10) == Ok(true); + /// option.map(|a| a > 10) != Some(true); + /// result.map(|a| a > 10) != Ok(true); /// ``` /// Use instead: /// ```no_run @@ -3876,11 +3882,16 @@ declare_clippy_lint! { /// # let result: Result = Ok(1); /// option.is_some_and(|a| a > 10); /// result.is_ok_and(|a| a > 10); + /// + /// option.is_some_and(|a| a > 10); + /// result.is_ok_and(|a| a > 10); + /// option.is_none_or(|a| a > 10); + /// !result.is_ok_and(|a| a > 10); /// ``` #[clippy::version = "1.77.0"] pub MANUAL_IS_VARIANT_AND, pedantic, - "using `.map(f).unwrap_or_default()`, which is more succinctly expressed as `is_some_and(f)` or `is_ok_and(f)`" + "using `.map(f).unwrap_or_default()` or `.map(f) == Some/Ok(true)`, which are more succinctly expressed as `is_some_and(f)` or `is_ok_and(f)`" } declare_clippy_lint! { @@ -5275,10 +5286,6 @@ impl Methods { } map_identity::check(cx, expr, recv, m_arg, name, span); manual_inspect::check(cx, expr, m_arg, name, span, self.msrv); - crate::useless_conversion::check_function_application(cx, expr, recv, m_arg); - }, - (sym::map_break | sym::map_continue, [m_arg]) => { - crate::useless_conversion::check_function_application(cx, expr, recv, m_arg); }, (sym::map_or, [def, map]) => { option_map_or_none::check(cx, expr, recv, def, map); @@ -5546,7 +5553,7 @@ impl Methods { // Handle method calls whose receiver and arguments may come from expansion if let ExprKind::MethodCall(path, recv, args, _call_span) = expr.kind { match (path.ident.name, args) { - (sym::expect, [_]) if !matches!(method_call(recv), Some((sym::ok | sym::err, _, [], _, _))) => { + (sym::expect, [_]) => { unwrap_expect_used::check( cx, expr, diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index 6ce7dd3d4d0a..04f0e3c0479e 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -242,15 +242,23 @@ pub(super) fn check<'tcx>( let inner_arg = peel_blocks(arg); for_each_expr(cx, inner_arg, |ex| { let is_top_most_expr = ex.hir_id == inner_arg.hir_id; - if let hir::ExprKind::Call(fun, fun_args) = ex.kind { - let fun_span = if fun_args.is_empty() && is_top_most_expr { - Some(fun.span) - } else { - None - }; - if check_or_fn_call(cx, name, method_span, receiver, arg, Some(lambda), expr.span, fun_span) { - return ControlFlow::Break(()); - } + match ex.kind { + hir::ExprKind::Call(fun, fun_args) => { + let fun_span = if fun_args.is_empty() && is_top_most_expr { + Some(fun.span) + } else { + None + }; + if check_or_fn_call(cx, name, method_span, receiver, arg, Some(lambda), expr.span, fun_span) { + return ControlFlow::Break(()); + } + }, + hir::ExprKind::MethodCall(..) => { + if check_or_fn_call(cx, name, method_span, receiver, arg, Some(lambda), expr.span, None) { + return ControlFlow::Break(()); + } + }, + _ => {}, } ControlFlow::Continue(()) }); diff --git a/clippy_lints/src/missing_fields_in_debug.rs b/clippy_lints/src/missing_fields_in_debug.rs index 760ecf075892..18e2b384a463 100644 --- a/clippy_lints/src/missing_fields_in_debug.rs +++ b/clippy_lints/src/missing_fields_in_debug.rs @@ -7,9 +7,7 @@ use clippy_utils::{is_path_lang_item, sym}; use rustc_ast::LitKind; use rustc_data_structures::fx::FxHashSet; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{ - Block, Expr, ExprKind, Impl, Item, ItemKind, LangItem, Node, QPath, TyKind, VariantData, -}; +use rustc_hir::{Block, Expr, ExprKind, Impl, Item, ItemKind, LangItem, Node, QPath, TyKind, VariantData}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{Ty, TypeckResults}; use rustc_session::declare_lint_pass; diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index c4a3d10299b6..5a5025973b5b 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint; use rustc_attr_data_structures::{AttributeKind, find_attr}; -use rustc_hir as hir; -use rustc_hir::Attribute; +use rustc_hir::def_id::DefId; +use rustc_hir::{self as hir, Attribute}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::AssocItemContainer; use rustc_session::declare_lint_pass; @@ -97,11 +97,23 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { } match it.kind { hir::ItemKind::Fn { .. } => { + if fn_is_externally_exported(cx, it.owner_id.to_def_id()) { + return; + } + let desc = "a function"; let attrs = cx.tcx.hir_attrs(it.hir_id()); check_missing_inline_attrs(cx, attrs, it.span, desc); }, - hir::ItemKind::Trait(ref _constness, ref _is_auto, ref _unsafe, _ident, _generics, _bounds, trait_items) => { + hir::ItemKind::Trait( + ref _constness, + ref _is_auto, + ref _unsafe, + _ident, + _generics, + _bounds, + trait_items, + ) => { // note: we need to check if the trait is exported so we can't use // `LateLintPass::check_trait_item` here. for &tit in trait_items { @@ -173,3 +185,10 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { check_missing_inline_attrs(cx, attrs, impl_item.span, desc); } } + +/// Checks if this function is externally exported, where #[inline] wouldn't have the desired effect +/// and a rustc warning would be triggered, see #15301 +fn fn_is_externally_exported(cx: &LateContext<'_>, def_id: DefId) -> bool { + let attrs = cx.tcx.codegen_fn_attrs(def_id); + attrs.contains_extern_indicator() +} diff --git a/clippy_lints/src/missing_trait_methods.rs b/clippy_lints/src/missing_trait_methods.rs index fa61d0fa11af..399bf4e18064 100644 --- a/clippy_lints/src/missing_trait_methods.rs +++ b/clippy_lints/src/missing_trait_methods.rs @@ -66,7 +66,9 @@ impl<'tcx> LateLintPass<'tcx> for MissingTraitMethods { }) = item.kind && let Some(trait_id) = trait_ref.trait_def_id() { - let trait_item_ids: DefIdSet = cx.tcx.associated_items(item.owner_id) + let trait_item_ids: DefIdSet = cx + .tcx + .associated_items(item.owner_id) .in_definition_order() .filter_map(|assoc_item| assoc_item.trait_item_def_id) .collect(); diff --git a/clippy_lints/src/mixed_read_write_in_expression.rs b/clippy_lints/src/mixed_read_write_in_expression.rs index d9f4fb271fb4..a489c0a4a5a1 100644 --- a/clippy_lints/src/mixed_read_write_in_expression.rs +++ b/clippy_lints/src/mixed_read_write_in_expression.rs @@ -171,14 +171,11 @@ impl<'tcx> Visitor<'tcx> for DivergenceVisitor<'_, 'tcx> { ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => self.report_diverging_sub_expr(e), ExprKind::Call(func, _) => { let typ = self.cx.typeck_results().expr_ty(func); - match typ.kind() { - ty::FnDef(..) | ty::FnPtr(..) => { - let sig = typ.fn_sig(self.cx.tcx); - if self.cx.tcx.instantiate_bound_regions_with_erased(sig).output().kind() == &ty::Never { - self.report_diverging_sub_expr(e); - } - }, - _ => {}, + if typ.is_fn() { + let sig = typ.fn_sig(self.cx.tcx); + if self.cx.tcx.instantiate_bound_regions_with_erased(sig).output().kind() == &ty::Never { + self.report_diverging_sub_expr(e); + } } }, ExprKind::MethodCall(..) => { diff --git a/clippy_lints/src/mut_reference.rs b/clippy_lints/src/mut_reference.rs index 2f1ab3d2652a..31f51b457540 100644 --- a/clippy_lints/src/mut_reference.rs +++ b/clippy_lints/src/mut_reference.rs @@ -79,7 +79,7 @@ fn check_arguments<'tcx>( name: &str, fn_kind: &str, ) { - if let ty::FnDef(..) | ty::FnPtr(..) = type_definition.kind() { + if type_definition.is_fn() { let parameters = type_definition.fn_sig(cx.tcx).skip_binder().inputs(); for (argument, parameter) in iter::zip(arguments, parameters) { if let ty::Ref(_, _, Mutability::Not) | ty::RawPtr(_, Mutability::Not) = parameter.kind() diff --git a/clippy_lints/src/needless_for_each.rs b/clippy_lints/src/needless_for_each.rs index 6a7c8436bad4..a67545e419ce 100644 --- a/clippy_lints/src/needless_for_each.rs +++ b/clippy_lints/src/needless_for_each.rs @@ -6,7 +6,7 @@ use rustc_session::declare_lint_pass; use rustc_span::Span; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::ty::has_iter_method; use clippy_utils::{is_trait_method, sym}; @@ -101,18 +101,23 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessForEach { let body_param_sugg = snippet_with_applicability(cx, body.params[0].pat.span, "..", &mut applicability); let for_each_rev_sugg = snippet_with_applicability(cx, for_each_recv.span, "..", &mut applicability); - let body_value_sugg = snippet_with_applicability(cx, body.value.span, "..", &mut applicability); + let (body_value_sugg, is_macro_call) = + snippet_with_context(cx, body.value.span, for_each_recv.span.ctxt(), "..", &mut applicability); let sugg = format!( "for {} in {} {}", body_param_sugg, for_each_rev_sugg, - match body.value.kind { - ExprKind::Block(block, _) if is_let_desugar(block) => { - format!("{{ {body_value_sugg} }}") - }, - ExprKind::Block(_, _) => body_value_sugg.to_string(), - _ => format!("{{ {body_value_sugg}; }}"), + if is_macro_call { + format!("{{ {body_value_sugg}; }}") + } else { + match body.value.kind { + ExprKind::Block(block, _) if is_let_desugar(block) => { + format!("{{ {body_value_sugg} }}") + }, + ExprKind::Block(_, _) => body_value_sugg.to_string(), + _ => format!("{{ {body_value_sugg}; }}"), + } } ); diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index c97ecce75b46..2006a824402d 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -246,8 +246,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { for (span, suggestion) in clone_spans { diag.span_suggestion( span, - span.get_source_text(cx) - .map_or("change the call to".to_owned(), |src| format!("change `{src}` to")), + span.get_source_text(cx).map_or_else( + || "change the call to".to_owned(), + |src| format!("change `{src}` to"), + ), suggestion, Applicability::Unspecified, ); @@ -275,8 +277,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { for (span, suggestion) in clone_spans { diag.span_suggestion( span, - span.get_source_text(cx) - .map_or("change the call to".to_owned(), |src| format!("change `{src}` to")), + span.get_source_text(cx).map_or_else( + || "change the call to".to_owned(), + |src| format!("change `{src}` to"), + ), suggestion, Applicability::Unspecified, ); diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index 3b86f1d1f593..b598a390005b 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -65,11 +65,16 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { .. }) = item.kind { - for assoc_item in cx.tcx.associated_items(item.owner_id.def_id) + 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(); + 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; } diff --git a/clippy_lints/src/operators/arithmetic_side_effects.rs b/clippy_lints/src/operators/arithmetic_side_effects.rs index a78a342d4fe3..466beb04b074 100644 --- a/clippy_lints/src/operators/arithmetic_side_effects.rs +++ b/clippy_lints/src/operators/arithmetic_side_effects.rs @@ -3,12 +3,11 @@ use clippy_config::Conf; use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{expr_or_init, is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary}; +use clippy_utils::{expr_or_init, is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary, sym}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; use rustc_session::impl_lint_pass; -use rustc_span::symbol::sym; use rustc_span::{Span, Symbol}; use {rustc_ast as ast, rustc_hir as hir}; @@ -89,6 +88,18 @@ impl ArithmeticSideEffects { self.allowed_unary.contains(ty_string_elem) } + fn is_non_zero_u(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { + if let ty::Adt(adt, substs) = ty.kind() + && cx.tcx.is_diagnostic_item(sym::NonZero, adt.did()) + && let int_type = substs.type_at(0) + && matches!(int_type.kind(), ty::Uint(_)) + { + true + } else { + false + } + } + /// Verifies built-in types that have specific allowed operations fn has_specific_allowed_type_and_operation<'tcx>( cx: &LateContext<'tcx>, @@ -97,33 +108,12 @@ impl ArithmeticSideEffects { rhs_ty: Ty<'tcx>, ) -> bool { let is_div_or_rem = matches!(op, hir::BinOpKind::Div | hir::BinOpKind::Rem); - let is_non_zero_u = |cx: &LateContext<'tcx>, ty: Ty<'tcx>| { - let tcx = cx.tcx; - - let ty::Adt(adt, substs) = ty.kind() else { return false }; - - if !tcx.is_diagnostic_item(sym::NonZero, adt.did()) { - return false; - } - - let int_type = substs.type_at(0); - let unsigned_int_types = [ - tcx.types.u8, - tcx.types.u16, - tcx.types.u32, - tcx.types.u64, - tcx.types.u128, - tcx.types.usize, - ]; - - unsigned_int_types.contains(&int_type) - }; let is_sat_or_wrap = |ty: Ty<'_>| { is_type_diagnostic_item(cx, ty, sym::Saturating) || is_type_diagnostic_item(cx, ty, sym::Wrapping) }; // If the RHS is `NonZero`, then division or module by zero will never occur. - if is_non_zero_u(cx, rhs_ty) && is_div_or_rem { + if Self::is_non_zero_u(cx, rhs_ty) && is_div_or_rem { return true; } @@ -219,6 +209,18 @@ impl ArithmeticSideEffects { let (mut actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs); actual_lhs = expr_or_init(cx, actual_lhs); actual_rhs = expr_or_init(cx, actual_rhs); + + // `NonZeroU*.get() - 1`, will never overflow + if let hir::BinOpKind::Sub = op + && let hir::ExprKind::MethodCall(method, receiver, [], _) = actual_lhs.kind + && method.ident.name == sym::get + && let receiver_ty = cx.typeck_results().expr_ty(receiver).peel_refs() + && Self::is_non_zero_u(cx, receiver_ty) + && let Some(1) = Self::literal_integer(cx, actual_rhs) + { + return; + } + let lhs_ty = cx.typeck_results().expr_ty(actual_lhs).peel_refs(); let rhs_ty = cx.typeck_results().expr_ty_adjusted(actual_rhs).peel_refs(); if self.has_allowed_binary(lhs_ty, rhs_ty) { @@ -227,6 +229,7 @@ impl ArithmeticSideEffects { if Self::has_specific_allowed_type_and_operation(cx, lhs_ty, op, rhs_ty) { return; } + let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) { if let hir::BinOpKind::Shl | hir::BinOpKind::Shr = op { // At least for integers, shifts are already handled by the CTFE diff --git a/clippy_lints/src/operators/manual_is_multiple_of.rs b/clippy_lints/src/operators/manual_is_multiple_of.rs index 821178a43158..55bb78cfce5f 100644 --- a/clippy_lints/src/operators/manual_is_multiple_of.rs +++ b/clippy_lints/src/operators/manual_is_multiple_of.rs @@ -2,11 +2,12 @@ use clippy_utils::consts::is_zero_integer_const; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::sugg::Sugg; +use clippy_utils::ty::expr_type_is_certain; use rustc_ast::BinOpKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; -use rustc_middle::ty; +use rustc_middle::ty::{self, Ty}; use super::MANUAL_IS_MULTIPLE_OF; @@ -22,9 +23,21 @@ pub(super) fn check<'tcx>( && let Some(operand) = uint_compare_to_zero(cx, op, lhs, rhs) && let ExprKind::Binary(operand_op, operand_left, operand_right) = operand.kind && operand_op.node == BinOpKind::Rem + && matches!( + cx.typeck_results().expr_ty_adjusted(operand_left).peel_refs().kind(), + ty::Uint(_) + ) + && matches!( + cx.typeck_results().expr_ty_adjusted(operand_right).peel_refs().kind(), + ty::Uint(_) + ) + && expr_type_is_certain(cx, operand_left) { let mut app = Applicability::MachineApplicable; - let divisor = Sugg::hir_with_applicability(cx, operand_right, "_", &mut app); + let divisor = deref_sugg( + Sugg::hir_with_applicability(cx, operand_right, "_", &mut app), + cx.typeck_results().expr_ty_adjusted(operand_right), + ); span_lint_and_sugg( cx, MANUAL_IS_MULTIPLE_OF, @@ -64,3 +77,11 @@ fn uint_compare_to_zero<'tcx>( matches!(cx.typeck_results().expr_ty_adjusted(operand).kind(), ty::Uint(_)).then_some(operand) } + +fn deref_sugg<'a>(sugg: Sugg<'a>, ty: Ty<'_>) -> Sugg<'a> { + if let ty::Ref(_, target_ty, _) = ty.kind() { + deref_sugg(sugg.deref(), *target_ty) + } else { + sugg + } +} diff --git a/clippy_lints/src/pattern_type_mismatch.rs b/clippy_lints/src/pattern_type_mismatch.rs index 19d9acfc9305..4197680dd047 100644 --- a/clippy_lints/src/pattern_type_mismatch.rs +++ b/clippy_lints/src/pattern_type_mismatch.rs @@ -96,6 +96,12 @@ impl<'tcx> LateLintPass<'tcx> for PatternTypeMismatch { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let ExprKind::Match(_, arms, _) = expr.kind { + // if the match is generated by an external macro, the writer does not control + // how the scrutinee (`match &scrutiny { ... }`) is matched + if expr.span.in_external_macro(cx.sess().source_map()) { + return; + } + for arm in arms { let pat = &arm.pat; if apply_lint(cx, pat, DerefPossible::Possible) { diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 94cdcf000548..b3058c51afdb 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -584,7 +584,13 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[ Some((Node::Stmt(_), _)) => (), Some((Node::LetStmt(l), _)) => { // Only trace simple bindings. e.g `let x = y;` - if let PatKind::Binding(BindingMode::NONE, id, _, None) = l.pat.kind { + if let PatKind::Binding(BindingMode::NONE, id, ident, None) = l.pat.kind + // Let's not lint for the current parameter. The user may still intend to mutate + // (or, if not mutate, then perhaps call a method that's not otherwise available + // for) the referenced value behind the parameter through this local let binding + // with the underscore being only temporary. + && !ident.name.as_str().starts_with('_') + { self.bindings.insert(id, args_idx); } else { set_skip_flag(); @@ -650,7 +656,14 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[ .filter_map(|(i, arg)| { let param = &body.params[arg.idx]; match param.pat.kind { - PatKind::Binding(BindingMode::NONE, id, _, None) if !is_lint_allowed(cx, PTR_ARG, param.hir_id) => { + PatKind::Binding(BindingMode::NONE, id, ident, None) + if !is_lint_allowed(cx, PTR_ARG, param.hir_id) + // Let's not lint for the current parameter. The user may still intend to mutate + // (or, if not mutate, then perhaps call a method that's not otherwise available + // for) the referenced value behind the parameter with the underscore being only + // temporary. + && !ident.name.as_str().starts_with('_') => + { Some((id, i)) }, _ => { diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index d292ed86ea4c..9281678b3d83 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -4,15 +4,20 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_the use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::{SpanRangeExt, snippet, snippet_with_applicability}; use clippy_utils::sugg::Sugg; -use clippy_utils::{get_parent_expr, higher, is_in_const_context, is_integer_const, path_to_local}; +use clippy_utils::ty::implements_trait; +use clippy_utils::{ + expr_use_ctxt, fn_def_id, get_parent_expr, higher, is_in_const_context, is_integer_const, is_path_lang_item, + path_to_local, +}; +use rustc_ast::Mutability; use rustc_ast::ast::RangeLimits; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind, HirId}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; +use rustc_hir::{BinOpKind, Expr, ExprKind, HirId, LangItem, Node}; +use rustc_lint::{LateContext, LateLintPass, Lint}; +use rustc_middle::ty::{self, ClauseKind, GenericArgKind, PredicatePolarity, Ty}; use rustc_session::impl_lint_pass; -use rustc_span::Span; use rustc_span::source_map::Spanned; +use rustc_span::{Span, sym}; use std::cmp::Ordering; declare_clippy_lint! { @@ -24,6 +29,12 @@ declare_clippy_lint! { /// The code is more readable with an inclusive range /// like `x..=y`. /// + /// ### Limitations + /// The lint is conservative and will trigger only when switching + /// from an exclusive to an inclusive range is provably safe from + /// a typing point of view. This corresponds to situations where + /// the range is used as an iterator, or for indexing. + /// /// ### Known problems /// Will add unnecessary pair of parentheses when the /// expression is not wrapped in a pair but starts with an opening parenthesis @@ -34,11 +45,6 @@ declare_clippy_lint! { /// exclusive ranges, because they essentially add an extra branch that /// LLVM may fail to hoist out of the loop. /// - /// This will cause a warning that cannot be fixed if the consumer of the - /// range only accepts a specific range type, instead of the generic - /// `RangeBounds` trait - /// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)). - /// /// ### Example /// ```no_run /// # let x = 0; @@ -71,11 +77,11 @@ declare_clippy_lint! { /// The code is more readable with an exclusive range /// like `x..y`. /// - /// ### Known problems - /// This will cause a warning that cannot be fixed if - /// the consumer of the range only accepts a specific range type, instead of - /// the generic `RangeBounds` trait - /// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)). + /// ### Limitations + /// The lint is conservative and will trigger only when switching + /// from an inclusive to an exclusive range is provably safe from + /// a typing point of view. This corresponds to situations where + /// the range is used as an iterator, or for indexing. /// /// ### Example /// ```no_run @@ -344,70 +350,188 @@ fn check_range_bounds<'a, 'tcx>(cx: &'a LateContext<'tcx>, ex: &'a Expr<'_>) -> None } -// exclusive range plus one: `x..(y+1)` -fn check_exclusive_range_plus_one(cx: &LateContext<'_>, expr: &Expr<'_>) { - if expr.span.can_be_used_for_suggestions() - && let Some(higher::Range { - start, - end: Some(end), - limits: RangeLimits::HalfOpen, - }) = higher::Range::hir(expr) - && let Some(y) = y_plus_one(cx, end) +/// Check whether `expr` could switch range types without breaking the typing requirements. This is +/// generally the case when `expr` is used as an iterator for example, or as a slice or `&str` +/// index. +/// +/// FIXME: Note that the current implementation may still return false positives. A proper fix would +/// check that the obligations are still satisfied after switching the range type. +fn can_switch_ranges<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + original: RangeLimits, + inner_ty: Ty<'tcx>, +) -> bool { + let use_ctxt = expr_use_ctxt(cx, expr); + let (Node::Expr(parent_expr), false) = (use_ctxt.node, use_ctxt.is_ty_unified) else { + return false; + }; + + // Check if `expr` is the argument of a compiler-generated `IntoIter::into_iter(expr)` + if let ExprKind::Call(func, [arg]) = parent_expr.kind + && arg.hir_id == use_ctxt.child_id + && is_path_lang_item(cx, func, LangItem::IntoIterIntoIter) { - let span = expr.span; - span_lint_and_then( - cx, - RANGE_PLUS_ONE, - span, - "an inclusive range would be more readable", - |diag| { - let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_paren().to_string()); - let end = Sugg::hir(cx, y, "y").maybe_paren(); - match span.with_source_text(cx, |src| src.starts_with('(') && src.ends_with(')')) { - Some(true) => { - diag.span_suggestion(span, "use", format!("({start}..={end})"), Applicability::MaybeIncorrect); - }, - Some(false) => { - diag.span_suggestion( - span, - "use", - format!("{start}..={end}"), - Applicability::MachineApplicable, // snippet - ); - }, - None => {}, - } - }, - ); + return true; } + + // Check if `expr` is used as the receiver of a method of the `Iterator`, `IntoIterator`, + // or `RangeBounds` traits. + if let ExprKind::MethodCall(_, receiver, _, _) = parent_expr.kind + && receiver.hir_id == use_ctxt.child_id + && let Some(method_did) = cx.typeck_results().type_dependent_def_id(parent_expr.hir_id) + && let Some(trait_did) = cx.tcx.trait_of_item(method_did) + && matches!( + cx.tcx.get_diagnostic_name(trait_did), + Some(sym::Iterator | sym::IntoIterator | sym::RangeBounds) + ) + { + return true; + } + + // Check if `expr` is an argument of a call which requires an `Iterator`, `IntoIterator`, + // or `RangeBounds` trait. + if let ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) = parent_expr.kind + && let Some(id) = fn_def_id(cx, parent_expr) + && let Some(arg_idx) = args.iter().position(|e| e.hir_id == use_ctxt.child_id) + { + let input_idx = if matches!(parent_expr.kind, ExprKind::MethodCall(..)) { + arg_idx + 1 + } else { + arg_idx + }; + let inputs = cx + .tcx + .liberate_late_bound_regions(id, cx.tcx.fn_sig(id).instantiate_identity()) + .inputs(); + let expr_ty = inputs[input_idx]; + // Check that the `expr` type is present only once, otherwise modifying just one of them might be + // risky if they are referenced using the same generic type for example. + if inputs.iter().enumerate().all(|(n, ty)| + n == input_idx + || !ty.walk().any(|arg| matches!(arg.kind(), + GenericArgKind::Type(ty) if ty == expr_ty))) + // Look for a clause requiring `Iterator`, `IntoIterator`, or `RangeBounds`, and resolving to `expr_type`. + && cx + .tcx + .param_env(id) + .caller_bounds() + .into_iter() + .any(|p| { + if let ClauseKind::Trait(t) = p.kind().skip_binder() + && t.polarity == PredicatePolarity::Positive + && matches!( + cx.tcx.get_diagnostic_name(t.trait_ref.def_id), + Some(sym::Iterator | sym::IntoIterator | sym::RangeBounds) + ) + { + t.self_ty() == expr_ty + } else { + false + } + }) + { + return true; + } + } + + // Check if `expr` is used for indexing, and if the switched range type could be used + // as well. + if let ExprKind::Index(outer_expr, index, _) = parent_expr.kind + && index.hir_id == expr.hir_id + // Build the switched range type (for example `RangeInclusive`). + && let Some(switched_range_def_id) = match original { + RangeLimits::HalfOpen => cx.tcx.lang_items().range_inclusive_struct(), + RangeLimits::Closed => cx.tcx.lang_items().range_struct(), + } + && let switched_range_ty = cx + .tcx + .type_of(switched_range_def_id) + .instantiate(cx.tcx, &[inner_ty.into()]) + // Check that the switched range type can be used for indexing the original expression + // through the `Index` or `IndexMut` trait. + && let ty::Ref(_, outer_ty, mutability) = cx.typeck_results().expr_ty_adjusted(outer_expr).kind() + && let Some(index_def_id) = match mutability { + Mutability::Not => cx.tcx.lang_items().index_trait(), + Mutability::Mut => cx.tcx.lang_items().index_mut_trait(), + } + && implements_trait(cx, *outer_ty, index_def_id, &[switched_range_ty.into()]) + // We could also check that the associated item of the `index_def_id` trait with the switched range type + // return the same type, but it is reasonable to expect so. We can't check that the result is identical + // in both `Index>` and `Index>` anyway. + { + return true; + } + + false +} + +// exclusive range plus one: `x..(y+1)` +fn check_exclusive_range_plus_one<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + check_range_switch( + cx, + expr, + RangeLimits::HalfOpen, + y_plus_one, + RANGE_PLUS_ONE, + "an inclusive range would be more readable", + "..=", + ); } // inclusive range minus one: `x..=(y-1)` -fn check_inclusive_range_minus_one(cx: &LateContext<'_>, expr: &Expr<'_>) { +fn check_inclusive_range_minus_one<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + check_range_switch( + cx, + expr, + RangeLimits::Closed, + y_minus_one, + RANGE_MINUS_ONE, + "an exclusive range would be more readable", + "..", + ); +} + +/// Check for a `kind` of range in `expr`, check for `predicate` on the end, +/// and emit the `lint` with `msg` and the `operator`. +fn check_range_switch<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + kind: RangeLimits, + predicate: impl for<'hir> FnOnce(&LateContext<'_>, &Expr<'hir>) -> Option<&'hir Expr<'hir>>, + lint: &'static Lint, + msg: &'static str, + operator: &str, +) { if expr.span.can_be_used_for_suggestions() && let Some(higher::Range { start, end: Some(end), - limits: RangeLimits::Closed, + limits, }) = higher::Range::hir(expr) - && let Some(y) = y_minus_one(cx, end) + && limits == kind + && let Some(y) = predicate(cx, end) + && can_switch_ranges(cx, expr, kind, cx.typeck_results().expr_ty(y)) { - span_lint_and_then( - cx, - RANGE_MINUS_ONE, - expr.span, - "an exclusive range would be more readable", - |diag| { - let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_paren().to_string()); - let end = Sugg::hir(cx, y, "y").maybe_paren(); - diag.span_suggestion( - expr.span, - "use", - format!("{start}..{end}"), - Applicability::MachineApplicable, // snippet - ); - }, - ); + let span = expr.span; + span_lint_and_then(cx, lint, span, msg, |diag| { + let mut app = Applicability::MachineApplicable; + let start = start.map_or(String::new(), |x| { + Sugg::hir_with_applicability(cx, x, "", &mut app) + .maybe_paren() + .to_string() + }); + let end = Sugg::hir_with_applicability(cx, y, "", &mut app).maybe_paren(); + match span.with_source_text(cx, |src| src.starts_with('(') && src.ends_with(')')) { + Some(true) => { + diag.span_suggestion(span, "use", format!("({start}{operator}{end})"), app); + }, + Some(false) => { + diag.span_suggestion(span, "use", format!("{start}{operator}{end}"), app); + }, + None => {}, + } + }); } } @@ -494,7 +618,7 @@ fn check_reversed_empty_range(cx: &LateContext<'_>, expr: &Expr<'_>) { } } -fn y_plus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'t>> { +fn y_plus_one<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { match expr.kind { ExprKind::Binary( Spanned { @@ -515,7 +639,7 @@ fn y_plus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<' } } -fn y_minus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'t>> { +fn y_minus_one<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { match expr.kind { ExprKind::Binary( Spanned { diff --git a/clippy_lints/src/same_name_method.rs b/clippy_lints/src/same_name_method.rs index 85fde780e681..67eb71f7d074 100644 --- a/clippy_lints/src/same_name_method.rs +++ b/clippy_lints/src/same_name_method.rs @@ -3,7 +3,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{HirId, Impl, ItemKind, Node, Path, QPath, TraitRef, TyKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{AssocKind, AssocItem}; +use rustc_middle::ty::{AssocItem, AssocKind}; use rustc_session::declare_lint_pass; use rustc_span::Span; use rustc_span::symbol::Symbol; @@ -53,11 +53,7 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod { for id in cx.tcx.hir_free_items() { if matches!(cx.tcx.def_kind(id.owner_id), DefKind::Impl { .. }) && let item = cx.tcx.hir_item(id) - && let ItemKind::Impl(Impl { - of_trait, - self_ty, - .. - }) = &item.kind + && let ItemKind::Impl(Impl { of_trait, self_ty, .. }) = &item.kind && let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind { if !map.contains_key(res) { @@ -127,7 +123,9 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod { }, None => { for assoc_item in cx.tcx.associated_items(id.owner_id).in_definition_order() { - let AssocKind::Fn { name, .. } = assoc_item.kind else { continue }; + let AssocKind::Fn { name, .. } = assoc_item.kind else { + continue; + }; let impl_span = cx.tcx.def_span(assoc_item.def_id); let hir_id = cx.tcx.local_def_id_to_hir_id(assoc_item.def_id.expect_local()); if let Some(trait_spans) = existing_name.trait_methods.get(&name) { @@ -140,10 +138,7 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod { |diag| { // TODO should we `span_note` on every trait? // iterate on trait_spans? - diag.span_note( - trait_spans[0], - format!("existing `{name}` defined here"), - ); + diag.span_note(trait_spans[0], format!("existing `{name}` defined here")); }, ); } diff --git a/clippy_lints/src/unused_async.rs b/clippy_lints/src/unused_async.rs index e67afc7f5a8b..5a3e4b7adf64 100644 --- a/clippy_lints/src/unused_async.rs +++ b/clippy_lints/src/unused_async.rs @@ -1,8 +1,12 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::is_def_id_trait_method; +use clippy_utils::usage::is_todo_unimplemented_stub; use rustc_hir::def::DefKind; use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn}; -use rustc_hir::{Body, Defaultness, Expr, ExprKind, FnDecl, HirId, Node, TraitItem, YieldSource}; +use rustc_hir::{ + Body, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Defaultness, Expr, ExprKind, FnDecl, HirId, Node, + TraitItem, YieldSource, +}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_session::impl_lint_pass; @@ -81,11 +85,8 @@ impl<'tcx> Visitor<'tcx> for AsyncFnVisitor<'_, 'tcx> { let is_async_block = matches!( ex.kind, - ExprKind::Closure(rustc_hir::Closure { - kind: rustc_hir::ClosureKind::Coroutine(rustc_hir::CoroutineKind::Desugared( - rustc_hir::CoroutineDesugaring::Async, - _ - )), + ExprKind::Closure(Closure { + kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)), .. }) ); @@ -120,6 +121,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync { && fn_kind.asyncness().is_async() && !is_def_id_trait_method(cx, def_id) && !is_default_trait_impl(cx, def_id) + && !async_fn_contains_todo_unimplemented_macro(cx, body) { let mut visitor = AsyncFnVisitor { cx, @@ -203,3 +205,18 @@ fn is_default_trait_impl(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { }) ) } + +fn async_fn_contains_todo_unimplemented_macro(cx: &LateContext<'_>, body: &Body<'_>) -> bool { + if let ExprKind::Closure(closure) = body.value.kind + && let ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) = closure.kind + && let body = cx.tcx.hir_body(closure.body) + && let ExprKind::Block(block, _) = body.value.kind + && block.stmts.is_empty() + && let Some(expr) = block.expr + && let ExprKind::DropTemps(inner) = expr.kind + { + return is_todo_unimplemented_stub(cx, inner); + } + + false +} diff --git a/clippy_lints/src/unused_self.rs b/clippy_lints/src/unused_self.rs index 12da891a71b1..dff39974a373 100644 --- a/clippy_lints/src/unused_self.rs +++ b/clippy_lints/src/unused_self.rs @@ -1,12 +1,10 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::macros::root_macro_call_first_node; -use clippy_utils::sym; +use clippy_utils::usage::is_todo_unimplemented_stub; use clippy_utils::visitors::is_local_used; -use rustc_hir::{Body, Impl, ImplItem, ImplItemKind, ItemKind}; +use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; -use std::ops::ControlFlow; declare_clippy_lint! { /// ### What it does @@ -60,18 +58,6 @@ impl<'tcx> LateLintPass<'tcx> for UnusedSelf { let parent = cx.tcx.hir_get_parent_item(impl_item.hir_id()).def_id; let parent_item = cx.tcx.hir_expect_item(parent); let assoc_item = cx.tcx.associated_item(impl_item.owner_id); - let contains_todo = |cx, body: &'_ Body<'_>| -> bool { - clippy_utils::visitors::for_each_expr_without_closures(body.value, |e| { - if let Some(macro_call) = root_macro_call_first_node(cx, e) - && cx.tcx.is_diagnostic_item(sym::todo_macro, macro_call.def_id) - { - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) - } - }) - .is_some() - }; if let ItemKind::Impl(Impl { of_trait: None, .. }) = parent_item.kind && assoc_item.is_method() && let ImplItemKind::Fn(.., body_id) = &impl_item.kind @@ -79,7 +65,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedSelf { && let body = cx.tcx.hir_body(*body_id) && let [self_param, ..] = body.params && !is_local_used(cx, body, self_param.pat.hir_id) - && !contains_todo(cx, body) + && !is_todo_unimplemented_stub(cx, body.value) { span_lint_and_help( cx, diff --git a/clippy_lints/src/unused_trait_names.rs b/clippy_lints/src/unused_trait_names.rs index b7a1d5b2123e..12f2804dbaa1 100644 --- a/clippy_lints/src/unused_trait_names.rs +++ b/clippy_lints/src/unused_trait_names.rs @@ -6,7 +6,7 @@ use clippy_utils::source::snippet_opt; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Item, ItemKind, UseKind}; -use rustc_lint::{LateContext, LateLintPass, LintContext as _}; +use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::Visibility; use rustc_session::impl_lint_pass; use rustc_span::symbol::kw; @@ -59,7 +59,7 @@ impl_lint_pass!(UnusedTraitNames => [UNUSED_TRAIT_NAMES]); impl<'tcx> LateLintPass<'tcx> for UnusedTraitNames { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { - if !item.span.in_external_macro(cx.sess().source_map()) + if !item.span.from_expansion() && let ItemKind::Use(path, UseKind::Single(ident)) = item.kind // Ignore imports that already use Underscore && ident.name != kw::Underscore diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index 380ddea4e1e8..e5b20c0e0a13 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -176,6 +176,33 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { } }, + ExprKind::MethodCall(path, recv, [arg], _) => { + if matches!( + path.ident.name, + sym::map | sym::map_err | sym::map_break | sym::map_continue + ) && has_eligible_receiver(cx, recv, e) + && (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) + { + span_lint_and_then( + cx, + USELESS_CONVERSION, + e.span.with_lo(recv.span.hi()), + format!("useless conversion to the same type: `{from_ty}`"), + |diag| { + diag.suggest_remove_item( + cx, + e.span.with_lo(recv.span.hi()), + "consider removing", + Applicability::MachineApplicable, + ); + }, + ); + } + }, + ExprKind::MethodCall(name, recv, [], _) => { if is_trait_method(cx, e, sym::Into) && name.ident.name == sym::into { let a = cx.typeck_results().expr_ty(e); @@ -412,32 +439,6 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { } } -/// Check if `arg` is a `Into::into` or `From::from` applied to `receiver` to give `expr`, through a -/// higher-order mapping function. -pub fn check_function_application(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) { - if has_eligible_receiver(cx, recv, expr) - && (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) - { - span_lint_and_then( - cx, - USELESS_CONVERSION, - expr.span.with_lo(recv.span.hi()), - format!("useless conversion to the same type: `{from_ty}`"), - |diag| { - diag.suggest_remove_item( - cx, - expr.span.with_lo(recv.span.hi()), - "consider removing", - Applicability::MachineApplicable, - ); - }, - ); - } -} - fn has_eligible_receiver(cx: &LateContext<'_>, recv: &Expr<'_>, expr: &Expr<'_>) -> bool { if is_inherent_method_call(cx, expr) { matches!( diff --git a/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs b/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs index 88b099c477f9..41fafc08c259 100644 --- a/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs +++ b/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs @@ -2,6 +2,7 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::paths; use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::{AttrStyle, DelimArgs}; +use rustc_attr_data_structures::{AttributeKind, find_attr}; use rustc_hir::def::Res; use rustc_hir::def_id::LocalDefId; use rustc_hir::{ @@ -11,7 +12,6 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_lint_defs::declare_tool_lint; use rustc_middle::ty::TyCtxt; use rustc_session::declare_lint_pass; -use rustc_span::sym; declare_tool_lint! { /// ### What it does @@ -88,7 +88,10 @@ impl<'tcx> LateLintPass<'tcx> for DeriveDeserializeAllowingUnknown { } // Is it derived? - if !find_attr!(cx.tcx.get_all_attrs(item.owner_id), AttributeKind::AutomaticallyDerived(..)) { + if !find_attr!( + cx.tcx.get_all_attrs(item.owner_id), + AttributeKind::AutomaticallyDerived(..) + ) { return; } diff --git a/clippy_lints_internal/src/lint_without_lint_pass.rs b/clippy_lints_internal/src/lint_without_lint_pass.rs index 45a866030b2d..fda65bc84eda 100644 --- a/clippy_lints_internal/src/lint_without_lint_pass.rs +++ b/clippy_lints_internal/src/lint_without_lint_pass.rs @@ -1,7 +1,7 @@ use crate::internal_paths; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; -use clippy_utils::is_lint_allowed; use clippy_utils::macros::root_macro_call_first_node; +use clippy_utils::{is_lint_allowed, sym}; use rustc_ast::ast::LitKind; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_hir as hir; @@ -12,9 +12,9 @@ use rustc_hir::{ExprKind, HirId, Item, MutTy, Mutability, Path, TyKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::Span; use rustc_span::source_map::Spanned; use rustc_span::symbol::Symbol; -use rustc_span::{Span, sym}; declare_tool_lint! { /// ### What it does @@ -160,9 +160,8 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { let body = cx.tcx.hir_body_owned_by( impl_item_refs .iter() - .find(|iiref| iiref.ident.as_str() == "lint_vec") + .find(|&&iiref| cx.tcx.item_name(iiref.owner_id) == sym::lint_vec) .expect("LintPass needs to implement lint_vec") - .id .owner_id .def_id, ); diff --git a/clippy_lints_internal/src/msrv_attr_impl.rs b/clippy_lints_internal/src/msrv_attr_impl.rs index 70b3c03d2bbd..66aeb910891a 100644 --- a/clippy_lints_internal/src/msrv_attr_impl.rs +++ b/clippy_lints_internal/src/msrv_attr_impl.rs @@ -1,6 +1,7 @@ use crate::internal_paths; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; +use clippy_utils::sym; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -40,7 +41,9 @@ impl LateLintPass<'_> for MsrvAttrImpl { .filter(|t| matches!(t.kind(), GenericArgKind::Type(_))) .any(|t| internal_paths::MSRV_STACK.matches_ty(cx, t.expect_ty())) }) - && !items.iter().any(|item| item.ident.name.as_str() == "check_attributes") + && !items + .iter() + .any(|&item| cx.tcx.item_name(item.owner_id) == sym::check_attributes) { let span = cx.sess().source_map().span_through_char(item.span, '{'); span_lint_and_sugg( diff --git a/clippy_utils/README.md b/clippy_utils/README.md index 645b644d9f4e..19e71f6af1de 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-07-10 +nightly-2025-07-25 ``` diff --git a/clippy_utils/src/diagnostics.rs b/clippy_utils/src/diagnostics.rs index 8453165818b3..625e1eead213 100644 --- a/clippy_utils/src/diagnostics.rs +++ b/clippy_utils/src/diagnostics.rs @@ -22,10 +22,13 @@ fn docs_link(diag: &mut Diag<'_, ()>, lint: &'static Lint) { { diag.help(format!( "for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{lint}", - &option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| { - // extract just major + minor version and ignore patch versions - format!("rust-{}", n.rsplit_once('.').unwrap().1) - }) + &option_env!("RUST_RELEASE_NUM").map_or_else( + || "master".to_string(), + |n| { + // extract just major + minor version and ignore patch versions + format!("rust-{}", n.rsplit_once('.').unwrap().1) + } + ) )); } } diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index ff1ee663f9bf..ce5af4d2f482 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -89,8 +89,8 @@ use std::sync::{Mutex, MutexGuard, OnceLock}; use itertools::Itertools; use rustc_abi::Integer; -use rustc_ast::join_path_syms; use rustc_ast::ast::{self, LitKind, RangeLimits}; +use rustc_ast::join_path_syms; use rustc_attr_data_structures::{AttributeKind, find_attr}; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::packed::Pu128; @@ -114,7 +114,7 @@ use rustc_middle::hir::nested_filter; use rustc_middle::hir::place::PlaceBase; use rustc_middle::lint::LevelAndSource; use rustc_middle::mir::{AggregateKind, Operand, RETURN_PLACE, Rvalue, StatementKind, TerminatorKind}; -use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; +use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, PointerCoercion}; use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::{ self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, GenericArgKind, GenericArgsRef, IntTy, Ty, TyCtxt, @@ -1897,6 +1897,7 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { /// * `|x| { return x }` /// * `|x| { return x; }` /// * `|(x, y)| (x, y)` +/// * `|[x, y]| [x, y]` /// /// Consider calling [`is_expr_untyped_identity_function`] or [`is_expr_identity_function`] instead. fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool { @@ -1907,9 +1908,9 @@ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool { .get(pat.hir_id) .is_some_and(|mode| matches!(mode.0, ByRef::Yes(_))) { - // If a tuple `(x, y)` is of type `&(i32, i32)`, then due to match ergonomics, - // the inner patterns become references. Don't consider this the identity function - // as that changes types. + // If the parameter is `(x, y)` of type `&(T, T)`, or `[x, y]` of type `&[T; 2]`, then + // due to match ergonomics, the inner patterns become references. Don't consider this + // the identity function as that changes types. return false; } @@ -1922,6 +1923,13 @@ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool { { pats.iter().zip(tup).all(|(pat, expr)| check_pat(cx, pat, expr)) }, + (PatKind::Slice(before, slice, after), ExprKind::Array(arr)) + if slice.is_none() && before.len() + after.len() == arr.len() => + { + (before.iter().chain(after)) + .zip(arr) + .all(|(pat, expr)| check_pat(cx, pat, expr)) + }, _ => false, } } @@ -3269,15 +3277,13 @@ fn maybe_get_relative_path(from: &DefPath, to: &DefPath, max_super: usize) -> St if go_up_by > max_super { // `super` chain would be too long, just use the absolute path instead - join_path_syms( - once(kw::Crate).chain(to.data.iter().filter_map(|el| { - if let DefPathData::TypeNs(sym) = el.data { - Some(sym) - } else { - None - } - })) - ) + join_path_syms(once(kw::Crate).chain(to.data.iter().filter_map(|el| { + if let DefPathData::TypeNs(sym) = el.data { + Some(sym) + } else { + None + } + }))) } else { join_path_syms(repeat_n(kw::Super, go_up_by).chain(path)) } @@ -3560,3 +3566,14 @@ pub fn potential_return_of_enclosing_body(cx: &LateContext<'_>, expr: &Expr<'_>) // enclosing body. false } + +/// Checks if the expression has adjustments that require coercion, for example: dereferencing with +/// overloaded deref, coercing pointers and `dyn` objects. +pub fn expr_adjustment_requires_coercion(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + cx.typeck_results().expr_adjustments(expr).iter().any(|adj| { + matches!( + adj.kind, + Adjust::Deref(Some(_)) | Adjust::Pointer(PointerCoercion::Unsize) | Adjust::NeverToAny + ) + }) +} diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index c681806517af..ea8cfc59356a 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -308,10 +308,11 @@ fn local_item_child_by_name(tcx: TyCtxt<'_>, local_id: LocalDefId, ns: PathNS, n None } }), - ItemKind::Impl(..) | ItemKind::Trait(..) - => tcx.associated_items(local_id).filter_by_name_unhygienic(name) - .find(|assoc_item| ns.matches(Some(assoc_item.namespace()))) - .map(|assoc_item| assoc_item.def_id), + ItemKind::Impl(..) | ItemKind::Trait(..) => tcx + .associated_items(local_id) + .filter_by_name_unhygienic(name) + .find(|assoc_item| ns.matches(Some(assoc_item.namespace()))) + .map(|assoc_item| assoc_item.def_id), _ => None, } } diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index 8a8218c6976f..934be97d94e5 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -98,6 +98,7 @@ generate! { ceil_char_boundary, chain, chars, + check_attributes, checked_abs, checked_add, checked_isqrt, @@ -196,6 +197,7 @@ generate! { kw, last, lazy_static, + lint_vec, ln, lock, lock_api, @@ -261,6 +263,7 @@ generate! { read_to_end, read_to_string, read_unaligned, + redundant_imports, redundant_pub_crate, regex, rem_euclid, diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index fe208c032f4c..d70232ef3aa5 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -492,10 +492,7 @@ pub fn peel_mid_ty_refs_is_mutable(ty: Ty<'_>) -> (Ty<'_>, usize, Mutability) { /// Returns `true` if the given type is an `unsafe` function. pub fn type_is_unsafe_function<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { - match ty.kind() { - ty::FnDef(..) | ty::FnPtr(..) => ty.fn_sig(cx.tcx).safety().is_unsafe(), - _ => false, - } + ty.is_fn() && ty.fn_sig(cx.tcx).safety().is_unsafe() } /// Returns the base type for HIR references and pointers. diff --git a/clippy_utils/src/ty/type_certainty/mod.rs b/clippy_utils/src/ty/type_certainty/mod.rs index 84df36c75bf8..d9c7e6eac9f6 100644 --- a/clippy_utils/src/ty/type_certainty/mod.rs +++ b/clippy_utils/src/ty/type_certainty/mod.rs @@ -12,10 +12,11 @@ //! be considered a bug. use crate::paths::{PathNS, lookup_path}; +use rustc_ast::{LitFloatType, LitIntType, LitKind}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::{InferKind, Visitor, VisitorExt, walk_qpath, walk_ty}; -use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, GenericArgs, HirId, Node, PathSegment, QPath, TyKind}; +use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, GenericArgs, HirId, Node, Param, PathSegment, QPath, TyKind}; use rustc_lint::LateContext; use rustc_middle::ty::{self, AdtDef, GenericArgKind, Ty}; use rustc_span::Span; @@ -24,22 +25,24 @@ mod certainty; use certainty::{Certainty, Meet, join, meet}; pub fn expr_type_is_certain(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - expr_type_certainty(cx, expr).is_certain() + expr_type_certainty(cx, expr, false).is_certain() } -fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty { +/// Determine the type certainty of `expr`. `in_arg` indicates that the expression happens within +/// the evaluation of a function or method call argument. +fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>, in_arg: bool) -> Certainty { let certainty = match &expr.kind { ExprKind::Unary(_, expr) | ExprKind::Field(expr, _) | ExprKind::Index(expr, _, _) - | ExprKind::AddrOf(_, _, expr) => expr_type_certainty(cx, expr), + | ExprKind::AddrOf(_, _, expr) => expr_type_certainty(cx, expr, in_arg), - ExprKind::Array(exprs) => join(exprs.iter().map(|expr| expr_type_certainty(cx, expr))), + ExprKind::Array(exprs) => join(exprs.iter().map(|expr| expr_type_certainty(cx, expr, in_arg))), ExprKind::Call(callee, args) => { - let lhs = expr_type_certainty(cx, callee); + let lhs = expr_type_certainty(cx, callee, false); let rhs = if type_is_inferable_from_arguments(cx, expr) { - meet(args.iter().map(|arg| expr_type_certainty(cx, arg))) + meet(args.iter().map(|arg| expr_type_certainty(cx, arg, true))) } else { Certainty::Uncertain }; @@ -47,7 +50,7 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty { }, ExprKind::MethodCall(method, receiver, args, _) => { - let mut receiver_type_certainty = expr_type_certainty(cx, receiver); + let mut receiver_type_certainty = expr_type_certainty(cx, receiver, false); // Even if `receiver_type_certainty` is `Certain(Some(..))`, the `Self` type in the method // identified by `type_dependent_def_id(..)` can differ. This can happen as a result of a `deref`, // for example. So update the `DefId` in `receiver_type_certainty` (if any). @@ -59,7 +62,8 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty { let lhs = path_segment_certainty(cx, receiver_type_certainty, method, false); let rhs = if type_is_inferable_from_arguments(cx, expr) { meet( - std::iter::once(receiver_type_certainty).chain(args.iter().map(|arg| expr_type_certainty(cx, arg))), + std::iter::once(receiver_type_certainty) + .chain(args.iter().map(|arg| expr_type_certainty(cx, arg, true))), ) } else { Certainty::Uncertain @@ -67,16 +71,39 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty { lhs.join(rhs) }, - ExprKind::Tup(exprs) => meet(exprs.iter().map(|expr| expr_type_certainty(cx, expr))), + ExprKind::Tup(exprs) => meet(exprs.iter().map(|expr| expr_type_certainty(cx, expr, in_arg))), - ExprKind::Binary(_, lhs, rhs) => expr_type_certainty(cx, lhs).meet(expr_type_certainty(cx, rhs)), + ExprKind::Binary(_, lhs, rhs) => { + // If one of the side of the expression is uncertain, the certainty will come from the other side, + // with no information on the type. + match ( + expr_type_certainty(cx, lhs, in_arg), + expr_type_certainty(cx, rhs, in_arg), + ) { + (Certainty::Uncertain, Certainty::Certain(_)) | (Certainty::Certain(_), Certainty::Uncertain) => { + Certainty::Certain(None) + }, + (l, r) => l.meet(r), + } + }, - ExprKind::Lit(_) => Certainty::Certain(None), + ExprKind::Lit(lit) => { + if !in_arg + && matches!( + lit.node, + LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed) + ) + { + Certainty::Uncertain + } else { + Certainty::Certain(None) + } + }, ExprKind::Cast(_, ty) => type_certainty(cx, ty), ExprKind::If(_, if_expr, Some(else_expr)) => { - expr_type_certainty(cx, if_expr).join(expr_type_certainty(cx, else_expr)) + expr_type_certainty(cx, if_expr, in_arg).join(expr_type_certainty(cx, else_expr, in_arg)) }, ExprKind::Path(qpath) => qpath_certainty(cx, qpath, false), @@ -188,6 +215,20 @@ fn qpath_certainty(cx: &LateContext<'_>, qpath: &QPath<'_>, resolves_to_type: bo certainty } +/// Tries to tell whether `param` resolves to something certain, e.g., a non-wildcard type if +/// present. The certainty `DefId` is cleared before returning. +fn param_certainty(cx: &LateContext<'_>, param: &Param<'_>) -> Certainty { + let owner_did = cx.tcx.hir_enclosing_body_owner(param.hir_id); + let Some(fn_decl) = cx.tcx.hir_fn_decl_by_hir_id(cx.tcx.local_def_id_to_hir_id(owner_did)) else { + return Certainty::Uncertain; + }; + let inputs = fn_decl.inputs; + let body_params = cx.tcx.hir_body_owned_by(owner_did).params; + std::iter::zip(body_params, inputs) + .find(|(p, _)| p.hir_id == param.hir_id) + .map_or(Certainty::Uncertain, |(_, ty)| type_certainty(cx, ty).clear_def_id()) +} + fn path_segment_certainty( cx: &LateContext<'_>, parent_certainty: Certainty, @@ -240,15 +281,16 @@ fn path_segment_certainty( // `get_parent` because `hir_id` refers to a `Pat`, and we're interested in the node containing the `Pat`. Res::Local(hir_id) => match cx.tcx.parent_hir_node(hir_id) { - // An argument's type is always certain. - Node::Param(..) => Certainty::Certain(None), + // A parameter's type is not always certain, as it may come from an untyped closure definition, + // or from a wildcard in a typed closure definition. + Node::Param(param) => param_certainty(cx, param), // A local's type is certain if its type annotation is certain or it has an initializer whose // type is certain. Node::LetStmt(local) => { let lhs = local.ty.map_or(Certainty::Uncertain, |ty| type_certainty(cx, ty)); let rhs = local .init - .map_or(Certainty::Uncertain, |init| expr_type_certainty(cx, init)); + .map_or(Certainty::Uncertain, |init| expr_type_certainty(cx, init, false)); let certainty = lhs.join(rhs); if resolves_to_type { certainty diff --git a/clippy_utils/src/usage.rs b/clippy_utils/src/usage.rs index 1b049b6d12c4..76d43feee12e 100644 --- a/clippy_utils/src/usage.rs +++ b/clippy_utils/src/usage.rs @@ -1,3 +1,4 @@ +use crate::macros::root_macro_call_first_node; use crate::visitors::{Descend, Visitable, for_each_expr, for_each_expr_without_closures}; use crate::{self as utils, get_enclosing_loop_or_multi_call_closure}; use core::ops::ControlFlow; @@ -9,6 +10,7 @@ use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::mir::FakeReadCause; use rustc_middle::ty; +use rustc_span::sym; /// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined. pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Option { @@ -140,6 +142,46 @@ impl<'tcx> Visitor<'tcx> for BindingUsageFinder<'_, 'tcx> { } } +/// Checks if the given expression is a macro call to `todo!()` or `unimplemented!()`. +pub fn is_todo_unimplemented_macro(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + root_macro_call_first_node(cx, expr).is_some_and(|macro_call| { + [sym::todo_macro, sym::unimplemented_macro] + .iter() + .any(|&sym| cx.tcx.is_diagnostic_item(sym, macro_call.def_id)) + }) +} + +/// Checks if the given expression is a stub, i.e., a `todo!()` or `unimplemented!()` expression, +/// or a block whose last expression is a `todo!()` or `unimplemented!()`. +pub fn is_todo_unimplemented_stub(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let ExprKind::Block(block, _) = expr.kind { + if let Some(last_expr) = block.expr { + return is_todo_unimplemented_macro(cx, last_expr); + } + + return block.stmts.last().is_some_and(|stmt| { + if let hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr) = stmt.kind { + return is_todo_unimplemented_macro(cx, expr); + } + false + }); + } + + is_todo_unimplemented_macro(cx, expr) +} + +/// Checks if the given expression contains macro call to `todo!()` or `unimplemented!()`. +pub fn contains_todo_unimplement_macro(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool { + for_each_expr_without_closures(expr, |e| { + if is_todo_unimplemented_macro(cx, e) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_some() +} + pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool { for_each_expr_without_closures(expression, |e| { match e.kind { diff --git a/rust-toolchain.toml b/rust-toolchain.toml index f46e079db3f1..0edb80edd04e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2025-07-10" +channel = "nightly-2025-07-25" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/rustc_tools_util/src/lib.rs b/rustc_tools_util/src/lib.rs index b45edf234558..194ed84d04c2 100644 --- a/rustc_tools_util/src/lib.rs +++ b/rustc_tools_util/src/lib.rs @@ -157,7 +157,8 @@ pub fn get_commit_date() -> Option { #[must_use] pub fn get_compiler_version() -> Option { - get_output("rustc", &["-V"]) + let compiler = std::option_env!("RUSTC").unwrap_or("rustc"); + get_output(compiler, &["-V"]) } #[must_use] @@ -172,6 +173,8 @@ pub fn get_channel(compiler_version: Option) -> String { return String::from("beta"); } else if rustc_output.contains("nightly") { return String::from("nightly"); + } else if rustc_output.contains("dev") { + return String::from("dev"); } } diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 83f91ccaa7b4..464efc45c6b4 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -162,6 +162,10 @@ impl TestContext { // however has some staging logic that is hurting us here, so to work around // that we set both the "real" and "staging" rustc to TEST_RUSTC, including the // associated library paths. + #[expect( + clippy::option_env_unwrap, + reason = "TEST_RUSTC will ensure that the requested env vars are set during compile time" + )] if let Some(rustc) = option_env!("TEST_RUSTC") { let libdir = option_env!("TEST_RUSTC_LIB").unwrap(); let sysroot = option_env!("TEST_SYSROOT").unwrap(); @@ -169,10 +173,7 @@ impl TestContext { p.envs.push(("RUSTC_REAL_LIBDIR".into(), Some(libdir.into()))); p.envs.push(("RUSTC_SNAPSHOT".into(), Some(rustc.into()))); p.envs.push(("RUSTC_SNAPSHOT_LIBDIR".into(), Some(libdir.into()))); - p.envs.push(( - "RUSTC_SYSROOT".into(), - Some(sysroot.into()), - )); + p.envs.push(("RUSTC_SYSROOT".into(), Some(sysroot.into()))); } p }, diff --git a/tests/ui-toml/check_incompatible_msrv_in_tests/check_incompatible_msrv_in_tests.enabled.stderr b/tests/ui-toml/check_incompatible_msrv_in_tests/check_incompatible_msrv_in_tests.enabled.stderr index 8a85d38fba3c..608264beb102 100644 --- a/tests/ui-toml/check_incompatible_msrv_in_tests/check_incompatible_msrv_in_tests.enabled.stderr +++ b/tests/ui-toml/check_incompatible_msrv_in_tests/check_incompatible_msrv_in_tests.enabled.stderr @@ -18,6 +18,8 @@ error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is | LL | sleep(Duration::new(1, 0)); | ^^^^^ + | + = note: you may want to conditionally increase the MSRV considered by Clippy using the `clippy::msrv` attribute error: aborting due to 3 previous errors diff --git a/tests/ui-toml/enum_variant_size/enum_variant_size.stderr b/tests/ui-toml/enum_variant_size/enum_variant_size.stderr index 020b3cc78782..a5dfd7015a39 100644 --- a/tests/ui-toml/enum_variant_size/enum_variant_size.stderr +++ b/tests/ui-toml/enum_variant_size/enum_variant_size.stderr @@ -12,7 +12,7 @@ LL | | } | = note: `-D clippy::large-enum-variant` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::large_enum_variant)]` -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B([u8; 501]), LL + B(Box<[u8; 501]>), diff --git a/tests/ui/approx_const.rs b/tests/ui/approx_const.rs index 6461666be8f5..fc493421a165 100644 --- a/tests/ui/approx_const.rs +++ b/tests/ui/approx_const.rs @@ -106,4 +106,19 @@ fn main() { //~^ approx_constant let no_tau = 6.3; + + // issue #15194 + #[allow(clippy::excessive_precision)] + let x: f64 = 3.1415926535897932384626433832; + //~^ approx_constant + + #[allow(clippy::excessive_precision)] + let _: f64 = 003.14159265358979311599796346854418516159057617187500; + //~^ approx_constant + + let almost_frac_1_sqrt_2 = 00.70711; + //~^ approx_constant + + let almost_frac_1_sqrt_2 = 00.707_11; + //~^ approx_constant } diff --git a/tests/ui/approx_const.stderr b/tests/ui/approx_const.stderr index f7bda0468cbd..32a3517ff2e4 100644 --- a/tests/ui/approx_const.stderr +++ b/tests/ui/approx_const.stderr @@ -184,5 +184,37 @@ LL | let almost_tau = 6.28; | = help: consider using the constant directly -error: aborting due to 23 previous errors +error: approximate value of `f{32, 64}::consts::PI` found + --> tests/ui/approx_const.rs:112:18 + | +LL | let x: f64 = 3.1415926535897932384626433832; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using the constant directly + +error: approximate value of `f{32, 64}::consts::PI` found + --> tests/ui/approx_const.rs:116:18 + | +LL | let _: f64 = 003.14159265358979311599796346854418516159057617187500; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using the constant directly + +error: approximate value of `f{32, 64}::consts::FRAC_1_SQRT_2` found + --> tests/ui/approx_const.rs:119:32 + | +LL | let almost_frac_1_sqrt_2 = 00.70711; + | ^^^^^^^^ + | + = help: consider using the constant directly + +error: approximate value of `f{32, 64}::consts::FRAC_1_SQRT_2` found + --> tests/ui/approx_const.rs:122:32 + | +LL | let almost_frac_1_sqrt_2 = 00.707_11; + | ^^^^^^^^^ + | + = help: consider using the constant directly + +error: aborting due to 27 previous errors diff --git a/tests/ui/arc_with_non_send_sync.stderr b/tests/ui/arc_with_non_send_sync.stderr index 5556b0df88c9..ce726206b0cd 100644 --- a/tests/ui/arc_with_non_send_sync.stderr +++ b/tests/ui/arc_with_non_send_sync.stderr @@ -5,7 +5,7 @@ LL | let _ = Arc::new(RefCell::new(42)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `Arc>` is not `Send` and `Sync` as `RefCell` is not `Sync` - = help: if the `Arc` will not used be across threads replace it with an `Rc` + = help: if the `Arc` will not be used across threads replace it with an `Rc` = help: otherwise make `RefCell` `Send` and `Sync` or consider a wrapper type such as `Mutex` = note: `-D clippy::arc-with-non-send-sync` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::arc_with_non_send_sync)]` @@ -17,7 +17,7 @@ LL | let _ = Arc::new(mutex.lock().unwrap()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `Arc>` is not `Send` and `Sync` as `MutexGuard<'_, i32>` is not `Send` - = help: if the `Arc` will not used be across threads replace it with an `Rc` + = help: if the `Arc` will not be used across threads replace it with an `Rc` = help: otherwise make `MutexGuard<'_, i32>` `Send` and `Sync` or consider a wrapper type such as `Mutex` error: usage of an `Arc` that is not `Send` and `Sync` @@ -27,7 +27,7 @@ LL | let _ = Arc::new(&42 as *const i32); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `Arc<*const i32>` is not `Send` and `Sync` as `*const i32` is neither `Send` nor `Sync` - = help: if the `Arc` will not used be across threads replace it with an `Rc` + = help: if the `Arc` will not be used across threads replace it with an `Rc` = help: otherwise make `*const i32` `Send` and `Sync` or consider a wrapper type such as `Mutex` error: aborting due to 3 previous errors diff --git a/tests/ui/arithmetic_side_effects.rs b/tests/ui/arithmetic_side_effects.rs index 21be2af201f0..3245b2c983e1 100644 --- a/tests/ui/arithmetic_side_effects.rs +++ b/tests/ui/arithmetic_side_effects.rs @@ -664,6 +664,20 @@ pub fn issue_12318() { //~^ arithmetic_side_effects } +pub fn issue_15225() { + use core::num::{NonZero, NonZeroU8}; + + let one = const { NonZeroU8::new(1).unwrap() }; + let _ = one.get() - 1; + + let one: NonZero = const { NonZero::new(1).unwrap() }; + let _ = one.get() - 1; + + type AliasedType = u8; + let one: NonZero = const { NonZero::new(1).unwrap() }; + let _ = one.get() - 1; +} + pub fn explicit_methods() { use core::ops::Add; let one: i32 = 1; diff --git a/tests/ui/arithmetic_side_effects.stderr b/tests/ui/arithmetic_side_effects.stderr index e15fb612be5e..4150493ba94a 100644 --- a/tests/ui/arithmetic_side_effects.stderr +++ b/tests/ui/arithmetic_side_effects.stderr @@ -758,13 +758,13 @@ LL | one.sub_assign(1); | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:670:5 + --> tests/ui/arithmetic_side_effects.rs:684:5 | LL | one.add(&one); | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:672:5 + --> tests/ui/arithmetic_side_effects.rs:686:5 | LL | Box::new(one).add(one); | ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/assign_ops.fixed b/tests/ui/assign_ops.fixed index eee61f949e76..3754b9dfe744 100644 --- a/tests/ui/assign_ops.fixed +++ b/tests/ui/assign_ops.fixed @@ -84,6 +84,7 @@ mod issue14871 { const ONE: Self; } + #[rustfmt::skip] // rustfmt doesn't understand the order of pub const on traits (yet) pub const trait NumberConstants { fn constant(value: usize) -> Self; } diff --git a/tests/ui/assign_ops.rs b/tests/ui/assign_ops.rs index 13ffcee0a3c8..0b878d4f490b 100644 --- a/tests/ui/assign_ops.rs +++ b/tests/ui/assign_ops.rs @@ -84,6 +84,7 @@ mod issue14871 { const ONE: Self; } + #[rustfmt::skip] // rustfmt doesn't understand the order of pub const on traits (yet) pub const trait NumberConstants { fn constant(value: usize) -> Self; } diff --git a/tests/ui/auxiliary/external_item.rs b/tests/ui/auxiliary/external_item.rs index ca4bc369e449..621e18f5c017 100644 --- a/tests/ui/auxiliary/external_item.rs +++ b/tests/ui/auxiliary/external_item.rs @@ -4,4 +4,4 @@ impl _ExternalStruct { pub fn _foo(self) {} } -pub fn _exernal_foo() {} +pub fn _external_foo() {} diff --git a/tests/ui/checked_conversions.fixed b/tests/ui/checked_conversions.fixed index 279a5b6e1ff8..6175275ef047 100644 --- a/tests/ui/checked_conversions.fixed +++ b/tests/ui/checked_conversions.fixed @@ -95,7 +95,7 @@ pub const fn issue_8898(i: u32) -> bool { #[clippy::msrv = "1.33"] fn msrv_1_33() { let value: i64 = 33; - let _ = value <= (u32::MAX as i64) && value >= 0; + let _ = value <= (u32::max_value() as i64) && value >= 0; } #[clippy::msrv = "1.34"] diff --git a/tests/ui/checked_conversions.rs b/tests/ui/checked_conversions.rs index c339bc674bbb..9ed0e8f660d0 100644 --- a/tests/ui/checked_conversions.rs +++ b/tests/ui/checked_conversions.rs @@ -95,13 +95,13 @@ pub const fn issue_8898(i: u32) -> bool { #[clippy::msrv = "1.33"] fn msrv_1_33() { let value: i64 = 33; - let _ = value <= (u32::MAX as i64) && value >= 0; + let _ = value <= (u32::max_value() as i64) && value >= 0; } #[clippy::msrv = "1.34"] fn msrv_1_34() { let value: i64 = 34; - let _ = value <= (u32::MAX as i64) && value >= 0; + let _ = value <= (u32::max_value() as i64) && value >= 0; //~^ checked_conversions } diff --git a/tests/ui/checked_conversions.stderr b/tests/ui/checked_conversions.stderr index 3841b9d5a4dd..624876dacb26 100644 --- a/tests/ui/checked_conversions.stderr +++ b/tests/ui/checked_conversions.stderr @@ -100,8 +100,8 @@ LL | let _ = value <= u16::MAX as u32 && value as i32 == 5; error: checked cast can be simplified --> tests/ui/checked_conversions.rs:104:13 | -LL | let _ = value <= (u32::MAX as i64) && value >= 0; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()` +LL | let _ = value <= (u32::max_value() as i64) && value >= 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()` error: aborting due to 17 previous errors diff --git a/tests/ui/expect.rs b/tests/ui/expect.rs index 8f7379f00214..1ab01ecfcfe3 100644 --- a/tests/ui/expect.rs +++ b/tests/ui/expect.rs @@ -16,7 +16,26 @@ fn expect_result() { //~^ expect_used } +#[allow(clippy::ok_expect)] +#[allow(clippy::err_expect)] +fn issue_15247() { + let x: Result = Err(0); + x.ok().expect("Huh"); + //~^ expect_used + + { x.ok() }.expect("..."); + //~^ expect_used + + let y: Result = Ok(0); + y.err().expect("Huh"); + //~^ expect_used + + { y.err() }.expect("..."); + //~^ expect_used +} + fn main() { expect_option(); expect_result(); + issue_15247(); } diff --git a/tests/ui/expect.stderr b/tests/ui/expect.stderr index 70cf3072003e..353fb7765315 100644 --- a/tests/ui/expect.stderr +++ b/tests/ui/expect.stderr @@ -24,5 +24,37 @@ LL | let _ = res.expect_err(""); | = note: if this value is an `Ok`, it will panic -error: aborting due to 3 previous errors +error: used `expect()` on an `Option` value + --> tests/ui/expect.rs:23:5 + | +LL | x.ok().expect("Huh"); + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: if this value is `None`, it will panic + +error: used `expect()` on an `Option` value + --> tests/ui/expect.rs:26:5 + | +LL | { x.ok() }.expect("..."); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: if this value is `None`, it will panic + +error: used `expect()` on an `Option` value + --> tests/ui/expect.rs:30:5 + | +LL | y.err().expect("Huh"); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: if this value is `None`, it will panic + +error: used `expect()` on an `Option` value + --> tests/ui/expect.rs:33:5 + | +LL | { y.err() }.expect("..."); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: if this value is `None`, it will panic + +error: aborting due to 7 previous errors diff --git a/tests/ui/expect_fun_call.fixed b/tests/ui/expect_fun_call.fixed index 73eaebf773c8..b923521afde1 100644 --- a/tests/ui/expect_fun_call.fixed +++ b/tests/ui/expect_fun_call.fixed @@ -90,17 +90,30 @@ fn main() { "foo" } - Some("foo").unwrap_or_else(|| { panic!("{}", get_string()) }); + const fn const_evaluable() -> &'static str { + "foo" + } + + Some("foo").unwrap_or_else(|| panic!("{}", get_string())); //~^ expect_fun_call - Some("foo").unwrap_or_else(|| { panic!("{}", get_string()) }); + Some("foo").unwrap_or_else(|| panic!("{}", get_string())); //~^ expect_fun_call - Some("foo").unwrap_or_else(|| { panic!("{}", get_string()) }); + Some("foo").unwrap_or_else(|| panic!("{}", get_string())); //~^ expect_fun_call - Some("foo").unwrap_or_else(|| { panic!("{}", get_static_str()) }); + Some("foo").unwrap_or_else(|| panic!("{}", get_static_str())); //~^ expect_fun_call - Some("foo").unwrap_or_else(|| { panic!("{}", get_non_static_str(&0).to_string()) }); + Some("foo").unwrap_or_else(|| panic!("{}", get_non_static_str(&0))); //~^ expect_fun_call + + Some("foo").unwrap_or_else(|| panic!("{}", const_evaluable())); + //~^ expect_fun_call + + const { + Some("foo").expect(const_evaluable()); + } + + Some("foo").expect(const { const_evaluable() }); } //Issue #3839 @@ -122,4 +135,15 @@ fn main() { let format_capture_and_value: Option = None; format_capture_and_value.unwrap_or_else(|| panic!("{error_code}, {}", 1)); //~^ expect_fun_call + + // Issue #15056 + let a = false; + Some(5).expect(if a { "a" } else { "b" }); + + let return_in_expect: Option = None; + return_in_expect.expect(if true { + "Error" + } else { + return; + }); } diff --git a/tests/ui/expect_fun_call.rs b/tests/ui/expect_fun_call.rs index ecebc9ebfb6f..bc58d24bc812 100644 --- a/tests/ui/expect_fun_call.rs +++ b/tests/ui/expect_fun_call.rs @@ -90,6 +90,10 @@ fn main() { "foo" } + const fn const_evaluable() -> &'static str { + "foo" + } + Some("foo").expect(&get_string()); //~^ expect_fun_call Some("foo").expect(get_string().as_ref()); @@ -101,6 +105,15 @@ fn main() { //~^ expect_fun_call Some("foo").expect(get_non_static_str(&0)); //~^ expect_fun_call + + Some("foo").expect(const_evaluable()); + //~^ expect_fun_call + + const { + Some("foo").expect(const_evaluable()); + } + + Some("foo").expect(const { const_evaluable() }); } //Issue #3839 @@ -122,4 +135,15 @@ fn main() { let format_capture_and_value: Option = None; format_capture_and_value.expect(&format!("{error_code}, {}", 1)); //~^ expect_fun_call + + // Issue #15056 + let a = false; + Some(5).expect(if a { "a" } else { "b" }); + + let return_in_expect: Option = None; + return_in_expect.expect(if true { + "Error" + } else { + return; + }); } diff --git a/tests/ui/expect_fun_call.stderr b/tests/ui/expect_fun_call.stderr index 36713196cb92..0692ecb4862e 100644 --- a/tests/ui/expect_fun_call.stderr +++ b/tests/ui/expect_fun_call.stderr @@ -37,59 +37,65 @@ error: function call inside of `expect` LL | Some("foo").expect(format!("{} {}", 1, 2).as_ref()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{} {}", 1, 2))` -error: function call inside of `expect` - --> tests/ui/expect_fun_call.rs:93:21 - | -LL | Some("foo").expect(&get_string()); - | ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_string()) })` - -error: function call inside of `expect` - --> tests/ui/expect_fun_call.rs:95:21 - | -LL | Some("foo").expect(get_string().as_ref()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_string()) })` - error: function call inside of `expect` --> tests/ui/expect_fun_call.rs:97:21 | -LL | Some("foo").expect(get_string().as_str()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_string()) })` +LL | Some("foo").expect(&get_string()); + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_string()))` error: function call inside of `expect` - --> tests/ui/expect_fun_call.rs:100:21 + --> tests/ui/expect_fun_call.rs:99:21 + | +LL | Some("foo").expect(get_string().as_ref()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_string()))` + +error: function call inside of `expect` + --> tests/ui/expect_fun_call.rs:101:21 + | +LL | Some("foo").expect(get_string().as_str()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_string()))` + +error: function call inside of `expect` + --> tests/ui/expect_fun_call.rs:104:21 | LL | Some("foo").expect(get_static_str()); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_static_str()) })` + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_static_str()))` error: function call inside of `expect` - --> tests/ui/expect_fun_call.rs:102:21 + --> tests/ui/expect_fun_call.rs:106:21 | LL | Some("foo").expect(get_non_static_str(&0)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| { panic!("{}", get_non_static_str(&0).to_string()) })` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", get_non_static_str(&0)))` error: function call inside of `expect` - --> tests/ui/expect_fun_call.rs:107:16 + --> tests/ui/expect_fun_call.rs:109:21 + | +LL | Some("foo").expect(const_evaluable()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", const_evaluable()))` + +error: function call inside of `expect` + --> tests/ui/expect_fun_call.rs:120:16 | LL | Some(true).expect(&format!("key {}, {}", 1, 2)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("key {}, {}", 1, 2))` error: function call inside of `expect` - --> tests/ui/expect_fun_call.rs:114:17 + --> tests/ui/expect_fun_call.rs:127:17 | LL | opt_ref.expect(&format!("{:?}", opt_ref)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{:?}", opt_ref))` error: function call inside of `expect` - --> tests/ui/expect_fun_call.rs:119:20 + --> tests/ui/expect_fun_call.rs:132:20 | LL | format_capture.expect(&format!("{error_code}")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{error_code}"))` error: function call inside of `expect` - --> tests/ui/expect_fun_call.rs:123:30 + --> tests/ui/expect_fun_call.rs:136:30 | LL | format_capture_and_value.expect(&format!("{error_code}, {}", 1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{error_code}, {}", 1))` -error: aborting due to 15 previous errors +error: aborting due to 16 previous errors diff --git a/tests/ui/filter_map_bool_then.fixed b/tests/ui/filter_map_bool_then.fixed index b3e112f19eb4..d370b85a67ef 100644 --- a/tests/ui/filter_map_bool_then.fixed +++ b/tests/ui/filter_map_bool_then.fixed @@ -89,3 +89,24 @@ fn issue11503() { let _: Vec = bools.iter().enumerate().filter(|&(i, b)| ****b).map(|(i, b)| i).collect(); //~^ filter_map_bool_then } + +fn issue15047() { + #[derive(Clone, Copy)] + enum MyEnum { + A, + B, + C, + } + + macro_rules! foo { + ($e:expr) => { + $e + 1 + }; + } + + let x = 1; + let _ = [(MyEnum::A, "foo", 1i32)] + .iter() + .filter(|&(t, s, i)| matches!(t, MyEnum::A if s.starts_with("bar"))).map(|(t, s, i)| foo!(x)); + //~^ filter_map_bool_then +} diff --git a/tests/ui/filter_map_bool_then.rs b/tests/ui/filter_map_bool_then.rs index d996b3cb3c52..12295cc24823 100644 --- a/tests/ui/filter_map_bool_then.rs +++ b/tests/ui/filter_map_bool_then.rs @@ -89,3 +89,24 @@ fn issue11503() { let _: Vec = bools.iter().enumerate().filter_map(|(i, b)| b.then(|| i)).collect(); //~^ filter_map_bool_then } + +fn issue15047() { + #[derive(Clone, Copy)] + enum MyEnum { + A, + B, + C, + } + + macro_rules! foo { + ($e:expr) => { + $e + 1 + }; + } + + let x = 1; + let _ = [(MyEnum::A, "foo", 1i32)] + .iter() + .filter_map(|(t, s, i)| matches!(t, MyEnum::A if s.starts_with("bar")).then(|| foo!(x))); + //~^ filter_map_bool_then +} diff --git a/tests/ui/filter_map_bool_then.stderr b/tests/ui/filter_map_bool_then.stderr index aeb1baeb35e6..edf6c6559396 100644 --- a/tests/ui/filter_map_bool_then.stderr +++ b/tests/ui/filter_map_bool_then.stderr @@ -61,5 +61,11 @@ error: usage of `bool::then` in `filter_map` LL | let _: Vec = bools.iter().enumerate().filter_map(|(i, b)| b.then(|| i)).collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&(i, b)| ****b).map(|(i, b)| i)` -error: aborting due to 10 previous errors +error: usage of `bool::then` in `filter_map` + --> tests/ui/filter_map_bool_then.rs:110:10 + | +LL | .filter_map(|(t, s, i)| matches!(t, MyEnum::A if s.starts_with("bar")).then(|| foo!(x))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `filter` then `map` instead: `filter(|&(t, s, i)| matches!(t, MyEnum::A if s.starts_with("bar"))).map(|(t, s, i)| foo!(x))` + +error: aborting due to 11 previous errors diff --git a/tests/ui/flat_map_identity.fixed b/tests/ui/flat_map_identity.fixed index f62062326126..06a3eee9d84c 100644 --- a/tests/ui/flat_map_identity.fixed +++ b/tests/ui/flat_map_identity.fixed @@ -16,3 +16,16 @@ fn main() { let _ = iterator.flatten(); //~^ flat_map_identity } + +fn issue15198() { + let x = [[1, 2], [3, 4]]; + // don't lint: this is an `Iterator` + // match ergonomics makes the binding patterns into references + // so that its type changes to `Iterator` + let _ = x.iter().flat_map(|[x, y]| [x, y]); + let _ = x.iter().flat_map(|x| [x[0]]); + + // no match ergonomics for `[i32, i32]` + let _ = x.iter().copied().flatten(); + //~^ flat_map_identity +} diff --git a/tests/ui/flat_map_identity.rs b/tests/ui/flat_map_identity.rs index c59e749474ee..1cab7d559d8f 100644 --- a/tests/ui/flat_map_identity.rs +++ b/tests/ui/flat_map_identity.rs @@ -16,3 +16,16 @@ fn main() { let _ = iterator.flat_map(|x| return x); //~^ flat_map_identity } + +fn issue15198() { + let x = [[1, 2], [3, 4]]; + // don't lint: this is an `Iterator` + // match ergonomics makes the binding patterns into references + // so that its type changes to `Iterator` + let _ = x.iter().flat_map(|[x, y]| [x, y]); + let _ = x.iter().flat_map(|x| [x[0]]); + + // no match ergonomics for `[i32, i32]` + let _ = x.iter().copied().flat_map(|[x, y]| [x, y]); + //~^ flat_map_identity +} diff --git a/tests/ui/flat_map_identity.stderr b/tests/ui/flat_map_identity.stderr index 75137f5d9e57..18c863bf96d5 100644 --- a/tests/ui/flat_map_identity.stderr +++ b/tests/ui/flat_map_identity.stderr @@ -19,5 +19,11 @@ error: use of `flat_map` with an identity function LL | let _ = iterator.flat_map(|x| return x); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `flatten()` -error: aborting due to 3 previous errors +error: use of `flat_map` with an identity function + --> tests/ui/flat_map_identity.rs:29:31 + | +LL | let _ = x.iter().copied().flat_map(|[x, y]| [x, y]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `flatten()` + +error: aborting due to 4 previous errors diff --git a/tests/ui/if_then_some_else_none.fixed b/tests/ui/if_then_some_else_none.fixed index f774608712d1..d14a805b6667 100644 --- a/tests/ui/if_then_some_else_none.fixed +++ b/tests/ui/if_then_some_else_none.fixed @@ -122,3 +122,46 @@ const fn issue12103(x: u32) -> Option { // Should not issue an error in `const` context if x > 42 { Some(150) } else { None } } + +mod issue15257 { + struct Range { + start: u8, + end: u8, + } + + fn can_be_safely_rewrite(rs: &[&Range]) -> Option> { + (rs.len() == 1 && rs[0].start == rs[0].end).then(|| vec![rs[0].start]) + } + + fn reborrow_as_ptr(i: *mut i32) -> Option<*const i32> { + let modulo = unsafe { *i % 2 }; + (modulo == 0).then_some(i) + } + + fn reborrow_as_fn_ptr(i: i32) { + fn do_something(fn_: Option) { + todo!() + } + + fn item_fn(i: i32) { + todo!() + } + + do_something((i % 2 == 0).then_some(item_fn)); + } + + fn reborrow_as_fn_unsafe(i: i32) { + fn do_something(fn_: Option) { + todo!() + } + + fn item_fn(i: i32) { + todo!() + } + + do_something((i % 2 == 0).then_some(item_fn)); + + let closure_fn = |i: i32| {}; + do_something((i % 2 == 0).then_some(closure_fn)); + } +} diff --git a/tests/ui/if_then_some_else_none.rs b/tests/ui/if_then_some_else_none.rs index 8b8ff0a6ea00..bb0072f31573 100644 --- a/tests/ui/if_then_some_else_none.rs +++ b/tests/ui/if_then_some_else_none.rs @@ -143,3 +143,71 @@ const fn issue12103(x: u32) -> Option { // Should not issue an error in `const` context if x > 42 { Some(150) } else { None } } + +mod issue15257 { + struct Range { + start: u8, + end: u8, + } + + fn can_be_safely_rewrite(rs: &[&Range]) -> Option> { + if rs.len() == 1 && rs[0].start == rs[0].end { + //~^ if_then_some_else_none + Some(vec![rs[0].start]) + } else { + None + } + } + + fn reborrow_as_ptr(i: *mut i32) -> Option<*const i32> { + let modulo = unsafe { *i % 2 }; + if modulo == 0 { + //~^ if_then_some_else_none + Some(i) + } else { + None + } + } + + fn reborrow_as_fn_ptr(i: i32) { + fn do_something(fn_: Option) { + todo!() + } + + fn item_fn(i: i32) { + todo!() + } + + do_something(if i % 2 == 0 { + //~^ if_then_some_else_none + Some(item_fn) + } else { + None + }); + } + + fn reborrow_as_fn_unsafe(i: i32) { + fn do_something(fn_: Option) { + todo!() + } + + fn item_fn(i: i32) { + todo!() + } + + do_something(if i % 2 == 0 { + //~^ if_then_some_else_none + Some(item_fn) + } else { + None + }); + + let closure_fn = |i: i32| {}; + do_something(if i % 2 == 0 { + //~^ if_then_some_else_none + Some(closure_fn) + } else { + None + }); + } +} diff --git a/tests/ui/if_then_some_else_none.stderr b/tests/ui/if_then_some_else_none.stderr index 71285574ef24..c2e624a0a73b 100644 --- a/tests/ui/if_then_some_else_none.stderr +++ b/tests/ui/if_then_some_else_none.stderr @@ -58,5 +58,63 @@ error: this could be simplified with `bool::then` LL | if s == "1" { Some(true) } else { None } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(s == "1").then(|| true)` -error: aborting due to 6 previous errors +error: this could be simplified with `bool::then` + --> tests/ui/if_then_some_else_none.rs:154:9 + | +LL | / if rs.len() == 1 && rs[0].start == rs[0].end { +LL | | +LL | | Some(vec![rs[0].start]) +LL | | } else { +LL | | None +LL | | } + | |_________^ help: try: `(rs.len() == 1 && rs[0].start == rs[0].end).then(|| vec![rs[0].start])` + +error: this could be simplified with `bool::then_some` + --> tests/ui/if_then_some_else_none.rs:164:9 + | +LL | / if modulo == 0 { +LL | | +LL | | Some(i) +LL | | } else { +LL | | None +LL | | } + | |_________^ help: try: `(modulo == 0).then_some(i)` + +error: this could be simplified with `bool::then_some` + --> tests/ui/if_then_some_else_none.rs:181:22 + | +LL | do_something(if i % 2 == 0 { + | ______________________^ +LL | | +LL | | Some(item_fn) +LL | | } else { +LL | | None +LL | | }); + | |_________^ help: try: `(i % 2 == 0).then_some(item_fn)` + +error: this could be simplified with `bool::then_some` + --> tests/ui/if_then_some_else_none.rs:198:22 + | +LL | do_something(if i % 2 == 0 { + | ______________________^ +LL | | +LL | | Some(item_fn) +LL | | } else { +LL | | None +LL | | }); + | |_________^ help: try: `(i % 2 == 0).then_some(item_fn)` + +error: this could be simplified with `bool::then_some` + --> tests/ui/if_then_some_else_none.rs:206:22 + | +LL | do_something(if i % 2 == 0 { + | ______________________^ +LL | | +LL | | Some(closure_fn) +LL | | } else { +LL | | None +LL | | }); + | |_________^ help: try: `(i % 2 == 0).then_some(closure_fn)` + +error: aborting due to 11 previous errors diff --git a/tests/ui/if_then_some_else_none_unfixable.rs b/tests/ui/if_then_some_else_none_unfixable.rs new file mode 100644 index 000000000000..be04299a6ab1 --- /dev/null +++ b/tests/ui/if_then_some_else_none_unfixable.rs @@ -0,0 +1,35 @@ +#![warn(clippy::if_then_some_else_none)] +#![allow(clippy::manual_is_multiple_of)] + +mod issue15257 { + use std::pin::Pin; + + #[derive(Default)] + pub struct Foo {} + pub trait Bar {} + impl Bar for Foo {} + + fn pointer_unsized_coercion(i: u32) -> Option> { + if i % 2 == 0 { + //~^ if_then_some_else_none + Some(Box::new(Foo::default())) + } else { + None + } + } + + fn reborrow_as_pin(i: Pin<&mut i32>) { + use std::ops::Rem; + + fn do_something(i: Option<&i32>) { + todo!() + } + + do_something(if i.rem(2) == 0 { + //~^ if_then_some_else_none + Some(&i) + } else { + None + }); + } +} diff --git a/tests/ui/if_then_some_else_none_unfixable.stderr b/tests/ui/if_then_some_else_none_unfixable.stderr new file mode 100644 index 000000000000..f77ce7910e76 --- /dev/null +++ b/tests/ui/if_then_some_else_none_unfixable.stderr @@ -0,0 +1,28 @@ +error: this could be simplified with `bool::then` + --> tests/ui/if_then_some_else_none_unfixable.rs:13:9 + | +LL | / if i % 2 == 0 { +LL | | +LL | | Some(Box::new(Foo::default())) +LL | | } else { +LL | | None +LL | | } + | |_________^ + | + = note: `-D clippy::if-then-some-else-none` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::if_then_some_else_none)]` + +error: this could be simplified with `bool::then` + --> tests/ui/if_then_some_else_none_unfixable.rs:28:22 + | +LL | do_something(if i.rem(2) == 0 { + | ______________________^ +LL | | +LL | | Some(&i) +LL | | } else { +LL | | None +LL | | }); + | |_________^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/incompatible_msrv.rs b/tests/ui/incompatible_msrv.rs index 99101b2bb8f2..f7f21e1850d0 100644 --- a/tests/ui/incompatible_msrv.rs +++ b/tests/ui/incompatible_msrv.rs @@ -1,8 +1,10 @@ #![warn(clippy::incompatible_msrv)] #![feature(custom_inner_attributes)] -#![feature(panic_internals)] +#![allow(stable_features)] +#![feature(strict_provenance)] // For use in test #![clippy::msrv = "1.3.0"] +use std::cell::Cell; use std::collections::HashMap; use std::collections::hash_map::Entry; use std::future::Future; @@ -13,6 +15,8 @@ fn foo() { let mut map: HashMap<&str, u32> = HashMap::new(); assert_eq!(map.entry("poneyland").key(), &"poneyland"); //~^ incompatible_msrv + //~| NOTE: `-D clippy::incompatible-msrv` implied by `-D warnings` + //~| HELP: to override `-D warnings` add `#[allow(clippy::incompatible_msrv)]` if let Entry::Vacant(v) = map.entry("poneyland") { v.into_key(); @@ -23,6 +27,18 @@ fn foo() { //~^ incompatible_msrv } +#[clippy::msrv = "1.2.0"] +static NO_BODY_BAD_MSRV: Option = None; +//~^ incompatible_msrv + +static NO_BODY_GOOD_MSRV: Option = None; + +#[clippy::msrv = "1.2.0"] +fn bad_type_msrv() { + let _: Option = None; + //~^ incompatible_msrv +} + #[test] fn test() { sleep(Duration::new(1, 0)); @@ -43,21 +59,22 @@ fn core_special_treatment(p: bool) { // But still lint code calling `core` functions directly if p { - core::panicking::panic("foo"); - //~^ ERROR: is `1.3.0` but this item is stable since `1.6.0` + let _ = core::iter::once_with(|| 0); + //~^ incompatible_msrv } // Lint code calling `core` from non-`core` macros macro_rules! my_panic { ($msg:expr) => { - core::panicking::panic($msg) - }; //~^ ERROR: is `1.3.0` but this item is stable since `1.6.0` + let _ = core::iter::once_with(|| $msg); + //~^ incompatible_msrv + }; } my_panic!("foo"); // Lint even when the macro comes from `core` and calls `core` functions - assert!(core::panicking::panic("out of luck")); - //~^ ERROR: is `1.3.0` but this item is stable since `1.6.0` + assert!(core::iter::once_with(|| 0).next().is_some()); + //~^ incompatible_msrv } #[clippy::msrv = "1.26.0"] @@ -70,7 +87,85 @@ fn lang_items() { #[clippy::msrv = "1.80.0"] fn issue14212() { let _ = std::iter::repeat_n((), 5); - //~^ ERROR: is `1.80.0` but this item is stable since `1.82.0` + //~^ incompatible_msrv +} + +#[clippy::msrv = "1.0.0"] +fn cstr_and_cstring_ok() { + let _: Option<&'static std::ffi::CStr> = None; + let _: Option = None; +} + +fn local_msrv_change_suggestion() { + let _ = std::iter::repeat_n((), 5); + //~^ incompatible_msrv + + #[cfg(any(test, not(test)))] + { + let _ = std::iter::repeat_n((), 5); + //~^ incompatible_msrv + //~| NOTE: you may want to conditionally increase the MSRV + + // Emit the additional note only once + let _ = std::iter::repeat_n((), 5); + //~^ incompatible_msrv + } +} + +#[clippy::msrv = "1.78.0"] +fn feature_enable_14425(ptr: *const u8) -> usize { + // Do not warn, because it is enabled through a feature even though + // it is stabilized only since Rust 1.84.0. + let r = ptr.addr(); + + // Warn about this which has been introduced in the same Rust version + // but is not allowed through a feature. + r.isqrt() + //~^ incompatible_msrv +} + +fn non_fn_items() { + let _ = std::io::ErrorKind::CrossesDevices; + //~^ incompatible_msrv +} + +#[clippy::msrv = "1.87.0"] +fn msrv_non_ok_in_const() { + { + let c = Cell::new(42); + _ = c.get(); + } + const { + let c = Cell::new(42); + _ = c.get(); + //~^ incompatible_msrv + } +} + +#[clippy::msrv = "1.88.0"] +fn msrv_ok_in_const() { + { + let c = Cell::new(42); + _ = c.get(); + } + const { + let c = Cell::new(42); + _ = c.get(); + } +} + +#[clippy::msrv = "1.86.0"] +fn enum_variant_not_ok() { + let _ = std::io::ErrorKind::InvalidFilename; + //~^ incompatible_msrv + let _ = const { std::io::ErrorKind::InvalidFilename }; + //~^ incompatible_msrv +} + +#[clippy::msrv = "1.87.0"] +fn enum_variant_ok() { + let _ = std::io::ErrorKind::InvalidFilename; + let _ = const { std::io::ErrorKind::InvalidFilename }; } fn main() {} diff --git a/tests/ui/incompatible_msrv.stderr b/tests/ui/incompatible_msrv.stderr index 5ea2bb9cc58b..e42360d296f5 100644 --- a/tests/ui/incompatible_msrv.stderr +++ b/tests/ui/incompatible_msrv.stderr @@ -1,5 +1,5 @@ error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.10.0` - --> tests/ui/incompatible_msrv.rs:14:39 + --> tests/ui/incompatible_msrv.rs:16:39 | LL | assert_eq!(map.entry("poneyland").key(), &"poneyland"); | ^^^^^ @@ -8,45 +8,107 @@ LL | assert_eq!(map.entry("poneyland").key(), &"poneyland"); = help: to override `-D warnings` add `#[allow(clippy::incompatible_msrv)]` error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.12.0` - --> tests/ui/incompatible_msrv.rs:18:11 + --> tests/ui/incompatible_msrv.rs:22:11 | LL | v.into_key(); | ^^^^^^^^^^ error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.4.0` - --> tests/ui/incompatible_msrv.rs:22:5 + --> tests/ui/incompatible_msrv.rs:26:5 | LL | sleep(Duration::new(1, 0)); | ^^^^^ -error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.6.0` - --> tests/ui/incompatible_msrv.rs:46:9 +error: current MSRV (Minimum Supported Rust Version) is `1.2.0` but this item is stable since `1.3.0` + --> tests/ui/incompatible_msrv.rs:31:33 | -LL | core::panicking::panic("foo"); - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | static NO_BODY_BAD_MSRV: Option = None; + | ^^^^^^^^ -error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.6.0` - --> tests/ui/incompatible_msrv.rs:53:13 +error: current MSRV (Minimum Supported Rust Version) is `1.2.0` but this item is stable since `1.3.0` + --> tests/ui/incompatible_msrv.rs:38:19 | -LL | core::panicking::panic($msg) - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | let _: Option = None; + | ^^^^^^^^ + +error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.43.0` + --> tests/ui/incompatible_msrv.rs:62:17 + | +LL | let _ = core::iter::once_with(|| 0); + | ^^^^^^^^^^^^^^^^^^^^^ + +error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.43.0` + --> tests/ui/incompatible_msrv.rs:69:21 + | +LL | let _ = core::iter::once_with(|| $msg); + | ^^^^^^^^^^^^^^^^^^^^^ ... LL | my_panic!("foo"); | ---------------- in this macro invocation | = note: this error originates in the macro `my_panic` (in Nightly builds, run with -Z macro-backtrace for more info) -error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.6.0` - --> tests/ui/incompatible_msrv.rs:59:13 +error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.43.0` + --> tests/ui/incompatible_msrv.rs:76:13 | -LL | assert!(core::panicking::panic("out of luck")); - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | assert!(core::iter::once_with(|| 0).next().is_some()); + | ^^^^^^^^^^^^^^^^^^^^^ error: current MSRV (Minimum Supported Rust Version) is `1.80.0` but this item is stable since `1.82.0` - --> tests/ui/incompatible_msrv.rs:72:13 + --> tests/ui/incompatible_msrv.rs:89:13 | LL | let _ = std::iter::repeat_n((), 5); | ^^^^^^^^^^^^^^^^^^^ -error: aborting due to 7 previous errors +error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.82.0` + --> tests/ui/incompatible_msrv.rs:100:13 + | +LL | let _ = std::iter::repeat_n((), 5); + | ^^^^^^^^^^^^^^^^^^^ + +error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.82.0` + --> tests/ui/incompatible_msrv.rs:105:17 + | +LL | let _ = std::iter::repeat_n((), 5); + | ^^^^^^^^^^^^^^^^^^^ + | + = note: you may want to conditionally increase the MSRV considered by Clippy using the `clippy::msrv` attribute + +error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.82.0` + --> tests/ui/incompatible_msrv.rs:110:17 + | +LL | let _ = std::iter::repeat_n((), 5); + | ^^^^^^^^^^^^^^^^^^^ + +error: current MSRV (Minimum Supported Rust Version) is `1.78.0` but this item is stable since `1.84.0` + --> tests/ui/incompatible_msrv.rs:123:7 + | +LL | r.isqrt() + | ^^^^^^^ + +error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.85.0` + --> tests/ui/incompatible_msrv.rs:128:13 + | +LL | let _ = std::io::ErrorKind::CrossesDevices; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: current MSRV (Minimum Supported Rust Version) is `1.87.0` but this item is stable in a `const` context since `1.88.0` + --> tests/ui/incompatible_msrv.rs:140:15 + | +LL | _ = c.get(); + | ^^^^^ + +error: current MSRV (Minimum Supported Rust Version) is `1.86.0` but this item is stable since `1.87.0` + --> tests/ui/incompatible_msrv.rs:159:13 + | +LL | let _ = std::io::ErrorKind::InvalidFilename; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: current MSRV (Minimum Supported Rust Version) is `1.86.0` but this item is stable since `1.87.0` + --> tests/ui/incompatible_msrv.rs:161:21 + | +LL | let _ = const { std::io::ErrorKind::InvalidFilename }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 17 previous errors diff --git a/tests/ui/large_enum_variant.32bit.stderr b/tests/ui/large_enum_variant.32bit.stderr index 80ca5daa1d57..ac1ed27a6b3b 100644 --- a/tests/ui/large_enum_variant.32bit.stderr +++ b/tests/ui/large_enum_variant.32bit.stderr @@ -12,7 +12,7 @@ LL | | } | = note: `-D clippy::large-enum-variant` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::large_enum_variant)]` -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B([i32; 8000]), LL + B(Box<[i32; 8000]>), @@ -30,7 +30,7 @@ LL | | ContainingLargeEnum(LargeEnum), LL | | } | |_^ the entire enum is at least 32004 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - ContainingLargeEnum(LargeEnum), LL + ContainingLargeEnum(Box), @@ -49,7 +49,7 @@ LL | | StructLikeLittle { x: i32, y: i32 }, LL | | } | |_^ the entire enum is at least 70008 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - ContainingMoreThanOneField(i32, [i32; 8000], [i32; 9500]), LL + ContainingMoreThanOneField(i32, Box<[i32; 8000]>, Box<[i32; 9500]>), @@ -67,7 +67,7 @@ LL | | StructLikeLarge { x: [i32; 8000], y: i32 }, LL | | } | |_^ the entire enum is at least 32008 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - StructLikeLarge { x: [i32; 8000], y: i32 }, LL + StructLikeLarge { x: Box<[i32; 8000]>, y: i32 }, @@ -85,7 +85,7 @@ LL | | StructLikeLarge2 { x: [i32; 8000] }, LL | | } | |_^ the entire enum is at least 32004 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - StructLikeLarge2 { x: [i32; 8000] }, LL + StructLikeLarge2 { x: Box<[i32; 8000]> }, @@ -104,7 +104,7 @@ LL | | C([u8; 200]), LL | | } | |_^ the entire enum is at least 1256 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B([u8; 1255]), LL + B(Box<[u8; 1255]>), @@ -122,7 +122,7 @@ LL | | ContainingMoreThanOneField([i32; 8000], [i32; 2], [i32; 9500], [i32; LL | | } | |_^ the entire enum is at least 70132 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - ContainingMoreThanOneField([i32; 8000], [i32; 2], [i32; 9500], [i32; 30]), LL + ContainingMoreThanOneField(Box<[i32; 8000]>, [i32; 2], Box<[i32; 9500]>, [i32; 30]), @@ -140,7 +140,7 @@ LL | | B(Struct2), LL | | } | |_^ the entire enum is at least 32004 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B(Struct2), LL + B(Box), @@ -158,7 +158,7 @@ LL | | B(Struct2), LL | | } | |_^ the entire enum is at least 32000 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B(Struct2), LL + B(Box), @@ -176,7 +176,7 @@ LL | | B(Struct2), LL | | } | |_^ the entire enum is at least 32000 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B(Struct2), LL + B(Box), @@ -199,7 +199,7 @@ note: boxing a variant would require the type no longer be `Copy` | LL | enum CopyableLargeEnum { | ^^^^^^^^^^^^^^^^^ -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum --> tests/ui/large_enum_variant.rs:118:5 | LL | B([u64; 8000]), @@ -222,7 +222,7 @@ note: boxing a variant would require the type no longer be `Copy` | LL | enum ManuallyCopyLargeEnum { | ^^^^^^^^^^^^^^^^^^^^^ -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum --> tests/ui/large_enum_variant.rs:124:5 | LL | B([u64; 8000]), @@ -245,7 +245,7 @@ note: boxing a variant would require the type no longer be `Copy` | LL | enum SomeGenericPossiblyCopyEnum { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum --> tests/ui/large_enum_variant.rs:138:5 | LL | B([u64; 4000]), @@ -263,7 +263,7 @@ LL | | Large((T, [u8; 512])), LL | | } | |_^ the entire enum is at least 512 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Large((T, [u8; 512])), LL + Large(Box<(T, [u8; 512])>), @@ -281,7 +281,7 @@ LL | | Small(u8), LL | | } | |_^ the entire enum is at least 516 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Large([Foo; 64]), LL + Large(Box<[Foo; 64]>), @@ -299,7 +299,7 @@ LL | | Error(PossiblyLargeEnumWithConst<256>), LL | | } | |_^ the entire enum is at least 514 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Error(PossiblyLargeEnumWithConst<256>), LL + Error(Box>), @@ -317,7 +317,7 @@ LL | | Recursive(Box), LL | | } | |_^ the entire enum is at least 516 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Large([u64; 64]), LL + Large(Box<[u64; 64]>), @@ -335,7 +335,7 @@ LL | | Error(WithRecursionAndGenerics), LL | | } | |_^ the entire enum is at least 516 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Error(WithRecursionAndGenerics), LL + Error(Box>), diff --git a/tests/ui/large_enum_variant.64bit.stderr b/tests/ui/large_enum_variant.64bit.stderr index 559bdf2a2f50..d8199f9090f6 100644 --- a/tests/ui/large_enum_variant.64bit.stderr +++ b/tests/ui/large_enum_variant.64bit.stderr @@ -12,7 +12,7 @@ LL | | } | = note: `-D clippy::large-enum-variant` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::large_enum_variant)]` -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B([i32; 8000]), LL + B(Box<[i32; 8000]>), @@ -30,7 +30,7 @@ LL | | ContainingLargeEnum(LargeEnum), LL | | } | |_^ the entire enum is at least 32004 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - ContainingLargeEnum(LargeEnum), LL + ContainingLargeEnum(Box), @@ -49,7 +49,7 @@ LL | | StructLikeLittle { x: i32, y: i32 }, LL | | } | |_^ the entire enum is at least 70008 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - ContainingMoreThanOneField(i32, [i32; 8000], [i32; 9500]), LL + ContainingMoreThanOneField(i32, Box<[i32; 8000]>, Box<[i32; 9500]>), @@ -67,7 +67,7 @@ LL | | StructLikeLarge { x: [i32; 8000], y: i32 }, LL | | } | |_^ the entire enum is at least 32008 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - StructLikeLarge { x: [i32; 8000], y: i32 }, LL + StructLikeLarge { x: Box<[i32; 8000]>, y: i32 }, @@ -85,7 +85,7 @@ LL | | StructLikeLarge2 { x: [i32; 8000] }, LL | | } | |_^ the entire enum is at least 32004 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - StructLikeLarge2 { x: [i32; 8000] }, LL + StructLikeLarge2 { x: Box<[i32; 8000]> }, @@ -104,7 +104,7 @@ LL | | C([u8; 200]), LL | | } | |_^ the entire enum is at least 1256 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B([u8; 1255]), LL + B(Box<[u8; 1255]>), @@ -122,7 +122,7 @@ LL | | ContainingMoreThanOneField([i32; 8000], [i32; 2], [i32; 9500], [i32; LL | | } | |_^ the entire enum is at least 70132 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - ContainingMoreThanOneField([i32; 8000], [i32; 2], [i32; 9500], [i32; 30]), LL + ContainingMoreThanOneField(Box<[i32; 8000]>, [i32; 2], Box<[i32; 9500]>, [i32; 30]), @@ -140,7 +140,7 @@ LL | | B(Struct2), LL | | } | |_^ the entire enum is at least 32004 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B(Struct2), LL + B(Box), @@ -158,7 +158,7 @@ LL | | B(Struct2), LL | | } | |_^ the entire enum is at least 32000 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B(Struct2), LL + B(Box), @@ -176,7 +176,7 @@ LL | | B(Struct2), LL | | } | |_^ the entire enum is at least 32000 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - B(Struct2), LL + B(Box), @@ -199,7 +199,7 @@ note: boxing a variant would require the type no longer be `Copy` | LL | enum CopyableLargeEnum { | ^^^^^^^^^^^^^^^^^ -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum --> tests/ui/large_enum_variant.rs:118:5 | LL | B([u64; 8000]), @@ -222,7 +222,7 @@ note: boxing a variant would require the type no longer be `Copy` | LL | enum ManuallyCopyLargeEnum { | ^^^^^^^^^^^^^^^^^^^^^ -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum --> tests/ui/large_enum_variant.rs:124:5 | LL | B([u64; 8000]), @@ -245,7 +245,7 @@ note: boxing a variant would require the type no longer be `Copy` | LL | enum SomeGenericPossiblyCopyEnum { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum --> tests/ui/large_enum_variant.rs:138:5 | LL | B([u64; 4000]), @@ -263,7 +263,7 @@ LL | | Large((T, [u8; 512])), LL | | } | |_^ the entire enum is at least 512 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Large((T, [u8; 512])), LL + Large(Box<(T, [u8; 512])>), @@ -281,7 +281,7 @@ LL | | Small(u8), LL | | } | |_^ the entire enum is at least 520 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Large([Foo; 64]), LL + Large(Box<[Foo; 64]>), @@ -299,7 +299,7 @@ LL | | Error(PossiblyLargeEnumWithConst<256>), LL | | } | |_^ the entire enum is at least 514 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Error(PossiblyLargeEnumWithConst<256>), LL + Error(Box>), @@ -317,7 +317,7 @@ LL | | Recursive(Box), LL | | } | |_^ the entire enum is at least 520 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Large([u64; 64]), LL + Large(Box<[u64; 64]>), @@ -335,7 +335,7 @@ LL | | Error(WithRecursionAndGenerics), LL | | } | |_^ the entire enum is at least 520 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - Error(WithRecursionAndGenerics), LL + Error(Box>), @@ -353,7 +353,7 @@ LL | | _SmallBoi(u8), LL | | } | |_____^ the entire enum is at least 296 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - BigBoi(PublishWithBytes), LL + BigBoi(Box), @@ -371,7 +371,7 @@ LL | | _SmallBoi(u8), LL | | } | |_____^ the entire enum is at least 224 bytes | -help: consider boxing the large fields to reduce the total size of the enum +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum | LL - BigBoi(PublishWithVec), LL + BigBoi(Box), diff --git a/tests/ui/large_enum_variant_no_std.rs b/tests/ui/large_enum_variant_no_std.rs new file mode 100644 index 000000000000..ff0213155b6c --- /dev/null +++ b/tests/ui/large_enum_variant_no_std.rs @@ -0,0 +1,8 @@ +#![no_std] +#![warn(clippy::large_enum_variant)] + +enum Myenum { + //~^ ERROR: large size difference between variants + Small(u8), + Large([u8; 1024]), +} diff --git a/tests/ui/large_enum_variant_no_std.stderr b/tests/ui/large_enum_variant_no_std.stderr new file mode 100644 index 000000000000..4f32e3e4835f --- /dev/null +++ b/tests/ui/large_enum_variant_no_std.stderr @@ -0,0 +1,22 @@ +error: large size difference between variants + --> tests/ui/large_enum_variant_no_std.rs:4:1 + | +LL | / enum Myenum { +LL | | +LL | | Small(u8), + | | --------- the second-largest variant contains at least 1 bytes +LL | | Large([u8; 1024]), + | | ----------------- the largest variant contains at least 1024 bytes +LL | | } + | |_^ the entire enum is at least 1025 bytes + | +help: consider boxing the large fields or introducing indirection in some other way to reduce the total size of the enum + --> tests/ui/large_enum_variant_no_std.rs:7:5 + | +LL | Large([u8; 1024]), + | ^^^^^^^^^^^^^^^^^ + = note: `-D clippy::large-enum-variant` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::large_enum_variant)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/legacy_numeric_constants.fixed b/tests/ui/legacy_numeric_constants.fixed index 30bb549a9d65..d90e7bec027c 100644 --- a/tests/ui/legacy_numeric_constants.fixed +++ b/tests/ui/legacy_numeric_constants.fixed @@ -79,9 +79,31 @@ fn main() { f64::consts::E; b!(); + std::primitive::i32::MAX; + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead [(0, "", i128::MAX)]; //~^ ERROR: usage of a legacy numeric constant //~| HELP: use the associated constant instead + i32::MAX; + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead + assert_eq!(0, -i32::MAX); + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead + i128::MAX; + //~^ ERROR: usage of a legacy numeric constant + //~| HELP: use the associated constant instead + u32::MAX; + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead + i32::MAX; + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead + type Ω = i32; + Ω::MAX; + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead } #[warn(clippy::legacy_numeric_constants)] diff --git a/tests/ui/legacy_numeric_constants.rs b/tests/ui/legacy_numeric_constants.rs index d3878199055f..4a2ef3f70c21 100644 --- a/tests/ui/legacy_numeric_constants.rs +++ b/tests/ui/legacy_numeric_constants.rs @@ -79,9 +79,31 @@ fn main() { f64::consts::E; b!(); + ::max_value(); + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead [(0, "", std::i128::MAX)]; //~^ ERROR: usage of a legacy numeric constant //~| HELP: use the associated constant instead + (i32::max_value()); + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead + assert_eq!(0, -(i32::max_value())); + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead + (std::i128::MAX); + //~^ ERROR: usage of a legacy numeric constant + //~| HELP: use the associated constant instead + (::max_value()); + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead + ((i32::max_value)()); + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead + type Ω = i32; + Ω::max_value(); + //~^ ERROR: usage of a legacy numeric method + //~| HELP: use the associated constant instead } #[warn(clippy::legacy_numeric_constants)] diff --git a/tests/ui/legacy_numeric_constants.stderr b/tests/ui/legacy_numeric_constants.stderr index 4d69b8165a34..0b4f32e0abc3 100644 --- a/tests/ui/legacy_numeric_constants.stderr +++ b/tests/ui/legacy_numeric_constants.stderr @@ -72,10 +72,10 @@ LL | u32::MAX; | +++++ error: usage of a legacy numeric method - --> tests/ui/legacy_numeric_constants.rs:50:10 + --> tests/ui/legacy_numeric_constants.rs:50:5 | LL | i32::max_value(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^ | help: use the associated constant instead | @@ -84,10 +84,10 @@ LL + i32::MAX; | error: usage of a legacy numeric method - --> tests/ui/legacy_numeric_constants.rs:53:9 + --> tests/ui/legacy_numeric_constants.rs:53:5 | LL | u8::max_value(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ | help: use the associated constant instead | @@ -96,10 +96,10 @@ LL + u8::MAX; | error: usage of a legacy numeric method - --> tests/ui/legacy_numeric_constants.rs:56:9 + --> tests/ui/legacy_numeric_constants.rs:56:5 | LL | u8::min_value(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ | help: use the associated constant instead | @@ -120,10 +120,10 @@ LL + u8::MIN; | error: usage of a legacy numeric method - --> tests/ui/legacy_numeric_constants.rs:62:27 + --> tests/ui/legacy_numeric_constants.rs:62:5 | LL | ::std::primitive::u8::min_value(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: use the associated constant instead | @@ -132,10 +132,10 @@ LL + ::std::primitive::u8::MIN; | error: usage of a legacy numeric method - --> tests/ui/legacy_numeric_constants.rs:65:26 + --> tests/ui/legacy_numeric_constants.rs:65:5 | LL | std::primitive::i32::max_value(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: use the associated constant instead | @@ -171,8 +171,20 @@ LL - let x = std::u64::MAX; LL + let x = u64::MAX; | +error: usage of a legacy numeric method + --> tests/ui/legacy_numeric_constants.rs:82:5 + | +LL | ::max_value(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use the associated constant instead + | +LL - ::max_value(); +LL + std::primitive::i32::MAX; + | + error: usage of a legacy numeric constant - --> tests/ui/legacy_numeric_constants.rs:82:14 + --> tests/ui/legacy_numeric_constants.rs:85:14 | LL | [(0, "", std::i128::MAX)]; | ^^^^^^^^^^^^^^ @@ -183,8 +195,80 @@ LL - [(0, "", std::i128::MAX)]; LL + [(0, "", i128::MAX)]; | +error: usage of a legacy numeric method + --> tests/ui/legacy_numeric_constants.rs:88:5 + | +LL | (i32::max_value()); + | ^^^^^^^^^^^^^^^^^^ + | +help: use the associated constant instead + | +LL - (i32::max_value()); +LL + i32::MAX; + | + +error: usage of a legacy numeric method + --> tests/ui/legacy_numeric_constants.rs:91:20 + | +LL | assert_eq!(0, -(i32::max_value())); + | ^^^^^^^^^^^^^^^^^^ + | +help: use the associated constant instead + | +LL - assert_eq!(0, -(i32::max_value())); +LL + assert_eq!(0, -i32::MAX); + | + error: usage of a legacy numeric constant - --> tests/ui/legacy_numeric_constants.rs:116:5 + --> tests/ui/legacy_numeric_constants.rs:94:5 + | +LL | (std::i128::MAX); + | ^^^^^^^^^^^^^^^^ + | +help: use the associated constant instead + | +LL - (std::i128::MAX); +LL + i128::MAX; + | + +error: usage of a legacy numeric method + --> tests/ui/legacy_numeric_constants.rs:97:5 + | +LL | (::max_value()); + | ^^^^^^^^^^^^^^^^^^^^ + | +help: use the associated constant instead + | +LL - (::max_value()); +LL + u32::MAX; + | + +error: usage of a legacy numeric method + --> tests/ui/legacy_numeric_constants.rs:100:5 + | +LL | ((i32::max_value)()); + | ^^^^^^^^^^^^^^^^^^^^ + | +help: use the associated constant instead + | +LL - ((i32::max_value)()); +LL + i32::MAX; + | + +error: usage of a legacy numeric method + --> tests/ui/legacy_numeric_constants.rs:104:5 + | +LL | Ω::max_value(); + | ^^^^^^^^^^^^^^ + | +help: use the associated constant instead + | +LL - Ω::max_value(); +LL + Ω::MAX; + | + +error: usage of a legacy numeric constant + --> tests/ui/legacy_numeric_constants.rs:138:5 | LL | std::u32::MAX; | ^^^^^^^^^^^^^ @@ -195,5 +279,5 @@ LL - std::u32::MAX; LL + u32::MAX; | -error: aborting due to 16 previous errors +error: aborting due to 23 previous errors diff --git a/tests/ui/manual_abs_diff.fixed b/tests/ui/manual_abs_diff.fixed index f1b1278ea6d2..2766942140ce 100644 --- a/tests/ui/manual_abs_diff.fixed +++ b/tests/ui/manual_abs_diff.fixed @@ -104,3 +104,7 @@ fn non_primitive_ty() { let (a, b) = (S(10), S(20)); let _ = if a < b { b - a } else { a - b }; } + +fn issue15254(a: &usize, b: &usize) -> usize { + b.abs_diff(*a) +} diff --git a/tests/ui/manual_abs_diff.rs b/tests/ui/manual_abs_diff.rs index 60ef819c12d3..2c408f2be375 100644 --- a/tests/ui/manual_abs_diff.rs +++ b/tests/ui/manual_abs_diff.rs @@ -114,3 +114,12 @@ fn non_primitive_ty() { let (a, b) = (S(10), S(20)); let _ = if a < b { b - a } else { a - b }; } + +fn issue15254(a: &usize, b: &usize) -> usize { + if a < b { + //~^ manual_abs_diff + b - a + } else { + a - b + } +} diff --git a/tests/ui/manual_abs_diff.stderr b/tests/ui/manual_abs_diff.stderr index c14c1dc830fb..bb6d312b435f 100644 --- a/tests/ui/manual_abs_diff.stderr +++ b/tests/ui/manual_abs_diff.stderr @@ -79,5 +79,16 @@ error: manual absolute difference pattern without using `abs_diff` LL | let _ = if a > b { (a - b) as u32 } else { (b - a) as u32 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `abs_diff`: `a.abs_diff(b)` -error: aborting due to 11 previous errors +error: manual absolute difference pattern without using `abs_diff` + --> tests/ui/manual_abs_diff.rs:119:5 + | +LL | / if a < b { +LL | | +LL | | b - a +LL | | } else { +LL | | a - b +LL | | } + | |_____^ help: replace with `abs_diff`: `b.abs_diff(*a)` + +error: aborting due to 12 previous errors diff --git a/tests/ui/manual_assert.edition2018.stderr b/tests/ui/manual_assert.edition2018.stderr index 8cedf2c68636..221cddf069db 100644 --- a/tests/ui/manual_assert.edition2018.stderr +++ b/tests/ui/manual_assert.edition2018.stderr @@ -189,5 +189,23 @@ LL - }; LL + const BAR: () = assert!(!(N == 0), ); | -error: aborting due to 10 previous errors +error: only a `panic!` in `if`-then statement + --> tests/ui/manual_assert.rs:116:5 + | +LL | / if !is_x86_feature_detected!("ssse3") { +LL | | +LL | | panic!("SSSE3 is not supported"); +LL | | } + | |_____^ + | +help: try instead + | +LL - if !is_x86_feature_detected!("ssse3") { +LL - +LL - panic!("SSSE3 is not supported"); +LL - } +LL + assert!(is_x86_feature_detected!("ssse3"), "SSSE3 is not supported"); + | + +error: aborting due to 11 previous errors diff --git a/tests/ui/manual_assert.edition2021.stderr b/tests/ui/manual_assert.edition2021.stderr index 8cedf2c68636..221cddf069db 100644 --- a/tests/ui/manual_assert.edition2021.stderr +++ b/tests/ui/manual_assert.edition2021.stderr @@ -189,5 +189,23 @@ LL - }; LL + const BAR: () = assert!(!(N == 0), ); | -error: aborting due to 10 previous errors +error: only a `panic!` in `if`-then statement + --> tests/ui/manual_assert.rs:116:5 + | +LL | / if !is_x86_feature_detected!("ssse3") { +LL | | +LL | | panic!("SSSE3 is not supported"); +LL | | } + | |_____^ + | +help: try instead + | +LL - if !is_x86_feature_detected!("ssse3") { +LL - +LL - panic!("SSSE3 is not supported"); +LL - } +LL + assert!(is_x86_feature_detected!("ssse3"), "SSSE3 is not supported"); + | + +error: aborting due to 11 previous errors diff --git a/tests/ui/manual_assert.rs b/tests/ui/manual_assert.rs index 46a42c3d00af..ab02bd5f5e53 100644 --- a/tests/ui/manual_assert.rs +++ b/tests/ui/manual_assert.rs @@ -105,3 +105,17 @@ fn issue12505() { }; } } + +fn issue15227(left: u64, right: u64) -> u64 { + macro_rules! is_x86_feature_detected { + ($feature:literal) => { + $feature.len() > 0 && $feature.starts_with("ss") + }; + } + + if !is_x86_feature_detected!("ssse3") { + //~^ manual_assert + panic!("SSSE3 is not supported"); + } + unsafe { todo!() } +} diff --git a/tests/ui/manual_is_multiple_of.fixed b/tests/ui/manual_is_multiple_of.fixed index 6735b99f298c..03f75e725ed5 100644 --- a/tests/ui/manual_is_multiple_of.fixed +++ b/tests/ui/manual_is_multiple_of.fixed @@ -23,3 +23,81 @@ fn f(a: u64, b: u64) { fn g(a: u64, b: u64) { let _ = a % b == 0; } + +fn needs_deref(a: &u64, b: &u64) { + let _ = a.is_multiple_of(*b); //~ manual_is_multiple_of +} + +fn closures(a: u64, b: u64) { + // Do not lint, types are ambiguous at this point + let cl = |a, b| a % b == 0; + let _ = cl(a, b); + + // Do not lint, types are ambiguous at this point + let cl = |a: _, b: _| a % b == 0; + let _ = cl(a, b); + + // Type of `a` is enough + let cl = |a: u64, b| a.is_multiple_of(b); //~ manual_is_multiple_of + let _ = cl(a, b); + + // Type of `a` is enough + let cl = |a: &u64, b| a.is_multiple_of(b); //~ manual_is_multiple_of + let _ = cl(&a, b); + + // Type of `b` is not enough + let cl = |a, b: u64| a % b == 0; + let _ = cl(&a, b); +} + +fn any_rem>(a: T, b: T) { + // An arbitrary `Rem` implementation should not lint + let _ = a % b == 0; +} + +mod issue15103 { + fn foo() -> Option { + let mut n: u64 = 150_000_000; + + (2..).find(|p| { + while n.is_multiple_of(*p) { + //~^ manual_is_multiple_of + n /= p; + } + n <= 1 + }) + } + + const fn generate_primes() -> [u64; N] { + let mut result = [0; N]; + if N == 0 { + return result; + } + result[0] = 2; + if N == 1 { + return result; + } + let mut idx = 1; + let mut p = 3; + while idx < N { + let mut j = 0; + while j < idx && p % result[j] != 0 { + j += 1; + } + if j == idx { + result[idx] = p; + idx += 1; + } + p += 1; + } + result + } + + fn bar() -> u32 { + let d = |n: u32| -> u32 { (1..=n / 2).filter(|i| n.is_multiple_of(*i)).sum() }; + //~^ manual_is_multiple_of + + let d = |n| (1..=n / 2).filter(|i| n % i == 0).sum(); + (1..1_000).filter(|&i| i == d(d(i)) && i != d(i)).sum() + } +} diff --git a/tests/ui/manual_is_multiple_of.rs b/tests/ui/manual_is_multiple_of.rs index 00b638e4fd9f..7b6fa64c843d 100644 --- a/tests/ui/manual_is_multiple_of.rs +++ b/tests/ui/manual_is_multiple_of.rs @@ -23,3 +23,81 @@ fn f(a: u64, b: u64) { fn g(a: u64, b: u64) { let _ = a % b == 0; } + +fn needs_deref(a: &u64, b: &u64) { + let _ = a % b == 0; //~ manual_is_multiple_of +} + +fn closures(a: u64, b: u64) { + // Do not lint, types are ambiguous at this point + let cl = |a, b| a % b == 0; + let _ = cl(a, b); + + // Do not lint, types are ambiguous at this point + let cl = |a: _, b: _| a % b == 0; + let _ = cl(a, b); + + // Type of `a` is enough + let cl = |a: u64, b| a % b == 0; //~ manual_is_multiple_of + let _ = cl(a, b); + + // Type of `a` is enough + let cl = |a: &u64, b| a % b == 0; //~ manual_is_multiple_of + let _ = cl(&a, b); + + // Type of `b` is not enough + let cl = |a, b: u64| a % b == 0; + let _ = cl(&a, b); +} + +fn any_rem>(a: T, b: T) { + // An arbitrary `Rem` implementation should not lint + let _ = a % b == 0; +} + +mod issue15103 { + fn foo() -> Option { + let mut n: u64 = 150_000_000; + + (2..).find(|p| { + while n % p == 0 { + //~^ manual_is_multiple_of + n /= p; + } + n <= 1 + }) + } + + const fn generate_primes() -> [u64; N] { + let mut result = [0; N]; + if N == 0 { + return result; + } + result[0] = 2; + if N == 1 { + return result; + } + let mut idx = 1; + let mut p = 3; + while idx < N { + let mut j = 0; + while j < idx && p % result[j] != 0 { + j += 1; + } + if j == idx { + result[idx] = p; + idx += 1; + } + p += 1; + } + result + } + + fn bar() -> u32 { + let d = |n: u32| -> u32 { (1..=n / 2).filter(|i| n % i == 0).sum() }; + //~^ manual_is_multiple_of + + let d = |n| (1..=n / 2).filter(|i| n % i == 0).sum(); + (1..1_000).filter(|&i| i == d(d(i)) && i != d(i)).sum() + } +} diff --git a/tests/ui/manual_is_multiple_of.stderr b/tests/ui/manual_is_multiple_of.stderr index 0b1ae70c2a70..8523599ec402 100644 --- a/tests/ui/manual_is_multiple_of.stderr +++ b/tests/ui/manual_is_multiple_of.stderr @@ -37,5 +37,35 @@ error: manual implementation of `.is_multiple_of()` LL | let _ = 0 < a % b; | ^^^^^^^^^ help: replace with: `!a.is_multiple_of(b)` -error: aborting due to 6 previous errors +error: manual implementation of `.is_multiple_of()` + --> tests/ui/manual_is_multiple_of.rs:28:13 + | +LL | let _ = a % b == 0; + | ^^^^^^^^^^ help: replace with: `a.is_multiple_of(*b)` + +error: manual implementation of `.is_multiple_of()` + --> tests/ui/manual_is_multiple_of.rs:41:26 + | +LL | let cl = |a: u64, b| a % b == 0; + | ^^^^^^^^^^ help: replace with: `a.is_multiple_of(b)` + +error: manual implementation of `.is_multiple_of()` + --> tests/ui/manual_is_multiple_of.rs:45:27 + | +LL | let cl = |a: &u64, b| a % b == 0; + | ^^^^^^^^^^ help: replace with: `a.is_multiple_of(b)` + +error: manual implementation of `.is_multiple_of()` + --> tests/ui/manual_is_multiple_of.rs:63:19 + | +LL | while n % p == 0 { + | ^^^^^^^^^^ help: replace with: `n.is_multiple_of(*p)` + +error: manual implementation of `.is_multiple_of()` + --> tests/ui/manual_is_multiple_of.rs:97:58 + | +LL | let d = |n: u32| -> u32 { (1..=n / 2).filter(|i| n % i == 0).sum() }; + | ^^^^^^^^^^ help: replace with: `n.is_multiple_of(*i)` + +error: aborting due to 11 previous errors diff --git a/tests/ui/map_identity.fixed b/tests/ui/map_identity.fixed index 83b2dac5fc51..b82d3e6d9567 100644 --- a/tests/ui/map_identity.fixed +++ b/tests/ui/map_identity.fixed @@ -87,3 +87,15 @@ fn issue13904() { let _ = { it }.next(); //~^ map_identity } + +// same as `issue11764`, but for arrays +fn issue15198() { + let x = [[1, 2], [3, 4]]; + // don't lint: `&[i32; 2]` becomes `[&i32; 2]` + let _ = x.iter().map(|[x, y]| [x, y]); + let _ = x.iter().map(|x| [x[0]]).map(|[x]| x); + + // no match ergonomics for `[i32, i32]` + let _ = x.iter().copied(); + //~^ map_identity +} diff --git a/tests/ui/map_identity.rs b/tests/ui/map_identity.rs index e839c551364b..c295bf872701 100644 --- a/tests/ui/map_identity.rs +++ b/tests/ui/map_identity.rs @@ -93,3 +93,15 @@ fn issue13904() { let _ = { it }.map(|x| x).next(); //~^ map_identity } + +// same as `issue11764`, but for arrays +fn issue15198() { + let x = [[1, 2], [3, 4]]; + // don't lint: `&[i32; 2]` becomes `[&i32; 2]` + let _ = x.iter().map(|[x, y]| [x, y]); + let _ = x.iter().map(|x| [x[0]]).map(|[x]| x); + + // no match ergonomics for `[i32, i32]` + let _ = x.iter().copied().map(|[x, y]| [x, y]); + //~^ map_identity +} diff --git a/tests/ui/map_identity.stderr b/tests/ui/map_identity.stderr index 9836f3b4cc5f..9b624a0dc755 100644 --- a/tests/ui/map_identity.stderr +++ b/tests/ui/map_identity.stderr @@ -87,5 +87,11 @@ error: unnecessary map of the identity function LL | let _ = { it }.map(|x| x).next(); | ^^^^^^^^^^^ help: remove the call to `map` -error: aborting due to 13 previous errors +error: unnecessary map of the identity function + --> tests/ui/map_identity.rs:105:30 + | +LL | let _ = x.iter().copied().map(|[x, y]| [x, y]); + | ^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` + +error: aborting due to 14 previous errors diff --git a/tests/ui/missing_inline.rs b/tests/ui/missing_inline.rs index c1801005b777..223c7447975a 100644 --- a/tests/ui/missing_inline.rs +++ b/tests/ui/missing_inline.rs @@ -80,3 +80,20 @@ impl PubFoo { // do not lint this since users cannot control the external code #[derive(Debug)] pub struct S; + +pub mod issue15301 { + #[unsafe(no_mangle)] + pub extern "C" fn call_from_c() { + println!("Just called a Rust function from C!"); + } + + #[unsafe(no_mangle)] + pub extern "Rust" fn call_from_rust() { + println!("Just called a Rust function from Rust!"); + } + + #[unsafe(no_mangle)] + pub fn call_from_rust_no_extern() { + println!("Just called a Rust function from Rust!"); + } +} diff --git a/tests/ui/module_name_repetitions.rs b/tests/ui/module_name_repetitions.rs index 2fde98d7927c..5d16858bf85e 100644 --- a/tests/ui/module_name_repetitions.rs +++ b/tests/ui/module_name_repetitions.rs @@ -55,3 +55,21 @@ pub mod foo { } fn main() {} + +pub mod issue14095 { + pub mod widget { + #[macro_export] + macro_rules! define_widget { + ($id:ident) => { + /* ... */ + }; + } + + #[macro_export] + macro_rules! widget_impl { + ($id:ident) => { + /* ... */ + }; + } + } +} diff --git a/tests/ui/must_use_candidates.fixed b/tests/ui/must_use_candidates.fixed index 4c1d6b1ccb59..1e8589cf39d6 100644 --- a/tests/ui/must_use_candidates.fixed +++ b/tests/ui/must_use_candidates.fixed @@ -13,13 +13,15 @@ use std::sync::atomic::{AtomicBool, Ordering}; pub struct MyAtomic(AtomicBool); pub struct MyPure; -#[must_use] pub fn pure(i: u8) -> u8 { +#[must_use] +pub fn pure(i: u8) -> u8 { //~^ must_use_candidate i } impl MyPure { - #[must_use] pub fn inherent_pure(&self) -> u8 { + #[must_use] + pub fn inherent_pure(&self) -> u8 { //~^ must_use_candidate 0 } @@ -51,7 +53,8 @@ pub fn with_callback bool>(f: &F) -> bool { f(0) } -#[must_use] pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool { +#[must_use] +pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool { //~^ must_use_candidate true } @@ -64,7 +67,8 @@ pub fn atomics(b: &AtomicBool) -> bool { b.load(Ordering::SeqCst) } -#[must_use] pub fn rcd(_x: Rc) -> bool { +#[must_use] +pub fn rcd(_x: Rc) -> bool { //~^ must_use_candidate true } @@ -73,7 +77,8 @@ pub fn rcmut(_x: Rc<&mut u32>) -> bool { true } -#[must_use] pub fn arcd(_x: Arc) -> bool { +#[must_use] +pub fn arcd(_x: Arc) -> bool { //~^ must_use_candidate false } diff --git a/tests/ui/must_use_candidates.stderr b/tests/ui/must_use_candidates.stderr index 590253d95f9e..5ddbd0260629 100644 --- a/tests/ui/must_use_candidates.stderr +++ b/tests/ui/must_use_candidates.stderr @@ -1,35 +1,64 @@ error: this function could have a `#[must_use]` attribute - --> tests/ui/must_use_candidates.rs:16:1 + --> tests/ui/must_use_candidates.rs:16:8 | LL | pub fn pure(i: u8) -> u8 { - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn pure(i: u8) -> u8` + | ^^^^ | = note: `-D clippy::must-use-candidate` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::must_use_candidate)]` +help: add the attribute + | +LL + #[must_use] +LL | pub fn pure(i: u8) -> u8 { + | error: this method could have a `#[must_use]` attribute - --> tests/ui/must_use_candidates.rs:22:5 + --> tests/ui/must_use_candidates.rs:22:12 | LL | pub fn inherent_pure(&self) -> u8 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn inherent_pure(&self) -> u8` + | ^^^^^^^^^^^^^ + | +help: add the attribute + | +LL ~ #[must_use] +LL ~ pub fn inherent_pure(&self) -> u8 { + | error: this function could have a `#[must_use]` attribute - --> tests/ui/must_use_candidates.rs:54:1 + --> tests/ui/must_use_candidates.rs:54:8 | LL | pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool` + | ^^^^^^^^^^^ + | +help: add the attribute + | +LL + #[must_use] +LL | pub fn with_marker(_d: std::marker::PhantomData<&mut u32>) -> bool { + | error: this function could have a `#[must_use]` attribute - --> tests/ui/must_use_candidates.rs:67:1 + --> tests/ui/must_use_candidates.rs:67:8 | LL | pub fn rcd(_x: Rc) -> bool { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn rcd(_x: Rc) -> bool` + | ^^^ + | +help: add the attribute + | +LL + #[must_use] +LL | pub fn rcd(_x: Rc) -> bool { + | error: this function could have a `#[must_use]` attribute - --> tests/ui/must_use_candidates.rs:76:1 + --> tests/ui/must_use_candidates.rs:76:8 | LL | pub fn arcd(_x: Arc) -> bool { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn arcd(_x: Arc) -> bool` + | ^^^^ + | +help: add the attribute + | +LL + #[must_use] +LL | pub fn arcd(_x: Arc) -> bool { + | error: aborting due to 5 previous errors diff --git a/tests/ui/needless_for_each_fixable.fixed b/tests/ui/needless_for_each_fixable.fixed index a73aff556399..a6d64d9afc1a 100644 --- a/tests/ui/needless_for_each_fixable.fixed +++ b/tests/ui/needless_for_each_fixable.fixed @@ -143,3 +143,9 @@ mod issue14734 { //~^ needless_for_each } } + +fn issue15256() { + let vec: Vec = Vec::new(); + for v in vec.iter() { println!("{v}"); } + //~^ needless_for_each +} diff --git a/tests/ui/needless_for_each_fixable.rs b/tests/ui/needless_for_each_fixable.rs index d92f055d3f45..7e74d2b428fd 100644 --- a/tests/ui/needless_for_each_fixable.rs +++ b/tests/ui/needless_for_each_fixable.rs @@ -143,3 +143,9 @@ mod issue14734 { //~^ needless_for_each } } + +fn issue15256() { + let vec: Vec = Vec::new(); + vec.iter().for_each(|v| println!("{v}")); + //~^ needless_for_each +} diff --git a/tests/ui/needless_for_each_fixable.stderr b/tests/ui/needless_for_each_fixable.stderr index f80144560976..204cfa36b022 100644 --- a/tests/ui/needless_for_each_fixable.stderr +++ b/tests/ui/needless_for_each_fixable.stderr @@ -148,5 +148,11 @@ error: needless use of `for_each` LL | rows.iter().for_each(|x| do_something(x, 1u8)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in rows.iter() { do_something(x, 1u8); }` -error: aborting due to 10 previous errors +error: needless use of `for_each` + --> tests/ui/needless_for_each_fixable.rs:149:5 + | +LL | vec.iter().for_each(|v| println!("{v}")); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for v in vec.iter() { println!("{v}"); }` + +error: aborting due to 11 previous errors diff --git a/tests/ui/needless_range_loop.rs b/tests/ui/needless_range_loop.rs index 8a1c1be289cf..70cf9fa7369f 100644 --- a/tests/ui/needless_range_loop.rs +++ b/tests/ui/needless_range_loop.rs @@ -185,3 +185,28 @@ mod issue_2496 { unimplemented!() } } + +fn needless_loop() { + use std::hint::black_box; + let x = [0; 64]; + for i in 0..64 { + let y = [0; 64]; + + black_box(x[i]); + black_box(y[i]); + } + + for i in 0..64 { + black_box(x[i]); + black_box([0; 64][i]); + } + + for i in 0..64 { + black_box(x[i]); + black_box([1, 2, 3, 4, 5, 6, 7, 8][i]); + } + + for i in 0..64 { + black_box([1, 2, 3, 4, 5, 6, 7, 8][i]); + } +} diff --git a/tests/ui/never_loop.rs b/tests/ui/never_loop.rs index e0f54ef899b0..48d4b8ad1519 100644 --- a/tests/ui/never_loop.rs +++ b/tests/ui/never_loop.rs @@ -466,3 +466,35 @@ fn main() { test13(); test14(); } + +fn issue15059() { + 'a: for _ in 0..1 { + //~^ never_loop + break 'a; + } + + let mut b = 1; + 'a: for i in 0..1 { + //~^ never_loop + match i { + 0 => { + b *= 2; + break 'a; + }, + x => { + b += x; + break 'a; + }, + } + } + + #[allow(clippy::unused_unit)] + for v in 0..10 { + //~^ never_loop + break; + println!("{v}"); + // This is comment and should be kept + println!("This is a comment"); + () + } +} diff --git a/tests/ui/never_loop.stderr b/tests/ui/never_loop.stderr index bc9a7ec48b48..54b463266a3a 100644 --- a/tests/ui/never_loop.stderr +++ b/tests/ui/never_loop.stderr @@ -176,8 +176,10 @@ LL | | } | help: if you need the first element of the iterator, try writing | -LL - for v in 0..10 { -LL + if let Some(v) = (0..10).next() { +LL ~ if let Some(v) = (0..10).next() { +LL | +LL ~ +LL ~ | error: this loop never actually loops @@ -232,5 +234,68 @@ LL | | break 'inner; LL | | } | |_________^ -error: aborting due to 21 previous errors +error: this loop never actually loops + --> tests/ui/never_loop.rs:471:5 + | +LL | / 'a: for _ in 0..1 { +LL | | +LL | | break 'a; +LL | | } + | |_____^ + | +help: if you need the first element of the iterator, try writing + | +LL ~ if let Some(_) = (0..1).next() { +LL | +LL ~ + | + +error: this loop never actually loops + --> tests/ui/never_loop.rs:477:5 + | +LL | / 'a: for i in 0..1 { +LL | | +LL | | match i { +LL | | 0 => { +... | +LL | | } + | |_____^ + | +help: if you need the first element of the iterator, try writing + | +LL ~ if let Some(i) = (0..1).next() { +LL | +... +LL | b *= 2; +LL ~ +LL | }, +LL | x => { +LL | b += x; +LL ~ + | + +error: this loop never actually loops + --> tests/ui/never_loop.rs:492:5 + | +LL | / for v in 0..10 { +LL | | +LL | | break; +LL | | println!("{v}"); +... | +LL | | () +LL | | } + | |_____^ + | +help: if you need the first element of the iterator, try writing + | +LL ~ if let Some(v) = (0..10).next() { +LL | +LL ~ +LL ~ +LL | // This is comment and should be kept +LL ~ +LL ~ + | + +error: aborting due to 24 previous errors diff --git a/tests/ui/or_fun_call.fixed b/tests/ui/or_fun_call.fixed index bcd2602edb6a..0a8525a12f5e 100644 --- a/tests/ui/or_fun_call.fixed +++ b/tests/ui/or_fun_call.fixed @@ -283,6 +283,8 @@ mod issue8993 { let _ = Some(4).map_or_else(g, f); //~^ or_fun_call let _ = Some(4).map_or(0, f); + let _ = Some(4).map_or_else(|| "asd".to_string().len() as i32, f); + //~^ or_fun_call } } @@ -426,6 +428,8 @@ mod result_map_or { let _ = x.map_or_else(|_| g(), f); //~^ or_fun_call let _ = x.map_or(0, f); + let _ = x.map_or_else(|_| "asd".to_string().len() as i32, f); + //~^ or_fun_call } } diff --git a/tests/ui/or_fun_call.rs b/tests/ui/or_fun_call.rs index 8d1202ebf914..b4f9b950a7fe 100644 --- a/tests/ui/or_fun_call.rs +++ b/tests/ui/or_fun_call.rs @@ -283,6 +283,8 @@ mod issue8993 { let _ = Some(4).map_or(g(), f); //~^ or_fun_call let _ = Some(4).map_or(0, f); + let _ = Some(4).map_or("asd".to_string().len() as i32, f); + //~^ or_fun_call } } @@ -426,6 +428,8 @@ mod result_map_or { let _ = x.map_or(g(), f); //~^ or_fun_call let _ = x.map_or(0, f); + let _ = x.map_or("asd".to_string().len() as i32, f); + //~^ or_fun_call } } diff --git a/tests/ui/or_fun_call.stderr b/tests/ui/or_fun_call.stderr index 585ee2d0e19d..3e4df772668d 100644 --- a/tests/ui/or_fun_call.stderr +++ b/tests/ui/or_fun_call.stderr @@ -154,62 +154,68 @@ error: function call inside of `map_or` 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 + | +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:315:18 + --> tests/ui/or_fun_call.rs:317: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:319:28 + --> tests/ui/or_fun_call.rs:321: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:323:27 + --> tests/ui/or_fun_call.rs:325: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:327:22 + --> tests/ui/or_fun_call.rs:329: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:331:23 + --> tests/ui/or_fun_call.rs:333: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:335:25 + --> tests/ui/or_fun_call.rs:337: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:339:25 + --> tests/ui/or_fun_call.rs:341: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:381:17 + --> tests/ui/or_fun_call.rs:383: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:386:17 + --> tests/ui/or_fun_call.rs:388: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:391:17 + --> tests/ui/or_fun_call.rs:393:17 | LL | let _ = opt.unwrap_or({ | _________________^ @@ -229,52 +235,58 @@ LL ~ }); | error: function call inside of `map_or` - --> tests/ui/or_fun_call.rs:397:17 + --> tests/ui/or_fun_call.rs:399: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:402:17 + --> tests/ui/or_fun_call.rs:404: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:409:21 + --> tests/ui/or_fun_call.rs:411: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:424:19 + --> tests/ui/or_fun_call.rs:426: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:426:19 + --> tests/ui/or_fun_call.rs:428: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 + | +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:438:15 + --> tests/ui/or_fun_call.rs:442: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:448:15 + --> tests/ui/or_fun_call.rs:452:15 | LL | let _ = x.and(g()); | ^^^^^^^^ help: try: `and_then(|_| g())` error: function call inside of `and` - --> tests/ui/or_fun_call.rs:458:15 + --> tests/ui/or_fun_call.rs:462:15 | LL | let _ = x.and(g()); | ^^^^^^^^ help: try: `and_then(|_| g())` -error: aborting due to 43 previous errors +error: aborting due to 45 previous errors diff --git a/tests/ui/pattern_type_mismatch/auxiliary/external.rs b/tests/ui/pattern_type_mismatch/auxiliary/external.rs new file mode 100644 index 000000000000..cd27c5c74aa9 --- /dev/null +++ b/tests/ui/pattern_type_mismatch/auxiliary/external.rs @@ -0,0 +1,13 @@ +//! **FAKE** external macro crate. + +#[macro_export] +macro_rules! macro_with_match { + ( $p:pat ) => { + let something = (); + + match &something { + $p => true, + _ => false, + } + }; +} diff --git a/tests/ui/pattern_type_mismatch/syntax.rs b/tests/ui/pattern_type_mismatch/syntax.rs index 49ea1d3f7a67..aa988a577df2 100644 --- a/tests/ui/pattern_type_mismatch/syntax.rs +++ b/tests/ui/pattern_type_mismatch/syntax.rs @@ -6,6 +6,9 @@ clippy::single_match )] +//@aux-build:external.rs +use external::macro_with_match; + fn main() {} fn syntax_match() { @@ -159,3 +162,9 @@ fn macro_expansion() { let value = &Some(23); matching_macro!(value); } + +fn external_macro_expansion() { + macro_with_match! { + () + }; +} diff --git a/tests/ui/pattern_type_mismatch/syntax.stderr b/tests/ui/pattern_type_mismatch/syntax.stderr index cd604d604c12..636841e0a21a 100644 --- a/tests/ui/pattern_type_mismatch/syntax.stderr +++ b/tests/ui/pattern_type_mismatch/syntax.stderr @@ -1,5 +1,5 @@ error: type of pattern does not match the expression type - --> tests/ui/pattern_type_mismatch/syntax.rs:16:9 + --> tests/ui/pattern_type_mismatch/syntax.rs:19:9 | LL | Some(_) => (), | ^^^^^^^ @@ -9,7 +9,7 @@ LL | Some(_) => (), = help: to override `-D warnings` add `#[allow(clippy::pattern_type_mismatch)]` error: type of pattern does not match the expression type - --> tests/ui/pattern_type_mismatch/syntax.rs:36:12 + --> tests/ui/pattern_type_mismatch/syntax.rs:39:12 | LL | if let Some(_) = ref_value {} | ^^^^^^^ @@ -17,7 +17,7 @@ LL | if let Some(_) = ref_value {} = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings error: type of pattern does not match the expression type - --> tests/ui/pattern_type_mismatch/syntax.rs:48:15 + --> tests/ui/pattern_type_mismatch/syntax.rs:51:15 | LL | while let Some(_) = ref_value { | ^^^^^^^ @@ -25,7 +25,7 @@ LL | while let Some(_) = ref_value { = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings error: type of pattern does not match the expression type - --> tests/ui/pattern_type_mismatch/syntax.rs:68:9 + --> tests/ui/pattern_type_mismatch/syntax.rs:71:9 | LL | for (_a, _b) in slice.iter() {} | ^^^^^^^^ @@ -33,7 +33,7 @@ LL | for (_a, _b) in slice.iter() {} = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings error: type of pattern does not match the expression type - --> tests/ui/pattern_type_mismatch/syntax.rs:79:9 + --> tests/ui/pattern_type_mismatch/syntax.rs:82:9 | LL | let (_n, _m) = ref_value; | ^^^^^^^^ @@ -41,7 +41,7 @@ LL | let (_n, _m) = ref_value; = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings error: type of pattern does not match the expression type - --> tests/ui/pattern_type_mismatch/syntax.rs:89:12 + --> tests/ui/pattern_type_mismatch/syntax.rs:92:12 | LL | fn foo((_a, _b): &(i32, i32)) {} | ^^^^^^^^ @@ -49,7 +49,7 @@ LL | fn foo((_a, _b): &(i32, i32)) {} = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings error: type of pattern does not match the expression type - --> tests/ui/pattern_type_mismatch/syntax.rs:104:10 + --> tests/ui/pattern_type_mismatch/syntax.rs:107:10 | LL | foo(|(_a, _b)| ()); | ^^^^^^^^ @@ -57,7 +57,7 @@ LL | foo(|(_a, _b)| ()); = help: explicitly match against a `&_` pattern and adjust the enclosed variable bindings error: type of pattern does not match the expression type - --> tests/ui/pattern_type_mismatch/syntax.rs:121:9 + --> tests/ui/pattern_type_mismatch/syntax.rs:124:9 | LL | Some(_) => (), | ^^^^^^^ @@ -65,7 +65,7 @@ LL | Some(_) => (), = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings error: type of pattern does not match the expression type - --> tests/ui/pattern_type_mismatch/syntax.rs:142:17 + --> tests/ui/pattern_type_mismatch/syntax.rs:145:17 | LL | Some(_) => (), | ^^^^^^^ diff --git a/tests/ui/ptr_arg.rs b/tests/ui/ptr_arg.rs index 578641e910dc..be14e0762ff4 100644 --- a/tests/ui/ptr_arg.rs +++ b/tests/ui/ptr_arg.rs @@ -123,7 +123,7 @@ fn test_cow_with_ref(c: &Cow<[i32]>) {} //~^ ptr_arg fn test_cow(c: Cow<[i32]>) { - let _c = c; + let d = c; } trait Foo2 { @@ -141,36 +141,36 @@ mod issue_5644 { use std::path::PathBuf; fn allowed( - #[allow(clippy::ptr_arg)] _v: &Vec, - #[allow(clippy::ptr_arg)] _s: &String, - #[allow(clippy::ptr_arg)] _p: &PathBuf, - #[allow(clippy::ptr_arg)] _c: &Cow<[i32]>, - #[expect(clippy::ptr_arg)] _expect: &Cow<[i32]>, + #[allow(clippy::ptr_arg)] v: &Vec, + #[allow(clippy::ptr_arg)] s: &String, + #[allow(clippy::ptr_arg)] p: &PathBuf, + #[allow(clippy::ptr_arg)] c: &Cow<[i32]>, + #[expect(clippy::ptr_arg)] expect: &Cow<[i32]>, ) { } - fn some_allowed(#[allow(clippy::ptr_arg)] _v: &Vec, _s: &String) {} + fn some_allowed(#[allow(clippy::ptr_arg)] v: &Vec, s: &String) {} //~^ ptr_arg struct S; impl S { fn allowed( - #[allow(clippy::ptr_arg)] _v: &Vec, - #[allow(clippy::ptr_arg)] _s: &String, - #[allow(clippy::ptr_arg)] _p: &PathBuf, - #[allow(clippy::ptr_arg)] _c: &Cow<[i32]>, - #[expect(clippy::ptr_arg)] _expect: &Cow<[i32]>, + #[allow(clippy::ptr_arg)] v: &Vec, + #[allow(clippy::ptr_arg)] s: &String, + #[allow(clippy::ptr_arg)] p: &PathBuf, + #[allow(clippy::ptr_arg)] c: &Cow<[i32]>, + #[expect(clippy::ptr_arg)] expect: &Cow<[i32]>, ) { } } trait T { fn allowed( - #[allow(clippy::ptr_arg)] _v: &Vec, - #[allow(clippy::ptr_arg)] _s: &String, - #[allow(clippy::ptr_arg)] _p: &PathBuf, - #[allow(clippy::ptr_arg)] _c: &Cow<[i32]>, - #[expect(clippy::ptr_arg)] _expect: &Cow<[i32]>, + #[allow(clippy::ptr_arg)] v: &Vec, + #[allow(clippy::ptr_arg)] s: &String, + #[allow(clippy::ptr_arg)] p: &PathBuf, + #[allow(clippy::ptr_arg)] c: &Cow<[i32]>, + #[expect(clippy::ptr_arg)] expect: &Cow<[i32]>, ) { } } @@ -182,22 +182,22 @@ mod issue6509 { fn foo_vec(vec: &Vec) { //~^ ptr_arg - let _ = vec.clone().pop(); - let _ = vec.clone().clone(); + let a = vec.clone().pop(); + let b = vec.clone().clone(); } fn foo_path(path: &PathBuf) { //~^ ptr_arg - let _ = path.clone().pop(); - let _ = path.clone().clone(); + let c = path.clone().pop(); + let d = path.clone().clone(); } - fn foo_str(str: &PathBuf) { + fn foo_str(str: &String) { //~^ ptr_arg - let _ = str.clone().pop(); - let _ = str.clone().clone(); + let e = str.clone().pop(); + let f = str.clone().clone(); } } @@ -340,8 +340,8 @@ mod issue_13308 { ToOwned::clone_into(source, destination); } - fn h1(_: &::Target) {} - fn h2(_: T, _: &T::Target) {} + fn h1(x: &::Target) {} + fn h2(x: T, y: &T::Target) {} // Other cases that are still ok to lint and ideally shouldn't regress fn good(v1: &String, v2: &String) { @@ -352,3 +352,91 @@ mod issue_13308 { h2(String::new(), v2); } } + +mod issue_13489_and_13728 { + // This is a no-lint from now on. + fn foo(_x: &Vec) { + todo!(); + } + + // But this still gives us a lint. + fn foo_used(x: &Vec) { + //~^ ptr_arg + + todo!(); + } + + // This is also a no-lint from now on. + fn foo_local(x: &Vec) { + let _y = x; + + todo!(); + } + + // But this still gives us a lint. + fn foo_local_used(x: &Vec) { + //~^ ptr_arg + + let y = x; + + todo!(); + } + + // This only lints once from now on. + fn foofoo(_x: &Vec, y: &String) { + //~^ ptr_arg + + todo!(); + } + + // And this is also a no-lint from now on. + fn foofoo_local(_x: &Vec, y: &String) { + let _z = y; + + todo!(); + } +} + +mod issue_13489_and_13728_mut { + // This is a no-lint from now on. + fn bar(_x: &mut Vec) { + todo!() + } + + // But this still gives us a lint. + fn bar_used(x: &mut Vec) { + //~^ ptr_arg + + todo!() + } + + // This is also a no-lint from now on. + fn bar_local(x: &mut Vec) { + let _y = x; + + todo!() + } + + // But this still gives us a lint. + fn bar_local_used(x: &mut Vec) { + //~^ ptr_arg + + let y = x; + + todo!() + } + + // This only lints once from now on. + fn barbar(_x: &mut Vec, y: &mut String) { + //~^ ptr_arg + + todo!() + } + + // And this is also a no-lint from now on. + fn barbar_local(_x: &mut Vec, y: &mut String) { + let _z = y; + + todo!() + } +} diff --git a/tests/ui/ptr_arg.stderr b/tests/ui/ptr_arg.stderr index fd9ceddfe11c..87235057349e 100644 --- a/tests/ui/ptr_arg.stderr +++ b/tests/ui/ptr_arg.stderr @@ -127,10 +127,10 @@ LL | fn test_cow_with_ref(c: &Cow<[i32]>) {} | ^^^^^^^^^^^ help: change this to: `&[i32]` error: writing `&String` instead of `&str` involves a new object where a slice will do - --> tests/ui/ptr_arg.rs:152:66 + --> tests/ui/ptr_arg.rs:152:64 | -LL | fn some_allowed(#[allow(clippy::ptr_arg)] _v: &Vec, _s: &String) {} - | ^^^^^^^ help: change this to: `&str` +LL | fn some_allowed(#[allow(clippy::ptr_arg)] v: &Vec, s: &String) {} + | ^^^^^^^ help: change this to: `&str` error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do --> tests/ui/ptr_arg.rs:182:21 @@ -143,8 +143,8 @@ help: change this to LL ~ fn foo_vec(vec: &[u8]) { LL | LL | -LL ~ let _ = vec.to_owned().pop(); -LL ~ let _ = vec.to_owned().clone(); +LL ~ let a = vec.to_owned().pop(); +LL ~ let b = vec.to_owned().clone(); | error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do @@ -158,23 +158,23 @@ help: change this to LL ~ fn foo_path(path: &Path) { LL | LL | -LL ~ let _ = path.to_path_buf().pop(); -LL ~ let _ = path.to_path_buf().clone(); +LL ~ let c = path.to_path_buf().pop(); +LL ~ let d = path.to_path_buf().clone(); | -error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do +error: writing `&String` instead of `&str` involves a new object where a slice will do --> tests/ui/ptr_arg.rs:196:21 | -LL | fn foo_str(str: &PathBuf) { - | ^^^^^^^^ +LL | fn foo_str(str: &String) { + | ^^^^^^^ | help: change this to | -LL ~ fn foo_str(str: &Path) { +LL ~ fn foo_str(str: &str) { LL | LL | -LL ~ let _ = str.to_path_buf().pop(); -LL ~ let _ = str.to_path_buf().clone(); +LL ~ let e = str.to_owned().pop(); +LL ~ let f = str.to_owned().clone(); | error: writing `&mut Vec` instead of `&mut [_]` involves a new object where a slice will do @@ -231,6 +231,42 @@ error: writing `&String` instead of `&str` involves a new object where a slice w LL | fn good(v1: &String, v2: &String) { | ^^^^^^^ help: change this to: `&str` +error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do + --> tests/ui/ptr_arg.rs:363:20 + | +LL | fn foo_used(x: &Vec) { + | ^^^^^^^^^ help: change this to: `&[i32]` + +error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do + --> tests/ui/ptr_arg.rs:377:26 + | +LL | fn foo_local_used(x: &Vec) { + | ^^^^^^^^^ help: change this to: `&[i32]` + +error: writing `&String` instead of `&str` involves a new object where a slice will do + --> tests/ui/ptr_arg.rs:386:33 + | +LL | fn foofoo(_x: &Vec, y: &String) { + | ^^^^^^^ help: change this to: `&str` + +error: writing `&mut Vec` instead of `&mut [_]` involves a new object where a slice will do + --> tests/ui/ptr_arg.rs:407:20 + | +LL | fn bar_used(x: &mut Vec) { + | ^^^^^^^^^^^^^ help: change this to: `&mut [u32]` + +error: writing `&mut Vec` instead of `&mut [_]` involves a new object where a slice will do + --> tests/ui/ptr_arg.rs:421:26 + | +LL | fn bar_local_used(x: &mut Vec) { + | ^^^^^^^^^^^^^ help: change this to: `&mut [u32]` + +error: writing `&mut String` instead of `&mut str` involves a new object where a slice will do + --> tests/ui/ptr_arg.rs:430:37 + | +LL | fn barbar(_x: &mut Vec, y: &mut String) { + | ^^^^^^^^^^^ help: change this to: `&mut str` + error: eliding a lifetime that's named elsewhere is confusing --> tests/ui/ptr_arg.rs:314:36 | @@ -248,5 +284,5 @@ help: consistently use `'a` LL | fn cow_good_ret_ty<'a>(input: &'a Cow<'a, str>) -> &'a str { | ++ -error: aborting due to 27 previous errors +error: aborting due to 33 previous errors diff --git a/tests/ui/ptr_as_ptr.fixed b/tests/ui/ptr_as_ptr.fixed index 2033f31c1eec..71fea6144e76 100644 --- a/tests/ui/ptr_as_ptr.fixed +++ b/tests/ui/ptr_as_ptr.fixed @@ -219,3 +219,11 @@ mod null_entire_infer { //~^ ptr_as_ptr } } + +#[allow(clippy::transmute_null_to_fn)] +fn issue15283() { + unsafe { + let _: fn() = std::mem::transmute(std::ptr::null::()); + //~^ ptr_as_ptr + } +} diff --git a/tests/ui/ptr_as_ptr.rs b/tests/ui/ptr_as_ptr.rs index 224d09b0eb6e..4d507592a1e3 100644 --- a/tests/ui/ptr_as_ptr.rs +++ b/tests/ui/ptr_as_ptr.rs @@ -219,3 +219,11 @@ mod null_entire_infer { //~^ ptr_as_ptr } } + +#[allow(clippy::transmute_null_to_fn)] +fn issue15283() { + unsafe { + let _: fn() = std::mem::transmute(std::ptr::null::<()>() as *const u8); + //~^ ptr_as_ptr + } +} diff --git a/tests/ui/ptr_as_ptr.stderr b/tests/ui/ptr_as_ptr.stderr index 66dae8e0135e..adad159bb0f2 100644 --- a/tests/ui/ptr_as_ptr.stderr +++ b/tests/ui/ptr_as_ptr.stderr @@ -201,5 +201,11 @@ error: `as` casting between raw pointers without changing their constness LL | core::ptr::null() as _ | ^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null()` -error: aborting due to 33 previous errors +error: `as` casting between raw pointers without changing their constness + --> tests/ui/ptr_as_ptr.rs:226:43 + | +LL | let _: fn() = std::mem::transmute(std::ptr::null::<()>() as *const u8); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null::()` + +error: aborting due to 34 previous errors diff --git a/tests/ui/range_plus_minus_one.fixed b/tests/ui/range_plus_minus_one.fixed index ee716ef3a6a0..5c6da6d5aed3 100644 --- a/tests/ui/range_plus_minus_one.fixed +++ b/tests/ui/range_plus_minus_one.fixed @@ -1,5 +1,9 @@ +#![warn(clippy::range_minus_one, clippy::range_plus_one)] #![allow(unused_parens)] #![allow(clippy::iter_with_drain)] + +use std::ops::{Index, IndexMut, Range, RangeBounds, RangeInclusive}; + fn f() -> usize { 42 } @@ -20,8 +24,6 @@ macro_rules! macro_minus_one { }; } -#[warn(clippy::range_plus_one)] -#[warn(clippy::range_minus_one)] fn main() { for _ in 0..2 {} for _ in 0..=2 {} @@ -45,15 +47,13 @@ fn main() { //~^ range_plus_one for _ in 0..=(1 + f()) {} + // Those are not linted, as in the general case we cannot be sure that the exact type won't be + // important. let _ = ..11 - 1; - let _ = ..11; - //~^ range_minus_one - let _ = ..11; - //~^ range_minus_one - let _ = (1..=11); - //~^ range_plus_one - let _ = ((f() + 1)..=f()); - //~^ range_plus_one + let _ = ..=11 - 1; + let _ = ..=(11 - 1); + let _ = (1..11 + 1); + let _ = (f() + 1)..(f() + 1); const ONE: usize = 1; // integer consts are linted, too @@ -65,4 +65,118 @@ fn main() { macro_plus_one!(5); macro_minus_one!(5); + + // As an instance of `Iterator` + (1..=10).for_each(|_| {}); + //~^ range_plus_one + + // As an instance of `IntoIterator` + #[allow(clippy::useless_conversion)] + (1..=10).into_iter().for_each(|_| {}); + //~^ range_plus_one + + // As an instance of `RangeBounds` + { + let _ = (1..=10).start_bound(); + //~^ range_plus_one + } + + // As a `SliceIndex` + let a = [10, 20, 30]; + let _ = &a[1..=1]; + //~^ range_plus_one + + // As method call argument + vec.drain(2..=3); + //~^ range_plus_one + + // As function call argument + take_arg(10..=20); + //~^ range_plus_one + + // As function call argument inside a block + take_arg({ 10..=20 }); + //~^ range_plus_one + + // Do not lint in case types are unified + take_arg(if true { 10..20 } else { 10..20 + 1 }); + + // Do not lint, as the same type is used for both parameters + take_args(10..20 + 1, 10..21); + + // Do not lint, as the range type is also used indirectly in second parameter + take_arg_and_struct(10..20 + 1, S { t: 1..2 }); + + // As target of `IndexMut` + let mut a = [10, 20, 30]; + a[0..=2][0] = 1; + //~^ range_plus_one +} + +fn take_arg>(_: T) {} +fn take_args>(_: T, _: T) {} + +struct S { + t: T, +} +fn take_arg_and_struct>(_: T, _: S) {} + +fn no_index_by_range_inclusive(a: usize) { + struct S; + + impl Index> for S { + type Output = [u32]; + fn index(&self, _: Range) -> &Self::Output { + &[] + } + } + + _ = &S[0..a + 1]; +} + +fn no_index_mut_with_switched_range(a: usize) { + struct S(u32); + + impl Index> for S { + type Output = u32; + fn index(&self, _: Range) -> &Self::Output { + &self.0 + } + } + + impl IndexMut> for S { + fn index_mut(&mut self, _: Range) -> &mut Self::Output { + &mut self.0 + } + } + + impl Index> for S { + type Output = u32; + fn index(&self, _: RangeInclusive) -> &Self::Output { + &self.0 + } + } + + S(2)[0..a + 1] = 3; +} + +fn issue9908() { + // Simplified test case + let _ = || 0..=1; + + // Original test case + let full_length = 1024; + let range = { + // do some stuff, omit here + None + }; + + let range = range.map(|(s, t)| s..=t).unwrap_or(0..=(full_length - 1)); + + assert_eq!(range, 0..=1023); +} + +fn issue9908_2(n: usize) -> usize { + (1..n).sum() + //~^ range_minus_one } diff --git a/tests/ui/range_plus_minus_one.rs b/tests/ui/range_plus_minus_one.rs index f2d5ae2c1506..7172da6034b8 100644 --- a/tests/ui/range_plus_minus_one.rs +++ b/tests/ui/range_plus_minus_one.rs @@ -1,5 +1,9 @@ +#![warn(clippy::range_minus_one, clippy::range_plus_one)] #![allow(unused_parens)] #![allow(clippy::iter_with_drain)] + +use std::ops::{Index, IndexMut, Range, RangeBounds, RangeInclusive}; + fn f() -> usize { 42 } @@ -20,8 +24,6 @@ macro_rules! macro_minus_one { }; } -#[warn(clippy::range_plus_one)] -#[warn(clippy::range_minus_one)] fn main() { for _ in 0..2 {} for _ in 0..=2 {} @@ -45,15 +47,13 @@ fn main() { //~^ range_plus_one for _ in 0..=(1 + f()) {} + // Those are not linted, as in the general case we cannot be sure that the exact type won't be + // important. let _ = ..11 - 1; let _ = ..=11 - 1; - //~^ range_minus_one let _ = ..=(11 - 1); - //~^ range_minus_one let _ = (1..11 + 1); - //~^ range_plus_one let _ = (f() + 1)..(f() + 1); - //~^ range_plus_one const ONE: usize = 1; // integer consts are linted, too @@ -65,4 +65,118 @@ fn main() { macro_plus_one!(5); macro_minus_one!(5); + + // As an instance of `Iterator` + (1..10 + 1).for_each(|_| {}); + //~^ range_plus_one + + // As an instance of `IntoIterator` + #[allow(clippy::useless_conversion)] + (1..10 + 1).into_iter().for_each(|_| {}); + //~^ range_plus_one + + // As an instance of `RangeBounds` + { + let _ = (1..10 + 1).start_bound(); + //~^ range_plus_one + } + + // As a `SliceIndex` + let a = [10, 20, 30]; + let _ = &a[1..1 + 1]; + //~^ range_plus_one + + // As method call argument + vec.drain(2..3 + 1); + //~^ range_plus_one + + // As function call argument + take_arg(10..20 + 1); + //~^ range_plus_one + + // As function call argument inside a block + take_arg({ 10..20 + 1 }); + //~^ range_plus_one + + // Do not lint in case types are unified + take_arg(if true { 10..20 } else { 10..20 + 1 }); + + // Do not lint, as the same type is used for both parameters + take_args(10..20 + 1, 10..21); + + // Do not lint, as the range type is also used indirectly in second parameter + take_arg_and_struct(10..20 + 1, S { t: 1..2 }); + + // As target of `IndexMut` + let mut a = [10, 20, 30]; + a[0..2 + 1][0] = 1; + //~^ range_plus_one +} + +fn take_arg>(_: T) {} +fn take_args>(_: T, _: T) {} + +struct S { + t: T, +} +fn take_arg_and_struct>(_: T, _: S) {} + +fn no_index_by_range_inclusive(a: usize) { + struct S; + + impl Index> for S { + type Output = [u32]; + fn index(&self, _: Range) -> &Self::Output { + &[] + } + } + + _ = &S[0..a + 1]; +} + +fn no_index_mut_with_switched_range(a: usize) { + struct S(u32); + + impl Index> for S { + type Output = u32; + fn index(&self, _: Range) -> &Self::Output { + &self.0 + } + } + + impl IndexMut> for S { + fn index_mut(&mut self, _: Range) -> &mut Self::Output { + &mut self.0 + } + } + + impl Index> for S { + type Output = u32; + fn index(&self, _: RangeInclusive) -> &Self::Output { + &self.0 + } + } + + S(2)[0..a + 1] = 3; +} + +fn issue9908() { + // Simplified test case + let _ = || 0..=1; + + // Original test case + let full_length = 1024; + let range = { + // do some stuff, omit here + None + }; + + let range = range.map(|(s, t)| s..=t).unwrap_or(0..=(full_length - 1)); + + assert_eq!(range, 0..=1023); +} + +fn issue9908_2(n: usize) -> usize { + (1..=n - 1).sum() + //~^ range_minus_one } diff --git a/tests/ui/range_plus_minus_one.stderr b/tests/ui/range_plus_minus_one.stderr index 9b23a8b8c0b4..a419d935bd62 100644 --- a/tests/ui/range_plus_minus_one.stderr +++ b/tests/ui/range_plus_minus_one.stderr @@ -1,5 +1,5 @@ error: an inclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:29:14 + --> tests/ui/range_plus_minus_one.rs:31:14 | LL | for _ in 0..3 + 1 {} | ^^^^^^^^ help: use: `0..=3` @@ -8,55 +8,85 @@ LL | for _ in 0..3 + 1 {} = help: to override `-D warnings` add `#[allow(clippy::range_plus_one)]` error: an inclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:33:14 + --> tests/ui/range_plus_minus_one.rs:35:14 | LL | for _ in 0..1 + 5 {} | ^^^^^^^^ help: use: `0..=5` error: an inclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:37:14 + --> tests/ui/range_plus_minus_one.rs:39:14 | LL | for _ in 1..1 + 1 {} | ^^^^^^^^ help: use: `1..=1` error: an inclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:44:14 + --> tests/ui/range_plus_minus_one.rs:46:14 | LL | for _ in 0..(1 + f()) {} | ^^^^^^^^^^^^ help: use: `0..=f()` -error: an exclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:49:13 - | -LL | let _ = ..=11 - 1; - | ^^^^^^^^^ help: use: `..11` - | - = note: `-D clippy::range-minus-one` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::range_minus_one)]` - -error: an exclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:51:13 - | -LL | let _ = ..=(11 - 1); - | ^^^^^^^^^^^ help: use: `..11` - -error: an inclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:53:13 - | -LL | let _ = (1..11 + 1); - | ^^^^^^^^^^^ help: use: `(1..=11)` - -error: an inclusive range would be more readable - --> tests/ui/range_plus_minus_one.rs:55:13 - | -LL | let _ = (f() + 1)..(f() + 1); - | ^^^^^^^^^^^^^^^^^^^^ help: use: `((f() + 1)..=f())` - error: an inclusive range would be more readable --> tests/ui/range_plus_minus_one.rs:60:14 | LL | for _ in 1..ONE + ONE {} | ^^^^^^^^^^^^ help: use: `1..=ONE` -error: aborting due to 9 previous errors +error: an inclusive range would be more readable + --> tests/ui/range_plus_minus_one.rs:70:5 + | +LL | (1..10 + 1).for_each(|_| {}); + | ^^^^^^^^^^^ help: use: `(1..=10)` + +error: an inclusive range would be more readable + --> tests/ui/range_plus_minus_one.rs:75:5 + | +LL | (1..10 + 1).into_iter().for_each(|_| {}); + | ^^^^^^^^^^^ help: use: `(1..=10)` + +error: an inclusive range would be more readable + --> tests/ui/range_plus_minus_one.rs:80:17 + | +LL | let _ = (1..10 + 1).start_bound(); + | ^^^^^^^^^^^ help: use: `(1..=10)` + +error: an inclusive range would be more readable + --> tests/ui/range_plus_minus_one.rs:86:16 + | +LL | let _ = &a[1..1 + 1]; + | ^^^^^^^^ help: use: `1..=1` + +error: an inclusive range would be more readable + --> tests/ui/range_plus_minus_one.rs:90:15 + | +LL | vec.drain(2..3 + 1); + | ^^^^^^^^ help: use: `2..=3` + +error: an inclusive range would be more readable + --> tests/ui/range_plus_minus_one.rs:94:14 + | +LL | take_arg(10..20 + 1); + | ^^^^^^^^^^ help: use: `10..=20` + +error: an inclusive range would be more readable + --> tests/ui/range_plus_minus_one.rs:98:16 + | +LL | take_arg({ 10..20 + 1 }); + | ^^^^^^^^^^ help: use: `10..=20` + +error: an inclusive range would be more readable + --> tests/ui/range_plus_minus_one.rs:112:7 + | +LL | a[0..2 + 1][0] = 1; + | ^^^^^^^^ help: use: `0..=2` + +error: an exclusive range would be more readable + --> tests/ui/range_plus_minus_one.rs:180:5 + | +LL | (1..=n - 1).sum() + | ^^^^^^^^^^^ help: use: `(1..n)` + | + = note: `-D clippy::range-minus-one` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::range_minus_one)]` + +error: aborting due to 14 previous errors diff --git a/tests/ui/single_match_else_deref_patterns.fixed b/tests/ui/single_match_else_deref_patterns.fixed new file mode 100644 index 000000000000..7a9f80630964 --- /dev/null +++ b/tests/ui/single_match_else_deref_patterns.fixed @@ -0,0 +1,53 @@ +#![feature(deref_patterns)] +#![allow( + incomplete_features, + clippy::eq_op, + clippy::op_ref, + clippy::deref_addrof, + clippy::borrow_deref_ref, + clippy::needless_if +)] +#![deny(clippy::single_match_else)] + +fn string() { + if *"" == *"" {} + + if *&*&*&*"" == *"" {} + + if ***&&"" == *"" {} + + if *&*&*"" == *"" {} + + if **&&*"" == *"" {} +} + +fn int() { + if &&&1 == &&&2 { unreachable!() } else { + // ok + } + //~^^^^^^ single_match_else + if &&1 == &&2 { unreachable!() } else { + // ok + } + //~^^^^^^ single_match_else + if &&1 == &&2 { unreachable!() } else { + // ok + } + //~^^^^^^ single_match_else + if &1 == &2 { unreachable!() } else { + // ok + } + //~^^^^^^ single_match_else + if &1 == &2 { unreachable!() } else { + // ok + } + //~^^^^^^ single_match_else + if 1 == 2 { unreachable!() } else { + // ok + } + //~^^^^^^ single_match_else + if 1 == 2 { unreachable!() } else { + // ok + } + //~^^^^^^ single_match_else +} diff --git a/tests/ui/single_match_else_deref_patterns.rs b/tests/ui/single_match_else_deref_patterns.rs new file mode 100644 index 000000000000..ef19c7cbde2b --- /dev/null +++ b/tests/ui/single_match_else_deref_patterns.rs @@ -0,0 +1,94 @@ +#![feature(deref_patterns)] +#![allow( + incomplete_features, + clippy::eq_op, + clippy::op_ref, + clippy::deref_addrof, + clippy::borrow_deref_ref, + clippy::needless_if +)] +#![deny(clippy::single_match_else)] + +fn string() { + match *"" { + //~^ single_match + "" => {}, + _ => {}, + } + + match *&*&*&*"" { + //~^ single_match + "" => {}, + _ => {}, + } + + match ***&&"" { + //~^ single_match + "" => {}, + _ => {}, + } + + match *&*&*"" { + //~^ single_match + "" => {}, + _ => {}, + } + + match **&&*"" { + //~^ single_match + "" => {}, + _ => {}, + } +} + +fn int() { + match &&&1 { + &&&2 => unreachable!(), + _ => { + // ok + }, + } + //~^^^^^^ single_match_else + match &&&1 { + &&2 => unreachable!(), + _ => { + // ok + }, + } + //~^^^^^^ single_match_else + match &&1 { + &&2 => unreachable!(), + _ => { + // ok + }, + } + //~^^^^^^ single_match_else + match &&&1 { + &2 => unreachable!(), + _ => { + // ok + }, + } + //~^^^^^^ single_match_else + match &&1 { + &2 => unreachable!(), + _ => { + // ok + }, + } + //~^^^^^^ single_match_else + match &&&1 { + 2 => unreachable!(), + _ => { + // ok + }, + } + //~^^^^^^ single_match_else + match &&1 { + 2 => unreachable!(), + _ => { + // ok + }, + } + //~^^^^^^ single_match_else +} diff --git a/tests/ui/single_match_else_deref_patterns.stderr b/tests/ui/single_match_else_deref_patterns.stderr new file mode 100644 index 000000000000..a47df55459be --- /dev/null +++ b/tests/ui/single_match_else_deref_patterns.stderr @@ -0,0 +1,188 @@ +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:13:5 + | +LL | / match *"" { +LL | | +LL | | "" => {}, +LL | | _ => {}, +LL | | } + | |_____^ help: try: `if *"" == *"" {}` + | + = note: you might want to preserve the comments from inside the `match` + = note: `-D clippy::single-match` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::single_match)]` + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:19:5 + | +LL | / match *&*&*&*"" { +LL | | +LL | | "" => {}, +LL | | _ => {}, +LL | | } + | |_____^ help: try: `if *&*&*&*"" == *"" {}` + | + = note: you might want to preserve the comments from inside the `match` + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:25:5 + | +LL | / match ***&&"" { +LL | | +LL | | "" => {}, +LL | | _ => {}, +LL | | } + | |_____^ help: try: `if ***&&"" == *"" {}` + | + = note: you might want to preserve the comments from inside the `match` + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:31:5 + | +LL | / match *&*&*"" { +LL | | +LL | | "" => {}, +LL | | _ => {}, +LL | | } + | |_____^ help: try: `if *&*&*"" == *"" {}` + | + = note: you might want to preserve the comments from inside the `match` + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:37:5 + | +LL | / match **&&*"" { +LL | | +LL | | "" => {}, +LL | | _ => {}, +LL | | } + | |_____^ help: try: `if **&&*"" == *"" {}` + | + = note: you might want to preserve the comments from inside the `match` + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:45:5 + | +LL | / match &&&1 { +LL | | &&&2 => unreachable!(), +LL | | _ => { +... | +LL | | } + | |_____^ + | +note: the lint level is defined here + --> tests/ui/single_match_else_deref_patterns.rs:10:9 + | +LL | #![deny(clippy::single_match_else)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +help: try + | +LL ~ if &&&1 == &&&2 { unreachable!() } else { +LL + // ok +LL + } + | + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:52:5 + | +LL | / match &&&1 { +LL | | &&2 => unreachable!(), +LL | | _ => { +... | +LL | | } + | |_____^ + | +help: try + | +LL ~ if &&1 == &&2 { unreachable!() } else { +LL + // ok +LL + } + | + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:59:5 + | +LL | / match &&1 { +LL | | &&2 => unreachable!(), +LL | | _ => { +... | +LL | | } + | |_____^ + | +help: try + | +LL ~ if &&1 == &&2 { unreachable!() } else { +LL + // ok +LL + } + | + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:66:5 + | +LL | / match &&&1 { +LL | | &2 => unreachable!(), +LL | | _ => { +... | +LL | | } + | |_____^ + | +help: try + | +LL ~ if &1 == &2 { unreachable!() } else { +LL + // ok +LL + } + | + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:73:5 + | +LL | / match &&1 { +LL | | &2 => unreachable!(), +LL | | _ => { +... | +LL | | } + | |_____^ + | +help: try + | +LL ~ if &1 == &2 { unreachable!() } else { +LL + // ok +LL + } + | + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:80:5 + | +LL | / match &&&1 { +LL | | 2 => unreachable!(), +LL | | _ => { +... | +LL | | } + | |_____^ + | +help: try + | +LL ~ if 1 == 2 { unreachable!() } else { +LL + // ok +LL + } + | + +error: you seem to be trying to use `match` for an equality check. Consider using `if` + --> tests/ui/single_match_else_deref_patterns.rs:87:5 + | +LL | / match &&1 { +LL | | 2 => unreachable!(), +LL | | _ => { +... | +LL | | } + | |_____^ + | +help: try + | +LL ~ if 1 == 2 { unreachable!() } else { +LL + // ok +LL + } + | + +error: aborting due to 12 previous errors + diff --git a/tests/ui/unsafe_derive_deserialize.rs b/tests/ui/unsafe_derive_deserialize.rs index 14371bc203b3..d0022f3b6d93 100644 --- a/tests/ui/unsafe_derive_deserialize.rs +++ b/tests/ui/unsafe_derive_deserialize.rs @@ -82,3 +82,32 @@ impl H { } fn main() {} + +mod issue15120 { + macro_rules! uns { + ($e:expr) => { + unsafe { $e } + }; + } + + #[derive(serde::Deserialize)] + struct Foo; + + impl Foo { + fn foo(&self) { + // Do not lint if `unsafe` comes from the `core::pin::pin!()` macro + std::pin::pin!(()); + } + } + + //~v unsafe_derive_deserialize + #[derive(serde::Deserialize)] + struct Bar; + + impl Bar { + fn bar(&self) { + // Lint if `unsafe` comes from the another macro + _ = uns!(42); + } + } +} diff --git a/tests/ui/unsafe_derive_deserialize.stderr b/tests/ui/unsafe_derive_deserialize.stderr index f2d4429f707a..4b5dd6e61fcc 100644 --- a/tests/ui/unsafe_derive_deserialize.stderr +++ b/tests/ui/unsafe_derive_deserialize.stderr @@ -36,5 +36,14 @@ LL | #[derive(Deserialize)] = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 4 previous errors +error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` + --> tests/ui/unsafe_derive_deserialize.rs:104:14 + | +LL | #[derive(serde::Deserialize)] + | ^^^^^^^^^^^^^^^^^^ + | + = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html + = note: this error originates in the derive macro `serde::Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 5 previous errors diff --git a/tests/ui/unused_async.rs b/tests/ui/unused_async.rs index 433459253dd7..7a0be825a2de 100644 --- a/tests/ui/unused_async.rs +++ b/tests/ui/unused_async.rs @@ -127,3 +127,13 @@ mod issue14704 { async fn cancel(self: Arc) {} } } + +mod issue15305 { + async fn todo_task() -> Result<(), String> { + todo!("Implement task"); + } + + async fn unimplemented_task() -> Result<(), String> { + unimplemented!("Implement task"); + } +} diff --git a/tests/ui/unused_trait_names.fixed b/tests/ui/unused_trait_names.fixed index 17e32ddfd9d9..6abbed01bb02 100644 --- a/tests/ui/unused_trait_names.fixed +++ b/tests/ui/unused_trait_names.fixed @@ -200,11 +200,11 @@ fn msrv_1_33() { MyStruct.do_things(); } +// Linting inside macro expansion is no longer supported mod lint_inside_macro_expansion_bad { macro_rules! foo { () => { - use std::any::Any as _; - //~^ unused_trait_names + use std::any::Any; fn bar() { "bar".type_id(); } diff --git a/tests/ui/unused_trait_names.rs b/tests/ui/unused_trait_names.rs index 3cf8597e5351..4a06f062dc37 100644 --- a/tests/ui/unused_trait_names.rs +++ b/tests/ui/unused_trait_names.rs @@ -200,11 +200,11 @@ fn msrv_1_33() { MyStruct.do_things(); } +// Linting inside macro expansion is no longer supported mod lint_inside_macro_expansion_bad { macro_rules! foo { () => { use std::any::Any; - //~^ unused_trait_names fn bar() { "bar".type_id(); } diff --git a/tests/ui/unused_trait_names.stderr b/tests/ui/unused_trait_names.stderr index 3183289d8533..28067e17414f 100644 --- a/tests/ui/unused_trait_names.stderr +++ b/tests/ui/unused_trait_names.stderr @@ -58,16 +58,5 @@ error: importing trait that is only used anonymously LL | use simple_trait::{MyStruct, MyTrait}; | ^^^^^^^ help: use: `MyTrait as _` -error: importing trait that is only used anonymously - --> tests/ui/unused_trait_names.rs:206:27 - | -LL | use std::any::Any; - | ^^^ help: use: `Any as _` -... -LL | foo!(); - | ------ in this macro invocation - | - = note: this error originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: aborting due to 10 previous errors +error: aborting due to 9 previous errors diff --git a/tests/ui/used_underscore_items.rs b/tests/ui/used_underscore_items.rs index 7e8289f1406b..aecdd32693c1 100644 --- a/tests/ui/used_underscore_items.rs +++ b/tests/ui/used_underscore_items.rs @@ -62,13 +62,13 @@ fn main() { //~^ used_underscore_items } -// should not lint exteranl crate. +// should not lint external crate. // user cannot control how others name their items fn external_item_call() { let foo_struct3 = external_item::_ExternalStruct {}; foo_struct3._foo(); - external_item::_exernal_foo(); + external_item::_external_foo(); } // should not lint foreign functions. diff --git a/tests/ui/useless_attribute.fixed b/tests/ui/useless_attribute.fixed index 930bc1eaecf9..be4fb55ddfb8 100644 --- a/tests/ui/useless_attribute.fixed +++ b/tests/ui/useless_attribute.fixed @@ -146,3 +146,15 @@ pub mod unknown_namespace { #[allow(rustc::non_glob_import_of_type_ir_inherent)] use some_module::SomeType; } + +// Regression test for https://github.com/rust-lang/rust-clippy/issues/15316 +pub mod redundant_imports_issue { + macro_rules! empty { + () => {}; + } + + #[expect(redundant_imports)] + pub(crate) use empty; + + empty!(); +} diff --git a/tests/ui/useless_attribute.rs b/tests/ui/useless_attribute.rs index 50fafd478e51..5a1bcf97a5b4 100644 --- a/tests/ui/useless_attribute.rs +++ b/tests/ui/useless_attribute.rs @@ -146,3 +146,15 @@ pub mod unknown_namespace { #[allow(rustc::non_glob_import_of_type_ir_inherent)] use some_module::SomeType; } + +// Regression test for https://github.com/rust-lang/rust-clippy/issues/15316 +pub mod redundant_imports_issue { + macro_rules! empty { + () => {}; + } + + #[expect(redundant_imports)] + pub(crate) use empty; + + empty!(); +} diff --git a/triagebot.toml b/triagebot.toml index 805baf2af6dd..a62b6269a3bf 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -54,6 +54,7 @@ contributing_url = "https://github.com/rust-lang/rust-clippy/blob/master/CONTRIB users_on_vacation = [ "matthiaskrgr", "Manishearth", + "samueltardieu", ] [assign.owners] diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index 6f380ec8feef..5d65ea585df6 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -49,9 +49,7 @@ Otherwise, have a great day =^.^= {# #}
{# #} - {# #} +

Clippy Lints

{# #} {# #} -
{# #} - - {% for lint in lints %} -
{# #} - {# #} - {# #} - -
{# #} -
{{Self::markdown(lint.docs)}}
{# #} -
- {# Applicability #} -
{# #} - Applicability: {#+ #} - {{ lint.applicability_str() }} {# #} - (?) {# #} -
- {# Clippy version #} -
{# #} - {% if lint.group == "deprecated" %}Deprecated{% else %} Added{% endif +%} in: {#+ #} - {{lint.version}} {# #} -
- {# Open related issues #} -
{# #} - Related Issues {# #} -
- - {# Jump to source #} - {% if let Some(id_location) = lint.id_location %} -
{# #} - View Source {# #} -
- {% endif %} -
{# #} +
{# #} +
{# #} + {# #} + {# #} + {# #} + {# #} + {# #}
{# #} -
- {% endfor %} -
{# #} +
{# #} +
{# #} + {# #} + {# #} +
{# #} + {# #} + + {% for lint in lints %} +
{# #} + {# #} + {# #} + +
{# #} +
{{Self::markdown(lint.docs)}}
{# #} +
+ {# Applicability #} +
{# #} + Applicability: {#+ #} + {{ lint.applicability_str() }} {# #} + (?) {# #} +
+ {# Clippy version #} +
{# #} + {% if lint.group == "deprecated" %}Deprecated{% else %} Added{% endif +%} in: {#+ #} + {{lint.version}} {# #} +
+ {# Open related issues #} +
{# #} + Related Issues {# #} +
+ + {# Jump to source #} + {% if let Some(id_location) = lint.id_location %} +
{# #} + View Source {# #} +
+ {% endif %} +
{# #} +
{# #} +
+ {% endfor %} {# #} lint.group != "deprecated"); + const totalLints = allLints.length; + + const countElement = document.getElementById("lint-count"); + if (countElement) { + countElement.innerText = `Total number: ${totalLints}`; + } +} + generateSettings(); generateSearch(); parseURLFilters(); scrollToLintByURL(); filters.filterLints(); updateLintCount(); - -function updateLintCount() { - const allLints = filters.getAllLints().filter(lint => lint.group != "deprecated"); - const totalLints = allLints.length; - - const countElement = document.getElementById("lint-count"); - if (countElement) { - countElement.innerText = `Total number: ${totalLints}`; - } -} diff --git a/util/gh-pages/style.css b/util/gh-pages/style.css index 022ea8752000..66abf4598b0e 100644 --- a/util/gh-pages/style.css +++ b/util/gh-pages/style.css @@ -30,17 +30,25 @@ blockquote { font-size: 1em; } background-color: var(--theme-hover); } -div.panel div.panel-body button { +.container > * { + margin-bottom: 20px; + border-radius: 4px; + background: var(--bg); + border: 1px solid var(--theme-popup-border); + box-shadow: 0 1px 1px rgba(0,0,0,.05); +} + +div.panel-body button { background: var(--searchbar-bg); color: var(--searchbar-fg); border-color: var(--theme-popup-border); } -div.panel div.panel-body button:hover { +div.panel-body button:hover { box-shadow: 0 0 3px var(--searchbar-shadow-color); } -div.panel div.panel-body button.open { +div.panel-body button.open { filter: brightness(90%); } @@ -48,8 +56,6 @@ div.panel div.panel-body button.open { background-color: #777; } -.panel-heading { cursor: pointer; } - .lint-title { cursor: pointer; margin-top: 0; @@ -70,8 +76,8 @@ div.panel div.panel-body button.open { .panel-title-name { flex: 1; min-width: 400px;} -.panel .panel-title-name .anchor { display: none; } -.panel:hover .panel-title-name .anchor { display: inline;} +.panel-title-name .anchor { display: none; } +article:hover .panel-title-name .anchor { display: inline;} .search-control { margin-top: 15px; @@ -111,40 +117,48 @@ div.panel div.panel-body button.open { padding-bottom: 0.3em; } -.label-lint-group { - min-width: 8em; -} -.label-lint-level { +.lint-level { min-width: 4em; } - -.label-lint-level-allow { +.level-allow { background-color: #5cb85c; } -.label-lint-level-warn { +.level-warn { background-color: #f0ad4e; } -.label-lint-level-deny { +.level-deny { background-color: #d9534f; } -.label-lint-level-none { +.level-none { background-color: #777777; opacity: 0.5; } -.label-group-deprecated { +.lint-group { + min-width: 8em; +} +.group-deprecated { opacity: 0.5; } -.label-doc-folding { +.doc-folding { color: #000; background-color: #fff; border: 1px solid var(--theme-popup-border); } -.label-doc-folding:hover { +.doc-folding:hover { background-color: #e6e6e6; } +.lint-doc-md { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background: 0%; + border-bottom: 1px solid var(--theme-popup-border); + border-top: 1px solid var(--theme-popup-border); +} .lint-doc-md > h3 { border-top: 1px solid var(--theme-popup-border); padding: 10px 15px; @@ -157,32 +171,32 @@ div.panel div.panel-body button.open { } @media (max-width:749px) { - .lint-additional-info-container { + .lint-additional-info { display: flex; flex-flow: column; } - .lint-additional-info-container > div + div { + .lint-additional-info > div + div { border-top: 1px solid var(--theme-popup-border); } } @media (min-width:750px) { - .lint-additional-info-container { + .lint-additional-info { display: flex; flex-flow: row; } - .lint-additional-info-container > div + div { + .lint-additional-info > div + div { border-left: 1px solid var(--theme-popup-border); } } -.lint-additional-info-container > div { +.lint-additional-info > div { display: inline-flex; min-width: 200px; flex-grow: 1; padding: 9px 5px 5px 15px; } -.label-applicability { +.applicability { background-color: #777777; margin: auto 5px; } @@ -332,21 +346,12 @@ L4.75,12h2.5l0.5393066-2.1572876 c0.2276001-0.1062012,0.4459839-0.2269287,0.649 border: 1px solid var(--theme-popup-border); } .page-header { - border-color: var(--theme-popup-border); + border: 0; + border-bottom: 1px solid var(--theme-popup-border); + padding-bottom: 19px; + border-radius: 0; } -.panel-default .panel-heading { - background: var(--theme-hover); - color: var(--fg); - border: 1px solid var(--theme-popup-border); -} -.panel-default .panel-heading:hover { - filter: brightness(90%); -} -.list-group-item { - background: 0%; - border: 1px solid var(--theme-popup-border); -} -.panel, pre, hr { +pre, hr { background: var(--bg); border: 1px solid var(--theme-popup-border); } @@ -442,14 +447,15 @@ article > label { article > input[type="checkbox"] { display: none; } -article > input[type="checkbox"] + label .label-doc-folding::before { +article > input[type="checkbox"] + label .doc-folding::before { content: "+"; } -article > input[type="checkbox"]:checked + label .label-doc-folding::before { +article > input[type="checkbox"]:checked + label .doc-folding::before { content: "−"; } .lint-docs { display: none; + margin-bottom: 0; } article > input[type="checkbox"]:checked ~ .lint-docs { display: block; From 03986e640cc5a3ca70259484a6f271831c0bc241 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Fri, 25 Jul 2025 22:51:39 +0200 Subject: [PATCH 20/65] Get myself back on assignment rotation --- triagebot.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/triagebot.toml b/triagebot.toml index a62b6269a3bf..805baf2af6dd 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -54,7 +54,6 @@ contributing_url = "https://github.com/rust-lang/rust-clippy/blob/master/CONTRIB users_on_vacation = [ "matthiaskrgr", "Manishearth", - "samueltardieu", ] [assign.owners] From e910c0e7e6338bdc4c63b584bfff3f2abf1c3896 Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Fri, 28 Mar 2025 12:27:04 -0400 Subject: [PATCH 21/65] Deprecate `string_to_string` --- clippy_lints/src/declared_lints.rs | 1 - clippy_lints/src/deprecated_lints.rs | 2 + clippy_lints/src/lib.rs | 1 - clippy_lints/src/strings.rs | 109 ------------------------ tests/ui/deprecated.rs | 1 + tests/ui/deprecated.stderr | 18 ++-- tests/ui/string_to_string.fixed | 21 ----- tests/ui/string_to_string.rs | 21 ----- tests/ui/string_to_string.stderr | 23 ----- tests/ui/string_to_string_in_map.fixed | 20 ----- tests/ui/string_to_string_in_map.rs | 20 ----- tests/ui/string_to_string_in_map.stderr | 38 --------- 12 files changed, 15 insertions(+), 260 deletions(-) delete mode 100644 tests/ui/string_to_string.fixed delete mode 100644 tests/ui/string_to_string.rs delete mode 100644 tests/ui/string_to_string.stderr delete mode 100644 tests/ui/string_to_string_in_map.fixed delete mode 100644 tests/ui/string_to_string_in_map.rs delete mode 100644 tests/ui/string_to_string_in_map.stderr diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 5fcb851dfebc..1b0e87ffa710 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -683,7 +683,6 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::strings::STRING_FROM_UTF8_AS_BYTES_INFO, crate::strings::STRING_LIT_AS_BYTES_INFO, crate::strings::STRING_SLICE_INFO, - crate::strings::STRING_TO_STRING_INFO, crate::strings::STR_TO_STRING_INFO, crate::strings::TRIM_SPLIT_WHITESPACE_INFO, crate::strlen_on_c_strings::STRLEN_ON_C_STRINGS_INFO, diff --git a/clippy_lints/src/deprecated_lints.rs b/clippy_lints/src/deprecated_lints.rs index 5204f73ea0a9..88aebc3e6a16 100644 --- a/clippy_lints/src/deprecated_lints.rs +++ b/clippy_lints/src/deprecated_lints.rs @@ -34,6 +34,8 @@ 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::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"), #[clippy::version = "pre 1.29.0"] diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index ca1a0b357108..cd9917e3ac88 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -782,7 +782,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax)); store.register_late_pass(|_| Box::new(empty_drop::EmptyDrop)); store.register_late_pass(|_| Box::new(strings::StrToString)); - store.register_late_pass(|_| Box::new(strings::StringToString)); store.register_late_pass(|_| Box::new(zero_sized_map_values::ZeroSizedMapValues)); store.register_late_pass(|_| Box::::default()); store.register_late_pass(|_| Box::new(redundant_slicing::RedundantSlicing)); diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 9fcef84eeb34..d520df95ae89 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -13,8 +13,6 @@ use rustc_middle::ty; use rustc_session::declare_lint_pass; use rustc_span::source_map::Spanned; -use std::ops::ControlFlow; - declare_clippy_lint! { /// ### What it does /// Checks for string appends of the form `x = x + y` (without @@ -411,113 +409,6 @@ impl<'tcx> LateLintPass<'tcx> for StrToString { } } -declare_clippy_lint! { - /// ### What it does - /// This lint checks for `.to_string()` method calls on values of type `String`. - /// - /// ### Why restrict this? - /// The `to_string` method is also used on other types to convert them to a string. - /// When called on a `String` it only clones the `String`, which can be more specifically - /// expressed with `.clone()`. - /// - /// ### Example - /// ```no_run - /// let msg = String::from("Hello World"); - /// let _ = msg.to_string(); - /// ``` - /// Use instead: - /// ```no_run - /// let msg = String::from("Hello World"); - /// let _ = msg.clone(); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub STRING_TO_STRING, - restriction, - "using `to_string()` on a `String`, which should be `clone()`" -} - -declare_lint_pass!(StringToString => [STRING_TO_STRING]); - -fn is_parent_map_like(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { - if let Some(parent_expr) = get_parent_expr(cx, expr) - && let ExprKind::MethodCall(name, _, _, parent_span) = parent_expr.kind - && name.ident.name == sym::map - && let Some(caller_def_id) = cx.typeck_results().type_dependent_def_id(parent_expr.hir_id) - && (clippy_utils::is_diag_item_method(cx, caller_def_id, sym::Result) - || clippy_utils::is_diag_item_method(cx, caller_def_id, sym::Option) - || clippy_utils::is_diag_trait_item(cx, caller_def_id, sym::Iterator)) - { - Some(parent_span) - } else { - None - } -} - -fn is_called_from_map_like(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { - // Look for a closure as parent of `expr`, discarding simple blocks - let parent_closure = cx - .tcx - .hir_parent_iter(expr.hir_id) - .try_fold(expr.hir_id, |child_hir_id, (_, node)| match node { - // Check that the child expression is the only expression in the block - Node::Block(block) if block.stmts.is_empty() && block.expr.map(|e| e.hir_id) == Some(child_hir_id) => { - ControlFlow::Continue(block.hir_id) - }, - Node::Expr(expr) if matches!(expr.kind, ExprKind::Block(..)) => ControlFlow::Continue(expr.hir_id), - Node::Expr(expr) if matches!(expr.kind, ExprKind::Closure(_)) => ControlFlow::Break(Some(expr)), - _ => ControlFlow::Break(None), - }) - .break_value()?; - is_parent_map_like(cx, parent_closure?) -} - -fn suggest_cloned_string_to_string(cx: &LateContext<'_>, span: rustc_span::Span) { - span_lint_and_sugg( - cx, - STRING_TO_STRING, - span, - "`to_string()` called on a `String`", - "try", - "cloned()".to_string(), - Applicability::MachineApplicable, - ); -} - -impl<'tcx> LateLintPass<'tcx> for StringToString { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { - if expr.span.from_expansion() { - return; - } - - match &expr.kind { - ExprKind::MethodCall(path, self_arg, [], _) => { - if path.ident.name == sym::to_string - && let ty = cx.typeck_results().expr_ty(self_arg) - && is_type_lang_item(cx, ty.peel_refs(), LangItem::String) - && let Some(parent_span) = is_called_from_map_like(cx, expr) - { - suggest_cloned_string_to_string(cx, parent_span); - } - }, - ExprKind::Path(QPath::TypeRelative(ty, segment)) => { - if segment.ident.name == sym::to_string - && let rustc_hir::TyKind::Path(QPath::Resolved(_, path)) = ty.peel_refs().kind - && let rustc_hir::def::Res::Def(_, def_id) = path.res - && cx - .tcx - .lang_items() - .get(LangItem::String) - .is_some_and(|lang_id| lang_id == def_id) - && let Some(parent_span) = is_parent_map_like(cx, expr) - { - suggest_cloned_string_to_string(cx, parent_span); - } - }, - _ => {}, - } - } -} - declare_clippy_lint! { /// ### What it does /// Warns about calling `str::trim` (or variants) before `str::split_whitespace`. diff --git a/tests/ui/deprecated.rs b/tests/ui/deprecated.rs index 6b69bdd29cea..9743a83fb934 100644 --- a/tests/ui/deprecated.rs +++ b/tests/ui/deprecated.rs @@ -12,6 +12,7 @@ #![warn(clippy::regex_macro)] //~ ERROR: lint `clippy::regex_macro` #![warn(clippy::replace_consts)] //~ ERROR: lint `clippy::replace_consts` #![warn(clippy::should_assert_eq)] //~ ERROR: lint `clippy::should_assert_eq` +#![warn(clippy::string_to_string)] //~ ERROR: lint `clippy::string_to_string` #![warn(clippy::unsafe_vector_initialization)] //~ ERROR: lint `clippy::unsafe_vector_initialization` #![warn(clippy::unstable_as_mut_slice)] //~ ERROR: lint `clippy::unstable_as_mut_slice` #![warn(clippy::unstable_as_slice)] //~ ERROR: lint `clippy::unstable_as_slice` diff --git a/tests/ui/deprecated.stderr b/tests/ui/deprecated.stderr index 07e59d33d608..cd225da611c4 100644 --- a/tests/ui/deprecated.stderr +++ b/tests/ui/deprecated.stderr @@ -61,35 +61,41 @@ error: lint `clippy::should_assert_eq` has been removed: `assert!(a == b)` can n LL | #![warn(clippy::should_assert_eq)] | ^^^^^^^^^^^^^^^^^^^^^^^^ -error: lint `clippy::unsafe_vector_initialization` has been removed: the suggested alternative could be substantially slower +error: lint `clippy::string_to_string` has been removed: `clippy:implicit_clone` covers those cases --> tests/ui/deprecated.rs:15:9 | +LL | #![warn(clippy::string_to_string)] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: lint `clippy::unsafe_vector_initialization` has been removed: the suggested alternative could be substantially slower + --> tests/ui/deprecated.rs:16:9 + | LL | #![warn(clippy::unsafe_vector_initialization)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: lint `clippy::unstable_as_mut_slice` has been removed: `Vec::as_mut_slice` is now stable - --> tests/ui/deprecated.rs:16:9 + --> tests/ui/deprecated.rs:17:9 | LL | #![warn(clippy::unstable_as_mut_slice)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: lint `clippy::unstable_as_slice` has been removed: `Vec::as_slice` is now stable - --> tests/ui/deprecated.rs:17:9 + --> tests/ui/deprecated.rs:18:9 | LL | #![warn(clippy::unstable_as_slice)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: lint `clippy::unused_collect` has been removed: `Iterator::collect` is now marked as `#[must_use]` - --> tests/ui/deprecated.rs:18:9 + --> tests/ui/deprecated.rs:19:9 | LL | #![warn(clippy::unused_collect)] | ^^^^^^^^^^^^^^^^^^^^^^ error: lint `clippy::wrong_pub_self_convention` has been removed: `clippy::wrong_self_convention` now covers this case via the `avoid-breaking-exported-api` config - --> tests/ui/deprecated.rs:19:9 + --> tests/ui/deprecated.rs:20:9 | LL | #![warn(clippy::wrong_pub_self_convention)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 15 previous errors +error: aborting due to 16 previous errors diff --git a/tests/ui/string_to_string.fixed b/tests/ui/string_to_string.fixed deleted file mode 100644 index 751f451cb685..000000000000 --- a/tests/ui/string_to_string.fixed +++ /dev/null @@ -1,21 +0,0 @@ -#![warn(clippy::implicit_clone, clippy::string_to_string)] -#![allow(clippy::redundant_clone, clippy::unnecessary_literal_unwrap)] - -fn main() { - let mut message = String::from("Hello"); - let mut v = message.clone(); - //~^ ERROR: implicitly cloning a `String` by calling `to_string` on its dereferenced type - - let variable1 = String::new(); - let v = &variable1; - let variable2 = Some(v); - let _ = variable2.map(|x| { - println!(); - x.clone() - //~^ ERROR: implicitly cloning a `String` by calling `to_string` on its dereferenced type - }); - - let x = Some(String::new()); - let _ = x.unwrap_or_else(|| v.clone()); - //~^ ERROR: implicitly cloning a `String` by calling `to_string` on its dereferenced type -} diff --git a/tests/ui/string_to_string.rs b/tests/ui/string_to_string.rs deleted file mode 100644 index d519677d59ec..000000000000 --- a/tests/ui/string_to_string.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![warn(clippy::implicit_clone, clippy::string_to_string)] -#![allow(clippy::redundant_clone, clippy::unnecessary_literal_unwrap)] - -fn main() { - let mut message = String::from("Hello"); - let mut v = message.to_string(); - //~^ ERROR: implicitly cloning a `String` by calling `to_string` on its dereferenced type - - let variable1 = String::new(); - let v = &variable1; - let variable2 = Some(v); - let _ = variable2.map(|x| { - println!(); - x.to_string() - //~^ ERROR: implicitly cloning a `String` by calling `to_string` on its dereferenced type - }); - - let x = Some(String::new()); - let _ = x.unwrap_or_else(|| v.to_string()); - //~^ ERROR: implicitly cloning a `String` by calling `to_string` on its dereferenced type -} diff --git a/tests/ui/string_to_string.stderr b/tests/ui/string_to_string.stderr deleted file mode 100644 index 220fe3170c6e..000000000000 --- a/tests/ui/string_to_string.stderr +++ /dev/null @@ -1,23 +0,0 @@ -error: implicitly cloning a `String` by calling `to_string` on its dereferenced type - --> tests/ui/string_to_string.rs:6:17 - | -LL | let mut v = message.to_string(); - | ^^^^^^^^^^^^^^^^^^^ help: consider using: `message.clone()` - | - = note: `-D clippy::implicit-clone` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::implicit_clone)]` - -error: implicitly cloning a `String` by calling `to_string` on its dereferenced type - --> tests/ui/string_to_string.rs:14:9 - | -LL | x.to_string() - | ^^^^^^^^^^^^^ help: consider using: `x.clone()` - -error: implicitly cloning a `String` by calling `to_string` on its dereferenced type - --> tests/ui/string_to_string.rs:19:33 - | -LL | let _ = x.unwrap_or_else(|| v.to_string()); - | ^^^^^^^^^^^^^ help: consider using: `v.clone()` - -error: aborting due to 3 previous errors - diff --git a/tests/ui/string_to_string_in_map.fixed b/tests/ui/string_to_string_in_map.fixed deleted file mode 100644 index efc085539f15..000000000000 --- a/tests/ui/string_to_string_in_map.fixed +++ /dev/null @@ -1,20 +0,0 @@ -#![deny(clippy::string_to_string)] -#![allow(clippy::unnecessary_literal_unwrap, clippy::useless_vec, clippy::iter_cloned_collect)] - -fn main() { - let variable1 = String::new(); - let v = &variable1; - let variable2 = Some(v); - let _ = variable2.cloned(); - //~^ string_to_string - let _ = variable2.cloned(); - //~^ string_to_string - #[rustfmt::skip] - let _ = variable2.cloned(); - //~^ string_to_string - - let _ = vec![String::new()].iter().cloned().collect::>(); - //~^ string_to_string - let _ = vec![String::new()].iter().cloned().collect::>(); - //~^ string_to_string -} diff --git a/tests/ui/string_to_string_in_map.rs b/tests/ui/string_to_string_in_map.rs deleted file mode 100644 index 5bf1d7ba5a2e..000000000000 --- a/tests/ui/string_to_string_in_map.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![deny(clippy::string_to_string)] -#![allow(clippy::unnecessary_literal_unwrap, clippy::useless_vec, clippy::iter_cloned_collect)] - -fn main() { - let variable1 = String::new(); - let v = &variable1; - let variable2 = Some(v); - let _ = variable2.map(String::to_string); - //~^ string_to_string - let _ = variable2.map(|x| x.to_string()); - //~^ string_to_string - #[rustfmt::skip] - let _ = variable2.map(|x| { x.to_string() }); - //~^ string_to_string - - let _ = vec![String::new()].iter().map(String::to_string).collect::>(); - //~^ string_to_string - let _ = vec![String::new()].iter().map(|x| x.to_string()).collect::>(); - //~^ string_to_string -} diff --git a/tests/ui/string_to_string_in_map.stderr b/tests/ui/string_to_string_in_map.stderr deleted file mode 100644 index 35aeed656eed..000000000000 --- a/tests/ui/string_to_string_in_map.stderr +++ /dev/null @@ -1,38 +0,0 @@ -error: `to_string()` called on a `String` - --> tests/ui/string_to_string_in_map.rs:8:23 - | -LL | let _ = variable2.map(String::to_string); - | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `cloned()` - | -note: the lint level is defined here - --> tests/ui/string_to_string_in_map.rs:1:9 - | -LL | #![deny(clippy::string_to_string)] - | ^^^^^^^^^^^^^^^^^^^^^^^^ - -error: `to_string()` called on a `String` - --> tests/ui/string_to_string_in_map.rs:10:23 - | -LL | let _ = variable2.map(|x| x.to_string()); - | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `cloned()` - -error: `to_string()` called on a `String` - --> tests/ui/string_to_string_in_map.rs:13:23 - | -LL | let _ = variable2.map(|x| { x.to_string() }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `cloned()` - -error: `to_string()` called on a `String` - --> tests/ui/string_to_string_in_map.rs:16:40 - | -LL | let _ = vec![String::new()].iter().map(String::to_string).collect::>(); - | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `cloned()` - -error: `to_string()` called on a `String` - --> tests/ui/string_to_string_in_map.rs:18:40 - | -LL | let _ = vec![String::new()].iter().map(|x| x.to_string()).collect::>(); - | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `cloned()` - -error: aborting due to 5 previous errors - From 31b6b666386146e5e88ff9f5d1327ee541c02939 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Mon, 7 Jul 2025 22:28:37 +0200 Subject: [PATCH 22/65] Fix tooling Signed-off-by: Jonathan Brouwer --- clippy_lints/src/needless_pass_by_value.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index 2006a824402d..7b057998063f 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -312,9 +312,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { /// Functions marked with these attributes must have the exact signature. pub(crate) fn requires_exact_signature(attrs: &[Attribute]) -> bool { attrs.iter().any(|attr| { - [sym::proc_macro, sym::proc_macro_attribute, sym::proc_macro_derive] - .iter() - .any(|&allow| attr.has_name(allow)) + attr.is_proc_macro_attr() }) } From c78f3aedfc262c95b08d944c878976dcc133dcf3 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Sun, 27 Jul 2025 15:13:34 +0800 Subject: [PATCH 23/65] fix: `empty_structs_with_brackets` suggests wrongly on generics --- clippy_lints/src/empty_with_brackets.rs | 4 ++-- tests/ui/empty_structs_with_brackets.fixed | 14 ++++++++++++++ tests/ui/empty_structs_with_brackets.rs | 14 ++++++++++++++ tests/ui/empty_structs_with_brackets.stderr | 10 +++++++++- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/empty_with_brackets.rs b/clippy_lints/src/empty_with_brackets.rs index f2757407ba57..e7230ebf8cba 100644 --- a/clippy_lints/src/empty_with_brackets.rs +++ b/clippy_lints/src/empty_with_brackets.rs @@ -93,11 +93,11 @@ impl_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM_VA impl LateLintPass<'_> for EmptyWithBrackets { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { // FIXME: handle `struct $name {}` - if let ItemKind::Struct(ident, _, var_data) = &item.kind + if let ItemKind::Struct(ident, generics, var_data) = &item.kind && !item.span.from_expansion() && !ident.span.from_expansion() && has_brackets(var_data) - && let span_after_ident = item.span.with_lo(ident.span.hi()) + && let span_after_ident = item.span.with_lo(generics.span.hi()) && has_no_fields(cx, var_data, span_after_ident) { span_lint_and_then( diff --git a/tests/ui/empty_structs_with_brackets.fixed b/tests/ui/empty_structs_with_brackets.fixed index 419cf2354f89..22386245ffe6 100644 --- a/tests/ui/empty_structs_with_brackets.fixed +++ b/tests/ui/empty_structs_with_brackets.fixed @@ -32,3 +32,17 @@ macro_rules! empty_struct { empty_struct!(FromMacro); fn main() {} + +mod issue15349 { + trait Bar {} + impl Bar for [u8; 7] {} + + struct Foo; + //~^ empty_structs_with_brackets + impl Foo + where + [u8; N]: Bar<[(); N]>, + { + fn foo() {} + } +} diff --git a/tests/ui/empty_structs_with_brackets.rs b/tests/ui/empty_structs_with_brackets.rs index 90c415c12206..5cb54b661349 100644 --- a/tests/ui/empty_structs_with_brackets.rs +++ b/tests/ui/empty_structs_with_brackets.rs @@ -32,3 +32,17 @@ macro_rules! empty_struct { empty_struct!(FromMacro); fn main() {} + +mod issue15349 { + trait Bar {} + impl Bar for [u8; 7] {} + + struct Foo {} + //~^ empty_structs_with_brackets + impl Foo + where + [u8; N]: Bar<[(); N]>, + { + fn foo() {} + } +} diff --git a/tests/ui/empty_structs_with_brackets.stderr b/tests/ui/empty_structs_with_brackets.stderr index 86ef43aa9600..f662bb9423e3 100644 --- a/tests/ui/empty_structs_with_brackets.stderr +++ b/tests/ui/empty_structs_with_brackets.stderr @@ -16,5 +16,13 @@ LL | struct MyEmptyTupleStruct(); // should trigger lint | = help: remove the brackets -error: aborting due to 2 previous errors +error: found empty brackets on struct declaration + --> tests/ui/empty_structs_with_brackets.rs:40:31 + | +LL | struct Foo {} + | ^^^ + | + = help: remove the brackets + +error: aborting due to 3 previous errors From 81fc22753e3e75dae62a5837c2b641c0fbd57cde Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 25 Jul 2025 22:32:41 +0200 Subject: [PATCH 24/65] fix: `unnecessary_map_or` don't add parens if the parent expr comes from a macro --- .../src/methods/unnecessary_map_or.rs | 14 ++++++--- tests/ui/unnecessary_map_or.fixed | 20 +++++++++++++ tests/ui/unnecessary_map_or.rs | 20 +++++++++++++ tests/ui/unnecessary_map_or.stderr | 30 +++++++++++++++++-- 4 files changed, 77 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/methods/unnecessary_map_or.rs b/clippy_lints/src/methods/unnecessary_map_or.rs index 4a9007c607c8..1f5e3de6e7a2 100644 --- a/clippy_lints/src/methods/unnecessary_map_or.rs +++ b/clippy_lints/src/methods/unnecessary_map_or.rs @@ -109,10 +109,16 @@ pub(super) fn check<'a>( ); let sugg = if let Some(parent_expr) = get_parent_expr(cx, expr) { - match parent_expr.kind { - ExprKind::Binary(..) | ExprKind::Unary(..) | ExprKind::Cast(..) => binop.maybe_paren(), - ExprKind::MethodCall(_, receiver, _, _) if receiver.hir_id == expr.hir_id => binop.maybe_paren(), - _ => binop, + if parent_expr.span.eq_ctxt(expr.span) { + match parent_expr.kind { + ExprKind::Binary(..) | ExprKind::Unary(..) | ExprKind::Cast(..) => binop.maybe_paren(), + ExprKind::MethodCall(_, receiver, _, _) if receiver.hir_id == expr.hir_id => binop.maybe_paren(), + _ => binop, + } + } else { + // if our parent expr is created by a macro, then it should be the one taking care of + // parenthesising us if necessary + binop } } else { binop diff --git a/tests/ui/unnecessary_map_or.fixed b/tests/ui/unnecessary_map_or.fixed index 3109c4af8e28..10552431d65d 100644 --- a/tests/ui/unnecessary_map_or.fixed +++ b/tests/ui/unnecessary_map_or.fixed @@ -131,6 +131,26 @@ fn issue14201(a: Option, b: Option, s: &String) -> bool { x && y } +fn issue14714() { + assert!(Some("test") == Some("test")); + //~^ unnecessary_map_or + + // even though we're in a macro context, we still need to parenthesise because of the `then` + assert!((Some("test") == Some("test")).then(|| 1).is_some()); + //~^ unnecessary_map_or + + // method lints don't fire on macros + macro_rules! m { + ($x:expr) => { + // should become !($x == Some(1)) + let _ = !$x.map_or(false, |v| v == 1); + // should become $x == Some(1) + let _ = $x.map_or(false, |v| v == 1); + }; + } + m!(Some(5)); +} + fn issue15180() { let s = std::sync::Mutex::new(Some("foo")); _ = s.lock().unwrap().is_some_and(|s| s == "foo"); diff --git a/tests/ui/unnecessary_map_or.rs b/tests/ui/unnecessary_map_or.rs index 52a55f9fc9e4..4b406ec2998b 100644 --- a/tests/ui/unnecessary_map_or.rs +++ b/tests/ui/unnecessary_map_or.rs @@ -135,6 +135,26 @@ fn issue14201(a: Option, b: Option, s: &String) -> bool { x && y } +fn issue14714() { + assert!(Some("test").map_or(false, |x| x == "test")); + //~^ unnecessary_map_or + + // even though we're in a macro context, we still need to parenthesise because of the `then` + assert!(Some("test").map_or(false, |x| x == "test").then(|| 1).is_some()); + //~^ unnecessary_map_or + + // method lints don't fire on macros + macro_rules! m { + ($x:expr) => { + // should become !($x == Some(1)) + let _ = !$x.map_or(false, |v| v == 1); + // should become $x == Some(1) + let _ = $x.map_or(false, |v| v == 1); + }; + } + m!(Some(5)); +} + fn issue15180() { let s = std::sync::Mutex::new(Some("foo")); _ = s.lock().unwrap().map_or(false, |s| s == "foo"); diff --git a/tests/ui/unnecessary_map_or.stderr b/tests/ui/unnecessary_map_or.stderr index 99e17e8b34ba..b8a22346c378 100644 --- a/tests/ui/unnecessary_map_or.stderr +++ b/tests/ui/unnecessary_map_or.stderr @@ -327,7 +327,31 @@ LL + let y = b.is_none_or(|b| b == *s); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:140:9 + --> tests/ui/unnecessary_map_or.rs:139:13 + | +LL | assert!(Some("test").map_or(false, |x| x == "test")); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use a standard comparison instead + | +LL - assert!(Some("test").map_or(false, |x| x == "test")); +LL + assert!(Some("test") == Some("test")); + | + +error: this `map_or` can be simplified + --> tests/ui/unnecessary_map_or.rs:143:13 + | +LL | assert!(Some("test").map_or(false, |x| x == "test").then(|| 1).is_some()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use a standard comparison instead + | +LL - assert!(Some("test").map_or(false, |x| x == "test").then(|| 1).is_some()); +LL + assert!((Some("test") == Some("test")).then(|| 1).is_some()); + | + +error: this `map_or` can be simplified + --> tests/ui/unnecessary_map_or.rs:160:9 | LL | _ = s.lock().unwrap().map_or(false, |s| s == "foo"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -339,7 +363,7 @@ LL + _ = s.lock().unwrap().is_some_and(|s| s == "foo"); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:144:9 + --> tests/ui/unnecessary_map_or.rs:164:9 | LL | _ = s.map_or(false, |s| s == "foo"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -350,5 +374,5 @@ LL - _ = s.map_or(false, |s| s == "foo"); LL + _ = s.is_some_and(|s| s == "foo"); | -error: aborting due to 28 previous errors +error: aborting due to 30 previous errors From 9339d8cac3e6ea8c10ad11c581f8cfed5014d89e Mon Sep 17 00:00:00 2001 From: Samuel Moelius <35515885+smoelius@users.noreply.github.com> Date: Sun, 27 Jul 2025 08:55:33 -0400 Subject: [PATCH 25/65] Fix typo non_std_lazy_statics.rs Changes `MaybeIncorret` to `MaybeIncorrect`. And since this is part of a doc comment, it might as well be a link. changelog: none --- clippy_lints/src/non_std_lazy_statics.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/non_std_lazy_statics.rs b/clippy_lints/src/non_std_lazy_statics.rs index abee3c44c5a3..7ecde40aee01 100644 --- a/clippy_lints/src/non_std_lazy_statics.rs +++ b/clippy_lints/src/non_std_lazy_statics.rs @@ -49,7 +49,7 @@ declare_clippy_lint! { /// Some functions could be replaced as well if we have replaced `Lazy` to `LazyLock`, /// therefore after suggesting replace the type, we need to make sure the function calls can be /// replaced, otherwise the suggestions cannot be applied thus the applicability should be -/// `Unspecified` or `MaybeIncorret`. +/// [`Applicability::Unspecified`] or [`Applicability::MaybeIncorrect`]. static FUNCTION_REPLACEMENTS: &[(&str, Option<&str>)] = &[ ("once_cell::sync::Lazy::force", Some("std::sync::LazyLock::force")), ("once_cell::sync::Lazy::get", None), // `std::sync::LazyLock::get` is experimental From 18a13b15fe1ebac207eb9efe789dfa717b4c4a97 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Mon, 28 Jul 2025 15:20:27 +0200 Subject: [PATCH 26/65] Do not treat NixOS as a Pascal-cased identifier --- book/src/lint_configuration.md | 2 +- clippy_config/src/conf.rs | 2 +- tests/ui/doc/doc-fixable.fixed | 2 +- tests/ui/doc/doc-fixable.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 992ed2c6aaaa..7f16f3a98105 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -555,7 +555,7 @@ default configuration of Clippy. By default, any configuration will replace the * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`. * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. -**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` +**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` --- **Affected lints:** diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 555f54bcfb8b..8167d75583ee 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -44,7 +44,7 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[ "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", - "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", + "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase", diff --git a/tests/ui/doc/doc-fixable.fixed b/tests/ui/doc/doc-fixable.fixed index 8cf20d8b1a11..bbbd5973036e 100644 --- a/tests/ui/doc/doc-fixable.fixed +++ b/tests/ui/doc/doc-fixable.fixed @@ -83,7 +83,7 @@ fn test_units() { /// WebGL WebGL2 WebGPU WebRTC WebSocket WebTransport /// TensorFlow /// TrueType -/// iOS macOS FreeBSD NetBSD OpenBSD +/// iOS macOS FreeBSD NetBSD OpenBSD NixOS /// TeX LaTeX BibTeX BibLaTeX /// MinGW /// CamelCase (see also #2395) diff --git a/tests/ui/doc/doc-fixable.rs b/tests/ui/doc/doc-fixable.rs index 5b6f2bd8330c..1077d3580d3c 100644 --- a/tests/ui/doc/doc-fixable.rs +++ b/tests/ui/doc/doc-fixable.rs @@ -83,7 +83,7 @@ fn test_units() { /// WebGL WebGL2 WebGPU WebRTC WebSocket WebTransport /// TensorFlow /// TrueType -/// iOS macOS FreeBSD NetBSD OpenBSD +/// iOS macOS FreeBSD NetBSD OpenBSD NixOS /// TeX LaTeX BibTeX BibLaTeX /// MinGW /// CamelCase (see also #2395) From 938e79fdac508c1cca01743b85cff11ed5f7aab6 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Sun, 27 Jul 2025 22:11:36 +0800 Subject: [PATCH 27/65] fix: `cast-lossless` should not suggest when casting type is from macro input --- clippy_lints/src/casts/cast_lossless.rs | 6 ++++++ tests/ui/cast_lossless_integer_unfixable.rs | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 tests/ui/cast_lossless_integer_unfixable.rs diff --git a/clippy_lints/src/casts/cast_lossless.rs b/clippy_lints/src/casts/cast_lossless.rs index c1d6cec1b62e..c924fba6b5c8 100644 --- a/clippy_lints/src/casts/cast_lossless.rs +++ b/clippy_lints/src/casts/cast_lossless.rs @@ -25,6 +25,12 @@ pub(super) fn check( return; } + // If the `as` is from a macro and the casting type is from macro input, whether it is lossless is + // dependent on the input + if expr.span.from_expansion() && !cast_to_hir.span.eq_ctxt(expr.span) { + return; + } + span_lint_and_then( cx, CAST_LOSSLESS, diff --git a/tests/ui/cast_lossless_integer_unfixable.rs b/tests/ui/cast_lossless_integer_unfixable.rs new file mode 100644 index 000000000000..db9cbbb5b663 --- /dev/null +++ b/tests/ui/cast_lossless_integer_unfixable.rs @@ -0,0 +1,17 @@ +//@check-pass +#![warn(clippy::cast_lossless)] + +fn issue15348() { + macro_rules! zero { + ($int:ty) => {{ + let data: [u8; 3] = [0, 0, 0]; + data[0] as $int + }}; + } + + let _ = zero!(u8); + let _ = zero!(u16); + let _ = zero!(u32); + let _ = zero!(u64); + let _ = zero!(u128); +} From b0740198ae5fe909d814a6fb4eb25c300cfb311a Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Thu, 24 Jul 2025 19:05:31 -0500 Subject: [PATCH 28/65] Rename trait_of_item -> trait_of_assoc --- clippy_lints/src/dereference.rs | 2 +- clippy_lints/src/loops/needless_range_loop.rs | 2 +- clippy_lints/src/methods/clone_on_copy.rs | 2 +- clippy_lints/src/methods/iter_overeager_cloned.rs | 4 ++-- clippy_lints/src/methods/manual_str_repeat.rs | 2 +- clippy_lints/src/methods/map_clone.rs | 2 +- clippy_lints/src/methods/open_options.rs | 2 +- clippy_lints/src/methods/str_splitn.rs | 6 +++--- .../src/methods/unnecessary_fallible_conversions.rs | 2 +- clippy_lints/src/methods/unnecessary_to_owned.rs | 2 +- clippy_lints/src/methods/useless_asref.rs | 2 +- clippy_lints/src/operators/cmp_owned.rs | 2 +- clippy_lints/src/ranges.rs | 2 +- clippy_lints/src/unconditional_recursion.rs | 8 ++++---- clippy_lints/src/unused_io_amount.rs | 2 +- clippy_utils/src/lib.rs | 4 ++-- clippy_utils/src/qualify_min_const_fn.rs | 2 +- 17 files changed, 24 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 5099df3fa023..995a1209595e 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -364,7 +364,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { // * `&self` methods on `&T` can have auto-borrow, but `&self` methods on `T` will take // priority. if let Some(fn_id) = typeck.type_dependent_def_id(hir_id) - && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) + && let Some(trait_id) = cx.tcx.trait_of_assoc(fn_id) && let arg_ty = cx.tcx.erase_regions(adjusted_ty) && let ty::Ref(_, sub_ty, _) = *arg_ty.kind() && let args = diff --git a/clippy_lints/src/loops/needless_range_loop.rs b/clippy_lints/src/loops/needless_range_loop.rs index 972b0b110e0e..7bb684d65bb4 100644 --- a/clippy_lints/src/loops/needless_range_loop.rs +++ b/clippy_lints/src/loops/needless_range_loop.rs @@ -317,7 +317,7 @@ impl<'tcx> Visitor<'tcx> for VarVisitor<'_, 'tcx> { .cx .typeck_results() .type_dependent_def_id(expr.hir_id) - .and_then(|def_id| self.cx.tcx.trait_of_item(def_id)) + .and_then(|def_id| self.cx.tcx.trait_of_assoc(def_id)) && ((meth.ident.name == sym::index && self.cx.tcx.lang_items().index_trait() == Some(trait_id)) || (meth.ident.name == sym::index_mut && self.cx.tcx.lang_items().index_mut_trait() == Some(trait_id))) && !self.check(args_1, args_0, expr) diff --git a/clippy_lints/src/methods/clone_on_copy.rs b/clippy_lints/src/methods/clone_on_copy.rs index 2ecf3eb89798..0a456d1057ad 100644 --- a/clippy_lints/src/methods/clone_on_copy.rs +++ b/clippy_lints/src/methods/clone_on_copy.rs @@ -28,7 +28,7 @@ pub(super) fn check( if cx .typeck_results() .type_dependent_def_id(expr.hir_id) - .and_then(|id| cx.tcx.trait_of_item(id)) + .and_then(|id| cx.tcx.trait_of_assoc(id)) .zip(cx.tcx.lang_items().clone_trait()) .is_none_or(|(x, y)| x != y) { diff --git a/clippy_lints/src/methods/iter_overeager_cloned.rs b/clippy_lints/src/methods/iter_overeager_cloned.rs index f5fe4316eb0d..f851ebe91f37 100644 --- a/clippy_lints/src/methods/iter_overeager_cloned.rs +++ b/clippy_lints/src/methods/iter_overeager_cloned.rs @@ -44,9 +44,9 @@ pub(super) fn check<'tcx>( let typeck = cx.typeck_results(); if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator) && let Some(method_id) = typeck.type_dependent_def_id(expr.hir_id) - && cx.tcx.trait_of_item(method_id) == Some(iter_id) + && cx.tcx.trait_of_assoc(method_id) == Some(iter_id) && let Some(method_id) = typeck.type_dependent_def_id(cloned_call.hir_id) - && cx.tcx.trait_of_item(method_id) == Some(iter_id) + && cx.tcx.trait_of_assoc(method_id) == Some(iter_id) && let cloned_recv_ty = typeck.expr_ty_adjusted(cloned_recv) && let Some(iter_assoc_ty) = cx.get_associated_type(cloned_recv_ty, iter_id, sym::Item) && matches!(*iter_assoc_ty.kind(), ty::Ref(_, ty, _) if !is_copy(cx, ty)) diff --git a/clippy_lints/src/methods/manual_str_repeat.rs b/clippy_lints/src/methods/manual_str_repeat.rs index 8167e4f96053..a811dd1cee18 100644 --- a/clippy_lints/src/methods/manual_str_repeat.rs +++ b/clippy_lints/src/methods/manual_str_repeat.rs @@ -59,7 +59,7 @@ pub(super) fn check( && is_type_lang_item(cx, cx.typeck_results().expr_ty(collect_expr), LangItem::String) && let Some(take_id) = cx.typeck_results().type_dependent_def_id(take_expr.hir_id) && let Some(iter_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator) - && cx.tcx.trait_of_item(take_id) == Some(iter_trait_id) + && cx.tcx.trait_of_assoc(take_id) == Some(iter_trait_id) && let Some(repeat_kind) = parse_repeat_arg(cx, repeat_arg) && let ctxt = collect_expr.span.ctxt() && ctxt == take_expr.span.ctxt() diff --git a/clippy_lints/src/methods/map_clone.rs b/clippy_lints/src/methods/map_clone.rs index 333a33f7527d..d016ace28bd4 100644 --- a/clippy_lints/src/methods/map_clone.rs +++ b/clippy_lints/src/methods/map_clone.rs @@ -69,7 +69,7 @@ pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_ hir::ExprKind::MethodCall(method, obj, [], _) => { if ident_eq(name, obj) && method.ident.name == sym::clone && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id) - && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) + && let Some(trait_id) = cx.tcx.trait_of_assoc(fn_id) && cx.tcx.lang_items().clone_trait() == Some(trait_id) // no autoderefs && !cx.typeck_results().expr_adjustments(obj).iter() diff --git a/clippy_lints/src/methods/open_options.rs b/clippy_lints/src/methods/open_options.rs index 9b5f138295c3..87e6cf021023 100644 --- a/clippy_lints/src/methods/open_options.rs +++ b/clippy_lints/src/methods/open_options.rs @@ -111,7 +111,7 @@ fn get_open_options( // This might be a user defined extension trait with a method like `truncate_write` // which would be a false positive if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(argument.hir_id) - && cx.tcx.trait_of_item(method_def_id).is_some() + && cx.tcx.trait_of_assoc(method_def_id).is_some() { return false; } diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints/src/methods/str_splitn.rs index 6f78d6c61281..51dd4ac313a6 100644 --- a/clippy_lints/src/methods/str_splitn.rs +++ b/clippy_lints/src/methods/str_splitn.rs @@ -286,7 +286,7 @@ fn parse_iter_usage<'tcx>( let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?; match (name.ident.name, args) { - (sym::next, []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span), + (sym::next, []) if cx.tcx.trait_of_assoc(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span), (sym::next_tuple, []) => { return if paths::ITERTOOLS_NEXT_TUPLE.matches(cx, did) && let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind() @@ -303,7 +303,7 @@ fn parse_iter_usage<'tcx>( None }; }, - (sym::nth | sym::skip, [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => { + (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) { let span = if name.ident.as_str() == "nth" { e.span @@ -312,7 +312,7 @@ fn parse_iter_usage<'tcx>( && next_name.ident.name == sym::next && next_expr.span.ctxt() == ctxt && let Some(next_id) = cx.typeck_results().type_dependent_def_id(next_expr.hir_id) - && cx.tcx.trait_of_item(next_id) == Some(iter_id) + && cx.tcx.trait_of_assoc(next_id) == Some(iter_id) { next_expr.span } else { diff --git a/clippy_lints/src/methods/unnecessary_fallible_conversions.rs b/clippy_lints/src/methods/unnecessary_fallible_conversions.rs index ce81282ddfeb..0ec2d8b4fc31 100644 --- a/clippy_lints/src/methods/unnecessary_fallible_conversions.rs +++ b/clippy_lints/src/methods/unnecessary_fallible_conversions.rs @@ -165,7 +165,7 @@ pub(super) fn check_method(cx: &LateContext<'_>, expr: &Expr<'_>) { pub(super) fn check_function(cx: &LateContext<'_>, expr: &Expr<'_>, callee: &Expr<'_>) { if let ExprKind::Path(ref qpath) = callee.kind && let Some(item_def_id) = cx.qpath_res(qpath, callee.hir_id).opt_def_id() - && let Some(trait_def_id) = cx.tcx.trait_of_item(item_def_id) + && let Some(trait_def_id) = cx.tcx.trait_of_assoc(item_def_id) { let qpath_spans = match qpath { QPath::Resolved(_, path) => { diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index 769526d131bf..a86428808216 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -734,7 +734,7 @@ fn check_if_applicable_to_argument<'tcx>(cx: &LateContext<'tcx>, arg: &Expr<'tcx fn check_borrow_predicate<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { if let ExprKind::MethodCall(_, caller, &[arg], _) = expr.kind && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) - && cx.tcx.trait_of_item(method_def_id).is_none() + && cx.tcx.trait_of_assoc(method_def_id).is_none() && let Some(borrow_id) = cx.tcx.get_diagnostic_item(sym::Borrow) && cx.tcx.predicates_of(method_def_id).predicates.iter().any(|(pred, _)| { if let ClauseKind::Trait(trait_pred) = pred.kind().skip_binder() diff --git a/clippy_lints/src/methods/useless_asref.rs b/clippy_lints/src/methods/useless_asref.rs index d30c12e0c483..d9cf8273fada 100644 --- a/clippy_lints/src/methods/useless_asref.rs +++ b/clippy_lints/src/methods/useless_asref.rs @@ -131,7 +131,7 @@ fn is_calling_clone(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool { hir::ExprKind::MethodCall(method, obj, [], _) => { if method.ident.name == sym::clone && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id) - && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) + && let Some(trait_id) = cx.tcx.trait_of_assoc(fn_id) // We check it's the `Clone` trait. && cx.tcx.lang_items().clone_trait().is_some_and(|id| id == trait_id) // no autoderefs diff --git a/clippy_lints/src/operators/cmp_owned.rs b/clippy_lints/src/operators/cmp_owned.rs index 9b2cfd91b853..22ec4fe60fb0 100644 --- a/clippy_lints/src/operators/cmp_owned.rs +++ b/clippy_lints/src/operators/cmp_owned.rs @@ -41,7 +41,7 @@ fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) ExprKind::MethodCall(_, arg, [], _) if typeck .type_dependent_def_id(expr.hir_id) - .and_then(|id| cx.tcx.trait_of_item(id)) + .and_then(|id| cx.tcx.trait_of_assoc(id)) .is_some_and(|id| matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ToString | sym::ToOwned))) => { (arg, arg.span) diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index 9281678b3d83..03d00ba849f3 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -380,7 +380,7 @@ fn can_switch_ranges<'tcx>( if let ExprKind::MethodCall(_, receiver, _, _) = parent_expr.kind && receiver.hir_id == use_ctxt.child_id && let Some(method_did) = cx.typeck_results().type_dependent_def_id(parent_expr.hir_id) - && let Some(trait_did) = cx.tcx.trait_of_item(method_did) + && let Some(trait_did) = cx.tcx.trait_of_assoc(method_did) && matches!( cx.tcx.get_diagnostic_name(trait_did), Some(sym::Iterator | sym::IntoIterator | sym::RangeBounds) diff --git a/clippy_lints/src/unconditional_recursion.rs b/clippy_lints/src/unconditional_recursion.rs index d321c48f6aff..dcddff557d1c 100644 --- a/clippy_lints/src/unconditional_recursion.rs +++ b/clippy_lints/src/unconditional_recursion.rs @@ -206,7 +206,7 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca let arg_ty = cx.typeck_results().expr_ty_adjusted(arg); if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) - && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) + && let Some(trait_id) = cx.tcx.trait_of_assoc(fn_id) && trait_id == trait_def_id && matches_ty(receiver_ty, arg_ty, self_arg, other_arg) { @@ -250,7 +250,7 @@ fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: Local let is_bad = match expr.kind { ExprKind::MethodCall(segment, _receiver, &[_arg], _) if segment.ident.name == name.name => { if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) - && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) + && let Some(trait_id) = cx.tcx.trait_of_assoc(fn_id) && trait_id == trait_def_id { true @@ -318,7 +318,7 @@ where && let ExprKind::Path(qpath) = f.kind && is_default_method_on_current_ty(self.cx.tcx, qpath, self.implemented_ty_id) && let Some(method_def_id) = path_def_id(self.cx, f) - && let Some(trait_def_id) = self.cx.tcx.trait_of_item(method_def_id) + && let Some(trait_def_id) = self.cx.tcx.trait_of_assoc(method_def_id) && self.cx.tcx.is_diagnostic_item(sym::Default, trait_def_id) { span_error(self.cx, self.method_span, expr); @@ -426,7 +426,7 @@ fn check_from(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId if let Some((fn_def_id, node_args)) = fn_def_id_with_node_args(cx, expr) && let [s1, s2] = **node_args && let (Some(s1), Some(s2)) = (s1.as_type(), s2.as_type()) - && let Some(trait_def_id) = cx.tcx.trait_of_item(fn_def_id) + && let Some(trait_def_id) = cx.tcx.trait_of_assoc(fn_def_id) && cx.tcx.is_diagnostic_item(sym::Into, trait_def_id) && get_impl_trait_def_id(cx, method_def_id) == cx.tcx.get_diagnostic_item(sym::From) && s1 == sig.inputs()[0] diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs index 12cc10938993..bed5c0fc0150 100644 --- a/clippy_lints/src/unused_io_amount.rs +++ b/clippy_lints/src/unused_io_amount.rs @@ -300,7 +300,7 @@ fn check_io_mode(cx: &LateContext<'_>, call: &hir::Expr<'_>) -> Option { }; if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(call.hir_id) - && let Some(trait_def_id) = cx.tcx.trait_of_item(method_def_id) + && let Some(trait_def_id) = cx.tcx.trait_of_assoc(method_def_id) { if let Some(diag_name) = cx.tcx.get_diagnostic_name(trait_def_id) { match diag_name { diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index ce5af4d2f482..211af4d5b089 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -349,7 +349,7 @@ pub fn is_ty_alias(qpath: &QPath<'_>) -> bool { /// Checks if the given method call expression calls an inherent method. pub fn is_inherent_method_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { - cx.tcx.trait_of_item(method_id).is_none() + cx.tcx.trait_of_assoc(method_id).is_none() } else { false } @@ -367,7 +367,7 @@ pub fn is_diag_item_method(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbo /// Checks if a method is in a diagnostic item trait pub fn is_diag_trait_item(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool { - if let Some(trait_did) = cx.tcx.trait_of_item(def_id) { + if let Some(trait_did) = cx.tcx.trait_of_assoc(def_id) { return cx.tcx.is_diagnostic_item(diag_item, trait_did); } false diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index b3356450d387..11c17a77b154 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -420,7 +420,7 @@ pub fn is_stable_const_fn(cx: &LateContext<'_>, def_id: DefId, msrv: Msrv) -> bo .lookup_const_stability(def_id) .or_else(|| { cx.tcx - .trait_of_item(def_id) + .trait_of_assoc(def_id) .and_then(|trait_def_id| cx.tcx.lookup_const_stability(trait_def_id)) }) .is_none_or(|const_stab| { From 86ff11e729e780f533030208f1c0ab25e52e6f47 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Thu, 24 Jul 2025 19:09:05 -0500 Subject: [PATCH 29/65] Rename impl_of_method -> impl_of_assoc --- clippy_lints/src/assigning_clones.rs | 2 +- clippy_lints/src/casts/cast_ptr_alignment.rs | 2 +- clippy_lints/src/casts/confusing_method_to_numeric_cast.rs | 2 +- clippy_lints/src/implicit_saturating_sub.rs | 4 ++-- clippy_lints/src/methods/bytes_count_to_len.rs | 2 +- .../src/methods/case_sensitive_file_extension_comparisons.rs | 2 +- clippy_lints/src/methods/get_first.rs | 2 +- clippy_lints/src/methods/implicit_clone.rs | 2 +- clippy_lints/src/methods/manual_ok_or.rs | 2 +- clippy_lints/src/methods/map_clone.rs | 2 +- clippy_lints/src/methods/map_err_ignore.rs | 2 +- clippy_lints/src/methods/mut_mutex_lock.rs | 2 +- clippy_lints/src/methods/open_options.rs | 2 +- clippy_lints/src/methods/path_buf_push_overwrite.rs | 2 +- clippy_lints/src/methods/stable_sort_primitive.rs | 2 +- clippy_lints/src/methods/suspicious_splitn.rs | 2 +- clippy_lints/src/methods/unnecessary_sort_by.rs | 2 +- clippy_lints/src/methods/unnecessary_to_owned.rs | 2 +- clippy_lints/src/methods/useless_asref.rs | 2 +- clippy_lints/src/methods/vec_resize_to_zero.rs | 2 +- clippy_lints/src/operators/op_ref.rs | 2 +- clippy_lints/src/return_self_not_must_use.rs | 2 +- clippy_lints/src/unused_io_amount.rs | 2 +- clippy_utils/src/eager_or_lazy.rs | 2 +- clippy_utils/src/lib.rs | 4 ++-- 25 files changed, 27 insertions(+), 27 deletions(-) diff --git a/clippy_lints/src/assigning_clones.rs b/clippy_lints/src/assigning_clones.rs index 8b8b42bbf722..52287be34c78 100644 --- a/clippy_lints/src/assigning_clones.rs +++ b/clippy_lints/src/assigning_clones.rs @@ -98,7 +98,7 @@ impl<'tcx> LateLintPass<'tcx> for AssigningClones { // That is overly conservative - the lint should fire even if there was no initializer, // but the variable has been initialized before `lhs` was evaluated. && path_to_local(lhs).is_none_or(|lhs| local_is_initialized(cx, lhs)) - && let Some(resolved_impl) = cx.tcx.impl_of_method(resolved_fn.def_id()) + && let Some(resolved_impl) = cx.tcx.impl_of_assoc(resolved_fn.def_id()) // Derived forms don't implement `clone_from`/`clone_into`. // See https://github.com/rust-lang/rust/pull/98445#issuecomment-1190681305 && !cx.tcx.is_builtin_derived(resolved_impl) diff --git a/clippy_lints/src/casts/cast_ptr_alignment.rs b/clippy_lints/src/casts/cast_ptr_alignment.rs index e4dafde0f9df..a1543cabd2f9 100644 --- a/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -63,7 +63,7 @@ fn is_used_as_unaligned(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { ExprKind::MethodCall(name, self_arg, ..) if self_arg.hir_id == e.hir_id => { if matches!(name.ident.name, sym::read_unaligned | sym::write_unaligned) && let Some(def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id) - && let Some(def_id) = cx.tcx.impl_of_method(def_id) + && let Some(def_id) = cx.tcx.impl_of_assoc(def_id) && cx.tcx.type_of(def_id).instantiate_identity().is_raw_ptr() { true diff --git a/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs b/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs index 849de22cfbaa..73347e7141eb 100644 --- a/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs +++ b/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs @@ -37,7 +37,7 @@ fn get_const_name_and_ty_name( } else { return None; } - } else if let Some(impl_id) = cx.tcx.impl_of_method(method_def_id) + } else if let Some(impl_id) = cx.tcx.impl_of_assoc(method_def_id) && let Some(ty_name) = get_primitive_ty_name(cx.tcx.type_of(impl_id).instantiate_identity()) && matches!( method_name, diff --git a/clippy_lints/src/implicit_saturating_sub.rs b/clippy_lints/src/implicit_saturating_sub.rs index c743501da253..c634c12e1877 100644 --- a/clippy_lints/src/implicit_saturating_sub.rs +++ b/clippy_lints/src/implicit_saturating_sub.rs @@ -339,7 +339,7 @@ fn check_with_condition<'tcx>( ExprKind::Path(QPath::TypeRelative(_, name)) => { if name.ident.name == sym::MIN && let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_method(const_id) + && let Some(impl_id) = cx.tcx.impl_of_assoc(const_id) && let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl && cx.tcx.type_of(impl_id).instantiate_identity().is_integral() { @@ -350,7 +350,7 @@ fn check_with_condition<'tcx>( if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind && name.ident.name == sym::min_value && let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_method(func_id) + && let Some(impl_id) = cx.tcx.impl_of_assoc(func_id) && let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl && cx.tcx.type_of(impl_id).instantiate_identity().is_integral() { diff --git a/clippy_lints/src/methods/bytes_count_to_len.rs b/clippy_lints/src/methods/bytes_count_to_len.rs index a9f6a41c2357..b8cc5ddd845c 100644 --- a/clippy_lints/src/methods/bytes_count_to_len.rs +++ b/clippy_lints/src/methods/bytes_count_to_len.rs @@ -14,7 +14,7 @@ pub(super) fn check<'tcx>( bytes_recv: &'tcx hir::Expr<'_>, ) { if let Some(bytes_id) = cx.typeck_results().type_dependent_def_id(count_recv.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_method(bytes_id) + && let Some(impl_id) = cx.tcx.impl_of_assoc(bytes_id) && cx.tcx.type_of(impl_id).instantiate_identity().is_str() && let ty = cx.typeck_results().expr_ty(bytes_recv).peel_refs() && (ty.is_str() || is_type_lang_item(cx, ty, hir::LangItem::String)) diff --git a/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs b/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs index 292fa08b5984..6f9702f6c6c3 100644 --- a/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs +++ b/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs @@ -30,7 +30,7 @@ pub(super) fn check<'tcx>( } if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_method(method_id) + && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) && cx.tcx.type_of(impl_id).instantiate_identity().is_str() && let ExprKind::Lit(Spanned { node: LitKind::Str(ext_literal, ..), diff --git a/clippy_lints/src/methods/get_first.rs b/clippy_lints/src/methods/get_first.rs index f4465e654c2e..2e1d71ce284d 100644 --- a/clippy_lints/src/methods/get_first.rs +++ b/clippy_lints/src/methods/get_first.rs @@ -18,7 +18,7 @@ pub(super) fn check<'tcx>( arg: &'tcx hir::Expr<'_>, ) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_method(method_id) + && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) && let identity = cx.tcx.type_of(impl_id).instantiate_identity() && let hir::ExprKind::Lit(Spanned { node: LitKind::Int(Pu128(0), _), diff --git a/clippy_lints/src/methods/implicit_clone.rs b/clippy_lints/src/methods/implicit_clone.rs index 9724463f0c08..efa8cee58df7 100644 --- a/clippy_lints/src/methods/implicit_clone.rs +++ b/clippy_lints/src/methods/implicit_clone.rs @@ -50,7 +50,7 @@ pub fn is_clone_like(cx: &LateContext<'_>, method_name: Symbol, method_def_id: h sym::to_path_buf => is_diag_item_method(cx, method_def_id, sym::Path), sym::to_vec => cx .tcx - .impl_of_method(method_def_id) + .impl_of_assoc(method_def_id) .filter(|&impl_did| { cx.tcx.type_of(impl_did).instantiate_identity().is_slice() && cx.tcx.impl_trait_ref(impl_did).is_none() }) diff --git a/clippy_lints/src/methods/manual_ok_or.rs b/clippy_lints/src/methods/manual_ok_or.rs index c286c5faaed3..077957fa44dc 100644 --- a/clippy_lints/src/methods/manual_ok_or.rs +++ b/clippy_lints/src/methods/manual_ok_or.rs @@ -18,7 +18,7 @@ pub(super) fn check<'tcx>( map_expr: &'tcx Expr<'_>, ) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_method(method_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::Option) && let ExprKind::Call(err_path, [err_arg]) = or_expr.kind && is_res_lang_ctor(cx, path_res(cx, err_path), ResultErr) diff --git a/clippy_lints/src/methods/map_clone.rs b/clippy_lints/src/methods/map_clone.rs index d016ace28bd4..748be9bfcc62 100644 --- a/clippy_lints/src/methods/map_clone.rs +++ b/clippy_lints/src/methods/map_clone.rs @@ -23,7 +23,7 @@ fn should_run_lint(cx: &LateContext<'_>, e: &hir::Expr<'_>, method_id: DefId) -> return true; } // We check if it's an `Option` or a `Result`. - if let Some(id) = cx.tcx.impl_of_method(method_id) { + if let Some(id) = cx.tcx.impl_of_assoc(method_id) { let identity = cx.tcx.type_of(id).instantiate_identity(); if !is_type_diagnostic_item(cx, identity, sym::Option) && !is_type_diagnostic_item(cx, identity, sym::Result) { return false; diff --git a/clippy_lints/src/methods/map_err_ignore.rs b/clippy_lints/src/methods/map_err_ignore.rs index 5d0d4dae35fa..41beda9c5cb4 100644 --- a/clippy_lints/src/methods/map_err_ignore.rs +++ b/clippy_lints/src/methods/map_err_ignore.rs @@ -8,7 +8,7 @@ use super::MAP_ERR_IGNORE; pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, arg: &Expr<'_>) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_method(method_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::Result) && let ExprKind::Closure(&Closure { capture_clause: CaptureBy::Ref, diff --git a/clippy_lints/src/methods/mut_mutex_lock.rs b/clippy_lints/src/methods/mut_mutex_lock.rs index 320523aceb67..4235af882b0c 100644 --- a/clippy_lints/src/methods/mut_mutex_lock.rs +++ b/clippy_lints/src/methods/mut_mutex_lock.rs @@ -13,7 +13,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>, recv: &' && let (_, ref_depth, Mutability::Mut) = peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(recv)) && ref_depth >= 1 && let Some(method_id) = cx.typeck_results().type_dependent_def_id(ex.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_method(method_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) { span_lint_and_sugg( diff --git a/clippy_lints/src/methods/open_options.rs b/clippy_lints/src/methods/open_options.rs index 87e6cf021023..37a8e25bef96 100644 --- a/clippy_lints/src/methods/open_options.rs +++ b/clippy_lints/src/methods/open_options.rs @@ -18,7 +18,7 @@ fn is_open_options(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_method(method_id) + && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) && is_open_options(cx, cx.tcx.type_of(impl_id).instantiate_identity()) { let mut options = Vec::new(); diff --git a/clippy_lints/src/methods/path_buf_push_overwrite.rs b/clippy_lints/src/methods/path_buf_push_overwrite.rs index 38d9c5f16778..32752ef7435f 100644 --- a/clippy_lints/src/methods/path_buf_push_overwrite.rs +++ b/clippy_lints/src/methods/path_buf_push_overwrite.rs @@ -11,7 +11,7 @@ use super::PATH_BUF_PUSH_OVERWRITE; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_method(method_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::PathBuf) && let ExprKind::Lit(lit) = arg.kind && let LitKind::Str(ref path_lit, _) = lit.node diff --git a/clippy_lints/src/methods/stable_sort_primitive.rs b/clippy_lints/src/methods/stable_sort_primitive.rs index aef14435d8af..17d1a6abde0a 100644 --- a/clippy_lints/src/methods/stable_sort_primitive.rs +++ b/clippy_lints/src/methods/stable_sort_primitive.rs @@ -9,7 +9,7 @@ use super::STABLE_SORT_PRIMITIVE; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_method(method_id) + && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) && cx.tcx.type_of(impl_id).instantiate_identity().is_slice() && let Some(slice_type) = is_slice_of_primitives(cx, recv) { diff --git a/clippy_lints/src/methods/suspicious_splitn.rs b/clippy_lints/src/methods/suspicious_splitn.rs index f8b6d4349fbe..9876681ddbb3 100644 --- a/clippy_lints/src/methods/suspicious_splitn.rs +++ b/clippy_lints/src/methods/suspicious_splitn.rs @@ -10,7 +10,7 @@ use super::SUSPICIOUS_SPLITN; pub(super) fn check(cx: &LateContext<'_>, method_name: Symbol, expr: &Expr<'_>, self_arg: &Expr<'_>, count: u128) { if count <= 1 && let Some(call_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_method(call_id) + && let Some(impl_id) = cx.tcx.impl_of_assoc(call_id) && cx.tcx.impl_trait_ref(impl_id).is_none() && let self_ty = cx.tcx.type_of(impl_id).instantiate_identity() && (self_ty.is_slice() || self_ty.is_str()) diff --git a/clippy_lints/src/methods/unnecessary_sort_by.rs b/clippy_lints/src/methods/unnecessary_sort_by.rs index dbff08bc51c9..1de9f6ab4970 100644 --- a/clippy_lints/src/methods/unnecessary_sort_by.rs +++ b/clippy_lints/src/methods/unnecessary_sort_by.rs @@ -114,7 +114,7 @@ fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: &Ident, b_expr: &Expr<'_>, b_ident fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) -> Option { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_method(method_id) + && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) && cx.tcx.type_of(impl_id).instantiate_identity().is_slice() && let ExprKind::Closure(&Closure { body, .. }) = arg.kind && let closure_body = cx.tcx.hir_body(body) diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index a86428808216..54f45263275c 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -694,7 +694,7 @@ fn check_if_applicable_to_argument<'tcx>(cx: &LateContext<'tcx>, arg: &Expr<'tcx sym::to_string => cx.tcx.is_diagnostic_item(sym::to_string_method, method_def_id), sym::to_vec => cx .tcx - .impl_of_method(method_def_id) + .impl_of_assoc(method_def_id) .filter(|&impl_did| cx.tcx.type_of(impl_did).instantiate_identity().is_slice()) .is_some(), _ => false, diff --git a/clippy_lints/src/methods/useless_asref.rs b/clippy_lints/src/methods/useless_asref.rs index d9cf8273fada..38fad239f679 100644 --- a/clippy_lints/src/methods/useless_asref.rs +++ b/clippy_lints/src/methods/useless_asref.rs @@ -79,7 +79,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: Symbo applicability, ); } - } else if let Some(impl_id) = cx.tcx.impl_of_method(def_id) + } else if let Some(impl_id) = cx.tcx.impl_of_assoc(def_id) && let Some(adt) = cx.tcx.type_of(impl_id).instantiate_identity().ty_adt_def() && matches!(cx.tcx.get_diagnostic_name(adt.did()), Some(sym::Option | sym::Result)) { diff --git a/clippy_lints/src/methods/vec_resize_to_zero.rs b/clippy_lints/src/methods/vec_resize_to_zero.rs index 5ea4ada128a2..bfb481f4fc09 100644 --- a/clippy_lints/src/methods/vec_resize_to_zero.rs +++ b/clippy_lints/src/methods/vec_resize_to_zero.rs @@ -18,7 +18,7 @@ pub(super) fn check<'tcx>( name_span: Span, ) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_method(method_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::Vec) && let ExprKind::Lit(Spanned { node: LitKind::Int(Pu128(0), _), diff --git a/clippy_lints/src/operators/op_ref.rs b/clippy_lints/src/operators/op_ref.rs index 21e1ab0f4f27..0a1f2625f4cf 100644 --- a/clippy_lints/src/operators/op_ref.rs +++ b/clippy_lints/src/operators/op_ref.rs @@ -179,7 +179,7 @@ fn in_impl<'tcx>( bin_op: DefId, ) -> Option<(&'tcx rustc_hir::Ty<'tcx>, &'tcx rustc_hir::Ty<'tcx>)> { if let Some(block) = get_enclosing_block(cx, e.hir_id) - && let Some(impl_def_id) = cx.tcx.impl_of_method(block.hir_id.owner.to_def_id()) + && let Some(impl_def_id) = cx.tcx.impl_of_assoc(block.hir_id.owner.to_def_id()) && let item = cx.tcx.hir_expect_item(impl_def_id.expect_local()) && let ItemKind::Impl(item) = &item.kind && let Some(of_trait) = &item.of_trait diff --git a/clippy_lints/src/return_self_not_must_use.rs b/clippy_lints/src/return_self_not_must_use.rs index 25929b853af8..3497216d1c56 100644 --- a/clippy_lints/src/return_self_not_must_use.rs +++ b/clippy_lints/src/return_self_not_must_use.rs @@ -113,7 +113,7 @@ impl<'tcx> LateLintPass<'tcx> for ReturnSelfNotMustUse { ) { if matches!(kind, FnKind::Method(_, _)) // We are only interested in methods, not in functions or associated functions. - && let Some(impl_def) = cx.tcx.impl_of_method(fn_def.to_def_id()) + && let Some(impl_def) = cx.tcx.impl_of_assoc(fn_def.to_def_id()) // We don't want this method to be te implementation of a trait because the // `#[must_use]` should be put on the trait definition directly. && cx.tcx.trait_id_of_impl(impl_def).is_none() diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs index bed5c0fc0150..f3cd3f1bb286 100644 --- a/clippy_lints/src/unused_io_amount.rs +++ b/clippy_lints/src/unused_io_amount.rs @@ -84,7 +84,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount { /// get desugared to match. fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'tcx>) { let fn_def_id = block.hir_id.owner.to_def_id(); - if let Some(impl_id) = cx.tcx.impl_of_method(fn_def_id) + if let Some(impl_id) = cx.tcx.impl_of_assoc(fn_def_id) && let Some(trait_id) = cx.tcx.trait_id_of_impl(impl_id) { // We don't want to lint inside io::Read or io::Write implementations, as the author has more diff --git a/clippy_utils/src/eager_or_lazy.rs b/clippy_utils/src/eager_or_lazy.rs index 9d38672efada..eb3f442ac754 100644 --- a/clippy_utils/src/eager_or_lazy.rs +++ b/clippy_utils/src/eager_or_lazy.rs @@ -51,7 +51,7 @@ impl ops::BitOrAssign for EagernessSuggestion { fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg: bool) -> EagernessSuggestion { use EagernessSuggestion::{Eager, Lazy, NoChange}; - let ty = match cx.tcx.impl_of_method(fn_id) { + let ty = match cx.tcx.impl_of_assoc(fn_id) { Some(id) => cx.tcx.type_of(id).instantiate_identity(), None => return Lazy, }; diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 211af4d5b089..67e09e772a77 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -357,7 +357,7 @@ pub fn is_inherent_method_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { /// Checks if a method is defined in an impl of a diagnostic item pub fn is_diag_item_method(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool { - if let Some(impl_did) = cx.tcx.impl_of_method(def_id) + if let Some(impl_did) = cx.tcx.impl_of_assoc(def_id) && let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def() { return cx.tcx.is_diagnostic_item(diag_item, adt.did()); @@ -620,7 +620,7 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath< if let QPath::TypeRelative(_, method) = path && method.ident.name == sym::new - && let Some(impl_did) = cx.tcx.impl_of_method(def_id) + && let Some(impl_did) = cx.tcx.impl_of_assoc(def_id) && let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def() { return std_types_symbols.iter().any(|&symbol| { From 4f1044a5a6c7b9ea1c9a46f61a4a20c8e8761fec Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Mon, 28 Jul 2025 17:09:20 +0200 Subject: [PATCH 30/65] Do not specialize for `if_chain` any longer Now that `if let` chains have been introduced, the `if_chain` external crate is no longer necessary. Dropping special support for it also alleviates the need to keep the crate as a dependency in tests. --- clippy_lints/src/index_refutable_slice.rs | 4 ++-- clippy_test_deps/Cargo.lock | 7 ------- clippy_test_deps/Cargo.toml | 1 - clippy_utils/src/sym.rs | 1 - tests/compile-test.rs | 3 +-- .../slice_indexing_in_macro.fixed | 12 +++--------- .../index_refutable_slice/slice_indexing_in_macro.rs | 12 +++--------- .../slice_indexing_in_macro.stderr | 11 +++++------ 8 files changed, 14 insertions(+), 37 deletions(-) diff --git a/clippy_lints/src/index_refutable_slice.rs b/clippy_lints/src/index_refutable_slice.rs index 3d131a7825af..8f9f71a14769 100644 --- a/clippy_lints/src/index_refutable_slice.rs +++ b/clippy_lints/src/index_refutable_slice.rs @@ -4,7 +4,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::IfLet; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::ty::is_copy; -use clippy_utils::{is_expn_of, is_lint_allowed, path_to_local, sym}; +use clippy_utils::{is_lint_allowed, path_to_local}; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_errors::Applicability; use rustc_hir as hir; @@ -71,7 +71,7 @@ impl_lint_pass!(IndexRefutableSlice => [INDEX_REFUTABLE_SLICE]); impl<'tcx> LateLintPass<'tcx> for IndexRefutableSlice { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { if let Some(IfLet { let_pat, if_then, .. }) = IfLet::hir(cx, expr) - && (!expr.span.from_expansion() || is_expn_of(expr.span, sym::if_chain).is_some()) + && !expr.span.from_expansion() && !is_lint_allowed(cx, INDEX_REFUTABLE_SLICE, expr.hir_id) && let found_slices = find_slice_values(cx, let_pat) && !found_slices.is_empty() diff --git a/clippy_test_deps/Cargo.lock b/clippy_test_deps/Cargo.lock index 5be404f24e6f..2f987c0137c9 100644 --- a/clippy_test_deps/Cargo.lock +++ b/clippy_test_deps/Cargo.lock @@ -70,7 +70,6 @@ name = "clippy_test_deps" version = "0.1.0" dependencies = [ "futures", - "if_chain", "itertools", "libc", "parking_lot", @@ -182,12 +181,6 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "if_chain" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" - [[package]] name = "io-uring" version = "0.7.8" diff --git a/clippy_test_deps/Cargo.toml b/clippy_test_deps/Cargo.toml index fcedc5d4843c..e449b48bc460 100644 --- a/clippy_test_deps/Cargo.toml +++ b/clippy_test_deps/Cargo.toml @@ -9,7 +9,6 @@ edition = "2021" libc = "0.2" regex = "1.5.5" serde = { version = "1.0.145", features = ["derive"] } -if_chain = "1.0" quote = "1.0.25" syn = { version = "2.0", features = ["full"] } futures = "0.3" diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index 934be97d94e5..ce7cc9348fbd 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -171,7 +171,6 @@ generate! { has_significant_drop, hidden_glob_reexports, hygiene, - if_chain, insert, inspect, int_roundings, diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 464efc45c6b4..664a748ee21e 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -73,8 +73,7 @@ fn internal_extern_flags() -> Vec { && INTERNAL_TEST_DEPENDENCIES.contains(&name) { // A dependency may be listed twice if it is available in sysroot, - // and the sysroot dependencies are listed first. As of the writing, - // this only seems to apply to if_chain. + // and the sysroot dependencies are listed first. crates.insert(name, path); } } diff --git a/tests/ui/index_refutable_slice/slice_indexing_in_macro.fixed b/tests/ui/index_refutable_slice/slice_indexing_in_macro.fixed index 72edc539f043..edf6f014d3d0 100644 --- a/tests/ui/index_refutable_slice/slice_indexing_in_macro.fixed +++ b/tests/ui/index_refutable_slice/slice_indexing_in_macro.fixed @@ -1,8 +1,5 @@ #![deny(clippy::index_refutable_slice)] -extern crate if_chain; -use if_chain::if_chain; - macro_rules! if_let_slice_macro { () => { // This would normally be linted @@ -18,12 +15,9 @@ fn main() { if_let_slice_macro!(); // Do lint this - if_chain! { - let slice: Option<&[u32]> = Some(&[1, 2, 3]); - if let Some([slice_0, ..]) = slice; + let slice: Option<&[u32]> = Some(&[1, 2, 3]); + if let Some([slice_0, ..]) = slice { //~^ ERROR: this binding can be a slice pattern to avoid indexing - then { - println!("{}", slice_0); - } + println!("{}", slice_0); } } diff --git a/tests/ui/index_refutable_slice/slice_indexing_in_macro.rs b/tests/ui/index_refutable_slice/slice_indexing_in_macro.rs index 7b474ba423b9..76d4a2350f50 100644 --- a/tests/ui/index_refutable_slice/slice_indexing_in_macro.rs +++ b/tests/ui/index_refutable_slice/slice_indexing_in_macro.rs @@ -1,8 +1,5 @@ #![deny(clippy::index_refutable_slice)] -extern crate if_chain; -use if_chain::if_chain; - macro_rules! if_let_slice_macro { () => { // This would normally be linted @@ -18,12 +15,9 @@ fn main() { if_let_slice_macro!(); // Do lint this - if_chain! { - let slice: Option<&[u32]> = Some(&[1, 2, 3]); - if let Some(slice) = slice; + let slice: Option<&[u32]> = Some(&[1, 2, 3]); + if let Some(slice) = slice { //~^ ERROR: this binding can be a slice pattern to avoid indexing - then { - println!("{}", slice[0]); - } + println!("{}", slice[0]); } } diff --git a/tests/ui/index_refutable_slice/slice_indexing_in_macro.stderr b/tests/ui/index_refutable_slice/slice_indexing_in_macro.stderr index 64741abb9114..635e6d19aef2 100644 --- a/tests/ui/index_refutable_slice/slice_indexing_in_macro.stderr +++ b/tests/ui/index_refutable_slice/slice_indexing_in_macro.stderr @@ -1,8 +1,8 @@ error: this binding can be a slice pattern to avoid indexing - --> tests/ui/index_refutable_slice/slice_indexing_in_macro.rs:23:21 + --> tests/ui/index_refutable_slice/slice_indexing_in_macro.rs:19:17 | -LL | if let Some(slice) = slice; - | ^^^^^ +LL | if let Some(slice) = slice { + | ^^^^^ | note: the lint level is defined here --> tests/ui/index_refutable_slice/slice_indexing_in_macro.rs:1:9 @@ -11,10 +11,9 @@ LL | #![deny(clippy::index_refutable_slice)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace the binding and indexed access with a slice pattern | -LL ~ if let Some([slice_0, ..]) = slice; +LL ~ if let Some([slice_0, ..]) = slice { LL | -LL | then { -LL ~ println!("{}", slice_0); +LL ~ println!("{}", slice_0); | error: aborting due to 1 previous error From 431f95ec85c92ff7ae1b9d54ce94a8d1411fb166 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Mon, 28 Jul 2025 17:31:21 +0200 Subject: [PATCH 31/65] Fix typo in internal error message --- tests/symbols-used.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/symbols-used.rs b/tests/symbols-used.rs index bc0456711fb2..a1049ba64d54 100644 --- a/tests/symbols-used.rs +++ b/tests/symbols-used.rs @@ -75,7 +75,7 @@ fn all_symbols_are_used() -> Result<()> { for sym in extra { eprintln!(" - {sym}"); } - Err(format!("extra symbols found — remove them {SYM_FILE}"))?; + Err(format!("extra symbols found — remove them from {SYM_FILE}"))?; } Ok(()) } From 642bec7617b8ad2cb1d3a036991e130166c4ffaf Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Mon, 28 Jul 2025 18:35:23 +0200 Subject: [PATCH 32/65] Optimize some usages of double unary operators in suggestions When an expression is made of a `!` or `-` unary operator which does not change the type of the expression, use a new variant in `Sugg` to denote it. This allows replacing an extra application of the same operator by the removal of the original operator instead. Also, when adding a unary operator to a suggestion, do not enclose the operator argument in parentheses if it starts with a unary operator itself unless it is a binary operation (including `as`), because in this case parentheses are required to not bind the lhs only. Some suggestions will now be shown as `x` instead of `!!x`. Right now, no suggestion seems to produce `--x`. --- clippy_lints/src/floating_point_arithmetic.rs | 2 +- clippy_utils/src/sugg.rs | 103 ++++++++++++------ tests/ui/nonminimal_bool.rs | 18 +++ tests/ui/nonminimal_bool.stderr | 18 ++- 4 files changed, 101 insertions(+), 40 deletions(-) diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index d5abaa547e8e..84d39dd81c91 100644 --- a/clippy_lints/src/floating_point_arithmetic.rs +++ b/clippy_lints/src/floating_point_arithmetic.rs @@ -148,7 +148,7 @@ fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Su .into(); suggestion = match suggestion { - Sugg::MaybeParen(_) => Sugg::MaybeParen(op), + Sugg::MaybeParen(_) | Sugg::UnOp(UnOp::Neg, _) => Sugg::MaybeParen(op), _ => Sugg::NonParen(op), }; } diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 88f313c5f7e5..a63333c9b48f 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -4,8 +4,8 @@ use crate::source::{snippet, snippet_opt, snippet_with_applicability, snippet_with_context}; use crate::ty::expr_sig; use crate::{get_parent_expr_for_hir, higher}; -use rustc_ast::ast; use rustc_ast::util::parser::AssocOp; +use rustc_ast::{UnOp, ast}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::{self as hir, Closure, ExprKind, HirId, MutTy, Node, TyKind}; @@ -29,6 +29,11 @@ pub enum Sugg<'a> { /// A binary operator expression, including `as`-casts and explicit type /// coercion. BinOp(AssocOp, Cow<'a, str>, Cow<'a, str>), + /// A unary operator expression. This is used to sometimes represent `!` + /// or `-`, but only if the type with and without the operator is kept identical. + /// It means that doubling the operator can be used to remove it instead, in + /// order to provide better suggestions. + UnOp(UnOp, Box>), } /// Literal constant `0`, for convenience. @@ -40,9 +45,10 @@ pub const EMPTY: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("")); impl Display for Sugg<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - match *self { - Sugg::NonParen(ref s) | Sugg::MaybeParen(ref s) => s.fmt(f), - Sugg::BinOp(op, ref lhs, ref rhs) => binop_to_string(op, lhs, rhs).fmt(f), + match self { + Sugg::NonParen(s) | Sugg::MaybeParen(s) => s.fmt(f), + Sugg::BinOp(op, lhs, rhs) => binop_to_string(*op, lhs, rhs).fmt(f), + Sugg::UnOp(op, inner) => write!(f, "{}{}", op.as_str(), inner.clone().maybe_inner_paren()), } } } @@ -100,9 +106,19 @@ impl<'a> Sugg<'a> { applicability: &mut Applicability, ) -> Self { if expr.span.ctxt() == ctxt { - Self::hir_from_snippet(expr, |span| { - snippet_with_context(cx, span, ctxt, default, applicability).0 - }) + if let ExprKind::Unary(op, inner) = expr.kind + && matches!(op, UnOp::Neg | UnOp::Not) + && cx.typeck_results().expr_ty(expr) == cx.typeck_results().expr_ty(inner) + { + Sugg::UnOp( + op, + Box::new(Self::hir_with_context(cx, inner, ctxt, default, applicability)), + ) + } else { + Self::hir_from_snippet(expr, |span| { + snippet_with_context(cx, span, ctxt, default, applicability).0 + }) + } } else { let (snip, _) = snippet_with_context(cx, expr.span, ctxt, default, applicability); Sugg::NonParen(snip) @@ -341,6 +357,7 @@ impl<'a> Sugg<'a> { let sugg = binop_to_string(op, &lhs, &rhs); Sugg::NonParen(format!("({sugg})").into()) }, + Sugg::UnOp(op, inner) => Sugg::NonParen(format!("({}{})", op.as_str(), inner.maybe_inner_paren()).into()), } } @@ -348,6 +365,26 @@ impl<'a> Sugg<'a> { match self { Sugg::NonParen(p) | Sugg::MaybeParen(p) => p.into_owned(), Sugg::BinOp(b, l, r) => binop_to_string(b, &l, &r), + Sugg::UnOp(op, inner) => format!("{}{}", op.as_str(), inner.maybe_inner_paren()), + } + } + + /// Checks if `self` starts with a unary operator. + fn starts_with_unary_op(&self) -> bool { + match self { + Sugg::UnOp(..) => true, + Sugg::BinOp(..) => false, + Sugg::MaybeParen(s) | Sugg::NonParen(s) => s.starts_with(['*', '!', '-', '&']), + } + } + + /// Call `maybe_paren` on `self` if it doesn't start with a unary operator, + /// don't touch it otherwise. + fn maybe_inner_paren(self) -> Self { + if self.starts_with_unary_op() { + self + } else { + self.maybe_paren() } } } @@ -430,10 +467,11 @@ impl Sub for &Sugg<'_> { forward_binop_impls_to_ref!(impl Add, add for Sugg<'_>, type Output = Sugg<'static>); forward_binop_impls_to_ref!(impl Sub, sub for Sugg<'_>, type Output = Sugg<'static>); -impl Neg for Sugg<'_> { - type Output = Sugg<'static>; - fn neg(self) -> Sugg<'static> { - match &self { +impl<'a> Neg for Sugg<'a> { + type Output = Sugg<'a>; + fn neg(self) -> Self::Output { + match self { + Self::UnOp(UnOp::Neg, sugg) => *sugg, Self::BinOp(AssocOp::Cast, ..) => Sugg::MaybeParen(format!("-({self})").into()), _ => make_unop("-", self), } @@ -446,19 +484,21 @@ impl<'a> Not for Sugg<'a> { use AssocOp::Binary; use ast::BinOpKind::{Eq, Ge, Gt, Le, Lt, Ne}; - if let Sugg::BinOp(op, lhs, rhs) = self { - let to_op = match op { - Binary(Eq) => Binary(Ne), - Binary(Ne) => Binary(Eq), - Binary(Lt) => Binary(Ge), - Binary(Ge) => Binary(Lt), - Binary(Gt) => Binary(Le), - Binary(Le) => Binary(Gt), - _ => return make_unop("!", Sugg::BinOp(op, lhs, rhs)), - }; - Sugg::BinOp(to_op, lhs, rhs) - } else { - make_unop("!", self) + match self { + Sugg::BinOp(op, lhs, rhs) => { + let to_op = match op { + Binary(Eq) => Binary(Ne), + Binary(Ne) => Binary(Eq), + Binary(Lt) => Binary(Ge), + Binary(Ge) => Binary(Lt), + Binary(Gt) => Binary(Le), + Binary(Le) => Binary(Gt), + _ => return make_unop("!", Sugg::BinOp(op, lhs, rhs)), + }; + Sugg::BinOp(to_op, lhs, rhs) + }, + Sugg::UnOp(UnOp::Not, expr) => *expr, + _ => make_unop("!", self), } } } @@ -491,20 +531,11 @@ impl Display for ParenHelper { /// Builds the string for `` adding parenthesis when necessary. /// /// For convenience, the operator is taken as a string because all unary -/// operators have the same -/// precedence. +/// operators have the same precedence. pub fn make_unop(op: &str, expr: Sugg<'_>) -> Sugg<'static> { - // If the `expr` starts with `op` already, do not add wrap it in + // If the `expr` starts with a unary operator already, do not wrap it in // parentheses. - let expr = if let Sugg::MaybeParen(ref sugg) = expr - && !has_enclosing_paren(sugg) - && sugg.starts_with(op) - { - expr - } else { - expr.maybe_paren() - }; - Sugg::MaybeParen(format!("{op}{expr}").into()) + Sugg::MaybeParen(format!("{op}{}", expr.maybe_inner_paren()).into()) } /// Builds the string for ` ` adding parenthesis when necessary. diff --git a/tests/ui/nonminimal_bool.rs b/tests/ui/nonminimal_bool.rs index 1eecc3dee3dc..cacce9a7d1cb 100644 --- a/tests/ui/nonminimal_bool.rs +++ b/tests/ui/nonminimal_bool.rs @@ -236,3 +236,21 @@ mod issue14404 { } } } + +fn dont_simplify_double_not_if_types_differ() { + struct S; + + impl std::ops::Not for S { + type Output = bool; + fn not(self) -> bool { + true + } + } + + // The lint must propose `if !!S`, not `if S`. + // FIXME: `bool_comparison` will propose to use `S == true` + // which is invalid. + if !S != true {} + //~^ nonminimal_bool + //~| bool_comparison +} diff --git a/tests/ui/nonminimal_bool.stderr b/tests/ui/nonminimal_bool.stderr index ecb82a23da03..c20412974b20 100644 --- a/tests/ui/nonminimal_bool.stderr +++ b/tests/ui/nonminimal_bool.stderr @@ -179,7 +179,7 @@ error: inequality checks against true can be replaced by a negation --> tests/ui/nonminimal_bool.rs:186:8 | LL | if !b != true {} - | ^^^^^^^^^^ help: try simplifying it as shown: `!!b` + | ^^^^^^^^^^ help: try simplifying it as shown: `b` error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:189:8 @@ -209,7 +209,7 @@ error: inequality checks against true can be replaced by a negation --> tests/ui/nonminimal_bool.rs:193:8 | LL | if true != !b {} - | ^^^^^^^^^^ help: try simplifying it as shown: `!!b` + | ^^^^^^^^^^ help: try simplifying it as shown: `b` error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:196:8 @@ -235,5 +235,17 @@ error: this boolean expression can be simplified LL | if !(matches!(ty, TyKind::Ref(_, _, _)) && !is_mutable(&expr)) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `!matches!(ty, TyKind::Ref(_, _, _)) || is_mutable(&expr)` -error: aborting due to 31 previous errors +error: this boolean expression can be simplified + --> tests/ui/nonminimal_bool.rs:253:8 + | +LL | if !S != true {} + | ^^^^^^^^^^ help: try: `S == true` + +error: inequality checks against true can be replaced by a negation + --> tests/ui/nonminimal_bool.rs:253:8 + | +LL | if !S != true {} + | ^^^^^^^^^^ help: try simplifying it as shown: `!!S` + +error: aborting due to 33 previous errors From e8db4aa4706a4fdfc9a2cbf32a3cbbd11a6d14d1 Mon Sep 17 00:00:00 2001 From: Tom Webber Date: Mon, 14 Jul 2025 20:24:54 +0200 Subject: [PATCH 33/65] Fix min_ident_chars: add trait/impl awareness --- clippy_lints/src/min_ident_chars.rs | 79 +++++++++++++++++++++++++++- tests/ui/min_ident_chars.rs | 49 ++++++++++++++++++ tests/ui/min_ident_chars.stderr | 80 ++++++++++++++++++++++++++++- 3 files changed, 205 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/min_ident_chars.rs b/clippy_lints/src/min_ident_chars.rs index 99f01c8001a5..dbce29a86318 100644 --- a/clippy_lints/src/min_ident_chars.rs +++ b/clippy_lints/src/min_ident_chars.rs @@ -4,10 +4,14 @@ use clippy_utils::is_from_proc_macro; use rustc_data_structures::fx::FxHashSet; use rustc_hir::def::{DefKind, Res}; use rustc_hir::intravisit::{Visitor, walk_item, walk_trait_item}; -use rustc_hir::{GenericParamKind, HirId, Item, ItemKind, ItemLocalId, Node, Pat, PatKind, TraitItem, UsePath}; +use rustc_hir::{ + GenericParamKind, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, ItemLocalId, Node, Pat, PatKind, TraitItem, + UsePath, +}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::impl_lint_pass; -use rustc_span::Span; +use rustc_span::symbol::Ident; +use rustc_span::{Span, Symbol}; use std::borrow::Cow; declare_clippy_lint! { @@ -32,6 +36,10 @@ declare_clippy_lint! { /// let title = movie.title; /// } /// ``` + /// + /// ### Limitations + /// Trait implementations which use the same function or parameter name as the trait declaration will + /// not be warned about, even if the name is below the configured limit. #[clippy::version = "1.72.0"] pub MIN_IDENT_CHARS, restriction, @@ -76,6 +84,18 @@ impl LateLintPass<'_> for MinIdentChars { return; } + // If the function is declared but not defined in a trait, check_pat isn't called so we need to + // check this explicitly + if matches!(&item.kind, rustc_hir::TraitItemKind::Fn(_, _)) { + let param_names = cx.tcx.fn_arg_idents(item.owner_id.to_def_id()); + for ident in param_names.iter().flatten() { + let str = ident.as_str(); + if self.is_ident_too_short(cx, str, ident.span) { + emit_min_ident_chars(self, cx, str, ident.span); + } + } + } + walk_trait_item(&mut IdentVisitor { conf: self, cx }, item); } @@ -84,6 +104,7 @@ impl LateLintPass<'_> for MinIdentChars { if let PatKind::Binding(_, _, ident, ..) = pat.kind && let str = ident.as_str() && self.is_ident_too_short(cx, str, ident.span) + && is_not_in_trait_impl(cx, pat, ident) { emit_min_ident_chars(self, cx, str, ident.span); } @@ -118,6 +139,11 @@ impl Visitor<'_> for IdentVisitor<'_, '_> { let str = ident.as_str(); if conf.is_ident_too_short(cx, str, ident.span) { + // Check whether the node is part of a `impl` for a trait. + if matches!(cx.tcx.parent_hir_node(hir_id), Node::TraitRef(_)) { + return; + } + // Check whether the node is part of a `use` statement. We don't want to emit a warning if the user // has no control over the type. let usenode = opt_as_use_node(node).or_else(|| { @@ -201,3 +227,52 @@ fn opt_as_use_node(node: Node<'_>) -> Option<&'_ UsePath<'_>> { None } } + +/// Check if a pattern is a function param in an impl block for a trait and that the param name is +/// the same than in the trait definition. +fn is_not_in_trait_impl(cx: &LateContext<'_>, pat: &Pat<'_>, ident: Ident) -> bool { + let parent_node = cx.tcx.parent_hir_node(pat.hir_id); + if !matches!(parent_node, Node::Param(_)) { + return true; + } + + for (_, parent_node) in cx.tcx.hir_parent_iter(pat.hir_id) { + if let Node::ImplItem(impl_item) = parent_node + && matches!(impl_item.kind, ImplItemKind::Fn(_, _)) + { + let impl_parent_node = cx.tcx.parent_hir_node(impl_item.hir_id()); + if let Node::Item(parent_item) = impl_parent_node + && let ItemKind::Impl(Impl { of_trait: Some(_), .. }) = &parent_item.kind + && let Some(name) = get_param_name(impl_item, cx, ident) + { + return name != ident.name; + } + + return true; + } + } + + true +} + +fn get_param_name(impl_item: &ImplItem<'_>, cx: &LateContext<'_>, ident: Ident) -> Option { + if let Some(trait_item_def_id) = impl_item.trait_item_def_id { + let trait_param_names = cx.tcx.fn_arg_idents(trait_item_def_id); + + let ImplItemKind::Fn(_, body_id) = impl_item.kind else { + return None; + }; + + if let Some(param_index) = cx + .tcx + .hir_body_param_idents(body_id) + .position(|param_ident| param_ident.is_some_and(|param_ident| param_ident.span == ident.span)) + && let Some(trait_param_name) = trait_param_names.get(param_index) + && let Some(trait_param_ident) = trait_param_name + { + return Some(trait_param_ident.name); + } + } + + None +} diff --git a/tests/ui/min_ident_chars.rs b/tests/ui/min_ident_chars.rs index f473ac848a8e..e2f82e2a182f 100644 --- a/tests/ui/min_ident_chars.rs +++ b/tests/ui/min_ident_chars.rs @@ -124,3 +124,52 @@ fn wrong_pythagoras(a: f32, b: f32) -> f32 { mod issue_11163 { struct Array([T; N]); } + +struct Issue13396; + +impl core::fmt::Display for Issue13396 { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Issue13396") + } +} + +impl core::fmt::Debug for Issue13396 { + fn fmt(&self, g: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + //~^ min_ident_chars + write!(g, "Issue13396") + } +} + +fn issue13396() { + let a = |f: i8| f; + //~^ min_ident_chars + //~| min_ident_chars +} + +trait D { + //~^ min_ident_chars + fn f(g: i32); + //~^ min_ident_chars + //~| min_ident_chars + fn long(long: i32); + + fn g(arg: i8) { + //~^ min_ident_chars + fn c(d: u8) {} + //~^ min_ident_chars + //~| min_ident_chars + } +} + +impl D for Issue13396 { + fn f(g: i32) { + fn h() {} + //~^ min_ident_chars + fn inner(a: i32) {} + //~^ min_ident_chars + let a = |f: String| f; + //~^ min_ident_chars + //~| min_ident_chars + } + fn long(long: i32) {} +} diff --git a/tests/ui/min_ident_chars.stderr b/tests/ui/min_ident_chars.stderr index bd6c45cf648e..36bf89e79f00 100644 --- a/tests/ui/min_ident_chars.stderr +++ b/tests/ui/min_ident_chars.stderr @@ -193,5 +193,83 @@ error: this ident consists of a single char LL | fn wrong_pythagoras(a: f32, b: f32) -> f32 { | ^ -error: aborting due to 32 previous errors +error: this ident consists of a single char + --> tests/ui/min_ident_chars.rs:137:19 + | +LL | fn fmt(&self, g: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + | ^ + +error: this ident consists of a single char + --> tests/ui/min_ident_chars.rs:144:14 + | +LL | let a = |f: i8| f; + | ^ + +error: this ident consists of a single char + --> tests/ui/min_ident_chars.rs:144:9 + | +LL | let a = |f: i8| f; + | ^ + +error: this ident consists of a single char + --> tests/ui/min_ident_chars.rs:149:7 + | +LL | trait D { + | ^ + +error: this ident consists of a single char + --> tests/ui/min_ident_chars.rs:151:10 + | +LL | fn f(g: i32); + | ^ + +error: this ident consists of a single char + --> tests/ui/min_ident_chars.rs:151:8 + | +LL | fn f(g: i32); + | ^ + +error: this ident consists of a single char + --> tests/ui/min_ident_chars.rs:156:8 + | +LL | fn g(arg: i8) { + | ^ + +error: this ident consists of a single char + --> tests/ui/min_ident_chars.rs:158:12 + | +LL | fn c(d: u8) {} + | ^ + +error: this ident consists of a single char + --> tests/ui/min_ident_chars.rs:158:14 + | +LL | fn c(d: u8) {} + | ^ + +error: this ident consists of a single char + --> tests/ui/min_ident_chars.rs:166:12 + | +LL | fn h() {} + | ^ + +error: this ident consists of a single char + --> tests/ui/min_ident_chars.rs:168:18 + | +LL | fn inner(a: i32) {} + | ^ + +error: this ident consists of a single char + --> tests/ui/min_ident_chars.rs:170:18 + | +LL | let a = |f: String| f; + | ^ + +error: this ident consists of a single char + --> tests/ui/min_ident_chars.rs:170:13 + | +LL | let a = |f: String| f; + | ^ + +error: aborting due to 45 previous errors From c57876242df63e33e2ccb268e386de1ee38c70dd Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Wed, 30 Jul 2025 09:44:31 +0200 Subject: [PATCH 34/65] Output lintcheck summary HTML markdown in order The data in the table needs to be in the same order as the headings. --- lintcheck/src/json.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lintcheck/src/json.rs b/lintcheck/src/json.rs index 808997ff0220..79c1255c5ffe 100644 --- a/lintcheck/src/json.rs +++ b/lintcheck/src/json.rs @@ -66,7 +66,7 @@ impl fmt::Display for Summary { } in &self.0 { let html_id = to_html_id(name); - writeln!(f, "| [`{name}`](#{html_id}) | {added} | {changed} | {removed} |")?; + writeln!(f, "| [`{name}`](#{html_id}) | {added} | {removed} | {changed} |")?; } Ok(()) From 64276e688c464c4d02a9be6357ca0157167ea075 Mon Sep 17 00:00:00 2001 From: houpo-bob Date: Wed, 30 Jul 2025 15:49:56 +0800 Subject: [PATCH 35/65] chore: fix some minor issues in comments Signed-off-by: houpo-bob --- clippy_lints/src/infallible_try_from.rs | 2 +- tests/ui/manual_strip.rs | 2 +- tests/ui/manual_unwrap_or_default.fixed | 2 +- tests/ui/manual_unwrap_or_default.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/infallible_try_from.rs b/clippy_lints/src/infallible_try_from.rs index f7cdf05359a3..fba11adcd627 100644 --- a/clippy_lints/src/infallible_try_from.rs +++ b/clippy_lints/src/infallible_try_from.rs @@ -13,7 +13,7 @@ declare_clippy_lint! { /// /// ### Why is this bad? /// - /// Infalliable conversions should be implemented via `From` with the blanket conversion. + /// Infallible conversions should be implemented via `From` with the blanket conversion. /// /// ### Example /// ```no_run diff --git a/tests/ui/manual_strip.rs b/tests/ui/manual_strip.rs index 086b75a39875..0fdaa1c045e0 100644 --- a/tests/ui/manual_strip.rs +++ b/tests/ui/manual_strip.rs @@ -75,7 +75,7 @@ fn main() { s4[2..].to_string(); } - // Don't propose to reuse the `stripped` identifier as it is overriden + // Don't propose to reuse the `stripped` identifier as it is overridden if s.starts_with("ab") { let stripped = &s["ab".len()..]; //~^ ERROR: stripping a prefix manually diff --git a/tests/ui/manual_unwrap_or_default.fixed b/tests/ui/manual_unwrap_or_default.fixed index 41ca44ceef4e..189fe876aa5d 100644 --- a/tests/ui/manual_unwrap_or_default.fixed +++ b/tests/ui/manual_unwrap_or_default.fixed @@ -102,7 +102,7 @@ fn issue_12928() { let y = if let Some(Y(a, ..)) = x { a } else { 0 }; } -// For symetry with `manual_unwrap_or` test +// For symmetry with `manual_unwrap_or` test fn allowed_manual_unwrap_or_zero() -> u32 { Some(42).unwrap_or_default() } diff --git a/tests/ui/manual_unwrap_or_default.rs b/tests/ui/manual_unwrap_or_default.rs index 343fbc4879ce..ca87926763c9 100644 --- a/tests/ui/manual_unwrap_or_default.rs +++ b/tests/ui/manual_unwrap_or_default.rs @@ -138,7 +138,7 @@ fn issue_12928() { let y = if let Some(Y(a, ..)) = x { a } else { 0 }; } -// For symetry with `manual_unwrap_or` test +// For symmetry with `manual_unwrap_or` test fn allowed_manual_unwrap_or_zero() -> u32 { if let Some(x) = Some(42) { //~^ manual_unwrap_or_default From 7d662eeb935fae3a6c33670ef68ed12eba71fc88 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 30 Jul 2025 17:48:24 +0000 Subject: [PATCH 36/65] Distinguish appending and replacing self ty in predicates --- clippy_lints/src/needless_borrows_for_generic_args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/needless_borrows_for_generic_args.rs b/clippy_lints/src/needless_borrows_for_generic_args.rs index 17d251a7bbb9..120a4b98a65a 100644 --- a/clippy_lints/src/needless_borrows_for_generic_args.rs +++ b/clippy_lints/src/needless_borrows_for_generic_args.rs @@ -417,7 +417,7 @@ fn replace_types<'tcx>( { let projection = projection_predicate .projection_term - .with_self_ty(cx.tcx, new_ty) + .with_replaced_self_ty(cx.tcx, new_ty) .expect_ty(cx.tcx) .to_ty(cx.tcx); From e295a7d9f9bd87ab5cae8f9959c3122e30eadf1a Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Mon, 28 Jul 2025 18:35:23 +0200 Subject: [PATCH 37/65] Simplify boolean expression in `manual_assert` --- clippy_lints/src/manual_assert.rs | 16 +++++----------- tests/ui/manual_assert.edition2018.stderr | 4 ++-- tests/ui/manual_assert.edition2021.stderr | 4 ++-- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/clippy_lints/src/manual_assert.rs b/clippy_lints/src/manual_assert.rs index ea6b01a053a3..76cb22864779 100644 --- a/clippy_lints/src/manual_assert.rs +++ b/clippy_lints/src/manual_assert.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::{is_panic, root_macro_call}; -use clippy_utils::{is_else_clause, is_parent_stmt, peel_blocks_with_stmt, span_extract_comment, sugg}; +use clippy_utils::{higher, is_else_clause, is_parent_stmt, peel_blocks_with_stmt, span_extract_comment, sugg}; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, UnOp}; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::declare_lint_pass; @@ -35,7 +35,7 @@ declare_lint_pass!(ManualAssert => [MANUAL_ASSERT]); impl<'tcx> LateLintPass<'tcx> for ManualAssert { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { - if let ExprKind::If(cond, then, None) = expr.kind + if let Some(higher::If { cond, then, r#else: None }) = higher::If::hir(expr) && !matches!(cond.kind, ExprKind::Let(_)) && !expr.span.from_expansion() && let then = peel_blocks_with_stmt(then) @@ -51,19 +51,13 @@ impl<'tcx> LateLintPass<'tcx> for ManualAssert { && !is_else_clause(cx.tcx, expr) { let mut applicability = Applicability::MachineApplicable; - let cond = cond.peel_drop_temps(); let mut comments = span_extract_comment(cx.sess().source_map(), expr.span); if !comments.is_empty() { comments += "\n"; } - let (cond, not) = match cond.kind { - ExprKind::Unary(UnOp::Not, e) => (e, ""), - _ => (cond, "!"), - }; - let cond_sugg = - sugg::Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "..", &mut applicability).maybe_paren(); + let cond_sugg = !sugg::Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "..", &mut applicability); let semicolon = if is_parent_stmt(cx, expr.hir_id) { ";" } else { "" }; - let sugg = format!("assert!({not}{cond_sugg}, {format_args_snip}){semicolon}"); + let sugg = format!("assert!({cond_sugg}, {format_args_snip}){semicolon}"); // we show to the user the suggestion without the comments, but when applying the fix, include the // comments in the block span_lint_and_then( diff --git a/tests/ui/manual_assert.edition2018.stderr b/tests/ui/manual_assert.edition2018.stderr index 221cddf069db..2e9c9045caae 100644 --- a/tests/ui/manual_assert.edition2018.stderr +++ b/tests/ui/manual_assert.edition2018.stderr @@ -167,7 +167,7 @@ LL - comment */ LL - /// Doc comment LL - panic!("panic with comment") // comment after `panic!` LL - } -LL + assert!(!(a > 2), "panic with comment"); +LL + assert!(a <= 2, "panic with comment"); | error: only a `panic!` in `if`-then statement @@ -186,7 +186,7 @@ LL - const BAR: () = if N == 0 { LL - LL - panic!() LL - }; -LL + const BAR: () = assert!(!(N == 0), ); +LL + const BAR: () = assert!(N != 0, ); | error: only a `panic!` in `if`-then statement diff --git a/tests/ui/manual_assert.edition2021.stderr b/tests/ui/manual_assert.edition2021.stderr index 221cddf069db..2e9c9045caae 100644 --- a/tests/ui/manual_assert.edition2021.stderr +++ b/tests/ui/manual_assert.edition2021.stderr @@ -167,7 +167,7 @@ LL - comment */ LL - /// Doc comment LL - panic!("panic with comment") // comment after `panic!` LL - } -LL + assert!(!(a > 2), "panic with comment"); +LL + assert!(a <= 2, "panic with comment"); | error: only a `panic!` in `if`-then statement @@ -186,7 +186,7 @@ LL - const BAR: () = if N == 0 { LL - LL - panic!() LL - }; -LL + const BAR: () = assert!(!(N == 0), ); +LL + const BAR: () = assert!(N != 0, ); | error: only a `panic!` in `if`-then statement From c78d589243cede0605a4ba26b452c50f4a09ccc3 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 31 Jul 2025 09:08:46 +1000 Subject: [PATCH 38/65] Remove `TyCtxt::get_attrs_unchecked`. It's identical to `TyCtxt::get_all_attrs` except it takes `DefId` instead of `impl Into`. --- clippy_lints/src/matches/significant_drop_in_scrutinee.rs | 2 +- clippy_lints/src/significant_drop_tightening.rs | 2 +- clippy_utils/src/macros.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 88b4d9b7d544..027dd7ce0534 100644 --- a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -185,7 +185,7 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> { if let Some(adt) = ty.ty_adt_def() && get_attr( self.cx.sess(), - self.cx.tcx.get_attrs_unchecked(adt.did()), + self.cx.tcx.get_all_attrs(adt.did()), sym::has_significant_drop, ) .count() diff --git a/clippy_lints/src/significant_drop_tightening.rs b/clippy_lints/src/significant_drop_tightening.rs index 521754f7bab7..9110f684bd10 100644 --- a/clippy_lints/src/significant_drop_tightening.rs +++ b/clippy_lints/src/significant_drop_tightening.rs @@ -168,7 +168,7 @@ impl<'cx, 'others, 'tcx> AttrChecker<'cx, 'others, 'tcx> { if let Some(adt) = ty.ty_adt_def() { let mut iter = get_attr( self.cx.sess(), - self.cx.tcx.get_attrs_unchecked(adt.did()), + self.cx.tcx.get_all_attrs(adt.did()), sym::has_significant_drop, ); if iter.next().is_some() { diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index ba126fcd05de..60473a26493d 100644 --- a/clippy_utils/src/macros.rs +++ b/clippy_utils/src/macros.rs @@ -42,7 +42,7 @@ pub fn is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool { } else { // Allow users to tag any macro as being format!-like // TODO: consider deleting FORMAT_MACRO_DIAG_ITEMS and using just this method - get_unique_attr(cx.sess(), cx.tcx.get_attrs_unchecked(macro_def_id), sym::format_args).is_some() + get_unique_attr(cx.sess(), cx.tcx.get_all_attrs(macro_def_id), sym::format_args).is_some() } } From d7b7e236e74f45bdef7e082ac361b699abac20a2 Mon Sep 17 00:00:00 2001 From: xizheyin Date: Thu, 31 Jul 2025 00:44:22 +0800 Subject: [PATCH 39/65] Extend `is_case_difference` to handle digit-letter confusables Signed-off-by: xizheyin --- tests/ui/match_str_case_mismatch.stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/match_str_case_mismatch.stderr b/tests/ui/match_str_case_mismatch.stderr index 8068edfff947..c2b58b952aaa 100644 --- a/tests/ui/match_str_case_mismatch.stderr +++ b/tests/ui/match_str_case_mismatch.stderr @@ -18,7 +18,7 @@ error: this `match` arm has a differing case than its expression LL | "~!@#$%^&*()-_=+Foo" => {}, | ^^^^^^^^^^^^^^^^^^^^ | -help: consider changing the case of this arm to respect `to_ascii_lowercase` (notice the capitalization difference) +help: consider changing the case of this arm to respect `to_ascii_lowercase` (notice the capitalization) | LL - "~!@#$%^&*()-_=+Foo" => {}, LL + "~!@#$%^&*()-_=+foo" => {}, From 618a90d1b0682f687b63cc591ef78814538d5a8f Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 31 Jul 2025 11:52:42 +1000 Subject: [PATCH 40/65] Deduplicate `IntTy`/`UintTy`/`FloatTy`. There are identical definitions in `rustc_type_ir` and `rustc_ast`. This commit removes them and places a single definition in `rustc_ast_ir`. This requires adding `rust_span` as a dependency of `rustc_ast_ir`, but means a bunch of silly conversion functions can be removed. The one annoying wrinkle is that the old version had differences in their `Debug` impls, e.g. one printed `u32` while the other printed `U32`. Some compiler error messages rely on the former (yuk), and some clippy output depends on the latter. So the commit also changes clippy to not rely on `Debug` and just implement what it needs itself. --- clippy_lints/src/float_literal.rs | 10 ++++----- clippy_lints/src/utils/author.rs | 35 ++++++++++++++++++++++++++++--- clippy_utils/src/consts.rs | 10 ++++----- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/float_literal.rs b/clippy_lints/src/float_literal.rs index c51267567d01..ccaf38aee4d8 100644 --- a/clippy_lints/src/float_literal.rs +++ b/clippy_lints/src/float_literal.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::numeric_literal; -use rustc_ast::ast::{self, LitFloatType, LitKind}; +use rustc_ast::ast::{LitFloatType, LitKind}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; @@ -75,10 +75,10 @@ impl<'tcx> LateLintPass<'tcx> for FloatLiteral { let digits = count_digits(sym_str); let max = max_digits(fty); let type_suffix = match lit_float_ty { - LitFloatType::Suffixed(ast::FloatTy::F16) => Some("f16"), - LitFloatType::Suffixed(ast::FloatTy::F32) => Some("f32"), - LitFloatType::Suffixed(ast::FloatTy::F64) => Some("f64"), - LitFloatType::Suffixed(ast::FloatTy::F128) => Some("f128"), + LitFloatType::Suffixed(FloatTy::F16) => Some("f16"), + LitFloatType::Suffixed(FloatTy::F32) => Some("f32"), + LitFloatType::Suffixed(FloatTy::F64) => Some("f64"), + LitFloatType::Suffixed(FloatTy::F128) => Some("f128"), LitFloatType::Unsuffixed => None, }; let (is_whole, is_inf, mut float_str) = match fty { diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index ac92ab5a245c..299317384122 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -9,6 +9,7 @@ use rustc_hir::{ FnRetTy, HirId, Lit, PatExprKind, PatKind, QPath, StmtKind, StructTailExpr, }; use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::ty::{FloatTy, IntTy, UintTy}; use rustc_session::declare_lint_pass; use rustc_span::symbol::{Ident, Symbol}; use std::cell::Cell; @@ -337,15 +338,43 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { LitKind::Byte(b) => kind!("Byte({b})"), LitKind::Int(i, suffix) => { let int_ty = match suffix { - LitIntType::Signed(int_ty) => format!("LitIntType::Signed(IntTy::{int_ty:?})"), - LitIntType::Unsigned(uint_ty) => format!("LitIntType::Unsigned(UintTy::{uint_ty:?})"), + LitIntType::Signed(int_ty) => { + let t = match int_ty { + IntTy::Isize => "Isize", + IntTy::I8 => "I8", + IntTy::I16 => "I16", + IntTy::I32 => "I32", + IntTy::I64 => "I64", + IntTy::I128 => "I128", + }; + format!("LitIntType::Signed(IntTy::{t})") + } + LitIntType::Unsigned(uint_ty) => { + let t = match uint_ty { + UintTy::Usize => "Usize", + UintTy::U8 => "U8", + UintTy::U16 => "U16", + UintTy::U32 => "U32", + UintTy::U64 => "U64", + UintTy::U128 => "U128", + }; + format!("LitIntType::Unsigned(UintTy::{t})") + } LitIntType::Unsuffixed => String::from("LitIntType::Unsuffixed"), }; kind!("Int({i}, {int_ty})"); }, LitKind::Float(_, suffix) => { let float_ty = match suffix { - LitFloatType::Suffixed(suffix_ty) => format!("LitFloatType::Suffixed(FloatTy::{suffix_ty:?})"), + LitFloatType::Suffixed(suffix_ty) => { + let t = match suffix_ty { + FloatTy::F16 => "F16", + FloatTy::F32 => "F32", + FloatTy::F64 => "F64", + FloatTy::F128 => "F128", + }; + format!("LitFloatType::Suffixed(FloatTy::{t})") + } LitFloatType::Unsuffixed => String::from("LitFloatType::Unsuffixed"), }; kind!("Float(_, {float_ty})"); diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 25afa12e95d6..ecd88daa6b39 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -10,7 +10,7 @@ use crate::{clip, is_direct_expn_of, sext, unsext}; use rustc_abi::Size; use rustc_apfloat::Float; use rustc_apfloat::ieee::{Half, Quad}; -use rustc_ast::ast::{self, LitFloatType, LitKind}; +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, @@ -309,10 +309,10 @@ pub fn lit_to_mir_constant<'tcx>(lit: &LitKind, ty: Option>) -> Constan LitKind::Int(n, _) => Constant::Int(n.get()), LitKind::Float(ref is, LitFloatType::Suffixed(fty)) => match fty { // FIXME(f16_f128): just use `parse()` directly when available for `f16`/`f128` - ast::FloatTy::F16 => Constant::parse_f16(is.as_str()), - ast::FloatTy::F32 => Constant::F32(is.as_str().parse().unwrap()), - ast::FloatTy::F64 => Constant::F64(is.as_str().parse().unwrap()), - ast::FloatTy::F128 => Constant::parse_f128(is.as_str()), + FloatTy::F16 => Constant::parse_f16(is.as_str()), + FloatTy::F32 => Constant::F32(is.as_str().parse().unwrap()), + FloatTy::F64 => Constant::F64(is.as_str().parse().unwrap()), + FloatTy::F128 => Constant::parse_f128(is.as_str()), }, LitKind::Float(ref is, LitFloatType::Unsuffixed) => match ty.expect("type of float is known").kind() { ty::Float(FloatTy::F16) => Constant::parse_f16(is.as_str()), From 05e3a7a4997157e31b05f61f0a094e75574b025c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Thu, 31 Jul 2025 11:00:40 +0200 Subject: [PATCH 41/65] remove rustc_attr_data_structures --- clippy_lints/src/approx_const.rs | 2 +- clippy_lints/src/arbitrary_source_item_ordering.rs | 2 +- clippy_lints/src/attrs/inline_always.rs | 3 ++- clippy_lints/src/attrs/repr_attributes.rs | 3 ++- clippy_lints/src/booleans.rs | 2 +- clippy_lints/src/default_union_representation.rs | 3 ++- clippy_lints/src/doc/suspicious_doc_comments.rs | 2 +- clippy_lints/src/doc/too_long_first_doc_paragraph.rs | 2 +- clippy_lints/src/eta_reduction.rs | 3 ++- clippy_lints/src/exhaustive_items.rs | 3 ++- clippy_lints/src/format_args.rs | 3 ++- clippy_lints/src/functions/must_use.rs | 3 ++- clippy_lints/src/incompatible_msrv.rs | 2 +- clippy_lints/src/inline_fn_without_body.rs | 3 ++- clippy_lints/src/lib.rs | 1 - clippy_lints/src/macro_use.rs | 3 ++- clippy_lints/src/manual_non_exhaustive.rs | 3 ++- clippy_lints/src/missing_inline.rs | 3 ++- clippy_lints/src/no_mangle_with_rust_abi.rs | 2 +- clippy_lints/src/pass_by_ref_or_value.rs | 3 ++- clippy_lints/src/return_self_not_must_use.rs | 3 ++- clippy_lints/src/std_instead_of_core.rs | 2 +- .../src/derive_deserialize_allowing_unknown.rs | 3 ++- clippy_lints_internal/src/lib.rs | 1 - clippy_utils/src/attrs.rs | 3 ++- clippy_utils/src/lib.rs | 4 ++-- clippy_utils/src/msrvs.rs | 2 +- clippy_utils/src/qualify_min_const_fn.rs | 4 ++-- clippy_utils/src/ty/mod.rs | 3 ++- 29 files changed, 45 insertions(+), 31 deletions(-) diff --git a/clippy_lints/src/approx_const.rs b/clippy_lints/src/approx_const.rs index 184fbbf77962..ab47e3097524 100644 --- a/clippy_lints/src/approx_const.rs +++ b/clippy_lints/src/approx_const.rs @@ -2,7 +2,7 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::msrvs::{self, Msrv}; use rustc_ast::ast::{FloatTy, LitFloatType, LitKind}; -use rustc_attr_data_structures::RustcVersion; +use rustc_hir::RustcVersion; use rustc_hir::{HirId, Lit}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; diff --git a/clippy_lints/src/arbitrary_source_item_ordering.rs b/clippy_lints/src/arbitrary_source_item_ordering.rs index 7b4cf0336741..d6469d32931d 100644 --- a/clippy_lints/src/arbitrary_source_item_ordering.rs +++ b/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -6,7 +6,7 @@ use clippy_config::types::{ }; use clippy_utils::diagnostics::span_lint_and_note; use clippy_utils::is_cfg_test; -use rustc_attr_data_structures::AttributeKind; +use rustc_hir::attrs::AttributeKind; use rustc_hir::{ Attribute, FieldDef, HirId, ImplItemId, IsAuto, Item, ItemKind, Mod, OwnerId, QPath, TraitItemId, TyKind, Variant, VariantData, diff --git a/clippy_lints/src/attrs/inline_always.rs b/clippy_lints/src/attrs/inline_always.rs index b8f93ee5e2c1..409bb698665f 100644 --- a/clippy_lints/src/attrs/inline_always.rs +++ b/clippy_lints/src/attrs/inline_always.rs @@ -1,6 +1,7 @@ use super::INLINE_ALWAYS; use clippy_utils::diagnostics::span_lint; -use rustc_attr_data_structures::{AttributeKind, InlineAttr, find_attr}; +use rustc_hir::attrs::{AttributeKind, InlineAttr}; +use rustc_hir::find_attr; use rustc_hir::Attribute; use rustc_lint::LateContext; use rustc_span::Span; diff --git a/clippy_lints/src/attrs/repr_attributes.rs b/clippy_lints/src/attrs/repr_attributes.rs index 3e8808cec617..4ece3ed44fdf 100644 --- a/clippy_lints/src/attrs/repr_attributes.rs +++ b/clippy_lints/src/attrs/repr_attributes.rs @@ -1,4 +1,5 @@ -use rustc_attr_data_structures::{AttributeKind, ReprAttr, find_attr}; +use rustc_hir::attrs::{AttributeKind, ReprAttr}; +use rustc_hir::find_attr; use rustc_hir::Attribute; use rustc_lint::LateContext; use rustc_span::Span; diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 61c2fc49bd70..ba1135d745ae 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -7,7 +7,7 @@ use clippy_utils::sugg::Sugg; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use clippy_utils::{eq_expr_value, sym}; use rustc_ast::ast::LitKind; -use rustc_attr_data_structures::RustcVersion; +use rustc_hir::RustcVersion; use rustc_errors::Applicability; use rustc_hir::intravisit::{FnKind, Visitor, walk_expr}; use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, UnOp}; diff --git a/clippy_lints/src/default_union_representation.rs b/clippy_lints/src/default_union_representation.rs index 9bf2144e445d..f41255e54db6 100644 --- a/clippy_lints/src/default_union_representation.rs +++ b/clippy_lints/src/default_union_representation.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; -use rustc_attr_data_structures::{AttributeKind, ReprAttr, find_attr}; +use rustc_hir::attrs::{AttributeKind, ReprAttr}; +use rustc_hir::find_attr; use rustc_hir::{HirId, Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::layout::LayoutOf; diff --git a/clippy_lints/src/doc/suspicious_doc_comments.rs b/clippy_lints/src/doc/suspicious_doc_comments.rs index ebfc9972aefd..1e7d1f92fa31 100644 --- a/clippy_lints/src/doc/suspicious_doc_comments.rs +++ b/clippy_lints/src/doc/suspicious_doc_comments.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use rustc_ast::AttrStyle; use rustc_ast::token::CommentKind; -use rustc_attr_data_structures::AttributeKind; +use rustc_hir::attrs::AttributeKind; use rustc_errors::Applicability; use rustc_hir::Attribute; use rustc_lint::LateContext; diff --git a/clippy_lints/src/doc/too_long_first_doc_paragraph.rs b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs index 7f7224ecfc65..32ba696b3ec7 100644 --- a/clippy_lints/src/doc/too_long_first_doc_paragraph.rs +++ b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs @@ -1,4 +1,4 @@ -use rustc_attr_data_structures::AttributeKind; +use rustc_hir::attrs::AttributeKind; use rustc_errors::Applicability; use rustc_hir::{Attribute, Item, ItemKind}; use rustc_lint::LateContext; diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 9b627678bd35..ba539d05b6be 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -7,7 +7,8 @@ use clippy_utils::{ get_path_from_caller_to_method_type, is_adjusted, is_no_std_crate, path_to_local, path_to_local_id, }; use rustc_abi::ExternAbi; -use rustc_attr_data_structures::{AttributeKind, find_attr}; +use rustc_hir::attrs::{AttributeKind}; +use rustc_hir::find_attr; use rustc_errors::Applicability; use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, GenericArgs, Param, PatKind, QPath, Safety, TyKind}; use rustc_infer::infer::TyCtxtInferExt; diff --git a/clippy_lints/src/exhaustive_items.rs b/clippy_lints/src/exhaustive_items.rs index 8ad092790718..5f40e5764435 100644 --- a/clippy_lints/src/exhaustive_items.rs +++ b/clippy_lints/src/exhaustive_items.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::indent_of; -use rustc_attr_data_structures::{AttributeKind, find_attr}; +use rustc_hir::attrs::{AttributeKind}; +use rustc_hir::find_attr; use rustc_errors::Applicability; use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index a251f15ba3da..af4202422e44 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -17,7 +17,8 @@ use rustc_ast::{ FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions, FormatPlaceholder, FormatTrait, }; -use rustc_attr_data_structures::{AttributeKind, RustcVersion, find_attr}; +use rustc_hir::attrs::{AttributeKind}; +use rustc_hir::{find_attr,RustcVersion}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; use rustc_errors::SuggestionStyle::{CompletelyHidden, ShowCode}; diff --git a/clippy_lints/src/functions/must_use.rs b/clippy_lints/src/functions/must_use.rs index b8d0cec5aeb1..55ca0d9ecb71 100644 --- a/clippy_lints/src/functions/must_use.rs +++ b/clippy_lints/src/functions/must_use.rs @@ -14,7 +14,8 @@ use clippy_utils::source::snippet_indent; use clippy_utils::ty::is_must_use_ty; use clippy_utils::visitors::for_each_expr_without_closures; use clippy_utils::{return_ty, trait_ref_of_method}; -use rustc_attr_data_structures::{AttributeKind, find_attr}; +use rustc_hir::attrs::{AttributeKind}; +use rustc_hir::find_attr; use rustc_span::Symbol; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; diff --git a/clippy_lints/src/incompatible_msrv.rs b/clippy_lints/src/incompatible_msrv.rs index 116d63c3bb15..85ebc830d3bc 100644 --- a/clippy_lints/src/incompatible_msrv.rs +++ b/clippy_lints/src/incompatible_msrv.rs @@ -2,7 +2,7 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::Msrv; use clippy_utils::{is_in_const_context, is_in_test}; -use rustc_attr_data_structures::{RustcVersion, StabilityLevel, StableSince}; +use rustc_hir::{RustcVersion, StabilityLevel, StableSince}; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::DefKind; use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, HirId, QPath}; diff --git a/clippy_lints/src/inline_fn_without_body.rs b/clippy_lints/src/inline_fn_without_body.rs index ffe6ad14f630..ee59a4cc8cb7 100644 --- a/clippy_lints/src/inline_fn_without_body.rs +++ b/clippy_lints/src/inline_fn_without_body.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::sugg::DiagExt; -use rustc_attr_data_structures::{AttributeKind, find_attr}; +use rustc_hir::attrs::{AttributeKind}; +use rustc_hir::find_attr; use rustc_errors::Applicability; use rustc_hir::{TraitFn, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass}; diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 96a6dee58852..914aa6b9b804 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -35,7 +35,6 @@ extern crate rustc_abi; extern crate rustc_arena; extern crate rustc_ast; extern crate rustc_ast_pretty; -extern crate rustc_attr_data_structures; extern crate rustc_data_structures; extern crate rustc_driver; extern crate rustc_errors; diff --git a/clippy_lints/src/macro_use.rs b/clippy_lints/src/macro_use.rs index 3aa449f64118..bf89556fbb61 100644 --- a/clippy_lints/src/macro_use.rs +++ b/clippy_lints/src/macro_use.rs @@ -1,7 +1,8 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::snippet; use hir::def::{DefKind, Res}; -use rustc_attr_data_structures::{AttributeKind, find_attr}; +use rustc_hir::attrs::{AttributeKind}; +use rustc_hir::find_attr; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::{self as hir, AmbigArg}; diff --git a/clippy_lints/src/manual_non_exhaustive.rs b/clippy_lints/src/manual_non_exhaustive.rs index 2d52a93f34e3..6b0f74468492 100644 --- a/clippy_lints/src/manual_non_exhaustive.rs +++ b/clippy_lints/src/manual_non_exhaustive.rs @@ -4,7 +4,8 @@ use clippy_utils::is_doc_hidden; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_indent; use itertools::Itertools; -use rustc_attr_data_structures::{AttributeKind, find_attr}; +use rustc_hir::attrs::{AttributeKind}; +use rustc_hir::find_attr; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index 5a5025973b5b..c637fb247ff2 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint; -use rustc_attr_data_structures::{AttributeKind, find_attr}; +use rustc_hir::attrs::{AttributeKind}; +use rustc_hir::find_attr; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, Attribute}; use rustc_lint::{LateContext, LateLintPass, LintContext}; diff --git a/clippy_lints/src/no_mangle_with_rust_abi.rs b/clippy_lints/src/no_mangle_with_rust_abi.rs index dee8efeb2910..791bbbe30a8e 100644 --- a/clippy_lints/src/no_mangle_with_rust_abi.rs +++ b/clippy_lints/src/no_mangle_with_rust_abi.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{snippet, snippet_with_applicability}; use rustc_abi::ExternAbi; -use rustc_attr_data_structures::AttributeKind; +use rustc_hir::attrs::AttributeKind; use rustc_errors::Applicability; use rustc_hir::{Attribute, Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; diff --git a/clippy_lints/src/pass_by_ref_or_value.rs b/clippy_lints/src/pass_by_ref_or_value.rs index b8005dfd6f8e..303c5dfed891 100644 --- a/clippy_lints/src/pass_by_ref_or_value.rs +++ b/clippy_lints/src/pass_by_ref_or_value.rs @@ -5,7 +5,8 @@ use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy}; use clippy_utils::{is_self, is_self_ty}; use core::ops::ControlFlow; use rustc_abi::ExternAbi; -use rustc_attr_data_structures::{AttributeKind, InlineAttr, find_attr}; +use rustc_hir::attrs::{AttributeKind, InlineAttr}; +use rustc_hir::find_attr; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir as hir; diff --git a/clippy_lints/src/return_self_not_must_use.rs b/clippy_lints/src/return_self_not_must_use.rs index 3497216d1c56..2cdb8ef3a654 100644 --- a/clippy_lints/src/return_self_not_must_use.rs +++ b/clippy_lints/src/return_self_not_must_use.rs @@ -1,7 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::ty::is_must_use_ty; use clippy_utils::{nth_arg, return_ty}; -use rustc_attr_data_structures::{AttributeKind, find_attr}; +use rustc_hir::attrs::{AttributeKind}; +use rustc_hir::find_attr; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::FnKind; use rustc_hir::{Body, FnDecl, OwnerId, TraitItem, TraitItemKind}; diff --git a/clippy_lints/src/std_instead_of_core.rs b/clippy_lints/src/std_instead_of_core.rs index 216f168471e6..50c44a8e75c4 100644 --- a/clippy_lints/src/std_instead_of_core.rs +++ b/clippy_lints/src/std_instead_of_core.rs @@ -2,7 +2,7 @@ use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; use clippy_utils::is_from_proc_macro; use clippy_utils::msrvs::Msrv; -use rustc_attr_data_structures::{StabilityLevel, StableSince}; +use rustc_hir::{StabilityLevel, StableSince}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; diff --git a/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs b/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs index 41fafc08c259..e0ae0c11cc2d 100644 --- a/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs +++ b/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs @@ -2,7 +2,8 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::paths; use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::{AttrStyle, DelimArgs}; -use rustc_attr_data_structures::{AttributeKind, find_attr}; +use rustc_hir::attrs::{AttributeKind}; +use rustc_hir::find_attr; use rustc_hir::def::Res; use rustc_hir::def_id::LocalDefId; use rustc_hir::{ diff --git a/clippy_lints_internal/src/lib.rs b/clippy_lints_internal/src/lib.rs index 0c94d100c417..43cde86504f5 100644 --- a/clippy_lints_internal/src/lib.rs +++ b/clippy_lints_internal/src/lib.rs @@ -20,7 +20,6 @@ #![allow(clippy::missing_clippy_version_attribute)] extern crate rustc_ast; -extern crate rustc_attr_data_structures; extern crate rustc_attr_parsing; extern crate rustc_data_structures; extern crate rustc_errors; diff --git a/clippy_utils/src/attrs.rs b/clippy_utils/src/attrs.rs index 34472eaab93c..4ccd9c5300b1 100644 --- a/clippy_utils/src/attrs.rs +++ b/clippy_utils/src/attrs.rs @@ -2,7 +2,8 @@ use crate::source::SpanRangeExt; use crate::{sym, tokenize_with_text}; use rustc_ast::attr; use rustc_ast::attr::AttributeExt; -use rustc_attr_data_structures::{AttributeKind, find_attr}; +use rustc_hir::attrs::{AttributeKind}; +use rustc_hir::find_attr; use rustc_errors::Applicability; use rustc_lexer::TokenKind; use rustc_lint::LateContext; diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 67e09e772a77..5b9b0ef30014 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -28,7 +28,6 @@ extern crate indexmap; extern crate rustc_abi; extern crate rustc_ast; -extern crate rustc_attr_data_structures; extern crate rustc_attr_parsing; extern crate rustc_const_eval; extern crate rustc_data_structures; @@ -91,7 +90,8 @@ use itertools::Itertools; use rustc_abi::Integer; use rustc_ast::ast::{self, LitKind, RangeLimits}; use rustc_ast::join_path_syms; -use rustc_attr_data_structures::{AttributeKind, find_attr}; +use rustc_hir::attrs::{AttributeKind}; +use rustc_hir::find_attr; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::packed::Pu128; use rustc_data_structures::unhash::UnindexMap; diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index 24ed4c3a8bec..480e0687756f 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -1,7 +1,7 @@ use crate::sym; use rustc_ast::Attribute; use rustc_ast::attr::AttributeExt; -use rustc_attr_data_structures::RustcVersion; +use rustc_hir::RustcVersion; use rustc_attr_parsing::parse_version; use rustc_lint::LateContext; use rustc_session::Session; diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 11c17a77b154..79116eba971a 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -5,7 +5,7 @@ use crate::msrvs::{self, Msrv}; use hir::LangItem; -use rustc_attr_data_structures::{RustcVersion, StableSince}; +use rustc_hir::{RustcVersion, StableSince}; use rustc_const_eval::check_consts::ConstCx; use rustc_hir as hir; use rustc_hir::def_id::DefId; @@ -424,7 +424,7 @@ pub fn is_stable_const_fn(cx: &LateContext<'_>, def_id: DefId, msrv: Msrv) -> bo .and_then(|trait_def_id| cx.tcx.lookup_const_stability(trait_def_id)) }) .is_none_or(|const_stab| { - if let rustc_attr_data_structures::StabilityLevel::Stable { since, .. } = const_stab.level { + if let rustc_hir::StabilityLevel::Stable { since, .. } = const_stab.level { // Checking MSRV is manually necessary because `rustc` has no such concept. This entire // function could be removed if `rustc` provided a MSRV-aware version of `is_stable_const_fn`. // as a part of an unimplemented MSRV check https://github.com/rust-lang/rust/issues/65262. diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index d70232ef3aa5..02a8eda5893e 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -6,7 +6,8 @@ use core::ops::ControlFlow; use itertools::Itertools; use rustc_abi::VariantIdx; use rustc_ast::ast::Mutability; -use rustc_attr_data_structures::{AttributeKind, find_attr}; +use rustc_hir::attrs::{AttributeKind}; +use rustc_hir::find_attr; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; From 9f7a8f8f9efb57af1f46faf45086201c50e1afde Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Wed, 30 Jul 2025 20:31:27 +0500 Subject: [PATCH 42/65] Changelog for Clippy 1.89 --- CHANGELOG.md | 100 ++++++++++++++++++- clippy_lints/src/casts/mod.rs | 2 +- clippy_lints/src/cloned_ref_to_slice_refs.rs | 2 +- clippy_lints/src/coerce_container_to_any.rs | 2 +- clippy_lints/src/doc/mod.rs | 2 +- clippy_lints/src/format_args.rs | 2 +- clippy_lints/src/infallible_try_from.rs | 2 +- 7 files changed, 105 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a92fbdc767bd..fa95823f259f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,105 @@ document. ## Unreleased / Beta / In Rust Nightly -[03a5b6b9...master](https://github.com/rust-lang/rust-clippy/compare/03a5b6b9...master) +[4ef75291...master](https://github.com/rust-lang/rust-clippy/compare/4ef75291...master) + +## Rust 1.89 + +Current stable, released 2025-08-07 + +[View all 137 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2025-05-01T16%3A52%3A57Z..2025-06-13T08%3A33%3A27Z+base%3Amaster) + +### New Lints + +* Added [`coerce_container_to_any`] to `nursery` [#14812](https://github.com/rust-lang/rust-clippy/pull/14812) +* Added [`ip_constant`] to `pedantic` [#14878](https://github.com/rust-lang/rust-clippy/pull/14878) +* Added [`infallible_try_from`] to `suspicious` [#14813](https://github.com/rust-lang/rust-clippy/pull/14813) +* Added [`doc_suspicious_footnotes`] to `suspicious` [#14708](https://github.com/rust-lang/rust-clippy/pull/14708) +* Added [`pointer_format`] to `restriction` [#14792](https://github.com/rust-lang/rust-clippy/pull/14792) +* Added [`useless_concat`] to `complexity` [#13829](https://github.com/rust-lang/rust-clippy/pull/13829) +* Added [`cloned_ref_to_slice_refs`] to `perf` [#14284](https://github.com/rust-lang/rust-clippy/pull/14284) +* Added [`confusing_method_to_numeric_cast`] to `suspicious` [#13979](https://github.com/rust-lang/rust-clippy/pull/13979) + +### Moves and Deprecations + +* Removed superseded lints: `transmute_float_to_int`, `transmute_int_to_char`, + `transmute_int_to_float`, `transmute_num_to_bytes` (now in rustc) + [#14703](https://github.com/rust-lang/rust-clippy/pull/14703) + +### Enhancements + +* [`module_name_repetitions`] added `allow_exact_repetitions` configuration option + [#14261](https://github.com/rust-lang/rust-clippy/pull/14261) +* [`missing_docs_in_private_items`] added `allow_unused` config for underscored fields + [#14453](https://github.com/rust-lang/rust-clippy/pull/14453) +* [`unnecessary_unwrap`] fixed being emitted twice in closure + [#14763](https://github.com/rust-lang/rust-clippy/pull/14763) +* [`needless_return`] lint span no longer wraps to previous line + [#14790](https://github.com/rust-lang/rust-clippy/pull/14790) +* [`trivial-copy-size-limit`] now defaults to `target_pointer_width` + [#13319](https://github.com/rust-lang/rust-clippy/pull/13319) +* [`integer_division`] fixed false negative for NonZero denominators + [#14664](https://github.com/rust-lang/rust-clippy/pull/14664) +* [`arbitrary_source_item_ordering`] no longer lints inside items with `#[repr]` attribute + [#14610](https://github.com/rust-lang/rust-clippy/pull/14610) +* [`excessive_precision`] no longer triggers on exponent with leading zeros + [#14824](https://github.com/rust-lang/rust-clippy/pull/14824) +* [`to_digit_is_some`] no longer lints in const contexts when MSRV is below 1.87 + [#14771](https://github.com/rust-lang/rust-clippy/pull/14771) + +### False Positive Fixes + +* [`std_instead_of_core`] fixed FP when part of the `use` cannot be replaced + [#15016](https://github.com/rust-lang/rust-clippy/pull/15016) +* [`unused_unit`] fixed FP for `Fn` bounds + [#14962](https://github.com/rust-lang/rust-clippy/pull/14962) +* [`unnecessary_debug_formatting`] fixed FP inside `Debug` impl + [#14955](https://github.com/rust-lang/rust-clippy/pull/14955) +* [`assign_op_pattern`] fixed FP on unstable const trait + [#14886](https://github.com/rust-lang/rust-clippy/pull/14886) +* [`useless_conversion`] fixed FP when using `.into_iter().any()` + [#14800](https://github.com/rust-lang/rust-clippy/pull/14800) +* [`collapsible_if`] fixed FP on block stmt before expr + [#14730](https://github.com/rust-lang/rust-clippy/pull/14730) +* [`manual_unwrap_or_default`] fixed FP on ref binding + [#14731](https://github.com/rust-lang/rust-clippy/pull/14731) +* [`unused_async`] fixed FP on default impl + [#14720](https://github.com/rust-lang/rust-clippy/pull/14720) +* [`manual_slice_fill`] fixed FP on `IndexMut` overload + [#14719](https://github.com/rust-lang/rust-clippy/pull/14719) +* [`unnecessary_to_owned`] fixed FP when map key is a reference + [#14834](https://github.com/rust-lang/rust-clippy/pull/14834) + +### ICE Fixes + +* [`mutable_key_type`] fixed ICE when infinitely associated generic types are used + [#14965](https://github.com/rust-lang/rust-clippy/pull/14965) +* [`zero_sized_map_values`] fixed ICE while computing type layout + [#14837](https://github.com/rust-lang/rust-clippy/pull/14837) +* [`useless_asref`] fixed ICE on trait method + [#14830](https://github.com/rust-lang/rust-clippy/pull/14830) +* [`manual_slice_size_calculation`] fixed ICE in suggestion and triggers in `const` context + [#14804](https://github.com/rust-lang/rust-clippy/pull/14804) +* [`missing_const_for_fn`]: fix ICE with some compilation options + [#14776](https://github.com/rust-lang/rust-clippy/pull/14776) + +### Documentation Improvements + +* [`manual_contains`] improved documentation wording + [#14917](https://github.com/rust-lang/rust-clippy/pull/14917) + +### Performance improvements + +* [`strlen_on_c_strings`] optimized by 99.75% (31M → 76k instructions) + [#15043](https://github.com/rust-lang/rust-clippy/pull/15043) +* Reduced documentation lints execution time by 85% (7.5% → 1% of total runtime) + [#14870](https://github.com/rust-lang/rust-clippy/pull/14870) +* [`unit_return_expecting_ord`] optimized to reduce binder instantiation from 95k to 10k calls + [#14905](https://github.com/rust-lang/rust-clippy/pull/14905) +* [`doc_markdown`] optimized by 50% + [#14693](https://github.com/rust-lang/rust-clippy/pull/14693) +* Refactor and speed up `cargo dev fmt` + [#14638](https://github.com/rust-lang/rust-clippy/pull/14638) ## Rust 1.88 diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 37accff5eaa8..dcc439a272cf 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -807,7 +807,7 @@ declare_clippy_lint! { /// ```no_run /// let _ = u16::MAX as usize; /// ``` - #[clippy::version = "1.86.0"] + #[clippy::version = "1.89.0"] pub CONFUSING_METHOD_TO_NUMERIC_CAST, suspicious, "casting a primitive method pointer to any integer type" diff --git a/clippy_lints/src/cloned_ref_to_slice_refs.rs b/clippy_lints/src/cloned_ref_to_slice_refs.rs index e33a8e0fb742..72ab292ee3c6 100644 --- a/clippy_lints/src/cloned_ref_to_slice_refs.rs +++ b/clippy_lints/src/cloned_ref_to_slice_refs.rs @@ -37,7 +37,7 @@ declare_clippy_lint! { /// let data_ref = &data; /// take_slice(slice::from_ref(data_ref)); /// ``` - #[clippy::version = "1.87.0"] + #[clippy::version = "1.89.0"] pub CLONED_REF_TO_SLICE_REFS, perf, "cloning a reference for slice references" diff --git a/clippy_lints/src/coerce_container_to_any.rs b/clippy_lints/src/coerce_container_to_any.rs index 6217fc4c8977..2e3acb7748e2 100644 --- a/clippy_lints/src/coerce_container_to_any.rs +++ b/clippy_lints/src/coerce_container_to_any.rs @@ -41,7 +41,7 @@ declare_clippy_lint! { /// // Succeeds since we have a &dyn Any to the inner u32! /// assert_eq!(dyn_any_of_u32.downcast_ref::(), Some(&0u32)); /// ``` - #[clippy::version = "1.88.0"] + #[clippy::version = "1.89.0"] pub COERCE_CONTAINER_TO_ANY, nursery, "coercing to `&dyn Any` when dereferencing could produce a `dyn Any` without coercion is usually not intended" diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index ea0da0d24675..d27d68d38664 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -662,7 +662,7 @@ declare_clippy_lint! { /// /// [^1]: defined here /// fn my_fn() {} /// ``` - #[clippy::version = "1.88.0"] + #[clippy::version = "1.89.0"] pub DOC_SUSPICIOUS_FOOTNOTES, suspicious, "looks like a link or footnote ref, but with no definition" diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index a251f15ba3da..aa8efcc1df17 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -222,7 +222,7 @@ declare_clippy_lint! { /// ``` /// /// [pointer format]: https://doc.rust-lang.org/std/fmt/index.html#formatting-traits - #[clippy::version = "1.88.0"] + #[clippy::version = "1.89.0"] pub POINTER_FORMAT, restriction, "formatting a pointer" diff --git a/clippy_lints/src/infallible_try_from.rs b/clippy_lints/src/infallible_try_from.rs index fba11adcd627..aa6fc78dbbcf 100644 --- a/clippy_lints/src/infallible_try_from.rs +++ b/clippy_lints/src/infallible_try_from.rs @@ -35,7 +35,7 @@ declare_clippy_lint! { /// } /// } /// ``` - #[clippy::version = "1.88.0"] + #[clippy::version = "1.89.0"] pub INFALLIBLE_TRY_FROM, suspicious, "TryFrom with infallible Error type" From eea4d6dc3cdc96cea7d4367726a6c0f18ab5a4ba Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 12 Jul 2025 14:47:28 +0200 Subject: [PATCH 43/65] `{flat_,}map_identity`: recognize (tuple) struct de- and restructuring base check same fields different struct reordered fields different paths to the same struct same for tuple structs style: use `zip`-the-function all over the place makes the code a bit more concise by removing the need for explicit `.iter()` style: move precondition checking to the match guard the match arms above put the "sanity" checks in the guard, and call only `check_pat` in the body. With this commit, the (tuple) struct cases follow that convention as well. Well, almost -- I think the ident check belongs to the second category of checks, so I put it in the body as well misc: use `None` in the pattern directly this'll probably be marginally faster thanks to the equality check being now structural move the tests to the right file --- clippy_utils/src/lib.rs | 47 ++++++++++++++++++++------ tests/ui/map_identity.fixed | 64 +++++++++++++++++++++++++++++++++++- tests/ui/map_identity.rs | 64 +++++++++++++++++++++++++++++++++++- tests/ui/map_identity.stderr | 38 ++++++++++++++++++++- 4 files changed, 200 insertions(+), 13 deletions(-) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index d8dd767d2e1d..a14ea72ba0c7 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -84,7 +84,7 @@ pub use self::hir_utils::{ use core::mem; use core::ops::ControlFlow; use std::collections::hash_map::Entry; -use std::iter::{once, repeat_n}; +use std::iter::{once, repeat_n, zip}; use std::sync::{Mutex, MutexGuard, OnceLock}; use itertools::Itertools; @@ -582,7 +582,7 @@ pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) - return false; } - for (x1, x2) in s1.iter().zip(s2.iter()) { + for (x1, x2) in zip(&s1, &s2) { if expr_custom_deref_adjustment(cx, x1).is_some() || expr_custom_deref_adjustment(cx, x2).is_some() { return false; } @@ -1898,6 +1898,8 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { /// * `|x| { return x; }` /// * `|(x, y)| (x, y)` /// * `|[x, y]| [x, y]` +/// * `|Foo(bar, baz)| Foo(bar, baz)` +/// * `|Foo { bar, baz }| Foo { bar, baz }` /// /// Consider calling [`is_expr_untyped_identity_function`] or [`is_expr_identity_function`] instead. fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool { @@ -1942,6 +1944,8 @@ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool { /// * `x` is the identity representation of `x` /// * `(x, y)` is the identity representation of `(x, y)` /// * `[x, y]` is the identity representation of `[x, y]` +/// * `Foo(bar, baz)` is the identity representation of `Foo(bar, baz)` +/// * `Foo { bar, baz }` is the identity representation of `Foo { bar, baz }` /// /// Note that `by_hir` is used to determine bindings are checked by their `HirId` or by their name. /// This can be useful when checking patterns in `let` bindings or `match` arms. @@ -1958,6 +1962,9 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr< return false; } + // NOTE: we're inside a (function) body, so this won't ICE + let qpath_res = |qpath, hir| cx.typeck_results().qpath_res(qpath, hir); + match (pat.kind, expr.kind) { (PatKind::Binding(_, id, _, _), _) if by_hir => { path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty() @@ -1968,16 +1975,36 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr< (PatKind::Tuple(pats, dotdot), ExprKind::Tup(tup)) if dotdot.as_opt_usize().is_none() && pats.len() == tup.len() => { - pats.iter() - .zip(tup) - .all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir)) + zip(pats, tup).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir)) }, - (PatKind::Slice(before, slice, after), ExprKind::Array(arr)) - if slice.is_none() && before.len() + after.len() == arr.len() => + (PatKind::Slice(before, None, after), ExprKind::Array(arr)) if before.len() + after.len() == arr.len() => { + zip(before.iter().chain(after), arr).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir)) + }, + (PatKind::TupleStruct(pat_ident, field_pats, dotdot), ExprKind::Call(ident, fields)) + if dotdot.as_opt_usize().is_none() && field_pats.len() == fields.len() => { - (before.iter().chain(after)) - .zip(arr) - .all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir)) + // check ident + if let ExprKind::Path(ident) = &ident.kind + && qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id) + // check fields + && zip(field_pats, fields).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr,by_hir)) + { + true + } else { + false + } + }, + (PatKind::Struct(pat_ident, field_pats, false), ExprKind::Struct(ident, fields, hir::StructTailExpr::None)) + if field_pats.len() == fields.len() => + { + // check ident + qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id) + // check fields + && field_pats.iter().all(|field_pat| { + fields.iter().any(|field| { + field_pat.ident == field.ident && is_expr_identity_of_pat(cx, field_pat.pat, field.expr, by_hir) + }) + }) }, _ => false, } diff --git a/tests/ui/map_identity.fixed b/tests/ui/map_identity.fixed index b82d3e6d9567..6c971ba63384 100644 --- a/tests/ui/map_identity.fixed +++ b/tests/ui/map_identity.fixed @@ -1,5 +1,5 @@ #![warn(clippy::map_identity)] -#![allow(clippy::needless_return)] +#![allow(clippy::needless_return, clippy::disallowed_names)] fn main() { let x: [u16; 3] = [1, 2, 3]; @@ -99,3 +99,65 @@ fn issue15198() { let _ = x.iter().copied(); //~^ map_identity } + +mod foo { + #[derive(Clone, Copy)] + pub struct Foo { + pub foo: u8, + pub bar: u8, + } + + #[derive(Clone, Copy)] + pub struct Foo2(pub u8, pub u8); +} +use foo::{Foo, Foo2}; + +struct Bar { + foo: u8, + bar: u8, +} + +struct Bar2(u8, u8); + +fn structs() { + let x = [Foo { foo: 0, bar: 0 }]; + + let _ = x.into_iter(); + //~^ map_identity + + // still lint when different paths are used for the same struct + let _ = x.into_iter(); + //~^ map_identity + + // don't lint: same fields but different structs + let _ = x.into_iter().map(|Foo { foo, bar }| Bar { foo, bar }); + + // still lint with redundant field names + #[allow(clippy::redundant_field_names)] + let _ = x.into_iter(); + //~^ map_identity + + // still lint with field order change + let _ = x.into_iter(); + //~^ map_identity + + // don't lint: switched field assignment + let _ = x.into_iter().map(|Foo { foo, bar }| Foo { foo: bar, bar: foo }); +} + +fn tuple_structs() { + let x = [Foo2(0, 0)]; + + let _ = x.into_iter(); + //~^ map_identity + + // still lint when different paths are used for the same struct + let _ = x.into_iter(); + //~^ map_identity + + // don't lint: same fields but different structs + let _ = x.into_iter().map(|Foo2(foo, bar)| Bar2(foo, bar)); + + // don't lint: switched field assignment + let _ = x.into_iter().map(|Foo2(foo, bar)| Foo2(bar, foo)); +} diff --git a/tests/ui/map_identity.rs b/tests/ui/map_identity.rs index c295bf872701..59dcfcda3b6e 100644 --- a/tests/ui/map_identity.rs +++ b/tests/ui/map_identity.rs @@ -1,5 +1,5 @@ #![warn(clippy::map_identity)] -#![allow(clippy::needless_return)] +#![allow(clippy::needless_return, clippy::disallowed_names)] fn main() { let x: [u16; 3] = [1, 2, 3]; @@ -105,3 +105,65 @@ fn issue15198() { let _ = x.iter().copied().map(|[x, y]| [x, y]); //~^ map_identity } + +mod foo { + #[derive(Clone, Copy)] + pub struct Foo { + pub foo: u8, + pub bar: u8, + } + + #[derive(Clone, Copy)] + pub struct Foo2(pub u8, pub u8); +} +use foo::{Foo, Foo2}; + +struct Bar { + foo: u8, + bar: u8, +} + +struct Bar2(u8, u8); + +fn structs() { + let x = [Foo { foo: 0, bar: 0 }]; + + let _ = x.into_iter().map(|Foo { foo, bar }| Foo { foo, bar }); + //~^ map_identity + + // still lint when different paths are used for the same struct + let _ = x.into_iter().map(|Foo { foo, bar }| foo::Foo { foo, bar }); + //~^ map_identity + + // don't lint: same fields but different structs + let _ = x.into_iter().map(|Foo { foo, bar }| Bar { foo, bar }); + + // still lint with redundant field names + #[allow(clippy::redundant_field_names)] + let _ = x.into_iter().map(|Foo { foo, bar }| Foo { foo: foo, bar: bar }); + //~^ map_identity + + // still lint with field order change + let _ = x.into_iter().map(|Foo { foo, bar }| Foo { bar, foo }); + //~^ map_identity + + // don't lint: switched field assignment + let _ = x.into_iter().map(|Foo { foo, bar }| Foo { foo: bar, bar: foo }); +} + +fn tuple_structs() { + let x = [Foo2(0, 0)]; + + let _ = x.into_iter().map(|Foo2(foo, bar)| Foo2(foo, bar)); + //~^ map_identity + + // still lint when different paths are used for the same struct + let _ = x.into_iter().map(|Foo2(foo, bar)| foo::Foo2(foo, bar)); + //~^ map_identity + + // don't lint: same fields but different structs + let _ = x.into_iter().map(|Foo2(foo, bar)| Bar2(foo, bar)); + + // don't lint: switched field assignment + let _ = x.into_iter().map(|Foo2(foo, bar)| Foo2(bar, foo)); +} diff --git a/tests/ui/map_identity.stderr b/tests/ui/map_identity.stderr index 9b624a0dc755..a50c0d6b87b5 100644 --- a/tests/ui/map_identity.stderr +++ b/tests/ui/map_identity.stderr @@ -93,5 +93,41 @@ error: unnecessary map of the identity function LL | let _ = x.iter().copied().map(|[x, y]| [x, y]); | ^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` -error: aborting due to 14 previous errors +error: unnecessary map of the identity function + --> tests/ui/map_identity.rs:131:26 + | +LL | let _ = x.into_iter().map(|Foo { foo, bar }| Foo { foo, bar }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` + +error: unnecessary map of the identity function + --> tests/ui/map_identity.rs:135:26 + | +LL | let _ = x.into_iter().map(|Foo { foo, bar }| foo::Foo { foo, bar }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` + +error: unnecessary map of the identity function + --> tests/ui/map_identity.rs:143:26 + | +LL | let _ = x.into_iter().map(|Foo { foo, bar }| Foo { foo: foo, bar: bar }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` + +error: unnecessary map of the identity function + --> tests/ui/map_identity.rs:147:26 + | +LL | let _ = x.into_iter().map(|Foo { foo, bar }| Foo { bar, foo }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` + +error: unnecessary map of the identity function + --> tests/ui/map_identity.rs:157:26 + | +LL | let _ = x.into_iter().map(|Foo2(foo, bar)| Foo2(foo, bar)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` + +error: unnecessary map of the identity function + --> tests/ui/map_identity.rs:161:26 + | +LL | let _ = x.into_iter().map(|Foo2(foo, bar)| foo::Foo2(foo, bar)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map` + +error: aborting due to 20 previous errors From 01d960f645b3f1e47a79ea925f9e04a5bfbbd769 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Sat, 2 Aug 2025 01:57:57 +0200 Subject: [PATCH 44/65] Optimize broken_links by 99.77% --- clippy_lints/src/doc/broken_link.rs | 76 ++++++++++++++--------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/clippy_lints/src/doc/broken_link.rs b/clippy_lints/src/doc/broken_link.rs index 4af10510023d..8878fa9180fe 100644 --- a/clippy_lints/src/doc/broken_link.rs +++ b/clippy_lints/src/doc/broken_link.rs @@ -19,52 +19,52 @@ pub fn check(cx: &LateContext<'_>, bl: &PullDownBrokenLink<'_>, doc: &str, fragm } fn warn_if_broken_link(cx: &LateContext<'_>, bl: &PullDownBrokenLink<'_>, doc: &str, fragments: &[DocFragment]) { - if let Some((span, _)) = source_span_for_markdown_range(cx.tcx, doc, &bl.span, fragments) { - let mut len = 0; + let mut len = 0; - // grab raw link data - let (_, raw_link) = doc.split_at(bl.span.start); + // grab raw link data + let (_, raw_link) = doc.split_at(bl.span.start); - // strip off link text part - let raw_link = match raw_link.split_once(']') { - None => return, - Some((prefix, suffix)) => { - len += prefix.len() + 1; - suffix - }, - }; + // strip off link text part + let raw_link = match raw_link.split_once(']') { + None => return, + Some((prefix, suffix)) => { + len += prefix.len() + 1; + suffix + }, + }; - let raw_link = match raw_link.split_once('(') { - None => return, - Some((prefix, suffix)) => { - if !prefix.is_empty() { - // there is text between ']' and '(' chars, so it is not a valid link - return; - } - len += prefix.len() + 1; - suffix - }, - }; + let raw_link = match raw_link.split_once('(') { + None => return, + Some((prefix, suffix)) => { + if !prefix.is_empty() { + // there is text between ']' and '(' chars, so it is not a valid link + return; + } + len += prefix.len() + 1; + suffix + }, + }; - if raw_link.starts_with("(http") { - // reduce chances of false positive reports - // by limiting this checking only to http/https links. + if raw_link.starts_with("(http") { + // reduce chances of false positive reports + // by limiting this checking only to http/https links. + return; + } + + for c in raw_link.chars() { + if c == ')' { + // it is a valid link return; } - for c in raw_link.chars() { - if c == ')' { - // it is a valid link - return; - } - - if c == '\n' { - report_broken_link(cx, span, len); - break; - } - - len += 1; + if c == '\n' + && let Some((span, _)) = source_span_for_markdown_range(cx.tcx, doc, &bl.span, fragments) + { + report_broken_link(cx, span, len); + break; } + + len += 1; } } From 0f98da7c5ccb5e873ddc08aef7508213a98b6aaa Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 2 Aug 2025 09:13:16 +0200 Subject: [PATCH 45/65] fix: let_with_type_underscore don't eat closing paren in let (i): _ = 0; add failing tests fix also remove whitespace before `:` --- clippy_lints/src/let_with_type_underscore.rs | 11 ++++- tests/ui/let_with_type_underscore.fixed | 12 +++++ tests/ui/let_with_type_underscore.rs | 12 +++++ tests/ui/let_with_type_underscore.stderr | 50 +++++++++++++++++++- 4 files changed, 82 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/let_with_type_underscore.rs b/clippy_lints/src/let_with_type_underscore.rs index 1917ca24a05b..5b0f95ffc377 100644 --- a/clippy_lints/src/let_with_type_underscore.rs +++ b/clippy_lints/src/let_with_type_underscore.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_from_proc_macro; +use clippy_utils::source::{IntoSpan, SpanRangeExt}; use rustc_errors::Applicability; use rustc_hir::{LetStmt, TyKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -30,9 +31,15 @@ impl<'tcx> LateLintPass<'tcx> for UnderscoreTyped { 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) - && !local.span.in_external_macro(cx.tcx.sess.source_map()) + && let sm = cx.tcx.sess.source_map() + && !local.span.in_external_macro(sm) && !is_from_proc_macro(cx, ty) { + let span_to_remove = sm + .span_extend_to_prev_char_before(ty.span, ':', true) + .with_leading_whitespace(cx) + .into_span(); + span_lint_and_then( cx, LET_WITH_TYPE_UNDERSCORE, @@ -40,7 +47,7 @@ impl<'tcx> LateLintPass<'tcx> for UnderscoreTyped { "variable declared with type underscore", |diag| { diag.span_suggestion_verbose( - ty.span.with_lo(local.pat.span.hi()), + span_to_remove, "remove the explicit type `_` declaration", "", Applicability::MachineApplicable, diff --git a/tests/ui/let_with_type_underscore.fixed b/tests/ui/let_with_type_underscore.fixed index 7a4af4e3d1e7..6339fe595c21 100644 --- a/tests/ui/let_with_type_underscore.fixed +++ b/tests/ui/let_with_type_underscore.fixed @@ -45,3 +45,15 @@ fn main() { x = (); }; } + +fn issue15377() { + let (a) = 0; + //~^ let_with_type_underscore + let ((a)) = 0; + //~^ let_with_type_underscore + let ((a,)) = (0,); + //~^ let_with_type_underscore + #[rustfmt::skip] + let ( (a ) ) = 0; + //~^ let_with_type_underscore +} diff --git a/tests/ui/let_with_type_underscore.rs b/tests/ui/let_with_type_underscore.rs index a7c2f598b56d..bd85346cf01a 100644 --- a/tests/ui/let_with_type_underscore.rs +++ b/tests/ui/let_with_type_underscore.rs @@ -45,3 +45,15 @@ fn main() { x = (); }; } + +fn issue15377() { + let (a): _ = 0; + //~^ let_with_type_underscore + let ((a)): _ = 0; + //~^ let_with_type_underscore + let ((a,)): _ = (0,); + //~^ let_with_type_underscore + #[rustfmt::skip] + let ( (a ) ): _ = 0; + //~^ let_with_type_underscore +} diff --git a/tests/ui/let_with_type_underscore.stderr b/tests/ui/let_with_type_underscore.stderr index 9179f9922071..c3f6c82397b8 100644 --- a/tests/ui/let_with_type_underscore.stderr +++ b/tests/ui/let_with_type_underscore.stderr @@ -60,5 +60,53 @@ LL - let x : _ = 1; LL + let x = 1; | -error: aborting due to 5 previous errors +error: variable declared with type underscore + --> tests/ui/let_with_type_underscore.rs:50:5 + | +LL | let (a): _ = 0; + | ^^^^^^^^^^^^^^^ + | +help: remove the explicit type `_` declaration + | +LL - let (a): _ = 0; +LL + let (a) = 0; + | + +error: variable declared with type underscore + --> tests/ui/let_with_type_underscore.rs:52:5 + | +LL | let ((a)): _ = 0; + | ^^^^^^^^^^^^^^^^^ + | +help: remove the explicit type `_` declaration + | +LL - let ((a)): _ = 0; +LL + let ((a)) = 0; + | + +error: variable declared with type underscore + --> tests/ui/let_with_type_underscore.rs:54:5 + | +LL | let ((a,)): _ = (0,); + | ^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit type `_` declaration + | +LL - let ((a,)): _ = (0,); +LL + let ((a,)) = (0,); + | + +error: variable declared with type underscore + --> tests/ui/let_with_type_underscore.rs:57:5 + | +LL | let ( (a ) ): _ = 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit type `_` declaration + | +LL - let ( (a ) ): _ = 0; +LL + let ( (a ) ) = 0; + | + +error: aborting due to 9 previous errors From b8c16e47f4a2757992941a421423534be2dc8a87 Mon Sep 17 00:00:00 2001 From: Zihan Date: Sat, 2 Aug 2025 12:20:31 -0400 Subject: [PATCH 46/65] fix: `option_if_let_else` keep deref op if the inner expr is a raw pointer closes rust-clippy/issues/15379 Signed-off-by: Zihan --- clippy_lints/src/option_if_let_else.rs | 3 ++- tests/ui/option_if_let_else.fixed | 7 +++++++ tests/ui/option_if_let_else.rs | 7 +++++++ tests/ui/option_if_let_else.stderr | 8 +++++++- 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index 9487cec87efb..6c0bf2ad1545 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -127,7 +127,8 @@ fn try_get_option_occurrence<'tcx>( if_else: &'tcx Expr<'_>, ) -> Option { let cond_expr = match expr.kind { - ExprKind::Unary(UnOp::Deref, inner_expr) | ExprKind::AddrOf(_, _, inner_expr) => inner_expr, + ExprKind::AddrOf(_, _, inner_expr) => inner_expr, + ExprKind::Unary(UnOp::Deref, inner_expr) if !cx.typeck_results().expr_ty(inner_expr).is_raw_ptr() => inner_expr, _ => expr, }; let (inner_pat, is_result) = try_get_inner_pat_and_is_result(cx, pat)?; diff --git a/tests/ui/option_if_let_else.fixed b/tests/ui/option_if_let_else.fixed index fe3ac9e8f92c..c418a358dcb3 100644 --- a/tests/ui/option_if_let_else.fixed +++ b/tests/ui/option_if_let_else.fixed @@ -302,3 +302,10 @@ mod issue11059 { if let Some(o) = o { o } else { &S } } } + +fn issue15379() { + let opt = Some(0usize); + let opt_raw_ptr = &opt as *const Option; + let _ = unsafe { (*opt_raw_ptr).map_or(1, |o| o) }; + //~^ option_if_let_else +} diff --git a/tests/ui/option_if_let_else.rs b/tests/ui/option_if_let_else.rs index 5b7498bc8e23..4c3e4be3caeb 100644 --- a/tests/ui/option_if_let_else.rs +++ b/tests/ui/option_if_let_else.rs @@ -365,3 +365,10 @@ mod issue11059 { if let Some(o) = o { o } else { &S } } } + +fn issue15379() { + let opt = Some(0usize); + let opt_raw_ptr = &opt as *const Option; + let _ = unsafe { if let Some(o) = *opt_raw_ptr { o } else { 1 } }; + //~^ option_if_let_else +} diff --git a/tests/ui/option_if_let_else.stderr b/tests/ui/option_if_let_else.stderr index 9eb41f81a539..514ecca41142 100644 --- a/tests/ui/option_if_let_else.stderr +++ b/tests/ui/option_if_let_else.stderr @@ -334,5 +334,11 @@ error: use Option::map_or_else instead of an if let/else 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: aborting due to 25 previous errors +error: use Option::map_or instead of an if let/else + --> tests/ui/option_if_let_else.rs:372: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: aborting due to 26 previous errors From 8f6b43dd656743730d32f5f7ee5a768434d25189 Mon Sep 17 00:00:00 2001 From: Zihan Date: Sat, 2 Aug 2025 12:54:04 -0400 Subject: [PATCH 47/65] fix: `option_if_let_else` don't suggest argless function for Result::map_or_else closes rust-clippy/issues/15002 Signed-off-by: Zihan --- clippy_lints/src/option_if_let_else.rs | 6 +++--- tests/ui/option_if_let_else.fixed | 5 +++++ tests/ui/option_if_let_else.rs | 9 +++++++++ tests/ui/option_if_let_else.stderr | 13 ++++++++++++- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index 6c0bf2ad1545..3483f3081a58 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -224,8 +224,8 @@ fn try_get_option_occurrence<'tcx>( let mut app = Applicability::Unspecified; - let (none_body, is_argless_call) = match none_body.kind { - ExprKind::Call(call_expr, []) if !none_body.span.from_expansion() => (call_expr, true), + let (none_body, can_omit_arg) = match none_body.kind { + ExprKind::Call(call_expr, []) if !none_body.span.from_expansion() && !is_result => (call_expr, true), _ => (none_body, false), }; @@ -242,7 +242,7 @@ fn try_get_option_occurrence<'tcx>( ), none_expr: format!( "{}{}", - if method_sugg == "map_or" || is_argless_call { + if method_sugg == "map_or" || can_omit_arg { "" } else if is_result { "|_| " diff --git a/tests/ui/option_if_let_else.fixed b/tests/ui/option_if_let_else.fixed index c418a358dcb3..0f86de5646cd 100644 --- a/tests/ui/option_if_let_else.fixed +++ b/tests/ui/option_if_let_else.fixed @@ -309,3 +309,8 @@ fn issue15379() { let _ = unsafe { (*opt_raw_ptr).map_or(1, |o| o) }; //~^ option_if_let_else } + +fn issue15002() { + let res: Result = Ok("_".to_string()); + let _ = res.map_or_else(|_| String::new(), |s| s.clone()); +} diff --git a/tests/ui/option_if_let_else.rs b/tests/ui/option_if_let_else.rs index 4c3e4be3caeb..7aabd778f87e 100644 --- a/tests/ui/option_if_let_else.rs +++ b/tests/ui/option_if_let_else.rs @@ -372,3 +372,12 @@ fn issue15379() { let _ = unsafe { if let Some(o) = *opt_raw_ptr { o } else { 1 } }; //~^ option_if_let_else } + +fn issue15002() { + let res: Result = Ok("_".to_string()); + let _ = match res { + //~^ option_if_let_else + Ok(s) => s.clone(), + Err(_) => String::new(), + }; +} diff --git a/tests/ui/option_if_let_else.stderr b/tests/ui/option_if_let_else.stderr index 514ecca41142..2e2fe6f20492 100644 --- a/tests/ui/option_if_let_else.stderr +++ b/tests/ui/option_if_let_else.stderr @@ -340,5 +340,16 @@ error: use Option::map_or instead of an if let/else LL | let _ = unsafe { if let Some(o) = *opt_raw_ptr { o } else { 1 } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(*opt_raw_ptr).map_or(1, |o| o)` -error: aborting due to 26 previous errors +error: use Option::map_or_else instead of an if let/else + --> tests/ui/option_if_let_else.rs:378:13 + | +LL | let _ = match res { + | _____________^ +LL | | +LL | | Ok(s) => s.clone(), +LL | | Err(_) => String::new(), +LL | | }; + | |_____^ help: try: `res.map_or_else(|_| String::new(), |s| s.clone())` + +error: aborting due to 27 previous errors From 02ebef4c6aef49cdc866c1367b2f12ef9082e1ea Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 3 Aug 2025 17:13:54 +0200 Subject: [PATCH 48/65] don't allocate a `Vec` in an `Iterator::chain` --- clippy_lints/src/loops/never_loop.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index 8a253ae5810f..2ccff7680976 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -202,7 +202,7 @@ fn all_spans_after_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> Vec { .iter() .skip_while(|inner| inner.hir_id != stmt.hir_id) .map(stmt_source_span) - .chain(if let Some(e) = block.expr { vec![e.span] } else { vec![] }) + .chain(block.expr.map(|e| e.span)) .collect(); } From 3b907eada71ea36710566e6fd22a268c42b55e94 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 3 Aug 2025 12:09:06 +0200 Subject: [PATCH 49/65] use parens for clearer formatting the first and second lines now each represent one approach to getting a `source` --- clippy_utils/src/source.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index 7d21336be1cd..277b074ec587 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -342,10 +342,7 @@ impl SourceFileRange { /// Attempts to get the text from the source file. This can fail if the source text isn't /// loaded. pub fn as_str(&self) -> Option<&str> { - self.sf - .src - .as_ref() - .map(|src| src.as_str()) + (self.sf.src.as_ref().map(|src| src.as_str())) .or_else(|| self.sf.external_src.get().and_then(|src| src.get_source())) .and_then(|x| x.get(self.range.clone())) } From 2d8d45e20184628bbf35883164278c0892a7364d Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 3 Aug 2025 12:12:52 +0200 Subject: [PATCH 50/65] use `?` for brevity --- clippy_utils/src/source.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index 277b074ec587..e675291b6f3a 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -343,7 +343,7 @@ impl SourceFileRange { /// loaded. pub fn as_str(&self) -> Option<&str> { (self.sf.src.as_ref().map(|src| src.as_str())) - .or_else(|| self.sf.external_src.get().and_then(|src| src.get_source())) + .or_else(|| self.sf.external_src.get()?.get_source()) .and_then(|x| x.get(self.range.clone())) } } From 68ef9c4ceed5f376f36e4b3e23094a115dd0d4c0 Mon Sep 17 00:00:00 2001 From: alexey semenyuk Date: Sat, 2 Aug 2025 21:47:53 +0500 Subject: [PATCH 51/65] Task priority --- CONTRIBUTING.md | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 72ab08792d37..42ed624ec212 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -199,26 +199,34 @@ currently. Between writing new lints, fixing issues, reviewing pull requests and responding to issues there may not always be enough time to stay on top of it all. -Our highest priority is fixing [ICEs][I-ICE] and [bugs][C-bug], for example -an ICE in a popular crate that many other crates depend on. We don't -want Clippy to crash on your code and we want it to be as reliable as the -suggestions from Rust compiler errors. +To find things to fix, go to the [tracking issue][tracking_issue], find an issue that you like, +and claim it with `@rustbot claim`. -We have prioritization labels and a sync-blocker label, which are described below. -- [P-low][p-low]: Requires attention (fix/response/evaluation) by a team member but isn't urgent. -- [P-medium][p-medium]: Should be addressed by a team member until the next sync. -- [P-high][p-high]: Should be immediately addressed and will require an out-of-cycle sync or a backport. -- [L-sync-blocker][l-sync-blocker]: An issue that "blocks" a sync. -Or rather: before the sync this should be addressed, -e.g. by removing a lint again, so it doesn't hit beta/stable. +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. [triage]: https://forge.rust-lang.org/release/triage-procedure.html -[I-ICE]: https://github.com/rust-lang/rust-clippy/labels/I-ICE -[C-bug]: https://github.com/rust-lang/rust-clippy/labels/C-bug -[p-low]: https://github.com/rust-lang/rust-clippy/labels/P-low -[p-medium]: https://github.com/rust-lang/rust-clippy/labels/P-medium -[p-high]: https://github.com/rust-lang/rust-clippy/labels/P-high -[l-sync-blocker]: https://github.com/rust-lang/rust-clippy/labels/L-sync-blocker +[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 ## Contributions From 1fc5e81436d6e08dacf2e6672ada8936d93e94b9 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 2 Aug 2025 09:27:07 +0200 Subject: [PATCH 52/65] clean-up `semicolon_inside_block` match on `StmtKind` directly use a let-chain break up match for a nicer let-chain shorten `get_line` move `expr` check down in the inside case as well for consistency use `shrink_to_hi` Apply suggestion Co-authored-by: Samuel Tardieu --- clippy_lints/src/semicolon_block.rs | 36 ++++++++++------------------- 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/semicolon_block.rs b/clippy_lints/src/semicolon_block.rs index f6c128d4c529..db91c57b1816 100644 --- a/clippy_lints/src/semicolon_block.rs +++ b/clippy_lints/src/semicolon_block.rs @@ -102,7 +102,7 @@ impl SemicolonBlock { } fn semicolon_outside_block(&self, cx: &LateContext<'_>, block: &Block<'_>, tail_stmt_expr: &Expr<'_>) { - let insert_span = block.span.with_lo(block.span.hi()); + let insert_span = block.span.shrink_to_hi(); // For macro call semicolon statements (`mac!();`), the statement's span does not actually // include the semicolon itself, so use `mac_call_stmt_semi_span`, which finds the semicolon @@ -144,28 +144,20 @@ impl LateLintPass<'_> for SemicolonBlock { kind: ExprKind::Block(block, _), .. }) if !block.span.from_expansion() && stmt.span.contains(block.span) => { - let Block { - expr: None, - stmts: [.., stmt], - .. - } = block - else { - return; - }; - let &Stmt { - kind: StmtKind::Semi(expr), - .. - } = stmt - else { - return; - }; - self.semicolon_outside_block(cx, block, expr); + if block.expr.is_none() + && let [.., stmt] = block.stmts + && let StmtKind::Semi(expr) = stmt.kind + { + self.semicolon_outside_block(cx, block, expr); + } }, StmtKind::Semi(Expr { - kind: ExprKind::Block(block @ Block { expr: Some(tail), .. }, _), + kind: ExprKind::Block(block, _), .. }) if !block.span.from_expansion() => { - self.semicolon_inside_block(cx, block, tail, stmt.span); + if let Some(tail) = block.expr { + self.semicolon_inside_block(cx, block, tail, stmt.span); + } }, _ => (), } @@ -173,9 +165,5 @@ impl LateLintPass<'_> for SemicolonBlock { } fn get_line(cx: &LateContext<'_>, span: Span) -> Option { - if let Ok(line) = cx.sess().source_map().lookup_line(span.lo()) { - return Some(line.line); - } - - None + cx.sess().source_map().lookup_line(span.lo()).ok().map(|line| line.line) } From 0708b6a1e685f140f653fa5f77a3c1318f0f74ff Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Mon, 4 Aug 2025 18:25:46 +0500 Subject: [PATCH 53/65] Fix not showing deprecated lints --- util/gh-pages/script.js | 1 + 1 file changed, 1 insertion(+) diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index d32049675319..cfc5fb7b27ca 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -208,6 +208,7 @@ const LEVEL_FILTERS_DEFAULT = { allow: true, warn: true, deny: true, + none: true, }; const APPLICABILITIES_FILTER_DEFAULT = { Unspecified: true, From 5b22c0c1edeee30f59b5e3e015f5a2279add821b Mon Sep 17 00:00:00 2001 From: Fayti1703 Date: Mon, 4 Aug 2025 16:38:31 +0200 Subject: [PATCH 54/65] Remove rogue comma from infallible_try_from lint message --- clippy_lints/src/infallible_try_from.rs | 2 +- tests/ui/infallible_try_from.stderr | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/infallible_try_from.rs b/clippy_lints/src/infallible_try_from.rs index aa6fc78dbbcf..589c294a678a 100644 --- a/clippy_lints/src/infallible_try_from.rs +++ b/clippy_lints/src/infallible_try_from.rs @@ -71,7 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for InfallibleTryFrom { cx, INFALLIBLE_TRY_FROM, span, - "infallible TryFrom impl; consider implementing From, instead", + "infallible TryFrom impl; consider implementing From instead", ); } } diff --git a/tests/ui/infallible_try_from.stderr b/tests/ui/infallible_try_from.stderr index 705b1188489c..d1e0d9e7d3bb 100644 --- a/tests/ui/infallible_try_from.stderr +++ b/tests/ui/infallible_try_from.stderr @@ -1,4 +1,4 @@ -error: infallible TryFrom impl; consider implementing From, instead +error: infallible TryFrom impl; consider implementing From instead --> tests/ui/infallible_try_from.rs:8:1 | LL | impl TryFrom for MyStruct { @@ -10,7 +10,7 @@ LL | type Error = !; = note: `-D clippy::infallible-try-from` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::infallible_try_from)]` -error: infallible TryFrom impl; consider implementing From, instead +error: infallible TryFrom impl; consider implementing From instead --> tests/ui/infallible_try_from.rs:16:1 | LL | impl TryFrom for MyStruct { From 6c7fa3b2cb23bf88fd6adfb855c45871a719bc4c Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Tue, 5 Aug 2025 11:18:26 +0200 Subject: [PATCH 55/65] Do not lint for `wildcard_imports` in external macro --- clippy_lints/src/wildcard_imports.rs | 2 +- tests/ui-toml/wildcard_imports/wildcard_imports.fixed | 11 +++++++++++ tests/ui-toml/wildcard_imports/wildcard_imports.rs | 11 +++++++++++ .../ui-toml/wildcard_imports/wildcard_imports.stderr | 6 +++--- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/wildcard_imports.rs b/clippy_lints/src/wildcard_imports.rs index 22fd15d153ab..a2523b5fb07c 100644 --- a/clippy_lints/src/wildcard_imports.rs +++ b/clippy_lints/src/wildcard_imports.rs @@ -118,7 +118,7 @@ impl_lint_pass!(WildcardImports => [ENUM_GLOB_USE, WILDCARD_IMPORTS]); impl LateLintPass<'_> for WildcardImports { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { - if cx.sess().is_test_crate() { + if cx.sess().is_test_crate() || item.span.in_external_macro(cx.sess().source_map()) { return; } diff --git a/tests/ui-toml/wildcard_imports/wildcard_imports.fixed b/tests/ui-toml/wildcard_imports/wildcard_imports.fixed index 20511cbed165..ec9fbcfd4a37 100644 --- a/tests/ui-toml/wildcard_imports/wildcard_imports.fixed +++ b/tests/ui-toml/wildcard_imports/wildcard_imports.fixed @@ -1,3 +1,4 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs #![warn(clippy::wildcard_imports)] mod prelude { @@ -13,6 +14,10 @@ mod my_crate { pub mod utils { pub fn my_util_fn() {} } + + pub mod utils2 { + pub const SOME_CONST: u32 = 1; + } } pub use utils::{BAR, print}; @@ -22,6 +27,12 @@ use my_crate::utils::my_util_fn; use prelude::FOO; //~^ ERROR: usage of wildcard import +proc_macros::external! { + use my_crate::utils2::*; + + static SOME_STATIC: u32 = SOME_CONST; +} + fn main() { let _ = FOO; let _ = BAR; diff --git a/tests/ui-toml/wildcard_imports/wildcard_imports.rs b/tests/ui-toml/wildcard_imports/wildcard_imports.rs index 8d05910f471b..233ee19f89b9 100644 --- a/tests/ui-toml/wildcard_imports/wildcard_imports.rs +++ b/tests/ui-toml/wildcard_imports/wildcard_imports.rs @@ -1,3 +1,4 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs #![warn(clippy::wildcard_imports)] mod prelude { @@ -13,6 +14,10 @@ mod my_crate { pub mod utils { pub fn my_util_fn() {} } + + pub mod utils2 { + pub const SOME_CONST: u32 = 1; + } } pub use utils::*; @@ -22,6 +27,12 @@ use my_crate::utils::*; use prelude::*; //~^ ERROR: usage of wildcard import +proc_macros::external! { + use my_crate::utils2::*; + + static SOME_STATIC: u32 = SOME_CONST; +} + fn main() { let _ = FOO; let _ = BAR; diff --git a/tests/ui-toml/wildcard_imports/wildcard_imports.stderr b/tests/ui-toml/wildcard_imports/wildcard_imports.stderr index 5e624dd6c3cd..5d37cb705f51 100644 --- a/tests/ui-toml/wildcard_imports/wildcard_imports.stderr +++ b/tests/ui-toml/wildcard_imports/wildcard_imports.stderr @@ -1,5 +1,5 @@ error: usage of wildcard import - --> tests/ui-toml/wildcard_imports/wildcard_imports.rs:18:9 + --> tests/ui-toml/wildcard_imports/wildcard_imports.rs:23:9 | LL | pub use utils::*; | ^^^^^^^^ help: try: `utils::{BAR, print}` @@ -8,13 +8,13 @@ LL | pub use utils::*; = help: to override `-D warnings` add `#[allow(clippy::wildcard_imports)]` error: usage of wildcard import - --> tests/ui-toml/wildcard_imports/wildcard_imports.rs:20:5 + --> tests/ui-toml/wildcard_imports/wildcard_imports.rs:25:5 | LL | use my_crate::utils::*; | ^^^^^^^^^^^^^^^^^^ help: try: `my_crate::utils::my_util_fn` error: usage of wildcard import - --> tests/ui-toml/wildcard_imports/wildcard_imports.rs:22:5 + --> tests/ui-toml/wildcard_imports/wildcard_imports.rs:27:5 | LL | use prelude::*; | ^^^^^^^^^^ help: try: `prelude::FOO` From 0ea413fe6b6acd4b30e5e96ffe6d73882b22e8b7 Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Tue, 5 Aug 2025 10:35:28 +0000 Subject: [PATCH 56/65] Generate lint count in template --- tests/compile-test.rs | 8 +++++++- util/gh-pages/index_template.html | 2 +- util/gh-pages/script.js | 12 ------------ 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 664a748ee21e..6b6dfd7b81ea 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -433,6 +433,7 @@ fn ui_cargo_toml_metadata() { #[derive(Template)] #[template(path = "index_template.html")] struct Renderer<'a> { + count: usize, lints: &'a Vec, } @@ -512,7 +513,12 @@ impl DiagnosticCollector { fs::write( "util/gh-pages/index.html", - Renderer { lints: &metadata }.render().unwrap(), + Renderer { + count: LINTS.len(), + lints: &metadata, + } + .render() + .unwrap(), ) .unwrap(); }); diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index 5d65ea585df6..327ccf6439e4 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -49,7 +49,7 @@ Otherwise, have a great day =^.^= {# #}
{# #} -

Clippy Lints

{# #} +

Clippy Lints Total number: {{+ count }}

{# #}