// ---------------------------------------------------------------------- // Checking loans // // Phase 2 of check: we walk down the tree and check that: // 1. assignments are always made to mutable locations; // 2. loans made in overlapping scopes do not conflict // 3. assignments do not affect things loaned out as immutable // 4. moves do not affect things loaned out in any way use UseError::*; use crate::borrowck::*; use crate::borrowck::InteriorKind::{InteriorElement, InteriorField}; use rustc::middle::expr_use_visitor as euv; use rustc::middle::expr_use_visitor::MutateMode; use rustc::middle::mem_categorization as mc; use rustc::middle::mem_categorization::Categorization; use rustc::middle::region; use rustc::ty::{self, TyCtxt, RegionKind}; use syntax_pos::Span; use rustc::hir; use rustc::hir::Node; use rustc_mir::util::borrowck_errors::{BorrowckErrors, Origin}; use log::debug; use std::rc::Rc; // FIXME (#16118): These functions are intended to allow the borrow checker to // be less precise in its handling of Box while still allowing moves out of a // Box. They should be removed when Unique is removed from LoanPath. fn owned_ptr_base_path<'a, 'tcx>(loan_path: &'a LoanPath<'tcx>) -> &'a LoanPath<'tcx> { //! Returns the base of the leftmost dereference of an Unique in //! `loan_path`. If there is no dereference of an Unique in `loan_path`, //! then it just returns `loan_path` itself. return match helper(loan_path) { Some(new_loan_path) => new_loan_path, None => loan_path.clone() }; fn helper<'a, 'tcx>(loan_path: &'a LoanPath<'tcx>) -> Option<&'a LoanPath<'tcx>> { match loan_path.kind { LpVar(_) | LpUpvar(_) => None, LpExtend(ref lp_base, _, LpDeref(mc::Unique)) => { match helper(&lp_base) { v @ Some(_) => v, None => Some(&lp_base) } } LpDowncast(ref lp_base, _) | LpExtend(ref lp_base, ..) => helper(&lp_base) } } } fn owned_ptr_base_path_rc<'tcx>(loan_path: &Rc>) -> Rc> { //! The equivalent of `owned_ptr_base_path` for an &Rc rather than //! a &LoanPath. return match helper(loan_path) { Some(new_loan_path) => new_loan_path, None => loan_path.clone() }; fn helper<'tcx>(loan_path: &Rc>) -> Option>> { match loan_path.kind { LpVar(_) | LpUpvar(_) => None, LpExtend(ref lp_base, _, LpDeref(mc::Unique)) => { match helper(lp_base) { v @ Some(_) => v, None => Some(lp_base.clone()) } } LpDowncast(ref lp_base, _) | LpExtend(ref lp_base, ..) => helper(lp_base) } } } struct CheckLoanCtxt<'a, 'tcx: 'a> { bccx: &'a BorrowckCtxt<'a, 'tcx>, dfcx_loans: &'a LoanDataFlow<'a, 'tcx>, move_data: &'a move_data::FlowedMoveData<'a, 'tcx>, all_loans: &'a [Loan<'tcx>], movable_generator: bool, } impl<'a, 'tcx> euv::Delegate<'tcx> for CheckLoanCtxt<'a, 'tcx> { fn consume(&mut self, consume_id: hir::HirId, consume_span: Span, cmt: &mc::cmt_<'tcx>, mode: euv::ConsumeMode) { debug!("consume(consume_id={}, cmt={:?}, mode={:?})", consume_id, cmt, mode); self.consume_common(consume_id.local_id, consume_span, cmt, mode); } fn matched_pat(&mut self, _matched_pat: &hir::Pat, _cmt: &mc::cmt_<'_>, _mode: euv::MatchMode) { } fn consume_pat(&mut self, consume_pat: &hir::Pat, cmt: &mc::cmt_<'tcx>, mode: euv::ConsumeMode) { debug!("consume_pat(consume_pat={:?}, cmt={:?}, mode={:?})", consume_pat, cmt, mode); self.consume_common(consume_pat.hir_id.local_id, consume_pat.span, cmt, mode); } fn borrow(&mut self, borrow_id: hir::HirId, borrow_span: Span, cmt: &mc::cmt_<'tcx>, loan_region: ty::Region<'tcx>, bk: ty::BorrowKind, loan_cause: euv::LoanCause) { debug!("borrow(borrow_id={}, cmt={:?}, loan_region={:?}, \ bk={:?}, loan_cause={:?})", borrow_id, cmt, loan_region, bk, loan_cause); if let Some(lp) = opt_loan_path(cmt) { let moved_value_use_kind = match loan_cause { euv::ClosureCapture(_) => MovedInCapture, _ => MovedInUse, }; self.check_if_path_is_moved(borrow_id.local_id, borrow_span, moved_value_use_kind, &lp); } self.check_for_conflicting_loans(borrow_id.local_id); self.check_for_loans_across_yields(cmt, loan_region, borrow_span); } fn mutate(&mut self, assignment_id: hir::HirId, assignment_span: Span, assignee_cmt: &mc::cmt_<'tcx>, mode: euv::MutateMode) { debug!("mutate(assignment_id={}, assignee_cmt={:?})", assignment_id, assignee_cmt); if let Some(lp) = opt_loan_path(assignee_cmt) { match mode { MutateMode::Init | MutateMode::JustWrite => { // In a case like `path = 1`, then path does not // have to be *FULLY* initialized, but we still // must be careful lest it contains derefs of // pointers. self.check_if_assigned_path_is_moved(assignee_cmt.hir_id.local_id, assignment_span, MovedInUse, &lp); } MutateMode::WriteAndRead => { // In a case like `path += 1`, then path must be // fully initialized, since we will read it before // we write it. self.check_if_path_is_moved(assignee_cmt.hir_id.local_id, assignment_span, MovedInUse, &lp); } } } self.check_assignment(assignment_id.local_id, assignment_span, assignee_cmt); } fn decl_without_init(&mut self, _id: hir::HirId, _span: Span) { } } pub fn check_loans<'a, 'b, 'c, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, dfcx_loans: &LoanDataFlow<'b, 'tcx>, move_data: &move_data::FlowedMoveData<'c, 'tcx>, all_loans: &[Loan<'tcx>], body: &hir::Body) { debug!("check_loans(body id={})", body.value.hir_id); let def_id = bccx.tcx.hir().body_owner_def_id(body.id()); let hir_id = bccx.tcx.hir().as_local_hir_id(def_id).unwrap(); let movable_generator = !match bccx.tcx.hir().get_by_hir_id(hir_id) { Node::Expr(&hir::Expr { node: hir::ExprKind::Closure(.., Some(hir::GeneratorMovability::Static)), .. }) => true, _ => false, }; let param_env = bccx.tcx.param_env(def_id); let mut clcx = CheckLoanCtxt { bccx, dfcx_loans, move_data, all_loans, movable_generator, }; let rvalue_promotable_map = bccx.tcx.rvalue_promotable_map(def_id); euv::ExprUseVisitor::new(&mut clcx, bccx.tcx, param_env, &bccx.region_scope_tree, bccx.tables, Some(rvalue_promotable_map)) .consume_body(body); } #[derive(PartialEq)] enum UseError<'tcx> { UseOk, UseWhileBorrowed(/*loan*/Rc>, /*loan*/Span) } fn compatible_borrow_kinds(borrow_kind1: ty::BorrowKind, borrow_kind2: ty::BorrowKind) -> bool { borrow_kind1 == ty::ImmBorrow && borrow_kind2 == ty::ImmBorrow } impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { pub fn tcx(&self) -> TyCtxt<'a, 'tcx, 'tcx> { self.bccx.tcx } pub fn each_issued_loan(&self, node: hir::ItemLocalId, mut op: F) -> bool where F: FnMut(&Loan<'tcx>) -> bool, { //! Iterates over each loan that has been issued //! on entrance to `node`, 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. self.dfcx_loans.each_bit_on_entry(node, |loan_index| { let loan = &self.all_loans[loan_index]; op(loan) }) } pub fn each_in_scope_loan(&self, scope: region::Scope, mut op: F) -> bool where F: FnMut(&Loan<'tcx>) -> bool, { //! Like `each_issued_loan()`, but only considers loans that are //! currently in scope. self.each_issued_loan(scope.item_local_id(), |loan| { if self.bccx.region_scope_tree.is_subscope_of(scope, loan.kill_scope) { op(loan) } else { true } }) } fn each_in_scope_loan_affecting_path(&self, scope: region::Scope, loan_path: &LoanPath<'tcx>, mut op: F) -> bool where F: FnMut(&Loan<'tcx>) -> bool, { //! Iterates through all of the in-scope loans affecting `loan_path`, //! calling `op`, and ceasing iteration if `false` is returned. // First, we check for a loan restricting the path P being used. This // accounts for borrows of P but also borrows of subpaths, like P.a.b. // Consider the following example: // // let x = &mut a.b.c; // Restricts a, a.b, and a.b.c // let y = a; // Conflicts with restriction let loan_path = owned_ptr_base_path(loan_path); let cont = self.each_in_scope_loan(scope, |loan| { let mut ret = true; for restr_path in &loan.restricted_paths { if **restr_path == *loan_path { if !op(loan) { ret = false; break; } } } ret }); if !cont { return false; } // Next, we must check for *loans* (not restrictions) on the path P or // any base path. This rejects examples like the following: // // let x = &mut a.b; // let y = a.b.c; // // Limiting this search to *loans* and not *restrictions* means that // examples like the following continue to work: // // let x = &mut a.b; // let y = a.c; let mut loan_path = loan_path; loop { match loan_path.kind { LpVar(_) | LpUpvar(_) => { break; } LpDowncast(ref lp_base, _) | LpExtend(ref lp_base, ..) => { loan_path = &lp_base; } } let cont = self.each_in_scope_loan(scope, |loan| { if *loan.loan_path == *loan_path { op(loan) } else { true } }); if !cont { return false; } } return true; } pub fn loans_generated_by(&self, node: hir::ItemLocalId) -> Vec { //! Returns a vector of the loans that are generated as //! we enter `node`. let mut result = Vec::new(); self.dfcx_loans.each_gen_bit(node, |loan_index| { result.push(loan_index); true }); return result; } pub fn check_for_loans_across_yields(&self, cmt: &mc::cmt_<'tcx>, loan_region: ty::Region<'tcx>, borrow_span: Span) { pub fn borrow_of_local_data<'tcx>(cmt: &mc::cmt_<'tcx>) -> bool { match cmt.cat { // Borrows of static items is allowed Categorization::StaticItem => false, // Reborrow of already borrowed data is ignored // Any errors will be caught on the initial borrow Categorization::Deref(..) => false, // By-ref upvars has Derefs so they will get ignored. // Generators counts as FnOnce so this leaves only // by-move upvars, which is local data for generators Categorization::Upvar(..) => true, Categorization::ThreadLocal(region) | Categorization::Rvalue(region) => { // Rvalues promoted to 'static are no longer local if let RegionKind::ReStatic = *region { false } else { true } } // Borrow of local data must be checked Categorization::Local(..) => true, // For interior references and downcasts, find out if the base is local Categorization::Downcast(ref cmt_base, _) | Categorization::Interior(ref cmt_base, _) => borrow_of_local_data(&cmt_base), } } if !self.movable_generator { return; } if !borrow_of_local_data(cmt) { return; } let scope = match *loan_region { // A concrete region in which we will look for a yield expression RegionKind::ReScope(scope) => scope, // There cannot be yields inside an empty region RegionKind::ReEmpty => return, // Local data cannot have these lifetimes RegionKind::ReEarlyBound(..) | RegionKind::ReLateBound(..) | RegionKind::ReFree(..) | RegionKind::ReStatic => { self.bccx .tcx .sess.delay_span_bug(borrow_span, &format!("unexpected region for local data {:?}", loan_region)); return } // These cannot exist in borrowck RegionKind::ReVar(..) | RegionKind::RePlaceholder(..) | RegionKind::ReClosureBound(..) | RegionKind::ReErased => span_bug!(borrow_span, "unexpected region in borrowck {:?}", loan_region), }; let body_id = self.bccx.body.value.hir_id.local_id; if self.bccx.region_scope_tree.containing_body(scope) != Some(body_id) { // We are borrowing local data longer than its storage. // This should result in other borrowck errors. self.bccx.tcx.sess.delay_span_bug(borrow_span, "borrowing local data longer than its storage"); return; } if let Some(yield_span) = self.bccx .region_scope_tree .yield_in_scope_for_expr(scope, cmt.hir_id, self.bccx.body) { self.bccx.cannot_borrow_across_generator_yield(borrow_span, yield_span, Origin::Ast).emit(); self.bccx.signal_error(); } } pub fn check_for_conflicting_loans(&self, node: hir::ItemLocalId) { //! Checks to see whether any of the loans that are issued //! on entrance to `node` conflict with loans that have already been //! issued when we enter `node` (for example, we do not //! permit two `&mut` borrows of the same variable). //! //! (Note that some loans can be *issued* without necessarily //! taking effect yet.) debug!("check_for_conflicting_loans(node={:?})", node); let new_loan_indices = self.loans_generated_by(node); debug!("new_loan_indices = {:?}", new_loan_indices); for &new_loan_index in &new_loan_indices { self.each_issued_loan(node, |issued_loan| { let new_loan = &self.all_loans[new_loan_index]; // Only report an error for the first issued loan that conflicts // to avoid O(n^2) errors. self.report_error_if_loans_conflict(issued_loan, new_loan) }); } for (i, &x) in new_loan_indices.iter().enumerate() { let old_loan = &self.all_loans[x]; for &y in &new_loan_indices[(i+1) ..] { let new_loan = &self.all_loans[y]; self.report_error_if_loans_conflict(old_loan, new_loan); } } } pub fn report_error_if_loans_conflict(&self, old_loan: &Loan<'tcx>, new_loan: &Loan<'tcx>) -> bool { //! Checks whether `old_loan` and `new_loan` can safely be issued //! simultaneously. debug!("report_error_if_loans_conflict(old_loan={:?}, new_loan={:?})", old_loan, new_loan); // Should only be called for loans that are in scope at the same time. assert!(self.bccx.region_scope_tree.scopes_intersect(old_loan.kill_scope, new_loan.kill_scope)); let err_old_new = self.report_error_if_loan_conflicts_with_restriction( old_loan, new_loan, old_loan, new_loan).err(); let err_new_old = self.report_error_if_loan_conflicts_with_restriction( new_loan, old_loan, old_loan, new_loan).err(); match (err_old_new, err_new_old) { (Some(mut err), None) | (None, Some(mut err)) => { err.emit(); self.bccx.signal_error(); } (Some(mut err_old), Some(mut err_new)) => { err_old.emit(); self.bccx.signal_error(); err_new.cancel(); } (None, None) => return true, } false } pub fn report_error_if_loan_conflicts_with_restriction(&self, loan1: &Loan<'tcx>, loan2: &Loan<'tcx>, old_loan: &Loan<'tcx>, new_loan: &Loan<'tcx>) -> Result<(), DiagnosticBuilder<'a>> { //! 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={:?}, loan2={:?})", loan1, loan2); if compatible_borrow_kinds(loan1.kind, loan2.kind) { return Ok(()); } let loan2_base_path = owned_ptr_base_path_rc(&loan2.loan_path); for restr_path in &loan1.restricted_paths { if *restr_path != loan2_base_path { continue; } // If new_loan is something like `x.a`, and old_loan is something like `x.b`, we would // normally generate a rather confusing message (in this case, for multiple mutable // borrows): // // error: cannot borrow `x.b` as mutable more than once at a time // note: previous borrow of `x.a` occurs here; the mutable borrow prevents // subsequent moves, borrows, or modification of `x.a` until the borrow ends // // What we want to do instead is get the 'common ancestor' of the two borrow paths and // use that for most of the message instead, giving is something like this: // // error: cannot borrow `x` as mutable more than once at a time // note: previous borrow of `x` occurs here (through borrowing `x.a`); the mutable // borrow prevents subsequent moves, borrows, or modification of `x` until the // borrow ends let common = new_loan.loan_path.common(&old_loan.loan_path); let (nl, ol, new_loan_msg, old_loan_msg) = { if new_loan.loan_path.has_fork(&old_loan.loan_path) && common.is_some() { let nl = self.bccx.loan_path_to_string(&common.unwrap()); let ol = nl.clone(); let new_loan_msg = self.bccx.loan_path_to_string(&new_loan.loan_path); let old_loan_msg = self.bccx.loan_path_to_string(&old_loan.loan_path); (nl, ol, new_loan_msg, old_loan_msg) } else { (self.bccx.loan_path_to_string(&new_loan.loan_path), self.bccx.loan_path_to_string(&old_loan.loan_path), String::new(), String::new()) } }; let ol_pronoun = if new_loan.loan_path == old_loan.loan_path { "it".to_string() } else { format!("`{}`", ol) }; // We want to assemble all the relevant locations for the error. // // 1. Where did the new loan occur. // - if due to closure creation, where was the variable used in closure? // 2. Where did old loan occur. // 3. Where does old loan expire. let previous_end_span = Some(self.tcx().sess.source_map().end_point( old_loan.kill_scope.span(self.tcx(), &self.bccx.region_scope_tree))); let mut err = match (new_loan.kind, old_loan.kind) { (ty::MutBorrow, ty::MutBorrow) => self.bccx.cannot_mutably_borrow_multiply( new_loan.span, &nl, &new_loan_msg, old_loan.span, &old_loan_msg, previous_end_span, Origin::Ast), (ty::UniqueImmBorrow, ty::UniqueImmBorrow) => self.bccx.cannot_uniquely_borrow_by_two_closures( new_loan.span, &nl, old_loan.span, previous_end_span, Origin::Ast), (ty::UniqueImmBorrow, _) => self.bccx.cannot_uniquely_borrow_by_one_closure( new_loan.span, "closure", &nl, &new_loan_msg, old_loan.span, &ol_pronoun, &old_loan_msg, previous_end_span, Origin::Ast), (_, ty::UniqueImmBorrow) => { let new_loan_str = &new_loan.kind.to_user_str(); self.bccx.cannot_reborrow_already_uniquely_borrowed( new_loan.span, "closure", &nl, &new_loan_msg, new_loan_str, old_loan.span, &old_loan_msg, previous_end_span, "", Origin::Ast) } (..) => self.bccx.cannot_reborrow_already_borrowed( new_loan.span, &nl, &new_loan_msg, &new_loan.kind.to_user_str(), old_loan.span, &ol_pronoun, &old_loan.kind.to_user_str(), &old_loan_msg, previous_end_span, Origin::Ast) }; match new_loan.cause { euv::ClosureCapture(span) => { err.span_label( span, format!("borrow occurs due to use of `{}` in closure", nl)); } _ => { } } match old_loan.cause { euv::ClosureCapture(span) => { err.span_label( span, format!("previous borrow occurs due to use of `{}` in closure", ol)); } _ => { } } return Err(err); } Ok(()) } fn consume_common(&self, id: hir::ItemLocalId, span: Span, cmt: &mc::cmt_<'tcx>, mode: euv::ConsumeMode) { if let Some(lp) = opt_loan_path(cmt) { let moved_value_use_kind = match mode { euv::Copy => { self.check_for_copy_of_frozen_path(id, span, &lp); MovedInUse } euv::Move(_) => { match self.move_data.kind_of_move_of_path(id, &lp) { None => { // Sometimes moves don't have a move kind; // this either means that the original move // was from something illegal to move, // or was moved from referent of an unsafe // pointer or something like that. MovedInUse } Some(move_kind) => { self.check_for_move_of_borrowed_path(id, span, &lp, move_kind); if move_kind == move_data::Captured { MovedInCapture } else { MovedInUse } } } } }; self.check_if_path_is_moved(id, span, moved_value_use_kind, &lp); } } fn check_for_copy_of_frozen_path(&self, id: hir::ItemLocalId, span: Span, copy_path: &LoanPath<'tcx>) { match self.analyze_restrictions_on_use(id, copy_path, ty::ImmBorrow) { UseOk => { } UseWhileBorrowed(loan_path, loan_span) => { let desc = self.bccx.loan_path_to_string(copy_path); self.bccx.cannot_use_when_mutably_borrowed( span, &desc, loan_span, &self.bccx.loan_path_to_string(&loan_path), Origin::Ast) .emit(); self.bccx.signal_error(); } } } fn check_for_move_of_borrowed_path(&self, id: hir::ItemLocalId, span: Span, move_path: &LoanPath<'tcx>, move_kind: move_data::MoveKind) { // We want to detect if there are any loans at all, so we search for // any loans incompatible with MutBorrrow, since all other kinds of // loans are incompatible with that. match self.analyze_restrictions_on_use(id, move_path, ty::MutBorrow) { UseOk => { } UseWhileBorrowed(loan_path, loan_span) => { let mut err = match move_kind { move_data::Captured => { let mut err = self.bccx.cannot_move_into_closure( span, &self.bccx.loan_path_to_string(move_path), Origin::Ast); err.span_label( loan_span, format!("borrow of `{}` occurs here", &self.bccx.loan_path_to_string(&loan_path)) ); err.span_label( span, "move into closure occurs here" ); err } move_data::Declared | move_data::MoveExpr | move_data::MovePat => { let desc = self.bccx.loan_path_to_string(move_path); let mut err = self.bccx.cannot_move_when_borrowed(span, &desc, Origin::Ast); err.span_label( loan_span, format!("borrow of `{}` occurs here", &self.bccx.loan_path_to_string(&loan_path)) ); err.span_label( span, format!("move out of `{}` occurs here", &self.bccx.loan_path_to_string(move_path)) ); err } }; err.emit(); self.bccx.signal_error(); } } } pub fn analyze_restrictions_on_use(&self, expr_id: hir::ItemLocalId, use_path: &LoanPath<'tcx>, borrow_kind: ty::BorrowKind) -> UseError<'tcx> { debug!("analyze_restrictions_on_use(expr_id={:?}, use_path={:?})", expr_id, use_path); let mut ret = UseOk; let scope = region::Scope { id: expr_id, data: region::ScopeData::Node }; self.each_in_scope_loan_affecting_path( scope, use_path, |loan| { if !compatible_borrow_kinds(loan.kind, borrow_kind) { ret = UseWhileBorrowed(loan.loan_path.clone(), loan.span); false } else { true } }); return ret; } /// Reports an error if `expr` (which should be a path) /// is using a moved/uninitialized value fn check_if_path_is_moved(&self, id: hir::ItemLocalId, span: Span, use_kind: MovedValueUseKind, lp: &Rc>) { debug!("check_if_path_is_moved(id={:?}, use_kind={:?}, lp={:?})", id, use_kind, lp); // FIXME: if you find yourself tempted to cut and paste // the body below and then specializing the error reporting, // consider refactoring this instead! let base_lp = owned_ptr_base_path_rc(lp); self.move_data.each_move_of(id, &base_lp, |the_move, moved_lp| { self.bccx.report_use_of_moved_value( span, use_kind, &lp, the_move, moved_lp); false }); } /// Reports an error if assigning to `lp` will use a /// moved/uninitialized value. Mainly this is concerned with /// detecting derefs of uninitialized pointers. /// /// For example: /// /// ``` /// let a: i32; /// a = 10; // ok, even though a is uninitialized /// ``` /// /// ``` /// struct Point { x: u32, y: u32 } /// let mut p: Point; /// p.x = 22; // ok, even though `p` is uninitialized /// ``` /// /// ```compile_fail,E0381 /// # struct Point { x: u32, y: u32 } /// let mut p: Box; /// (*p).x = 22; // not ok, p is uninitialized, can't deref /// ``` fn check_if_assigned_path_is_moved(&self, id: hir::ItemLocalId, span: Span, use_kind: MovedValueUseKind, lp: &Rc>) { match lp.kind { LpVar(_) | LpUpvar(_) => { // assigning to `x` does not require that `x` is initialized } LpDowncast(ref lp_base, _) => { // assigning to `(P->Variant).f` is ok if assigning to `P` is ok self.check_if_assigned_path_is_moved(id, span, use_kind, lp_base); } LpExtend(ref lp_base, _, LpInterior(_, InteriorField(_))) => { match lp_base.to_type().sty { ty::Adt(def, _) if def.has_dtor(self.tcx()) => { // In the case where the owner implements drop, then // the path must be initialized to prevent a case of // partial reinitialization // // FIXME: could refactor via hypothetical // generalized check_if_path_is_moved let loan_path = owned_ptr_base_path_rc(lp_base); self.move_data.each_move_of(id, &loan_path, |_, _| { self.bccx .report_partial_reinitialization_of_uninitialized_structure( span, &loan_path); false }); return; }, _ => {}, } // assigning to `P.f` is ok if assigning to `P` is ok self.check_if_assigned_path_is_moved(id, span, use_kind, lp_base); } LpExtend(ref lp_base, _, LpInterior(_, InteriorElement)) | LpExtend(ref lp_base, _, LpDeref(_)) => { // assigning to `P[i]` requires `P` is initialized // assigning to `(*P)` requires `P` is initialized self.check_if_path_is_moved(id, span, use_kind, lp_base); } } } fn check_assignment(&self, assignment_id: hir::ItemLocalId, assignment_span: Span, assignee_cmt: &mc::cmt_<'tcx>) { debug!("check_assignment(assignee_cmt={:?})", assignee_cmt); // Check that we don't invalidate any outstanding loans if let Some(loan_path) = opt_loan_path(assignee_cmt) { let scope = region::Scope { id: assignment_id, data: region::ScopeData::Node }; self.each_in_scope_loan_affecting_path(scope, &loan_path, |loan| { self.report_illegal_mutation(assignment_span, &loan_path, loan); false }); } // Check for reassignments to (immutable) local variables. This // needs to be done here instead of in check_loans because we // depend on move data. if let Categorization::Local(hir_id) = assignee_cmt.cat { let lp = opt_loan_path(assignee_cmt).unwrap(); self.move_data.each_assignment_of(assignment_id, &lp, |assign| { if assignee_cmt.mutbl.is_mutable() { self.bccx.used_mut_nodes.borrow_mut().insert(hir_id); } else { self.bccx.report_reassigned_immutable_variable( assignment_span, &lp, assign); } false }); return } } pub fn report_illegal_mutation(&self, span: Span, loan_path: &LoanPath<'tcx>, loan: &Loan<'_>) { self.bccx.cannot_assign_to_borrowed( span, loan.span, &self.bccx.loan_path_to_string(loan_path), Origin::Ast) .emit(); self.bccx.signal_error(); } }