Skip to content

Commit

Permalink
add upvar_borrow_map to tcx and fcx in typeck
Browse files Browse the repository at this point in the history
  • Loading branch information
nikomatsakis committed Feb 11, 2014
1 parent 807def0 commit b1962a2
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 1 deletion.
144 changes: 143 additions & 1 deletion src/librustc/middle/ty.rs
Expand Up @@ -351,6 +351,9 @@ pub struct ctxt_ {
// is used for lazy resolution of traits.
populated_external_traits: RefCell<HashSet<ast::DefId>>,

// Borrows
upvar_borrow_map: RefCell<UpvarBorrowMap>,

// These two caches are used by const_eval when decoding external statics
// and variants that are found.
extern_const_statics: RefCell<HashMap<ast::DefId, Option<@ast::Expr>>>,
Expand Down Expand Up @@ -494,6 +497,120 @@ pub enum Region {
ReEmpty,
}

/**
* Upvars do not get their own node-id. Instead, we use the pair of
* the original var id (that is, the root variable that is referenced
* by the upvar) and the id of the closure expression.
*/
#[deriving(Clone, Eq, IterBytes)]
pub struct UpvarId {
var_id: ast::NodeId,
closure_expr_id: ast::NodeId,
}

#[deriving(Clone, Eq, IterBytes)]
pub enum BorrowKind {
/// Data must be immutable and is aliasable.
ImmBorrow,

/// Data must be immutable but not aliasable. This kind of borrow
/// cannot currently be expressed by the user and is used only in
/// implicit closure bindings. It is needed when you the closure
/// is borrowing or mutating a mutable referent, e.g.:
///
/// let x: &mut int = ...;
/// let y = || *x += 5;
///
/// If we were to try to translate this closure into a more explicit
/// form, we'd encounter an error with the code as written:
///
/// struct Env { x: & &mut int }
/// let x: &mut int = ...;
/// let y = (&mut Env { &x }, fn_ptr); // Closure is pair of env and fn
/// fn fn_ptr(env: &mut Env) { **env.x += 5; }
///
/// This is then illegal because you cannot mutate a `&mut` found
/// in an aliasable location. To solve, you'd have to translate with
/// an `&mut` borrow:
///
/// struct Env { x: & &mut int }
/// let x: &mut int = ...;
/// let y = (&mut Env { &mut x }, fn_ptr); // changed from &x to &mut x
/// fn fn_ptr(env: &mut Env) { **env.x += 5; }
///
/// Now the assignment to `**env.x` is legal, but creating a
/// mutable pointer to `x` is not because `x` is not mutable. We
/// could fix this by declaring `x` as `let mut x`. This is ok in
/// user code, if awkward, but extra weird for closures, since the
/// borrow is hidden.
///
/// So we introduce a "unique imm" borrow -- the referent is
/// immutable, but not aliasable. This solves the problem. For
/// simplicity, we don't give users the way to express this
/// borrow, it's just used when translating closures.
UniqueImmBorrow,

/// Data is mutable and not aliasable.
MutBorrow
}

/**
* Information describing the borrowing of an upvar. This is computed
* during `typeck`, specifically by `regionck`. The general idea is
* that the compiler analyses treat closures like:
*
* let closure: &'e fn() = || {
* x = 1; // upvar x is assigned to
* use(y); // upvar y is read
* foo(&z); // upvar z is borrowed immutably
* };
*
* as if they were "desugared" to something loosely like:
*
* struct Vars<'x,'y,'z> { x: &'x mut int,
* y: &'y const int,
* z: &'z int }
* let closure: &'e fn() = {
* fn f(env: &Vars) {
* *env.x = 1;
* use(*env.y);
* foo(env.z);
* }
* let env: &'e mut Vars<'x,'y,'z> = &mut Vars { x: &'x mut x,
* y: &'y const y,
* z: &'z z };
* (env, f)
* };
*
* This is basically what happens at runtime. The closure is basically
* an existentially quantified version of the `(env, f)` pair.
*
* This data structure indicates the region and mutability of a single
* one of the `x...z` borrows.
*
* It may not be obvious why each borrowed variable gets its own
* lifetime (in the desugared version of the example, these are indicated
* by the lifetime parameters `'x`, `'y`, and `'z` in the `Vars` definition).
* Each such lifetime must encompass the lifetime `'e` of the closure itself,
* but need not be identical to it. The reason that this makes sense:
*
* - Callers are only permitted to invoke the closure, and hence to
* use the pointers, within the lifetime `'e`, so clearly `'e` must
* be a sublifetime of `'x...'z`.
* - The closure creator knows which upvars were borrowed by the closure
* and thus `x...z` will be reserved for `'x...'z` respectively.
* - Through mutation, the borrowed upvars can actually escape the
* the closure, so sometimes it is necessary for them to be larger
* than the closure lifetime itself.
*/
#[deriving(Eq, Clone)]
pub struct UpvarBorrow {
kind: BorrowKind,
region: ty::Region,
}

pub type UpvarBorrowMap = HashMap<UpvarId, UpvarBorrow>;

impl Region {
pub fn is_bound(&self) -> bool {
match self {
Expand Down Expand Up @@ -999,7 +1116,7 @@ pub fn mk_ctxt(s: session::Session,
impl_vtables: RefCell::new(HashMap::new()),
populated_external_types: RefCell::new(HashSet::new()),
populated_external_traits: RefCell::new(HashSet::new()),

upvar_borrow_map: RefCell::new(HashMap::new()),
extern_const_statics: RefCell::new(HashMap::new()),
extern_const_variants: RefCell::new(HashMap::new()),
}
Expand Down Expand Up @@ -5100,3 +5217,28 @@ impl substs {
}
}
}

impl BorrowKind {
pub fn from_mutbl(m: ast::Mutability) -> BorrowKind {
match m {
ast::MutMutable => MutBorrow,
ast::MutImmutable => ImmBorrow,
}
}

pub fn to_user_str(&self) -> &'static str {
match *self {
MutBorrow => "mutable",
ImmBorrow => "immutable",
UniqueImmBorrow => "uniquely immutable",
}
}

pub fn to_short_str(&self) -> &'static str {
match *self {
MutBorrow => "mut",
ImmBorrow => "imm",
UniqueImmBorrow => "own",
}
}
}
2 changes: 2 additions & 0 deletions src/librustc/middle/typeck/check/mod.rs
Expand Up @@ -164,6 +164,7 @@ pub struct Inherited {
adjustments: RefCell<HashMap<ast::NodeId, @ty::AutoAdjustment>>,
method_map: method_map,
vtable_map: vtable_map,
upvar_borrow_map: RefCell<ty::UpvarBorrowMap>,
}

#[deriving(Clone)]
Expand Down Expand Up @@ -266,6 +267,7 @@ impl Inherited {
adjustments: RefCell::new(HashMap::new()),
method_map: @RefCell::new(HashMap::new()),
vtable_map: @RefCell::new(HashMap::new()),
upvar_borrow_map: RefCell::new(HashMap::new()),
}
}
}
Expand Down
36 changes: 36 additions & 0 deletions src/librustc/middle/typeck/check/writeback.rs
Expand Up @@ -378,10 +378,45 @@ impl Visitor<()> for WbCtxt {
fn visit_ty(&mut self, _t: &ast::Ty, _: ()) {}
}

fn resolve_upvar_borrow_map(wbcx: &mut WbCtxt) {
if !wbcx.success {
return;
}

let fcx = wbcx.fcx;
let tcx = fcx.tcx();
let upvar_borrow_map = fcx.inh.upvar_borrow_map.borrow();
for (upvar_id, upvar_borrow) in upvar_borrow_map.get().iter() {
let r = upvar_borrow.region;
match resolve_region(fcx.infcx(), r, resolve_all | force_all) {
Ok(r) => {
let new_upvar_borrow = ty::UpvarBorrow {
kind: upvar_borrow.kind,
region: r
};
debug!("Upvar borrow for {} resolved to {}",
upvar_id.repr(tcx), new_upvar_borrow.repr(tcx));
let mut tcx_upvar_borrow_map = tcx.upvar_borrow_map.borrow_mut();
tcx_upvar_borrow_map.get().insert(*upvar_id, new_upvar_borrow);
}
Err(e) => {
let span = ty::expr_span(tcx, upvar_id.closure_expr_id);
fcx.ccx.tcx.sess.span_err(
span, format!("cannot resolve lifetime for \
captured variable `{}`: {}",
ty::local_var_name_str(tcx, upvar_id.var_id).get().to_str(),
infer::fixup_err_to_str(e)));
wbcx.success = false;
}
};
}
}

pub fn resolve_type_vars_in_expr(fcx: @FnCtxt, e: &ast::Expr) -> bool {
let mut wbcx = WbCtxt { fcx: fcx, success: true };
let wbcx = &mut wbcx;
wbcx.visit_expr(e, ());
resolve_upvar_borrow_map(wbcx);
return wbcx.success;
}

Expand All @@ -397,5 +432,6 @@ pub fn resolve_type_vars_in_fn(fcx: @FnCtxt, decl: &ast::FnDecl,
resolve_type_vars_for_node(wbcx, arg.pat.span, arg.pat.id);
}
}
resolve_upvar_borrow_map(wbcx);
return wbcx.success;
}

0 comments on commit b1962a2

Please sign in to comment.