Skip to content

Commit 40af5be

Browse files
committedOct 27, 2022
Auto merge of rust-lang#9674 - smoelius:needless-borrow-fp, r=Jarcho
Fix `needless_borrow` false positive The PR fixes the false positive exposed by `@BusyJay's` example in: rust-lang/rust-clippy#9111 (comment) The current approach is described in rust-lang/rust-clippy#9674 (comment) and rust-lang/rust-clippy#9674 (comment). 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
2 parents 70187c7 + 83771c5 commit 40af5be

File tree

3 files changed

+90
-3
lines changed

3 files changed

+90
-3
lines changed
 

‎clippy_lints/src/dereference.rs

+44-3
Original file line numberDiff line numberDiff line change
@@ -1049,7 +1049,7 @@ fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool {
10491049
// If the conditions are met, returns `Some(Position::ImplArg(..))`; otherwise, returns `None`.
10501050
// The "is copyable" condition is to avoid the case where removing the `&` means `e` would have to
10511051
// be moved, but it cannot be.
1052-
#[expect(clippy::too_many_arguments)]
1052+
#[expect(clippy::too_many_arguments, clippy::too_many_lines)]
10531053
fn needless_borrow_impl_arg_position<'tcx>(
10541054
cx: &LateContext<'tcx>,
10551055
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
@@ -1092,7 +1092,7 @@ fn needless_borrow_impl_arg_position<'tcx>(
10921092
.iter()
10931093
.filter_map(|predicate| {
10941094
if let PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder()
1095-
&& trait_predicate.trait_ref.self_ty() == param_ty.to_ty(cx.tcx)
1095+
&& trait_predicate.self_ty() == param_ty.to_ty(cx.tcx)
10961096
{
10971097
Some(trait_predicate.trait_ref.def_id)
10981098
} else {
@@ -1111,6 +1111,16 @@ fn needless_borrow_impl_arg_position<'tcx>(
11111111
return Position::Other(precedence);
11121112
}
11131113

1114+
// See:
1115+
// - https://github.com/rust-lang/rust-clippy/pull/9674#issuecomment-1289294201
1116+
// - https://github.com/rust-lang/rust-clippy/pull/9674#issuecomment-1292225232
1117+
if projection_predicates
1118+
.iter()
1119+
.any(|projection_predicate| is_mixed_projection_predicate(cx, callee_def_id, projection_predicate))
1120+
{
1121+
return Position::Other(precedence);
1122+
}
1123+
11141124
// `substs_with_referent_ty` can be constructed outside of `check_referent` because the same
11151125
// elements are modified each time `check_referent` is called.
11161126
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 {
11901200
})
11911201
}
11921202

1193-
fn referent_used_exactly_once<'tcx>(
1203+
fn is_mixed_projection_predicate<'tcx>(
11941204
cx: &LateContext<'tcx>,
1205+
callee_def_id: DefId,
1206+
projection_predicate: &ProjectionPredicate<'tcx>,
1207+
) -> bool {
1208+
let generics = cx.tcx.generics_of(callee_def_id);
1209+
// The predicate requires the projected type to equal a type parameter from the parent context.
1210+
if let Some(term_ty) = projection_predicate.term.ty()
1211+
&& let ty::Param(term_param_ty) = term_ty.kind()
1212+
&& (term_param_ty.index as usize) < generics.parent_count
1213+
{
1214+
// The inner-most self type is a type parameter from the current function.
1215+
let mut projection_ty = projection_predicate.projection_ty;
1216+
loop {
1217+
match projection_ty.self_ty().kind() {
1218+
ty::Projection(inner_projection_ty) => {
1219+
projection_ty = *inner_projection_ty;
1220+
}
1221+
ty::Param(param_ty) => {
1222+
return (param_ty.index as usize) >= generics.parent_count;
1223+
}
1224+
_ => {
1225+
return false;
1226+
}
1227+
}
1228+
}
1229+
} else {
1230+
false
1231+
}
1232+
}
1233+
1234+
fn referent_used_exactly_once<'a, 'tcx>(
1235+
cx: &'a LateContext<'tcx>,
11951236
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
11961237
reference: &Expr<'tcx>,
11971238
) -> bool {

‎tests/ui/needless_borrow.fixed

+23
Original file line numberDiff line numberDiff line change
@@ -385,3 +385,26 @@ mod used_more_than_once {
385385
fn use_x(_: impl AsRef<str>) {}
386386
fn use_x_again(_: impl AsRef<str>) {}
387387
}
388+
389+
// https://github.com/rust-lang/rust-clippy/issues/9111#issuecomment-1277114280
390+
#[allow(dead_code)]
391+
mod issue_9111 {
392+
struct A;
393+
394+
impl Extend<u8> for A {
395+
fn extend<T: IntoIterator<Item = u8>>(&mut self, _: T) {
396+
unimplemented!()
397+
}
398+
}
399+
400+
impl<'a> Extend<&'a u8> for A {
401+
fn extend<T: IntoIterator<Item = &'a u8>>(&mut self, _: T) {
402+
unimplemented!()
403+
}
404+
}
405+
406+
fn main() {
407+
let mut a = A;
408+
a.extend(&[]); // vs a.extend([]);
409+
}
410+
}

‎tests/ui/needless_borrow.rs

+23
Original file line numberDiff line numberDiff line change
@@ -385,3 +385,26 @@ mod used_more_than_once {
385385
fn use_x(_: impl AsRef<str>) {}
386386
fn use_x_again(_: impl AsRef<str>) {}
387387
}
388+
389+
// https://github.com/rust-lang/rust-clippy/issues/9111#issuecomment-1277114280
390+
#[allow(dead_code)]
391+
mod issue_9111 {
392+
struct A;
393+
394+
impl Extend<u8> for A {
395+
fn extend<T: IntoIterator<Item = u8>>(&mut self, _: T) {
396+
unimplemented!()
397+
}
398+
}
399+
400+
impl<'a> Extend<&'a u8> for A {
401+
fn extend<T: IntoIterator<Item = &'a u8>>(&mut self, _: T) {
402+
unimplemented!()
403+
}
404+
}
405+
406+
fn main() {
407+
let mut a = A;
408+
a.extend(&[]); // vs a.extend([]);
409+
}
410+
}

0 commit comments

Comments
 (0)