Skip to content

Commit 5a6a6d6

Browse files
committed
Fix needless_borrow suggestion when calling a trait method taking self
1 parent 41a9597 commit 5a6a6d6

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, TyS, 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
@@ -155,7 +157,6 @@ pub struct Dereferencing {
155157

156158
struct DerefedBorrow {
157159
count: usize,
158-
required_precedence: i8,
159160
msg: &'static str,
160161
position: Position,
161162
}
@@ -312,19 +313,19 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
312313
"this expression creates a reference which is immediately dereferenced by the compiler";
313314
let borrow_msg = "this expression borrows a value the compiler would automatically borrow";
314315

315-
let (required_refs, required_precedence, msg) = if position.can_auto_borrow() {
316-
(1, PREC_POSTFIX, if deref_count == 1 { borrow_msg } else { deref_msg })
316+
let (required_refs, msg) = if position.can_auto_borrow() {
317+
(1, if deref_count == 1 { borrow_msg } else { deref_msg })
317318
} else if let Some(&Adjust::Borrow(AutoBorrow::Ref(_, mutability))) =
318319
next_adjust.map(|a| &a.kind)
319320
{
320321
if matches!(mutability, AutoBorrowMutability::Mut { .. }) && !position.is_reborrow_stable()
321322
{
322-
(3, 0, deref_msg)
323+
(3, deref_msg)
323324
} else {
324-
(2, 0, deref_msg)
325+
(2, deref_msg)
325326
}
326327
} else {
327-
(2, 0, deref_msg)
328+
(2, deref_msg)
328329
};
329330

330331
if deref_count >= required_refs {
@@ -333,7 +334,6 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
333334
// One of the required refs is for the current borrow expression, the remaining ones
334335
// can't be removed without breaking the code. See earlier comment.
335336
count: deref_count - required_refs,
336-
required_precedence,
337337
msg,
338338
position,
339339
}),
@@ -542,6 +542,8 @@ fn deref_method_same_type(result_ty: Ty<'_>, arg_ty: Ty<'_>) -> bool {
542542
#[derive(Clone, Copy)]
543543
enum Position {
544544
MethodReceiver,
545+
/// The method is defined on a reference type. e.g. `impl Foo for &T`
546+
MethodReceiverRefImpl,
545547
Callee,
546548
FieldAccess(Symbol),
547549
Postfix,
@@ -568,6 +570,13 @@ impl Position {
568570
fn lint_explicit_deref(self) -> bool {
569571
matches!(self, Self::Other | Self::DerefStable | Self::ReborrowStable)
570572
}
573+
574+
fn needs_parens(self, precedence: i8) -> bool {
575+
matches!(
576+
self,
577+
Self::MethodReceiver | Self::MethodReceiverRefImpl | Self::Callee | Self::FieldAccess(_) | Self::Postfix
578+
) && precedence < PREC_POSTFIX
579+
}
571580
}
572581

573582
/// Walks up the parent expressions attempting to determine both how stable the auto-deref result
@@ -673,10 +682,34 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &
673682
let id = cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap();
674683
args.iter().position(|arg| arg.hir_id == child_id).map(|i| {
675684
if i == 0 {
676-
if e.hir_id == child_id {
677-
Position::MethodReceiver
678-
} else {
685+
// Check for calls to trait methods where the trait is implemented on a reference.
686+
// Two cases need to be handled:
687+
// * `self` methods on `&T` will never have auto-borrow
688+
// * `&self` methods on `&T` can have auto-borrow, but `&self` methods on `T` will take
689+
// priority.
690+
if e.hir_id != child_id {
679691
Position::ReborrowStable
692+
} else if let Some(trait_id) = cx.tcx.trait_of_item(id)
693+
&& let arg_ty = cx.tcx.erase_regions(cx.typeck_results().expr_ty_adjusted(e))
694+
&& let ty::Ref(_, sub_ty, _) = *arg_ty.kind()
695+
&& let subs = cx.typeck_results().node_substs_opt(child_id).unwrap_or_else(
696+
|| cx.tcx.mk_substs([].iter())
697+
) && let impl_ty = if cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref() {
698+
// Trait methods taking `&self`
699+
sub_ty
700+
} else {
701+
// Trait methods taking `self`
702+
arg_ty
703+
} && impl_ty.is_ref()
704+
&& cx.tcx.infer_ctxt().enter(|infcx|
705+
infcx
706+
.type_implements_trait(trait_id, impl_ty, subs, cx.param_env)
707+
.must_apply_modulo_regions()
708+
)
709+
{
710+
Position::MethodReceiverRefImpl
711+
} else {
712+
Position::MethodReceiver
680713
}
681714
} else {
682715
param_auto_deref_stability(cx.tcx.fn_sig(id).skip_binder().inputs()[i])
@@ -912,7 +945,7 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, span: Span) {
912945
span,
913946
state.msg,
914947
"change this to",
915-
if state.required_precedence > expr.precedence().order() && !has_enclosing_paren(&snip) {
948+
if state.position.needs_parens(expr.precedence().order()) && !has_enclosing_paren(&snip) {
916949
format!("({})", snip)
917950
} else {
918951
snip.into()

tests/ui/needless_borrow.fixed

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

88118
#[allow(clippy::needless_borrowed_reference)]

tests/ui/needless_borrow.rs

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

88118
#[allow(clippy::needless_borrowed_reference)]

tests/ui/needless_borrow.stderr

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

129-
error: aborting due to 21 previous errors
129+
error: this expression creates a reference which is immediately dereferenced by the compiler
130+
--> $DIR/needless_borrow.rs:95:5
131+
|
132+
LL | (&&()).foo();
133+
| ^^^^^^ help: change this to: `(&())`
134+
135+
error: this expression creates a reference which is immediately dereferenced by the compiler
136+
--> $DIR/needless_borrow.rs:104:5
137+
|
138+
LL | (&&5).foo();
139+
| ^^^^^ help: change this to: `(&5)`
140+
141+
error: aborting due to 23 previous errors
130142

0 commit comments

Comments
 (0)