From 4d8044dea0e11f8c4b32157ad29c75e2003c44e3 Mon Sep 17 00:00:00 2001 From: tanndlin Date: Fri, 10 Jul 2026 23:32:49 -0400 Subject: [PATCH 1/4] Create deref_method_call_chain lint --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + .../src/methods/deref_method_call_chain.rs | 116 ++++++++++++++++ clippy_lints/src/methods/mod.rs | 36 +++++ clippy_utils/src/sym.rs | 5 + tests/ui/deref_method_call_chain.fixed | 129 ++++++++++++++++++ tests/ui/deref_method_call_chain.rs | 129 ++++++++++++++++++ tests/ui/deref_method_call_chain.stderr | 83 +++++++++++ 8 files changed, 500 insertions(+) create mode 100644 clippy_lints/src/methods/deref_method_call_chain.rs create mode 100644 tests/ui/deref_method_call_chain.fixed create mode 100644 tests/ui/deref_method_call_chain.rs create mode 100644 tests/ui/deref_method_call_chain.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 447cc4050405..d0e29e4aa455 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6845,6 +6845,7 @@ Released 2018-09-13 [`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver [`deref_addrof`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_addrof [`deref_by_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_by_slicing +[`deref_method_call_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_method_call_chain [`derivable_impls`]: https://rust-lang.github.io/rust-clippy/master/index.html#derivable_impls [`derive_hash_xor_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq [`derive_ord_xor_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 83ab6e31d2b2..4ddc70d97684 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -376,6 +376,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::methods::CLONED_INSTEAD_OF_COPIED_INFO, crate::methods::COLLAPSIBLE_STR_REPLACE_INFO, crate::methods::CONST_IS_EMPTY_INFO, + crate::methods::DEREF_METHOD_CALL_CHAIN_INFO, crate::methods::DOUBLE_ENDED_ITERATOR_LAST_INFO, crate::methods::DRAIN_COLLECT_INFO, crate::methods::ERR_EXPECT_INFO, diff --git a/clippy_lints/src/methods/deref_method_call_chain.rs b/clippy_lints/src/methods/deref_method_call_chain.rs new file mode 100644 index 000000000000..0aacb7d767d7 --- /dev/null +++ b/clippy_lints/src/methods/deref_method_call_chain.rs @@ -0,0 +1,116 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::sym; +use clippy_utils::ty::implements_trait; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::{Span, Symbol}; + +use super::{DEREF_METHOD_CALL_CHAIN, method_call}; + +/// Checks whether the receiver of `outer_name` is a redundant deref-like conversion call +/// (e.g. `Vec::as_slice`, `String::as_str`) which can be removed because method resolution +/// would find the same method on the original value through deref coercion. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + outer_name: Symbol, + outer_span: Span, + recv: &'tcx Expr<'tcx>, +) { + let Some((inner_name, inner_recv, [], inner_span, _)) = method_call(recv) else { + return; + }; + if !matches!( + inner_name, + sym::as_c_str + | sym::as_mut_slice + | sym::as_mut_str + | sym::as_os_str + | sym::as_path + | sym::as_slice + | sym::as_str + ) { + return; + } + + let Some(inner_did) = cx.typeck_results().type_dependent_def_id(recv.hir_id) else { + return; + }; + let inner_diag = cx.tcx.get_diagnostic_name(inner_did); + let is_std_conversion = matches!( + inner_diag, + Some( + sym::cstring_as_c_str + | sym::os_string_as_os_str + | sym::pathbuf_as_path + | sym::string_as_mut_str + | sym::string_as_str + | sym::vec_as_mut_slice + | sym::vec_as_slice + ) + ); + + // `String::as_str()` followed by these methods is covered by `REDUNDANT_AS_STR` + if inner_diag == Some(sym::string_as_str) && matches!(outer_name, sym::as_bytes | sym::is_empty) { + return; + } + + // The conversion must be deref-equivalent: it returns a reference to exactly + // `::Target`, so removing it lets auto-deref do the same work + let recv_base_ty = cx.typeck_results().expr_ty(inner_recv).peel_refs(); + let ty::Ref(_, target_ty, _) = *cx.typeck_results().expr_ty(recv).kind() else { + return; + }; + let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref) else { + return; + }; + if !implements_trait(cx, recv_base_ty, deref_trait_id, &[]) + || cx.get_associated_type(recv_base_ty, deref_trait_id, sym::Target) != Some(target_ty) + { + return; + } + + // The following method must be an inherent method of the deref target. Trait methods + // could resolve to a different impl on the original receiver (e.g. `IntoIterator` on + // `Vec` vs `&[T]`), changing semantics + let Some(outer_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) else { + return; + }; + let Some(impl_id) = cx.tcx.inherent_impl_of_assoc(outer_did) else { + return; + }; + let outer_args = cx.typeck_results().node_args(expr.hir_id); + let impl_args = outer_args.truncate_to(cx.tcx, cx.tcx.generics_of(impl_id)); + if cx.tcx.type_of(impl_id).instantiate(cx.tcx, impl_args).skip_norm_wip() != target_ty { + return; + } + + // For user-defined conversions, a same-named inherent method on the receiver type would + // shadow the target's method. Std receivers (`Vec`, `String`, ...) only shadow with + // delegating methods (`Vec::len`, ...), which are exactly the idiomatic suggestion + if !is_std_conversion + && let Some(adt) = recv_base_ty.ty_adt_def() + && cx + .tcx + .inherent_impls(adt.did()) + .iter() + .flat_map(|&impl_id| cx.tcx.associated_items(impl_id).filter_by_name_unhygienic(outer_name)) + .any(ty::AssocItem::is_method) + { + return; + } + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + DEREF_METHOD_CALL_CHAIN, + inner_span.to(outer_span), + format!("this `{inner_name}` call is redundant as `{outer_name}` can be called on the receiver directly"), + "call the method directly", + snippet_with_applicability(cx, outer_span, "..", &mut applicability).into_owned(), + applicability, + ); +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index ddb2fba94bba..6a22762c2779 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -16,6 +16,7 @@ mod clone_on_copy; mod clone_on_ref_ptr; mod cloned_instead_of_copied; mod collapsible_str_replace; +mod deref_method_call_chain; mod double_ended_iterator_last; mod drain_collect; mod err_expect; @@ -540,6 +541,39 @@ declare_clippy_lint! { "is_empty() called on strings known at compile time" } +declare_clippy_lint! { + /// ### What it does + /// Checks for method calls whose receiver is a redundant deref-like conversion call + /// such as `as_slice`, `as_str`, or `as_path`, when the method could be called on the + /// original value directly through deref coercion. + /// + /// ### Why is this bad? + /// The conversion call adds noise without changing behavior: method resolution + /// automatically dereferences the receiver, so the same method is called either way. + /// + /// ### Known problems + /// Receivers behind more than one level of deref (e.g. `Box>`) are not detected. + /// + /// ### Example + /// ```no_run + /// let vec = vec![1, 2, 3]; + /// let string = String::from("hello world"); + /// let _ = vec.as_slice().first(); + /// let _ = string.as_str().find('l'); + /// ``` + /// Use instead: + /// ```no_run + /// let vec = vec![1, 2, 3]; + /// let string = String::from("hello world"); + /// let _ = vec.first(); + /// let _ = string.find('l'); + /// ``` + #[clippy::version = "1.99.0"] + pub DEREF_METHOD_CALL_CHAIN, + complexity, + "redundant deref-conversion call preceding a method call" +} + declare_clippy_lint! { /// ### What it does /// @@ -4936,6 +4970,7 @@ impl_lint_pass!(Methods => [ CLONE_ON_REF_PTR, COLLAPSIBLE_STR_REPLACE, CONST_IS_EMPTY, + DEREF_METHOD_CALL_CHAIN, DOUBLE_ENDED_ITERATOR_LAST, DRAIN_COLLECT, ERR_EXPECT, @@ -5268,6 +5303,7 @@ impl Methods { fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { // Handle method calls whose receiver and arguments may not come from expansion if let Some((name, recv, args, span, call_span)) = method_call(expr) { + deref_method_call_chain::check(cx, expr, name, span, recv); match (name, args) { (sym::add | sym::sub | sym::wrapping_add | sym::wrapping_sub, [_arg]) => { zst_offset::check(cx, expr, recv); diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index 5da94bfda5e6..fb2f2a09b972 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -135,11 +135,16 @@ generate! { applicability, arg, as_bytes, + as_c_str, as_deref, as_deref_mut, as_mut, + as_mut_slice, + as_mut_str, + as_os_str, as_path, as_ptr, + as_slice, as_str, assert_failed, author, diff --git a/tests/ui/deref_method_call_chain.fixed b/tests/ui/deref_method_call_chain.fixed new file mode 100644 index 000000000000..69805bb85d9e --- /dev/null +++ b/tests/ui/deref_method_call_chain.fixed @@ -0,0 +1,129 @@ +#![warn(clippy::deref_method_call_chain)] +#![allow(clippy::needless_borrow, clippy::redundant_as_str, clippy::into_iter_on_ref)] + +mod issue2094 { + use std::ffi::{CString, OsString}; + use std::ops::Deref; + use std::path::{Path, PathBuf}; + + struct DerefWrapper(Vec); + + impl Deref for DerefWrapper { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.0 + } + } + + impl DerefWrapper { + fn as_slice(&self) -> &[u8] { + &self.0 + } + } + + struct Shadowing(Vec); + + impl Deref for Shadowing { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.0 + } + } + + impl Shadowing { + fn as_slice(&self) -> &[u8] { + &self.0 + } + + fn first(&self) -> u32 { + 42 + } + } + + struct NotDeref(String); + + impl NotDeref { + fn as_str(&self) -> &str { + &self.0 + } + } + + macro_rules! in_macro { + ($v:expr) => { + $v.as_slice().first() + }; + } + + fn check() { + let mut vec = vec![1, 2, 3]; + let mut string = String::from("hello world"); + let pathbuf = PathBuf::from("path"); + let os_string = OsString::from("hello"); + let c_string = CString::new("hello").unwrap(); + + // The conversion is redundant: the method exists on the deref target and + // is found on the original receiver through deref coercion + let _ = vec.first(); + //~^ deref_method_call_chain + let _ = vec.len(); + //~^ deref_method_call_chain + vec.sort_unstable(); + //~^ deref_method_call_chain + let _ = string.find('l'); + //~^ deref_method_call_chain + let _ = string.parse::(); + //~^ deref_method_call_chain + string.make_ascii_uppercase(); + //~^ deref_method_call_chain + let _ = pathbuf.exists(); + //~^ deref_method_call_chain + let _ = os_string.len(); + //~^ deref_method_call_chain + let _ = c_string.to_bytes(); + //~^ deref_method_call_chain + let _ = (&vec).first(); + //~^ deref_method_call_chain + let vec_ref: &Vec = &vec; + let _ = vec_ref.first(); + //~^ deref_method_call_chain + let _ = vec.first().copied(); + //~^ deref_method_call_chain + + // User types with a deref-equivalent conversion are detected too + let wrapper = DerefWrapper(vec![1, 2, 3]); + let _ = wrapper.first(); + //~^ deref_method_call_chain + + // Trait methods could resolve to a different impl on the original receiver + let _ = vec.as_slice().into_iter(); + let _ = string.as_str().to_owned(); + let _ = string.as_str().to_string(); + + // Covered by `redundant_as_str` + let _ = string.as_str().is_empty(); + let _ = string.as_str().as_bytes(); + + // Arrays don't implement `Deref` + let _ = [1, 2, 3].as_slice().first(); + + // `Path` doesn't implement `Deref` + let path: &Path = pathbuf.as_path(); + let _ = path.as_os_str().len(); + + // Not a deref-equivalent conversion (receiver doesn't implement `Deref`) + let not_deref = NotDeref(String::from("hi")); + let _ = not_deref.as_str().len(); + + // The receiver type has its own `first`, which would shadow `<[u8]>::first` + let shadowing = Shadowing(vec![1, 2, 3]); + let _ = shadowing.as_slice().first(); + + // No method follows the conversion + let _ = vec.as_slice(); + + // Not linted inside macro expansions + let _ = in_macro!(vec); + } +} + +fn main() {} diff --git a/tests/ui/deref_method_call_chain.rs b/tests/ui/deref_method_call_chain.rs new file mode 100644 index 000000000000..ca46c89cb059 --- /dev/null +++ b/tests/ui/deref_method_call_chain.rs @@ -0,0 +1,129 @@ +#![warn(clippy::deref_method_call_chain)] +#![allow(clippy::needless_borrow, clippy::redundant_as_str, clippy::into_iter_on_ref)] + +mod issue2094 { + use std::ffi::{CString, OsString}; + use std::ops::Deref; + use std::path::{Path, PathBuf}; + + struct DerefWrapper(Vec); + + impl Deref for DerefWrapper { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.0 + } + } + + impl DerefWrapper { + fn as_slice(&self) -> &[u8] { + &self.0 + } + } + + struct Shadowing(Vec); + + impl Deref for Shadowing { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.0 + } + } + + impl Shadowing { + fn as_slice(&self) -> &[u8] { + &self.0 + } + + fn first(&self) -> u32 { + 42 + } + } + + struct NotDeref(String); + + impl NotDeref { + fn as_str(&self) -> &str { + &self.0 + } + } + + macro_rules! in_macro { + ($v:expr) => { + $v.as_slice().first() + }; + } + + fn check() { + let mut vec = vec![1, 2, 3]; + let mut string = String::from("hello world"); + let pathbuf = PathBuf::from("path"); + let os_string = OsString::from("hello"); + let c_string = CString::new("hello").unwrap(); + + // The conversion is redundant: the method exists on the deref target and + // is found on the original receiver through deref coercion + let _ = vec.as_slice().first(); + //~^ deref_method_call_chain + let _ = vec.as_slice().len(); + //~^ deref_method_call_chain + vec.as_mut_slice().sort_unstable(); + //~^ deref_method_call_chain + let _ = string.as_str().find('l'); + //~^ deref_method_call_chain + let _ = string.as_str().parse::(); + //~^ deref_method_call_chain + string.as_mut_str().make_ascii_uppercase(); + //~^ deref_method_call_chain + let _ = pathbuf.as_path().exists(); + //~^ deref_method_call_chain + let _ = os_string.as_os_str().len(); + //~^ deref_method_call_chain + let _ = c_string.as_c_str().to_bytes(); + //~^ deref_method_call_chain + let _ = (&vec).as_slice().first(); + //~^ deref_method_call_chain + let vec_ref: &Vec = &vec; + let _ = vec_ref.as_slice().first(); + //~^ deref_method_call_chain + let _ = vec.as_slice().first().copied(); + //~^ deref_method_call_chain + + // User types with a deref-equivalent conversion are detected too + let wrapper = DerefWrapper(vec![1, 2, 3]); + let _ = wrapper.as_slice().first(); + //~^ deref_method_call_chain + + // Trait methods could resolve to a different impl on the original receiver + let _ = vec.as_slice().into_iter(); + let _ = string.as_str().to_owned(); + let _ = string.as_str().to_string(); + + // Covered by `redundant_as_str` + let _ = string.as_str().is_empty(); + let _ = string.as_str().as_bytes(); + + // Arrays don't implement `Deref` + let _ = [1, 2, 3].as_slice().first(); + + // `Path` doesn't implement `Deref` + let path: &Path = pathbuf.as_path(); + let _ = path.as_os_str().len(); + + // Not a deref-equivalent conversion (receiver doesn't implement `Deref`) + let not_deref = NotDeref(String::from("hi")); + let _ = not_deref.as_str().len(); + + // The receiver type has its own `first`, which would shadow `<[u8]>::first` + let shadowing = Shadowing(vec![1, 2, 3]); + let _ = shadowing.as_slice().first(); + + // No method follows the conversion + let _ = vec.as_slice(); + + // Not linted inside macro expansions + let _ = in_macro!(vec); + } +} + +fn main() {} diff --git a/tests/ui/deref_method_call_chain.stderr b/tests/ui/deref_method_call_chain.stderr new file mode 100644 index 000000000000..44984b95e58b --- /dev/null +++ b/tests/ui/deref_method_call_chain.stderr @@ -0,0 +1,83 @@ +error: this `as_slice` call is redundant as `first` can be called on the receiver directly + --> tests/ui/deref_method_call_chain.rs:66:21 + | +LL | let _ = vec.as_slice().first(); + | ^^^^^^^^^^^^^^^^ help: call the method directly: `first` + | + = note: `-D clippy::deref-method-call-chain` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::deref_method_call_chain)]` + +error: this `as_slice` call is redundant as `len` can be called on the receiver directly + --> tests/ui/deref_method_call_chain.rs:68:21 + | +LL | let _ = vec.as_slice().len(); + | ^^^^^^^^^^^^^^ help: call the method directly: `len` + +error: this `as_mut_slice` call is redundant as `sort_unstable` can be called on the receiver directly + --> tests/ui/deref_method_call_chain.rs:70:13 + | +LL | vec.as_mut_slice().sort_unstable(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: call the method directly: `sort_unstable` + +error: this `as_str` call is redundant as `find` can be called on the receiver directly + --> tests/ui/deref_method_call_chain.rs:72:24 + | +LL | let _ = string.as_str().find('l'); + | ^^^^^^^^^^^^^ help: call the method directly: `find` + +error: this `as_str` call is redundant as `parse` can be called on the receiver directly + --> tests/ui/deref_method_call_chain.rs:74:24 + | +LL | let _ = string.as_str().parse::(); + | ^^^^^^^^^^^^^^ help: call the method directly: `parse` + +error: this `as_mut_str` call is redundant as `make_ascii_uppercase` can be called on the receiver directly + --> tests/ui/deref_method_call_chain.rs:76:16 + | +LL | string.as_mut_str().make_ascii_uppercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: call the method directly: `make_ascii_uppercase` + +error: this `as_path` call is redundant as `exists` can be called on the receiver directly + --> tests/ui/deref_method_call_chain.rs:78:25 + | +LL | let _ = pathbuf.as_path().exists(); + | ^^^^^^^^^^^^^^^^ help: call the method directly: `exists` + +error: this `as_os_str` call is redundant as `len` can be called on the receiver directly + --> tests/ui/deref_method_call_chain.rs:80:27 + | +LL | let _ = os_string.as_os_str().len(); + | ^^^^^^^^^^^^^^^ help: call the method directly: `len` + +error: this `as_c_str` call is redundant as `to_bytes` can be called on the receiver directly + --> tests/ui/deref_method_call_chain.rs:82:26 + | +LL | let _ = c_string.as_c_str().to_bytes(); + | ^^^^^^^^^^^^^^^^^^^ help: call the method directly: `to_bytes` + +error: this `as_slice` call is redundant as `first` can be called on the receiver directly + --> tests/ui/deref_method_call_chain.rs:84:24 + | +LL | let _ = (&vec).as_slice().first(); + | ^^^^^^^^^^^^^^^^ help: call the method directly: `first` + +error: this `as_slice` call is redundant as `first` can be called on the receiver directly + --> tests/ui/deref_method_call_chain.rs:87:25 + | +LL | let _ = vec_ref.as_slice().first(); + | ^^^^^^^^^^^^^^^^ help: call the method directly: `first` + +error: this `as_slice` call is redundant as `first` can be called on the receiver directly + --> tests/ui/deref_method_call_chain.rs:89:21 + | +LL | let _ = vec.as_slice().first().copied(); + | ^^^^^^^^^^^^^^^^ help: call the method directly: `first` + +error: this `as_slice` call is redundant as `first` can be called on the receiver directly + --> tests/ui/deref_method_call_chain.rs:94:25 + | +LL | let _ = wrapper.as_slice().first(); + | ^^^^^^^^^^^^^^^^ help: call the method directly: `first` + +error: aborting due to 13 previous errors + From a5eb3f084c968ca6079989e0b9f14611c0bc0677 Mon Sep 17 00:00:00 2001 From: tanndlin Date: Fri, 10 Jul 2026 23:34:01 -0400 Subject: [PATCH 2/4] Run bless --- tests/ui/into_iter_without_iter.rs | 1 + tests/ui/into_iter_without_iter.stderr | 12 ++++---- tests/ui/manual_find_fixable.fixed | 2 +- tests/ui/manual_find_fixable.rs | 2 +- tests/ui/needless_for_each_fixable.fixed | 1 + tests/ui/needless_for_each_fixable.rs | 1 + tests/ui/needless_for_each_fixable.stderr | 24 ++++++++-------- tests/ui/new_without_default.fixed | 1 + tests/ui/new_without_default.rs | 1 + tests/ui/new_without_default.stderr | 32 ++++++++++----------- tests/ui/redundant_as_str.fixed | 1 + tests/ui/redundant_as_str.rs | 1 + tests/ui/redundant_as_str.stderr | 4 +-- tests/ui/return_and_then.fixed | 1 + tests/ui/return_and_then.rs | 1 + tests/ui/return_and_then.stderr | 34 +++++++++++------------ 16 files changed, 64 insertions(+), 55 deletions(-) diff --git a/tests/ui/into_iter_without_iter.rs b/tests/ui/into_iter_without_iter.rs index f0b86e5620e9..ac6fb8acd93e 100644 --- a/tests/ui/into_iter_without_iter.rs +++ b/tests/ui/into_iter_without_iter.rs @@ -1,6 +1,7 @@ //@no-rustfix: suggestions reference out of scope lifetimes/types //@aux-build:proc_macros.rs #![warn(clippy::into_iter_without_iter)] +#![allow(clippy::deref_method_call_chain)] extern crate proc_macros; use std::iter::IntoIterator; diff --git a/tests/ui/into_iter_without_iter.stderr b/tests/ui/into_iter_without_iter.stderr index a033ff645f49..63c9e1d46923 100644 --- a/tests/ui/into_iter_without_iter.stderr +++ b/tests/ui/into_iter_without_iter.stderr @@ -1,5 +1,5 @@ error: `IntoIterator` implemented for a reference type without an `iter` method - --> tests/ui/into_iter_without_iter.rs:9:1 + --> tests/ui/into_iter_without_iter.rs:10:1 | LL | / impl<'a> IntoIterator for &'a S1 { LL | | @@ -22,7 +22,7 @@ LL + } | error: `IntoIterator` implemented for a reference type without an `iter_mut` method - --> tests/ui/into_iter_without_iter.rs:17:1 + --> tests/ui/into_iter_without_iter.rs:18:1 | LL | / impl<'a> IntoIterator for &'a mut S1 { LL | | @@ -43,7 +43,7 @@ LL + } | error: `IntoIterator` implemented for a reference type without an `iter` method - --> tests/ui/into_iter_without_iter.rs:27:1 + --> tests/ui/into_iter_without_iter.rs:28:1 | LL | / impl<'a, T> IntoIterator for &'a S2 { LL | | @@ -64,7 +64,7 @@ LL + } | error: `IntoIterator` implemented for a reference type without an `iter_mut` method - --> tests/ui/into_iter_without_iter.rs:35:1 + --> tests/ui/into_iter_without_iter.rs:36:1 | LL | / impl<'a, T> IntoIterator for &'a mut S2 { LL | | @@ -85,7 +85,7 @@ LL + } | error: `IntoIterator` implemented for a reference type without an `iter_mut` method - --> tests/ui/into_iter_without_iter.rs:86:1 + --> tests/ui/into_iter_without_iter.rs:87:1 | LL | / impl<'a, T> IntoIterator for &mut S4<'a, T> { LL | | @@ -106,7 +106,7 @@ LL + } | error: `IntoIterator` implemented for a reference type without an `iter` method - --> tests/ui/into_iter_without_iter.rs:120:9 + --> tests/ui/into_iter_without_iter.rs:121:9 | LL | / impl<'a> IntoIterator for &'a Issue12037 { LL | | diff --git a/tests/ui/manual_find_fixable.fixed b/tests/ui/manual_find_fixable.fixed index f9e2ccffe3f0..e63ef9dbf852 100644 --- a/tests/ui/manual_find_fixable.fixed +++ b/tests/ui/manual_find_fixable.fixed @@ -1,5 +1,5 @@ #![warn(clippy::manual_find)] -#![allow(clippy::needless_return)] +#![allow(clippy::needless_return, clippy::deref_method_call_chain)] use std::collections::HashMap; diff --git a/tests/ui/manual_find_fixable.rs b/tests/ui/manual_find_fixable.rs index 4d5facdc5fc8..800a0c46426a 100644 --- a/tests/ui/manual_find_fixable.rs +++ b/tests/ui/manual_find_fixable.rs @@ -1,5 +1,5 @@ #![warn(clippy::manual_find)] -#![allow(clippy::needless_return)] +#![allow(clippy::needless_return, clippy::deref_method_call_chain)] use std::collections::HashMap; diff --git a/tests/ui/needless_for_each_fixable.fixed b/tests/ui/needless_for_each_fixable.fixed index d6c7e42e13d3..5f42e4312766 100644 --- a/tests/ui/needless_for_each_fixable.fixed +++ b/tests/ui/needless_for_each_fixable.fixed @@ -1,4 +1,5 @@ #![warn(clippy::needless_for_each)] +#![allow(clippy::deref_method_call_chain)] #![expect( clippy::let_unit_value, clippy::match_single_binding, diff --git a/tests/ui/needless_for_each_fixable.rs b/tests/ui/needless_for_each_fixable.rs index 9bb2e6cf07d2..a9b678969dab 100644 --- a/tests/ui/needless_for_each_fixable.rs +++ b/tests/ui/needless_for_each_fixable.rs @@ -1,4 +1,5 @@ #![warn(clippy::needless_for_each)] +#![allow(clippy::deref_method_call_chain)] #![expect( clippy::let_unit_value, clippy::match_single_binding, diff --git a/tests/ui/needless_for_each_fixable.stderr b/tests/ui/needless_for_each_fixable.stderr index 26d0da69c855..121669d15072 100644 --- a/tests/ui/needless_for_each_fixable.stderr +++ b/tests/ui/needless_for_each_fixable.stderr @@ -1,5 +1,5 @@ error: needless use of `for_each` - --> tests/ui/needless_for_each_fixable.rs:14:5 + --> tests/ui/needless_for_each_fixable.rs:15:5 | LL | / v.iter().for_each(|elem| { LL | | @@ -18,7 +18,7 @@ LL + } | error: needless use of `for_each` - --> tests/ui/needless_for_each_fixable.rs:18:5 + --> tests/ui/needless_for_each_fixable.rs:19:5 | LL | / v.into_iter().for_each(|elem| { LL | | @@ -35,7 +35,7 @@ LL + } | error: needless use of `for_each` - --> tests/ui/needless_for_each_fixable.rs:23:5 + --> tests/ui/needless_for_each_fixable.rs:24:5 | LL | / [1, 2, 3].iter().for_each(|elem| { LL | | @@ -52,7 +52,7 @@ LL + } | error: needless use of `for_each` - --> tests/ui/needless_for_each_fixable.rs:29:5 + --> tests/ui/needless_for_each_fixable.rs:30:5 | LL | / hash_map.iter().for_each(|(k, v)| { LL | | @@ -69,7 +69,7 @@ LL + } | error: needless use of `for_each` - --> tests/ui/needless_for_each_fixable.rs:33:5 + --> tests/ui/needless_for_each_fixable.rs:34:5 | LL | / hash_map.iter_mut().for_each(|(k, v)| { LL | | @@ -86,7 +86,7 @@ LL + } | error: needless use of `for_each` - --> tests/ui/needless_for_each_fixable.rs:37:5 + --> tests/ui/needless_for_each_fixable.rs:38:5 | LL | / hash_map.keys().for_each(|k| { LL | | @@ -103,7 +103,7 @@ LL + } | error: needless use of `for_each` - --> tests/ui/needless_for_each_fixable.rs:41:5 + --> tests/ui/needless_for_each_fixable.rs:42:5 | LL | / hash_map.values().for_each(|v| { LL | | @@ -120,7 +120,7 @@ LL + } | error: needless use of `for_each` - --> tests/ui/needless_for_each_fixable.rs:49:5 + --> tests/ui/needless_for_each_fixable.rs:50:5 | LL | / my_vec().iter().for_each(|elem| { LL | | @@ -137,25 +137,25 @@ LL + } | error: needless use of `for_each` - --> tests/ui/needless_for_each_fixable.rs:134:9 + --> tests/ui/needless_for_each_fixable.rs:135:9 | LL | rows.iter().for_each(|x| _ = v.push(x)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in rows.iter() { _ = v.push(x) }` error: needless use of `for_each` - --> tests/ui/needless_for_each_fixable.rs:141:9 + --> tests/ui/needless_for_each_fixable.rs:142:9 | LL | rows.iter().for_each(|x| do_something(x, 1u8)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in rows.iter() { do_something(x, 1u8); }` error: needless use of `for_each` - --> tests/ui/needless_for_each_fixable.rs:148:5 + --> tests/ui/needless_for_each_fixable.rs:149:5 | LL | vec.iter().for_each(|v| println!("{v}")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for v in vec.iter() { println!("{v}"); }` error: needless use of `for_each` - --> tests/ui/needless_for_each_fixable.rs:154:5 + --> tests/ui/needless_for_each_fixable.rs:155:5 | LL | / vec.iter().for_each(|elem| { LL | | diff --git a/tests/ui/new_without_default.fixed b/tests/ui/new_without_default.fixed index a9710996f7df..888fc68f8ba9 100644 --- a/tests/ui/new_without_default.fixed +++ b/tests/ui/new_without_default.fixed @@ -1,4 +1,5 @@ #![warn(clippy::new_without_default)] +#![allow(clippy::deref_method_call_chain)] #![expect(clippy::extra_unused_lifetimes, clippy::missing_safety_doc)] pub struct Foo; diff --git a/tests/ui/new_without_default.rs b/tests/ui/new_without_default.rs index d6a42036e401..4eba01cc63f8 100644 --- a/tests/ui/new_without_default.rs +++ b/tests/ui/new_without_default.rs @@ -1,4 +1,5 @@ #![warn(clippy::new_without_default)] +#![allow(clippy::deref_method_call_chain)] #![expect(clippy::extra_unused_lifetimes, clippy::missing_safety_doc)] pub struct Foo; diff --git a/tests/ui/new_without_default.stderr b/tests/ui/new_without_default.stderr index 63a9baa6c5a2..209ce9f116d8 100644 --- a/tests/ui/new_without_default.stderr +++ b/tests/ui/new_without_default.stderr @@ -1,5 +1,5 @@ error: you should consider adding a `Default` implementation for `Foo` - --> tests/ui/new_without_default.rs:7:5 + --> tests/ui/new_without_default.rs:8:5 | LL | / pub fn new() -> Foo { LL | | @@ -22,7 +22,7 @@ LL + } | error: you should consider adding a `Default` implementation for `Bar` - --> tests/ui/new_without_default.rs:17:5 + --> tests/ui/new_without_default.rs:18:5 | LL | / pub fn new() -> Self { LL | | @@ -43,7 +43,7 @@ LL + } | error: you should consider adding a `Default` implementation for `LtKo<'c>` - --> tests/ui/new_without_default.rs:83:5 + --> tests/ui/new_without_default.rs:84:5 | LL | / pub fn new() -> LtKo<'c> { LL | | @@ -64,7 +64,7 @@ LL + } | error: you should consider adding a `Default` implementation for `Const` - --> tests/ui/new_without_default.rs:117:5 + --> tests/ui/new_without_default.rs:118:5 | LL | / pub const fn new() -> Const { LL | | @@ -84,7 +84,7 @@ LL + } | error: you should consider adding a `Default` implementation for `NewNotEqualToDerive` - --> tests/ui/new_without_default.rs:178:5 + --> tests/ui/new_without_default.rs:179:5 | LL | / pub fn new() -> Self { LL | | @@ -105,7 +105,7 @@ LL + } | error: you should consider adding a `Default` implementation for `FooGenerics` - --> tests/ui/new_without_default.rs:188:5 + --> tests/ui/new_without_default.rs:189:5 | LL | / pub fn new() -> Self { LL | | @@ -126,7 +126,7 @@ LL + } | error: you should consider adding a `Default` implementation for `BarGenerics` - --> tests/ui/new_without_default.rs:197:5 + --> tests/ui/new_without_default.rs:198:5 | LL | / pub fn new() -> Self { LL | | @@ -147,7 +147,7 @@ LL + } | error: you should consider adding a `Default` implementation for `Foo` - --> tests/ui/new_without_default.rs:210:9 + --> tests/ui/new_without_default.rs:211:9 | LL | / pub fn new() -> Self { LL | | @@ -168,7 +168,7 @@ LL + } | error: you should consider adding a `Default` implementation for `MyStruct` - --> tests/ui/new_without_default.rs:257:5 + --> tests/ui/new_without_default.rs:258:5 | LL | / pub fn new() -> Self { LL | | @@ -191,7 +191,7 @@ LL + } | error: you should consider adding a `Default` implementation for `NewWithCfg` - --> tests/ui/new_without_default.rs:268:5 + --> tests/ui/new_without_default.rs:269:5 | LL | / pub fn new() -> Self { LL | | @@ -212,7 +212,7 @@ LL + } | error: you should consider adding a `Default` implementation for `NewWith2Cfgs` - --> tests/ui/new_without_default.rs:278:5 + --> tests/ui/new_without_default.rs:279:5 | LL | / pub fn new() -> Self { LL | | @@ -234,7 +234,7 @@ LL + } | error: you should consider adding a `Default` implementation for `NewWithExtraneous` - --> tests/ui/new_without_default.rs:287:5 + --> tests/ui/new_without_default.rs:288:5 | LL | / pub fn new() -> Self { LL | | @@ -254,7 +254,7 @@ LL + } | error: you should consider adding a `Default` implementation for `NewWithCfgAndExtraneous` - --> tests/ui/new_without_default.rs:297:5 + --> tests/ui/new_without_default.rs:298:5 | LL | / pub fn new() -> Self { LL | | @@ -275,7 +275,7 @@ LL + } | error: you should consider adding a `Default` implementation for `Foo` - --> tests/ui/new_without_default.rs:335:9 + --> tests/ui/new_without_default.rs:336:9 | LL | / pub fn new() -> Self LL | | @@ -302,7 +302,7 @@ LL + } | error: you should consider adding a `Default` implementation for `Bar` - --> tests/ui/new_without_default.rs:349:9 + --> tests/ui/new_without_default.rs:350:9 | LL | / pub fn new() -> Self LL | | @@ -328,7 +328,7 @@ LL + } | error: you should consider adding a `Default` implementation for `S` - --> tests/ui/new_without_default.rs:372:9 + --> tests/ui/new_without_default.rs:373:9 | LL | / pub fn new() -> S { LL | | diff --git a/tests/ui/redundant_as_str.fixed b/tests/ui/redundant_as_str.fixed index 33a83bddb3b8..50ff18965781 100644 --- a/tests/ui/redundant_as_str.fixed +++ b/tests/ui/redundant_as_str.fixed @@ -1,5 +1,6 @@ #![warn(clippy::redundant_as_str)] #![expect(clippy::const_is_empty)] +#![allow(clippy::deref_method_call_chain)] fn main() { let string = "Hello, world!".to_owned(); diff --git a/tests/ui/redundant_as_str.rs b/tests/ui/redundant_as_str.rs index efcfc3a12d0d..8264fd62ed52 100644 --- a/tests/ui/redundant_as_str.rs +++ b/tests/ui/redundant_as_str.rs @@ -1,5 +1,6 @@ #![warn(clippy::redundant_as_str)] #![expect(clippy::const_is_empty)] +#![allow(clippy::deref_method_call_chain)] fn main() { let string = "Hello, world!".to_owned(); diff --git a/tests/ui/redundant_as_str.stderr b/tests/ui/redundant_as_str.stderr index fc99c008220e..19a13d757338 100644 --- a/tests/ui/redundant_as_str.stderr +++ b/tests/ui/redundant_as_str.stderr @@ -1,5 +1,5 @@ error: this `as_str` is redundant and can be removed as the method immediately following exists on `String` too - --> tests/ui/redundant_as_str.rs:8:29 + --> tests/ui/redundant_as_str.rs:9:29 | LL | let _redundant = string.as_str().as_bytes(); | ^^^^^^^^^^^^^^^^^ help: try: `as_bytes` @@ -8,7 +8,7 @@ LL | let _redundant = string.as_str().as_bytes(); = help: to override `-D warnings` add `#[allow(clippy::redundant_as_str)]` error: this `as_str` is redundant and can be removed as the method immediately following exists on `String` too - --> tests/ui/redundant_as_str.rs:10:29 + --> tests/ui/redundant_as_str.rs:11:29 | LL | let _redundant = string.as_str().is_empty(); | ^^^^^^^^^^^^^^^^^ help: try: `is_empty` diff --git a/tests/ui/return_and_then.fixed b/tests/ui/return_and_then.fixed index a0a6314038aa..6cdaf0672af7 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::deref_method_call_chain)] #![expect(clippy::manual_filter)] fn main() { diff --git a/tests/ui/return_and_then.rs b/tests/ui/return_and_then.rs index 8ab52ed016dc..b32cc8f81617 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::deref_method_call_chain)] #![expect(clippy::manual_filter)] fn main() { diff --git a/tests/ui/return_and_then.stderr b/tests/ui/return_and_then.stderr index 37d92a22bf8b..e15ebce24d8c 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:6:9 + --> tests/ui/return_and_then.rs:7: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:15:9 + --> tests/ui/return_and_then.rs:16: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:20:9 + --> tests/ui/return_and_then.rs:21: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:25:9 + --> tests/ui/return_and_then.rs:26: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:30:9 + --> tests/ui/return_and_then.rs:31: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:36:9 + --> tests/ui/return_and_then.rs:37: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:42:9 + --> tests/ui/return_and_then.rs:43: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:70:13 + --> tests/ui/return_and_then.rs:71: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:78:20 + --> tests/ui/return_and_then.rs:79: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:86:20 + --> tests/ui/return_and_then.rs:87: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:98:20 + --> tests/ui/return_and_then.rs:99: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:106:13 + --> tests/ui/return_and_then.rs:107: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:115:22 + --> tests/ui/return_and_then.rs:116: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:127:21 + --> tests/ui/return_and_then.rs:128: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:142:23 + --> tests/ui/return_and_then.rs:143: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:147:32 + --> tests/ui/return_and_then.rs:148: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:152:28 + --> tests/ui/return_and_then.rs:153:28 | LL | break 'bar i.and_then(|i| if i > 3 { Some(i) } else { None }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From d6b8a952e3ed409221af30cb833698dbdde5bda1 Mon Sep 17 00:00:00 2001 From: tanndlin Date: Fri, 10 Jul 2026 23:34:09 -0400 Subject: [PATCH 3/4] Run dogfood --- lintcheck/src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lintcheck/src/main.rs b/lintcheck/src/main.rs index 6a9e6cab8957..1ea0ce81a228 100644 --- a/lintcheck/src/main.rs +++ b/lintcheck/src/main.rs @@ -472,13 +472,12 @@ fn get_perf_data_filename(source_path: &Path) -> String { .filter_map(Result::ok) .filter(|path| { path.file_name() - .as_os_str() .to_string_lossy() // We don't care about data loss, as we're checking for equality .starts_with("perf.data") }) .for_each(|path| { let file_name = path.file_name(); - let file_name = file_name.as_os_str().to_str().unwrap().split('.').next_back().unwrap(); + let file_name = file_name.to_str().unwrap().split('.').next_back().unwrap(); if let Ok(parsed_file_name) = file_name.parse::() && parsed_file_name >= max_number { From ee6b42f5c1eb88772e6a5d16b4eff42416947377 Mon Sep 17 00:00:00 2001 From: tanndlin Date: Sat, 11 Jul 2026 00:20:47 -0400 Subject: [PATCH 4/4] Gate deref_method_call_chain on DerefMut for as_mut_* conversions --- .../src/methods/deref_method_call_chain.rs | 14 +++++++++- tests/ui/deref_method_call_chain.fixed | 19 ++++++++++++++ tests/ui/deref_method_call_chain.rs | 19 ++++++++++++++ tests/ui/deref_method_call_chain.stderr | 26 +++++++++---------- 4 files changed, 64 insertions(+), 14 deletions(-) diff --git a/clippy_lints/src/methods/deref_method_call_chain.rs b/clippy_lints/src/methods/deref_method_call_chain.rs index 0aacb7d767d7..32831d3f2728 100644 --- a/clippy_lints/src/methods/deref_method_call_chain.rs +++ b/clippy_lints/src/methods/deref_method_call_chain.rs @@ -61,7 +61,7 @@ pub(super) fn check<'tcx>( // The conversion must be deref-equivalent: it returns a reference to exactly // `::Target`, so removing it lets auto-deref do the same work let recv_base_ty = cx.typeck_results().expr_ty(inner_recv).peel_refs(); - let ty::Ref(_, target_ty, _) = *cx.typeck_results().expr_ty(recv).kind() else { + let ty::Ref(_, target_ty, mutability) = *cx.typeck_results().expr_ty(recv).kind() else { return; }; let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref) else { @@ -72,6 +72,18 @@ pub(super) fn check<'tcx>( { return; } + // `as_mut_*`-style conversions return `&mut Target`, which means the outer method needs + // `&mut self`. Reaching that mutable reference via auto-deref also requires `DerefMut`, + // even though the redundant conversion itself only needed `Deref` + if mutability.is_mut() + && !cx + .tcx + .lang_items() + .deref_mut_trait() + .is_some_and(|deref_mut_trait_id| implements_trait(cx, recv_base_ty, deref_mut_trait_id, &[])) + { + return; + } // The following method must be an inherent method of the deref target. Trait methods // could resolve to a different impl on the original receiver (e.g. `IntoIterator` on diff --git a/tests/ui/deref_method_call_chain.fixed b/tests/ui/deref_method_call_chain.fixed index 69805bb85d9e..024fb568a52d 100644 --- a/tests/ui/deref_method_call_chain.fixed +++ b/tests/ui/deref_method_call_chain.fixed @@ -48,6 +48,21 @@ mod issue2094 { } } + struct DerefOnly(Vec); + + impl Deref for DerefOnly { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.0 + } + } + + impl DerefOnly { + fn as_mut_slice(&mut self) -> &mut [u8] { + &mut self.0 + } + } + macro_rules! in_macro { ($v:expr) => { $v.as_slice().first() @@ -114,6 +129,10 @@ mod issue2094 { let not_deref = NotDeref(String::from("hi")); let _ = not_deref.as_str().len(); + // `as_mut_slice` needs `DerefMut` to be removable, but this type only implements `Deref` + let mut deref_only = DerefOnly(vec![1, 2, 3]); + deref_only.as_mut_slice().sort_unstable(); + // The receiver type has its own `first`, which would shadow `<[u8]>::first` let shadowing = Shadowing(vec![1, 2, 3]); let _ = shadowing.as_slice().first(); diff --git a/tests/ui/deref_method_call_chain.rs b/tests/ui/deref_method_call_chain.rs index ca46c89cb059..c2eadbc2128e 100644 --- a/tests/ui/deref_method_call_chain.rs +++ b/tests/ui/deref_method_call_chain.rs @@ -48,6 +48,21 @@ mod issue2094 { } } + struct DerefOnly(Vec); + + impl Deref for DerefOnly { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.0 + } + } + + impl DerefOnly { + fn as_mut_slice(&mut self) -> &mut [u8] { + &mut self.0 + } + } + macro_rules! in_macro { ($v:expr) => { $v.as_slice().first() @@ -114,6 +129,10 @@ mod issue2094 { let not_deref = NotDeref(String::from("hi")); let _ = not_deref.as_str().len(); + // `as_mut_slice` needs `DerefMut` to be removable, but this type only implements `Deref` + let mut deref_only = DerefOnly(vec![1, 2, 3]); + deref_only.as_mut_slice().sort_unstable(); + // The receiver type has its own `first`, which would shadow `<[u8]>::first` let shadowing = Shadowing(vec![1, 2, 3]); let _ = shadowing.as_slice().first(); diff --git a/tests/ui/deref_method_call_chain.stderr b/tests/ui/deref_method_call_chain.stderr index 44984b95e58b..532ede643ee5 100644 --- a/tests/ui/deref_method_call_chain.stderr +++ b/tests/ui/deref_method_call_chain.stderr @@ -1,5 +1,5 @@ error: this `as_slice` call is redundant as `first` can be called on the receiver directly - --> tests/ui/deref_method_call_chain.rs:66:21 + --> tests/ui/deref_method_call_chain.rs:81:21 | LL | let _ = vec.as_slice().first(); | ^^^^^^^^^^^^^^^^ help: call the method directly: `first` @@ -8,73 +8,73 @@ LL | let _ = vec.as_slice().first(); = help: to override `-D warnings` add `#[allow(clippy::deref_method_call_chain)]` error: this `as_slice` call is redundant as `len` can be called on the receiver directly - --> tests/ui/deref_method_call_chain.rs:68:21 + --> tests/ui/deref_method_call_chain.rs:83:21 | LL | let _ = vec.as_slice().len(); | ^^^^^^^^^^^^^^ help: call the method directly: `len` error: this `as_mut_slice` call is redundant as `sort_unstable` can be called on the receiver directly - --> tests/ui/deref_method_call_chain.rs:70:13 + --> tests/ui/deref_method_call_chain.rs:85:13 | LL | vec.as_mut_slice().sort_unstable(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: call the method directly: `sort_unstable` error: this `as_str` call is redundant as `find` can be called on the receiver directly - --> tests/ui/deref_method_call_chain.rs:72:24 + --> tests/ui/deref_method_call_chain.rs:87:24 | LL | let _ = string.as_str().find('l'); | ^^^^^^^^^^^^^ help: call the method directly: `find` error: this `as_str` call is redundant as `parse` can be called on the receiver directly - --> tests/ui/deref_method_call_chain.rs:74:24 + --> tests/ui/deref_method_call_chain.rs:89:24 | LL | let _ = string.as_str().parse::(); | ^^^^^^^^^^^^^^ help: call the method directly: `parse` error: this `as_mut_str` call is redundant as `make_ascii_uppercase` can be called on the receiver directly - --> tests/ui/deref_method_call_chain.rs:76:16 + --> tests/ui/deref_method_call_chain.rs:91:16 | LL | string.as_mut_str().make_ascii_uppercase(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: call the method directly: `make_ascii_uppercase` error: this `as_path` call is redundant as `exists` can be called on the receiver directly - --> tests/ui/deref_method_call_chain.rs:78:25 + --> tests/ui/deref_method_call_chain.rs:93:25 | LL | let _ = pathbuf.as_path().exists(); | ^^^^^^^^^^^^^^^^ help: call the method directly: `exists` error: this `as_os_str` call is redundant as `len` can be called on the receiver directly - --> tests/ui/deref_method_call_chain.rs:80:27 + --> tests/ui/deref_method_call_chain.rs:95:27 | LL | let _ = os_string.as_os_str().len(); | ^^^^^^^^^^^^^^^ help: call the method directly: `len` error: this `as_c_str` call is redundant as `to_bytes` can be called on the receiver directly - --> tests/ui/deref_method_call_chain.rs:82:26 + --> tests/ui/deref_method_call_chain.rs:97:26 | LL | let _ = c_string.as_c_str().to_bytes(); | ^^^^^^^^^^^^^^^^^^^ help: call the method directly: `to_bytes` error: this `as_slice` call is redundant as `first` can be called on the receiver directly - --> tests/ui/deref_method_call_chain.rs:84:24 + --> tests/ui/deref_method_call_chain.rs:99:24 | LL | let _ = (&vec).as_slice().first(); | ^^^^^^^^^^^^^^^^ help: call the method directly: `first` error: this `as_slice` call is redundant as `first` can be called on the receiver directly - --> tests/ui/deref_method_call_chain.rs:87:25 + --> tests/ui/deref_method_call_chain.rs:102:25 | LL | let _ = vec_ref.as_slice().first(); | ^^^^^^^^^^^^^^^^ help: call the method directly: `first` error: this `as_slice` call is redundant as `first` can be called on the receiver directly - --> tests/ui/deref_method_call_chain.rs:89:21 + --> tests/ui/deref_method_call_chain.rs:104:21 | LL | let _ = vec.as_slice().first().copied(); | ^^^^^^^^^^^^^^^^ help: call the method directly: `first` error: this `as_slice` call is redundant as `first` can be called on the receiver directly - --> tests/ui/deref_method_call_chain.rs:94:25 + --> tests/ui/deref_method_call_chain.rs:109:25 | LL | let _ = wrapper.as_slice().first(); | ^^^^^^^^^^^^^^^^ help: call the method directly: `first`