Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect borrow checker errors where .clone() would be an appropriate user action #122603

Merged
merged 19 commits into from
Apr 13, 2024

Conversation

estebank
Copy link
Contributor

@estebank estebank commented Mar 16, 2024

When a value is moved twice, suggest cloning the earlier move:

error[E0509]: cannot move out of type `U2`, which implements the `Drop` trait
  --> $DIR/union-move.rs:49:18
   |
LL |         move_out(x.f1_nocopy);
   |                  ^^^^^^^^^^^
   |                  |
   |                  cannot move out of here
   |                  move occurs because `x.f1_nocopy` has type `ManuallyDrop<RefCell<i32>>`, which does not implement the `Copy` trait
   |
help: consider cloning the value if the performance cost is acceptable
   |
LL |         move_out(x.f1_nocopy.clone());
   |                             ++++++++

When a value is borrowed by an fn call, consider if cloning the result of the call would be reasonable, and suggest cloning that, instead of the argument:

error[E0505]: cannot move out of `a` because it is borrowed
  --> $DIR/variance-issue-20533.rs:53:14
   |
LL |         let a = AffineU32(1);
   |             - binding `a` declared here
LL |         let x = bat(&a);
   |                     -- borrow of `a` occurs here
LL |         drop(a);
   |              ^ move out of `a` occurs here
LL |         drop(x);
   |              - borrow later used here
   |
help: consider cloning the value if the performance cost is acceptable
   |
LL |         let x = bat(&a).clone();
   |                        ++++++++

otherwise, suggest cloning the argument:

error[E0505]: cannot move out of `a` because it is borrowed
  --> $DIR/variance-issue-20533.rs:59:14
   |
LL |         let a = ClonableAffineU32(1);
   |             - binding `a` declared here
LL |         let x = foo(&a);
   |                     -- borrow of `a` occurs here
LL |         drop(a);
   |              ^ move out of `a` occurs here
LL |         drop(x);
   |              - borrow later used here
   |
help: consider cloning the value if the performance cost is acceptable
   |
LL -         let x = foo(&a);
LL +         let x = foo(a.clone());
   |

This suggestion doesn't attempt to square out the types between what's cloned and what the fn expects, to allow the user to make a determination on whether to change the fn call or fn definition themselves.

Special case move errors caused by FnOnce:

error[E0382]: use of moved value: `blk`
  --> $DIR/once-cant-call-twice-on-heap.rs:8:5
   |
LL | fn foo<F:FnOnce()>(blk: F) {
   |                    --- move occurs because `blk` has type `F`, which does not implement the `Copy` trait
LL |     blk();
   |     ----- `blk` moved due to this call
LL |     blk();
   |     ^^^ value used here after move
   |
note: `FnOnce` closures can only be called once
  --> $DIR/once-cant-call-twice-on-heap.rs:6:10
   |
LL | fn foo<F:FnOnce()>(blk: F) {
   |          ^^^^^^^^ `F` is made to be an `FnOnce` closure here
LL |     blk();
   |     ----- this value implements `FnOnce`, which causes it to be moved when called

Account for redundant .clone() calls in resulting suggestions:

error[E0507]: cannot move out of dereference of `S`
  --> $DIR/needs-clone-through-deref.rs:15:18
   |
LL |         for _ in self.clone().into_iter() {}
   |                  ^^^^^^^^^^^^ ----------- value moved due to this method call
   |                  |
   |                  move occurs because value has type `Vec<usize>`, which does not implement the `Copy` trait
   |
note: `into_iter` takes ownership of the receiver `self`, which moves value
  --> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL
help: you can `clone` the value and consume it, but this might not be your desired behavior
   |
LL |         for _ in <Vec<usize> as Clone>::clone(&self).into_iter() {}
   |                  ++++++++++++++++++++++++++++++    ~

We use the presence of &mut values in a move error as a proxy for the user caring about side effects, so we don't emit a clone suggestion in that case:

error[E0505]: cannot move out of `s` because it is borrowed
  --> $DIR/borrowck-overloaded-index-move-index.rs:53:7
   |
LL |     let mut s = "hello".to_string();
   |         ----- binding `s` declared here
LL |     let rs = &mut s;
   |              ------ borrow of `s` occurs here
...
LL |     f[s] = 10;
   |       ^ move out of `s` occurs here
...
LL |     use_mut(rs);
   |             -- borrow later used here

We properly account for foo += foo; errors where we don't suggest foo.clone() += foo;, instead suggesting foo += foo.clone();.


Each commit can be reviewed in isolation. There are some "cleanup" commits, but kept them separate in order to show why specific changes were being made, and their effect on tests' output.

Fix #49693, CC #64167.

@rustbot
Copy link
Collaborator

rustbot commented Mar 16, 2024

r? @lcnr

rustbot has assigned @lcnr.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Mar 16, 2024
@rust-log-analyzer

This comment has been minimized.

@bors
Copy link
Contributor

bors commented Mar 18, 2024

☔ The latest upstream changes (presumably #121652) made this pull request unmergeable. Please resolve the merge conflicts.

Copy link
Contributor

@lcnr lcnr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

went through this PR and it mostly looks good.

I ended up only scimming over some of the functions as if let chains with 10 components are difficult to read.

nits, then r=me

@@ -977,6 +977,21 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
can_suggest_clone
}

pub(crate) fn clone_on_reference(&self, expr: &hir::Expr<'_>) -> Option<Span> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add a doc comment, I don't fully understand this function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's poinit is to detect x.clone() calls where the type of x matches the return type which is caused by an auto-ref?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, correct.

&& let Some(expr_ty) = typeck_results.node_type_opt(expr.hir_id)
&& let Some(rcvr_ty) = typeck_results.node_type_opt(rcvr.hir_id)
&& rcvr_ty == expr_ty
&& segment.ident.name == sym::clone
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is clone not a diagnostics item?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, but the res in the method call is Err for these cases :-/

tests/ui/associated-types/associated-types-outlives.stderr Outdated Show resolved Hide resolved
compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs Outdated Show resolved Hide resolved
compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs Outdated Show resolved Hide resolved
@@ -3,10 +3,10 @@
// fn body, causing this (invalid) code to be accepted.

pub trait Foo<'a> {
type Bar;
type Bar: Clone;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that the suggestion denormalise(&x).clone() would actually come up, otherwise it would suggest denormalise(x.clone()), and I wanted to verify that logic worked with associated types too (checking that the return type of denormalise could be cloned and side step the longer borrow of x).

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs Outdated Show resolved Hide resolved
compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs Outdated Show resolved Hide resolved
tests/ui/borrowck/borrowck-struct-update-with-dtor.stderr Outdated Show resolved Hide resolved
```
error[E0507]: cannot move out of `*x` which is behind a shared reference
  --> $DIR/borrowck-fn-in-const-a.rs:6:16
   |
LL |         return *x
   |                ^^ move occurs because `*x` has type `String`, which does not implement the `Copy` trait
   |
help: consider cloning the value if the performance cost is acceptable
   |
LL -         return *x
LL +         return x.clone()
   |
```
```
error[E0507]: cannot move out of `val`, a captured variable in an `FnMut` closure
  --> $DIR/issue-87456-point-to-closure.rs:10:28
   |
LL |     let val = String::new();
   |         --- captured outer variable
LL |
LL |     take_mut(|| {
   |              -- captured by this `FnMut` closure
LL |
LL |         let _foo: String = val;
   |                            ^^^ move occurs because `val` has type `String`, which does not implement the `Copy` trait
   |
help: consider borrowing here
   |
LL |         let _foo: String = &val;
   |                            +
help: consider cloning the value if the performance cost is acceptable
   |
LL |         let _foo: String = val.clone();
   |                               ++++++++
```
…ument

```
error[E0505]: cannot move out of `a` because it is borrowed
  --> $DIR/variance-issue-20533.rs:28:14
   |
LL |         let a = AffineU32(1);
   |             - binding `a` declared here
LL |         let x = foo(&a);
   |                     -- borrow of `a` occurs here
LL |         drop(a);
   |              ^ move out of `a` occurs here
LL |         drop(x);
   |              - borrow later used here
   |
help: consider cloning the value if the performance cost is acceptable
   |
LL |         let x = foo(&a).clone();
   |                        ++++++++
```
Explicitly look for `expr += other_expr;` and avoid suggesting
`expr.clone() += other_expr;`, instead suggesting `expr += other_expr.clone();`.
```
error[E0382]: use of moved value: `blk`
  --> $DIR/once-cant-call-twice-on-heap.rs:8:5
   |
LL | fn foo<F:FnOnce()>(blk: F) {
   |                    --- move occurs because `blk` has type `F`, which does not implement the `Copy` trait
LL |     blk();
   |     ----- `blk` moved due to this call
LL |     blk();
   |     ^^^ value used here after move
   |
note: `FnOnce` closures can only be called once
  --> $DIR/once-cant-call-twice-on-heap.rs:6:10
   |
LL | fn foo<F:FnOnce()>(blk: F) {
   |          ^^^^^^^^ `F` is made to be an `FnOnce` closure here
LL |     blk();
   |     ----- this value implements `FnOnce`, which causes it to be moved when called
```
We attempt to suggest an appropriate clone for move errors on expressions
like `S { ..s }` where a field isn't `Copy`. If we can't suggest, we still don't
emit the incorrect suggestion of `S { ..s }.clone()`.

```
error[E0509]: cannot move out of type `S<K>`, which implements the `Drop` trait
  --> $DIR/borrowck-struct-update-with-dtor.rs:28:19
   |
LL |         let _s2 = S { a: 2, ..s0 };
   |                   ^^^^^^^^^^^^^^^^
   |                   |
   |                   cannot move out of here
   |                   move occurs because `s0.c` has type `K`, which does not implement the `Copy` trait
   |
help: clone the value from the field instead of using the spread operator syntax
   |
LL |         let _s2 = S { a: 2, c: s0.c.clone(), ..s0 };
   |                           +++++++++++++++++
```
```
error[E0509]: cannot move out of type `S<()>`, which implements the `Drop` trait
  --> $DIR/borrowck-struct-update-with-dtor.rs:20:19
   |
LL |         let _s2 = S { a: 2, ..s0 };
   |                   ^^^^^^^^^^^^^^^^
   |                   |
   |                   cannot move out of here
   |                   move occurs because `s0.b` has type `B`, which does not implement the `Copy` trait
   |
note: `B` doesn't implement `Copy` or `Clone`
  --> $DIR/borrowck-struct-update-with-dtor.rs:4:1
   |
LL | struct B;
   | ^^^^^^^^
help: if `B` implemented `Clone`, you could clone the value from the field instead of using the spread operator syntax
   |
LL |         let _s2 = S { a: 2, b: s0.b.clone(), ..s0 };
   |                           +++++++++++++++++
```
Added comments and reworded messages
@estebank
Copy link
Contributor Author

@bors r=lcnr

Addressed the comments, approving as per #122603 (review)

@bors
Copy link
Contributor

bors commented Apr 12, 2024

📌 Commit 4c7213c has been approved by lcnr

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 12, 2024
@bors
Copy link
Contributor

bors commented Apr 13, 2024

⌛ Testing commit 4c7213c with merge 6eaa7fb...

@bors
Copy link
Contributor

bors commented Apr 13, 2024

☀️ Test successful - checks-actions
Approved by: lcnr
Pushing 6eaa7fb to master...

1 similar comment
@bors
Copy link
Contributor

bors commented Apr 13, 2024

☀️ Test successful - checks-actions
Approved by: lcnr
Pushing 6eaa7fb to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Apr 13, 2024
@bors bors merged commit 6eaa7fb into rust-lang:master Apr 13, 2024
12 checks passed
@rust-timer
Copy link
Collaborator

Finished benchmarking commit (6eaa7fb): comparison URL.

Overall result: ✅ improvements - no action needed

@rustbot label: -perf-regression

Instruction count

This is a highly reliable metric that was used to determine the overall result at the top of this comment.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-0.3% [-0.4%, -0.3%] 6
All ❌✅ (primary) - - 0

Max RSS (memory usage)

This benchmark run did not return any relevant results for this metric.

Cycles

This benchmark run did not return any relevant results for this metric.

Binary size

This benchmark run did not return any relevant results for this metric.

Bootstrap: 676.802s -> 675.908s (-0.13%)
Artifact size: 316.07 MiB -> 316.10 MiB (0.01%)

@cuviper cuviper added this to the 1.79.0 milestone May 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

"error[E0507]: cannot move out of borrowed content" could suggest adding the Copy trait
7 participants