Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 6d21b79

Browse files
committed
Fix needless_borrow suggestion when calling a trait method taking self
1 parent 0b4ba73 commit 6d21b79

File tree

4 files changed

+117
-12
lines changed

4 files changed

+117
-12
lines changed

clippy_lints/src/dereference.rs

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ use rustc_hir::{
1111
ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem,
1212
TraitItemKind, TyKind, UnOp,
1313
};
14+
use rustc_infer::infer::TyCtxtInferExt;
1415
use rustc_lint::{LateContext, LateLintPass};
1516
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
1617
use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeckResults};
1718
use rustc_session::{declare_tool_lint, impl_lint_pass};
1819
use rustc_span::{symbol::sym, Span, Symbol};
20+
use rustc_trait_selection::infer::InferCtxtExt;
1921

2022
declare_clippy_lint! {
2123
/// ### What it does
@@ -165,7 +167,6 @@ struct StateData {
165167

166168
struct DerefedBorrow {
167169
count: usize,
168-
required_precedence: i8,
169170
msg: &'static str,
170171
position: Position,
171172
}
@@ -329,19 +330,19 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
329330
"this expression creates a reference which is immediately dereferenced by the compiler";
330331
let borrow_msg = "this expression borrows a value the compiler would automatically borrow";
331332

332-
let (required_refs, required_precedence, msg) = if position.can_auto_borrow() {
333-
(1, PREC_POSTFIX, if deref_count == 1 { borrow_msg } else { deref_msg })
333+
let (required_refs, msg) = if position.can_auto_borrow() {
334+
(1, if deref_count == 1 { borrow_msg } else { deref_msg })
334335
} else if let Some(&Adjust::Borrow(AutoBorrow::Ref(_, mutability))) =
335336
next_adjust.map(|a| &a.kind)
336337
{
337338
if matches!(mutability, AutoBorrowMutability::Mut { .. }) && !position.is_reborrow_stable()
338339
{
339-
(3, 0, deref_msg)
340+
(3, deref_msg)
340341
} else {
341-
(2, 0, deref_msg)
342+
(2, deref_msg)
342343
}
343344
} else {
344-
(2, 0, deref_msg)
345+
(2, deref_msg)
345346
};
346347

347348
if deref_count >= required_refs {
@@ -350,7 +351,6 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
350351
// One of the required refs is for the current borrow expression, the remaining ones
351352
// can't be removed without breaking the code. See earlier comment.
352353
count: deref_count - required_refs,
353-
required_precedence,
354354
msg,
355355
position,
356356
}),
@@ -601,6 +601,8 @@ fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool {
601601
#[derive(Clone, Copy)]
602602
enum Position {
603603
MethodReceiver,
604+
/// The method is defined on a reference type. e.g. `impl Foo for &T`
605+
MethodReceiverRefImpl,
604606
Callee,
605607
FieldAccess(Symbol),
606608
Postfix,
@@ -627,6 +629,13 @@ impl Position {
627629
fn lint_explicit_deref(self) -> bool {
628630
matches!(self, Self::Other | Self::DerefStable | Self::ReborrowStable)
629631
}
632+
633+
fn needs_parens(self, precedence: i8) -> bool {
634+
matches!(
635+
self,
636+
Self::MethodReceiver | Self::MethodReceiverRefImpl | Self::Callee | Self::FieldAccess(_) | Self::Postfix
637+
) && precedence < PREC_POSTFIX
638+
}
630639
}
631640

632641
/// Walks up the parent expressions attempting to determine both how stable the auto-deref result
@@ -730,10 +739,34 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &
730739
let id = cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap();
731740
args.iter().position(|arg| arg.hir_id == child_id).map(|i| {
732741
if i == 0 {
733-
if e.hir_id == child_id {
734-
Position::MethodReceiver
735-
} else {
742+
// Check for calls to trait methods where the trait is implemented on a reference.
743+
// Two cases need to be handled:
744+
// * `self` methods on `&T` will never have auto-borrow
745+
// * `&self` methods on `&T` can have auto-borrow, but `&self` methods on `T` will take
746+
// priority.
747+
if e.hir_id != child_id {
736748
Position::ReborrowStable
749+
} else if let Some(trait_id) = cx.tcx.trait_of_item(id)
750+
&& let arg_ty = cx.tcx.erase_regions(cx.typeck_results().expr_ty_adjusted(e))
751+
&& let ty::Ref(_, sub_ty, _) = *arg_ty.kind()
752+
&& let subs = cx.typeck_results().node_substs_opt(child_id).unwrap_or_else(
753+
|| cx.tcx.mk_substs([].iter())
754+
) && let impl_ty = if cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref() {
755+
// Trait methods taking `&self`
756+
sub_ty
757+
} else {
758+
// Trait methods taking `self`
759+
arg_ty
760+
} && impl_ty.is_ref()
761+
&& cx.tcx.infer_ctxt().enter(|infcx|
762+
infcx
763+
.type_implements_trait(trait_id, impl_ty, subs, cx.param_env)
764+
.must_apply_modulo_regions()
765+
)
766+
{
767+
Position::MethodReceiverRefImpl
768+
} else {
769+
Position::MethodReceiver
737770
}
738771
} else {
739772
param_auto_deref_stability(cx.tcx.fn_sig(id).skip_binder().inputs()[i])
@@ -964,7 +997,7 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
964997
let mut app = Applicability::MachineApplicable;
965998
let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0;
966999
span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, state.msg, |diag| {
967-
let sugg = if state.required_precedence > expr.precedence().order() && !has_enclosing_paren(&snip) {
1000+
let sugg = if state.position.needs_parens(expr.precedence().order()) && !has_enclosing_paren(&snip) {
9681001
format!("({})", snip)
9691002
} else {
9701003
snip.into()

tests/ui/needless_borrow.fixed

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,36 @@ fn main() {
8585
let _ = x.0;
8686
let x = &x as *const (i32, i32);
8787
let _ = unsafe { (*x).0 };
88+
89+
// Issue #8367
90+
trait Foo {
91+
fn foo(self);
92+
}
93+
impl Foo for &'_ () {
94+
fn foo(self) {}
95+
}
96+
(&()).foo(); // Don't lint. `()` doesn't implement `Foo`
97+
(&()).foo();
98+
99+
impl Foo for i32 {
100+
fn foo(self) {}
101+
}
102+
impl Foo for &'_ i32 {
103+
fn foo(self) {}
104+
}
105+
(&5).foo(); // Don't lint. `5` will call `<i32 as Foo>::foo`
106+
(&5).foo();
107+
108+
trait FooRef {
109+
fn foo_ref(&self);
110+
}
111+
impl FooRef for () {
112+
fn foo_ref(&self) {}
113+
}
114+
impl FooRef for &'_ () {
115+
fn foo_ref(&self) {}
116+
}
117+
(&&()).foo_ref(); // Don't lint. `&()` will call `<() as FooRef>::foo_ref`
88118
}
89119

90120
#[allow(clippy::needless_borrowed_reference)]

tests/ui/needless_borrow.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,36 @@ fn main() {
8585
let _ = (&x).0;
8686
let x = &x as *const (i32, i32);
8787
let _ = unsafe { (&*x).0 };
88+
89+
// Issue #8367
90+
trait Foo {
91+
fn foo(self);
92+
}
93+
impl Foo for &'_ () {
94+
fn foo(self) {}
95+
}
96+
(&()).foo(); // Don't lint. `()` doesn't implement `Foo`
97+
(&&()).foo();
98+
99+
impl Foo for i32 {
100+
fn foo(self) {}
101+
}
102+
impl Foo for &'_ i32 {
103+
fn foo(self) {}
104+
}
105+
(&5).foo(); // Don't lint. `5` will call `<i32 as Foo>::foo`
106+
(&&5).foo();
107+
108+
trait FooRef {
109+
fn foo_ref(&self);
110+
}
111+
impl FooRef for () {
112+
fn foo_ref(&self) {}
113+
}
114+
impl FooRef for &'_ () {
115+
fn foo_ref(&self) {}
116+
}
117+
(&&()).foo_ref(); // Don't lint. `&()` will call `<() as FooRef>::foo_ref`
88118
}
89119

90120
#[allow(clippy::needless_borrowed_reference)]

tests/ui/needless_borrow.stderr

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,17 @@ error: this expression borrows a value the compiler would automatically borrow
108108
LL | let _ = unsafe { (&*x).0 };
109109
| ^^^^^ help: change this to: `(*x)`
110110

111-
error: aborting due to 18 previous errors
111+
error: this expression creates a reference which is immediately dereferenced by the compiler
112+
--> $DIR/needless_borrow.rs:97:5
113+
|
114+
LL | (&&()).foo();
115+
| ^^^^^^ help: change this to: `(&())`
116+
117+
error: this expression creates a reference which is immediately dereferenced by the compiler
118+
--> $DIR/needless_borrow.rs:106:5
119+
|
120+
LL | (&&5).foo();
121+
| ^^^^^ help: change this to: `(&5)`
122+
123+
error: aborting due to 20 previous errors
112124

0 commit comments

Comments
 (0)