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

Add help on reinitialization between move and access #85686

Merged
merged 1 commit into from
Jul 18, 2021

Conversation

ptrojahn
Copy link
Contributor

Fixes #83760

@rust-highfive
Copy link
Collaborator

r? @estebank

(rust-highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label May 25, 2021
// Check if we can reach these reinits from a move location.
let mut maybe_reinitialized = false;
let mut visited = FxHashSet::default();
let mut stack = reinits;
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason for the rename of reinits?

Copy link
Contributor Author

@ptrojahn ptrojahn May 28, 2021

Choose a reason for hiding this comment

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

At first we just collect the reinits we found and then use those to initialize the stack for the search. I thought this would be less confusing. Alternatively we could rename reinits to something like stack_reinits and remove the rename.

Copy link
Contributor

Choose a reason for hiding this comment

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

The rename makes sense to me.

@@ -1478,6 +1487,8 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
}));

let mut visited = FxHashSet::default();
let mut reinits = vec![];
let mut move_locations = vec![];
Copy link
Contributor

Choose a reason for hiding this comment

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

This should likely be an FxHashSet instead of a Vec.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree.

LL | while let Some(foo) = val {
| ^^^ value moved here, in previous iteration of loop
|
= help: make sure that it is reinitialized on all paths
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not entirely happy with the help wording because it feels like it might lead to confusion. It's likely better than we have already, but I'd feel better with a more newcomer friendly wording and potentially some spans pointing at the move locations when stating this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How about something like:

46 |     while let Some(_foo) = val { //~ ERROR use of moved value
   |                    ^^^^
   |                    |
   |                    the value might not be reinitialized at this point
   |                    value moved here, in previous iteration of loop

We could also print the spans of the reinits that might get skipped, though I think this would probably clutter the output.

Copy link
Contributor

Choose a reason for hiding this comment

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

We could also print the spans of the reinits that might get skipped, though I think this would probably clutter the output.

Could we see what that would look like in the tests we have available? Something we do in other cases is provide a full print out if few things are involved, and hide almost everything when they become too verbose. We could do something like that, but I'd like to see what it'd look like first.

@ptrojahn
Copy link
Contributor Author

I actually think this is an improvement:

error[E0382]: use of moved value
   --> scratchpad.rs:116:20
    |
116 |     while let Some(_foo) = val { //~ ERROR use of moved value
    |                    ^^^^ value moved here, in previous iteration of loop
117 |         if true {
118 |             val = None;
    |             ---------- this reinitialization might get skipped
    |
    = note: move occurs because value has type `Struct`, which does not implement the `Copy` trait

error[E0382]: use of moved value: `foo`
   --> scratchpad.rs:132:14
    |
126 |     let mut foo = Some(Struct);
    |         ------- move occurs because `foo` has type `Option<Struct>`, which does not implement the `Copy` trait
127 |     let _x = foo.unwrap();
    |                  -------- `foo` moved due to this method call
128 |     if true {
129 |         foo = Some(Struct);
    |         ------------------ this reinitialization might get skipped
...
132 |     let _y = foo; //~ ERROR use of moved value: `foo`
    |              ^^^ value used here after move
    |
note: this function takes ownership of the receiver `self`, which moves `foo`
   --> /home/paul/projects/rust/library/core/src/option.rs:383:25
    |
383 |     pub const fn unwrap(self) -> T {
    |                         ^^^^

If we have multiple ones we could do something like:

error[E0382]: use of moved value: `foo`
   --> scratchpad.rs:148:14
    |
136 |     let mut foo = Some(Struct);
    |         ------- move occurs because `foo` has type `Option<Struct>`, which does not implement the `Copy` trait
137 |     let _x = foo.unwrap();
    |                  -------- `foo` moved due to this method call
...
148 |     let _y = foo; //~ ERROR use of moved value: `foo`
    |              ^^^ value used here after move
    |
note: these reinitializations might get skipped
   --> scratchpad.rs:141:9
    |
141 |         foo = Some(Struct);
    |         ^^^^^^^^^^^^^^^^^^
142 |     } else if 2 + 2 == 3 {
143 |         foo = Some(Struct);
    |         ^^^^^^^^^^^^^^^^^^
144 |     } else if 2 + 2 == 5 {
145 |         foo = Some(Struct);
    |         ^^^^^^^^^^^^^^^^^^
note: this function takes ownership of the receiver `self`, which moves `foo`
   --> /home/paul/projects/rust/library/core/src/option.rs:383:25
    |
383 |     pub const fn unwrap(self) -> T {
    |                         ^^^^

I capped the number of printed reinitializations at 3. What do you think?

@ptrojahn ptrojahn force-pushed the loop_reinitialize branch from 7fdcb77 to ab1a719 Compare June 13, 2021 16:36
Copy link
Contributor

@estebank estebank left a comment

Choose a reason for hiding this comment

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

I love it and am ok to merge as is, but there's a small tweak I'd like to address as well to avoid accidentally misleading when there are more than 3 reinits.

Comment on lines 279 to 292
if let UseSpans::PatUse(span) = move_spans {
err.span_suggestion_verbose(
span.shrink_to_lo(),
&format!(
"borrow this field in the pattern to avoid moving {}",
self.describe_place(moved_place.as_ref())
.map(|n| format!("`{}`", n))
.unwrap_or_else(|| "the value".to_string())
),
"ref ".to_string(),
Applicability::MachineApplicable,
);
in_pattern = true;
if maybe_reinitialized_locations.is_empty() {
Copy link
Contributor

Choose a reason for hiding this comment

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

You can merge these two lines with something like the following to avoid one level of indentation:

if let (UseSpans::PatUse(span), []) = (move_spans, &maybe_reinitialized_locations[..]) {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines +1595 to +1632
// Check if we can reach these reinits from a move location.
let reinits_reachable = reinits
.into_iter()
.filter(|reinit| {
let mut visited = FxHashSet::default();
let mut stack = vec![*reinit];
while let Some(location) = stack.pop() {
if !visited.insert(location) {
continue;
}
if move_locations.contains(&location) {
return true;
}
stack.extend(predecessor_locations(self.body, location));
}
false
})
.collect::<Vec<Location>>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this happen unconditionally, even in the happy path? We might want to do a perf run just to be sure we don't regress things too much.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The only place where get_moved_indexes is called is in report_use_of_moved_or_uninitialized. I am pretty sure that this function is only called if we know for sure that an error occurred. This shouldn't run in normal error free compilation.

Comment on lines 141 to 166
let reinit_spans = maybe_reinitialized_locations
.iter()
.take(3)
.map(|loc| {
self.move_spans(self.move_data.move_paths[mpi].place.as_ref(), *loc)
.args_or_use()
})
.collect::<Vec<Span>>();
if reinit_spans.len() == 1 {
err.span_label(reinit_spans[0], "this reinitialization might get skipped");
} else if reinit_spans.len() > 1 {
err.span_note(
MultiSpan::from_spans(reinit_spans),
"these reinitializations might get skipped",
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I love the new output, but I would like it if for the case where there are more than 3 we provided something like

note: these 3 reinitializations and N others might get skipped
  --> $DIR/issue-83760.rs:30:9
   |
LL |         foo = Some(Struct);
   |         ^^^^^^^^^^^^^^^^^^
LL |     } else if true {
LL |         foo = Some(Struct);
   |         ^^^^^^^^^^^^^^^^^^
LL |     } else if true {
LL |         foo = Some(Struct);
   |         ^^^^^^^^^^^^^^^^^^

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea.

@ptrojahn ptrojahn force-pushed the loop_reinitialize branch from ab1a719 to 76f3613 Compare June 15, 2021 13:31
@JohnCSimon JohnCSimon added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 5, 2021
@bors
Copy link
Contributor

bors commented Jul 7, 2021

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

@ptrojahn ptrojahn force-pushed the loop_reinitialize branch from 76f3613 to 34ff259 Compare July 7, 2021 16:46
@estebank
Copy link
Contributor

@bors r+

@bors
Copy link
Contributor

bors commented Jul 18, 2021

📌 Commit 34ff259 has been approved by estebank

@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 Jul 18, 2021
@bors
Copy link
Contributor

bors commented Jul 18, 2021

⌛ Testing commit 34ff259 with merge 77d1559...

@bors
Copy link
Contributor

bors commented Jul 18, 2021

☀️ Test successful - checks-actions
Approved by: estebank
Pushing 77d1559 to master...

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.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Misleading error message when variable is not re-initialized in loop
6 participants