Skip to content

Commit adef9da

Browse files
committed
Auto merge of #75213 - dingxiangfei2009:yield-point-in-match-guard, r=tmandry
[generator] Special cases for match guard when analyzing interior types in generators 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.
2 parents d65c08e + 50627a3 commit adef9da

File tree

4 files changed

+122
-12
lines changed

4 files changed

+122
-12
lines changed

compiler/rustc_middle/src/middle/region.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -283,23 +283,27 @@ pub struct ScopeTree {
283283
/// To see that this method works, consider:
284284
///
285285
/// Let `D` be our binding/temporary and `U` be our other HIR node, with
286-
/// `HIR-postorder(U) < HIR-postorder(D)` (in our example, U would be
287-
/// the yield and D would be one of the calls). Let's show that
288-
/// `D` is storage-dead at `U`.
286+
/// `HIR-postorder(U) < HIR-postorder(D)`. Suppose, as in our example,
287+
/// U is the yield and D is one of the calls.
288+
/// Let's show that `D` is storage-dead at `U`.
289289
///
290290
/// Remember that storage-live/storage-dead refers to the state of
291291
/// the *storage*, and does not consider moves/drop flags.
292292
///
293293
/// Then:
294+
///
294295
/// 1. From the ordering guarantee of HIR visitors (see
295296
/// `rustc_hir::intravisit`), `D` does not dominate `U`.
297+
///
296298
/// 2. Therefore, `D` is *potentially* storage-dead at `U` (because
297299
/// we might visit `U` without ever getting to `D`).
300+
///
298301
/// 3. However, we guarantee that at each HIR point, each
299302
/// binding/temporary is always either always storage-live
300303
/// or always storage-dead. This is what is being guaranteed
301304
/// by `terminating_scopes` including all blocks where the
302305
/// count of executions is not guaranteed.
306+
///
303307
/// 4. By `2.` and `3.`, `D` is *statically* storage-dead at `U`,
304308
/// QED.
305309
///

compiler/rustc_mir_build/src/build/matches/simplify.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ use std::mem;
2828
impl<'a, 'tcx> Builder<'a, 'tcx> {
2929
/// Simplify a candidate so that all match pairs require a test.
3030
///
31-
/// This method will also split a candidate where the only match-pair is an
32-
/// or-pattern into multiple candidates. This is so that
31+
/// This method will also split a candidate, in which the only
32+
/// match-pair is an or-pattern, into multiple candidates.
33+
/// This is so that
3334
///
3435
/// match x {
3536
/// 0 | 1 => { ... },

compiler/rustc_typeck/src/check/generator_interior.rs

+88-7
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
88
use rustc_hir as hir;
99
use rustc_hir::def::{CtorKind, DefKind, Res};
1010
use rustc_hir::def_id::DefId;
11+
use rustc_hir::hir_id::HirIdSet;
1112
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
12-
use rustc_hir::{Expr, ExprKind, Pat, PatKind};
13+
use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind};
1314
use rustc_middle::middle::region::{self, YieldData};
1415
use rustc_middle::ty::{self, Ty};
1516
use rustc_span::Span;
17+
use smallvec::SmallVec;
1618

1719
struct InteriorVisitor<'a, 'tcx> {
1820
fcx: &'a FnCtxt<'a, 'tcx>,
@@ -21,6 +23,13 @@ struct InteriorVisitor<'a, 'tcx> {
2123
expr_count: usize,
2224
kind: hir::GeneratorKind,
2325
prev_unresolved_span: Option<Span>,
26+
/// Match arm guards have temporary borrows from the pattern bindings.
27+
/// In case there is a yield point in a guard with a reference to such bindings,
28+
/// such borrows can span across this yield point.
29+
/// As such, we need to track these borrows and record them despite of the fact
30+
/// that they may succeed the said yield point in the post-order.
31+
guard_bindings: SmallVec<[SmallVec<[HirId; 4]>; 1]>,
32+
guard_bindings_set: HirIdSet,
2433
}
2534

2635
impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
@@ -30,6 +39,7 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
3039
scope: Option<region::Scope>,
3140
expr: Option<&'tcx Expr<'tcx>>,
3241
source_span: Span,
42+
guard_borrowing_from_pattern: bool,
3343
) {
3444
use rustc_span::DUMMY_SP;
3545

@@ -53,7 +63,12 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
5363
yield_data.expr_and_pat_count, self.expr_count, source_span
5464
);
5565

56-
if yield_data.expr_and_pat_count >= self.expr_count {
66+
// If it is a borrowing happening in the guard,
67+
// it needs to be recorded regardless because they
68+
// do live across this yield point.
69+
if guard_borrowing_from_pattern
70+
|| yield_data.expr_and_pat_count >= self.expr_count
71+
{
5772
Some(yield_data)
5873
} else {
5974
None
@@ -134,6 +149,8 @@ pub fn resolve_interior<'a, 'tcx>(
134149
expr_count: 0,
135150
kind,
136151
prev_unresolved_span: None,
152+
guard_bindings: <_>::default(),
153+
guard_bindings_set: <_>::default(),
137154
};
138155
intravisit::walk_body(&mut visitor, body);
139156

@@ -210,6 +227,38 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> {
210227
NestedVisitorMap::None
211228
}
212229

230+
fn visit_arm(&mut self, arm: &'tcx Arm<'tcx>) {
231+
let Arm { guard, pat, body, .. } = arm;
232+
self.visit_pat(pat);
233+
if let Some(ref g) = guard {
234+
self.guard_bindings.push(<_>::default());
235+
ArmPatCollector {
236+
guard_bindings_set: &mut self.guard_bindings_set,
237+
guard_bindings: self
238+
.guard_bindings
239+
.last_mut()
240+
.expect("should have pushed at least one earlier"),
241+
}
242+
.visit_pat(pat);
243+
244+
match g {
245+
Guard::If(ref e) => {
246+
self.visit_expr(e);
247+
}
248+
}
249+
250+
let mut scope_var_ids =
251+
self.guard_bindings.pop().expect("should have pushed at least one earlier");
252+
for var_id in scope_var_ids.drain(..) {
253+
assert!(
254+
self.guard_bindings_set.remove(&var_id),
255+
"variable should be placed in scope earlier"
256+
);
257+
}
258+
}
259+
self.visit_expr(body);
260+
}
261+
213262
fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) {
214263
intravisit::walk_pat(self, pat);
215264

@@ -218,11 +267,12 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> {
218267
if let PatKind::Binding(..) = pat.kind {
219268
let scope = self.region_scope_tree.var_scope(pat.hir_id.local_id);
220269
let ty = self.fcx.typeck_results.borrow().pat_ty(pat);
221-
self.record(ty, Some(scope), None, pat.span);
270+
self.record(ty, Some(scope), None, pat.span, false);
222271
}
223272
}
224273

225274
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
275+
let mut guard_borrowing_from_pattern = false;
226276
match &expr.kind {
227277
ExprKind::Call(callee, args) => match &callee.kind {
228278
ExprKind::Path(qpath) => {
@@ -249,6 +299,16 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> {
249299
}
250300
_ => intravisit::walk_expr(self, expr),
251301
},
302+
ExprKind::Path(qpath) => {
303+
intravisit::walk_expr(self, expr);
304+
let res = self.fcx.typeck_results.borrow().qpath_res(qpath, expr.hir_id);
305+
match res {
306+
Res::Local(id) if self.guard_bindings_set.contains(&id) => {
307+
guard_borrowing_from_pattern = true;
308+
}
309+
_ => {}
310+
}
311+
}
252312
_ => intravisit::walk_expr(self, expr),
253313
}
254314

@@ -259,18 +319,18 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> {
259319
// If there are adjustments, then record the final type --
260320
// this is the actual value that is being produced.
261321
if let Some(adjusted_ty) = self.fcx.typeck_results.borrow().expr_ty_adjusted_opt(expr) {
262-
self.record(adjusted_ty, scope, Some(expr), expr.span);
322+
self.record(adjusted_ty, scope, Some(expr), expr.span, guard_borrowing_from_pattern);
263323
}
264324

265325
// Also record the unadjusted type (which is the only type if
266326
// there are no adjustments). The reason for this is that the
267327
// unadjusted value is sometimes a "temporary" that would wind
268328
// up in a MIR temporary.
269329
//
270-
// As an example, consider an expression like `vec![].push()`.
330+
// As an example, consider an expression like `vec![].push(x)`.
271331
// Here, the `vec![]` would wind up MIR stored into a
272332
// temporary variable `t` which we can borrow to invoke
273-
// `<Vec<_>>::push(&mut t)`.
333+
// `<Vec<_>>::push(&mut t, x)`.
274334
//
275335
// Note that an expression can have many adjustments, and we
276336
// are just ignoring those intermediate types. This is because
@@ -287,9 +347,30 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> {
287347
// The type table might not have information for this expression
288348
// if it is in a malformed scope. (#66387)
289349
if let Some(ty) = self.fcx.typeck_results.borrow().expr_ty_opt(expr) {
290-
self.record(ty, scope, Some(expr), expr.span);
350+
self.record(ty, scope, Some(expr), expr.span, guard_borrowing_from_pattern);
291351
} else {
292352
self.fcx.tcx.sess.delay_span_bug(expr.span, "no type for node");
293353
}
294354
}
295355
}
356+
357+
struct ArmPatCollector<'a> {
358+
guard_bindings_set: &'a mut HirIdSet,
359+
guard_bindings: &'a mut SmallVec<[HirId; 4]>,
360+
}
361+
362+
impl<'a, 'tcx> Visitor<'tcx> for ArmPatCollector<'a> {
363+
type Map = intravisit::ErasedMap<'tcx>;
364+
365+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
366+
NestedVisitorMap::None
367+
}
368+
369+
fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) {
370+
intravisit::walk_pat(self, pat);
371+
if let PatKind::Binding(_, id, ..) = pat.kind {
372+
self.guard_bindings.push(id);
373+
self.guard_bindings_set.insert(id);
374+
}
375+
}
376+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// check-pass
2+
// edition:2018
3+
4+
// This test is derived from
5+
// https://github.com/rust-lang/rust/issues/72651#issuecomment-668720468
6+
7+
// This test demonstrates that, in `async fn g()`,
8+
// indeed a temporary borrow `y` from `x` is live
9+
// while `f().await` is being evaluated.
10+
// Thus, `&'_ u8` should be included in type signature
11+
// of the underlying generator.
12+
13+
async fn f() -> u8 { 1 }
14+
15+
pub async fn g(x: u8) {
16+
match x {
17+
y if f().await == y => (),
18+
_ => (),
19+
}
20+
}
21+
22+
fn main() {
23+
let _ = g(10);
24+
}

0 commit comments

Comments
 (0)