Skip to content

Commit 4fe3727

Browse files
committed
Auto merge of #9701 - smoelius:improve-possible-borrower, r=Jarcho
Improve `possible_borrower` This PR makes several improvements to `clippy_uitls::mir::possible_borrower`. These changes benefit both `needless_borrow` and `redundant clone`. 1. **Use the compiler's `MaybeStorageLive` analysis** I could spot not functional differences between the one in the compiler and the one in Clippy's repository. So, I removed the latter in favor of the the former. 2. **Make `PossibleBorrower` a dataflow analysis instead of a visitor** The main benefit of this change is that allows `possible_borrower` to take advantage of statements' relative locations, which is easier to do in an analysis than in a visitor. This is easier to illustrate with an example, so consider this one: ```rust fn foo(cx: &LateContext<'_>, lint: &'static Lint) { cx.struct_span_lint(lint, rustc_span::Span::default(), "", |diag| diag.note(&String::new())); // ^ } ``` We would like to flag the `&` pointed to by the `^` for removal. `foo`'s MIR begins like this: ```rust fn span_lint::foo::{closure#0}(_1: [closure@$DIR/needless_borrow.rs:396:68: 396:74], _2: &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>) -> &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()> { debug diag => _2; // in scope 0 at $DIR/needless_borrow.rs:396:69: 396:73 let mut _0: &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>; // return place in scope 0 at $DIR/needless_borrow.rs:396:75: 396:75 let mut _3: &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>; // in scope 0 at $DIR/needless_borrow.rs:396:75: 396:100 let mut _4: &mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>; // in scope 0 at $DIR/needless_borrow.rs:396:75: 396:100 let mut _5: &std::string::String; // in scope 0 at $DIR/needless_borrow.rs:396:85: 396:99 let _6: std::string::String; // in scope 0 at $DIR/needless_borrow.rs:396:86: 396:99 bb0: { StorageLive(_3); // scope 0 at $DIR/needless_borrow.rs:396:75: 396:100 StorageLive(_4); // scope 0 at $DIR/needless_borrow.rs:396:75: 396:100 _4 = &mut (*_2); // scope 0 at $DIR/needless_borrow.rs:396:75: 396:100 StorageLive(_5); // scope 0 at $DIR/needless_borrow.rs:396:85: 396:99 StorageLive(_6); // scope 0 at $DIR/needless_borrow.rs:396:86: 396:99 _6 = std::string::String::new() -> bb1; // scope 0 at $DIR/needless_borrow.rs:396:86: 396:99 // mir::Constant // + span: $DIR/needless_borrow.rs:396:86: 396:97 // + literal: Const { ty: fn() -> std::string::String {std::string::String::new}, val: Value(<ZST>) } } bb1: { _5 = &_6; // scope 0 at $DIR/needless_borrow.rs:396:85: 396:99 _3 = rustc_errors::diagnostic_builder::DiagnosticBuilder::<'_, ()>::note::<&std::string::String>(move _4, move _5) -> [return: bb2, unwind: bb4]; // scope 0 at $DIR/needless_borrow.rs:396:75: 396:100 // mir::Constant // + span: $DIR/needless_borrow.rs:396:80: 396:84 // + literal: Const { ty: for<'a> fn(&'a mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()>, &std::string::String) -> &'a mut rustc_errors::diagnostic_builder::DiagnosticBuilder<'_, ()> {rustc_errors::diagnostic_builder::DiagnosticBuilder::<'_, ()>::note::<&std::string::String>}, val: Value(<ZST>) } } ``` The call to `diag.note` appears in `bb1` on the line beginning with `_3 =`. The `String` is owned by `_6`. So, in the call to `diag.note`, we would like to know whether there are any references to `_6` besides `_5`. The old, visitor approach did not consider the relative locations of statements. So all borrows were treated the same, *even if they occurred after the location of interest*. For example, before the `_3 = ...` call, the possible borrowers of `_6` would be just `_5`. But after the call, the possible borrowers would include `_2`, `_3`, and `_4`. So, in a sense, the call from which we are try to remove the needless borrow is trying to prevent us from removing the needless borrow(!). With an analysis, things do not get so muddled. We can determine the set of possible borrowers at any specific location, e.g., using a `ResultsCursor`. 3. **Change `only_borrowers` to `at_most_borrowers`** `possible_borrowers` exposed a function `only_borrowers` that determined whether the borrowers of some local were *exactly* some set `S`. But, from what I can tell, this was overkill. For the lints that currently use `possible_borrower` (`needless_borrow` and `redundant_clone`), all we really want to know is whether there are borrowers *other than* those in `S`. (Put another way, we only care about the subset relation in one direction.) The new function `at_most_borrowers` takes this more tailored approach. 4. **Compute relations "on the fly" rather than using `transitive_relation`** The visitor would compute and store the transitive closure of the possible borrower relation for an entire MIR body. But with an analysis, there is effectively a different possible borrower relation at each location in the body. Computing and storing a transitive closure at each location would not be practical. So the new approach is to compute the transitive closure on the fly, as needed. But the new approach might actually be more efficient, as I now explain. In all current uses of `at_most_borrowers` (previously `only_borrowers`), the size of the set of borrowers `S` is at most 2. So you need only check at most three borrowers to determine whether the subset relation holds. That is, once you have found a third borrower, you can stop, since you know the relation cannot hold. Note that `transitive_relation` is still used by `clippy_uitls::mir::possible_origin` (a kind of "subroutine" of `possible_borrower`). cc: `@Jarcho` --- changelog: [`needless_borrow`], [`redundant_clone`]: Now track references better and detect more cases [#9701](rust-lang/rust-clippy#9701) <!-- changelog_checked -->
2 parents 8a6e6fd + 4dbd8ad commit 4fe3727

38 files changed

+277
-209
lines changed

clippy_lints/src/casts/cast_slice_different_sizes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: &Msrv
5454

5555
diag.span_suggestion(
5656
expr.span,
57-
&format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
57+
format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
5858
sugg,
5959
rustc_errors::Applicability::HasPlaceholders,
6060
);

clippy_lints/src/dereference.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,10 +1282,10 @@ fn referent_used_exactly_once<'tcx>(
12821282
possible_borrowers.push((body_owner_local_def_id, PossibleBorrowerMap::new(cx, mir)));
12831283
}
12841284
let possible_borrower = &mut possible_borrowers.last_mut().unwrap().1;
1285-
// If `only_borrowers` were used here, the `copyable_iterator::warn` test would fail. The reason is
1286-
// that `PossibleBorrowerVisitor::visit_terminator` considers `place.local` a possible borrower of
1287-
// itself. See the comment in that method for an explanation as to why.
1288-
possible_borrower.bounded_borrowers(&[local], &[local, place.local], place.local, location)
1285+
// If `place.local` were not included here, the `copyable_iterator::warn` test would fail. The
1286+
// reason is that `PossibleBorrowerVisitor::visit_terminator` considers `place.local` a possible
1287+
// borrower of itself. See the comment in that method for an explanation as to why.
1288+
possible_borrower.at_most_borrowers(cx, &[local, place.local], place.local, location)
12891289
&& used_exactly_once(mir, place.local).unwrap_or(false)
12901290
} else {
12911291
false

clippy_lints/src/format_args.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symb
377377
call_site,
378378
&format!("`format!` in `{name}!` args"),
379379
|diag| {
380-
diag.help(&format!(
380+
diag.help(format!(
381381
"combine the `format!(..)` arguments with the outer `{name}!(..)` call"
382382
));
383383
diag.help("or consider changing `format!` to `format_args!`");

clippy_lints/src/large_enum_variant.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
111111
);
112112
diag.span_label(
113113
def.variants[variants_size[1].ind].span,
114-
&if variants_size[1].fields_size.is_empty() {
114+
if variants_size[1].fields_size.is_empty() {
115115
"the second-largest variant carries no data at all".to_owned()
116116
} else {
117117
format!(

clippy_lints/src/len_zero.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ fn check_for_is_empty<'tcx>(
361361
db.span_note(span, "`is_empty` defined here");
362362
}
363363
if let Some(self_kind) = self_kind {
364-
db.note(&output.expected_sig(self_kind));
364+
db.note(output.expected_sig(self_kind));
365365
}
366366
});
367367
}

clippy_lints/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ pub fn read_conf(sess: &Session, path: &io::Result<Option<PathBuf>>) -> Conf {
336336
Ok(Some(path)) => path,
337337
Ok(None) => return Conf::default(),
338338
Err(error) => {
339-
sess.struct_err(&format!("error finding Clippy's configuration file: {error}"))
339+
sess.struct_err(format!("error finding Clippy's configuration file: {error}"))
340340
.emit();
341341
return Conf::default();
342342
},

clippy_lints/src/loops/explicit_counter_loop.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pub(super) fn check<'tcx>(
7777
applicability,
7878
);
7979

80-
diag.note(&format!(
80+
diag.note(format!(
8181
"`{name}` is of type `{int_name}`, making it ineligible for `Iterator::enumerate`"
8282
));
8383
},

clippy_lints/src/manual_async_fn.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn {
7676
let help = format!("make the function `async` and {ret_sugg}");
7777
diag.span_suggestion(
7878
header_span,
79-
&help,
79+
help,
8080
format!("async {}{ret_snip}", &header_snip[..ret_pos]),
8181
Applicability::MachineApplicable
8282
);

clippy_lints/src/manual_strip.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
109109

110110
let test_span = expr.span.until(then.span);
111111
span_lint_and_then(cx, MANUAL_STRIP, strippings[0], &format!("stripping a {kind_word} manually"), |diag| {
112-
diag.span_note(test_span, &format!("the {kind_word} was tested here"));
112+
diag.span_note(test_span, format!("the {kind_word} was tested here"));
113113
multispan_sugg(
114114
diag,
115115
&format!("try using the `strip_{kind_word}` method"),

clippy_lints/src/methods/inefficient_to_string.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub fn check(
3636
expr.span,
3737
&format!("calling `to_string` on `{arg_ty}`"),
3838
|diag| {
39-
diag.help(&format!(
39+
diag.help(format!(
4040
"`{self_ty}` implements `ToString` through a slower blanket impl, but `{deref_self_ty}` has a fast specialization of `ToString`"
4141
));
4242
let mut applicability = Applicability::MachineApplicable;

0 commit comments

Comments
 (0)