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