-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
[generator] Special cases for match guard when analyzing interior types in generators #75213
[generator] Special cases for match guard when analyzing interior types in generators #75213
Conversation
r? @eddyb (rust_highfive has picked a reviewer for you, use r? to override) |
ade5e15
to
10762e9
Compare
@eddyb I just know that reviewers are randomly assigned. Would you mind suggesting another reviewer if you find the current assignment is not suitable? |
Sorry! I should've asked someone to remove me from the list of reviewers, as I'm really behind. |
@@ -0,0 +1,29 @@ | |||
// check-pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This MVCE no longer ICEs for me, whereas this one does: #72651 (comment). No need to leave credit, just an issue number is enough.
(Feel free to also include this one if you can confirm it did ICE at one point, though).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test case has been updated to refer to the said MVCE.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tmandry Forgot to mention this. This test case compiles if the async functions are not used in fn main
. As you can see, as soon as async function g
is called in main
, broken MIR ICE can be reproduced. I only found this out recently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, in that case let's put both test cases in!
10762e9
to
b4a321a
Compare
31131dd
to
9291c31
Compare
☔ The latest upstream changes (presumably #74862) made this pull request unmergeable. Please resolve the merge conflicts. |
9291c31
to
f5e9e99
Compare
Hi @matthewjasper would you mind a comment or feedback on this change? |
@rust-lang/wg-async-foundations has a meeting tomorrow focused on how this code works; after that would be a natural time for one of us to review this change. |
r? @tmandry |
fn visit_arm(&mut self, arm: &'tcx Arm<'tcx>) { | ||
if arm.guard.is_some() { | ||
self.nested_scope_of_guards.push(<_>::default()); | ||
self.arm_has_guard = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like we'd only want to set this while visiting the guard if expression, not the pattern.
"variable should be placed in scope earlier" | ||
); | ||
} | ||
self.arm_has_guard = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should reset back to the original value, which won't always be false (if there were a match nested inside the guard). Here's a (very contrived) example..
match x {
Some(y) if match y.len() { 0 => false, l if l % 2 == 0 => true, _ => false } => (),
_ => (),
}
In fact, I'm tempted to say this flag is redundant and we should remove it. We can query current_scope_of_guards
every time, and only find a way to optimize that if it's slow.
let scope = self.region_scope_tree.var_scope(pat.hir_id.local_id); | ||
let ty = self.fcx.typeck_results.borrow().pat_ty(pat); | ||
self.record(ty, Some(scope), None, pat.span); | ||
self.record(ty, Some(scope), None, pat.span, false); | ||
if self.arm_has_guard { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may want to check that this binding is actually introduced by the if guard, instead of by some arbitrary expression inside it.
That said, it's okay if we overapproximate the set of types inside our generator. Since it's very rare to have other bindings introduced inside a match guard, we could leave this as is. If we do that we should leave a comment explaining that this is an overapproximation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I gave some thoughts about it. I think a better way is to use a separate and simple visitor that only walks the patterns in the arm, given that the arm has a guard. Most part of the original visit_pat
will be kept the same.
@@ -134,6 +147,9 @@ pub fn resolve_interior<'a, 'tcx>( | |||
expr_count: 0, | |||
kind, | |||
prev_unresolved_span: None, | |||
nested_scope_of_guards: <_>::default(), | |||
current_scope_of_guards: <_>::default(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
naming suggestion: guard_bindings_set
@@ -134,6 +147,9 @@ pub fn resolve_interior<'a, 'tcx>( | |||
expr_count: 0, | |||
kind, | |||
prev_unresolved_span: None, | |||
nested_scope_of_guards: <_>::default(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
naming suggestion: guard_bindings
/// such borrows can span across this yield point. | ||
/// As such, we need to track these borrows and record them despite of the fact | ||
/// that they may succeed the said yield point in the post-order. | ||
nested_scope_of_guards: SmallVec<[SmallVec<[HirId; 4]>; 4]>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The outer SmallVec could probably be of len 1, since nesting these would be extremely rare in practice.
Friendly ping @dingxiangfei2009, are you still able to work on this? |
Sorry for keeping you waiting, @tmandry , I will apply your suggestions this morning. |
f5e9e99
to
29f61ac
Compare
Sorry, I was on call last week but am taking a look now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, after my comments I think this is good to go. Thanks for doing this!
@@ -53,7 +63,9 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { | |||
yield_data.expr_and_pat_count, self.expr_count, source_span | |||
); | |||
|
|||
if yield_data.expr_and_pat_count >= self.expr_count { | |||
if guard_borrowing_from_pattern |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: It may worth be restating what you said above in a comment here, i.e. that we need to record these regardless of where they appear in the post-order traversal.
self.guard_bindings.push(<_>::default()); | ||
} | ||
self.visit_pat(&arm.pat); | ||
if let Some(ref g) = arm.guard { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of accessing the arm's fields directly, could you destructure it so if new fields are added, this gets updated? e.g. let Arm { guard, pat } = arm;
This code is duplicating what normally goes in super_visit_arm()
so it's helpful to have static checks like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is a good idea. I also updated other field accesses to arm
towards the end of this function. There are other fields in arm
such as hir_id
, but I am going to elide them for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you use ..
it sort of defeats the purpose of what I was suggesting. I'd rather list out all fields and assign them to _
.
if arm.guard.is_some() { | ||
self.guard_bindings.push(<_>::default()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is duplicated below, I think maybe you meant to take it out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I need to remove it.
so that it reflects the fact that borrowing these pattern locals is happening before any yield points in match guards
29f61ac
to
50627a3
Compare
📌 Commit 50627a3 has been approved by |
☀️ Test successful - checks-actions, checks-azure |
Always record reference to binding in match if guards When encountering a binding from a `match` pattern in its `if` guard when computing a generator's interior types, we must always record the type of a reference to the binding because of how `if` guards are lowered to MIR. This was missed in rust-lang#75213 because the binding in that test case was autorefed and we recorded that adjusted type anyway. Fixes rust-lang#78366
Fix #72651
This proposes one of ways to fix the mentioned issue. One cause of #72651 is that the interior type analysis misses out types of match pattern locals. Those locals are manifested as temporary borrows in the scopes of match arm guards. If uses of these locals appear after yield points, the borrows from them were not considered live across the yield points. However, this is not the case since the borrowing always happens at the very beginning of the match guard.
This calls for special treatment to analysis of types appearing in the match guard. Those borrows are recorded as the HIR tree is walked by
InteriorVisitor
and their uses are recorded whenever a yield point is crossed.