check_loans.rs 31.1 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

21
use std::hashmap::HashSet;
N
Niko Matsakis 已提交
22
use mc = middle::mem_categorization;
23 24
use middle::borrowck::*;
use middle::moves;
25
use middle::ty;
26
use syntax::ast::m_mutbl;
27 28
use syntax::ast;
use syntax::ast_util;
N
Niko Matsakis 已提交
29
use syntax::codemap::span;
30 31
use syntax::visit;
use syntax::visit::Visitor;
32
use util::ppaux::Repr;
33

34
#[deriving(Clone)]
N
Niko Matsakis 已提交
35
struct CheckLoanCtxt<'self> {
36
    bccx: @BorrowckCtxt,
37
    dfcx_loans: &'self LoanDataFlow,
38
    move_data: @move_data::FlowedMoveData,
N
Niko Matsakis 已提交
39
    all_loans: &'self [Loan],
40
    reported: @mut HashSet<ast::NodeId>,
41 42
}

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
struct CheckLoanVisitor;

impl<'self> Visitor<CheckLoanCtxt<'self>> for CheckLoanVisitor {
    fn visit_expr<'a>(&mut self, ex:@ast::expr, e:CheckLoanCtxt<'a>) {
        check_loans_in_expr(self, ex, e);
    }
    fn visit_local(&mut self, l:@ast::Local, e:CheckLoanCtxt) {
        check_loans_in_local(self, l, e);
    }
    fn visit_block(&mut self, b:&ast::Block, e:CheckLoanCtxt) {
        check_loans_in_block(self, b, e);
    }
    fn visit_pat(&mut self, p:@ast::pat, e:CheckLoanCtxt) {
        check_loans_in_pat(self, p, e);
    }
    fn visit_fn(&mut self, fk:&visit::fn_kind, fd:&ast::fn_decl,
                b:&ast::Block, s:span, n:ast::NodeId, e:CheckLoanCtxt) {
        check_loans_in_fn(self, fk, fd, b, s, n, e);
    }
}

64
pub fn check_loans(bccx: @BorrowckCtxt,
65 66
                   dfcx_loans: &LoanDataFlow,
                   move_data: move_data::FlowedMoveData,
N
Niko Matsakis 已提交
67
                   all_loans: &[Loan],
68
                   body: &ast::Block) {
69
    debug!("check_loans(body id=%?)", body.id);
N
Niko Matsakis 已提交
70

71
    let clcx = CheckLoanCtxt {
72
        bccx: bccx,
73
        dfcx_loans: dfcx_loans,
74
        move_data: @move_data,
N
Niko Matsakis 已提交
75 76
        all_loans: all_loans,
        reported: @mut HashSet::new(),
77
    };
N
Niko Matsakis 已提交
78

79 80
    let mut vt = CheckLoanVisitor;
    vt.visit_block(body, clcx);
81 82
}

N
Niko Matsakis 已提交
83 84
enum MoveError {
    MoveOk,
85
    MoveWhileBorrowed(/*loan*/@LoanPath, /*loan*/span)
86 87
}

88 89
impl<'self> CheckLoanCtxt<'self> {
    pub fn tcx(&self) -> ty::ctxt { self.bccx.tcx }
90

91
    pub fn each_issued_loan(&self,
92
                            scope_id: ast::NodeId,
93 94
                            op: &fn(&Loan) -> bool)
                            -> bool {
95
        //! Iterates over each loan that has been issued
96 97 98 99
        //! 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.
100

101
        do self.dfcx_loans.each_bit_on_entry_frozen(scope_id) |loan_index| {
102
            let loan = &self.all_loans[loan_index];
103
            op(loan)
104 105 106
        }
    }

107
    pub fn each_in_scope_loan(&self,
108
                              scope_id: ast::NodeId,
109 110
                              op: &fn(&Loan) -> bool)
                              -> bool {
111 112
        //! Like `each_issued_loan()`, but only considers loans that are
        //! currently in scope.
113

114
        let region_maps = self.tcx().region_maps;
115
        do self.each_issued_loan(scope_id) |loan| {
116
            if region_maps.is_subscope_of(scope_id, loan.kill_scope) {
117 118 119
                op(loan)
            } else {
                true
120 121 122 123
            }
        }
    }

124
    pub fn each_in_scope_restriction(&self,
125
                                     scope_id: ast::NodeId,
126 127 128
                                     loan_path: @LoanPath,
                                     op: &fn(&Loan, &Restriction) -> bool)
                                     -> bool {
129 130 131
        //! Iterates through all the in-scope restrictions for the
        //! given `loan_path`

132 133
        do self.each_in_scope_loan(scope_id) |loan| {
            let mut ret = true;
D
Daniel Micay 已提交
134
            for restr in loan.restrictions.iter() {
135 136
                if restr.loan_path == loan_path {
                    if !op(loan, restr) {
137 138
                        ret = false;
                        break;
139 140 141
                    }
                }
            }
142
            ret
143 144
        }
    }
145

146
    pub fn loans_generated_by(&self, scope_id: ast::NodeId) -> ~[uint] {
N
Niko Matsakis 已提交
147 148
        //! Returns a vector of the loans that are generated as
        //! we encounter `scope_id`.
149

N
Niko Matsakis 已提交
150
        let mut result = ~[];
151
        do self.dfcx_loans.each_gen_bit_frozen(scope_id) |loan_index| {
N
Niko Matsakis 已提交
152
            result.push(loan_index);
153 154
            true
        };
N
Niko Matsakis 已提交
155
        return result;
156 157
    }

158
    pub fn check_for_conflicting_loans(&self, scope_id: ast::NodeId) {
N
Niko Matsakis 已提交
159 160 161 162
        //! 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).
163

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

N
Niko Matsakis 已提交
166 167
        let new_loan_indices = self.loans_generated_by(scope_id);
        debug!("new_loan_indices = %?", new_loan_indices);
168

169
        do self.each_issued_loan(scope_id) |issued_loan| {
D
Daniel Micay 已提交
170
            for &new_loan_index in new_loan_indices.iter() {
N
Niko Matsakis 已提交
171 172
                let new_loan = &self.all_loans[new_loan_index];
                self.report_error_if_loans_conflict(issued_loan, new_loan);
173
            }
174 175
            true
        };
176

177 178 179 180
        for (i, &x) in new_loan_indices.iter().enumerate() {
            let old_loan = &self.all_loans[x];
            for &y in new_loan_indices.slice_from(i+1).iter() {
                let new_loan = &self.all_loans[y];
N
Niko Matsakis 已提交
181
                self.report_error_if_loans_conflict(old_loan, new_loan);
182 183 184 185
            }
        }
    }

186 187 188
    pub fn report_error_if_loans_conflict(&self,
                                          old_loan: &Loan,
                                          new_loan: &Loan) {
N
Niko Matsakis 已提交
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
        //! 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);
    }
206

207 208 209 210 211 212
    pub fn report_error_if_loan_conflicts_with_restriction(&self,
                                                           loan1: &Loan,
                                                           loan2: &Loan,
                                                           old_loan: &Loan,
                                                           new_loan: &Loan)
                                                           -> bool {
N
Niko Matsakis 已提交
213 214 215 216 217 218 219 220
        //! 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()));

221
        // Restrictions that would cause the new loan to be illegal:
N
Niko Matsakis 已提交
222
        let illegal_if = match loan2.mutbl {
223 224 225
            MutableMutability   => RESTR_ALIAS | RESTR_FREEZE | RESTR_CLAIM,
            ImmutableMutability => RESTR_ALIAS | RESTR_FREEZE,
            ConstMutability     => RESTR_ALIAS,
N
Niko Matsakis 已提交
226 227 228
        };
        debug!("illegal_if=%?", illegal_if);

D
Daniel Micay 已提交
229
        for restr in loan1.restrictions.iter() {
N
Niko Matsakis 已提交
230 231
            if !restr.set.intersects(illegal_if) { loop; }
            if restr.loan_path != loan2.loan_path { loop; }
232

N
Niko Matsakis 已提交
233
            match (new_loan.mutbl, old_loan.mutbl) {
234
                (MutableMutability, MutableMutability) => {
N
Niko Matsakis 已提交
235 236 237
                    self.bccx.span_err(
                        new_loan.span,
                        fmt!("cannot borrow `%s` as mutable \
238
                              more than once at a time",
N
Niko Matsakis 已提交
239 240 241 242 243 244 245 246 247 248 249 250
                             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 \
251
                              it is also borrowed as %s",
N
Niko Matsakis 已提交
252 253 254 255 256 257 258 259 260
                             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;
                }
261 262
            }
        }
N
Niko Matsakis 已提交
263 264

        true
265 266
    }

267
    pub fn is_local_variable(&self, cmt: mc::cmt) -> bool {
268
        match cmt.cat {
N
Niko Matsakis 已提交
269
          mc::cat_local(_) => true,
B
Brian Anderson 已提交
270
          _ => false
271 272 273
        }
    }

274
    pub fn check_if_path_is_moved(&self,
275
                                  id: ast::NodeId,
276 277 278
                                  span: span,
                                  use_kind: MovedValueUseKind,
                                  lp: @LoanPath) {
279 280 281 282 283 284 285
        /*!
         * Reports an error if `expr` (which should be a path)
         * is using a moved/uninitialized value
         */

        debug!("check_if_path_is_moved(id=%?, use_kind=%?, lp=%s)",
               id, use_kind, lp.repr(self.bccx.tcx));
286
        do self.move_data.each_move_of(id, lp) |move, moved_lp| {
287 288 289 290 291 292
            self.bccx.report_use_of_moved_value(
                span,
                use_kind,
                lp,
                move,
                moved_lp);
293 294
            false
        };
295 296
    }

297
    pub fn check_assignment(&self, expr: @ast::expr) {
298 299
        // 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 已提交
300 301 302
        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)
303
        };
304

N
Niko Matsakis 已提交
305 306
        debug!("check_assignment(cmt=%s)", cmt.repr(self.tcx()));

307 308 309 310 311 312 313 314 315
        // Mutable values can be assigned, as long as they obey loans
        // and aliasing restrictions:
        if cmt.mutbl.is_mutable() {
            if check_for_aliasable_mutable_writes(self, expr, cmt) {
                if check_for_assignment_to_restricted_or_frozen_location(
                    self, expr, cmt)
                {
                    // Safe, but record for lint pass later:
                    mark_variable_as_used_mut(self, cmt);
316
                }
N
Niko Matsakis 已提交
317
            }
318 319 320 321 322 323 324
            return;
        }

        // For immutable local variables, assignments are legal
        // if they cannot already have been assigned
        if self.is_local_variable(cmt) {
            assert!(cmt.mutbl.is_immutable()); // no "const" locals
325
            let lp = opt_loan_path(cmt).unwrap();
326
            do self.move_data.each_assignment_of(expr.id, lp) |assign| {
327 328 329 330
                self.bccx.report_reassigned_immutable_variable(
                    expr.span,
                    lp,
                    assign);
331 332
                false
            };
333
            return;
334 335
        }

336 337 338
        // Otherwise, just a plain error.
        self.bccx.span_err(
            expr.span,
339
            fmt!("cannot assign to %s %s",
340 341 342
                 cmt.mutbl.to_user_str(),
                 self.bccx.cmt_to_str(cmt)));
        return;
343

344
        fn mark_variable_as_used_mut(this: &CheckLoanCtxt,
345
                                     cmt: mc::cmt) {
N
Niko Matsakis 已提交
346
            //! If the mutability of the `cmt` being written is inherited
347
            //! from a local variable, liveness will
N
Niko Matsakis 已提交
348 349
            //! not have been able to detect that this variable's mutability
            //! is important, so we must add the variable to the
350
            //! `used_mut_nodes` table here.
N
Niko Matsakis 已提交
351 352 353 354

            let mut cmt = cmt;
            loop {
                debug!("mark_writes_through_upvars_as_used_mut(cmt=%s)",
355
                       cmt.repr(this.tcx()));
N
Niko Matsakis 已提交
356 357
                match cmt.cat {
                    mc::cat_local(id) |
358
                    mc::cat_arg(id) |
N
Niko Matsakis 已提交
359
                    mc::cat_self(id) => {
360
                        this.tcx().used_mut_nodes.insert(id);
N
Niko Matsakis 已提交
361 362
                        return;
                    }
363

N
Niko Matsakis 已提交
364 365 366
                    mc::cat_stack_upvar(b) => {
                        cmt = b;
                    }
367

368
                    mc::cat_rvalue(*) |
N
Niko Matsakis 已提交
369 370 371 372 373 374 375 376
                    mc::cat_static_item |
                    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;
                    }
377

N
Niko Matsakis 已提交
378
                    mc::cat_discr(b, _) |
379
                    mc::cat_deref(b, _, mc::uniq_ptr) => {
N
Niko Matsakis 已提交
380 381 382 383
                        assert_eq!(cmt.mutbl, mc::McInherited);
                        cmt = b;
                    }

384
                    mc::cat_downcast(b) |
N
Niko Matsakis 已提交
385 386 387 388 389 390
                    mc::cat_interior(b, _) => {
                        if cmt.mutbl == mc::McInherited {
                            cmt = b;
                        } else {
                            return; // field declared as mutable or some such
                        }
391 392 393 394
                    }
                }
            }
        }
395

396
        fn check_for_aliasable_mutable_writes(this: &CheckLoanCtxt,
N
Niko Matsakis 已提交
397 398 399 400 401
                                              expr: @ast::expr,
                                              cmt: mc::cmt) -> bool {
            //! Safety checks related to writes to aliasable, mutable locations

            let guarantor = cmt.guarantor();
402
            debug!("check_for_aliasable_mutable_writes(cmt=%s, guarantor=%s)",
403
                   cmt.repr(this.tcx()), guarantor.repr(this.tcx()));
N
Niko Matsakis 已提交
404 405 406 407
            match guarantor.cat {
                mc::cat_deref(b, _, mc::region_ptr(m_mutbl, _)) => {
                    // Statically prohibit writes to `&mut` when aliasable

408
                    check_for_aliasability_violation(this, expr, b);
409
                }
N
Niko Matsakis 已提交
410

411
                mc::cat_deref(_, deref_count, mc::gc_ptr(ast::m_mutbl)) => {
N
Niko Matsakis 已提交
412 413 414
                    // Dynamically check writes to `@mut`

                    let key = root_map_key {
415
                        id: guarantor.id,
N
Niko Matsakis 已提交
416 417
                        derefs: deref_count
                    };
418
                    debug!("Inserting write guard at %?", key);
419
                    this.bccx.write_guard_map.insert(key);
N
Niko Matsakis 已提交
420 421 422
                }

                _ => {}
423 424
            }

N
Niko Matsakis 已提交
425
            return true; // no errors reported
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
        fn check_for_aliasability_violation(this: &CheckLoanCtxt,
                                            expr: @ast::expr,
                                            cmt: mc::cmt) -> bool {
            let mut cmt = cmt;

            loop {
                match cmt.cat {
                    mc::cat_deref(b, _, mc::region_ptr(m_mutbl, _)) |
                    mc::cat_downcast(b) |
                    mc::cat_stack_upvar(b) |
                    mc::cat_deref(b, _, mc::uniq_ptr) |
                    mc::cat_interior(b, _) |
                    mc::cat_discr(b, _) => {
                        // Aliasability depends on base cmt
                        cmt = b;
                    }

                    mc::cat_copied_upvar(_) |
                    mc::cat_rvalue(*) |
                    mc::cat_local(*) |
                    mc::cat_arg(_) |
                    mc::cat_self(*) |
                    mc::cat_deref(_, _, mc::unsafe_ptr(*)) |
                    mc::cat_static_item(*) |
                    mc::cat_deref(_, _, mc::gc_ptr(_)) |
                    mc::cat_deref(_, _, mc::region_ptr(m_const, _)) |
                    mc::cat_deref(_, _, mc::region_ptr(m_imm, _)) => {
                        // Aliasability is independent of base cmt
                        match cmt.freely_aliasable() {
                            None => {
                                return true;
                            }
                            Some(cause) => {
                                this.bccx.report_aliasability_violation(
                                    expr.span,
                                    MutabilityViolation,
                                    cause);
                                return false;
                            }
                        }
                    }
                }
            }
        }

N
Niko Matsakis 已提交
473
        fn check_for_assignment_to_restricted_or_frozen_location(
474
            this: &CheckLoanCtxt,
N
Niko Matsakis 已提交
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 506 507 508 509 510 511 512 513 514 515
            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.
516
            let cont = do this.each_in_scope_restriction(expr.id, loan_path)
N
Niko Matsakis 已提交
517 518 519
                |loan, restr|
            {
                if restr.set.intersects(RESTR_MUTATE) {
520
                    this.report_illegal_mutation(expr, loan_path, loan);
521 522 523
                    false
                } else {
                    true
N
Niko Matsakis 已提交
524
                }
525 526 527
            };

            if !cont { return false }
N
Niko Matsakis 已提交
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549

            // 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).
            //
550 551 552 553 554 555
            // Mutability of a path can be dependent on the super path
            // in two ways. First, it might be inherited mutability.
            // Second, the pointee of an `&mut` pointer can only be
            // mutated if it is found in an unaliased location, so we
            // have to check that the owner location is not borrowed.
            //
N
Niko Matsakis 已提交
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
            // 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 {
573 574 575 576 577 578
                    // Peel back one layer if, for `loan_path` to be
                    // mutable, `lp_base` must be mutable. This occurs
                    // with inherited mutability and with `&mut`
                    // pointers.
                    LpExtend(lp_base, mc::McInherited, _) |
                    LpExtend(lp_base, _, LpDeref(mc::region_ptr(ast::m_mutbl, _))) => {
N
Niko Matsakis 已提交
579 580 581 582 583 584 585 586 587 588 589 590
                        loan_path = lp_base;
                    }

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

                // Check for a non-const loan of `loan_path`
591
                let cont = do this.each_in_scope_loan(expr.id) |loan| {
592 593 594 595 596
                    if loan.loan_path == loan_path &&
                            loan.mutbl != ConstMutability {
                        this.report_illegal_mutation(expr,
                                                     full_loan_path,
                                                     loan);
597 598 599
                        false
                    } else {
                        true
N
Niko Matsakis 已提交
600
                    }
601 602 603
                };

                if !cont { return false }
604 605 606 607
            }
        }
    }

608 609 610 611
    pub fn report_illegal_mutation(&self,
                                   expr: @ast::expr,
                                   loan_path: &LoanPath,
                                   loan: &Loan) {
N
Niko Matsakis 已提交
612 613 614 615 616 617 618 619 620 621
        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)));
    }

622 623 624 625 626 627
    fn check_move_out_from_expr(&self, expr: @ast::expr) {
        match expr.node {
            ast::expr_fn_block(*) => {
                // moves due to capture clauses are checked
                // in `check_loans_in_fn`, so that we can
                // give a better error message
628 629
            }
            _ => {
630 631 632 633 634
                self.check_move_out_from_id(expr.id, expr.span)
            }
        }
    }

635
    fn check_move_out_from_id(&self, id: ast::NodeId, span: span) {
636
        do self.move_data.each_path_moved_by(id) |_, move_path| {
637 638 639 640 641 642 643 644 645 646 647 648
            match self.analyze_move_out_from(id, move_path) {
                MoveOk => {}
                MoveWhileBorrowed(loan_path, loan_span) => {
                    self.bccx.span_err(
                        span,
                        fmt!("cannot move out of `%s` \
                              because it is borrowed",
                             self.bccx.loan_path_to_str(move_path)));
                    self.bccx.span_note(
                        loan_span,
                        fmt!("borrow of `%s` occurs here",
                             self.bccx.loan_path_to_str(loan_path)));
649 650
                }
            }
651 652
            true
        };
653 654
    }

655
    pub fn analyze_move_out_from(&self,
656
                                 expr_id: ast::NodeId,
657 658 659
                                 move_path: @LoanPath) -> MoveError {
        debug!("analyze_move_out_from(expr_id=%?, move_path=%s)",
               expr_id, move_path.repr(self.tcx()));
660

N
Niko Matsakis 已提交
661
        // FIXME(#4384) inadequare if/when we permit `move a.b`
662

663 664
        let mut ret = MoveOk;

665
        // check for a conflicting loan:
666
        do self.each_in_scope_restriction(expr_id, move_path) |loan, _| {
667
            // Any restriction prevents moves.
668 669 670
            ret = MoveWhileBorrowed(loan.loan_path, loan.span);
            false
        };
671

672
        ret
673 674
    }

675
    pub fn check_call(&self,
676 677
                      _expr: @ast::expr,
                      _callee: Option<@ast::expr>,
678
                      _callee_id: ast::NodeId,
679 680
                      _callee_span: span,
                      _args: &[@ast::expr]) {
N
Niko Matsakis 已提交
681 682 683 684 685
        // 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.
        //
N
Niko Matsakis 已提交
686
        // FIXME(#6268) nested method calls
N
Niko Matsakis 已提交
687
        // self.check_for_conflicting_loans(callee_id);
688
    }
689 690
}

691 692
fn check_loans_in_fn<'a>(visitor: &mut CheckLoanVisitor,
                         fk: &visit::fn_kind,
N
Niko Matsakis 已提交
693
                         decl: &ast::fn_decl,
694
                         body: &ast::Block,
N
Niko Matsakis 已提交
695
                         sp: span,
696
                         id: ast::NodeId,
697
                         this: CheckLoanCtxt<'a>) {
698
    match *fk {
699 700
        visit::fk_item_fn(*) |
        visit::fk_method(*) => {
N
Niko Matsakis 已提交
701 702
            // Don't process nested items.
            return;
703 704
        }

705 706
        visit::fk_anon(*) |
        visit::fk_fn_block(*) => {
707
            check_captured_variables(this, id, sp);
708 709
        }
    }
710

711
    visit::walk_fn(visitor, fk, decl, body, sp, id, this);
712

713
    fn check_captured_variables(this: CheckLoanCtxt,
714
                                closure_id: ast::NodeId,
715 716
                                span: span) {
        let cap_vars = this.bccx.capture_map.get(&closure_id);
D
Daniel Micay 已提交
717
        for cap_var in cap_vars.iter() {
718 719 720 721
            let var_id = ast_util::def_id_of_def(cap_var.def).node;
            let var_path = @LpVar(var_id);
            this.check_if_path_is_moved(closure_id, span,
                                        MovedInCapture, var_path);
722
            match cap_var.mode {
723
                moves::CapRef | moves::CapCopy => {}
724
                moves::CapMove => {
725
                    check_by_move_capture(this, closure_id, cap_var, var_path);
726 727 728 729 730
                }
            }
        }
        return;

731
        fn check_by_move_capture(this: CheckLoanCtxt,
732
                                 closure_id: ast::NodeId,
733 734
                                 cap_var: &moves::CaptureVar,
                                 move_path: @LoanPath) {
735
            let move_err = this.analyze_move_out_from(closure_id, move_path);
736 737
            match move_err {
                MoveOk => {}
738
                MoveWhileBorrowed(loan_path, loan_span) => {
739 740 741 742
                    this.bccx.span_err(
                        cap_var.span,
                        fmt!("cannot move `%s` into closure \
                              because it is borrowed",
743
                             this.bccx.loan_path_to_str(move_path)));
744 745 746 747
                    this.bccx.span_note(
                        loan_span,
                        fmt!("borrow of `%s` occurs here",
                             this.bccx.loan_path_to_str(loan_path)));
748 749 750 751
                }
            }
        }
    }
752 753
}

754 755 756 757
fn check_loans_in_local<'a>(vt: &mut CheckLoanVisitor,
                            local: @ast::Local,
                            this: CheckLoanCtxt<'a>) {
    visit::walk_local(vt, local, this);
758 759
}

760 761 762 763
fn check_loans_in_expr<'a>(vt: &mut CheckLoanVisitor,
                           expr: @ast::expr,
                           this: CheckLoanCtxt<'a>) {
    visit::walk_expr(vt, expr, this);
764

N
Niko Matsakis 已提交
765
    debug!("check_loans_in_expr(expr=%s)",
766
           expr.repr(this.tcx()));
N
Niko Matsakis 已提交
767

768
    this.check_for_conflicting_loans(expr.id);
769
    this.check_move_out_from_expr(expr);
770

771
    match expr.node {
772 773 774 775 776
      ast::expr_self |
      ast::expr_path(*) => {
          if !this.move_data.is_assignee(expr.id) {
              let cmt = this.bccx.cat_expr_unadjusted(expr);
              debug!("path cmt=%s", cmt.repr(this.tcx()));
777
              let r = opt_loan_path(cmt);
D
Daniel Micay 已提交
778
              for &lp in r.iter() {
779
                  this.check_if_path_is_moved(expr.id, expr.span, MovedInUse, lp);
780 781 782
              }
          }
      }
783
      ast::expr_assign(dest, _) |
784
      ast::expr_assign_op(_, _, dest, _) => {
785
        this.check_assignment(dest);
786
      }
787
      ast::expr_call(f, ref args, _) => {
788
        this.check_call(expr, Some(f), f.id, f.span, *args);
789
      }
790 791
      ast::expr_method_call(callee_id, _, _, _, ref args, _) => {
        this.check_call(expr, None, callee_id, expr.span, *args);
792
      }
793 794
      ast::expr_index(callee_id, _, rval) |
      ast::expr_binary(callee_id, _, _, rval)
795 796
      if this.bccx.method_map.contains_key(&expr.id) => {
        this.check_call(expr,
B
Brian Anderson 已提交
797
                        None,
798
                        callee_id,
799
                        expr.span,
800
                        [rval]);
801
      }
802
      ast::expr_unary(callee_id, _, _) | ast::expr_index(callee_id, _, _)
803 804
      if this.bccx.method_map.contains_key(&expr.id) => {
        this.check_call(expr,
B
Brian Anderson 已提交
805
                        None,
806
                        callee_id,
807
                        expr.span,
808
                        []);
809
      }
B
Brian Anderson 已提交
810
      _ => { }
811 812 813
    }
}

814 815 816
fn check_loans_in_pat<'a>(vt: &mut CheckLoanVisitor,
                          pat: @ast::pat,
                          this: CheckLoanCtxt<'a>)
N
Niko Matsakis 已提交
817
{
818
    this.check_for_conflicting_loans(pat.id);
819
    this.check_move_out_from_id(pat.id, pat.span);
820
    visit::walk_pat(vt, pat, this);
N
Niko Matsakis 已提交
821
}
822

823 824 825
fn check_loans_in_block<'a>(vt: &mut CheckLoanVisitor,
                            blk: &ast::Block,
                            this: CheckLoanCtxt<'a>)
N
Niko Matsakis 已提交
826
{
827
    visit::walk_block(vt, blk, this);
828
    this.check_for_conflicting_loans(blk.id);
829
}