Skip to content

Commit 0fa945e

Browse files
committed
Change how we compute yield_in_scope
Compound operators (e.g. 'a += b') have two different possible evaluation orders. When the left-hand side is a primitive type, the expression is evaluated right-to-left. However, when the left-hand side is a non-primitive type, the expression is evaluated left-to-right. This causes problems when we try to determine if a type is live across a yield point. Since we need to perform this computation before typecheck has run, we can't simply check the types of the operands. This commit calculates the most 'pessimistic' scenario - that is, erring on the side of treating more types as live, rather than fewer. This is perfectly safe - in fact, this initial liveness computation is already overly conservative (e.g. issue #57478). The important thing is that we compute a superset of the types that are actually live across yield points. When we generate MIR, we'll determine which types actually need to stay live across a given yield point, and which ones cam actually be dropped. Concretely, we force the computed HIR traversal index for right-hand-side yield expression to be equal to the maximum index for the left-hand side. This covers both possible execution orders: * If the expression is evalauted right-to-left, our 'pessismitic' index is unecessary, but safe. We visit the expressions in an ExprKind::AssignOp from right to left, so it actually would have been safe to do nothing. However, while increasing the index of a yield point might cause the compiler to reject code that could actually compile, it will never cause incorrect code to be accepted. * If the expression is evaluated left-to-right, our 'pessimistic' index correctly ensures that types in the left-hand-side are seen as occuring before the yield - which is exactly what we want
1 parent 9d0960a commit 0fa945e

File tree

4 files changed

+138
-1
lines changed

4 files changed

+138
-1
lines changed

src/librustc/hir/intravisit.rs

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

src/librustc/middle/region.rs

+115
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,19 @@ struct RegionResolutionVisitor<'tcx> {
371371

372372
// The number of expressions and patterns visited in the current body
373373
expr_and_pat_count: usize,
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+
// Each time we encounter an ExprKind::AssignOp, we push
380+
// a new Vec into the outermost Vec. This inner Vec is uesd
381+
// to store any scopes we encounter when visiting the inenr expressions
382+
// of the AssignOp. Once we finish visiting the inner expressions, we pop
383+
// off the inner Vec, and process the Scopes it contains.
384+
// This allows us to handle nested AssignOps - while a terrible idea,
385+
// they are valid Rust, so we need to handle them.
386+
fixup_scopes: Vec<Vec<Scope>>,
374387

375388
// Generated scope tree:
376389
scope_tree: ScopeTree,
@@ -947,12 +960,108 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h
947960
}
948961
}
949962

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

9581067
_ => intravisit::walk_expr(visitor, expr)
@@ -972,6 +1081,10 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h
9721081
source: *source,
9731082
};
9741083
visitor.scope_tree.yield_in_scope.insert(scope, data);
1084+
if visitor.pessimistic_yield {
1085+
debug!("resolve_expr in pessimistic_yield - marking scope {:?} for fixup", scope);
1086+
visitor.fixup_scopes.last_mut().unwrap().push(scope);
1087+
}
9751088

9761089
// Keep traversing up while we can.
9771090
match visitor.scope_tree.parent_map.get(&scope) {
@@ -1360,6 +1473,8 @@ fn region_scope_tree<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> &'tcx ScopeTree
13601473
var_parent: None,
13611474
},
13621475
terminating_scopes: Default::default(),
1476+
pessimistic_yield: false,
1477+
fixup_scopes: vec![]
13631478
};
13641479

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

src/librustc_typeck/check/generator_interior.rs

+6
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,14 @@ 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| {
37+
38+
3339
// If we are recording an expression that is the last yield
3440
// in the scope, or that has a postorder CFG index larger
3541
// than the one of all of the yields, then its value can't

src/test/run-pass/issues/issue-61442.rs

+16
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ fn foo() {
55
let mut s = String::new();
66
s += { yield; "" };
77
};
8+
9+
let _y = static || {
10+
let x = &mut 0;
11+
*{ yield; x } += match String::new() { _ => 0 };
12+
};
13+
14+
// Please don't ever actually write something like this
15+
let _z = static || {
16+
let x = &mut 0;
17+
*{
18+
let inner = &mut 1;
19+
*{ yield (); inner } += match String::new() { _ => 1};
20+
yield;
21+
x
22+
} += match String::new() { _ => 2 };
23+
};
824
}
925

1026
fn main() {

0 commit comments

Comments
 (0)