auto merge of #14318 : zwarich/rust/check-loans-refactor, r=nikomatsakis

I tried to split up the less mechanical changes into separate commits so they are easier to review. One thing I'm not quite sure of is whether `MoveReason` should just be replaced with `move_data::MoveKind`.
This commit is contained in:
bors 2014-06-06 12:17:10 -07:00
commit 61d79175c0
16 changed files with 726 additions and 277 deletions

View File

@ -20,15 +20,10 @@
use middle::borrowck::*;
use euv = middle::expr_use_visitor;
use middle::freevars;
use mc = middle::mem_categorization;
use middle::ty;
use middle::typeck::MethodCall;
use syntax::ast;
use syntax::ast_util;
use syntax::codemap::Span;
use syntax::visit::Visitor;
use syntax::visit;
use util::ppaux::Repr;
use std::rc::Rc;
@ -40,35 +35,104 @@ struct CheckLoanCtxt<'a> {
all_loans: &'a [Loan],
}
impl<'a> Visitor<()> for CheckLoanCtxt<'a> {
impl<'a> euv::Delegate for CheckLoanCtxt<'a> {
fn consume(&mut self,
consume_id: ast::NodeId,
consume_span: Span,
cmt: mc::cmt,
mode: euv::ConsumeMode) {
debug!("consume(consume_id={}, cmt={}, mode={})",
consume_id, cmt.repr(self.tcx()), mode);
fn visit_expr(&mut self, ex: &ast::Expr, _: ()) {
check_loans_in_expr(self, ex);
}
fn visit_local(&mut self, l: &ast::Local, _: ()) {
check_loans_in_local(self, l);
}
fn visit_block(&mut self, b: &ast::Block, _: ()) {
check_loans_in_block(self, b);
}
fn visit_pat(&mut self, p: &ast::Pat, _: ()) {
check_loans_in_pat(self, p);
}
fn visit_fn(&mut self, _fk: &visit::FnKind, _fd: &ast::FnDecl,
_b: &ast::Block, _s: Span, _n: ast::NodeId, _: ()) {
// Don't process nested items or closures here,
// the outer loop will take care of it.
return;
self.consume_common(consume_id, consume_span, cmt, mode);
}
// FIXME(#10894) should continue recursing
fn visit_ty(&mut self, _t: &ast::Ty, _: ()) {}
fn consume_pat(&mut self,
consume_pat: &ast::Pat,
cmt: mc::cmt,
mode: euv::ConsumeMode) {
debug!("consume_pat(consume_pat={}, cmt={}, mode={})",
consume_pat.repr(self.tcx()),
cmt.repr(self.tcx()),
mode);
self.consume_common(consume_pat.id, consume_pat.span, cmt, mode);
}
fn borrow(&mut self,
borrow_id: ast::NodeId,
borrow_span: Span,
cmt: mc::cmt,
loan_region: ty::Region,
bk: ty::BorrowKind,
loan_cause: euv::LoanCause)
{
debug!("borrow(borrow_id={}, cmt={}, loan_region={}, \
bk={}, loan_cause={:?})",
borrow_id, cmt.repr(self.tcx()), loan_region,
bk, loan_cause);
match opt_loan_path(&cmt) {
Some(lp) => {
let moved_value_use_kind = match loan_cause {
euv::ClosureCapture(_) => MovedInCapture,
_ => MovedInUse,
};
self.check_if_path_is_moved(borrow_id, borrow_span, moved_value_use_kind, &lp);
}
None => { }
}
self.check_for_conflicting_loans(borrow_id);
}
fn mutate(&mut self,
assignment_id: ast::NodeId,
assignment_span: Span,
assignee_cmt: mc::cmt,
mode: euv::MutateMode)
{
debug!("mutate(assignment_id={}, assignee_cmt={})",
assignment_id, assignee_cmt.repr(self.tcx()));
match opt_loan_path(&assignee_cmt) {
Some(lp) => {
match mode {
euv::Init | euv::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.id,
assignment_span,
MovedInUse,
&lp);
}
euv::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.id,
assignment_span,
MovedInUse,
&lp);
}
}
}
None => { }
}
self.check_assignment(assignment_id, assignment_span, assignee_cmt, mode);
}
fn decl_without_init(&mut self, _id: ast::NodeId, _span: Span) { }
}
pub fn check_loans(bccx: &BorrowckCtxt,
dfcx_loans: &LoanDataFlow,
move_data: move_data::FlowedMoveData,
all_loans: &[Loan],
decl: &ast::FnDecl,
body: &ast::Block) {
debug!("check_loans(body id={:?})", body.id);
@ -79,7 +143,10 @@ pub fn check_loans(bccx: &BorrowckCtxt,
all_loans: all_loans,
};
clcx.visit_block(body, ());
{
let mut euv = euv::ExprUseVisitor::new(&mut clcx, bccx.tcx);
euv.walk_fn(decl, body);
}
}
#[deriving(PartialEq)]
@ -355,18 +422,130 @@ impl<'a> CheckLoanCtxt<'a> {
true
}
pub fn is_local_variable(&self, cmt: mc::cmt) -> bool {
pub fn is_local_variable_or_arg(&self, cmt: mc::cmt) -> bool {
match cmt.cat {
mc::cat_local(_) => true,
mc::cat_local(_) | mc::cat_arg(_) => true,
_ => false
}
}
pub fn check_if_path_is_moved(&self,
id: ast::NodeId,
span: Span,
use_kind: MovedValueUseKind,
lp: &Rc<LoanPath>) {
fn consume_common(&self,
id: ast::NodeId,
span: Span,
cmt: mc::cmt,
mode: euv::ConsumeMode) {
match opt_loan_path(&cmt) {
Some(lp) => {
let moved_value_use_kind = match mode {
euv::Copy => {
// FIXME(#12624) -- If we are copying the value,
// we don't care if it's borrowed.
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);
}
None => { }
}
}
fn check_for_move_of_borrowed_path(&self,
id: ast::NodeId,
span: Span,
move_path: &Rc<LoanPath>,
move_kind: move_data::MoveKind) {
match self.analyze_move_out_from(id, &**move_path) {
MoveOk => { }
MoveWhileBorrowed(loan_path, loan_span) => {
let err_message = match move_kind {
move_data::Captured =>
format!("cannot move `{}` into closure because it is borrowed",
self.bccx.loan_path_to_str(&**move_path).as_slice()),
move_data::Declared |
move_data::MoveExpr |
move_data::MovePat =>
format!("cannot move out of `{}` because it is borrowed",
self.bccx.loan_path_to_str(&**move_path).as_slice())
};
self.bccx.span_err(span, err_message.as_slice());
self.bccx.span_note(
loan_span,
format!("borrow of `{}` occurs here",
self.bccx.loan_path_to_str(&*loan_path).as_slice())
.as_slice());
}
}
}
fn check_if_assigned_path_is_moved(&self,
id: ast::NodeId,
span: Span,
use_kind: MovedValueUseKind,
lp: &Rc<LoanPath>)
{
/*!
* 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: int;
* a = 10; // ok, even though a is uninitialized
*
* struct Point { x: uint, y: uint }
* let p: Point;
* p.x = 22; // ok, even though `p` is uninitialized
*
* let p: ~Point;
* (*p).x = 22; // not ok, p is uninitialized, can't deref
*/
match **lp {
LpVar(_) => {
// assigning to `x` does not require that `x` is initialized
}
LpExtend(ref lp_base, _, LpInterior(_)) => {
// 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, _, LpDeref(_)) => {
// assigning to `(*P)` requires that `P` be initialized
self.check_if_path_is_moved(id, span,
use_kind, lp_base);
}
}
}
fn check_if_path_is_moved(&self,
id: ast::NodeId,
span: Span,
use_kind: MovedValueUseKind,
lp: &Rc<LoanPath>) {
/*!
* Reports an error if `expr` (which should be a path)
* is using a moved/uninitialized value
@ -385,24 +564,23 @@ impl<'a> CheckLoanCtxt<'a> {
});
}
pub fn check_assignment(&self, expr: &ast::Expr) {
// We don't use cat_expr() here because we don't want to treat
// auto-ref'd parameters in overloaded operators as rvalues.
let cmt = match self.bccx.tcx.adjustments.borrow().find(&expr.id) {
None => self.bccx.cat_expr_unadjusted(expr),
Some(adj) => self.bccx.cat_expr_autoderefd(expr, adj)
};
debug!("check_assignment(cmt={})", cmt.repr(self.tcx()));
fn check_assignment(&self,
assignment_id: ast::NodeId,
assignment_span: Span,
assignee_cmt: mc::cmt,
mode: euv::MutateMode) {
debug!("check_assignment(assignee_cmt={})", assignee_cmt.repr(self.tcx()));
// 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.clone()) {
if check_for_assignment_to_restricted_or_frozen_location(
self, expr, cmt.clone()) {
if assignee_cmt.mutbl.is_mutable() {
if check_for_aliasable_mutable_writes(self, assignment_span, assignee_cmt.clone()) {
if mode != euv::Init &&
check_for_assignment_to_restricted_or_frozen_location(
self, assignment_id, assignment_span, assignee_cmt.clone())
{
// Safe, but record for lint pass later:
mark_variable_as_used_mut(self, cmt);
mark_variable_as_used_mut(self, assignee_cmt);
}
}
return;
@ -410,12 +588,12 @@ impl<'a> CheckLoanCtxt<'a> {
// For immutable local variables, assignments are legal
// if they cannot already have been assigned
if self.is_local_variable(cmt.clone()) {
assert!(cmt.mutbl.is_immutable()); // no "const" locals
let lp = opt_loan_path(&cmt).unwrap();
self.move_data.each_assignment_of(expr.id, &lp, |assign| {
if self.is_local_variable_or_arg(assignee_cmt.clone()) {
assert!(assignee_cmt.mutbl.is_immutable()); // no "const" locals
let lp = opt_loan_path(&assignee_cmt).unwrap();
self.move_data.each_assignment_of(assignment_id, &lp, |assign| {
self.bccx.report_reassigned_immutable_variable(
expr.span,
assignment_span,
&*lp,
assign);
false
@ -424,21 +602,21 @@ impl<'a> CheckLoanCtxt<'a> {
}
// Otherwise, just a plain error.
match opt_loan_path(&cmt) {
match opt_loan_path(&assignee_cmt) {
Some(lp) => {
self.bccx.span_err(
expr.span,
assignment_span,
format!("cannot assign to {} {} `{}`",
cmt.mutbl.to_user_str(),
self.bccx.cmt_to_str(&*cmt),
assignee_cmt.mutbl.to_user_str(),
self.bccx.cmt_to_str(&*assignee_cmt),
self.bccx.loan_path_to_str(&*lp)).as_slice());
}
None => {
self.bccx.span_err(
expr.span,
assignment_span,
format!("cannot assign to {} {}",
cmt.mutbl.to_user_str(),
self.bccx.cmt_to_str(&*cmt)).as_slice());
assignee_cmt.mutbl.to_user_str(),
self.bccx.cmt_to_str(&*assignee_cmt)).as_slice());
}
}
return;
@ -495,7 +673,7 @@ impl<'a> CheckLoanCtxt<'a> {
}
fn check_for_aliasable_mutable_writes(this: &CheckLoanCtxt,
expr: &ast::Expr,
span: Span,
cmt: mc::cmt) -> bool {
//! Safety checks related to writes to aliasable, mutable locations
@ -506,7 +684,7 @@ impl<'a> CheckLoanCtxt<'a> {
mc::cat_deref(ref b, _, mc::BorrowedPtr(ty::MutBorrow, _)) => {
// Statically prohibit writes to `&mut` when aliasable
check_for_aliasability_violation(this, expr, b.clone());
check_for_aliasability_violation(this, span, b.clone());
}
_ => {}
@ -516,7 +694,7 @@ impl<'a> CheckLoanCtxt<'a> {
}
fn check_for_aliasability_violation(this: &CheckLoanCtxt,
expr: &ast::Expr,
span: Span,
cmt: mc::cmt)
-> bool {
match cmt.freely_aliasable(this.tcx()) {
@ -528,7 +706,7 @@ impl<'a> CheckLoanCtxt<'a> {
}
Some(cause) => {
this.bccx.report_aliasability_violation(
expr.span,
span,
MutabilityViolation,
cause);
return false;
@ -538,13 +716,14 @@ impl<'a> CheckLoanCtxt<'a> {
fn check_for_assignment_to_restricted_or_frozen_location(
this: &CheckLoanCtxt,
expr: &ast::Expr,
cmt: mc::cmt) -> bool
assignment_id: ast::NodeId,
assignment_span: Span,
assignee_cmt: mc::cmt) -> bool
{
//! Check for assignments that violate the terms of an
//! outstanding loan.
let loan_path = match opt_loan_path(&cmt) {
let loan_path = match opt_loan_path(&assignee_cmt) {
Some(lp) => lp,
None => { return true; /* no loan path, can't be any loans */ }
};
@ -579,11 +758,11 @@ impl<'a> CheckLoanCtxt<'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.
let cont = this.each_in_scope_restriction(expr.id,
let cont = this.each_in_scope_restriction(assignment_id,
&*loan_path,
|loan, restr| {
if restr.set.intersects(RESTR_MUTATE) {
this.report_illegal_mutation(expr, &*loan_path, loan);
this.report_illegal_mutation(assignment_span, &*loan_path, loan);
false
} else {
true
@ -656,9 +835,9 @@ impl<'a> CheckLoanCtxt<'a> {
};
// Check for a non-const loan of `loan_path`
let cont = this.each_in_scope_loan(expr.id, |loan| {
let cont = this.each_in_scope_loan(assignment_id, |loan| {
if loan.loan_path == loan_path {
this.report_illegal_mutation(expr, &*full_loan_path, loan);
this.report_illegal_mutation(assignment_span, &*full_loan_path, loan);
false
} else {
true
@ -671,11 +850,11 @@ impl<'a> CheckLoanCtxt<'a> {
}
pub fn report_illegal_mutation(&self,
expr: &ast::Expr,
span: Span,
loan_path: &LoanPath,
loan: &Loan) {
self.bccx.span_err(
expr.span,
span,
format!("cannot assign to `{}` because it is borrowed",
self.bccx.loan_path_to_str(loan_path)).as_slice());
self.bccx.span_note(
@ -684,86 +863,6 @@ impl<'a> CheckLoanCtxt<'a> {
self.bccx.loan_path_to_str(loan_path)).as_slice());
}
fn check_move_out_from_expr(&self, expr: &ast::Expr) {
match expr.node {
ast::ExprFnBlock(..) | ast::ExprProc(..) => {
// Moves due to captures are checked in
// check_captured_variables() because it allows
// us to give a more precise error message with
// a more precise span.
}
_ => {
self.check_move_out_from_id(expr.id, expr.span)
}
}
}
fn check_move_out_from_id(&self, id: ast::NodeId, span: Span) {
self.move_data.each_path_moved_by(id, |_, move_path| {
match self.analyze_move_out_from(id, move_path) {
MoveOk => {}
MoveWhileBorrowed(loan_path, loan_span) => {
self.bccx.span_err(
span,
format!("cannot move out of `{}` \
because it is borrowed",
self.bccx.loan_path_to_str(
move_path)).as_slice());
self.bccx.span_note(
loan_span,
format!("borrow of `{}` occurs here",
self.bccx.loan_path_to_str(
&*loan_path)).as_slice());
}
}
true
});
}
fn check_captured_variables(&self,
closure_id: ast::NodeId,
span: Span) {
let freevar_mode = freevars::get_capture_mode(self.tcx(), closure_id);
freevars::with_freevars(self.tcx(), closure_id, |freevars| {
for freevar in freevars.iter() {
let var_id = ast_util::def_id_of_def(freevar.def).node;
let var_path = Rc::new(LpVar(var_id));
self.check_if_path_is_moved(closure_id, span,
MovedInCapture, &var_path);
match freevar_mode {
freevars::CaptureByRef => { }
freevars::CaptureByValue => {
check_by_move_capture(self, closure_id, freevar, &*var_path);
}
}
}
});
return;
fn check_by_move_capture(this: &CheckLoanCtxt,
closure_id: ast::NodeId,
freevar: &freevars::freevar_entry,
move_path: &LoanPath) {
let move_err = this.analyze_move_out_from(closure_id, move_path);
match move_err {
MoveOk => {}
MoveWhileBorrowed(loan_path, loan_span) => {
this.bccx.span_err(
freevar.span,
format!("cannot move `{}` into closure \
because it is borrowed",
this.bccx.loan_path_to_str(
move_path)).as_slice());
this.bccx.span_note(
loan_span,
format!("borrow of `{}` occurs here",
this.bccx.loan_path_to_str(
&*loan_path)).as_slice());
}
}
}
}
pub fn analyze_move_out_from(&self,
expr_id: ast::NodeId,
move_path: &LoanPath)
@ -794,89 +893,5 @@ impl<'a> CheckLoanCtxt<'a> {
}
}
}
pub fn check_call(&self,
_expr: &ast::Expr,
_callee: Option<@ast::Expr>,
_callee_span: Span,
_args: &[@ast::Expr]) {
// NB: This call to check for conflicting loans is not truly
// necessary, because the callee_id never issues new loans.
// However, I added it for consistency and lest the system
// should change in the future.
//
// FIXME(#6268) nested method calls
// self.check_for_conflicting_loans(callee_id);
}
}
fn check_loans_in_local<'a>(this: &mut CheckLoanCtxt<'a>,
local: &ast::Local) {
visit::walk_local(this, local, ());
}
fn check_loans_in_expr<'a>(this: &mut CheckLoanCtxt<'a>,
expr: &ast::Expr) {
visit::walk_expr(this, expr, ());
debug!("check_loans_in_expr(expr={})",
expr.repr(this.tcx()));
this.check_for_conflicting_loans(expr.id);
this.check_move_out_from_expr(expr);
let method_map = this.bccx.tcx.method_map.borrow();
match expr.node {
ast::ExprPath(..) => {
if !this.move_data.is_assignee(expr.id) {
let cmt = this.bccx.cat_expr_unadjusted(expr);
debug!("path cmt={}", cmt.repr(this.tcx()));
for lp in opt_loan_path(&cmt).iter() {
this.check_if_path_is_moved(expr.id, expr.span, MovedInUse, lp);
}
}
}
ast::ExprFnBlock(..) | ast::ExprProc(..) => {
this.check_captured_variables(expr.id, expr.span)
}
ast::ExprAssign(dest, _) |
ast::ExprAssignOp(_, dest, _) => {
this.check_assignment(dest);
}
ast::ExprCall(f, ref args) => {
this.check_call(expr, Some(f), f.span, args.as_slice());
}
ast::ExprMethodCall(_, _, ref args) => {
this.check_call(expr, None, expr.span, args.as_slice());
}
ast::ExprIndex(_, rval) | ast::ExprBinary(_, _, rval)
if method_map.contains_key(&MethodCall::expr(expr.id)) => {
this.check_call(expr, None, expr.span, [rval]);
}
ast::ExprUnary(_, _) | ast::ExprIndex(_, _)
if method_map.contains_key(&MethodCall::expr(expr.id)) => {
this.check_call(expr, None, expr.span, []);
}
ast::ExprInlineAsm(ref ia) => {
for &(_, out) in ia.outputs.iter() {
this.check_assignment(out);
}
}
_ => {}
}
}
fn check_loans_in_pat<'a>(this: &mut CheckLoanCtxt<'a>,
pat: &ast::Pat)
{
this.check_for_conflicting_loans(pat.id);
this.check_move_out_from_id(pat.id, pat.span);
visit::walk_pat(this, pat, ());
}
fn check_loans_in_block<'a>(this: &mut CheckLoanCtxt<'a>,
blk: &ast::Block)
{
visit::walk_block(this, blk, ());
this.check_for_conflicting_loans(blk.id);
}

View File

@ -45,10 +45,15 @@ pub fn gather_move_from_expr(bccx: &BorrowckCtxt,
move_data: &MoveData,
move_error_collector: &MoveErrorCollector,
move_expr_id: ast::NodeId,
cmt: mc::cmt) {
cmt: mc::cmt,
move_reason: euv::MoveReason) {
let kind = match move_reason {
euv::DirectRefMove | euv::PatBindingMove => MoveExpr,
euv::CaptureMove => Captured
};
let move_info = GatherMoveInfo {
id: move_expr_id,
kind: MoveExpr,
kind: kind,
cmt: cmt,
span_path_opt: None,
};

View File

@ -75,13 +75,13 @@ impl<'a> euv::Delegate for GatherLoanCtxt<'a> {
consume_id, cmt.repr(self.tcx()), mode);
match mode {
euv::Copy => { return; }
euv::Move => { }
euv::Move(move_reason) => {
gather_moves::gather_move_from_expr(
self.bccx, &self.move_data, &self.move_error_collector,
consume_id, cmt, move_reason);
}
euv::Copy => { }
}
gather_moves::gather_move_from_expr(
self.bccx, &self.move_data, &self.move_error_collector,
consume_id, cmt);
}
fn consume_pat(&mut self,
@ -95,7 +95,7 @@ impl<'a> euv::Delegate for GatherLoanCtxt<'a> {
match mode {
euv::Copy => { return; }
euv::Move => { }
euv::Move(_) => { }
}
gather_moves::gather_move_from_pat(

View File

@ -142,7 +142,7 @@ fn borrowck_fn(this: &mut BorrowckCtxt,
body);
check_loans::check_loans(this, &loan_dfcx, flowed_moves,
all_loans.as_slice(), body);
all_loans.as_slice(), decl, body);
visit::walk_fn(this, fk, decl, body, sp, ());
}

View File

@ -115,6 +115,7 @@ pub struct MovePath {
pub next_sibling: MovePathIndex,
}
#[deriving(PartialEq)]
pub enum MoveKind {
Declared, // When declared, variables start out "moved".
MoveExpr, // Expression or binding that moves a variable
@ -356,7 +357,7 @@ impl MoveData {
let path_index = self.move_path(tcx, lp.clone());
match mode {
euv::JustWrite => {
euv::Init | euv::JustWrite => {
self.assignee_ids.borrow_mut().insert(assignee_id);
}
euv::WriteAndRead => { }
@ -537,6 +538,28 @@ impl<'a> FlowedMoveData<'a> {
})
}
pub fn kind_of_move_of_path(&self,
id: ast::NodeId,
loan_path: &Rc<LoanPath>)
-> Option<MoveKind> {
//! Returns the kind of a move of `loan_path` by `id`, if one exists.
let mut ret = None;
for loan_path_index in self.move_data.path_map.borrow().find(&*loan_path).iter() {
self.dfcx_moves.each_gen_bit_frozen(id, |move_index| {
let move = self.move_data.moves.borrow();
let move = move.get(move_index);
if move.path == **loan_path_index {
ret = Some(move.kind);
false
} else {
true
}
});
}
ret
}
pub fn each_move_of(&self,
id: ast::NodeId,
loan_path: &Rc<LoanPath>,

View File

@ -80,12 +80,20 @@ pub enum LoanCause {
#[deriving(PartialEq,Show)]
pub enum ConsumeMode {
Copy, // reference to x where x has a type that copies
Move, // reference to x where x has a type that moves
Copy, // reference to x where x has a type that copies
Move(MoveReason), // reference to x where x has a type that moves
}
#[deriving(PartialEq,Show)]
pub enum MoveReason {
DirectRefMove,
PatBindingMove,
CaptureMove,
}
#[deriving(PartialEq,Show)]
pub enum MutateMode {
Init,
JustWrite, // x = y
WriteAndRead, // x += y
}
@ -160,7 +168,7 @@ impl<'d,'t,TYPER:mc::Typer> ExprUseVisitor<'d,'t,TYPER> {
consume_id: ast::NodeId,
consume_span: Span,
cmt: mc::cmt) {
let mode = copy_or_move(self.tcx(), cmt.ty);
let mode = copy_or_move(self.tcx(), cmt.ty, DirectRefMove);
self.delegate.consume(consume_id, consume_span, cmt, mode);
}
@ -712,7 +720,7 @@ impl<'d,'t,TYPER:mc::Typer> ExprUseVisitor<'d,'t,TYPER> {
let def = def_map.borrow().get_copy(&pat.id);
match mc.cat_def(pat.id, pat.span, pat_ty, def) {
Ok(binding_cmt) => {
delegate.mutate(pat.id, pat.span, binding_cmt, JustWrite);
delegate.mutate(pat.id, pat.span, binding_cmt, Init);
}
Err(_) => { }
}
@ -728,7 +736,7 @@ impl<'d,'t,TYPER:mc::Typer> ExprUseVisitor<'d,'t,TYPER> {
r, bk, RefBinding);
}
ast::PatIdent(ast::BindByValue(_), _, _) => {
let mode = copy_or_move(typer.tcx(), cmt_pat.ty);
let mode = copy_or_move(typer.tcx(), cmt_pat.ty, PatBindingMove);
delegate.consume_pat(pat, cmt_pat, mode);
}
_ => {
@ -834,7 +842,8 @@ impl<'d,'t,TYPER:mc::Typer> ExprUseVisitor<'d,'t,TYPER> {
let cmt_var = return_if_err!(self.cat_captured_var(closure_expr.id,
closure_expr.span,
freevar.def));
self.delegate_consume(closure_expr.id, freevar.span, cmt_var);
let mode = copy_or_move(self.tcx(), cmt_var.ty, CaptureMove);
self.delegate.consume(closure_expr.id, freevar.span, cmt_var, mode);
}
}
@ -851,7 +860,7 @@ impl<'d,'t,TYPER:mc::Typer> ExprUseVisitor<'d,'t,TYPER> {
}
}
fn copy_or_move(tcx: &ty::ctxt, ty: ty::t) -> ConsumeMode {
if ty::type_moves_by_default(tcx, ty) { Move } else { Copy }
fn copy_or_move(tcx: &ty::ctxt, ty: ty::t, move_reason: MoveReason) -> ConsumeMode {
if ty::type_moves_by_default(tcx, ty) { Move(move_reason) } else { Copy }
}

View File

@ -390,12 +390,10 @@ impl<'t,TYPER:Typer> MemCategorizationContext<'t,TYPER> {
Some(adjustment) => {
match *adjustment {
ty::AutoObject(..) => {
// Implicity casts a concrete object to trait object
// so just patch up the type
// Implicity cast a concrete object to trait object.
// Result is an rvalue.
let expr_ty = if_ok!(self.expr_ty_adjusted(expr));
let mut expr_cmt = (*if_ok!(self.cat_expr_unadjusted(expr))).clone();
expr_cmt.ty = expr_ty;
Ok(Rc::new(expr_cmt))
Ok(self.cat_rvalue_node(expr.id(), expr.span(), expr_ty))
}
ty::AutoAddEnv(..) => {

View File

@ -0,0 +1,124 @@
// Copyright 2014 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.
struct A { a: int, b: Box<int> }
fn borrow<T>(_: &T) { }
fn use_after_move() {
let x = A { a: 1, b: box 2 };
drop(x.b);
drop(*x.b); //~ ERROR use of partially moved value: `*x.b`
}
fn use_after_fu_move() {
let x = A { a: 1, b: box 2 };
let y = A { a: 3, .. x };
drop(*x.b); //~ ERROR use of partially moved value: `*x.b`
}
fn borrow_after_move() {
let x = A { a: 1, b: box 2 };
drop(x.b);
borrow(&x.b); //~ ERROR use of moved value: `x.b`
}
fn borrow_after_fu_move() {
let x = A { a: 1, b: box 2 };
let _y = A { a: 3, .. x };
borrow(&x.b); //~ ERROR use of moved value: `x.b`
}
fn move_after_borrow() {
let x = A { a: 1, b: box 2 };
let y = &x.b;
drop(x.b); //~ ERROR cannot move out of `x.b` because it is borrowed
borrow(&*y);
}
fn fu_move_after_borrow() {
let x = A { a: 1, b: box 2 };
let y = &x.b;
let _z = A { a: 3, .. x }; //~ ERROR cannot move out of `x.b` because it is borrowed
borrow(&*y);
}
fn mut_borrow_after_mut_borrow() {
let mut x = A { a: 1, b: box 2 };
let y = &mut x.a;
let z = &mut x.a; //~ ERROR cannot borrow `x.a` as mutable more than once at a time
drop(*y);
drop(*z);
}
fn move_after_move() {
let x = A { a: 1, b: box 2 };
drop(x.b);
drop(x.b); //~ ERROR use of moved value: `x.b`
}
fn move_after_fu_move() {
let x = A { a: 1, b: box 2 };
let _y = A { a: 3, .. x };
drop(x.b); //~ ERROR use of moved value: `x.b`
}
fn fu_move_after_move() {
let x = A { a: 1, b: box 2 };
drop(x.b);
let _z = A { a: 3, .. x }; //~ ERROR use of moved value: `x.b`
}
fn fu_move_after_fu_move() {
let x = A { a: 1, b: box 2 };
let _y = A { a: 3, .. x };
let _z = A { a: 4, .. x }; //~ ERROR use of moved value: `x.b`
}
// The following functions aren't yet accepted, but they should be.
fn use_after_field_assign_after_uninit() {
let mut x: A;
x.a = 1;
drop(x.a); //~ ERROR use of possibly uninitialized variable: `x.a`
}
fn borrow_after_field_assign_after_uninit() {
let mut x: A;
x.a = 1;
borrow(&x.a); //~ ERROR use of possibly uninitialized variable: `x.a`
}
fn move_after_field_assign_after_uninit() {
let mut x: A;
x.b = box 1;
drop(x.b); //~ ERROR use of possibly uninitialized variable: `x.b`
}
fn main() {
use_after_move();
use_after_fu_move();
borrow_after_move();
borrow_after_fu_move();
move_after_borrow();
fu_move_after_borrow();
mut_borrow_after_mut_borrow();
move_after_move();
move_after_fu_move();
fu_move_after_move();
fu_move_after_fu_move();
use_after_field_assign_after_uninit();
borrow_after_field_assign_after_uninit();
move_after_field_assign_after_uninit();
}

View File

@ -16,6 +16,6 @@ struct point {
fn main() {
let mut origin: point;
origin = point {x: 10,.. origin}; //~ ERROR use of possibly uninitialized variable: `origin`
origin = point {x: 10,.. origin}; //~ ERROR use of possibly uninitialized variable: `origin.y`
origin.clone();
}

View File

@ -0,0 +1,64 @@
// Copyright 2014 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.
use std::task;
fn borrow<T>(_: &T) { }
fn different_vars_after_borrows() {
let x1 = box 1;
let p1 = &x1;
let x2 = box 2;
let p2 = &x2;
task::spawn(proc() {
drop(x1); //~ ERROR cannot move `x1` into closure because it is borrowed
drop(x2); //~ ERROR cannot move `x2` into closure because it is borrowed
});
borrow(&*p1);
borrow(&*p2);
}
fn different_vars_after_moves() {
let x1 = box 1;
drop(x1);
let x2 = box 2;
drop(x2);
task::spawn(proc() {
drop(x1); //~ ERROR capture of moved value: `x1`
drop(x2); //~ ERROR capture of moved value: `x2`
});
}
fn same_var_after_borrow() {
let x = box 1;
let p = &x;
task::spawn(proc() {
drop(x); //~ ERROR cannot move `x` into closure because it is borrowed
drop(x); //~ ERROR use of moved value: `x`
});
borrow(&*p);
}
fn same_var_after_move() {
let x = box 1;
drop(x);
task::spawn(proc() {
drop(x); //~ ERROR capture of moved value: `x`
drop(x); //~ ERROR use of moved value: `x`
});
}
fn main() {
different_vars_after_borrows();
different_vars_after_moves();
same_var_after_borrow();
same_var_after_move();
}

View File

@ -13,6 +13,6 @@ extern crate debug;
fn main() {
let x = box 5;
let y = x;
println!("{:?}", *x); //~ ERROR use of moved value: `x`
println!("{:?}", *x); //~ ERROR use of partially moved value: `*x`
y.clone();
}

View File

@ -17,12 +17,6 @@ struct Foo { f: String, y: int }
fn consume(_s: String) {}
fn touch<A>(_a: &A) {}
fn f10() {
let x = Foo { f: "hi".to_string(), y: 3 };
consume(x.f);
touch(&x.y); //~ ERROR use of partially moved value: `x`
}
fn f20() {
let x = vec!("hi".to_string());
consume(x.move_iter().next().unwrap());

View File

@ -26,13 +26,7 @@ fn test0(f: Foo, g: Noncopyable, h: Noncopyable) {
fn test1(f: Foo, g: Noncopyable, h: Noncopyable) {
// copying move-by-default fields from `f`, so move:
let _b = Foo {noncopyable: g, ..f};
let _c = Foo {noncopyable: h, ..f}; //~ ERROR use of partially moved value: `f`
}
fn test2(f: Foo, g: Noncopyable) {
// move non-copyable field
let _b = Foo {copied: 22, moved: box 23, ..f};
let _c = Foo {noncopyable: g, ..f}; //~ ERROR use of partially moved value: `f`
let _c = Foo {noncopyable: h, ..f}; //~ ERROR use of moved value: `f.moved`
}
fn main() {}

View File

@ -19,7 +19,7 @@ impl Drop for S {
impl S {
pub fn foo(self) -> int {
self.bar();
return self.x; //~ ERROR use of moved value: `self`
return self.x; //~ ERROR use of partially moved value: `self.x`
}
pub fn bar(self) {}

View File

@ -16,7 +16,7 @@ struct S {
impl S {
pub fn foo(self) -> int {
self.bar();
return *self.x; //~ ERROR use of moved value: `self`
return *self.x; //~ ERROR use of partially moved value: `*self.x`
}
pub fn bar(self) {}

View File

@ -0,0 +1,223 @@
// Copyright 2014 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.
struct A { a: int, b: Box<int> }
struct B { a: Box<int>, b: Box<int> }
fn borrow<T>(_: &T) { }
fn move_after_use() {
let x = A { a: 1, b: box 2 };
drop(x.a);
drop(x.b);
}
fn move_after_fu_use() {
let x = A { a: 1, b: box 2 };
let _y = A { b: box 3, .. x };
drop(x.b);
}
fn fu_move_after_use() {
let x = A { a: 1, b: box 2 };
drop(x.a);
let y = A { a: 3, .. x };
drop(y.b);
}
fn fu_move_after_fu_use() {
let x = A { a: 1, b: box 2 };
let _y = A { b: box 3, .. x };
let z = A { a: 4, .. x };
drop(z.b);
}
fn use_after_move() {
let x = A { a: 1, b: box 2 };
drop(x.b);
drop(x.a);
}
fn use_after_fu_move() {
let x = A { a: 1, b: box 2 };
let y = A { a: 3, .. x };
drop(x.a);
drop(y.b);
}
fn fu_use_after_move() {
let x = A { a: 1, b: box 2 };
drop(x.b);
let _y = A { b: box 3, .. x };
}
fn fu_use_after_fu_move() {
let x = A { a: 1, b: box 2 };
let y = A { a: 3, .. x };
let _z = A { b: box 3, .. x };
drop(y.b);
}
fn borrow_after_move() {
let x = A { a: 1, b: box 2 };
drop(x.b);
borrow(&x.a);
}
fn borrow_after_fu_move() {
let x = A { a: 1, b: box 2 };
let y = A { a: 3, .. x };
borrow(&x.a);
drop(y.b);
}
fn move_after_borrow() {
let x = A { a: 1, b: box 2 };
borrow(&x.a);
drop(x.b);
}
fn fu_move_after_borrow() {
let x = A { a: 1, b: box 2 };
borrow(&x.a);
let y = A { a: 3, .. x };
drop(y.b);
}
fn mut_borrow_after_mut_borrow() {
let mut x = A { a: 1, b: box 2 };
let y = &mut x.a;
let z = &mut x.b;
drop(*y);
drop(**z);
}
fn move_after_move() {
let x = B { a: box 1, b: box 2 };
drop(x.a);
drop(x.b);
}
fn move_after_fu_move() {
let x = B { a: box 1, b: box 2 };
let y = B { a: box 3, .. x };
drop(x.a);
drop(y.b);
}
fn fu_move_after_move() {
let x = B { a: box 1, b: box 2 };
drop(x.a);
let z = B { a: box 3, .. x };
drop(z.b);
}
fn fu_move_after_fu_move() {
let x = B { a: box 1, b: box 2 };
let y = B { b: box 3, .. x };
let z = B { a: box 4, .. x };
drop(y.a);
drop(z.b);
}
fn use_after_assign_after_move() {
let mut x = A { a: 1, b: box 2 };
drop(x.b);
x = A { a: 3, b: box 4 };
drop(*x.b);
}
fn use_after_field_assign_after_move() {
let mut x = A { a: 1, b: box 2 };
drop(x.b);
x.b = box 3;
drop(*x.b);
}
fn borrow_after_assign_after_move() {
let mut x = A { a: 1, b: box 2 };
drop(x.b);
x = A { a: 3, b: box 4 };
borrow(&x.b);
}
fn borrow_after_field_assign_after_move() {
let mut x = A { a: 1, b: box 2 };
drop(x.b);
x.b = box 3;
borrow(&x.b);
}
fn move_after_assign_after_move() {
let mut x = A { a: 1, b: box 2 };
let y = x.b;
x = A { a: 3, b: box 4 };
drop(x.b);
drop(y);
}
fn move_after_field_assign_after_move() {
let mut x = A { a: 1, b: box 2 };
drop(x.b);
x.b = box 3;
drop(x.b);
}
fn use_after_assign_after_uninit() {
let mut x: A;
x = A { a: 1, b: box 2 };
drop(x.a);
}
fn borrow_after_assign_after_uninit() {
let mut x: A;
x = A { a: 1, b: box 2 };
borrow(&x.a);
}
fn move_after_assign_after_uninit() {
let mut x: A;
x = A { a: 1, b: box 2 };
drop(x.b);
}
fn main() {
move_after_use();
move_after_fu_use();
fu_move_after_use();
fu_move_after_fu_use();
use_after_move();
use_after_fu_move();
fu_use_after_move();
fu_use_after_fu_move();
borrow_after_move();
borrow_after_fu_move();
move_after_borrow();
fu_move_after_borrow();
mut_borrow_after_mut_borrow();
move_after_move();
move_after_fu_move();
fu_move_after_move();
fu_move_after_fu_move();
use_after_assign_after_move();
use_after_field_assign_after_move();
borrow_after_assign_after_move();
borrow_after_field_assign_after_move();
move_after_assign_after_move();
move_after_field_assign_after_move();
use_after_assign_after_uninit();
borrow_after_assign_after_uninit();
move_after_assign_after_uninit();
}