// Copyright 2012 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. /*! This file actually contains two passes related to regions. The first pass builds up the `region_map`, which describes the parent links in the region hierarchy. The second pass infers which types must be region parameterized. */ use core::prelude::*; use driver::session::Session; use metadata::csearch; use middle::resolve; use middle::ty::{region_variance, rv_covariant, rv_invariant}; use middle::ty::{rv_contravariant}; use middle::ty; use util::common::stmt_set; use core::cmp; use core::dvec::DVec; use core::vec; use std::list; use std::list::list; use std::oldmap::HashMap; use syntax::ast_map; use syntax::codemap::span; use syntax::print::pprust; use syntax::{ast, visit}; pub type parent = Option; /* Records the parameter ID of a region name. */ pub type binding = { node_id: ast::node_id, name: ~str, br: ty::bound_region }; /** Encodes the bounding lifetime for a given AST node: - Expressions are mapped to the expression or block encoding the maximum (static) lifetime of a value produced by that expression. This is generally the innermost call, statement, match, or block. - Variables and bindings are mapped to the block in which they are declared. */ pub type region_map = HashMap; pub struct ctxt { sess: Session, def_map: resolve::DefMap, // Generated maps: region_map: region_map, // Generally speaking, expressions are parented to their innermost // enclosing block. But some kinds of expressions serve as // parents: calls, methods, etc. In addition, some expressions // serve as parents by virtue of where they appear. For example, // the condition in a while loop is always a parent. In those // cases, we add the node id of such an expression to this set so // that when we visit it we can view it as a parent. root_exprs: HashMap, // The parent scope is the innermost block, statement, call, or match // expression during the execution of which the current expression // will be evaluated. Generally speaking, the innermost parent // scope is also the closest suitable ancestor in the AST tree. // // There is a subtle point concerning call arguments. Imagine // you have a call: // // { // block a // foo( // call b // x, // y); // } // // In what lifetime are the expressions `x` and `y` evaluated? At // first, I imagine the answer was the block `a`, as the arguments // are evaluated before the call takes place. But this turns out // to be wrong. The lifetime of the call must encompass the // argument evaluation as well. // // The reason is that evaluation of an earlier argument could // create a borrow which exists during the evaluation of later // arguments. Consider this torture test, for example, // // fn test1(x: @mut ~int) { // foo(&**x, *x = ~5); // } // // Here, the first argument `&**x` will be a borrow of the `~int`, // but the second argument overwrites that very value! Bad. // (This test is borrowck-pure-scope-in-call.rs, btw) parent: parent, } /// Returns true if `subscope` is equal to or is lexically nested inside /// `superscope` and false otherwise. pub fn scope_contains(region_map: region_map, superscope: ast::node_id, subscope: ast::node_id) -> bool { let mut subscope = subscope; while superscope != subscope { match region_map.find(subscope) { None => return false, Some(scope) => subscope = scope } } return true; } /// Determines whether one region is a subregion of another. This is /// intended to run *after inference* and sadly the logic is somewhat /// duplicated with the code in infer.rs. pub fn is_subregion_of(region_map: region_map, sub_region: ty::Region, super_region: ty::Region) -> bool { sub_region == super_region || match (sub_region, super_region) { (_, ty::re_static) => { true } (ty::re_scope(sub_scope), ty::re_scope(super_scope)) | (ty::re_scope(sub_scope), ty::re_free(super_scope, _)) => { scope_contains(region_map, super_scope, sub_scope) } _ => { false } } } /// Finds the nearest common ancestor (if any) of two scopes. That /// is, finds the smallest scope which is greater than or equal to /// both `scope_a` and `scope_b`. pub fn nearest_common_ancestor(region_map: region_map, scope_a: ast::node_id, scope_b: ast::node_id) -> Option { fn ancestors_of(region_map: region_map, scope: ast::node_id) -> ~[ast::node_id] { let mut result = ~[scope]; let mut scope = scope; loop { match region_map.find(scope) { None => return result, Some(superscope) => { result.push(superscope); scope = superscope; } } } } if scope_a == scope_b { return Some(scope_a); } let a_ancestors = ancestors_of(region_map, scope_a); let b_ancestors = ancestors_of(region_map, scope_b); let mut a_index = vec::len(a_ancestors) - 1u; let mut b_index = vec::len(b_ancestors) - 1u; // Here, ~[ab]_ancestors is a vector going from narrow to broad. // The end of each vector will be the item where the scope is // defined; if there are any common ancestors, then the tails of // the vector will be the same. So basically we want to walk // backwards from the tail of each vector and find the first point // where they diverge. If one vector is a suffix of the other, // then the corresponding scope is a superscope of the other. if a_ancestors[a_index] != b_ancestors[b_index] { return None; } loop { // Loop invariant: a_ancestors[a_index] == b_ancestors[b_index] // for all indices between a_index and the end of the array if a_index == 0u { return Some(scope_a); } if b_index == 0u { return Some(scope_b); } a_index -= 1u; b_index -= 1u; if a_ancestors[a_index] != b_ancestors[b_index] { return Some(a_ancestors[a_index + 1u]); } } } /// Extracts that current parent from cx, failing if there is none. pub fn parent_id(cx: ctxt, span: span) -> ast::node_id { match cx.parent { None => { cx.sess.span_bug(span, ~"crate should not be parent here"); } Some(parent_id) => { parent_id } } } /// Records the current parent (if any) as the parent of `child_id`. pub fn record_parent(cx: ctxt, child_id: ast::node_id) { for cx.parent.each |parent_id| { debug!("parent of node %d is node %d", child_id, *parent_id); cx.region_map.insert(child_id, *parent_id); } } pub fn resolve_block(blk: ast::blk, cx: ctxt, visitor: visit::vt) { // Record the parent of this block. record_parent(cx, blk.node.id); // Descend. let new_cx: ctxt = ctxt {parent: Some(blk.node.id),.. cx}; visit::visit_block(blk, new_cx, visitor); } pub fn resolve_arm(arm: ast::arm, cx: ctxt, visitor: visit::vt) { visit::visit_arm(arm, cx, visitor); } pub fn resolve_pat(pat: @ast::pat, cx: ctxt, visitor: visit::vt) { match pat.node { ast::pat_ident(*) => { let defn_opt = cx.def_map.find(pat.id); match defn_opt { Some(ast::def_variant(_,_)) => { /* Nothing to do; this names a variant. */ } _ => { /* This names a local. Bind it to the containing scope. */ record_parent(cx, pat.id); } } } _ => { /* no-op */ } } visit::visit_pat(pat, cx, visitor); } pub fn resolve_stmt(stmt: @ast::stmt, cx: ctxt, visitor: visit::vt) { match stmt.node { ast::stmt_decl(*) => { visit::visit_stmt(stmt, cx, visitor); } // This code has to be kept consistent with trans::base::trans_stmt ast::stmt_expr(_, stmt_id) | ast::stmt_semi(_, stmt_id) => { record_parent(cx, stmt_id); let mut expr_cx = cx; expr_cx.parent = Some(stmt_id); visit::visit_stmt(stmt, expr_cx, visitor); } ast::stmt_mac(*) => cx.sess.bug(~"unexpanded macro") } } pub fn resolve_expr(expr: @ast::expr, cx: ctxt, visitor: visit::vt) { record_parent(cx, expr.id); let mut new_cx = cx; match expr.node { // Calls or overloadable operators // FIXME #3387 // ast::expr_index(*) | ast::expr_binary(*) | // ast::expr_unary(*) | ast::expr_call(*) | ast::expr_method_call(*) => { debug!("node %d: %s", expr.id, pprust::expr_to_str(expr, cx.sess.intr())); new_cx.parent = Some(expr.id); } ast::expr_match(*) => { debug!("node %d: %s", expr.id, pprust::expr_to_str(expr, cx.sess.intr())); new_cx.parent = Some(expr.id); } ast::expr_while(cond, _) => { new_cx.root_exprs.insert(cond.id, ()); } _ => {} }; if new_cx.root_exprs.contains_key_ref(&expr.id) { new_cx.parent = Some(expr.id); } visit::visit_expr(expr, new_cx, visitor); } pub fn resolve_local(local: @ast::local, cx: ctxt, visitor: visit::vt) { record_parent(cx, local.node.id); visit::visit_local(local, cx, visitor); } pub fn resolve_item(item: @ast::item, cx: ctxt, visitor: visit::vt) { // Items create a new outer block scope as far as we're concerned. let new_cx: ctxt = ctxt {parent: None,.. cx}; visit::visit_item(item, new_cx, visitor); } pub fn resolve_fn(fk: visit::fn_kind, decl: ast::fn_decl, body: ast::blk, sp: span, id: ast::node_id, cx: ctxt, visitor: visit::vt) { let fn_cx = match fk { visit::fk_item_fn(*) | visit::fk_method(*) | visit::fk_dtor(*) => { // Top-level functions are a root scope. ctxt {parent: Some(id),.. cx} } visit::fk_anon(*) | visit::fk_fn_block(*) => { // Closures continue with the inherited scope. cx } }; // Record the ID of `self`. match fk { visit::fk_method(_, _, method) => { cx.region_map.insert(method.self_id, body.node.id); } _ => {} } debug!("visiting fn with body %d. cx.parent: %? \ fn_cx.parent: %?", body.node.id, cx.parent, fn_cx.parent); for decl.inputs.each |input| { cx.region_map.insert(input.id, body.node.id); } visit::visit_fn(fk, decl, body, sp, id, fn_cx, visitor); } pub fn resolve_crate(sess: Session, def_map: resolve::DefMap, crate: @ast::crate) -> region_map { let cx: ctxt = ctxt {sess: sess, def_map: def_map, region_map: HashMap(), root_exprs: HashMap(), parent: None}; let visitor = visit::mk_vt(@visit::Visitor { visit_block: resolve_block, visit_item: resolve_item, visit_fn: resolve_fn, visit_arm: resolve_arm, visit_pat: resolve_pat, visit_stmt: resolve_stmt, visit_expr: resolve_expr, visit_local: resolve_local, .. *visit::default_visitor() }); visit::visit_crate(*crate, cx, visitor); return cx.region_map; } // ___________________________________________________________________________ // Determining region parameterization // // Infers which type defns must be region parameterized---this is done // by scanning their contents to see whether they reference a region // type, directly or indirectly. This is a fixed-point computation. // // We do it in two passes. First we walk the AST and construct a map // from each type defn T1 to other defns which make use of it. For example, // if we have a type like: // // type S = *int; // type T = S; // // Then there would be a map entry from S to T. During the same walk, // we also construct add any types that reference regions to a set and // a worklist. We can then process the worklist, propagating indirect // dependencies until a fixed point is reached. pub type region_paramd_items = HashMap; #[deriving_eq] pub struct region_dep { ambient_variance: region_variance, id: ast::node_id } pub type dep_map = HashMap>; pub type determine_rp_ctxt_ = { sess: Session, ast_map: ast_map::map, def_map: resolve::DefMap, region_paramd_items: region_paramd_items, dep_map: dep_map, worklist: DVec, // the innermost enclosing item id mut item_id: ast::node_id, // true when we are within an item but not within a method. // see long discussion on region_is_relevant() mut anon_implies_rp: bool, // encodes the context of the current type; invariant if // mutable, covariant otherwise mut ambient_variance: region_variance, }; pub enum determine_rp_ctxt { determine_rp_ctxt_(@determine_rp_ctxt_) } pub fn join_variance(++variance1: region_variance, ++variance2: region_variance) -> region_variance { match (variance1, variance2) { (rv_invariant, _) => {rv_invariant} (_, rv_invariant) => {rv_invariant} (rv_covariant, rv_contravariant) => {rv_invariant} (rv_contravariant, rv_covariant) => {rv_invariant} (rv_covariant, rv_covariant) => {rv_covariant} (rv_contravariant, rv_contravariant) => {rv_contravariant} } } /// Combines the ambient variance with the variance of a /// particular site to yield the final variance of the reference. /// /// Example: if we are checking function arguments then the ambient /// variance is contravariant. If we then find a `&r/T` pointer, `r` /// appears in a co-variant position. This implies that this /// occurrence of `r` is contra-variant with respect to the current /// item, and hence the function returns `rv_contravariant`. pub fn add_variance(+ambient_variance: region_variance, +variance: region_variance) -> region_variance { match (ambient_variance, variance) { (rv_invariant, _) => rv_invariant, (_, rv_invariant) => rv_invariant, (rv_covariant, c) => c, (c, rv_covariant) => c, (rv_contravariant, rv_contravariant) => rv_covariant } } pub impl determine_rp_ctxt { fn add_variance(variance: region_variance) -> region_variance { add_variance(self.ambient_variance, variance) } /// Records that item `id` is region-parameterized with the /// variance `variance`. If `id` was already parameterized, then /// the new variance is joined with the old variance. fn add_rp(id: ast::node_id, variance: region_variance) { assert id != 0; let old_variance = self.region_paramd_items.find(id); let joined_variance = match old_variance { None => variance, Some(v) => join_variance(v, variance) }; debug!("add_rp() variance for %s: %? == %? ^ %?", ast_map::node_id_to_str(self.ast_map, id, self.sess.parse_sess.interner), joined_variance, old_variance, variance); if Some(joined_variance) != old_variance { self.region_paramd_items.insert(id, joined_variance); self.worklist.push(id); } } /// Indicates that the region-parameterization of the current item /// is dependent on the region-parameterization of the item /// `from`. Put another way, it indicates that the current item /// contains a value of type `from`, so if `from` is /// region-parameterized, so is the current item. fn add_dep(from: ast::node_id) { debug!("add dependency from %d -> %d (%s -> %s) with variance %?", from, self.item_id, ast_map::node_id_to_str(self.ast_map, from, self.sess.parse_sess.interner), ast_map::node_id_to_str(self.ast_map, self.item_id, self.sess.parse_sess.interner), copy self.ambient_variance); let vec = match self.dep_map.find(from) { Some(vec) => vec, None => { let vec = @DVec(); self.dep_map.insert(from, vec); vec } }; let dep = region_dep { ambient_variance: self.ambient_variance, id: self.item_id }; if !vec.contains(&dep) { vec.push(dep); } } // Determines whether a reference to a region that appears in the // AST implies that the enclosing type is region-parameterized. // // This point is subtle. Here are four examples to make it more // concrete. // // 1. impl foo for &int { ... } // 2. impl foo for &self/int { ... } // 3. impl foo for bar { fn m() -> &self/int { ... } } // 4. impl foo for bar { fn m() -> &int { ... } } // // In case 1, the anonymous region is being referenced, // but it appears in a context where the anonymous region // resolves to self, so the impl foo is region-parameterized. // // In case 2, the self parameter is written explicitly. // // In case 3, the method refers to self, so that implies that the // impl must be region parameterized. (If the type bar is not // region parameterized, that is an error, because the self region // is effectively unconstrained, but that is detected elsewhere). // // In case 4, the anonymous region is referenced, but it // bound by the method, so it does not refer to self. This impl // need not be region parameterized. // // So the rules basically are: the `self` region always implies // that the enclosing type is region parameterized. The anonymous // region also does, unless it appears within a method, in which // case it is bound. We handle this by setting a flag // (anon_implies_rp) to true when we enter an item and setting // that flag to false when we enter a method. fn region_is_relevant(r: @ast::region) -> bool { match r.node { ast::re_static => false, ast::re_anon => self.anon_implies_rp, ast::re_self => true, ast::re_named(_) => false } } // For named types like Foo, if there is no explicit region // parameter, then we will add the anonymous region, so there is // a dependency if the anonymous region implies rp. // // If the region is explicitly specified, then we follows the // normal rules. fn opt_region_is_relevant(opt_r: Option<@ast::region>) -> bool { debug!("opt_region_is_relevant: %? (anon_implies_rp=%b)", opt_r, self.anon_implies_rp); match opt_r { None => self.anon_implies_rp, Some(r) => self.region_is_relevant(r) } } fn with(item_id: ast::node_id, anon_implies_rp: bool, f: fn()) { let old_item_id = self.item_id; let old_anon_implies_rp = self.anon_implies_rp; self.item_id = item_id; self.anon_implies_rp = anon_implies_rp; debug!("with_item_id(%d, %b)", item_id, anon_implies_rp); let _i = ::util::common::indenter(); f(); self.item_id = old_item_id; self.anon_implies_rp = old_anon_implies_rp; } fn with_ambient_variance(variance: region_variance, f: fn()) { let old_ambient_variance = self.ambient_variance; self.ambient_variance = self.add_variance(variance); f(); self.ambient_variance = old_ambient_variance; } } pub fn determine_rp_in_item(item: @ast::item, &&cx: determine_rp_ctxt, visitor: visit::vt) { do cx.with(item.id, true) { visit::visit_item(item, cx, visitor); } } pub fn determine_rp_in_fn(fk: visit::fn_kind, decl: ast::fn_decl, body: ast::blk, _sp: span, _id: ast::node_id, &&cx: determine_rp_ctxt, visitor: visit::vt) { do cx.with(cx.item_id, false) { do cx.with_ambient_variance(rv_contravariant) { for decl.inputs.each |a| { (visitor.visit_ty)(a.ty, cx, visitor); } } (visitor.visit_ty)(decl.output, cx, visitor); (visitor.visit_ty_params)(visit::tps_of_fn(fk), cx, visitor); (visitor.visit_block)(body, cx, visitor); } } pub fn determine_rp_in_ty_method(ty_m: ast::ty_method, &&cx: determine_rp_ctxt, visitor: visit::vt) { do cx.with(cx.item_id, false) { visit::visit_ty_method(ty_m, cx, visitor); } } pub fn determine_rp_in_ty(ty: @ast::Ty, &&cx: determine_rp_ctxt, visitor: visit::vt) { // we are only interested in types that will require an item to // be region-parameterized. if cx.item_id is zero, then this type // is not a member of a type defn nor is it a constitutent of an // impl etc. So we can ignore it and its components. if cx.item_id == 0 { return; } // if this type directly references a region pointer like &r/ty, // add to the worklist/set. Note that &r/ty is contravariant with // respect to &r, because &r/ty can be used whereever a *smaller* // region is expected (and hence is a supertype of those // locations) match ty.node { ast::ty_rptr(r, _) => { debug!("referenced rptr type %s", pprust::ty_to_str(ty, cx.sess.intr())); if cx.region_is_relevant(r) { cx.add_rp(cx.item_id, cx.add_variance(rv_contravariant)) } } ast::ty_fn(f) => { debug!("referenced fn type: %s", pprust::ty_to_str(ty, cx.sess.intr())); match f.region { Some(r) => { if cx.region_is_relevant(r) { cx.add_rp(cx.item_id, cx.add_variance(rv_contravariant)) } } None => { if f.proto == ast::ProtoBorrowed && cx.anon_implies_rp { cx.add_rp(cx.item_id, cx.add_variance(rv_contravariant)); } } } } _ => {} } // if this references another named type, add the dependency // to the dep_map. If the type is not defined in this crate, // then check whether it is region-parameterized and consider // that as a direct dependency. match ty.node { ast::ty_path(path, id) => { match cx.def_map.find(id) { Some(ast::def_ty(did)) | Some(ast::def_struct(did)) => { if did.crate == ast::local_crate { if cx.opt_region_is_relevant(path.rp) { cx.add_dep(did.node); } } else { let cstore = cx.sess.cstore; match csearch::get_region_param(cstore, did) { None => {} Some(variance) => { debug!("reference to external, rp'd type %s", pprust::ty_to_str(ty, cx.sess.intr())); if cx.opt_region_is_relevant(path.rp) { cx.add_rp(cx.item_id, cx.add_variance(variance)) } } } } } _ => {} } } _ => {} } match ty.node { ast::ty_box(mt) | ast::ty_uniq(mt) | ast::ty_vec(mt) | ast::ty_rptr(_, mt) | ast::ty_ptr(mt) => { visit_mt(mt, cx, visitor); } ast::ty_rec(ref fields) => { for (*fields).each |field| { visit_mt(field.node.mt, cx, visitor); } } ast::ty_path(path, _) => { // type parameters are---for now, anyway---always invariant do cx.with_ambient_variance(rv_invariant) { for path.types.each |tp| { (visitor.visit_ty)(*tp, cx, visitor); } } } ast::ty_fn(f) => { // fn() binds the & region, so do not consider &T types that // appear *inside* a fn() type to affect the enclosing item: do cx.with(cx.item_id, false) { // parameters are contravariant do cx.with_ambient_variance(rv_contravariant) { for f.decl.inputs.each |a| { (visitor.visit_ty)(a.ty, cx, visitor); } } visit::visit_ty_param_bounds(f.bounds, cx, visitor); (visitor.visit_ty)(f.decl.output, cx, visitor); } } _ => { visit::visit_ty(ty, cx, visitor); } } fn visit_mt(mt: ast::mt, &&cx: determine_rp_ctxt, visitor: visit::vt) { // mutability is invariant if mt.mutbl == ast::m_mutbl { do cx.with_ambient_variance(rv_invariant) { (visitor.visit_ty)(mt.ty, cx, visitor); } } else { (visitor.visit_ty)(mt.ty, cx, visitor); } } } pub fn determine_rp_in_struct_field(cm: @ast::struct_field, &&cx: determine_rp_ctxt, visitor: visit::vt) { match cm.node.kind { ast::named_field(_, ast::struct_mutable, _) => { do cx.with_ambient_variance(rv_invariant) { visit::visit_struct_field(cm, cx, visitor); } } ast::named_field(_, ast::struct_immutable, _) | ast::unnamed_field => { visit::visit_struct_field(cm, cx, visitor); } } } pub fn determine_rp_in_crate(sess: Session, ast_map: ast_map::map, def_map: resolve::DefMap, crate: @ast::crate) -> region_paramd_items { let cx = determine_rp_ctxt_(@{sess: sess, ast_map: ast_map, def_map: def_map, region_paramd_items: HashMap(), dep_map: HashMap(), worklist: DVec(), mut item_id: 0, mut anon_implies_rp: false, mut ambient_variance: rv_covariant}); // Gather up the base set, worklist and dep_map let visitor = visit::mk_vt(@visit::Visitor { visit_fn: determine_rp_in_fn, visit_item: determine_rp_in_item, visit_ty: determine_rp_in_ty, visit_ty_method: determine_rp_in_ty_method, visit_struct_field: determine_rp_in_struct_field, .. *visit::default_visitor() }); visit::visit_crate(*crate, cx, visitor); // Propagate indirect dependencies // // Each entry in the worklist is the id of an item C whose region // parameterization has been updated. So we pull ids off of the // worklist, find the current variance, and then iterate through // all of the dependent items (that is, those items that reference // C). For each dependent item D, we combine the variance of C // with the ambient variance where the reference occurred and then // update the region-parameterization of D to reflect the result. while cx.worklist.len() != 0 { let c_id = cx.worklist.pop(); let c_variance = cx.region_paramd_items.get(c_id); debug!("popped %d from worklist", c_id); match cx.dep_map.find(c_id) { None => {} Some(deps) => { for deps.each |dep| { let v = add_variance(dep.ambient_variance, c_variance); cx.add_rp(dep.id, v); } } } } debug!("%s", { debug!("Region variance results:"); for cx.region_paramd_items.each_ref |&key, &value| { debug!("item %? (%s) is parameterized with variance %?", key, ast_map::node_id_to_str(ast_map, key, sess.parse_sess.interner), value); } "----" }); // return final set return cx.region_paramd_items; }