Skip to content

Commit 272bbfb

Browse files
committed
Auto merge of #9386 - smoelius:further-enhance-needless-borrow, r=Jarcho
Further enhance `needless_borrow`, mildly refactor `redundant_clone` This PR does the following: * Moves some code from `redundant_clone` into a new `clippy_utils` module called `mir`, and wraps that code in a function called `dropped_without_further_use`. * Relaxes the "is copyable" condition condition from #9136 by also suggesting to remove borrows from values dropped without further use. The changes involve the just mentioned function. * Separates `redundant_clone` into modules. Strictly speaking, the last bullet is independent of the others. `redundant_clone` is somewhat hairy, IMO. Separating it into modules makes it slightly less so, by helping to delineate what depends upon what. I've tried to break everything up into digestible commits. r? `@Jarcho` (`@Jarcho` I hope you don't mind.) changelog: continuation of #9136
2 parents 292e313 + 9cc8da2 commit 272bbfb

22 files changed

+879
-504
lines changed

clippy_dev/src/serve.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ fn mtime(path: impl AsRef<Path>) -> SystemTime {
4949
.into_iter()
5050
.flatten()
5151
.flatten()
52-
.map(|entry| mtime(&entry.path()))
52+
.map(|entry| mtime(entry.path()))
5353
.max()
5454
.unwrap_or(SystemTime::UNIX_EPOCH)
5555
} else {

clippy_dev/src/update_lints.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ fn generate_lint_files(
128128
for (lint_group, lints) in Lint::by_lint_group(usable_lints.into_iter().chain(internal_lints)) {
129129
let content = gen_lint_group_list(&lint_group, lints.iter());
130130
process_file(
131-
&format!("clippy_lints/src/lib.register_{lint_group}.rs"),
131+
format!("clippy_lints/src/lib.register_{lint_group}.rs"),
132132
update_mode,
133133
&content,
134134
);

clippy_lints/src/dereference.rs

Lines changed: 84 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
2+
use clippy_utils::mir::{enclosing_mir, expr_local, local_assignments, used_exactly_once, PossibleBorrowerMap};
23
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
34
use clippy_utils::sugg::has_enclosing_paren;
45
use clippy_utils::ty::{expr_sig, is_copy, peel_mid_ty_refs, ty_sig, variant_of_res};
@@ -11,13 +12,16 @@ use rustc_data_structures::fx::FxIndexMap;
1112
use rustc_errors::Applicability;
1213
use rustc_hir::intravisit::{walk_ty, Visitor};
1314
use rustc_hir::{
14-
self as hir, def_id::DefId, BindingAnnotation, Body, BodyId, BorrowKind, Closure, Expr, ExprKind, FnRetTy,
15-
GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind,
16-
Path, QPath, TraitItem, TraitItemKind, TyKind, UnOp,
15+
self as hir,
16+
def_id::{DefId, LocalDefId},
17+
BindingAnnotation, Body, BodyId, BorrowKind, Closure, Expr, ExprKind, FnRetTy, GenericArg, HirId, ImplItem,
18+
ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem,
19+
TraitItemKind, TyKind, UnOp,
1720
};
1821
use rustc_index::bit_set::BitSet;
1922
use rustc_infer::infer::TyCtxtInferExt;
2023
use rustc_lint::{LateContext, LateLintPass};
24+
use rustc_middle::mir::{Rvalue, StatementKind};
2125
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
2226
use rustc_middle::ty::{
2327
self, Binder, BoundVariableKind, EarlyBinder, FnSig, GenericArgKind, List, ParamTy, PredicateKind,
@@ -141,15 +145,15 @@ declare_clippy_lint! {
141145
"dereferencing when the compiler would automatically dereference"
142146
}
143147

144-
impl_lint_pass!(Dereferencing => [
148+
impl_lint_pass!(Dereferencing<'_> => [
145149
EXPLICIT_DEREF_METHODS,
146150
NEEDLESS_BORROW,
147151
REF_BINDING_TO_REFERENCE,
148152
EXPLICIT_AUTO_DEREF,
149153
]);
150154

151155
#[derive(Default)]
152-
pub struct Dereferencing {
156+
pub struct Dereferencing<'tcx> {
153157
state: Option<(State, StateData)>,
154158

155159
// While parsing a `deref` method call in ufcs form, the path to the function is itself an
@@ -170,11 +174,16 @@ pub struct Dereferencing {
170174
/// e.g. `m!(x) | Foo::Bar(ref x)`
171175
ref_locals: FxIndexMap<HirId, Option<RefPat>>,
172176

177+
/// Stack of (body owner, `PossibleBorrowerMap`) pairs. Used by
178+
/// `needless_borrow_impl_arg_position` to determine when a borrowed expression can instead
179+
/// be moved.
180+
possible_borrowers: Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
181+
173182
// `IntoIterator` for arrays requires Rust 1.53.
174183
msrv: Option<RustcVersion>,
175184
}
176185

177-
impl Dereferencing {
186+
impl<'tcx> Dereferencing<'tcx> {
178187
#[must_use]
179188
pub fn new(msrv: Option<RustcVersion>) -> Self {
180189
Self {
@@ -244,7 +253,7 @@ struct RefPat {
244253
hir_id: HirId,
245254
}
246255

247-
impl<'tcx> LateLintPass<'tcx> for Dereferencing {
256+
impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
248257
#[expect(clippy::too_many_lines)]
249258
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
250259
// Skip path expressions from deref calls. e.g. `Deref::deref(e)`
@@ -278,7 +287,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
278287
match (self.state.take(), kind) {
279288
(None, kind) => {
280289
let expr_ty = typeck.expr_ty(expr);
281-
let (position, adjustments) = walk_parents(cx, expr, self.msrv);
290+
let (position, adjustments) = walk_parents(cx, &mut self.possible_borrowers, expr, self.msrv);
282291
match kind {
283292
RefOp::Deref => {
284293
if let Position::FieldAccess {
@@ -550,6 +559,12 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
550559
}
551560

552561
fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
562+
if self.possible_borrowers.last().map_or(false, |&(local_def_id, _)| {
563+
local_def_id == cx.tcx.hir().body_owner_def_id(body.id())
564+
}) {
565+
self.possible_borrowers.pop();
566+
}
567+
553568
if Some(body.id()) == self.current_body {
554569
for pat in self.ref_locals.drain(..).filter_map(|(_, x)| x) {
555570
let replacements = pat.replacements;
@@ -682,6 +697,7 @@ impl Position {
682697
#[expect(clippy::too_many_lines)]
683698
fn walk_parents<'tcx>(
684699
cx: &LateContext<'tcx>,
700+
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
685701
e: &'tcx Expr<'_>,
686702
msrv: Option<RustcVersion>,
687703
) -> (Position, &'tcx [Adjustment<'tcx>]) {
@@ -796,7 +812,16 @@ fn walk_parents<'tcx>(
796812
Some(hir_ty) => binding_ty_auto_deref_stability(cx, hir_ty, precedence, ty.bound_vars()),
797813
None => {
798814
if let ty::Param(param_ty) = ty.skip_binder().kind() {
799-
needless_borrow_impl_arg_position(cx, parent, i, *param_ty, e, precedence, msrv)
815+
needless_borrow_impl_arg_position(
816+
cx,
817+
possible_borrowers,
818+
parent,
819+
i,
820+
*param_ty,
821+
e,
822+
precedence,
823+
msrv,
824+
)
800825
} else {
801826
ty_auto_deref_stability(cx, cx.tcx.erase_late_bound_regions(ty), precedence)
802827
.position_for_arg()
@@ -844,7 +869,16 @@ fn walk_parents<'tcx>(
844869
args.iter().position(|arg| arg.hir_id == child_id).map(|i| {
845870
let ty = cx.tcx.fn_sig(id).skip_binder().inputs()[i + 1];
846871
if let ty::Param(param_ty) = ty.kind() {
847-
needless_borrow_impl_arg_position(cx, parent, i + 1, *param_ty, e, precedence, msrv)
872+
needless_borrow_impl_arg_position(
873+
cx,
874+
possible_borrowers,
875+
parent,
876+
i + 1,
877+
*param_ty,
878+
e,
879+
precedence,
880+
msrv,
881+
)
848882
} else {
849883
ty_auto_deref_stability(
850884
cx,
@@ -1018,8 +1052,10 @@ fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool {
10181052
// If the conditions are met, returns `Some(Position::ImplArg(..))`; otherwise, returns `None`.
10191053
// The "is copyable" condition is to avoid the case where removing the `&` means `e` would have to
10201054
// be moved, but it cannot be.
1055+
#[expect(clippy::too_many_arguments)]
10211056
fn needless_borrow_impl_arg_position<'tcx>(
10221057
cx: &LateContext<'tcx>,
1058+
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
10231059
parent: &Expr<'tcx>,
10241060
arg_index: usize,
10251061
param_ty: ParamTy,
@@ -1082,10 +1118,13 @@ fn needless_borrow_impl_arg_position<'tcx>(
10821118
// elements are modified each time `check_referent` is called.
10831119
let mut substs_with_referent_ty = substs_with_expr_ty.to_vec();
10841120

1085-
let mut check_referent = |referent| {
1121+
let mut check_reference_and_referent = |reference, referent| {
10861122
let referent_ty = cx.typeck_results().expr_ty(referent);
10871123

1088-
if !is_copy(cx, referent_ty) {
1124+
if !is_copy(cx, referent_ty)
1125+
&& (referent_ty.has_significant_drop(cx.tcx, cx.param_env)
1126+
|| !referent_used_exactly_once(cx, possible_borrowers, reference))
1127+
{
10891128
return false;
10901129
}
10911130

@@ -1127,7 +1166,7 @@ fn needless_borrow_impl_arg_position<'tcx>(
11271166

11281167
let mut needless_borrow = false;
11291168
while let ExprKind::AddrOf(_, _, referent) = expr.kind {
1130-
if !check_referent(referent) {
1169+
if !check_reference_and_referent(expr, referent) {
11311170
break;
11321171
}
11331172
expr = referent;
@@ -1155,6 +1194,36 @@ fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool {
11551194
})
11561195
}
11571196

1197+
fn referent_used_exactly_once<'a, 'tcx>(
1198+
cx: &'a LateContext<'tcx>,
1199+
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
1200+
reference: &Expr<'tcx>,
1201+
) -> bool {
1202+
let mir = enclosing_mir(cx.tcx, reference.hir_id);
1203+
if let Some(local) = expr_local(cx.tcx, reference)
1204+
&& let [location] = *local_assignments(mir, local).as_slice()
1205+
&& let StatementKind::Assign(box (_, Rvalue::Ref(_, _, place))) =
1206+
mir.basic_blocks[location.block].statements[location.statement_index].kind
1207+
&& !place.has_deref()
1208+
{
1209+
let body_owner_local_def_id = cx.tcx.hir().enclosing_body_owner(reference.hir_id);
1210+
if possible_borrowers
1211+
.last()
1212+
.map_or(true, |&(local_def_id, _)| local_def_id != body_owner_local_def_id)
1213+
{
1214+
possible_borrowers.push((body_owner_local_def_id, PossibleBorrowerMap::new(cx, mir)));
1215+
}
1216+
let possible_borrower = &mut possible_borrowers.last_mut().unwrap().1;
1217+
// If `only_borrowers` were used here, the `copyable_iterator::warn` test would fail. The reason is
1218+
// that `PossibleBorrowerVisitor::visit_terminator` considers `place.local` a possible borrower of
1219+
// itself. See the comment in that method for an explanation as to why.
1220+
possible_borrower.bounded_borrowers(&[local], &[local, place.local], place.local, location)
1221+
&& used_exactly_once(mir, place.local).unwrap_or(false)
1222+
} else {
1223+
false
1224+
}
1225+
}
1226+
11581227
// Iteratively replaces `param_ty` with `new_ty` in `substs`, and similarly for each resulting
11591228
// projected type that is a type parameter. Returns `false` if replacing the types would have an
11601229
// effect on the function signature beyond substituting `new_ty` for `param_ty`.
@@ -1439,8 +1508,8 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
14391508
}
14401509
}
14411510

1442-
impl Dereferencing {
1443-
fn check_local_usage<'tcx>(&mut self, cx: &LateContext<'tcx>, e: &Expr<'tcx>, local: HirId) {
1511+
impl<'tcx> Dereferencing<'tcx> {
1512+
fn check_local_usage(&mut self, cx: &LateContext<'tcx>, e: &Expr<'tcx>, local: HirId) {
14441513
if let Some(outer_pat) = self.ref_locals.get_mut(&local) {
14451514
if let Some(pat) = outer_pat {
14461515
// Check for auto-deref

clippy_lints/src/lib.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ extern crate rustc_infer;
3838
extern crate rustc_lexer;
3939
extern crate rustc_lint;
4040
extern crate rustc_middle;
41-
extern crate rustc_mir_dataflow;
4241
extern crate rustc_parse;
4342
extern crate rustc_session;
4443
extern crate rustc_span;
@@ -418,7 +417,7 @@ pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Se
418417

419418
let msrv = conf.msrv.as_ref().and_then(|s| {
420419
parse_msrv(s, None, None).or_else(|| {
421-
sess.err(&format!(
420+
sess.err(format!(
422421
"error reading Clippy's configuration file. `{s}` is not a valid Rust version"
423422
));
424423
None
@@ -434,7 +433,7 @@ fn read_msrv(conf: &Conf, sess: &Session) -> Option<RustcVersion> {
434433
.and_then(|v| parse_msrv(&v, None, None));
435434
let clippy_msrv = conf.msrv.as_ref().and_then(|s| {
436435
parse_msrv(s, None, None).or_else(|| {
437-
sess.err(&format!(
436+
sess.err(format!(
438437
"error reading Clippy's configuration file. `{s}` is not a valid Rust version"
439438
));
440439
None
@@ -445,7 +444,7 @@ fn read_msrv(conf: &Conf, sess: &Session) -> Option<RustcVersion> {
445444
if let Some(clippy_msrv) = clippy_msrv {
446445
// if both files have an msrv, let's compare them and emit a warning if they differ
447446
if clippy_msrv != cargo_msrv {
448-
sess.warn(&format!(
447+
sess.warn(format!(
449448
"the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`"
450449
));
451450
}
@@ -474,7 +473,7 @@ pub fn read_conf(sess: &Session) -> Conf {
474473
let TryConf { conf, errors, warnings } = utils::conf::read(&file_name);
475474
// all conf errors are non-fatal, we just use the default conf in case of error
476475
for error in errors {
477-
sess.err(&format!(
476+
sess.err(format!(
478477
"error reading Clippy's configuration file `{}`: {}",
479478
file_name.display(),
480479
format_error(error)

clippy_lints/src/nonstandard_macro_braces.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ impl<'de> Deserialize<'de> for MacroMatcher {
266266
.iter()
267267
.find(|b| b.0 == brace)
268268
.map(|(o, c)| ((*o).to_owned(), (*c).to_owned()))
269-
.ok_or_else(|| de::Error::custom(&format!("expected one of `(`, `{{`, `[` found `{brace}`")))?,
269+
.ok_or_else(|| de::Error::custom(format!("expected one of `(`, `{{`, `[` found `{brace}`")))?,
270270
})
271271
}
272272
}

0 commit comments

Comments
 (0)