]> git.lizzy.rs Git - rust.git/blob - src/librustc/middle/borrowck/check_loans.rs
auto merge of #8797 : nikomatsakis/rust/issue-8625-assign-to-andmut-in-borrowed-loc...
[rust.git] / src / librustc / middle / borrowck / check_loans.rs
1 // Copyright 2012 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 // ----------------------------------------------------------------------
12 // Checking loans
13 //
14 // Phase 2 of check: we walk down the tree and check that:
15 // 1. assignments are always made to mutable locations;
16 // 2. loans made in overlapping scopes do not conflict
17 // 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;
22 use mc = middle::mem_categorization;
23 use middle::borrowck::*;
24 use middle::moves;
25 use middle::ty;
26 use syntax::ast::{m_mutbl, m_imm, m_const};
27 use syntax::ast;
28 use syntax::ast_util;
29 use syntax::codemap::span;
30 use syntax::visit;
31 use syntax::visit::Visitor;
32 use util::ppaux::Repr;
33
34 #[deriving(Clone)]
35 struct CheckLoanCtxt<'self> {
36     bccx: @BorrowckCtxt,
37     dfcx_loans: &'self LoanDataFlow,
38     move_data: @move_data::FlowedMoveData,
39     all_loans: &'self [Loan],
40     reported: @mut HashSet<ast::NodeId>,
41 }
42
43 struct CheckLoanVisitor;
44
45 impl<'self> Visitor<CheckLoanCtxt<'self>> for CheckLoanVisitor {
46     fn visit_expr<'a>(&mut self, ex:@ast::expr, e:CheckLoanCtxt<'a>) {
47         check_loans_in_expr(self, ex, e);
48     }
49     fn visit_local(&mut self, l:@ast::Local, e:CheckLoanCtxt) {
50         check_loans_in_local(self, l, e);
51     }
52     fn visit_block(&mut self, b:&ast::Block, e:CheckLoanCtxt) {
53         check_loans_in_block(self, b, e);
54     }
55     fn visit_pat(&mut self, p:@ast::pat, e:CheckLoanCtxt) {
56         check_loans_in_pat(self, p, e);
57     }
58     fn visit_fn(&mut self, fk:&visit::fn_kind, fd:&ast::fn_decl,
59                 b:&ast::Block, s:span, n:ast::NodeId, e:CheckLoanCtxt) {
60         check_loans_in_fn(self, fk, fd, b, s, n, e);
61     }
62 }
63
64 pub fn check_loans(bccx: @BorrowckCtxt,
65                    dfcx_loans: &LoanDataFlow,
66                    move_data: move_data::FlowedMoveData,
67                    all_loans: &[Loan],
68                    body: &ast::Block) {
69     debug!("check_loans(body id=%?)", body.id);
70
71     let clcx = CheckLoanCtxt {
72         bccx: bccx,
73         dfcx_loans: dfcx_loans,
74         move_data: @move_data,
75         all_loans: all_loans,
76         reported: @mut HashSet::new(),
77     };
78
79     let mut vt = CheckLoanVisitor;
80     vt.visit_block(body, clcx);
81 }
82
83 enum MoveError {
84     MoveOk,
85     MoveWhileBorrowed(/*loan*/@LoanPath, /*loan*/span)
86 }
87
88 impl<'self> CheckLoanCtxt<'self> {
89     pub fn tcx(&self) -> ty::ctxt { self.bccx.tcx }
90
91     pub fn each_issued_loan(&self,
92                             scope_id: ast::NodeId,
93                             op: &fn(&Loan) -> bool)
94                             -> bool {
95         //! Iterates over each loan that has been issued
96         //! on entrance to `scope_id`, regardless of whether it is
97         //! actually *in scope* at that point.  Sometimes loans
98         //! are issued for future scopes and thus they may have been
99         //! *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                               op: &fn(&Loan) -> bool)
110                               -> bool {
111         //! Like `each_issued_loan()`, but only considers loans that are
112         //! 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                 op(loan)
118             } else {
119                 true
120             }
121         }
122     }
123
124     pub fn each_in_scope_restriction(&self,
125                                      scope_id: ast::NodeId,
126                                      loan_path: @LoanPath,
127                                      op: &fn(&Loan, &Restriction) -> bool)
128                                      -> bool {
129         //! Iterates through all the in-scope restrictions for the
130         //! given `loan_path`
131
132         do self.each_in_scope_loan(scope_id) |loan| {
133             let mut ret = true;
134             for restr in loan.restrictions.iter() {
135                 if restr.loan_path == loan_path {
136                     if !op(loan, restr) {
137                         ret = false;
138                         break;
139                     }
140                 }
141             }
142             ret
143         }
144     }
145
146     pub fn loans_generated_by(&self, scope_id: ast::NodeId) -> ~[uint] {
147         //! Returns a vector of the loans that are generated as
148         //! we encounter `scope_id`.
149
150         let mut result = ~[];
151         do self.dfcx_loans.each_gen_bit_frozen(scope_id) |loan_index| {
152             result.push(loan_index);
153             true
154         };
155         return result;
156     }
157
158     pub fn check_for_conflicting_loans(&self, scope_id: ast::NodeId) {
159         //! Checks to see whether any of the loans that are issued
160         //! by `scope_id` conflict with loans that have already been
161         //! issued when we enter `scope_id` (for example, we do not
162         //! permit two `&mut` borrows of the same variable).
163
164         debug!("check_for_conflicting_loans(scope_id=%?)", scope_id);
165
166         let new_loan_indices = self.loans_generated_by(scope_id);
167         debug!("new_loan_indices = %?", new_loan_indices);
168
169         do self.each_issued_loan(scope_id) |issued_loan| {
170             for &new_loan_index in new_loan_indices.iter() {
171                 let new_loan = &self.all_loans[new_loan_index];
172                 self.report_error_if_loans_conflict(issued_loan, new_loan);
173             }
174             true
175         };
176
177         for (i, &x) in new_loan_indices.iter().enumerate() {
178             let old_loan = &self.all_loans[x];
179             for &y in new_loan_indices.slice_from(i+1).iter() {
180                 let new_loan = &self.all_loans[y];
181                 self.report_error_if_loans_conflict(old_loan, new_loan);
182             }
183         }
184     }
185
186     pub fn report_error_if_loans_conflict(&self,
187                                           old_loan: &Loan,
188                                           new_loan: &Loan) {
189         //! Checks whether `old_loan` and `new_loan` can safely be issued
190         //! simultaneously.
191
192         debug!("report_error_if_loans_conflict(old_loan=%s, new_loan=%s)",
193                old_loan.repr(self.tcx()),
194                new_loan.repr(self.tcx()));
195
196         // Should only be called for loans that are in scope at the same time.
197         let region_maps = self.tcx().region_maps;
198         assert!(region_maps.scopes_intersect(old_loan.kill_scope,
199                                              new_loan.kill_scope));
200
201         self.report_error_if_loan_conflicts_with_restriction(
202             old_loan, new_loan, old_loan, new_loan) &&
203         self.report_error_if_loan_conflicts_with_restriction(
204             new_loan, old_loan, old_loan, new_loan);
205     }
206
207     pub fn report_error_if_loan_conflicts_with_restriction(&self,
208                                                            loan1: &Loan,
209                                                            loan2: &Loan,
210                                                            old_loan: &Loan,
211                                                            new_loan: &Loan)
212                                                            -> bool {
213         //! Checks whether the restrictions introduced by `loan1` would
214         //! prohibit `loan2`. Returns false if an error is reported.
215
216         debug!("report_error_if_loan_conflicts_with_restriction(\
217                 loan1=%s, loan2=%s)",
218                loan1.repr(self.tcx()),
219                loan2.repr(self.tcx()));
220
221         // Restrictions that would cause the new loan to be illegal:
222         let illegal_if = match loan2.mutbl {
223             m_mutbl => RESTR_ALIAS | RESTR_FREEZE | RESTR_CLAIM,
224             m_imm =>   RESTR_ALIAS | RESTR_FREEZE,
225             m_const => RESTR_ALIAS,
226         };
227         debug!("illegal_if=%?", illegal_if);
228
229         for restr in loan1.restrictions.iter() {
230             if !restr.set.intersects(illegal_if) { loop; }
231             if restr.loan_path != loan2.loan_path { loop; }
232
233             match (new_loan.mutbl, old_loan.mutbl) {
234                 (m_mutbl, m_mutbl) => {
235                     self.bccx.span_err(
236                         new_loan.span,
237                         fmt!("cannot borrow `%s` as mutable \
238                               more than once at a time",
239                              self.bccx.loan_path_to_str(new_loan.loan_path)));
240                     self.bccx.span_note(
241                         old_loan.span,
242                         fmt!("second borrow of `%s` as mutable occurs here",
243                              self.bccx.loan_path_to_str(new_loan.loan_path)));
244                     return false;
245                 }
246
247                 _ => {
248                     self.bccx.span_err(
249                         new_loan.span,
250                         fmt!("cannot borrow `%s` as %s because \
251                               it is also borrowed as %s",
252                              self.bccx.loan_path_to_str(new_loan.loan_path),
253                              self.bccx.mut_to_str(new_loan.mutbl),
254                              self.bccx.mut_to_str(old_loan.mutbl)));
255                     self.bccx.span_note(
256                         old_loan.span,
257                         fmt!("second borrow of `%s` occurs here",
258                              self.bccx.loan_path_to_str(new_loan.loan_path)));
259                     return false;
260                 }
261             }
262         }
263
264         true
265     }
266
267     pub fn is_local_variable(&self, cmt: mc::cmt) -> bool {
268         match cmt.cat {
269           mc::cat_local(_) => true,
270           _ => false
271         }
272     }
273
274     pub fn check_if_path_is_moved(&self,
275                                   id: ast::NodeId,
276                                   span: span,
277                                   use_kind: MovedValueUseKind,
278                                   lp: @LoanPath) {
279         /*!
280          * Reports an error if `expr` (which should be a path)
281          * is using a moved/uninitialized value
282          */
283
284         debug!("check_if_path_is_moved(id=%?, use_kind=%?, lp=%s)",
285                id, use_kind, lp.repr(self.bccx.tcx));
286         do self.move_data.each_move_of(id, lp) |move, moved_lp| {
287             self.bccx.report_use_of_moved_value(
288                 span,
289                 use_kind,
290                 lp,
291                 move,
292                 moved_lp);
293             false
294         };
295     }
296
297     pub fn check_assignment(&self, expr: @ast::expr) {
298         // We don't use cat_expr() here because we don't want to treat
299         // auto-ref'd parameters in overloaded operators as rvalues.
300         let cmt = match self.bccx.tcx.adjustments.find(&expr.id) {
301             None => self.bccx.cat_expr_unadjusted(expr),
302             Some(&adj) => self.bccx.cat_expr_autoderefd(expr, adj)
303         };
304
305         debug!("check_assignment(cmt=%s)", cmt.repr(self.tcx()));
306
307         // Mutable values can be assigned, as long as they obey loans
308         // and aliasing restrictions:
309         if cmt.mutbl.is_mutable() {
310             if check_for_aliasable_mutable_writes(self, expr, cmt) {
311                 if check_for_assignment_to_restricted_or_frozen_location(
312                     self, expr, cmt)
313                 {
314                     // Safe, but record for lint pass later:
315                     mark_variable_as_used_mut(self, cmt);
316                 }
317             }
318             return;
319         }
320
321         // For immutable local variables, assignments are legal
322         // if they cannot already have been assigned
323         if self.is_local_variable(cmt) {
324             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                 self.bccx.report_reassigned_immutable_variable(
328                     expr.span,
329                     lp,
330                     assign);
331                 false
332             };
333             return;
334         }
335
336         // Otherwise, just a plain error.
337         self.bccx.span_err(
338             expr.span,
339             fmt!("cannot assign to %s %s",
340                  cmt.mutbl.to_user_str(),
341                  self.bccx.cmt_to_str(cmt)));
342         return;
343
344         fn mark_variable_as_used_mut(this: &CheckLoanCtxt,
345                                      cmt: mc::cmt) {
346             //! If the mutability of the `cmt` being written is inherited
347             //! from a local variable, liveness will
348             //! not have been able to detect that this variable's mutability
349             //! is important, so we must add the variable to the
350             //! `used_mut_nodes` table here.
351
352             let mut cmt = cmt;
353             loop {
354                 debug!("mark_writes_through_upvars_as_used_mut(cmt=%s)",
355                        cmt.repr(this.tcx()));
356                 match cmt.cat {
357                     mc::cat_local(id) |
358                     mc::cat_arg(id) |
359                     mc::cat_self(id) => {
360                         this.tcx().used_mut_nodes.insert(id);
361                         return;
362                     }
363
364                     mc::cat_stack_upvar(b) => {
365                         cmt = b;
366                     }
367
368                     mc::cat_rvalue(*) |
369                     mc::cat_static_item |
370                     mc::cat_copied_upvar(*) |
371                     mc::cat_deref(_, _, mc::unsafe_ptr(*)) |
372                     mc::cat_deref(_, _, mc::gc_ptr(*)) |
373                     mc::cat_deref(_, _, mc::region_ptr(*)) => {
374                         assert_eq!(cmt.mutbl, mc::McDeclared);
375                         return;
376                     }
377
378                     mc::cat_discr(b, _) |
379                     mc::cat_deref(b, _, mc::uniq_ptr) => {
380                         assert_eq!(cmt.mutbl, mc::McInherited);
381                         cmt = b;
382                     }
383
384                     mc::cat_downcast(b) |
385                     mc::cat_interior(b, _) => {
386                         if cmt.mutbl == mc::McInherited {
387                             cmt = b;
388                         } else {
389                             return; // field declared as mutable or some such
390                         }
391                     }
392                 }
393             }
394         }
395
396         fn check_for_aliasable_mutable_writes(this: &CheckLoanCtxt,
397                                               expr: @ast::expr,
398                                               cmt: mc::cmt) -> bool {
399             //! Safety checks related to writes to aliasable, mutable locations
400
401             let guarantor = cmt.guarantor();
402             debug!("check_for_aliasable_mutable_writes(cmt=%s, guarantor=%s)",
403                    cmt.repr(this.tcx()), guarantor.repr(this.tcx()));
404             match guarantor.cat {
405                 mc::cat_deref(b, _, mc::region_ptr(m_mutbl, _)) => {
406                     // Statically prohibit writes to `&mut` when aliasable
407
408                     check_for_aliasability_violation(this, expr, b);
409                 }
410
411                 mc::cat_deref(_, deref_count, mc::gc_ptr(ast::m_mutbl)) => {
412                     // Dynamically check writes to `@mut`
413
414                     let key = root_map_key {
415                         id: guarantor.id,
416                         derefs: deref_count
417                     };
418                     debug!("Inserting write guard at %?", key);
419                     this.bccx.write_guard_map.insert(key);
420                 }
421
422                 _ => {}
423             }
424
425             return true; // no errors reported
426         }
427
428         fn check_for_aliasability_violation(this: &CheckLoanCtxt,
429                                             expr: @ast::expr,
430                                             cmt: mc::cmt) -> bool {
431             let mut cmt = cmt;
432
433             loop {
434                 match cmt.cat {
435                     mc::cat_deref(b, _, mc::region_ptr(m_mutbl, _)) |
436                     mc::cat_downcast(b) |
437                     mc::cat_stack_upvar(b) |
438                     mc::cat_deref(b, _, mc::uniq_ptr) |
439                     mc::cat_interior(b, _) |
440                     mc::cat_discr(b, _) => {
441                         // Aliasability depends on base cmt
442                         cmt = b;
443                     }
444
445                     mc::cat_copied_upvar(_) |
446                     mc::cat_rvalue(*) |
447                     mc::cat_local(*) |
448                     mc::cat_arg(_) |
449                     mc::cat_self(*) |
450                     mc::cat_deref(_, _, mc::unsafe_ptr(*)) |
451                     mc::cat_static_item(*) |
452                     mc::cat_deref(_, _, mc::gc_ptr(_)) |
453                     mc::cat_deref(_, _, mc::region_ptr(m_const, _)) |
454                     mc::cat_deref(_, _, mc::region_ptr(m_imm, _)) => {
455                         // Aliasability is independent of base cmt
456                         match cmt.freely_aliasable() {
457                             None => {
458                                 return true;
459                             }
460                             Some(cause) => {
461                                 this.bccx.report_aliasability_violation(
462                                     expr.span,
463                                     MutabilityViolation,
464                                     cause);
465                                 return false;
466                             }
467                         }
468                     }
469                 }
470             }
471         }
472
473         fn check_for_assignment_to_restricted_or_frozen_location(
474             this: &CheckLoanCtxt,
475             expr: @ast::expr,
476             cmt: mc::cmt) -> bool
477         {
478             //! Check for assignments that violate the terms of an
479             //! outstanding loan.
480
481             let loan_path = match opt_loan_path(cmt) {
482                 Some(lp) => lp,
483                 None => { return true; /* no loan path, can't be any loans */ }
484             };
485
486             // Start by searching for an assignment to a *restricted*
487             // location. Here is one example of the kind of error caught
488             // by this check:
489             //
490             //    let mut v = ~[1, 2, 3];
491             //    let p = &v;
492             //    v = ~[4];
493             //
494             // In this case, creating `p` triggers a RESTR_MUTATE
495             // restriction on the path `v`.
496             //
497             // Here is a second, more subtle example:
498             //
499             //    let mut v = ~[1, 2, 3];
500             //    let p = &const v[0];
501             //    v[0] = 4;                   // OK
502             //    v[1] = 5;                   // OK
503             //    v = ~[4, 5, 3];             // Error
504             //
505             // In this case, `p` is pointing to `v[0]`, and it is a
506             // `const` pointer in any case. So the first two
507             // assignments are legal (and would be permitted by this
508             // check). However, the final assignment (which is
509             // logically equivalent) is forbidden, because it would
510             // cause the existing `v` array to be freed, thus
511             // invalidating `p`. In the code, this error results
512             // because `gather_loans::restrictions` adds a
513             // `RESTR_MUTATE` restriction whenever the contents of an
514             // owned pointer are borrowed, and hence while `v[*]` is not
515             // restricted from being written, `v` is.
516             let cont = do this.each_in_scope_restriction(expr.id, loan_path)
517                 |loan, restr|
518             {
519                 if restr.set.intersects(RESTR_MUTATE) {
520                     this.report_illegal_mutation(expr, loan_path, loan);
521                     false
522                 } else {
523                     true
524                 }
525             };
526
527             if !cont { return false }
528
529             // The previous code handled assignments to paths that
530             // have been restricted. This covers paths that have been
531             // directly lent out and their base paths, but does not
532             // cover random extensions of those paths. For example,
533             // the following program is not declared illegal by the
534             // previous check:
535             //
536             //    let mut v = ~[1, 2, 3];
537             //    let p = &v;
538             //    v[0] = 4; // declared error by loop below, not code above
539             //
540             // The reason that this passes the previous check whereas
541             // an assignment like `v = ~[4]` fails is because the assignment
542             // here is to `v[*]`, and the existing restrictions were issued
543             // for `v`, not `v[*]`.
544             //
545             // So in this loop, we walk back up the loan path so long
546             // as the mutability of the path is dependent on a super
547             // path, and check that the super path was not lent out as
548             // mutable or immutable (a const loan is ok).
549             //
550             // Mutability of a path can be dependent on the super path
551             // in two ways. First, it might be inherited mutability.
552             // Second, the pointee of an `&mut` pointer can only be
553             // mutated if it is found in an unaliased location, so we
554             // have to check that the owner location is not borrowed.
555             //
556             // Note that we are *not* checking for any and all
557             // restrictions.  We are only interested in the pointers
558             // that the user created, whereas we add restrictions for
559             // all kinds of paths that are not directly aliased. If we checked
560             // for all restrictions, and not just loans, then the following
561             // valid program would be considered illegal:
562             //
563             //    let mut v = ~[1, 2, 3];
564             //    let p = &const v[0];
565             //    v[1] = 5; // ok
566             //
567             // Here the restriction that `v` not be mutated would be misapplied
568             // to block the subpath `v[1]`.
569             let full_loan_path = loan_path;
570             let mut loan_path = loan_path;
571             loop {
572                 match *loan_path {
573                     // Peel back one layer if, for `loan_path` to be
574                     // mutable, `lp_base` must be mutable. This occurs
575                     // with inherited mutability and with `&mut`
576                     // pointers.
577                     LpExtend(lp_base, mc::McInherited, _) |
578                     LpExtend(lp_base, _, LpDeref(mc::region_ptr(ast::m_mutbl, _))) => {
579                         loan_path = lp_base;
580                     }
581
582                     // Otherwise stop iterating
583                     LpExtend(_, mc::McDeclared, _) |
584                     LpExtend(_, mc::McImmutable, _) |
585                     LpExtend(_, mc::McReadOnly, _) |
586                     LpVar(_) => {
587                         return true;
588                     }
589                 }
590
591                 // Check for a non-const loan of `loan_path`
592                 let cont = do this.each_in_scope_loan(expr.id) |loan| {
593                     if loan.loan_path == loan_path && loan.mutbl != m_const {
594                         this.report_illegal_mutation(expr, full_loan_path, loan);
595                         false
596                     } else {
597                         true
598                     }
599                 };
600
601                 if !cont { return false }
602             }
603         }
604     }
605
606     pub fn report_illegal_mutation(&self,
607                                    expr: @ast::expr,
608                                    loan_path: &LoanPath,
609                                    loan: &Loan) {
610         self.bccx.span_err(
611             expr.span,
612             fmt!("cannot assign to `%s` because it is borrowed",
613                  self.bccx.loan_path_to_str(loan_path)));
614         self.bccx.span_note(
615             loan.span,
616             fmt!("borrow of `%s` occurs here",
617                  self.bccx.loan_path_to_str(loan_path)));
618     }
619
620     fn check_move_out_from_expr(&self, expr: @ast::expr) {
621         match expr.node {
622             ast::expr_fn_block(*) => {
623                 // moves due to capture clauses are checked
624                 // in `check_loans_in_fn`, so that we can
625                 // give a better error message
626             }
627             _ => {
628                 self.check_move_out_from_id(expr.id, expr.span)
629             }
630         }
631     }
632
633     fn check_move_out_from_id(&self, id: ast::NodeId, span: span) {
634         do self.move_data.each_path_moved_by(id) |_, move_path| {
635             match self.analyze_move_out_from(id, move_path) {
636                 MoveOk => {}
637                 MoveWhileBorrowed(loan_path, loan_span) => {
638                     self.bccx.span_err(
639                         span,
640                         fmt!("cannot move out of `%s` \
641                               because it is borrowed",
642                              self.bccx.loan_path_to_str(move_path)));
643                     self.bccx.span_note(
644                         loan_span,
645                         fmt!("borrow of `%s` occurs here",
646                              self.bccx.loan_path_to_str(loan_path)));
647                 }
648             }
649             true
650         };
651     }
652
653     pub fn analyze_move_out_from(&self,
654                                  expr_id: ast::NodeId,
655                                  move_path: @LoanPath) -> MoveError {
656         debug!("analyze_move_out_from(expr_id=%?, move_path=%s)",
657                expr_id, move_path.repr(self.tcx()));
658
659         // FIXME(#4384) inadequare if/when we permit `move a.b`
660
661         let mut ret = MoveOk;
662
663         // check for a conflicting loan:
664         do self.each_in_scope_restriction(expr_id, move_path) |loan, _| {
665             // Any restriction prevents moves.
666             ret = MoveWhileBorrowed(loan.loan_path, loan.span);
667             false
668         };
669
670         ret
671     }
672
673     pub fn check_call(&self,
674                       _expr: @ast::expr,
675                       _callee: Option<@ast::expr>,
676                       _callee_id: ast::NodeId,
677                       _callee_span: span,
678                       _args: &[@ast::expr]) {
679         // NB: This call to check for conflicting loans is not truly
680         // necessary, because the callee_id never issues new loans.
681         // However, I added it for consistency and lest the system
682         // should change in the future.
683         //
684         // FIXME(#6268) nested method calls
685         // self.check_for_conflicting_loans(callee_id);
686     }
687 }
688
689 fn check_loans_in_fn<'a>(visitor: &mut CheckLoanVisitor,
690                          fk: &visit::fn_kind,
691                          decl: &ast::fn_decl,
692                          body: &ast::Block,
693                          sp: span,
694                          id: ast::NodeId,
695                          this: CheckLoanCtxt<'a>) {
696     match *fk {
697         visit::fk_item_fn(*) |
698         visit::fk_method(*) => {
699             // Don't process nested items.
700             return;
701         }
702
703         visit::fk_anon(*) |
704         visit::fk_fn_block(*) => {
705             check_captured_variables(this, id, sp);
706         }
707     }
708
709     visit::walk_fn(visitor, fk, decl, body, sp, id, this);
710
711     fn check_captured_variables(this: CheckLoanCtxt,
712                                 closure_id: ast::NodeId,
713                                 span: span) {
714         let cap_vars = this.bccx.capture_map.get(&closure_id);
715         for cap_var in cap_vars.iter() {
716             let var_id = ast_util::def_id_of_def(cap_var.def).node;
717             let var_path = @LpVar(var_id);
718             this.check_if_path_is_moved(closure_id, span,
719                                         MovedInCapture, var_path);
720             match cap_var.mode {
721                 moves::CapRef | moves::CapCopy => {}
722                 moves::CapMove => {
723                     check_by_move_capture(this, closure_id, cap_var, var_path);
724                 }
725             }
726         }
727         return;
728
729         fn check_by_move_capture(this: CheckLoanCtxt,
730                                  closure_id: ast::NodeId,
731                                  cap_var: &moves::CaptureVar,
732                                  move_path: @LoanPath) {
733             let move_err = this.analyze_move_out_from(closure_id, move_path);
734             match move_err {
735                 MoveOk => {}
736                 MoveWhileBorrowed(loan_path, loan_span) => {
737                     this.bccx.span_err(
738                         cap_var.span,
739                         fmt!("cannot move `%s` into closure \
740                               because it is borrowed",
741                              this.bccx.loan_path_to_str(move_path)));
742                     this.bccx.span_note(
743                         loan_span,
744                         fmt!("borrow of `%s` occurs here",
745                              this.bccx.loan_path_to_str(loan_path)));
746                 }
747             }
748         }
749     }
750 }
751
752 fn check_loans_in_local<'a>(vt: &mut CheckLoanVisitor,
753                             local: @ast::Local,
754                             this: CheckLoanCtxt<'a>) {
755     visit::walk_local(vt, local, this);
756 }
757
758 fn check_loans_in_expr<'a>(vt: &mut CheckLoanVisitor,
759                            expr: @ast::expr,
760                            this: CheckLoanCtxt<'a>) {
761     visit::walk_expr(vt, expr, this);
762
763     debug!("check_loans_in_expr(expr=%s)",
764            expr.repr(this.tcx()));
765
766     this.check_for_conflicting_loans(expr.id);
767     this.check_move_out_from_expr(expr);
768
769     match expr.node {
770       ast::expr_self |
771       ast::expr_path(*) => {
772           if !this.move_data.is_assignee(expr.id) {
773               let cmt = this.bccx.cat_expr_unadjusted(expr);
774               debug!("path cmt=%s", cmt.repr(this.tcx()));
775               let r = opt_loan_path(cmt);
776               for &lp in r.iter() {
777                   this.check_if_path_is_moved(expr.id, expr.span, MovedInUse, lp);
778               }
779           }
780       }
781       ast::expr_assign(dest, _) |
782       ast::expr_assign_op(_, _, dest, _) => {
783         this.check_assignment(dest);
784       }
785       ast::expr_call(f, ref args, _) => {
786         this.check_call(expr, Some(f), f.id, f.span, *args);
787       }
788       ast::expr_method_call(callee_id, _, _, _, ref args, _) => {
789         this.check_call(expr, None, callee_id, expr.span, *args);
790       }
791       ast::expr_index(callee_id, _, rval) |
792       ast::expr_binary(callee_id, _, _, rval)
793       if this.bccx.method_map.contains_key(&expr.id) => {
794         this.check_call(expr,
795                         None,
796                         callee_id,
797                         expr.span,
798                         [rval]);
799       }
800       ast::expr_unary(callee_id, _, _) | ast::expr_index(callee_id, _, _)
801       if this.bccx.method_map.contains_key(&expr.id) => {
802         this.check_call(expr,
803                         None,
804                         callee_id,
805                         expr.span,
806                         []);
807       }
808       _ => { }
809     }
810 }
811
812 fn check_loans_in_pat<'a>(vt: &mut CheckLoanVisitor,
813                           pat: @ast::pat,
814                           this: CheckLoanCtxt<'a>)
815 {
816     this.check_for_conflicting_loans(pat.id);
817     this.check_move_out_from_id(pat.id, pat.span);
818     visit::walk_pat(vt, pat, this);
819 }
820
821 fn check_loans_in_block<'a>(vt: &mut CheckLoanVisitor,
822                             blk: &ast::Block,
823                             this: CheckLoanCtxt<'a>)
824 {
825     visit::walk_block(vt, blk, this);
826     this.check_for_conflicting_loans(blk.id);
827 }