From 3ef7ff8b892aa2ad7e821d8858c349e22d1197ba Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 6 Jul 2012 09:14:57 -0700 Subject: [PATCH] infer the scope of borrows --- src/rustc/middle/typeck/check.rs | 160 ++++++---- src/rustc/middle/typeck/check/demand.rs | 5 +- src/rustc/middle/typeck/check/method.rs | 10 +- src/rustc/middle/typeck/check/regionck.rs | 38 ++- src/rustc/middle/typeck/check/vtable.rs | 5 +- src/rustc/middle/typeck/check/writeback.rs | 13 +- src/rustc/middle/typeck/coherence.rs | 2 +- src/rustc/middle/typeck/infer.rs | 276 +++++++++++------- src/rustc/util/ppaux.rs | 6 +- src/test/compile-fail/regions-borrow.rs | 8 - .../regions-infer-borrow-scope-too-big.rs | 14 + .../regions-infer-borrow-scope-within-loop.rs | 11 + .../regions-escape-into-other-fn.rs | 2 +- .../regions-infer-borrow-scope-view.rs | 9 + ...gions-infer-borrow-scope-within-loop-ok.rs | 10 + .../run-pass/regions-infer-borrow-scope.rs | 12 + 16 files changed, 378 insertions(+), 203 deletions(-) delete mode 100644 src/test/compile-fail/regions-borrow.rs create mode 100644 src/test/compile-fail/regions-infer-borrow-scope-too-big.rs create mode 100644 src/test/compile-fail/regions-infer-borrow-scope-within-loop.rs rename src/test/{compile-fail => run-pass}/regions-escape-into-other-fn.rs (65%) create mode 100644 src/test/run-pass/regions-infer-borrow-scope-view.rs create mode 100644 src/test/run-pass/regions-infer-borrow-scope-within-loop-ok.rs create mode 100644 src/test/run-pass/regions-infer-borrow-scope.rs diff --git a/src/rustc/middle/typeck/check.rs b/src/rustc/middle/typeck/check.rs index 4f5ea3fe4f8..bf2e4cb0e33 100644 --- a/src/rustc/middle/typeck/check.rs +++ b/src/rustc/middle/typeck/check.rs @@ -74,8 +74,7 @@ import rscope::{anon_rscope, binding_rscope, empty_rscope, in_anon_rscope}; import rscope::{in_binding_rscope, region_scope, type_rscope}; import syntax::ast::ty_i; import typeck::infer::{unify_methods}; // infcx.set() -import typeck::infer::{force_level, force_none, force_ty_vars_only, - force_all}; +import typeck::infer::{resolve_type, force_tvar}; type fn_ctxt = // var_bindings, locals and next_var_id are shared @@ -89,7 +88,22 @@ type fn_ctxt = infcx: infer::infer_ctxt, locals: hashmap, - mut blocks: ~[ast::node_id], // stack of blocks in scope, may be empty + // Sometimes we generate region pointers where the precise region + // to use is not known. For example, an expression like `&x.f` + // where `x` is of type `@T`: in this case, we will be rooting + // `x` onto the stack frame, and we could choose to root it until + // the end of (almost) any enclosing block or expression. We + // want to pick the narrowest block that encompasses all uses. + // + // What we do in such cases is to generate a region variable and + // assign it the following two fields as bounds. The lower bound + // is always the innermost enclosing expression. The upper bound + // is the outermost enclosing expression that we could legally + // use. In practice, this is the innermost loop or function + // body. + mut region_lb: ast::node_id, + mut region_ub: ast::node_id, + in_scope_regions: isr_alist, node_types: smallintmap::smallintmap, @@ -98,7 +112,8 @@ type fn_ctxt = ccx: @crate_ctxt}; // Used by check_const and check_enum_variants -fn blank_fn_ctxt(ccx: @crate_ctxt, rty: ty::t) -> @fn_ctxt { +fn blank_fn_ctxt(ccx: @crate_ctxt, rty: ty::t, + region_bnd: ast::node_id) -> @fn_ctxt { // It's kind of a kludge to manufacture a fake function context // and statement context, but we might as well do write the code only once @{self_ty: none, @@ -107,7 +122,8 @@ fn blank_fn_ctxt(ccx: @crate_ctxt, rty: ty::t) -> @fn_ctxt { purity: ast::pure_fn, infcx: infer::new_infer_ctxt(ccx.tcx), locals: int_hash(), - mut blocks: ~[], + mut region_lb: region_bnd, + mut region_ub: region_bnd, in_scope_regions: @nil, node_types: smallintmap::mk(), node_type_substs: map::int_hash(), @@ -217,7 +233,8 @@ fn check_fn(ccx: @crate_ctxt, purity: purity, infcx: infcx, locals: locals, - mut blocks: ~[], + mut region_lb: body.node.id, + mut region_ub: body.node.id, in_scope_regions: isr, node_types: node_types, node_type_substs: node_type_substs, @@ -307,9 +324,12 @@ fn check_fn(ccx: @crate_ctxt, }; let visit_block = fn@(b: ast::blk, &&e: (), v: visit::vt<()>) { - vec::push(fcx.blocks, b.node.id); - visit::visit_block(b, e, v); - vec::pop(fcx.blocks); + // non-obvious: the `blk` variable maps to region lb, so + // we have to keep this up-to-date. This + // is... unfortunate. It'd be nice to not need this. + do fcx.with_region_lb(b.node.id) { + visit::visit_block(b, e, v); + } }; // Don't descend into fns and items @@ -430,7 +450,7 @@ impl of ast_conv for @fn_ctxt { impl of region_scope for @fn_ctxt { fn anon_region() -> result { - result::ok(self.infcx.next_region_var()) + result::ok(self.infcx.next_region_var_nb()) } fn named_region(id: ast::ident) -> result { do empty_rscope.named_region(id).chain_err |_e| { @@ -448,10 +468,7 @@ impl of region_scope for @fn_ctxt { impl methods for @fn_ctxt { fn tag() -> ~str { #fmt["%x", ptr::addr_of(*self) as uint] } fn block_region() -> result { - alt vec::last_opt(self.blocks) { - some(bid) { result::ok(ty::re_scope(bid)) } - none { result::err(~"no block is in scope here") } - } + result::ok(ty::re_scope(self.region_lb)) } #[inline(always)] fn write_ty(node_id: ast::node_id, ty: ty::t) { @@ -534,15 +551,17 @@ impl methods for @fn_ctxt { infer::can_mk_subty(self.infcx, sub, sup) } - fn mk_assignty(expr: @ast::expr, borrow_scope: ast::node_id, + fn mk_assignty(expr: @ast::expr, borrow_lb: ast::node_id, sub: ty::t, sup: ty::t) -> result<(), ty::type_err> { - let anmnt = {expr_id: expr.id, borrow_scope: borrow_scope}; + let anmnt = {expr_id: expr.id, borrow_lb: borrow_lb, + borrow_ub: self.region_ub}; infer::mk_assignty(self.infcx, anmnt, sub, sup) } - fn can_mk_assignty(expr: @ast::expr, borrow_scope: ast::node_id, + fn can_mk_assignty(expr: @ast::expr, borrow_lb: ast::node_id, sub: ty::t, sup: ty::t) -> result<(), ty::type_err> { - let anmnt = {expr_id: expr.id, borrow_scope: borrow_scope}; + let anmnt = {expr_id: expr.id, borrow_lb: borrow_lb, + borrow_ub: self.region_ub}; infer::can_mk_assignty(self.infcx, anmnt, sub, sup) } @@ -564,6 +583,20 @@ impl methods for @fn_ctxt { } } } + fn with_region_lb(lb: ast::node_id, f: fn() -> R) -> R { + let old_region_lb = self.region_lb; + self.region_lb = lb; + let v <- f(); + self.region_lb = old_region_lb; + ret v; + } + fn with_region_ub(ub: ast::node_id, f: fn() -> R) -> R { + let old_region_ub = self.region_ub; + self.region_ub = ub; + let v <- f(); + self.region_ub = old_region_ub; + ret v; + } } fn do_autoderef(fcx: @fn_ctxt, sp: span, t: ty::t) -> ty::t { @@ -682,7 +715,7 @@ fn impl_self_ty(fcx: @fn_ctxt, did: ast::def_id) -> ty_param_substs_and_ty { raw_ty: ity.ty} }; - let self_r = if rp {some(fcx.infcx.next_region_var())} else {none}; + let self_r = if rp {some(fcx.infcx.next_region_var_nb())} else {none}; let tps = fcx.infcx.next_ty_vars(n_tps); let substs = {self_r: self_r, self_ty: none, tps: tps}; @@ -733,7 +766,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, sty @ ty::ty_fn(fn_ty) { replace_bound_regions_in_fn_ty( fcx.ccx.tcx, @nil, none, fn_ty, - |_br| fcx.infcx.next_region_var()).fn_ty + |_br| fcx.infcx.next_region_var_nb()).fn_ty } sty { // I would like to make this span_err, but it's @@ -1017,7 +1050,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, -> option { alt expected { some(t) { - alt infer::resolve_shallow(fcx.infcx, t, force_none) { + alt resolve_type(fcx.infcx, t, force_tvar) { result::ok(t) { unpack(ty::get(t).struct) } _ { none } } @@ -1119,9 +1152,9 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, // this will be the call or block that immediately // encloses the method call - let borrow_scope = fcx.tcx().region_map.get(expr.id); + let borrow_lb = fcx.tcx().region_map.get(expr.id); - let lkup = method::lookup(fcx, expr, base, borrow_scope, + let lkup = method::lookup(fcx, expr, base, borrow_lb, expr.id, field, expr_t, tps, is_self_ref); alt lkup.method() { @@ -1364,13 +1397,17 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, } ast::expr_while(cond, body) { bot = check_expr_with(fcx, cond, ty::mk_bool(tcx)); - check_block_no_value(fcx, body); + do fcx.with_region_ub(body.node.id) { + check_block_no_value(fcx, body); + } fcx.write_ty(id, ty::mk_nil(tcx)); } ast::expr_loop(body) { - check_block_no_value(fcx, body); - fcx.write_ty(id, ty::mk_nil(tcx)); - bot = !may_break(body); + do fcx.with_region_ub(body.node.id) { + check_block_no_value(fcx, body); + } + fcx.write_ty(id, ty::mk_nil(tcx)); + bot = !may_break(body); } ast::expr_alt(discrim, arms, _) { bot = alt::check_alt(fcx, expr, discrim, arms); @@ -1754,44 +1791,44 @@ fn check_block(fcx0: @fn_ctxt, blk: ast::blk) -> bool { ast::unsafe_blk { @{purity: ast::unsafe_fn with *fcx0} } ast::default_blk { fcx0 } }; - vec::push(fcx.blocks, blk.node.id); - let mut bot = false; - let mut warned = false; - for blk.node.stmts.each |s| { - if bot && !warned && - alt s.node { - ast::stmt_decl(@{node: ast::decl_local(_), _}, _) | - ast::stmt_expr(_, _) | ast::stmt_semi(_, _) { - true - } - _ { false } - } { - fcx.ccx.tcx.sess.span_warn(s.span, ~"unreachable statement"); - warned = true; + do fcx.with_region_lb(blk.node.id) { + let mut bot = false; + let mut warned = false; + for blk.node.stmts.each |s| { + if bot && !warned && + alt s.node { + ast::stmt_decl(@{node: ast::decl_local(_), _}, _) | + ast::stmt_expr(_, _) | ast::stmt_semi(_, _) { + true + } + _ { false } + } { + fcx.ccx.tcx.sess.span_warn(s.span, ~"unreachable statement"); + warned = true; + } + bot |= check_stmt(fcx, s); } - bot |= check_stmt(fcx, s); - } - alt blk.node.expr { - none { fcx.write_nil(blk.node.id); } - some(e) { - if bot && !warned { - fcx.ccx.tcx.sess.span_warn(e.span, ~"unreachable expression"); + alt blk.node.expr { + none { fcx.write_nil(blk.node.id); } + some(e) { + if bot && !warned { + fcx.ccx.tcx.sess.span_warn(e.span, ~"unreachable expression"); + } + bot |= check_expr(fcx, e, none); + let ety = fcx.expr_ty(e); + fcx.write_ty(blk.node.id, ety); + } } - bot |= check_expr(fcx, e, none); - let ety = fcx.expr_ty(e); - fcx.write_ty(blk.node.id, ety); - } + if bot { + fcx.write_bot(blk.node.id); + } + bot } - if bot { - fcx.write_bot(blk.node.id); - } - vec::pop(fcx.blocks); - ret bot; } fn check_const(ccx: @crate_ctxt, _sp: span, e: @ast::expr, id: ast::node_id) { let rty = ty::node_id_to_type(ccx.tcx, id); - let fcx = blank_fn_ctxt(ccx, rty); + let fcx = blank_fn_ctxt(ccx, rty, e.id); check_expr(fcx, e, none); let cty = fcx.expr_ty(e); let declty = fcx.ccx.tcx.tcache.get(local_def(id)).ty; @@ -1817,13 +1854,13 @@ fn check_enum_variants(ccx: @crate_ctxt, vs: ~[ast::variant], id: ast::node_id) { let rty = ty::node_id_to_type(ccx.tcx, id); - let fcx = blank_fn_ctxt(ccx, rty); let mut disr_vals: ~[int] = ~[]; let mut disr_val = 0; let mut variants = ~[]; for vs.each |v| { alt v.node.disr_expr { some(e) { + let fcx = blank_fn_ctxt(ccx, rty, e.id); check_expr(fcx, e, none); let cty = fcx.expr_ty(e); let declty = ty::mk_int(ccx.tcx); @@ -2003,7 +2040,7 @@ fn instantiate_path(fcx: @fn_ctxt, some(ast_region_to_region(fcx, fcx, sp, r)) } none if tpt.rp => { - some(fcx.infcx.next_region_var()) + some(fcx.infcx.next_region_var_nb()) } none => { none @@ -2037,8 +2074,7 @@ fn instantiate_path(fcx: @fn_ctxt, // Resolves `typ` by a single level if `typ` is a type variable. If no // resolution is possible, then an error is reported. fn structurally_resolved_type(fcx: @fn_ctxt, sp: span, tp: ty::t) -> ty::t { - alt infer::resolve_shallow(fcx.infcx, tp, - force_ty_vars_only) { + alt infer::resolve_type(fcx.infcx, tp, force_tvar) { result::ok(t_s) if !ty::type_is_var(t_s) { ret t_s; } _ { fcx.ccx.tcx.sess.span_fatal diff --git a/src/rustc/middle/typeck/check/demand.rs b/src/rustc/middle/typeck/check/demand.rs index d61aa687511..3718660472b 100644 --- a/src/rustc/middle/typeck/check/demand.rs +++ b/src/rustc/middle/typeck/check/demand.rs @@ -26,11 +26,10 @@ fn eqtype(fcx: @fn_ctxt, sp: span, } // Checks that the type `actual` can be assigned to `expected`. -fn assign(fcx: @fn_ctxt, sp: span, borrow_scope: ast::node_id, +fn assign(fcx: @fn_ctxt, sp: span, borrow_lb: ast::node_id, expected: ty::t, expr: @ast::expr) { let expr_ty = fcx.expr_ty(expr); - let anmnt = {expr_id: expr.id, borrow_scope: borrow_scope}; - alt infer::mk_assignty(fcx.infcx, anmnt, expr_ty, expected) { + alt fcx.mk_assignty(expr, borrow_lb, expr_ty, expected) { result::ok(()) { /* ok */ } result::err(err) { fcx.report_mismatched_types(sp, expected, expr_ty, err); diff --git a/src/rustc/middle/typeck/check/method.rs b/src/rustc/middle/typeck/check/method.rs index 2dc80593e02..5627625daa3 100644 --- a/src/rustc/middle/typeck/check/method.rs +++ b/src/rustc/middle/typeck/check/method.rs @@ -19,7 +19,7 @@ class lookup { let fcx: @fn_ctxt; let expr: @ast::expr; let self_expr: @ast::expr; - let borrow_scope: ast::node_id; + let borrow_lb: ast::node_id; let node_id: ast::node_id; let m_name: ast::ident; let mut self_ty: ty::t; @@ -32,7 +32,7 @@ class lookup { new(fcx: @fn_ctxt, expr: @ast::expr, //expr for a.b in a.b() self_expr: @ast::expr, //a in a.b(...) - borrow_scope: ast::node_id, //scope to borrow the expr for + borrow_lb: ast::node_id, //scope to borrow the expr for node_id: ast::node_id, //node id where to store type of fn m_name: ast::ident, //b in a.b(...) self_ty: ty::t, //type of a in a.b(...) @@ -42,7 +42,7 @@ class lookup { self.fcx = fcx; self.expr = expr; self.self_expr = self_expr; - self.borrow_scope = borrow_scope; + self.borrow_lb = borrow_lb; self.node_id = node_id; self.m_name = m_name; self.self_ty = self_ty; @@ -315,7 +315,7 @@ class lookup { // type assignability. Collect the matches. let matches = if use_assignability { self.fcx.can_mk_assignty( - self.self_expr, self.borrow_scope, + self.self_expr, self.borrow_lb, self.self_ty, impl_ty) } else { self.fcx.can_mk_subty(self.self_ty, impl_ty) @@ -377,7 +377,7 @@ class lookup { // Make the actual receiver type (cand.self_ty) assignable to the // required receiver type (cand.rcvr_ty). If this method is not // from an impl, this'll basically be a no-nop. - alt self.fcx.mk_assignty(self.self_expr, self.borrow_scope, + alt self.fcx.mk_assignty(self.self_expr, self.borrow_lb, cand.self_ty, cand.rcvr_ty) { result::ok(_) {} result::err(_) { diff --git a/src/rustc/middle/typeck/check/regionck.rs b/src/rustc/middle/typeck/check/regionck.rs index 9350f577804..e136ccd7b69 100644 --- a/src/rustc/middle/typeck/check/regionck.rs +++ b/src/rustc/middle/typeck/check/regionck.rs @@ -8,13 +8,19 @@ know whether a given type will be a region pointer or not until this phase. In particular, we ensure that, if the type of an expression or -variable is `&r.T`, then the expression or variable must occur within -the region scope `r`. +variable is `&r/T`, then the expression or variable must occur within +the region scope `r`. Note that in some cases `r` may still be a +region variable, so this gives us a chance to influence the value for +`r` that we infer to ensure we choose a value large enough to enclose +all uses. There is a lengthy comment in visit_node() that explains +this point a bit better. */ import util::ppaux; import syntax::print::pprust; +import infer::{resolve_type, resolve_all, force_all, + resolve_rvar, force_rvar}; type rcx = @{fcx: @fn_ctxt, mut errors_reported: uint}; type rvt = visit::vt; @@ -114,8 +120,31 @@ fn visit_node(id: ast::node_id, span: span, rcx: rcx) -> bool { // Try to resolve the type. If we encounter an error, then typeck // is going to fail anyway, so just stop here and let typeck // report errors later on in the writeback phase. + // + // Note one important point: we do not attempt to resolve *region + // variables* here. This is because regionck is essentially adding + // constraints to those region variables and so may yet influence + // how they are resolved. + // + // Consider this silly example: + // + // fn borrow(x: &int) -> &int {x} + // fn foo(x: @int) -> int { /* block: B */ + // let b = borrow(x); /* region: */ + // *b + // } + // + // Here, the region of `b` will be ``. `` is constrainted + // to be some subregion of the block B and some superregion of + // the call. If we forced it now, we'd choose the smaller region + // (the call). But that would make the *b illegal. Since we don't + // resolve, the type of b will be `&.int` and then `*b` will require + // that `` be bigger than the let and the `*b` expression, so we + // will effectively resolve `` to be the block B. let ty0 = fcx.node_ty(id); - let ty = alt infer::resolve_deep(fcx.infcx, ty0, force_none) { + let ty = alt resolve_type(fcx.infcx, ty0, + (resolve_all | force_all) - + (resolve_rvar | force_rvar)) { result::err(_) { ret true; } result::ok(ty) { ty } }; @@ -161,11 +190,12 @@ fn visit_node(id: ast::node_id, span: span, rcx: rcx) -> bool { alt rcx.fcx.mk_subr(encl_region, region) { result::err(_) { + let region1 = rcx.fcx.infcx.resolve_region_if_possible(region); tcx.sess.span_err( span, #fmt["reference is not valid outside \ of its lifetime, %s", - ppaux::region_to_str(tcx, region)]); + ppaux::region_to_str(tcx, region1)]); rcx.errors_reported += 1u; } result::ok(()) { diff --git a/src/rustc/middle/typeck/check/vtable.rs b/src/rustc/middle/typeck/check/vtable.rs index a883946252c..a4dd6bae933 100644 --- a/src/rustc/middle/typeck/check/vtable.rs +++ b/src/rustc/middle/typeck/check/vtable.rs @@ -1,4 +1,5 @@ import check::{fn_ctxt, impl_self_ty, methods}; +import infer::{resolve_type, resolve_all, force_all, fixup_err_to_str}; fn has_trait_bounds(tps: ~[ty::param_bounds]) -> bool { vec::any(tps, |bs| { @@ -178,14 +179,14 @@ fn lookup_vtable(fcx: @fn_ctxt, isc: resolve::iscopes, sp: span, fn fixup_ty(fcx: @fn_ctxt, sp: span, ty: ty::t) -> ty::t { let tcx = fcx.ccx.tcx; - alt infer::resolve_deep(fcx.infcx, ty, force_all) { + alt resolve_type(fcx.infcx, ty, resolve_all | force_all) { result::ok(new_type) { new_type } result::err(e) { tcx.sess.span_fatal( sp, #fmt["cannot determine a type \ for this bounded type parameter: %s", - infer::fixup_err_to_str(e)]) + fixup_err_to_str(e)]) } } } diff --git a/src/rustc/middle/typeck/check/writeback.rs b/src/rustc/middle/typeck/check/writeback.rs index a55c0515d76..720c85bf18e 100644 --- a/src/rustc/middle/typeck/check/writeback.rs +++ b/src/rustc/middle/typeck/check/writeback.rs @@ -3,14 +3,14 @@ // substitutions. import check::{fn_ctxt, lookup_local, methods}; - +import infer::{resolve_type, resolve_all, force_all}; export resolve_type_vars_in_fn; export resolve_type_vars_in_expr; fn resolve_type_vars_in_type(fcx: @fn_ctxt, sp: span, typ: ty::t) -> option { if !ty::type_needs_infer(typ) { ret some(typ); } - alt infer::resolve_deep(fcx.infcx, typ, force_all) { + alt resolve_type(fcx.infcx, typ, resolve_all | force_all) { result::ok(new_type) { ret some(new_type); } result::err(e) { if !fcx.ccx.tcx.sess.has_errors() { @@ -130,7 +130,8 @@ fn visit_pat(p: @ast::pat, wbcx: wb_ctxt, v: wb_vt) { fn visit_local(l: @ast::local, wbcx: wb_ctxt, v: wb_vt) { if !wbcx.success { ret; } let var_id = lookup_local(wbcx.fcx, l.span, l.node.id); - alt infer::resolve_deep_var(wbcx.fcx.infcx, var_id, force_all) { + let var_ty = ty::mk_var(wbcx.fcx.tcx(), var_id); + alt resolve_type(wbcx.fcx.infcx, var_ty, resolve_all | force_all) { result::ok(lty) { #debug["Type for local %s (id %d) resolved to %s", pat_to_str(l.node.pat), l.node.id, @@ -166,6 +167,9 @@ fn resolve_type_vars_in_expr(fcx: @fn_ctxt, e: @ast::expr) -> bool { let wbcx = {fcx: fcx, mut success: true}; let visit = mk_visitor(); visit.visit_expr(e, wbcx, visit); + if wbcx.success { + infer::resolve_borrowings(fcx.infcx); + } ret wbcx.success; } @@ -178,5 +182,8 @@ fn resolve_type_vars_in_fn(fcx: @fn_ctxt, for decl.inputs.each |arg| { resolve_type_vars_for_node(wbcx, arg.ty.span, arg.id); } + if wbcx.success { + infer::resolve_borrowings(fcx.infcx); + } ret wbcx.success; } diff --git a/src/rustc/middle/typeck/coherence.rs b/src/rustc/middle/typeck/coherence.rs index 228bf408836..593b7d2a2e1 100644 --- a/src/rustc/middle/typeck/coherence.rs +++ b/src/rustc/middle/typeck/coherence.rs @@ -242,7 +242,7 @@ class CoherenceChecker { fn universally_quantify_polytype(polytype: ty_param_bounds_and_ty) -> t { let self_region = if polytype.rp {none} - else {some(self.inference_context.next_region_var())}; + else {some(self.inference_context.next_region_var_nb())}; let bounds_count = polytype.bounds.len(); let type_parameters = diff --git a/src/rustc/middle/typeck/infer.rs b/src/rustc/middle/typeck/infer.rs index 9b725072549..f437aa2ed62 100644 --- a/src/rustc/middle/typeck/infer.rs +++ b/src/rustc/middle/typeck/infer.rs @@ -181,6 +181,7 @@ import driver::session::session; import util::common::{indent, indenter}; import ast::{unsafe_fn, impure_fn, pure_fn, extern_fn}; import ast::{m_const, m_imm, m_mutbl}; +import dvec::{dvec, extensions}; export infer_ctxt; export new_infer_ctxt; @@ -188,16 +189,16 @@ export mk_subty, can_mk_subty; export mk_subr; export mk_eqty; export mk_assignty, can_mk_assignty; -export resolve_shallow; -export resolve_deep; -export resolve_deep_var; +export resolve_nested_tvar, resolve_rvar, resolve_ivar, resolve_all; +export force_tvar, force_rvar, force_ivar, force_all; +export resolve_type, resolve_region; +export resolve_borrowings; export methods; // for infer_ctxt export unify_methods; // for infer_ctxt export fixup_err, fixup_err_to_str; export assignment; export root, to_str; export int_ty_set_all; -export force_level, force_none, force_ty_vars_only, force_all; // Bitvector to represent sets of integral types enum int_ty_set = uint; @@ -280,7 +281,8 @@ fn convert_integral_ty_to_int_ty_set(tcx: ty::ctxt, t: ty::t) // value should be borrowed. type assignment = { expr_id: ast::node_id, - borrow_scope: ast::node_id + borrow_lb: ast::node_id, + borrow_ub: ast::node_id, }; type bound = option; @@ -321,6 +323,10 @@ enum infer_ctxt = @{ ty_var_counter: @mut uint, ty_var_integral_counter: @mut uint, region_var_counter: @mut uint, + + borrowings: dvec<{expr_id: ast::node_id, + scope: ty::region, + mutbl: ast::mutability}> }; enum fixup_err { @@ -328,7 +334,7 @@ enum fixup_err { unresolved_ty(tv_vid), cyclic_ty(tv_vid), unresolved_region(region_vid), - cyclic_region(region_vid) + region_var_bound_by_region_var(region_vid, region_vid) } fn fixup_err_to_str(f: fixup_err) -> ~str { @@ -337,7 +343,10 @@ fn fixup_err_to_str(f: fixup_err) -> ~str { unresolved_ty(_) { ~"unconstrained type" } cyclic_ty(_) { ~"cyclic type of infinite size" } unresolved_region(_) { ~"unconstrained region" } - cyclic_region(_) { ~"cyclic region" } + region_var_bound_by_region_var(r1, r2) { + #fmt["region var %? bound by another region var %?; this is \ + a bug in rustc", r1, r2] + } } } @@ -351,7 +360,8 @@ fn new_infer_ctxt(tcx: ty::ctxt) -> infer_ctxt { rb: {vals: smallintmap::mk(), mut bindings: ~[]}, ty_var_counter: @mut 0u, ty_var_integral_counter: @mut 0u, - region_var_counter: @mut 0u})} + region_var_counter: @mut 0u, + borrowings: dvec()})} fn mk_subty(cx: infer_ctxt, a: ty::t, b: ty::t) -> ures { #debug["mk_subty(%s <: %s)", a.to_str(cx), b.to_str(cx)]; @@ -387,10 +397,11 @@ fn can_mk_assignty(cx: infer_ctxt, anmnt: assignment, #debug["can_mk_assignty(%? / %s <: %s)", anmnt, a.to_str(cx), b.to_str(cx)]; - // FIXME (#2593): this will not unroll any entries we make in the - // borrowings table. But this is OK for the moment because this is only - // used in method lookup, and there must be exactly one match or an - // error is reported. Still, it should be fixed. + // FIXME(#2593)---this will not unroll any entries we make in the + // borrowings table. But this is OK for the moment because this + // is only used in method lookup, and there must be exactly one + // match or an error is reported. Still, it should be fixed. (#2593) + // NDM OUTDATED indent(|| cx.probe(|| cx.assign_tys(anmnt, a, b) @@ -398,21 +409,32 @@ fn can_mk_assignty(cx: infer_ctxt, anmnt: assignment, } // See comment on the type `resolve_state` below -fn resolve_shallow(cx: infer_ctxt, a: ty::t, - force_vars: force_level) -> fres { - resolver(cx, false, force_vars).resolve(a) -} - -// See comment on the type `resolve_state` below -fn resolve_deep_var(cx: infer_ctxt, vid: tv_vid, - force_vars: force_level) -> fres { - resolver(cx, true, force_vars).resolve(ty::mk_var(cx.tcx, vid)) -} - -// See comment on the type `resolve_state` below -fn resolve_deep(cx: infer_ctxt, a: ty::t, force_vars: force_level) +fn resolve_type(cx: infer_ctxt, a: ty::t, modes: uint) -> fres { - resolver(cx, true, force_vars).resolve(a) + resolver(cx, modes).resolve_type_chk(a) +} + +fn resolve_region(cx: infer_ctxt, r: ty::region, modes: uint) + -> fres { + resolver(cx, modes).resolve_region_chk(r) +} + +fn resolve_borrowings(cx: infer_ctxt) { + for cx.borrowings.each |item| { + alt resolve_region(cx, item.scope, resolve_all|force_all) { + ok(ty::re_scope(scope_id)) => { + #debug["borrowing for expr %d resolved to scope %d, mutbl %?", + item.expr_id, scope_id, item.mutbl]; + cx.tcx.borrowings.insert( + item.expr_id, {scope_id: scope_id, mutbl: item.mutbl}); + } + + r => { + cx.tcx.sess.bug( + #fmt["borrowing resolved to %?, not a valid scope", r]); + } + } + } } impl methods for ures { @@ -567,6 +589,8 @@ impl transaction_methods for infer_ctxt { let tvbl = self.tvb.bindings.len(); let rbl = self.rb.bindings.len(); + let bl = self.borrowings.len(); + #debug["try(tvbl=%u, rbl=%u)", tvbl, rbl]; let r <- f(); alt r { @@ -575,6 +599,7 @@ impl transaction_methods for infer_ctxt { #debug["try--rollback"]; rollback_to(self.tvb, tvbl); rollback_to(self.rb, rbl); + while self.borrowings.len() != bl { self.borrowings.pop(); } } } ret r; @@ -621,16 +646,19 @@ impl methods for infer_ctxt { ty::mk_var_integral(self.tcx, self.next_ty_var_integral_id()) } - fn next_region_var_id() -> region_vid { + fn next_region_var_id(bnds: bounds) -> region_vid { let id = *self.region_var_counter; *self.region_var_counter += 1u; - self.rb.vals.insert(id, - root({lb: none, ub: none}, 0u)); + self.rb.vals.insert(id, root(bnds, 0)); ret region_vid(id); } - fn next_region_var() -> ty::region { - ty::re_var(self.next_region_var_id()) + fn next_region_var(bnds: bounds) -> ty::region { + ty::re_var(self.next_region_var_id(bnds)) + } + + fn next_region_var_nb() -> ty::region { // nb == "no bounds" + self.next_region_var({lb: none, ub: none}) } fn ty_to_str(t: ty::t) -> ~str { @@ -639,11 +667,18 @@ impl methods for infer_ctxt { } fn resolve_type_vars_if_possible(typ: ty::t) -> ty::t { - alt infer::resolve_deep(self, typ, force_none) { + alt resolve_type(self, typ, resolve_all) { result::ok(new_type) { ret new_type; } result::err(_) { ret typ; } } } + + fn resolve_region_if_possible(oldr: ty::region) -> ty::region { + alt resolve_region(self, oldr, resolve_all) { + result::ok(newr) { ret newr; } + result::err(_) { ret oldr; } + } + } } impl unify_methods for infer_ctxt { @@ -1029,67 +1064,70 @@ impl unify_methods for infer_ctxt { // the behavior in the face of unconstrained type and region // variables. -enum force_level { - // Any unconstrained variables are OK. - force_none, - - // Unconstrained region vars and integral ty vars are OK; - // unconstrained general-purpose ty vars result in an error. - force_ty_vars_only, - - // Any unconstrained variables result in an error. - force_all, -} - +const resolve_nested_tvar: uint = 0b00000001; +const resolve_rvar: uint = 0b00000010; +const resolve_ivar: uint = 0b00000100; +const resolve_all: uint = 0b00000111; +const force_tvar: uint = 0b00010000; +const force_rvar: uint = 0b00100000; +const force_ivar: uint = 0b01000000; +const force_all: uint = 0b01110000; type resolve_state = @{ infcx: infer_ctxt, - deep: bool, - force_vars: force_level, + modes: uint, mut err: option, - mut r_seen: ~[region_vid], mut v_seen: ~[tv_vid] }; -fn resolver(infcx: infer_ctxt, deep: bool, fvars: force_level) +fn resolver(infcx: infer_ctxt, modes: uint) -> resolve_state { @{infcx: infcx, - deep: deep, - force_vars: fvars, + modes: modes, mut err: none, - mut r_seen: ~[], mut v_seen: ~[]} } impl methods for resolve_state { - fn resolve(typ: ty::t) -> fres { + fn should(mode: uint) -> bool { + (self.modes & mode) == mode + } + + fn resolve_type_chk(typ: ty::t) -> fres { self.err = none; - #debug["Resolving %s (deep=%b, force_vars=%?)", + #debug["Resolving %s (modes=%x)", ty_to_str(self.infcx.tcx, typ), - self.deep, - self.force_vars]; + self.modes]; // n.b. This is a hokey mess because the current fold doesn't // allow us to pass back errors in any useful way. - assert vec::is_empty(self.v_seen) && vec::is_empty(self.r_seen); - let rty = indent(|| self.resolve1(typ) ); - assert vec::is_empty(self.v_seen) && vec::is_empty(self.r_seen); + assert vec::is_empty(self.v_seen); + let rty = indent(|| self.resolve_type(typ) ); + assert vec::is_empty(self.v_seen); alt self.err { none { - #debug["Resolved to %s (deep=%b, force_vars=%?)", + #debug["Resolved to %s (modes=%x)", ty_to_str(self.infcx.tcx, rty), - self.deep, - self.force_vars]; + self.modes]; ret ok(rty); } some(e) { ret err(e); } } } - fn resolve1(typ: ty::t) -> ty::t { - #debug("Resolve1(%s)", typ.to_str(self.infcx)); + fn resolve_region_chk(orig: ty::region) -> fres { + self.err = none; + let resolved = indent(|| self.resolve_region(orig) ); + alt self.err { + none {ok(resolved)} + some(e) {err(e)} + } + } + + fn resolve_type(typ: ty::t) -> ty::t { + #debug("resolve_type(%s)", typ.to_str(self.infcx)); indent(fn&() -> ty::t { if !ty::type_needs_infer(typ) { ret typ; } @@ -1100,23 +1138,30 @@ impl methods for resolve_state { ty::ty_var_integral(vid) { self.resolve_ty_var_integral(vid) } - _ if !ty::type_has_regions(typ) && !self.deep { - typ - } _ { - ty::fold_regions_and_ty( - self.infcx.tcx, typ, - |r| self.resolve_region(r), - |t| self.resolve_if_deep(t), - |t| self.resolve_if_deep(t)) + if !self.should(resolve_rvar) && + !self.should(resolve_nested_tvar) { + // shortcircuit for efficiency + typ + } else { + ty::fold_regions_and_ty( + self.infcx.tcx, typ, + |r| self.resolve_region(r), + |t| self.resolve_nested_tvar(t), + |t| self.resolve_nested_tvar(t)) + } } } }) } - fn resolve_if_deep(typ: ty::t) -> ty::t { + fn resolve_nested_tvar(typ: ty::t) -> ty::t { #debug("Resolve_if_deep(%s)", typ.to_str(self.infcx)); - if !self.deep {typ} else {self.resolve1(typ)} + if !self.should(resolve_nested_tvar) { + typ + } else { + self.resolve_type(typ) + } } fn resolve_region(orig: ty::region) -> ty::region { @@ -1128,29 +1173,29 @@ impl methods for resolve_state { } fn resolve_region_var(rid: region_vid) -> ty::region { - if vec::contains(self.r_seen, rid) { - self.err = some(cyclic_region(rid)); - ret ty::re_var(rid); - } else { - vec::push(self.r_seen, rid); - let nde = self.infcx.get(self.infcx.rb, rid); - let bounds = nde.possible_types; + if !self.should(resolve_rvar) { + ret ty::re_var(rid) + } + let nde = self.infcx.get(self.infcx.rb, rid); + let bounds = nde.possible_types; + alt bounds { + { ub:_, lb:some(r) } => { self.assert_not_rvar(rid, r); r } + { ub:some(r), lb:_ } => { self.assert_not_rvar(rid, r); r } + { ub:none, lb:none } => { + if self.should(force_rvar) { + self.err = some(unresolved_region(rid)); + } + ty::re_var(rid) + } + } + } - let r1 = alt bounds { - { ub:_, lb:some(t) } { self.resolve_region(t) } - { ub:some(t), lb:_ } { self.resolve_region(t) } - { ub:none, lb:none } { - alt self.force_vars { - force_all { - self.err = some(unresolved_region(rid)); - } - _ { /* ok */ } - } - ty::re_var(rid) - } - }; - vec::pop(self.r_seen); - ret r1; + fn assert_not_rvar(rid: region_vid, r: ty::region) { + alt r { + ty::re_var(rid2) => { + self.err = some(region_var_bound_by_region_var(rid, rid2)); + } + _ => { } } } @@ -1172,15 +1217,12 @@ impl methods for resolve_state { let bounds = nde.possible_types; let t1 = alt bounds { - { ub:_, lb:some(t) } if !type_is_bot(t) { self.resolve1(t) } - { ub:some(t), lb:_ } { self.resolve1(t) } - { ub:_, lb:some(t) } { self.resolve1(t) } + { ub:_, lb:some(t) } if !type_is_bot(t) { self.resolve_type(t) } + { ub:some(t), lb:_ } { self.resolve_type(t) } + { ub:_, lb:some(t) } { self.resolve_type(t) } { ub:none, lb:none } { - alt self.force_vars { - force_ty_vars_only | force_all { + if self.should(force_tvar) { self.err = some(unresolved_ty(vid)); - } - force_none { /* ok */ } } ty::mk_var(tcx, vid) } @@ -1191,6 +1233,10 @@ impl methods for resolve_state { } fn resolve_ty_var_integral(vid: tvi_vid) -> ty::t { + if !self.should(resolve_ivar) { + ret ty::mk_var_integral(self.infcx.tcx, vid); + } + let nde = self.infcx.get(self.infcx.tvib, vid); let pt = nde.possible_types; @@ -1199,8 +1245,7 @@ impl methods for resolve_state { alt single_type_contained_in(self.infcx.tcx, pt) { some(t) { t } none { - alt self.force_vars { - force_all { + if self.should(force_ivar) { // As a last resort, default to int. let ty = ty::mk_int(self.infcx.tcx); self.infcx.set( @@ -1209,10 +1254,8 @@ impl methods for resolve_state { ty), nde.rank)); ty - } - force_none | force_ty_vars_only { + } else { ty::mk_var_integral(self.infcx.tcx, vid) - } } } } @@ -1393,15 +1436,22 @@ impl assignment for infer_ctxt { do indent { do self.sub_tys(a, nr_b).then { - let r_a = ty::re_scope(anmnt.borrow_scope); + // Create a fresh region variable `r_a` with the given + // borrow bounds: + let r_lb = ty::re_scope(anmnt.borrow_lb); + let r_ub = ty::re_scope(anmnt.borrow_ub); + let r_a = self.next_region_var({lb: some(r_lb), + ub: some(r_ub)}); + #debug["anmnt=%?", anmnt]; do sub(self).contraregions(r_a, r_b).chain |_r| { // if successful, add an entry indicating that // borrowing occurred - #debug["borrowing expression #%?", anmnt]; - let borrow = {scope_id: anmnt.borrow_scope, - mutbl: m}; - self.tcx.borrowings.insert(anmnt.expr_id, borrow); + #debug["borrowing expression #%?, scope=%?, m=%?", + anmnt, r_a, m]; + self.borrowings.push({expr_id: anmnt.expr_id, + scope: r_a, + mutbl: m}); uok() } } @@ -1921,7 +1971,7 @@ impl of combine for sub { // anything to do with the region variable that's created // for it. The only thing we're doing with `br` here is // using it in the debug message. - let rvar = self.infcx().next_region_var(); + let rvar = self.infcx().next_region_var_nb(); #debug["Bound region %s maps to %s", bound_region_to_str(self.tcx, br), region_to_str(self.tcx, rvar)]; diff --git a/src/rustc/util/ppaux.rs b/src/rustc/util/ppaux.rs index 04152eb69d3..cbcd4b31e53 100644 --- a/src/rustc/util/ppaux.rs +++ b/src/rustc/util/ppaux.rs @@ -43,7 +43,11 @@ fn re_scope_id_to_str(cx: ctxt, node_id: ast::node_id) -> ~str { #fmt("", codemap::span_to_str(expr.span, cx.sess.codemap)) } - ast::expr_field(*) | ast::expr_unary(*) | ast::expr_binary(*) { + ast::expr_assign_op(*) | + ast::expr_field(*) | + ast::expr_unary(*) | + ast::expr_binary(*) | + ast::expr_index(*) { #fmt("", codemap::span_to_str(expr.span, cx.sess.codemap)) } diff --git a/src/test/compile-fail/regions-borrow.rs b/src/test/compile-fail/regions-borrow.rs deleted file mode 100644 index 5e893246cdc..00000000000 --- a/src/test/compile-fail/regions-borrow.rs +++ /dev/null @@ -1,8 +0,0 @@ -fn foo(x: &uint) -> &uint { x } - -fn main() { - let p = @3u; - let r = foo(p); - //~^ ERROR reference is not valid - assert *p == *r; -} diff --git a/src/test/compile-fail/regions-infer-borrow-scope-too-big.rs b/src/test/compile-fail/regions-infer-borrow-scope-too-big.rs new file mode 100644 index 00000000000..aeb864d92a7 --- /dev/null +++ b/src/test/compile-fail/regions-infer-borrow-scope-too-big.rs @@ -0,0 +1,14 @@ +type point = {x: int, y: int}; + +fn x_coord(p: &point) -> &int { + ret &p.x; +} + +fn foo(p: @point) -> &int { + let xc = x_coord(p); + assert *xc == 3; + ret xc; //~ ERROR mismatched types: expected `&int` but found +} + +fn main() {} + diff --git a/src/test/compile-fail/regions-infer-borrow-scope-within-loop.rs b/src/test/compile-fail/regions-infer-borrow-scope-within-loop.rs new file mode 100644 index 00000000000..e89d801b361 --- /dev/null +++ b/src/test/compile-fail/regions-infer-borrow-scope-within-loop.rs @@ -0,0 +1,11 @@ +fn borrow(x: &T) -> &T {x} + +fn main() { + let x = @3; + let y: ∫ //~ ERROR reference is not valid outside of its lifetime + while true { + y = borrow(x); + assert *x == *y; + } + assert *x == *y; +} diff --git a/src/test/compile-fail/regions-escape-into-other-fn.rs b/src/test/run-pass/regions-escape-into-other-fn.rs similarity index 65% rename from src/test/compile-fail/regions-escape-into-other-fn.rs rename to src/test/run-pass/regions-escape-into-other-fn.rs index 67c7dd3317a..8f71d9821fe 100644 --- a/src/test/compile-fail/regions-escape-into-other-fn.rs +++ b/src/test/run-pass/regions-escape-into-other-fn.rs @@ -3,5 +3,5 @@ fn bar(x: &uint) -> uint { *x } fn main() { let p = @3u; - bar(foo(p)); //~ ERROR reference is not valid + assert bar(foo(p)) == 3; } diff --git a/src/test/run-pass/regions-infer-borrow-scope-view.rs b/src/test/run-pass/regions-infer-borrow-scope-view.rs new file mode 100644 index 00000000000..4f3b668269a --- /dev/null +++ b/src/test/run-pass/regions-infer-borrow-scope-view.rs @@ -0,0 +1,9 @@ +fn view(x: &[T]) -> &[T] {x} + +fn main() { + let v = ~[1, 2, 3]; + let x = view(v); + let y = view(x); + assert (v[0] == x[0]) && (v[0] == y[0]); +} + diff --git a/src/test/run-pass/regions-infer-borrow-scope-within-loop-ok.rs b/src/test/run-pass/regions-infer-borrow-scope-within-loop-ok.rs new file mode 100644 index 00000000000..bd51c4beca5 --- /dev/null +++ b/src/test/run-pass/regions-infer-borrow-scope-within-loop-ok.rs @@ -0,0 +1,10 @@ +fn borrow(x: &T) -> &T {x} + +fn main() { + let x = @3; + loop { + let y = borrow(x); + assert *x == *y; + break; + } +} diff --git a/src/test/run-pass/regions-infer-borrow-scope.rs b/src/test/run-pass/regions-infer-borrow-scope.rs new file mode 100644 index 00000000000..0f099672f40 --- /dev/null +++ b/src/test/run-pass/regions-infer-borrow-scope.rs @@ -0,0 +1,12 @@ +type point = {x: int, y: int}; + +fn x_coord(p: &point) -> &int { + ret &p.x; +} + +fn main() { + let p = @{x: 3, y: 4}; + let xc = x_coord(p); + assert *xc == 3; +} +