check_loans.rs 29.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

11 12 13 14 15 16 17
// ----------------------------------------------------------------------
// 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
18
// 4. moves do not affect things loaned out in any way
19

20
use middle::moves;
N
Niko Matsakis 已提交
21 22
use middle::borrowck::*;
use mc = middle::mem_categorization;
23
use middle::ty;
N
Niko Matsakis 已提交
24
use util::ppaux::Repr;
25
use core::hashmap::HashSet;
N
Niko Matsakis 已提交
26
use syntax::ast::{m_mutbl, m_imm, m_const};
27 28 29
use syntax::ast;
use syntax::ast_util;
use syntax::visit;
N
Niko Matsakis 已提交
30
use syntax::codemap::span;
31

N
Niko Matsakis 已提交
32
struct CheckLoanCtxt<'self> {
33
    bccx: @BorrowckCtxt,
N
Niko Matsakis 已提交
34 35 36
    dfcx: &'self LoanDataFlow,
    all_loans: &'self [Loan],
    reported: @mut HashSet<ast::node_id>,
37 38
}

39
pub fn check_loans(bccx: @BorrowckCtxt,
N
Niko Matsakis 已提交
40 41 42 43 44
                   dfcx: &LoanDataFlow,
                   all_loans: &[Loan],
                   body: &ast::blk) {
    debug!("check_loans(body id=%?)", body.node.id);

45 46
    let clcx = @mut CheckLoanCtxt {
        bccx: bccx,
N
Niko Matsakis 已提交
47 48 49
        dfcx: dfcx,
        all_loans: all_loans,
        reported: @mut HashSet::new(),
50
    };
N
Niko Matsakis 已提交
51

52 53 54
    let vt = visit::mk_vt(@visit::Visitor {visit_expr: check_loans_in_expr,
                                           visit_local: check_loans_in_local,
                                           visit_block: check_loans_in_block,
N
Niko Matsakis 已提交
55
                                           visit_pat: check_loans_in_pat,
56 57
                                           visit_fn: check_loans_in_fn,
                                           .. *visit::default_visitor()});
N
Niko Matsakis 已提交
58
    (vt.visit_block)(body, clcx, vt);
59 60
}

N
Niko Matsakis 已提交
61 62 63 64
enum MoveError {
    MoveOk,
    MoveFromIllegalCmt(mc::cmt),
    MoveWhileBorrowed(/*loan*/@LoanPath, /*loan*/span)
65 66
}

N
Niko Matsakis 已提交
67
pub impl<'self> CheckLoanCtxt<'self> {
68
    fn tcx(&self) -> ty::ctxt { self.bccx.tcx }
69

N
Niko Matsakis 已提交
70 71 72
    fn each_issued_loan(&self,
                        scope_id: ast::node_id,
                        op: &fn(&Loan) -> bool)
73
    {
N
Niko Matsakis 已提交
74 75 76 77 78 79 80 81 82 83
        //! Iterates over each loan that that has been issued
        //! on entrance to `scope_id`, regardless of whether it is
        //! actually *in scope* at that point.  Sometimes loans
        //! are issued for future scopes and thus they may have been
        //! *issued* but not yet be in effect.

        for self.dfcx.each_bit_on_entry(scope_id) |loan_index| {
            let loan = &self.all_loans[loan_index];
            if !op(loan) {
                return;
84 85 86 87
            }
        }
    }

N
Niko Matsakis 已提交
88 89 90 91 92 93
    fn each_in_scope_loan(&self,
                          scope_id: ast::node_id,
                          op: &fn(&Loan) -> bool)
    {
        //! Like `each_issued_loan()`, but only considers loans that are
        //! currently in scope.
94

N
Niko Matsakis 已提交
95 96 97 98
        let region_maps = self.tcx().region_maps;
        for self.each_issued_loan(scope_id) |loan| {
            if region_maps.is_subscope_of(scope_id, loan.kill_scope) {
                if !op(loan) {
B
Brian Anderson 已提交
99 100
                    return;
                }
101
            }
102
        }
N
Niko Matsakis 已提交
103
    }
104

N
Niko Matsakis 已提交
105 106 107 108 109 110 111 112 113 114 115 116 117
    fn each_in_scope_restriction(&self,
                                 scope_id: ast::node_id,
                                 loan_path: @LoanPath,
                                 op: &fn(&Loan, &Restriction) -> bool)
    {
        //! Iterates through all the in-scope restrictions for the
        //! given `loan_path`

        for self.each_in_scope_loan(scope_id) |loan| {
            for loan.restrictions.each |restr| {
                if restr.loan_path == loan_path {
                    if !op(loan, restr) {
                        return;
118 119
                    }
                }
120 121 122 123
            }
        }
    }

N
Niko Matsakis 已提交
124 125 126
    fn loans_generated_by(&self, scope_id: ast::node_id) -> ~[uint] {
        //! Returns a vector of the loans that are generated as
        //! we encounter `scope_id`.
127

N
Niko Matsakis 已提交
128 129 130 131 132
        let mut result = ~[];
        for self.dfcx.each_gen_bit(scope_id) |loan_index| {
            result.push(loan_index);
        }
        return result;
133 134
    }

135
    fn check_for_conflicting_loans(&mut self, scope_id: ast::node_id) {
N
Niko Matsakis 已提交
136 137 138 139
        //! Checks to see whether any of the loans that are issued
        //! by `scope_id` conflict with loans that have already been
        //! issued when we enter `scope_id` (for example, we do not
        //! permit two `&mut` borrows of the same variable).
140

N
Niko Matsakis 已提交
141
        debug!("check_for_conflicting_loans(scope_id=%?)", scope_id);
142

N
Niko Matsakis 已提交
143 144
        let new_loan_indices = self.loans_generated_by(scope_id);
        debug!("new_loan_indices = %?", new_loan_indices);
145

N
Niko Matsakis 已提交
146 147 148 149
        for self.each_issued_loan(scope_id) |issued_loan| {
            for new_loan_indices.each |&new_loan_index| {
                let new_loan = &self.all_loans[new_loan_index];
                self.report_error_if_loans_conflict(issued_loan, new_loan);
150 151 152
            }
        }

N
Niko Matsakis 已提交
153 154 155 156 157
        for uint::range(0, new_loan_indices.len()) |i| {
            let old_loan = &self.all_loans[new_loan_indices[i]];
            for uint::range(i+1, new_loan_indices.len()) |j| {
                let new_loan = &self.all_loans[new_loan_indices[j]];
                self.report_error_if_loans_conflict(old_loan, new_loan);
158 159 160 161
            }
        }
    }

162
    fn report_error_if_loans_conflict(&self,
163 164
                                      old_loan: &Loan,
                                      new_loan: &Loan) {
N
Niko Matsakis 已提交
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
        //! Checks whether `old_loan` and `new_loan` can safely be issued
        //! simultaneously.

        debug!("report_error_if_loans_conflict(old_loan=%s, new_loan=%s)",
               old_loan.repr(self.tcx()),
               new_loan.repr(self.tcx()));

        // Should only be called for loans that are in scope at the same time.
        let region_maps = self.tcx().region_maps;
        assert!(region_maps.scopes_intersect(old_loan.kill_scope,
                                             new_loan.kill_scope));

        self.report_error_if_loan_conflicts_with_restriction(
            old_loan, new_loan, old_loan, new_loan) &&
        self.report_error_if_loan_conflicts_with_restriction(
            new_loan, old_loan, old_loan, new_loan);
    }
182

N
Niko Matsakis 已提交
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
    fn report_error_if_loan_conflicts_with_restriction(&self,
                                                       loan1: &Loan,
                                                       loan2: &Loan,
                                                       old_loan: &Loan,
                                                       new_loan: &Loan) -> bool {
        //! Checks whether the restrictions introduced by `loan1` would
        //! prohibit `loan2`. Returns false if an error is reported.

        debug!("report_error_if_loan_conflicts_with_restriction(\
                loan1=%s, loan2=%s)",
               loan1.repr(self.tcx()),
               loan2.repr(self.tcx()));

        // Restrictions that would cause the new loan to be immutable:
        let illegal_if = match loan2.mutbl {
            m_mutbl => RESTR_ALIAS | RESTR_FREEZE | RESTR_MUTATE,
            m_imm =>   RESTR_ALIAS | RESTR_FREEZE,
            m_const => RESTR_ALIAS,
        };
        debug!("illegal_if=%?", illegal_if);

        for loan1.restrictions.each |restr| {
            if !restr.set.intersects(illegal_if) { loop; }
            if restr.loan_path != loan2.loan_path { loop; }
207

N
Niko Matsakis 已提交
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
            match (new_loan.mutbl, old_loan.mutbl) {
                (m_mutbl, m_mutbl) => {
                    self.bccx.span_err(
                        new_loan.span,
                        fmt!("cannot borrow `%s` as mutable \
                              more than once at at a time",
                             self.bccx.loan_path_to_str(new_loan.loan_path)));
                    self.bccx.span_note(
                        old_loan.span,
                        fmt!("second borrow of `%s` as mutable occurs here",
                             self.bccx.loan_path_to_str(new_loan.loan_path)));
                    return false;
                }

                _ => {
                    self.bccx.span_err(
                        new_loan.span,
                        fmt!("cannot borrow `%s` as %s because \
                              it is also borrowed as %s"
                             self.bccx.loan_path_to_str(new_loan.loan_path),
                             self.bccx.mut_to_str(new_loan.mutbl),
                             self.bccx.mut_to_str(old_loan.mutbl)));
                    self.bccx.span_note(
                        old_loan.span,
                        fmt!("second borrow of `%s` occurs here",
                             self.bccx.loan_path_to_str(new_loan.loan_path)));
                    return false;
                }
236 237
            }
        }
N
Niko Matsakis 已提交
238 239

        true
240 241
    }

N
Niko Matsakis 已提交
242
    fn is_local_variable(&self, cmt: mc::cmt) -> bool {
243
        match cmt.cat {
N
Niko Matsakis 已提交
244
          mc::cat_local(_) => true,
B
Brian Anderson 已提交
245
          _ => false
246 247 248
        }
    }

N
Niko Matsakis 已提交
249
    fn check_assignment(&self, expr: @ast::expr) {
250 251
        // We don't use cat_expr() here because we don't want to treat
        // auto-ref'd parameters in overloaded operators as rvalues.
N
Niko Matsakis 已提交
252 253 254
        let cmt = match self.bccx.tcx.adjustments.find(&expr.id) {
            None => self.bccx.cat_expr_unadjusted(expr),
            Some(&adj) => self.bccx.cat_expr_autoderefd(expr, adj)
255
        };
256

N
Niko Matsakis 已提交
257 258 259 260 261 262
        debug!("check_assignment(cmt=%s)", cmt.repr(self.tcx()));

        // check that the value being assigned is declared as mutable
        // and report an error otherwise.
        match cmt.mutbl {
            mc::McDeclared => {
263 264 265 266 267 268 269 270 271 272 273
                // OK, but we have to mark arguments as requiring mut
                // if they are assigned (other cases are handled by liveness,
                // since we need to distinguish local variables assigned
                // once vs those assigned multiple times)
                match cmt.cat {
                    mc::cat_self(*) |
                    mc::cat_arg(*) => {
                        mark_variable_as_used_mut(self, cmt);
                    }
                    _ => {}
                }
N
Niko Matsakis 已提交
274 275 276
            }
            mc::McInherited => {
                // OK, but we may have to add an entry to `used_mut_nodes`
277
                mark_variable_as_used_mut(self, cmt);
N
Niko Matsakis 已提交
278 279 280 281 282 283 284 285 286
            }
            mc::McReadOnly | mc::McImmutable => {
                // Subtle: liveness guarantees that immutable local
                // variables are only assigned once, so no need to
                // report an error for an assignment to a local
                // variable (note also that it is not legal to borrow
                // for a local variable before it has been assigned
                // for the first time).
                if !self.is_local_variable(cmt) {
287
                    self.bccx.span_err(
N
Niko Matsakis 已提交
288 289 290 291
                        expr.span,
                        fmt!("cannot assign to %s %s"
                             cmt.mutbl.to_user_str(),
                             self.bccx.cmt_to_str(cmt)));
292
                }
N
Niko Matsakis 已提交
293
                return;
294 295 296
            }
        }

N
Niko Matsakis 已提交
297 298 299
        if check_for_aliasable_mutable_writes(self, expr, cmt) {
            check_for_assignment_to_restricted_or_frozen_location(
                self, expr, cmt);
300 301
        }

302 303
        fn mark_variable_as_used_mut(self: &CheckLoanCtxt,
                                     cmt: mc::cmt) {
N
Niko Matsakis 已提交
304
            //! If the mutability of the `cmt` being written is inherited
305
            //! from a local variable, liveness will
N
Niko Matsakis 已提交
306 307
            //! not have been able to detect that this variable's mutability
            //! is important, so we must add the variable to the
308
            //! `used_mut_nodes` table here.
N
Niko Matsakis 已提交
309 310 311 312 313 314 315

            let mut cmt = cmt;
            loop {
                debug!("mark_writes_through_upvars_as_used_mut(cmt=%s)",
                       cmt.repr(self.tcx()));
                match cmt.cat {
                    mc::cat_local(id) |
316
                    mc::cat_arg(id) |
N
Niko Matsakis 已提交
317
                    mc::cat_self(id) => {
318
                        self.tcx().used_mut_nodes.insert(id);
N
Niko Matsakis 已提交
319 320
                        return;
                    }
321

N
Niko Matsakis 已提交
322 323 324
                    mc::cat_stack_upvar(b) => {
                        cmt = b;
                    }
325

N
Niko Matsakis 已提交
326 327 328 329 330 331 332 333 334 335
                    mc::cat_rvalue |
                    mc::cat_static_item |
                    mc::cat_implicit_self |
                    mc::cat_copied_upvar(*) |
                    mc::cat_deref(_, _, mc::unsafe_ptr(*)) |
                    mc::cat_deref(_, _, mc::gc_ptr(*)) |
                    mc::cat_deref(_, _, mc::region_ptr(*)) => {
                        assert_eq!(cmt.mutbl, mc::McDeclared);
                        return;
                    }
336

N
Niko Matsakis 已提交
337 338 339 340 341 342 343 344 345 346 347 348
                    mc::cat_discr(b, _) |
                    mc::cat_deref(b, _, mc::uniq_ptr(*)) => {
                        assert_eq!(cmt.mutbl, mc::McInherited);
                        cmt = b;
                    }

                    mc::cat_interior(b, _) => {
                        if cmt.mutbl == mc::McInherited {
                            cmt = b;
                        } else {
                            return; // field declared as mutable or some such
                        }
349 350 351 352
                    }
                }
            }
        }
353

N
Niko Matsakis 已提交
354 355 356 357 358 359
        fn check_for_aliasable_mutable_writes(self: &CheckLoanCtxt,
                                              expr: @ast::expr,
                                              cmt: mc::cmt) -> bool {
            //! Safety checks related to writes to aliasable, mutable locations

            let guarantor = cmt.guarantor();
360 361
            debug!("check_for_aliasable_mutable_writes(cmt=%s, guarantor=%s)",
                   cmt.repr(self.tcx()), guarantor.repr(self.tcx()));
N
Niko Matsakis 已提交
362 363 364 365 366 367 368 369 370 371 372 373 374
            match guarantor.cat {
                mc::cat_deref(b, _, mc::region_ptr(m_mutbl, _)) => {
                    // Statically prohibit writes to `&mut` when aliasable

                    match b.freely_aliasable() {
                        None => {}
                        Some(cause) => {
                            self.bccx.report_aliasability_violation(
                                expr.span,
                                MutabilityViolation,
                                cause);
                        }
                    }
375
                }
N
Niko Matsakis 已提交
376 377 378 379 380 381 382 383

                mc::cat_deref(base, deref_count, mc::gc_ptr(ast::m_mutbl)) => {
                    // Dynamically check writes to `@mut`

                    let key = root_map_key {
                        id: base.id,
                        derefs: deref_count
                    };
384
                    debug!("Inserting write guard at %?", key);
N
Niko Matsakis 已提交
385 386 387 388
                    self.bccx.write_guard_map.insert(key);
                }

                _ => {}
389 390
            }

N
Niko Matsakis 已提交
391
            return true; // no errors reported
392 393
        }

N
Niko Matsakis 已提交
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
        fn check_for_assignment_to_restricted_or_frozen_location(
            self: &CheckLoanCtxt,
            expr: @ast::expr,
            cmt: mc::cmt) -> bool
        {
            //! Check for assignments that violate the terms of an
            //! outstanding loan.

            let loan_path = match opt_loan_path(cmt) {
                Some(lp) => lp,
                None => { return true; /* no loan path, can't be any loans */ }
            };

            // Start by searching for an assignment to a *restricted*
            // location. Here is one example of the kind of error caught
            // by this check:
            //
            //    let mut v = ~[1, 2, 3];
            //    let p = &v;
            //    v = ~[4];
            //
            // In this case, creating `p` triggers a RESTR_MUTATE
            // restriction on the path `v`.
            //
            // Here is a second, more subtle example:
            //
            //    let mut v = ~[1, 2, 3];
            //    let p = &const v[0];
            //    v[0] = 4;                   // OK
            //    v[1] = 5;                   // OK
            //    v = ~[4, 5, 3];             // Error
            //
            // In this case, `p` is pointing to `v[0]`, and it is a
            // `const` pointer in any case. So the first two
            // assignments are legal (and would be permitted by this
            // check). However, the final assignment (which is
            // logically equivalent) is forbidden, because it would
            // cause the existing `v` array to be freed, thus
            // invalidating `p`. In the code, this error results
            // because `gather_loans::restrictions` adds a
            // `RESTR_MUTATE` restriction whenever the contents of an
            // owned pointer are borrowed, and hence while `v[*]` is not
            // restricted from being written, `v` is.
            for self.each_in_scope_restriction(expr.id, loan_path)
                |loan, restr|
            {
                if restr.set.intersects(RESTR_MUTATE) {
                    self.report_illegal_mutation(expr, loan_path, loan);
                    return false;
                }
            }

            // The previous code handled assignments to paths that
            // have been restricted. This covers paths that have been
            // directly lent out and their base paths, but does not
            // cover random extensions of those paths. For example,
            // the following program is not declared illegal by the
            // previous check:
            //
            //    let mut v = ~[1, 2, 3];
            //    let p = &v;
            //    v[0] = 4; // declared error by loop below, not code above
            //
            // The reason that this passes the previous check whereas
            // an assignment like `v = ~[4]` fails is because the assignment
            // here is to `v[*]`, and the existing restrictions were issued
            // for `v`, not `v[*]`.
            //
            // So in this loop, we walk back up the loan path so long
            // as the mutability of the path is dependent on a super
            // path, and check that the super path was not lent out as
            // mutable or immutable (a const loan is ok).
            //
            // Note that we are *not* checking for any and all
            // restrictions.  We are only interested in the pointers
            // that the user created, whereas we add restrictions for
            // all kinds of paths that are not directly aliased. If we checked
            // for all restrictions, and not just loans, then the following
            // valid program would be considered illegal:
            //
            //    let mut v = ~[1, 2, 3];
            //    let p = &const v[0];
            //    v[1] = 5; // ok
            //
            // Here the restriction that `v` not be mutated would be misapplied
            // to block the subpath `v[1]`.
            let full_loan_path = loan_path;
            let mut loan_path = loan_path;
            loop {
                match *loan_path {
                    // Peel back one layer if `loan_path` has
                    // inherited mutability
                    LpExtend(lp_base, mc::McInherited, _) => {
                        loan_path = lp_base;
                    }

                    // Otherwise stop iterating
                    LpExtend(_, mc::McDeclared, _) |
                    LpExtend(_, mc::McImmutable, _) |
                    LpExtend(_, mc::McReadOnly, _) |
                    LpVar(_) => {
                        return true;
                    }
                }

                // Check for a non-const loan of `loan_path`
                for self.each_in_scope_loan(expr.id) |loan| {
                    if loan.loan_path == loan_path && loan.mutbl != m_const {
                        self.report_illegal_mutation(expr, full_loan_path, loan);
                        return false;
                    }
                }
506 507 508 509
            }
        }
    }

N
Niko Matsakis 已提交
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
    fn report_illegal_mutation(&self,
                               expr: @ast::expr,
                               loan_path: &LoanPath,
                               loan: &Loan) {
        self.bccx.span_err(
            expr.span,
            fmt!("cannot assign to `%s` because it is borrowed",
                 self.bccx.loan_path_to_str(loan_path)));
        self.bccx.span_note(
            loan.span,
            fmt!("borrow of `%s` occurs here",
                 self.bccx.loan_path_to_str(loan_path)));
    }

    fn check_move_out_from_expr(&self, ex: @ast::expr) {
525 526 527 528 529 530 531 532 533 534 535 536 537
        match ex.node {
            ast::expr_paren(*) => {
                /* In the case of an expr_paren(), the expression inside
                 * the parens will also be marked as being moved.  Ignore
                 * the parents then so as not to report duplicate errors. */
            }
            _ => {
                let cmt = self.bccx.cat_expr(ex);
                match self.analyze_move_out_from_cmt(cmt) {
                    MoveOk => {}
                    MoveFromIllegalCmt(_) => {
                        self.bccx.span_err(
                            cmt.span,
N
Niko Matsakis 已提交
538
                            fmt!("cannot move out of %s",
539 540
                                 self.bccx.cmt_to_str(cmt)));
                    }
N
Niko Matsakis 已提交
541
                    MoveWhileBorrowed(loan_path, loan_span) => {
542 543
                        self.bccx.span_err(
                            cmt.span,
N
Niko Matsakis 已提交
544 545 546
                            fmt!("cannot move out of `%s` \
                                  because it is borrowed",
                                 self.bccx.loan_path_to_str(loan_path)));
547
                        self.bccx.span_note(
N
Niko Matsakis 已提交
548 549 550
                            loan_span,
                            fmt!("borrow of `%s` occurs here",
                                 self.bccx.loan_path_to_str(loan_path)));
551 552 553 554
                    }
                }
            }
        }
555 556
    }

N
Niko Matsakis 已提交
557 558
    fn analyze_move_out_from_cmt(&self, cmt: mc::cmt) -> MoveError {
        debug!("check_move_out_from_cmt(cmt=%s)", cmt.repr(self.tcx()));
559

560
        match cmt.cat {
N
Niko Matsakis 已提交
561 562
            // Rvalues, locals, and arguments can be moved:
            mc::cat_rvalue | mc::cat_local(_) |
563
            mc::cat_arg(_) | mc::cat_self(_) => {}
N
Niko Matsakis 已提交
564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579

            // It seems strange to allow a move out of a static item,
            // but what happens in practice is that you have a
            // reference to a constant with a type that should be
            // moved, like `None::<~int>`.  The type of this constant
            // is technically `Option<~int>`, which moves, but we know
            // that the content of static items will never actually
            // contain allocated pointers, so we can just memcpy it.
            mc::cat_static_item => {}

            mc::cat_deref(_, _, mc::unsafe_ptr(*)) => {}

            // Nothing else.
            _ => {
                return MoveFromIllegalCmt(cmt);
            }
580 581
        }

N
Niko Matsakis 已提交
582
        // NOTE inadequare if/when we permit `move a.b`
583 584

        // check for a conflicting loan:
N
Niko Matsakis 已提交
585 586 587 588
        for opt_loan_path(cmt).each |&lp| {
            for self.each_in_scope_restriction(cmt.id, lp) |loan, _| {
                // Any restriction prevents moves.
                return MoveWhileBorrowed(loan.loan_path, loan.span);
589
            }
590
        }
591 592

        return MoveOk;
593 594
    }

595
    fn check_call(&mut self,
N
Niko Matsakis 已提交
596 597 598 599 600 601 602 603 604 605 606 607 608
                  _expr: @ast::expr,
                  _callee: Option<@ast::expr>,
                  _callee_id: ast::node_id,
                  _callee_span: span,
                  _args: &[@ast::expr])
    {
        // NB: This call to check for conflicting loans is not truly
        // necessary, because the callee_id never issues new loans.
        // However, I added it for consistency and lest the system
        // should change in the future.
        //
        // FIXME(#5074) nested method calls
        // self.check_for_conflicting_loans(callee_id);
609
    }
610 611
}

N
Niko Matsakis 已提交
612 613 614 615 616 617 618
fn check_loans_in_fn<'a>(fk: &visit::fn_kind,
                         decl: &ast::fn_decl,
                         body: &ast::blk,
                         sp: span,
                         id: ast::node_id,
                         self: @mut CheckLoanCtxt<'a>,
                         visitor: visit::vt<@mut CheckLoanCtxt<'a>>) {
619
    match *fk {
N
Niko Matsakis 已提交
620 621
        visit::fk_item_fn(*) |
        visit::fk_method(*) |
622
        visit::fk_dtor(*) => {
N
Niko Matsakis 已提交
623 624
            // Don't process nested items.
            return;
625 626
        }

N
Niko Matsakis 已提交
627 628 629
        visit::fk_anon(*) |
        visit::fk_fn_block(*) => {
            let fty = ty::node_id_to_type(self.tcx(), id);
630 631 632 633
            let fty_sigil = ty::ty_closure_sigil(fty);
            check_moves_from_captured_variables(self, id, fty_sigil);
        }
    }
634

N
Niko Matsakis 已提交
635
    visit::visit_fn(fk, decl, body, sp, id, self, visitor);
636

637
    fn check_moves_from_captured_variables(self: @mut CheckLoanCtxt,
638
                                           id: ast::node_id,
639
                                           fty_sigil: ast::Sigil) {
640 641
        match fty_sigil {
            ast::ManagedSigil | ast::OwnedSigil => {
642
                let cap_vars = self.bccx.capture_map.get(&id);
643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
                for cap_vars.each |cap_var| {
                    match cap_var.mode {
                        moves::CapRef | moves::CapCopy => { loop; }
                        moves::CapMove => { }
                    }
                    let def_id = ast_util::def_id_of_def(cap_var.def).node;
                    let ty = ty::node_id_to_type(self.tcx(), def_id);
                    let cmt = self.bccx.cat_def(id, cap_var.span,
                                                ty, cap_var.def);
                    let move_err = self.analyze_move_out_from_cmt(cmt);
                    match move_err {
                        MoveOk => {}
                        MoveFromIllegalCmt(move_cmt) => {
                            self.bccx.span_err(
                                cap_var.span,
                                fmt!("illegal by-move capture of %s",
                                     self.bccx.cmt_to_str(move_cmt)));
                        }
N
Niko Matsakis 已提交
661
                        MoveWhileBorrowed(loan_path, loan_span) => {
662 663
                            self.bccx.span_err(
                                cap_var.span,
N
Niko Matsakis 已提交
664 665 666
                                fmt!("cannot move `%s` into closure \
                                      because it is borrowed",
                                     self.bccx.loan_path_to_str(loan_path)));
667
                            self.bccx.span_note(
N
Niko Matsakis 已提交
668 669 670
                                loan_span,
                                fmt!("borrow of `%s` occurs here",
                                     self.bccx.loan_path_to_str(loan_path)));
671 672 673 674 675
                        }
                    }
                }
            }

676
            ast::BorrowedSigil => {}
677 678
        }
    }
679 680
}

N
Niko Matsakis 已提交
681 682 683
fn check_loans_in_local<'a>(local: @ast::local,
                            self: @mut CheckLoanCtxt<'a>,
                            vt: visit::vt<@mut CheckLoanCtxt<'a>>) {
684 685 686
    visit::visit_local(local, self, vt);
}

N
Niko Matsakis 已提交
687 688 689 690 691 692 693
fn check_loans_in_expr<'a>(expr: @ast::expr,
                           self: @mut CheckLoanCtxt<'a>,
                           vt: visit::vt<@mut CheckLoanCtxt<'a>>) {
    debug!("check_loans_in_expr(expr=%s)",
           expr.repr(self.tcx()));

    visit::visit_expr(expr, self, vt);
694

695 696
    self.check_for_conflicting_loans(expr.id);

697
    if self.bccx.moves_map.contains(&expr.id) {
698
        self.check_move_out_from_expr(expr);
699 700
    }

701
    match expr.node {
B
Brian Anderson 已提交
702
      ast::expr_swap(l, r) => {
N
Niko Matsakis 已提交
703 704
        self.check_assignment(l);
        self.check_assignment(r);
705 706
      }
      ast::expr_assign(dest, _) |
B
Brian Anderson 已提交
707
      ast::expr_assign_op(_, dest, _) => {
N
Niko Matsakis 已提交
708
        self.check_assignment(dest);
709
      }
710 711
      ast::expr_call(f, ref args, _) => {
        self.check_call(expr, Some(f), f.id, f.span, *args);
712
      }
713 714
      ast::expr_method_call(_, _, _, ref args, _) => {
        self.check_call(expr, None, expr.callee_id, expr.span, *args);
715
      }
716 717
      ast::expr_index(_, rval) |
      ast::expr_binary(_, _, rval)
718
      if self.bccx.method_map.contains_key(&expr.id) => {
719
        self.check_call(expr,
B
Brian Anderson 已提交
720
                        None,
T
Tim Chevalier 已提交
721
                        expr.callee_id,
722
                        expr.span,
723
                        ~[rval]);
724
      }
725
      ast::expr_unary(*) | ast::expr_index(*)
726
      if self.bccx.method_map.contains_key(&expr.id) => {
727
        self.check_call(expr,
B
Brian Anderson 已提交
728
                        None,
T
Tim Chevalier 已提交
729
                        expr.callee_id,
730
                        expr.span,
731
                        ~[]);
732
      }
B
Brian Anderson 已提交
733
      _ => { }
734 735 736
    }
}

N
Niko Matsakis 已提交
737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755
fn check_loans_in_pat<'a>(pat: @ast::pat,
                          self: @mut CheckLoanCtxt<'a>,
                          vt: visit::vt<@mut CheckLoanCtxt<'a>>)
{
    self.check_for_conflicting_loans(pat.id);

    // Note: moves out of pattern bindings are not checked by
    // the borrow checker, at least not directly.  What happens
    // is that if there are any moved bindings, the discriminant
    // will be considered a move, and this will be checked as
    // normal.  Then, in `middle::check_match`, we will check
    // that no move occurs in a binding that is underneath an
    // `@` or `&`.  Together these give the same guarantees as
    // `check_move_out_from_expr()` without requiring us to
    // rewalk the patterns and rebuild the pattern
    // categorizations.

    visit::visit_pat(pat, self, vt);
}
756

N
Niko Matsakis 已提交
757 758 759 760 761 762
fn check_loans_in_block<'a>(blk: &ast::blk,
                            self: @mut CheckLoanCtxt<'a>,
                            vt: visit::vt<@mut CheckLoanCtxt<'a>>)
{
    visit::visit_block(blk, self, vt);
    self.check_for_conflicting_loans(blk.node.id);
763 764
}