2012-06-01 12:46:17 -05:00
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
// Checking loans
|
|
|
|
//
|
|
|
|
// Phase 2 of check: we walk down the tree and check that:
|
|
|
|
// 1. assignments are always made to mutable locations;
|
|
|
|
// 2. loans made in overlapping scopes do not conflict
|
|
|
|
// 3. assignments do not affect things loaned out as immutable
|
|
|
|
// 4. moves to dnot affect things loaned out in any way
|
|
|
|
|
2012-09-04 13:54:36 -05:00
|
|
|
use dvec::DVec;
|
2012-06-01 12:46:17 -05:00
|
|
|
|
|
|
|
export check_loans;
|
|
|
|
|
|
|
|
enum check_loan_ctxt = @{
|
|
|
|
bccx: borrowck_ctxt,
|
|
|
|
req_maps: req_maps,
|
|
|
|
|
2012-09-10 17:38:28 -05:00
|
|
|
reported: HashMap<ast::node_id, ()>,
|
2012-06-01 12:46:17 -05:00
|
|
|
|
|
|
|
// Keep track of whether we're inside a ctor, so as to
|
|
|
|
// allow mutating immutable fields in the same class if
|
|
|
|
// we are in a ctor, we track the self id
|
|
|
|
mut in_ctor: bool,
|
|
|
|
mut declared_purity: ast::purity,
|
2012-06-29 18:26:56 -05:00
|
|
|
mut fn_args: @~[ast::node_id]
|
2012-06-01 12:46:17 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
// if we are enforcing purity, why are we doing so?
|
|
|
|
enum purity_cause {
|
|
|
|
// enforcing purity because fn was declared pure:
|
|
|
|
pc_pure_fn,
|
|
|
|
|
|
|
|
// enforce purity because we need to guarantee the
|
|
|
|
// validity of some alias; `bckerr` describes the
|
|
|
|
// reason we needed to enforce purity.
|
|
|
|
pc_cmt(bckerr)
|
|
|
|
}
|
|
|
|
|
2012-08-27 18:26:35 -05:00
|
|
|
impl purity_cause : cmp::Eq {
|
|
|
|
pure fn eq(&&other: purity_cause) -> bool {
|
|
|
|
match self {
|
|
|
|
pc_pure_fn => {
|
|
|
|
match other {
|
|
|
|
pc_pure_fn => true,
|
|
|
|
_ => false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pc_cmt(e0a) => {
|
|
|
|
match other {
|
|
|
|
pc_cmt(e0b) => e0a == e0b,
|
|
|
|
_ => false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-09-07 14:06:02 -05:00
|
|
|
pure fn ne(&&other: purity_cause) -> bool { !self.eq(other) }
|
2012-08-27 18:26:35 -05:00
|
|
|
}
|
|
|
|
|
2012-06-01 12:46:17 -05:00
|
|
|
fn check_loans(bccx: borrowck_ctxt,
|
|
|
|
req_maps: req_maps,
|
|
|
|
crate: @ast::crate) {
|
|
|
|
let clcx = check_loan_ctxt(@{bccx: bccx,
|
|
|
|
req_maps: req_maps,
|
|
|
|
reported: int_hash(),
|
|
|
|
mut in_ctor: false,
|
|
|
|
mut declared_purity: ast::impure_fn,
|
2012-06-29 18:26:56 -05:00
|
|
|
mut fn_args: @~[]});
|
2012-06-01 12:46:17 -05:00
|
|
|
let vt = visit::mk_vt(@{visit_expr: check_loans_in_expr,
|
2012-06-20 22:08:25 -05:00
|
|
|
visit_local: check_loans_in_local,
|
2012-06-01 12:46:17 -05:00
|
|
|
visit_block: check_loans_in_block,
|
2012-09-04 15:29:32 -05:00
|
|
|
visit_fn: check_loans_in_fn,
|
|
|
|
.. *visit::default_visitor()});
|
2012-06-01 12:46:17 -05:00
|
|
|
visit::visit_crate(*crate, clcx, vt);
|
|
|
|
}
|
|
|
|
|
|
|
|
enum assignment_type {
|
|
|
|
at_straight_up,
|
2012-09-11 23:25:01 -05:00
|
|
|
at_swap
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
|
2012-09-07 20:53:14 -05:00
|
|
|
impl assignment_type : cmp::Eq {
|
|
|
|
pure fn eq(&&other: assignment_type) -> bool {
|
|
|
|
(self as uint) == (other as uint)
|
|
|
|
}
|
|
|
|
pure fn ne(&&other: assignment_type) -> bool { !self.eq(other) }
|
|
|
|
}
|
|
|
|
|
2012-08-07 20:10:06 -05:00
|
|
|
impl assignment_type {
|
2012-06-01 12:46:17 -05:00
|
|
|
fn checked_by_liveness() -> bool {
|
|
|
|
// the liveness pass guarantees that immutable local variables
|
|
|
|
// are only assigned once; but it doesn't consider &mut
|
2012-08-06 14:34:08 -05:00
|
|
|
match self {
|
2012-08-03 21:59:04 -05:00
|
|
|
at_straight_up => true,
|
2012-09-11 23:25:01 -05:00
|
|
|
at_swap => true
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
}
|
2012-07-14 00:57:48 -05:00
|
|
|
fn ing_form(desc: ~str) -> ~str {
|
2012-08-06 14:34:08 -05:00
|
|
|
match self {
|
2012-08-03 21:59:04 -05:00
|
|
|
at_straight_up => ~"assigning to " + desc,
|
2012-09-11 23:25:01 -05:00
|
|
|
at_swap => ~"swapping to and from " + desc
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-07 20:10:06 -05:00
|
|
|
impl check_loan_ctxt {
|
2012-06-01 12:46:17 -05:00
|
|
|
fn tcx() -> ty::ctxt { self.bccx.tcx }
|
|
|
|
|
2012-08-20 14:23:37 -05:00
|
|
|
fn purity(scope_id: ast::node_id) -> Option<purity_cause> {
|
2012-08-06 14:34:08 -05:00
|
|
|
let default_purity = match self.declared_purity {
|
2012-06-01 12:46:17 -05:00
|
|
|
// an unsafe declaration overrides all
|
2012-08-20 14:23:37 -05:00
|
|
|
ast::unsafe_fn => return None,
|
2012-06-01 12:46:17 -05:00
|
|
|
|
|
|
|
// otherwise, remember what was declared as the
|
|
|
|
// default, but we must scan for requirements
|
|
|
|
// imposed by the borrow check
|
2012-08-20 14:23:37 -05:00
|
|
|
ast::pure_fn => Some(pc_pure_fn),
|
|
|
|
ast::extern_fn | ast::impure_fn => None
|
2012-06-01 12:46:17 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
// scan to see if this scope or any enclosing scope requires
|
|
|
|
// purity. if so, that overrides the declaration.
|
|
|
|
|
|
|
|
let mut scope_id = scope_id;
|
|
|
|
let region_map = self.tcx().region_map;
|
|
|
|
let pure_map = self.req_maps.pure_map;
|
|
|
|
loop {
|
2012-08-06 14:34:08 -05:00
|
|
|
match pure_map.find(scope_id) {
|
2012-08-20 14:23:37 -05:00
|
|
|
None => (),
|
|
|
|
Some(e) => return Some(pc_cmt(e))
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
|
2012-08-06 14:34:08 -05:00
|
|
|
match region_map.find(scope_id) {
|
2012-08-20 14:23:37 -05:00
|
|
|
None => return default_purity,
|
|
|
|
Some(next_scope_id) => scope_id = next_scope_id
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn walk_loans(scope_id: ast::node_id,
|
|
|
|
f: fn(loan) -> bool) {
|
|
|
|
let mut scope_id = scope_id;
|
|
|
|
let region_map = self.tcx().region_map;
|
|
|
|
let req_loan_map = self.req_maps.req_loan_map;
|
|
|
|
|
|
|
|
loop {
|
2012-06-30 18:19:07 -05:00
|
|
|
for req_loan_map.find(scope_id).each |loanss| {
|
|
|
|
for (*loanss).each |loans| {
|
|
|
|
for (*loans).each |loan| {
|
2012-08-01 19:30:05 -05:00
|
|
|
if !f(loan) { return; }
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-06 14:34:08 -05:00
|
|
|
match region_map.find(scope_id) {
|
2012-08-20 14:23:37 -05:00
|
|
|
None => return,
|
|
|
|
Some(next_scope_id) => scope_id = next_scope_id,
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn walk_loans_of(scope_id: ast::node_id,
|
|
|
|
lp: @loan_path,
|
|
|
|
f: fn(loan) -> bool) {
|
2012-06-30 18:19:07 -05:00
|
|
|
for self.walk_loans(scope_id) |loan| {
|
2012-06-01 12:46:17 -05:00
|
|
|
if loan.lp == lp {
|
2012-08-01 19:30:05 -05:00
|
|
|
if !f(loan) { return; }
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// when we are in a pure context, we check each call to ensure
|
|
|
|
// that the function which is invoked is itself pure.
|
2012-06-01 23:54:38 -05:00
|
|
|
//
|
|
|
|
// 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,
|
2012-08-20 14:23:37 -05:00
|
|
|
opt_expr: Option<@ast::expr>,
|
2012-06-01 23:54:38 -05:00
|
|
|
callee_id: ast::node_id,
|
|
|
|
callee_span: span) {
|
2012-06-01 12:46:17 -05:00
|
|
|
let tcx = self.tcx();
|
|
|
|
|
2012-08-22 19:24:52 -05:00
|
|
|
debug!("check_pure_callee_or_arg(pc=%?, expr=%?, \
|
2012-06-01 23:54:38 -05:00
|
|
|
callee_id=%d, ty=%s)",
|
|
|
|
pc,
|
2012-07-18 18:18:02 -05:00
|
|
|
opt_expr.map(|e| pprust::expr_to_str(e, tcx.sess.intr()) ),
|
2012-06-01 23:54:38 -05:00
|
|
|
callee_id,
|
2012-08-22 19:24:52 -05:00
|
|
|
ty_to_str(self.tcx(), ty::node_id_to_type(tcx, callee_id)));
|
2012-06-01 12:46:17 -05:00
|
|
|
|
|
|
|
// 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
|
|
|
|
// following holds:
|
|
|
|
//
|
|
|
|
// (a) A was declared pure and B is one of its arguments;
|
|
|
|
// (b) B is a stack closure;
|
|
|
|
// (c) B is a pure fn;
|
|
|
|
// (d) B is not a fn.
|
|
|
|
|
2012-08-06 14:34:08 -05:00
|
|
|
match opt_expr {
|
2012-08-20 14:23:37 -05:00
|
|
|
Some(expr) => {
|
2012-08-06 14:34:08 -05:00
|
|
|
match expr.node {
|
2012-08-03 21:59:04 -05:00
|
|
|
ast::expr_path(_) if pc == pc_pure_fn => {
|
2012-06-01 23:54:38 -05:00
|
|
|
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 &&
|
2012-06-04 10:03:33 -05:00
|
|
|
(*self.fn_args).contains(did.node);
|
2012-08-01 19:30:05 -05:00
|
|
|
if is_fn_arg { return; } // case (a) above
|
2012-06-01 23:54:38 -05:00
|
|
|
}
|
|
|
|
ast::expr_fn_block(*) | ast::expr_fn(*) |
|
2012-08-03 21:59:04 -05:00
|
|
|
ast::expr_loop_body(*) | ast::expr_do_body(*) => {
|
2012-08-01 19:30:05 -05:00
|
|
|
if self.is_stack_closure(expr.id) {
|
|
|
|
// case (b) above
|
|
|
|
return;
|
|
|
|
}
|
2012-06-01 23:54:38 -05:00
|
|
|
}
|
2012-08-03 21:59:04 -05:00
|
|
|
_ => ()
|
2012-06-01 23:54:38 -05:00
|
|
|
}
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
2012-08-20 14:23:37 -05:00
|
|
|
None => ()
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
|
2012-06-01 23:54:38 -05:00
|
|
|
let callee_ty = ty::node_id_to_type(tcx, callee_id);
|
2012-09-11 18:20:31 -05:00
|
|
|
match ty::get(callee_ty).sty {
|
2012-08-03 21:59:04 -05:00
|
|
|
ty::ty_fn(fn_ty) => {
|
2012-09-07 09:37:19 -05:00
|
|
|
match fn_ty.meta.purity {
|
2012-08-03 21:59:04 -05:00
|
|
|
ast::pure_fn => return, // case (c) above
|
|
|
|
ast::impure_fn | ast::unsafe_fn | ast::extern_fn => {
|
2012-06-01 12:46:17 -05:00
|
|
|
self.report_purity_error(
|
2012-06-01 23:54:38 -05:00
|
|
|
pc, callee_span,
|
2012-08-22 19:24:52 -05:00
|
|
|
fmt!("access to %s function",
|
2012-09-07 09:37:19 -05:00
|
|
|
pprust::purity_to_str(fn_ty.meta.purity)));
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-08-03 21:59:04 -05:00
|
|
|
_ => return, // case (d) above
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// True if the expression with the given `id` is a stack closure.
|
|
|
|
// The expression must be an expr_fn(*) or expr_fn_block(*)
|
|
|
|
fn is_stack_closure(id: ast::node_id) -> bool {
|
|
|
|
let fn_ty = ty::node_id_to_type(self.tcx(), id);
|
|
|
|
let proto = ty::ty_fn_proto(fn_ty);
|
2012-08-10 20:15:08 -05:00
|
|
|
return ty::is_blockish(proto);
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
fn is_allowed_pure_arg(expr: @ast::expr) -> bool {
|
2012-08-06 14:34:08 -05:00
|
|
|
return match expr.node {
|
2012-08-03 21:59:04 -05:00
|
|
|
ast::expr_path(_) => {
|
2012-06-01 12:46:17 -05:00
|
|
|
let def = self.tcx().def_map.get(expr.id);
|
|
|
|
let did = ast_util::def_id_of_def(def);
|
2012-06-04 10:03:33 -05:00
|
|
|
did.crate == ast::local_crate &&
|
|
|
|
(*self.fn_args).contains(did.node)
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
2012-08-03 21:59:04 -05:00
|
|
|
ast::expr_fn_block(*) | ast::expr_fn(*) => {
|
2012-06-01 12:46:17 -05:00
|
|
|
self.is_stack_closure(expr.id)
|
|
|
|
}
|
2012-08-03 21:59:04 -05:00
|
|
|
_ => false
|
2012-06-01 12:46:17 -05:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
fn check_for_conflicting_loans(scope_id: ast::node_id) {
|
2012-08-06 14:34:08 -05:00
|
|
|
let new_loanss = match self.req_maps.req_loan_map.find(scope_id) {
|
2012-08-20 14:23:37 -05:00
|
|
|
None => return,
|
|
|
|
Some(loanss) => loanss
|
2012-06-01 12:46:17 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
let par_scope_id = self.tcx().region_map.get(scope_id);
|
2012-06-30 18:19:07 -05:00
|
|
|
for self.walk_loans(par_scope_id) |old_loan| {
|
|
|
|
for (*new_loanss).each |new_loans| {
|
|
|
|
for (*new_loans).each |new_loan| {
|
2012-09-07 17:32:04 -05:00
|
|
|
if old_loan.lp != new_loan.lp { loop; }
|
2012-08-06 14:34:08 -05:00
|
|
|
match (old_loan.mutbl, new_loan.mutbl) {
|
2012-06-01 12:46:17 -05:00
|
|
|
(m_const, _) | (_, m_const) |
|
2012-08-03 21:59:04 -05:00
|
|
|
(m_mutbl, m_mutbl) | (m_imm, m_imm) => {
|
2012-06-01 12:46:17 -05:00
|
|
|
/*ok*/
|
|
|
|
}
|
|
|
|
|
2012-08-03 21:59:04 -05:00
|
|
|
(m_mutbl, m_imm) | (m_imm, m_mutbl) => {
|
2012-06-01 12:46:17 -05:00
|
|
|
self.bccx.span_err(
|
|
|
|
new_loan.cmt.span,
|
2012-08-22 19:24:52 -05:00
|
|
|
fmt!("loan of %s as %s \
|
2012-06-01 12:46:17 -05:00
|
|
|
conflicts with prior loan",
|
|
|
|
self.bccx.cmt_to_str(new_loan.cmt),
|
2012-08-22 19:24:52 -05:00
|
|
|
self.bccx.mut_to_str(new_loan.mutbl)));
|
2012-06-01 12:46:17 -05:00
|
|
|
self.bccx.span_note(
|
|
|
|
old_loan.cmt.span,
|
2012-08-22 19:24:52 -05:00
|
|
|
fmt!("prior loan as %s granted here",
|
|
|
|
self.bccx.mut_to_str(old_loan.mutbl)));
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_local_variable(cmt: cmt) -> bool {
|
2012-08-06 14:34:08 -05:00
|
|
|
match cmt.cat {
|
2012-08-03 21:59:04 -05:00
|
|
|
cat_local(_) => true,
|
|
|
|
_ => false
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_self_field(cmt: cmt) -> bool {
|
2012-08-06 14:34:08 -05:00
|
|
|
match cmt.cat {
|
2012-08-03 21:59:04 -05:00
|
|
|
cat_comp(cmt_base, comp_field(*)) => {
|
2012-08-06 14:34:08 -05:00
|
|
|
match cmt_base.cat {
|
2012-08-03 21:59:04 -05:00
|
|
|
cat_special(sk_self) => true,
|
|
|
|
_ => false
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
}
|
2012-08-03 21:59:04 -05:00
|
|
|
_ => false
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn check_assignment(at: assignment_type, ex: @ast::expr) {
|
|
|
|
let cmt = self.bccx.cat_expr(ex);
|
|
|
|
|
2012-08-22 19:24:52 -05:00
|
|
|
debug!("check_assignment(cmt=%s)",
|
|
|
|
self.bccx.cmt_to_repr(cmt));
|
2012-06-01 12:46:17 -05:00
|
|
|
|
|
|
|
if self.in_ctor && self.is_self_field(cmt)
|
|
|
|
&& at.checked_by_liveness() {
|
|
|
|
// assigning to self.foo in a ctor is always allowed.
|
|
|
|
} else if self.is_local_variable(cmt) && at.checked_by_liveness() {
|
|
|
|
// liveness guarantees that immutable local variables
|
|
|
|
// are only assigned once
|
|
|
|
} else {
|
2012-08-06 14:34:08 -05:00
|
|
|
match cmt.mutbl {
|
2012-08-03 21:59:04 -05:00
|
|
|
m_mutbl => { /*ok*/ }
|
|
|
|
m_const | m_imm => {
|
2012-06-01 12:46:17 -05:00
|
|
|
self.bccx.span_err(
|
|
|
|
ex.span,
|
|
|
|
at.ing_form(self.bccx.cmt_to_str(cmt)));
|
2012-08-01 19:30:05 -05:00
|
|
|
return;
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if this is a pure function, only loan-able state can be
|
|
|
|
// assigned, because it is uniquely tied to this function and
|
|
|
|
// is not visible from the outside
|
2012-08-06 14:34:08 -05:00
|
|
|
match self.purity(ex.id) {
|
2012-08-20 14:23:37 -05:00
|
|
|
None => (),
|
|
|
|
Some(pc @ pc_cmt(_)) => {
|
2012-08-09 16:18:43 -05:00
|
|
|
// Subtle: Issue #3162. If we are enforcing purity
|
|
|
|
// because there is a reference to aliasable, mutable data
|
|
|
|
// that we require to be immutable, we can't allow writes
|
|
|
|
// even to data owned by the current stack frame. This is
|
|
|
|
// because that aliasable data might have been located on
|
|
|
|
// the current stack frame, we don't know.
|
2012-08-23 16:46:59 -05:00
|
|
|
self.report_purity_error(
|
|
|
|
pc, ex.span, at.ing_form(self.bccx.cmt_to_str(cmt)));
|
2012-08-09 16:18:43 -05:00
|
|
|
}
|
2012-08-20 14:23:37 -05:00
|
|
|
Some(pc_pure_fn) => {
|
2012-08-09 16:18:43 -05:00
|
|
|
if cmt.lp.is_none() {
|
|
|
|
self.report_purity_error(
|
|
|
|
pc_pure_fn, ex.span,
|
|
|
|
at.ing_form(self.bccx.cmt_to_str(cmt)));
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check for a conflicting loan as well, except in the case of
|
|
|
|
// taking a mutable ref. that will create a loan of its own
|
|
|
|
// which will be checked for compat separately in
|
|
|
|
// check_for_conflicting_loans()
|
2012-09-11 23:25:01 -05:00
|
|
|
for cmt.lp.each |lp| {
|
|
|
|
self.check_for_loan_conflicting_with_assignment(
|
|
|
|
at, ex, cmt, lp);
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
self.bccx.add_to_mutbl_map(cmt);
|
|
|
|
}
|
|
|
|
|
2012-06-01 17:46:32 -05:00
|
|
|
fn check_for_loan_conflicting_with_assignment(
|
|
|
|
at: assignment_type,
|
|
|
|
ex: @ast::expr,
|
|
|
|
cmt: cmt,
|
|
|
|
lp: @loan_path) {
|
|
|
|
|
2012-06-30 18:19:07 -05:00
|
|
|
for self.walk_loans_of(ex.id, lp) |loan| {
|
2012-08-06 14:34:08 -05:00
|
|
|
match loan.mutbl {
|
2012-08-03 21:59:04 -05:00
|
|
|
m_mutbl | m_const => { /*ok*/ }
|
|
|
|
m_imm => {
|
2012-06-01 17:46:32 -05:00
|
|
|
self.bccx.span_err(
|
|
|
|
ex.span,
|
2012-08-22 19:24:52 -05:00
|
|
|
fmt!("%s prohibited due to outstanding loan",
|
|
|
|
at.ing_form(self.bccx.cmt_to_str(cmt))));
|
2012-06-01 17:46:32 -05:00
|
|
|
self.bccx.span_note(
|
|
|
|
loan.cmt.span,
|
2012-08-22 19:24:52 -05:00
|
|
|
fmt!("loan of %s granted here",
|
|
|
|
self.bccx.cmt_to_str(loan.cmt)));
|
2012-08-01 19:30:05 -05:00
|
|
|
return;
|
2012-06-01 17:46:32 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Subtle: if the mutability of the component being assigned
|
|
|
|
// is inherited from the thing that the component is embedded
|
|
|
|
// within, then we have to check whether that thing has been
|
|
|
|
// loaned out as immutable! An example:
|
2012-08-20 14:23:37 -05:00
|
|
|
// let mut x = {f: Some(3)};
|
2012-06-01 17:46:32 -05:00
|
|
|
// let y = &x; // x loaned out as immutable
|
|
|
|
// x.f = none; // changes type of y.f, which appears to be imm
|
2012-08-06 14:34:08 -05:00
|
|
|
match *lp {
|
2012-08-03 21:59:04 -05:00
|
|
|
lp_comp(lp_base, ck) if inherent_mutability(ck) != m_mutbl => {
|
2012-06-01 17:46:32 -05:00
|
|
|
self.check_for_loan_conflicting_with_assignment(
|
|
|
|
at, ex, cmt, lp_base);
|
|
|
|
}
|
2012-08-03 21:59:04 -05:00
|
|
|
lp_comp(*) | lp_local(*) | lp_arg(*) | lp_deref(*) => ()
|
2012-06-01 17:46:32 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-14 00:57:48 -05:00
|
|
|
fn report_purity_error(pc: purity_cause, sp: span, msg: ~str) {
|
2012-08-06 14:34:08 -05:00
|
|
|
match pc {
|
2012-08-03 21:59:04 -05:00
|
|
|
pc_pure_fn => {
|
2012-06-01 12:46:17 -05:00
|
|
|
self.tcx().sess.span_err(
|
|
|
|
sp,
|
2012-08-22 19:24:52 -05:00
|
|
|
fmt!("%s prohibited in pure context", msg));
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
2012-08-03 21:59:04 -05:00
|
|
|
pc_cmt(e) => {
|
2012-06-01 12:46:17 -05:00
|
|
|
if self.reported.insert(e.cmt.id, ()) {
|
|
|
|
self.tcx().sess.span_err(
|
|
|
|
e.cmt.span,
|
2012-08-22 19:24:52 -05:00
|
|
|
fmt!("illegal borrow unless pure: %s",
|
2012-09-11 23:25:01 -05:00
|
|
|
self.bccx.bckerr_to_str(e)));
|
|
|
|
self.bccx.note_and_explain_bckerr(e);
|
2012-06-01 12:46:17 -05:00
|
|
|
self.tcx().sess.span_note(
|
|
|
|
sp,
|
2012-08-22 19:24:52 -05:00
|
|
|
fmt!("impure due to %s", msg));
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn check_move_out(ex: @ast::expr) {
|
|
|
|
let cmt = self.bccx.cat_expr(ex);
|
|
|
|
self.check_move_out_from_cmt(cmt);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn check_move_out_from_cmt(cmt: cmt) {
|
2012-08-22 19:24:52 -05:00
|
|
|
debug!("check_move_out_from_cmt(cmt=%s)",
|
|
|
|
self.bccx.cmt_to_repr(cmt));
|
2012-06-01 12:46:17 -05:00
|
|
|
|
2012-08-06 14:34:08 -05:00
|
|
|
match cmt.cat {
|
2012-06-01 12:46:17 -05:00
|
|
|
// Rvalues, locals, and arguments can be moved:
|
2012-08-03 21:59:04 -05:00
|
|
|
cat_rvalue | cat_local(_) | cat_arg(_) => {}
|
2012-06-01 12:46:17 -05:00
|
|
|
|
|
|
|
// We allow moving out of static items because the old code
|
|
|
|
// did. This seems consistent with permitting moves out of
|
|
|
|
// rvalues, I guess.
|
2012-08-03 21:59:04 -05:00
|
|
|
cat_special(sk_static_item) => {}
|
2012-06-01 12:46:17 -05:00
|
|
|
|
2012-08-03 21:59:04 -05:00
|
|
|
cat_deref(_, _, unsafe_ptr) => {}
|
2012-06-20 22:08:25 -05:00
|
|
|
|
2012-06-01 12:46:17 -05:00
|
|
|
// Nothing else.
|
2012-08-03 21:59:04 -05:00
|
|
|
_ => {
|
2012-06-01 12:46:17 -05:00
|
|
|
self.bccx.span_err(
|
|
|
|
cmt.span,
|
2012-08-22 19:24:52 -05:00
|
|
|
fmt!("moving out of %s", self.bccx.cmt_to_str(cmt)));
|
2012-08-01 19:30:05 -05:00
|
|
|
return;
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.bccx.add_to_mutbl_map(cmt);
|
|
|
|
|
|
|
|
// check for a conflicting loan:
|
2012-08-06 14:34:08 -05:00
|
|
|
let lp = match cmt.lp {
|
2012-08-20 14:23:37 -05:00
|
|
|
None => return,
|
|
|
|
Some(lp) => lp
|
2012-06-01 12:46:17 -05:00
|
|
|
};
|
2012-06-30 18:19:07 -05:00
|
|
|
for self.walk_loans_of(cmt.id, lp) |loan| {
|
2012-06-01 12:46:17 -05:00
|
|
|
self.bccx.span_err(
|
|
|
|
cmt.span,
|
2012-08-22 19:24:52 -05:00
|
|
|
fmt!("moving out of %s prohibited due to outstanding loan",
|
|
|
|
self.bccx.cmt_to_str(cmt)));
|
2012-06-01 12:46:17 -05:00
|
|
|
self.bccx.span_note(
|
|
|
|
loan.cmt.span,
|
2012-08-22 19:24:52 -05:00
|
|
|
fmt!("loan of %s granted here",
|
|
|
|
self.bccx.cmt_to_str(loan.cmt)));
|
2012-08-01 19:30:05 -05:00
|
|
|
return;
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
}
|
2012-06-01 23:54:38 -05:00
|
|
|
|
2012-06-17 18:16:41 -05:00
|
|
|
// Very subtle (#2633): liveness can mark options as last_use even
|
|
|
|
// when there is an outstanding loan. In that case, it is not
|
|
|
|
// safe to consider the use a last_use.
|
|
|
|
fn check_last_use(expr: @ast::expr) {
|
2012-08-28 17:54:45 -05:00
|
|
|
debug!("Checking last use of expr %?", expr.id);
|
2012-06-17 18:16:41 -05:00
|
|
|
let cmt = self.bccx.cat_expr(expr);
|
2012-08-06 14:34:08 -05:00
|
|
|
let lp = match cmt.lp {
|
2012-08-28 17:54:45 -05:00
|
|
|
None => {
|
|
|
|
debug!("Not a loanable expression");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Some(lp) => lp
|
2012-06-17 18:16:41 -05:00
|
|
|
};
|
2012-06-30 18:19:07 -05:00
|
|
|
for self.walk_loans_of(cmt.id, lp) |_loan| {
|
2012-08-22 19:24:52 -05:00
|
|
|
debug!("Removing last use entry %? due to outstanding loan",
|
|
|
|
expr.id);
|
2012-06-17 18:16:41 -05:00
|
|
|
self.bccx.last_use_map.remove(expr.id);
|
2012-08-01 19:30:05 -05:00
|
|
|
return;
|
2012-06-17 18:16:41 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-01 23:54:38 -05:00
|
|
|
fn check_call(expr: @ast::expr,
|
2012-08-20 14:23:37 -05:00
|
|
|
callee: Option<@ast::expr>,
|
2012-06-01 23:54:38 -05:00
|
|
|
callee_id: ast::node_id,
|
|
|
|
callee_span: span,
|
2012-06-29 18:26:56 -05:00
|
|
|
args: ~[@ast::expr]) {
|
2012-08-06 14:34:08 -05:00
|
|
|
match self.purity(expr.id) {
|
2012-08-20 14:23:37 -05:00
|
|
|
None => {}
|
|
|
|
Some(pc) => {
|
2012-06-01 23:54:38 -05:00
|
|
|
self.check_pure_callee_or_arg(
|
|
|
|
pc, callee, callee_id, callee_span);
|
2012-06-30 18:19:07 -05:00
|
|
|
for args.each |arg| {
|
2012-06-01 23:54:38 -05:00
|
|
|
self.check_pure_callee_or_arg(
|
2012-08-20 14:23:37 -05:00
|
|
|
pc, Some(arg), arg.id, arg.span);
|
2012-06-01 23:54:38 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let arg_tys =
|
|
|
|
ty::ty_fn_args(
|
|
|
|
ty::node_id_to_type(self.tcx(), callee_id));
|
2012-06-30 18:19:07 -05:00
|
|
|
do vec::iter2(args, arg_tys) |arg, arg_ty| {
|
2012-08-06 14:34:08 -05:00
|
|
|
match ty::resolved_mode(self.tcx(), arg_ty.mode) {
|
2012-09-11 23:25:01 -05:00
|
|
|
ast::by_move => {
|
|
|
|
self.check_move_out(arg);
|
|
|
|
}
|
|
|
|
ast::by_mutbl_ref | ast::by_ref |
|
|
|
|
ast::by_copy | ast::by_val => {
|
|
|
|
}
|
2012-06-01 23:54:38 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
fn check_loans_in_fn(fk: visit::fn_kind, decl: ast::fn_decl, body: ast::blk,
|
|
|
|
sp: span, id: ast::node_id, &&self: check_loan_ctxt,
|
|
|
|
visitor: visit::vt<check_loan_ctxt>) {
|
|
|
|
|
2012-08-22 19:24:52 -05:00
|
|
|
debug!("purity on entry=%?", copy self.declared_purity);
|
2012-07-04 14:04:28 -05:00
|
|
|
do save_and_restore(self.in_ctor) {
|
|
|
|
do save_and_restore(self.declared_purity) {
|
|
|
|
do save_and_restore(self.fn_args) {
|
2012-06-01 12:46:17 -05:00
|
|
|
let is_stack_closure = self.is_stack_closure(id);
|
2012-08-24 16:01:08 -05:00
|
|
|
let fty = ty::node_id_to_type(self.tcx(), id);
|
|
|
|
self.declared_purity = ty::determine_inherited_purity(
|
|
|
|
copy self.declared_purity,
|
|
|
|
ty::ty_fn_purity(fty),
|
|
|
|
ty::ty_fn_proto(fty));
|
2012-06-01 12:46:17 -05:00
|
|
|
|
|
|
|
// In principle, we could consider fk_anon(*) or
|
|
|
|
// fk_fn_block(*) to be in a ctor, I suppose, but the
|
|
|
|
// purpose of the in_ctor flag is to allow modifications
|
|
|
|
// of otherwise immutable fields and typestate wouldn't be
|
|
|
|
// able to "see" into those functions anyway, so it
|
|
|
|
// wouldn't be very helpful.
|
2012-08-06 14:34:08 -05:00
|
|
|
match fk {
|
2012-08-03 21:59:04 -05:00
|
|
|
visit::fk_ctor(*) => {
|
2012-06-01 12:46:17 -05:00
|
|
|
self.in_ctor = true;
|
2012-06-30 18:19:07 -05:00
|
|
|
self.fn_args = @decl.inputs.map(|i| i.id );
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
visit::fk_anon(*) |
|
2012-08-03 21:59:04 -05:00
|
|
|
visit::fk_fn_block(*) if is_stack_closure => {
|
2012-06-01 12:46:17 -05:00
|
|
|
self.in_ctor = false;
|
2012-08-24 16:01:08 -05:00
|
|
|
// inherits the fn_args from enclosing ctxt
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
visit::fk_anon(*) | visit::fk_fn_block(*) |
|
|
|
|
visit::fk_method(*) | visit::fk_item_fn(*) |
|
2012-08-03 21:59:04 -05:00
|
|
|
visit::fk_dtor(*) => {
|
2012-06-01 12:46:17 -05:00
|
|
|
self.in_ctor = false;
|
2012-06-30 18:19:07 -05:00
|
|
|
self.fn_args = @decl.inputs.map(|i| i.id );
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
visit::visit_fn(fk, decl, body, sp, id, self, visitor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-08-22 19:24:52 -05:00
|
|
|
debug!("purity on exit=%?", copy self.declared_purity);
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
|
2012-06-20 22:08:25 -05:00
|
|
|
fn check_loans_in_local(local: @ast::local,
|
|
|
|
&&self: check_loan_ctxt,
|
|
|
|
vt: visit::vt<check_loan_ctxt>) {
|
2012-08-06 14:34:08 -05:00
|
|
|
match local.node.init {
|
2012-08-20 14:23:37 -05:00
|
|
|
Some({op: ast::init_move, expr: expr}) => {
|
2012-06-20 22:08:25 -05:00
|
|
|
self.check_move_out(expr);
|
|
|
|
}
|
2012-08-20 14:23:37 -05:00
|
|
|
Some({op: ast::init_assign, _}) | None => {}
|
2012-06-20 22:08:25 -05:00
|
|
|
}
|
|
|
|
visit::visit_local(local, self, vt);
|
|
|
|
}
|
|
|
|
|
2012-06-01 12:46:17 -05:00
|
|
|
fn check_loans_in_expr(expr: @ast::expr,
|
|
|
|
&&self: check_loan_ctxt,
|
|
|
|
vt: visit::vt<check_loan_ctxt>) {
|
2012-08-28 17:54:45 -05:00
|
|
|
debug!("check_loans_in_expr(expr=%?/%s)",
|
|
|
|
expr.id, pprust::expr_to_str(expr, self.tcx().sess.intr()));
|
|
|
|
|
2012-06-01 12:46:17 -05:00
|
|
|
self.check_for_conflicting_loans(expr.id);
|
|
|
|
|
2012-08-06 14:34:08 -05:00
|
|
|
match expr.node {
|
2012-08-03 21:59:04 -05:00
|
|
|
ast::expr_path(*) if self.bccx.last_use_map.contains_key(expr.id) => {
|
2012-06-17 18:16:41 -05:00
|
|
|
self.check_last_use(expr);
|
|
|
|
}
|
|
|
|
|
2012-08-03 21:59:04 -05:00
|
|
|
ast::expr_swap(l, r) => {
|
2012-06-01 12:46:17 -05:00
|
|
|
self.check_assignment(at_swap, l);
|
|
|
|
self.check_assignment(at_swap, r);
|
|
|
|
}
|
2012-08-03 21:59:04 -05:00
|
|
|
ast::expr_move(dest, src) => {
|
2012-06-01 12:46:17 -05:00
|
|
|
self.check_assignment(at_straight_up, dest);
|
|
|
|
self.check_move_out(src);
|
|
|
|
}
|
2012-08-03 21:59:04 -05:00
|
|
|
ast::expr_unary_move(src) => {
|
2012-08-02 18:00:45 -05:00
|
|
|
self.check_move_out(src);
|
|
|
|
}
|
2012-06-01 12:46:17 -05:00
|
|
|
ast::expr_assign(dest, _) |
|
2012-08-03 21:59:04 -05:00
|
|
|
ast::expr_assign_op(_, dest, _) => {
|
2012-06-01 12:46:17 -05:00
|
|
|
self.check_assignment(at_straight_up, dest);
|
|
|
|
}
|
|
|
|
ast::expr_fn(_, _, _, cap_clause) |
|
2012-08-03 21:59:04 -05:00
|
|
|
ast::expr_fn_block(_, _, cap_clause) => {
|
2012-06-30 18:19:07 -05:00
|
|
|
for (*cap_clause).each |cap_item| {
|
2012-06-01 12:46:17 -05:00
|
|
|
if cap_item.is_move {
|
|
|
|
let def = self.tcx().def_map.get(cap_item.id);
|
|
|
|
|
|
|
|
// Hack: the type that is used in the cmt doesn't actually
|
|
|
|
// matter here, so just subst nil instead of looking up
|
|
|
|
// the type of the def that is referred to
|
|
|
|
let cmt = self.bccx.cat_def(cap_item.id, cap_item.span,
|
|
|
|
ty::mk_nil(self.tcx()), def);
|
|
|
|
self.check_move_out_from_cmt(cmt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-08-03 21:59:04 -05:00
|
|
|
ast::expr_call(f, args, _) => {
|
2012-08-20 14:23:37 -05:00
|
|
|
self.check_call(expr, Some(f), f.id, f.span, args);
|
2012-06-01 23:54:38 -05:00
|
|
|
}
|
|
|
|
ast::expr_index(_, rval) |
|
|
|
|
ast::expr_binary(_, _, rval)
|
2012-08-03 21:59:04 -05:00
|
|
|
if self.bccx.method_map.contains_key(expr.id) => {
|
2012-06-01 23:54:38 -05:00
|
|
|
self.check_call(expr,
|
2012-08-20 14:23:37 -05:00
|
|
|
None,
|
2012-07-11 16:31:35 -05:00
|
|
|
expr.callee_id,
|
2012-06-01 23:54:38 -05:00
|
|
|
expr.span,
|
2012-06-29 18:26:56 -05:00
|
|
|
~[rval]);
|
2012-06-01 23:54:38 -05:00
|
|
|
}
|
2012-06-15 16:55:03 -05:00
|
|
|
ast::expr_unary(*) | ast::expr_index(*)
|
2012-08-03 21:59:04 -05:00
|
|
|
if self.bccx.method_map.contains_key(expr.id) => {
|
2012-06-01 23:54:38 -05:00
|
|
|
self.check_call(expr,
|
2012-08-20 14:23:37 -05:00
|
|
|
None,
|
2012-07-11 16:31:35 -05:00
|
|
|
expr.callee_id,
|
2012-06-01 23:54:38 -05:00
|
|
|
expr.span,
|
2012-06-29 18:26:56 -05:00
|
|
|
~[]);
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
2012-08-03 21:59:04 -05:00
|
|
|
_ => { }
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
visit::visit_expr(expr, self, vt);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn check_loans_in_block(blk: ast::blk,
|
|
|
|
&&self: check_loan_ctxt,
|
|
|
|
vt: visit::vt<check_loan_ctxt>) {
|
2012-07-04 14:04:28 -05:00
|
|
|
do save_and_restore(self.declared_purity) {
|
2012-06-01 12:46:17 -05:00
|
|
|
self.check_for_conflicting_loans(blk.node.id);
|
|
|
|
|
2012-08-06 14:34:08 -05:00
|
|
|
match blk.node.rules {
|
2012-08-03 21:59:04 -05:00
|
|
|
ast::default_blk => {
|
2012-06-01 12:46:17 -05:00
|
|
|
}
|
2012-08-03 21:59:04 -05:00
|
|
|
ast::unchecked_blk => {
|
2012-06-01 12:46:17 -05:00
|
|
|
self.declared_purity = ast::impure_fn;
|
|
|
|
}
|
2012-08-03 21:59:04 -05:00
|
|
|
ast::unsafe_blk => {
|
2012-06-01 12:46:17 -05:00
|
|
|
self.declared_purity = ast::unsafe_fn;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
visit::visit_block(blk, self, vt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|