Skip to content

Commit df560fa

Browse files
committed
Enhance needless_borrow to consider trait implementations
1 parent 4562dd0 commit df560fa

File tree

4 files changed

+160
-19
lines changed

4 files changed

+160
-19
lines changed

clippy_lints/src/dereference.rs

Lines changed: 137 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
22
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
33
use clippy_utils::sugg::has_enclosing_paren;
4-
use clippy_utils::ty::{expr_sig, peel_mid_ty_refs, variant_of_res};
5-
use clippy_utils::{get_parent_expr, is_lint_allowed, path_to_local, walk_to_expr_usage};
4+
use clippy_utils::ty::{contains_ty, expr_sig, implements_trait, is_copy, peel_mid_ty_refs, variant_of_res};
5+
use clippy_utils::{fn_def_id, get_parent_expr, is_lint_allowed, path_to_local, walk_to_expr_usage};
66
use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
77
use rustc_data_structures::fx::FxIndexMap;
88
use rustc_errors::Applicability;
@@ -15,7 +15,10 @@ use rustc_hir::{
1515
use rustc_infer::infer::TyCtxtInferExt;
1616
use rustc_lint::{LateContext, LateLintPass};
1717
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
18-
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitable, TypeckResults};
18+
use rustc_middle::ty::{
19+
self, subst::GenericArgKind, PredicateKind, TraitPredicate, TraitRef, Ty, TyCtxt, TypeFoldable, TypeVisitable,
20+
TypeckResults, TypeckResults,
21+
};
1922
use rustc_session::{declare_tool_lint, impl_lint_pass};
2023
use rustc_span::{symbol::sym, Span, Symbol};
2124
use rustc_trait_selection::infer::InferCtxtExt;
@@ -170,6 +173,7 @@ struct StateData {
170173
struct DerefedBorrow {
171174
count: usize,
172175
msg: &'static str,
176+
snip_expr: Option<HirId>,
173177
}
174178

175179
enum State {
@@ -331,20 +335,23 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
331335
let deref_msg =
332336
"this expression creates a reference which is immediately dereferenced by the compiler";
333337
let borrow_msg = "this expression borrows a value the compiler would automatically borrow";
338+
let impl_msg = "the borrowed expression implements the required traits";
334339

335-
let (required_refs, msg) = if position.can_auto_borrow() {
336-
(1, if deref_count == 1 { borrow_msg } else { deref_msg })
340+
let (required_refs, msg, snip_expr) = if position.can_auto_borrow() {
341+
(1, if deref_count == 1 { borrow_msg } else { deref_msg }, None)
342+
} else if let Position::ImplArg(hir_id) = position {
343+
(0, impl_msg, Some(hir_id))
337344
} else if let Some(&Adjust::Borrow(AutoBorrow::Ref(_, mutability))) =
338345
next_adjust.map(|a| &a.kind)
339346
{
340347
if matches!(mutability, AutoBorrowMutability::Mut { .. }) && !position.is_reborrow_stable()
341348
{
342-
(3, deref_msg)
349+
(3, deref_msg, None)
343350
} else {
344-
(2, deref_msg)
351+
(2, deref_msg, None)
345352
}
346353
} else {
347-
(2, deref_msg)
354+
(2, deref_msg, None)
348355
};
349356

350357
if deref_count >= required_refs {
@@ -354,6 +361,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
354361
// can't be removed without breaking the code. See earlier comment.
355362
count: deref_count - required_refs,
356363
msg,
364+
snip_expr,
357365
}),
358366
StateData { span: expr.span, hir_id: expr.hir_id, position },
359367
));
@@ -521,7 +529,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
521529
spans: vec![pat.span],
522530
app,
523531
replacements: vec![(pat.span, snip.into())],
524-
hir_id: pat.hir_id
532+
hir_id: pat.hir_id,
525533
}),
526534
);
527535
}
@@ -605,6 +613,7 @@ enum Position {
605613
/// The method is defined on a reference type. e.g. `impl Foo for &T`
606614
MethodReceiverRefImpl,
607615
Callee,
616+
ImplArg(HirId),
608617
FieldAccess(Symbol),
609618
Postfix,
610619
Deref,
@@ -638,7 +647,7 @@ impl Position {
638647
| Self::Callee
639648
| Self::FieldAccess(_)
640649
| Self::Postfix => PREC_POSTFIX,
641-
Self::Deref => PREC_PREFIX,
650+
Self::ImplArg(_) | Self::Deref => PREC_PREFIX,
642651
Self::DerefStable(p) | Self::ReborrowStable(p) | Self::Other(p) => p,
643652
}
644653
}
@@ -751,12 +760,14 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &
751760
.iter()
752761
.position(|arg| arg.hir_id == child_id)
753762
.zip(expr_sig(cx, func))
754-
.and_then(|(i, sig)| sig.input_with_hir(i))
755-
.map(|(hir_ty, ty)| match hir_ty {
756-
// Type inference for closures can depend on how they're called. Only go by the explicit
757-
// types here.
758-
Some(ty) => binding_ty_auto_deref_stability(ty, precedence),
759-
None => param_auto_deref_stability(ty.skip_binder(), precedence),
763+
.and_then(|(i, sig)| {
764+
sig.input_with_hir(i).map(|(hir_ty, ty)| match hir_ty {
765+
// Type inference for closures can depend on how they're called. Only go by the explicit
766+
// types here.
767+
Some(ty) => binding_ty_auto_deref_stability(ty, precedence),
768+
None => needless_borrow_impl_arg_position(cx, parent, i, ty.skip_binder(), child_id)
769+
.unwrap_or_else(|| param_auto_deref_stability(ty.skip_binder(), precedence)),
770+
})
760771
}),
761772
ExprKind::MethodCall(_, args, _) => {
762773
let id = cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap();
@@ -797,7 +808,9 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &
797808
Position::MethodReceiver
798809
}
799810
} else {
800-
param_auto_deref_stability(cx.tcx.fn_sig(id).skip_binder().inputs()[i], precedence)
811+
let ty = cx.tcx.fn_sig(id).skip_binder().inputs()[i];
812+
needless_borrow_impl_arg_position(cx, parent, i, ty, child_id)
813+
.unwrap_or_else(|| param_auto_deref_stability(ty, precedence))
801814
}
802815
})
803816
},
@@ -920,6 +933,111 @@ fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool {
920933
v.0
921934
}
922935

936+
// Checks whether:
937+
// * child is an expression of the form `&e` in an argument position requiring an `impl Trait`
938+
// * `e`'s type implements `Trait` and is copyable
939+
// If the conditions are met, returns `Some(Position::ImplArg(..))`; otherwise, returns `None`.
940+
// The "is copyable" condition is to avoid the case where removing the `&` means `e` would have to
941+
// be moved, but it cannot be.
942+
fn needless_borrow_impl_arg_position<'tcx>(
943+
cx: &LateContext<'tcx>,
944+
parent: &Expr<'tcx>,
945+
child_arg_index: usize,
946+
child_arg_ty: Ty<'tcx>,
947+
child_id: HirId,
948+
) -> Option<Position> {
949+
let sized_trait_def_id = cx.tcx.lang_items().sized_trait();
950+
951+
let Some(callee_def_id) = fn_def_id(cx, parent) else { return None };
952+
953+
let traits = cx
954+
.tcx
955+
.predicates_of(callee_def_id)
956+
.predicates
957+
.iter()
958+
.filter_map(|(predicate, _)| {
959+
if_chain! {
960+
if let PredicateKind::Trait(TraitPredicate {
961+
trait_ref:
962+
TraitRef {
963+
def_id: trait_def_id,
964+
substs,
965+
..
966+
},
967+
..
968+
}) = predicate.kind().skip_binder();
969+
if let Some(arg) = substs.iter().next();
970+
if let GenericArgKind::Type(arg_ty) = arg.unpack();
971+
if arg_ty == child_arg_ty;
972+
then { Some((trait_def_id, &substs[1..])) } else { None }
973+
}
974+
})
975+
.collect::<Vec<_>>();
976+
977+
// If only `Sized` was found, return.
978+
if traits
979+
.iter()
980+
.all(|&(trait_def_id, _)| Some(trait_def_id) == sized_trait_def_id)
981+
{
982+
return None;
983+
}
984+
985+
// If `child_arg_ty` is a type parameter that appears in more than one place, then substituting
986+
// it with `T` instead of `&T` could cause a type error.
987+
if cx
988+
.tcx
989+
.fn_sig(callee_def_id)
990+
.skip_binder()
991+
.inputs_and_output
992+
.iter()
993+
.enumerate()
994+
.any(|(i, ty)| i != child_arg_index && contains_ty(ty, child_arg_ty))
995+
{
996+
return None;
997+
}
998+
999+
let check_referent = |referent| {
1000+
let referent_ty = cx.typeck_results().expr_ty(referent);
1001+
1002+
if !is_copy(cx, referent_ty) {
1003+
return false;
1004+
}
1005+
1006+
// * If an applicable trait is found and `referent_ty` implements it, `needless_borrow` is set to
1007+
// `true`.
1008+
// * If an applicable trait is found that `referent_ty` does not implement, `false` is returned
1009+
// immediately.
1010+
// * If no applicable traits are found, `needless_borrow` remains `false`.
1011+
let mut needless_borrow = false;
1012+
for &(trait_def_id, substs) in &traits {
1013+
if implements_trait(cx, referent_ty, trait_def_id, substs) {
1014+
// Ignore `Sized` since it is required by default.
1015+
needless_borrow = Some(trait_def_id) != sized_trait_def_id;
1016+
} else {
1017+
return false;
1018+
}
1019+
}
1020+
needless_borrow
1021+
};
1022+
1023+
let Node::Expr(mut descendent) = cx.tcx.hir().get(child_id) else { return None };
1024+
1025+
let mut needless_borrow = false;
1026+
while let ExprKind::AddrOf(_, _, referent) = descendent.kind {
1027+
if !check_referent(referent) {
1028+
break;
1029+
}
1030+
descendent = referent;
1031+
needless_borrow = true;
1032+
}
1033+
1034+
if needless_borrow {
1035+
Some(Position::ImplArg(descendent.hir_id))
1036+
} else {
1037+
None
1038+
}
1039+
}
1040+
9231041
// Checks whether a type is stable when switching to auto dereferencing,
9241042
fn param_auto_deref_stability(ty: Ty<'_>, precedence: i8) -> Position {
9251043
let ty::Ref(_, mut ty, _) = *ty.kind() else {
@@ -1026,7 +1144,8 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
10261144
},
10271145
State::DerefedBorrow(state) => {
10281146
let mut app = Applicability::MachineApplicable;
1029-
let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
1147+
let snip_expr = state.snip_expr.map_or(expr, |hir_id| cx.tcx.hir().expect_expr(hir_id));
1148+
let (snip, snip_is_macro) = snippet_with_context(cx, snip_expr.span, data.span.ctxt(), "..", &mut app);
10301149
span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, state.msg, |diag| {
10311150
let sugg = if !snip_is_macro
10321151
&& expr.precedence().order() < data.position.precedence()

tests/ui/needless_borrow.fixed

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ fn main() {
127127
0
128128
}
129129
}
130+
131+
let _ = std::process::Command::new("ls").args(["-a", "-l"]).status().unwrap();
132+
let _ = std::path::Path::new(".").join(".");
133+
134+
let _ = Box::new(&""); // Don't lint. Type parameter has no trait bounds
130135
}
131136

132137
#[allow(clippy::needless_borrowed_reference)]

tests/ui/needless_borrow.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ fn main() {
127127
0
128128
}
129129
}
130+
131+
let _ = std::process::Command::new("ls").args(&["-a", "-l"]).status().unwrap();
132+
let _ = std::path::Path::new(".").join(&&".");
133+
134+
let _ = Box::new(&""); // Don't lint. Type parameter has no trait bounds
130135
}
131136

132137
#[allow(clippy::needless_borrowed_reference)]

tests/ui/needless_borrow.stderr

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,17 @@ error: this expression creates a reference which is immediately dereferenced by
120120
LL | (&&5).foo();
121121
| ^^^^^ help: change this to: `(&5)`
122122

123-
error: aborting due to 20 previous errors
123+
error: the borrowed expression implements the required traits
124+
--> $DIR/needless_borrow.rs:131:51
125+
|
126+
LL | let _ = std::process::Command::new("ls").args(&["-a", "-l"]).status().unwrap();
127+
| ^^^^^^^^^^^^^ help: change this to: `["-a", "-l"]`
128+
129+
error: the borrowed expression implements the required traits
130+
--> $DIR/needless_borrow.rs:132:44
131+
|
132+
LL | let _ = std::path::Path::new(".").join(&&".");
133+
| ^^^^^ help: change this to: `"."`
134+
135+
error: aborting due to 22 previous errors
124136

0 commit comments

Comments
 (0)