Upvar Paths

Done so far

Apr 9th 2019:

Exploratory coding branch (on top of recent master) can be found here.
  • Use the cmt_ machinery to collect the access paths for all(?) consumes/borrows, where each (upvar, path) is associated with an UpvarCapture.
  • This required extending Rvalue(ty::Region<'tcx>) to Rvalue(ty::Region<'tcx>, Option<Box<cmt_<'tcx>>>), to be able to thread the cmt_s.
  • When a function carries the rustc_dump_closure_captures attribute, dump the capture sets for all closures defined in it.

Apr 16th 2019:

  • start writing tests (most here), some inline below
  • fix a couple of grave bugs  revealed by the tests
  • propagate the UpvarCapture decisions for children paths to their ancestors
  • Implement “would a narrower definition cause us to move out of drop” check, but see below

Apr 23rd 2019

  • Explicitly handle ThreadLocal, StaticItem and Local.

Apr 23rd 2019 (Nicholas Matsakis):

  • Questions to ponder:
  • Presently, we are using the ExprUseVisitor to do our closure upvar analysis; it constructs cmts from moved paths etc. It gives us a bit more information than we presently need — if we were to do design an interface from scratch though, how would it differ?
  • It seems like it would wind up giving roughly the same information that we give now.
  • I think in my ideal world we would use HAIR as the basis for this translation, but HAIR presently makes the captures of upvars quite explicit.
  • Why do we extend Rvalue with additional information? When we have a cmt that borrows an rvalue, that implies that we believe a temporary was created. The extra information is tracking the origin of that value. Perhaps the creation of that temporary itself needs to be “recorded”? Something feels a bit fishy here, maybe?
  • Can we settle on the behavior? What are the edge cases?
  • e.g., I imagine that if we have &mut x.y.z and &x.y, we would propagate &mut x.y only, and rewrite the former to &mut (*upvar).z and the latter to &*upvar.
  • But in what cases do we “freeze” propagation etc.
  • Probably I should re-skim the RFC
  • Example of a tricky scenario: x: (&(u32,), ). Consider the path x.0.0 — if we are captured &x.0.0 precisely, we would then be able to mutate x and the closure wouldn’t notice (example on play):
let mut x = (&(22,),);
let p = &x.0.0; // imagine this is part of constructing a closure
x.0 = &(44,);
println!("{:?}", x);
println!("{:?}", p);

A vague plan takes shape:
  • Exhaustive test cases
  • what is the “matrix” we can use? we want to be sure that we have:
  • closures capturing paths that traverse
  • indexing
  • structs with fields that have drop (but not the path itself)
  • moving out of structs that impl drop
  • box
  • Rc (only overloads Deref, sort of the easy case I suspect)
  • something that overloads DerefMut but is not Box
  • crossed by a main function that
  • accesses 
  • also replace cmt with something simpler, more like MIR place, let’s call it CapturePath
  • this refactoring can be done independently as it should not change existing behavior
  • Note though that, in an ideal world, we would be able to unit test the CapturePath interaction — so that e.g. we can unit test what happens as we traverse the