From 6a16a8fe5bf586d617e237be1a0fa971712759fe Mon Sep 17 00:00:00 2001 From: Zihan Date: Mon, 30 Mar 2026 16:54:31 +0800 Subject: [PATCH 01/38] add new module style restriction lint that checks for use of inline mods changelog: new lint: [`inline_modules`] Signed-off-by: Zihan --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/module_style.rs | 165 +++++++++++++++--- clippy_utils/src/ast_utils/mod.rs | 17 +- .../module_style/inline_mod/Cargo.stderr | 71 ++++++++ .../module_style/inline_mod/Cargo.toml | 9 + .../module_style/inline_mod/src/foo.rs | 2 + .../module_style/inline_mod/src/lib.rs | 34 ++++ .../module_style/inline_mod/src/other.rs | 1 + .../module_style/inline_mod/src/qux/foo.rs | 1 + .../module_style/inline_mod/src/qux/mod.rs | 1 + 11 files changed, 277 insertions(+), 26 deletions(-) create mode 100644 tests/ui-cargo/module_style/inline_mod/Cargo.stderr create mode 100644 tests/ui-cargo/module_style/inline_mod/Cargo.toml create mode 100644 tests/ui-cargo/module_style/inline_mod/src/foo.rs create mode 100644 tests/ui-cargo/module_style/inline_mod/src/lib.rs create mode 100644 tests/ui-cargo/module_style/inline_mod/src/other.rs create mode 100644 tests/ui-cargo/module_style/inline_mod/src/qux/foo.rs create mode 100644 tests/ui-cargo/module_style/inline_mod/src/qux/mod.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 45bfb65b2e67e..929ca2ad40951 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6692,6 +6692,7 @@ Released 2018-09-13 [`inline_asm_x86_att_syntax`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_asm_x86_att_syntax [`inline_asm_x86_intel_syntax`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_asm_x86_intel_syntax [`inline_fn_without_body`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_fn_without_body +[`inline_modules`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_modules [`inspect_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#inspect_for_each [`int_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#int_plus_one [`integer_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_arithmetic diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 441b907eaf2f8..b3e8951b76e80 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -534,6 +534,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::missing_trait_methods::MISSING_TRAIT_METHODS_INFO, crate::mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION_INFO, crate::mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION_INFO, + crate::module_style::INLINE_MODULES_INFO, crate::module_style::MOD_MODULE_FILES_INFO, crate::module_style::SELF_NAMED_MODULE_FILES_INFO, crate::multi_assignments::MULTI_ASSIGNMENTS_INFO, diff --git a/clippy_lints/src/module_style.rs b/clippy_lints/src/module_style.rs index befd50a5c85fa..500ee2864e46c 100644 --- a/clippy_lints/src/module_style.rs +++ b/clippy_lints/src/module_style.rs @@ -1,12 +1,46 @@ +use clippy_utils::ast_utils::is_cfg_test; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet; use rustc_ast::ast::{self, Inline, ItemKind, ModKind}; use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext}; use rustc_session::impl_lint_pass; use rustc_span::def_id::LOCAL_CRATE; -use rustc_span::{FileName, SourceFile, Span, SyntaxContext, sym}; +use rustc_span::{FileName, Ident, SourceFile, Span, SyntaxContext, sym}; use std::path::{Component, Path, PathBuf}; use std::sync::Arc; +declare_clippy_lint! { + /// ### What it does + /// Checks that module layout does not use inline modules. + /// Inline test modules (anything annotated with `#[cfg(test)]`) are not linted. + /// + /// ### Why restrict this? + /// Having multiple module layout styles in a project can be confusing. + /// + /// ### Known problems + /// The lint currently doesn't lint inline modules whose parent module is annotated + /// with the `#[path]` attribute. + /// + /// ### Example + /// ```ignore + /// // in `src/lib.rs` + /// mod foo { + /// /* module contents */ + /// } + /// ``` + /// Use instead: + /// ```ignore + /// // in `src/lib.rs` + /// mod foo; + /// // in `src/foo.rs` (or `src/foo/mod.rs`) + /// /* module contents */ + /// ``` + #[clippy::version = "1.96.0"] + pub INLINE_MODULES, + restriction, + "checks that module layout does not use inline modules" +} + declare_clippy_lint! { /// ### What it does /// Checks that module layout uses only self named module files; bans `mod.rs` files. @@ -65,18 +99,36 @@ declare_clippy_lint! { "checks that module layout is consistent" } -impl_lint_pass!(ModStyle => [MOD_MODULE_FILES, SELF_NAMED_MODULE_FILES]); +impl_lint_pass!(ModStyle => [ + INLINE_MODULES, + MOD_MODULE_FILES, + SELF_NAMED_MODULE_FILES, +]); pub struct ModState { + mod_file: Arc, + mod_ident: Ident, + path_from_working_dir: Option, contains_external: bool, has_path_attr: bool, - mod_file: Arc, + is_cfg_test: bool, } #[derive(Default)] pub struct ModStyle { working_dir: Option, - module_stack: Vec, + regular_mod_stack: Vec, + inline_mod_stack: Vec, +} + +impl ModStyle { + fn inside_cfg_test_inline_mod(&self) -> bool { + self.inline_mod_stack.last().is_some_and(|last| last.is_cfg_test) + } + + fn get_relative_path_from_working_dir(&self, file: &SourceFile) -> Option { + try_trim_file_path_prefix(file, self.working_dir.as_ref()?).map(Path::to_path_buf) + } } impl EarlyLintPass for ModStyle { @@ -87,45 +139,83 @@ impl EarlyLintPass for ModStyle { fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { if cx.builder.lint_level(MOD_MODULE_FILES).level == Level::Allow && cx.builder.lint_level(SELF_NAMED_MODULE_FILES).level == Level::Allow + && cx.builder.lint_level(INLINE_MODULES).level == Level::Allow { return; } - if let ItemKind::Mod(.., ModKind::Loaded(_, Inline::No { .. }, mod_spans, ..)) = &item.kind { + if let ItemKind::Mod(_, mod_ident, ModKind::Loaded(_, inline_or_not, mod_spans, ..)) = &item.kind { let has_path_attr = item.attrs.iter().any(|attr| attr.has_name(sym::path)); - if !has_path_attr && let Some(current) = self.module_stack.last_mut() { - current.contains_external = true; - } let mod_file = cx.sess().source_map().lookup_source_file(mod_spans.inner_span.lo()); - self.module_stack.push(ModState { + let path_from_working_dir = self.get_relative_path_from_working_dir(&mod_file); + let current = ModState { + mod_file, + mod_ident: *mod_ident, + path_from_working_dir, contains_external: false, has_path_attr, - mod_file, - }); + is_cfg_test: self.inside_cfg_test_inline_mod() || is_cfg_test(item), + }; + match inline_or_not { + Inline::Yes => { + if !current.is_cfg_test + && !item.span.from_expansion() + && self.regular_mod_stack.last().is_none_or(|last| !last.has_path_attr) + && let Some(path) = ¤t.path_from_working_dir + { + let opt_extra_mod_dir = self.regular_mod_stack.last().and_then(|last| { + if last.path_from_working_dir.as_ref()?.ends_with("mod.rs") { + None + } else { + Some(&last.mod_ident) + } + }); + check_inline_module( + cx, + path, + *mod_ident, + item.span, + opt_extra_mod_dir + .into_iter() + .chain(self.inline_mod_stack.iter().map(|state| &state.mod_ident)), + ); + } + self.inline_mod_stack.push(current); + }, + Inline::No { .. } => { + if !has_path_attr && let Some(last) = self.regular_mod_stack.last_mut() { + last.contains_external = true; + } + self.regular_mod_stack.push(current); + }, + } } } fn check_item_post(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { if cx.builder.lint_level(MOD_MODULE_FILES).level == Level::Allow && cx.builder.lint_level(SELF_NAMED_MODULE_FILES).level == Level::Allow + && cx.builder.lint_level(INLINE_MODULES).level == Level::Allow { return; } - if let ItemKind::Mod(.., ModKind::Loaded(_, Inline::No { .. }, ..)) = &item.kind - && let Some(current) = self.module_stack.pop() - && !current.has_path_attr - { - let Some(path) = self - .working_dir - .as_ref() - .and_then(|src| try_trim_file_path_prefix(¤t.mod_file, src)) - else { - return; - }; - if current.contains_external { - check_self_named_module(cx, path, ¤t.mod_file); + if let ItemKind::Mod(.., ModKind::Loaded(_, inline_or_not, ..)) = &item.kind { + match inline_or_not { + Inline::Yes => { + self.inline_mod_stack.pop(); + }, + Inline::No { .. } => { + if let Some(current) = self.regular_mod_stack.pop() + && let Some(path) = ¤t.path_from_working_dir + && !current.has_path_attr + { + if current.contains_external { + check_self_named_module(cx, path, ¤t.mod_file); + } + check_mod_module(cx, path, ¤t.mod_file); + } + }, } - check_mod_module(cx, path, ¤t.mod_file); } } } @@ -173,6 +263,31 @@ fn check_mod_module(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) { } } +fn check_inline_module<'a>( + cx: &EarlyContext<'_>, + path: &Path, + mod_ident: Ident, + mod_span: Span, + ancestor_mods: impl Iterator, +) { + let Some(parent) = path.parent() else { return }; + + span_lint_and_then(cx, INLINE_MODULES, mod_span, "inline module found", |diag| { + let mut mod_folder = parent.to_path_buf(); + mod_folder.extend(ancestor_mods.map(Ident::as_str)); + let mod_name = mod_ident.as_str(); + + let mod_file = mod_folder.join(mod_name).join("mod.rs"); + let self_named_mod_file = mod_folder.join(format!("{mod_name}.rs")); + let outlined_mod = snippet(cx, mod_span.with_hi(mod_ident.span.hi()), ""); + diag.help(format!( + "move the contents of the module to `{}` or `{}`, and replace this with `{outlined_mod};`", + self_named_mod_file.display(), + mod_file.display(), + )); + }); +} + fn try_trim_file_path_prefix<'a>(file: &'a SourceFile, prefix: &'a Path) -> Option<&'a Path> { if let FileName::Real(name) = &file.name && let Some(mut path) = name.local_path() diff --git a/clippy_utils/src/ast_utils/mod.rs b/clippy_utils/src/ast_utils/mod.rs index 50cfb0ed89dec..af95330b0ddd5 100644 --- a/clippy_utils/src/ast_utils/mod.rs +++ b/clippy_utils/src/ast_utils/mod.rs @@ -5,7 +5,8 @@ #![allow(clippy::wildcard_imports, clippy::enum_glob_use)] use crate::{both, over}; -use rustc_ast::{self as ast, *}; +use rustc_ast::{self as ast, HasAttrs, *}; +use rustc_span::sym; use rustc_span::symbol::Ident; use std::mem; @@ -1040,3 +1041,17 @@ pub fn eq_delim_args(l: &DelimArgs, r: &DelimArgs) -> bool { && l.tokens.len() == r.tokens.len() && l.tokens.iter().zip(r.tokens.iter()).all(|(a, b)| a.eq_unspanned(b)) } + +/// Checks whether `#[cfg(test)]` is directly applied to `item`. +pub fn is_cfg_test(item: &impl HasAttrs) -> bool { + item.attrs().iter().any(|attr| { + if attr.has_name(sym::cfg) + && let Some(item_list) = attr.meta_item_list() + && item_list.iter().any(|item| item.has_name(sym::test)) + { + true + } else { + false + } + }) +} diff --git a/tests/ui-cargo/module_style/inline_mod/Cargo.stderr b/tests/ui-cargo/module_style/inline_mod/Cargo.stderr new file mode 100644 index 0000000000000..e3f18e37db9a2 --- /dev/null +++ b/tests/ui-cargo/module_style/inline_mod/Cargo.stderr @@ -0,0 +1,71 @@ +error: inline module found + --> src/other.rs:1:1 + | +1 | mod foo {} + | ^^^^^^^^^^ + | + = help: move the contents of the module to `src/other/foo.rs` or `src/other/foo/mod.rs`, and replace this with `mod foo;` + = note: `-D clippy::inline-modules` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::inline_modules)]` + +error: inline module found + --> src/qux/foo.rs:1:1 + | +1 | mod bar {} + | ^^^^^^^^^^ + | + = help: move the contents of the module to `src/qux/foo/bar.rs` or `src/qux/foo/bar/mod.rs`, and replace this with `mod bar;` + +error: inline module found + --> src/lib.rs:9:1 + | + 9 | / pub mod test_nested_inline_mods { +10 | | mod bar { +11 | | mod baz {} +12 | | } +13 | | } + | |_^ + | + = help: move the contents of the module to `src/test_nested_inline_mods.rs` or `src/test_nested_inline_mods/mod.rs`, and replace this with `pub mod test_nested_inline_mods;` + +error: inline module found + --> src/lib.rs:10:5 + | +10 | / mod bar { +11 | | mod baz {} +12 | | } + | |_____^ + | + = help: move the contents of the module to `src/test_nested_inline_mods/bar.rs` or `src/test_nested_inline_mods/bar/mod.rs`, and replace this with `mod bar;` + +error: inline module found + --> src/lib.rs:11:9 + | +11 | mod baz {} + | ^^^^^^^^^^ + | + = help: move the contents of the module to `src/test_nested_inline_mods/bar/baz.rs` or `src/test_nested_inline_mods/bar/baz/mod.rs`, and replace this with `mod baz;` + +error: inline module found + --> src/lib.rs:20:1 + | +20 | / mod partially_escaped_test_mod { +21 | | #[cfg(test)] +22 | | mod tests { +23 | | mod bar {} +24 | | } +25 | | mod baz {} +26 | | } + | |_^ + | + = help: move the contents of the module to `src/partially_escaped_test_mod.rs` or `src/partially_escaped_test_mod/mod.rs`, and replace this with `mod partially_escaped_test_mod;` + +error: inline module found + --> src/lib.rs:25:5 + | +25 | mod baz {} + | ^^^^^^^^^^ + | + = help: move the contents of the module to `src/partially_escaped_test_mod/baz.rs` or `src/partially_escaped_test_mod/baz/mod.rs`, and replace this with `mod baz;` + +error: could not compile `inline-mod` (lib) due to 7 previous errors diff --git a/tests/ui-cargo/module_style/inline_mod/Cargo.toml b/tests/ui-cargo/module_style/inline_mod/Cargo.toml new file mode 100644 index 0000000000000..4108d8aaa5c0d --- /dev/null +++ b/tests/ui-cargo/module_style/inline_mod/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "inline-mod" +version = "0.1.0" +edition = "2024" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/module_style/inline_mod/src/foo.rs b/tests/ui-cargo/module_style/inline_mod/src/foo.rs new file mode 100644 index 0000000000000..9e19e6e44fddc --- /dev/null +++ b/tests/ui-cargo/module_style/inline_mod/src/foo.rs @@ -0,0 +1,2 @@ +/// Lint shouldn't fire because parent mod has a path attribute. +mod foo {} diff --git a/tests/ui-cargo/module_style/inline_mod/src/lib.rs b/tests/ui-cargo/module_style/inline_mod/src/lib.rs new file mode 100644 index 0000000000000..df5816e251dc1 --- /dev/null +++ b/tests/ui-cargo/module_style/inline_mod/src/lib.rs @@ -0,0 +1,34 @@ +#![warn(clippy::inline_modules)] + +pub mod other; +#[path = "qux/mod.rs"] +pub mod something; +#[path = "foo.rs"] +pub mod stuff; + +pub mod test_nested_inline_mods { + mod bar { + mod baz {} + } +} + +#[cfg(test)] +mod escaped_test_mod { + mod bar {} +} + +mod partially_escaped_test_mod { + #[cfg(test)] + mod tests { + mod bar {} + } + mod baz {} +} + +macro_rules! inline_mod_from_expansion { + () => { + mod _foo {} + }; +} + +inline_mod_from_expansion!(); diff --git a/tests/ui-cargo/module_style/inline_mod/src/other.rs b/tests/ui-cargo/module_style/inline_mod/src/other.rs new file mode 100644 index 0000000000000..e5736aedfdc7c --- /dev/null +++ b/tests/ui-cargo/module_style/inline_mod/src/other.rs @@ -0,0 +1 @@ +mod foo {} diff --git a/tests/ui-cargo/module_style/inline_mod/src/qux/foo.rs b/tests/ui-cargo/module_style/inline_mod/src/qux/foo.rs new file mode 100644 index 0000000000000..e4cdd207c6635 --- /dev/null +++ b/tests/ui-cargo/module_style/inline_mod/src/qux/foo.rs @@ -0,0 +1 @@ +mod bar {} diff --git a/tests/ui-cargo/module_style/inline_mod/src/qux/mod.rs b/tests/ui-cargo/module_style/inline_mod/src/qux/mod.rs new file mode 100644 index 0000000000000..f4ad3bff5c973 --- /dev/null +++ b/tests/ui-cargo/module_style/inline_mod/src/qux/mod.rs @@ -0,0 +1 @@ +mod foo; From 23d734604a73b1e71c55153821ba57084d3125f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 30 Mar 2026 20:39:58 +0000 Subject: [PATCH 02/38] Make `span_suggestions` always verbose `span_suggestions` is to provide mutually exclusive suggestions. When it was introduced, we made its behavior be that if a single suggestion is given to it, we present the suggestion inline, otherwise in patch format. Changing this to make all of its uses be verbose, as that is closer in intent of output. --- tests/ui/blocks_in_conditions.stderr | 7 ++- tests/ui/crashes/ice-96721.stderr | 7 ++- tests/ui/nonminimal_bool.stderr | 79 +++++++++++++++++++++---- tests/ui/nonminimal_bool_methods.stderr | 16 ++++- 4 files changed, 95 insertions(+), 14 deletions(-) diff --git a/tests/ui/blocks_in_conditions.stderr b/tests/ui/blocks_in_conditions.stderr index 282c42a98bfc2..975716deccee4 100644 --- a/tests/ui/blocks_in_conditions.stderr +++ b/tests/ui/blocks_in_conditions.stderr @@ -29,10 +29,15 @@ error: this boolean expression can be simplified --> tests/ui/blocks_in_conditions.rs:48:8 | LL | if true && x == 3 { 6 } else { 10 } - | ^^^^^^^^^^^^^^ help: try: `x == 3` + | ^^^^^^^^^^^^^^ | = note: `-D clippy::nonminimal-bool` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::nonminimal_bool)]` +help: try + | +LL - if true && x == 3 { 6 } else { 10 } +LL + if x == 3 { 6 } else { 10 } + | error: aborting due to 3 previous errors diff --git a/tests/ui/crashes/ice-96721.stderr b/tests/ui/crashes/ice-96721.stderr index 23f7300178ecb..70f3d48379fde 100644 --- a/tests/ui/crashes/ice-96721.stderr +++ b/tests/ui/crashes/ice-96721.stderr @@ -2,9 +2,14 @@ error: malformed `path` attribute input --> tests/ui/crashes/ice-96721.rs:7:1 | LL | #[path = foo!()] - | ^^^^^^^^^^^^^^^^ help: must be of the form: `#[path = "file"]` + | ^^^^^^^^^^^^^^^^ | = note: for more information, visit +help: must be of the form + | +LL - #[path = foo!()] +LL + #[path = "file"] + | error: aborting due to 1 previous error diff --git a/tests/ui/nonminimal_bool.stderr b/tests/ui/nonminimal_bool.stderr index 6a20b9216da52..29387cf31ee8b 100644 --- a/tests/ui/nonminimal_bool.stderr +++ b/tests/ui/nonminimal_bool.stderr @@ -2,46 +2,87 @@ error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:17:13 | LL | let _ = !true; - | ^^^^^ help: try: `false` + | ^^^^^ | = note: `-D clippy::nonminimal-bool` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::nonminimal_bool)]` +help: try + | +LL - let _ = !true; +LL + let _ = false; + | error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:20:13 | LL | let _ = !false; - | ^^^^^^ help: try: `true` + | ^^^^^^ + | +help: try + | +LL - let _ = !false; +LL + let _ = true; + | error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:23:13 | LL | let _ = !!a; - | ^^^ help: try: `a` + | ^^^ + | +help: try + | +LL - let _ = !!a; +LL + let _ = a; + | error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:26:13 | LL | let _ = false || a; - | ^^^^^^^^^^ help: try: `a` + | ^^^^^^^^^^ + | +help: try + | +LL - let _ = false || a; +LL + let _ = a; + | error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:32:13 | LL | let _ = !(!a && b); - | ^^^^^^^^^^ help: try: `a || !b` + | ^^^^^^^^^^ + | +help: try + | +LL - let _ = !(!a && b); +LL + let _ = a || !b; + | error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:35:13 | LL | let _ = !(!a || b); - | ^^^^^^^^^^ help: try: `a && !b` + | ^^^^^^^^^^ + | +help: try + | +LL - let _ = !(!a || b); +LL + let _ = a && !b; + | error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:38:13 | LL | let _ = !a && !(b && c); - | ^^^^^^^^^^^^^^^ help: try: `!(a || b && c)` + | ^^^^^^^^^^^^^^^ + | +help: try + | +LL - let _ = !a && !(b && c); +LL + let _ = !(a || b && c); + | error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:47:13 @@ -122,7 +163,13 @@ error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:90:8 | LL | if matches!(true, true) && true { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(true, true)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if matches!(true, true) && true { +LL + if matches!(true, true) { + | error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:171:8 @@ -215,13 +262,25 @@ error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:212:8 | LL | if !(a < 2.0 && !b) { - | ^^^^^^^^^^^^^^^^ help: try: `a >= 2.0 || b` + | ^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if !(a < 2.0 && !b) { +LL + if a >= 2.0 || b { + | error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:231:12 | LL | if !(matches!(ty, TyKind::Ref(_, _, _)) && !is_mutable(&expr)) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `!matches!(ty, TyKind::Ref(_, _, _)) || is_mutable(&expr)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if !(matches!(ty, TyKind::Ref(_, _, _)) && !is_mutable(&expr)) { +LL + if !matches!(ty, TyKind::Ref(_, _, _)) || is_mutable(&expr) { + | error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:251:8 diff --git a/tests/ui/nonminimal_bool_methods.stderr b/tests/ui/nonminimal_bool_methods.stderr index 568e880077279..948c28dcb537b 100644 --- a/tests/ui/nonminimal_bool_methods.stderr +++ b/tests/ui/nonminimal_bool_methods.stderr @@ -29,13 +29,25 @@ error: this boolean expression can be simplified --> tests/ui/nonminimal_bool_methods.rs:20:13 | LL | let _ = !(a.is_some() && !c); - | ^^^^^^^^^^^^^^^^^^^^ help: try: `a.is_none() || c` + | ^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - let _ = !(a.is_some() && !c); +LL + let _ = a.is_none() || c; + | error: this boolean expression can be simplified --> tests/ui/nonminimal_bool_methods.rs:22:13 | LL | let _ = !(a.is_some() || !c); - | ^^^^^^^^^^^^^^^^^^^^ help: try: `a.is_none() && c` + | ^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - let _ = !(a.is_some() || !c); +LL + let _ = a.is_none() && c; + | error: this boolean expression can be simplified --> tests/ui/nonminimal_bool_methods.rs:24:26 From c610584b0b225c2a13e9f21ec9c17560e5df69e5 Mon Sep 17 00:00:00 2001 From: teor Date: Thu, 16 Apr 2026 07:07:25 +1000 Subject: [PATCH 03/38] Format missed clippy lint source files --- clippy_lints/src/arbitrary_source_item_ordering.rs | 13 ++++++++++--- .../src/derive/derive_partial_eq_without_eq.rs | 5 +---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/arbitrary_source_item_ordering.rs b/clippy_lints/src/arbitrary_source_item_ordering.rs index 7f0f0a0245f4c..dae0c8439ea82 100644 --- a/clippy_lints/src/arbitrary_source_item_ordering.rs +++ b/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -306,9 +306,16 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering { cur_f = Some(field); } }, - ItemKind::Trait(_constness, is_auto, _safety, _impl_restriction, _ident, _generics, _generic_bounds, item_ref) - if self.enable_ordering_for_trait && *is_auto == IsAuto::No => - { + ItemKind::Trait( + _constness, + is_auto, + _safety, + _impl_restriction, + _ident, + _generics, + _generic_bounds, + item_ref, + ) if self.enable_ordering_for_trait && *is_auto == IsAuto::No => { let mut cur_t: Option<(TraitItemId, Ident)> = None; for &item in *item_ref { diff --git a/clippy_lints/src/derive/derive_partial_eq_without_eq.rs b/clippy_lints/src/derive/derive_partial_eq_without_eq.rs index 22943cd9ee5e2..3782c1dab3557 100644 --- a/clippy_lints/src/derive/derive_partial_eq_without_eq.rs +++ b/clippy_lints/src/derive/derive_partial_eq_without_eq.rs @@ -85,8 +85,5 @@ fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> .upcast(tcx) }), ))); - ty::TypingEnv::new( - param_env, - ty::TypingMode::non_body_analysis(), - ) + ty::TypingEnv::new(param_env, ty::TypingMode::non_body_analysis()) } From 70fcd997b5208af5001ae26c084fc2e0f0630388 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 8 Apr 2026 15:01:26 +1000 Subject: [PATCH 04/38] Refactor FnDecl and FnSig flags into packed structs --- clippy_lints/src/eta_reduction.rs | 5 ++--- clippy_lints/src/functions/misnamed_getters.rs | 2 +- clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs | 2 +- clippy_lints/src/inherent_to_string.rs | 2 +- clippy_lints/src/iter_not_returning_iterator.rs | 2 +- clippy_lints/src/iter_without_into_iter.rs | 2 +- clippy_lints/src/len_without_is_empty.rs | 4 ++-- clippy_lints/src/lifetimes.rs | 4 ++-- clippy_lints/src/methods/mod.rs | 4 ++-- clippy_lints/src/only_used_in_recursion.rs | 4 ++-- clippy_lints/src/return_self_not_must_use.rs | 2 +- clippy_lints/src/self_named_constructors.rs | 2 +- clippy_lints/src/unconditional_recursion.rs | 2 +- clippy_utils/src/hir_utils.rs | 8 ++++---- clippy_utils/src/visitors.rs | 2 +- 15 files changed, 23 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 3562200cbd929..229af104799d1 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -4,7 +4,6 @@ use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::{snippet_opt, snippet_with_applicability}; use clippy_utils::usage::{local_used_after_expr, local_used_in}; use clippy_utils::{get_path_from_caller_to_method_type, is_adjusted, is_no_std_crate}; -use rustc_abi::ExternAbi; use rustc_errors::Applicability; use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, GenericArgs, Param, PatKind, QPath, Safety, TyKind, find_attr}; use rustc_infer::infer::TyCtxtInferExt; @@ -173,7 +172,7 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx && let output = typeck.expr_ty(body.value) && let ty::Tuple(tys) = *subs.type_at(1).kind() { - cx.tcx.mk_fn_sig(tys, output, false, Safety::Safe, ExternAbi::Rust) + cx.tcx.mk_fn_sig_safe_rust_abi(tys, output) } else { return; } @@ -318,7 +317,7 @@ fn check_inputs( } fn check_sig<'tcx>(closure_sig: FnSig<'tcx>, call_sig: FnSig<'tcx>) -> bool { - call_sig.safety.is_safe() && !has_late_bound_to_non_late_bound_regions(closure_sig, call_sig) + call_sig.safety().is_safe() && !has_late_bound_to_non_late_bound_regions(closure_sig, call_sig) } /// This walks through both signatures and checks for any time a late-bound region is expected by an diff --git a/clippy_lints/src/functions/misnamed_getters.rs b/clippy_lints/src/functions/misnamed_getters.rs index fa63876410f01..215039952ca5a 100644 --- a/clippy_lints/src/functions/misnamed_getters.rs +++ b/clippy_lints/src/functions/misnamed_getters.rs @@ -23,7 +23,7 @@ pub fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, body: let name = ident.name.as_str(); - let name = match decl.implicit_self { + let name = match decl.implicit_self() { ImplicitSelfKind::RefMut => { let Some(name) = name.strip_suffix("_mut") else { return; diff --git a/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs b/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs index c6b0e7c54c06f..e49dee4164b88 100644 --- a/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs +++ b/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs @@ -59,7 +59,7 @@ fn check_raw_ptr<'tcx>( }, hir::ExprKind::MethodCall(_, recv, args, _) => { let def_id = typeck.type_dependent_def_id(e.hir_id).unwrap(); - if cx.tcx.fn_sig(def_id).skip_binder().skip_binder().safety.is_unsafe() { + if cx.tcx.fn_sig(def_id).skip_binder().skip_binder().safety().is_unsafe() { check_arg(cx, &raw_ptrs, recv); for arg in args { check_arg(cx, &raw_ptrs, arg); diff --git a/clippy_lints/src/inherent_to_string.rs b/clippy_lints/src/inherent_to_string.rs index 22082646eb316..afe2a1033ceeb 100644 --- a/clippy_lints/src/inherent_to_string.rs +++ b/clippy_lints/src/inherent_to_string.rs @@ -103,7 +103,7 @@ impl<'tcx> LateLintPass<'tcx> for InherentToString { && header.abi == ExternAbi::Rust && impl_item.ident.name == sym::to_string && let decl = signature.decl - && decl.implicit_self.has_implicit_self() + && decl.implicit_self().has_implicit_self() && decl.inputs.len() == 1 && impl_item.generics.params.iter().all(|p| matches!(p.kind, GenericParamKind::Lifetime { .. })) && !impl_item.span.from_expansion() diff --git a/clippy_lints/src/iter_not_returning_iterator.rs b/clippy_lints/src/iter_not_returning_iterator.rs index 753360906d666..73e4a856c0468 100644 --- a/clippy_lints/src/iter_not_returning_iterator.rs +++ b/clippy_lints/src/iter_not_returning_iterator.rs @@ -64,7 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for IterNotReturningIterator { } fn check_sig(cx: &LateContext<'_>, name: Symbol, sig: &FnSig<'_>, fn_id: LocalDefId) { - if sig.decl.implicit_self.has_implicit_self() { + if sig.decl.implicit_self().has_implicit_self() { let ret_ty = cx .tcx .instantiate_bound_regions_with_erased(cx.tcx.fn_sig(fn_id).instantiate_identity().output()); diff --git a/clippy_lints/src/iter_without_into_iter.rs b/clippy_lints/src/iter_without_into_iter.rs index 4e56f9c8472de..369037dad94fc 100644 --- a/clippy_lints/src/iter_without_into_iter.rs +++ b/clippy_lints/src/iter_without_into_iter.rs @@ -205,7 +205,7 @@ impl {self_ty_without_ref} {{ && let FnRetTy::Return(ret) = sig.decl.output && is_nameable_in_impl_trait(ret) && cx.tcx.generics_of(item_did).is_own_empty() - && sig.decl.implicit_self == expected_implicit_self + && sig.decl.implicit_self() == expected_implicit_self && sig.decl.inputs.len() == 1 && let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id()) && imp.of_trait.is_none() diff --git a/clippy_lints/src/len_without_is_empty.rs b/clippy_lints/src/len_without_is_empty.rs index 60dbd6cd3570f..de6ccf56ab2a2 100644 --- a/clippy_lints/src/len_without_is_empty.rs +++ b/clippy_lints/src/len_without_is_empty.rs @@ -54,7 +54,7 @@ impl<'tcx> LateLintPass<'tcx> for LenWithoutIsEmpty { fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) { if item.ident.name == sym::len && let ImplItemKind::Fn(sig, _) = &item.kind - && sig.decl.implicit_self.has_implicit_self() + && sig.decl.implicit_self().has_implicit_self() && sig.decl.inputs.len() == 1 && cx.effective_visibilities.is_exported(item.owner_id.def_id) && matches!(sig.decl.output, FnRetTy::Return(_)) @@ -79,7 +79,7 @@ impl<'tcx> LateLintPass<'tcx> for LenWithoutIsEmpty { check_for_is_empty( cx, sig.span, - sig.decl.implicit_self, + sig.decl.implicit_self(), output, ty_id, name, diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 0bb4992cf6bb4..ea99b523f09b0 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -411,7 +411,7 @@ fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxIndexSet(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option, msrv: Msrv) -> bool { if let Some(ident) = ident && ident.name == kw::SelfLower - && !func.implicit_self.has_implicit_self() + && !func.implicit_self().has_implicit_self() && let Some(self_ty) = func.inputs.first() && !msrv.meets(cx, msrvs::EXPLICIT_SELF_TYPE_ELISION) { @@ -697,7 +697,7 @@ fn is_candidate_for_elision(fd: &FnDecl<'_>) -> bool { } } - if fd.lifetime_elision_allowed + if fd.lifetime_elision_allowed() && let Return(ret_ty) = fd.output && walk_unambig_ty(&mut V, ret_ty).is_break() { diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index b39aec6e521ce..3d456c8e77918 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -5062,7 +5062,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { let first_arg_ty_opt = method_sig.inputs().iter().next().copied(); should_implement_trait::check_impl_item(cx, impl_item, self_ty, implements_trait, first_arg_ty_opt, sig); - if sig.decl.implicit_self.has_implicit_self() + if sig.decl.implicit_self().has_implicit_self() && !(self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id)) && let Some(first_arg) = iter_input_pats(sig.decl, cx.tcx.hir_body(id)).next() @@ -5089,7 +5089,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { } if let TraitItemKind::Fn(ref sig, _) = item.kind { - if sig.decl.implicit_self.has_implicit_self() + if sig.decl.implicit_self().has_implicit_self() && let Some(first_arg_hir_ty) = sig.decl.inputs.first() && let Some(&first_arg_ty) = cx .tcx diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs index 6c45964da0dab..f1625b1a4c6e0 100644 --- a/clippy_lints/src/only_used_in_recursion.rs +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -321,7 +321,7 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion { }) => ( owner_id.to_def_id(), FnKind::TraitFn, - usize::from(sig.decl.implicit_self.has_implicit_self()), + usize::from(sig.decl.implicit_self().has_implicit_self()), ), Node::ImplItem(&ImplItem { kind: ImplItemKind::Fn(ref sig, _), @@ -339,7 +339,7 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion { FnKind::ImplTraitFn( std::ptr::from_ref(cx.tcx.erase_and_anonymize_regions(trait_ref.args)) as usize ), - usize::from(sig.decl.implicit_self.has_implicit_self()), + usize::from(sig.decl.implicit_self().has_implicit_self()), ) } else { (owner_id.to_def_id(), FnKind::Fn, 0) diff --git a/clippy_lints/src/return_self_not_must_use.rs b/clippy_lints/src/return_self_not_must_use.rs index 78f5167fa5436..8c892b7085b7c 100644 --- a/clippy_lints/src/return_self_not_must_use.rs +++ b/clippy_lints/src/return_self_not_must_use.rs @@ -70,7 +70,7 @@ declare_lint_pass!(ReturnSelfNotMustUse => [RETURN_SELF_NOT_MUST_USE]); fn check_method(cx: &LateContext<'_>, decl: &FnDecl<'_>, fn_def: LocalDefId, span: Span, owner_id: OwnerId) { if !span.in_external_macro(cx.sess().source_map()) // If it comes from an external macro, better ignore it. - && decl.implicit_self.has_implicit_self() + && decl.implicit_self().has_implicit_self() // We only show this warning for public exported methods. && cx.effective_visibilities.is_exported(fn_def) // We don't want to emit this lint if the `#[must_use]` attribute is already there. diff --git a/clippy_lints/src/self_named_constructors.rs b/clippy_lints/src/self_named_constructors.rs index 534ba3a50c6b4..e32cf944536bf 100644 --- a/clippy_lints/src/self_named_constructors.rs +++ b/clippy_lints/src/self_named_constructors.rs @@ -44,7 +44,7 @@ impl<'tcx> LateLintPass<'tcx> for SelfNamedConstructors { fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) { match impl_item.kind { ImplItemKind::Fn(ref sig, _) => { - if sig.decl.implicit_self.has_implicit_self() { + if sig.decl.implicit_self().has_implicit_self() { return; } }, diff --git a/clippy_lints/src/unconditional_recursion.rs b/clippy_lints/src/unconditional_recursion.rs index 297f4c2df040d..3df16bf71ce7e 100644 --- a/clippy_lints/src/unconditional_recursion.rs +++ b/clippy_lints/src/unconditional_recursion.rs @@ -376,7 +376,7 @@ impl UnconditionalRecursion { method_def_id: LocalDefId, ) { // We're only interested into static methods. - if decl.implicit_self.has_implicit_self() { + if decl.implicit_self().has_implicit_self() { return; } // We don't check trait implementations. diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index a4d8fd20e4d3f..4d9ddb388f3cb 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -257,9 +257,9 @@ impl HirEqInterExpr<'_, '_, '_> { (FnRetTy::Return(l_ty), FnRetTy::Return(r_ty)) => self.eq_ty(l_ty, r_ty), _ => false, }) - && left.c_variadic == right.c_variadic - && left.implicit_self == right.implicit_self - && left.lifetime_elision_allowed == right.lifetime_elision_allowed + && left.c_variadic() == right.c_variadic() + && left.implicit_self() == right.implicit_self() + && left.lifetime_elision_allowed() == right.lifetime_elision_allowed() } fn eq_generics(&mut self, left: &Generics<'_>, right: &Generics<'_>) -> bool { @@ -1571,7 +1571,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_ty(ty); }, } - fn_ptr.decl.c_variadic.hash(&mut self.s); + fn_ptr.decl.c_variadic().hash(&mut self.s); }, TyKind::Tup(ty_list) => { for ty in *ty_list { diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index 6ac979d595f49..28449a75a8fc5 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -437,7 +437,7 @@ pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool { ty::FnDef(id, _) if self.cx.tcx.fn_sig(id).skip_binder().safety().is_unsafe() => { ControlFlow::Break(()) }, - ty::FnPtr(_, hdr) if hdr.safety.is_unsafe() => ControlFlow::Break(()), + ty::FnPtr(_, hdr) if hdr.safety().is_unsafe() => ControlFlow::Break(()), _ => walk_expr(self, e), }, ExprKind::Path(ref p) From 9b5a48b05eb1db58c22f09ee8a0189cd8adba2ee Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Thu, 26 Mar 2026 20:49:26 -0700 Subject: [PATCH 05/38] Require that a `<_ as Try>::Residual` implement `Residual` The `Residual` trait was even more experimental than `Try`, but now that RFC3721 is merged, I think it would make sense to require this. --- tests/ui/manual_try_fold.rs | 19 ++++++++++++++++--- tests/ui/manual_try_fold.stderr | 8 ++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/tests/ui/manual_try_fold.rs b/tests/ui/manual_try_fold.rs index c91ea41bb84c5..cd2b0720f5bff 100644 --- a/tests/ui/manual_try_fold.rs +++ b/tests/ui/manual_try_fold.rs @@ -2,8 +2,9 @@ #![allow(clippy::unnecessary_fold, unused)] #![warn(clippy::manual_try_fold)] #![feature(try_trait_v2)] +#![feature(try_trait_v2_residual)] //@no-rustfix -use std::ops::{ControlFlow, FromResidual, Try}; +use std::ops::{ControlFlow, FromResidual, Residual, Try}; #[macro_use] extern crate proc_macros; @@ -11,15 +12,21 @@ extern crate proc_macros; // Test custom `Try` with more than 1 argument struct NotOption(i32, i32); +struct NotOptionResidual; + impl FromResidual for NotOption { fn from_residual(_: R) -> Self { todo!() } } +impl Residual<()> for NotOptionResidual { + type TryType = NotOption; +} + impl Try for NotOption { type Output = (); - type Residual = (); + type Residual = NotOptionResidual; fn from_output(_: Self::Output) -> Self { todo!() @@ -34,15 +41,21 @@ impl Try for NotOption { #[derive(Default)] struct NotOptionButWorse(i32); +struct NotOptionButWorseResidual; + impl FromResidual for NotOptionButWorse { fn from_residual(_: R) -> Self { todo!() } } +impl Residual<()> for NotOptionButWorseResidual { + type TryType = NotOptionButWorse; +} + impl Try for NotOptionButWorse { type Output = (); - type Residual = (); + type Residual = NotOptionButWorseResidual; fn from_output(_: Self::Output) -> Self { todo!() diff --git a/tests/ui/manual_try_fold.stderr b/tests/ui/manual_try_fold.stderr index f2740187878fe..80e1e76cd9554 100644 --- a/tests/ui/manual_try_fold.stderr +++ b/tests/ui/manual_try_fold.stderr @@ -1,5 +1,5 @@ error: usage of `Iterator::fold` on a type that implements `Try` - --> tests/ui/manual_try_fold.rs:59:10 + --> tests/ui/manual_try_fold.rs:72:10 | LL | .fold(Some(0i32), |sum, i| sum?.checked_add(*i)) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `try_fold` instead: `try_fold(0i32, |sum, i| ...)` @@ -8,19 +8,19 @@ LL | .fold(Some(0i32), |sum, i| sum?.checked_add(*i)) = help: to override `-D warnings` add `#[allow(clippy::manual_try_fold)]` error: usage of `Iterator::fold` on a type that implements `Try` - --> tests/ui/manual_try_fold.rs:64:10 + --> tests/ui/manual_try_fold.rs:77:10 | LL | .fold(NotOption(0i32, 0i32), |sum, i| NotOption(0i32, 0i32)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `try_fold` instead: `try_fold(..., |sum, i| ...)` error: usage of `Iterator::fold` on a type that implements `Try` - --> tests/ui/manual_try_fold.rs:68:10 + --> tests/ui/manual_try_fold.rs:81:10 | LL | .fold(NotOptionButWorse(0i32), |sum, i| NotOptionButWorse(0i32)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `try_fold` instead: `try_fold(0i32, |sum, i| ...)` error: usage of `Iterator::fold` on a type that implements `Try` - --> tests/ui/manual_try_fold.rs:99:10 + --> tests/ui/manual_try_fold.rs:112:10 | LL | .fold(Some(0i32), |sum, i| sum?.checked_add(*i)) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `try_fold` instead: `try_fold(0i32, |sum, i| ...)` From e3fa7086f6d17e5aff7d46ab6b668fa7e49f7f8e Mon Sep 17 00:00:00 2001 From: Charlie Chang Date: Thu, 16 Apr 2026 22:08:43 +0800 Subject: [PATCH 06/38] Improve suggestion for destructuring assignment with `?` operator in `question_mark` lint --- clippy_lints/src/question_mark.rs | 2 +- tests/ui/question_mark.fixed | 11 +++++++++++ tests/ui/question_mark.rs | 12 ++++++++++++ tests/ui/question_mark.stderr | 20 +++++++++++++++++++- 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 4bd6b1696b354..f07cc10cbced5 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -495,7 +495,7 @@ fn check_if_try_match<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { let mut sugg = reindent_multiline(&arm_body_snippet, true, Some(indent)); let binding_snippet = snippet_with_applicability(cx, binding_span, "..", &mut applicability); let inner_indent = " ".repeat(indent + 4); - if matches!(arm_body.kind, ExprKind::Block(..)) { + if matches!(arm_body.kind, ExprKind::Block(..)) && sugg.starts_with('{') { sugg.insert_str( 1, &format!("\n{inner_indent}let {binding_snippet} = {scrutinee_snippet}?;"), diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index bf4b4ff0a21ed..786431bc1f539 100644 --- a/tests/ui/question_mark.fixed +++ b/tests/ui/question_mark.fixed @@ -564,3 +564,14 @@ fn issue16751(mut v: Option) -> Option { if n > 10 { Some(42) } else { None } } } + +fn issue_destructuring_assignment() -> Option<(i32, i32)> { + let mut a = 0i32; + let mut b = 0i32; + let opt: Option<(i32, i32)> = Some((1, 2)); + { + let x = opt?; + (a, b) = x + } + Some((a, b)) +} diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs index 93f76f16576c6..7cbcc604eb591 100644 --- a/tests/ui/question_mark.rs +++ b/tests/ui/question_mark.rs @@ -710,3 +710,15 @@ fn issue16751(mut v: Option) -> Option { None => return None, } } + +fn issue_destructuring_assignment() -> Option<(i32, i32)> { + let mut a = 0i32; + let mut b = 0i32; + let opt: Option<(i32, i32)> = Some((1, 2)); + match opt { + //~^ question_mark + Some(x) => (a, b) = x, + None => return None, + } + Some((a, b)) +} diff --git a/tests/ui/question_mark.stderr b/tests/ui/question_mark.stderr index 9d7cfc2057641..74fb2c45a2549 100644 --- a/tests/ui/question_mark.stderr +++ b/tests/ui/question_mark.stderr @@ -498,5 +498,23 @@ LL + if n > 10 { Some(42) } else { None } LL + } | -error: aborting due to 46 previous errors +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:718:5 + | +LL | / match opt { +LL | | +LL | | Some(x) => (a, b) = x, +LL | | None => return None, +LL | | } + | |_____^ + | +help: try instead + | +LL ~ { +LL + let x = opt?; +LL + (a, b) = x +LL + } + | + +error: aborting due to 47 previous errors From f9c23c1fcb96fb42e8706fa9c61043dc70423ba9 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 16 Apr 2026 19:48:14 +0200 Subject: [PATCH 07/38] Merge commit 'f6d310692116e9a527ce6d0b3526c965d9c5d7b9' into clippy-subtree-update --- CHANGELOG.md | 124 +++++- Cargo.toml | 4 +- book/src/lint_configuration.md | 1 + clippy_config/Cargo.toml | 2 +- clippy_config/src/conf.rs | 1 + clippy_lints/Cargo.toml | 4 +- .../src/arbitrary_source_item_ordering.rs | 13 +- clippy_lints/src/booleans.rs | 7 +- clippy_lints/src/byte_char_slices.rs | 92 +++-- .../src/cargo/multiple_crate_versions.rs | 2 +- .../src/cargo/wildcard_dependencies.rs | 2 +- .../src/casts/fn_to_numeric_cast_any.rs | 5 - clippy_lints/src/casts/mod.rs | 6 +- clippy_lints/src/casts/ref_as_ptr.rs | 5 +- clippy_lints/src/casts/unnecessary_cast.rs | 349 ++++++++++++++++- clippy_lints/src/cognitive_complexity.rs | 2 +- clippy_lints/src/dereference.rs | 30 +- .../derive/derive_partial_eq_without_eq.rs | 5 +- clippy_lints/src/disallowed_fields.rs | 2 +- clippy_lints/src/float_literal.rs | 7 +- clippy_lints/src/implicit_saturating_sub.rs | 50 +-- clippy_lints/src/int_plus_one.rs | 10 +- clippy_lints/src/lib.rs | 4 +- clippy_lints/src/manual_is_power_of_two.rs | 2 +- clippy_lints/src/manual_noop_waker.rs | 17 +- clippy_lints/src/manual_rotate.rs | 22 +- clippy_lints/src/manual_take.rs | 2 +- clippy_lints/src/matches/manual_filter.rs | 73 +++- clippy_lints/src/matches/manual_utils.rs | 38 +- clippy_lints/src/matches/mod.rs | 2 +- clippy_lints/src/methods/expect_fun_call.rs | 8 +- .../src/methods/iter_out_of_bounds.rs | 5 +- clippy_lints/src/methods/manual_inspect.rs | 12 +- clippy_lints/src/methods/manual_repeat_n.rs | 4 +- .../map_with_unused_argument_over_ranges.rs | 60 ++- clippy_lints/src/methods/mod.rs | 4 + clippy_lints/src/methods/unnecessary_fold.rs | 12 +- clippy_lints/src/misc_early/mod.rs | 4 +- .../misc_early/unneeded_wildcard_pattern.rs | 15 +- clippy_lints/src/needless_bool.rs | 2 +- .../src/needless_borrows_for_generic_args.rs | 11 +- clippy_lints/src/operators/bit_mask.rs | 9 +- clippy_lints/src/operators/identity_op.rs | 4 +- clippy_lints/src/operators/manual_div_ceil.rs | 5 +- .../src/operators/needless_bitwise_bool.rs | 14 +- clippy_lints/src/question_mark.rs | 163 +++++--- clippy_lints/src/ranges.rs | 21 +- clippy_lints/src/returns/let_and_return.rs | 46 +-- clippy_lints/src/returns/needless_return.rs | 2 +- clippy_lints/src/swap.rs | 2 +- clippy_lints/src/unsafe_removed_from_name.rs | 3 + clippy_lints/src/unused_async.rs | 1 - clippy_lints/src/unwrap.rs | 19 +- clippy_lints/src/write/empty_string.rs | 28 +- clippy_utils/Cargo.toml | 2 +- clippy_utils/README.md | 2 +- clippy_utils/src/lib.rs | 361 ++++++++++------- clippy_utils/src/msrvs.rs | 2 +- clippy_utils/src/sugg.rs | 5 + clippy_utils/src/ty/mod.rs | 19 +- declare_clippy_lint/Cargo.toml | 2 +- lintcheck/Cargo.toml | 2 +- rust-toolchain.toml | 2 +- tests/ui/bind_instead_of_map.fixed | 1 + tests/ui/bind_instead_of_map.rs | 1 + tests/ui/bind_instead_of_map.stderr | 6 +- tests/ui/bit_masks.rs | 10 + tests/ui/byte_char_slices.fixed | 40 +- tests/ui/byte_char_slices.rs | 38 +- tests/ui/byte_char_slices.stderr | 43 ++- tests/ui/expect_fun_call.fixed | 10 + tests/ui/expect_fun_call.rs | 10 + tests/ui/expect_fun_call.stderr | 14 +- tests/ui/fn_to_numeric_cast_any.rs | 2 +- tests/ui/fn_to_numeric_cast_any.stderr | 13 +- tests/ui/if_then_some_else_none.fixed | 6 +- tests/ui/if_then_some_else_none.rs | 6 +- tests/ui/if_then_some_else_none.stderr | 28 +- tests/ui/implicit_saturating_sub.fixed | 18 + tests/ui/implicit_saturating_sub.rs | 23 ++ tests/ui/implicit_saturating_sub.stderr | 14 +- tests/ui/int_plus_one.fixed | 14 + tests/ui/int_plus_one.rs | 14 + tests/ui/int_plus_one.stderr | 38 +- tests/ui/let_and_return.edition2021.fixed | 23 ++ tests/ui/let_and_return.edition2021.stderr | 16 +- tests/ui/let_and_return.edition2024.fixed | 23 ++ tests/ui/let_and_return.edition2024.stderr | 16 +- tests/ui/let_and_return.rs | 23 ++ tests/ui/manual_div_ceil.fixed | 9 + tests/ui/manual_div_ceil.rs | 9 + tests/ui/manual_div_ceil.stderr | 50 +-- tests/ui/manual_filter.fixed | 28 +- tests/ui/manual_filter.rs | 28 +- tests/ui/manual_filter.stderr | 62 ++- tests/ui/manual_is_power_of_two.fixed | 12 + tests/ui/manual_is_power_of_two.rs | 12 + tests/ui/manual_is_power_of_two.stderr | 8 +- tests/ui/manual_noop_waker.rs | 25 ++ tests/ui/manual_noop_waker.stderr | 10 +- tests/ui/manual_rotate.fixed | 23 ++ tests/ui/manual_rotate.rs | 23 ++ tests/ui/manual_rotate.stderr | 14 +- tests/ui/map_flatten.fixed | 2 +- tests/ui/map_flatten.rs | 2 +- ...map_with_unused_argument_over_ranges.fixed | 14 + .../map_with_unused_argument_over_ranges.rs | 14 + ...ap_with_unused_argument_over_ranges.stderr | 26 +- tests/ui/needless_bitwise_bool.fixed | 13 + tests/ui/needless_bitwise_bool.rs | 13 + tests/ui/needless_bitwise_bool.stderr | 8 +- tests/ui/needless_bool/fixable.fixed | 12 + tests/ui/needless_bool/fixable.rs | 16 + tests/ui/needless_bool/fixable.stderr | 12 +- tests/ui/needless_borrowed_ref.fixed | 3 +- tests/ui/needless_borrowed_ref.rs | 3 +- tests/ui/needless_borrowed_ref.stderr | 34 +- tests/ui/needless_match.fixed | 2 +- tests/ui/needless_match.rs | 2 +- tests/ui/println_empty_string.fixed | 25 ++ tests/ui/println_empty_string.rs | 25 ++ tests/ui/println_empty_string.stderr | 50 ++- tests/ui/ptr_offset_by_literal.fixed | 2 +- tests/ui/ptr_offset_by_literal.rs | 2 +- tests/ui/question_mark.fixed | 49 ++- tests/ui/question_mark.rs | 44 ++- tests/ui/question_mark.stderr | 199 ++++++++-- tests/ui/range_plus_minus_one.fixed | 13 + tests/ui/range_plus_minus_one.rs | 13 + tests/ui/range_plus_minus_one.stderr | 8 +- tests/ui/return_and_then.fixed | 1 + tests/ui/return_and_then.rs | 1 + tests/ui/return_and_then.stderr | 34 +- tests/ui/swap.fixed | 11 + tests/ui/swap.rs | 14 + tests/ui/swap.stderr | 11 +- tests/ui/unnecessary_cast.fixed | 363 ++++++++++++++++++ tests/ui/unnecessary_cast.rs | 363 ++++++++++++++++++ tests/ui/unnecessary_cast.stderr | 112 +++++- tests/ui/unneeded_wildcard_pattern.fixed | 26 ++ tests/ui/unneeded_wildcard_pattern.rs | 26 ++ tests/ui/unneeded_wildcard_pattern.stderr | 26 +- tests/ui/unsafe_removed_from_name.rs | 7 + tests/ui/unsafe_removed_from_name.stderr | 14 +- tests/ui/unused_async.rs | 11 + 145 files changed, 3428 insertions(+), 695 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 748e283edffb8..1276ab3d4bd37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,124 @@ document. ## Unreleased / Beta / In Rust Nightly -[500e0ff...master](https://github.com/rust-lang/rust-clippy/compare/500e0ff...master) +[df995e...master](https://github.com/rust-lang/rust-clippy/compare/df995e...master) + +## Rust 1.95 + +Current stable, released 2026-04-16 + +[View all 107 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2026-01-09T11%3A07%3A27Z..2026-02-23T22%3A37%3A09Z+base%3Amaster) + +### New Lints + +* Added [`unnecessary_trailing_comma`] to `pedantic` + [#16530](https://github.com/rust-lang/rust-clippy/pull/16530) +* Added [`disallowed_fields`] to `style` + [#16218](https://github.com/rust-lang/rust-clippy/pull/16218) +* Added [`manual_checked_ops`] to `complexity` + [#16149](https://github.com/rust-lang/rust-clippy/pull/16149) +* Added [`duration_suboptimal_units`] to `pedantic` + [#16250](https://github.com/rust-lang/rust-clippy/pull/16250) +* Added [`manual_take`] to `complexity` + [#16368](https://github.com/rust-lang/rust-clippy/pull/16368) + +### Enhancements + +* [`explicit_counter_loop`] fix FN when loop counter starts at non-zero + [#16620](https://github.com/rust-lang/rust-clippy/pull/16620) +* [`manual_is_variant_and`] extend to cover `filter` chaining `is_some` + [#16521](https://github.com/rust-lang/rust-clippy/pull/16521) +* [`manual_is_variant_and`] enhance to cover manual `is_none_or` + [#16424](https://github.com/rust-lang/rust-clippy/pull/16424) +* [`collapsible_match`] extend to cover if-elses + [#16560](https://github.com/rust-lang/rust-clippy/pull/16560) +* [`useless_conversion`] also fire inside compiler desugarings + [#16594](https://github.com/rust-lang/rust-clippy/pull/16594) +* [`unwrap_used`] and [`expect_used`] add `allow-unwrap-types` configuration + [#16605](https://github.com/rust-lang/rust-clippy/pull/16605) +* [`unwrap_used`] and [`expect_used`] optimize `allow-unwrap-types` evaluation to eliminate performance regression + [#16652](https://github.com/rust-lang/rust-clippy/pull/16652) +* [`unchecked_time_subtraction`] extend to better handle `Duration` literals + [#16528](https://github.com/rust-lang/rust-clippy/pull/16528) +* [`unnecessary_fold`] match against an accumulator on both sides + [#16604](https://github.com/rust-lang/rust-clippy/pull/16604) +* [`iter_kv_map`] extend to cover `flat_map` and `filter_map` + [#16519](https://github.com/rust-lang/rust-clippy/pull/16519) +* [`question_mark`] enhance to cover `else if` + [#16455](https://github.com/rust-lang/rust-clippy/pull/16455) +* [`double_comparisons`] check for expressions such as `x != y && x >= y` + [#16033](https://github.com/rust-lang/rust-clippy/pull/16033) +* [`needless_collect`] enhance to cover vec `push`-alike cases + [#16305](https://github.com/rust-lang/rust-clippy/pull/16305) +* [`strlen_on_c_strings`] changes suggestion to use `CStr::count_bytes()` + [#16323](https://github.com/rust-lang/rust-clippy/pull/16323) +* [`transmuting_null`] now checks for `ptr::without_provenance` and `ptr::without_provenance_mut` + [#16336](https://github.com/rust-lang/rust-clippy/pull/16336) +* [`map_unwrap_or`] add cover for `Result::unwrap_or` + [#15718](https://github.com/rust-lang/rust-clippy/pull/15718) +* [`clone_on_ref_ptr`] don't add a `&` to the receiver if it's a reference + [#15742](https://github.com/rust-lang/rust-clippy/pull/15742) +* [`double_must_use`], [`drop_non_drop`], [`let_underscore_must_use`] consider `Result` and + `ControlFlow` as `T` wrt `#[must_use]` if `U` is uninhabited + [#16353](https://github.com/rust-lang/rust-clippy/pull/16353) +* [`str_to_string`] handle a case when `ToString::to_string` is passed as function parameter + [#16512](https://github.com/rust-lang/rust-clippy/pull/16512) +* [`must_use_candidate`] no longer lints `main` functions with return values + [#16552](https://github.com/rust-lang/rust-clippy/pull/16552) +* [`needless_continue`] `allow` and `expect` attributes can also be used on the statement + [#16265](https://github.com/rust-lang/rust-clippy/pull/16265) +* [`int_plus_one`] fix FN with negative literals, e.g. `-1 + x <= y` + [#16373](https://github.com/rust-lang/rust-clippy/pull/16373) + +### False Positive Fixes + +* [`assertions_on_result_states`] and [`missing_assert_message`] fix FP on edition 2015 and 2018 + [#16473](https://github.com/rust-lang/rust-clippy/pull/16473) +* [`redundant_iter_cloned`] fix FP with move closures and coroutines + [#16494](https://github.com/rust-lang/rust-clippy/pull/16494) +* [`str_to_string`] fix FP on non-str types + [#16571](https://github.com/rust-lang/rust-clippy/pull/16571) +* [`unnecessary_cast`] do not warn on casts of external function return type + [#16415](https://github.com/rust-lang/rust-clippy/pull/16415) +* [`cmp_owned`] fix FP when `to_string` comes from macro input + [#16468](https://github.com/rust-lang/rust-clippy/pull/16468) +* [`useless_attribute`] fix FP on `exported_private_dependencies` lint attributes + [#16470](https://github.com/rust-lang/rust-clippy/pull/16470) +* [`manual_dangling_ptr`] fix FP when pointee type is not `Sized` + [#16469](https://github.com/rust-lang/rust-clippy/pull/16469) +* [`test_attr_in_doctest`] fix FP on `test_harness` + [#16454](https://github.com/rust-lang/rust-clippy/pull/16454) +* [`doc_paragraphs_missing_punctuation`] allow unpunctuated paragraphs before lists and code blocks + [#16487](https://github.com/rust-lang/rust-clippy/pull/16487) +* [`elidable_lifetime_names`] skip linting proc-macro generated code + [#16402](https://github.com/rust-lang/rust-clippy/pull/16402) +* [`undocumented_unsafe_blocks`] recognize safety comments inside blocks and on same line in macros + [#16339](https://github.com/rust-lang/rust-clippy/pull/16339) + +### ICE Fixes + +* [`match_same_arms`] fix ICE in `match_same_arms` + [#16685](https://github.com/rust-lang/rust-clippy/pull/16685) +* [`nonminimal_bool`] fix ICE in `swap_binop()` by using the proper `TypeckResults` + [#16659](https://github.com/rust-lang/rust-clippy/pull/16659) +* [`redundant_closure_for_method_calls`] fix ICE when computing the path from a type to itself + [#16362](https://github.com/rust-lang/rust-clippy/pull/16362) + +### Documentation Improvements + +* [`cast_possible_wrap`] mention `cast_{un,}signed()` methods in the documentation + [#16407](https://github.com/rust-lang/rust-clippy/pull/16407) +* [`ignore_without_reason`] and [`redundant_test_prefix`] mention an extra `clippy` argument + needed to check tests + [#16205](https://github.com/rust-lang/rust-clippy/pull/16205) +* [`doc_paragraphs_missing_punctuation`] improve its documentation + [#16377](https://github.com/rust-lang/rust-clippy/pull/16377) +* [`missing_trait_methods`] better help message + [#16380](https://github.com/rust-lang/rust-clippy/pull/16380) +* [`strlen_on_c_strings`] mention the specific type (`CString` or `CStr`) + [#16391](https://github.com/rust-lang/rust-clippy/pull/16391) +* [`suspicious_to_owned`] improve lint messages + [#16376](https://github.com/rust-lang/rust-clippy/pull/16376) ## Rust 1.94 @@ -104,11 +221,6 @@ Current stable, released 2026-03-05 * [`needless_type_cast`] do not ICE on struct constructor [#16245](https://github.com/rust-lang/rust-clippy/pull/16245) -### New Lints - -* Added [`unnecessary_trailing_comma`] to `style` (single-line format-like macros only) - [#13965](https://github.com/rust-lang/rust-clippy/issues/13965) - ## Rust 1.93 Current stable, released 2026-01-22 diff --git a/Cargo.toml b/Cargo.toml index bcd800930c517..234478eadbf96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy" -version = "0.1.96" +version = "0.1.97" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" @@ -33,7 +33,7 @@ color-print = "0.3.4" anstream = "0.6.18" [dev-dependencies] -cargo_metadata = "0.18.1" +cargo_metadata = "0.23" ui_test = "0.30.2" regex = "1.5.5" serde = { version = "1.0.145", features = ["derive"] } diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index c87f8e9a68de1..64d0bf9b62f69 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -928,6 +928,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`manual_let_else`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else) * [`manual_midpoint`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_midpoint) * [`manual_non_exhaustive`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive) +* [`manual_noop_waker`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_noop_waker) * [`manual_option_as_slice`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_option_as_slice) * [`manual_pattern_char_comparison`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_pattern_char_comparison) * [`manual_range_contains`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains) diff --git a/clippy_config/Cargo.toml b/clippy_config/Cargo.toml index 366c776b8f1a5..3d5b425ac530d 100644 --- a/clippy_config/Cargo.toml +++ b/clippy_config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_config" -version = "0.1.96" +version = "0.1.97" edition = "2024" publish = false diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 41099f94b0448..465e88a783ed8 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -789,6 +789,7 @@ define_Conf! { manual_let_else, manual_midpoint, manual_non_exhaustive, + manual_noop_waker, manual_option_as_slice, manual_pattern_char_comparison, manual_range_contains, diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 51e753efb52ea..52b85103209b4 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_lints" -version = "0.1.96" +version = "0.1.97" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" @@ -10,7 +10,7 @@ edition = "2024" [dependencies] arrayvec = { version = "0.7", default-features = false } -cargo_metadata = "0.18" +cargo_metadata = "0.23" clippy_config = { path = "../clippy_config" } clippy_utils = { path = "../clippy_utils" } declare_clippy_lint = { path = "../declare_clippy_lint" } diff --git a/clippy_lints/src/arbitrary_source_item_ordering.rs b/clippy_lints/src/arbitrary_source_item_ordering.rs index 7f0f0a0245f4c..dae0c8439ea82 100644 --- a/clippy_lints/src/arbitrary_source_item_ordering.rs +++ b/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -306,9 +306,16 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering { cur_f = Some(field); } }, - ItemKind::Trait(_constness, is_auto, _safety, _impl_restriction, _ident, _generics, _generic_bounds, item_ref) - if self.enable_ordering_for_trait && *is_auto == IsAuto::No => - { + ItemKind::Trait( + _constness, + is_auto, + _safety, + _impl_restriction, + _ident, + _generics, + _generic_bounds, + item_ref, + ) if self.enable_ordering_for_trait && *is_auto == IsAuto::No => { let mut cur_t: Option<(TraitItemId, Ident)> = None; for &item in *item_ref { diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 8b7619d11a83d..986e75577412a 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -30,6 +30,8 @@ declare_clippy_lint! { /// Ignores short circuiting behavior of `||` and /// `&&`. Ignores `|`, `&` and `^`. /// + /// Creates a big toll on performance, **only enable sporadically** + /// /// ### Example /// ```ignore /// if a && true {} @@ -43,7 +45,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "pre 1.29.0"] pub NONMINIMAL_BOOL, - complexity, + pedantic, "boolean expressions that can be written more concisely" } @@ -57,6 +59,7 @@ declare_clippy_lint! { /// /// ### Known problems /// Ignores short circuiting behavior. + /// Creates a big toll on performance, **only enable sporadically** /// /// ### Example /// ```rust,ignore @@ -70,7 +73,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "pre 1.29.0"] pub OVERLY_COMPLEX_BOOL_EXPR, - correctness, + pedantic, "boolean expressions that contain terminals which can be eliminated" } diff --git a/clippy_lints/src/byte_char_slices.rs b/clippy_lints/src/byte_char_slices.rs index 6c023189f69e1..5104594790fe8 100644 --- a/clippy_lints/src/byte_char_slices.rs +++ b/clippy_lints/src/byte_char_slices.rs @@ -1,9 +1,15 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use rustc_ast::ast::{BorrowKind, Expr, ExprKind, Mutability}; -use rustc_ast::token::{Lit, LitKind}; +use std::borrow::Cow; + +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::sugg::Sugg; +use clippy_utils::{get_parent_expr, span_contains_cfg, span_contains_comment}; +use rustc_ast::LitKind; use rustc_errors::Applicability; -use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability}; +use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; +use rustc_span::Span; declare_clippy_lint! { /// ### What it does @@ -30,47 +36,73 @@ declare_clippy_lint! { declare_lint_pass!(ByteCharSlice => [BYTE_CHAR_SLICES]); -impl EarlyLintPass for ByteCharSlice { - fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { +impl<'tcx> LateLintPass<'tcx> for ByteCharSlice { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if !expr.span.from_expansion() - && let Some(slice) = is_byte_char_slices(expr) + && let Some((has_ref, slice)) = is_byte_char_slices(cx, expr) { - span_lint_and_sugg( + span_lint_and_then( cx, BYTE_CHAR_SLICES, expr.span, "can be more succinctly written as a byte str", - "try", - format!("b\"{slice}\""), - Applicability::MachineApplicable, + |diag| { + let mut app = Applicability::MachineApplicable; + let mut sugg = Sugg::hir_from_snippet(cx, expr, |_| { + let mut slice = slice.iter().fold("b\"".to_owned(), |mut acc, span| { + let snippet = snippet_with_applicability(cx, *span, "b'?'", &mut app); + acc.push_str(match &snippet[2..snippet.len() - 1] { + "\"" => "\\\"", + "\\'" => "'", + other => other, + }); + acc + }); + slice.push('"'); + Cow::Owned(slice) + }); + if !has_ref && !cx.typeck_results().expr_ty_adjusted(expr).is_array_slice() { + sugg = sugg.deref(); + } + + diag.span_suggestion(expr.span, "try", sugg, app); + }, ); } } } /// Checks whether the slice is that of byte chars, and if so, builds a byte-string out of it -fn is_byte_char_slices(expr: &Expr) -> Option { - if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, expr) = &expr.kind - && let ExprKind::Array(members) = &expr.kind +fn is_byte_char_slices<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<(bool, Vec)> { + let (has_ref, expr) = if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = expr.kind { + (true, inner) + } else if let Some(parent) = get_parent_expr(cx, expr) // Already checked by the parent expr. + && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind + { + return None; + } else { + (false, expr) + }; + + if let ExprKind::Array(members) = expr.kind && !members.is_empty() + && !span_contains_comment(cx, expr.span) + && !span_contains_cfg(cx, expr.span) { - members + return members .iter() - .map(|member| match &member.kind { - ExprKind::Lit(Lit { - kind: LitKind::Byte, - symbol, - .. - }) => Some(symbol.as_str()), - _ => None, - }) - .map(|maybe_quote| match maybe_quote { - Some("\"") => Some("\\\""), - Some("\\'") => Some("'"), - other => other, + .try_fold(Vec::new(), |mut acc, member| { + if let ExprKind::Lit(lit) = member.kind + && let LitKind::Byte(_) = lit.node + && expr.span.eq_ctxt(member.span) + { + acc.push(lit.span); + return Some(acc); + } + None }) - .collect::>() - } else { - None + .map(|s| (has_ref, s)); } + + None } diff --git a/clippy_lints/src/cargo/multiple_crate_versions.rs b/clippy_lints/src/cargo/multiple_crate_versions.rs index 44cd1f7192fb1..21b5acf640733 100644 --- a/clippy_lints/src/cargo/multiple_crate_versions.rs +++ b/clippy_lints/src/cargo/multiple_crate_versions.rs @@ -32,7 +32,7 @@ pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata, allowed_duplicate { for (name, group) in &packages .iter() - .filter(|p| !allowed_duplicate_crates.contains(&p.name)) + .filter(|p| !allowed_duplicate_crates.contains(p.name.as_str())) .group_by(|p| &p.name) { let group: Vec<&Package> = group.collect(); diff --git a/clippy_lints/src/cargo/wildcard_dependencies.rs b/clippy_lints/src/cargo/wildcard_dependencies.rs index 0cf687d01928c..c09b02ba449a5 100644 --- a/clippy_lints/src/cargo/wildcard_dependencies.rs +++ b/clippy_lints/src/cargo/wildcard_dependencies.rs @@ -10,7 +10,7 @@ pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) { // VersionReq::any() does not work if let Ok(wildcard_ver) = semver::VersionReq::parse("*") && let Some(ref source) = dep.source - && !source.starts_with("git") + && !source.repr.starts_with("git") && dep.req == wildcard_ver { span_lint( 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 43ee91af6e5a0..0ce2741ad059b 100644 --- a/clippy_lints/src/casts/fn_to_numeric_cast_any.rs +++ b/clippy_lints/src/casts/fn_to_numeric_cast_any.rs @@ -8,11 +8,6 @@ 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. - if cast_to.is_fn() { - return; - } - 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/mod.rs b/clippy_lints/src/casts/mod.rs index 151f9c956c6db..de34b2691abc6 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -911,10 +911,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts { ptr_cast_constness::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv); ptr_as_ptr::check(cx, expr, cast_from_expr, cast_from, cast_to_hir, cast_to, self.msrv); as_ptr_cast_mut::check(cx, expr, cast_from_expr, cast_to); - fn_to_numeric_cast_any::check(cx, expr, cast_from_expr, cast_from, cast_to); confusing_method_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to); - fn_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to); - fn_to_numeric_cast_with_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to); zero_ptr::check(cx, expr, cast_from_expr, cast_to_hir, self.msrv); if self.msrv.meets(cx, msrvs::MANUAL_DANGLING_PTR) { @@ -932,6 +929,9 @@ impl<'tcx> LateLintPass<'tcx> for Casts { } cast_lossless::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir, self.msrv); cast_enum_constructor::check(cx, expr, cast_from_expr, cast_from); + fn_to_numeric_cast_any::check(cx, expr, cast_from_expr, cast_from, cast_to); + fn_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to); + fn_to_numeric_cast_with_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to); } as_underscore::check(cx, expr, cast_to_hir); diff --git a/clippy_lints/src/casts/ref_as_ptr.rs b/clippy_lints/src/casts/ref_as_ptr.rs index b3805c6781749..aef04dc9f7f9a 100644 --- a/clippy_lints/src/casts/ref_as_ptr.rs +++ b/clippy_lints/src/casts/ref_as_ptr.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; -use clippy_utils::{ExprUseNode, expr_use_ctxt, is_expr_temporary_value, std_or_core}; +use clippy_utils::{ExprUseNode, get_expr_use_site, is_expr_temporary_value, std_or_core}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, Mutability, Ty, TyKind}; use rustc_lint::LateContext; @@ -22,13 +22,12 @@ pub(super) fn check<'tcx>( if matches!(cast_from.kind(), ty::Ref(..)) && let ty::RawPtr(_, to_mutbl) = cast_to.kind() - && let use_cx = expr_use_ctxt(cx, expr) && let Some(std_or_core) = std_or_core(cx) { if let ExprKind::AddrOf(_, _, addr_inner) = cast_expr.kind && is_expr_temporary_value(cx, addr_inner) && matches!( - use_cx.use_node(cx), + get_expr_use_site(cx.tcx, cx.typeck_results(), expr.span.ctxt(), expr).use_node(cx), ExprUseNode::LetStmt(_) | ExprUseNode::ConstStatic(_) ) { diff --git a/clippy_lints/src/casts/unnecessary_cast.rs b/clippy_lints/src/casts/unnecessary_cast.rs index f822590a721db..333f31ba00eac 100644 --- a/clippy_lints/src/casts/unnecessary_cast.rs +++ b/clippy_lints/src/casts/unnecessary_cast.rs @@ -8,10 +8,10 @@ use clippy_utils::{get_parent_expr, is_hir_ty_cfg_dependant, is_ty_alias, sym}; use rustc_ast::{LitFloatType, LitIntType, LitKind}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{Expr, ExprKind, FnRetTy, Lit, Node, Path, QPath, TyKind, UnOp}; +use rustc_hir::{Expr, ExprKind, FnRetTy, HirId, Lit, Node, Path, QPath, TyKind, UnOp}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty::adjustment::Adjust; -use rustc_middle::ty::{self, FloatTy, InferTy, Ty}; +use rustc_middle::ty::{self, FloatTy, GenericArg, InferTy, Ty}; use rustc_span::Symbol; use std::ops::ControlFlow; @@ -173,6 +173,16 @@ pub(super) fn check<'tcx>( cx.tcx.get_diagnostic_name(def_id).is_some_and(|sym| ALLOWED_MACROS.contains(&sym))) } + // Removing the cast here can change inference along the path to an outer + // method receiver, so avoid linting in that case. + if is_inference_sensitive_inner_expr(cx, cast_expr) + && contains_unsuffixed_numeric_literal(cast_expr) + && feeds_outer_method_receiver(cx, expr) + && has_lint_blocking_context_on_receiver_path(cx, expr) + { + return false; + } + if let Some(id) = cast_expr.res_local_id() && !cx.tcx.hir_span(id).eq_ctxt(cast_expr.span) { @@ -340,3 +350,338 @@ fn emit_lint( applicability, ); } + +fn contains_unsuffixed_numeric_literal<'e>(expr: &'e Expr<'e>) -> bool { + for_each_expr_without_closures(expr, |e| { + if let Some(lit) = get_numeric_literal(e) + && matches!( + lit.node, + LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed) + ) + { + return ControlFlow::Break(()); + } + + ControlFlow::Continue(()) + }) + .is_some() +} + +// Returns `true` for expressions whose resolved type or method depends on inference. +fn is_inference_sensitive_inner_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::MethodCall(..) | ExprKind::Binary(..) | ExprKind::Unary(..) | ExprKind::Index(..) => cx + .typeck_results() + .type_dependent_def_id(expr.hir_id) + .and_then(|def_id| cx.tcx.opt_associated_item(def_id)) + .is_some_and(|assoc| assoc.trait_container(cx.tcx).is_some()), + _ => false, + } +} + +// Returns `true` if the function's output type contains a type parameter +// originating from the selected input. +fn output_depends_on_input_param(cx: &LateContext<'_>, def_id: rustc_hir::def_id::DefId, input_index: usize) -> bool { + let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder(); + + let Some(input_ty) = sig.inputs().get(input_index) else { + return false; + }; + + let output_ty = sig.output(); + + input_ty.walk().filter_map(GenericArg::as_type).any(|input_part| { + match input_part.kind() { + ty::Param(input_param) => output_ty + .walk() + .filter_map(GenericArg::as_type) + .any(|output_part| { + matches!(output_part.kind(), ty::Param(output_param) if output_param.index == input_param.index) + }), + _ => false, + } + }) +} + +// Returns `true` if the generic arguments include at least one explicit type or const +// argument and none of the provided generic arguments are placeholders like `::<_>`. +fn has_explicit_type_or_const_args(args: Option<&rustc_hir::GenericArgs<'_>>) -> bool { + let Some(args) = args else { + return false; + }; + + let mut has_explicit = false; + + for arg in args.args { + match arg { + rustc_hir::GenericArg::Type(_) | rustc_hir::GenericArg::Const(_) => { + has_explicit = true; + }, + rustc_hir::GenericArg::Infer(_) => return false, + rustc_hir::GenericArg::Lifetime(_) => {}, + } + } + + has_explicit +} + +// Controls whether the receiver path walk is looking for an outer method +// receiver or for a context where linting should stop. +#[derive(Copy, Clone)] +enum ReceiverPathMode { + FindReceiver, + FindLintBlockingContext, +} + +enum ReceiverPathResult { + Continue(HirId), + Stop(bool), +} + +fn stop_if_lint_blocking_else_continue(parent_hir_id: HirId, mode: ReceiverPathMode) -> ReceiverPathResult { + if matches!(mode, ReceiverPathMode::FindLintBlockingContext) { + ReceiverPathResult::Stop(true) + } else { + ReceiverPathResult::Continue(parent_hir_id) + } +} + +fn walk_receiver_path_method_call( + cx: &LateContext<'_>, + current_hir_id: HirId, + parent: &Expr<'_>, + segment: &rustc_hir::PathSegment<'_>, + receiver: &Expr<'_>, + args: &[Expr<'_>], + mode: ReceiverPathMode, +) -> ReceiverPathResult { + if receiver.hir_id == current_hir_id { + return if matches!(mode, ReceiverPathMode::FindLintBlockingContext) { + ReceiverPathResult::Continue(parent.hir_id) + } else { + ReceiverPathResult::Stop(true) + }; + } + + let Some(arg_index) = args.iter().position(|arg| arg.hir_id == current_hir_id) else { + return ReceiverPathResult::Stop(false); + }; + + let passthrough = !has_explicit_type_or_const_args(segment.args) + && cx + .typeck_results() + .type_dependent_def_id(parent.hir_id) + .is_some_and(|def_id| output_depends_on_input_param(cx, def_id, arg_index + 1)); + + if matches!(mode, ReceiverPathMode::FindLintBlockingContext) { + if passthrough + || args.iter().any(|arg| { + arg.hir_id != current_hir_id + && get_numeric_literal(arg).is_none() + && !cx.typeck_results().expr_ty(arg).is_primitive() + }) + { + ReceiverPathResult::Stop(true) + } else { + ReceiverPathResult::Continue(parent.hir_id) + } + } else if passthrough { + ReceiverPathResult::Continue(parent.hir_id) + } else { + ReceiverPathResult::Stop(false) + } +} + +fn walk_receiver_path_call( + cx: &LateContext<'_>, + current_hir_id: HirId, + parent: &Expr<'_>, + callee: &Expr<'_>, + args: &[Expr<'_>], + mode: ReceiverPathMode, +) -> ReceiverPathResult { + if callee.hir_id == current_hir_id { + return ReceiverPathResult::Continue(parent.hir_id); + } + + let Some(arg_index) = args.iter().position(|arg| arg.hir_id == current_hir_id) else { + return ReceiverPathResult::Stop(false); + }; + + let passthrough = if let ExprKind::Path(qpath) = callee.kind + && let Res::Def(DefKind::Fn, def_id) = cx.qpath_res(&qpath, callee.hir_id) + { + let has_explicit_args = match &qpath { + QPath::Resolved(_, path) => path + .segments + .last() + .is_some_and(|seg| has_explicit_type_or_const_args(seg.args)), + QPath::TypeRelative(_, segment) => has_explicit_type_or_const_args(segment.args), + }; + + !has_explicit_args && output_depends_on_input_param(cx, def_id, arg_index) + } else { + false + }; + + if matches!(mode, ReceiverPathMode::FindLintBlockingContext) { + ReceiverPathResult::Stop(passthrough) + } else if passthrough { + ReceiverPathResult::Continue(parent.hir_id) + } else { + ReceiverPathResult::Stop(false) + } +} + +// Walk one step up the receiver path for the current mode. +fn walk_receiver_path_step(cx: &LateContext<'_>, current_hir_id: HirId, mode: ReceiverPathMode) -> ReceiverPathResult { + match cx.tcx.parent_hir_node(current_hir_id) { + Node::Expr(parent) => match parent.kind { + // Main case. + // The current node may be the receiver. + // Or it may flow through a passthrough method. + ExprKind::MethodCall(segment, receiver, args, _) => { + walk_receiver_path_method_call(cx, current_hir_id, parent, segment, receiver, args, mode) + }, + // Regular calls only keep the path alive + // if the output still depends on this input. + ExprKind::Call(callee, args) => walk_receiver_path_call(cx, current_hir_id, parent, callee, args, mode), + // A sibling that is not primitive blocks the lint. + ExprKind::Binary(_, left, right) | ExprKind::Index(left, right, _) + if left.hir_id == current_hir_id || right.hir_id == current_hir_id => + { + if matches!(mode, ReceiverPathMode::FindLintBlockingContext) { + let sibling = if left.hir_id == current_hir_id { right } else { left }; + if get_numeric_literal(sibling).is_none() && !cx.typeck_results().expr_ty(sibling).is_primitive() { + ReceiverPathResult::Stop(true) + } else { + ReceiverPathResult::Continue(parent.hir_id) + } + } else { + ReceiverPathResult::Continue(parent.hir_id) + } + }, + // These expressions don't block the lint, so we continue walking up the path. + ExprKind::Unary(_, inner) + | ExprKind::Cast(inner, _) + | ExprKind::AddrOf(_, _, inner) + | ExprKind::Field(inner, _) + | ExprKind::DropTemps(inner) + if inner.hir_id == current_hir_id => + { + ReceiverPathResult::Continue(parent.hir_id) + }, + // A block can forward its tail expression, so we keep walking through it. + ExprKind::Block(block, _) + if block.hir_id == current_hir_id || block.expr.is_some_and(|tail| tail.hir_id == current_hir_id) => + { + ReceiverPathResult::Continue(parent.hir_id) + }, + // Depending on the mode, either keep walking or block the lint. + ExprKind::Loop(block, ..) if block.hir_id == current_hir_id => { + stop_if_lint_blocking_else_continue(parent.hir_id, mode) + }, + // Tuples and arrays wrap the current expression, so we continue walking up the path. + ExprKind::Tup(exprs) | ExprKind::Array(exprs) if exprs.iter().any(|e| e.hir_id == current_hir_id) => { + ReceiverPathResult::Continue(parent.hir_id) + }, + // The expression is stored in a field. We continue walking up the path to see how the struct is used. + ExprKind::Struct(_, fields, _) + if fields + .iter() + .any(|field| field.hir_id == current_hir_id || field.expr.hir_id == current_hir_id) => + { + ReceiverPathResult::Continue(parent.hir_id) + }, + // Depending on the mode, either keep walking or block the lint. + ExprKind::If(cond, then_expr, else_expr) + if cond.hir_id == current_hir_id + || then_expr.hir_id == current_hir_id + || else_expr.is_some_and(|else_expr| else_expr.hir_id == current_hir_id) => + { + stop_if_lint_blocking_else_continue(parent.hir_id, mode) + }, + // Depending on the mode, either keep walking or block the lint. + ExprKind::Match(scrutinee, arms, _) + if scrutinee.hir_id == current_hir_id + || arms + .iter() + .any(|arm| arm.hir_id == current_hir_id || arm.body.hir_id == current_hir_id) => + { + stop_if_lint_blocking_else_continue(parent.hir_id, mode) + }, + // Depending on the mode, either keep walking or block the lint. + ExprKind::Break(_, Some(inner)) if inner.hir_id == current_hir_id => { + stop_if_lint_blocking_else_continue(parent.hir_id, mode) + }, + _ => ReceiverPathResult::Stop(false), + }, + // These are structural HIR nodes. We just skip them and keep walking. + Node::ExprField(_) | Node::Block(_) | Node::Arm(_) | Node::Stmt(_) => { + ReceiverPathResult::Continue(cx.tcx.parent_hir_id(current_hir_id)) + }, + // Handle `let x = init; x` in the same block. + // Depending on the mode, either keep walking init or block the lint. + Node::LetStmt(local) => { + if let Some(block_hir_id) = let_init_to_block_hir_id(cx, local, current_hir_id) { + stop_if_lint_blocking_else_continue(block_hir_id, mode) + } else { + ReceiverPathResult::Stop(false) + } + }, + _ => ReceiverPathResult::Stop(false), + } +} + +// Returns `true` if `expr` eventually becomes the receiver of an outer method call. +fn feeds_outer_method_receiver(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let mut current_hir_id = expr.hir_id; + + loop { + match walk_receiver_path_step(cx, current_hir_id, ReceiverPathMode::FindReceiver) { + ReceiverPathResult::Continue(next) => current_hir_id = next, + ReceiverPathResult::Stop(result) => return result, + } + } +} + +// Returns `true` if the receiver path contains a context that should block the lint. +fn has_lint_blocking_context_on_receiver_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let mut current_hir_id = expr.hir_id; + + loop { + match walk_receiver_path_step(cx, current_hir_id, ReceiverPathMode::FindLintBlockingContext) { + ReceiverPathResult::Continue(next) => current_hir_id = next, + ReceiverPathResult::Stop(result) => return result, + } + } +} + +// If the initializer flows into the tail expression of the same block, returns that block HirId. +fn let_init_to_block_hir_id( + cx: &LateContext<'_>, + local: &rustc_hir::LetStmt<'_>, + current_hir_id: HirId, +) -> Option { + let init = local.init?; + if init.hir_id != current_hir_id { + return None; + } + + let stmt_hir_id = match cx.tcx.parent_hir_node(local.hir_id) { + Node::Stmt(stmt) => stmt.hir_id, + _ => return None, + }; + + let Node::Block(block) = cx.tcx.parent_hir_node(stmt_hir_id) else { + return None; + }; + + let tail = block.expr?; + let binding_hir_id = tail.res_local_id()?; + + match local.pat.kind { + rustc_hir::PatKind::Binding(_, local_hir_id, ..) if local_hir_id == binding_hir_id => Some(block.hir_id), + _ => None, + } +} diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs index 63d9064bbc371..95b99c1d2435f 100644 --- a/clippy_lints/src/cognitive_complexity.rs +++ b/clippy_lints/src/cognitive_complexity.rs @@ -145,7 +145,7 @@ impl<'tcx> LateLintPass<'tcx> for CognitiveComplexity { def_id: LocalDefId, ) { #[allow(deprecated)] - if !cx.tcx.get_attrs(def_id, sym::test).next().is_some() { + if cx.tcx.get_attrs(def_id, sym::test).next().is_none() { let expr = if kind.asyncness().is_async() { match get_async_fn_body(cx.tcx, body) { Some(b) => b, diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 89a6d445e8180..ebaa7a39213de 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -4,7 +4,7 @@ use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::ty::{adjust_derefs_manually_drop, implements_trait, is_manually_drop, peel_and_count_ty_refs}; use clippy_utils::{ - DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_from_proc_macro, is_lint_allowed, sym, + DefinedTy, ExprUseNode, get_expr_use_site, get_parent_expr, is_block_like, is_from_proc_macro, is_lint_allowed, sym, }; use rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::fx::FxIndexMap; @@ -19,7 +19,7 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, TypeckResults}; use rustc_session::impl_lint_pass; -use rustc_span::{Span, Symbol}; +use rustc_span::{Span, Symbol, SyntaxContext}; use std::borrow::Cow; declare_clippy_lint! { @@ -276,15 +276,15 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { match (self.state.take(), kind) { (None, kind) => { let expr_ty = typeck.expr_ty(expr); - let use_cx = expr_use_ctxt(cx, expr); - let adjusted_ty = use_cx.adjustments.last().map_or(expr_ty, |a| a.target); + let use_site = get_expr_use_site(cx.tcx, typeck, SyntaxContext::root(), expr); + let adjusted_ty = use_site.adjustments.last().map_or(expr_ty, |a| a.target); match kind { - RefOp::Deref if use_cx.same_ctxt => { - let use_node = use_cx.use_node(cx); + RefOp::Deref if use_site.same_ctxt => { + let use_node = use_site.use_node(cx); let sub_ty = typeck.expr_ty(sub_expr); if let ExprUseNode::FieldAccess(name) = use_node - && !use_cx.moved_before_use + && !use_site.moved_before_use && !ty_contains_field(sub_ty, name.name) { self.state = Some(( @@ -331,9 +331,9 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { }, )); }, - RefOp::AddrOf(mutability) if use_cx.same_ctxt => { + RefOp::AddrOf(mutability) if use_site.same_ctxt => { // Find the number of times the borrow is auto-derefed. - let mut iter = use_cx.adjustments.iter(); + let mut iter = use_site.adjustments.iter(); let mut deref_count = 0usize; let next_adjust = loop { match iter.next() { @@ -350,13 +350,13 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { } }; - let use_node = use_cx.use_node(cx); + let use_node = use_site.use_node(cx); let stability = use_node.defined_ty(cx).map_or(TyCoercionStability::None, |ty| { TyCoercionStability::for_defined_ty(cx, ty, use_node.is_return()) }); let can_auto_borrow = match use_node { ExprUseNode::FieldAccess(_) - if !use_cx.moved_before_use && matches!(sub_expr.kind, ExprKind::Field(..)) => + if !use_site.moved_before_use && matches!(sub_expr.kind, ExprKind::Field(..)) => { // `DerefMut` will not be automatically applied to `ManuallyDrop<_>` // field expressions when the base type is a union and the parent @@ -364,10 +364,10 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { // // e.g. `&mut x.y.z` where `x` is a union, and accessing `z` requires a // deref through `ManuallyDrop<_>` will not compile. - !adjust_derefs_manually_drop(use_cx.adjustments, expr_ty) + !adjust_derefs_manually_drop(use_site.adjustments, expr_ty) }, - ExprUseNode::Callee | ExprUseNode::FieldAccess(_) if !use_cx.moved_before_use => true, - ExprUseNode::MethodArg(hir_id, _, 0) if !use_cx.moved_before_use => { + ExprUseNode::Callee | ExprUseNode::FieldAccess(_) if !use_site.moved_before_use => true, + ExprUseNode::MethodArg(hir_id, _, 0) if !use_site.moved_before_use => { // Check for calls to trait methods where the trait is implemented // on a reference. // Two cases need to be handled: @@ -455,7 +455,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { msg, stability, for_field_access: if let ExprUseNode::FieldAccess(name) = use_node - && !use_cx.moved_before_use + && !use_site.moved_before_use { Some(name.name) } else { diff --git a/clippy_lints/src/derive/derive_partial_eq_without_eq.rs b/clippy_lints/src/derive/derive_partial_eq_without_eq.rs index 22943cd9ee5e2..3782c1dab3557 100644 --- a/clippy_lints/src/derive/derive_partial_eq_without_eq.rs +++ b/clippy_lints/src/derive/derive_partial_eq_without_eq.rs @@ -85,8 +85,5 @@ fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> .upcast(tcx) }), ))); - ty::TypingEnv::new( - param_env, - ty::TypingMode::non_body_analysis(), - ) + ty::TypingEnv::new(param_env, ty::TypingMode::non_body_analysis()) } diff --git a/clippy_lints/src/disallowed_fields.rs b/clippy_lints/src/disallowed_fields.rs index 9873c32f427f1..28fcb46c50b69 100644 --- a/clippy_lints/src/disallowed_fields.rs +++ b/clippy_lints/src/disallowed_fields.rs @@ -50,7 +50,7 @@ declare_clippy_lint! { /// let range = Range { start: 0, end: 1 }; /// println!("{}", range.end); // `end` is _not_ disallowed in the config. /// ``` - #[clippy::version = "1.93.0"] + #[clippy::version = "1.95.0"] pub DISALLOWED_FIELDS, style, "declaration of a disallowed field use" diff --git a/clippy_lints/src/float_literal.rs b/clippy_lints/src/float_literal.rs index c6ae2cfc25454..ab1f5b88bacee 100644 --- a/clippy_lints/src/float_literal.rs +++ b/clippy_lints/src/float_literal.rs @@ -1,6 +1,6 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{ExprUseNode, expr_use_ctxt, numeric_literal}; +use clippy_utils::{ExprUseNode, get_expr_use_site, numeric_literal}; use rustc_ast::ast::{LitFloatType, LitKind}; use rustc_errors::Applicability; use rustc_hir as hir; @@ -143,7 +143,10 @@ impl<'tcx> LateLintPass<'tcx> for FloatLiteral { } } else if digits > max as usize && count_digits(&float_str) < digits { if digits >= self.const_literal_digits_threshold - && matches!(expr_use_ctxt(cx, expr).use_node(cx), ExprUseNode::ConstStatic(_)) + && matches!( + get_expr_use_site(cx.tcx, cx.typeck_results(), expr.span.ctxt(), expr).use_node(cx), + ExprUseNode::ConstStatic(_) + ) { // If a big enough number of digits is specified and it's a constant // we assume the user is definining a constant, and excessive precision is ok diff --git a/clippy_lints/src/implicit_saturating_sub.rs b/clippy_lints/src/implicit_saturating_sub.rs index ee3619ea6746b..666c4cb737f4f 100644 --- a/clippy_lints/src/implicit_saturating_sub.rs +++ b/clippy_lints/src/implicit_saturating_sub.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::{Sugg, make_binop}; use clippy_utils::{ SpanlessEq, eq_expr_value, higher, is_in_const_context, is_integer_literal, is_integer_literal_untyped, @@ -246,33 +246,35 @@ fn check_subtraction( // This part of the condition is voluntarily split from the one before to ensure that // if `snippet_opt` fails, it won't try the next conditions. if !is_in_const_context(cx) || msrv.meets(cx, msrvs::SATURATING_SUB_CONST) { - let mut applicability = Applicability::MachineApplicable; - let big_expr_sugg = (if is_integer_literal_untyped(big_expr) { - let get_snippet = |span: Span| { - let snippet = snippet_with_applicability(cx, span, "..", &mut applicability); - let big_expr_ty = cx.typeck_results().expr_ty(big_expr); - Cow::Owned(format!("{snippet}_{big_expr_ty}")) - }; - Sugg::hir_from_snippet(cx, big_expr, get_snippet) - } else { - Sugg::hir_with_applicability(cx, big_expr, "..", &mut applicability) - }) - .maybe_paren(); - let little_expr_sugg = Sugg::hir_with_applicability(cx, little_expr, "..", &mut applicability); - - let sugg = format!( - "{}{big_expr_sugg}.saturating_sub({little_expr_sugg}){}", - if is_composited { "{ " } else { "" }, - if is_composited { " }" } else { "" } - ); - span_lint_and_sugg( + span_lint_and_then( cx, IMPLICIT_SATURATING_SUB, expr_span, "manual arithmetic check found", - "replace it with", - sugg, - applicability, + |diag| { + let mut applicability = Applicability::MachineApplicable; + let expr_span_ctxt = expr_span.ctxt(); + let big_expr_sugg = (if is_integer_literal_untyped(big_expr) { + let get_snippet = |span: Span| { + let (snippet, _) = + snippet_with_context(cx, span, expr_span_ctxt, "..", &mut applicability); + let big_expr_ty = cx.typeck_results().expr_ty(big_expr); + Cow::Owned(format!("{snippet}_{big_expr_ty}")) + }; + Sugg::hir_from_snippet(cx, big_expr, get_snippet) + } else { + Sugg::hir_with_context(cx, big_expr, expr_span_ctxt, "..", &mut applicability) + }) + .maybe_paren(); + let little_expr_sugg = + Sugg::hir_with_context(cx, little_expr, expr_span_ctxt, "..", &mut applicability); + let sugg = format!( + "{}{big_expr_sugg}.saturating_sub({little_expr_sugg}){}", + if is_composited { "{ " } else { "" }, + if is_composited { " }" } else { "" } + ); + diag.span_suggestion(expr_span, "replace it with", sugg, applicability); + }, ); } } else if eq_expr_value(cx, left, little_expr) diff --git a/clippy_lints/src/int_plus_one.rs b/clippy_lints/src/int_plus_one.rs index f8184b30f4000..524cb6bf75363 100644 --- a/clippy_lints/src/int_plus_one.rs +++ b/clippy_lints/src/int_plus_one.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::sugg; use rustc_ast::ast::{BinOpKind, Expr, ExprKind, LitKind, UnOp}; +use rustc_ast::util::parser::AssocOp; use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; @@ -134,12 +135,19 @@ impl IntPlusOne { |diag| { let mut app = Applicability::MachineApplicable; let ctxt = expr.span.ctxt(); - let new_lhs = sugg::Sugg::ast(cx, new_lhs, "_", ctxt, &mut app); + let mut new_lhs = sugg::Sugg::ast(cx, new_lhs, "_", ctxt, &mut app); let new_rhs = sugg::Sugg::ast(cx, new_rhs, "_", ctxt, &mut app); let new_binop = match le_or_ge { LeOrGe::Ge => BinOpKind::Gt, LeOrGe::Le => BinOpKind::Lt, }; + // When the replacement operator is `<`, an `as` cast on the LHS + // must be parenthesized. Otherwise, the parser interprets the `<` + // as the start of generic arguments on the cast type + // (e.g., `x as usize < y` is parsed as `x as usize`). + if matches!(new_lhs, sugg::Sugg::BinOp(AssocOp::Cast, ..)) && new_binop == BinOpKind::Lt { + new_lhs = new_lhs.maybe_paren(); + } let rec = sugg::make_binop(new_binop, &new_lhs, &new_rhs); diag.span_suggestion(expr.span, "change it to", rec, app); }, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 68a8f51e7f4d3..72ee5cca03970 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -515,7 +515,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co Box::new(|| Box::new(visibility::Visibility)), Box::new(|| Box::new(multiple_bound_locations::MultipleBoundLocations)), Box::new(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)), - Box::new(|| Box::new(byte_char_slices::ByteCharSlice)), Box::new(|| Box::new(cfg_not_test::CfgNotTest)), Box::new(|| Box::new(empty_line_after::EmptyLineAfter::new())), // add early passes here, used by `cargo dev new_lint` @@ -866,7 +865,8 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co Box::new(move |_| Box::new(manual_take::ManualTake::new(conf))), Box::new(|_| Box::new(manual_checked_ops::ManualCheckedOps)), Box::new(move |tcx| Box::new(manual_pop_if::ManualPopIf::new(tcx, conf))), - Box::new(|_| Box::new(manual_noop_waker::ManualNoopWaker)), + Box::new(move |_| Box::new(manual_noop_waker::ManualNoopWaker::new(conf))), + Box::new(|_| Box::new(byte_char_slices::ByteCharSlice)), // add late passes here, used by `cargo dev new_lint` ]; store.late_passes.extend(late_lints); diff --git a/clippy_lints/src/manual_is_power_of_two.rs b/clippy_lints/src/manual_is_power_of_two.rs index 2ba79113e7bc2..4501612540fb3 100644 --- a/clippy_lints/src/manual_is_power_of_two.rs +++ b/clippy_lints/src/manual_is_power_of_two.rs @@ -51,7 +51,7 @@ impl ManualIsPowerOfTwo { } let mut applicability = Applicability::MachineApplicable; - let snippet = Sugg::hir_with_applicability(cx, receiver, "_", &mut applicability); + let snippet = Sugg::hir_with_context(cx, receiver, expr.span.ctxt(), "_", &mut applicability); span_lint_and_sugg( cx, diff --git a/clippy_lints/src/manual_noop_waker.rs b/clippy_lints/src/manual_noop_waker.rs index c5de39dbf7f90..fb0e8a1d363f4 100644 --- a/clippy_lints/src/manual_noop_waker.rs +++ b/clippy_lints/src/manual_noop_waker.rs @@ -1,8 +1,10 @@ +use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::{is_empty_block, sym}; use rustc_hir::{ImplItemKind, Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; declare_clippy_lint! { /// ### What it does @@ -35,7 +37,17 @@ declare_clippy_lint! { "manual implementations of noop wakers can be simplified using Waker::noop()" } -declare_lint_pass!(ManualNoopWaker => [MANUAL_NOOP_WAKER]); +impl_lint_pass!(ManualNoopWaker => [MANUAL_NOOP_WAKER]); + +pub struct ManualNoopWaker { + msrv: Msrv, +} + +impl ManualNoopWaker { + pub fn new(conf: &'static Conf) -> Self { + Self { msrv: conf.msrv } + } +} impl<'tcx> LateLintPass<'tcx> for ManualNoopWaker { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { @@ -43,6 +55,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualNoopWaker { && let Some(trait_ref) = imp.of_trait && let Some(trait_id) = trait_ref.trait_ref.trait_def_id() && cx.tcx.is_diagnostic_item(sym::Wake, trait_id) + && self.msrv.meets(cx, msrvs::WAKER_NOOP) { for impl_item_ref in imp.items { let impl_item = cx diff --git a/clippy_lints/src/manual_rotate.rs b/clippy_lints/src/manual_rotate.rs index e8db44698d9ce..c371e5b47df82 100644 --- a/clippy_lints/src/manual_rotate.rs +++ b/clippy_lints/src/manual_rotate.rs @@ -1,7 +1,7 @@ use std::fmt::Display; use clippy_utils::consts::{ConstEvalCtxt, Constant}; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::sugg; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind}; @@ -116,17 +116,23 @@ impl LateLintPass<'_> for ManualRotate { } }; - let mut applicability = Applicability::MachineApplicable; - let expr_sugg = sugg::Sugg::hir_with_applicability(cx, l_expr, "_", &mut applicability).maybe_paren(); - let amount = sugg::Sugg::hir_with_applicability(cx, amount, "_", &mut applicability); - span_lint_and_sugg( + span_lint_and_then( cx, MANUAL_ROTATE, expr.span, "there is no need to manually implement bit rotation", - "this expression can be rewritten as", - format!("{expr_sugg}.{shift_function}({amount})"), - Applicability::MachineApplicable, + |diag| { + let mut applicability = Applicability::MachineApplicable; + let expr_sugg = sugg::Sugg::hir_with_context(cx, l_expr, expr.span.ctxt(), "_", &mut applicability) + .maybe_paren(); + let amount = sugg::Sugg::hir_with_context(cx, amount, expr.span.ctxt(), "_", &mut applicability); + diag.span_suggestion( + expr.span, + "this expression can be rewritten as", + format!("{expr_sugg}.{shift_function}({amount})"), + applicability, + ); + }, ); } } diff --git a/clippy_lints/src/manual_take.rs b/clippy_lints/src/manual_take.rs index a0c701b6c24a9..dd8b0554a9ce4 100644 --- a/clippy_lints/src/manual_take.rs +++ b/clippy_lints/src/manual_take.rs @@ -33,7 +33,7 @@ declare_clippy_lint! { /// let mut x = true; /// let _ = std::mem::take(&mut x); /// ``` - #[clippy::version = "1.94.0"] + #[clippy::version = "1.95.0"] pub MANUAL_TAKE, complexity, "manual `mem::take` implementation" diff --git a/clippy_lints/src/matches/manual_filter.rs b/clippy_lints/src/matches/manual_filter.rs index da68f8421c168..9c2d60eb9c575 100644 --- a/clippy_lints/src/matches/manual_filter.rs +++ b/clippy_lints/src/matches/manual_filter.rs @@ -1,12 +1,15 @@ use clippy_utils::as_some_expr; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; +use clippy_utils::source::snippet_with_context; +use clippy_utils::sugg::Sugg; +use clippy_utils::ty::is_copy; use clippy_utils::visitors::contains_unsafe_block; - +use rustc_errors::Applicability; use rustc_hir::LangItem::OptionNone; use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatKind}; use rustc_lint::LateContext; -use rustc_span::{SyntaxContext, sym}; +use rustc_span::{Span, SyntaxContext, sym}; use super::MANUAL_FILTER; use super::manual_utils::{SomeExpr, check_with}; @@ -21,8 +24,8 @@ fn get_cond_expr<'tcx>( expr: &'tcx Expr<'_>, ctxt: SyntaxContext, ) -> Option> { - if let Some(block_expr) = peels_blocks_incl_unsafe_opt(expr) - && let ExprKind::If(cond, then_expr, Some(else_expr)) = block_expr.kind + let block_expr = peels_blocks_incl_unsafe(expr); + if let ExprKind::If(cond, then_expr, Some(else_expr)) = block_expr.kind && let PatKind::Binding(_, target, ..) = pat.kind && (is_some_expr(cx, target, ctxt, then_expr) && is_none_expr(cx, else_expr) || is_none_expr(cx, then_expr) && is_some_expr(cx, target, ctxt, else_expr)) @@ -89,6 +92,66 @@ fn add_ampersand_if_copy(body_str: String, has_copy_trait: bool) -> String { } } +/// Checks for the following pattern: +/// `opt.and_then(|x| if /* predicate on x */ { Some(x) } else { None })` +/// and suggests replacing with: +/// `opt.filter(|&x| /* predicate on x */ )` +pub(crate) fn check_and_then_method<'tcx>( + cx: &LateContext<'tcx>, + scrutinee: &'tcx Expr<'_>, + arg: &'tcx Expr<'_>, + call_span: Span, + expr: &'tcx Expr<'_>, +) { + let ty = cx.typeck_results().expr_ty(scrutinee); + if ty.is_diag_item(cx, sym::Option) + && let ExprKind::Closure(closure) = arg.kind + && let body = cx.tcx.hir_body(closure.body) + && let Some(fn_arg_span) = closure.fn_arg_span + && let [param] = body.params + && let expr_span_ctxt = expr.span.ctxt() + && let Some(some_expr) = get_cond_expr(cx, param.pat, body.value, expr_span_ctxt) + { + span_lint_and_then( + cx, + MANUAL_FILTER, + call_span, + "manual implementation of `Option::filter`", + |diag| { + let mut applicability = Applicability::MachineApplicable; + + let mut cond_snip = + Sugg::hir_with_context(cx, some_expr.expr, expr_span_ctxt, "..", &mut applicability); + if some_expr.needs_unsafe_block { + cond_snip = cond_snip.unsafeify(); + } + if some_expr.needs_negated { + cond_snip = !cond_snip; + } + + let (prefix_snip, _) = snippet_with_context( + cx, + closure.fn_decl_span.until(fn_arg_span), + expr_span_ctxt, + "..", + &mut applicability, + ); + let (param_snip, _) = + snippet_with_context(cx, param.pat.span, expr_span_ctxt, "..", &mut applicability); + diag.span_suggestion( + call_span, + "try", + format!( + "filter({prefix_snip}|{}{param_snip}| {cond_snip})", + if is_copy(cx, ty) { "&" } else { "" } + ), + applicability, + ); + }, + ); + } +} + pub(super) fn check_match<'tcx>( cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'_>, diff --git a/clippy_lints/src/matches/manual_utils.rs b/clippy_lints/src/matches/manual_utils.rs index 6a755fac45fe3..26fd936767aa7 100644 --- a/clippy_lints/src/matches/manual_utils.rs +++ b/clippy_lints/src/matches/manual_utils.rs @@ -87,28 +87,24 @@ where None => "", }; - match can_move_expr_to_closure(cx, some_expr.expr) { - Some(captures) => { - // Check if captures the closure will need conflict with borrows made in the scrutinee. - // TODO: check all the references made in the scrutinee expression. This will require interacting - // with the borrow checker. Currently only `[.]*` is checked for. - if let Some(binding_ref_mutability) = binding_ref { - let e = peel_hir_expr_while(scrutinee, |e| match e.kind { - ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e), - _ => None, - }); - if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(l), .. })) = e.kind { - match captures.get(l) { - Some(CaptureKind::Value | CaptureKind::Use | CaptureKind::Ref(Mutability::Mut)) => return None, - Some(CaptureKind::Ref(Mutability::Not)) if binding_ref_mutability == Mutability::Mut => { - return None; - }, - Some(CaptureKind::Ref(Mutability::Not)) | None => (), - } - } + let captures = can_move_expr_to_closure(cx, some_expr.expr)?; + // Check if captures the closure will need conflict with borrows made in the scrutinee. + // TODO: check all the references made in the scrutinee expression. This will require interacting + // with the borrow checker. Currently only `[.]*` is checked for. + if let Some(binding_ref_mutability) = binding_ref { + let e = peel_hir_expr_while(scrutinee, |e| match e.kind { + ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e), + _ => None, + }); + if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(l), .. })) = e.kind { + match captures.get(l) { + Some(CaptureKind::Value | CaptureKind::Use | CaptureKind::Ref(Mutability::Mut)) => return None, + Some(CaptureKind::Ref(Mutability::Not)) if binding_ref_mutability == Mutability::Mut => { + return None; + }, + Some(CaptureKind::Ref(Mutability::Not)) | None => (), } - }, - None => return None, + } } let mut app = Applicability::MachineApplicable; diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 717c47b8aed36..0e43bba60682c 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1,6 +1,6 @@ mod collapsible_match; mod infallible_destructuring_match; -mod manual_filter; +pub(crate) mod manual_filter; mod manual_map; mod manual_ok_err; mod manual_unwrap_or; diff --git a/clippy_lints/src/methods/expect_fun_call.rs b/clippy_lints/src/methods/expect_fun_call.rs index 081f958bc8b79..89b37d507b702 100644 --- a/clippy_lints/src/methods/expect_fun_call.rs +++ b/clippy_lints/src/methods/expect_fun_call.rs @@ -75,7 +75,13 @@ fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Ex let mut arg_root = peel_blocks(arg); loop { arg_root = match &arg_root.kind { - hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr, + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => { + let expr_ty = cx.typeck_results().expr_ty(expr); + if expr_ty.is_str() { + break; + } + expr + }, hir::ExprKind::MethodCall(method_name, receiver, [], ..) => { if (method_name.ident.name == sym::as_str || method_name.ident.name == sym::as_ref) && { let arg_type = cx.typeck_results().expr_ty(receiver); diff --git a/clippy_lints/src/methods/iter_out_of_bounds.rs b/clippy_lints/src/methods/iter_out_of_bounds.rs index 6e998ccf896d4..b0e805815bc9b 100644 --- a/clippy_lints/src/methods/iter_out_of_bounds.rs +++ b/clippy_lints/src/methods/iter_out_of_bounds.rs @@ -35,13 +35,12 @@ fn get_iterator_length<'tcx>(cx: &LateContext<'tcx>, iter: &'tcx Expr<'tcx>) -> if let ty::Array(_, len) = cx.typeck_results().expr_ty(recv).peel_refs().kind() { // For slice::Iter<'_, T>, the receiver might be an array literal: [1,2,3].iter().skip(..) len.try_to_target_usize(cx.tcx).map(u128::from) - } else if let Some(args) = VecArgs::hir(cx, expr_or_init(cx, recv)) { + } else { + let args = VecArgs::hir(cx, expr_or_init(cx, recv))?; match args { VecArgs::Vec(vec) => vec.len().try_into().ok(), VecArgs::Repeat(_, len) => expr_as_u128(cx, len), } - } else { - None } }, Some(sym::IterEmpty) => Some(0), diff --git a/clippy_lints/src/methods/manual_inspect.rs b/clippy_lints/src/methods/manual_inspect.rs index 1a5b180b0c86f..a89a656a6bc7e 100644 --- a/clippy_lints/src/methods/manual_inspect.rs +++ b/clippy_lints/src/methods/manual_inspect.rs @@ -4,13 +4,13 @@ use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::{IntoSpan, SpanRangeExt}; use clippy_utils::ty::get_field_by_name; use clippy_utils::visitors::{for_each_expr, for_each_expr_without_closures}; -use clippy_utils::{ExprUseNode, expr_use_ctxt, sym}; +use clippy_utils::{ExprUseNode, get_expr_use_site, sym}; use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::{BindingMode, BorrowKind, ByRef, ClosureKind, Expr, ExprKind, Mutability, Node, PatKind}; use rustc_lint::LateContext; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; -use rustc_span::{DUMMY_SP, Span, Symbol}; +use rustc_span::{DUMMY_SP, Span, Symbol, SyntaxContext}; use super::MANUAL_INSPECT; @@ -49,7 +49,7 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: // Nested closures don't need to treat returns specially. let _: Option = for_each_expr(cx, cx.tcx.hir_body(c.body).value, |e| { if e.res_local_id() == Some(arg_id) { - let (kind, same_ctxt) = check_use(cx, e); + let (kind, same_ctxt) = check_use(cx, ctxt, e); match (kind, same_ctxt && e.span.ctxt() == ctxt) { (_, false) | (UseKind::Deref | UseKind::Return(..), true) => { requires_copy = true; @@ -67,7 +67,7 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: } else if matches!(e.kind, ExprKind::Ret(_)) { ret_count += 1; } else if e.res_local_id() == Some(arg_id) { - let (kind, same_ctxt) = check_use(cx, e); + let (kind, same_ctxt) = check_use(cx, ctxt, e); match (kind, same_ctxt && e.span.ctxt() == ctxt) { (UseKind::Return(..), false) => { return ControlFlow::Break(()); @@ -209,8 +209,8 @@ enum UseKind<'tcx> { } /// Checks how the value is used, and whether it was used in the same `SyntaxContext`. -fn check_use<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (UseKind<'tcx>, bool) { - let use_cx = expr_use_ctxt(cx, e); +fn check_use<'tcx>(cx: &LateContext<'tcx>, ctxt: SyntaxContext, e: &'tcx Expr<'_>) -> (UseKind<'tcx>, bool) { + let use_cx = get_expr_use_site(cx.tcx, cx.typeck_results(), ctxt, e); if use_cx .adjustments .first() diff --git a/clippy_lints/src/methods/manual_repeat_n.rs b/clippy_lints/src/methods/manual_repeat_n.rs index 1bb112732fa5f..6f65fc48b38af 100644 --- a/clippy_lints/src/methods/manual_repeat_n.rs +++ b/clippy_lints/src/methods/manual_repeat_n.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::{snippet, snippet_with_context}; -use clippy_utils::{expr_use_ctxt, fn_def_id, std_or_core, sym}; +use clippy_utils::{fn_def_id, get_expr_use_site, std_or_core, sym}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; @@ -21,7 +21,7 @@ pub(super) fn check<'tcx>( && let ExprKind::Call(_, [repeat_arg]) = repeat_expr.kind && let Some(def_id) = fn_def_id(cx, repeat_expr) && cx.tcx.is_diagnostic_item(sym::iter_repeat, def_id) - && !expr_use_ctxt(cx, expr).is_ty_unified + && !get_expr_use_site(cx.tcx, cx.typeck_results(), expr.span.ctxt(), expr).is_ty_unified && let Some(std_or_core) = std_or_core(cx) && msrv.meets(cx, msrvs::REPEAT_N) { diff --git a/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs b/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs index f60387fe86f71..216ca7dbc27f5 100644 --- a/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs +++ b/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs @@ -1,7 +1,7 @@ use crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::Sugg; use clippy_utils::{eager_or_lazy, higher, std_or_core, usage}; use rustc_ast::LitKind; @@ -10,12 +10,13 @@ use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; use rustc_hir::{Body, Closure, Expr, ExprKind}; use rustc_lint::LateContext; -use rustc_span::Span; +use rustc_span::{Span, SyntaxContext}; fn extract_count_with_applicability( cx: &LateContext<'_>, range: higher::Range<'_>, applicability: &mut Applicability, + ctxt: SyntaxContext, ) -> Option { let start = range.start?; let end = range.end?; @@ -40,7 +41,7 @@ fn extract_count_with_applicability( }; return Some(format!("{count}")); } - let end_snippet = Sugg::hir_with_applicability(cx, end, "...", applicability) + let end_snippet = Sugg::hir_with_context(cx, end, ctxt, "...", applicability) .maybe_paren() .into_string(); if lower_bound == 0 { @@ -74,45 +75,23 @@ pub(super) fn check( value: body_expr, } = body_hir && !usage::BindingUsageFinder::are_params_used(cx, body_hir) - && let Some(count) = extract_count_with_applicability(cx, range, &mut applicability) + && let ctxt = ex.span.ctxt() + && let Some(count) = extract_count_with_applicability(cx, range, &mut applicability, ctxt) && let Some(exec_context) = std_or_core(cx) { - let method_to_use_name; - let new_span; - let use_take; - - if eager_or_lazy::switch_to_eager_eval(cx, body_expr) { + let (method_to_use_name, new_span, use_take) = if eager_or_lazy::switch_to_eager_eval(cx, body_expr) { if msrv.meets(cx, msrvs::REPEAT_N) { - method_to_use_name = "repeat_n"; - let body_snippet = snippet_with_applicability(cx, body_expr.span, "..", &mut applicability); - new_span = (arg.span, format!("{body_snippet}, {count}")); - use_take = false; + let (body_snippet, _) = snippet_with_context(cx, body_expr.span, ctxt, "..", &mut applicability); + ("repeat_n", (arg.span, format!("{body_snippet}, {count}")), false) } else { - method_to_use_name = "repeat"; - let body_snippet = snippet_with_applicability(cx, body_expr.span, "..", &mut applicability); - new_span = (arg.span, body_snippet.to_string()); - use_take = true; + let (body_snippet, _) = snippet_with_context(cx, body_expr.span, ctxt, "..", &mut applicability); + ("repeat", (arg.span, body_snippet.to_string()), true) } } else if msrv.meets(cx, msrvs::REPEAT_WITH) { - method_to_use_name = "repeat_with"; - new_span = (param.span, String::new()); - use_take = true; + ("repeat_with", (param.span, String::new()), true) } else { return; - } - - // We need to provide nonempty parts to diag.multipart_suggestion so we - // collate all our parts here and then remove those that are empty. - let mut parts = vec![ - ( - ex.span.with_hi(method_name_span.hi()), - format!("{exec_context}::iter::{method_to_use_name}"), - ), - new_span, - ]; - if use_take { - parts.push((ex.span.shrink_to_hi(), format!(".take({count})"))); - } + }; span_lint_and_then( cx, @@ -120,6 +99,19 @@ pub(super) fn check( ex.span, "map of a closure that does not depend on its parameter over a range", |diag| { + // We need to provide nonempty parts to diag.multipart_suggestion so we + // collate all our parts here and then remove those that are empty. + let mut parts = vec![ + ( + ex.span.with_hi(method_name_span.hi()), + format!("{exec_context}::iter::{method_to_use_name}"), + ), + new_span, + ]; + if use_take { + parts.push((ex.span.shrink_to_hi(), format!(".take({count})"))); + } + diag.multipart_suggestion( if use_take { format!("remove the explicit range and use `{method_to_use_name}` and `take`") diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index b39aec6e521ce..d9774426ec25c 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -163,6 +163,8 @@ use rustc_middle::ty::TraitRef; use rustc_session::impl_lint_pass; use rustc_span::{Span, Symbol}; +use crate::matches::manual_filter; + declare_clippy_lint! { /// ### What it does /// Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))` @@ -5154,6 +5156,8 @@ impl Methods { return_and_then::check(cx, expr, recv, arg); } } + + manual_filter::check_and_then_method(cx, recv, arg, call_span, expr); }, (sym::any, [arg]) => { needless_character_iteration::check(cx, expr, recv, arg, false); diff --git a/clippy_lints/src/methods/unnecessary_fold.rs b/clippy_lints/src/methods/unnecessary_fold.rs index 0b38bdcdf5443..15337082c03a5 100644 --- a/clippy_lints/src/methods/unnecessary_fold.rs +++ b/clippy_lints/src/methods/unnecessary_fold.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath, MaybeTypeckRes}; use clippy_utils::source::snippet_with_context; -use clippy_utils::{DefinedTy, ExprUseNode, expr_use_ctxt, peel_blocks, strip_pat_refs}; +use clippy_utils::{DefinedTy, ExprUseNode, get_expr_use_site, peel_blocks, strip_pat_refs}; use rustc_ast::ast; use rustc_data_structures::packed::Pu128; use rustc_errors::{Applicability, Diag}; @@ -17,10 +17,10 @@ use super::UNNECESSARY_FOLD; /// Do we need to suggest turbofish when suggesting a replacement method? /// Changing `fold` to `sum` needs it sometimes when the return type can't be /// inferred. This checks for some common cases where it can be safely omitted -fn needs_turbofish<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) -> bool { - let use_cx = expr_use_ctxt(cx, expr); - if use_cx.same_ctxt - && let use_node = use_cx.use_node(cx) +fn needs_turbofish<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) -> bool { + let use_site = get_expr_use_site(cx.tcx, cx.typeck_results(), expr.span.ctxt(), expr); + if use_site.same_ctxt + && let use_node = use_site.use_node(cx) && let Some(ty) = use_node.defined_ty(cx) { // some common cases where turbofish isn't needed: @@ -209,7 +209,7 @@ fn check_fold_with_method( pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, - expr: &hir::Expr<'tcx>, + expr: &'tcx hir::Expr<'tcx>, init: &hir::Expr<'_>, acc: &hir::Expr<'_>, fold_span: Span, diff --git a/clippy_lints/src/misc_early/mod.rs b/clippy_lints/src/misc_early/mod.rs index 604b946d91a72..314004621ce05 100644 --- a/clippy_lints/src/misc_early/mod.rs +++ b/clippy_lints/src/misc_early/mod.rs @@ -197,7 +197,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for tuple patterns with a wildcard + /// Checks for tuple and struct patterns with a wildcard /// pattern (`_`) is next to a rest pattern (`..`). /// /// _NOTE_: While `_, ..` means there is at least one element left, `..` @@ -231,7 +231,7 @@ declare_clippy_lint! { #[clippy::version = "1.40.0"] pub UNNEEDED_WILDCARD_PATTERN, complexity, - "tuple patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)" + "tuple and struct patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)" } declare_clippy_lint! { diff --git a/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs b/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs index 0eb5c36a28a2a..897d720036ee9 100644 --- a/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs +++ b/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use rustc_ast::ast::{Pat, PatKind}; +use rustc_ast::ast::{Pat, PatFieldsRest, PatKind}; use rustc_errors::Applicability; use rustc_lint::EarlyContext; use rustc_span::Span; @@ -32,6 +32,19 @@ pub(super) fn check(cx: &EarlyContext<'_>, pat: &Pat) { right_index == 0, ); } + } else if let PatKind::Struct(_, _, patfields, rest) = &pat.kind + && let PatFieldsRest::Rest(rspan) = rest + && let Some((right_index, _right_pat)) = patfields + .iter() + .rev() + .take_while(|patfield| matches!(patfield.pat.kind, PatKind::Wild)) + .enumerate() + .last() + { + // Unlike the tuples above, structs have patfields rather than patterns, and separate out the + // `..` into a separate parameter. Also, the `..` can only be at the end of the pattern. + let singlewild = patfields.len() - right_index - 1; + span_lint(cx, patfields[singlewild].span.until(*rspan), right_index == 0); } } diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index a0ad1fe00c62f..064c1b8909fb5 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -112,7 +112,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool { { let reduce = |ret, not| { let mut applicability = Applicability::MachineApplicable; - let snip = Sugg::hir_with_applicability(cx, cond, "", &mut applicability); + let snip = Sugg::hir_with_context(cx, cond, e.span.ctxt(), "", &mut applicability); let mut snip = if not { !snip } else { snip }; if ret { diff --git a/clippy_lints/src/needless_borrows_for_generic_args.rs b/clippy_lints/src/needless_borrows_for_generic_args.rs index 1374d8ed774ba..3dbfd58cd04ec 100644 --- a/clippy_lints/src/needless_borrows_for_generic_args.rs +++ b/clippy_lints/src/needless_borrows_for_generic_args.rs @@ -4,7 +4,7 @@ use clippy_utils::mir::{PossibleBorrowerMap, enclosing_mir, expr_local, local_as use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{implements_trait, is_copy}; -use clippy_utils::{DefinedTy, ExprUseNode, expr_use_ctxt, peel_n_hir_expr_refs, sym}; +use clippy_utils::{DefinedTy, ExprUseNode, get_expr_use_site, peel_n_hir_expr_refs, sym}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId}; @@ -17,6 +17,7 @@ use rustc_middle::ty::{ self, ClauseKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, ParamTy, ProjectionPredicate, Ty, }; use rustc_session::impl_lint_pass; +use rustc_span::SyntaxContext; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use rustc_trait_selection::traits::{Obligation, ObligationCause}; use std::collections::VecDeque; @@ -82,10 +83,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowsForGenericArgs<'tcx> { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if matches!(expr.kind, ExprKind::AddrOf(..)) && !expr.span.from_expansion() - && let use_cx = expr_use_ctxt(cx, expr) - && use_cx.same_ctxt - && !use_cx.is_ty_unified - && let use_node = use_cx.use_node(cx) + && let use_site = get_expr_use_site(cx.tcx, cx.typeck_results(), SyntaxContext::root(), expr) + && use_site.same_ctxt + && !use_site.is_ty_unified + && let use_node = use_site.use_node(cx) && let Some(DefinedTy::Mir { def_site_def_id: _, ty }) = use_node.defined_ty(cx) && let ty::Param(param_ty) = *ty.skip_binder().kind() && let Some((hir_id, fn_id, i)) = match use_node { diff --git a/clippy_lints/src/operators/bit_mask.rs b/clippy_lints/src/operators/bit_mask.rs index 7f6dea573de83..104f786ead165 100644 --- a/clippy_lints/src/operators/bit_mask.rs +++ b/clippy_lints/src/operators/bit_mask.rs @@ -36,12 +36,19 @@ fn invert_cmp(cmp: BinOpKind) -> BinOpKind { } } -fn check_compare<'a>(cx: &LateContext<'a>, bit_op: &Expr<'a>, cmp_op: BinOpKind, cmp_value: u128, span: Span) { +fn check_compare<'a>(cx: &LateContext<'a>, bit_op: &Expr<'a>, cmp_op: BinOpKind, mut cmp_value: u128, span: Span) { if let ExprKind::Binary(op, left, right) = &bit_op.kind { if op.node != BinOpKind::BitAnd && op.node != BinOpKind::BitOr || is_from_proc_macro(cx, bit_op) { return; } if let Some(mask) = fetch_int_literal(cx, right).or_else(|| fetch_int_literal(cx, left)) { + let ty = cx.typeck_results().expr_ty(bit_op); + if !ty.is_ptr_sized_integral() + && let bits = ty.primitive_size(cx.tcx) + { + // Strip high bits that don't fit into the result type as they won't be used in the comparison + cmp_value &= bits.unsigned_int_max(); + } check_bit_mask(cx, op.node, cmp_op, mask, cmp_value, span); } } diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index ce50e6e35dcc5..b18957aaff44e 100644 --- a/clippy_lints/src/operators/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -1,7 +1,7 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant, FullInt, integer_const, is_zero_integer_const}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{ExprUseNode, clip, expr_use_ctxt, peel_hir_expr_refs, unsext}; +use clippy_utils::{ExprUseNode, clip, get_expr_use_site, peel_hir_expr_refs, unsext}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{BinOpKind, Expr, ExprKind, Node, Path, QPath}; @@ -250,7 +250,7 @@ fn span_ineffective_operation( } fn is_expr_used_with_type_annotation<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { - match expr_use_ctxt(cx, expr).use_node(cx) { + match get_expr_use_site(cx.tcx, cx.typeck_results(), expr.span.ctxt(), expr).use_node(cx) { ExprUseNode::LetStmt(letstmt) => letstmt.ty.is_some(), ExprUseNode::Return(_) => true, _ => false, diff --git a/clippy_lints/src/operators/manual_div_ceil.rs b/clippy_lints/src/operators/manual_div_ceil.rs index a0f61f6d36c4e..5a4823ddfcf63 100644 --- a/clippy_lints/src/operators/manual_div_ceil.rs +++ b/clippy_lints/src/operators/manual_div_ceil.rs @@ -148,7 +148,8 @@ fn build_suggestion( rhs: &Expr<'_>, applicability: &mut Applicability, ) { - let dividend_sugg = Sugg::hir_with_applicability(cx, lhs, "..", applicability).maybe_paren(); + let ctxt = expr.span.ctxt(); + let dividend_sugg = Sugg::hir_with_context(cx, lhs, ctxt, "..", applicability).maybe_paren(); let rhs_ty = cx.typeck_results().expr_ty(rhs); let type_suffix = if cx.typeck_results().expr_ty(lhs).is_numeric() && matches!( @@ -186,7 +187,7 @@ fn build_suggestion( }; // Dereference the RHS if it is a reference type - let divisor_snippet = match Sugg::hir_with_context(cx, rhs, expr.span.ctxt(), "_", applicability) { + let divisor_snippet = match Sugg::hir_with_context(cx, rhs, ctxt, "_", applicability) { sugg if rhs_ty.is_ref() => sugg.deref(), sugg => sugg, }; diff --git a/clippy_lints/src/operators/needless_bitwise_bool.rs b/clippy_lints/src/operators/needless_bitwise_bool.rs index 9d8e833ef6d75..cac3169bd2899 100644 --- a/clippy_lints/src/operators/needless_bitwise_bool.rs +++ b/clippy_lints/src/operators/needless_bitwise_bool.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::SpanRangeExt; +use clippy_utils::source::snippet_with_context; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; @@ -24,12 +24,12 @@ pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, op: BinOpKind, lhs: &Exp e.span, "use of bitwise operator instead of lazy operator between booleans", |diag| { - if let Some(lhs_snip) = lhs.span.get_source_text(cx) - && let Some(rhs_snip) = rhs.span.get_source_text(cx) - { - let sugg = format!("{lhs_snip} {op_str} {rhs_snip}"); - diag.span_suggestion(e.span, "try", sugg, Applicability::MachineApplicable); - } + let mut applicability = Applicability::MachineApplicable; + let expr_span_ctxt = e.span.ctxt(); + let (lhs_snip, _) = snippet_with_context(cx, lhs.span, expr_span_ctxt, "..", &mut applicability); + let (rhs_snip, _) = snippet_with_context(cx, rhs.span, expr_span_ctxt, "..", &mut applicability); + + diag.span_suggestion(e.span, "try", format!("{lhs_snip} {op_str} {rhs_snip}"), applicability); }, ); } diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index dfd7834a149b1..4bd6b1696b354 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -2,10 +2,10 @@ use crate::manual_let_else::MANUAL_LET_ELSE; use crate::question_mark_used::QUESTION_MARK_USED; use clippy_config::Conf; use clippy_config::types::MatchLintBehaviour; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; -use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; +use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{implements_trait, is_copy}; use clippy_utils::usage::local_used_after_expr; @@ -24,6 +24,7 @@ use rustc_hir::{ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; use rustc_session::impl_lint_pass; +use rustc_span::Span; use rustc_span::symbol::Symbol; declare_clippy_lint! { @@ -222,13 +223,11 @@ fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_ // We only need to check `if let Some(x) = option` not `if let None = option`, // because the later one will be suggested as `if option.is_none()` thus causing conflict. res.ctor_parent(cx).is_lang_item(cx, OptionSome) - && if_else.is_some() - && expr_return_none_or_err(smbl, cx, if_else.unwrap(), let_expr, None) + && matches!(if_else, Some(inner) if expr_return_none_or_err(smbl, cx, inner, let_expr, None)) }, sym::Result => { (res.ctor_parent(cx).is_lang_item(cx, ResultOk) - && if_else.is_some() - && expr_return_none_or_err(smbl, cx, if_else.unwrap(), let_expr, Some(let_pat_sym))) + && matches!(if_else, Some(inner) if expr_return_none_or_err(smbl, cx, inner, let_expr, Some(let_pat_sym)))) || res.ctor_parent(cx).is_lang_item(cx, ResultErr) && expr_return_none_or_err(smbl, cx, if_then, let_expr, Some(let_pat_sym)) && if_else.is_none() @@ -328,7 +327,7 @@ enum TryMode { } fn find_try_mode<'tcx>(cx: &LateContext<'tcx>, scrutinee: &Expr<'tcx>) -> Option { - let scrutinee_ty = cx.typeck_results().expr_ty_adjusted(scrutinee); + let scrutinee_ty = cx.typeck_results().expr_ty_adjusted(scrutinee).peel_refs(); let ty::Adt(scrutinee_adt_def, _) = scrutinee_ty.kind() else { return None; }; @@ -360,14 +359,18 @@ fn extract_ctor_call<'a, 'tcx>( // Extracts the local ID of a plain `val` pattern. fn extract_binding_pat(pat: &Pat<'_>) -> Option { - if let PatKind::Binding(BindingMode::NONE, binding, _, None) = pat.kind { + if let PatKind::Binding(_, binding, _, None) = pat.kind { Some(binding) } else { None } } -fn check_arm_is_some_or_ok<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm: &Arm<'tcx>) -> bool { +fn check_arm_is_some_or_ok<'tcx>( + cx: &LateContext<'tcx>, + mode: TryMode, + arm: &Arm<'tcx>, +) -> Option> { let happy_ctor = match mode { TryMode::Result => ResultOk, TryMode::Option => OptionSome, @@ -378,13 +381,16 @@ fn check_arm_is_some_or_ok<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm: &Ar && let Some(val_binding) = extract_ctor_call(cx, happy_ctor, arm.pat) // Extract out `val` && let Some(binding) = extract_binding_pat(val_binding) - // Check body is just `=> val` - && peel_blocks(arm.body).res_local_id() == Some(binding) { - true - } else { - false + // Check body is just `=> val` + return Some(if peel_blocks(arm.body).res_local_id() == Some(binding) { + IfLetOrMatchThen::DirectReturn + } else { + IfLetOrMatchThen::ManualUnwrap(val_binding.span, arm.body) + }); } + + None } fn check_arm_is_none_or_err<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm: &Arm<'tcx>) -> bool { @@ -439,9 +445,23 @@ fn is_local_or_local_into(cx: &LateContext<'_>, expr: &Expr<'_>, val: HirId) -> } } -fn check_arms_are_try<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm1: &Arm<'tcx>, arm2: &Arm<'tcx>) -> bool { - (check_arm_is_some_or_ok(cx, mode, arm1) && check_arm_is_none_or_err(cx, mode, arm2)) - || (check_arm_is_some_or_ok(cx, mode, arm2) && check_arm_is_none_or_err(cx, mode, arm1)) +fn check_arms_are_try<'tcx>( + cx: &LateContext<'tcx>, + mode: TryMode, + arm1: &Arm<'tcx>, + arm2: &Arm<'tcx>, +) -> Option> { + (check_arm_is_none_or_err(cx, mode, arm2).then(|| check_arm_is_some_or_ok(cx, mode, arm1))) + .or_else(|| check_arm_is_none_or_err(cx, mode, arm1).then(|| check_arm_is_some_or_ok(cx, mode, arm2))) + .flatten() +} + +#[derive(Debug)] +enum IfLetOrMatchThen<'tcx> { + /// Return the binding from an if let or match arm as is. + DirectReturn, + /// Working on the binding from an if let or match arm as if it comes from a `?`. + ManualUnwrap(Span, &'tcx Expr<'tcx>), } fn check_if_try_match<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { @@ -449,19 +469,47 @@ fn check_if_try_match<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { && !expr.span.from_expansion() && let Some(mode) = find_try_mode(cx, scrutinee) && !span_contains_cfg(cx, expr.span) - && check_arms_are_try(cx, mode, arm1, arm2) + && let Some(if_let_or_match_then) = check_arms_are_try(cx, mode, arm1, arm2) { - let mut applicability = Applicability::MachineApplicable; - let snippet = snippet_with_applicability(cx, scrutinee.span.source_callsite(), "..", &mut applicability); - - span_lint_and_sugg( + span_lint_and_then( cx, QUESTION_MARK, expr.span, "this `match` expression can be replaced with `?`", - "try instead", - snippet.into_owned() + "?", - applicability, + |diag| { + let mut applicability = Applicability::MachineApplicable; + let scrutinee_snippet = + snippet_with_applicability(cx, scrutinee.span.source_callsite(), "..", &mut applicability); + match if_let_or_match_then { + IfLetOrMatchThen::DirectReturn => { + diag.span_suggestion( + expr.span, + "try instead", + scrutinee_snippet.into_owned() + "?", + applicability, + ); + }, + IfLetOrMatchThen::ManualUnwrap(binding_span, arm_body) => { + let indent = indent_of(cx, expr.span).unwrap_or_default(); + let arm_body_snippet = snippet_with_applicability(cx, arm_body.span, "..", &mut applicability); + let mut sugg = reindent_multiline(&arm_body_snippet, true, Some(indent)); + let binding_snippet = snippet_with_applicability(cx, binding_span, "..", &mut applicability); + let inner_indent = " ".repeat(indent + 4); + if matches!(arm_body.kind, ExprKind::Block(..)) { + sugg.insert_str( + 1, + &format!("\n{inner_indent}let {binding_snippet} = {scrutinee_snippet}?;"), + ); + } else { + let outer_indent = " ".repeat(indent); + sugg = format!( + "{{\n{inner_indent}let {binding_snippet} = {scrutinee_snippet}?;\n{inner_indent}{sugg}\n{outer_indent}}}" + ); + } + diag.span_suggestion(expr.span, "try instead", sugg, applicability); + }, + } + }, ); } } @@ -486,8 +534,8 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: if_then, if_else, ) - && ((is_early_return(sym::Option, cx, &if_block) && peel_blocks(if_then).res_local_id() == Some(bind_id)) - || is_early_return(sym::Result, cx, &if_block)) + && let is_option_early_return = is_early_return(sym::Option, cx, &if_block) + && (is_option_early_return || is_early_return(sym::Result, cx, &if_block)) && if_else .map(|e| eq_expr_value(cx, let_expr, peel_blocks(e))) .is_none_or(|e| !e) @@ -499,32 +547,53 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: return; } - let mut applicability = Applicability::MachineApplicable; - let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability); - let parent = cx.tcx.parent_hir_node(expr.hir_id); - let requires_semi = matches!(parent, Node::Stmt(_)) || cx.typeck_results().expr_ty(expr).is_unit(); - let method_call_str = match by_ref { - ByRef::Yes(_, Mutability::Mut) => ".as_mut()", - ByRef::Yes(_, Mutability::Not) => ".as_ref()", - ByRef::No => "", - }; - - let mut sugg = format!( - "{receiver_str}{method_call_str}?{}", - if requires_semi { ";" } else { "" } - ); - if is_else_clause(cx.tcx, expr) || (requires_semi && !matches!(parent, Node::Stmt(_) | Node::Block(_))) { - sugg = format!("{{ {sugg} }}"); + // Leave `if let Some(x) = opt { .. } else { None }` to `needless_match` or `manual_map_option`. + if is_option_early_return + && if_else.is_some_and(|else_| !matches!(peel_blocks_with_stmt(else_).kind, ExprKind::Ret(_))) + { + return; } - span_lint_and_sugg( + span_lint_and_then( cx, QUESTION_MARK, expr.span, "this block may be rewritten with the `?` operator", - "replace it with", - sugg, - applicability, + |diag| { + let mut applicability = Applicability::MachineApplicable; + let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability); + if !is_option_early_return || peel_blocks(if_then).res_local_id() == Some(bind_id) { + let parent = cx.tcx.parent_hir_node(expr.hir_id); + let requires_semi = matches!(parent, Node::Stmt(_)) || cx.typeck_results().expr_ty(expr).is_unit(); + let method_call_str = match by_ref { + ByRef::Yes(_, Mutability::Mut) => ".as_mut()", + ByRef::Yes(_, Mutability::Not) => ".as_ref()", + ByRef::No => "", + }; + + let mut sugg = format!( + "{receiver_str}{method_call_str}?{}", + if requires_semi { ";" } else { "" } + ); + if is_else_clause(cx.tcx, expr) + || (requires_semi && !matches!(parent, Node::Stmt(_) | Node::Block(_))) + { + sugg = format!("{{ {sugg} }}"); + } + + diag.span_suggestion(expr.span, "replace it with", sugg, applicability); + return; + } + + let mut sugg = snippet_with_applicability(cx, if_then.span, "..", &mut applicability).into_owned(); + let binding_snippet = snippet_with_applicability(cx, field.span, "..", &mut applicability); + let indent = indent_of(cx, expr.span).unwrap_or_default(); + sugg.insert_str( + 1, + &format!("\n{}let {binding_snippet} = {receiver_str}?;", " ".repeat(indent + 4)), + ); + diag.span_suggestion(expr.span, "replace it with", sugg, applicability); + }, ); } } diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index 39019c646bd5a..2f79ca18e8136 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -6,7 +6,7 @@ use clippy_utils::res::MaybeResPath; use clippy_utils::source::{SpanRangeExt, snippet, snippet_with_applicability}; use clippy_utils::sugg::Sugg; 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, sym}; +use clippy_utils::{fn_def_id, get_expr_use_site, get_parent_expr, higher, is_in_const_context, is_integer_const, sym}; use rustc_ast::Mutability; use rustc_ast::ast::RangeLimits; use rustc_errors::Applicability; @@ -14,7 +14,7 @@ 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::{DesugaringKind, Span, Spanned}; +use rustc_span::{DesugaringKind, Span, Spanned, SyntaxContext}; use std::cmp::Ordering; declare_clippy_lint! { @@ -355,18 +355,19 @@ fn check_range_bounds<'a>(cx: &'a LateContext<'_>, ex: &'a Expr<'_>) -> Option( cx: &LateContext<'tcx>, + ctxt: SyntaxContext, 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 { + let use_site = get_expr_use_site(cx.tcx, cx.typeck_results(), ctxt, expr); + let (Node::Expr(parent_expr), false) = (use_site.node, use_site.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 + && arg.hir_id == use_site.child_id && let ExprKind::Path(qpath) = func.kind && cx.tcx.qpath_is_lang_item(qpath, LangItem::IntoIterIntoIter) && parent_expr.span.is_desugaring(DesugaringKind::ForLoop) @@ -377,7 +378,7 @@ fn can_switch_ranges<'tcx>( // 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 + && receiver.hir_id == use_site.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_assoc(method_did) && matches!( @@ -392,7 +393,7 @@ fn can_switch_ranges<'tcx>( // 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 Some(arg_idx) = args.iter().position(|e| e.hir_id == use_site.child_id) { let input_idx = if matches!(parent_expr.kind, ExprKind::MethodCall(..)) { arg_idx + 1 @@ -512,16 +513,16 @@ fn check_range_switch<'tcx>( && span.can_be_used_for_suggestions() && limits == kind && let Some(y) = predicate(cx, end) - && can_switch_ranges(cx, expr, kind, cx.typeck_results().expr_ty(y)) + && can_switch_ranges(cx, span.ctxt(), expr, kind, cx.typeck_results().expr_ty(y)) { 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) + Sugg::hir_with_context(cx, x, span.ctxt(), "", &mut app) .maybe_paren() .to_string() }); - let end = Sugg::hir_with_applicability(cx, y, "", &mut app).maybe_paren(); + let end = Sugg::hir_with_context(cx, y, span.ctxt(), "", &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); diff --git a/clippy_lints/src/returns/let_and_return.rs b/clippy_lints/src/returns/let_and_return.rs index b19935959c4d8..57f33632a5bce 100644 --- a/clippy_lints/src/returns/let_and_return.rs +++ b/clippy_lints/src/returns/let_and_return.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::res::MaybeResPath; -use clippy_utils::source::SpanRangeExt; +use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::visitors::for_each_expr; use clippy_utils::{binary_expr_needs_parentheses, fn_def_id, span_contains_non_whitespace}; @@ -19,6 +19,7 @@ pub(super) fn check_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'_>) && let Some(stmt) = block.stmts.last() && let StmtKind::Let(local) = &stmt.kind && local.ty.is_none() + && local.els.is_none() && cx.tcx.hir_attrs(local.hir_id).is_empty() && let Some(initexpr) = &local.init && let PatKind::Binding(_, local_id, _, _) = local.pat.kind @@ -38,30 +39,29 @@ pub(super) fn check_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'_>) |err| { err.span_label(local.span, "unnecessary `let` binding"); - if let Some(src) = initexpr.span.get_source_text(cx) { - let sugg = if binary_expr_needs_parentheses(initexpr) { - if has_enclosing_paren(&src) { - src.to_owned() - } else { - format!("({src})") - } - } else if !cx.typeck_results().expr_adjustments(retexpr).is_empty() { - if has_enclosing_paren(&src) { - format!("{src} as _") - } else { - format!("({src}) as _") - } + let mut app = Applicability::MachineApplicable; + let (src, _) = snippet_with_context(cx, initexpr.span, local.span.ctxt(), "..", &mut app); + + let sugg = if binary_expr_needs_parentheses(initexpr) { + if has_enclosing_paren(&src) { + src.to_string() + } else { + format!("({src})") + } + } else if !cx.typeck_results().expr_adjustments(retexpr).is_empty() { + if has_enclosing_paren(&src) { + format!("{src} as _") } else { - src.to_owned() - }; - err.multipart_suggestion( - "return the expression directly", - vec![(local.span, String::new()), (retexpr.span, sugg)], - Applicability::MachineApplicable, - ); + format!("({src}) as _") + } } else { - err.span_help(initexpr.span, "this expression can be directly returned"); - } + src.to_string() + }; + err.multipart_suggestion( + "return the expression directly", + vec![(local.span, String::new()), (retexpr.span, sugg)], + app, + ); }, ); } diff --git a/clippy_lints/src/returns/needless_return.rs b/clippy_lints/src/returns/needless_return.rs index 04e4f379e37c1..619a70cd8dd10 100644 --- a/clippy_lints/src/returns/needless_return.rs +++ b/clippy_lints/src/returns/needless_return.rs @@ -259,7 +259,7 @@ fn emit_return_lint( // Go backwards while encountering whitespace and extend the given Span to that point. fn extend_span_to_previous_non_ws(cx: &LateContext<'_>, sp: Span) -> Span { if let Ok(prev_source) = cx.sess().source_map().span_to_prev_source(sp) { - let ws = [b' ', b'\t', b'\n']; + let ws = *b" \t\n"; if let Some(non_ws_pos) = prev_source.bytes().rposition(|c| !ws.contains(&c)) { let len = prev_source.len() - non_ws_pos - 1; return sp.with_lo(sp.lo() - BytePos::from_usize(len)); diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index 01482b2475f7c..262612a2a2d3c 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -112,7 +112,7 @@ fn generate_swap_warning<'tcx>( || ty.is_diag_item(cx, sym::Vec) || ty.is_diag_item(cx, sym::VecDeque) { - let slice = Sugg::hir_with_applicability(cx, lhs1, "", &mut applicability); + let slice = Sugg::hir_with_context(cx, lhs1, ctxt, "", &mut applicability); span_lint_and_sugg( cx, diff --git a/clippy_lints/src/unsafe_removed_from_name.rs b/clippy_lints/src/unsafe_removed_from_name.rs index 90fac7bc0da02..d92e98d04de84 100644 --- a/clippy_lints/src/unsafe_removed_from_name.rs +++ b/clippy_lints/src/unsafe_removed_from_name.rs @@ -40,6 +40,9 @@ impl EarlyLintPass for UnsafeNameRemoval { fn check_use_tree(use_tree: &UseTree, cx: &EarlyContext<'_>, span: Span) { match use_tree.kind { UseTreeKind::Simple(Some(new_name)) => { + if new_name.as_str() == "_" { + return; + } let old_name = use_tree .prefix .segments diff --git a/clippy_lints/src/unused_async.rs b/clippy_lints/src/unused_async.rs index a4ebd8860dc04..f5e903426d578 100644 --- a/clippy_lints/src/unused_async.rs +++ b/clippy_lints/src/unused_async.rs @@ -211,7 +211,6 @@ fn async_fn_contains_todo_unimplemented_macro(cx: &LateContext<'_>, body: &Body< && 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 { diff --git a/clippy_lints/src/unwrap.rs b/clippy_lints/src/unwrap.rs index cfd3f420db5a9..d87289e1362dd 100644 --- a/clippy_lints/src/unwrap.rs +++ b/clippy_lints/src/unwrap.rs @@ -308,18 +308,15 @@ fn extract_local(cx: &LateContext<'_>, mut expr: &Expr<'_>) -> Option { field_indices.push(field_idx); expr = recv; } - if let Some(local_id) = expr.res_local_id() { - if field_indices.is_empty() { - Some(Local::Pure { local_id }) - } else { - Some(Local::WithFieldAccess { - local_id, - field_indices, - span, - }) - } + let local_id = expr.res_local_id()?; + if field_indices.is_empty() { + Some(Local::Pure { local_id }) } else { - None + Some(Local::WithFieldAccess { + local_id, + field_indices, + span, + }) } } diff --git a/clippy_lints/src/write/empty_string.rs b/clippy_lints/src/write/empty_string.rs index fa2ad4ba94a20..31869e0d2cda9 100644 --- a/clippy_lints/src/write/empty_string.rs +++ b/clippy_lints/src/write/empty_string.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::MacroCall; -use clippy_utils::source::expand_past_previous_comma; +use clippy_utils::source::{expand_past_previous_comma, snippet_opt}; use clippy_utils::{span_extract_comments, sym}; use rustc_ast::{FormatArgs, FormatArgsPiece}; use rustc_errors::Applicability; @@ -23,17 +23,23 @@ pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: format!("empty string literal in `{name}!`"), |diag| { if span_extract_comments(cx, macro_call.span).is_empty() { - let closing_paren = cx.sess().source_map().span_extend_to_prev_char_before( - macro_call.span.shrink_to_hi(), - ')', - false, - ); - let mut span = format_args.span.with_hi(closing_paren.lo()); - if is_writeln { - span = expand_past_previous_comma(cx, span); - } + let closing_delim = snippet_opt(cx, macro_call.span) + .and_then(|snippet| snippet.chars().last()) + .filter(|ch| matches!(ch, ')' | ']' | '}')); + + if let Some(closing_delim) = closing_delim { + let closing_paren = cx.sess().source_map().span_extend_to_prev_char_before( + macro_call.span.shrink_to_hi(), + closing_delim, + false, + ); + let mut span = format_args.span.with_hi(closing_paren.lo()); + if is_writeln { + span = expand_past_previous_comma(cx, span); + } - diag.span_suggestion(span, "remove the empty string", "", Applicability::MachineApplicable); + diag.span_suggestion(span, "remove the empty string", "", Applicability::MachineApplicable); + } } else { // If there is a comment in the span of macro call, we don't provide an auto-fix suggestion. diag.span_note(format_args.span, "remove the empty string"); diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml index 9800a75035fa3..3d87129e8953c 100644 --- a/clippy_utils/Cargo.toml +++ b/clippy_utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_utils" -version = "0.1.96" +version = "0.1.97" edition = "2024" description = "Helpful tools for writing lints, provided as they are used in Clippy" repository = "https://github.com/rust-lang/rust-clippy" diff --git a/clippy_utils/README.md b/clippy_utils/README.md index 683ca090e92a1..99489cb11e734 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-2026-04-02 +nightly-2026-04-16 ``` diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 396b63870dea3..a1860adb4407e 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1,6 +1,5 @@ #![feature(box_patterns)] #![feature(macro_metavar_expr)] -#![feature(never_type)] #![feature(rustc_private)] #![feature(unwrap_infallible)] #![recursion_limit = "512"] @@ -94,11 +93,12 @@ use rustc_hir::definitions::{DefPath, DefPathData}; use rustc_hir::hir_id::{HirIdMap, HirIdSet}; use rustc_hir::intravisit::{Visitor, walk_expr}; use rustc_hir::{ - self as hir, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstArgKind, CoroutineDesugaring, - CoroutineKind, CoroutineSource, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs, - HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, - OwnerNode, Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem, - TraitItemKind, TraitRef, TyKind, UnOp, def, find_attr, + self as hir, AnonConst, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, CRATE_HIR_ID, Closure, ConstArg, + ConstArgKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Destination, Expr, ExprField, ExprKind, + FieldDef, FnDecl, FnRetTy, GenericArg, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, LangItem, + LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode, Param, Pat, PatExpr, PatExprKind, PatKind, Path, + PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp, Variant, def, + find_attr, }; use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize}; use rustc_lint::{LateContext, Level, Lint, LintContext}; @@ -110,12 +110,12 @@ use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, DerefAdjustKi use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::{ self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, GenericArgKind, GenericArgsRef, IntTy, Ty, TyCtxt, - TypeFlags, TypeVisitableExt, UintTy, UpvarCapture, + TypeFlags, TypeVisitableExt, TypeckResults, UintTy, UpvarCapture, }; use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::source_map::SourceMap; use rustc_span::symbol::{Ident, Symbol, kw}; -use rustc_span::{InnerSpan, Span}; +use rustc_span::{InnerSpan, Span, SyntaxContext}; use source::{SpanRangeExt, walk_span_to_context}; use visitors::{Visitable, for_each_unconsumed_temporary}; @@ -840,11 +840,10 @@ pub fn capture_local_usage(cx: &LateContext<'_>, e: &Expr<'_>) -> CaptureKind { ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(_), .. })) )); - let mut child_id = e.hir_id; let mut capture = CaptureKind::Value; let mut capture_expr_ty = e; - for (parent_id, parent) in cx.tcx.hir_parent_iter(e.hir_id) { + for (parent, child_id) in hir_parent_with_src_iter(cx.tcx, e.hir_id) { if let [ Adjustment { kind: Adjust::Deref(_) | Adjust::Borrow(AutoBorrow::Ref(..)), @@ -900,8 +899,6 @@ pub fn capture_local_usage(cx: &LateContext<'_>, e: &Expr<'_>) -> CaptureKind { }, _ => break, } - - child_id = parent_id; } if capture == CaptureKind::Value && is_copy(cx, cx.typeck_results().expr_ty(capture_expr_ty)) { @@ -1297,38 +1294,28 @@ pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { /// Checks if the given expression is a part of `let else` /// returns `true` for both the `init` and the `else` part pub fn is_inside_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { - let mut child_id = expr.hir_id; - for (parent_id, node) in tcx.hir_parent_iter(child_id) { - if let Node::LetStmt(LetStmt { - init: Some(init), - els: Some(els), - .. - }) = node - && (init.hir_id == child_id || els.hir_id == child_id) - { - return true; - } - - child_id = parent_id; - } - - false + hir_parent_with_src_iter(tcx, expr.hir_id).any(|(node, child_id)| { + matches!( + node, + Node::LetStmt(LetStmt { + init: Some(init), + els: Some(els), + .. + }) + if init.hir_id == child_id || els.hir_id == child_id + ) + }) } /// Checks if the given expression is the else clause of a `let else` expression pub fn is_else_clause_in_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { - let mut child_id = expr.hir_id; - for (parent_id, node) in tcx.hir_parent_iter(child_id) { - if let Node::LetStmt(LetStmt { els: Some(els), .. }) = node - && els.hir_id == child_id - { - return true; - } - - child_id = parent_id; - } - - false + hir_parent_with_src_iter(tcx, expr.hir_id).any(|(node, child_id)| { + matches!( + node, + Node::LetStmt(LetStmt { els: Some(els), .. }) + if els.hir_id == child_id + ) + }) } /// Checks whether the given `Expr` is a range equivalent to a `RangeFull`. @@ -2048,22 +2035,20 @@ pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool /// Gets the node where an expression is either used, or it's type is unified with another branch. /// Returns both the node and the `HirId` of the closest child node. pub fn get_expr_use_or_unification_node<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<(Node<'tcx>, HirId)> { - let mut child_id = expr.hir_id; - let mut iter = tcx.hir_parent_iter(child_id); - loop { - match iter.next() { - None => break None, - Some((id, Node::Block(_))) => child_id = id, - Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id, - Some((_, Node::Expr(expr))) => match expr.kind { - ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => child_id = expr.hir_id, - ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = expr.hir_id, - ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => break None, - _ => break Some((Node::Expr(expr), child_id)), + for (node, child_id) in hir_parent_with_src_iter(tcx, expr.hir_id) { + match node { + Node::Block(_) => {}, + Node::Arm(arm) if arm.body.hir_id == child_id => {}, + Node::Expr(expr) => match expr.kind { + ExprKind::Block(..) | ExprKind::DropTemps(_) => {}, + ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => {}, + ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => return None, + _ => return Some((Node::Expr(expr), child_id)), }, - Some((_, node)) => break Some((node, child_id)), + node => return Some((node, child_id)), } } + None } /// Checks if the result of an expression is used, or it's type is unified with another branch. @@ -2507,60 +2492,12 @@ pub fn is_in_test(tcx: TyCtxt<'_>, hir_id: HirId) -> bool { pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { find_attr!(tcx, def_id, CfgTrace(..)) || find_attr!( - tcx.hir_parent_iter(tcx.local_def_id_to_hir_id(def_id)) - .flat_map(|(parent_id, _)| tcx.hir_attrs(parent_id)), + tcx.hir_parent_id_iter(tcx.local_def_id_to_hir_id(def_id)) + .flat_map(|parent_id| tcx.hir_attrs(parent_id)), CfgTrace(..) ) } -/// Walks up the HIR tree from the given expression in an attempt to find where the value is -/// consumed. -/// -/// Termination has three conditions: -/// - The given function returns `Break`. This function will return the value. -/// - The consuming node is found. This function will return `Continue(use_node, child_id)`. -/// - No further parent nodes are found. This will trigger a debug assert or return `None`. -/// -/// This allows walking through `if`, `match`, `break`, and block expressions to find where the -/// value produced by the expression is consumed. -pub fn walk_to_expr_usage<'tcx, T>( - cx: &LateContext<'tcx>, - e: &Expr<'tcx>, - mut f: impl FnMut(HirId, Node<'tcx>, HirId) -> ControlFlow, -) -> Option, HirId)>> { - let mut iter = cx.tcx.hir_parent_iter(e.hir_id); - let mut child_id = e.hir_id; - - while let Some((parent_id, parent)) = iter.next() { - if let ControlFlow::Break(x) = f(parent_id, parent, child_id) { - return Some(ControlFlow::Break(x)); - } - let parent_expr = match parent { - Node::Expr(e) => e, - Node::Block(Block { expr: Some(body), .. }) | Node::Arm(Arm { body, .. }) if body.hir_id == child_id => { - child_id = parent_id; - continue; - }, - Node::Arm(a) if a.body.hir_id == child_id => { - child_id = parent_id; - continue; - }, - _ => return Some(ControlFlow::Continue((parent, child_id))), - }; - match parent_expr.kind { - ExprKind::If(child, ..) | ExprKind::Match(child, ..) if child.hir_id != child_id => child_id = parent_id, - ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => { - child_id = id; - iter = cx.tcx.hir_parent_iter(id); - }, - ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = parent_id, - _ => return Some(ControlFlow::Continue((parent, child_id))), - } - } - debug_assert!(false, "no parent node found for `{child_id:?}`"); - None -} - /// A type definition as it would be viewed from within a function. #[derive(Clone, Copy)] pub enum DefinedTy<'tcx> { @@ -2579,11 +2516,11 @@ pub enum DefinedTy<'tcx> { }, } -/// The context an expressions value is used in. -pub struct ExprUseCtxt<'tcx> { +/// The location that recives the value of an expression. +pub struct ExprUseSite<'tcx> { /// The parent node which consumes the value. pub node: Node<'tcx>, - /// The child id of the node the value came from. + /// The ID of the immediate child of the use node. pub child_id: HirId, /// Any adjustments applied to the type. pub adjustments: &'tcx [Adjustment<'tcx>], @@ -2594,7 +2531,7 @@ pub struct ExprUseCtxt<'tcx> { /// Whether the use site has the same `SyntaxContext` as the value. pub same_ctxt: bool, } -impl<'tcx> ExprUseCtxt<'tcx> { +impl<'tcx> ExprUseSite<'tcx> { pub fn use_node(&self, cx: &LateContext<'tcx>) -> ExprUseNode<'tcx> { match self.node { Node::LetStmt(l) => ExprUseNode::LetStmt(l), @@ -2760,54 +2697,178 @@ impl<'tcx> ExprUseNode<'tcx> { } } -/// Gets the context an expression's value is used in. -pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'tcx>) -> ExprUseCtxt<'tcx> { - let mut adjustments = [].as_slice(); +struct ReplacingFilterMap(I, F); +impl Iterator for ReplacingFilterMap +where + I: Iterator, + F: FnMut(&mut I, I::Item) -> Option, +{ + type Item = U; + fn next(&mut self) -> Option { + while let Some(x) = self.0.next() { + if let Some(x) = (self.1)(&mut self.0, x) { + return Some(x); + } + } + None + } +} + +/// Returns an iterator which walks successive value using parent nodes skipping any node +/// which simply moves a value. +#[expect(clippy::too_many_lines)] +pub fn expr_use_sites<'tcx>( + tcx: TyCtxt<'tcx>, + typeck: &'tcx TypeckResults<'tcx>, + mut ctxt: SyntaxContext, + e: &'tcx Expr<'tcx>, +) -> impl Iterator> { + let mut adjustments: &[_] = typeck.expr_adjustments(e); let mut is_ty_unified = false; let mut moved_before_use = false; let mut same_ctxt = true; - let ctxt = e.span.ctxt(); - let node = walk_to_expr_usage(cx, e, &mut |parent_id, parent, child_id| -> ControlFlow { - if adjustments.is_empty() - && let Node::Expr(e) = cx.tcx.hir_node(child_id) - { - adjustments = cx.typeck_results().expr_adjustments(e); - } - same_ctxt &= cx.tcx.hir_span(parent_id).ctxt() == ctxt; - if let Node::Expr(e) = parent { - match e.kind { - ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => { - is_ty_unified = true; - moved_before_use = true; + ReplacingFilterMap( + hir_parent_with_src_iter(tcx, e.hir_id), + move |iter: &mut _, (parent, child_id)| { + let parent_ctxt; + let mut parent_adjustments: &[_] = &[]; + match parent { + Node::Expr(parent_expr) => { + parent_ctxt = parent_expr.span.ctxt(); + same_ctxt &= parent_ctxt == ctxt; + parent_adjustments = typeck.expr_adjustments(parent_expr); + match parent_expr.kind { + ExprKind::Match(scrutinee, arms, _) if scrutinee.hir_id != child_id => { + is_ty_unified |= arms.len() != 1; + moved_before_use = true; + if adjustments.is_empty() { + adjustments = parent_adjustments; + } + return None; + }, + ExprKind::If(cond, _, else_) if cond.hir_id != child_id => { + is_ty_unified |= else_.is_some(); + moved_before_use = true; + if adjustments.is_empty() { + adjustments = parent_adjustments; + } + return None; + }, + ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => { + is_ty_unified = true; + moved_before_use = true; + *iter = hir_parent_with_src_iter(tcx, id); + if adjustments.is_empty() { + adjustments = parent_adjustments; + } + return None; + }, + ExprKind::Block(b, _) => { + is_ty_unified |= b.targeted_by_break; + moved_before_use = true; + if adjustments.is_empty() { + adjustments = parent_adjustments; + } + return None; + }, + ExprKind::DropTemps(_) | ExprKind::Type(..) => { + if adjustments.is_empty() { + adjustments = parent_adjustments; + } + return None; + }, + _ => {}, + } }, - ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => { - is_ty_unified = true; - moved_before_use = true; + Node::Arm(arm) => { + parent_ctxt = arm.span.ctxt(); + same_ctxt &= parent_ctxt == ctxt; + if arm.body.hir_id == child_id { + return None; + } + }, + Node::Block(b) => { + same_ctxt &= b.span.ctxt() == ctxt; + return None; + }, + Node::ConstBlock(_) => parent_ctxt = ctxt, + Node::ExprField(&ExprField { span, .. }) => { + parent_ctxt = span.ctxt(); + same_ctxt &= parent_ctxt == ctxt; + }, + Node::AnonConst(&AnonConst { span, .. }) + | Node::ConstArg(&ConstArg { span, .. }) + | Node::Field(&FieldDef { span, .. }) + | Node::ImplItem(&ImplItem { span, .. }) + | Node::Item(&Item { span, .. }) + | Node::LetStmt(&LetStmt { span, .. }) + | Node::Stmt(&Stmt { span, .. }) + | Node::TraitItem(&TraitItem { span, .. }) + | Node::Variant(&Variant { span, .. }) => { + parent_ctxt = span.ctxt(); + same_ctxt &= parent_ctxt == ctxt; + *iter = hir_parent_with_src_iter(tcx, CRATE_HIR_ID); + }, + Node::AssocItemConstraint(_) + | Node::ConstArgExprField(_) + | Node::Crate(_) + | Node::Ctor(_) + | Node::Err(_) + | Node::ForeignItem(_) + | Node::GenericParam(_) + | Node::Infer(_) + | Node::Lifetime(_) + | Node::OpaqueTy(_) + | Node::Param(_) + | Node::Pat(_) + | Node::PatExpr(_) + | Node::PatField(_) + | Node::PathSegment(_) + | Node::PreciseCapturingNonLifetimeArg(_) + | Node::Synthetic + | Node::TraitRef(_) + | Node::Ty(_) + | Node::TyPat(_) + | Node::WherePredicate(_) => { + // This shouldn't be possible to hit; the inner iterator should have + // been moved to the end before we hit any of these nodes. + debug_assert!(false, "found {parent:?} which is after the final use node"); + return None; }, - ExprKind::Block(..) => moved_before_use = true, - _ => {}, } - } - ControlFlow::Continue(()) - }); - match node { - Some(ControlFlow::Continue((node, child_id))) => ExprUseCtxt { - node, - child_id, - adjustments, - is_ty_unified, - moved_before_use, - same_ctxt, + + ctxt = parent_ctxt; + Some(ExprUseSite { + node: parent, + child_id, + adjustments: mem::replace(&mut adjustments, parent_adjustments), + is_ty_unified: mem::replace(&mut is_ty_unified, false), + moved_before_use: mem::replace(&mut moved_before_use, false), + same_ctxt: mem::replace(&mut same_ctxt, true), + }) }, - None => ExprUseCtxt { - node: Node::Crate(cx.tcx.hir_root_module()), - child_id: HirId::INVALID, + ) +} + +pub fn get_expr_use_site<'tcx>( + tcx: TyCtxt<'tcx>, + typeck: &'tcx TypeckResults<'tcx>, + ctxt: SyntaxContext, + e: &'tcx Expr<'tcx>, +) -> ExprUseSite<'tcx> { + // The value in `unwrap_or` doesn't actually matter; an expression always + // has a use site. + expr_use_sites(tcx, typeck, ctxt, e).next().unwrap_or_else(|| { + debug_assert!(false, "failed to find a use site for expr {e:?}"); + ExprUseSite { + node: Node::Synthetic, // The crate root would also work. + child_id: CRATE_HIR_ID, adjustments: &[], - is_ty_unified: true, - moved_before_use: true, + is_ty_unified: false, + moved_before_use: false, same_ctxt: false, - }, - } + } + }) } /// Tokenizes the input while keeping the text associated with each token. @@ -3642,3 +3703,11 @@ pub fn is_expr_async_block(expr: &Expr<'_>) -> bool { pub fn can_use_if_let_chains(cx: &LateContext<'_>, msrv: Msrv) -> bool { cx.tcx.sess.edition().at_least_rust_2024() && msrv.meets(cx, msrvs::LET_CHAINS) } + +/// Returns an iterator over successive parent nodes paired with the ID of the node which +/// immediatly preceeded them. +#[inline] +pub fn hir_parent_with_src_iter(tcx: TyCtxt<'_>, mut id: HirId) -> impl Iterator, HirId)> { + tcx.hir_parent_id_iter(id) + .map(move |parent| (tcx.hir_node(parent), mem::replace(&mut id, parent))) +} diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index 4f9a064bf7a6a..a56e729c70bbb 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -28,7 +28,7 @@ msrv_aliases! { 1,88,0 { LET_CHAINS } 1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT, UNSIGNED_IS_MULTIPLE_OF, INTEGER_SIGN_CAST } 1,86,0 { VEC_POP_IF } - 1,85,0 { UINT_FLOAT_MIDPOINT, CONST_SIZE_OF_VAL } + 1,85,0 { UINT_FLOAT_MIDPOINT, CONST_SIZE_OF_VAL, WAKER_NOOP } 1,84,0 { CONST_OPTION_AS_SLICE, MANUAL_DANGLING_PTR } 1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP } 1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP, SPECIALIZED_TO_STRING_FOR_REFS } diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 641c6684a0bd1..9df2baa0d39ae 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -335,6 +335,11 @@ impl<'a> Sugg<'a> { Sugg::NonParen(Cow::Owned(format!("{{ {self} }}"))) } + /// Convenience method to wrap the expression in an `unsafe` block. + pub fn unsafeify(self) -> Sugg<'static> { + Sugg::NonParen(Cow::Owned(format!("unsafe {{ {self} }}"))) + } + /// Convenience method to prefix the expression with the `async` keyword. /// Can be used after `blockify` to create an async block. pub fn asyncify(self) -> Sugg<'static> { diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index ac807c0382fef..7f22df27f5c11 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -1265,17 +1265,14 @@ pub fn deref_chain<'cx, 'tcx>(cx: &'cx LateContext<'tcx>, ty: Ty<'tcx>) -> impl /// This does not look for impls in the type's `Deref::Target` type. /// If you need this, you should wrap this call in `clippy_utils::ty::deref_chain().any(...)`. pub fn get_adt_inherent_method<'a>(cx: &'a LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> Option<&'a AssocItem> { - if let Some(ty_did) = ty.ty_adt_def().map(AdtDef::did) { - cx.tcx.inherent_impls(ty_did).iter().find_map(|&did| { - cx.tcx - .associated_items(did) - .filter_by_name_unhygienic(method_name) - .next() - .filter(|item| item.tag() == AssocTag::Fn) - }) - } else { - None - } + let ty_did = ty.ty_adt_def().map(AdtDef::did)?; + cx.tcx.inherent_impls(ty_did).iter().find_map(|&did| { + cx.tcx + .associated_items(did) + .filter_by_name_unhygienic(method_name) + .next() + .filter(|item| item.tag() == AssocTag::Fn) + }) } /// Gets the type of a field by name. diff --git a/declare_clippy_lint/Cargo.toml b/declare_clippy_lint/Cargo.toml index c55e084ad52f1..d350af46d97a2 100644 --- a/declare_clippy_lint/Cargo.toml +++ b/declare_clippy_lint/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "declare_clippy_lint" -version = "0.1.96" +version = "0.1.97" edition = "2024" repository = "https://github.com/rust-lang/rust-clippy" license = "MIT OR Apache-2.0" diff --git a/lintcheck/Cargo.toml b/lintcheck/Cargo.toml index 34281f9f721b8..197a8574af2d0 100644 --- a/lintcheck/Cargo.toml +++ b/lintcheck/Cargo.toml @@ -11,7 +11,7 @@ publish = false default-run = "lintcheck" [dependencies] -cargo_metadata = "0.15.3" +cargo_metadata = "0.23" clap = { version = "4.4", features = ["derive", "env"] } crossbeam-channel = "0.5.6" diff = "0.1.13" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 22b5488488051..97c8cf260cad5 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2026-04-02" +channel = "nightly-2026-04-16" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/tests/ui/bind_instead_of_map.fixed b/tests/ui/bind_instead_of_map.fixed index fa35a01242d1d..a1c4cb5a4823a 100644 --- a/tests/ui/bind_instead_of_map.fixed +++ b/tests/ui/bind_instead_of_map.fixed @@ -1,4 +1,5 @@ #![deny(clippy::bind_instead_of_map)] +#![allow(clippy::manual_filter)] // need a main anyway, use it get rid of unused warnings too pub fn main() { diff --git a/tests/ui/bind_instead_of_map.rs b/tests/ui/bind_instead_of_map.rs index 403077e72ff98..1308fa9f416be 100644 --- a/tests/ui/bind_instead_of_map.rs +++ b/tests/ui/bind_instead_of_map.rs @@ -1,4 +1,5 @@ #![deny(clippy::bind_instead_of_map)] +#![allow(clippy::manual_filter)] // need a main anyway, use it get rid of unused warnings too pub fn main() { diff --git a/tests/ui/bind_instead_of_map.stderr b/tests/ui/bind_instead_of_map.stderr index 3f8d631591e93..08f85fb58549c 100644 --- a/tests/ui/bind_instead_of_map.stderr +++ b/tests/ui/bind_instead_of_map.stderr @@ -1,5 +1,5 @@ error: using `Option.and_then(Some)`, which is a no-op - --> tests/ui/bind_instead_of_map.rs:7:13 + --> tests/ui/bind_instead_of_map.rs:8:13 | LL | let _ = x.and_then(Some); | ^^^^^^^^^^^^^^^^ help: use the expression directly: `x` @@ -11,13 +11,13 @@ LL | #![deny(clippy::bind_instead_of_map)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)` - --> tests/ui/bind_instead_of_map.rs:9:13 + --> tests/ui/bind_instead_of_map.rs:10:13 | LL | let _ = x.and_then(|o| Some(o + 1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.map(|o| o + 1)` error: using `Result.and_then(Ok)`, which is a no-op - --> tests/ui/bind_instead_of_map.rs:16:13 + --> tests/ui/bind_instead_of_map.rs:17:13 | LL | let _ = x.and_then(Ok); | ^^^^^^^^^^^^^^ help: use the expression directly: `x` diff --git a/tests/ui/bit_masks.rs b/tests/ui/bit_masks.rs index 87dcdb3084d00..bca5b2ec34e12 100644 --- a/tests/ui/bit_masks.rs +++ b/tests/ui/bit_masks.rs @@ -89,3 +89,13 @@ fn ineffective() { x | 3 > 4; // not an error (yet), better written as x >= 4 x | 4 <= 19; } + +mod issue16781 { + fn unsigned(x: u8) -> bool { + x & 0xf0 == 0x11 << 4 + } + + fn signed(x: i8) -> bool { + x & 0x70 == 0x11 << 4 + } +} diff --git a/tests/ui/byte_char_slices.fixed b/tests/ui/byte_char_slices.fixed index 87934d6362f75..661ae8fc85fee 100644 --- a/tests/ui/byte_char_slices.fixed +++ b/tests/ui/byte_char_slices.fixed @@ -1,4 +1,5 @@ #![warn(clippy::byte_char_slices)] +#![allow(clippy::useless_vec)] fn main() { let bad = b"abc"; @@ -11,7 +12,40 @@ fn main() { //~^ byte_char_slices let good = &[b'a', 0x42]; - let good = [b'a', b'a']; - //~^ useless_vec - let good: u8 = [b'a', b'c'].into_iter().sum(); + let good = vec![b'a', b'a']; +} + +fn takes_array_ref(_: &[u8; 2]) {} + +fn takes_array_ref_ref(_: &&[u8; 2]) {} + +fn issue16759(bytes: [u32; 3]) { + const START: u32 = u32::from_le_bytes(*b"WORK"); + //~^ byte_char_slices + + let auto_deref_to_slice: u8 = b"ac".iter().copied().sum(); + //~^ byte_char_slices + + let with_comment = [ + // 1 2 3 + b'a', b'b', b'c', // x + b'd', b'e', b'f', // 2x + b'g', b'h', b'i', // 3x + ]; + let with_cfg = [ + b'a', + b'b', + b'c', + #[cfg(feature = "foo")] + b'd', + ]; + + let with_escape: u8 = b"'\"\x00\n\\".iter().copied().sum(); + //~^ byte_char_slices + + takes_array_ref(b"ab"); + //~^ byte_char_slices + + takes_array_ref_ref(&b"ab"); + //~^ byte_char_slices } diff --git a/tests/ui/byte_char_slices.rs b/tests/ui/byte_char_slices.rs index 0de7cf66fda89..e8dc9e9611ded 100644 --- a/tests/ui/byte_char_slices.rs +++ b/tests/ui/byte_char_slices.rs @@ -1,4 +1,5 @@ #![warn(clippy::byte_char_slices)] +#![allow(clippy::useless_vec)] fn main() { let bad = &[b'a', b'b', b'c']; @@ -12,6 +13,39 @@ fn main() { let good = &[b'a', 0x42]; let good = vec![b'a', b'a']; - //~^ useless_vec - let good: u8 = [b'a', b'c'].into_iter().sum(); +} + +fn takes_array_ref(_: &[u8; 2]) {} + +fn takes_array_ref_ref(_: &&[u8; 2]) {} + +fn issue16759(bytes: [u32; 3]) { + const START: u32 = u32::from_le_bytes([b'W', b'O', b'R', b'K']); + //~^ byte_char_slices + + let auto_deref_to_slice: u8 = [b'a', b'c'].iter().copied().sum(); + //~^ byte_char_slices + + let with_comment = [ + // 1 2 3 + b'a', b'b', b'c', // x + b'd', b'e', b'f', // 2x + b'g', b'h', b'i', // 3x + ]; + let with_cfg = [ + b'a', + b'b', + b'c', + #[cfg(feature = "foo")] + b'd', + ]; + + let with_escape: u8 = [b'\'', b'"', b'\x00', b'\n', b'\\'].iter().copied().sum(); + //~^ byte_char_slices + + takes_array_ref(&[b'a', b'b']); + //~^ byte_char_slices + + takes_array_ref_ref(&&[b'a', b'b']); + //~^ byte_char_slices } diff --git a/tests/ui/byte_char_slices.stderr b/tests/ui/byte_char_slices.stderr index c1b7e4ca2f17d..0f72cf13a0b6d 100644 --- a/tests/ui/byte_char_slices.stderr +++ b/tests/ui/byte_char_slices.stderr @@ -1,5 +1,5 @@ error: can be more succinctly written as a byte str - --> tests/ui/byte_char_slices.rs:4:15 + --> tests/ui/byte_char_slices.rs:5:15 | LL | let bad = &[b'a', b'b', b'c']; | ^^^^^^^^^^^^^^^^^^^ help: try: `b"abc"` @@ -8,31 +8,52 @@ LL | let bad = &[b'a', b'b', b'c']; = help: to override `-D warnings` add `#[allow(clippy::byte_char_slices)]` error: can be more succinctly written as a byte str - --> tests/ui/byte_char_slices.rs:6:18 + --> tests/ui/byte_char_slices.rs:7:18 | LL | let quotes = &[b'"', b'H', b'i']; | ^^^^^^^^^^^^^^^^^^^ help: try: `b"\"Hi"` error: can be more succinctly written as a byte str - --> tests/ui/byte_char_slices.rs:8:18 + --> tests/ui/byte_char_slices.rs:9:18 | LL | let quotes = &[b'\'', b'S', b'u', b'p']; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b"'Sup"` error: can be more succinctly written as a byte str - --> tests/ui/byte_char_slices.rs:10:19 + --> tests/ui/byte_char_slices.rs:11:19 | LL | let escapes = &[b'\x42', b'E', b's', b'c']; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b"\x42Esc"` -error: useless use of `vec!` - --> tests/ui/byte_char_slices.rs:14:16 +error: can be more succinctly written as a byte str + --> tests/ui/byte_char_slices.rs:23:43 + | +LL | const START: u32 = u32::from_le_bytes([b'W', b'O', b'R', b'K']); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `*b"WORK"` + +error: can be more succinctly written as a byte str + --> tests/ui/byte_char_slices.rs:26:35 + | +LL | let auto_deref_to_slice: u8 = [b'a', b'c'].iter().copied().sum(); + | ^^^^^^^^^^^^ help: try: `b"ac"` + +error: can be more succinctly written as a byte str + --> tests/ui/byte_char_slices.rs:43:27 + | +LL | let with_escape: u8 = [b'\'', b'"', b'\x00', b'\n', b'\\'].iter().copied().sum(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b"'\"\x00\n\\"` + +error: can be more succinctly written as a byte str + --> tests/ui/byte_char_slices.rs:46:21 | -LL | let good = vec![b'a', b'a']; - | ^^^^^^^^^^^^^^^^ help: you can use an array directly: `[b'a', b'a']` +LL | takes_array_ref(&[b'a', b'b']); + | ^^^^^^^^^^^^^ help: try: `b"ab"` + +error: can be more succinctly written as a byte str + --> tests/ui/byte_char_slices.rs:49:26 | - = note: `-D clippy::useless-vec` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::useless_vec)]` +LL | takes_array_ref_ref(&&[b'a', b'b']); + | ^^^^^^^^^^^^^ help: try: `b"ab"` -error: aborting due to 5 previous errors +error: aborting due to 9 previous errors diff --git a/tests/ui/expect_fun_call.fixed b/tests/ui/expect_fun_call.fixed index b923521afde1c..cc407adc75adb 100644 --- a/tests/ui/expect_fun_call.fixed +++ b/tests/ui/expect_fun_call.fixed @@ -147,3 +147,13 @@ fn main() { return; }); } + +fn issue16747() { + let x = 42; + let _c = char::from_u32(x).unwrap_or_else(|| panic!("{}", &format!("Illegal: {x}")[..])); + //~^ expect_fun_call + + let s = "hello"; + let _c = char::from_u32(x).unwrap_or_else(|| panic!("{}", &s.to_lowercase()[..2])); + //~^ expect_fun_call +} diff --git a/tests/ui/expect_fun_call.rs b/tests/ui/expect_fun_call.rs index bc58d24bc8128..ab3b436865523 100644 --- a/tests/ui/expect_fun_call.rs +++ b/tests/ui/expect_fun_call.rs @@ -147,3 +147,13 @@ fn main() { return; }); } + +fn issue16747() { + let x = 42; + let _c = char::from_u32(x).expect(&format!("Illegal: {x}")[..]); + //~^ expect_fun_call + + let s = "hello"; + let _c = char::from_u32(x).expect(&s.to_lowercase()[..2]); + //~^ expect_fun_call +} diff --git a/tests/ui/expect_fun_call.stderr b/tests/ui/expect_fun_call.stderr index 0692ecb4862e8..4ef114ac406ba 100644 --- a/tests/ui/expect_fun_call.stderr +++ b/tests/ui/expect_fun_call.stderr @@ -97,5 +97,17 @@ error: function call inside of `expect` LL | format_capture_and_value.expect(&format!("{error_code}, {}", 1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{error_code}, {}", 1))` -error: aborting due to 16 previous errors +error: function call inside of `expect` + --> tests/ui/expect_fun_call.rs:153:32 + | +LL | let _c = char::from_u32(x).expect(&format!("Illegal: {x}")[..]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", &format!("Illegal: {x}")[..]))` + +error: function call inside of `expect` + --> tests/ui/expect_fun_call.rs:157:32 + | +LL | let _c = char::from_u32(x).expect(&s.to_lowercase()[..2]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", &s.to_lowercase()[..2]))` + +error: aborting due to 18 previous errors diff --git a/tests/ui/fn_to_numeric_cast_any.rs b/tests/ui/fn_to_numeric_cast_any.rs index 83c1e9a8387ef..2884dcb367f0e 100644 --- a/tests/ui/fn_to_numeric_cast_any.rs +++ b/tests/ui/fn_to_numeric_cast_any.rs @@ -82,7 +82,7 @@ fn closure_to_fn_to_integer() { fn fn_to_raw_ptr() { let _ = foo as *const (); - //~^ fn_to_numeric_cast_any + let _ = foo as *mut (); } fn cast_fn_to_self() { diff --git a/tests/ui/fn_to_numeric_cast_any.stderr b/tests/ui/fn_to_numeric_cast_any.stderr index f7c49b8ff88b5..251c55395bd19 100644 --- a/tests/ui/fn_to_numeric_cast_any.stderr +++ b/tests/ui/fn_to_numeric_cast_any.stderr @@ -176,16 +176,5 @@ help: did you mean to invoke the function? LL | let _ = (clos as fn(u32) -> u32)() as usize; | ++ -error: casting function pointer `foo` to `*const ()` - --> tests/ui/fn_to_numeric_cast_any.rs:84:13 - | -LL | let _ = foo as *const (); - | ^^^^^^^^^^^^^^^^ - | -help: did you mean to invoke the function? - | -LL | let _ = foo() as *const (); - | ++ - -error: aborting due to 17 previous errors +error: aborting due to 16 previous errors diff --git a/tests/ui/if_then_some_else_none.fixed b/tests/ui/if_then_some_else_none.fixed index ce122ac69b12e..6bf636bb2b0bc 100644 --- a/tests/ui/if_then_some_else_none.fixed +++ b/tests/ui/if_then_some_else_none.fixed @@ -1,5 +1,9 @@ #![warn(clippy::if_then_some_else_none)] -#![allow(clippy::redundant_pattern_matching, clippy::unnecessary_lazy_evaluations)] +#![allow( + clippy::redundant_pattern_matching, + clippy::unnecessary_lazy_evaluations, + clippy::manual_filter +)] fn main() { // Should issue an error. diff --git a/tests/ui/if_then_some_else_none.rs b/tests/ui/if_then_some_else_none.rs index 1d6c86d944924..2cb14ea4d1bcd 100644 --- a/tests/ui/if_then_some_else_none.rs +++ b/tests/ui/if_then_some_else_none.rs @@ -1,5 +1,9 @@ #![warn(clippy::if_then_some_else_none)] -#![allow(clippy::redundant_pattern_matching, clippy::unnecessary_lazy_evaluations)] +#![allow( + clippy::redundant_pattern_matching, + clippy::unnecessary_lazy_evaluations, + clippy::manual_filter +)] fn main() { // Should issue an error. diff --git a/tests/ui/if_then_some_else_none.stderr b/tests/ui/if_then_some_else_none.stderr index eff5f8c82dcb2..112f8221b59d6 100644 --- a/tests/ui/if_then_some_else_none.stderr +++ b/tests/ui/if_then_some_else_none.stderr @@ -1,5 +1,5 @@ error: this could be simplified with `bool::then` - --> tests/ui/if_then_some_else_none.rs:6:13 + --> tests/ui/if_then_some_else_none.rs:10:13 | LL | let _ = if foo() { | _____________^ @@ -24,7 +24,7 @@ LL ~ }); | error: this could be simplified with `bool::then` - --> tests/ui/if_then_some_else_none.rs:16:13 + --> tests/ui/if_then_some_else_none.rs:20:13 | LL | let _ = if matches!(true, true) { | _____________^ @@ -47,19 +47,19 @@ LL ~ }); | error: this could be simplified with `bool::then_some` - --> tests/ui/if_then_some_else_none.rs:27:28 + --> tests/ui/if_then_some_else_none.rs:31:28 | LL | let _ = x.and_then(|o| if o < 32 { Some(o) } else { None }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(o < 32).then_some(o)` error: this could be simplified with `bool::then_some` - --> tests/ui/if_then_some_else_none.rs:32:13 + --> tests/ui/if_then_some_else_none.rs:36:13 | LL | let _ = if !x { Some(0) } else { None }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(!x).then_some(0)` error: this could be simplified with `bool::then` - --> tests/ui/if_then_some_else_none.rs:88:13 + --> tests/ui/if_then_some_else_none.rs:92:13 | LL | let _ = if foo() { | _____________^ @@ -82,13 +82,13 @@ LL ~ }); | error: this could be simplified with `bool::then` - --> tests/ui/if_then_some_else_none.rs:138:5 + --> tests/ui/if_then_some_else_none.rs:142:5 | LL | if s == "1" { Some(true) } else { None } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(s == "1").then(|| true)` error: this could be simplified with `bool::then` - --> tests/ui/if_then_some_else_none.rs:154:9 + --> tests/ui/if_then_some_else_none.rs:158:9 | LL | / if rs.len() == 1 && rs[0].start == rs[0].end { LL | | @@ -99,7 +99,7 @@ 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 + --> tests/ui/if_then_some_else_none.rs:168:9 | LL | / if modulo == 0 { LL | | @@ -110,7 +110,7 @@ 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 + --> tests/ui/if_then_some_else_none.rs:185:22 | LL | do_something(if i % 2 == 0 { | ______________________^ @@ -122,7 +122,7 @@ 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 + --> tests/ui/if_then_some_else_none.rs:202:22 | LL | do_something(if i % 2 == 0 { | ______________________^ @@ -134,7 +134,7 @@ 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 + --> tests/ui/if_then_some_else_none.rs:210:22 | LL | do_something(if i % 2 == 0 { | ______________________^ @@ -146,7 +146,7 @@ LL | | }); | |_________^ help: try: `(i % 2 == 0).then_some(closure_fn)` error: this could be simplified with `bool::then` - --> tests/ui/if_then_some_else_none.rs:231:13 + --> tests/ui/if_then_some_else_none.rs:235:13 | LL | / if self.count < 5 { LL | | self.count += 1; @@ -165,7 +165,7 @@ LL + }) | error: this could be simplified with `bool::then` - --> tests/ui/if_then_some_else_none.rs:249:13 + --> tests/ui/if_then_some_else_none.rs:253:13 | LL | let _ = if true { | _____________^ @@ -185,7 +185,7 @@ LL ~ }); | error: this could be simplified with `bool::then` - --> tests/ui/if_then_some_else_none.rs:292:5 + --> tests/ui/if_then_some_else_none.rs:296:5 | LL | / if 1 <= 3 { LL | | let a = UnsafeCell::new(1); diff --git a/tests/ui/implicit_saturating_sub.fixed b/tests/ui/implicit_saturating_sub.fixed index 22e59bbd2705b..85f67bb2381ec 100644 --- a/tests/ui/implicit_saturating_sub.fixed +++ b/tests/ui/implicit_saturating_sub.fixed @@ -260,3 +260,21 @@ fn issue16307() { println!("{y}"); } + +fn wrongly_unmangled_macros() { + macro_rules! test_big { + ($val:expr) => { + ($val * 2 + 1) + }; + } + + macro_rules! test_little { + ($val:expr) => { + ($val * 2 + 0) + }; + } + + let a = 15u64; + let b = 20u64; + let _ = test_big!(a).saturating_sub(test_little!(b)); +} diff --git a/tests/ui/implicit_saturating_sub.rs b/tests/ui/implicit_saturating_sub.rs index 7fa19f0c8ad21..81f19b3000f73 100644 --- a/tests/ui/implicit_saturating_sub.rs +++ b/tests/ui/implicit_saturating_sub.rs @@ -334,3 +334,26 @@ fn issue16307() { println!("{y}"); } + +fn wrongly_unmangled_macros() { + macro_rules! test_big { + ($val:expr) => { + ($val * 2 + 1) + }; + } + + macro_rules! test_little { + ($val:expr) => { + ($val * 2 + 0) + }; + } + + let a = 15u64; + let b = 20u64; + let _ = if test_big!(a) > test_little!(b) { + //~^ implicit_saturating_sub + test_big!(a) - test_little!(b) + } else { + 0 + }; +} diff --git a/tests/ui/implicit_saturating_sub.stderr b/tests/ui/implicit_saturating_sub.stderr index 2f3d2ba787e8c..c7a4330603a29 100644 --- a/tests/ui/implicit_saturating_sub.stderr +++ b/tests/ui/implicit_saturating_sub.stderr @@ -244,5 +244,17 @@ error: manual arithmetic check found LL | let y = if x >= 100 { 0 } else { 100 - x }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `100_u8.saturating_sub(x)` -error: aborting due to 28 previous errors +error: manual arithmetic check found + --> tests/ui/implicit_saturating_sub.rs:353:13 + | +LL | let _ = if test_big!(a) > test_little!(b) { + | _____________^ +LL | | +LL | | test_big!(a) - test_little!(b) +LL | | } else { +LL | | 0 +LL | | }; + | |_____^ help: replace it with: `test_big!(a).saturating_sub(test_little!(b))` + +error: aborting due to 29 previous errors diff --git a/tests/ui/int_plus_one.fixed b/tests/ui/int_plus_one.fixed index cdd19515e9a7e..02087a0720e75 100644 --- a/tests/ui/int_plus_one.fixed +++ b/tests/ui/int_plus_one.fixed @@ -16,4 +16,18 @@ fn main() { let _ = x > y; // should be ok let _ = y < x; // should be ok + + // When the suggestion replaces `<=`/`>=` with `<`, an `as` cast on + // the LHS must be parenthesized to avoid parser ambiguity + // (e.g., `x as usize < y` is parsed as `x as usize`). + let z = 0usize; + let _ = (x as usize) < z; //~ int_plus_one + let _ = z > x as usize; //~ int_plus_one + // No parentheses needed when the replacement operator is `>`. + let _ = x as usize > z; //~ int_plus_one + let _ = z < x as usize; //~ int_plus_one + + // Nested and parenthesized casts on the LHS. + let _ = ((x as usize) as u8) < 5u8; //~ int_plus_one + let _ = (x as usize) < z; //~ int_plus_one } diff --git a/tests/ui/int_plus_one.rs b/tests/ui/int_plus_one.rs index 8d7d2e8982d84..235175e63ee02 100644 --- a/tests/ui/int_plus_one.rs +++ b/tests/ui/int_plus_one.rs @@ -16,4 +16,18 @@ fn main() { let _ = x > y; // should be ok let _ = y < x; // should be ok + + // When the suggestion replaces `<=`/`>=` with `<`, an `as` cast on + // the LHS must be parenthesized to avoid parser ambiguity + // (e.g., `x as usize < y` is parsed as `x as usize`). + let z = 0usize; + let _ = x as usize + 1 <= z; //~ int_plus_one + let _ = z >= x as usize + 1; //~ int_plus_one + // No parentheses needed when the replacement operator is `>`. + let _ = x as usize - 1 >= z; //~ int_plus_one + let _ = z <= x as usize - 1; //~ int_plus_one + + // Nested and parenthesized casts on the LHS. + let _ = ((x as usize) as u8) + 1 <= 5u8; //~ int_plus_one + let _ = (x as usize) + 1 <= z; //~ int_plus_one } diff --git a/tests/ui/int_plus_one.stderr b/tests/ui/int_plus_one.stderr index 8bdff5680bdc4..4081b7a76367d 100644 --- a/tests/ui/int_plus_one.stderr +++ b/tests/ui/int_plus_one.stderr @@ -49,5 +49,41 @@ error: unnecessary `>= y + 1` or `x - 1 >=` LL | let _ = y <= -1 + x; | ^^^^^^^^^^^ help: change it to: `y < x` -error: aborting due to 8 previous errors +error: unnecessary `>= y + 1` or `x - 1 >=` + --> tests/ui/int_plus_one.rs:24:13 + | +LL | let _ = x as usize + 1 <= z; + | ^^^^^^^^^^^^^^^^^^^ help: change it to: `(x as usize) < z` + +error: unnecessary `>= y + 1` or `x - 1 >=` + --> tests/ui/int_plus_one.rs:25:13 + | +LL | let _ = z >= x as usize + 1; + | ^^^^^^^^^^^^^^^^^^^ help: change it to: `z > x as usize` + +error: unnecessary `>= y + 1` or `x - 1 >=` + --> tests/ui/int_plus_one.rs:27:13 + | +LL | let _ = x as usize - 1 >= z; + | ^^^^^^^^^^^^^^^^^^^ help: change it to: `x as usize > z` + +error: unnecessary `>= y + 1` or `x - 1 >=` + --> tests/ui/int_plus_one.rs:28:13 + | +LL | let _ = z <= x as usize - 1; + | ^^^^^^^^^^^^^^^^^^^ help: change it to: `z < x as usize` + +error: unnecessary `>= y + 1` or `x - 1 >=` + --> tests/ui/int_plus_one.rs:31:13 + | +LL | let _ = ((x as usize) as u8) + 1 <= 5u8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: change it to: `((x as usize) as u8) < 5u8` + +error: unnecessary `>= y + 1` or `x - 1 >=` + --> tests/ui/int_plus_one.rs:32:13 + | +LL | let _ = (x as usize) + 1 <= z; + | ^^^^^^^^^^^^^^^^^^^^^ help: change it to: `(x as usize) < z` + +error: aborting due to 14 previous errors diff --git a/tests/ui/let_and_return.edition2021.fixed b/tests/ui/let_and_return.edition2021.fixed index 6ca0febc2b8d9..1c814f54c9391 100644 --- a/tests/ui/let_and_return.edition2021.fixed +++ b/tests/ui/let_and_return.edition2021.fixed @@ -279,4 +279,27 @@ fn has_comment() -> Vec { v } +fn wrongly_unmangled_macros() -> i32 { + let x = 1; + macro_rules! plus_one { + ($e:expr) => { + $e + 1 + }; + } + + + plus_one!(x) + //~^ let_and_return +} + +fn issue16820() -> Option { + let value = Some(42); + + let v @ None = value else { + panic!("uh oh!"); + }; + + v +} + fn main() {} diff --git a/tests/ui/let_and_return.edition2021.stderr b/tests/ui/let_and_return.edition2021.stderr index f9536d1b54776..2203dc3377b56 100644 --- a/tests/ui/let_and_return.edition2021.stderr +++ b/tests/ui/let_and_return.edition2021.stderr @@ -148,5 +148,19 @@ LL ~ LL ~ ({ true } || { false } && { 2 <= 3 }) | -error: aborting due to 10 previous errors +error: returning the result of a `let` binding from a block + --> tests/ui/let_and_return.rs:291:5 + | +LL | let y = plus_one!(x); + | --------------------- unnecessary `let` binding +LL | y + | ^ + | +help: return the expression directly + | +LL ~ +LL ~ plus_one!(x) + | + +error: aborting due to 11 previous errors diff --git a/tests/ui/let_and_return.edition2024.fixed b/tests/ui/let_and_return.edition2024.fixed index 0fce22936ae6e..b0c5a8359b7ac 100644 --- a/tests/ui/let_and_return.edition2024.fixed +++ b/tests/ui/let_and_return.edition2024.fixed @@ -279,4 +279,27 @@ fn has_comment() -> Vec { v } +fn wrongly_unmangled_macros() -> i32 { + let x = 1; + macro_rules! plus_one { + ($e:expr) => { + $e + 1 + }; + } + + + plus_one!(x) + //~^ let_and_return +} + +fn issue16820() -> Option { + let value = Some(42); + + let v @ None = value else { + panic!("uh oh!"); + }; + + v +} + fn main() {} diff --git a/tests/ui/let_and_return.edition2024.stderr b/tests/ui/let_and_return.edition2024.stderr index ca378fa432327..6ea8b9dafc9ea 100644 --- a/tests/ui/let_and_return.edition2024.stderr +++ b/tests/ui/let_and_return.edition2024.stderr @@ -224,5 +224,19 @@ LL + None => Ok(Ok(0)), LL + }? | -error: aborting due to 15 previous errors +error: returning the result of a `let` binding from a block + --> tests/ui/let_and_return.rs:291:5 + | +LL | let y = plus_one!(x); + | --------------------- unnecessary `let` binding +LL | y + | ^ + | +help: return the expression directly + | +LL ~ +LL ~ plus_one!(x) + | + +error: aborting due to 16 previous errors diff --git a/tests/ui/let_and_return.rs b/tests/ui/let_and_return.rs index 301f153ca8b10..57660f7e031aa 100644 --- a/tests/ui/let_and_return.rs +++ b/tests/ui/let_and_return.rs @@ -279,4 +279,27 @@ fn has_comment() -> Vec { v } +fn wrongly_unmangled_macros() -> i32 { + let x = 1; + macro_rules! plus_one { + ($e:expr) => { + $e + 1 + }; + } + + let y = plus_one!(x); + y + //~^ let_and_return +} + +fn issue16820() -> Option { + let value = Some(42); + + let v @ None = value else { + panic!("uh oh!"); + }; + + v +} + fn main() {} diff --git a/tests/ui/manual_div_ceil.fixed b/tests/ui/manual_div_ceil.fixed index 8ffd107dd42ea..db46f67208914 100644 --- a/tests/ui/manual_div_ceil.fixed +++ b/tests/ui/manual_div_ceil.fixed @@ -16,6 +16,12 @@ macro_rules! eight { }; } +macro_rules! plus_one { + ($val:expr) => { + ($val + 1) + }; +} + fn main() { let x = 7_u32; let y = 4_u32; @@ -55,6 +61,9 @@ fn main() { // Also test if RHS should be result of macro expansion let _ = 33u32.div_ceil(eight!()); //~^ manual_div_ceil + + let _ = plus_one!(x).div_ceil(y); + //~^ manual_div_ceil } fn issue_13843() { diff --git a/tests/ui/manual_div_ceil.rs b/tests/ui/manual_div_ceil.rs index 859fb5a13c444..d60ecaa2af7a3 100644 --- a/tests/ui/manual_div_ceil.rs +++ b/tests/ui/manual_div_ceil.rs @@ -16,6 +16,12 @@ macro_rules! eight { }; } +macro_rules! plus_one { + ($val:expr) => { + ($val + 1) + }; +} + fn main() { let x = 7_u32; let y = 4_u32; @@ -55,6 +61,9 @@ fn main() { // Also test if RHS should be result of macro expansion let _ = (33u32 + 7) / eight!(); //~^ manual_div_ceil + + let _ = (plus_one!(x) + (y - 1)) / y; + //~^ manual_div_ceil } fn issue_13843() { diff --git a/tests/ui/manual_div_ceil.stderr b/tests/ui/manual_div_ceil.stderr index 0efc114c70786..1ebcbc04eeb95 100644 --- a/tests/ui/manual_div_ceil.stderr +++ b/tests/ui/manual_div_ceil.stderr @@ -1,5 +1,5 @@ error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:25:13 + --> tests/ui/manual_div_ceil.rs:31:13 | LL | let _ = (x + (y - 1)) / y; | ^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(y)` @@ -8,25 +8,25 @@ LL | let _ = (x + (y - 1)) / y; = help: to override `-D warnings` add `#[allow(clippy::manual_div_ceil)]` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:27:13 + --> tests/ui/manual_div_ceil.rs:33:13 | LL | let _ = ((y - 1) + x) / y; | ^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(y)` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:29:13 + --> tests/ui/manual_div_ceil.rs:35:13 | LL | let _ = (x + y - 1) / y; | ^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(y)` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:32:13 + --> tests/ui/manual_div_ceil.rs:38:13 | LL | let _ = (7_u32 + (4 - 1)) / 4; | ^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `7_u32.div_ceil(4)` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:34:13 + --> tests/ui/manual_div_ceil.rs:40:13 | LL | let _ = (7_i32 as u32 + (4 - 1)) / 4; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `(7_i32 as u32).div_ceil(4)` @@ -54,100 +54,106 @@ LL | y!(); = note: this error originates in the macro `y` (in Nightly builds, run with -Z macro-backtrace for more info) error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:56:13 + --> tests/ui/manual_div_ceil.rs:62:13 | LL | let _ = (33u32 + 7) / eight!(); | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `33u32.div_ceil(eight!())` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:62:13 + --> tests/ui/manual_div_ceil.rs:65:13 + | +LL | let _ = (plus_one!(x) + (y - 1)) / y; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `plus_one!(x).div_ceil(y)` + +error: manually reimplementing `div_ceil` + --> tests/ui/manual_div_ceil.rs:71:13 | LL | let _ = (2048 + x - 1) / x; | ^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `2048_usize.div_ceil(x)` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:66:13 + --> tests/ui/manual_div_ceil.rs:75:13 | LL | let _ = (2048usize + x - 1) / x; | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `2048usize.div_ceil(x)` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:70:13 + --> tests/ui/manual_div_ceil.rs:79:13 | LL | let _ = (2048_usize + x - 1) / x; | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `2048_usize.div_ceil(x)` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:74:13 + --> tests/ui/manual_div_ceil.rs:83:13 | LL | let _ = (x + 4 - 1) / 4; | ^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(4)` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:77:18 + --> tests/ui/manual_div_ceil.rs:86:18 | LL | let _: u32 = (2048 + 6 - 1) / 6; | ^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `2048_u32.div_ceil(6)` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:79:20 + --> tests/ui/manual_div_ceil.rs:88:20 | LL | let _: usize = (2048 + 6 - 1) / 6; | ^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `2048_usize.div_ceil(6)` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:81:18 + --> tests/ui/manual_div_ceil.rs:90:18 | LL | let _: u32 = (0x2048 + 0x6 - 1) / 0x6; | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `0x2048_u32.div_ceil(0x6)` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:84:13 + --> tests/ui/manual_div_ceil.rs:93:13 | LL | let _ = (2048 + 6u32 - 1) / 6u32; | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `2048_u32.div_ceil(6u32)` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:87:13 + --> tests/ui/manual_div_ceil.rs:96:13 | LL | let _ = (1_000_000 + 6u32 - 1) / 6u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `1_000_000_u32.div_ceil(6u32)` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:93:13 + --> tests/ui/manual_div_ceil.rs:102:13 | LL | let _ = (x + 7) / 8; | ^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(8)` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:95:13 + --> tests/ui/manual_div_ceil.rs:104:13 | LL | let _ = (7 + x) / 8; | ^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(8)` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:105:13 + --> tests/ui/manual_div_ceil.rs:114:13 | LL | let _ = (size + c - 1) / c; | ^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `size.div_ceil(*c)` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:121:13 + --> tests/ui/manual_div_ceil.rs:130:13 | LL | let _ = x.next_multiple_of(8) / 8; | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(8)` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:123:13 + --> tests/ui/manual_div_ceil.rs:132:13 | LL | let _ = u32::next_multiple_of(x, 8) / 8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(8)` error: manually reimplementing `div_ceil` - --> tests/ui/manual_div_ceil.rs:127:13 + --> tests/ui/manual_div_ceil.rs:136:13 | LL | let _ = y.next_multiple_of(8) / 8; | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `y.div_ceil(8)` -error: aborting due to 23 previous errors +error: aborting due to 24 previous errors diff --git a/tests/ui/manual_filter.fixed b/tests/ui/manual_filter.fixed index a0fb0e32d6010..3e2cebee40fe1 100644 --- a/tests/ui/manual_filter.fixed +++ b/tests/ui/manual_filter.fixed @@ -1,5 +1,10 @@ #![warn(clippy::manual_filter)] -#![allow(unused_variables, dead_code, clippy::useless_vec)] +#![allow( + unused_variables, + clippy::question_mark, + clippy::useless_vec, + clippy::nonminimal_bool +)] fn main() { Some(0).filter(|&x| x <= 0); @@ -156,3 +161,24 @@ fn main() { fn maybe_some() -> Option { Some(0) } + +fn issue14440(opt: Option) { + opt.filter(|&x| x != 0); + //~^ manual_filter + + let y = 1i32; + opt.filter(move |&x| x == y); + //~^ manual_filter + + let opt1 = Some("123".to_string()); + opt1.filter(|s| s.len() > 2); + //~^ manual_filter + + unsafe fn f(x: u32) -> bool { + true + } + opt.filter(|&x| unsafe { f(x as u32) }); + //~^ manual_filter + opt.filter(|&x| unsafe { f(x as u32) }); + //~^ manual_filter +} diff --git a/tests/ui/manual_filter.rs b/tests/ui/manual_filter.rs index a9d0c35f8bb72..2b80cb450e058 100644 --- a/tests/ui/manual_filter.rs +++ b/tests/ui/manual_filter.rs @@ -1,5 +1,10 @@ #![warn(clippy::manual_filter)] -#![allow(unused_variables, dead_code, clippy::useless_vec)] +#![allow( + unused_variables, + clippy::question_mark, + clippy::useless_vec, + clippy::nonminimal_bool +)] fn main() { match Some(0) { @@ -293,3 +298,24 @@ fn main() { fn maybe_some() -> Option { Some(0) } + +fn issue14440(opt: Option) { + opt.and_then(|x| if x == 0 { None } else { Some(x) }); + //~^ manual_filter + + let y = 1i32; + opt.and_then(move |x| if x == y { Some(x) } else { None }); + //~^ manual_filter + + let opt1 = Some("123".to_string()); + opt1.and_then(|s| if s.len() > 2 { Some(s) } else { None }); + //~^ manual_filter + + unsafe fn f(x: u32) -> bool { + true + } + opt.and_then(|x| if unsafe { f(x as u32) } { Some(x) } else { None }); + //~^ manual_filter + opt.and_then(|x| unsafe { if f(x as u32) { Some(x) } else { None } }); + //~^ manual_filter +} diff --git a/tests/ui/manual_filter.stderr b/tests/ui/manual_filter.stderr index 63463997f4357..c5fdf14a9b432 100644 --- a/tests/ui/manual_filter.stderr +++ b/tests/ui/manual_filter.stderr @@ -1,5 +1,5 @@ error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:5:5 + --> tests/ui/manual_filter.rs:10:5 | LL | / match Some(0) { LL | | @@ -14,7 +14,7 @@ LL | | }; = help: to override `-D warnings` add `#[allow(clippy::manual_filter)]` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:17:5 + --> tests/ui/manual_filter.rs:22:5 | LL | / match Some(1) { LL | | @@ -26,7 +26,7 @@ LL | | }; | |_____^ help: try: `Some(1).filter(|&x| x <= 0)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:29:5 + --> tests/ui/manual_filter.rs:34:5 | LL | / match Some(2) { LL | | @@ -38,7 +38,7 @@ LL | | }; | |_____^ help: try: `Some(2).filter(|&x| x <= 0)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:41:5 + --> tests/ui/manual_filter.rs:46:5 | LL | / match Some(3) { LL | | @@ -50,7 +50,7 @@ LL | | }; | |_____^ help: try: `Some(3).filter(|&x| x > 0)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:54:5 + --> tests/ui/manual_filter.rs:59:5 | LL | / match y { LL | | @@ -62,7 +62,7 @@ LL | | }; | |_____^ help: try: `y.filter(|&x| x <= 0)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:67:5 + --> tests/ui/manual_filter.rs:72:5 | LL | / match Some(5) { LL | | @@ -74,7 +74,7 @@ LL | | }; | |_____^ help: try: `Some(5).filter(|&x| x > 0)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:79:5 + --> tests/ui/manual_filter.rs:84:5 | LL | / match Some(6) { LL | | @@ -86,7 +86,7 @@ LL | | }; | |_____^ help: try: `Some(6).as_ref().filter(|&x| x > &0)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:92:5 + --> tests/ui/manual_filter.rs:97:5 | LL | / match Some(String::new()) { LL | | @@ -98,7 +98,7 @@ LL | | }; | |_____^ help: try: `Some(String::new()).filter(|x| external_cond)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:104:5 + --> tests/ui/manual_filter.rs:109:5 | LL | / if let Some(x) = Some(7) { LL | | @@ -109,7 +109,7 @@ LL | | }; | |_____^ help: try: `Some(7).filter(|&x| external_cond)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:111:5 + --> tests/ui/manual_filter.rs:116:5 | LL | / match &Some(8) { LL | | @@ -121,7 +121,7 @@ LL | | }; | |_____^ help: try: `Some(8).filter(|&x| x != 0)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:123:5 + --> tests/ui/manual_filter.rs:128:5 | LL | / match Some(9) { LL | | @@ -133,7 +133,7 @@ LL | | }; | |_____^ help: try: `Some(9).filter(|&x| x > 10 && x < 100)` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:150:5 + --> tests/ui/manual_filter.rs:155:5 | LL | / match Some(11) { LL | | @@ -153,7 +153,7 @@ LL ~ }); | error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:195:13 + --> tests/ui/manual_filter.rs:200:13 | LL | let _ = match Some(14) { | _____________^ @@ -166,7 +166,7 @@ LL | | }; | |_____^ help: try: `Some(14).filter(|&x| unsafe { f(x) })` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:206:13 + --> tests/ui/manual_filter.rs:211:13 | LL | let _ = match Some(15) { | _____________^ @@ -177,7 +177,7 @@ LL | | }; | |_____^ help: try: `Some(15).filter(|&x| unsafe { f(x) })` error: manual implementation of `Option::filter` - --> tests/ui/manual_filter.rs:215:12 + --> tests/ui/manual_filter.rs:220:12 | LL | } else if let Some(x) = Some(16) { | ____________^ @@ -189,5 +189,35 @@ LL | | None LL | | }; | |_____^ help: try: `{ Some(16).filter(|&x| x % 2 == 0) }` -error: aborting due to 15 previous errors +error: manual implementation of `Option::filter` + --> tests/ui/manual_filter.rs:303:9 + | +LL | opt.and_then(|x| if x == 0 { None } else { Some(x) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|&x| x != 0)` + +error: manual implementation of `Option::filter` + --> tests/ui/manual_filter.rs:307:9 + | +LL | opt.and_then(move |x| if x == y { Some(x) } else { None }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(move |&x| x == y)` + +error: manual implementation of `Option::filter` + --> tests/ui/manual_filter.rs:311:10 + | +LL | opt1.and_then(|s| if s.len() > 2 { Some(s) } else { None }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|s| s.len() > 2)` + +error: manual implementation of `Option::filter` + --> tests/ui/manual_filter.rs:317:9 + | +LL | opt.and_then(|x| if unsafe { f(x as u32) } { Some(x) } else { None }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|&x| unsafe { f(x as u32) })` + +error: manual implementation of `Option::filter` + --> tests/ui/manual_filter.rs:319:9 + | +LL | opt.and_then(|x| unsafe { if f(x as u32) { Some(x) } else { None } }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|&x| unsafe { f(x as u32) })` + +error: aborting due to 20 previous errors diff --git a/tests/ui/manual_is_power_of_two.fixed b/tests/ui/manual_is_power_of_two.fixed index 8a1ab785dfbfd..8c0074b562a64 100644 --- a/tests/ui/manual_is_power_of_two.fixed +++ b/tests/ui/manual_is_power_of_two.fixed @@ -58,3 +58,15 @@ const fn high_msrv(a: u32) -> bool { a.is_power_of_two() //~^ manual_is_power_of_two } + +fn wrongly_unmangled_macros() { + macro_rules! test_val { + ($val:expr) => { + ($val * 2 + 1) + }; + } + + let a = 16_u64; + let _ = test_val!(a).is_power_of_two(); + //~^ manual_is_power_of_two +} diff --git a/tests/ui/manual_is_power_of_two.rs b/tests/ui/manual_is_power_of_two.rs index 57a3b05e0336a..580a1d0f72814 100644 --- a/tests/ui/manual_is_power_of_two.rs +++ b/tests/ui/manual_is_power_of_two.rs @@ -58,3 +58,15 @@ const fn high_msrv(a: u32) -> bool { a & (a - 1) == 0 //~^ manual_is_power_of_two } + +fn wrongly_unmangled_macros() { + macro_rules! test_val { + ($val:expr) => { + ($val * 2 + 1) + }; + } + + let a = 16_u64; + let _ = test_val!(a).count_ones() == 1; + //~^ manual_is_power_of_two +} diff --git a/tests/ui/manual_is_power_of_two.stderr b/tests/ui/manual_is_power_of_two.stderr index 5781a093d5f2b..e84cd6f42726e 100644 --- a/tests/ui/manual_is_power_of_two.stderr +++ b/tests/ui/manual_is_power_of_two.stderr @@ -55,5 +55,11 @@ error: manually reimplementing `is_power_of_two` LL | a & (a - 1) == 0 | ^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `a.is_power_of_two()` -error: aborting due to 9 previous errors +error: manually reimplementing `is_power_of_two` + --> tests/ui/manual_is_power_of_two.rs:70:13 + | +LL | let _ = test_val!(a).count_ones() == 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `test_val!(a).is_power_of_two()` + +error: aborting due to 10 previous errors diff --git a/tests/ui/manual_noop_waker.rs b/tests/ui/manual_noop_waker.rs index 9b4dd90e273cf..ecfbffeb008f4 100644 --- a/tests/ui/manual_noop_waker.rs +++ b/tests/ui/manual_noop_waker.rs @@ -38,3 +38,28 @@ mod custom_module { fn wake_by_ref(self: &Arc) {} } } + +#[clippy::msrv = "1.84"] +mod msrv_1_84 { + use std::sync::Arc; + use std::task::Wake; + + struct CustomWaker; + impl Wake for CustomWaker { + fn wake(self: Arc) {} + fn wake_by_ref(self: &Arc) {} + } +} + +#[clippy::msrv = "1.85"] +mod msrv_1_85 { + use std::sync::Arc; + use std::task::Wake; + + struct CustomWaker; + impl Wake for CustomWaker { + //~^ manual_noop_waker + fn wake(self: Arc) {} + fn wake_by_ref(self: &Arc) {} + } +} diff --git a/tests/ui/manual_noop_waker.stderr b/tests/ui/manual_noop_waker.stderr index b3b30f96a08f1..4a01a4d0b47aa 100644 --- a/tests/ui/manual_noop_waker.stderr +++ b/tests/ui/manual_noop_waker.stderr @@ -16,5 +16,13 @@ LL | impl Wake for MyWakerPartial { | = help: use `std::task::Waker::noop()` instead -error: aborting due to 2 previous errors +error: manual implementation of a no-op waker + --> tests/ui/manual_noop_waker.rs:60:10 + | +LL | impl Wake for CustomWaker { + | ^^^^ + | + = help: use `std::task::Waker::noop()` instead + +error: aborting due to 3 previous errors diff --git a/tests/ui/manual_rotate.fixed b/tests/ui/manual_rotate.fixed index 1012ffc1aa2d8..17fb4a3122f30 100644 --- a/tests/ui/manual_rotate.fixed +++ b/tests/ui/manual_rotate.fixed @@ -61,3 +61,26 @@ fn issue13028() { // don't lint, because `s` and `u` are different variables, albeit with the same value let _ = (x << s) | (x >> (32 - u)); } + +fn wrongly_unmangled_macros() { + macro_rules! test_expr { + ($val:expr) => { + $val.inner + }; + } + + struct Wrapper { + inner: u32, + } + + let x = Wrapper { inner: 42 }; + let _ = test_expr!(x).rotate_left(3); + //~^ manual_rotate + + let y = Wrapper { inner: 100 }; + + let _ = x.inner.rotate_left(test_expr!(y)); + //~^ manual_rotate + + let _ = (test_expr!(x) << 3) | (x.inner >> 29); +} diff --git a/tests/ui/manual_rotate.rs b/tests/ui/manual_rotate.rs index 3cdc79673c816..5899166f51f78 100644 --- a/tests/ui/manual_rotate.rs +++ b/tests/ui/manual_rotate.rs @@ -61,3 +61,26 @@ fn issue13028() { // don't lint, because `s` and `u` are different variables, albeit with the same value let _ = (x << s) | (x >> (32 - u)); } + +fn wrongly_unmangled_macros() { + macro_rules! test_expr { + ($val:expr) => { + $val.inner + }; + } + + struct Wrapper { + inner: u32, + } + + let x = Wrapper { inner: 42 }; + let _ = (test_expr!(x) << 3) | (test_expr!(x) >> 29); + //~^ manual_rotate + + let y = Wrapper { inner: 100 }; + + let _ = (x.inner << test_expr!(y)) | (x.inner >> (32 - test_expr!(y))); + //~^ manual_rotate + + let _ = (test_expr!(x) << 3) | (x.inner >> 29); +} diff --git a/tests/ui/manual_rotate.stderr b/tests/ui/manual_rotate.stderr index ea04ee028db67..76af443ae89ef 100644 --- a/tests/ui/manual_rotate.stderr +++ b/tests/ui/manual_rotate.stderr @@ -91,5 +91,17 @@ error: there is no need to manually implement bit rotation LL | let _ = (x >> 9) | (x << (32 - 9)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x.rotate_right(9)` -error: aborting due to 15 previous errors +error: there is no need to manually implement bit rotation + --> tests/ui/manual_rotate.rs:77:13 + | +LL | let _ = (test_expr!(x) << 3) | (test_expr!(x) >> 29); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `test_expr!(x).rotate_left(3)` + +error: there is no need to manually implement bit rotation + --> tests/ui/manual_rotate.rs:82:13 + | +LL | let _ = (x.inner << test_expr!(y)) | (x.inner >> (32 - test_expr!(y))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x.inner.rotate_left(test_expr!(y))` + +error: aborting due to 17 previous errors diff --git a/tests/ui/map_flatten.fixed b/tests/ui/map_flatten.fixed index 12fca6706e412..a1af89139e363 100644 --- a/tests/ui/map_flatten.fixed +++ b/tests/ui/map_flatten.fixed @@ -1,5 +1,5 @@ #![warn(clippy::map_flatten)] -#![allow(clippy::unnecessary_filter_map)] +#![allow(clippy::unnecessary_filter_map, clippy::manual_filter)] // issue #8506, multi-line #[rustfmt::skip] diff --git a/tests/ui/map_flatten.rs b/tests/ui/map_flatten.rs index 3f1b7a268822d..563d39a96639d 100644 --- a/tests/ui/map_flatten.rs +++ b/tests/ui/map_flatten.rs @@ -1,5 +1,5 @@ #![warn(clippy::map_flatten)] -#![allow(clippy::unnecessary_filter_map)] +#![allow(clippy::unnecessary_filter_map, clippy::manual_filter)] // issue #8506, multi-line #[rustfmt::skip] diff --git a/tests/ui/map_with_unused_argument_over_ranges.fixed b/tests/ui/map_with_unused_argument_over_ranges.fixed index 254c46ecae389..97cb727488395 100644 --- a/tests/ui/map_with_unused_argument_over_ranges.fixed +++ b/tests/ui/map_with_unused_argument_over_ranges.fixed @@ -89,3 +89,17 @@ fn msrv_1_82() { std::iter::repeat(3).take(10); //~^ map_with_unused_argument_over_ranges } + +fn wrongly_unmangled_macros() { + macro_rules! test { + ($val:expr) => { + ($val * 2 + 1) + }; + } + + std::iter::repeat_with(|| do_something()).take(test!(10)); + //~^ map_with_unused_argument_over_ranges + + std::iter::repeat_n(test!(3), 10); + //~^ map_with_unused_argument_over_ranges +} diff --git a/tests/ui/map_with_unused_argument_over_ranges.rs b/tests/ui/map_with_unused_argument_over_ranges.rs index 05c8d64f8012b..f0222f407b8ca 100644 --- a/tests/ui/map_with_unused_argument_over_ranges.rs +++ b/tests/ui/map_with_unused_argument_over_ranges.rs @@ -89,3 +89,17 @@ fn msrv_1_82() { (0..10).map(|_| 3); //~^ map_with_unused_argument_over_ranges } + +fn wrongly_unmangled_macros() { + macro_rules! test { + ($val:expr) => { + ($val * 2 + 1) + }; + } + + (0..test!(10)).map(|_| do_something()); + //~^ map_with_unused_argument_over_ranges + + (0..10).map(|_| test!(3)); + //~^ map_with_unused_argument_over_ranges +} diff --git a/tests/ui/map_with_unused_argument_over_ranges.stderr b/tests/ui/map_with_unused_argument_over_ranges.stderr index e5c93ceac02aa..ba72a6b9d89f4 100644 --- a/tests/ui/map_with_unused_argument_over_ranges.stderr +++ b/tests/ui/map_with_unused_argument_over_ranges.stderr @@ -223,5 +223,29 @@ LL - (0..10).map(|_| 3); LL + std::iter::repeat(3).take(10); | -error: aborting due to 18 previous errors +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:100:5 + | +LL | (0..test!(10)).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (0..test!(10)).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(test!(10)); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:103:5 + | +LL | (0..10).map(|_| test!(3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_n` + | +LL - (0..10).map(|_| test!(3)); +LL + std::iter::repeat_n(test!(3), 10); + | + +error: aborting due to 20 previous errors diff --git a/tests/ui/needless_bitwise_bool.fixed b/tests/ui/needless_bitwise_bool.fixed index 751d3d2570000..eedeecd8a8cbc 100644 --- a/tests/ui/needless_bitwise_bool.fixed +++ b/tests/ui/needless_bitwise_bool.fixed @@ -40,3 +40,16 @@ fn main() { println!("true") // This is a BinOp with no side effects } } + +fn wrongly_unmangled_macros() { + let (x, y) = (false, true); + macro_rules! inverse { + ($e:expr) => { + !$e + }; + } + if inverse!(y) && inverse!(x) { + //~^ needless_bitwise_bool + println!("true") + } +} diff --git a/tests/ui/needless_bitwise_bool.rs b/tests/ui/needless_bitwise_bool.rs index 5d3ff3b2079cd..5c0a4ccf266c3 100644 --- a/tests/ui/needless_bitwise_bool.rs +++ b/tests/ui/needless_bitwise_bool.rs @@ -40,3 +40,16 @@ fn main() { println!("true") // This is a BinOp with no side effects } } + +fn wrongly_unmangled_macros() { + let (x, y) = (false, true); + macro_rules! inverse { + ($e:expr) => { + !$e + }; + } + if inverse!(y) & inverse!(x) { + //~^ needless_bitwise_bool + println!("true") + } +} diff --git a/tests/ui/needless_bitwise_bool.stderr b/tests/ui/needless_bitwise_bool.stderr index 4f64c71369169..7ee792d90e89b 100644 --- a/tests/ui/needless_bitwise_bool.stderr +++ b/tests/ui/needless_bitwise_bool.stderr @@ -13,5 +13,11 @@ error: use of bitwise operator instead of lazy operator between booleans LL | if y & (0 < 1) { | ^^^^^^^^^^^ help: try: `y && (0 < 1)` -error: aborting due to 2 previous errors +error: use of bitwise operator instead of lazy operator between booleans + --> tests/ui/needless_bitwise_bool.rs:51:8 + | +LL | if inverse!(y) & inverse!(x) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `inverse!(y) && inverse!(x)` + +error: aborting due to 3 previous errors diff --git a/tests/ui/needless_bool/fixable.fixed b/tests/ui/needless_bool/fixable.fixed index 2589819f019b8..0af4f87bec677 100644 --- a/tests/ui/needless_bool/fixable.fixed +++ b/tests/ui/needless_bool/fixable.fixed @@ -167,3 +167,15 @@ fn issue12846() { let _x = a.then(|| todo!()); //~^ needless_bool } + +fn wrongly_unmangled_macros() { + macro_rules! test_expr { + ($val:expr) => { + ($val + 1 > 0) + }; + } + + let x = 42; + test_expr!(x); + //~^^^^^ needless_bool +} diff --git a/tests/ui/needless_bool/fixable.rs b/tests/ui/needless_bool/fixable.rs index f9cc0122f5e7b..2e8719bd041e0 100644 --- a/tests/ui/needless_bool/fixable.rs +++ b/tests/ui/needless_bool/fixable.rs @@ -227,3 +227,19 @@ fn issue12846() { let _x = if a { true } else { false }.then(|| todo!()); //~^ needless_bool } + +fn wrongly_unmangled_macros() { + macro_rules! test_expr { + ($val:expr) => { + ($val + 1 > 0) + }; + } + + let x = 42; + if test_expr!(x) { + true + } else { + false + }; + //~^^^^^ needless_bool +} diff --git a/tests/ui/needless_bool/fixable.stderr b/tests/ui/needless_bool/fixable.stderr index 9404d07ba0e08..e15260242f4dd 100644 --- a/tests/ui/needless_bool/fixable.stderr +++ b/tests/ui/needless_bool/fixable.stderr @@ -209,5 +209,15 @@ error: this if-then-else expression returns a bool literal LL | let _x = if a { true } else { false }.then(|| todo!()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `a` -error: aborting due to 24 previous errors +error: this if-then-else expression returns a bool literal + --> tests/ui/needless_bool/fixable.rs:239:5 + | +LL | / if test_expr!(x) { +LL | | true +LL | | } else { +LL | | false +LL | | }; + | |_____^ help: you can reduce it to: `test_expr!(x)` + +error: aborting due to 25 previous errors diff --git a/tests/ui/needless_borrowed_ref.fixed b/tests/ui/needless_borrowed_ref.fixed index 6d74899218124..94f8011185830 100644 --- a/tests/ui/needless_borrowed_ref.fixed +++ b/tests/ui/needless_borrowed_ref.fixed @@ -4,7 +4,8 @@ irrefutable_let_patterns, non_shorthand_field_patterns, clippy::needless_borrow, - clippy::needless_ifs + clippy::needless_ifs, + clippy::unneeded_wildcard_pattern )] fn main() {} diff --git a/tests/ui/needless_borrowed_ref.rs b/tests/ui/needless_borrowed_ref.rs index a4cb899231647..77334e07b25e4 100644 --- a/tests/ui/needless_borrowed_ref.rs +++ b/tests/ui/needless_borrowed_ref.rs @@ -4,7 +4,8 @@ irrefutable_let_patterns, non_shorthand_field_patterns, clippy::needless_borrow, - clippy::needless_ifs + clippy::needless_ifs, + clippy::unneeded_wildcard_pattern )] fn main() {} diff --git a/tests/ui/needless_borrowed_ref.stderr b/tests/ui/needless_borrowed_ref.stderr index bfa3cafdedeb1..f29789c66334a 100644 --- a/tests/ui/needless_borrowed_ref.stderr +++ b/tests/ui/needless_borrowed_ref.stderr @@ -1,5 +1,5 @@ error: this pattern takes a reference on something that is being dereferenced - --> tests/ui/needless_borrowed_ref.rs:30:34 + --> tests/ui/needless_borrowed_ref.rs:31:34 | LL | let _ = v.iter_mut().filter(|&ref a| a.is_empty()); | ^^^^^^ @@ -13,7 +13,7 @@ LL + let _ = v.iter_mut().filter(|a| a.is_empty()); | error: this pattern takes a reference on something that is being dereferenced - --> tests/ui/needless_borrowed_ref.rs:35:17 + --> tests/ui/needless_borrowed_ref.rs:36:17 | LL | if let Some(&ref v) = thingy {} | ^^^^^^ @@ -25,7 +25,7 @@ LL + if let Some(v) = thingy {} | error: this pattern takes a reference on something that is being dereferenced - --> tests/ui/needless_borrowed_ref.rs:38:14 + --> tests/ui/needless_borrowed_ref.rs:39:14 | LL | if let &[&ref a, ref b] = slice_of_refs {} | ^^^^^^ @@ -37,7 +37,7 @@ LL + if let &[a, ref b] = slice_of_refs {} | error: dereferencing a slice pattern where every element takes a reference - --> tests/ui/needless_borrowed_ref.rs:41:9 + --> tests/ui/needless_borrowed_ref.rs:42:9 | LL | let &[ref a, ..] = &array; | ^^^^^^^^^^^^ @@ -49,7 +49,7 @@ LL + let [a, ..] = &array; | error: dereferencing a slice pattern where every element takes a reference - --> tests/ui/needless_borrowed_ref.rs:43:9 + --> tests/ui/needless_borrowed_ref.rs:44:9 | LL | let &[ref a, ref b, ..] = &array; | ^^^^^^^^^^^^^^^^^^^ @@ -61,7 +61,7 @@ LL + let [a, b, ..] = &array; | error: dereferencing a slice pattern where every element takes a reference - --> tests/ui/needless_borrowed_ref.rs:46:12 + --> tests/ui/needless_borrowed_ref.rs:47:12 | LL | if let &[ref a, ref b] = slice {} | ^^^^^^^^^^^^^^^ @@ -73,7 +73,7 @@ LL + if let [a, b] = slice {} | error: dereferencing a slice pattern where every element takes a reference - --> tests/ui/needless_borrowed_ref.rs:48:12 + --> tests/ui/needless_borrowed_ref.rs:49:12 | LL | if let &[ref a, ref b] = &vec[..] {} | ^^^^^^^^^^^^^^^ @@ -85,7 +85,7 @@ LL + if let [a, b] = &vec[..] {} | error: dereferencing a slice pattern where every element takes a reference - --> tests/ui/needless_borrowed_ref.rs:51:12 + --> tests/ui/needless_borrowed_ref.rs:52:12 | LL | if let &[ref a, ref b, ..] = slice {} | ^^^^^^^^^^^^^^^^^^^ @@ -97,7 +97,7 @@ LL + if let [a, b, ..] = slice {} | error: dereferencing a slice pattern where every element takes a reference - --> tests/ui/needless_borrowed_ref.rs:53:12 + --> tests/ui/needless_borrowed_ref.rs:54:12 | LL | if let &[ref a, .., ref b] = slice {} | ^^^^^^^^^^^^^^^^^^^ @@ -109,7 +109,7 @@ LL + if let [a, .., b] = slice {} | error: dereferencing a slice pattern where every element takes a reference - --> tests/ui/needless_borrowed_ref.rs:55:12 + --> tests/ui/needless_borrowed_ref.rs:56:12 | LL | if let &[.., ref a, ref b] = slice {} | ^^^^^^^^^^^^^^^^^^^ @@ -121,7 +121,7 @@ LL + if let [.., a, b] = slice {} | error: dereferencing a slice pattern where every element takes a reference - --> tests/ui/needless_borrowed_ref.rs:58:12 + --> tests/ui/needless_borrowed_ref.rs:59:12 | LL | if let &[ref a, _] = slice {} | ^^^^^^^^^^^ @@ -133,7 +133,7 @@ LL + if let [a, _] = slice {} | error: dereferencing a tuple pattern where every element takes a reference - --> tests/ui/needless_borrowed_ref.rs:61:12 + --> tests/ui/needless_borrowed_ref.rs:62:12 | LL | if let &(ref a, ref b, ref c) = &tuple {} | ^^^^^^^^^^^^^^^^^^^^^^ @@ -145,7 +145,7 @@ LL + if let (a, b, c) = &tuple {} | error: dereferencing a tuple pattern where every element takes a reference - --> tests/ui/needless_borrowed_ref.rs:63:12 + --> tests/ui/needless_borrowed_ref.rs:64:12 | LL | if let &(ref a, _, ref c) = &tuple {} | ^^^^^^^^^^^^^^^^^^ @@ -157,7 +157,7 @@ LL + if let (a, _, c) = &tuple {} | error: dereferencing a tuple pattern where every element takes a reference - --> tests/ui/needless_borrowed_ref.rs:65:12 + --> tests/ui/needless_borrowed_ref.rs:66:12 | LL | if let &(ref a, ..) = &tuple {} | ^^^^^^^^^^^^ @@ -169,7 +169,7 @@ LL + if let (a, ..) = &tuple {} | error: dereferencing a tuple pattern where every element takes a reference - --> tests/ui/needless_borrowed_ref.rs:68:12 + --> tests/ui/needless_borrowed_ref.rs:69:12 | LL | if let &TupleStruct(ref a, ..) = &tuple_struct {} | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -181,7 +181,7 @@ LL + if let TupleStruct(a, ..) = &tuple_struct {} | error: dereferencing a struct pattern where every field's pattern takes a reference - --> tests/ui/needless_borrowed_ref.rs:71:12 + --> tests/ui/needless_borrowed_ref.rs:72:12 | LL | if let &Struct { | ____________^ @@ -202,7 +202,7 @@ LL ~ c: renamed, | error: dereferencing a struct pattern where every field's pattern takes a reference - --> tests/ui/needless_borrowed_ref.rs:79:12 + --> tests/ui/needless_borrowed_ref.rs:80:12 | LL | if let &Struct { ref a, b: _, .. } = &s {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/needless_match.fixed b/tests/ui/needless_match.fixed index 57273012a192b..2d7b0fa2bb2c9 100644 --- a/tests/ui/needless_match.fixed +++ b/tests/ui/needless_match.fixed @@ -1,5 +1,5 @@ #![warn(clippy::needless_match)] -#![allow(clippy::manual_map)] +#![allow(clippy::manual_map, clippy::question_mark)] #![allow(dead_code)] #![allow(unused)] #[derive(Clone, Copy)] diff --git a/tests/ui/needless_match.rs b/tests/ui/needless_match.rs index 3eb577868f2b4..83e3e73feb6c5 100644 --- a/tests/ui/needless_match.rs +++ b/tests/ui/needless_match.rs @@ -1,5 +1,5 @@ #![warn(clippy::needless_match)] -#![allow(clippy::manual_map)] +#![allow(clippy::manual_map, clippy::question_mark)] #![allow(dead_code)] #![allow(unused)] #[derive(Clone, Copy)] diff --git a/tests/ui/println_empty_string.fixed b/tests/ui/println_empty_string.fixed index 2c2901bc715a6..3a31cbc041340 100644 --- a/tests/ui/println_empty_string.fixed +++ b/tests/ui/println_empty_string.fixed @@ -39,3 +39,28 @@ fn issue_16167() { //~^ println_empty_string } } + +#[rustfmt::skip] +fn issue_16843() { + println!{}; + //~^ println_empty_string + + println![]; + //~^ println_empty_string + + eprintln!{}; + //~^ println_empty_string + + eprintln![]; + //~^ println_empty_string + + match "a" { + _ => println!{}, + //~^ println_empty_string + } + + match "a" { + _ => println![], + //~^ println_empty_string + } +} diff --git a/tests/ui/println_empty_string.rs b/tests/ui/println_empty_string.rs index bc2971f54f2cd..79309080131f9 100644 --- a/tests/ui/println_empty_string.rs +++ b/tests/ui/println_empty_string.rs @@ -43,3 +43,28 @@ fn issue_16167() { //~^ println_empty_string } } + +#[rustfmt::skip] +fn issue_16843() { + println!{""}; + //~^ println_empty_string + + println![""]; + //~^ println_empty_string + + eprintln!{""}; + //~^ println_empty_string + + eprintln![""]; + //~^ println_empty_string + + match "a" { + _ => println!{""}, + //~^ println_empty_string + } + + match "a" { + _ => println![""], + //~^ println_empty_string + } +} diff --git a/tests/ui/println_empty_string.stderr b/tests/ui/println_empty_string.stderr index bdac1bb3b8ef9..82ef3378f1100 100644 --- a/tests/ui/println_empty_string.stderr +++ b/tests/ui/println_empty_string.stderr @@ -70,5 +70,53 @@ LL | _ => eprintln!("" ,), // tab and space between "" and comma | | | help: remove the empty string -error: aborting due to 8 previous errors +error: empty string literal in `println!` + --> tests/ui/println_empty_string.rs:49:5 + | +LL | println!{""}; + | ^^^^^^^^^--^ + | | + | help: remove the empty string + +error: empty string literal in `println!` + --> tests/ui/println_empty_string.rs:52:5 + | +LL | println![""]; + | ^^^^^^^^^--^ + | | + | help: remove the empty string + +error: empty string literal in `eprintln!` + --> tests/ui/println_empty_string.rs:55:5 + | +LL | eprintln!{""}; + | ^^^^^^^^^^--^ + | | + | help: remove the empty string + +error: empty string literal in `eprintln!` + --> tests/ui/println_empty_string.rs:58:5 + | +LL | eprintln![""]; + | ^^^^^^^^^^--^ + | | + | help: remove the empty string + +error: empty string literal in `println!` + --> tests/ui/println_empty_string.rs:62:14 + | +LL | _ => println!{""}, + | ^^^^^^^^^--^ + | | + | help: remove the empty string + +error: empty string literal in `println!` + --> tests/ui/println_empty_string.rs:67:14 + | +LL | _ => println![""], + | ^^^^^^^^^--^ + | | + | help: remove the empty string + +error: aborting due to 14 previous errors diff --git a/tests/ui/ptr_offset_by_literal.fixed b/tests/ui/ptr_offset_by_literal.fixed index bd9e41def938d..174616b1e1513 100644 --- a/tests/ui/ptr_offset_by_literal.fixed +++ b/tests/ui/ptr_offset_by_literal.fixed @@ -1,5 +1,5 @@ #![warn(clippy::ptr_offset_by_literal)] -#![allow(clippy::inconsistent_digit_grouping)] +#![allow(clippy::inconsistent_digit_grouping, clippy::byte_char_slices)] fn main() { let arr = [b'a', b'b', b'c']; diff --git a/tests/ui/ptr_offset_by_literal.rs b/tests/ui/ptr_offset_by_literal.rs index b8e3f9b26c68f..d3202cbff9826 100644 --- a/tests/ui/ptr_offset_by_literal.rs +++ b/tests/ui/ptr_offset_by_literal.rs @@ -1,5 +1,5 @@ #![warn(clippy::ptr_offset_by_literal)] -#![allow(clippy::inconsistent_digit_grouping)] +#![allow(clippy::inconsistent_digit_grouping, clippy::byte_char_slices)] fn main() { let arr = [b'a', b'b', b'c']; diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index e209da5c82582..bf4b4ff0a21ed 100644 --- a/tests/ui/question_mark.fixed +++ b/tests/ui/question_mark.fixed @@ -1,5 +1,10 @@ #![feature(try_blocks)] -#![allow(clippy::unnecessary_wraps, clippy::no_effect)] +#![allow( + clippy::unnecessary_wraps, + clippy::no_effect, + clippy::needless_return, + clippy::toplevel_ref_arg +)] use std::sync::MutexGuard; @@ -110,10 +115,7 @@ fn func() -> Option { let _val = f()?; - let s: &str = match &Some(String::new()) { - Some(v) => v, - None => return None, - }; + let s: &str = &Some(String::new())?; f()?; @@ -124,12 +126,10 @@ fn func() -> Option { None => return opt_none!(), }; - match f() { - Some(val) => { - println!("{val}"); - val - }, - None => return None, + { + let val = f()?; + println!("{val}"); + val }; Some(0) @@ -537,3 +537,30 @@ fn issue16654() -> Result<(), i32> { Ok(()) } + +#[rustfmt::skip] +fn issue16751(mut v: Option) -> Option { + let _ = { + let n = &v?; + println!("{n}"); + Some(42) + }; + + let _ = { + let ref mut n = v?; + println!("{n}"); + Some(42) + }; + + let _ = { + let ref mut n = v?; + //~^ question_mark + println!("{n}"); + 42 + }; + + { + let n = v?; + if n > 10 { Some(42) } else { None } + } +} diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs index 579b51461d138..93f76f16576c6 100644 --- a/tests/ui/question_mark.rs +++ b/tests/ui/question_mark.rs @@ -1,5 +1,10 @@ #![feature(try_blocks)] -#![allow(clippy::unnecessary_wraps, clippy::no_effect)] +#![allow( + clippy::unnecessary_wraps, + clippy::no_effect, + clippy::needless_return, + clippy::toplevel_ref_arg +)] use std::sync::MutexGuard; @@ -156,6 +161,7 @@ fn func() -> Option { }; let s: &str = match &Some(String::new()) { + //~^ question_mark Some(v) => v, None => return None, }; @@ -178,6 +184,7 @@ fn func() -> Option { }; match f() { + //~^ question_mark Some(val) => { println!("{val}"); val @@ -668,3 +675,38 @@ fn issue16654() -> Result<(), i32> { Ok(()) } + +#[rustfmt::skip] +fn issue16751(mut v: Option) -> Option { + let _ = match &v { + //~^ question_mark + Some(n) => { + println!("{n}"); + Some(42) + } + None => return None, + }; + + let _ = match v { + //~^ question_mark + Some(ref mut n) => { + println!("{n}"); + Some(42) + } + None => return None, + }; + + let _ = if let Some(ref mut n) = v { + //~^ question_mark + println!("{n}"); + 42 + } else { + return None; + }; + + match v { + //~^ question_mark + Some(n) => if n > 10 { Some(42) } else { None }, + None => return None, + } +} diff --git a/tests/ui/question_mark.stderr b/tests/ui/question_mark.stderr index 1d7f665a2662e..9d7cfc2057641 100644 --- a/tests/ui/question_mark.stderr +++ b/tests/ui/question_mark.stderr @@ -1,5 +1,5 @@ error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:7:5 + --> tests/ui/question_mark.rs:12:5 | LL | / if a.is_none() { LL | | @@ -11,7 +11,7 @@ LL | | } = help: to override `-D warnings` add `#[allow(clippy::question_mark)]` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:53:9 + --> tests/ui/question_mark.rs:58:9 | LL | / if (self.opt).is_none() { LL | | @@ -20,7 +20,7 @@ LL | | } | |_________^ help: replace it with: `(self.opt)?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:58:9 + --> tests/ui/question_mark.rs:63:9 | LL | / if self.opt.is_none() { LL | | @@ -29,7 +29,7 @@ LL | | } | |_________^ help: replace it with: `self.opt?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:63:17 + --> tests/ui/question_mark.rs:68:17 | LL | let _ = if self.opt.is_none() { | _________________^ @@ -41,7 +41,7 @@ LL | | }; | |_________^ help: replace it with: `Some(self.opt?)` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:70:17 + --> tests/ui/question_mark.rs:75:17 | LL | let _ = if let Some(x) = self.opt { | _________________^ @@ -53,7 +53,7 @@ LL | | }; | |_________^ help: replace it with: `self.opt?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:88:9 + --> tests/ui/question_mark.rs:93:9 | LL | / if self.opt.is_none() { LL | | @@ -62,7 +62,7 @@ LL | | } | |_________^ help: replace it with: `self.opt.as_ref()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:97:9 + --> tests/ui/question_mark.rs:102:9 | LL | / if self.opt.is_none() { LL | | @@ -71,7 +71,7 @@ LL | | } | |_________^ help: replace it with: `self.opt.as_ref()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:106:9 + --> tests/ui/question_mark.rs:111:9 | LL | / if self.opt.is_none() { LL | | @@ -80,7 +80,7 @@ LL | | } | |_________^ help: replace it with: `self.opt.as_ref()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:114:26 + --> tests/ui/question_mark.rs:119:26 | LL | let v: &Vec<_> = if let Some(ref v) = self.opt { | __________________________^ @@ -92,7 +92,7 @@ LL | | }; | |_________^ help: replace it with: `self.opt.as_ref()?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:125:17 + --> tests/ui/question_mark.rs:130:17 | LL | let v = if let Some(v) = self.opt { | _________________^ @@ -104,7 +104,7 @@ LL | | }; | |_________^ help: replace it with: `self.opt?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:147:5 + --> tests/ui/question_mark.rs:152:5 | LL | / if f().is_none() { LL | | @@ -113,7 +113,7 @@ LL | | } | |_____^ help: replace it with: `f()?;` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:152:16 + --> tests/ui/question_mark.rs:157:16 | LL | let _val = match f() { | ________________^ @@ -124,7 +124,18 @@ LL | | }; | |_____^ help: try instead: `f()?` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:163:5 + --> tests/ui/question_mark.rs:163:19 + | +LL | let s: &str = match &Some(String::new()) { + | ___________________^ +LL | | +LL | | Some(v) => v, +LL | | None => return None, +LL | | }; + | |_____^ help: try instead: `&Some(String::new())?` + +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:169:5 | LL | / match f() { LL | | @@ -134,7 +145,7 @@ LL | | }; | |_____^ help: try instead: `f()?` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:169:5 + --> tests/ui/question_mark.rs:175:5 | LL | / match opt_none!() { LL | | @@ -143,14 +154,35 @@ LL | | None => return None, LL | | }; | |_____^ help: try instead: `opt_none!()?` +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:186:5 + | +LL | / match f() { +LL | | +LL | | Some(val) => { +LL | | println!("{val}"); +... | +LL | | None => return None, +LL | | }; + | |_____^ + | +help: try instead + | +LL ~ { +LL + let val = f()?; +LL + println!("{val}"); +LL + val +LL ~ }; + | + error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:196:13 + --> tests/ui/question_mark.rs:203:13 | LL | let _ = if let Ok(x) = x { x } else { return x }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:199:5 + --> tests/ui/question_mark.rs:206:5 | LL | / if x.is_err() { LL | | @@ -159,7 +191,7 @@ LL | | } | |_____^ help: replace it with: `x?;` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:204:16 + --> tests/ui/question_mark.rs:211:16 | LL | let _val = match func_returning_result() { | ________________^ @@ -170,7 +202,7 @@ LL | | }; | |_____^ help: try instead: `func_returning_result()?` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:210:5 + --> tests/ui/question_mark.rs:217:5 | LL | / match func_returning_result() { LL | | @@ -180,7 +212,7 @@ LL | | }; | |_____^ help: try instead: `func_returning_result()?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:302:5 + --> tests/ui/question_mark.rs:309:5 | LL | / if let Err(err) = func_returning_result() { LL | | @@ -189,7 +221,7 @@ LL | | } | |_____^ help: replace it with: `func_returning_result()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:310:5 + --> tests/ui/question_mark.rs:317:5 | LL | / if let Err(err) = func_returning_result() { LL | | @@ -198,7 +230,7 @@ LL | | } | |_____^ help: replace it with: `func_returning_result()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:393:13 + --> tests/ui/question_mark.rs:400:13 | LL | / if a.is_none() { LL | | @@ -208,7 +240,7 @@ LL | | } | |_____________^ help: replace it with: `a?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:454:5 + --> tests/ui/question_mark.rs:461:5 | LL | / let Some(v) = bar.foo.owned.clone() else { LL | | return None; @@ -216,7 +248,7 @@ LL | | }; | |______^ help: replace it with: `let v = bar.foo.owned.clone()?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:469:5 + --> tests/ui/question_mark.rs:476:5 | LL | / let Some(ref x) = foo.opt_x else { LL | | return None; @@ -224,7 +256,7 @@ LL | | }; | |______^ help: replace it with: `let x = foo.opt_x.as_ref()?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:479:5 + --> tests/ui/question_mark.rs:486:5 | LL | / let Some(ref mut x) = foo.opt_x else { LL | | return None; @@ -232,7 +264,7 @@ LL | | }; | |______^ help: replace it with: `let x = foo.opt_x.as_mut()?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:490:5 + --> tests/ui/question_mark.rs:497:5 | LL | / let Some(ref x @ ref y) = foo.opt_x else { LL | | return None; @@ -240,7 +272,7 @@ LL | | }; | |______^ help: replace it with: `let x @ y = foo.opt_x.as_ref()?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:494:5 + --> tests/ui/question_mark.rs:501:5 | LL | / let Some(ref x @ WrapperStructWithString(_)) = bar else { LL | | return None; @@ -248,7 +280,7 @@ LL | | }; | |______^ help: replace it with: `let x @ &WrapperStructWithString(_) = bar.as_ref()?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:498:5 + --> tests/ui/question_mark.rs:505:5 | LL | / let Some(ref mut x @ WrapperStructWithString(_)) = bar else { LL | | return None; @@ -256,7 +288,7 @@ LL | | }; | |______^ help: replace it with: `let x @ &mut WrapperStructWithString(_) = bar.as_mut()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:520:5 + --> tests/ui/question_mark.rs:527:5 | LL | / if arg.is_none() { LL | | @@ -265,7 +297,7 @@ LL | | } | |_____^ help: replace it with: `arg?;` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:524:15 + --> tests/ui/question_mark.rs:531:15 | LL | let val = match arg { | _______________^ @@ -276,7 +308,7 @@ LL | | }; | |_____^ help: try instead: `arg?` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:534:5 + --> tests/ui/question_mark.rs:541:5 | LL | / let Some(a) = *a else { LL | | return None; @@ -284,7 +316,7 @@ LL | | }; | |______^ help: replace it with: `let a = (*a)?;` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:566:5 + --> tests/ui/question_mark.rs:573:5 | LL | / match some_result { LL | | @@ -294,7 +326,7 @@ LL | | }; | |_____^ help: try instead: `some_result?` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:572:5 + --> tests/ui/question_mark.rs:579:5 | LL | / match some_result { LL | | @@ -304,7 +336,7 @@ LL | | }; | |_____^ help: try instead: `some_result?` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:578:5 + --> tests/ui/question_mark.rs:585:5 | LL | / match some_result { LL | | @@ -314,7 +346,7 @@ LL | | }; | |_____^ help: try instead: `some_result?` error: this `match` expression can be replaced with `?` - --> tests/ui/question_mark.rs:596:17 + --> tests/ui/question_mark.rs:603:17 | LL | let x = match result { | _________________^ @@ -325,7 +357,7 @@ LL | | }; | |_________^ help: try instead: `result?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:609:9 + --> tests/ui/question_mark.rs:616:9 | LL | / if let Err(reason) = result { LL | | @@ -334,7 +366,7 @@ LL | | } | |_________^ help: replace it with: `result?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:626:5 + --> tests/ui/question_mark.rs:633:5 | LL | / let Some(x) = test_expr!(42) else { LL | | return None; @@ -342,7 +374,7 @@ LL | | }; | |______^ help: replace it with: `let x = test_expr!(42)?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:632:5 + --> tests/ui/question_mark.rs:639:5 | LL | / if test_expr!(42).is_none() { LL | | @@ -351,7 +383,7 @@ LL | | } | |_____^ help: replace it with: `test_expr!(42)?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:643:12 + --> tests/ui/question_mark.rs:650:12 | LL | } else if let Some(x) = a { | ____________^ @@ -363,7 +395,7 @@ LL | | }; | |_____^ help: replace it with: `{ a? }` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:658:9 + --> tests/ui/question_mark.rs:665:9 | LL | / if let Err(err) = result { LL | | @@ -372,7 +404,7 @@ LL | | } | |_________^ help: replace it with: `result?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:664:10 + --> tests/ui/question_mark.rs:671:10 | LL | _ = [if let Err(err) = result { | __________^ @@ -381,5 +413,90 @@ LL | | return Err(err); LL | | }]; | |_____^ help: replace it with: `{ result?; }` -error: aborting due to 40 previous errors +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:681:13 + | +LL | let _ = match &v { + | _____________^ +LL | | +LL | | Some(n) => { +LL | | println!("{n}"); +... | +LL | | None => return None, +LL | | }; + | |_____^ + | +help: try instead + | +LL ~ let _ = { +LL + let n = &v?; +LL + println!("{n}"); +LL + Some(42) +LL ~ }; + | + +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:690:13 + | +LL | let _ = match v { + | _____________^ +LL | | +LL | | Some(ref mut n) => { +LL | | println!("{n}"); +... | +LL | | None => return None, +LL | | }; + | |_____^ + | +help: try instead + | +LL ~ let _ = { +LL + let ref mut n = v?; +LL + println!("{n}"); +LL + Some(42) +LL ~ }; + | + +error: this block may be rewritten with the `?` operator + --> tests/ui/question_mark.rs:699:13 + | +LL | let _ = if let Some(ref mut n) = v { + | _____________^ +LL | | +LL | | println!("{n}"); +LL | | 42 +LL | | } else { +LL | | return None; +LL | | }; + | |_____^ + | +help: replace it with + | +LL ~ let _ = { +LL + let ref mut n = v?; +LL + +LL + println!("{n}"); +LL + 42 +LL ~ }; + | + +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:707:5 + | +LL | / match v { +LL | | +LL | | Some(n) => if n > 10 { Some(42) } else { None }, +LL | | None => return None, +LL | | } + | |_____^ + | +help: try instead + | +LL ~ { +LL + let n = v?; +LL + if n > 10 { Some(42) } else { None } +LL + } + | + +error: aborting due to 46 previous errors diff --git a/tests/ui/range_plus_minus_one.fixed b/tests/ui/range_plus_minus_one.fixed index 5c6da6d5aed3a..e07a0e07368b5 100644 --- a/tests/ui/range_plus_minus_one.fixed +++ b/tests/ui/range_plus_minus_one.fixed @@ -180,3 +180,16 @@ fn issue9908_2(n: usize) -> usize { (1..n).sum() //~^ range_minus_one } + +fn wrongly_unmangled_macros() { + macro_rules! test { + ($val:expr) => { + ($val * 2 + 0) + }; + } + + let x = 5usize; + for _ in test!(x)..=test!(x) { + //~^ range_plus_one + } +} diff --git a/tests/ui/range_plus_minus_one.rs b/tests/ui/range_plus_minus_one.rs index 7172da6034b83..3e6e4f629a51a 100644 --- a/tests/ui/range_plus_minus_one.rs +++ b/tests/ui/range_plus_minus_one.rs @@ -180,3 +180,16 @@ fn issue9908_2(n: usize) -> usize { (1..=n - 1).sum() //~^ range_minus_one } + +fn wrongly_unmangled_macros() { + macro_rules! test { + ($val:expr) => { + ($val * 2 + 0) + }; + } + + let x = 5usize; + for _ in test!(x)..test!(x) + 1 { + //~^ range_plus_one + } +} diff --git a/tests/ui/range_plus_minus_one.stderr b/tests/ui/range_plus_minus_one.stderr index 60abe50efa10a..79c482aeaa6bb 100644 --- a/tests/ui/range_plus_minus_one.stderr +++ b/tests/ui/range_plus_minus_one.stderr @@ -88,5 +88,11 @@ LL | (1..=n - 1).sum() = 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 +error: an inclusive range would be more readable + --> tests/ui/range_plus_minus_one.rs:192:14 + | +LL | for _ in test!(x)..test!(x) + 1 { + | ^^^^^^^^^^^^^^^^^^^^^^ help: use: `test!(x)..=test!(x)` + +error: aborting due to 15 previous errors diff --git a/tests/ui/return_and_then.fixed b/tests/ui/return_and_then.fixed index 8ee259b97f3d4..be9a8e1da5c6b 100644 --- a/tests/ui/return_and_then.fixed +++ b/tests/ui/return_and_then.fixed @@ -1,4 +1,5 @@ #![warn(clippy::return_and_then)] +#![allow(clippy::manual_filter)] fn main() { fn test_opt_block(opt: Option) -> Option { diff --git a/tests/ui/return_and_then.rs b/tests/ui/return_and_then.rs index dcb344f142bb6..5d8372e418bf5 100644 --- a/tests/ui/return_and_then.rs +++ b/tests/ui/return_and_then.rs @@ -1,4 +1,5 @@ #![warn(clippy::return_and_then)] +#![allow(clippy::manual_filter)] fn main() { fn test_opt_block(opt: Option) -> Option { diff --git a/tests/ui/return_and_then.stderr b/tests/ui/return_and_then.stderr index 33867ea818aa7..37d92a22bf8bc 100644 --- a/tests/ui/return_and_then.stderr +++ b/tests/ui/return_and_then.stderr @@ -1,5 +1,5 @@ error: use the `?` operator instead of an `and_then` call - --> tests/ui/return_and_then.rs:5:9 + --> tests/ui/return_and_then.rs:6:9 | LL | / opt.and_then(|n| { LL | | @@ -21,7 +21,7 @@ LL + if n > 1 { Some(ret) } else { None } | error: use the `?` operator instead of an `and_then` call - --> tests/ui/return_and_then.rs:14:9 + --> tests/ui/return_and_then.rs:15:9 | LL | opt.and_then(|n| test_opt_block(Some(n))) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -33,7 +33,7 @@ LL + test_opt_block(Some(n)) | error: use the `?` operator instead of an `and_then` call - --> tests/ui/return_and_then.rs:19:9 + --> tests/ui/return_and_then.rs:20:9 | LL | gen_option(1).and_then(|n| test_opt_block(Some(n))) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -45,7 +45,7 @@ LL + test_opt_block(Some(n)) | error: use the `?` operator instead of an `and_then` call - --> tests/ui/return_and_then.rs:24:9 + --> tests/ui/return_and_then.rs:25:9 | LL | opt.and_then(|n| if n > 1 { Ok(n + 1) } else { Err(n) }) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -57,7 +57,7 @@ LL + if n > 1 { Ok(n + 1) } else { Err(n) } | error: use the `?` operator instead of an `and_then` call - --> tests/ui/return_and_then.rs:29:9 + --> tests/ui/return_and_then.rs:30:9 | LL | opt.and_then(|n| test_res_block(Ok(n))) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -69,7 +69,7 @@ LL + test_res_block(Ok(n)) | error: use the `?` operator instead of an `and_then` call - --> tests/ui/return_and_then.rs:35:9 + --> tests/ui/return_and_then.rs:36:9 | LL | Some("").and_then(|x| if x.len() > 2 { Some(3) } else { None }) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -81,7 +81,7 @@ LL + if x.len() > 2 { Some(3) } else { None } | error: use the `?` operator instead of an `and_then` call - --> tests/ui/return_and_then.rs:41:9 + --> tests/ui/return_and_then.rs:42:9 | LL | / Some(match (vec![1, 2, 3], vec![1, 2, 4]) { LL | | @@ -102,7 +102,7 @@ LL + if x.len() > 2 { Some(3) } else { None } | error: use the `?` operator instead of an `and_then` call - --> tests/ui/return_and_then.rs:69:13 + --> tests/ui/return_and_then.rs:70:13 | LL | Some("").and_then(|x| if x.len() > 2 { Some(3) } else { None }) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -114,7 +114,7 @@ LL + if x.len() > 2 { Some(3) } else { None } | error: use the `?` operator instead of an `and_then` call - --> tests/ui/return_and_then.rs:77:20 + --> tests/ui/return_and_then.rs:78:20 | LL | return Some("").and_then(|x| if x.len() > 2 { Some(3) } else { None }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -128,7 +128,7 @@ LL ~ }; | error: use the `?` operator instead of an `and_then` call - --> tests/ui/return_and_then.rs:85:20 + --> tests/ui/return_and_then.rs:86:20 | LL | return Some("").and_then(|mut x| { | ____________________^ @@ -147,7 +147,7 @@ LL ~ }; | error: use the `?` operator instead of an `and_then` call - --> tests/ui/return_and_then.rs:97:20 + --> tests/ui/return_and_then.rs:98:20 | LL | return Some("").and_then(|x| if x.len() > 2 { Some(3) } else { None }), | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -161,7 +161,7 @@ LL ~ }, | error: use the `?` operator instead of an `and_then` call - --> tests/ui/return_and_then.rs:105:13 + --> tests/ui/return_and_then.rs:106:13 | LL | i.and_then(|i| if i > 3 { Some(i) } else { None }) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -173,7 +173,7 @@ LL + if i > 3 { Some(i) } else { None } | error: use the `?` operator instead of an `and_then` call - --> tests/ui/return_and_then.rs:114:22 + --> tests/ui/return_and_then.rs:115:22 | LL | 1 | 2 => i.and_then(|i| if i > 3 { Some(i) } else { None }), | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -187,7 +187,7 @@ LL ~ }, | error: use the `?` operator instead of an `and_then` call - --> tests/ui/return_and_then.rs:126:21 + --> tests/ui/return_and_then.rs:127:21 | LL | i.and_then(|i| if i > 3 { Some(i) } else { None }) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -199,7 +199,7 @@ LL + if i > 3 { Some(i) } else { None } | error: use the `?` operator instead of an `and_then` call - --> tests/ui/return_and_then.rs:141:23 + --> tests/ui/return_and_then.rs:142:23 | LL | break i.and_then(|i| if i > 3 { Some(i) } else { None }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -213,7 +213,7 @@ LL ~ }); | error: use the `?` operator instead of an `and_then` call - --> tests/ui/return_and_then.rs:146:32 + --> tests/ui/return_and_then.rs:147:32 | LL | break 'foo i.and_then(|i| if i > 3 { Some(i) } else { None }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -227,7 +227,7 @@ LL ~ }); | error: use the `?` operator instead of an `and_then` call - --> tests/ui/return_and_then.rs:151:28 + --> tests/ui/return_and_then.rs:152:28 | LL | break 'bar i.and_then(|i| if i > 3 { Some(i) } else { None }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/swap.fixed b/tests/ui/swap.fixed index 6a64e64e98fa2..0d968e96ff794 100644 --- a/tests/ui/swap.fixed +++ b/tests/ui/swap.fixed @@ -195,3 +195,14 @@ const fn issue_10421(x: u32) -> u32 { let a = a; a } + +fn wrongly_unmangled_macros() { + macro_rules! test_slice { + ($val:expr) => { + *&mut $val + }; + } + + let mut foo = [1, 2]; + test_slice!(foo).swap(0, 1); +} diff --git a/tests/ui/swap.rs b/tests/ui/swap.rs index e2d89c47382da..c78c320332fbc 100644 --- a/tests/ui/swap.rs +++ b/tests/ui/swap.rs @@ -241,3 +241,17 @@ const fn issue_10421(x: u32) -> u32 { let a = a; a } + +fn wrongly_unmangled_macros() { + macro_rules! test_slice { + ($val:expr) => { + *&mut $val + }; + } + + let mut foo = [1, 2]; + let temp = test_slice!(foo)[0]; + //~^ manual_swap + test_slice!(foo)[0] = test_slice!(foo)[1]; + test_slice!(foo)[1] = temp; +} diff --git a/tests/ui/swap.stderr b/tests/ui/swap.stderr index 195b888187e6d..e730be40e3492 100644 --- a/tests/ui/swap.stderr +++ b/tests/ui/swap.stderr @@ -173,5 +173,14 @@ LL | | s.0.y = t; | = note: or maybe you should use `std::mem::replace`? -error: aborting due to 17 previous errors +error: this looks like you are swapping elements of `test_slice!(foo)` manually + --> tests/ui/swap.rs:253:5 + | +LL | / let temp = test_slice!(foo)[0]; +LL | | +LL | | test_slice!(foo)[0] = test_slice!(foo)[1]; +LL | | test_slice!(foo)[1] = temp; + | |_______________________________^ help: try: `test_slice!(foo).swap(0, 1);` + +error: aborting due to 18 previous errors diff --git a/tests/ui/unnecessary_cast.fixed b/tests/ui/unnecessary_cast.fixed index 2b34111c93579..1ecc3ecf57a00 100644 --- a/tests/ui/unnecessary_cast.fixed +++ b/tests/ui/unnecessary_cast.fixed @@ -284,6 +284,369 @@ mod fixable { let _ = 5i32 as i64; //~^ unnecessary_cast } + + mod issue_16449_support { + use std::marker::PhantomData; + use std::ops::{Add, Mul}; + + pub trait PowLike { + type Output; + fn pow_like(self, rhs: Rhs) -> Self::Output; + } + + impl PowLike for f64 { + type Output = f64; + fn pow_like(self, rhs: f64) -> f64 { + self.powf(rhs) + } + } + + impl PowLike for f64 { + type Output = f64; + fn pow_like(self, _: i32) -> f64 { + self + } + } + + impl PowLike for f64 { + type Output = f32; + fn pow_like(self, _: u32) -> f32 { + self as f32 + } + } + + pub struct Mat(pub PhantomData); + + pub fn mat(_: &[[T; 1]; 1]) -> Mat { + Mat(PhantomData) + } + + pub struct Out(pub PhantomData); + + impl Out { + pub fn view(self) {} + } + + impl Out { + pub fn view(self) {} + } + + impl Mul<&Mat> for f64 { + type Output = Out; + + fn mul(self, _: &Mat) -> Self::Output { + Out(PhantomData) + } + } + + impl Mul<&Mat> for f32 { + type Output = Out; + + fn mul(self, _: &Mat) -> Self::Output { + Out(PhantomData) + } + } + + pub fn id(x: T) -> T { + x + } + + pub fn id_with(x: T, _: U) -> T { + x + } + + pub struct Wrap { + pub inner: T, + } + + pub fn wrap(inner: T) -> Wrap { + Wrap { inner } + } + + pub struct MethodWrap; + + impl MethodWrap { + pub fn id(&self, x: T) -> T { + x + } + + pub fn id_with(&self, x: T, _: U) -> T { + x + } + + pub fn wrap(&self, inner: T) -> Wrap { + Wrap { inner } + } + } + + pub struct X; + + impl Add for X { + type Output = f64; + fn add(self, _: i32) -> f64 { + 1.0 + } + } + + impl Add for X { + type Output = f32; + fn add(self, _: u32) -> f32 { + 1.0 + } + } + + pub struct Y; + + impl Add for Y { + type Output = f64; + fn add(self, _: i32) -> f64 { + 1.0 + } + } + + pub trait PowLikeSingleImpl { + type Output; + fn pow_like_single_impl(self, rhs: Rhs) -> Self::Output; + } + + impl PowLikeSingleImpl for f64 { + type Output = f64; + + fn pow_like_single_impl(self, _: i32) -> f64 { + self + } + } + } + + // Issue #16449: removing the cast still affects inference / impl selection, + // so these must not lint. + + // Minimal reproduction of the original issue. + fn issue_16449_minimal_original_reproduction() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + (1.0_f64.pow_like(2) as f64 * &a).view(); + } + + // Wrappers that preserve the inference sensitive path. + fn issue_16449_struct_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + Wrap { + inner: 1.0_f64.pow_like(2) as f64 * &a, + } + .inner + .view(); + } + + fn issue_16449_free_identity_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + id(1.0_f64.pow_like(2) as f64 * &a).view(); + } + + fn issue_16449_method_identity_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + let s = MethodWrap; + s.id(1.0_f64.pow_like(2) as f64 * &a).view(); + } + + fn issue_16449_free_wrap_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + wrap(1.0_f64.pow_like(2) as f64 * &a).inner.view(); + } + + fn issue_16449_method_wrap_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + let s = MethodWrap; + s.wrap(1.0_f64.pow_like(2) as f64 * &a).inner.view(); + } + + fn issue_16449_block_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + ({ 1.0_f64.pow_like(2) as f64 * &a }).view(); + } + + #[allow(clippy::if_same_then_else)] + fn issue_16449_if_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + (if true { + 1.0_f64.pow_like(2) as f64 * &a + } else { + 1.0_f64.pow_like(2) as f64 * &a + }) + .view(); + } + + fn issue_16449_match_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + (match 0 { + 0 => 1.0_f64.pow_like(2) as f64 * &a, + _ => 1.0_f64.pow_like(2) as f64 * &a, + }) + .view(); + } + + #[allow(clippy::let_and_return)] + fn issue_16449_let_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + ({ + let x = 1.0_f64.pow_like(2) as f64 * &a; + x + }) + .view(); + } + + #[allow(clippy::never_loop)] + fn issue_16449_loop_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + (loop { + break 1.0_f64.pow_like(2) as f64 * &a; + }) + .view(); + } + + #[allow(clippy::double_parens)] + fn issue_16449_operator_reproduction() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + ((X + 2) as f64 * &a).view(); + } + + // Placeholder generic arguments still leave inference active, + // so these must not lint. + fn issue_16449_placeholder_generics_do_not_lint() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + let s = MethodWrap; + + // placeholder call + id::<_>(1.0_f64.pow_like(2) as f64 * &a).view(); + // placeholder method call + s.id::<_>(1.0_f64.pow_like(2) as f64 * &a).view(); + // mixed placeholder call + id_with::<_, u8>(1.0_f64.pow_like(2) as f64 * &a, 0).view(); + // mixed placeholder method call + s.id_with::<_, u8>(1.0_f64.pow_like(2) as f64 * &a, 0).view(); + } + + // These look similar, but inference no longer depends on the cast, so they should lint. + + fn issue_16449_explicit_generics_still_lint() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + let s = MethodWrap; + + // explicit call + id::>(1.0_f64.pow_like(2) * &a).view(); + //~^ unnecessary_cast + + // explicit method call + s.id::>(1.0_f64.pow_like(2) * &a).view(); + //~^ unnecessary_cast + + // explicit free wrap + wrap::>(1.0_f64.pow_like(2) * &a).inner.view(); + //~^ unnecessary_cast + + // explicit method wrap + s.wrap::>(1.0_f64.pow_like(2) * &a).inner.view(); + //~^ unnecessary_cast + } + + // A nonprimitive method receiver alone should not suppress the lint. + fn issue_16449_nonprimitive_receiver_should_still_lint() { + use self::issue_16449_support::PowLikeSingleImpl; + struct Receiver; + + impl Receiver { + fn take(&self, x: f64) -> f64 { + x + } + } + + let receiver = Receiver; + let _ = receiver.take(1.0_f64.pow_like_single_impl(2)).abs(); + //~^ unnecessary_cast + } + + fn issue_16449_wrapper_still_lints() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + let s = MethodWrap; + + let _ = id(1.0_f64.powi(2)).abs(); + //~^ unnecessary_cast + + let _ = wrap(1.0_f64.powi(2)).inner.abs(); + //~^ unnecessary_cast + + let _ = s.id(1.0_f64.powi(2)).abs(); + //~^ unnecessary_cast + + let _ = s.wrap(1.0_f64.powi(2)).inner.abs(); + //~^ unnecessary_cast + + let _ = id(1.0_f64.powi(2) * &a); + //~^ unnecessary_cast + + let _ = s.id(1.0_f64.powi(2) * &a); + //~^ unnecessary_cast + } + + // To guarantee that good suggestions given before continue + // to be given even after the fix. + #[allow(clippy::double_parens)] + fn issue_16449_still_lints() { + use self::issue_16449_support::*; + + const ONE: f64 = 1.0; + let one = 1.0_f64; + + let _ = 1.0_f64.pow_like(0.5); + //~^ unnecessary_cast + + let _ = 1.0_f64.pow_like(2); + //~^ unnecessary_cast + + let _ = 1.0_f64.powi(2).abs(); + //~^ unnecessary_cast + + let _ = ((Y + 2)).abs(); + //~^ unnecessary_cast + + let _ = (1.0_f64.pow_like_single_impl(2) + 1.0_f64).abs(); + //~^ unnecessary_cast + + let _ = (1.0_f64.pow_like_single_impl(2) + ONE).abs(); + //~^ unnecessary_cast + + let _ = (1.0_f64.pow_like_single_impl(2) + one).abs(); + //~^ unnecessary_cast + } } fn issue16475() -> *const u8 { diff --git a/tests/ui/unnecessary_cast.rs b/tests/ui/unnecessary_cast.rs index 213b6bac3d2bd..1a6cc831aa4b8 100644 --- a/tests/ui/unnecessary_cast.rs +++ b/tests/ui/unnecessary_cast.rs @@ -284,6 +284,369 @@ mod fixable { let _ = 5i32 as i64 as i64; //~^ unnecessary_cast } + + mod issue_16449_support { + use std::marker::PhantomData; + use std::ops::{Add, Mul}; + + pub trait PowLike { + type Output; + fn pow_like(self, rhs: Rhs) -> Self::Output; + } + + impl PowLike for f64 { + type Output = f64; + fn pow_like(self, rhs: f64) -> f64 { + self.powf(rhs) + } + } + + impl PowLike for f64 { + type Output = f64; + fn pow_like(self, _: i32) -> f64 { + self + } + } + + impl PowLike for f64 { + type Output = f32; + fn pow_like(self, _: u32) -> f32 { + self as f32 + } + } + + pub struct Mat(pub PhantomData); + + pub fn mat(_: &[[T; 1]; 1]) -> Mat { + Mat(PhantomData) + } + + pub struct Out(pub PhantomData); + + impl Out { + pub fn view(self) {} + } + + impl Out { + pub fn view(self) {} + } + + impl Mul<&Mat> for f64 { + type Output = Out; + + fn mul(self, _: &Mat) -> Self::Output { + Out(PhantomData) + } + } + + impl Mul<&Mat> for f32 { + type Output = Out; + + fn mul(self, _: &Mat) -> Self::Output { + Out(PhantomData) + } + } + + pub fn id(x: T) -> T { + x + } + + pub fn id_with(x: T, _: U) -> T { + x + } + + pub struct Wrap { + pub inner: T, + } + + pub fn wrap(inner: T) -> Wrap { + Wrap { inner } + } + + pub struct MethodWrap; + + impl MethodWrap { + pub fn id(&self, x: T) -> T { + x + } + + pub fn id_with(&self, x: T, _: U) -> T { + x + } + + pub fn wrap(&self, inner: T) -> Wrap { + Wrap { inner } + } + } + + pub struct X; + + impl Add for X { + type Output = f64; + fn add(self, _: i32) -> f64 { + 1.0 + } + } + + impl Add for X { + type Output = f32; + fn add(self, _: u32) -> f32 { + 1.0 + } + } + + pub struct Y; + + impl Add for Y { + type Output = f64; + fn add(self, _: i32) -> f64 { + 1.0 + } + } + + pub trait PowLikeSingleImpl { + type Output; + fn pow_like_single_impl(self, rhs: Rhs) -> Self::Output; + } + + impl PowLikeSingleImpl for f64 { + type Output = f64; + + fn pow_like_single_impl(self, _: i32) -> f64 { + self + } + } + } + + // Issue #16449: removing the cast still affects inference / impl selection, + // so these must not lint. + + // Minimal reproduction of the original issue. + fn issue_16449_minimal_original_reproduction() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + (1.0_f64.pow_like(2) as f64 * &a).view(); + } + + // Wrappers that preserve the inference sensitive path. + fn issue_16449_struct_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + Wrap { + inner: 1.0_f64.pow_like(2) as f64 * &a, + } + .inner + .view(); + } + + fn issue_16449_free_identity_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + id(1.0_f64.pow_like(2) as f64 * &a).view(); + } + + fn issue_16449_method_identity_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + let s = MethodWrap; + s.id(1.0_f64.pow_like(2) as f64 * &a).view(); + } + + fn issue_16449_free_wrap_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + wrap(1.0_f64.pow_like(2) as f64 * &a).inner.view(); + } + + fn issue_16449_method_wrap_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + let s = MethodWrap; + s.wrap(1.0_f64.pow_like(2) as f64 * &a).inner.view(); + } + + fn issue_16449_block_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + ({ 1.0_f64.pow_like(2) as f64 * &a }).view(); + } + + #[allow(clippy::if_same_then_else)] + fn issue_16449_if_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + (if true { + 1.0_f64.pow_like(2) as f64 * &a + } else { + 1.0_f64.pow_like(2) as f64 * &a + }) + .view(); + } + + fn issue_16449_match_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + (match 0 { + 0 => 1.0_f64.pow_like(2) as f64 * &a, + _ => 1.0_f64.pow_like(2) as f64 * &a, + }) + .view(); + } + + #[allow(clippy::let_and_return)] + fn issue_16449_let_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + ({ + let x = 1.0_f64.pow_like(2) as f64 * &a; + x + }) + .view(); + } + + #[allow(clippy::never_loop)] + fn issue_16449_loop_wrapper() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + (loop { + break 1.0_f64.pow_like(2) as f64 * &a; + }) + .view(); + } + + #[allow(clippy::double_parens)] + fn issue_16449_operator_reproduction() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + ((X + 2) as f64 * &a).view(); + } + + // Placeholder generic arguments still leave inference active, + // so these must not lint. + fn issue_16449_placeholder_generics_do_not_lint() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + let s = MethodWrap; + + // placeholder call + id::<_>(1.0_f64.pow_like(2) as f64 * &a).view(); + // placeholder method call + s.id::<_>(1.0_f64.pow_like(2) as f64 * &a).view(); + // mixed placeholder call + id_with::<_, u8>(1.0_f64.pow_like(2) as f64 * &a, 0).view(); + // mixed placeholder method call + s.id_with::<_, u8>(1.0_f64.pow_like(2) as f64 * &a, 0).view(); + } + + // These look similar, but inference no longer depends on the cast, so they should lint. + + fn issue_16449_explicit_generics_still_lint() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + let s = MethodWrap; + + // explicit call + id::>(1.0_f64.pow_like(2) as f64 * &a).view(); + //~^ unnecessary_cast + + // explicit method call + s.id::>(1.0_f64.pow_like(2) as f64 * &a).view(); + //~^ unnecessary_cast + + // explicit free wrap + wrap::>(1.0_f64.pow_like(2) as f64 * &a).inner.view(); + //~^ unnecessary_cast + + // explicit method wrap + s.wrap::>(1.0_f64.pow_like(2) as f64 * &a).inner.view(); + //~^ unnecessary_cast + } + + // A nonprimitive method receiver alone should not suppress the lint. + fn issue_16449_nonprimitive_receiver_should_still_lint() { + use self::issue_16449_support::PowLikeSingleImpl; + struct Receiver; + + impl Receiver { + fn take(&self, x: f64) -> f64 { + x + } + } + + let receiver = Receiver; + let _ = receiver.take(1.0_f64.pow_like_single_impl(2) as f64).abs(); + //~^ unnecessary_cast + } + + fn issue_16449_wrapper_still_lints() { + use self::issue_16449_support::*; + + let a = mat(&[[1.0]]); + let s = MethodWrap; + + let _ = id(1.0_f64.powi(2) as f64).abs(); + //~^ unnecessary_cast + + let _ = wrap(1.0_f64.powi(2) as f64).inner.abs(); + //~^ unnecessary_cast + + let _ = s.id(1.0_f64.powi(2) as f64).abs(); + //~^ unnecessary_cast + + let _ = s.wrap(1.0_f64.powi(2) as f64).inner.abs(); + //~^ unnecessary_cast + + let _ = id(1.0_f64.powi(2) as f64 * &a); + //~^ unnecessary_cast + + let _ = s.id(1.0_f64.powi(2) as f64 * &a); + //~^ unnecessary_cast + } + + // To guarantee that good suggestions given before continue + // to be given even after the fix. + #[allow(clippy::double_parens)] + fn issue_16449_still_lints() { + use self::issue_16449_support::*; + + const ONE: f64 = 1.0; + let one = 1.0_f64; + + let _ = 1.0_f64.pow_like(0.5) as f64; + //~^ unnecessary_cast + + let _ = 1.0_f64.pow_like(2) as f64; + //~^ unnecessary_cast + + let _ = (1.0_f64.powi(2) as f64).abs(); + //~^ unnecessary_cast + + let _ = ((Y + 2) as f64).abs(); + //~^ unnecessary_cast + + let _ = (1.0_f64.pow_like_single_impl(2) as f64 + 1.0_f64).abs(); + //~^ unnecessary_cast + + let _ = (1.0_f64.pow_like_single_impl(2) as f64 + ONE).abs(); + //~^ unnecessary_cast + + let _ = (1.0_f64.pow_like_single_impl(2) as f64 + one).abs(); + //~^ unnecessary_cast + } } fn issue16475() -> *const u8 { diff --git a/tests/ui/unnecessary_cast.stderr b/tests/ui/unnecessary_cast.stderr index 14c14e5831344..19d2afcb4f255 100644 --- a/tests/ui/unnecessary_cast.stderr +++ b/tests/ui/unnecessary_cast.stderr @@ -277,11 +277,119 @@ error: casting to the same type is unnecessary (`i64` -> `i64`) LL | let _ = 5i32 as i64 as i64; | ^^^^^^^^^^^^^^^^^^ help: try: `5i32 as i64` +error: casting to the same type is unnecessary (`f64` -> `f64`) + --> tests/ui/unnecessary_cast.rs:563:24 + | +LL | id::>(1.0_f64.pow_like(2) as f64 * &a).view(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1.0_f64.pow_like(2)` + +error: casting to the same type is unnecessary (`f64` -> `f64`) + --> tests/ui/unnecessary_cast.rs:567:26 + | +LL | s.id::>(1.0_f64.pow_like(2) as f64 * &a).view(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1.0_f64.pow_like(2)` + +error: casting to the same type is unnecessary (`f64` -> `f64`) + --> tests/ui/unnecessary_cast.rs:571:26 + | +LL | wrap::>(1.0_f64.pow_like(2) as f64 * &a).inner.view(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1.0_f64.pow_like(2)` + +error: casting to the same type is unnecessary (`f64` -> `f64`) + --> tests/ui/unnecessary_cast.rs:575:28 + | +LL | s.wrap::>(1.0_f64.pow_like(2) as f64 * &a).inner.view(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1.0_f64.pow_like(2)` + +error: casting to the same type is unnecessary (`f64` -> `f64`) + --> tests/ui/unnecessary_cast.rs:591:31 + | +LL | let _ = receiver.take(1.0_f64.pow_like_single_impl(2) as f64).abs(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1.0_f64.pow_like_single_impl(2)` + +error: casting to the same type is unnecessary (`f64` -> `f64`) + --> tests/ui/unnecessary_cast.rs:601:20 + | +LL | let _ = id(1.0_f64.powi(2) as f64).abs(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `1.0_f64.powi(2)` + +error: casting to the same type is unnecessary (`f64` -> `f64`) + --> tests/ui/unnecessary_cast.rs:604:22 + | +LL | let _ = wrap(1.0_f64.powi(2) as f64).inner.abs(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `1.0_f64.powi(2)` + +error: casting to the same type is unnecessary (`f64` -> `f64`) + --> tests/ui/unnecessary_cast.rs:607:22 + | +LL | let _ = s.id(1.0_f64.powi(2) as f64).abs(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `1.0_f64.powi(2)` + +error: casting to the same type is unnecessary (`f64` -> `f64`) + --> tests/ui/unnecessary_cast.rs:610:24 + | +LL | let _ = s.wrap(1.0_f64.powi(2) as f64).inner.abs(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `1.0_f64.powi(2)` + +error: casting to the same type is unnecessary (`f64` -> `f64`) + --> tests/ui/unnecessary_cast.rs:613:20 + | +LL | let _ = id(1.0_f64.powi(2) as f64 * &a); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `1.0_f64.powi(2)` + +error: casting to the same type is unnecessary (`f64` -> `f64`) + --> tests/ui/unnecessary_cast.rs:616:22 + | +LL | let _ = s.id(1.0_f64.powi(2) as f64 * &a); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `1.0_f64.powi(2)` + +error: casting to the same type is unnecessary (`f64` -> `f64`) + --> tests/ui/unnecessary_cast.rs:629:17 + | +LL | let _ = 1.0_f64.pow_like(0.5) as f64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1.0_f64.pow_like(0.5)` + +error: casting to the same type is unnecessary (`f64` -> `f64`) + --> tests/ui/unnecessary_cast.rs:632:17 + | +LL | let _ = 1.0_f64.pow_like(2) as f64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1.0_f64.pow_like(2)` + +error: casting to the same type is unnecessary (`f64` -> `f64`) + --> tests/ui/unnecessary_cast.rs:635:17 + | +LL | let _ = (1.0_f64.powi(2) as f64).abs(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1.0_f64.powi(2)` + +error: casting to the same type is unnecessary (`f64` -> `f64`) + --> tests/ui/unnecessary_cast.rs:638:17 + | +LL | let _ = ((Y + 2) as f64).abs(); + | ^^^^^^^^^^^^^^^^ help: try: `((Y + 2))` + +error: casting to the same type is unnecessary (`f64` -> `f64`) + --> tests/ui/unnecessary_cast.rs:641:18 + | +LL | let _ = (1.0_f64.pow_like_single_impl(2) as f64 + 1.0_f64).abs(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1.0_f64.pow_like_single_impl(2)` + +error: casting to the same type is unnecessary (`f64` -> `f64`) + --> tests/ui/unnecessary_cast.rs:644:18 + | +LL | let _ = (1.0_f64.pow_like_single_impl(2) as f64 + ONE).abs(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1.0_f64.pow_like_single_impl(2)` + +error: casting to the same type is unnecessary (`f64` -> `f64`) + --> tests/ui/unnecessary_cast.rs:647:18 + | +LL | let _ = (1.0_f64.pow_like_single_impl(2) as f64 + one).abs(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1.0_f64.pow_like_single_impl(2)` + error: casting raw pointers to the same type and constness is unnecessary (`*const *const u8` -> `*const *const u8`) - --> tests/ui/unnecessary_cast.rs:292:10 + --> tests/ui/unnecessary_cast.rs:655:10 | LL | *(&NONE as *const _ as *const _ as *const *const u8 as *const *const u8) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(&NONE as *const _ as *const _ as *const *const u8)` -error: aborting due to 47 previous errors +error: aborting due to 65 previous errors diff --git a/tests/ui/unneeded_wildcard_pattern.fixed b/tests/ui/unneeded_wildcard_pattern.fixed index 32494435fff5e..ff99e436c661f 100644 --- a/tests/ui/unneeded_wildcard_pattern.fixed +++ b/tests/ui/unneeded_wildcard_pattern.fixed @@ -64,4 +64,30 @@ fn main() { let t = (0, 1, 2, 3); if let (0, _, ..) = t {}; } + + struct Struct4 { + a: u32, + b: u32, + c: u32, + d: u32, + } + + let fourval = Struct4 { + a: 5, + b: 10, + c: 15, + d: 20, + }; + + // unlike the tuple forms, the struct form can only have the `..` at the end of the list + let Struct4 { mut a, mut b, .. } = fourval; + //~^ unneeded_wildcard_pattern + let Struct4 { mut b, .. } = fourval; + //~^ unneeded_wildcard_pattern + let Struct4 { mut a, b, c, d: _ } = fourval; + let Struct4 { mut c, d, .. } = fourval; + let Struct4 { .. } = fourval; + //~^ unneeded_wildcard_pattern + let Struct4 { .. } = fourval; + //~^ unneeded_wildcard_pattern } diff --git a/tests/ui/unneeded_wildcard_pattern.rs b/tests/ui/unneeded_wildcard_pattern.rs index b3a0fb6098d61..5913735d3b4ba 100644 --- a/tests/ui/unneeded_wildcard_pattern.rs +++ b/tests/ui/unneeded_wildcard_pattern.rs @@ -64,4 +64,30 @@ fn main() { let t = (0, 1, 2, 3); if let (0, _, ..) = t {}; } + + struct Struct4 { + a: u32, + b: u32, + c: u32, + d: u32, + } + + let fourval = Struct4 { + a: 5, + b: 10, + c: 15, + d: 20, + }; + + // unlike the tuple forms, the struct form can only have the `..` at the end of the list + let Struct4 { mut a, mut b, c: _, .. } = fourval; + //~^ unneeded_wildcard_pattern + let Struct4 { mut b, c: _, d: _, .. } = fourval; + //~^ unneeded_wildcard_pattern + let Struct4 { mut a, b, c, d: _ } = fourval; + let Struct4 { mut c, d, .. } = fourval; + let Struct4 { b: _, c: _, .. } = fourval; + //~^ unneeded_wildcard_pattern + let Struct4 { c: _, .. } = fourval; + //~^ unneeded_wildcard_pattern } diff --git a/tests/ui/unneeded_wildcard_pattern.stderr b/tests/ui/unneeded_wildcard_pattern.stderr index 20666268a8cab..bfdceb53f7d4a 100644 --- a/tests/ui/unneeded_wildcard_pattern.stderr +++ b/tests/ui/unneeded_wildcard_pattern.stderr @@ -88,5 +88,29 @@ error: these patterns are unneeded as the `..` pattern can match those elements LL | if let S(0, .., _, _,) = s {}; | ^^^^^^ help: remove them -error: aborting due to 14 previous errors +error: this pattern is unneeded as the `..` pattern can match that element + --> tests/ui/unneeded_wildcard_pattern.rs:83:33 + | +LL | let Struct4 { mut a, mut b, c: _, .. } = fourval; + | ^^^^^^ help: remove it + +error: these patterns are unneeded as the `..` pattern can match those elements + --> tests/ui/unneeded_wildcard_pattern.rs:85:26 + | +LL | let Struct4 { mut b, c: _, d: _, .. } = fourval; + | ^^^^^^^^^^^^ help: remove them + +error: these patterns are unneeded as the `..` pattern can match those elements + --> tests/ui/unneeded_wildcard_pattern.rs:89:19 + | +LL | let Struct4 { b: _, c: _, .. } = fourval; + | ^^^^^^^^^^^^ help: remove them + +error: this pattern is unneeded as the `..` pattern can match that element + --> tests/ui/unneeded_wildcard_pattern.rs:91:19 + | +LL | let Struct4 { c: _, .. } = fourval; + | ^^^^^^ help: remove it + +error: aborting due to 18 previous errors diff --git a/tests/ui/unsafe_removed_from_name.rs b/tests/ui/unsafe_removed_from_name.rs index 2e1d8b60a0e2f..f3d318815c101 100644 --- a/tests/ui/unsafe_removed_from_name.rs +++ b/tests/ui/unsafe_removed_from_name.rs @@ -22,6 +22,7 @@ use std::cell::UnsafeCell as Bombsawayunsafe; mod mod_with_some_unsafe_things { pub struct Safe; pub struct Unsafe; + pub trait UnsafeTrait {} } use mod_with_some_unsafe_things::Unsafe as LieAboutModSafety; @@ -40,4 +41,10 @@ use mod_with_some_unsafe_things::Unsafe as SuperUnsafeModThing; #[allow(clippy::unsafe_removed_from_name)] use mod_with_some_unsafe_things::Unsafe as SuperSafeThing; +// issue #16768, don't lint when "renaming" to '_' +use mod_with_some_unsafe_things::UnsafeTrait as _; + +use mod_with_some_unsafe_things::UnsafeTrait as FakeSafeTrait; +//~^ unsafe_removed_from_name + fn main() {} diff --git a/tests/ui/unsafe_removed_from_name.stderr b/tests/ui/unsafe_removed_from_name.stderr index 5268c16ec9ba5..cd3c1f80a3002 100644 --- a/tests/ui/unsafe_removed_from_name.stderr +++ b/tests/ui/unsafe_removed_from_name.stderr @@ -14,22 +14,28 @@ LL | use std::cell::UnsafeCell as TotallySafeCellAgain; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: removed `unsafe` from the name of `Unsafe` in use as `LieAboutModSafety` - --> tests/ui/unsafe_removed_from_name.rs:27:1 + --> tests/ui/unsafe_removed_from_name.rs:28:1 | LL | use mod_with_some_unsafe_things::Unsafe as LieAboutModSafety; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: removed `unsafe` from the name of `Unsafe` in use as `A` - --> tests/ui/unsafe_removed_from_name.rs:31:1 + --> tests/ui/unsafe_removed_from_name.rs:32:1 | LL | use mod_with_some_unsafe_things::{Unsafe as A, Unsafe as B}; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: removed `unsafe` from the name of `Unsafe` in use as `B` - --> tests/ui/unsafe_removed_from_name.rs:31:1 + --> tests/ui/unsafe_removed_from_name.rs:32:1 | LL | use mod_with_some_unsafe_things::{Unsafe as A, Unsafe as B}; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 5 previous errors +error: removed `unsafe` from the name of `UnsafeTrait` in use as `FakeSafeTrait` + --> tests/ui/unsafe_removed_from_name.rs:47:1 + | +LL | use mod_with_some_unsafe_things::UnsafeTrait as FakeSafeTrait; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 6 previous errors diff --git a/tests/ui/unused_async.rs b/tests/ui/unused_async.rs index 3f9244ab49707..b6780d240b17c 100644 --- a/tests/ui/unused_async.rs +++ b/tests/ui/unused_async.rs @@ -134,3 +134,14 @@ mod issue15305 { unimplemented!("Implement task"); } } + +mod issue16835 { + async fn todo_task(_arg: i32) { + todo!() + } + + async fn unimplemented_task(_arg: i32) { + let a = 1; + unimplemented!() + } +} From c36706495b2abc49b01b11ad3ba9035c39b9cc63 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 16 Apr 2026 19:44:06 +0200 Subject: [PATCH 08/38] Do not propose to refactor when no variant constructor is used If all the ways to "leave" the closure is through a divergent statement other than `return` (not detected as a potential return value by the visitor), do not trigger the lint. --- clippy_lints/src/methods/bind_instead_of_map.rs | 1 + tests/ui/bind_instead_of_map.fixed | 6 ++++++ tests/ui/bind_instead_of_map.rs | 6 ++++++ tests/ui/bind_instead_of_map.stderr | 14 +++++++++++++- 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/methods/bind_instead_of_map.rs b/clippy_lints/src/methods/bind_instead_of_map.rs index f8520c23ea503..6390680c067cb 100644 --- a/clippy_lints/src/methods/bind_instead_of_map.rs +++ b/clippy_lints/src/methods/bind_instead_of_map.rs @@ -128,6 +128,7 @@ impl BindInsteadOfMap { } }); let (span, msg) = if can_sugg + && !suggs.is_empty() && let hir::ExprKind::MethodCall(segment, ..) = expr.kind && let Some(msg) = self.lint_msg(cx) { diff --git a/tests/ui/bind_instead_of_map.fixed b/tests/ui/bind_instead_of_map.fixed index a1c4cb5a4823a..b1c83970ae9fe 100644 --- a/tests/ui/bind_instead_of_map.fixed +++ b/tests/ui/bind_instead_of_map.fixed @@ -26,3 +26,9 @@ pub fn foo() -> Option { pub fn example2(x: bool) -> Option<&'static str> { Some("a").and_then(|s| Some(if x { s } else { return None })) } + +fn issue16861(b: bool, res: Result) { + let _: Result = res.or_else(|_| panic!("should not happen")); + let _: Result = res.map_err(|_| if b { panic!("should not happen") } else { 42 }); + //~^ bind_instead_of_map +} diff --git a/tests/ui/bind_instead_of_map.rs b/tests/ui/bind_instead_of_map.rs index 1308fa9f416be..65e7e490a8901 100644 --- a/tests/ui/bind_instead_of_map.rs +++ b/tests/ui/bind_instead_of_map.rs @@ -26,3 +26,9 @@ pub fn foo() -> Option { pub fn example2(x: bool) -> Option<&'static str> { Some("a").and_then(|s| Some(if x { s } else { return None })) } + +fn issue16861(b: bool, res: Result) { + let _: Result = res.or_else(|_| panic!("should not happen")); + let _: Result = res.or_else(|_| if b { panic!("should not happen") } else { Err(42) }); + //~^ bind_instead_of_map +} diff --git a/tests/ui/bind_instead_of_map.stderr b/tests/ui/bind_instead_of_map.stderr index 08f85fb58549c..1e2b93adffade 100644 --- a/tests/ui/bind_instead_of_map.stderr +++ b/tests/ui/bind_instead_of_map.stderr @@ -22,5 +22,17 @@ error: using `Result.and_then(Ok)`, which is a no-op LL | let _ = x.and_then(Ok); | ^^^^^^^^^^^^^^ help: use the expression directly: `x` -error: aborting due to 3 previous errors +error: using `Result.or_else(|x| Err(y))`, which is more succinctly expressed as `map_err(|x| y)` + --> tests/ui/bind_instead_of_map.rs:32:31 + | +LL | let _: Result = res.or_else(|_| if b { panic!("should not happen") } else { Err(42) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `map_err` instead + | +LL - let _: Result = res.or_else(|_| if b { panic!("should not happen") } else { Err(42) }); +LL + let _: Result = res.map_err(|_| if b { panic!("should not happen") } else { 42 }); + | + +error: aborting due to 4 previous errors From 8dcf8e61e088628ad83dc4557efc1e20a2851dd2 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Fri, 6 Feb 2026 04:39:03 -0500 Subject: [PATCH 09/38] apply useless_borrows_in_formatting fixes minor code cleanup getting ready for the `useless_borrows_in_formatting` lint --- clippy_dev/src/new_lint.rs | 2 +- clippy_lints/src/booleans.rs | 2 +- clippy_lints/src/methods/wrong_self_convention.rs | 2 +- clippy_lints/src/operators/manual_div_ceil.rs | 6 +----- clippy_lints/src/ref_option_ref.rs | 2 +- clippy_lints/src/unit_types/unit_arg.rs | 3 +-- clippy_lints_internal/src/collapsible_span_lint_calls.rs | 4 ++-- lintcheck/src/main.rs | 4 ++-- 8 files changed, 10 insertions(+), 15 deletions(-) diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index dc2c6d8aa520e..a5e2050a3865e 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -502,7 +502,7 @@ fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str> file_contents.replace_range(arr_start + 1..arr_end, &new_arr_content); // Just add the mod declaration at the top, it'll be fixed by rustfmt - file_contents.insert_str(0, &format!("mod {};\n", &lint.name)); + file_contents.insert_str(0, &format!("mod {};\n", lint.name)); let mut file = OpenOptions::new() .write(true) diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 986e75577412a..ed963dc3e90e7 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -360,7 +360,7 @@ impl SuggestContext<'_, '_, '_> { if app != Applicability::MachineApplicable { return None; } - let _cannot_fail = write!(&mut self.output, "{}", &(!snip)); + let _cannot_fail = write!(&mut self.output, "{}", !snip); } }, True | False | Not(_) => { diff --git a/clippy_lints/src/methods/wrong_self_convention.rs b/clippy_lints/src/methods/wrong_self_convention.rs index 12a6f345168fe..439a1af93cadc 100644 --- a/clippy_lints/src/methods/wrong_self_convention.rs +++ b/clippy_lints/src/methods/wrong_self_convention.rs @@ -121,7 +121,7 @@ pub(super) fn check<'tcx>( format!("methods with the following characteristics: ({s})") } else { - format!("methods called {}", &conventions[0]) + format!("methods called {}", conventions[0]) } }; diff --git a/clippy_lints/src/operators/manual_div_ceil.rs b/clippy_lints/src/operators/manual_div_ceil.rs index 5a4823ddfcf63..304c51ba2627d 100644 --- a/clippy_lints/src/operators/manual_div_ceil.rs +++ b/clippy_lints/src/operators/manual_div_ceil.rs @@ -177,11 +177,7 @@ fn build_suggestion( // suggestion message, we want to make a suggestion string before `div_ceil` like // `(-2048_{type_suffix})`. let suggestion_before_div_ceil = if has_enclosing_paren(÷nd_sugg_str) { - format!( - "{}{})", - ÷nd_sugg_str[..dividend_sugg_str.len() - 1].to_string(), - type_suffix - ) + format!("{}{type_suffix})", ÷nd_sugg_str[..dividend_sugg_str.len() - 1]) } else { format!("{dividend_sugg_str}{type_suffix}") }; diff --git a/clippy_lints/src/ref_option_ref.rs b/clippy_lints/src/ref_option_ref.rs index 074345e753212..f1cf505482e47 100644 --- a/clippy_lints/src/ref_option_ref.rs +++ b/clippy_lints/src/ref_option_ref.rs @@ -58,7 +58,7 @@ impl<'tcx> LateLintPass<'tcx> for RefOptionRef { ty.span, "since `&` implements the `Copy` trait, `&Option<&T>` can be simplified to `Option<&T>`", "try", - format!("Option<{}>", &snippet(cx, inner_ty.span, "..")), + format!("Option<{}>", snippet(cx, inner_ty.span, "..")), Applicability::MaybeIncorrect, ); } diff --git a/clippy_lints/src/unit_types/unit_arg.rs b/clippy_lints/src/unit_types/unit_arg.rs index 973b2e95b474f..c48ca652ffd11 100644 --- a/clippy_lints/src/unit_types/unit_arg.rs +++ b/clippy_lints/src/unit_types/unit_arg.rs @@ -231,9 +231,8 @@ fn fmt_stmts_and_call( let block_indent = call_expr_indent + 4; stmts_and_call_snippet = reindent_multiline(&stmts_and_call_snippet, true, Some(block_indent)); stmts_and_call_snippet = format!( - "{{\n{}{}\n{}}}", + "{{\n{}{stmts_and_call_snippet}\n{}}}", " ".repeat(block_indent), - &stmts_and_call_snippet, " ".repeat(call_expr_indent) ); } diff --git a/clippy_lints_internal/src/collapsible_span_lint_calls.rs b/clippy_lints_internal/src/collapsible_span_lint_calls.rs index b048a1004b0d5..bbcef0856ba4d 100644 --- a/clippy_lints_internal/src/collapsible_span_lint_calls.rs +++ b/clippy_lints_internal/src/collapsible_span_lint_calls.rs @@ -232,8 +232,8 @@ fn suggest_help( "this call is collapsible", "collapse into", format!( - "span_lint_and_help({}, {}, {}, {}, {}, {help})", - and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg, &option_span, + "span_lint_and_help({}, {}, {}, {}, {option_span}, {help})", + and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg ), app, ); diff --git a/lintcheck/src/main.rs b/lintcheck/src/main.rs index b30df79023784..6a9e6cab8957b 100644 --- a/lintcheck/src/main.rs +++ b/lintcheck/src/main.rs @@ -85,12 +85,12 @@ impl Crate { if config.max_jobs == 1 { println!( "{index}/{total_crates_to_lint} {perc}% Linting {} {}", - &self.name, &self.version + self.name, self.version ); } else { println!( "{index}/{total_crates_to_lint} {perc}% Linting {} {} in target dir {thread_index:?}", - &self.name, &self.version + self.name, self.version ); } From 8b8265455f58d2d4fdc4c7a2b0e27d9c67af20fa Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 17 Apr 2026 16:06:17 +0200 Subject: [PATCH 10/38] Almost fully get rid of windows-sys 0.59 Only curl and stacker still depends on it. --- clippy_dev/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_dev/Cargo.toml b/clippy_dev/Cargo.toml index 238465210ee28..49dc5c9f10a89 100644 --- a/clippy_dev/Cargo.toml +++ b/clippy_dev/Cargo.toml @@ -9,7 +9,7 @@ chrono = { version = "0.4.38", default-features = false, features = ["clock"] } clap = { version = "4.4", features = ["derive"] } indoc = "1.0" itertools = "0.12" -opener = "0.7" +opener = "0.8" rustc-literal-escaper = "0.0.7" walkdir = "2.3" From aaa5bd244ee09a39fe90ffbfd36f152aa1975581 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Fri, 6 Feb 2026 04:39:03 -0500 Subject: [PATCH 11/38] Add useless_borrows_in_formatting lint Detect format macros where an argument is passed with an explicit `&` even though the formatter already takes references, e.g. `println!("{}", &s)` when `s: &str`. Some original micro-benchmarks showed ~6% performance improvement when redundant refs are removed. - Lint runs for both Display (`{}`) and Debug (`{:?}`) placeholders when the inner type is Sized and implements the corresponding trait. - Applies to the main value argument and to width/precision arguments (e.g. `format!("{0:1$.2$}", &v1, &v2, &v3)`). - Suggests removing the redundant `&` with MachineApplicable fix. - Skip when the argument comes from expansion or a proc macro. --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/format_args.rs | 86 +++++- tests/ui/explicit_deref_methods.fixed | 7 +- tests/ui/explicit_deref_methods.rs | 7 +- tests/ui/explicit_deref_methods.stderr | 26 +- tests/ui/recursive_format_impl.rs | 1 + tests/ui/recursive_format_impl.stderr | 20 +- tests/ui/useless_borrows_in_formatting.fixed | 118 ++++++++ tests/ui/useless_borrows_in_formatting.rs | 118 ++++++++ tests/ui/useless_borrows_in_formatting.stderr | 266 ++++++++++++++++++ 11 files changed, 618 insertions(+), 33 deletions(-) create mode 100644 tests/ui/useless_borrows_in_formatting.fixed create mode 100644 tests/ui/useless_borrows_in_formatting.rs create mode 100644 tests/ui/useless_borrows_in_formatting.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 1276ab3d4bd37..8161470761796 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7393,6 +7393,7 @@ Released 2018-09-13 [`used_underscore_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_items [`useless_asref`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref [`useless_attribute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_attribute +[`useless_borrows_in_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_borrows_in_formatting [`useless_concat`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_concat [`useless_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion [`useless_format`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_format diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index c164241673a31..837bf29f5f394 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -176,6 +176,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::format_args::UNNECESSARY_DEBUG_FORMATTING_INFO, crate::format_args::UNNECESSARY_TRAILING_COMMA_INFO, crate::format_args::UNUSED_FORMAT_SPECS_INFO, + crate::format_args::USELESS_BORROWS_IN_FORMATTING_INFO, crate::format_impl::PRINT_IN_FORMAT_IMPL_INFO, crate::format_impl::RECURSIVE_FORMAT_IMPL_INFO, crate::format_push_string::FORMAT_PUSH_STRING_INFO, diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index 12cf829167393..232aa35df088f 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -10,13 +10,13 @@ use clippy_utils::macros::{ }; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::MaybeDef; -use clippy_utils::source::{SpanRangeExt, snippet}; +use clippy_utils::source::{SpanRangeExt, snippet, snippet_opt}; use clippy_utils::ty::implements_trait; -use clippy_utils::{is_from_proc_macro, is_in_test, sym, trait_ref_of_method}; +use clippy_utils::{is_from_proc_macro, is_in_test, peel_hir_expr_while, sym, trait_ref_of_method}; use itertools::Itertools; use rustc_ast::{ - FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions, - FormatPlaceholder, FormatTrait, + BorrowKind, FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, + FormatOptions, FormatPlaceholder, FormatTrait, }; use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; @@ -257,6 +257,34 @@ declare_clippy_lint! { "use of a format specifier that has no effect" } +declare_clippy_lint! { + /// ### What it does + /// Detects format!-style macros (e.g. `format!`, `println!`, `write!`) where an argument + /// is passed with an explicit `&` but the value is already a reference, resulting in a + /// double reference (e.g. `&&T`). + /// + /// ### Why is this bad? + /// The extra `&` is redundant and can make the code less clear. Format macros take + /// references to the arguments internally, so passing `&x` when `x` is already a + /// reference produces a double reference. The compiler is currently unable to + /// optimize double references, which results in about 6% degradation per call. + /// + /// ### Example + /// ```no_run + /// let s: &str = "hello"; + /// println!("{}", &s); + /// ``` + /// Use instead: + /// ```no_run + /// let s: &str = "hello"; + /// println!("{}", s); + /// ``` + #[clippy::version = "1.97.0"] + pub USELESS_BORROWS_IN_FORMATTING, + perf, + "redundant reference in format args causes double reference" +} + impl_lint_pass!(FormatArgs<'_> => [ FORMAT_IN_FORMAT_ARGS, POINTER_FORMAT, @@ -265,6 +293,7 @@ impl_lint_pass!(FormatArgs<'_> => [ UNNECESSARY_DEBUG_FORMATTING, UNNECESSARY_TRAILING_COMMA, UNUSED_FORMAT_SPECS, + USELESS_BORROWS_IN_FORMATTING, ]); #[expect(clippy::struct_field_names)] @@ -363,6 +392,18 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { && let Some(arg_expr) = find_format_arg_expr(self.expr, arg) { self.check_unused_format_specifier(placeholder, arg_expr); + self.check_useless_borrows_in_formatting(placeholder, arg_expr); + + // Check width and precision arguments the same way as the value + for opt in [&placeholder.format_options.width, &placeholder.format_options.precision] { + if let Some(FormatCount::Argument(position)) = opt.as_ref() + && let Ok(pos_index) = position.index + && let Some(pos_arg) = self.format_args.arguments.all_args().get(pos_index) + && let Some(pos_arg_expr) = find_format_arg_expr(self.expr, pos_arg) + { + self.check_useless_borrows_in_formatting(placeholder, pos_arg_expr); + } + } if placeholder.format_trait == FormatTrait::Display && placeholder.format_options == FormatOptions::default() @@ -392,6 +433,43 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { } } + fn check_useless_borrows_in_formatting(&self, placeholder: &FormatPlaceholder, arg_expr: &Expr<'tcx>) { + if !arg_expr.span.from_expansion() + && !is_from_proc_macro(self.cx, arg_expr) + && let Some(fmt_trait) = match placeholder.format_trait { + FormatTrait::Display => self.cx.tcx.get_diagnostic_item(sym::Display), + FormatTrait::Debug => self.cx.tcx.get_diagnostic_item(sym::Debug), + _ => None, + } + && let Some(sized_trait) = self.cx.tcx.lang_items().sized_trait() + && let peeled_expr = peel_hir_expr_while(arg_expr, |e| { + // Need to handle `&&&T` to `&T` when a single ref is still required + if let ExprKind::AddrOf(BorrowKind::Ref, _, e) = e.kind + && let ty = self.cx.typeck_results().expr_ty(e) + && implements_trait(self.cx, ty, sized_trait, &[]) + && implements_trait(self.cx, ty, fmt_trait, &[]) + { + Some(e) + } else { + None + } + }) + && !std::ptr::eq(arg_expr, peeled_expr) + && let Some(peeled_snippet) = snippet_opt(self.cx, peeled_expr.span) + { + let name = self.cx.tcx.item_name(self.macro_call.def_id); + span_lint_and_sugg( + self.cx, + USELESS_BORROWS_IN_FORMATTING, + arg_expr.span, + format!("redundant reference in `{name}!` argument"), + "remove the redundant `&`", + peeled_snippet, + Applicability::MachineApplicable, + ); + } + } + fn check_unused_format_specifier(&self, placeholder: &FormatPlaceholder, arg: &Expr<'_>) { let options = &placeholder.format_options; diff --git a/tests/ui/explicit_deref_methods.fixed b/tests/ui/explicit_deref_methods.fixed index 6c29630dc3a57..35149755a34be 100644 --- a/tests/ui/explicit_deref_methods.fixed +++ b/tests/ui/explicit_deref_methods.fixed @@ -3,14 +3,15 @@ #![allow(unused_variables, unused_must_use)] #![allow( clippy::borrow_deref_ref, - suspicious_double_ref_op, - noop_method_call, + clippy::deref_addrof, clippy::explicit_auto_deref, clippy::needless_borrow, clippy::no_effect, + clippy::useless_borrows_in_formatting, clippy::uninlined_format_args, clippy::unnecessary_literal_unwrap, - clippy::deref_addrof + noop_method_call, + suspicious_double_ref_op )] use std::ops::{Deref, DerefMut}; diff --git a/tests/ui/explicit_deref_methods.rs b/tests/ui/explicit_deref_methods.rs index f6309cd404b85..215c5450597f0 100644 --- a/tests/ui/explicit_deref_methods.rs +++ b/tests/ui/explicit_deref_methods.rs @@ -3,14 +3,15 @@ #![allow(unused_variables, unused_must_use)] #![allow( clippy::borrow_deref_ref, - suspicious_double_ref_op, - noop_method_call, + clippy::deref_addrof, clippy::explicit_auto_deref, clippy::needless_borrow, clippy::no_effect, + clippy::useless_borrows_in_formatting, clippy::uninlined_format_args, clippy::unnecessary_literal_unwrap, - clippy::deref_addrof + noop_method_call, + suspicious_double_ref_op )] use std::ops::{Deref, DerefMut}; diff --git a/tests/ui/explicit_deref_methods.stderr b/tests/ui/explicit_deref_methods.stderr index e2f2e68720b17..d7e41e4409faa 100644 --- a/tests/ui/explicit_deref_methods.stderr +++ b/tests/ui/explicit_deref_methods.stderr @@ -1,5 +1,5 @@ error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:58:19 + --> tests/ui/explicit_deref_methods.rs:59:19 | LL | let b: &str = a.deref(); | ^^^^^^^^^ help: try: `&*a` @@ -8,73 +8,73 @@ LL | let b: &str = a.deref(); = help: to override `-D warnings` add `#[allow(clippy::explicit_deref_methods)]` error: explicit `deref_mut` method call - --> tests/ui/explicit_deref_methods.rs:61:23 + --> tests/ui/explicit_deref_methods.rs:62:23 | LL | let b: &mut str = a.deref_mut(); | ^^^^^^^^^^^^^ help: try: `&mut **a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:65:39 + --> tests/ui/explicit_deref_methods.rs:66:39 | LL | let b: String = format!("{}, {}", a.deref(), a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:65:50 + --> tests/ui/explicit_deref_methods.rs:66:50 | LL | let b: String = format!("{}, {}", a.deref(), a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:69:20 + --> tests/ui/explicit_deref_methods.rs:70:20 | LL | println!("{}", a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:73:11 + --> tests/ui/explicit_deref_methods.rs:74:11 | LL | match a.deref() { | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:78:28 + --> tests/ui/explicit_deref_methods.rs:79:28 | LL | let b: String = concat(a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:81:13 + --> tests/ui/explicit_deref_methods.rs:82:13 | LL | let b = just_return(a).deref(); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `just_return(a)` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:84:28 + --> tests/ui/explicit_deref_methods.rs:85:28 | LL | let b: String = concat(just_return(a).deref()); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `just_return(a)` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:124:31 + --> tests/ui/explicit_deref_methods.rs:125:31 | LL | let b: &str = expr_deref!(a.deref()); | ^^^^^^^^^ help: try: `&*a` error: explicit `deref` method call - --> tests/ui/explicit_deref_methods.rs:154:14 + --> tests/ui/explicit_deref_methods.rs:155:14 | LL | let _ = &Deref::deref(&"foo"); | ^^^^^^^^^^^^^^^^^^^^ help: try: `*&"foo"` error: explicit `deref_mut` method call - --> tests/ui/explicit_deref_methods.rs:156:14 + --> tests/ui/explicit_deref_methods.rs:157:14 | LL | let _ = &DerefMut::deref_mut(&mut x); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut **&mut x` error: explicit `deref_mut` method call - --> tests/ui/explicit_deref_methods.rs:157:14 + --> tests/ui/explicit_deref_methods.rs:158:14 | LL | let _ = &DerefMut::deref_mut((&mut &mut x).deref_mut()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut ***(&mut &mut x)` diff --git a/tests/ui/recursive_format_impl.rs b/tests/ui/recursive_format_impl.rs index 9f46fef62354a..936d56877055e 100644 --- a/tests/ui/recursive_format_impl.rs +++ b/tests/ui/recursive_format_impl.rs @@ -3,6 +3,7 @@ clippy::borrow_deref_ref, clippy::deref_addrof, clippy::inherent_to_string_shadow_display, + clippy::useless_borrows_in_formatting, clippy::to_string_in_format_args, clippy::uninlined_format_args )] diff --git a/tests/ui/recursive_format_impl.stderr b/tests/ui/recursive_format_impl.stderr index 4361d612bf2a5..c813bb349b86a 100644 --- a/tests/ui/recursive_format_impl.stderr +++ b/tests/ui/recursive_format_impl.stderr @@ -1,5 +1,5 @@ error: using `self.to_string` in `fmt::Display` implementation will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:31:25 + --> tests/ui/recursive_format_impl.rs:32:25 | LL | write!(f, "{}", self.to_string()) | ^^^^^^^^^^^^^^^^ @@ -8,55 +8,55 @@ LL | write!(f, "{}", self.to_string()) = help: to override `-D warnings` add `#[allow(clippy::recursive_format_impl)]` error: using `self` as `Display` in `impl Display` will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:76:9 + --> tests/ui/recursive_format_impl.rs:77:9 | LL | write!(f, "{}", self) | ^^^^^^^^^^^^^^^^^^^^^ error: using `self` as `Display` in `impl Display` will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:86:9 + --> tests/ui/recursive_format_impl.rs:87:9 | LL | write!(f, "{}", &self) | ^^^^^^^^^^^^^^^^^^^^^^ error: using `self` as `Debug` in `impl Debug` will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:93:9 + --> tests/ui/recursive_format_impl.rs:94:9 | LL | write!(f, "{:?}", &self) | ^^^^^^^^^^^^^^^^^^^^^^^^ error: using `self` as `Display` in `impl Display` will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:103:9 + --> tests/ui/recursive_format_impl.rs:104:9 | LL | write!(f, "{}", &&&self) | ^^^^^^^^^^^^^^^^^^^^^^^^ error: using `self` as `Display` in `impl Display` will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:178:9 + --> tests/ui/recursive_format_impl.rs:179:9 | LL | write!(f, "{}", &*self) | ^^^^^^^^^^^^^^^^^^^^^^^ error: using `self` as `Debug` in `impl Debug` will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:185:9 + --> tests/ui/recursive_format_impl.rs:186:9 | LL | write!(f, "{:?}", &*self) | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: using `self` as `Display` in `impl Display` will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:202:9 + --> tests/ui/recursive_format_impl.rs:203:9 | LL | write!(f, "{}", *self) | ^^^^^^^^^^^^^^^^^^^^^^ error: using `self` as `Display` in `impl Display` will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:219:9 + --> tests/ui/recursive_format_impl.rs:220:9 | LL | write!(f, "{}", **&&*self) | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: using `self` as `Display` in `impl Display` will cause infinite recursion - --> tests/ui/recursive_format_impl.rs:236:9 + --> tests/ui/recursive_format_impl.rs:237:9 | LL | write!(f, "{}", &&**&&*self) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/useless_borrows_in_formatting.fixed b/tests/ui/useless_borrows_in_formatting.fixed new file mode 100644 index 0000000000000..82319252b4688 --- /dev/null +++ b/tests/ui/useless_borrows_in_formatting.fixed @@ -0,0 +1,118 @@ +// When testing or blessing this lint, set TESTNAME so only this test runs: +// TESTNAME=useless_borrows_in_formatting cargo uitest +// TESTNAME=useless_borrows_in_formatting cargo uibless +#![warn(clippy::useless_borrows_in_formatting)] +#![allow(unused, clippy::useless_format)] + +fn main() { + let s: &str = "hello"; + println!("{}", s); //~ useless_borrows_in_formatting + println!("{:?}", s); //~ useless_borrows_in_formatting + println!("{}", s); //~ useless_borrows_in_formatting + + let string = String::from("world"); + println!("{}", string); //~ useless_borrows_in_formatting + println!("{:?}", string); //~ useless_borrows_in_formatting + println!("{}", string); //~ useless_borrows_in_formatting + println!("{}", &string[..2]); //~ useless_borrows_in_formatting + println!("{:?}", &string[..2]); //~ useless_borrows_in_formatting + // these are ok + println!("{}", &string[..2]); + println!("{:?}", &string[..2]); + + let n: i32 = 42; + println!("{}", n); //~ useless_borrows_in_formatting + println!("{:?}", n); //~ useless_borrows_in_formatting + println!("{}", n); //~ useless_borrows_in_formatting + + // Reference to slice element + let slice: [i32; 3] = [1, 2, 3]; + println!("{}", slice[0]); //~ useless_borrows_in_formatting + println!("{:?}", slice[0]); //~ useless_borrows_in_formatting + println!("{}", slice[0]); //~ useless_borrows_in_formatting + + // big array: should not suggest removing & because of the size of the output + println!( + "{:?}", + [ + //~^ useless_borrows_in_formatting + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + ] + ); + + println!("{:?}", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); + //~^ useless_borrows_in_formatting + + let a: [i32; 2] = [1, 2]; + println!("{:016x?}", [a[0], a[1], a[0], a[1]]); //~ useless_borrows_in_formatting + + // &slice[0..1] with {:?}: inner type [i32] is unsized, so we don't suggest removing & + println!("{:?}", &slice[0..1]); // don't change + println!("{:?}", &slice[0..1]); //~ useless_borrows_in_formatting + + // Pointer formatting ({:p}): never suggest any changes to it + let x: i32 = 0; + println!("{:p}", &x); // don't change + println!("{:p}", &&x); // should change, but out of scope + + struct Wrap(i32); + let w: Wrap = Wrap(42); + println!("{}", w.0); //~ useless_borrows_in_formatting + println!("{:?}", w.0); //~ useless_borrows_in_formatting + println!("{}", w.0); //~ useless_borrows_in_formatting + + struct WrapRef<'a>(&'a i32); + let n: i32 = 42; + let w: WrapRef<'_> = WrapRef(&n); + println!("{}", w.0); //~ useless_borrows_in_formatting + println!("{:?}", w.0); //~ useless_borrows_in_formatting + println!("{}", w.0); //~ useless_borrows_in_formatting + + let a: &mut String = &mut String::from("foo"); + println!("{}", *a); //~ useless_borrows_in_formatting + println!("{:?}", *a); //~ useless_borrows_in_formatting + + // Parenthesized expressions: &(expr) + let n: i32 = 42; + println!("{}", (n)); //~ useless_borrows_in_formatting + println!("{:?}", (n + 1)); //~ useless_borrows_in_formatting + println!("{}", (String::from("paren"))); //~ useless_borrows_in_formatting + + // Block expressions: &{ expr } + println!("{}", { n }); //~ useless_borrows_in_formatting + println!("{:?}", { n + 1 }); //~ useless_borrows_in_formatting + println!("{}", { String::from("block") }); //~ useless_borrows_in_formatting + + let v1 = 42.12345; + let v2 = 20; + let v3 = 10; + println!("{0:1$.2$}", v1, v2, v3); + //~^ useless_borrows_in_formatting + //~| useless_borrows_in_formatting + //~| useless_borrows_in_formatting + println!("{0:1$.2$?}", v1, v2, v3); + //~^ useless_borrows_in_formatting + //~| useless_borrows_in_formatting + //~| useless_borrows_in_formatting + println!("{0:1$.2$}", v1, v2, v3); //~ useless_borrows_in_formatting + println!("{0:1$.2$}", v1, v2, v3); //~ useless_borrows_in_formatting + println!("{0:1$.2$}", v1, v2, v3); //~ useless_borrows_in_formatting + + // Macro wrapping println! - should not lint (println! call is inside macro expansion) + macro_rules! my_println { + ($($args:tt)*) => { + println!($($args)*); + }; + } + my_println!("{}", &n); + + // Arguments coming from a macro - should not lint (& comes from expansion) + macro_rules! make_ref { + ($e:expr) => { + &$e + }; + } + println!("{}", make_ref!(n)); +} diff --git a/tests/ui/useless_borrows_in_formatting.rs b/tests/ui/useless_borrows_in_formatting.rs new file mode 100644 index 0000000000000..6d23c9c8e7bbc --- /dev/null +++ b/tests/ui/useless_borrows_in_formatting.rs @@ -0,0 +1,118 @@ +// When testing or blessing this lint, set TESTNAME so only this test runs: +// TESTNAME=useless_borrows_in_formatting cargo uitest +// TESTNAME=useless_borrows_in_formatting cargo uibless +#![warn(clippy::useless_borrows_in_formatting)] +#![allow(unused, clippy::useless_format)] + +fn main() { + let s: &str = "hello"; + println!("{}", &s); //~ useless_borrows_in_formatting + println!("{:?}", &s); //~ useless_borrows_in_formatting + println!("{}", &&s); //~ useless_borrows_in_formatting + + let string = String::from("world"); + println!("{}", &string); //~ useless_borrows_in_formatting + println!("{:?}", &string); //~ useless_borrows_in_formatting + println!("{}", &&string); //~ useless_borrows_in_formatting + println!("{}", &&string[..2]); //~ useless_borrows_in_formatting + println!("{:?}", &&string[..2]); //~ useless_borrows_in_formatting + // these are ok + println!("{}", &string[..2]); + println!("{:?}", &string[..2]); + + let n: i32 = 42; + println!("{}", &n); //~ useless_borrows_in_formatting + println!("{:?}", &n); //~ useless_borrows_in_formatting + println!("{}", &&n); //~ useless_borrows_in_formatting + + // Reference to slice element + let slice: [i32; 3] = [1, 2, 3]; + println!("{}", &slice[0]); //~ useless_borrows_in_formatting + println!("{:?}", &slice[0]); //~ useless_borrows_in_formatting + println!("{}", &&slice[0]); //~ useless_borrows_in_formatting + + // big array: should not suggest removing & because of the size of the output + println!( + "{:?}", + &[ + //~^ useless_borrows_in_formatting + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + ] + ); + + println!("{:?}", &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); + //~^ useless_borrows_in_formatting + + let a: [i32; 2] = [1, 2]; + println!("{:016x?}", &[a[0], a[1], a[0], a[1]]); //~ useless_borrows_in_formatting + + // &slice[0..1] with {:?}: inner type [i32] is unsized, so we don't suggest removing & + println!("{:?}", &slice[0..1]); // don't change + println!("{:?}", &&slice[0..1]); //~ useless_borrows_in_formatting + + // Pointer formatting ({:p}): never suggest any changes to it + let x: i32 = 0; + println!("{:p}", &x); // don't change + println!("{:p}", &&x); // should change, but out of scope + + struct Wrap(i32); + let w: Wrap = Wrap(42); + println!("{}", &w.0); //~ useless_borrows_in_formatting + println!("{:?}", &w.0); //~ useless_borrows_in_formatting + println!("{}", &&w.0); //~ useless_borrows_in_formatting + + struct WrapRef<'a>(&'a i32); + let n: i32 = 42; + let w: WrapRef<'_> = WrapRef(&n); + println!("{}", &w.0); //~ useless_borrows_in_formatting + println!("{:?}", &w.0); //~ useless_borrows_in_formatting + println!("{}", &&w.0); //~ useless_borrows_in_formatting + + let a: &mut String = &mut String::from("foo"); + println!("{}", &*a); //~ useless_borrows_in_formatting + println!("{:?}", &*a); //~ useless_borrows_in_formatting + + // Parenthesized expressions: &(expr) + let n: i32 = 42; + println!("{}", &(n)); //~ useless_borrows_in_formatting + println!("{:?}", &(n + 1)); //~ useless_borrows_in_formatting + println!("{}", &(String::from("paren"))); //~ useless_borrows_in_formatting + + // Block expressions: &{ expr } + println!("{}", &{ n }); //~ useless_borrows_in_formatting + println!("{:?}", &{ n + 1 }); //~ useless_borrows_in_formatting + println!("{}", &{ String::from("block") }); //~ useless_borrows_in_formatting + + let v1 = 42.12345; + let v2 = 20; + let v3 = 10; + println!("{0:1$.2$}", &v1, &v2, &v3); + //~^ useless_borrows_in_formatting + //~| useless_borrows_in_formatting + //~| useless_borrows_in_formatting + println!("{0:1$.2$?}", &v1, &v2, &v3); + //~^ useless_borrows_in_formatting + //~| useless_borrows_in_formatting + //~| useless_borrows_in_formatting + println!("{0:1$.2$}", &v1, v2, v3); //~ useless_borrows_in_formatting + println!("{0:1$.2$}", v1, &v2, v3); //~ useless_borrows_in_formatting + println!("{0:1$.2$}", v1, v2, &v3); //~ useless_borrows_in_formatting + + // Macro wrapping println! - should not lint (println! call is inside macro expansion) + macro_rules! my_println { + ($($args:tt)*) => { + println!($($args)*); + }; + } + my_println!("{}", &n); + + // Arguments coming from a macro - should not lint (& comes from expansion) + macro_rules! make_ref { + ($e:expr) => { + &$e + }; + } + println!("{}", make_ref!(n)); +} diff --git a/tests/ui/useless_borrows_in_formatting.stderr b/tests/ui/useless_borrows_in_formatting.stderr new file mode 100644 index 0000000000000..e9e028c81ac43 --- /dev/null +++ b/tests/ui/useless_borrows_in_formatting.stderr @@ -0,0 +1,266 @@ +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:9:20 + | +LL | println!("{}", &s); + | ^^ help: remove the redundant `&`: `s` + | + = note: `-D clippy::useless-borrows-in-formatting` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::useless_borrows_in_formatting)]` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:10:22 + | +LL | println!("{:?}", &s); + | ^^ help: remove the redundant `&`: `s` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:11:20 + | +LL | println!("{}", &&s); + | ^^^ help: remove the redundant `&`: `s` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:14:20 + | +LL | println!("{}", &string); + | ^^^^^^^ help: remove the redundant `&`: `string` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:15:22 + | +LL | println!("{:?}", &string); + | ^^^^^^^ help: remove the redundant `&`: `string` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:16:20 + | +LL | println!("{}", &&string); + | ^^^^^^^^ help: remove the redundant `&`: `string` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:17:20 + | +LL | println!("{}", &&string[..2]); + | ^^^^^^^^^^^^^ help: remove the redundant `&`: `&string[..2]` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:18:22 + | +LL | println!("{:?}", &&string[..2]); + | ^^^^^^^^^^^^^ help: remove the redundant `&`: `&string[..2]` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:24:20 + | +LL | println!("{}", &n); + | ^^ help: remove the redundant `&`: `n` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:25:22 + | +LL | println!("{:?}", &n); + | ^^ help: remove the redundant `&`: `n` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:26:20 + | +LL | println!("{}", &&n); + | ^^^ help: remove the redundant `&`: `n` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:30:20 + | +LL | println!("{}", &slice[0]); + | ^^^^^^^^^ help: remove the redundant `&`: `slice[0]` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:31:22 + | +LL | println!("{:?}", &slice[0]); + | ^^^^^^^^^ help: remove the redundant `&`: `slice[0]` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:32:20 + | +LL | println!("{}", &&slice[0]); + | ^^^^^^^^^^ help: remove the redundant `&`: `slice[0]` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:37:9 + | +LL | / &[ +LL | | +LL | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, +LL | | 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, +LL | | 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +LL | | ] + | |_________^ + | +help: remove the redundant `&` + | +LL ~ [ +LL + +LL + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, +LL + 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, +LL + 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +LL + ] + | + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:45:22 + | +LL | println!("{:?}", &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the redundant `&`: `[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:49:26 + | +LL | println!("{:016x?}", &[a[0], a[1], a[0], a[1]]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the redundant `&`: `[a[0], a[1], a[0], a[1]]` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:53:22 + | +LL | println!("{:?}", &&slice[0..1]); + | ^^^^^^^^^^^^^ help: remove the redundant `&`: `&slice[0..1]` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:62:20 + | +LL | println!("{}", &w.0); + | ^^^^ help: remove the redundant `&`: `w.0` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:63:22 + | +LL | println!("{:?}", &w.0); + | ^^^^ help: remove the redundant `&`: `w.0` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:64:20 + | +LL | println!("{}", &&w.0); + | ^^^^^ help: remove the redundant `&`: `w.0` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:69:20 + | +LL | println!("{}", &w.0); + | ^^^^ help: remove the redundant `&`: `w.0` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:70:22 + | +LL | println!("{:?}", &w.0); + | ^^^^ help: remove the redundant `&`: `w.0` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:71:20 + | +LL | println!("{}", &&w.0); + | ^^^^^ help: remove the redundant `&`: `w.0` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:74:20 + | +LL | println!("{}", &*a); + | ^^^ help: remove the redundant `&`: `*a` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:75:22 + | +LL | println!("{:?}", &*a); + | ^^^ help: remove the redundant `&`: `*a` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:79:20 + | +LL | println!("{}", &(n)); + | ^^^^ help: remove the redundant `&`: `(n)` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:80:22 + | +LL | println!("{:?}", &(n + 1)); + | ^^^^^^^^ help: remove the redundant `&`: `(n + 1)` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:81:20 + | +LL | println!("{}", &(String::from("paren"))); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the redundant `&`: `(String::from("paren"))` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:84:20 + | +LL | println!("{}", &{ n }); + | ^^^^^^ help: remove the redundant `&`: `{ n }` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:85:22 + | +LL | println!("{:?}", &{ n + 1 }); + | ^^^^^^^^^^ help: remove the redundant `&`: `{ n + 1 }` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:86:20 + | +LL | println!("{}", &{ String::from("block") }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the redundant `&`: `{ String::from("block") }` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:91:27 + | +LL | println!("{0:1$.2$}", &v1, &v2, &v3); + | ^^^ help: remove the redundant `&`: `v1` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:91:32 + | +LL | println!("{0:1$.2$}", &v1, &v2, &v3); + | ^^^ help: remove the redundant `&`: `v2` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:91:37 + | +LL | println!("{0:1$.2$}", &v1, &v2, &v3); + | ^^^ help: remove the redundant `&`: `v3` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:95:28 + | +LL | println!("{0:1$.2$?}", &v1, &v2, &v3); + | ^^^ help: remove the redundant `&`: `v1` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:95:33 + | +LL | println!("{0:1$.2$?}", &v1, &v2, &v3); + | ^^^ help: remove the redundant `&`: `v2` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:95:38 + | +LL | println!("{0:1$.2$?}", &v1, &v2, &v3); + | ^^^ help: remove the redundant `&`: `v3` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:99:27 + | +LL | println!("{0:1$.2$}", &v1, v2, v3); + | ^^^ help: remove the redundant `&`: `v1` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:100:31 + | +LL | println!("{0:1$.2$}", v1, &v2, v3); + | ^^^ help: remove the redundant `&`: `v2` + +error: redundant reference in `println!` argument + --> tests/ui/useless_borrows_in_formatting.rs:101:35 + | +LL | println!("{0:1$.2$}", v1, v2, &v3); + | ^^^ help: remove the redundant `&`: `v3` + +error: aborting due to 41 previous errors + From 0c147218939c343986d4cddccf1adae3d23fdd62 Mon Sep 17 00:00:00 2001 From: Souradip Pal Date: Sat, 18 Apr 2026 02:47:52 +0530 Subject: [PATCH 12/38] fix collapsible_match: skip if-guard lint when non-wildcard arms follow --- clippy_lints/src/matches/collapsible_match.rs | 19 ++++++++++++++++--- clippy_lints/src/methods/mod.rs | 9 ++------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/clippy_lints/src/matches/collapsible_match.rs b/clippy_lints/src/matches/collapsible_match.rs index cb784d1ff6600..b97e2decc39de 100644 --- a/clippy_lints/src/matches/collapsible_match.rs +++ b/clippy_lints/src/matches/collapsible_match.rs @@ -23,8 +23,19 @@ use super::{COLLAPSIBLE_MATCH, pat_contains_disallowed_or}; pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>], msrv: Msrv) { if let Some(els_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) { - for arm in arms { - check_arm(cx, true, arm.pat, expr, arm.body, arm.guard, Some(els_arm.body), msrv); + for (idx, arm) in arms.iter().enumerate() { + let only_wildcards_after = arms[idx + 1..].iter().all(|a| arm_is_wild_like(cx, a)); + check_arm( + cx, + true, + arm.pat, + expr, + arm.body, + arm.guard, + Some(els_arm.body), + msrv, + only_wildcards_after, + ); } } } @@ -37,7 +48,7 @@ pub(super) fn check_if_let<'tcx>( let_expr: &'tcx Expr<'_>, msrv: Msrv, ) { - check_arm(cx, false, pat, let_expr, body, None, else_expr, msrv); + check_arm(cx, false, pat, let_expr, body, None, else_expr, msrv, false); } #[expect(clippy::too_many_arguments, clippy::too_many_lines)] @@ -50,6 +61,7 @@ fn check_arm<'tcx>( outer_guard: Option<&'tcx Expr<'tcx>>, outer_else_body: Option<&'tcx Expr<'tcx>>, msrv: Msrv, + only_wildcards_after: bool, ) { let inner_expr = peel_blocks_with_stmt(outer_then_body); if let Some(inner) = IfLetOrMatch::parse(cx, inner_expr) @@ -126,6 +138,7 @@ fn check_arm<'tcx>( ); }); } else if outer_is_match // Leave if-let to the `collapsible_if` lint + && only_wildcards_after // adding a guard allows fall-through; unsafe if other arms follow && let Some(inner) = If::hir(inner_expr) && outer_pat.span.eq_ctxt(inner.cond.span) && match (outer_else_body, inner.r#else) { diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index d9774426ec25c..e9a2b17f31b5a 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -5221,7 +5221,6 @@ impl Methods { format_collect::check(cx, expr, m_arg, m_ident_span); }, Some((sym::take, take_self_arg, [take_arg], _, _)) => { - #[expect(clippy::collapsible_match)] if self.msrv.meets(cx, msrvs::STR_REPEAT) { manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg); } @@ -5546,9 +5545,7 @@ impl Methods { (sym::open, [_]) => { open_options::check(cx, expr, recv); }, - (sym::or_else, [arg]) => - { - #[expect(clippy::collapsible_match)] + (sym::or_else, [arg]) => { if !bind_instead_of_map::check_or_else_err(cx, expr, recv, arg) { unnecessary_lazy_eval::check(cx, expr, recv, arg, "or"); } @@ -5653,9 +5650,7 @@ impl Methods { (sym::try_into, []) if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::TryInto) => { unnecessary_fallible_conversions::check_method(cx, expr); }, - (sym::to_owned, []) => - { - #[expect(clippy::collapsible_match)] + (sym::to_owned, []) => { if !suspicious_to_owned::check(cx, expr, span) { implicit_clone::check(cx, name, expr, recv); } From 2e7b2ba1e1666a979c2fb2bc5cab5239d94f321e Mon Sep 17 00:00:00 2001 From: Souradip Pal Date: Sat, 18 Apr 2026 02:47:56 +0530 Subject: [PATCH 13/38] add tests for collapsible_match: no lint when intermediate non-wildcard arm present --- tests/ui/collapsible_match.rs | 20 ++++++++++++++++++++ tests/ui/collapsible_match_fixable.fixed | 15 +++++++++++++++ tests/ui/collapsible_match_fixable.rs | 16 ++++++++++++++++ tests/ui/collapsible_match_fixable.stderr | 20 +++++++++++++++++++- 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/tests/ui/collapsible_match.rs b/tests/ui/collapsible_match.rs index 98f2fcfdf479f..51cb0168c735b 100644 --- a/tests/ui/collapsible_match.rs +++ b/tests/ui/collapsible_match.rs @@ -390,6 +390,26 @@ fn take(t: T) {} fn main() {} +// https://github.com/rust-lang/rust-clippy/issues/16875 +// Adding a match guard allows fall-through to subsequent arms, which changes semantics +// when non-wildcard arms follow the arm being collapsed. +fn issue16875(a: Option<&str>, b: i32) -> i32 { + let mut res = 0; + // should NOT lint: `_ if b == 1` is not wild-like (has a guard), so collapsing + // `Some(_)` into `Some(_) if b == 0` would let `_ if b == 1` match Some values + // that previously fell through to the no-op arm body. + match a { + Some(_) => { + if b == 0 { + res = 1; + } + }, + _ if b == 1 => res = 2, + _ => {}, + } + res +} + fn issue16705(x: Option) { fn takes_ownership(s: String) -> bool { true diff --git a/tests/ui/collapsible_match_fixable.fixed b/tests/ui/collapsible_match_fixable.fixed index db76530aee144..8e5934d3693a3 100644 --- a/tests/ui/collapsible_match_fixable.fixed +++ b/tests/ui/collapsible_match_fixable.fixed @@ -28,3 +28,18 @@ fn issue16558() { _ => 1, }; } + +// https://github.com/rust-lang/rust-clippy/issues/16875 +// lint still fires when only wildcard-like arms follow (fall-through is harmless) +fn issue16875(a: Option<&str>, b: i32) -> i32 { + let mut res = 0; + match a { + Some(_) + if b == 0 => { + //~^ collapsible_match + res = 1; + }, + _ => {}, + } + res +} diff --git a/tests/ui/collapsible_match_fixable.rs b/tests/ui/collapsible_match_fixable.rs index 94bf1d6bfdfab..e2ccae4559612 100644 --- a/tests/ui/collapsible_match_fixable.rs +++ b/tests/ui/collapsible_match_fixable.rs @@ -29,3 +29,19 @@ fn issue16558() { _ => 1, }; } + +// https://github.com/rust-lang/rust-clippy/issues/16875 +// lint still fires when only wildcard-like arms follow (fall-through is harmless) +fn issue16875(a: Option<&str>, b: i32) -> i32 { + let mut res = 0; + match a { + Some(_) => { + if b == 0 { + //~^ collapsible_match + res = 1; + } + }, + _ => {}, + } + res +} diff --git a/tests/ui/collapsible_match_fixable.stderr b/tests/ui/collapsible_match_fixable.stderr index 4d501cbd0993d..0be47076fa472 100644 --- a/tests/ui/collapsible_match_fixable.stderr +++ b/tests/ui/collapsible_match_fixable.stderr @@ -46,5 +46,23 @@ LL | LL ~ , | -error: aborting due to 3 previous errors +error: this `if` can be collapsed into the outer `match` + --> tests/ui/collapsible_match_fixable.rs:39:13 + | +LL | / if b == 0 { +LL | | +LL | | res = 1; +LL | | } + | |_____________^ + | +help: collapse nested if block + | +LL ~ Some(_) +LL ~ if b == 0 => { +LL | +LL | res = 1; +LL ~ }, + | + +error: aborting due to 4 previous errors From c7cadb6fe4470568caf975cfad94fa07b02bd531 Mon Sep 17 00:00:00 2001 From: Souradip Pal Date: Sat, 18 Apr 2026 13:46:19 +0530 Subject: [PATCH 14/38] Update clippy_lints/src/matches/collapsible_match.rs Co-authored-by: Samuel Tardieu --- clippy_lints/src/matches/collapsible_match.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/matches/collapsible_match.rs b/clippy_lints/src/matches/collapsible_match.rs index b97e2decc39de..d86b05e5c8824 100644 --- a/clippy_lints/src/matches/collapsible_match.rs +++ b/clippy_lints/src/matches/collapsible_match.rs @@ -23,8 +23,9 @@ use super::{COLLAPSIBLE_MATCH, pat_contains_disallowed_or}; pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>], msrv: Msrv) { if let Some(els_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) { + let last_non_wildcard = arms.iter().rposition(|arm| !arm_is_wild_like(cx, arm)); for (idx, arm) in arms.iter().enumerate() { - let only_wildcards_after = arms[idx + 1..].iter().all(|a| arm_is_wild_like(cx, a)); + let only_wildcards_after = last_non_wildcard.is_none_or(|lnw| idx >= lnw); check_arm( cx, true, From aa7dd1826105739d45b01d9782b64ac3662e049e Mon Sep 17 00:00:00 2001 From: pocopepe Date: Sat, 18 Apr 2026 15:18:22 +0530 Subject: [PATCH 15/38] fix `from_over_into` false positive with conflicting blanket From impl --- clippy_lints/src/from_over_into.rs | 12 ++++++++++++ tests/ui/from_over_into.fixed | 20 ++++++++++++++++++++ tests/ui/from_over_into.rs | 20 ++++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/clippy_lints/src/from_over_into.rs b/clippy_lints/src/from_over_into.rs index 841d561b371cc..1bf7db8a5f7de 100644 --- a/clippy_lints/src/from_over_into.rs +++ b/clippy_lints/src/from_over_into.rs @@ -80,6 +80,8 @@ impl<'tcx> LateLintPass<'tcx> for FromOverInto { && cx.tcx.is_diagnostic_item(sym::Into, middle_trait_ref.def_id) && !matches!(middle_trait_ref.args.type_at(1).kind(), ty::Alias(ty::Opaque, _)) && self.msrv.meets(cx, msrvs::RE_REBALANCING_COHERENCE) + // skip if there's a blanket From impl, the suggested impl would conflict + && !has_blanket_from_impl(cx, middle_trait_ref.self_ty()) { span_lint_and_then( cx, @@ -163,6 +165,16 @@ impl<'tcx> Visitor<'tcx> for SelfFinder<'_, 'tcx> { } } +fn has_blanket_from_impl<'tcx>(cx: &LateContext<'tcx>, self_ty: ty::Ty<'tcx>) -> bool { + let Some(from_def_id) = cx.tcx.get_diagnostic_item(sym::From) else { + return false; + }; + cx.tcx.all_impls(from_def_id).any(|impl_id| { + let impl_trait_ref = cx.tcx.impl_trait_ref(impl_id).instantiate_identity(); + impl_trait_ref.self_ty() == self_ty && matches!(impl_trait_ref.args.type_at(1).kind(), ty::Param(_)) + }) +} + fn convert_to_from( cx: &LateContext<'_>, into_trait_seg: &PathSegment<'_>, diff --git a/tests/ui/from_over_into.fixed b/tests/ui/from_over_into.fixed index 7229e5a2d3589..3eed0ad52e9a7 100644 --- a/tests/ui/from_over_into.fixed +++ b/tests/ui/from_over_into.fixed @@ -116,4 +116,24 @@ fn issue_112502() { } } +fn issue_16823() { + pub struct Foo(pub String); + + impl From for Foo + where + String: From, + { + fn from(val: T) -> Self { + Self(String::from(val)) + } + } + + // no lint, From for String would conflict with the blanket impl above + impl Into for Foo { + fn into(self) -> String { + self.0 + } + } +} + fn main() {} diff --git a/tests/ui/from_over_into.rs b/tests/ui/from_over_into.rs index 9c75969c5c134..b4f58df0a4372 100644 --- a/tests/ui/from_over_into.rs +++ b/tests/ui/from_over_into.rs @@ -116,4 +116,24 @@ fn issue_112502() { } } +fn issue_16823() { + pub struct Foo(pub String); + + impl From for Foo + where + String: From, + { + fn from(val: T) -> Self { + Self(String::from(val)) + } + } + + // no lint, From for String would conflict with the blanket impl above + impl Into for Foo { + fn into(self) -> String { + self.0 + } + } +} + fn main() {} From 2d6808aa1107b08b14d4394f431dfa53c91d599c Mon Sep 17 00:00:00 2001 From: pocopepe Date: Sat, 18 Apr 2026 17:34:35 +0530 Subject: [PATCH 16/38] use unqualified Ty to fix rustc internal lint warning --- clippy_lints/src/from_over_into.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/from_over_into.rs b/clippy_lints/src/from_over_into.rs index 1bf7db8a5f7de..fdb816fcb7e7f 100644 --- a/clippy_lints/src/from_over_into.rs +++ b/clippy_lints/src/from_over_into.rs @@ -10,11 +10,11 @@ use rustc_errors::Applicability; use rustc_hir::intravisit::{Visitor, walk_path}; use rustc_hir::{ FnRetTy, GenericArg, GenericArgs, HirId, Impl, ImplItemId, ImplItemKind, Item, ItemKind, PatKind, Path, - PathSegment, Ty, TyKind, + PathSegment, Ty as HirTy, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter::OnlyBodies; -use rustc_middle::ty; +use rustc_middle::ty::{self, Ty}; use rustc_session::impl_lint_pass; use rustc_span::symbol::{kw, sym}; use rustc_span::{Span, Symbol}; @@ -165,7 +165,7 @@ impl<'tcx> Visitor<'tcx> for SelfFinder<'_, 'tcx> { } } -fn has_blanket_from_impl<'tcx>(cx: &LateContext<'tcx>, self_ty: ty::Ty<'tcx>) -> bool { +fn has_blanket_from_impl<'tcx>(cx: &LateContext<'tcx>, self_ty: Ty<'tcx>) -> bool { let Some(from_def_id) = cx.tcx.get_diagnostic_item(sym::From) else { return false; }; @@ -178,8 +178,8 @@ fn has_blanket_from_impl<'tcx>(cx: &LateContext<'tcx>, self_ty: ty::Ty<'tcx>) -> fn convert_to_from( cx: &LateContext<'_>, into_trait_seg: &PathSegment<'_>, - target_ty: &Ty<'_>, - self_ty: &Ty<'_>, + target_ty: &HirTy<'_>, + self_ty: &HirTy<'_>, impl_item_ref: ImplItemId, ) -> Option> { if !target_ty.find_self_aliases().is_empty() { From 7fdbf62bdf0bc5909c7cc6430867f904485fceff Mon Sep 17 00:00:00 2001 From: CoCo-Japan-pan <115922543+CoCo-Japan-pan@users.noreply.github.com> Date: Sun, 19 Apr 2026 00:01:43 +0900 Subject: [PATCH 17/38] Update AST pretty printing --- clippy_utils/src/ast_utils/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_utils/src/ast_utils/mod.rs b/clippy_utils/src/ast_utils/mod.rs index c96c0649753fd..82142d55e21d8 100644 --- a/clippy_utils/src/ast_utils/mod.rs +++ b/clippy_utils/src/ast_utils/mod.rs @@ -448,30 +448,30 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { }, ( Trait(box ast::Trait { + impl_restriction: liprt, constness: lc, is_auto: la, safety: lu, - impl_restriction: liprt, ident: li, generics: lg, bounds: lb, items: lis, }), Trait(box ast::Trait { + impl_restriction: riprt, constness: rc, is_auto: ra, safety: ru, - impl_restriction: riprt, ident: ri, generics: rg, bounds: rb, items: ris, }), ) => { - matches!(lc, ast::Const::No) == matches!(rc, ast::Const::No) + eq_impl_restriction(liprt, riprt) + && matches!(lc, ast::Const::No) == matches!(rc, ast::Const::No) && la == ra && matches!(lu, Safety::Default) == matches!(ru, Safety::Default) - && eq_impl_restriction(liprt, riprt) && eq_id(*li, *ri) && eq_generics(lg, rg) && over(lb, rb, eq_generic_bound) From f1b1096ca7c4d71b4f8b55a8983125788efc74af Mon Sep 17 00:00:00 2001 From: CoCo-Japan-pan <115922543+CoCo-Japan-pan@users.noreply.github.com> Date: Sun, 19 Apr 2026 00:22:40 +0900 Subject: [PATCH 18/38] Update HIR pretty printing --- clippy_lints/src/arbitrary_source_item_ordering.rs | 2 +- clippy_lints/src/doc/mod.rs | 2 +- clippy_utils/src/check_proc_macro.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/arbitrary_source_item_ordering.rs b/clippy_lints/src/arbitrary_source_item_ordering.rs index dae0c8439ea82..4a6c024cac9a6 100644 --- a/clippy_lints/src/arbitrary_source_item_ordering.rs +++ b/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -307,10 +307,10 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering { } }, ItemKind::Trait( + _impl_restriction, _constness, is_auto, _safety, - _impl_restriction, _ident, _generics, _generic_bounds, diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index a94133376cbfb..81812880a743a 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -769,7 +769,7 @@ impl<'tcx> LateLintPass<'tcx> for Documentation { { missing_headers::check(cx, item.owner_id, sig, headers, Some(body), self.check_private_items); }, - ItemKind::Trait(_, _, unsafety, ..) => match (headers.safety, unsafety) { + ItemKind::Trait(_, _, _, unsafety, ..) => match (headers.safety, unsafety) { (false, Safety::Unsafe) => span_lint( cx, MISSING_SAFETY_DOC, diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs index 601cf564062bb..44b9084cd4f69 100644 --- a/clippy_utils/src/check_proc_macro.rs +++ b/clippy_utils/src/check_proc_macro.rs @@ -265,14 +265,14 @@ fn item_search_pat(item: &Item<'_>) -> (Pat, Pat) { ItemKind::Struct(_, _, VariantData::Struct { .. }) => (Pat::Str("struct"), Pat::Str("}")), ItemKind::Struct(..) => (Pat::Str("struct"), Pat::Str(";")), ItemKind::Union(..) => (Pat::Str("union"), Pat::Str("}")), - ItemKind::Trait(_, _, Safety::Unsafe, ..) + ItemKind::Trait(_, _, _, Safety::Unsafe, ..) | ItemKind::Impl(Impl { of_trait: Some(TraitImplHeader { safety: Safety::Unsafe, .. }), .. }) => (Pat::Str("unsafe"), Pat::Str("}")), - ItemKind::Trait(_, IsAuto::Yes, ..) => (Pat::Str("auto"), Pat::Str("}")), + ItemKind::Trait(_, _, IsAuto::Yes, ..) => (Pat::Str("auto"), Pat::Str("}")), ItemKind::Trait(..) => (Pat::Str("trait"), Pat::Str("}")), ItemKind::Impl(_) => (Pat::Str("impl"), Pat::Str("}")), ItemKind::Mod(..) => (Pat::Str("mod"), Pat::Str("")), From 10eb2e7fadb9dabf6f8c5e1cc7c4cff32c38517a Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Mon, 9 Feb 2026 01:15:52 -0500 Subject: [PATCH 19/38] feat: add `format_width_ignored` lint Warn when a format width is less than the minimum output size for the format trait (e.g. `{:#02x}` yields "0x1", so width 2 is ignored). Help suggests removing the width or increasing it above the minimum. --- clippy_lints/src/format_args.rs | 55 +++++++- tests/ui/unused_format_specs_width.rs | 41 ++++++ tests/ui/unused_format_specs_width.stderr | 148 ++++++++++++++++++++++ 3 files changed, 237 insertions(+), 7 deletions(-) create mode 100644 tests/ui/unused_format_specs_width.rs create mode 100644 tests/ui/unused_format_specs_width.stderr diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index 232aa35df088f..239a2708de195 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -2,7 +2,7 @@ use std::collections::hash_map::Entry; use arrayvec::ArrayVec; use clippy_config::Conf; -use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::macros::{ FormatArgsStorage, FormatParamUsage, MacroCall, find_format_arg_expr, format_arg_removal_span, format_placeholder_format_span, is_assert_macro, is_format_macro, is_panic, matching_root_macro_call, @@ -14,9 +14,10 @@ use clippy_utils::source::{SpanRangeExt, snippet, snippet_opt}; use clippy_utils::ty::implements_trait; use clippy_utils::{is_from_proc_macro, is_in_test, peel_hir_expr_while, sym, trait_ref_of_method}; use itertools::Itertools; +use rustc_ast::FormatTrait::{Binary, Debug, Display, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex}; use rustc_ast::{ BorrowKind, FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, - FormatOptions, FormatPlaceholder, FormatTrait, + FormatOptions, FormatPlaceholder, }; use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; @@ -171,6 +172,11 @@ declare_clippy_lint! { /// ### What it does /// Checks for `Debug` formatting (`{:?}`) applied to an `OsStr` or `Path`. /// + /// This includes: + /// - Format specifiers on `format_args!()` (width, precision have no effect) + /// - Format width too small for the format trait (e.g. `{:#02x}` outputs "0x1" + /// so width 2 has no effect; minimum is 4 for alternate hex/octal/binary) + /// /// ### Why is this bad? /// Rust doesn't guarantee what `Debug` formatting looks like, and it could /// change in the future. `OsStr`s and `Path`s can be `Display` formatted @@ -231,6 +237,11 @@ declare_clippy_lint! { /// Detects [formatting parameters] that have no effect on the output of /// `format!()`, `println!()` or similar macros. /// + /// This includes: + /// - Format specifiers on `format_args!()` (width, precision have no effect) + /// - Format width too small for the format trait (e.g. `{:#02x}` outputs "0x1" + /// so width 2 has no effect; minimum is 4 for alternate hex/octal/binary) + /// /// ### Why is this bad? /// Shorter format specifiers are easier to read, it may also indicate that /// an expected formatting operation such as adding padding isn't happening. @@ -240,6 +251,9 @@ declare_clippy_lint! { /// println!("{:.}", 1.0); /// /// println!("not padded: {:5}", format_args!("...")); + /// + /// // width 2 has no effect for alternate hex (outputs "0x1") + /// format!("{:#02x}", 1_u8); /// ``` /// Use instead: /// ```no_run @@ -248,6 +262,8 @@ declare_clippy_lint! { /// println!("not padded: {}", format_args!("...")); /// // OR /// println!("padded: {:5}", format!("...")); + /// + /// format!("{:#04x}", 1_u8); // width 4 for two-digit zero-padded hex /// ``` /// /// [formatting parameters]: https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters @@ -392,6 +408,7 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { && let Some(arg_expr) = find_format_arg_expr(self.expr, arg) { self.check_unused_format_specifier(placeholder, arg_expr); + self.check_useless_format_width(placeholder); self.check_useless_borrows_in_formatting(placeholder, arg_expr); // Check width and precision arguments the same way as the value @@ -405,7 +422,7 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { } } - if placeholder.format_trait == FormatTrait::Display + if placeholder.format_trait == Display && placeholder.format_options == FormatOptions::default() && !self.is_aliased(index) { @@ -414,7 +431,7 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { self.check_to_string_in_format_args(name, arg_expr); } - if placeholder.format_trait == FormatTrait::Debug { + if placeholder.format_trait == Debug { let name = self.cx.tcx.item_name(self.macro_call.def_id); self.check_unnecessary_debug_formatting(name, arg_expr); if let Some(span) = placeholder.span @@ -424,7 +441,7 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { } } - if placeholder.format_trait == FormatTrait::Pointer + if placeholder.format_trait == Pointer && let Some(span) = placeholder.span { span_lint(self.cx, POINTER_FORMAT, span, "pointer formatting detected"); @@ -437,8 +454,8 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { if !arg_expr.span.from_expansion() && !is_from_proc_macro(self.cx, arg_expr) && let Some(fmt_trait) = match placeholder.format_trait { - FormatTrait::Display => self.cx.tcx.get_diagnostic_item(sym::Display), - FormatTrait::Debug => self.cx.tcx.get_diagnostic_item(sym::Debug), + Display => self.cx.tcx.get_diagnostic_item(sym::Display), + Debug => self.cx.tcx.get_diagnostic_item(sym::Debug), _ => None, } && let Some(sized_trait) = self.cx.tcx.lang_items().sized_trait() @@ -520,6 +537,30 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { } } + /// Lint when format width has no effect on the output because the format trait's + /// minimum output is larger (e.g. `{:#02X}` outputs "0x1" so width 2 has no effect). + fn check_useless_format_width(&self, placeholder: &FormatPlaceholder) { + let min_width = match placeholder.format_trait { + // 0x prefix, e.g. 0x1, 0o1, 0b1 + LowerHex | UpperHex | Octal | Binary if placeholder.format_options.alternate => 4, + LowerExp | UpperExp | Pointer => 4, // e.g. 1e0 with exponent, 0x1 for pointer + _ => return, + }; + if let Some(FormatCount::Literal(width_value)) = placeholder.format_options.width + && width_value < min_width + && let Some(placeholder_span) = placeholder.span + { + span_lint_and_help( + self.cx, + UNUSED_FORMAT_SPECS, + placeholder_span, + "format width has no effect on the output for this format trait", + None, + format!("consider removing the width or increasing it to at least {min_width}"), + ); + } + } + fn check_uninlined_args(&self) { if self.format_args.span.from_expansion() { return; diff --git a/tests/ui/unused_format_specs_width.rs b/tests/ui/unused_format_specs_width.rs new file mode 100644 index 0000000000000..e894ea76fc186 --- /dev/null +++ b/tests/ui/unused_format_specs_width.rs @@ -0,0 +1,41 @@ +//@no-rustfix +// Format width has no effect for certain traits (issue #15039) + +#![warn(clippy::unused_format_specs)] +#![allow(clippy::zero_ptr, clippy::manual_dangling_ptr)] + +fn main() { + // Integer formats with # (alternate): 0x/0o/0b prefix makes min width 4 + println!("{:#02X}", 1u8); //~ ERROR: format width has no effect on the output + println!("{:#2X}", 1u8); //~ ERROR: format width has no effect on the output + println!("{:#02x}", 1u8); //~ ERROR: format width has no effect on the output + println!("{:#02o}", 1u8); //~ ERROR: format width has no effect on the output + println!("{:#02b}", 1u8); //~ ERROR: format width has no effect on the output + + // Exponent formats: min width 4 (e.g. 1e0) + println!("{:02e}", 1u8); //~ ERROR: format width has no effect on the output + println!("{:02E}", 1u8); //~ ERROR: format width has no effect on the output + println!("{:2e}", 1.0); //~ ERROR: format width has no effect on the output + println!("{:2E}", 1.0); //~ ERROR: format width has no effect on the output + println!("{:2e}", 0.1); //~ ERROR: format width has no effect on the output + println!("{:2E}", 0.1); //~ ERROR: format width has no effect on the output + + // Pointer: min width 4 (0x1) + println!("{:2p}", 0 as *const usize); //~ ERROR: format width has no effect on the output + println!("{:02p}", 1 as *const usize); //~ ERROR: format width has no effect on the output + + // Width 2 still too small for exponent; precision+width + println!("{:2.2e}", 1.0); //~ ERROR: format width has no effect on the output + println!("{:2.2E}", 1.0); //~ ERROR: format width has no effect on the output + println!("{:2.2e}", 0.1); //~ ERROR: format width has no effect on the output + println!("{:2.2E}", 0.1); //~ ERROR: format width has no effect on the output + + // Width 3 is exactly the minimum for alternate hex, still warn + println!("{:#03X}", 1u8); //~ ERROR: format width has no effect on the output + + // Not linted: width more than 3, or no # for x/o/b + println!("{:#04X}", 1u8); + println!("{:2X}", 1u8); // no #, so no prefix + println!("{:2o}", 1u8); + println!("{}", 1); +} diff --git a/tests/ui/unused_format_specs_width.stderr b/tests/ui/unused_format_specs_width.stderr new file mode 100644 index 0000000000000..defb96ac08bf8 --- /dev/null +++ b/tests/ui/unused_format_specs_width.stderr @@ -0,0 +1,148 @@ +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:9:15 + | +LL | println!("{:#02X}", 1u8); + | ^^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + = note: `-D clippy::unused-format-specs` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unused_format_specs)]` + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:10:15 + | +LL | println!("{:#2X}", 1u8); + | ^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:11:15 + | +LL | println!("{:#02x}", 1u8); + | ^^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:12:15 + | +LL | println!("{:#02o}", 1u8); + | ^^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:13:15 + | +LL | println!("{:#02b}", 1u8); + | ^^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:16:15 + | +LL | println!("{:02e}", 1u8); + | ^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:17:15 + | +LL | println!("{:02E}", 1u8); + | ^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:18:15 + | +LL | println!("{:2e}", 1.0); + | ^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:19:15 + | +LL | println!("{:2E}", 1.0); + | ^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:20:15 + | +LL | println!("{:2e}", 0.1); + | ^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:21:15 + | +LL | println!("{:2E}", 0.1); + | ^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:24:15 + | +LL | println!("{:2p}", 0 as *const usize); + | ^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:25:15 + | +LL | println!("{:02p}", 1 as *const usize); + | ^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:28:15 + | +LL | println!("{:2.2e}", 1.0); + | ^^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:29:15 + | +LL | println!("{:2.2E}", 1.0); + | ^^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:30:15 + | +LL | println!("{:2.2e}", 0.1); + | ^^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:31:15 + | +LL | println!("{:2.2E}", 0.1); + | ^^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: format width has no effect on the output for this format trait + --> tests/ui/unused_format_specs_width.rs:34:15 + | +LL | println!("{:#03X}", 1u8); + | ^^^^^^^ + | + = help: consider removing the width or increasing it to at least 4 + +error: aborting due to 18 previous errors + From 078e0ef53ff487ddcd5c44e6598ed0ad401f9365 Mon Sep 17 00:00:00 2001 From: Adwin White Date: Wed, 15 Apr 2026 12:17:20 +0800 Subject: [PATCH 20/38] fix all errors --- clippy_lints/src/bool_assert_comparison.rs | 3 +- clippy_lints/src/casts/as_ptr_cast_mut.rs | 2 +- clippy_lints/src/casts/cast_ptr_alignment.rs | 2 +- .../casts/confusing_method_to_numeric_cast.rs | 2 +- clippy_lints/src/casts/needless_type_cast.rs | 4 +-- clippy_lints/src/copy_iterator.rs | 2 +- clippy_lints/src/default.rs | 2 +- clippy_lints/src/default_numeric_fallback.rs | 6 ++-- .../src/default_union_representation.rs | 2 +- clippy_lints/src/dereference.rs | 5 ++-- clippy_lints/src/derivable_impls.rs | 2 +- .../src/derive/derive_ord_xor_partial_ord.rs | 2 +- .../src/derive/derived_hash_with_manual_eq.rs | 2 +- .../src/derive/expl_impl_clone_on_copy.rs | 2 +- clippy_lints/src/derive/mod.rs | 2 +- clippy_lints/src/empty_with_brackets.rs | 4 +-- clippy_lints/src/enum_clike.rs | 2 +- clippy_lints/src/error_impl_error.rs | 2 +- clippy_lints/src/format_args.rs | 11 +++++-- clippy_lints/src/from_over_into.rs | 2 +- clippy_lints/src/functions/ref_option.rs | 4 +-- clippy_lints/src/functions/result.rs | 2 +- clippy_lints/src/future_not_send.rs | 4 +-- .../impl_hash_with_borrow_str_and_bytes.rs | 2 +- clippy_lints/src/implicit_saturating_sub.rs | 4 +-- clippy_lints/src/infallible_try_from.rs | 2 +- clippy_lints/src/inherent_impl.rs | 2 +- .../src/iter_not_returning_iterator.rs | 8 +++-- clippy_lints/src/iter_without_into_iter.rs | 4 +-- clippy_lints/src/large_const_arrays.rs | 7 +++-- clippy_lints/src/large_enum_variant.rs | 2 +- clippy_lints/src/len_without_is_empty.rs | 4 +-- clippy_lints/src/loops/explicit_iter_loop.rs | 2 +- clippy_lints/src/loops/needless_range_loop.rs | 2 +- clippy_lints/src/map_unit_fn.rs | 2 +- clippy_lints/src/matches/needless_match.rs | 1 + .../matches/rest_pat_in_fully_bound_struct.rs | 2 +- .../matches/significant_drop_in_scrutinee.rs | 2 +- .../src/methods/bytes_count_to_len.rs | 2 +- ...se_sensitive_file_extension_comparisons.rs | 2 +- clippy_lints/src/methods/get_first.rs | 2 +- clippy_lints/src/methods/implicit_clone.rs | 2 +- .../iter_on_single_or_empty_collections.rs | 2 +- clippy_lints/src/methods/manual_ok_or.rs | 3 +- clippy_lints/src/methods/map_err_ignore.rs | 3 +- clippy_lints/src/methods/mod.rs | 5 ++-- clippy_lints/src/methods/needless_collect.rs | 15 ++++++---- clippy_lints/src/methods/open_options.rs | 2 +- clippy_lints/src/methods/or_fun_call.rs | 2 +- .../src/methods/path_buf_push_overwrite.rs | 1 + .../src/methods/stable_sort_primitive.rs | 2 +- clippy_lints/src/methods/suspicious_splitn.rs | 2 +- clippy_lints/src/methods/type_id_on_box.rs | 3 +- .../src/methods/unnecessary_sort_by.rs | 2 +- .../src/methods/unnecessary_to_owned.rs | 6 ++-- .../src/methods/unwrap_expect_used.rs | 2 +- clippy_lints/src/methods/utils.rs | 2 +- .../src/methods/vec_resize_to_zero.rs | 1 + clippy_lints/src/missing_const_for_fn.rs | 2 +- clippy_lints/src/mut_key.rs | 2 +- .../src/needless_borrows_for_generic_args.rs | 12 +++++--- clippy_lints/src/needless_maybe_sized.rs | 7 +++-- clippy_lints/src/needless_pass_by_ref_mut.rs | 2 +- clippy_lints/src/needless_pass_by_value.rs | 2 +- clippy_lints/src/new_without_default.rs | 6 ++-- clippy_lints/src/no_effect.rs | 1 + clippy_lints/src/non_copy_const.rs | 29 ++++++++++-------- .../src/non_send_fields_in_send_ty.rs | 2 +- clippy_lints/src/only_used_in_recursion.rs | 2 +- clippy_lints/src/operators/identity_op.rs | 2 +- clippy_lints/src/pass_by_ref_or_value.rs | 2 +- clippy_lints/src/ptr/ptr_arg.rs | 4 +-- clippy_lints/src/ranges.rs | 3 +- clippy_lints/src/redundant_slicing.rs | 7 ++++- clippy_lints/src/returns/let_and_return.rs | 1 + clippy_lints/src/self_named_constructors.rs | 2 +- .../src/significant_drop_tightening.rs | 4 +-- .../src/transmute/transmute_undefined_repr.rs | 7 +++-- clippy_lints/src/transmute/utils.rs | 5 ++-- clippy_lints/src/unit_return_expecting_ord.rs | 2 +- clippy_lints/src/unit_types/let_unit_value.rs | 2 +- clippy_lints/src/unnecessary_mut_passed.rs | 2 +- clippy_lints/src/use_self.rs | 10 +++---- clippy_lints/src/useless_conversion.rs | 2 +- clippy_lints_internal/src/msrv_attr_impl.rs | 5 ++-- clippy_lints_internal/src/symbols.rs | 2 +- clippy_lints_internal/src/unusual_names.rs | 2 +- clippy_utils/src/eager_or_lazy.rs | 7 ++--- clippy_utils/src/lib.rs | 18 +++++------ clippy_utils/src/qualify_min_const_fn.rs | 2 +- clippy_utils/src/sugg.rs | 2 +- clippy_utils/src/ty/mod.rs | 30 +++++++++++-------- clippy_utils/src/ty/type_certainty/mod.rs | 2 +- 93 files changed, 207 insertions(+), 163 deletions(-) diff --git a/clippy_lints/src/bool_assert_comparison.rs b/clippy_lints/src/bool_assert_comparison.rs index 165941a859f74..8e85ce6171842 100644 --- a/clippy_lints/src/bool_assert_comparison.rs +++ b/clippy_lints/src/bool_assert_comparison.rs @@ -4,6 +4,7 @@ use clippy_utils::source::walk_span_to_context; use clippy_utils::sugg::Sugg; use clippy_utils::sym; use clippy_utils::ty::{implements_trait, is_copy}; +use rustc_middle::ty::Unnormalized; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, Lit}; @@ -64,7 +65,7 @@ fn is_impl_not_trait_with_bool_out<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) - }) .is_some_and(|assoc_item| { let proj = Ty::new_projection(cx.tcx, assoc_item.def_id, cx.tcx.mk_args_trait(ty, [])); - let nty = cx.tcx.normalize_erasing_regions(cx.typing_env(), proj); + let nty = cx.tcx.normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(proj)); nty.is_bool() }) diff --git a/clippy_lints/src/casts/as_ptr_cast_mut.rs b/clippy_lints/src/casts/as_ptr_cast_mut.rs index 10c97fac992e4..adc43e282fc5c 100644 --- a/clippy_lints/src/casts/as_ptr_cast_mut.rs +++ b/clippy_lints/src/casts/as_ptr_cast_mut.rs @@ -17,7 +17,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, && let Some(as_ptr_did) = cx .typeck_results() .type_dependent_def_id(cast_expr.peel_blocks().hir_id) - && let as_ptr_sig = cx.tcx.fn_sig(as_ptr_did).instantiate_identity() + && let as_ptr_sig = cx.tcx.fn_sig(as_ptr_did).instantiate_identity().skip_norm_wip() && let Some(first_param_ty) = as_ptr_sig.skip_binder().inputs().iter().next() && let ty::Ref(_, _, Mutability::Not) = first_param_ty.kind() && let Some(recv) = receiver.span.get_source_text(cx) diff --git a/clippy_lints/src/casts/cast_ptr_alignment.rs b/clippy_lints/src/casts/cast_ptr_alignment.rs index 7d14ba7fcf132..c54dab7e4aec0 100644 --- a/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -59,7 +59,7 @@ fn is_used_as_unaligned(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { 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_assoc(def_id) - && cx.tcx.type_of(def_id).instantiate_identity().is_raw_ptr() + && cx.tcx.type_of(def_id).instantiate_identity().skip_norm_wip().is_raw_ptr() { true } else { 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 73347e7141eb4..1dbcfafd6b3d3 100644 --- a/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs +++ b/clippy_lints/src/casts/confusing_method_to_numeric_cast.rs @@ -38,7 +38,7 @@ fn get_const_name_and_ty_name( return None; } } 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()) + && let Some(ty_name) = get_primitive_ty_name(cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip()) && matches!( method_name, sym::min | sym::max | sym::minimum | sym::maximum | sym::min_value | sym::max_value diff --git a/clippy_lints/src/casts/needless_type_cast.rs b/clippy_lints/src/casts/needless_type_cast.rs index 1d899a21c229e..844d4c7acbe7f 100644 --- a/clippy_lints/src/casts/needless_type_cast.rs +++ b/clippy_lints/src/casts/needless_type_cast.rs @@ -158,7 +158,7 @@ fn has_generic_return_type(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { .is_some(), ExprKind::MethodCall(..) => { if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { - let sig = cx.tcx.fn_sig(def_id).instantiate_identity(); + let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); let ret_ty = sig.output().skip_binder(); return ret_ty.has_param(); } @@ -168,7 +168,7 @@ fn has_generic_return_type(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { if let ExprKind::Path(qpath) = &callee.kind { let res = cx.qpath_res(qpath, callee.hir_id); if let Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) = res { - let sig = cx.tcx.fn_sig(def_id).instantiate_identity(); + let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); let ret_ty = sig.output().skip_binder(); return ret_ty.has_param(); } diff --git a/clippy_lints/src/copy_iterator.rs b/clippy_lints/src/copy_iterator.rs index 51aebd8b0cfba..dff79b2870026 100644 --- a/clippy_lints/src/copy_iterator.rs +++ b/clippy_lints/src/copy_iterator.rs @@ -40,7 +40,7 @@ impl<'tcx> LateLintPass<'tcx> for CopyIterator { of_trait: Some(of_trait), .. }) = item.kind - && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() + && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip() && is_copy(cx, ty) && let Some(trait_id) = of_trait.trait_ref.trait_def_id() && cx.tcx.is_diagnostic_item(sym::Iterator, trait_id) diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index 2064d896861ba..2f4a67e15b927 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -143,7 +143,7 @@ impl<'tcx> LateLintPass<'tcx> for Default { .fields .iter() .all(|field| { - is_copy(cx, cx.tcx.type_of(field.did).instantiate(cx.tcx, args)) + is_copy(cx, cx.tcx.type_of(field.did).instantiate(cx.tcx, args).skip_norm_wip()) }) && (!has_drop(cx, binding_type) || all_fields_are_copy) { diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index 1507f1ed30539..4324a8465be6f 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -166,7 +166,7 @@ impl<'tcx> Visitor<'tcx> for NumericFallbackVisitor<'_, 'tcx> { ExprKind::MethodCall(_, receiver, args, _) => { if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(expr.hir_id) { - let fn_sig = self.cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder(); + let fn_sig = self.cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip().skip_binder(); for (expr, bound) in iter::zip(iter::once(*receiver).chain(args.iter()), fn_sig.inputs()) { self.ty_bounds.push((*bound).into()); self.visit_expr(expr); @@ -188,7 +188,7 @@ impl<'tcx> Visitor<'tcx> for NumericFallbackVisitor<'_, 'tcx> { for field in *fields { let bound = fields_def.iter().find_map(|f_def| { if f_def.ident(self.cx.tcx) == field.ident { - Some(self.cx.tcx.type_of(f_def.did).instantiate_identity()) + Some(self.cx.tcx.type_of(f_def.did).instantiate_identity().skip_norm_wip()) } else { None } @@ -251,7 +251,7 @@ fn fn_sig_opt<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option Some(cx.tcx.fn_sig(*def_id).instantiate_identity()), + ty::FnDef(def_id, _) => Some(cx.tcx.fn_sig(*def_id).instantiate_identity().skip_norm_wip()), ty::FnPtr(sig_tys, hdr) => Some(sig_tys.with(*hdr)), _ => None, } diff --git a/clippy_lints/src/default_union_representation.rs b/clippy_lints/src/default_union_representation.rs index 61656d6cede51..49d550f1cbd4b 100644 --- a/clippy_lints/src/default_union_representation.rs +++ b/clippy_lints/src/default_union_representation.rs @@ -80,7 +80,7 @@ impl<'tcx> LateLintPass<'tcx> for DefaultUnionRepresentation { /// of that field does not matter either.) fn is_union_with_two_non_zst_fields<'tcx>(cx: &LateContext<'tcx>, item: &Item<'tcx>) -> bool { if let ItemKind::Union(..) = &item.kind - && let ty::Adt(adt_def, args) = cx.tcx.type_of(item.owner_id).instantiate_identity().kind() + && let ty::Adt(adt_def, args) = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip().kind() { adt_def.all_fields().filter(|f| !is_zst(cx, f, args)).count() >= 2 } else { diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index ebaa7a39213de..c1ebb675c58ee 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -6,6 +6,7 @@ use clippy_utils::ty::{adjust_derefs_manually_drop, implements_trait, is_manuall use clippy_utils::{ DefinedTy, ExprUseNode, get_expr_use_site, get_parent_expr, is_block_like, is_from_proc_macro, is_lint_allowed, sym, }; +use rustc_middle::ty::Unnormalized; use rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::Applicability; @@ -381,7 +382,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { && let args = typeck.node_args_opt(hir_id).map(|args| &args[1..]).unwrap_or_default() && let impl_ty = - if cx.tcx.fn_sig(fn_id).instantiate_identity().skip_binder().inputs()[0] + if cx.tcx.fn_sig(fn_id).instantiate_identity().skip_norm_wip().skip_binder().inputs()[0] .is_ref() { // Trait methods taking `&self` @@ -876,7 +877,7 @@ impl TyCoercionStability { if let Some(def_id) = def_site_def_id { let typing_env = ty::TypingEnv::non_body_analysis(tcx, def_id); - ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty); + ty = tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)).unwrap_or(ty); } loop { break match *ty.kind() { diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index c04163b1c7a07..5e1144254fdb2 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -243,7 +243,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls { && let Node::ImplItem(impl_item) = cx.tcx.hir_node(impl_item_hir) && let ImplItemKind::Fn(_, b) = &impl_item.kind && let Body { value: func_expr, .. } = cx.tcx.hir_body(*b) - && let &ty::Adt(adt_def, args) = cx.tcx.type_of(item.owner_id).instantiate_identity().kind() + && let &ty::Adt(adt_def, args) = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip().kind() && let attrs = cx.tcx.hir_attrs(item.hir_id()) && !attrs.iter().any(|attr| attr.doc_str().is_some()) && cx.tcx.hir_attrs(impl_item_hir).is_empty() diff --git a/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs b/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs index 316d800a70c96..2d7d09285c16d 100644 --- a/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs +++ b/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs @@ -35,7 +35,7 @@ pub(super) fn check<'tcx>( // Only care about `impl PartialOrd for Foo` // For `impl PartialOrd for A, input_types is [A, B] - if trait_ref.instantiate_identity().args.type_at(1) == ty { + if trait_ref.instantiate_identity().skip_norm_wip().args.type_at(1) == ty { let mess = if partial_ord_is_automatically_derived { "you are implementing `Ord` explicitly but have derived `PartialOrd`" } else { diff --git a/clippy_lints/src/derive/derived_hash_with_manual_eq.rs b/clippy_lints/src/derive/derived_hash_with_manual_eq.rs index dc3fbe5d7012c..b57a47526b354 100644 --- a/clippy_lints/src/derive/derived_hash_with_manual_eq.rs +++ b/clippy_lints/src/derive/derived_hash_with_manual_eq.rs @@ -31,7 +31,7 @@ pub(super) fn check<'tcx>( // Only care about `impl PartialEq for Foo` // For `impl PartialEq for A, input_types is [A, B] - if trait_ref.instantiate_identity().args.type_at(1) == ty { + if trait_ref.instantiate_identity().skip_norm_wip().args.type_at(1) == ty { span_lint_hir_and_then( cx, DERIVED_HASH_WITH_MANUAL_EQ, diff --git a/clippy_lints/src/derive/expl_impl_clone_on_copy.rs b/clippy_lints/src/derive/expl_impl_clone_on_copy.rs index b2bc6402561f5..0604a8db46796 100644 --- a/clippy_lints/src/derive/expl_impl_clone_on_copy.rs +++ b/clippy_lints/src/derive/expl_impl_clone_on_copy.rs @@ -32,7 +32,7 @@ pub(super) fn check<'tcx>( if !is_copy(cx, ty) { if ty_subs.non_erasable_generics().next().is_some() { let has_copy_impl = cx.tcx.local_trait_impls(copy_id).iter().any(|&id| { - matches!(cx.tcx.type_of(id).instantiate_identity().kind(), ty::Adt(adt, _) + matches!(cx.tcx.type_of(id).instantiate_identity().skip_norm_wip().kind(), ty::Adt(adt, _) if ty_adt.did() == adt.did()) }); if !has_copy_impl { diff --git a/clippy_lints/src/derive/mod.rs b/clippy_lints/src/derive/mod.rs index fa1a7037154eb..d6f928d738cbd 100644 --- a/clippy_lints/src/derive/mod.rs +++ b/clippy_lints/src/derive/mod.rs @@ -204,7 +204,7 @@ impl<'tcx> LateLintPass<'tcx> for Derive { { let adt_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id); let trait_ref = &of_trait.trait_ref; - let ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); + let ty = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip(); let is_automatically_derived = cx.tcx.is_automatically_derived(item.owner_id.to_def_id()); derived_hash_with_manual_eq::check(cx, item.span, trait_ref, ty, adt_hir_id, is_automatically_derived); diff --git a/clippy_lints/src/empty_with_brackets.rs b/clippy_lints/src/empty_with_brackets.rs index 53e6b7f95ae32..ee1be48417222 100644 --- a/clippy_lints/src/empty_with_brackets.rs +++ b/clippy_lints/src/empty_with_brackets.rs @@ -303,7 +303,7 @@ fn check_expr_for_enum_as_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> Opt ExprKind::Struct(qpath, ..) if let Def(DefKind::Variant, mut def_id) = cx.typeck_results().qpath_res(qpath, expr.hir_id) => { - let ty = cx.tcx.type_of(def_id).instantiate_identity(); + let ty = cx.tcx.type_of(def_id).instantiate_identity().skip_norm_wip(); if let ty::FnDef(ctor_def_id, _) = ty.kind() { def_id = *ctor_def_id; } @@ -324,7 +324,7 @@ fn check_pat_for_enum_as_function(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option PatKind::Struct(qpath, ..) if let Def(DefKind::Variant, mut def_id) = cx.typeck_results().qpath_res(&qpath, pat.hir_id) => { - let ty = cx.tcx.type_of(def_id).instantiate_identity(); + let ty = cx.tcx.type_of(def_id).instantiate_identity().skip_norm_wip(); if let ty::FnDef(ctor_def_id, _) = ty.kind() { def_id = *ctor_def_id; } diff --git a/clippy_lints/src/enum_clike.rs b/clippy_lints/src/enum_clike.rs index 1a56c8f810ee7..2a8b6778fd0ac 100644 --- a/clippy_lints/src/enum_clike.rs +++ b/clippy_lints/src/enum_clike.rs @@ -42,7 +42,7 @@ impl<'tcx> LateLintPass<'tcx> for UnportableVariant { for var in def.variants { if let Some(anon_const) = &var.disr_expr { let def_id = cx.tcx.hir_body_owner_def_id(anon_const.body); - let mut ty = cx.tcx.type_of(def_id.to_def_id()).instantiate_identity(); + let mut ty = cx.tcx.type_of(def_id.to_def_id()).instantiate_identity().skip_norm_wip(); let constant = cx.tcx.const_eval_poly(def_id.to_def_id()).ok(); if let Some(Constant::Int(val)) = constant.and_then(|c| mir_to_const(cx.tcx, c, ty)) { if let ty::Adt(adt, _) = ty.kind() diff --git a/clippy_lints/src/error_impl_error.rs b/clippy_lints/src/error_impl_error.rs index 32be1ad1e644d..4b9e3cee011c6 100644 --- a/clippy_lints/src/error_impl_error.rs +++ b/clippy_lints/src/error_impl_error.rs @@ -41,7 +41,7 @@ impl<'tcx> LateLintPass<'tcx> for ErrorImplError { ItemKind::TyAlias(ident, ..) if ident.name == sym::Error && is_visible_outside_module(cx, item.owner_id.def_id) - && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() + && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip() && let Some(error_def_id) = cx.tcx.get_diagnostic_item(sym::Error) && implements_trait(cx, ty, error_def_id, &[]) => { diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index 12cf829167393..fd2b9826bb20f 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -14,6 +14,7 @@ use clippy_utils::source::{SpanRangeExt, snippet}; use clippy_utils::ty::implements_trait; use clippy_utils::{is_from_proc_macro, is_in_test, sym, trait_ref_of_method}; use itertools::Itertools; +use rustc_middle::ty::Unnormalized; use rustc_ast::{ FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions, FormatPlaceholder, FormatTrait, @@ -689,7 +690,7 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { } let depth = depth + 1; let typing_env = cx.typing_env(); - let ty = tcx.normalize_erasing_regions(typing_env, ty); + let ty = tcx.normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)); match ty.kind() { ty::RawPtr(..) | ty::FnPtr(..) | ty::FnDef(..) => true, ty::Ref(_, t, _) | ty::Slice(t) | ty::Array(t, _) => self.has_pointer_debug(*t, depth), @@ -724,7 +725,13 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { }; let pointer_debug = derived_debug && adt.all_fields().any(|f| { - self.has_pointer_debug(tcx.normalize_erasing_regions(typing_env, f.ty(tcx, args)), depth) + self.has_pointer_debug( + tcx.normalize_erasing_regions( + typing_env, + Unnormalized::new_wip(f.ty(tcx, args)) + ), + depth, + ) }); self.has_pointer_format.insert(ty, pointer_debug); pointer_debug diff --git a/clippy_lints/src/from_over_into.rs b/clippy_lints/src/from_over_into.rs index 79ffe1ea417e4..944c4eee90257 100644 --- a/clippy_lints/src/from_over_into.rs +++ b/clippy_lints/src/from_over_into.rs @@ -76,7 +76,7 @@ impl<'tcx> LateLintPass<'tcx> for FromOverInto { // `impl Into for self_ty` && let Some(GenericArgs { args: [GenericArg::Type(target_ty)], .. }) = into_trait_seg.args && span_is_local(item.span) - && let middle_trait_ref = cx.tcx.impl_trait_ref(item.owner_id).instantiate_identity() + && let middle_trait_ref = cx.tcx.impl_trait_ref(item.owner_id).instantiate_identity().skip_norm_wip() && cx.tcx.is_diagnostic_item(sym::Into, middle_trait_ref.def_id) && !matches!(middle_trait_ref.args.type_at(1).kind(), ty::Alias(ty::AliasTy { kind: ty::Opaque{..} , .. })) && self.msrv.meets(cx, msrvs::RE_REBALANCING_COHERENCE) diff --git a/clippy_lints/src/functions/ref_option.rs b/clippy_lints/src/functions/ref_option.rs index cc9dc47e15f2b..c5c0cc0b5ab61 100644 --- a/clippy_lints/src/functions/ref_option.rs +++ b/clippy_lints/src/functions/ref_option.rs @@ -107,7 +107,7 @@ pub(crate) fn check_fn<'a>( check_fn_sig(cx, decl, inputs_output_span, sig); } else if !is_trait_impl_item(cx, hir_id) { - let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder(); + let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip().skip_binder(); if is_from_proc_macro(cx, &(&kind, body, hir_id, span)) { return; @@ -128,7 +128,7 @@ pub(super) fn check_trait_item<'a>( && !is_from_proc_macro(cx, trait_item) { let def_id = trait_item.owner_id.def_id; - let ty_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder(); + let ty_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip().skip_binder(); check_fn_sig(cx, sig.decl, sig.span, ty_sig); } } diff --git a/clippy_lints/src/functions/result.rs b/clippy_lints/src/functions/result.rs index 77fec93714252..829f054d8854e 100644 --- a/clippy_lints/src/functions/result.rs +++ b/clippy_lints/src/functions/result.rs @@ -25,7 +25,7 @@ fn result_err_ty<'tcx>( && let hir::FnRetTy::Return(hir_ty) = decl.output && let ty = cx .tcx - .instantiate_bound_regions_with_erased(cx.tcx.fn_sig(id).instantiate_identity().output()) + .instantiate_bound_regions_with_erased(cx.tcx.fn_sig(id).instantiate_identity().skip_norm_wip().output()) && ty.is_diag_item(cx, sym::Result) && let ty::Adt(_, args) = ty.kind() { diff --git a/clippy_lints/src/future_not_send.rs b/clippy_lints/src/future_not_send.rs index eb54585466900..fcdc12e3438cf 100644 --- a/clippy_lints/src/future_not_send.rs +++ b/clippy_lints/src/future_not_send.rs @@ -9,6 +9,7 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::print::PrintTraitRefExt; use rustc_middle::ty::{ self, AliasTy, Binder, ClauseKind, PredicateKind, Ty, TyCtxt, TypeVisitable, TypeVisitableExt, TypeVisitor, + Unnormalized, }; use rustc_session::declare_lint_pass; use rustc_span::def_id::LocalDefId; @@ -80,8 +81,7 @@ impl<'tcx> LateLintPass<'tcx> for FutureNotSend { && let Some(send_trait) = cx.tcx.get_diagnostic_item(sym::Send) && let preds = cx.tcx.explicit_item_self_bounds(def_id) // If is a Future - && preds - .iter_instantiated_copied(cx.tcx, args) + && preds.iter_instantiated_copied(cx.tcx, args).map(Unnormalized::skip_norm_wip) .filter_map(|(p, _)| p.as_trait_clause()) .any(|trait_pred| trait_pred.skip_binder().trait_ref.def_id == future_trait) { diff --git a/clippy_lints/src/impl_hash_with_borrow_str_and_bytes.rs b/clippy_lints/src/impl_hash_with_borrow_str_and_bytes.rs index dec08d94e89dc..56de38f8ed765 100644 --- a/clippy_lints/src/impl_hash_with_borrow_str_and_bytes.rs +++ b/clippy_lints/src/impl_hash_with_borrow_str_and_bytes.rs @@ -79,7 +79,7 @@ impl LateLintPass<'_> for ImplHashWithBorrowStrBytes { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { if let ItemKind::Impl(imp) = item.kind && let Some(of_trait) = imp.of_trait - && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() + && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip() && let Some(hash_id) = cx.tcx.get_diagnostic_item(sym::Hash) && Res::Def(DefKind::Trait, hash_id) == of_trait.trait_ref.path.res && let Some(borrow_id) = cx.tcx.get_diagnostic_item(sym::Borrow) diff --git a/clippy_lints/src/implicit_saturating_sub.rs b/clippy_lints/src/implicit_saturating_sub.rs index 666c4cb737f4f..f2e85a717dca4 100644 --- a/clippy_lints/src/implicit_saturating_sub.rs +++ b/clippy_lints/src/implicit_saturating_sub.rs @@ -360,7 +360,7 @@ fn check_with_condition<'tcx>( 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.inherent_impl_of_assoc(const_id) - && cx.tcx.type_of(impl_id).instantiate_identity().is_integral() + && cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip().is_integral() { print_lint_and_sugg(cx, var_name, expr); } @@ -370,7 +370,7 @@ fn check_with_condition<'tcx>( && 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.inherent_impl_of_assoc(func_id) - && cx.tcx.type_of(impl_id).instantiate_identity().is_integral() + && cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip().is_integral() { print_lint_and_sugg(cx, var_name, expr); } diff --git a/clippy_lints/src/infallible_try_from.rs b/clippy_lints/src/infallible_try_from.rs index 511df733ed910..b7cbe667b3346 100644 --- a/clippy_lints/src/infallible_try_from.rs +++ b/clippy_lints/src/infallible_try_from.rs @@ -58,7 +58,7 @@ impl<'tcx> LateLintPass<'tcx> for InfallibleTryFrom { .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(); + let ii_ty = cx.tcx.type_of(ii.def_id).instantiate_identity().skip_norm_wip(); 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 diff --git a/clippy_lints/src/inherent_impl.rs b/clippy_lints/src/inherent_impl.rs index 14928a1be13bc..257a165ba8315 100644 --- a/clippy_lints/src/inherent_impl.rs +++ b/clippy_lints/src/inherent_impl.rs @@ -90,7 +90,7 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { } for impl_id in impl_ids.iter().map(|id| id.expect_local()) { - let impl_ty = cx.tcx.type_of(impl_id).instantiate_identity(); + let impl_ty = cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip(); let hir_id = cx.tcx.local_def_id_to_hir_id(impl_id); let criterion = match self.scope { InherentImplLintScope::Module => Criterion::Module(cx.tcx.parent_module(hir_id)), diff --git a/clippy_lints/src/iter_not_returning_iterator.rs b/clippy_lints/src/iter_not_returning_iterator.rs index 73e4a856c0468..11de287c0ea57 100644 --- a/clippy_lints/src/iter_not_returning_iterator.rs +++ b/clippy_lints/src/iter_not_returning_iterator.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::ty::implements_trait; +use rustc_middle::ty::Unnormalized; use rustc_hir::def_id::LocalDefId; use rustc_hir::{FnSig, ImplItem, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -67,10 +68,11 @@ fn check_sig(cx: &LateContext<'_>, name: Symbol, sig: &FnSig<'_>, fn_id: LocalDe if sig.decl.implicit_self().has_implicit_self() { let ret_ty = cx .tcx - .instantiate_bound_regions_with_erased(cx.tcx.fn_sig(fn_id).instantiate_identity().output()); + .instantiate_bound_regions_with_erased(cx.tcx + .fn_sig(fn_id).instantiate_identity().skip_norm_wip().output() + ); let ret_ty = cx - .tcx - .try_normalize_erasing_regions(cx.typing_env(), ret_ty) + .tcx.try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(ret_ty)) .unwrap_or(ret_ty); if cx .tcx diff --git a/clippy_lints/src/iter_without_into_iter.rs b/clippy_lints/src/iter_without_into_iter.rs index 369037dad94fc..3746c9384957d 100644 --- a/clippy_lints/src/iter_without_into_iter.rs +++ b/clippy_lints/src/iter_without_into_iter.rs @@ -134,7 +134,7 @@ impl LateLintPass<'_> for IterWithoutIntoIter { .trait_def_id() .is_some_and(|did| cx.tcx.is_diagnostic_item(sym::IntoIterator, did)) && !item.span.in_external_macro(cx.sess().source_map()) - && let &ty::Ref(_, ty, mtbl) = cx.tcx.type_of(item.owner_id).instantiate_identity().kind() + && let &ty::Ref(_, ty, mtbl) = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip().kind() && let expected_method_name = match mtbl { Mutability::Mut => sym::iter_mut, Mutability::Not => sym::iter, @@ -211,7 +211,7 @@ impl {self_ty_without_ref} {{ && imp.of_trait.is_none() && let sig = cx.tcx.liberate_late_bound_regions( item_did, - cx.tcx.fn_sig(item_did).instantiate_identity() + cx.tcx.fn_sig(item_did).instantiate_identity().skip_norm_wip() ) && let ref_ty = sig.inputs()[0] && let Some(into_iter_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator) diff --git a/clippy_lints/src/large_const_arrays.rs b/clippy_lints/src/large_const_arrays.rs index 383f2721fad21..11b5a339c18b4 100644 --- a/clippy_lints/src/large_const_arrays.rs +++ b/clippy_lints/src/large_const_arrays.rs @@ -1,5 +1,6 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; +use rustc_middle::ty::Unnormalized; use rustc_errors::Applicability; use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -54,10 +55,12 @@ impl<'tcx> LateLintPass<'tcx> for LargeConstArrays { // doesn't account for empty where-clauses that only consist of keyword `where` IINM. && generics.params.is_empty() && !generics.has_where_clause_predicates && !item.span.from_expansion() - && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() + && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip() && let ty::Array(element_type, cst) = ty.kind() && let Some(element_count) = cx.tcx - .try_normalize_erasing_regions(cx.typing_env(), *cst).unwrap_or(*cst).try_to_target_usize(cx.tcx) + .try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(*cst)) + .unwrap_or(*cst) + .try_to_target_usize(cx.tcx) && let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()) && u128::from(self.maximum_allowed_size) < u128::from(element_count) * u128::from(element_size) { diff --git a/clippy_lints/src/large_enum_variant.rs b/clippy_lints/src/large_enum_variant.rs index 50767b32bb71d..f5af7acdcf51d 100644 --- a/clippy_lints/src/large_enum_variant.rs +++ b/clippy_lints/src/large_enum_variant.rs @@ -75,7 +75,7 @@ impl LargeEnumVariant { impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &Item<'tcx>) { if let ItemKind::Enum(ident, _, ref def) = item.kind - && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() + && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip() && let ty::Adt(adt, subst) = ty.kind() && adt.variants().len() > 1 && !item.span.in_external_macro(cx.tcx.sess.source_map()) diff --git a/clippy_lints/src/len_without_is_empty.rs b/clippy_lints/src/len_without_is_empty.rs index de6ccf56ab2a2..1f019531f602b 100644 --- a/clippy_lints/src/len_without_is_empty.rs +++ b/clippy_lints/src/len_without_is_empty.rs @@ -64,7 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for LenWithoutIsEmpty { && let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id() && let Some(local_id) = ty_id.as_local() && let ty_hir_id = cx.tcx.local_def_id_to_hir_id(local_id) - && let Some(output) = LenOutput::new(cx, cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder()) + && let Some(output) = LenOutput::new(cx, cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_norm_wip().skip_binder()) { let (name, kind) = match cx.tcx.hir_node(ty_hir_id) { Node::ForeignItem(x) => (x.ident.name, "extern type"), @@ -313,7 +313,7 @@ fn check_for_is_empty( if !(is_empty.is_method() && check_is_empty_sig( cx, - cx.tcx.fn_sig(is_empty.def_id).instantiate_identity().skip_binder(), + cx.tcx.fn_sig(is_empty.def_id).instantiate_identity().skip_norm_wip().skip_binder(), len_self_kind, len_output, )) => diff --git a/clippy_lints/src/loops/explicit_iter_loop.rs b/clippy_lints/src/loops/explicit_iter_loop.rs index 40d1d36bd162c..6ee93fa759a98 100644 --- a/clippy_lints/src/loops/explicit_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_iter_loop.rs @@ -139,7 +139,7 @@ fn is_ref_iterable<'tcx>( } let res_ty = cx.tcx.erase_and_anonymize_regions( - EarlyBinder::bind(req_res_ty).instantiate(cx.tcx, typeck.node_args(call_expr.hir_id)), + EarlyBinder::bind(req_res_ty).instantiate(cx.tcx, typeck.node_args(call_expr.hir_id)).skip_norm_wip(), ); let mutbl = if let ty::Ref(_, _, mutbl) = *req_self_ty.kind() { Some(mutbl) diff --git a/clippy_lints/src/loops/needless_range_loop.rs b/clippy_lints/src/loops/needless_range_loop.rs index f1b7d4bdfa932..307b8fcec1506 100644 --- a/clippy_lints/src/loops/needless_range_loop.rs +++ b/clippy_lints/src/loops/needless_range_loop.rs @@ -397,7 +397,7 @@ impl<'tcx> Visitor<'tcx> for VarVisitor<'_, 'tcx> { ExprKind::MethodCall(_, receiver, args, _) => { let def_id = self.cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap(); for (ty, expr) in iter::zip( - self.cx.tcx.fn_sig(def_id).instantiate_identity().inputs().skip_binder(), + self.cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip().inputs().skip_binder(), iter::once(receiver).chain(args.iter()), ) { self.prefer_mutable = false; diff --git a/clippy_lints/src/map_unit_fn.rs b/clippy_lints/src/map_unit_fn.rs index 6a02bb38d17b5..5089bd9e14e4a 100644 --- a/clippy_lints/src/map_unit_fn.rs +++ b/clippy_lints/src/map_unit_fn.rs @@ -102,7 +102,7 @@ fn is_unit_function(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { let ty = cx.typeck_results().expr_ty(expr); if let ty::FnDef(id, _) = *ty.kind() - && let Some(fn_type) = cx.tcx.fn_sig(id).instantiate_identity().no_bound_vars() + && let Some(fn_type) = cx.tcx.fn_sig(id).instantiate_identity().skip_norm_wip().no_bound_vars() { return is_unit_type(fn_type.output()); } diff --git a/clippy_lints/src/matches/needless_match.rs b/clippy_lints/src/matches/needless_match.rs index 9c6cf66019f01..699bb41f2dcbe 100644 --- a/clippy_lints/src/matches/needless_match.rs +++ b/clippy_lints/src/matches/needless_match.rs @@ -131,6 +131,7 @@ fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_> .tcx .fn_sig(item.owner_id) .instantiate_identity() + .skip_norm_wip() .output() .skip_binder(); return same_type_modulo_regions(output, cx.typeck_results().expr_ty(expr)); diff --git a/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs b/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs index 1130d82ab78f9..4a0da63681064 100644 --- a/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs +++ b/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs @@ -9,7 +9,7 @@ pub(crate) fn check(cx: &LateContext<'_>, pat: &Pat<'_>) { if !pat.span.from_expansion() && let PatKind::Struct(QPath::Resolved(_, path), fields, Some(dotdot)) = pat.kind && let Some(def_id) = path.res.opt_def_id() - && let ty = cx.tcx.type_of(def_id).instantiate_identity() + && let ty = cx.tcx.type_of(def_id).instantiate_identity().skip_norm_wip() && let ty::Adt(def, _) = ty.kind() && (def.is_struct() || def.is_union()) && fields.len() == def.non_enum_variant().fields.len() diff --git a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 4267cd1185523..213c09bc3cfec 100644 --- a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -344,7 +344,7 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> { }; let fn_sig = if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(parent_expr.hir_id) { - self.cx.tcx.fn_sig(def_id).instantiate_identity() + self.cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip() } else { return; }; diff --git a/clippy_lints/src/methods/bytes_count_to_len.rs b/clippy_lints/src/methods/bytes_count_to_len.rs index baea49296cd7a..5abdd82f1cde2 100644 --- a/clippy_lints/src/methods/bytes_count_to_len.rs +++ b/clippy_lints/src/methods/bytes_count_to_len.rs @@ -15,7 +15,7 @@ pub(super) fn check<'tcx>( ) { if let Some(bytes_id) = cx.typeck_results().type_dependent_def_id(count_recv.hir_id) && let Some(impl_id) = cx.tcx.impl_of_assoc(bytes_id) - && cx.tcx.type_of(impl_id).instantiate_identity().is_str() + && cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip().is_str() && let ty = cx.typeck_results().expr_ty(bytes_recv).peel_refs() && (ty.is_str() || ty.is_lang_item(cx, 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 229670486db3f..15f4d91e4bd9e 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_assoc(method_id) - && cx.tcx.type_of(impl_id).instantiate_identity().is_str() + && cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip().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 cb5d1ec374fce..c3598eb59478a 100644 --- a/clippy_lints/src/methods/get_first.rs +++ b/clippy_lints/src/methods/get_first.rs @@ -19,7 +19,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_assoc(method_id) - && let identity = cx.tcx.type_of(impl_id).instantiate_identity() + && let identity = cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip() && 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 57c0ba25ebbf0..768612d6bbdc1 100644 --- a/clippy_lints/src/methods/implicit_clone.rs +++ b/clippy_lints/src/methods/implicit_clone.rs @@ -49,7 +49,7 @@ pub fn is_clone_like(cx: &LateContext<'_>, method_name: Symbol, method_parent_id sym::to_string => method_parent_id.is_diag_item(cx, sym::ToString), sym::to_vec => method_parent_id .opt_impl_ty(cx) - .is_some_and(|ty| ty.instantiate_identity().is_slice()), + .is_some_and(|ty| ty.instantiate_identity().skip_norm_wip().is_slice()), _ => false, } } 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 cdef98be14af3..a69671faae783 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 @@ -86,7 +86,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method ExprKind::MethodCall(_name, recv, args, _span) => cx .typeck_results() .type_dependent_def_id(parent.hir_id) - .and_then(|def_id| ty_sig(cx, cx.tcx.type_of(def_id).instantiate_identity())) + .and_then(|def_id| ty_sig(cx, cx.tcx.type_of(def_id).instantiate_identity().skip_norm_wip())) .is_some_and(|fn_sig| { is_arg_ty_unified_in_fn(cx, fn_sig, child_id, once(recv).chain(args.iter()), true) }), diff --git a/clippy_lints/src/methods/manual_ok_or.rs b/clippy_lints/src/methods/manual_ok_or.rs index a8e30e44488cc..8f18de41e8a37 100644 --- a/clippy_lints/src/methods/manual_ok_or.rs +++ b/clippy_lints/src/methods/manual_ok_or.rs @@ -20,8 +20,7 @@ pub(super) fn check<'tcx>( && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) && cx .tcx - .type_of(impl_id) - .instantiate_identity() + .type_of(impl_id).instantiate_identity().skip_norm_wip() .is_diag_item(cx, sym::Option) && let ExprKind::Call(err_path, [err_arg]) = or_expr.kind && err_path.res(cx).ctor_parent(cx).is_lang_item(cx, ResultErr) diff --git a/clippy_lints/src/methods/map_err_ignore.rs b/clippy_lints/src/methods/map_err_ignore.rs index f7da24bed2b80..3e59e0a642cd2 100644 --- a/clippy_lints/src/methods/map_err_ignore.rs +++ b/clippy_lints/src/methods/map_err_ignore.rs @@ -11,8 +11,7 @@ pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, arg: &Expr<'_>) { && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) && cx .tcx - .type_of(impl_id) - .instantiate_identity() + .type_of(impl_id).instantiate_identity().skip_norm_wip() .is_diag_item(cx, sym::Result) && let ExprKind::Closure(&Closure { capture_clause: CaptureBy::Ref, diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 7ca98686b8b1b..cbcc99d264ff9 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -5056,10 +5056,10 @@ impl<'tcx> LateLintPass<'tcx> for Methods { if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind { let parent = cx.tcx.hir_get_parent_item(impl_item.hir_id()).def_id; let item = cx.tcx.hir_expect_item(parent); - let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); + let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip(); let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. })); - let method_sig = cx.tcx.fn_sig(impl_item.owner_id).instantiate_identity(); + let method_sig = cx.tcx.fn_sig(impl_item.owner_id).instantiate_identity().skip_norm_wip(); let method_sig = cx.tcx.instantiate_bound_regions_with_erased(method_sig); let first_arg_ty_opt = method_sig.inputs().iter().next().copied(); should_implement_trait::check_impl_item(cx, impl_item, self_ty, implements_trait, first_arg_ty_opt, sig); @@ -5097,6 +5097,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { .tcx .fn_sig(item.owner_id) .instantiate_identity() + .skip_norm_wip() .inputs() .skip_binder() .first() diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index debe433393cd4..b5809d3040241 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -8,6 +8,7 @@ use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{has_non_owning_mutable_access, make_normalized_projection, make_projection}; use clippy_utils::{CaptureKind, can_move_expr_to_closure, fn_def_id, get_enclosing_block, higher, sym}; +use rustc_middle::ty::Unnormalized; use rustc_data_structures::fx::FxHashMap; use rustc_errors::{Applicability, MultiSpan}; use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_stmt}; @@ -194,7 +195,7 @@ fn check_collect_into_intoiterator<'tcx>( // that contains `collect_expr` let inputs = cx .tcx - .liberate_late_bound_regions(id, cx.tcx.fn_sig(id).instantiate_identity()) + .liberate_late_bound_regions(id, cx.tcx.fn_sig(id).instantiate_identity().skip_norm_wip()) .inputs(); // map IntoIterator generic bounds to their signature @@ -233,7 +234,7 @@ fn check_collect_into_intoiterator<'tcx>( /// Checks if the given method call matches the expected signature of `([&[mut]] self) -> bool` fn is_is_empty_sig(cx: &LateContext<'_>, call_id: HirId) -> bool { cx.typeck_results().type_dependent_def_id(call_id).is_some_and(|id| { - let sig = cx.tcx.fn_sig(id).instantiate_identity().skip_binder(); + let sig = cx.tcx.fn_sig(id).instantiate_identity().skip_norm_wip().skip_binder(); sig.inputs().len() == 1 && sig.output().is_bool() }) } @@ -247,7 +248,9 @@ fn iterates_same_ty<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'tcx>, collect_ty: && let Some(into_iter_item_proj) = make_projection(cx.tcx, into_iter_trait, sym::Item, [collect_ty]) && let Ok(into_iter_item_ty) = cx.tcx.try_normalize_erasing_regions( cx.typing_env(), - Ty::new_projection_from_args(cx.tcx, into_iter_item_proj.kind.def_id(), into_iter_item_proj.args), + Unnormalized::new_wip( + Ty::new_projection_from_args(cx.tcx, into_iter_item_proj.kind.def_id(), into_iter_item_proj.args) + ), ) { iter_item_ty == into_iter_item_ty @@ -261,7 +264,7 @@ fn iterates_same_ty<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'tcx>, collect_ty: fn is_contains_sig(cx: &LateContext<'_>, call_id: HirId, iter_expr: &Expr<'_>) -> bool { let typeck = cx.typeck_results(); if let Some(id) = typeck.type_dependent_def_id(call_id) - && let sig = cx.tcx.fn_sig(id).instantiate_identity() + && let sig = cx.tcx.fn_sig(id).instantiate_identity().skip_norm_wip() && sig.skip_binder().output().is_bool() && let [_, search_ty] = *sig.skip_binder().inputs() && let ty::Ref(_, search_ty, Mutability::Not) = *cx @@ -277,9 +280,9 @@ fn is_contains_sig(cx: &LateContext<'_>, call_id: HirId, iter_expr: &Expr<'_>) - ) && let args = cx.tcx.mk_args(&[GenericArg::from(typeck.expr_ty_adjusted(iter_expr))]) && let proj_ty = Ty::new_projection_from_args(cx.tcx, iter_item.def_id, args) - && let Ok(item_ty) = cx.tcx.try_normalize_erasing_regions(cx.typing_env(), proj_ty) + && let Ok(item_ty) = cx.tcx.try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(proj_ty)) { - item_ty == EarlyBinder::bind(search_ty).instantiate(cx.tcx, cx.typeck_results().node_args(call_id)) + item_ty == EarlyBinder::bind(search_ty).instantiate(cx.tcx, cx.typeck_results().node_args(call_id)).skip_norm_wip() } else { false } diff --git a/clippy_lints/src/methods/open_options.rs b/clippy_lints/src/methods/open_options.rs index 36dd4e952a2d9..3ce28e41e6b78 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_assoc(method_id) - && is_open_options(cx, cx.tcx.type_of(impl_id).instantiate_identity()) + && is_open_options(cx, cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip()) { let mut options = Vec::new(); if get_open_options(cx, recv, &mut options) { diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index e12cce93c2a47..a5d88a49ab341 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -135,7 +135,7 @@ fn check_unwrap_or_default( let output_type_implements_default = |fun| { let fun_ty = cx.typeck_results().expr_ty(fun); if let ty::FnDef(def_id, args) = *fun_ty.kind() { - let output_ty = cx.tcx.fn_sig(def_id).instantiate(cx.tcx, args).skip_binder().output(); + let output_ty = cx.tcx.fn_sig(def_id).instantiate(cx.tcx, args).skip_norm_wip().skip_binder().output(); cx.tcx .get_diagnostic_item(sym::Default) .is_some_and(|default_trait_id| implements_trait(cx, output_ty, default_trait_id, &[])) diff --git a/clippy_lints/src/methods/path_buf_push_overwrite.rs b/clippy_lints/src/methods/path_buf_push_overwrite.rs index fe4904804b5b7..923e4ea4dd975 100644 --- a/clippy_lints/src/methods/path_buf_push_overwrite.rs +++ b/clippy_lints/src/methods/path_buf_push_overwrite.rs @@ -16,6 +16,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t .tcx .type_of(impl_id) .instantiate_identity() + .skip_norm_wip() .is_diag_item(cx, 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 17d1a6abde0a8..23a5ae2a866b5 100644 --- a/clippy_lints/src/methods/stable_sort_primitive.rs +++ b/clippy_lints/src/methods/stable_sort_primitive.rs @@ -10,7 +10,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_assoc(method_id) - && cx.tcx.type_of(impl_id).instantiate_identity().is_slice() + && cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip().is_slice() && let Some(slice_type) = is_slice_of_primitives(cx, recv) { span_lint_and_then( diff --git a/clippy_lints/src/methods/suspicious_splitn.rs b/clippy_lints/src/methods/suspicious_splitn.rs index cbb7da327506d..3a4c71fed3b5d 100644 --- a/clippy_lints/src/methods/suspicious_splitn.rs +++ b/clippy_lints/src/methods/suspicious_splitn.rs @@ -10,7 +10,7 @@ pub(super) fn check(cx: &LateContext<'_>, method_name: Symbol, expr: &Expr<'_>, if count <= 1 && let Some(call_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) && let Some(impl_id) = cx.tcx.inherent_impl_of_assoc(call_id) - && let self_ty = cx.tcx.type_of(impl_id).instantiate_identity() + && let self_ty = cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip() && (self_ty.is_slice() || self_ty.is_str()) { // Ignore empty slice and string literals when used with a literal count. diff --git a/clippy_lints/src/methods/type_id_on_box.rs b/clippy_lints/src/methods/type_id_on_box.rs index 418eb4a6b2ac4..3e4eae9e53668 100644 --- a/clippy_lints/src/methods/type_id_on_box.rs +++ b/clippy_lints/src/methods/type_id_on_box.rs @@ -7,7 +7,7 @@ use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty::adjustment::{Adjust, Adjustment, DerefAdjustKind}; use rustc_middle::ty::print::with_forced_trimmed_paths; -use rustc_middle::ty::{self, ExistentialPredicate, Ty}; +use rustc_middle::ty::{self, ExistentialPredicate, Ty, Unnormalized}; use rustc_span::Span; /// Checks if the given type is `dyn Any`, or a trait object that has `Any` as a supertrait. @@ -27,6 +27,7 @@ fn is_subtrait_of_any(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { .tcx .explicit_super_predicates_of(tr.def_id) .iter_identity_copied() + .map(Unnormalized::skip_norm_wip) .any(|(clause, _)| { matches!(clause.kind().skip_binder(), ty::ClauseKind::Trait(super_tr) if cx.tcx.is_diagnostic_item(sym::Any, super_tr.def_id())) diff --git a/clippy_lints/src/methods/unnecessary_sort_by.rs b/clippy_lints/src/methods/unnecessary_sort_by.rs index 3f81a6ecd2f8c..281d9e5f712f7 100644 --- a/clippy_lints/src/methods/unnecessary_sort_by.rs +++ b/clippy_lints/src/methods/unnecessary_sort_by.rs @@ -243,7 +243,7 @@ fn mapping_of_mirrored_pats(a_pat: &Pat<'_>, b_pat: &Pat<'_>) -> Option, expr: &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_assoc(method_id) - && cx.tcx.type_of(impl_id).instantiate_identity().is_slice() + && cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip().is_slice() && let ExprKind::Closure(&Closure { body, .. }) = arg.kind && let closure_body = cx.tcx.hir_body(body) && let &[Param { pat: l_pat, .. }, Param { pat: r_pat, .. }] = closure_body.params diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index af5498c353d43..d696bdfa6bd43 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -382,7 +382,7 @@ fn check_other_call_arg<'tcx>( ) -> bool { if let Some((maybe_call, maybe_arg)) = skip_addr_of_ancestors(cx, expr) && let Some((callee_def_id, _, recv, call_args)) = get_callee_generic_args_and_args(cx, maybe_call) - && let fn_sig = cx.tcx.fn_sig(callee_def_id).instantiate_identity().skip_binder() + && let fn_sig = cx.tcx.fn_sig(callee_def_id).instantiate_identity().skip_norm_wip().skip_binder() && let Some(i) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == maybe_arg.hir_id) && let Some(input) = fn_sig.inputs().get(i) && let (input, n_refs, _) = peel_and_count_ty_refs(*input) @@ -569,7 +569,7 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty< })); if trait_predicates.any(|predicate| { - let predicate = bound_fn_sig.rebind(predicate).instantiate(cx.tcx, new_subst); + let predicate = bound_fn_sig.rebind(predicate).instantiate(cx.tcx, new_subst).skip_norm_wip(); let obligation = Obligation::new(cx.tcx, ObligationCause::dummy(), cx.param_env, predicate); !cx.tcx .infer_ctxt() @@ -698,7 +698,7 @@ fn check_if_applicable_to_argument<'tcx>(cx: &LateContext<'tcx>, arg: &Expr<'tcx sym::to_vec => cx .tcx .impl_of_assoc(method_def_id) - .is_some_and(|impl_did| cx.tcx.type_of(impl_did).instantiate_identity().is_slice()), + .is_some_and(|impl_did| cx.tcx.type_of(impl_did).instantiate_identity().skip_norm_wip().is_slice()), _ => false, } && let original_arg_ty = cx.typeck_results().node_type(caller.hir_id).peel_refs() diff --git a/clippy_lints/src/methods/unwrap_expect_used.rs b/clippy_lints/src/methods/unwrap_expect_used.rs index c07c932176462..ad7b0fc47cf3a 100644 --- a/clippy_lints/src/methods/unwrap_expect_used.rs +++ b/clippy_lints/src/methods/unwrap_expect_used.rs @@ -74,7 +74,7 @@ pub(super) fn check( } for &def_id in unwrap_allowed_aliases { - let alias_ty = cx.tcx.type_of(def_id).instantiate_identity(); + let alias_ty = cx.tcx.type_of(def_id).instantiate_identity().skip_norm_wip(); if let (ty::Adt(adt, substs), ty::Adt(alias_adt, alias_substs)) = (ty.kind(), alias_ty.kind()) && adt.did() == alias_adt.did() { diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints/src/methods/utils.rs index 1e1b124b44866..012a148bb2a29 100644 --- a/clippy_lints/src/methods/utils.rs +++ b/clippy_lints/src/methods/utils.rs @@ -111,7 +111,7 @@ impl<'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'_, 'tcx> { ExprKind::MethodCall(.., args, _) => { if args.iter().all(|arg| !self.is_binding(arg)) && let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id) - && let method_ty = self.cx.tcx.type_of(method_def_id).instantiate_identity() + && let method_ty = self.cx.tcx.type_of(method_def_id).instantiate_identity().skip_norm_wip() && let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder() && matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not)) { diff --git a/clippy_lints/src/methods/vec_resize_to_zero.rs b/clippy_lints/src/methods/vec_resize_to_zero.rs index 8979f9973d6f6..eed2ebebc0281 100644 --- a/clippy_lints/src/methods/vec_resize_to_zero.rs +++ b/clippy_lints/src/methods/vec_resize_to_zero.rs @@ -22,6 +22,7 @@ pub(super) fn check<'tcx>( .tcx .type_of(impl_id) .instantiate_identity() + .skip_norm_wip() .is_diag_item(cx, sym::Vec) && let ExprKind::Lit(Spanned { node: LitKind::Int(Pu128(0), _), diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs index 99c3266718436..0839219c5b613 100644 --- a/clippy_lints/src/missing_const_for_fn.rs +++ b/clippy_lints/src/missing_const_for_fn.rs @@ -203,7 +203,7 @@ fn could_be_const_with_abi(cx: &LateContext<'_>, msrv: Msrv, abi: ExternAbi) -> /// Return `true` when the given `def_id` is a function that has `impl Trait` ty as one of /// its parameter types. fn fn_inputs_has_impl_trait_ty(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { - let inputs = cx.tcx.fn_sig(def_id).instantiate_identity().inputs().skip_binder(); + let inputs = cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip().inputs().skip_binder(); inputs.iter().any(|input| { matches!( input.kind(), diff --git a/clippy_lints/src/mut_key.rs b/clippy_lints/src/mut_key.rs index 86438a1753bdc..93282545bf2a1 100644 --- a/clippy_lints/src/mut_key.rs +++ b/clippy_lints/src/mut_key.rs @@ -110,7 +110,7 @@ impl<'tcx> MutableKeyType<'tcx> { } fn check_sig(&mut self, cx: &LateContext<'tcx>, fn_def_id: LocalDefId, decl: &hir::FnDecl<'tcx>) { - let fn_sig = cx.tcx.fn_sig(fn_def_id).instantiate_identity(); + let fn_sig = cx.tcx.fn_sig(fn_def_id).instantiate_identity().skip_norm_wip(); for (hir_ty, ty) in iter::zip(decl.inputs, fn_sig.inputs().skip_binder()) { self.check_ty_(cx, hir_ty.span, *ty); } diff --git a/clippy_lints/src/needless_borrows_for_generic_args.rs b/clippy_lints/src/needless_borrows_for_generic_args.rs index 3dbfd58cd04ec..5c0ed6e4aec9b 100644 --- a/clippy_lints/src/needless_borrows_for_generic_args.rs +++ b/clippy_lints/src/needless_borrows_for_generic_args.rs @@ -5,6 +5,7 @@ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{implements_trait, is_copy}; use clippy_utils::{DefinedTy, ExprUseNode, get_expr_use_site, peel_n_hir_expr_refs, sym}; +use rustc_middle::ty::Unnormalized; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId}; @@ -179,7 +180,7 @@ fn needless_borrow_count<'tcx>( let meta_sized_trait_def_id = cx.tcx.lang_items().meta_sized_trait(); let drop_trait_def_id = cx.tcx.lang_items().drop_trait(); - let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity().skip_binder(); + let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity().skip_norm_wip().skip_binder(); let predicates = cx.tcx.param_env(fn_id).caller_bounds(); let projection_predicates = predicates .iter() @@ -280,7 +281,7 @@ fn needless_borrow_count<'tcx>( return false; } - let predicate = EarlyBinder::bind(predicate).instantiate(cx.tcx, &args_with_referent_ty[..]); + let predicate = EarlyBinder::bind(predicate).instantiate(cx.tcx, &args_with_referent_ty[..]).skip_norm_wip(); let obligation = Obligation::new(cx.tcx, ObligationCause::dummy(), cx.param_env, predicate); let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode()); infcx.predicate_must_hold_modulo_regions(&obligation) @@ -308,6 +309,7 @@ fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool { .tcx .fn_sig(assoc_item.def_id) .instantiate_identity() + .skip_norm_wip() .skip_binder() .inputs()[0]; matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Mut)) @@ -428,8 +430,10 @@ fn replace_types<'tcx>( .expect_ty(cx.tcx) .to_ty(cx.tcx); - if let Ok(projected_ty) = cx.tcx.try_normalize_erasing_regions(cx.typing_env(), projection) - && args[term_param_ty.index as usize] != GenericArg::from(projected_ty) + if let Ok(projected_ty) = cx.tcx.try_normalize_erasing_regions( + cx.typing_env(), + Unnormalized::new_wip(projection), + ) && args[term_param_ty.index as usize] != GenericArg::from(projected_ty) { deque.push_back((*term_param_ty, projected_ty)); } diff --git a/clippy_lints/src/needless_maybe_sized.rs b/clippy_lints/src/needless_maybe_sized.rs index d718751e34281..ac3cf0250d0fc 100644 --- a/clippy_lints/src/needless_maybe_sized.rs +++ b/clippy_lints/src/needless_maybe_sized.rs @@ -4,7 +4,7 @@ use rustc_errors::Applicability; use rustc_hir::def_id::{DefId, DefIdMap}; use rustc_hir::{BoundPolarity, GenericBound, Generics, PolyTraitRef, TraitBoundModifiers, WherePredicateKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{ClauseKind, PredicatePolarity}; +use rustc_middle::ty::{ClauseKind, PredicatePolarity, Unnormalized}; use rustc_session::declare_lint_pass; use rustc_span::symbol::Ident; @@ -92,7 +92,10 @@ fn path_to_sized_bound(cx: &LateContext<'_>, trait_bound: &PolyTraitRef<'_>) -> return true; } - for (predicate, _) in cx.tcx.explicit_super_predicates_of(trait_def_id).iter_identity_copied() { + for (predicate, _) in cx.tcx.explicit_super_predicates_of(trait_def_id) + .iter_identity_copied() + .map(Unnormalized::skip_norm_wip) + { if let ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder() && trait_predicate.polarity == PredicatePolarity::Positive && !path.contains(&trait_predicate.def_id()) diff --git a/clippy_lints/src/needless_pass_by_ref_mut.rs b/clippy_lints/src/needless_pass_by_ref_mut.rs index 2b1f7574a83ac..74a37077b0a1d 100644 --- a/clippy_lints/src/needless_pass_by_ref_mut.rs +++ b/clippy_lints/src/needless_pass_by_ref_mut.rs @@ -173,7 +173,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { return; } - let fn_sig = cx.tcx.fn_sig(fn_def_id).instantiate_identity(); + let fn_sig = cx.tcx.fn_sig(fn_def_id).instantiate_identity().skip_norm_wip(); let fn_sig = cx.tcx.liberate_late_bound_regions(fn_def_id.to_def_id(), fn_sig); // If there are no `&mut` argument, no need to go any further. diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index fb5f21acf2af2..593eff6a9bbd1 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -145,7 +145,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { ctx }; - let fn_sig = cx.tcx.fn_sig(fn_def_id).instantiate_identity(); + let fn_sig = cx.tcx.fn_sig(fn_def_id).instantiate_identity().skip_norm_wip(); let fn_sig = cx.tcx.liberate_late_bound_regions(fn_def_id.to_def_id(), fn_sig); for (idx, ((input, &ty), arg)) in decl.inputs.iter().zip(fn_sig.inputs()).zip(body.params).enumerate() { diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index ce8ce2aa900a0..430b6f055f5f3 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -90,14 +90,14 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { && impl_item.generics.params.is_empty() && sig.decl.inputs.is_empty() && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) - && let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity() + && let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip() && self_ty == return_ty(cx, impl_item.owner_id) && let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default) { if self.impling_types.is_none() { let mut impls = HirIdSet::default(); for &d in cx.tcx.local_trait_impls(default_trait_id) { - let ty = cx.tcx.type_of(d).instantiate_identity(); + let ty = cx.tcx.type_of(d).instantiate_identity().skip_norm_wip(); if let Some(ty_def) = ty.ty_adt_def() && let Some(local_def_id) = ty_def.did().as_local() { @@ -110,7 +110,7 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { // Check if a Default implementation exists for the Self type, regardless of // generics if let Some(ref impling_types) = self.impling_types - && let self_def = cx.tcx.type_of(item.owner_id).instantiate_identity() + && let self_def = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip() && let Some(self_def) = self_def.ty_adt_def() && let Some(self_local_did) = self_def.did().as_local() && let self_id = cx.tcx.local_def_id_to_hir_id(self_local_did) diff --git a/clippy_lints/src/no_effect.rs b/clippy_lints/src/no_effect.rs index 7163daa5b0f3a..f2a54b99170e9 100644 --- a/clippy_lints/src/no_effect.rs +++ b/clippy_lints/src/no_effect.rs @@ -156,6 +156,7 @@ impl NoEffect { .tcx .fn_sig(item.owner_id) .instantiate_identity() + .skip_norm_wip() .output() .skip_binder(); diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index ea460803ef027..a06497e140ba4 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -24,6 +24,7 @@ use clippy_utils::macros::macro_backtrace; use clippy_utils::paths::{PathNS, lookup_path_str}; use clippy_utils::ty::{get_field_idx_by_name, implements_trait}; use clippy_utils::{is_in_const_context, sym}; +use rustc_middle::ty::Unnormalized; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, DefIdSet}; @@ -277,7 +278,7 @@ impl<'tcx> NonCopyConst<'tcx> { /// Checks if a value of the given type is `Freeze`, or may be depending on the value. fn is_ty_freeze(&mut self, tcx: TyCtxt<'tcx>, typing_env: TypingEnv<'tcx>, ty: Ty<'tcx>) -> IsFreeze { // FIXME: this should probably be using the trait solver - let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty); + let ty = tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)).unwrap_or(ty); match self.freeze_tys.entry(ty) { Entry::Occupied(e) => *e.get(), Entry::Vacant(e) => { @@ -345,7 +346,7 @@ impl<'tcx> NonCopyConst<'tcx> { ty: Ty<'tcx>, val: ConstValue, ) -> Result { - let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty); + let ty = tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)).unwrap_or(ty); match self.is_ty_freeze(tcx, typing_env, ty) { IsFreeze::Yes => Ok(true), IsFreeze::Maybe if matches!(ty.kind(), ty::Adt(..) | ty::Array(..) | ty::Tuple(..)) => { @@ -381,7 +382,7 @@ impl<'tcx> NonCopyConst<'tcx> { ) -> bool { // Make sure to instantiate all types coming from `typeck` with `gen_args`. let ty = EarlyBinder::bind(typeck.expr_ty(e)).instantiate(tcx, gen_args); - let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty); + let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty.skip_norm_wip()); match self.is_ty_freeze(tcx, typing_env, ty) { IsFreeze::Yes => true, IsFreeze::No => false, @@ -395,7 +396,7 @@ impl<'tcx> NonCopyConst<'tcx> { }, ExprKind::Path(ref p) => { let res = typeck.qpath_res(p, e.hir_id); - let gen_args = EarlyBinder::bind(typeck.node_args(e.hir_id)).instantiate(tcx, gen_args); + let gen_args = EarlyBinder::bind(typeck.node_args(e.hir_id)).instantiate(tcx, gen_args).skip_norm_wip(); match res { Res::Def(DefKind::Const { .. } | DefKind::AssocConst { .. }, did) if let Ok(val) = @@ -447,7 +448,7 @@ impl<'tcx> NonCopyConst<'tcx> { loop { let ty = typeck.expr_ty(src_expr); // Normalized as we need to check if this is an array later. - let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty); + let ty = tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)).unwrap_or(ty); let is_freeze = self.is_ty_freeze(tcx, typing_env, ty); if is_freeze.is_freeze() { return None; @@ -488,7 +489,7 @@ impl<'tcx> NonCopyConst<'tcx> { let mut ty = typeck.expr_ty(src_expr); loop { // Normalized as we need to check if this is an array later. - ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty); + ty = tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)).unwrap_or(ty); if let [adjust, ..] = typeck.expr_adjustments(src_expr) { let res = if let Some(cause) = does_adjust_borrow(adjust) && !self.is_value_freeze(tcx, typing_env, ty, val)? @@ -588,7 +589,9 @@ impl<'tcx> NonCopyConst<'tcx> { }, ExprKind::Path(ref init_path) => { let next_init_args = - EarlyBinder::bind(init_typeck.node_args(init_expr.hir_id)).instantiate(tcx, init_args); + EarlyBinder::bind(init_typeck.node_args(init_expr.hir_id)) + .instantiate(tcx, init_args) + .skip_norm_wip(); match init_typeck.qpath_res(init_path, init_expr.hir_id) { Res::Def(DefKind::Ctor(..), _) => return None, Res::Def(DefKind::Const { .. } | DefKind::AssocConst { .. }, did) @@ -624,7 +627,7 @@ impl<'tcx> NonCopyConst<'tcx> { // gets cached. let ty = typeck.expr_ty(src_expr); // Normalized as we need to check if this is an array later. - let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty); + let ty = tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)).unwrap_or(ty); if self.is_ty_freeze(tcx, typing_env, ty).is_freeze() { return None; } @@ -702,7 +705,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst<'tcx> { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Const(ident, .., ct_rhs) = item.kind && !ident.is_special() - && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() + && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip() && match self.is_ty_freeze(cx.tcx, cx.typing_env(), ty) { IsFreeze::No => true, IsFreeze::Yes => false, @@ -743,7 +746,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst<'tcx> { fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { if let TraitItemKind::Const(_, ct_rhs_opt, _) = item.kind - && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() + && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip() && match self.is_ty_freeze(cx.tcx, cx.typing_env(), ty) { IsFreeze::No => true, IsFreeze::Maybe if let Some(ct_rhs) = ct_rhs_opt => { @@ -778,7 +781,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst<'tcx> { fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) { if let ImplItemKind::Const(_, ct_rhs) = item.kind - && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() + && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip() && match self.is_ty_freeze(cx.tcx, cx.typing_env(), ty) { IsFreeze::Yes => false, IsFreeze::No => { @@ -795,9 +798,9 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst<'tcx> { let ty = (ReplaceAssocFolder { tcx: cx.tcx, trait_id, - self_ty: cx.tcx.type_of(parent_item.owner_id).instantiate_identity(), + self_ty: cx.tcx.type_of(parent_item.owner_id).instantiate_identity().skip_norm_wip(), }) - .fold_ty(cx.tcx.type_of(item.owner_id).instantiate_identity()); + .fold_ty(cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip()); // `ty` may not be normalizable, but that should be fine. !self.is_ty_freeze(cx.tcx, cx.typing_env(), ty).is_not_freeze() } else { diff --git a/clippy_lints/src/non_send_fields_in_send_ty.rs b/clippy_lints/src/non_send_fields_in_send_ty.rs index c6ed0082016ff..6f1fca8d402ee 100644 --- a/clippy_lints/src/non_send_fields_in_send_ty.rs +++ b/clippy_lints/src/non_send_fields_in_send_ty.rs @@ -88,7 +88,7 @@ impl<'tcx> LateLintPass<'tcx> for NonSendFieldInSendTy { && send_trait == trait_id && of_trait.polarity == ImplPolarity::Positive && let ty_trait_ref = cx.tcx.impl_trait_ref(item.owner_id) - && let self_ty = ty_trait_ref.instantiate_identity().self_ty() + && let self_ty = ty_trait_ref.instantiate_identity().skip_norm_wip().self_ty() && let ty::Adt(adt_def, impl_trait_args) = self_ty.kind() { let mut non_send_fields = Vec::new(); diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs index f1625b1a4c6e0..2bb5615cfb767 100644 --- a/clippy_lints/src/only_used_in_recursion.rs +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -333,7 +333,7 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion { && let Ok(trait_item_id) = trait_item_def_id { let impl_id = cx.tcx.local_parent(owner_id.def_id); - let trait_ref = cx.tcx.impl_trait_ref(impl_id).instantiate_identity(); + let trait_ref = cx.tcx.impl_trait_ref(impl_id).instantiate_identity().skip_norm_wip(); ( trait_item_id, FnKind::ImplTraitFn( diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index b18957aaff44e..832b12712f83f 100644 --- a/clippy_lints/src/operators/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -283,7 +283,7 @@ fn is_assoc_fn_without_type_instance<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<' .. }, )) = func.kind - && let output_ty = cx.tcx.fn_sig(*def_id).instantiate_identity().skip_binder().output() + && let output_ty = cx.tcx.fn_sig(*def_id).instantiate_identity().skip_norm_wip().skip_binder().output() && let ty::Param(ty::ParamTy { name: kw::SelfUpper, .. }) = output_ty.kind() diff --git a/clippy_lints/src/pass_by_ref_or_value.rs b/clippy_lints/src/pass_by_ref_or_value.rs index 6cb57b1d3b368..b4a1713222123 100644 --- a/clippy_lints/src/pass_by_ref_or_value.rs +++ b/clippy_lints/src/pass_by_ref_or_value.rs @@ -130,7 +130,7 @@ impl PassByRefOrValue { return; } - let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity(); + let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); let fn_body = cx.enclosing_body.map(|id| cx.tcx.hir_body(id)); // Gather all the lifetimes found in the output type which may affect whether diff --git a/clippy_lints/src/ptr/ptr_arg.rs b/clippy_lints/src/ptr/ptr_arg.rs index 4bfff64b1bd48..ea1be8899b292 100644 --- a/clippy_lints/src/ptr/ptr_arg.rs +++ b/clippy_lints/src/ptr/ptr_arg.rs @@ -36,7 +36,7 @@ pub(super) fn check_body<'tcx>( } let decl = sig.decl; - let sig = cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder(); + let sig = cx.tcx.fn_sig(item_id).instantiate_identity().skip_norm_wip().skip_binder(); let lint_args: Vec<_> = check_fn_args(cx, sig, decl.inputs, body.params) .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not) .collect(); @@ -68,7 +68,7 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item_id: OwnerId, s for arg in check_fn_args( cx, - cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder(), + cx.tcx.fn_sig(item_id).instantiate_identity().skip_norm_wip().skip_binder(), sig.decl.inputs, &[], ) diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index 2f79ca18e8136..aa840f400d42e 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -402,7 +402,7 @@ fn can_switch_ranges<'tcx>( }; let inputs = cx .tcx - .liberate_late_bound_regions(id, cx.tcx.fn_sig(id).instantiate_identity()) + .liberate_late_bound_regions(id, cx.tcx.fn_sig(id).instantiate_identity().skip_norm_wip()) .inputs(); let expr_ty = inputs[input_idx]; // Check that the `expr` type is present only once, otherwise modifying just one of them might be @@ -448,6 +448,7 @@ fn can_switch_ranges<'tcx>( .tcx .type_of(switched_range_def_id) .instantiate(cx.tcx, &[inner_ty.into()]) + .skip_norm_wip() // 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() diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index 1637156d7c377..3c0f04a8e87ce 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -3,6 +3,7 @@ use clippy_utils::get_parent_expr; use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::peel_and_count_ty_refs; +use rustc_middle::ty::Unnormalized; use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, LangItem, Mutability}; @@ -142,7 +143,11 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { } else if let Some(target_id) = cx.tcx.lang_items().deref_target() && let Ok(deref_ty) = cx.tcx.try_normalize_erasing_regions( cx.typing_env(), - Ty::new_projection_from_args(cx.tcx, target_id, cx.tcx.mk_args(&[GenericArg::from(indexed_ty)])), + Unnormalized::new_wip(Ty::new_projection_from_args( + cx.tcx, + target_id, + cx.tcx.mk_args(&[GenericArg::from(indexed_ty)]) + )) ) && deref_ty == expr_ty { diff --git a/clippy_lints/src/returns/let_and_return.rs b/clippy_lints/src/returns/let_and_return.rs index 57f33632a5bce..2ec921ed21c7d 100644 --- a/clippy_lints/src/returns/let_and_return.rs +++ b/clippy_lints/src/returns/let_and_return.rs @@ -73,6 +73,7 @@ fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) .tcx .fn_sig(def_id) .instantiate_identity() + .skip_norm_wip() .skip_binder() .output() .walk() diff --git a/clippy_lints/src/self_named_constructors.rs b/clippy_lints/src/self_named_constructors.rs index e32cf944536bf..c51959778b206 100644 --- a/clippy_lints/src/self_named_constructors.rs +++ b/clippy_lints/src/self_named_constructors.rs @@ -53,7 +53,7 @@ impl<'tcx> LateLintPass<'tcx> for SelfNamedConstructors { let parent = cx.tcx.hir_get_parent_item(impl_item.hir_id()).def_id; let item = cx.tcx.hir_expect_item(parent); - let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); + let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip(); let ret_ty = return_ty(cx, impl_item.owner_id); // Do not check trait impls diff --git a/clippy_lints/src/significant_drop_tightening.rs b/clippy_lints/src/significant_drop_tightening.rs index 3e77c98b88456..12ce318969686 100644 --- a/clippy_lints/src/significant_drop_tightening.rs +++ b/clippy_lints/src/significant_drop_tightening.rs @@ -2,6 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::MaybeResPath; use clippy_utils::source::{indent_of, snippet}; use clippy_utils::{expr_or_init, get_builtin_attr, peel_hir_expr_unary, sym}; +use rustc_middle::ty::Unnormalized; use rustc_ast::BindingMode; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_errors::Applicability; @@ -153,8 +154,7 @@ impl<'cx, 'others, 'tcx> AttrChecker<'cx, 'others, 'tcx> { } let ty = self .cx - .tcx - .try_normalize_erasing_regions(self.cx.typing_env(), ty) + .tcx.try_normalize_erasing_regions(self.cx.typing_env(), Unnormalized::new_wip(ty)) .unwrap_or(ty); match self.type_cache.entry(ty) { Entry::Occupied(e) => return *e.get(), diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index c097f7773099a..d5a9006095da1 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -1,6 +1,7 @@ use super::TRANSMUTE_UNDEFINED_REPR; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::is_c_void; +use rustc_middle::ty::Unnormalized; use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty::{self, GenericArgsRef, IntTy, Ty, UintTy}; @@ -240,7 +241,7 @@ enum ReducedTy<'tcx> { /// Reduce structs containing a single non-zero sized field to it's contained type. fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> { loop { - ty = cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty).unwrap_or(ty); + ty = cx.tcx.try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(ty)).unwrap_or(ty); return match *ty.kind() { ty::Pat(base, _) => { ty = base; @@ -270,7 +271,7 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> .non_enum_variant() .fields .iter() - .map(|f| cx.tcx.type_of(f.did).instantiate(cx.tcx, args)); + .map(|f| cx.tcx.type_of(f.did).instantiate(cx.tcx, args).skip_norm_wip()); let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else { return ReducedTy::TypeErasure { raw_ptr_only: false }; }; @@ -297,7 +298,7 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> } fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { - if let Ok(ty) = cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty) + if let Ok(ty) = cx.tcx.try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(ty)) && let Ok(layout) = cx.tcx.layout_of(cx.typing_env().as_query_input(ty)) { layout.layout.size().bytes() == 0 diff --git a/clippy_lints/src/transmute/utils.rs b/clippy_lints/src/transmute/utils.rs index 5baa67b1f3e8a..74494aff0cb62 100644 --- a/clippy_lints/src/transmute/utils.rs +++ b/clippy_lints/src/transmute/utils.rs @@ -1,3 +1,4 @@ +use rustc_middle::ty::Unnormalized; use rustc_lint::LateContext; use rustc_middle::ty::Ty; @@ -5,8 +6,8 @@ use rustc_middle::ty::Ty; // size or alignment pub(super) fn is_layout_incompatible<'tcx>(cx: &LateContext<'tcx>, from: Ty<'tcx>, to: Ty<'tcx>) -> bool { let typing_env = cx.typing_env(); - if let Ok(from) = cx.tcx.try_normalize_erasing_regions(typing_env, from) - && let Ok(to) = cx.tcx.try_normalize_erasing_regions(typing_env, to) + if let Ok(from) = cx.tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(from)) + && let Ok(to) = cx.tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(to)) && let Ok(from_layout) = cx.tcx.layout_of(typing_env.as_query_input(from)) && let Ok(to_layout) = cx.tcx.layout_of(typing_env.as_query_input(to)) { diff --git a/clippy_lints/src/unit_return_expecting_ord.rs b/clippy_lints/src/unit_return_expecting_ord.rs index 39f4130afcf36..735225a9e2cee 100644 --- a/clippy_lints/src/unit_return_expecting_ord.rs +++ b/clippy_lints/src/unit_return_expecting_ord.rs @@ -89,7 +89,7 @@ fn get_args_to_check<'tcx>( ) -> Vec<(usize, Symbol)> { let mut args_to_check = Vec::new(); if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { - let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity(); + let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); let generics = cx.tcx.predicates_of(def_id); let [fn_mut_preds, ord_preds, partial_ord_preds] = get_trait_predicates_for_trait_ids(cx, generics, &[Some(fn_mut_trait), ord_trait, partial_ord_trait]); diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index 2a23e5329e9e1..8bb6ceb863a37 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -317,7 +317,7 @@ fn needs_inferred_result_ty( }, _ => return false, }; - let sig = cx.tcx.fn_sig(id).instantiate_identity().skip_binder(); + let sig = cx.tcx.fn_sig(id).instantiate_identity().skip_norm_wip().skip_binder(); if let ty::Param(output_ty) = *sig.output().kind() { let args: Vec<&Expr<'_>> = if let Some(receiver) = receiver { std::iter::once(receiver).chain(args.iter()).collect() diff --git a/clippy_lints/src/unnecessary_mut_passed.rs b/clippy_lints/src/unnecessary_mut_passed.rs index eb2d7639e91f7..75b0b7b10687a 100644 --- a/clippy_lints/src/unnecessary_mut_passed.rs +++ b/clippy_lints/src/unnecessary_mut_passed.rs @@ -60,7 +60,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMutPassed { if let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) => { let args = cx.typeck_results().node_args(e.hir_id); - let method_type = cx.tcx.type_of(def_id).instantiate(cx.tcx, args); + let method_type = cx.tcx.type_of(def_id).instantiate(cx.tcx, args).skip_norm_wip(); check_arguments( cx, &mut iter::once(receiver).chain(arguments.iter()), diff --git a/clippy_lints/src/use_self.rs b/clippy_lints/src/use_self.rs index 620874e2aebf8..93d0870a30a21 100644 --- a/clippy_lints/src/use_self.rs +++ b/clippy_lints/src/use_self.rs @@ -153,7 +153,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { let impl_trait_ref = cx.tcx.impl_trait_ref(impl_id); // `self_ty` is the semantic self type of `impl for `. This cannot be // `Self`. - let self_ty = impl_trait_ref.instantiate_identity().self_ty(); + let self_ty = impl_trait_ref.instantiate_identity().skip_norm_wip().self_ty(); // `trait_method_sig` is the signature of the function, how it is declared in the // trait, not in the impl of the trait. @@ -161,7 +161,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { .tcx .trait_item_of(impl_item.owner_id) .expect("impl method matches a trait method"); - let trait_method_sig = cx.tcx.fn_sig(trait_method).instantiate_identity(); + let trait_method_sig = cx.tcx.fn_sig(trait_method).instantiate_identity().skip_norm_wip(); let trait_method_sig = cx.tcx.instantiate_bound_regions_with_erased(trait_method_sig); // `impl_inputs_outputs` is an iterator over the types (`hir::Ty`) declared in the @@ -217,7 +217,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { && !ty_is_in_generic_args(cx, hir_ty) && !types_to_skip.contains(&hir_ty.hir_id) && let ty = ty_from_hir_ty(cx, hir_ty.as_unambig_ty()) - && let impl_ty = cx.tcx.type_of(impl_id).instantiate_identity() + && let impl_ty = cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip() && same_type_modulo_regions(ty, impl_ty) // Ensure the type we encounter and the one from the impl have the same lifetime parameters. It may be that // the lifetime parameters of `ty` are elided (`impl<'a> Foo<'a> { fn new() -> Self { Foo{..} } }`), in @@ -232,7 +232,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { if !expr.span.from_expansion() && let Some(&StackItem::Check { impl_id, .. }) = self.stack.last() - && cx.typeck_results().expr_ty(expr) == cx.tcx.type_of(impl_id).instantiate_identity() + && cx.typeck_results().expr_ty(expr) == cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip() && self.msrv.meets(cx, msrvs::TYPE_ALIAS_ENUM_VARIANTS) { match expr.kind { @@ -255,7 +255,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { && let PatKind::Expr(&PatExpr { kind: PatExprKind::Path(QPath::Resolved(_, path)), .. }) | PatKind::TupleStruct(QPath::Resolved(_, path), _, _) | PatKind::Struct(QPath::Resolved(_, path), _, _) = pat.kind - && cx.typeck_results().pat_ty(pat) == cx.tcx.type_of(impl_id).instantiate_identity() + && cx.typeck_results().pat_ty(pat) == cx.tcx.type_of(impl_id).instantiate_identity().skip_norm_wip() && self.msrv.meets(cx, msrvs::TYPE_ALIAS_ENUM_VARIANTS) { check_path(cx, path); diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index 662da5929adb7..0174b90064b53 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -112,7 +112,7 @@ fn into_iter_bound<'tcx>( } })); - let predicate = EarlyBinder::bind(tr).instantiate(cx.tcx, args); + let predicate = EarlyBinder::bind(tr).instantiate(cx.tcx, args).skip_norm_wip(); let obligation = Obligation::new(cx.tcx, ObligationCause::dummy(), cx.param_env, predicate); if !cx .tcx diff --git a/clippy_lints_internal/src/msrv_attr_impl.rs b/clippy_lints_internal/src/msrv_attr_impl.rs index 6d5c7b86a0aed..a968629ac2dc1 100644 --- a/clippy_lints_internal/src/msrv_attr_impl.rs +++ b/clippy_lints_internal/src/msrv_attr_impl.rs @@ -26,14 +26,13 @@ impl LateLintPass<'_> for MsrvAttrImpl { items, .. }) = &item.kind - && let trait_ref = cx.tcx.impl_trait_ref(item.owner_id).instantiate_identity() + && let trait_ref = cx.tcx.impl_trait_ref(item.owner_id).instantiate_identity().skip_norm_wip() && internal_paths::EARLY_LINT_PASS.matches(cx, trait_ref.def_id) && let ty::Adt(self_ty_def, _) = trait_ref.self_ty().kind() && self_ty_def.is_struct() && self_ty_def.all_fields().any(|f| { cx.tcx - .type_of(f.did) - .instantiate_identity() + .type_of(f.did).instantiate_identity().skip_norm_wip() .walk() .filter(|t| matches!(t.kind(), GenericArgKind::Type(_))) .any(|t| internal_paths::MSRV_STACK.matches_ty(cx, t.expect_ty())) diff --git a/clippy_lints_internal/src/symbols.rs b/clippy_lints_internal/src/symbols.rs index 779485e49b394..ae74186ea90c2 100644 --- a/clippy_lints_internal/src/symbols.rs +++ b/clippy_lints_internal/src/symbols.rs @@ -114,7 +114,7 @@ impl<'tcx> LateLintPass<'tcx> for Symbols { for item in cx.tcx.module_children(*def_id) { if let Res::Def(DefKind::Const { .. }, item_def_id) = item.res - && let ty = cx.tcx.type_of(item_def_id).instantiate_identity() + && let ty = cx.tcx.type_of(item_def_id).instantiate_identity().skip_norm_wip() && internal_paths::SYMBOL.matches_ty(cx, ty) && let Ok(ConstValue::Scalar(value)) = cx.tcx.const_eval_poly(item_def_id) && let Some(value) = value.to_u32().discard_err() diff --git a/clippy_lints_internal/src/unusual_names.rs b/clippy_lints_internal/src/unusual_names.rs index e11a2868fb69c..b10f4be7497b9 100644 --- a/clippy_lints_internal/src/unusual_names.rs +++ b/clippy_lints_internal/src/unusual_names.rs @@ -68,7 +68,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusualNames { for (param, ty) in body .params .iter() - .zip(cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder().inputs()) + .zip(cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip().skip_binder().inputs()) { check_pat_name_for_ty(cx, param.pat, *ty, "parameter"); } diff --git a/clippy_utils/src/eager_or_lazy.rs b/clippy_utils/src/eager_or_lazy.rs index d184744162e4e..0e28cdf3b9ca3 100644 --- a/clippy_utils/src/eager_or_lazy.rs +++ b/clippy_utils/src/eager_or_lazy.rs @@ -52,7 +52,7 @@ fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg: use EagernessSuggestion::{Eager, Lazy, NoChange}; let ty = match cx.tcx.impl_of_assoc(fn_id) { - Some(id) => cx.tcx.type_of(id).instantiate_identity(), + Some(id) => cx.tcx.type_of(id).instantiate_identity().skip_norm_wip(), None => return Lazy, }; @@ -71,7 +71,7 @@ fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg: // Due to the limited operations on these types functions should be fairly cheap. if def.variants().iter().flat_map(|v| v.fields.iter()).any(|x| { matches!( - cx.tcx.type_of(x.did).instantiate_identity().peel_refs().kind(), + cx.tcx.type_of(x.did).instantiate_identity().skip_norm_wip().peel_refs().kind(), ty::Param(_) ) }) && all_predicates_of(cx.tcx, fn_id).all(|(pred, _)| match pred.kind().skip_binder() { @@ -82,8 +82,7 @@ fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg: // Limit the function to either `(self) -> bool` or `(&self) -> bool` match &**cx .tcx - .fn_sig(fn_id) - .instantiate_identity() + .fn_sig(fn_id).instantiate_identity().skip_norm_wip() .skip_binder() .inputs_and_output { diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index a1860adb4407e..e5420f9f9d166 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -548,7 +548,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_assoc(def_id) - && let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def() + && let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().skip_norm_wip().ty_adt_def() { return Some(adt.did()) == cx.tcx.lang_items().string() || (cx.tcx.get_diagnostic_name(adt.did())).is_some_and(|adt_name| std_types_symbols.contains(&adt_name)); @@ -1483,13 +1483,13 @@ pub fn is_direct_expn_of(span: Span, name: Symbol) -> Option { /// Convenience function to get the return type of a function. pub fn return_ty<'tcx>(cx: &LateContext<'tcx>, fn_def_id: OwnerId) -> Ty<'tcx> { - let ret_ty = cx.tcx.fn_sig(fn_def_id).instantiate_identity().output(); + let ret_ty = cx.tcx.fn_sig(fn_def_id).instantiate_identity().skip_norm_wip().output(); cx.tcx.instantiate_bound_regions_with_erased(ret_ty) } /// Convenience function to get the nth argument type of a function. pub fn nth_arg<'tcx>(cx: &LateContext<'tcx>, fn_def_id: OwnerId, nth: usize) -> Ty<'tcx> { - let arg = cx.tcx.fn_sig(fn_def_id).instantiate_identity().input(nth); + let arg = cx.tcx.fn_sig(fn_def_id).instantiate_identity().skip_norm_wip().input(nth); cx.tcx.instantiate_bound_regions_with_erased(arg) } @@ -2634,7 +2634,7 @@ impl<'tcx> ExprUseNode<'tcx> { Self::LetStmt(LetStmt { ty: Some(ty), .. }) => Some(DefinedTy::Hir(ty)), Self::ConstStatic(id) => Some(DefinedTy::Mir { def_site_def_id: Some(id.def_id.to_def_id()), - ty: Binder::dummy(cx.tcx.type_of(id).instantiate_identity()), + ty: Binder::dummy(cx.tcx.type_of(id).instantiate_identity().skip_norm_wip()), }), Self::Return(id) => { if let Node::Expr(Expr { @@ -2647,7 +2647,7 @@ impl<'tcx> ExprUseNode<'tcx> { FnRetTy::Return(ty) => Some(DefinedTy::Hir(ty)), } } else { - let ty = cx.tcx.fn_sig(id).instantiate_identity().output(); + let ty = cx.tcx.fn_sig(id).instantiate_identity().skip_norm_wip().output(); Some(DefinedTy::Mir { def_site_def_id: Some(id.def_id.to_def_id()), ty, @@ -2669,7 +2669,7 @@ impl<'tcx> ExprUseNode<'tcx> { }) .map(|(adt, field_def)| DefinedTy::Mir { def_site_def_id: Some(adt.did()), - ty: Binder::dummy(cx.tcx.type_of(field_def.did).instantiate_identity()), + ty: Binder::dummy(cx.tcx.type_of(field_def.did).instantiate_identity().skip_norm_wip()), }), _ => None, }, @@ -3282,7 +3282,7 @@ pub fn get_path_from_caller_to_method_type<'tcx>( match assoc_item.container { rustc_ty::AssocContainer::Trait => get_path_to_callee(tcx, from, def_id), rustc_ty::AssocContainer::InherentImpl | rustc_ty::AssocContainer::TraitImpl(_) => { - let ty = tcx.type_of(def_id).instantiate_identity(); + let ty = tcx.type_of(def_id).instantiate_identity().skip_norm_wip(); get_path_to_ty(tcx, from, ty, args) }, } @@ -3298,7 +3298,7 @@ fn get_path_to_ty<'tcx>(tcx: TyCtxt<'tcx>, from: LocalDefId, ty: Ty<'tcx>, args: | rustc_ty::RawPtr(_, _) | rustc_ty::Ref(..) | rustc_ty::Slice(_) - | rustc_ty::Tuple(_) => format!("<{}>", EarlyBinder::bind(ty).instantiate(tcx, args)), + | rustc_ty::Tuple(_) => format!("<{}>", EarlyBinder::bind(ty).instantiate(tcx, args).skip_norm_wip()), _ => ty.to_string(), } } @@ -3478,7 +3478,7 @@ pub fn expr_requires_coercion<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) - // actually have type adjustments. match expr.kind { ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) if let Some(def_id) = fn_def_id(cx, expr) => { - let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity(); + let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); if !fn_sig.output().skip_binder().has_type_flags(TypeFlags::HAS_TY_PARAM) { return false; diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 62eddd20b7269..8f7a140e91a86 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -50,7 +50,7 @@ pub fn is_min_const_fn<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, msrv: Ms // impl trait is gone in MIR, so check the return type manually check_ty( cx, - cx.tcx.fn_sig(def_id).instantiate_identity().output().skip_binder(), + cx.tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip().output().skip_binder(), body.local_decls.iter().next().unwrap().source_info.span, msrv, )?; diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 9df2baa0d39ae..a5d17b76aa5e9 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -896,7 +896,7 @@ impl<'tcx> DerefDelegate<'_, 'tcx> { .cx .typeck_results() .type_dependent_def_id(parent_expr.hir_id) - .map(|did| self.cx.tcx.fn_sig(did).instantiate_identity().skip_binder()) + .map(|did| self.cx.tcx.fn_sig(did).instantiate_identity().skip_norm_wip().skip_binder()) { std::iter::once(receiver) .chain(call_args.iter()) diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index 7f22df27f5c11..0c261d21c1ec5 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -22,6 +22,7 @@ use rustc_middle::ty::{ self, AdtDef, AliasTy, AssocItem, AssocTag, Binder, BoundRegion, BoundVarIndexKind, FnSig, GenericArg, GenericArgKind, GenericArgsRef, IntTy, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr, + Unnormalized, }; use rustc_span::symbol::Ident; use rustc_span::{DUMMY_SP, Span, Symbol}; @@ -110,7 +111,10 @@ pub fn contains_ty_adt_constructor_opaque<'tcx>(cx: &LateContext<'tcx>, ty: Ty<' return false; } - for (predicate, _span) in cx.tcx.explicit_item_self_bounds(def_id).iter_identity_copied() { + for (predicate, _span) in cx.tcx.explicit_item_self_bounds(def_id) + .iter_identity_copied() + .map(Unnormalized::skip_norm_wip) + { match predicate.kind().skip_binder() { // For `impl Trait`, it will register a predicate of `T: Trait`, so we go through // and check substitutions to find `U`. @@ -605,7 +609,7 @@ impl<'tcx> ExprFnSig<'tcx> { /// If the expression is function like, get the signature for it. pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option> { if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) = expr.res(cx) { - Some(ExprFnSig::Sig(cx.tcx.fn_sig(id).instantiate_identity(), Some(id))) + Some(ExprFnSig::Sig(cx.tcx.fn_sig(id).instantiate_identity().skip_norm_wip(), Some(id))) } else { ty_sig(cx, cx.typeck_results().expr_ty_adjusted(expr).peel_refs()) } @@ -623,7 +627,7 @@ pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option Some(ExprFnSig::Sig(cx.tcx.fn_sig(id).instantiate(cx.tcx, subs), Some(id))), + ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.fn_sig(id).instantiate(cx.tcx, subs).skip_norm_wip(), Some(id))), ty::Alias(AliasTy { kind: ty::Opaque { def_id }, args, @@ -631,7 +635,7 @@ pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option sig_from_bounds( cx, ty, - cx.tcx.item_self_bounds(def_id).iter_instantiated(cx.tcx, args), + cx.tcx.item_self_bounds(def_id).iter_instantiated(cx.tcx, args).map(Unnormalized::skip_norm_wip), cx.tcx.opt_parent(def_id), ), ty::FnPtr(sig_tys, hdr) => Some(ExprFnSig::Sig(sig_tys.with(hdr), None)), @@ -657,7 +661,7 @@ pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option match cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty) { + ) => match cx.tcx.try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(ty)) { Ok(normalized_ty) if normalized_ty != ty => ty_sig(cx, normalized_ty), _ => sig_for_projection(cx, proj).or_else(|| sig_from_bounds(cx, ty, cx.param_env.caller_bounds(), None)), }, @@ -717,6 +721,7 @@ fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: AliasTy<'tcx>) -> Option .tcx .explicit_item_bounds(ty.kind.def_id()) .iter_instantiated_copied(cx.tcx, ty.args) + .map(Unnormalized::skip_norm_wip) { match pred.kind().skip_binder() { ty::ClauseKind::Trait(p) @@ -764,7 +769,7 @@ impl core::ops::Add for EnumValue { /// Attempts to read the given constant as though it were an enum value. pub fn read_explicit_enum_value(tcx: TyCtxt<'_>, id: DefId) -> Option { if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) { - match tcx.type_of(id).instantiate_identity().kind() { + match tcx.type_of(id).instantiate_identity().skip_norm_wip().kind() { ty::Int(_) => Some(EnumValue::Signed(value.to_int(value.size()))), ty::Uint(_) => Some(EnumValue::Unsigned(value.to_uint(value.size()))), _ => None, @@ -886,7 +891,7 @@ pub fn adt_and_variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option< Some((adt, adt.variant_with_id(var_id))) }, Res::SelfCtor(id) => { - let adt = cx.tcx.type_of(id).instantiate_identity().ty_adt_def().unwrap(); + let adt = cx.tcx.type_of(id).instantiate_identity().skip_norm_wip().ty_adt_def().unwrap(); Some((adt, adt.non_enum_variant())) }, _ => None, @@ -1066,9 +1071,10 @@ pub fn make_normalized_projection<'tcx>( ); return None; } - match tcx - .try_normalize_erasing_regions(typing_env, Ty::new_projection_from_args(tcx, ty.kind.def_id(), ty.args)) - { + match tcx.try_normalize_erasing_regions( + typing_env, + Unnormalized::new_wip(Ty::new_projection_from_args(tcx, ty.kind.def_id(), ty.args)) + ) { Ok(ty) => Some(ty), Err(e) => { debug_assert!(false, "failed to normalize type `{ty}`: {e:#?}"); @@ -1173,7 +1179,7 @@ impl<'tcx> InteriorMut<'tcx> { ty::Alias(AliasTy { kind: ty::Projection { .. }, .. - }) => match cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty) { + }) => match cx.tcx.try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(ty)) { Ok(normalized_ty) if ty != normalized_ty => self.interior_mut_ty_chain_inner(cx, normalized_ty, depth), _ => None, }, @@ -1316,7 +1322,7 @@ pub fn option_arg_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option(cx: &LateContext<'tcx>, iter_ty: Ty<'tcx>) -> bool { fn normalize_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { - cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty).unwrap_or(ty) + cx.tcx.try_normalize_erasing_regions(cx.typing_env(), Unnormalized::new_wip(ty)).unwrap_or(ty) } /// Check if `ty` contains mutable references or equivalent, which includes: diff --git a/clippy_utils/src/ty/type_certainty/mod.rs b/clippy_utils/src/ty/type_certainty/mod.rs index eadb07a11be02..5d8d30b910955 100644 --- a/clippy_utils/src/ty/type_certainty/mod.rs +++ b/clippy_utils/src/ty/type_certainty/mod.rs @@ -249,7 +249,7 @@ fn path_segment_certainty( let certainty = lhs.join_clearing_def_ids(rhs); if resolves_to_type { if let DefKind::TyAlias = cx.tcx.def_kind(def_id) { - adt_def_id(cx.tcx.type_of(def_id).instantiate_identity()) + adt_def_id(cx.tcx.type_of(def_id).instantiate_identity().skip_norm_wip()) .map_or(certainty, |def_id| certainty.with_def_id(def_id)) } else { certainty.with_def_id(def_id) From 4351f2a7ab299d834ab3bbb2b41a9a4c01bdb1b6 Mon Sep 17 00:00:00 2001 From: linshuy2 Date: Thu, 9 Apr 2026 15:02:23 +0000 Subject: [PATCH 21/38] fix: `for_kv_map` FN when using `iter` and `iter_mut` --- clippy_lints/src/loops/for_kv_map.rs | 19 ++++++++--- tests/ui/for_kv_map.fixed | 22 ++++++++++++ tests/ui/for_kv_map.rs | 22 ++++++++++++ tests/ui/for_kv_map.stderr | 50 +++++++++++++++++++++++++++- 4 files changed, 107 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/loops/for_kv_map.rs b/clippy_lints/src/loops/for_kv_map.rs index 7fb8e51377a20..d28029790732b 100644 --- a/clippy_lints/src/loops/for_kv_map.rs +++ b/clippy_lints/src/loops/for_kv_map.rs @@ -23,7 +23,20 @@ pub(super) fn check<'tcx>( && pat.len() == 2 { let arg_span = arg.span; - let (new_pat_span, kind, ty, mutbl) = match *cx.typeck_results().expr_ty(arg).kind() { + let (arg, arg_ty) = match arg.kind { + // `for x in &expr` or `for x in &mut expr` + ExprKind::AddrOf(BorrowKind::Ref, _, expr) => (expr, cx.typeck_results().expr_ty(arg)), + // `for x in receiver.iter()` or `for x in receiver.iter_mut()` + ExprKind::MethodCall(path, receiver, [], ..) + if path.ident.name == sym::iter || path.ident.name == sym::iter_mut => + { + // Use `expr_ty_adjusted` because `.iter()` / `.iter_mut()` may introduce auto deferences + (receiver, cx.typeck_results().expr_ty_adjusted(receiver)) + }, + _ => (arg, cx.typeck_results().expr_ty(arg)), + }; + + let (new_pat_span, kind, ty, mutbl) = match *arg_ty.kind() { ty::Ref(_, ty, mutbl) => match (&pat[0].kind, &pat[1].kind) { (key, _) if pat_is_wild(cx, key, body) => (pat[1].span, "value", ty, mutbl), (_, value) if pat_is_wild(cx, value, body) => (pat[0].span, "key", ty, Mutability::Not), @@ -35,10 +48,6 @@ pub(super) fn check<'tcx>( Mutability::Not => "", Mutability::Mut => "_mut", }; - let arg = match arg.kind { - ExprKind::AddrOf(BorrowKind::Ref, _, expr) => expr, - _ => arg, - }; if matches!(ty.opt_diag_name(cx), Some(sym::HashMap | sym::BTreeMap)) && let Some(arg_span) = walk_span_to_context(arg_span, span.ctxt()) diff --git a/tests/ui/for_kv_map.fixed b/tests/ui/for_kv_map.fixed index 6ec4cb01ffd1d..2fd6d9ca499dd 100644 --- a/tests/ui/for_kv_map.fixed +++ b/tests/ui/for_kv_map.fixed @@ -86,3 +86,25 @@ fn wrongly_unmangled_macros() { let _v = v; } } + +fn issue16822(mut x: HashMap) { + for v in x.values() { + //~^ for_kv_map + println!("{}", v); + } + + for v in x.values_mut() { + //~^ for_kv_map + *v += 1; + } + + for k in x.keys() { + //~^ for_kv_map + println!("{}", k); + } + + for k in x.keys() { + //~^ for_kv_map + println!("{}", k); + } +} diff --git a/tests/ui/for_kv_map.rs b/tests/ui/for_kv_map.rs index 19e907ff10a62..12e16d1dbfd61 100644 --- a/tests/ui/for_kv_map.rs +++ b/tests/ui/for_kv_map.rs @@ -86,3 +86,25 @@ fn wrongly_unmangled_macros() { let _v = v; } } + +fn issue16822(mut x: HashMap) { + for (_, v) in x.iter() { + //~^ for_kv_map + println!("{}", v); + } + + for (_, v) in x.iter_mut() { + //~^ for_kv_map + *v += 1; + } + + for (k, _) in x.iter() { + //~^ for_kv_map + println!("{}", k); + } + + for (k, _) in x.iter_mut() { + //~^ for_kv_map + println!("{}", k); + } +} diff --git a/tests/ui/for_kv_map.stderr b/tests/ui/for_kv_map.stderr index 5436592f2ab6d..fac0407e10032 100644 --- a/tests/ui/for_kv_map.stderr +++ b/tests/ui/for_kv_map.stderr @@ -84,5 +84,53 @@ LL - for (_, v) in test_map!(wrapped) { LL + for v in test_map!(wrapped).values() { | -error: aborting due to 7 previous errors +error: you seem to want to iterate on a map's values + --> tests/ui/for_kv_map.rs:91:19 + | +LL | for (_, v) in x.iter() { + | ^^^^^^^^ + | +help: use the corresponding method + | +LL - for (_, v) in x.iter() { +LL + for v in x.values() { + | + +error: you seem to want to iterate on a map's values + --> tests/ui/for_kv_map.rs:96:19 + | +LL | for (_, v) in x.iter_mut() { + | ^^^^^^^^^^^^ + | +help: use the corresponding method + | +LL - for (_, v) in x.iter_mut() { +LL + for v in x.values_mut() { + | + +error: you seem to want to iterate on a map's keys + --> tests/ui/for_kv_map.rs:101:19 + | +LL | for (k, _) in x.iter() { + | ^^^^^^^^ + | +help: use the corresponding method + | +LL - for (k, _) in x.iter() { +LL + for k in x.keys() { + | + +error: you seem to want to iterate on a map's keys + --> tests/ui/for_kv_map.rs:106:19 + | +LL | for (k, _) in x.iter_mut() { + | ^^^^^^^^^^^^ + | +help: use the corresponding method + | +LL - for (k, _) in x.iter_mut() { +LL + for k in x.keys() { + | + +error: aborting due to 11 previous errors From 6f8aee0157bb00448eb1858a38ef5bffe1dd8ede Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 28 Oct 2025 20:30:40 +0100 Subject: [PATCH 22/38] feat(manual_assert_eq): new lint --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/eta_reduction.rs | 2 +- .../src/functions/renamed_function_params.rs | 2 +- clippy_lints/src/lib.rs | 2 + clippy_lints/src/manual_assert_eq.rs | 122 ++++++++++++++++++ lintcheck/src/output.rs | 4 +- tests/ui/assertions_on_constants.rs | 1 + tests/ui/assertions_on_constants.stderr | 34 ++--- tests/ui/cmp_null.fixed | 1 + tests/ui/cmp_null.rs | 1 + tests/ui/cmp_null.stderr | 14 +- tests/ui/infinite_loops.rs | 4 +- tests/ui/manual_assert_eq.fixed | 114 ++++++++++++++++ tests/ui/manual_assert_eq.rs | 114 ++++++++++++++++ tests/ui/manual_assert_eq.stderr | 88 +++++++++++++ tests/ui/missing_asserts_for_indexing.fixed | 2 +- tests/ui/missing_asserts_for_indexing.rs | 2 +- tests/ui/panic_in_result_fn_assertions.rs | 4 +- tests/ui/panic_in_result_fn_assertions.stderr | 6 +- .../ui/panic_in_result_fn_debug_assertions.rs | 4 +- tests/ui/uninit_vec.rs | 2 +- tests/ui/unnecessary_map_or.fixed | 11 +- tests/ui/unnecessary_map_or.rs | 11 +- tests/ui/unnecessary_map_or.stderr | 60 ++++----- 25 files changed, 529 insertions(+), 78 deletions(-) create mode 100644 clippy_lints/src/manual_assert_eq.rs create mode 100644 tests/ui/manual_assert_eq.fixed create mode 100644 tests/ui/manual_assert_eq.rs create mode 100644 tests/ui/manual_assert_eq.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 8161470761796..60d4cdf3608f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6878,6 +6878,7 @@ Released 2018-09-13 [`main_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#main_recursion [`manual_abs_diff`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_abs_diff [`manual_assert`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert +[`manual_assert_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert_eq [`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn [`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits [`manual_c_str_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 837bf29f5f394..2908278ecc55e 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -298,6 +298,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::main_recursion::MAIN_RECURSION_INFO, crate::manual_abs_diff::MANUAL_ABS_DIFF_INFO, crate::manual_assert::MANUAL_ASSERT_INFO, + crate::manual_assert_eq::MANUAL_ASSERT_EQ_INFO, crate::manual_async_fn::MANUAL_ASYNC_FN_INFO, crate::manual_bits::MANUAL_BITS_INFO, crate::manual_checked_ops::MANUAL_CHECKED_OPS_INFO, diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 3562200cbd929..8db543d659559 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -373,7 +373,7 @@ fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<' } } - assert!(from_sig.inputs_and_output.len() == to_sig.inputs_and_output.len()); + assert_eq!(from_sig.inputs_and_output.len(), to_sig.inputs_and_output.len()); from_sig .inputs_and_output .iter() diff --git a/clippy_lints/src/functions/renamed_function_params.rs b/clippy_lints/src/functions/renamed_function_params.rs index e25611d48817a..2d330835a037a 100644 --- a/clippy_lints/src/functions/renamed_function_params.rs +++ b/clippy_lints/src/functions/renamed_function_params.rs @@ -57,7 +57,7 @@ impl RenamedFnArgs { { let mut renamed: Vec<(Span, String)> = vec![]; - debug_assert!(default_idents.size_hint() == current_idents.size_hint()); + debug_assert_eq!(default_idents.size_hint(), current_idents.size_hint()); for (default_ident, current_ident) in iter::zip(default_idents, current_idents) { let has_name_to_check = |ident: Option| { ident diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 72ee5cca03970..0875982f3bbf1 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -196,6 +196,7 @@ mod macro_use; mod main_recursion; mod manual_abs_diff; mod manual_assert; +mod manual_assert_eq; mod manual_async_fn; mod manual_bits; mod manual_checked_ops; @@ -867,6 +868,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co Box::new(move |tcx| Box::new(manual_pop_if::ManualPopIf::new(tcx, conf))), Box::new(move |_| Box::new(manual_noop_waker::ManualNoopWaker::new(conf))), Box::new(|_| Box::new(byte_char_slices::ByteCharSlice)), + Box::new(|_| Box::new(manual_assert_eq::ManualAssertEq)), // add late passes here, used by `cargo dev new_lint` ]; store.late_passes.extend(late_lints); diff --git a/clippy_lints/src/manual_assert_eq.rs b/clippy_lints/src/manual_assert_eq.rs new file mode 100644 index 0000000000000..d1770d2e95a66 --- /dev/null +++ b/clippy_lints/src/manual_assert_eq.rs @@ -0,0 +1,122 @@ +use clippy_utils::consts::ConstEvalCtxt; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::{PanicCall, find_assert_args, root_macro_call_first_node}; +use clippy_utils::source::walk_span_to_context; +use clippy_utils::ty::implements_trait; +use clippy_utils::{is_in_const_context, sym}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_session::declare_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// Checks for `assert!` and `debug_assert!` that consist of only an (in)equality check + /// + /// ### Why is this bad? + /// `assert_{eq,ne}!` and `debug_assert_{eq,ne}!` achieves the same goal, and provides some + /// additional debug information + /// + /// ### Example + /// ```no_run + /// assert!(2 * 2 == 4); + /// assert!(2 * 2 != 5); + /// debug_assert!(2 * 2 == 4); + /// debug_assert!(2 * 2 != 5); + /// ``` + /// Use instead: + /// ```no_run + /// assert_eq!(2 * 2, 4); + /// assert_ne!(2 * 2, 5); + /// debug_assert_eq!(2 * 2, 4); + /// debug_assert_ne!(2 * 2, 5); + /// ``` + #[clippy::version = "1.97.0"] + pub MANUAL_ASSERT_EQ, + pedantic, + "checks for assertions consisting of an (in)equality check" +} + +declare_lint_pass!(ManualAssertEq => [MANUAL_ASSERT_EQ]); + +#[derive(Clone, Copy, PartialEq, Eq)] +enum EqKind { + Eq, + Ne, +} + +impl EqKind { + fn postfix(self) -> &'static str { + match self { + Self::Eq => "_eq", + Self::Ne => "_ne", + } + } +} + +impl LateLintPass<'_> for ManualAssertEq { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if let Some(macro_call) = root_macro_call_first_node(cx, expr) + && let macro_name = match cx.tcx.get_diagnostic_name(macro_call.def_id) { + Some(sym::assert_macro) => "assert", + Some(sym::debug_assert_macro) => "debug_assert", + _ => return, + } + && !is_in_const_context(cx) + && let Some((cond, panic_expn)) = find_assert_args(cx, expr, macro_call.expn) + // Don't lint if the user has a painstakingly written assertion message + && !matches!(panic_expn, PanicCall::Display(_) | PanicCall::Format(_)) + && let ExprKind::Binary(op, lhs, rhs) = cond.kind + && let eq_kind = match op.node { + BinOpKind::Eq => EqKind::Eq, + BinOpKind::Ne => EqKind::Ne, + _ => return, + } + && !cond.span.from_expansion() + && let Some(debug_trait) = cx.tcx.get_diagnostic_item(sym::Debug) + && let lhs_ty = cx.typeck_results().expr_ty(lhs) + && let rhs_ty = cx.typeck_results().expr_ty(rhs) + // Can't print the values unless the types implement `Debug` + && implements_trait(cx, lhs_ty, debug_trait, &[]) + && implements_trait(cx, rhs_ty, debug_trait, &[]) + // Printing raw pointers isn't very useful + && !lhs_ty.is_raw_ptr() + && !rhs_ty.is_raw_ptr() + // The output of `(debug_)assert_eq` isn't very useful when one of the sides is a constant value + && if eq_kind == EqKind::Ne { + let ecx = ConstEvalCtxt::new(cx); + ecx.eval(lhs).is_none() && ecx.eval(rhs).is_none() + } else { + true + } + { + span_lint_and_then( + cx, + MANUAL_ASSERT_EQ, + macro_call.span, + format!("used `{macro_name}!` with an equality comparison"), + |diag| { + let postfix = eq_kind.postfix(); + let new_name = format_args!("{macro_name}{postfix}"); + let msg = format!("replace it with `{new_name}!(..)`"); + + let ctxt = cond.span.ctxt(); + if let Some(lhs_span) = walk_span_to_context(lhs.span, ctxt) + && let Some(rhs_span) = walk_span_to_context(rhs.span, ctxt) + { + let macro_name_span = cx.sess().source_map().span_until_char(macro_call.span, '!'); + let eq_span = cond.span.with_lo(lhs_span.hi()).with_hi(rhs_span.lo()); + let suggestions = vec![ + (macro_name_span.shrink_to_hi(), postfix.to_string()), + (eq_span, ", ".to_string()), + ]; + + diag.multipart_suggestion(msg, suggestions, Applicability::MachineApplicable); + } else { + diag.span_help(expr.span, msg); + } + }, + ); + } + } +} diff --git a/lintcheck/src/output.rs b/lintcheck/src/output.rs index 1ecc3f7c24943..dfba1804642eb 100644 --- a/lintcheck/src/output.rs +++ b/lintcheck/src/output.rs @@ -228,8 +228,8 @@ fn print_stats(old_stats: HashMap, new_stats: HashMap<&String, us // remove duplicates from both hashmaps for (k, v) in &same_in_both_hashmaps { - assert!(old_stats_deduped.remove(k) == Some(*v)); - assert!(new_stats_deduped.remove(k) == Some(*v)); + assert_eq!(old_stats_deduped.remove(k), Some(*v)); + assert_eq!(new_stats_deduped.remove(k), Some(*v)); } println!("\nStats:"); diff --git a/tests/ui/assertions_on_constants.rs b/tests/ui/assertions_on_constants.rs index 1c49b6e6b7b11..44bcc5c724e97 100644 --- a/tests/ui/assertions_on_constants.rs +++ b/tests/ui/assertions_on_constants.rs @@ -1,4 +1,5 @@ #![allow(non_fmt_panics, clippy::needless_bool, clippy::eq_op)] +#![expect(clippy::manual_assert_eq)] macro_rules! assert_const { ($len:expr) => { diff --git a/tests/ui/assertions_on_constants.stderr b/tests/ui/assertions_on_constants.stderr index a996c41b69420..abace40735edd 100644 --- a/tests/ui/assertions_on_constants.stderr +++ b/tests/ui/assertions_on_constants.stderr @@ -1,5 +1,5 @@ error: this assertion is always `true` - --> tests/ui/assertions_on_constants.rs:10:5 + --> tests/ui/assertions_on_constants.rs:11:5 | LL | assert!(true); | ^^^^^^^^^^^^^ @@ -9,7 +9,7 @@ LL | assert!(true); = help: to override `-D warnings` add `#[allow(clippy::assertions_on_constants)]` error: this assertion is always `false` - --> tests/ui/assertions_on_constants.rs:13:5 + --> tests/ui/assertions_on_constants.rs:14:5 | LL | assert!(false); | ^^^^^^^^^^^^^^ @@ -17,7 +17,7 @@ LL | assert!(false); = help: replace this with `panic!()` or `unreachable!()` error: this assertion is always `true` - --> tests/ui/assertions_on_constants.rs:16:5 + --> tests/ui/assertions_on_constants.rs:17:5 | LL | assert!(true, "true message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL | assert!(true, "true message"); = help: remove the assertion error: this assertion is always `false` - --> tests/ui/assertions_on_constants.rs:19:5 + --> tests/ui/assertions_on_constants.rs:20:5 | LL | assert!(false, "false message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -33,7 +33,7 @@ LL | assert!(false, "false message"); = help: replace this with `panic!()` or `unreachable!()` error: this assertion is always `false` - --> tests/ui/assertions_on_constants.rs:23:5 + --> tests/ui/assertions_on_constants.rs:24:5 | LL | assert!(false, "{}", msg.to_uppercase()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -41,7 +41,7 @@ LL | assert!(false, "{}", msg.to_uppercase()); = help: replace this with `panic!()` or `unreachable!()` error: this assertion has a constant value - --> tests/ui/assertions_on_constants.rs:27:5 + --> tests/ui/assertions_on_constants.rs:28:5 | LL | assert!(B); | ^^^^^^^^^^ @@ -49,7 +49,7 @@ LL | assert!(B); = help: consider moving this into a const block: `const { assert!(..) }` error: this assertion has a constant value - --> tests/ui/assertions_on_constants.rs:31:5 + --> tests/ui/assertions_on_constants.rs:32:5 | LL | assert!(C); | ^^^^^^^^^^ @@ -57,7 +57,7 @@ LL | assert!(C); = help: consider moving this into a const block: `const { assert!(..) }` error: this assertion has a constant value - --> tests/ui/assertions_on_constants.rs:34:5 + --> tests/ui/assertions_on_constants.rs:35:5 | LL | assert!(C, "C message"); | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -65,7 +65,7 @@ LL | assert!(C, "C message"); = help: consider moving this into a const block: `const { assert!(..) }` error: this assertion is always `true` - --> tests/ui/assertions_on_constants.rs:37:5 + --> tests/ui/assertions_on_constants.rs:38:5 | LL | debug_assert!(true); | ^^^^^^^^^^^^^^^^^^^ @@ -73,7 +73,7 @@ LL | debug_assert!(true); = help: remove the assertion error: this assertion has a constant value - --> tests/ui/assertions_on_constants.rs:45:5 + --> tests/ui/assertions_on_constants.rs:46:5 | LL | assert!(cfg!(feature = "hey") || cfg!(not(feature = "asdf"))); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -81,7 +81,7 @@ LL | assert!(cfg!(feature = "hey") || cfg!(not(feature = "asdf"))); = help: consider moving this into a const block: `const { assert!(..) }` error: this assertion is always `true` - --> tests/ui/assertions_on_constants.rs:54:19 + --> tests/ui/assertions_on_constants.rs:55:19 | LL | const _: () = assert!(true); | ^^^^^^^^^^^^^ @@ -89,7 +89,7 @@ LL | const _: () = assert!(true); = help: remove the assertion error: this assertion is always `true` - --> tests/ui/assertions_on_constants.rs:57:5 + --> tests/ui/assertions_on_constants.rs:58:5 | LL | assert!(8 == (7 + 1)); | ^^^^^^^^^^^^^^^^^^^^^ @@ -97,7 +97,7 @@ LL | assert!(8 == (7 + 1)); = help: remove the assertion error: this assertion is always `true` - --> tests/ui/assertions_on_constants.rs:68:5 + --> tests/ui/assertions_on_constants.rs:69:5 | LL | assert!(true); | ^^^^^^^^^^^^^ @@ -105,7 +105,7 @@ LL | assert!(true); = help: remove the assertion error: this assertion is always `true` - --> tests/ui/assertions_on_constants.rs:71:5 + --> tests/ui/assertions_on_constants.rs:72:5 | LL | assert!(8 == (7 + 1)); | ^^^^^^^^^^^^^^^^^^^^^ @@ -113,7 +113,7 @@ LL | assert!(8 == (7 + 1)); = help: remove the assertion error: this assertion has a constant value - --> tests/ui/assertions_on_constants.rs:79:5 + --> tests/ui/assertions_on_constants.rs:80:5 | LL | assert!(C); | ^^^^^^^^^^ @@ -121,7 +121,7 @@ LL | assert!(C); = help: consider moving this to an anonymous constant: `const _: () = { assert!(..); }` error: this assertion has a constant value - --> tests/ui/assertions_on_constants.rs:90:5 + --> tests/ui/assertions_on_constants.rs:91:5 | LL | assert!(C); | ^^^^^^^^^^ @@ -129,7 +129,7 @@ LL | assert!(C); = help: consider moving this into a const block: `const { assert!(..) }` error: this assertion has a constant value - --> tests/ui/assertions_on_constants.rs:96:5 + --> tests/ui/assertions_on_constants.rs:97:5 | LL | assert!(C); | ^^^^^^^^^^ diff --git a/tests/ui/cmp_null.fixed b/tests/ui/cmp_null.fixed index 4a0ee439e94aa..1b49549e9712c 100644 --- a/tests/ui/cmp_null.fixed +++ b/tests/ui/cmp_null.fixed @@ -1,4 +1,5 @@ #![warn(clippy::cmp_null)] +#![allow(clippy::manual_assert_eq)] use std::ptr; diff --git a/tests/ui/cmp_null.rs b/tests/ui/cmp_null.rs index 26ea8960e5fb6..35de3a14afc6f 100644 --- a/tests/ui/cmp_null.rs +++ b/tests/ui/cmp_null.rs @@ -1,4 +1,5 @@ #![warn(clippy::cmp_null)] +#![allow(clippy::manual_assert_eq)] use std::ptr; diff --git a/tests/ui/cmp_null.stderr b/tests/ui/cmp_null.stderr index 51b98d2a2320d..9831b4f6eb32b 100644 --- a/tests/ui/cmp_null.stderr +++ b/tests/ui/cmp_null.stderr @@ -1,5 +1,5 @@ error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:8:8 + --> tests/ui/cmp_null.rs:9:8 | LL | if p == ptr::null() { | ^^^^^^^^^^^^^^^^ help: try: `p.is_null()` @@ -8,37 +8,37 @@ LL | if p == ptr::null() { = help: to override `-D warnings` add `#[allow(clippy::cmp_null)]` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:13:8 + --> tests/ui/cmp_null.rs:14:8 | LL | if ptr::null() == p { | ^^^^^^^^^^^^^^^^ help: try: `p.is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:21:8 + --> tests/ui/cmp_null.rs:22:8 | LL | if m == ptr::null_mut() { | ^^^^^^^^^^^^^^^^^^^^ help: try: `m.is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:26:8 + --> tests/ui/cmp_null.rs:27:8 | LL | if ptr::null_mut() == m { | ^^^^^^^^^^^^^^^^^^^^ help: try: `m.is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:32:13 + --> tests/ui/cmp_null.rs:33:13 | LL | let _ = x as *const () == ptr::null(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(x as *const ()).is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:38:19 + --> tests/ui/cmp_null.rs:39:19 | LL | debug_assert!(f != std::ptr::null_mut()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `!f.is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:56:8 + --> tests/ui/cmp_null.rs:57:8 | LL | if dot_value!(x) == ptr::null() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `dot_value!(x).is_null()` diff --git a/tests/ui/infinite_loops.rs b/tests/ui/infinite_loops.rs index 88e3328d662ec..c0f0087b374c5 100644 --- a/tests/ui/infinite_loops.rs +++ b/tests/ui/infinite_loops.rs @@ -295,7 +295,7 @@ fn panic_like_macros_1() { } fn panic_like_macros_2() { - let mut x = 0; + let mut x: i32 = 0; loop { do_something(); @@ -310,7 +310,7 @@ fn panic_like_macros_2() { } loop { do_something(); - assert!(x % 2 == 0); + assert!(x.is_positive()); } loop { do_something(); diff --git a/tests/ui/manual_assert_eq.fixed b/tests/ui/manual_assert_eq.fixed new file mode 100644 index 0000000000000..175c33e271924 --- /dev/null +++ b/tests/ui/manual_assert_eq.fixed @@ -0,0 +1,114 @@ +//@aux-build:proc_macros.rs +#![warn(clippy::manual_assert_eq)] +#![allow(clippy::manual_ignore_case_cmp)] // only raised before the fix +#![expect(clippy::eq_op, clippy::assertions_on_constants)] + +fn main() { + let a = "a"; + assert_eq!(a, "a".to_ascii_lowercase()); + //~^ manual_assert_eq + assert_ne!(a, "a".to_ascii_uppercase()); + //~^ manual_assert_eq + debug_assert_eq!(a, "a".to_ascii_lowercase()); + //~^ manual_assert_eq + debug_assert_ne!(a, "a".to_ascii_uppercase()); + //~^ manual_assert_eq + + // macros + let v = vec![]; + assert_eq!(v, vec![1, 2, 3]); + //~^ manual_assert_eq + assert_eq!(vec![1, 2, 3], v); + //~^ manual_assert_eq + assert_eq!(vec![1], vec![1, 2, 3]); + //~^ manual_assert_eq + + // Don't lint: has assert message + assert!(a == "a".to_ascii_lowercase(), "{a}"); + assert!(a == "a".to_ascii_lowercase(), "a==a"); + assert!(a == "a".to_ascii_lowercase(), "{a}==a"); + assert!(a != "a".to_ascii_uppercase(), "a!=A"); + debug_assert!(a == "a".to_ascii_lowercase(), "a==a"); + debug_assert!(a != "a".to_ascii_uppercase(), "a!=A"); + + // Don't lint: `!=`, and at least one of the sides is a constant value + assert!(a != "A"); + assert!("A" != a); + assert!("A" != "A"); + + // Don't lint: comparison of ptrs + fn cmp_ptrs(a: *const u8, b: *const u8) { + assert!(a == b); + } + + // Don't lint: one of the sides isn't `Debug` + { + #[derive(PartialEq)] + struct NotDebug; + + #[derive(PartialEq)] + struct NotDebug2; + + impl PartialEq for NotDebug { + fn eq(&self, other: &NotDebug2) -> bool { + unimplemented!() + } + } + impl PartialEq for NotDebug2 { + fn eq(&self, other: &NotDebug) -> bool { + unimplemented!() + } + } + + #[derive(Debug)] + struct IsDebug; + + impl PartialEq for NotDebug { + fn eq(&self, other: &IsDebug) -> bool { + unimplemented!() + } + } + impl PartialEq for IsDebug { + fn eq(&self, other: &NotDebug) -> bool { + unimplemented!() + } + } + + let nd = NotDebug; + assert!(nd == nd); + + let nd2 = NotDebug2; + assert!(nd == nd2); + assert!(nd2 == nd); + + let id = IsDebug; + assert!(id == nd); + assert!(nd == id); + } + + // Don't lint: in const context + const { + assert!(5 == 2 + 3); + } + + // Don't lint: in external macro + { + // NOTE: this only works because `root_macro_call_first_node` returns `external!`, + // which then gets rejected by the macro name check + proc_macros::external!(assert!('a' == 'b')); + proc_macros::external!({ + let some_padding_before = 'a'; + assert!('a' == 'b'); + let some_padding_after = 'b'; + }); + + // .. which also means that the following is _technically_ a FN -- but surely no one would write + // code like this (diverging/unit expression as a child expression of a macro call) + vec![(), assert!('a' == 'b'), ()]; + } +} + +// Don't lint: in const context +const _: () = { + assert!(8 == (7 + 1)); +}; diff --git a/tests/ui/manual_assert_eq.rs b/tests/ui/manual_assert_eq.rs new file mode 100644 index 0000000000000..0df5518bd3545 --- /dev/null +++ b/tests/ui/manual_assert_eq.rs @@ -0,0 +1,114 @@ +//@aux-build:proc_macros.rs +#![warn(clippy::manual_assert_eq)] +#![allow(clippy::manual_ignore_case_cmp)] // only raised before the fix +#![expect(clippy::eq_op, clippy::assertions_on_constants)] + +fn main() { + let a = "a"; + assert!(a == "a".to_ascii_lowercase()); + //~^ manual_assert_eq + assert!(a != "a".to_ascii_uppercase()); + //~^ manual_assert_eq + debug_assert!(a == "a".to_ascii_lowercase()); + //~^ manual_assert_eq + debug_assert!(a != "a".to_ascii_uppercase()); + //~^ manual_assert_eq + + // macros + let v = vec![]; + assert!(v == vec![1, 2, 3]); + //~^ manual_assert_eq + assert!(vec![1, 2, 3] == v); + //~^ manual_assert_eq + assert!(vec![1] == vec![1, 2, 3]); + //~^ manual_assert_eq + + // Don't lint: has assert message + assert!(a == "a".to_ascii_lowercase(), "{a}"); + assert!(a == "a".to_ascii_lowercase(), "a==a"); + assert!(a == "a".to_ascii_lowercase(), "{a}==a"); + assert!(a != "a".to_ascii_uppercase(), "a!=A"); + debug_assert!(a == "a".to_ascii_lowercase(), "a==a"); + debug_assert!(a != "a".to_ascii_uppercase(), "a!=A"); + + // Don't lint: `!=`, and at least one of the sides is a constant value + assert!(a != "A"); + assert!("A" != a); + assert!("A" != "A"); + + // Don't lint: comparison of ptrs + fn cmp_ptrs(a: *const u8, b: *const u8) { + assert!(a == b); + } + + // Don't lint: one of the sides isn't `Debug` + { + #[derive(PartialEq)] + struct NotDebug; + + #[derive(PartialEq)] + struct NotDebug2; + + impl PartialEq for NotDebug { + fn eq(&self, other: &NotDebug2) -> bool { + unimplemented!() + } + } + impl PartialEq for NotDebug2 { + fn eq(&self, other: &NotDebug) -> bool { + unimplemented!() + } + } + + #[derive(Debug)] + struct IsDebug; + + impl PartialEq for NotDebug { + fn eq(&self, other: &IsDebug) -> bool { + unimplemented!() + } + } + impl PartialEq for IsDebug { + fn eq(&self, other: &NotDebug) -> bool { + unimplemented!() + } + } + + let nd = NotDebug; + assert!(nd == nd); + + let nd2 = NotDebug2; + assert!(nd == nd2); + assert!(nd2 == nd); + + let id = IsDebug; + assert!(id == nd); + assert!(nd == id); + } + + // Don't lint: in const context + const { + assert!(5 == 2 + 3); + } + + // Don't lint: in external macro + { + // NOTE: this only works because `root_macro_call_first_node` returns `external!`, + // which then gets rejected by the macro name check + proc_macros::external!(assert!('a' == 'b')); + proc_macros::external!({ + let some_padding_before = 'a'; + assert!('a' == 'b'); + let some_padding_after = 'b'; + }); + + // .. which also means that the following is _technically_ a FN -- but surely no one would write + // code like this (diverging/unit expression as a child expression of a macro call) + vec![(), assert!('a' == 'b'), ()]; + } +} + +// Don't lint: in const context +const _: () = { + assert!(8 == (7 + 1)); +}; diff --git a/tests/ui/manual_assert_eq.stderr b/tests/ui/manual_assert_eq.stderr new file mode 100644 index 0000000000000..0694df01a8f81 --- /dev/null +++ b/tests/ui/manual_assert_eq.stderr @@ -0,0 +1,88 @@ +error: used `assert!` with an equality comparison + --> tests/ui/manual_assert_eq.rs:8:5 + | +LL | assert!(a == "a".to_ascii_lowercase()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::manual-assert-eq` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_assert_eq)]` +help: replace it with `assert_eq!(..)` + | +LL - assert!(a == "a".to_ascii_lowercase()); +LL + assert_eq!(a, "a".to_ascii_lowercase()); + | + +error: used `assert!` with an equality comparison + --> tests/ui/manual_assert_eq.rs:10:5 + | +LL | assert!(a != "a".to_ascii_uppercase()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert_ne!(..)` + | +LL - assert!(a != "a".to_ascii_uppercase()); +LL + assert_ne!(a, "a".to_ascii_uppercase()); + | + +error: used `debug_assert!` with an equality comparison + --> tests/ui/manual_assert_eq.rs:12:5 + | +LL | debug_assert!(a == "a".to_ascii_lowercase()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert_eq!(..)` + | +LL - debug_assert!(a == "a".to_ascii_lowercase()); +LL + debug_assert_eq!(a, "a".to_ascii_lowercase()); + | + +error: used `debug_assert!` with an equality comparison + --> tests/ui/manual_assert_eq.rs:14:5 + | +LL | debug_assert!(a != "a".to_ascii_uppercase()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert_ne!(..)` + | +LL - debug_assert!(a != "a".to_ascii_uppercase()); +LL + debug_assert_ne!(a, "a".to_ascii_uppercase()); + | + +error: used `assert!` with an equality comparison + --> tests/ui/manual_assert_eq.rs:19:5 + | +LL | assert!(v == vec![1, 2, 3]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert_eq!(..)` + | +LL - assert!(v == vec![1, 2, 3]); +LL + assert_eq!(v, vec![1, 2, 3]); + | + +error: used `assert!` with an equality comparison + --> tests/ui/manual_assert_eq.rs:21:5 + | +LL | assert!(vec![1, 2, 3] == v); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert_eq!(..)` + | +LL - assert!(vec![1, 2, 3] == v); +LL + assert_eq!(vec![1, 2, 3], v); + | + +error: used `assert!` with an equality comparison + --> tests/ui/manual_assert_eq.rs:23:5 + | +LL | assert!(vec![1] == vec![1, 2, 3]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert_eq!(..)` + | +LL - assert!(vec![1] == vec![1, 2, 3]); +LL + assert_eq!(vec![1], vec![1, 2, 3]); + | + +error: aborting due to 7 previous errors + diff --git a/tests/ui/missing_asserts_for_indexing.fixed b/tests/ui/missing_asserts_for_indexing.fixed index 50bc576dd1e23..f877cbc1f1145 100644 --- a/tests/ui/missing_asserts_for_indexing.fixed +++ b/tests/ui/missing_asserts_for_indexing.fixed @@ -1,4 +1,4 @@ -#![allow(unused)] +#![expect(clippy::manual_assert_eq)] #![warn(clippy::missing_asserts_for_indexing)] // ok diff --git a/tests/ui/missing_asserts_for_indexing.rs b/tests/ui/missing_asserts_for_indexing.rs index 9e219a2af0732..8084e0b71be98 100644 --- a/tests/ui/missing_asserts_for_indexing.rs +++ b/tests/ui/missing_asserts_for_indexing.rs @@ -1,4 +1,4 @@ -#![allow(unused)] +#![expect(clippy::manual_assert_eq)] #![warn(clippy::missing_asserts_for_indexing)] // ok diff --git a/tests/ui/panic_in_result_fn_assertions.rs b/tests/ui/panic_in_result_fn_assertions.rs index 4e70282415794..17b221044b1f2 100644 --- a/tests/ui/panic_in_result_fn_assertions.rs +++ b/tests/ui/panic_in_result_fn_assertions.rs @@ -7,7 +7,7 @@ impl A { fn result_with_assert_with_message(x: i32) -> Result // should emit lint //~^ panic_in_result_fn { - assert!(x == 5, "wrong argument"); + assert!(x.is_positive(), "wrong argument"); Ok(true) } @@ -27,7 +27,7 @@ impl A { fn other_with_assert_with_message(x: i32) // should not emit lint { - assert!(x == 5, "wrong argument"); + assert!(x.is_positive(), "wrong argument"); } fn other_with_assert_eq(x: i32) // should not emit lint diff --git a/tests/ui/panic_in_result_fn_assertions.stderr b/tests/ui/panic_in_result_fn_assertions.stderr index cdb7762510d9c..db881da06ba83 100644 --- a/tests/ui/panic_in_result_fn_assertions.stderr +++ b/tests/ui/panic_in_result_fn_assertions.stderr @@ -4,7 +4,7 @@ error: used `panic!()` or assertion in a function that returns `Result` LL | / fn result_with_assert_with_message(x: i32) -> Result // should emit lint LL | | LL | | { -LL | | assert!(x == 5, "wrong argument"); +LL | | assert!(x.is_positive(), "wrong argument"); LL | | Ok(true) LL | | } | |_____^ @@ -13,8 +13,8 @@ LL | | } note: return Err() instead of panicking --> tests/ui/panic_in_result_fn_assertions.rs:10:9 | -LL | assert!(x == 5, "wrong argument"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | assert!(x.is_positive(), "wrong argument"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: `-D clippy::panic-in-result-fn` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::panic_in_result_fn)]` diff --git a/tests/ui/panic_in_result_fn_debug_assertions.rs b/tests/ui/panic_in_result_fn_debug_assertions.rs index c4549c6b84123..9cce339f4d60d 100644 --- a/tests/ui/panic_in_result_fn_debug_assertions.rs +++ b/tests/ui/panic_in_result_fn_debug_assertions.rs @@ -9,7 +9,7 @@ struct A; impl A { fn result_with_debug_assert_with_message(x: i32) -> Result { - debug_assert!(x == 5, "wrong argument"); + debug_assert!(x.is_positive(), "wrong argument"); Ok(true) } @@ -24,7 +24,7 @@ impl A { } fn other_with_debug_assert_with_message(x: i32) { - debug_assert!(x == 5, "wrong argument"); + debug_assert!(x.is_positive(), "wrong argument"); } fn other_with_debug_assert_eq(x: i32) { diff --git a/tests/ui/uninit_vec.rs b/tests/ui/uninit_vec.rs index eeb281322da92..a0bac28e87292 100644 --- a/tests/ui/uninit_vec.rs +++ b/tests/ui/uninit_vec.rs @@ -87,7 +87,7 @@ fn main() { unsafe { // test the case where there are other statements in the following unsafe block vec.set_len(200); - assert!(vec.len() == 200); + assert_eq!(vec.len(), 200); } // handle vec stored in the field of a struct diff --git a/tests/ui/unnecessary_map_or.fixed b/tests/ui/unnecessary_map_or.fixed index 52c1143392925..b1f991b9b26c0 100644 --- a/tests/ui/unnecessary_map_or.fixed +++ b/tests/ui/unnecessary_map_or.fixed @@ -1,9 +1,12 @@ //@aux-build:proc_macros.rs #![warn(clippy::unnecessary_map_or)] -#![allow(clippy::no_effect)] -#![allow(clippy::eq_op)] -#![allow(clippy::unnecessary_lazy_evaluations)] -#![allow(clippy::nonminimal_bool)] +#![allow( + clippy::no_effect, + clippy::eq_op, + clippy::unnecessary_lazy_evaluations, + clippy::nonminimal_bool, + clippy::manual_assert_eq +)] #[clippy::msrv = "1.70.0"] #[macro_use] extern crate proc_macros; diff --git a/tests/ui/unnecessary_map_or.rs b/tests/ui/unnecessary_map_or.rs index dd2e1a569469c..edd5ea9d878fe 100644 --- a/tests/ui/unnecessary_map_or.rs +++ b/tests/ui/unnecessary_map_or.rs @@ -1,9 +1,12 @@ //@aux-build:proc_macros.rs #![warn(clippy::unnecessary_map_or)] -#![allow(clippy::no_effect)] -#![allow(clippy::eq_op)] -#![allow(clippy::unnecessary_lazy_evaluations)] -#![allow(clippy::nonminimal_bool)] +#![allow( + clippy::no_effect, + clippy::eq_op, + clippy::unnecessary_lazy_evaluations, + clippy::nonminimal_bool, + clippy::manual_assert_eq +)] #[clippy::msrv = "1.70.0"] #[macro_use] extern crate proc_macros; diff --git a/tests/ui/unnecessary_map_or.stderr b/tests/ui/unnecessary_map_or.stderr index d11e7179f9216..12a973c84213d 100644 --- a/tests/ui/unnecessary_map_or.stderr +++ b/tests/ui/unnecessary_map_or.stderr @@ -1,5 +1,5 @@ error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:13:13 + --> tests/ui/unnecessary_map_or.rs:16:13 | LL | let _ = Some(5).map_or(false, |n| n == 5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL + let _ = Some(5) == Some(5); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:15:13 + --> tests/ui/unnecessary_map_or.rs:18:13 | LL | let _ = Some(5).map_or(true, |n| n != 5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL + let _ = Some(5) != Some(5); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:17:13 + --> tests/ui/unnecessary_map_or.rs:20:13 | LL | let _ = Some(5).map_or(false, |n| { | _____________^ @@ -46,7 +46,7 @@ LL + let _ = Some(5) == Some(5); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:22:13 + --> tests/ui/unnecessary_map_or.rs:25:13 | LL | let _ = Some(5).map_or(false, |n| { | _____________^ @@ -63,7 +63,7 @@ LL + let _ = Some(5).is_some_and(|n| { | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:27:13 + --> tests/ui/unnecessary_map_or.rs:30:13 | LL | let _ = Some(vec![5]).map_or(false, |n| n == [5]); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -75,7 +75,7 @@ LL + let _ = Some(vec![5]).is_some_and(|n| n == [5]); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:29:13 + --> tests/ui/unnecessary_map_or.rs:32:13 | LL | let _ = Some(vec![1]).map_or(false, |n| vec![2] == n); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -87,7 +87,7 @@ LL + let _ = Some(vec![1]).is_some_and(|n| vec![2] == n); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:31:13 + --> tests/ui/unnecessary_map_or.rs:34:13 | LL | let _ = Some(5).map_or(false, |n| n == n); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -99,7 +99,7 @@ LL + let _ = Some(5).is_some_and(|n| n == n); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:33:13 + --> tests/ui/unnecessary_map_or.rs:36:13 | LL | let _ = Some(5).map_or(false, |n| n == if 2 > 1 { n } else { 0 }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -111,7 +111,7 @@ LL + let _ = Some(5).is_some_and(|n| n == if 2 > 1 { n } else { 0 }); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:35:13 + --> tests/ui/unnecessary_map_or.rs:38:13 | LL | let _ = Ok::, i32>(vec![5]).map_or(false, |n| n == [5]); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -123,7 +123,7 @@ LL + let _ = Ok::, i32>(vec![5]).is_ok_and(|n| n == [5]); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:37:13 + --> tests/ui/unnecessary_map_or.rs:40:13 | LL | let _ = Ok::(5).map_or(false, |n| n == 5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -135,7 +135,7 @@ LL + let _ = Ok::(5) == Ok(5); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:39:13 + --> tests/ui/unnecessary_map_or.rs:42:13 | LL | let _ = Some(5).map_or(false, |n| n == 5).then(|| 1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -147,7 +147,7 @@ LL + let _ = (Some(5) == Some(5)).then(|| 1); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:41:13 + --> tests/ui/unnecessary_map_or.rs:44:13 | LL | let _ = Some(5).map_or(true, |n| n == 5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -159,7 +159,7 @@ LL + let _ = Some(5).is_none_or(|n| n == 5); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:43:13 + --> tests/ui/unnecessary_map_or.rs:46:13 | LL | let _ = Some(5).map_or(true, |n| 5 == n); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -171,7 +171,7 @@ LL + let _ = Some(5).is_none_or(|n| 5 == n); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:45:14 + --> tests/ui/unnecessary_map_or.rs:48:14 | LL | let _ = !Some(5).map_or(false, |n| n == 5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -183,7 +183,7 @@ LL + let _ = !(Some(5) == Some(5)); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:47:13 + --> tests/ui/unnecessary_map_or.rs:50:13 | LL | let _ = Some(5).map_or(false, |n| n == 5) || false; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -195,7 +195,7 @@ LL + let _ = (Some(5) == Some(5)) || false; | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:49:13 + --> tests/ui/unnecessary_map_or.rs:52:13 | LL | let _ = Some(5).map_or(false, |n| n == 5) as usize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -207,7 +207,7 @@ LL + let _ = (Some(5) == Some(5)) as usize; | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:74:13 + --> tests/ui/unnecessary_map_or.rs:77:13 | LL | let _ = r.map_or(false, |x| x == 7); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -219,7 +219,7 @@ LL + let _ = r.is_ok_and(|x| x == 7); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:80:13 + --> tests/ui/unnecessary_map_or.rs:83:13 | LL | let _ = r.map_or(false, func); | ^^^^^^^^^^^^^^^^^^^^^ @@ -231,7 +231,7 @@ LL + let _ = r.is_ok_and(func); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:82:13 + --> tests/ui/unnecessary_map_or.rs:85:13 | LL | let _ = Some(5).map_or(false, func); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -243,7 +243,7 @@ LL + let _ = Some(5).is_some_and(func); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:84:13 + --> tests/ui/unnecessary_map_or.rs:87:13 | LL | let _ = Some(5).map_or(true, func); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -255,7 +255,7 @@ LL + let _ = Some(5).is_none_or(func); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:90:13 + --> tests/ui/unnecessary_map_or.rs:93:13 | LL | let _ = r.map_or(false, |x| x == 8); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -267,7 +267,7 @@ LL + let _ = r == Ok(8); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:111:5 + --> tests/ui/unnecessary_map_or.rs:114:5 | LL | o.map_or(true, |n| n > 5) || (o as &Option).map_or(true, |n| n < 5) | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -279,7 +279,7 @@ LL + o.is_none_or(|n| n > 5) || (o as &Option).map_or(true, |n| n < 5) | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:111:34 + --> tests/ui/unnecessary_map_or.rs:114:34 | LL | o.map_or(true, |n| n > 5) || (o as &Option).map_or(true, |n| n < 5) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -291,7 +291,7 @@ LL + o.map_or(true, |n| n > 5) || (o as &Option).is_none_or(|n| n < 5) | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:126:5 + --> tests/ui/unnecessary_map_or.rs:129:5 | LL | o.map_or(true, |n| n > 5) | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -303,7 +303,7 @@ LL + o.is_none_or(|n| n > 5) | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:131:13 + --> tests/ui/unnecessary_map_or.rs:134:13 | LL | let x = a.map_or(false, |a| a == *s); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -315,7 +315,7 @@ LL + let x = a.is_some_and(|a| a == *s); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:133:13 + --> tests/ui/unnecessary_map_or.rs:136:13 | LL | let y = b.map_or(true, |b| b == *s); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -327,7 +327,7 @@ LL + let y = b.is_none_or(|b| b == *s); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:139:13 + --> tests/ui/unnecessary_map_or.rs:142:13 | LL | assert!(Some("test").map_or(false, |x| x == "test")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -339,7 +339,7 @@ LL + assert!(Some("test") == Some("test")); | error: this `map_or` can be simplified - --> tests/ui/unnecessary_map_or.rs:143:13 + --> tests/ui/unnecessary_map_or.rs:146:13 | LL | assert!(Some("test").map_or(false, |x| x == "test").then(|| 1).is_some()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -351,7 +351,7 @@ 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 + --> tests/ui/unnecessary_map_or.rs:163:9 | LL | _ = s.lock().unwrap().map_or(false, |s| s == "foo"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -363,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:164:9 + --> tests/ui/unnecessary_map_or.rs:167:9 | LL | _ = s.map_or(false, |s| s == "foo"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 47bdbaa181f0d58cbdaa2dff91afe42d337167e3 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Sun, 19 Apr 2026 16:32:41 -0700 Subject: [PATCH 23/38] `useless_conversion`: do not lint `(a..b).into_iter()`. In a future edition of Rust or with the unstable `feature(new_range)`, the syntax `a..b` will change from producing type `core::ops::Range`, which implements `Iterator`, to producing type `core::range::Range`, which implements `IntoIterator`. Therefore, an `.into_iter()` call that is technically useless today will be useful for edition migration or unstable feature testing; do not remove it. Also: * Fix issue number for existing tests * Fix docs reference to `IntoIter` (not the name of the trait) * Rename some variables to be clearer --- clippy_lints/src/useless_conversion.rs | 36 ++++++++++++++++---- tests/ui/useless_conversion.fixed | 37 +++++++++++++++++---- tests/ui/useless_conversion.rs | 37 +++++++++++++++++---- tests/ui/useless_conversion.stderr | 46 ++++++++++++++++++-------- 4 files changed, 123 insertions(+), 33 deletions(-) diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index 662da5929adb7..5305185e3a7c8 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -6,7 +6,7 @@ use clippy_utils::ty::{is_copy, same_type_modulo_regions}; use clippy_utils::{get_parent_expr, is_ty_alias, sym}; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; -use rustc_hir::{BindingMode, Expr, ExprKind, HirId, MatchSource, Mutability, Node, PatKind}; +use rustc_hir::{BindingMode, Expr, ExprKind, HirId, LangItem, MatchSource, Mutability, Node, PatKind}; use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::traits::Obligation; use rustc_lint::{LateContext, LateLintPass}; @@ -19,7 +19,7 @@ use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; declare_clippy_lint! { /// ### What it does - /// Checks for `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIter` calls + /// Checks for `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIterator` calls /// which uselessly convert to the same type. /// /// ### Why is this bad? @@ -38,7 +38,7 @@ declare_clippy_lint! { #[clippy::version = "1.45.0"] pub USELESS_CONVERSION, complexity, - "calls to `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIter` which perform useless conversions to the same type" + "calls to `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIterator` which perform useless conversions to the same type" } impl_lint_pass!(UselessConversion => [USELESS_CONVERSION]); @@ -322,13 +322,13 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { return; } - let a = cx.typeck_results().expr_ty(e); - let b = cx.typeck_results().expr_ty(recv); + let iter_ty = cx.typeck_results().expr_ty(e); + let into_iter_ty = cx.typeck_results().expr_ty(recv); // If the types are identical then .into_iter() can be removed, unless the type // implements Copy, in which case .into_iter() returns a copy of the receiver and // cannot be safely omitted. - if same_type_modulo_regions(a, b) && !is_copy(cx, b) { + if same_type_modulo_regions(iter_ty, into_iter_ty) && !is_copy(cx, into_iter_ty) { // Below we check if the parent method call meets the following conditions: // 1. First parameter is `&mut self` (requires mutable reference) // 2. Second parameter implements the `FnMut` trait (e.g., Iterator::any) @@ -356,6 +356,28 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { return; } + // In a future edition of Rust (edition 2027, hopefully), or with the unstable + // `feature(new_range)`, the syntax `a..b` will change from producing type `core::ops::Range`, + // which implements `Iterator`, to producing type `core::range::Range`, which implements + // `IntoIterator` only. + // + // Therefore, an `(a..b).into_iter()` call that is technically useless today will be useful for + // edition migration or unstable feature testing; do not remove it. + // + // In the future, after most code has either migrated to the new range types or declined to + // do so, this special case will be much less useful and could be removed. + if let Some(parent) = get_parent_expr(cx, e) + // Is a method call, not, say, a for loop where the conversion *is* useless. + && let ExprKind::MethodCall(_, _, _, _) = parent.kind + // These lang items are the 3 core::ops range types that implement Iterator. + // All other range types do not implement Iterator, so this lint does not apply to them. + && (into_iter_ty.is_lang_item(cx, LangItem::Range) + || into_iter_ty.is_lang_item(cx, LangItem::RangeFrom) + || into_iter_ty.is_lang_item(cx, LangItem::RangeInclusiveStruct)) + { + return; + } + let mut applicability = Applicability::MachineApplicable; let sugg = snippet_with_context(cx, recv.span, e.span.ctxt(), "", &mut applicability) .0 @@ -364,7 +386,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { cx, USELESS_CONVERSION, e.span, - format!("useless conversion to the same type: `{b}`"), + format!("useless conversion to the same type: `{into_iter_ty}`"), "consider removing `.into_iter()`", sugg, applicability, diff --git a/tests/ui/useless_conversion.fixed b/tests/ui/useless_conversion.fixed index d0297ef6bcdc0..a22df7013f988 100644 --- a/tests/ui/useless_conversion.fixed +++ b/tests/ui/useless_conversion.fixed @@ -72,13 +72,13 @@ fn lint_into_iter_on_expr_implementing_iterator_2() { #[allow(const_item_mutation)] fn lint_into_iter_on_const_implementing_iterator() { - const NUMBERS: std::ops::Range = 0..10; + const NUMBERS: std::iter::Empty = std::iter::empty(); let _ = NUMBERS.next(); //~^ useless_conversion } fn lint_into_iter_on_const_implementing_iterator_2() { - const NUMBERS: std::ops::Range = 0..10; + const NUMBERS: std::iter::Empty = std::iter::empty(); let mut n = NUMBERS; //~^ useless_conversion n.next(); @@ -423,10 +423,8 @@ mod issue11819 { } } -fn issue14739() { - use std::ops::Range; - - const R: Range = 2..7; +fn issue14800() { + const R: std::iter::Empty = std::iter::empty(); R.into_iter().all(|_x| true); // no lint @@ -438,6 +436,33 @@ fn issue14739() { //~^ useless_conversion } +// In a future edition of Rust or with the unstable `feature(new_range)`, the syntax `a..b` +// will change from producing type `core::ops::Range`, which implements `Iterator`, to +// producing type `core::range::Range`, which implements `IntoIterator`. +// +// Therefore, an `.into_iter()` call that is technically useless today will be useful for +// edition migration or unstable feature testing; do not remove it. +// +// This test case tests that the ranges produced *by range syntax* aren’t linted on, which +// should be true both before and after the expected 2027 edition migration (but after such +// migration, this test will not really be testing anything). +fn do_not_lint_on_ops_range_into_iter_before_method() { + #![allow(clippy::never_loop)] + + // No lint on these + (0..10).into_iter().for_each(drop); + (0..=10).into_iter().for_each(drop); + (0..).into_iter().take(10).for_each(drop); + + // But do still lint on for loops + for _ in (0..10) {} //~ useless_conversion + for _ in (0..=10) {} //~ useless_conversion + for _ in (0..) { + //~^ useless_conversion + break; + } +} + fn issue16165() { macro_rules! mac { (iter $e:expr) => { diff --git a/tests/ui/useless_conversion.rs b/tests/ui/useless_conversion.rs index 20a0f6d72f9a8..1f170cf87ac58 100644 --- a/tests/ui/useless_conversion.rs +++ b/tests/ui/useless_conversion.rs @@ -72,13 +72,13 @@ fn lint_into_iter_on_expr_implementing_iterator_2() { #[allow(const_item_mutation)] fn lint_into_iter_on_const_implementing_iterator() { - const NUMBERS: std::ops::Range = 0..10; + const NUMBERS: std::iter::Empty = std::iter::empty(); let _ = NUMBERS.into_iter().next(); //~^ useless_conversion } fn lint_into_iter_on_const_implementing_iterator_2() { - const NUMBERS: std::ops::Range = 0..10; + const NUMBERS: std::iter::Empty = std::iter::empty(); let mut n = NUMBERS.into_iter(); //~^ useless_conversion n.next(); @@ -423,10 +423,8 @@ mod issue11819 { } } -fn issue14739() { - use std::ops::Range; - - const R: Range = 2..7; +fn issue14800() { + const R: std::iter::Empty = std::iter::empty(); R.into_iter().all(|_x| true); // no lint @@ -438,6 +436,33 @@ fn issue14739() { //~^ useless_conversion } +// In a future edition of Rust or with the unstable `feature(new_range)`, the syntax `a..b` +// will change from producing type `core::ops::Range`, which implements `Iterator`, to +// producing type `core::range::Range`, which implements `IntoIterator`. +// +// Therefore, an `.into_iter()` call that is technically useless today will be useful for +// edition migration or unstable feature testing; do not remove it. +// +// This test case tests that the ranges produced *by range syntax* aren’t linted on, which +// should be true both before and after the expected 2027 edition migration (but after such +// migration, this test will not really be testing anything). +fn do_not_lint_on_ops_range_into_iter_before_method() { + #![allow(clippy::never_loop)] + + // No lint on these + (0..10).into_iter().for_each(drop); + (0..=10).into_iter().for_each(drop); + (0..).into_iter().take(10).for_each(drop); + + // But do still lint on for loops + for _ in (0..10).into_iter() {} //~ useless_conversion + for _ in (0..=10).into_iter() {} //~ useless_conversion + for _ in (0..).into_iter() { + //~^ useless_conversion + break; + } +} + fn issue16165() { macro_rules! mac { (iter $e:expr) => { diff --git a/tests/ui/useless_conversion.stderr b/tests/ui/useless_conversion.stderr index 18d5c9d28c0dc..7042e18a2cad8 100644 --- a/tests/ui/useless_conversion.stderr +++ b/tests/ui/useless_conversion.stderr @@ -40,13 +40,13 @@ error: useless conversion to the same type: `std::str::Lines<'_>` LL | if Some("ok") == text.lines().into_iter().next() {} | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `text.lines()` -error: useless conversion to the same type: `std::ops::Range` +error: useless conversion to the same type: `std::iter::Empty` --> tests/ui/useless_conversion.rs:76:13 | LL | let _ = NUMBERS.into_iter().next(); | ^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `NUMBERS` -error: useless conversion to the same type: `std::ops::Range` +error: useless conversion to the same type: `std::iter::Empty` --> tests/ui/useless_conversion.rs:82:17 | LL | let mut n = NUMBERS.into_iter(); @@ -421,32 +421,50 @@ LL - takes_into_iter(self.my_field.into_iter()); LL + takes_into_iter(&mut *self.my_field); | -error: useless conversion to the same type: `std::ops::Range` - --> tests/ui/useless_conversion.rs:435:5 +error: useless conversion to the same type: `std::iter::Empty` + --> tests/ui/useless_conversion.rs:433:5 | LL | R.into_iter().for_each(|_x| {}); | ^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `R` -error: useless conversion to the same type: `std::ops::Range` - --> tests/ui/useless_conversion.rs:437:13 +error: useless conversion to the same type: `std::iter::Empty` + --> tests/ui/useless_conversion.rs:435:13 | LL | let _ = R.into_iter().map(|_x| 0); | ^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `R` +error: useless conversion to the same type: `std::ops::Range` + --> tests/ui/useless_conversion.rs:458:14 + | +LL | for _ in (0..10).into_iter() {} + | ^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `(0..10)` + +error: useless conversion to the same type: `std::ops::RangeInclusive` + --> tests/ui/useless_conversion.rs:459:14 + | +LL | for _ in (0..=10).into_iter() {} + | ^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `(0..=10)` + +error: useless conversion to the same type: `std::ops::RangeFrom` + --> tests/ui/useless_conversion.rs:460:14 + | +LL | for _ in (0..).into_iter() { + | ^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `(0..)` + error: useless conversion to the same type: `std::slice::Iter<'_, i32>` - --> tests/ui/useless_conversion.rs:448:14 + --> tests/ui/useless_conversion.rs:473:14 | LL | for _ in mac!(iter [1, 2]).into_iter() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `mac!(iter [1, 2])` error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:461:27 + --> tests/ui/useless_conversion.rs:486:27 | LL | takes_into_iter_usize(b.into_iter()); | ^^^^^^^^^^^^^ | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:452:34 + --> tests/ui/useless_conversion.rs:477:34 | LL | fn takes_into_iter_usize(_: impl IntoIterator) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -457,13 +475,13 @@ LL + takes_into_iter_usize(b); | error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:470:31 + --> tests/ui/useless_conversion.rs:495:31 | LL | takes_into_iter_usize(b.clone().into_iter()); | ^^^^^^^^^^^^^^^^^^^^^ | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:452:34 + --> tests/ui/useless_conversion.rs:477:34 | LL | fn takes_into_iter_usize(_: impl IntoIterator) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -474,13 +492,13 @@ LL + takes_into_iter_usize(b.clone()); | error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:478:34 + --> tests/ui/useless_conversion.rs:503:34 | LL | takes_into_iter_usize_result(b.clone().into_iter())?; | ^^^^^^^^^^^^^^^^^^^^^ | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:453:41 + --> tests/ui/useless_conversion.rs:478:41 | LL | fn takes_into_iter_usize_result(_: impl IntoIterator) -> Result<(), ()> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -490,5 +508,5 @@ LL - takes_into_iter_usize_result(b.clone().into_iter())?; LL + takes_into_iter_usize_result(b.clone())?; | -error: aborting due to 48 previous errors +error: aborting due to 51 previous errors From f1f5580093d67baa449abe2e3f7638513f73d4b4 Mon Sep 17 00:00:00 2001 From: Linshu Yang Date: Fri, 2 Jan 2026 03:32:45 +0000 Subject: [PATCH 24/38] fix: `cloned_ref_to_slice_refs` FN on `to_owned()` --- clippy_lints/src/cloned_ref_to_slice_refs.rs | 120 +++++++++++++++++-- clippy_lints/src/methods/mod.rs | 4 +- tests/ui/cloned_ref_to_slice_refs.fixed | 80 ++++++++++++- tests/ui/cloned_ref_to_slice_refs.rs | 80 ++++++++++++- tests/ui/cloned_ref_to_slice_refs.stderr | 62 ++++++++-- 5 files changed, 319 insertions(+), 27 deletions(-) diff --git a/clippy_lints/src/cloned_ref_to_slice_refs.rs b/clippy_lints/src/cloned_ref_to_slice_refs.rs index c5eabe4c2b88d..4f663f4aa9098 100644 --- a/clippy_lints/src/cloned_ref_to_slice_refs.rs +++ b/clippy_lints/src/cloned_ref_to_slice_refs.rs @@ -1,15 +1,22 @@ +use std::ops::ControlFlow; + use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::sugg::Sugg; use clippy_utils::visitors::is_const_evaluatable; -use clippy_utils::{is_in_const_context, is_mutable}; +use clippy_utils::{is_in_const_context, is_mutable, sym}; +use rustc_ast::Mutability; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::{Expr, ExprKind, HirId, LangItem}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_middle::ty::adjustment::{Adjust, DerefAdjustKind, OverloadedDeref}; use rustc_session::impl_lint_pass; -use rustc_span::sym; +use rustc_span::Symbol; + +use crate::methods::is_clone_like; declare_clippy_lint! { /// ### What it does @@ -73,29 +80,116 @@ impl<'tcx> LateLintPass<'tcx> for ClonedRefToSliceRefs<'_> { && let ExprKind::Array([item]) = &arr.kind // check for clones - && let ExprKind::MethodCall(_, val, _, _) = item.kind - && cx.ty_based_def(item).opt_parent(cx).is_diag_item(cx, sym::Clone) + && let ExprKind::MethodCall(path, recv, _, _) = item.kind + && let Some(adjustment) = is_needless_clone_or_equivalent(cx, recv, path.ident.name, item.hir_id) // check for immutability or purity - && (!is_mutable(cx, val) || is_const_evaluatable(cx, val)) + && (!is_mutable(cx, recv) || is_const_evaluatable(cx, recv)) // get appropriate crate for `slice::from_ref` && let Some(builtin_crate) = clippy_utils::std_or_core(cx) { - let mut sugg = Sugg::hir(cx, val, "_"); - if !cx.typeck_results().expr_ty(val).is_ref() { - sugg = sugg.addr(); - } + let mut applicability = Applicability::MachineApplicable; + let sugg = Sugg::hir_with_context(cx, recv, expr.span.ctxt(), "_", &mut applicability); span_lint_and_sugg( cx, CLONED_REF_TO_SLICE_REFS, expr.span, - format!("this call to `clone` can be replaced with `{builtin_crate}::slice::from_ref`"), + format!( + "unnecessary use of `{}` to create a slice from a reference", + path.ident.name + ), "try", - format!("{builtin_crate}::slice::from_ref({sugg})"), - Applicability::MaybeIncorrect, + format!("{builtin_crate}::slice::from_ref({adjustment}{sugg})"), + applicability, ); } } } + +/// Checks if a method call is a needless clone or equivalent. If so, returns the necessary +/// adjustments to use the method receiver directly without cloning. +/// For example, in the code below: +/// ```rust,no_run +/// use std::path::PathBuf; +/// +/// let w = &PathBuf::new(); +/// let b = &[w.to_path_buf()]; +/// ``` +/// We would replace `&[w.to_path_buf()]` with `std::slice::from_ref(&*w)`, +/// hence we return `Some("&*")` as the adjustment. +fn is_needless_clone_or_equivalent<'tcx>( + cx: &LateContext<'tcx>, + method_recv: &'tcx Expr<'tcx>, + method_name: Symbol, + hir_id: HirId, +) -> Option { + let method_def = cx.ty_based_def(hir_id).opt_parent(cx)?; + if !method_def.is_lang_item(cx, LangItem::Clone) && !is_clone_like(cx, method_name, method_def) { + return None; + } + + let method_ret_ty = cx.typeck_results().node_type(hir_id); + let method_recv_ty = cx.typeck_results().expr_ty_adjusted(method_recv); + let ty::Ref(_, method_recv_ty_inner, Mutability::Not) = method_recv_ty.kind() else { + return None; + }; + + let method_recv_adjustments = cx.typeck_results().expr_adjustments(method_recv); + + // The return type of the clone-like method should be the same as the inner type of the reference + // being cloned, except for the following special cases: + // 1. `OsString`, which is first dereferenced to `OsStr` and the borrowed as `&OsStr`. + // 2. `PathBuf`, which is first dereferenced to `Path` and then borrowed as `&Path`. + let adjust_target_ty = if method_ret_ty == *method_recv_ty_inner { + method_ret_ty + } else if let Some(after_special_case_ty_name @ (sym::OsStr | sym::Path)) = method_recv_ty_inner.opt_diag_name(cx) + // Looking for the `OSString -> OSStr` or `PathBuf -> Path` adjustment in the abovementioned special cases + && let [preceeding_derefs @ .., special_case, last_borrow] = method_recv_adjustments + && matches!( + special_case.kind, + Adjust::Deref(DerefAdjustKind::Overloaded(OverloadedDeref { + mutbl: Mutability::Not, + .. + })) + ) + && matches!(last_borrow.kind, Adjust::Borrow(_)) + && special_case.target.is_diag_item(cx, after_special_case_ty_name) + && let before_special_case_ty = preceeding_derefs + .last().map_or_else(|| cx.typeck_results().expr_ty(method_recv), |a| a.target) + && matches!( + (before_special_case_ty.opt_diag_name(cx)?, after_special_case_ty_name), + (sym::OsString, sym::OsStr) | (sym::PathBuf, sym::Path)) + { + before_special_case_ty + } else { + return None; + }; + + // Find the number of adjustments required until `method_recv_ty_source` becomes `adjust_target_ty` + let method_recv_ty_source = cx.typeck_results().expr_ty(method_recv); + let adjust_count = method_recv_adjustments + .iter() + .enumerate() + .try_fold(method_recv_ty_source, |ty, (i, a)| { + if ty == adjust_target_ty { + ControlFlow::Break(i) + } else { + ControlFlow::Continue(a.target) + } + }) + .break_value()?; + + let (needs_borrow, deref_count) = if adjust_count == 0 || !method_recv_ty_source.is_ref() { + (true, adjust_count) + } else { + (false, adjust_count - 1) + }; + + Some(if needs_borrow { + format!("&{}", "*".repeat(deref_count)) + } else { + "*".repeat(deref_count) + }) +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index d9774426ec25c..f860aa66d09f2 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -155,7 +155,6 @@ use clippy_utils::macros::FormatArgsStorage; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::{contains_return, iter_input_pats, peel_blocks, sym}; -pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES; use rustc_data_structures::fx::FxHashSet; use rustc_hir::{self as hir, Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -165,6 +164,9 @@ use rustc_span::{Span, Symbol}; use crate::matches::manual_filter; +pub use implicit_clone::is_clone_like; +pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES; + declare_clippy_lint! { /// ### What it does /// Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))` diff --git a/tests/ui/cloned_ref_to_slice_refs.fixed b/tests/ui/cloned_ref_to_slice_refs.fixed index 818c6e23259e3..5b39a31538096 100644 --- a/tests/ui/cloned_ref_to_slice_refs.fixed +++ b/tests/ui/cloned_ref_to_slice_refs.fixed @@ -1,3 +1,4 @@ +#![allow(clippy::borrow_deref_ref)] #![warn(clippy::cloned_ref_to_slice_refs)] #[derive(Clone)] @@ -7,18 +8,18 @@ fn main() { { let data = Data; let data_ref = &data; - let _ = std::slice::from_ref(data_ref); //~ ERROR: this call to `clone` can be replaced with `std::slice::from_ref` + let _ = std::slice::from_ref(data_ref); //~ cloned_ref_to_slice_refs } { - let _ = std::slice::from_ref(&Data); //~ ERROR: this call to `clone` can be replaced with `std::slice::from_ref` + let _ = std::slice::from_ref(&Data); //~ cloned_ref_to_slice_refs } { #[derive(Clone)] struct Point(i32, i32); - let _ = std::slice::from_ref(&Point(0, 0)); //~ ERROR: this call to `clone` can be replaced with `std::slice::from_ref` + let _ = std::slice::from_ref(&Point(0, 0)); //~ cloned_ref_to_slice_refs } // the string was cloned with the intention to not mutate @@ -62,3 +63,76 @@ fn main() { let _ = &[data_1.clone(), data_2.clone()]; } } + +fn issue16320(items: &[String]) { + use std::ffi::OsString; + use std::ops::Deref; + use std::path::PathBuf; + + let _a = String::new(); + let _b = std::slice::from_ref(&_a); + //~^ cloned_ref_to_slice_refs + let _c = std::slice::from_ref(&_a); + //~^ cloned_ref_to_slice_refs + + let _a = OsString::new(); + let _b = std::slice::from_ref(&_a); + //~^ cloned_ref_to_slice_refs + + let _a = PathBuf::new(); + let _b = std::slice::from_ref(&_a); + //~^ cloned_ref_to_slice_refs + + let _a = &PathBuf::new(); + let _b = std::slice::from_ref(_a); + //~^ cloned_ref_to_slice_refs + + #[derive(Clone)] + struct A(i32); + + impl std::fmt::Display for A { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } + } + + let a = A(42); + _ = &[a.to_string()]; + + struct Wrapper(T); + impl Deref for Wrapper { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + let w = Wrapper(String::from("hello")); + let w = Wrapper(w); + let _b = std::slice::from_ref(&**w); + //~^ cloned_ref_to_slice_refs + + let w = Wrapper(&PathBuf::new()); + let w = Wrapper(w); + let _b = std::slice::from_ref(&***w); + //~^ cloned_ref_to_slice_refs +} + +fn wrongly_unmangled_macros(items: &[String]) { + use std::path::PathBuf; + + struct Wrapper { + inner: PathBuf, + } + + let _a = Wrapper { inner: PathBuf::new() }; + + macro_rules! accessor { + ($e:expr) => { + $e.inner + }; + } + + let _d = std::slice::from_ref(&accessor!(_a)); + //~^ cloned_ref_to_slice_refs +} diff --git a/tests/ui/cloned_ref_to_slice_refs.rs b/tests/ui/cloned_ref_to_slice_refs.rs index 9517dbfd1569c..ce9f9a1151177 100644 --- a/tests/ui/cloned_ref_to_slice_refs.rs +++ b/tests/ui/cloned_ref_to_slice_refs.rs @@ -1,3 +1,4 @@ +#![allow(clippy::borrow_deref_ref)] #![warn(clippy::cloned_ref_to_slice_refs)] #[derive(Clone)] @@ -7,18 +8,18 @@ fn main() { { let data = Data; let data_ref = &data; - let _ = &[data_ref.clone()]; //~ ERROR: this call to `clone` can be replaced with `std::slice::from_ref` + let _ = &[data_ref.clone()]; //~ cloned_ref_to_slice_refs } { - let _ = &[Data.clone()]; //~ ERROR: this call to `clone` can be replaced with `std::slice::from_ref` + let _ = &[Data.clone()]; //~ cloned_ref_to_slice_refs } { #[derive(Clone)] struct Point(i32, i32); - let _ = &[Point(0, 0).clone()]; //~ ERROR: this call to `clone` can be replaced with `std::slice::from_ref` + let _ = &[Point(0, 0).clone()]; //~ cloned_ref_to_slice_refs } // the string was cloned with the intention to not mutate @@ -62,3 +63,76 @@ fn main() { let _ = &[data_1.clone(), data_2.clone()]; } } + +fn issue16320(items: &[String]) { + use std::ffi::OsString; + use std::ops::Deref; + use std::path::PathBuf; + + let _a = String::new(); + let _b = &[_a.to_owned()]; + //~^ cloned_ref_to_slice_refs + let _c = &[_a.to_string()]; + //~^ cloned_ref_to_slice_refs + + let _a = OsString::new(); + let _b = &[_a.to_os_string()]; + //~^ cloned_ref_to_slice_refs + + let _a = PathBuf::new(); + let _b = &[_a.to_path_buf()]; + //~^ cloned_ref_to_slice_refs + + let _a = &PathBuf::new(); + let _b = &[_a.to_path_buf()]; + //~^ cloned_ref_to_slice_refs + + #[derive(Clone)] + struct A(i32); + + impl std::fmt::Display for A { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } + } + + let a = A(42); + _ = &[a.to_string()]; + + struct Wrapper(T); + impl Deref for Wrapper { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + let w = Wrapper(String::from("hello")); + let w = Wrapper(w); + let _b = &[w.to_string()]; + //~^ cloned_ref_to_slice_refs + + let w = Wrapper(&PathBuf::new()); + let w = Wrapper(w); + let _b = &[w.to_path_buf()]; + //~^ cloned_ref_to_slice_refs +} + +fn wrongly_unmangled_macros(items: &[String]) { + use std::path::PathBuf; + + struct Wrapper { + inner: PathBuf, + } + + let _a = Wrapper { inner: PathBuf::new() }; + + macro_rules! accessor { + ($e:expr) => { + $e.inner + }; + } + + let _d = &[accessor!(_a).to_path_buf()]; + //~^ cloned_ref_to_slice_refs +} diff --git a/tests/ui/cloned_ref_to_slice_refs.stderr b/tests/ui/cloned_ref_to_slice_refs.stderr index 6a31d87823955..a37438147b524 100644 --- a/tests/ui/cloned_ref_to_slice_refs.stderr +++ b/tests/ui/cloned_ref_to_slice_refs.stderr @@ -1,5 +1,5 @@ -error: this call to `clone` can be replaced with `std::slice::from_ref` - --> tests/ui/cloned_ref_to_slice_refs.rs:10:17 +error: unnecessary use of `clone` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:11:17 | LL | let _ = &[data_ref.clone()]; | ^^^^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(data_ref)` @@ -7,17 +7,65 @@ LL | let _ = &[data_ref.clone()]; = note: `-D clippy::cloned-ref-to-slice-refs` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cloned_ref_to_slice_refs)]` -error: this call to `clone` can be replaced with `std::slice::from_ref` - --> tests/ui/cloned_ref_to_slice_refs.rs:14:17 +error: unnecessary use of `clone` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:15:17 | LL | let _ = &[Data.clone()]; | ^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&Data)` -error: this call to `clone` can be replaced with `std::slice::from_ref` - --> tests/ui/cloned_ref_to_slice_refs.rs:21:17 +error: unnecessary use of `clone` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:22:17 | LL | let _ = &[Point(0, 0).clone()]; | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&Point(0, 0))` -error: aborting due to 3 previous errors +error: unnecessary use of `to_owned` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:73:14 + | +LL | let _b = &[_a.to_owned()]; + | ^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&_a)` + +error: unnecessary use of `to_string` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:75:14 + | +LL | let _c = &[_a.to_string()]; + | ^^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&_a)` + +error: unnecessary use of `to_os_string` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:79:14 + | +LL | let _b = &[_a.to_os_string()]; + | ^^^^^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&_a)` + +error: unnecessary use of `to_path_buf` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:83:14 + | +LL | let _b = &[_a.to_path_buf()]; + | ^^^^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&_a)` + +error: unnecessary use of `to_path_buf` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:87:14 + | +LL | let _b = &[_a.to_path_buf()]; + | ^^^^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(_a)` + +error: unnecessary use of `to_string` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:112:14 + | +LL | let _b = &[w.to_string()]; + | ^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&**w)` + +error: unnecessary use of `to_path_buf` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:117:14 + | +LL | let _b = &[w.to_path_buf()]; + | ^^^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&***w)` + +error: unnecessary use of `to_path_buf` to create a slice from a reference + --> tests/ui/cloned_ref_to_slice_refs.rs:136:14 + | +LL | let _d = &[accessor!(_a).to_path_buf()]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::slice::from_ref(&accessor!(_a))` + +error: aborting due to 11 previous errors From d41c9592d5510dbdb1db11d330ddb5cdebf7ad60 Mon Sep 17 00:00:00 2001 From: Gri-ffin Date: Sun, 19 Apr 2026 16:51:53 +0100 Subject: [PATCH 25/38] fix: [zst_offset] check for NonNull pointers --- clippy_lints/src/methods/zst_offset.rs | 11 +++++++++-- tests/ui/zero_offset.rs | 13 +++++++++++++ tests/ui/zero_offset.stderr | 20 +++++++++++++++++++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/methods/zst_offset.rs b/clippy_lints/src/methods/zst_offset.rs index 102fa7bc8953e..941afb9e2a988 100644 --- a/clippy_lints/src/methods/zst_offset.rs +++ b/clippy_lints/src/methods/zst_offset.rs @@ -1,13 +1,20 @@ use clippy_utils::diagnostics::span_lint; +use clippy_utils::res::MaybeDef; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_middle::ty; +use rustc_span::sym; use super::ZST_OFFSET; pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { - if let ty::RawPtr(ty, _) = cx.typeck_results().expr_ty(recv).kind() - && let Ok(layout) = cx.tcx.layout_of(cx.typing_env().as_query_input(*ty)) + let recv_ty = cx.typeck_results().expr_ty(recv); + let pointee_ty = match recv_ty.kind() { + ty::RawPtr(ty, _) => *ty, + ty::Adt(_, args) if recv_ty.is_diag_item(cx, sym::NonNull) => args.type_at(0), + _ => return, + }; + if let Ok(layout) = cx.tcx.layout_of(cx.typing_env().as_query_input(pointee_ty)) && layout.is_zst() { span_lint(cx, ZST_OFFSET, expr.span, "offset calculation on zero-sized value"); diff --git a/tests/ui/zero_offset.rs b/tests/ui/zero_offset.rs index 5a9c3ac9248fa..93ed50fde488f 100644 --- a/tests/ui/zero_offset.rs +++ b/tests/ui/zero_offset.rs @@ -29,5 +29,18 @@ fn main() { let sized = &1 as *const i32; sized.offset(0); + + let nn = core::ptr::NonNull::<()>::dangling(); + nn.add(0); + //~^ zst_offset + + nn.offset(0); + //~^ zst_offset + + nn.sub(0); + //~^ zst_offset + + let nn_sized = core::ptr::NonNull::::dangling(); + nn_sized.add(0); } } diff --git a/tests/ui/zero_offset.stderr b/tests/ui/zero_offset.stderr index b69c7b92d56a6..e78620ddcba4c 100644 --- a/tests/ui/zero_offset.stderr +++ b/tests/ui/zero_offset.stderr @@ -48,5 +48,23 @@ error: offset calculation on zero-sized value LL | c.wrapping_sub(0); | ^^^^^^^^^^^^^^^^^ -error: aborting due to 8 previous errors +error: offset calculation on zero-sized value + --> tests/ui/zero_offset.rs:34:9 + | +LL | nn.add(0); + | ^^^^^^^^^ + +error: offset calculation on zero-sized value + --> tests/ui/zero_offset.rs:37:9 + | +LL | nn.offset(0); + | ^^^^^^^^^^^^ + +error: offset calculation on zero-sized value + --> tests/ui/zero_offset.rs:40:9 + | +LL | nn.sub(0); + | ^^^^^^^^^ + +error: aborting due to 11 previous errors From 22a4b16b95a29d58b96c0479fa651548e6f33251 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Tue, 7 Apr 2026 14:21:10 +0200 Subject: [PATCH 26/38] `AliasTermTy` refactor: fixup clippy --- clippy_utils/src/ty/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index 0c261d21c1ec5..5c6fecde238a4 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -696,7 +696,7 @@ fn sig_from_bounds<'tcx>( inputs = Some(i); }, ty::ClauseKind::Projection(p) - if Some(p.projection_term.def_id) == lang_items.fn_once_output() + if Some(p.projection_term.def_id()) == lang_items.fn_once_output() && p.projection_term.self_ty() == ty => { if output.is_some() { @@ -737,7 +737,7 @@ fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: AliasTy<'tcx>) -> Option } inputs = Some(i); }, - ty::ClauseKind::Projection(p) if Some(p.projection_term.def_id) == lang_items.fn_once_output() => { + ty::ClauseKind::Projection(p) if Some(p.projection_term.def_id()) == lang_items.fn_once_output() => { if output.is_some() { // Multiple different fn trait impls. Is this even allowed? return None; From c282756df126fc915950803fab19e63b3e461c37 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 23 Apr 2026 14:28:43 +1000 Subject: [PATCH 27/38] Simplify `Config::track_state`. This is a callback used to track otherwise untracked state. It was added in #116731 for Clippy. (It was originally named `hash_untracked_state`, and examples in the rustc-dev-guide still use that name.) The `StableHasher` argument is unused, and probably has never been used. There is a FIXME comment pointing this out, which was added more than a year ago. This commit removes the `StableHasher` callback argument. This also removes the need for `Options::untracked_state_hash`. --- src/driver.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/driver.rs b/src/driver.rs index 7b7aac09643af..c682f294048ba 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -121,7 +121,7 @@ struct RustcCallbacks { impl rustc_driver::Callbacks for RustcCallbacks { fn config(&mut self, config: &mut interface::Config) { let clippy_args_var = self.clippy_args_var.take(); - config.track_state = Some(Box::new(move |sess, _hasher| { + config.track_state = Some(Box::new(move |sess| { track_clippy_args(sess, clippy_args_var.as_deref()); })); config.extra_symbols = sym::EXTRA_SYMBOLS.into(); @@ -138,7 +138,7 @@ impl rustc_driver::Callbacks for ClippyCallbacks { let conf_path = clippy_config::lookup_conf_file(); let previous = config.register_lints.take(); let clippy_args_var = self.clippy_args_var.take(); - config.track_state = Some(Box::new(move |sess, _hasher| { + config.track_state = Some(Box::new(move |sess| { track_clippy_args(sess, clippy_args_var.as_deref()); track_files(sess); From ecfdc97b29ff02cc236d342695b8ce170911ceaf Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 23 Apr 2026 17:05:41 +0200 Subject: [PATCH 28/38] Remove myself from rotation I'll be away for around ten days. Apparently, setting oneself out of rotation using triagebot is not enough. --- triagebot.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/triagebot.toml b/triagebot.toml index 8e7c36dac8412..869b8d730507a 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -93,6 +93,7 @@ users_on_vacation = [ "Alexendoo", "y21", "blyxyas", + "samueltardieu", ] [assign.owners] From 146f9abb8ccfcd4a95d6a409b491f8cd09c6ca05 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Sat, 11 Apr 2026 11:53:10 +0530 Subject: [PATCH 29/38] Handle U+000B as whitespace in needless_ifs; add UI test with rustfmt::skip Signed-off-by: Ajay Singh --- clippy_lints/src/needless_ifs.rs | 2 +- tests/ui/needless_ifs.fixed | 10 +++++++++- tests/ui/needless_ifs.rs | 10 +++++++++- tests/ui/needless_ifs.stderr | 26 ++++++++++++++++---------- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/needless_ifs.rs b/clippy_lints/src/needless_ifs.rs index ad1da9441dacb..cdf9bd91339c6 100644 --- a/clippy_lints/src/needless_ifs.rs +++ b/clippy_lints/src/needless_ifs.rs @@ -55,7 +55,7 @@ impl LateLintPass<'_> for NeedlessIfs { // - comments // - #[cfg]'d out code src.bytes() - .all(|ch| matches!(ch, b'{' | b'}') || ch.is_ascii_whitespace()) + .all(|ch| matches!(ch, b'{' | b'}') || ch.is_ascii_whitespace() || ch == b'\x0b') }) && let Some(cond_span) = walk_span_to_context(cond.span, expr.span.ctxt()) && let Some(cond_snippet) = cond_span.get_source_text(cx) diff --git a/tests/ui/needless_ifs.fixed b/tests/ui/needless_ifs.fixed index 0e0b0fa39c9b7..89d5b1da7b3f0 100644 --- a/tests/ui/needless_ifs.fixed +++ b/tests/ui/needless_ifs.fixed @@ -13,7 +13,6 @@ unused )] #![warn(clippy::needless_ifs)] - extern crate proc_macros; use proc_macros::{external, with_span}; @@ -113,3 +112,12 @@ fn issue15960() -> i32 { 1 // put something here so that `if` is a statement not an expression } + +#[rustfmt::skip] +fn issue_16845() { + + // Vertical tab (U+000B) should be treated as whitespace, + + //~^ needless_ifs + let () = if maybe_side_effect() {}; +} diff --git a/tests/ui/needless_ifs.rs b/tests/ui/needless_ifs.rs index fb0ee5c9cc832..837eaaf9647b0 100644 --- a/tests/ui/needless_ifs.rs +++ b/tests/ui/needless_ifs.rs @@ -13,7 +13,6 @@ unused )] #![warn(clippy::needless_ifs)] - extern crate proc_macros; use proc_macros::{external, with_span}; @@ -114,3 +113,12 @@ fn issue15960() -> i32 { 1 // put something here so that `if` is a statement not an expression } + +#[rustfmt::skip] +fn issue_16845() { + + // Vertical tab (U+000B) should be treated as whitespace, + if true { } + //~^ needless_ifs + let () = if maybe_side_effect() {}; +} diff --git a/tests/ui/needless_ifs.stderr b/tests/ui/needless_ifs.stderr index 8684ec217a0a4..7c7fcdc183668 100644 --- a/tests/ui/needless_ifs.stderr +++ b/tests/ui/needless_ifs.stderr @@ -1,5 +1,5 @@ error: this `if` branch is empty - --> tests/ui/needless_ifs.rs:26:5 + --> tests/ui/needless_ifs.rs:25:5 | LL | if (true) {} | ^^^^^^^^^^^^ help: you can remove it @@ -8,13 +8,13 @@ LL | if (true) {} = help: to override `-D warnings` add `#[allow(clippy::needless_ifs)]` error: this `if` branch is empty - --> tests/ui/needless_ifs.rs:29:5 + --> tests/ui/needless_ifs.rs:28:5 | LL | if maybe_side_effect() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `maybe_side_effect();` error: this `if` branch is empty - --> tests/ui/needless_ifs.rs:35:5 + --> tests/ui/needless_ifs.rs:34:5 | LL | / if { LL | | @@ -31,7 +31,7 @@ LL + }); | error: this `if` branch is empty - --> tests/ui/needless_ifs.rs:50:5 + --> tests/ui/needless_ifs.rs:49:5 | LL | / if { LL | | @@ -57,34 +57,40 @@ LL + } && true); | error: this `if` branch is empty - --> tests/ui/needless_ifs.rs:95:5 + --> tests/ui/needless_ifs.rs:94:5 | LL | if { maybe_side_effect() } {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `({ maybe_side_effect() });` error: this `if` branch is empty - --> tests/ui/needless_ifs.rs:98:5 + --> tests/ui/needless_ifs.rs:97:5 | LL | if { maybe_side_effect() } && true {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `({ maybe_side_effect() } && true);` error: this `if` branch is empty - --> tests/ui/needless_ifs.rs:103:5 + --> tests/ui/needless_ifs.rs:102:5 | LL | if true {} | ^^^^^^^^^^ help: you can remove it: `true;` error: this `if` branch is empty - --> tests/ui/needless_ifs.rs:110:5 + --> tests/ui/needless_ifs.rs:109:5 | LL | if matches!(2, 3) {} | ^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `matches!(2, 3);` error: this `if` branch is empty - --> tests/ui/needless_ifs.rs:112:5 + --> tests/ui/needless_ifs.rs:111:5 | LL | if matches!(2, 3) == (2 * 2 == 5) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can remove it: `matches!(2, 3) == (2 * 2 == 5);` -error: aborting due to 9 previous errors +error: this `if` branch is empty + --> tests/ui/needless_ifs.rs:121:5 + | +LL | if true {␋} + | ^^^^^^^^^^^ help: you can remove it + +error: aborting due to 10 previous errors From 28758820f1eefedbf8642a4adba09fdac0e02e20 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 24 Apr 2026 15:25:52 +0200 Subject: [PATCH 30/38] `inline_modules`: fix the rust version the lint was introduced in --- clippy_lints/src/module_style.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/module_style.rs b/clippy_lints/src/module_style.rs index 500ee2864e46c..c1c5a01427197 100644 --- a/clippy_lints/src/module_style.rs +++ b/clippy_lints/src/module_style.rs @@ -35,7 +35,7 @@ declare_clippy_lint! { /// // in `src/foo.rs` (or `src/foo/mod.rs`) /// /* module contents */ /// ``` - #[clippy::version = "1.96.0"] + #[clippy::version = "1.97.0"] pub INLINE_MODULES, restriction, "checks that module layout does not use inline modules" From 70cc275b139562b635097a701f7369a18efe619e Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Fri, 24 Apr 2026 19:03:16 +0200 Subject: [PATCH 31/38] Change `ItemKind::Trait` to have named fields. --- .../src/arbitrary_source_item_ordering.rs | 20 +++++++++---------- clippy_lints/src/doc/mod.rs | 2 +- .../src/doc/too_long_first_doc_paragraph.rs | 2 +- clippy_lints/src/item_name_repetitions.rs | 2 +- clippy_lints/src/len_without_is_empty.rs | 2 +- clippy_lints/src/missing_const_for_fn.rs | 2 +- clippy_lints/src/missing_doc.rs | 2 +- clippy_lints/src/missing_inline.rs | 2 +- clippy_lints/src/needless_pass_by_ref_mut.rs | 2 +- clippy_lints/src/needless_pass_by_value.rs | 2 +- clippy_lints/src/pass_by_ref_or_value.rs | 2 +- clippy_lints/src/trait_bounds.rs | 4 ++-- clippy_lints/src/unnecessary_wraps.rs | 2 +- clippy_lints/src/upper_case_acronyms.rs | 2 +- clippy_utils/src/check_proc_macro.rs | 6 +++--- clippy_utils/src/paths.rs | 2 +- 16 files changed, 28 insertions(+), 28 deletions(-) diff --git a/clippy_lints/src/arbitrary_source_item_ordering.rs b/clippy_lints/src/arbitrary_source_item_ordering.rs index 4a6c024cac9a6..21cb3c5d0443c 100644 --- a/clippy_lints/src/arbitrary_source_item_ordering.rs +++ b/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -306,16 +306,16 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering { cur_f = Some(field); } }, - ItemKind::Trait( - _impl_restriction, - _constness, + ItemKind::Trait { + impl_restriction:_, + constness:_, is_auto, - _safety, - _ident, - _generics, - _generic_bounds, - item_ref, - ) if self.enable_ordering_for_trait && *is_auto == IsAuto::No => { + safety:_, + ident:_, + generics: _, + bounds: _, + items: item_ref} + if self.enable_ordering_for_trait && *is_auto == IsAuto::No => { let mut cur_t: Option<(TraitItemId, Ident)> = None; for &item in *item_ref { @@ -510,7 +510,7 @@ fn convert_module_item_kind(value: &ItemKind<'_>) -> SourceItemOrderingModuleIte ItemKind::Enum(..) => Enum, ItemKind::Struct(..) => Struct, ItemKind::Union(..) => Union, - ItemKind::Trait(..) => Trait, + ItemKind::Trait { .. } => Trait, ItemKind::TraitAlias(..) => TraitAlias, ItemKind::Impl(..) => Impl, } diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 81812880a743a..e772c20abf94d 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -769,7 +769,7 @@ impl<'tcx> LateLintPass<'tcx> for Documentation { { missing_headers::check(cx, item.owner_id, sig, headers, Some(body), self.check_private_items); }, - ItemKind::Trait(_, _, _, unsafety, ..) => match (headers.safety, unsafety) { + ItemKind::Trait { safety, .. } => match (headers.safety, safety) { (false, Safety::Unsafe) => span_lint( cx, MISSING_SAFETY_DOC, 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 674690e7e31d8..8ba6e9d332ba8 100644 --- a/clippy_lints/src/doc/too_long_first_doc_paragraph.rs +++ b/clippy_lints/src/doc/too_long_first_doc_paragraph.rs @@ -33,7 +33,7 @@ pub(super) fn check( | ItemKind::Enum(..) | ItemKind::Struct(..) | ItemKind::Union(..) - | ItemKind::Trait(..) + | ItemKind::Trait { .. } | ItemKind::TraitAlias(..) ) { diff --git a/clippy_lints/src/item_name_repetitions.rs b/clippy_lints/src/item_name_repetitions.rs index 06bea4ba1ffd8..5ded0efacb815 100644 --- a/clippy_lints/src/item_name_repetitions.rs +++ b/clippy_lints/src/item_name_repetitions.rs @@ -528,7 +528,7 @@ impl LateLintPass<'_> for ItemNameRepetitions { | ItemKind::Fn { ident, .. } | ItemKind::Macro(ident, ..) | ItemKind::Static(_, ident, ..) - | ItemKind::Trait(_, _, _, _, ident, ..) + | ItemKind::Trait { ident, ..} | ItemKind::TraitAlias(_, ident, ..) | ItemKind::TyAlias(ident, ..) | ItemKind::Union(ident, ..) diff --git a/clippy_lints/src/len_without_is_empty.rs b/clippy_lints/src/len_without_is_empty.rs index 1f019531f602b..9bf06dbf452d6 100644 --- a/clippy_lints/src/len_without_is_empty.rs +++ b/clippy_lints/src/len_without_is_empty.rs @@ -44,7 +44,7 @@ declare_lint_pass!(LenWithoutIsEmpty => [LEN_WITHOUT_IS_EMPTY]); impl<'tcx> LateLintPass<'tcx> for LenWithoutIsEmpty { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - if let ItemKind::Trait(_, _, _, _, ident, _, _, trait_items) = item.kind + if let ItemKind::Trait { ident, items: trait_items, .. } = item.kind && !item.span.from_expansion() { check_trait_items(cx, item, ident, trait_items); diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs index 0839219c5b613..b9378d2cdd8cc 100644 --- a/clippy_lints/src/missing_const_for_fn.rs +++ b/clippy_lints/src/missing_const_for_fn.rs @@ -141,7 +141,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { let parent = cx.tcx.hir_get_parent_item(hir_id).def_id; if parent != CRATE_DEF_ID && let hir::Node::Item(item) = cx.tcx.hir_node_by_def_id(parent) - && let hir::ItemKind::Trait(..) = &item.kind + && let hir::ItemKind::Trait { .. } = &item.kind { return; } diff --git a/clippy_lints/src/missing_doc.rs b/clippy_lints/src/missing_doc.rs index 35e75d34a5d3a..694a473ff5a97 100644 --- a/clippy_lints/src/missing_doc.rs +++ b/clippy_lints/src/missing_doc.rs @@ -159,7 +159,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { | ItemKind::Macro(ident, ..) | ItemKind::Static(_, ident, ..) | ItemKind::Struct(ident, ..) - | ItemKind::Trait(_, _, _, _, ident, ..) + | ItemKind::Trait { ident, .. } | ItemKind::TraitAlias(_, ident, ..) | ItemKind::TyAlias(ident, ..) | ItemKind::Union(ident, ..) => ident.span, diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index 16dae67c29865..93cfed38c43ed 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -111,7 +111,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { let attrs = cx.tcx.hir_attrs(it.hir_id()); check_missing_inline_attrs(cx, attrs, it.span, desc, None); }, - hir::ItemKind::Trait(.., trait_items) => { + hir::ItemKind::Trait { items: 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 { diff --git a/clippy_lints/src/needless_pass_by_ref_mut.rs b/clippy_lints/src/needless_pass_by_ref_mut.rs index 74a37077b0a1d..91358ef77fa1e 100644 --- a/clippy_lints/src/needless_pass_by_ref_mut.rs +++ b/clippy_lints/src/needless_pass_by_ref_mut.rs @@ -167,7 +167,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { if let Node::Item(item) = cx.tcx.parent_hir_node(hir_id) && matches!( item.kind, - ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..) + ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait { .. } ) { return; diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index 593eff6a9bbd1..4ff5b0b0b3c39 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -103,7 +103,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { if let Node::Item(item) = cx.tcx.parent_hir_node(hir_id) && matches!( item.kind, - ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..) + ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait { .. } ) { return; diff --git a/clippy_lints/src/pass_by_ref_or_value.rs b/clippy_lints/src/pass_by_ref_or_value.rs index b4a1713222123..6b81b9d117a37 100644 --- a/clippy_lints/src/pass_by_ref_or_value.rs +++ b/clippy_lints/src/pass_by_ref_or_value.rs @@ -292,7 +292,7 @@ impl<'tcx> LateLintPass<'tcx> for PassByRefOrValue { if let Node::Item(item) = cx.tcx.parent_hir_node(hir_id) && matches!( item.kind, - ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..) + ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait { .. } ) { return; diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index 4cd3707854c48..e4faf8e82a8e6 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -115,7 +115,7 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds { // special handling for self trait bounds as these are not considered generics // i.e. trait Foo: Display {} if let Item { - kind: ItemKind::Trait(_, _, _, _, _, _, bounds, ..), + kind: ItemKind::Trait { bounds, .. }, .. } = item { @@ -136,7 +136,7 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds { .. }) = segments.first() && let Some(Node::Item(Item { - kind: ItemKind::Trait(_, _, _, _, _, _, self_bounds, _), + kind: ItemKind::Trait {bounds: self_bounds,..}, .. })) = cx.tcx.hir_get_if_local(*def_id) { diff --git a/clippy_lints/src/unnecessary_wraps.rs b/clippy_lints/src/unnecessary_wraps.rs index 17e05db644b06..cecffaae69704 100644 --- a/clippy_lints/src/unnecessary_wraps.rs +++ b/clippy_lints/src/unnecessary_wraps.rs @@ -99,7 +99,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { if let Node::Item(item) = cx.tcx.parent_hir_node(hir_id) && matches!( item.kind, - ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..) + ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait { .. } ) { return; diff --git a/clippy_lints/src/upper_case_acronyms.rs b/clippy_lints/src/upper_case_acronyms.rs index 52dfcab363dbd..0b95468436127 100644 --- a/clippy_lints/src/upper_case_acronyms.rs +++ b/clippy_lints/src/upper_case_acronyms.rs @@ -131,7 +131,7 @@ impl LateLintPass<'_> for UpperCaseAcronyms { return; } match it.kind { - ItemKind::TyAlias(ident, ..) | ItemKind::Struct(ident, ..) | ItemKind::Trait(_, _, _, _, ident, ..) => { + ItemKind::TyAlias(ident, ..) | ItemKind::Struct(ident, ..) | ItemKind::Trait { ident, .. }=> { check_ident(cx, &ident, it.hir_id(), self.upper_case_acronyms_aggressive); }, ItemKind::Enum(ident, _, ref enumdef) => { diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs index 44b9084cd4f69..e1382f5b706c0 100644 --- a/clippy_utils/src/check_proc_macro.rs +++ b/clippy_utils/src/check_proc_macro.rs @@ -265,15 +265,15 @@ fn item_search_pat(item: &Item<'_>) -> (Pat, Pat) { ItemKind::Struct(_, _, VariantData::Struct { .. }) => (Pat::Str("struct"), Pat::Str("}")), ItemKind::Struct(..) => (Pat::Str("struct"), Pat::Str(";")), ItemKind::Union(..) => (Pat::Str("union"), Pat::Str("}")), - ItemKind::Trait(_, _, _, Safety::Unsafe, ..) + ItemKind::Trait { safety: Safety::Unsafe, .. } | ItemKind::Impl(Impl { of_trait: Some(TraitImplHeader { safety: Safety::Unsafe, .. }), .. }) => (Pat::Str("unsafe"), Pat::Str("}")), - ItemKind::Trait(_, _, IsAuto::Yes, ..) => (Pat::Str("auto"), Pat::Str("}")), - ItemKind::Trait(..) => (Pat::Str("trait"), Pat::Str("}")), + ItemKind::Trait { is_auto: IsAuto::Yes, .. } => (Pat::Str("auto"), Pat::Str("}")), + ItemKind::Trait { .. } => (Pat::Str("trait"), Pat::Str("}")), ItemKind::Impl(_) => (Pat::Str("impl"), Pat::Str("}")), ItemKind::Mod(..) => (Pat::Str("mod"), Pat::Str("")), ItemKind::Macro(_, def, _) => ( diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index c5fd66eeb93cd..f27c92f0d4921 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -332,7 +332,7 @@ fn local_item_child_by_name(tcx: TyCtxt<'_>, local_id: LocalDefId, ns: PathNS, n None } }), - ItemKind::Impl(..) | ItemKind::Trait(..) => tcx + ItemKind::Impl(..) | ItemKind::Trait { .. } => tcx .associated_items(local_id) .filter_by_name_unhygienic(name) .find(|assoc_item| ns.matches(Some(assoc_item.namespace()))) From 42250501e4cf1936f55efaa6715f991947044b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Gr=C3=BCninger?= Date: Sun, 26 Apr 2026 10:47:32 +0200 Subject: [PATCH 32/38] [ci] Update GitHub Actions to latest major release Fix deprecation warning that Node20.js will stop working in June. --- .github/workflows/clippy_mq.yml | 4 ++-- .github/workflows/lintcheck.yml | 18 +++++++++--------- .github/workflows/lintcheck_summary.yml | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/clippy_mq.yml b/.github/workflows/clippy_mq.yml index c49241bdff1bc..b612ea4611a9f 100644 --- a/.github/workflows/clippy_mq.yml +++ b/.github/workflows/clippy_mq.yml @@ -135,7 +135,7 @@ jobs: find $DIR ! -executable -o -type d ! -path $DIR | xargs rm -rf - name: Upload Binaries - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: binaries path: target/debug @@ -179,7 +179,7 @@ jobs: # Download - name: Download target dir - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v8 with: name: binaries path: target/debug diff --git a/.github/workflows/lintcheck.yml b/.github/workflows/lintcheck.yml index 9ce0b7f5fc46b..981fecd17c988 100644 --- a/.github/workflows/lintcheck.yml +++ b/.github/workflows/lintcheck.yml @@ -44,7 +44,7 @@ jobs: - name: Cache lintcheck bin id: cache-lintcheck-bin - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: target/debug/lintcheck key: lintcheck-bin-${{ hashfiles('lintcheck/**') }} @@ -59,7 +59,7 @@ jobs: - name: Cache results JSON id: cache-json - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: lintcheck-logs/ci_crates_logs.json key: ${{ steps.key.outputs.key }} @@ -69,7 +69,7 @@ jobs: run: env CLIPPY_CONF_DIR="$PWD/lintcheck/ci-config" ./target/debug/lintcheck --format json --all-lints --crates-toml ./lintcheck/ci_crates.toml - name: Upload base JSON - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: base path: lintcheck-logs/ci_crates_logs.json @@ -87,7 +87,7 @@ jobs: - name: Cache lintcheck bin id: cache-lintcheck-bin - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: target/debug/lintcheck key: lintcheck-bin-${{ hashfiles('lintcheck/**') }} @@ -100,7 +100,7 @@ jobs: run: env CLIPPY_CONF_DIR="$PWD/lintcheck/ci-config" ./target/debug/lintcheck --format json --all-lints --crates-toml ./lintcheck/ci_crates.toml - name: Upload head JSON - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: head path: lintcheck-logs/ci_crates_logs.json @@ -119,14 +119,14 @@ jobs: persist-credentials: false - name: Restore lintcheck bin - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: path: target/debug/lintcheck key: lintcheck-bin-${{ hashfiles('lintcheck/**') }} fail-on-cache-miss: true - name: Download JSON - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v8 - name: Store PR number run: echo ${{ github.event.pull_request.number }} > pr.txt @@ -140,13 +140,13 @@ jobs: ./target/debug/lintcheck diff {base,head}/ci_crates_logs.json --write-summary summary.json > full_diff.md - name: Upload full diff - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: full_diff path: full_diff.md - name: Upload summary - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: summary path: | diff --git a/.github/workflows/lintcheck_summary.yml b/.github/workflows/lintcheck_summary.yml index 6768cd65701ac..da6a7474cdb91 100644 --- a/.github/workflows/lintcheck_summary.yml +++ b/.github/workflows/lintcheck_summary.yml @@ -27,7 +27,7 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - name: Download artifact - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v8 with: name: summary path: untrusted @@ -35,7 +35,7 @@ jobs: github-token: ${{ github.token }} - name: Format comment - uses: actions/github-script@v8 + uses: actions/github-script@v9 with: script: | const fs = require("fs"); From 5c81d3af11f8d782b36d800ee6680a5bdacf60dd Mon Sep 17 00:00:00 2001 From: pocopepe Date: Sun, 26 Apr 2026 09:45:12 +0000 Subject: [PATCH 33/38] use non_blanket_impls_for_ty for the lookup --- clippy_lints/src/from_over_into.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/from_over_into.rs b/clippy_lints/src/from_over_into.rs index fdb816fcb7e7f..12b22c53928c9 100644 --- a/clippy_lints/src/from_over_into.rs +++ b/clippy_lints/src/from_over_into.rs @@ -169,9 +169,9 @@ fn has_blanket_from_impl<'tcx>(cx: &LateContext<'tcx>, self_ty: Ty<'tcx>) -> boo let Some(from_def_id) = cx.tcx.get_diagnostic_item(sym::From) else { return false; }; - cx.tcx.all_impls(from_def_id).any(|impl_id| { + cx.tcx.non_blanket_impls_for_ty(from_def_id, self_ty).any(|impl_id| { let impl_trait_ref = cx.tcx.impl_trait_ref(impl_id).instantiate_identity(); - impl_trait_ref.self_ty() == self_ty && matches!(impl_trait_ref.args.type_at(1).kind(), ty::Param(_)) + matches!(impl_trait_ref.args.type_at(1).kind(), ty::Param(_)) }) } From ff715696140998455fcb83d68f8880821cae4a6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?On=C3=A8?= <43485962+c-git@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:38:02 -0400 Subject: [PATCH 34/38] Fix wording for linting instructions in configuration.md --- book/src/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index b270c11ab397c..45faf039dd670 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -79,7 +79,7 @@ This also works with lint groups. For example, you can run Clippy with warnings cargo clippy -- -W clippy::pedantic ``` -If you care only about a certain lints, you can allow all others and then explicitly warn on the lints you are +If you care only about certain lints, you can allow all others and then explicitly warn on the lints you are interested in: ```terminal From 18e40cb6f1eaa76be3974dc22a3c83fcbdc6ea9d Mon Sep 17 00:00:00 2001 From: Sidney Cammeresi Date: Sun, 26 Apr 2026 19:34:26 -0700 Subject: [PATCH 35/38] Adjust diagnostic items for mpmc/mpsc Receiver and Sender `MpscReceiver` aligns with `MpscSender`. The original name appears to not actually have been in use, for better or worse. Along the way, sprinkle the attribute onto `mpmc::Receiver` and `mpmc::Sender` too. --- clippy_utils/src/sym.rs | 5 ++++- clippy_utils/src/ty/mod.rs | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index 4eaeafe127078..87aac25f5bd12 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -90,6 +90,10 @@ generate! { MAX, MIN, MaybeDef, + MpmcReceiver, + MpmcSender, + MpscReceiver, + MpscSender, MsrvStack, Octal, OpenOptions, @@ -99,7 +103,6 @@ generate! { PathBuf, PathLookup, RangeBounds, - Receiver, RefCellRef, RefCellRefMut, Regex, diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index 5c6fecde238a4..66d85b58eaae4 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -192,7 +192,8 @@ pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option< sym::HashMap, sym::PathBuf, sym::Path, - sym::Receiver, + sym::MpscReceiver, + sym::MpmcReceiver, ]; let ty_to_check = match probably_ref_ty.kind() { From 5c971e03d0c7bbc42b2071ab1f2dc028036f3da6 Mon Sep 17 00:00:00 2001 From: linshuy2 Date: Thu, 30 Apr 2026 04:25:30 +0000 Subject: [PATCH 36/38] `bad_bit_mask` fix ICE for overloaded bit ops --- clippy_lints/src/operators/bit_mask.rs | 3 ++- tests/ui/bit_masks.rs | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/operators/bit_mask.rs b/clippy_lints/src/operators/bit_mask.rs index 104f786ead165..1607d23e6a335 100644 --- a/clippy_lints/src/operators/bit_mask.rs +++ b/clippy_lints/src/operators/bit_mask.rs @@ -43,7 +43,8 @@ fn check_compare<'a>(cx: &LateContext<'a>, bit_op: &Expr<'a>, cmp_op: BinOpKind, } if let Some(mask) = fetch_int_literal(cx, right).or_else(|| fetch_int_literal(cx, left)) { let ty = cx.typeck_results().expr_ty(bit_op); - if !ty.is_ptr_sized_integral() + if ty.is_primitive() + && !ty.is_ptr_sized_integral() && let bits = ty.primitive_size(cx.tcx) { // Strip high bits that don't fit into the result type as they won't be used in the comparison diff --git a/tests/ui/bit_masks.rs b/tests/ui/bit_masks.rs index bca5b2ec34e12..30dd8f912acfd 100644 --- a/tests/ui/bit_masks.rs +++ b/tests/ui/bit_masks.rs @@ -99,3 +99,25 @@ mod issue16781 { x & 0x70 == 0x11 << 4 } } + +mod issue16935 { + struct Wrapper(usize); + + impl std::ops::BitAnd for Wrapper { + type Output = Self; + + fn bitand(self, rhs: usize) -> Self::Output { + Self(self.0 & rhs) + } + } + + impl PartialEq for Wrapper { + fn eq(&self, other: &usize) -> bool { + self.0.eq(other) + } + } + + fn check(value: Wrapper) -> bool { + value & 0x1 != 0 + } +} From 8fe8c68bbffb10a3ab5da312120727db0749f5c2 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 30 Apr 2026 11:41:05 +0200 Subject: [PATCH 37/38] Bump nightly version -> 2026-04-30 --- clippy_utils/README.md | 2 +- rust-toolchain.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_utils/README.md b/clippy_utils/README.md index 99489cb11e734..de7afb7cf13ac 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-2026-04-16 +nightly-2026-04-30 ``` diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 97c8cf260cad5..9992299153e21 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2026-04-16" +channel = "nightly-2026-04-30" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" From c9aa8af8362df5f963683ca47d4282ee7a94c0bb Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 30 Apr 2026 14:33:38 +0200 Subject: [PATCH 38/38] Fix Clippy lints in bootstrap --- src/bootstrap/src/core/build_steps/perf.rs | 2 +- src/bootstrap/src/core/build_steps/setup.rs | 2 +- src/bootstrap/src/core/build_steps/test.rs | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/perf.rs b/src/bootstrap/src/core/build_steps/perf.rs index 108b7f90c149e..7bdcb483eb923 100644 --- a/src/bootstrap/src/core/build_steps/perf.rs +++ b/src/bootstrap/src/core/build_steps/perf.rs @@ -196,7 +196,7 @@ Consider setting `rust.debuginfo-level = 1` in `bootstrap.toml`."#); apply_shared_opts(&mut cmd, opts); cmd.run(builder); - println!("You can find the results at `{}`", &results_dir.display()); + println!("You can find the results at `{}`", results_dir.display()); } PerfCommand::Benchmark { id, opts } => { cmd.arg("bench_local"); diff --git a/src/bootstrap/src/core/build_steps/setup.rs b/src/bootstrap/src/core/build_steps/setup.rs index 85c23ac18e763..8e65cb18d58fb 100644 --- a/src/bootstrap/src/core/build_steps/setup.rs +++ b/src/bootstrap/src/core/build_steps/setup.rs @@ -312,7 +312,7 @@ fn attempt_toolchain_link(builder: &Builder<'_>, stage_path: &str) { eprintln!( "To manually link stage 1 build to `stage1` toolchain, run:\n `rustup toolchain link stage1 {}`", - &stage_path + stage_path ); } } diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 991592ec522d0..8dd617f522209 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -1349,9 +1349,9 @@ impl Step for Tidy { /// for the `dev` or `nightly` channels. fn run(self, builder: &Builder<'_>) { let mut cmd = builder.tool_cmd(Tool::Tidy); - cmd.arg(format!("--root-path={}", &builder.src.display())); - cmd.arg(format!("--cargo-path={}", &builder.initial_cargo.display())); - cmd.arg(format!("--output-dir={}", &builder.out.display())); + cmd.arg(format!("--root-path={}", builder.src.display())); + cmd.arg(format!("--cargo-path={}", builder.initial_cargo.display())); + cmd.arg(format!("--output-dir={}", builder.out.display())); // Tidy is heavily IO constrained. Still respect `-j`, but use a higher limit if `jobs` hasn't been configured. let jobs = builder.config.jobs.unwrap_or_else(|| { 8 * std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get) as u32 @@ -2576,7 +2576,7 @@ Please disable assertions with `rust.debug-assertions = false`. builder.info(&format!( "Check compiletest suite={} mode={} compare_mode={} ({} -> {})", - suite, mode, compare_mode, &test_compiler.host, target + suite, mode, compare_mode, test_compiler.host, target )); let _time = helpers::timeit(builder); try_run_tests(builder, &mut cmd, false);