Rewrite the coercion code to be more readable, more sound, and to reborrow when

needed.

Regarding soundness: there was a subtle bug in how it was done before; see the
compile-fail test for an example.

Regarding reborrowing: reborrowing allows mut and const
slices/borrowed-pointers to be used with pure fns that expect immutable data.

r=brson
This commit is contained in:
Niko Matsakis 2013-01-22 17:20:08 -08:00
parent c07ae16de1
commit 2b67d88809
23 changed files with 644 additions and 303 deletions

View File

@ -15,7 +15,7 @@ use middle::pat_util::{pat_is_variant_or_struct};
use middle::ty;
use middle::typeck::check::demand;
use middle::typeck::check::{check_block, check_expr_has_type, fn_ctxt};
use middle::typeck::check::{instantiate_path, lookup_def, lookup_local};
use middle::typeck::check::{instantiate_path, lookup_def};
use middle::typeck::check::{structure_of, valid_range_bounds};
use middle::typeck::require_same_types;
@ -365,8 +365,7 @@ fn check_pat(pcx: pat_ctxt, pat: @ast::pat, expected: ty::t) {
fcx.write_ty(pat.id, const_tpt.ty);
}
ast::pat_ident(bm, name, sub) if pat_is_binding(tcx.def_map, pat) => {
let vid = lookup_local(fcx, pat.span, pat.id);
let mut typ = ty::mk_var(tcx, vid);
let typ = fcx.local_ty(pat.span, pat.id);
match bm {
ast::bind_by_ref(mutbl) => {
@ -389,8 +388,7 @@ fn check_pat(pcx: pat_ctxt, pat: @ast::pat, expected: ty::t) {
let canon_id = pcx.map.get(ast_util::path_to_ident(name));
if canon_id != pat.id {
let tv_id = lookup_local(fcx, pat.span, canon_id);
let ct = ty::mk_var(tcx, tv_id);
let ct = fcx.local_ty(pat.span, canon_id);
demand::eqtype(fcx, pat.span, ct, typ);
}
fcx.write_ty(pat.id, typ);

View File

@ -50,7 +50,10 @@ fn eqtype(fcx: @fn_ctxt, sp: span, expected: ty::t, actual: ty::t) {
}
// Checks that the type `actual` can be coerced to `expected`.
fn coerce(fcx: @fn_ctxt, sp: span, expected: ty::t, expr: @ast::expr) {
fn coerce(fcx: @fn_ctxt,
sp: span,
expected: ty::t,
expr: @ast::expr) {
let expr_ty = fcx.expr_ty(expr);
match fcx.mk_assignty(expr, expr_ty, expected) {
result::Ok(()) => { /* ok */ }

View File

@ -700,6 +700,8 @@ impl LookupContext {
autoderefs: uint)
-> Option<method_map_entry>
{
let (self_ty, autoadjust) =
self.consider_reborrow(self_ty, autoderefs);
match self.search_for_method(self_ty) {
None => None,
Some(move mme) => {
@ -707,13 +709,82 @@ impl LookupContext {
adjustment (%u) to %d",
autoderefs,
self.self_expr.id);
self.fcx.write_autoderef_adjustment(
self.self_expr.id, autoderefs);
self.fcx.write_adjustment(self.self_expr.id, @autoadjust);
Some(mme)
}
}
}
fn consider_reborrow(&self,
self_ty: ty::t,
autoderefs: uint) -> (ty::t, ty::AutoAdjustment)
{
/*!
*
* In the event that we are invoking a method with a receiver
* of a linear borrowed type like `&mut T` or `&[mut T]`,
* we will "reborrow" the receiver implicitly. For example, if
* you have a call `r.inc()` and where `r` has type `&mut T`,
* then we treat that like `(&mut *r).inc()`. This avoids
* consuming the original pointer.
*
* You might think that this would be a natural byproduct of
* the auto-deref/auto-ref process. This is true for `@mut T`
* but not for an `&mut T` receiver. With `@mut T`, we would
* begin by testing for methods with a self type `@mut T`,
* then autoderef to `T`, then autoref to `&mut T`. But with
* an `&mut T` receiver the process begins with `&mut T`, only
* without any autoadjustments.
*/
let tcx = self.tcx();
return match ty::get(self_ty).sty {
ty::ty_rptr(self_r, self_mt) if self_mt.mutbl == m_mutbl => {
let region = fresh_region(self, self_r);
(ty::mk_rptr(tcx, region, self_mt),
ty::AutoAdjustment {
autoderefs: autoderefs+1,
autoref: Some(ty::AutoRef {kind: AutoPtr,
region: region,
mutbl: self_mt.mutbl})})
}
ty::ty_evec(self_mt, vstore_slice(self_r))
if self_mt.mutbl == m_mutbl => {
let region = fresh_region(self, self_r);
(ty::mk_evec(tcx, self_mt, vstore_slice(region)),
ty::AutoAdjustment {
autoderefs: autoderefs,
autoref: Some(ty::AutoRef {kind: AutoBorrowVec,
region: region,
mutbl: self_mt.mutbl})})
}
_ => {
(self_ty, ty::AutoAdjustment {autoderefs: autoderefs,
autoref: None})
}
};
fn fresh_region(self: &LookupContext,
self_r: ty::Region) -> ty::Region {
let region = self.infcx().next_region_var(self.expr.span,
self.expr.id);
// FIXME(#3148)---in principle this dependency should
// be done more generally as part of regionck
match infer::mk_subr(self.infcx(), true, self.expr.span,
region, self_r) {
Ok(_) => {}
Err(e) => {
self.tcx().sess.span_bug(
self.expr.span,
fmt!("Failed with error: %?", e));
}
}
return region;
}
}
fn search_for_autosliced_method(
&self,
self_ty: ty::t,
@ -729,6 +800,7 @@ impl LookupContext {
match ty::get(self_ty).sty {
ty_evec(mt, vstore_box) |
ty_evec(mt, vstore_uniq) |
ty_evec(mt, vstore_slice(_)) | // NDM(#3148)
ty_evec(mt, vstore_fixed(_)) => {
// First try to borrow to a slice
let entry = self.search_for_some_kind_of_autorefd_method(

View File

@ -139,7 +139,6 @@ export regionck;
export demand;
export method;
export fn_ctxt;
export lookup_local;
export impl_self_ty;
export DerefArgs;
export DontDerefArgs;
@ -189,7 +188,7 @@ type self_info = {
/// share the inherited fields.
struct inherited {
infcx: @infer::InferCtxt,
locals: HashMap<ast::node_id, TyVid>,
locals: HashMap<ast::node_id, ty::t>,
node_types: HashMap<ast::node_id, ty::t>,
node_type_substs: HashMap<ast::node_id, ty::substs>,
adjustments: HashMap<ast::node_id, @ty::AutoAdjustment>
@ -376,8 +375,7 @@ fn check_fn(ccx: @crate_ctxt,
}
};
// XXX: Bad copy.
gather_locals(fcx, decl, body, copy arg_tys, self_info);
gather_locals(fcx, decl, body, arg_tys, self_info);
check_block(fcx, body);
// We unify the tail expr's type with the
@ -414,30 +412,31 @@ fn check_fn(ccx: @crate_ctxt,
fn gather_locals(fcx: @fn_ctxt,
decl: &ast::fn_decl,
body: ast::blk,
arg_tys: ~[ty::t],
arg_tys: &[ty::t],
self_info: Option<self_info>) {
let tcx = fcx.ccx.tcx;
let assign = fn@(span: span, nid: ast::node_id,
ty_opt: Option<ty::t>) {
let var_id = fcx.infcx().next_ty_var_id();
fcx.inh.locals.insert(nid, var_id);
let assign = fn@(nid: ast::node_id, ty_opt: Option<ty::t>) {
match ty_opt {
None => {/* nothing to do */ }
None => {
// infer the variable's type
let var_id = fcx.infcx().next_ty_var_id();
let var_ty = ty::mk_var(fcx.tcx(), var_id);
fcx.inh.locals.insert(nid, var_ty);
}
Some(typ) => {
infer::mk_eqty(fcx.infcx(), false, span,
ty::mk_var(tcx, var_id), typ);
// take type that the user specified
fcx.inh.locals.insert(nid, typ);
}
}
};
// Add the self parameter
for self_info.each |self_info| {
assign(self_info.explicit_self.span,
self_info.self_id,
Some(self_info.self_ty));
assign(self_info.self_id, Some(self_info.self_ty));
debug!("self is assigned to %s",
fcx.inh.locals.get(self_info.self_id).to_str());
fcx.infcx().ty_to_str(
fcx.inh.locals.get(self_info.self_id)));
}
// Add formal parameters.
@ -445,7 +444,7 @@ fn check_fn(ccx: @crate_ctxt,
// Create type variables for each argument.
do pat_util::pat_bindings(tcx.def_map, input.pat)
|_bm, pat_id, _sp, _path| {
assign(input.ty.span, pat_id, None);
assign(pat_id, None);
}
// Check the pattern.
@ -466,10 +465,11 @@ fn check_fn(ccx: @crate_ctxt,
ast::ty_infer => None,
_ => Some(fcx.to_ty(local.node.ty))
};
assign(local.span, local.node.id, o_ty);
debug!("Local variable %s is assigned to %s",
assign(local.node.id, o_ty);
debug!("Local variable %s is assigned type %s",
fcx.pat_to_str(local.node.pat),
fcx.inh.locals.get(local.node.id).to_str());
fcx.infcx().ty_to_str(
fcx.inh.locals.get(local.node.id)));
visit::visit_local(local, e, v);
};
@ -478,10 +478,11 @@ fn check_fn(ccx: @crate_ctxt,
match p.node {
ast::pat_ident(_, path, _)
if pat_util::pat_is_binding(fcx.ccx.tcx.def_map, p) => {
assign(p.span, p.id, None);
assign(p.id, None);
debug!("Pattern binding %s is assigned to %s",
tcx.sess.str_of(path.idents[0]),
fcx.inh.locals.get(p.id).to_str());
fcx.infcx().ty_to_str(
fcx.inh.locals.get(p.id)));
}
_ => {}
}
@ -694,6 +695,17 @@ impl @fn_ctxt: region_scope {
impl @fn_ctxt {
fn tag() -> ~str { fmt!("%x", ptr::addr_of(&(*self)) as uint) }
fn local_ty(span: span, nid: ast::node_id) -> ty::t {
match self.inh.locals.find(nid) {
Some(t) => t,
None => {
self.tcx().sess.span_bug(
span,
fmt!("No type for local variable %?", nid));
}
}
}
fn expr_to_str(expr: @ast::expr) -> ~str {
fmt!("expr(%?:%s)", expr.id,
pprust::expr_to_str(expr, self.tcx().sess.intr()))
@ -1359,10 +1371,8 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
fn check_for(fcx: @fn_ctxt, local: @ast::local,
element_ty: ty::t, body: ast::blk,
node_id: ast::node_id) -> bool {
let locid = lookup_local(fcx, local.span, local.node.id);
demand::suptype(fcx, local.span,
ty::mk_var(fcx.ccx.tcx, locid),
element_ty);
let local_ty = fcx.local_ty(local.span, local.node.id);
demand::suptype(fcx, local.span, local_ty, element_ty);
let bot = check_decl_local(fcx, local);
check_block_no_value(fcx, body);
fcx.write_nil(node_id);
@ -2551,15 +2561,15 @@ fn require_integral(fcx: @fn_ctxt, sp: span, t: ty::t) {
fn check_decl_initializer(fcx: @fn_ctxt, nid: ast::node_id,
init: @ast::expr) -> bool {
let lty = ty::mk_var(fcx.ccx.tcx, lookup_local(fcx, init.span, nid));
return check_expr_coercable_to_type(fcx, init, lty);
let local_ty = fcx.local_ty(init.span, nid);
return check_expr_coercable_to_type(fcx, init, local_ty);
}
fn check_decl_local(fcx: @fn_ctxt, local: @ast::local) -> bool {
let mut bot = false;
let tcx = fcx.ccx.tcx;
let t = ty::mk_var(tcx, fcx.inh.locals.get(local.node.id));
let t = fcx.local_ty(local.span, local.node.id);
fcx.write_ty(local.node.id, t);
match local.node.init {
@ -2819,17 +2829,6 @@ fn check_enum_variants(ccx: @crate_ctxt,
check_instantiable(ccx.tcx, sp, id);
}
pub fn lookup_local(fcx: @fn_ctxt, sp: span, id: ast::node_id) -> TyVid {
match fcx.inh.locals.find(id) {
Some(x) => x,
_ => {
fcx.ccx.tcx.sess.span_fatal(
sp,
~"internal error looking up a local var")
}
}
}
fn lookup_def(fcx: @fn_ctxt, sp: span, id: ast::node_id) -> ast::def {
lookup_def_ccx(fcx.ccx, sp, id)
}
@ -2841,9 +2840,8 @@ fn ty_param_bounds_and_ty_for_def(fcx: @fn_ctxt, sp: span, defn: ast::def) ->
match defn {
ast::def_arg(nid, _, _) | ast::def_local(nid, _) |
ast::def_self(nid, _) | ast::def_binding(nid, _) => {
assert (fcx.inh.locals.contains_key(nid));
let typ = ty::mk_var(fcx.ccx.tcx, lookup_local(fcx, sp, nid));
return no_params(typ);
let typ = fcx.local_ty(sp, nid);
return no_params(typ);
}
ast::def_fn(_, ast::extern_fn) => {
// extern functions are just u8 pointers

View File

@ -16,7 +16,7 @@ use core::prelude::*;
use middle::pat_util;
use middle::ty;
use middle::typeck::check::{fn_ctxt, lookup_local, self_info};
use middle::typeck::check::{fn_ctxt, self_info};
use middle::typeck::infer::{force_all, resolve_all, resolve_region};
use middle::typeck::infer::{resolve_type};
use middle::typeck::infer;
@ -216,8 +216,7 @@ 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 { return; }
let var_id = lookup_local(wbcx.fcx, l.span, l.node.id);
let var_ty = ty::mk_var(wbcx.fcx.tcx(), var_id);
let var_ty = wbcx.fcx.local_ty(l.span, l.node.id);
match resolve_type(wbcx.fcx.infcx(), var_ty, resolve_all | force_all) {
Ok(lty) => {
debug!("Type for local %s (id %d) resolved to %s",

View File

@ -8,280 +8,389 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// ______________________________________________________________________
// Type assignment
//
// True if rvalues of type `a` can be assigned to lvalues of type `b`.
// This may cause borrowing to the region scope enclosing `a_node_id`.
//
// The strategy here is somewhat non-obvious. The problem is
// that the constraint we wish to contend with is not a subtyping
// constraint. Currently, for variables, we only track what it
// must be a subtype of, not what types it must be assignable to
// (or from). Possibly, we should track that, but I leave that
// refactoring for another day.
//
// Instead, we look at each variable involved and try to extract
// *some* sort of bound. Typically, the type a is the argument
// supplied to a call; it typically has a *lower bound* (which
// comes from having been assigned a value). What we'd actually
// *like* here is an upper-bound, but we generally don't have
// one. The type b is the expected type and it typically has a
// lower-bound too, which is good.
//
// The way we deal with the fact that we often don't have the
// bounds we need is to be a bit careful. We try to get *some*
// bound from each side, preferring the upper from a and the
// lower from b. If we fail to get a bound from both sides, then
// we just fall back to requiring that a <: b.
//
// Assuming we have a bound from both sides, we will then examine
// these bounds and see if they have the form (@M_a T_a, &rb.M_b T_b)
// (resp. ~M_a T_a, ~[M_a T_a], etc). If they do not, we fall back to
// subtyping.
//
// If they *do*, then we know that the two types could never be
// subtypes of one another. We will then construct a type @const T_b
// and ensure that type a is a subtype of that. This allows for the
// possibility of assigning from a type like (say) @~[mut T1] to a type
// &~[T2] where T1 <: T2. This might seem surprising, since the `@`
// points at mutable memory but the `&` points at immutable memory.
// This would in fact be unsound, except for the borrowck, which comes
// later and guarantees that such mutability conversions are safe.
// See borrowck for more details. Next we require that the region for
// the enclosing scope be a superregion of the region r.
//
// You might wonder why we don't make the type &e.const T_a where e is
// the enclosing region and check that &e.const T_a <: B. The reason
// is that the type of A is (generally) just a *lower-bound*, so this
// would be imposing that lower-bound also as the upper-bound on type
// A. But this upper-bound might be stricter than what is truly
// needed.
/*!
# Type Coercion
Under certain circumstances we will coerce from one type to another,
for example by auto-borrowing. This occurs in situations where the
compiler has a firm 'expected type' that was supplied from the user,
and where the actual type is similar to that expected type in purpose
but not in representation (so actual subtyping is inappropriate).
## Reborrowing
Note that if we are expecting a borrowed pointer, we will *reborrow*
even if the argument provided was already a borrowed pointer. This is
useful for freezing mut/const things (that is, when the expected is &T
but you have &const T or &mut T) and also for avoiding the linearity
of mut things (when the expected is &mut T and you have &mut T). See
the various `src/test/run-pass/coerce-reborrow-*.rs` tests for
examples of where this is useful.
## Subtle note
When deciding what type coercions to consider, we do not attempt to
resolve any type variables we may encounter. This is because `b`
represents the expected type "as the user wrote it", meaning that if
the user defined a generic function like
fn foo<A>(a: A, b: A) { ... }
and then we wrote `foo(&1, @2)`, we will not auto-borrow
either argument. In older code we went to some lengths to
resolve the `b` variable, which could mean that we'd
auto-borrow later arguments but not earlier ones, which
seems very confusing.
## Subtler note
However, right now, if the user manually specifies the
values for the type variables, as so:
foo::<&int>(@1, @2)
then we *will* auto-borrow, because we can't distinguish this from a
function that declared `&int`. This is inconsistent but it's easiest
at the moment. The right thing to do, I think, is to consider the
*unsubstituted* type when deciding whether to auto-borrow, but the
*substituted* type when considering the bounds and so forth. But most
of our methods don't give access to the unsubstituted type, and
rightly so because they'd be error-prone. So maybe the thing to do is
to actually determine the kind of coercions that should occur
separately and pass them in. Or maybe it's ok as is. Anyway, it's
sort of a minor point so I've opted to leave it for later---after all
we may want to adjust precisely when coercions occur.
*/
use core::prelude::*;
use middle::ty::TyVar;
use middle::ty::{TyVar, AutoPtr, AutoBorrowVec, AutoBorrowFn};
use middle::ty::{AutoAdjustment, AutoRef};
use middle::ty::{vstore_slice, vstore_box, vstore_uniq, vstore_fixed};
use middle::ty::{FnMeta, FnTyBase, mt};
use middle::ty;
use middle::typeck::infer::{ares, cres};
use middle::typeck::infer::{CoerceResult, resolve_type};
use middle::typeck::infer::combine::CombineFields;
use middle::typeck::infer::sub::Sub;
use middle::typeck::infer::to_str::InferStr;
use middle::typeck::infer::resolve::try_resolve_tvar_shallow;
use util::common::{indent, indenter};
use core::option;
use syntax::ast::{m_const, m_imm, m_mutbl};
use syntax::ast;
fn to_ares<T>(+c: cres<T>) -> ares {
match c {
Ok(_) => Ok(None),
Err(ref e) => Err((*e))
}
}
// Note: Coerce is not actually a combiner, in that it does not
// conform to the same interface, though it performs a similar
// function.
pub enum Coerce = CombineFields;
impl Coerce {
fn tys(&self, a: ty::t, b: ty::t) -> ares {
fn tys(&self, a: ty::t, b: ty::t) -> CoerceResult {
debug!("Coerce.tys(%s => %s)",
a.inf_str(self.infcx),
b.inf_str(self.infcx));
let _indent = indenter();
let r = match (&ty::get(a).sty, &ty::get(b).sty) {
(&ty::ty_bot, _) => {
Ok(None)
// Examine the supertype and consider auto-borrowing.
//
// Note: does not attempt to resolve type variables we encounter.
// See above for details.
match ty::get(b).sty {
ty::ty_rptr(_, mt_b) => {
return do self.unpack_actual_value(a) |sty_a| {
self.coerce_borrowed_pointer(a, sty_a, b, mt_b)
};
}
(&ty::ty_infer(TyVar(a_id)), &ty::ty_infer(TyVar(b_id))) => {
let nde_a = self.infcx.get(a_id);
let nde_b = self.infcx.get(b_id);
let a_bounds = nde_a.possible_types;
let b_bounds = nde_b.possible_types;
let a_bnd = option::or(a_bounds.ub, a_bounds.lb);
let b_bnd = option::or(b_bounds.lb, b_bounds.ub);
self.coerce_tys_or_sub(a, b, a_bnd, b_bnd)
ty::ty_estr(vstore_slice(_)) => {
return do self.unpack_actual_value(a) |sty_a| {
self.coerce_borrowed_string(a, sty_a, b)
};
}
(&ty::ty_infer(TyVar(a_id)), _) => {
let nde_a = self.infcx.get(a_id);
let a_bounds = nde_a.possible_types;
let a_bnd = option::or(a_bounds.ub, a_bounds.lb);
self.coerce_tys_or_sub(a, b, a_bnd, Some(b))
ty::ty_evec(mt_b, vstore_slice(_)) => {
return do self.unpack_actual_value(a) |sty_a| {
self.coerce_borrowed_vector(a, sty_a, b, mt_b)
};
}
(_, &ty::ty_infer(TyVar(b_id))) => {
let nde_b = self.infcx.get(b_id);
let b_bounds = nde_b.possible_types;
let b_bnd = option::or(b_bounds.lb, b_bounds.ub);
self.coerce_tys_or_sub(a, b, Some(a), b_bnd)
ty::ty_fn(ref b_f) if b_f.meta.proto == ast::ProtoBorrowed => {
return do self.unpack_actual_value(a) |sty_a| {
self.coerce_borrowed_fn(a, sty_a, b)
};
}
(_, _) => {
self.coerce_tys_or_sub(a, b, Some(a), Some(b))
ty::ty_ptr(_) => {
return do self.unpack_actual_value(a) |sty_a| {
self.coerce_unsafe_ptr(a, sty_a, b)
};
}
_ => {}
}
do self.unpack_actual_value(a) |sty_a| {
match *sty_a {
ty::ty_fn(ref a_f) if a_f.meta.proto == ast::ProtoBare => {
// Bare functions are coercable to any closure type.
//
// FIXME(#3320) this should go away and be
// replaced with proper inference, got a patch
// underway - ndm
self.coerce_from_bare_fn(a, a_f, b)
}
_ => {
// Otherwise, just use subtyping rules.
self.subtype(a, b)
}
}
}
}
fn subtype(&self, a: ty::t, b: ty::t) -> CoerceResult {
match Sub(**self).tys(a, b) {
Ok(_) => Ok(None), // No coercion required.
Err(ref e) => Err(*e)
}
}
fn unpack_actual_value(&self,
a: ty::t,
f: &fn(&ty::sty) -> CoerceResult) -> CoerceResult
{
match resolve_type(self.infcx, a, try_resolve_tvar_shallow) {
Ok(t) => {
f(&ty::get(t).sty)
}
Err(e) => {
self.infcx.tcx.sess.span_bug(
self.span,
fmt!("Failed to resolve even without \
any force options: %?", e));
}
}
}
fn coerce_borrowed_pointer(&self,
a: ty::t,
sty_a: &ty::sty,
b: ty::t,
mt_b: ty::mt) -> CoerceResult
{
debug!("coerce_borrowed_pointer(a=%s, sty_a=%?, b=%s, mt_b=%?)",
a.inf_str(self.infcx), sty_a,
b.inf_str(self.infcx), mt_b);
// If we have a parameter of type `&M T_a` and the value
// provided is `expr`, we will be adding an implicit borrow,
// meaning that we convert `f(expr)` to `f(&M *expr)`. Therefore,
// to type check, we will construct the type that `&M*expr` would
// yield.
let sub = Sub(**self);
let r_borrow = self.infcx.next_region_var_nb(self.span);
let inner_ty = match *sty_a {
ty::ty_box(mt_a) => mt_a.ty,
ty::ty_uniq(mt_a) => mt_a.ty,
ty::ty_rptr(r_a, mt_a) => {
// Ensure that the pointer we are borrowing from lives
// at least as long as the borrowed result.
//
// FIXME(#3148)---in principle this dependency should
// be done more generally
if_ok!(sub.contraregions(r_a, r_borrow));
mt_a.ty
}
_ => {
return self.subtype(a, b);
}
};
debug!("Coerce.tys end");
move r
let a_borrowed = ty::mk_rptr(self.infcx.tcx,
r_borrow,
mt {ty: inner_ty, mutbl: mt_b.mutbl});
if_ok!(sub.tys(a_borrowed, b));
Ok(Some(@AutoAdjustment {
autoderefs: 1,
autoref: Some(AutoRef {
kind: AutoPtr,
region: r_borrow,
mutbl: mt_b.mutbl
})
}))
}
}
impl Coerce {
fn coerce_tys_or_sub(
&self,
+a: ty::t,
+b: ty::t,
+a_bnd: Option<ty::t>,
+b_bnd: Option<ty::t>) -> ares
fn coerce_borrowed_string(&self,
a: ty::t,
sty_a: &ty::sty,
b: ty::t) -> CoerceResult
{
debug!("Coerce.coerce_tys_or_sub(%s => %s, %s => %s)",
a.inf_str(self.infcx), b.inf_str(self.infcx),
a_bnd.inf_str(self.infcx), b_bnd.inf_str(self.infcx));
let _r = indenter();
debug!("coerce_borrowed_string(a=%s, sty_a=%?, b=%s)",
a.inf_str(self.infcx), sty_a,
b.inf_str(self.infcx));
fn is_borrowable(v: ty::vstore) -> bool {
match v {
ty::vstore_fixed(_) | ty::vstore_uniq | ty::vstore_box => true,
ty::vstore_slice(_) => false
match *sty_a {
ty::ty_estr(vstore_box) |
ty::ty_estr(vstore_uniq) => {}
_ => {
return self.subtype(a, b);
}
}
};
fn borrowable_protos(a_p: ast::Proto, b_p: ast::Proto) -> bool {
match (a_p, b_p) {
(ast::ProtoBox, ast::ProtoBorrowed) => true,
(ast::ProtoUniq, ast::ProtoBorrowed) => true,
_ => false
}
}
let r_a = self.infcx.next_region_var_nb(self.span);
let a_borrowed = ty::mk_estr(self.infcx.tcx, vstore_slice(r_a));
if_ok!(self.subtype(a_borrowed, b));
Ok(Some(@AutoAdjustment {
autoderefs: 0,
autoref: Some(AutoRef {
kind: AutoBorrowVec,
region: r_a,
mutbl: m_imm
})
}))
}
match (a_bnd, b_bnd) {
(Some(a_bnd), Some(b_bnd)) => {
match (&ty::get(a_bnd).sty, &ty::get(b_bnd).sty) {
// check for a case where a non-region pointer (@, ~) is
// being coerceed to a region pointer:
(&ty::ty_box(_), &ty::ty_rptr(r_b, mt_b)) => {
let nr_b = ty::mk_box(self.infcx.tcx,
ty::mt {ty: mt_b.ty,
mutbl: m_const});
self.try_coerce(1, ty::AutoPtr,
a, nr_b,
mt_b.mutbl, r_b)
}
(&ty::ty_uniq(_), &ty::ty_rptr(r_b, mt_b)) => {
let nr_b = ty::mk_uniq(self.infcx.tcx,
ty::mt {ty: mt_b.ty,
mutbl: m_const});
self.try_coerce(1, ty::AutoPtr,
a, nr_b,
mt_b.mutbl, r_b)
}
(&ty::ty_estr(vs_a),
&ty::ty_estr(ty::vstore_slice(r_b)))
if is_borrowable(vs_a) => {
let nr_b = ty::mk_estr(self.infcx.tcx, vs_a);
self.try_coerce(0, ty::AutoBorrowVec,
a, nr_b,
m_imm, r_b)
}
fn coerce_borrowed_vector(&self,
a: ty::t,
sty_a: &ty::sty,
b: ty::t,
mt_b: ty::mt) -> CoerceResult
{
debug!("coerce_borrowed_vector(a=%s, sty_a=%?, b=%s)",
a.inf_str(self.infcx), sty_a,
b.inf_str(self.infcx));
(&ty::ty_evec(_, vs_a),
&ty::ty_evec(mt_b, ty::vstore_slice(r_b)))
if is_borrowable(vs_a) => {
let nr_b = ty::mk_evec(self.infcx.tcx,
ty::mt {ty: mt_b.ty,
mutbl: m_const},
vs_a);
self.try_coerce(0, ty::AutoBorrowVec,
a, nr_b,
mt_b.mutbl, r_b)
}
(&ty::ty_fn(ref a_f), &ty::ty_fn(ref b_f))
if borrowable_protos(a_f.meta.proto, b_f.meta.proto) => {
let nr_b = ty::mk_fn(self.infcx.tcx, ty::FnTyBase {
meta: ty::FnMeta {proto: a_f.meta.proto,
..b_f.meta},
sig: copy b_f.sig
});
self.try_coerce(0, ty::AutoBorrowFn,
a, nr_b, m_imm, b_f.meta.region)
}
(&ty::ty_fn(ref a_f), &ty::ty_fn(ref b_f))
if a_f.meta.proto == ast::ProtoBare => {
let b1_f = ty::FnTyBase {
meta: ty::FnMeta {proto: ast::ProtoBare,
..b_f.meta},
sig: copy b_f.sig
};
// Eventually we will need to add some sort of
// adjustment here so that trans can add an
// extra NULL env pointer:
to_ares(Sub(**self).fns(a_f, &b1_f))
}
// check for &T being coerced to *T:
(&ty::ty_rptr(_, ref a_t), &ty::ty_ptr(ref b_t)) => {
to_ares(Sub(**self).mts(*a_t, *b_t))
}
// otherwise, coercement follows normal subtype rules:
_ => {
to_ares(Sub(**self).tys(a, b))
}
}
let sub = Sub(**self);
let r_borrow = self.infcx.next_region_var_nb(self.span);
let ty_inner = match *sty_a {
ty::ty_evec(mt, vstore_box) => mt.ty,
ty::ty_evec(mt, vstore_uniq) => mt.ty,
ty::ty_evec(mt, vstore_fixed(_)) => mt.ty,
ty::ty_evec(mt, vstore_slice(r_a)) => {
// Ensure that the pointer we are borrowing from lives
// at least as long as the borrowed result.
//
// FIXME(#3148)---in principle this dependency should
// be done more generally
if_ok!(sub.contraregions(r_a, r_borrow));
mt.ty
}
_ => {
// if insufficient bounds were available, just follow
// normal subtype rules:
to_ares(Sub(**self).tys(a, b))
return self.subtype(a, b);
}
};
let a_borrowed = ty::mk_evec(self.infcx.tcx,
mt {ty: ty_inner, mutbl: mt_b.mutbl},
vstore_slice(r_borrow));
if_ok!(sub.tys(a_borrowed, b));
Ok(Some(@AutoAdjustment {
autoderefs: 0,
autoref: Some(AutoRef {
kind: AutoBorrowVec,
region: r_borrow,
mutbl: mt_b.mutbl
})
}))
}
fn coerce_borrowed_fn(&self,
a: ty::t,
sty_a: &ty::sty,
b: ty::t) -> CoerceResult
{
debug!("coerce_borrowed_fn(a=%s, sty_a=%?, b=%s)",
a.inf_str(self.infcx), sty_a,
b.inf_str(self.infcx));
let fn_ty = match *sty_a {
ty::ty_fn(ref f) if f.meta.proto == ast::ProtoBox => {f}
ty::ty_fn(ref f) if f.meta.proto == ast::ProtoUniq => {f}
ty::ty_fn(ref f) if f.meta.proto == ast::ProtoBare => {
return self.coerce_from_bare_fn(a, f, b);
}
_ => {
return self.subtype(a, b);
}
};
let r_borrow = self.infcx.next_region_var_nb(self.span);
let meta = FnMeta {proto: ast::ProtoBorrowed,
region: r_borrow,
..fn_ty.meta};
let a_borrowed = ty::mk_fn(self.infcx.tcx,
FnTyBase {meta: meta,
sig: copy fn_ty.sig});
if_ok!(self.subtype(a_borrowed, b));
Ok(Some(@AutoAdjustment {
autoderefs: 0,
autoref: Some(AutoRef {
kind: AutoBorrowFn,
region: r_borrow,
mutbl: m_imm
})
}))
}
fn coerce_from_bare_fn(&self,
a: ty::t,
fn_ty_a: &ty::FnTy,
b: ty::t) -> CoerceResult
{
do self.unpack_actual_value(b) |sty_b| {
self.coerce_from_bare_fn_post_unpack(a, fn_ty_a, b, sty_b)
}
}
/// Given an coercement from a type like `@a` to `&r_b/m nr_b`,
/// this function checks that `a <: nr_b`. In that case, the
/// coercement is permitted, so it constructs a fresh region
/// variable `r_a >= r_b` and returns a corresponding coercement
/// record. See the discussion at the top of this file for more
/// details.
fn try_coerce(&self,
autoderefs: uint,
kind: ty::AutoRefKind,
a: ty::t,
nr_b: ty::t,
m: ast::mutability,
r_b: ty::Region) -> ares
fn coerce_from_bare_fn_post_unpack(&self,
a: ty::t,
fn_ty_a: &ty::FnTy,
b: ty::t,
sty_b: &ty::sty) -> CoerceResult
{
debug!("try_coerce(a=%s, nr_b=%s, m=%?, r_b=%s)",
a.inf_str(self.infcx),
nr_b.inf_str(self.infcx),
m,
r_b.inf_str(self.infcx));
debug!("coerce_from_bare_fn(a=%s, b=%s)",
a.inf_str(self.infcx), b.inf_str(self.infcx));
do indent {
let sub = Sub(**self);
do sub.tys(a, nr_b).chain |_t| {
let r_a = self.infcx.next_region_var_nb(self.span);
do sub.contraregions(r_a, r_b).chain |_r| {
Ok(Some(@ty::AutoAdjustment {
autoderefs: autoderefs,
autoref: Some(ty::AutoRef {
kind: kind,
region: r_a,
mutbl: m
})
}))
}
let fn_ty_b = match *sty_b {
ty::ty_fn(ref f) if f.meta.proto != ast::ProtoBare => {f}
_ => {
return self.subtype(a, b);
}
}
};
// for now, bare fn and closures have the same
// representation
let a_adapted = ty::mk_fn(self.infcx.tcx,
FnTyBase {meta: copy fn_ty_b.meta,
sig: copy fn_ty_a.sig});
self.subtype(a_adapted, b)
}
fn coerce_unsafe_ptr(&self,
a: ty::t,
sty_a: &ty::sty,
b: ty::t) -> CoerceResult
{
debug!("coerce_unsafe_ptr(a=%s, sty_a=%?, b=%s)",
a.inf_str(self.infcx), sty_a,
b.inf_str(self.infcx));
let mt_a = match *sty_a {
ty::ty_rptr(_, mt) => mt,
_ => {
return self.subtype(a, b);
}
};
// borrowed pointers and unsafe pointers have the same
// representation, so just check that the types which they
// point at are compatible:
let a_unsafe = ty::mk_ptr(self.infcx.tcx, mt_a);
self.subtype(a_unsafe, b)
}
}

View File

@ -317,12 +317,11 @@ export uok;
export cyclic_ty, unresolved_ty, region_var_bound_by_region_var;
export Bound, Bounds;
export ures;
export ares;
export CoerceResult;
export infer_ctxt;
export fixup_err;
export IntVarValue, IntType, UintType;
mod coercion;
#[legacy_exports]
mod combine;
#[legacy_exports]
@ -341,6 +340,7 @@ mod sub;
mod to_str;
#[legacy_exports]
mod unify;
mod coercion;
type Bound<T> = Option<T>;
type Bounds<T> = {lb: Bound<T>, ub: Bound<T>};
@ -348,7 +348,7 @@ type Bounds<T> = {lb: Bound<T>, ub: Bound<T>};
type cres<T> = Result<T,ty::type_err>; // "combine result"
type ures = cres<()>; // "unify result"
type fres<T> = Result<T, fixup_err>; // "fixup result"
type ares = cres<Option<@ty::AutoAdjustment>>; // "assignment result"
type CoerceResult = cres<Option<@ty::AutoAdjustment>>;
struct InferCtxt {
tcx: ty::ctxt,
@ -457,7 +457,8 @@ fn mk_eqty(cx: @InferCtxt, a_is_expected: bool, span: span,
}
fn mk_coercety(cx: @InferCtxt, a_is_expected: bool, span: span,
a: ty::t, b: ty::t) -> ares {
a: ty::t, b: ty::t) -> CoerceResult
{
debug!("mk_coercety(%s -> %s)", a.inf_str(cx), b.inf_str(cx));
do indent {
do cx.commit {

View File

@ -78,6 +78,7 @@ const force_all: uint = 0b1111100000;
const not_regions: uint = !(force_rvar | resolve_rvar);
const try_resolve_tvar_shallow: uint = 0;
const resolve_and_force_all_but_regions: uint =
(resolve_all | force_all) & not_regions;

View File

@ -16,9 +16,9 @@ use std::map;
fn main() {
let buggy_map :HashMap<uint, &uint> =
HashMap::<uint, &uint>();
buggy_map.insert(42, ~1); //~ ERROR illegal borrow
buggy_map.insert(42, &*~1); //~ ERROR illegal borrow
// but it is ok if we use a temporary
let tmp = ~2;
buggy_map.insert(43, tmp);
buggy_map.insert(43, &*tmp);
}

View File

@ -0,0 +1,17 @@
fn mutate(x: &mut @const int) {
*x = @3;
}
fn give_away1(y: @mut @mut int) {
mutate(y); //~ ERROR values differ in mutability
}
fn give_away2(y: @mut @const int) {
mutate(y);
}
fn give_away3(y: @mut @int) {
mutate(y); //~ ERROR values differ in mutability
}
fn main() {}

View File

@ -10,6 +10,5 @@
fn main () {
let mut _p: & int = & 4;
_p = ~3; //~ ERROR illegal borrow: borrowed value does not live long enough
//~^ NOTE ...but borrowed value is only valid for the statement
_p = &*~3; //~ ERROR illegal borrow
}

View File

@ -20,11 +20,12 @@ fn repeater<A:Copy>(v: @A) -> repeat<A> {
}
fn main() {
// Here, an error results as the type of y is inferred to
// repeater<&lt/3> where lt is the block.
let y = {
let x: &blk/int = &3; //~ ERROR cannot infer an appropriate lifetime
// Error results because the type of is inferred to be
// repeat<&blk/int> where blk is the lifetime of the block below.
let y = { //~ ERROR reference is not valid
let x: &blk/int = &3;
repeater(@x)
};
assert 3 == *(y.get());
assert 3 == *(y.get()); //~ ERROR reference is not valid
}

View File

@ -25,8 +25,7 @@ fn nested(x: &x/int) { // (1)
//~^ ERROR cannot infer an appropriate lifetime
return z(y, x, x);
//~^ ERROR mismatched types: expected `&x/int` but found `&y/int`
//~^^ ERROR mismatched types: expected `&y/int` but found `&x/int`
//~^ ERROR cannot infer an appropriate lifetime
}
) |foo| {

View File

@ -0,0 +1,17 @@
pure fn negate(x: &int) -> int {
-*x
}
fn negate_mut(y: &mut int) -> int {
negate(y)
}
fn negate_imm(y: &int) -> int {
negate(y)
}
fn negate_const(y: &const int) -> int {
negate(y)
}
fn main() {}

View File

@ -0,0 +1,16 @@
struct SpeechMaker {
speeches: uint
}
impl SpeechMaker {
pure fn how_many(&self) -> uint { self.speeches }
}
fn foo(speaker: &const SpeechMaker) -> uint {
speaker.how_many() + 33
}
fn main() {
let mut lincoln = SpeechMaker {speeches: 22};
assert foo(&const lincoln) == 55;
}

View File

@ -0,0 +1,19 @@
pure fn sum(x: &[int]) -> int {
let mut sum = 0;
for x.each |y| { sum += *y; }
return sum;
}
fn sum_mut(y: &[mut int]) -> int {
sum(y)
}
fn sum_imm(y: &[int]) -> int {
sum(y)
}
fn sum_const(y: &[const int]) -> int {
sum(y)
}
fn main() {}

View File

@ -0,0 +1,18 @@
fn foo(v: &[const uint]) -> ~[uint] {
v.to_vec()
}
fn bar(v: &[mut uint]) -> ~[uint] {
v.to_vec()
}
fn bip(v: &[uint]) -> ~[uint] {
v.to_vec()
}
fn main() {
let mut the_vec = ~[1, 2, 3, 100];
assert the_vec == foo(the_vec);
assert the_vec == bar(the_vec);
assert the_vec == bip(the_vec);
}

View File

@ -0,0 +1,22 @@
struct SpeechMaker {
speeches: uint
}
fn talk(x: &mut SpeechMaker) {
x.speeches += 1;
}
fn give_a_few_speeches(speaker: &mut SpeechMaker) {
// Here speaker is reborrowed for each call, so we don't get errors
// about speaker being moved.
talk(speaker);
talk(speaker);
talk(speaker);
}
fn main() {
let mut lincoln = SpeechMaker {speeches: 22};
give_a_few_speeches(&mut lincoln);
}

View File

@ -0,0 +1,24 @@
struct SpeechMaker {
speeches: uint
}
impl SpeechMaker {
fn talk(&mut self) {
self.speeches += 1;
}
}
fn give_a_few_speeches(speaker: &mut SpeechMaker) {
// Here speaker is reborrowed for each call, so we don't get errors
// about speaker being moved.
speaker.talk();
speaker.talk();
speaker.talk();
}
fn main() {
let mut lincoln = SpeechMaker {speeches: 22};
give_a_few_speeches(&mut lincoln);
}

View File

@ -0,0 +1,15 @@
trait Reverser {
fn reverse(&self);
}
fn bar(v: &[mut uint]) {
vec::reverse(v);
vec::reverse(v);
vec::reverse(v);
}
fn main() {
let mut the_vec = ~[1, 2, 3, 100];
bar(the_vec);
assert the_vec == ~[100, 3, 2, 1];
}

View File

@ -0,0 +1,21 @@
trait Reverser {
fn reverse(&self);
}
impl &[mut uint] : Reverser {
fn reverse(&self) {
vec::reverse(*self);
}
}
fn bar(v: &[mut uint]) {
v.reverse();
v.reverse();
v.reverse();
}
fn main() {
let mut the_vec = ~[1, 2, 3, 100];
bar(the_vec);
assert the_vec == ~[100, 3, 2, 1];
}

View File

@ -17,5 +17,5 @@ use std::map;
fn main() {
let buggy_map :HashMap<uint, &uint> = HashMap::<uint, &uint>();
let x = ~1;
buggy_map.insert(42, x);
buggy_map.insert(42, &*x);
}

View File

@ -14,15 +14,7 @@ fn f() {
io::println(b);
}
fn g() {
let c = ~"world";
let d: &str;
d = c;
io::println(d);
}
fn main() {
f();
g();
}