Allow rcvrs to be borrowed; check rcvrs in borrowck properly

This commit is contained in:
Niko Matsakis 2012-06-01 21:54:38 -07:00
parent cfac9b6833
commit 77c470d183
11 changed files with 361 additions and 91 deletions

View file

@ -145,12 +145,22 @@ impl methods for check_loan_ctxt {
// when we are in a pure context, we check each call to ensure
// that the function which is invoked is itself pure.
fn check_pure_callee_or_arg(pc: purity_cause, expr: @ast::expr) {
//
// note: we take opt_expr and expr_id separately because for
// overloaded operators the callee has an id but no expr.
// annoying.
fn check_pure_callee_or_arg(pc: purity_cause,
opt_expr: option<@ast::expr>,
callee_id: ast::node_id,
callee_span: span) {
let tcx = self.tcx();
#debug["check_pure_callee_or_arg(pc=%?, expr=%s, ty=%s)",
pc, pprust::expr_to_str(expr),
ty_to_str(self.tcx(), tcx.ty(expr))];
#debug["check_pure_callee_or_arg(pc=%?, expr=%?, \
callee_id=%d, ty=%s)",
pc,
opt_expr.map({|e| pprust::expr_to_str(e)}),
callee_id,
ty_to_str(self.tcx(), ty::node_id_to_type(tcx, callee_id))];
// Purity rules: an expr B is a legal callee or argument to a
// call within a pure function A if at least one of the
@ -161,29 +171,35 @@ impl methods for check_loan_ctxt {
// (c) B is a pure fn;
// (d) B is not a fn.
alt expr.node {
ast::expr_path(_) if pc == pc_pure_fn {
let def = self.tcx().def_map.get(expr.id);
let did = ast_util::def_id_of_def(def);
let is_fn_arg =
did.crate == ast::local_crate &&
self.fn_args.contains(did.node);
if is_fn_arg { ret; } // case (a) above
alt opt_expr {
some(expr) {
alt expr.node {
ast::expr_path(_) if pc == pc_pure_fn {
let def = self.tcx().def_map.get(expr.id);
let did = ast_util::def_id_of_def(def);
let is_fn_arg =
did.crate == ast::local_crate &&
self.fn_args.contains(did.node);
if is_fn_arg { ret; } // case (a) above
}
ast::expr_fn_block(*) | ast::expr_fn(*) |
ast::expr_loop_body(*) {
if self.is_stack_closure(expr.id) { ret; } // case (b) above
}
_ {}
}
}
ast::expr_fn_block(*) | ast::expr_fn(*) | ast::expr_loop_body(*) {
if self.is_stack_closure(expr.id) { ret; } // case (b) above
}
_ {}
none {}
}
let expr_ty = tcx.ty(expr);
alt ty::get(expr_ty).struct {
let callee_ty = ty::node_id_to_type(tcx, callee_id);
alt ty::get(callee_ty).struct {
ty::ty_fn(fn_ty) {
alt fn_ty.purity {
ast::pure_fn { ret; } // case (c) above
ast::impure_fn | ast::unsafe_fn | ast::crust_fn {
self.report_purity_error(
pc, expr.span,
pc, callee_span,
#fmt["access to %s function",
pprust::purity_to_str(fn_ty.purity)]);
}
@ -429,6 +445,39 @@ impl methods for check_loan_ctxt {
ret;
}
}
fn check_call(expr: @ast::expr,
callee: option<@ast::expr>,
callee_id: ast::node_id,
callee_span: span,
args: [@ast::expr]) {
alt self.purity(expr.id) {
none {}
some(pc) {
self.check_pure_callee_or_arg(
pc, callee, callee_id, callee_span);
for args.each { |arg|
self.check_pure_callee_or_arg(
pc, some(arg), arg.id, arg.span);
}
}
}
let arg_tys =
ty::ty_fn_args(
ty::node_id_to_type(self.tcx(), callee_id));
vec::iter2(args, arg_tys) { |arg, arg_ty|
alt ty::resolved_mode(self.tcx(), arg_ty.mode) {
ast::by_move {
self.check_move_out(arg);
}
ast::by_mutbl_ref {
self.check_assignment(at_mutbl_ref, arg);
}
ast::by_ref | ast::by_copy | ast::by_val {
}
}
}
}
}
fn check_loans_in_fn(fk: visit::fn_kind, decl: ast::fn_decl, body: ast::blk,
@ -521,26 +570,24 @@ fn check_loans_in_expr(expr: @ast::expr,
}
}
ast::expr_call(f, args, _) {
alt self.purity(expr.id) {
none {}
some(pc) {
self.check_pure_callee_or_arg(pc, f);
for args.each { |arg| self.check_pure_callee_or_arg(pc, arg) }
}
}
let arg_tys = ty::ty_fn_args(ty::expr_ty(self.tcx(), f));
vec::iter2(args, arg_tys) { |arg, arg_ty|
alt ty::resolved_mode(self.tcx(), arg_ty.mode) {
ast::by_move {
self.check_move_out(arg);
}
ast::by_mutbl_ref {
self.check_assignment(at_mutbl_ref, arg);
}
ast::by_ref | ast::by_copy | ast::by_val {
}
}
}
self.check_call(expr, some(f), f.id, f.span, args);
}
ast::expr_index(_, rval) |
ast::expr_binary(_, _, rval)
if self.bccx.method_map.contains_key(expr.id) {
self.check_call(expr,
none,
ast_util::op_expr_callee_id(expr),
expr.span,
[rval]);
}
ast::expr_unary(_, _)
if self.bccx.method_map.contains_key(expr.id) {
self.check_call(expr,
none,
ast_util::op_expr_callee_id(expr),
expr.span,
[]);
}
_ { }
}

View file

@ -112,19 +112,33 @@ fn req_loans_in_expr(ex: @ast::expr,
}
}
ast::expr_field(rcvr, _, _) |
ast::expr_index(rcvr, _) |
ast::expr_binary(_, rcvr, _) |
ast::expr_unary(_, rcvr) if self.bccx.method_map.contains_key(ex.id) {
// Receivers in method calls are always passed by ref.
//
// FIXME--this scope is both too large and too small. We make
// the scope the enclosing block, which surely includes any
// immediate call (a.b()) but which is too big. OTOH, in the
// case of a naked field `a.b`, the value is copied
// anyhow. This is probably best fixed if we address the
// syntactic ambiguity.
// Here, in an overloaded operator, the call is this expression,
// and hence the scope of the borrow is this call.
//
// FIXME/NOT REALLY---technically we should check the other
// argument and consider the argument mode. But how annoying.
// And this problem when goes away when argument modes are
// phased out. So I elect to leave this undone.
let scope_r = ty::re_scope(ex.id);
let rcvr_cmt = self.bccx.cat_expr(rcvr);
self.guarantee_valid(rcvr_cmt, m_imm, scope_r);
}
// let scope_r = ty::re_scope(ex.id);
ast::expr_field(rcvr, _, _)
if self.bccx.method_map.contains_key(ex.id) {
// Receivers in method calls are always passed by ref.
//
// Here, the field a.b is in fact a closure. Eventually, this
// should be an fn&, but for now it's an fn@. In any case,
// the enclosing scope is either the call where it is a rcvr
// (if used like `a.b(...)`), the call where it's an argument
// (if used like `x(a.b)`), or the block (if used like `let x
// = a.b`).
let scope_r = ty::re_scope(self.tcx().region_map.get(ex.id));
let rcvr_cmt = self.bccx.cat_expr(rcvr);
self.guarantee_valid(rcvr_cmt, m_imm, scope_r);

View file

@ -2883,7 +2883,7 @@ fn trans_arg_expr(cx: block, arg: ty::arg, lldestty: TypeRef, e: @ast::expr,
none { trans_temp_lval(cx, e) }
};
#debug(" pre-adaptation value: %s", val_str(lv.bcx.ccx().tn, lv.val));
let lv = adapt_borrowed_value(lv, arg, e);
let {lv, arg} = adapt_borrowed_value(lv, arg, e);
let mut bcx = lv.bcx;
let mut val = lv.val;
#debug(" adapted value: %s", val_str(bcx.ccx().tn, val));
@ -2897,6 +2897,8 @@ fn trans_arg_expr(cx: block, arg: ty::arg, lldestty: TypeRef, e: @ast::expr,
} else if arg_mode == ast::by_ref || arg_mode == ast::by_val {
let mut copied = false;
let imm = ty::type_is_immediate(arg.ty);
#debug[" arg.ty=%s, imm=%b, arg_mode=%?, lv.kind=%?",
ty_to_str(bcx.tcx(), arg.ty), imm, arg_mode, lv.kind];
if arg_mode == ast::by_ref && lv.kind != owned && imm {
val = do_spill_noroot(bcx, val);
copied = true;
@ -2953,29 +2955,29 @@ fn load_value_from_lval_result(lv: lval_result) -> ValueRef {
}
}
fn adapt_borrowed_value(lv: lval_result, _arg: ty::arg,
e: @ast::expr) -> lval_result {
// when invoking a method, an argument of type @T or ~T can be implicltly
// converted to an argument of type &T. Similarly, [T] can be converted to
// [T]/& and so on. If such a conversion (called borrowing) is necessary,
// then the borrowings table will have an appropriate entry inserted. This
// routine consults this table and performs these adaptations. It returns a
// new location for the borrowed result as well as a new type for the argument
// that reflects the borrowed value and not the original.
fn adapt_borrowed_value(lv: lval_result, arg: ty::arg,
e: @ast::expr) -> {lv: lval_result,
arg: ty::arg} {
let bcx = lv.bcx;
if !expr_is_borrowed(bcx, e) { ret lv; }
if !expr_is_borrowed(bcx, e) {
ret {lv:lv, arg:arg};
}
let e_ty = expr_ty(bcx, e);
alt ty::get(e_ty).struct {
ty::ty_box(mt) {
ty::ty_uniq(mt) | ty::ty_box(mt) {
let box_ptr = load_value_from_lval_result(lv);
let body_ptr = GEPi(bcx, box_ptr, [0u, abi::box_field_body]);
ret lval_temp(bcx, body_ptr);
}
ty::ty_uniq(_) {
let box_ptr = {
alt lv.kind {
temporary { lv.val }
owned { Load(bcx, lv.val) }
owned_imm { lv.val }
}
};
let body_ptr = GEPi(bcx, box_ptr, [0u, abi::box_field_body]);
ret lval_temp(bcx, body_ptr);
let rptr_ty = ty::mk_rptr(bcx.tcx(), ty::re_static, mt);
ret {lv: lval_temp(bcx, body_ptr),
arg: {ty: rptr_ty with arg}};
}
ty::ty_str | ty::ty_vec(_) |
@ -2999,7 +3001,16 @@ fn adapt_borrowed_value(lv: lval_result, _arg: ty::arg,
Store(bcx, base, GEPi(bcx, p, [0u, abi::slice_elt_base]));
Store(bcx, len, GEPi(bcx, p, [0u, abi::slice_elt_len]));
ret lval_temp(bcx, p);
// this isn't necessarily the type that rust would assign but it's
// close enough for trans purposes, as it will have the same runtime
// representation
let slice_ty = ty::mk_evec(bcx.tcx(),
{ty: unit_ty, mutbl: ast::m_imm},
ty::vstore_slice(ty::re_static));
ret {lv: lval_temp(bcx, p),
arg: {ty: slice_ty with arg}};
}
_ {

View file

@ -515,6 +515,18 @@ impl methods for @fn_ctxt {
infer::can_mk_subty(self.infcx, sub, sup)
}
fn mk_assignty(expr: @ast::expr, borrow_scope: ast::node_id,
sub: ty::t, sup: ty::t) -> result<(), ty::type_err> {
let anmnt = {expr_id: expr.id, borrow_scope: borrow_scope};
infer::mk_assignty(self.infcx, anmnt, sub, sup)
}
fn can_mk_assignty(expr: @ast::expr, borrow_scope: ast::node_id,
sub: ty::t, sup: ty::t) -> result<(), ty::type_err> {
let anmnt = {expr_id: expr.id, borrow_scope: borrow_scope};
infer::can_mk_assignty(self.infcx, anmnt, sub, sup)
}
fn mk_eqty(sub: ty::t, sup: ty::t) -> result<(), ty::type_err> {
infer::mk_eqty(self.infcx, sub, sup)
}
@ -867,12 +879,15 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
_ { none }
}
}
fn lookup_op_method(fcx: @fn_ctxt, op_ex: @ast::expr, self_t: ty::t,
fn lookup_op_method(fcx: @fn_ctxt, op_ex: @ast::expr,
self_ex: @ast::expr, self_t: ty::t,
opname: str, args: [option<@ast::expr>])
-> option<(ty::t, bool)> {
let callee_id = ast_util::op_expr_callee_id(op_ex);
let lkup = method::lookup({fcx: fcx,
expr: op_ex,
self_expr: self_ex,
borrow_scope: op_ex.id,
node_id: callee_id,
m_name: opname,
self_ty: self_t,
@ -950,18 +965,21 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
(_, _) {
let (result, rhs_bot) =
check_user_binop(fcx, expr, lhs_t, op, rhs);
check_user_binop(fcx, expr, lhs, lhs_t, op, rhs);
fcx.write_ty(expr.id, result);
lhs_bot | rhs_bot
}
};
}
fn check_user_binop(fcx: @fn_ctxt, ex: @ast::expr, lhs_resolved_t: ty::t,
fn check_user_binop(fcx: @fn_ctxt, ex: @ast::expr,
lhs_expr: @ast::expr, lhs_resolved_t: ty::t,
op: ast::binop, rhs: @ast::expr) -> (ty::t, bool) {
let tcx = fcx.ccx.tcx;
alt binop_method(op) {
some(name) {
alt lookup_op_method(fcx, ex, lhs_resolved_t, name, [some(rhs)]) {
alt lookup_op_method(fcx, ex,
lhs_expr, lhs_resolved_t,
name, [some(rhs)]) {
some(pair) { ret pair; }
_ {}
}
@ -977,8 +995,9 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
(lhs_resolved_t, false)
}
fn check_user_unop(fcx: @fn_ctxt, op_str: str, mname: str,
ex: @ast::expr, rhs_t: ty::t) -> ty::t {
alt lookup_op_method(fcx, ex, rhs_t, mname, []) {
ex: @ast::expr,
rhs_expr: @ast::expr, rhs_t: ty::t) -> ty::t {
alt lookup_op_method(fcx, ex, rhs_expr, rhs_t, mname, []) {
some((ret_ty, _)) { ret_ty }
_ {
fcx.ccx.tcx.sess.span_err(
@ -1161,14 +1180,16 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
oper_t = structurally_resolved_type(fcx, oper.span, oper_t);
if !(ty::type_is_integral(oper_t) ||
ty::get(oper_t).struct == ty::ty_bool) {
oper_t = check_user_unop(fcx, "!", "!", expr, oper_t);
oper_t = check_user_unop(fcx, "!", "!", expr,
oper, oper_t);
}
}
ast::neg {
oper_t = structurally_resolved_type(fcx, oper.span, oper_t);
if !(ty::type_is_integral(oper_t) ||
ty::type_is_fp(oper_t)) {
oper_t = check_user_unop(fcx, "-", "unary-", expr, oper_t);
oper_t = check_user_unop(fcx, "-", "unary-", expr,
oper, oper_t);
}
}
}
@ -1554,13 +1575,20 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
if !handled {
let tps = vec::map(tys) { |ty| fcx.to_ty(ty) };
let is_self_ref = self_ref(fcx, base.id);
// this will be the call or block that immediately
// encloses the method call
let borrow_scope = fcx.tcx().region_map.get(expr.id);
let lkup = method::lookup({fcx: fcx,
expr: expr,
node_id: expr.id,
m_name: field,
self_ty: expr_t,
supplied_tps: tps,
include_private: is_self_ref});
expr: expr,
self_expr: base,
borrow_scope: borrow_scope,
node_id: expr.id,
m_name: field,
self_ty: expr_t,
supplied_tps: tps,
include_private: is_self_ref});
alt lkup.method() {
some(origin) {
fcx.ccx.method_map.insert(id, origin);
@ -1591,7 +1619,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
none {
let resolved = structurally_resolved_type(fcx, expr.span,
raw_base_t);
alt lookup_op_method(fcx, expr, resolved, "[]",
alt lookup_op_method(fcx, expr, base, resolved, "[]",
[some(idx)]) {
some((ret_ty, _)) { fcx.write_ty(id, ret_ty); }
_ {
@ -1611,6 +1639,8 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
let lkup = method::lookup({fcx: fcx,
expr: p,
self_expr: p,
borrow_scope: expr.id,
node_id: alloc_id,
m_name: "alloc",
self_ty: p_ty,

View file

@ -6,6 +6,8 @@ import middle::typeck::infer::methods; // next_ty_vars
enum lookup = {
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, // if we have to borrow the expr, what scope?
node_id: ast::node_id, // node id of call (not always expr.id)
m_name: ast::ident, // b in a.b(...)
self_ty: ty::t, // type of a in a.b(...)
@ -207,7 +209,9 @@ impl methods for lookup {
// if we can assign the caller to the callee, that's a
// potential match. Collect those in the vector.
alt self.fcx.can_mk_subty(self.self_ty, impl_ty) {
alt self.fcx.can_mk_assignty(
self.self_expr, self.borrow_scope,
self.self_ty, impl_ty) {
result::err(_) { /* keep looking */ }
result::ok(_) {
results += [(impl_ty, impl_substs, m.n_tps, m.did)];
@ -243,12 +247,15 @@ impl methods for lookup {
}
let (impl_ty, impl_substs, n_tps, did) = results[0];
alt self.fcx.mk_subty(self.self_ty, impl_ty) {
alt self.fcx.mk_assignty(self.self_expr, self.borrow_scope,
self.self_ty, impl_ty) {
result::ok(_) {}
result::err(_) {
self.tcx().sess.span_bug(
self.expr.span,
"what was a subtype now is not?");
#fmt["%s was assignable to %s but now is not?",
self.fcx.infcx.ty_to_str(self.self_ty),
self.fcx.infcx.ty_to_str(impl_ty)]);
}
}
let fty = self.ty_from_did(did);

View file

@ -164,13 +164,14 @@ export new_infer_ctxt;
export mk_subty, can_mk_subty;
export mk_subr;
export mk_eqty;
export mk_assignty;
export mk_assignty, can_mk_assignty;
export resolve_shallow;
export resolve_deep;
export resolve_deep_var;
export methods; // for infer_ctxt
export compare_tys;
export fixup_err, fixup_err_to_str;
export assignment;
// Extra information needed to perform an assignment that may borrow.
// The `expr_id` is the is of the expression whose type is being
@ -260,6 +261,21 @@ fn mk_assignty(cx: infer_ctxt, anmnt: assignment,
} }.to_ures()
}
fn can_mk_assignty(cx: infer_ctxt, anmnt: assignment,
a: ty::t, b: ty::t) -> ures {
#debug["can_mk_assignty(%? / %s <: %s)",
anmnt, a.to_str(cx), b.to_str(cx)];
// FIXME---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.
indent {|| cx.probe {||
cx.assign_tys(anmnt, a, b)
} }.to_ures()
}
fn compare_tys(tcx: ty::ctxt, a: ty::t, b: ty::t) -> ures {
let infcx = new_infer_ctxt(tcx);
mk_eqty(infcx, a, b)

View file

@ -0,0 +1,47 @@
// xfail-fast (compile-flags unsupported on windows)
// compile-flags:--borrowck=err
type point = { x: int, y: int };
impl foo for point {
pure fn +(z: int) -> int { self.x + self.y + z }
fn *(z: int) -> int { self.x * self.y * z }
}
fn a() {
let mut p = {x: 3, y: 4};
// ok (we can loan out rcvr)
p + 3;
p * 3;
}
fn b() {
let mut p = {x: 3, y: 4};
// Here I create an outstanding loan and check that we get conflicts:
&mut p; //! NOTE prior loan as mutable granted here
//!^ NOTE prior loan as mutable granted here
p + 3; //! ERROR loan of mutable local variable as immutable conflicts with prior loan
p * 3; //! ERROR loan of mutable local variable as immutable conflicts with prior loan
}
fn c() {
// Here the receiver is in aliased memory and hence we cannot
// consider it immutable:
let q = @mut {x: 3, y: 4};
// ...this is ok for pure fns
*q + 3;
// ...but not impure fns
*q * 3; //! ERROR illegal borrow unless pure: creating immutable alias to aliasable, mutable memory
//!^ NOTE impure due to access to impure function
}
fn main() {
}

View file

@ -7,26 +7,31 @@ impl foo for point {
fn impurem() {
}
fn blockm(f: fn()) { f() }
pure fn purem() {
}
}
fn a() {
let mut p = {x: 3, y: 4};
p.purem();
p.impurem();
}
fn a2() {
let mut p = {x: 3, y: 4};
// Here: it's ok to call even though receiver is mutable, because we
// can loan it out.
p.purem();
p.impurem();
p.x = p.y;
// But in this case we do not honor the loan:
p.blockm {|| //! NOTE loan of mutable local variable granted here
p.x = 10; //! ERROR assigning to mutable field prohibited due to outstanding loan
}
}
fn b() {
let mut p = {x: 3, y: 4};
// Here I create an outstanding loan and check that we get conflicts:
&mut p; //! NOTE prior loan as mutable granted here
//!^ NOTE prior loan as mutable granted here
@ -35,8 +40,14 @@ fn b() {
}
fn c() {
// Here the receiver is in aliased memory and hence we cannot
// consider it immutable:
let q = @mut {x: 3, y: 4};
// ...this is ok for pure fns
(*q).purem();
// ...but not impure fns
(*q).impurem(); //! ERROR illegal borrow unless pure: creating immutable alias to aliasable, mutable memory
//!^ NOTE impure due to access to impure function
}

View file

@ -0,0 +1,26 @@
type point = { x: int, y: int };
impl foo for point {
// expr_binary
pure fn +(z: int) -> int { self.x + self.y + z }
fn *(z: int) -> int { self.x * self.y * z }
// expr_index
fn [](z: int) -> int { self.x * self.y * z }
// expr_unary
fn unary-() -> int { -(self.x * self.y) }
}
pure fn a(p: point) -> int { p + 3 }
pure fn b(p: point) -> int { p * 3 }
//!^ ERROR access to impure function prohibited in pure context
pure fn c(p: point) -> int { p[3] }
//!^ ERROR access to impure function prohibited in pure context
pure fn d(p: point) -> int { -p }
//!^ ERROR access to impure function prohibited in pure context
fn main() {}

View file

@ -0,0 +1,34 @@
// Note: impl on a slice
impl foo/& for &int {
fn get() -> int {
ret *self;
}
}
fn main() {
/*
let x = @mut 6;
let y = x.get();
assert y == 6;
*/
let x = @6;
let y = x.get();
#debug["y=%d", y];
assert y == 6;
let x = ~mut 6;
let y = x.get();
#debug["y=%d", y];
assert y == 6;
let x = ~6;
let y = x.get();
#debug["y=%d", y];
assert y == 6;
let x = &6;
let y = x.get();
#debug["y=%d", y];
assert y == 6;
}

View file

@ -0,0 +1,27 @@
// Note: impl on a slice
impl foo/& for [int]/& {
fn sum() -> int {
let mut sum = 0;
for vec::each(self) { |e| sum += e; }
ret sum;
}
}
fn call_sum(x: [int]/&) -> int { x.sum() }
fn main() {
let x = [1, 2, 3];
let y = call_sum(x);
#debug["y==%d", y];
assert y == 6;
let x = [mut 1, 2, 3];
let y = x.sum();
#debug["y==%d", y];
assert y == 6;
let x = [1, 2, 3];
let y = x.sum();
#debug["y==%d", y];
assert y == 6;
}