about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-10-27 05:59:56 +0000
committerbors <bors@rust-lang.org>2022-10-27 05:59:56 +0000
commit40af5be525903f0df63cd738eff97c17baafa2a1 (patch)
treef63a118a6f9ae85d5ccab9ce244277a1cbceb634
parent70187c7d112414e40f877aed9787258e5770467d (diff)
parent83771c524225176616db45ebe67045a6f89b7117 (diff)
downloadrust-40af5be525903f0df63cd738eff97c17baafa2a1.tar.gz
rust-40af5be525903f0df63cd738eff97c17baafa2a1.zip
Auto merge of #9674 - smoelius:needless-borrow-fp, r=Jarcho
Fix `needless_borrow` false positive

The PR fixes the false positive exposed by `@BusyJay's` example in: https://github.com/rust-lang/rust-clippy/issues/9111#issuecomment-1277114280

The current approach is described in https://github.com/rust-lang/rust-clippy/pull/9674#issuecomment-1289294201 and https://github.com/rust-lang/rust-clippy/pull/9674#issuecomment-1292225232.

The original approach appears below.

---

The proposed fix is to flag only "simple" trait implementations involving references, a concept
that I introduce next.

Intuitively, a trait implementation is "simple" if all it does is dereference and apply the trait
implementation of a type named by a type parameter. `AsRef` provides a good example of a simple
implementation: https://doc.rust-lang.org/std/convert/trait.AsRef.html#impl-AsRef%3CU%3E-for-%26T

We can make this idea more precise as follows. Given a trait implementation, first determine
whether the implementation is "used defined." If so, then examine its nested obligations.
Consider the implementation simple if-and-only-if:
- there is at least one nested obligation for the same trait
- for each type `X` in the nested obligation's substitution, either `X` is the same as that of
  the original obligation's substitution, or the original type is `&X`

For example, the following implementation from `@BusyJay's` example is "complex" (i.e., not simple)
because it produces no nested obligations:

```rust
impl<'a> Extend<&'a u8> for A { ... }
```

On the other hand, the following slightly modified implementation is simple, because it produces
a nested obligation for `Extend<X>`:

```rust
impl<'a, X> Extend<&'a X> for A where A: Extend<X> { ... }
```

How does flagging only simple implementations help? One way of interpreting the false positive in
`@BusyJay's` example is that it separates a reference from a concrete type. Doing so turns a
successful type inference into a failing one. By flagging only simple implementations, we
separate references from type variables only, thereby eliminating this class of false positives.

Note that `Deref` is a special case, as the obligations generated for it already involve the
underlying type.

r? `@Jarcho` (Sorry to keep pinging you with `needless_borrow` stuff. But my impression is no one knows this code better than you.)

changelog: fix `needless_borrow` false positive
-rw-r--r--clippy_lints/src/dereference.rs47
-rw-r--r--tests/ui/needless_borrow.fixed23
-rw-r--r--tests/ui/needless_borrow.rs23
3 files changed, 90 insertions, 3 deletions
diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs
index 7b5c10db4e1..7e2e32a20d4 100644
--- a/clippy_lints/src/dereference.rs
+++ b/clippy_lints/src/dereference.rs
@@ -1049,7 +1049,7 @@ fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool {
 // If the conditions are met, returns `Some(Position::ImplArg(..))`; otherwise, returns `None`.
 //   The "is copyable" condition is to avoid the case where removing the `&` means `e` would have to
 // be moved, but it cannot be.
-#[expect(clippy::too_many_arguments)]
+#[expect(clippy::too_many_arguments, clippy::too_many_lines)]
 fn needless_borrow_impl_arg_position<'tcx>(
     cx: &LateContext<'tcx>,
     possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
@@ -1092,7 +1092,7 @@ fn needless_borrow_impl_arg_position<'tcx>(
         .iter()
         .filter_map(|predicate| {
             if let PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder()
-                && trait_predicate.trait_ref.self_ty() == param_ty.to_ty(cx.tcx)
+                && trait_predicate.self_ty() == param_ty.to_ty(cx.tcx)
             {
                 Some(trait_predicate.trait_ref.def_id)
             } else {
@@ -1111,6 +1111,16 @@ fn needless_borrow_impl_arg_position<'tcx>(
         return Position::Other(precedence);
     }
 
+    // See:
+    // - https://github.com/rust-lang/rust-clippy/pull/9674#issuecomment-1289294201
+    // - https://github.com/rust-lang/rust-clippy/pull/9674#issuecomment-1292225232
+    if projection_predicates
+        .iter()
+        .any(|projection_predicate| is_mixed_projection_predicate(cx, callee_def_id, projection_predicate))
+    {
+        return Position::Other(precedence);
+    }
+
     // `substs_with_referent_ty` can be constructed outside of `check_referent` because the same
     // elements are modified each time `check_referent` is called.
     let mut substs_with_referent_ty = substs_with_expr_ty.to_vec();
@@ -1190,8 +1200,39 @@ fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool {
         })
 }
 
-fn referent_used_exactly_once<'tcx>(
+fn is_mixed_projection_predicate<'tcx>(
     cx: &LateContext<'tcx>,
+    callee_def_id: DefId,
+    projection_predicate: &ProjectionPredicate<'tcx>,
+) -> bool {
+    let generics = cx.tcx.generics_of(callee_def_id);
+    // The predicate requires the projected type to equal a type parameter from the parent context.
+    if let Some(term_ty) = projection_predicate.term.ty()
+        && let ty::Param(term_param_ty) = term_ty.kind()
+        && (term_param_ty.index as usize) < generics.parent_count
+    {
+        // The inner-most self type is a type parameter from the current function.
+        let mut projection_ty = projection_predicate.projection_ty;
+        loop {
+            match projection_ty.self_ty().kind() {
+                ty::Projection(inner_projection_ty) => {
+                    projection_ty = *inner_projection_ty;
+                }
+                ty::Param(param_ty) => {
+                    return (param_ty.index as usize) >= generics.parent_count;
+                }
+                _ => {
+                    return false;
+                }
+            }
+        }
+    } else {
+        false
+    }
+}
+
+fn referent_used_exactly_once<'a, 'tcx>(
+    cx: &'a LateContext<'tcx>,
     possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
     reference: &Expr<'tcx>,
 ) -> bool {
diff --git a/tests/ui/needless_borrow.fixed b/tests/ui/needless_borrow.fixed
index 340e89d2db1..57a682d62c0 100644
--- a/tests/ui/needless_borrow.fixed
+++ b/tests/ui/needless_borrow.fixed
@@ -385,3 +385,26 @@ mod used_more_than_once {
     fn use_x(_: impl AsRef<str>) {}
     fn use_x_again(_: impl AsRef<str>) {}
 }
+
+// https://github.com/rust-lang/rust-clippy/issues/9111#issuecomment-1277114280
+#[allow(dead_code)]
+mod issue_9111 {
+    struct A;
+
+    impl Extend<u8> for A {
+        fn extend<T: IntoIterator<Item = u8>>(&mut self, _: T) {
+            unimplemented!()
+        }
+    }
+
+    impl<'a> Extend<&'a u8> for A {
+        fn extend<T: IntoIterator<Item = &'a u8>>(&mut self, _: T) {
+            unimplemented!()
+        }
+    }
+
+    fn main() {
+        let mut a = A;
+        a.extend(&[]); // vs a.extend([]);
+    }
+}
diff --git a/tests/ui/needless_borrow.rs b/tests/ui/needless_borrow.rs
index c93711ac8e2..0d325b48ab8 100644
--- a/tests/ui/needless_borrow.rs
+++ b/tests/ui/needless_borrow.rs
@@ -385,3 +385,26 @@ mod used_more_than_once {
     fn use_x(_: impl AsRef<str>) {}
     fn use_x_again(_: impl AsRef<str>) {}
 }
+
+// https://github.com/rust-lang/rust-clippy/issues/9111#issuecomment-1277114280
+#[allow(dead_code)]
+mod issue_9111 {
+    struct A;
+
+    impl Extend<u8> for A {
+        fn extend<T: IntoIterator<Item = u8>>(&mut self, _: T) {
+            unimplemented!()
+        }
+    }
+
+    impl<'a> Extend<&'a u8> for A {
+        fn extend<T: IntoIterator<Item = &'a u8>>(&mut self, _: T) {
+            unimplemented!()
+        }
+    }
+
+    fn main() {
+        let mut a = A;
+        a.extend(&[]); // vs a.extend([]);
+    }
+}