Skip to content

Commit 53ae6d2

Browse files
committed
Auto merge of #61572 - Aaron1011:fix/generator-ref, r=varkor
Fix HIR visit order Fixes #61442 When rustc::middle::region::ScopeTree computes its yield_in_scope field, it relies on the HIR visitor order to properly compute which types must be live across yield points. In order for the computed scopes to agree with the generated MIR, we must ensure that expressions evaluated before a yield point are visited before the 'yield' expression. However, the visitor order for ExprKind::AssignOp was incorrect. The left-hand side of a compund assignment expression is evaluated before the right-hand side, but the right-hand expression was being visited before the left-hand expression. If the left-hand expression caused a new type to be introduced (e.g. through a deref-coercion), the new type would be incorrectly seen as occuring *after* the yield point, instead of before. This leads to a mismatch between the computed generator types and the MIR, since the MIR will correctly see the type as being live across the yield point. To fix this, we correct the visitor order for ExprKind::AssignOp to reflect the actual evaulation order.
2 parents a5b1729 + 770655a commit 53ae6d2

File tree

4 files changed

+146
-2
lines changed

4 files changed

+146
-2
lines changed

src/librustc/hir/intravisit.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1056,7 +1056,7 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr) {
10561056
}
10571057
ExprKind::AssignOp(_, ref left_expression, ref right_expression) => {
10581058
visitor.visit_expr(right_expression);
1059-
visitor.visit_expr(left_expression)
1059+
visitor.visit_expr(left_expression);
10601060
}
10611061
ExprKind::Field(ref subexpression, ident) => {
10621062
visitor.visit_expr(subexpression);

src/librustc/middle/region.rs

+107-1
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,12 @@ struct RegionResolutionVisitor<'tcx> {
371371

372372
// The number of expressions and patterns visited in the current body
373373
expr_and_pat_count: usize,
374-
374+
// When this is `true`, we record the `Scopes` we encounter
375+
// when processing a Yield expression. This allows us to fix
376+
// up their indices.
377+
pessimistic_yield: bool,
378+
// Stores scopes when pessimistic_yield is true.
379+
fixup_scopes: Vec<Scope>,
375380
// Generated scope tree:
376381
scope_tree: ScopeTree,
377382

@@ -947,12 +952,107 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h
947952
}
948953
}
949954

955+
let prev_pessimistic = visitor.pessimistic_yield;
956+
957+
// Ordinarily, we can rely on the visit order of HIR intravisit
958+
// to correspond to the actual execution order of statements.
959+
// However, there's a weird corner case with compund assignment
960+
// operators (e.g. `a += b`). The evaluation order depends on whether
961+
// or not the operator is overloaded (e.g. whether or not a trait
962+
// like AddAssign is implemented).
963+
964+
// For primitive types (which, despite having a trait impl, don't actually
965+
// end up calling it), the evluation order is right-to-left. For example,
966+
// the following code snippet:
967+
//
968+
// let y = &mut 0;
969+
// *{println!("LHS!"); y} += {println!("RHS!"); 1};
970+
//
971+
// will print:
972+
//
973+
// RHS!
974+
// LHS!
975+
//
976+
// However, if the operator is used on a non-primitive type,
977+
// the evaluation order will be left-to-right, since the operator
978+
// actually get desugared to a method call. For example, this
979+
// nearly identical code snippet:
980+
//
981+
// let y = &mut String::new();
982+
// *{println!("LHS String"); y} += {println!("RHS String"); "hi"};
983+
//
984+
// will print:
985+
// LHS String
986+
// RHS String
987+
//
988+
// To determine the actual execution order, we need to perform
989+
// trait resolution. Unfortunately, we need to be able to compute
990+
// yield_in_scope before type checking is even done, as it gets
991+
// used by AST borrowcheck.
992+
//
993+
// Fortunately, we don't need to know the actual execution order.
994+
// It suffices to know the 'worst case' order with respect to yields.
995+
// Specifically, we need to know the highest 'expr_and_pat_count'
996+
// that we could assign to the yield expression. To do this,
997+
// we pick the greater of the two values from the left-hand
998+
// and right-hand expressions. This makes us overly conservative
999+
// about what types could possibly live across yield points,
1000+
// but we will never fail to detect that a type does actually
1001+
// live across a yield point. The latter part is critical -
1002+
// we're already overly conservative about what types will live
1003+
// across yield points, as the generated MIR will determine
1004+
// when things are actually live. However, for typecheck to work
1005+
// properly, we can't miss any types.
1006+
1007+
9501008
match expr.node {
9511009
// Manually recurse over closures, because they are the only
9521010
// case of nested bodies that share the parent environment.
9531011
hir::ExprKind::Closure(.., body, _, _) => {
9541012
let body = visitor.tcx.hir().body(body);
9551013
visitor.visit_body(body);
1014+
},
1015+
hir::ExprKind::AssignOp(_, ref left_expr, ref right_expr) => {
1016+
debug!("resolve_expr - enabling pessimistic_yield, was previously {}",
1017+
prev_pessimistic);
1018+
1019+
let start_point = visitor.fixup_scopes.len();
1020+
visitor.pessimistic_yield = true;
1021+
1022+
// If the actual execution order turns out to be right-to-left,
1023+
// then we're fine. However, if the actual execution order is left-to-right,
1024+
// then we'll assign too low a count to any `yield` expressions
1025+
// we encounter in 'right_expression' - they should really occur after all of the
1026+
// expressions in 'left_expression'.
1027+
visitor.visit_expr(&right_expr);
1028+
visitor.pessimistic_yield = prev_pessimistic;
1029+
1030+
debug!("resolve_expr - restoring pessimistic_yield to {}", prev_pessimistic);
1031+
visitor.visit_expr(&left_expr);
1032+
debug!("resolve_expr - fixing up counts to {}", visitor.expr_and_pat_count);
1033+
1034+
// Remove and process any scopes pushed by the visitor
1035+
let target_scopes = visitor.fixup_scopes.drain(start_point..);
1036+
1037+
for scope in target_scopes {
1038+
let mut yield_data = visitor.scope_tree.yield_in_scope.get_mut(&scope).unwrap();
1039+
let count = yield_data.expr_and_pat_count;
1040+
let span = yield_data.span;
1041+
1042+
// expr_and_pat_count never decreases. Since we recorded counts in yield_in_scope
1043+
// before walking the left-hand side, it should be impossible for the recorded
1044+
// count to be greater than the left-hand side count.
1045+
if count > visitor.expr_and_pat_count {
1046+
bug!("Encountered greater count {} at span {:?} - expected no greater than {}",
1047+
count, span, visitor.expr_and_pat_count);
1048+
}
1049+
let new_count = visitor.expr_and_pat_count;
1050+
debug!("resolve_expr - increasing count for scope {:?} from {} to {} at span {:?}",
1051+
scope, count, new_count, span);
1052+
1053+
yield_data.expr_and_pat_count = new_count;
1054+
}
1055+
9561056
}
9571057

9581058
_ => intravisit::walk_expr(visitor, expr)
@@ -972,6 +1072,10 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h
9721072
source: *source,
9731073
};
9741074
visitor.scope_tree.yield_in_scope.insert(scope, data);
1075+
if visitor.pessimistic_yield {
1076+
debug!("resolve_expr in pessimistic_yield - marking scope {:?} for fixup", scope);
1077+
visitor.fixup_scopes.push(scope);
1078+
}
9751079

9761080
// Keep traversing up while we can.
9771081
match visitor.scope_tree.parent_map.get(&scope) {
@@ -1360,6 +1464,8 @@ fn region_scope_tree<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> &'tcx ScopeTree
13601464
var_parent: None,
13611465
},
13621466
terminating_scopes: Default::default(),
1467+
pessimistic_yield: false,
1468+
fixup_scopes: vec![],
13631469
};
13641470

13651471
let body = tcx.hir().body(body_id);

src/librustc_typeck/check/generator_interior.rs

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
2828
source_span: Span) {
2929
use syntax_pos::DUMMY_SP;
3030

31+
debug!("generator_interior: attempting to record type {:?} {:?} {:?} {:?}",
32+
ty, scope, expr, source_span);
33+
34+
3135
let live_across_yield = scope.map(|s| {
3236
self.region_scope_tree.yield_in_scope(s).and_then(|yield_data| {
3337
// If we are recording an expression that is the last yield
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Regression test for broken MIR error (#61442)
2+
// Due to the two possible evaluation orders for
3+
// a '+=' expression (depending on whether or not the 'AddAssign' trait
4+
// is being used), we were failing to account for all types that might
5+
// possibly be live across a yield point.
6+
7+
#![feature(generators)]
8+
9+
fn foo() {
10+
let _x = static || {
11+
let mut s = String::new();
12+
s += { yield; "" };
13+
};
14+
15+
let _y = static || {
16+
let x = &mut 0;
17+
*{ yield; x } += match String::new() { _ => 0 };
18+
};
19+
20+
// Please don't ever actually write something like this
21+
let _z = static || {
22+
let x = &mut 0;
23+
*{
24+
let inner = &mut 1;
25+
*{ yield (); inner } += match String::new() { _ => 1};
26+
yield;
27+
x
28+
} += match String::new() { _ => 2 };
29+
};
30+
}
31+
32+
fn main() {
33+
foo()
34+
}

0 commit comments

Comments
 (0)