auto merge of #6784 : nikomatsakis/rust/moves-into-borrowck, r=pcwalton

Move the computation of what data is moved out of `liveness` and into `borrowck`. The resulting code is cleaner, since before we had a split distribution of responsibilities, and also this avoids having multiple implementations of the dataflow code. Liveness is still used to report warnings about useless writes. This will go away when we get the control-flow graph code landed (working on that).

Also adds borrow checker documentation.

Fixes #4384.
Required to support once fns and to properly fix closures (#2202).
First step to generalize our treatment of moves somewhat as well.
This commit is contained in:
bors 2013-05-28 22:16:50 -07:00
commit d80642b519
48 changed files with 2197 additions and 1357 deletions

View File

@ -152,15 +152,15 @@ fn cmd_help(args: &[~str]) -> ValidUsage {
}
match args {
[command_string] => print_usage(command_string),
_ => Invalid
[ref command_string] => print_usage(copy *command_string),
_ => Invalid
}
}
fn cmd_test(args: &[~str]) -> ValidUsage {
match args {
[filename] => {
let test_exec = Path(filename).filestem().unwrap() + "test~";
[ref filename] => {
let test_exec = Path(*filename).filestem().unwrap() + "test~";
invoke("rustc", &[~"--test", filename.to_owned(),
~"-o", test_exec.to_owned()], rustc::main);
let exit_code = run::process_status(~"./" + test_exec, []);
@ -172,8 +172,8 @@ fn cmd_test(args: &[~str]) -> ValidUsage {
fn cmd_run(args: &[~str]) -> ValidUsage {
match args {
[filename, ..prog_args] => {
let exec = Path(filename).filestem().unwrap() + "~";
[ref filename, ..prog_args] => {
let exec = Path(*filename).filestem().unwrap() + "~";
invoke("rustc", &[filename.to_owned(), ~"-o", exec.to_owned()],
rustc::main);
let exit_code = run::process_status(~"./"+exec, prog_args);

View File

@ -263,8 +263,8 @@ pub fn compile_rest(sess: Session,
time(time_passes, ~"loop checking", ||
middle::check_loop::check_crate(ty_cx, crate));
let middle::moves::MoveMaps {moves_map, variable_moves_map,
moved_variables_set, capture_map} =
let middle::moves::MoveMaps {moves_map, moved_variables_set,
capture_map} =
time(time_passes, ~"compute moves", ||
middle::moves::compute_moves(ty_cx, method_map, crate));
@ -274,7 +274,6 @@ pub fn compile_rest(sess: Session,
time(time_passes, ~"liveness checking", ||
middle::liveness::check_crate(ty_cx, method_map,
variable_moves_map,
capture_map, crate));
let (root_map, write_guard_map) =

View File

@ -33,20 +33,23 @@ use syntax::codemap::span;
struct CheckLoanCtxt<'self> {
bccx: @BorrowckCtxt,
dfcx: &'self LoanDataFlow,
dfcx_loans: &'self LoanDataFlow,
move_data: move_data::FlowedMoveData,
all_loans: &'self [Loan],
reported: @mut HashSet<ast::node_id>,
}
pub fn check_loans(bccx: @BorrowckCtxt,
dfcx: &LoanDataFlow,
dfcx_loans: &LoanDataFlow,
move_data: move_data::FlowedMoveData,
all_loans: &[Loan],
body: &ast::blk) {
debug!("check_loans(body id=%?)", body.node.id);
let clcx = @mut CheckLoanCtxt {
bccx: bccx,
dfcx: dfcx,
dfcx_loans: dfcx_loans,
move_data: move_data,
all_loans: all_loans,
reported: @mut HashSet::new(),
};
@ -62,8 +65,7 @@ pub fn check_loans(bccx: @BorrowckCtxt,
enum MoveError {
MoveOk,
MoveFromIllegalCmt(mc::cmt),
MoveWhileBorrowed(/*loan*/@LoanPath, /*loan*/span)
MoveWhileBorrowed(/*move*/@LoanPath, /*loan*/@LoanPath, /*loan*/span)
}
pub impl<'self> CheckLoanCtxt<'self> {
@ -79,7 +81,7 @@ pub impl<'self> CheckLoanCtxt<'self> {
//! are issued for future scopes and thus they may have been
//! *issued* but not yet be in effect.
for self.dfcx.each_bit_on_entry(scope_id) |loan_index| {
for self.dfcx_loans.each_bit_on_entry(scope_id) |loan_index| {
let loan = &self.all_loans[loan_index];
if !op(loan) {
return false;
@ -131,7 +133,7 @@ pub impl<'self> CheckLoanCtxt<'self> {
//! we encounter `scope_id`.
let mut result = ~[];
for self.dfcx.each_gen_bit(scope_id) |loan_index| {
for self.dfcx_loans.each_gen_bit(scope_id) |loan_index| {
result.push(loan_index);
}
return result;
@ -198,9 +200,9 @@ pub impl<'self> CheckLoanCtxt<'self> {
loan1.repr(self.tcx()),
loan2.repr(self.tcx()));
// Restrictions that would cause the new loan to be immutable:
// Restrictions that would cause the new loan to be illegal:
let illegal_if = match loan2.mutbl {
m_mutbl => RESTR_ALIAS | RESTR_FREEZE | RESTR_MUTATE,
m_mutbl => RESTR_ALIAS | RESTR_FREEZE | RESTR_CLAIM,
m_imm => RESTR_ALIAS | RESTR_FREEZE,
m_const => RESTR_ALIAS,
};
@ -251,6 +253,29 @@ pub impl<'self> CheckLoanCtxt<'self> {
}
}
fn check_if_path_is_moved(&self,
id: ast::node_id,
span: span,
use_kind: MovedValueUseKind,
lp: @LoanPath) {
/*!
* Reports an error if `expr` (which should be a path)
* is using a moved/uninitialized value
*/
debug!("check_if_path_is_moved(id=%?, use_kind=%?, lp=%s)",
id, use_kind, lp.repr(self.bccx.tcx));
for self.move_data.each_move_of(id, lp) |move, moved_lp| {
self.bccx.report_use_of_moved_value(
span,
use_kind,
lp,
move,
moved_lp);
return;
}
}
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.
@ -261,49 +286,43 @@ pub impl<'self> CheckLoanCtxt<'self> {
debug!("check_assignment(cmt=%s)", cmt.repr(self.tcx()));
// check that the value being assigned is declared as mutable
// and report an error otherwise.
match cmt.mutbl {
mc::McDeclared => {
// OK, but we have to mark arguments as requiring mut
// if they are assigned (other cases are handled by liveness,
// since we need to distinguish local variables assigned
// once vs those assigned multiple times)
match cmt.cat {
mc::cat_self(*) |
mc::cat_arg(*) => {
mark_variable_as_used_mut(self, cmt);
}
_ => {}
// Mutable values can be assigned, as long as they obey loans
// and aliasing restrictions:
if cmt.mutbl.is_mutable() {
if check_for_aliasable_mutable_writes(self, expr, cmt) {
if check_for_assignment_to_restricted_or_frozen_location(
self, expr, cmt)
{
// Safe, but record for lint pass later:
mark_variable_as_used_mut(self, cmt);
}
}
mc::McInherited => {
// OK, but we may have to add an entry to `used_mut_nodes`
mark_variable_as_used_mut(self, cmt);
}
mc::McReadOnly | mc::McImmutable => {
// Subtle: liveness guarantees that immutable local
// variables are only assigned once, so no need to
// report an error for an assignment to a local
// variable (note also that it is not legal to borrow
// for a local variable before it has been assigned
// for the first time).
if !self.is_local_variable(cmt) {
self.bccx.span_err(
expr.span,
fmt!("cannot assign to %s %s"
cmt.mutbl.to_user_str(),
self.bccx.cmt_to_str(cmt)));
}
return;
}
return;
}
if check_for_aliasable_mutable_writes(self, expr, cmt) {
check_for_assignment_to_restricted_or_frozen_location(
self, expr, cmt);
// For immutable local variables, assignments are legal
// if they cannot already have been assigned
if self.is_local_variable(cmt) {
assert!(cmt.mutbl.is_immutable()); // no "const" locals
let lp = opt_loan_path(cmt).get();
for self.move_data.each_assignment_of(expr.id, lp) |assign| {
self.bccx.report_reassigned_immutable_variable(
expr.span,
lp,
assign);
return;
}
return;
}
// Otherwise, just a plain error.
self.bccx.span_err(
expr.span,
fmt!("cannot assign to %s %s"
cmt.mutbl.to_user_str(),
self.bccx.cmt_to_str(cmt)));
return;
fn mark_variable_as_used_mut(this: &CheckLoanCtxt,
cmt: mc::cmt) {
//! If the mutability of the `cmt` being written is inherited
@ -538,18 +557,12 @@ pub impl<'self> CheckLoanCtxt<'self> {
let cmt = self.bccx.cat_expr(ex);
match self.analyze_move_out_from_cmt(cmt) {
MoveOk => {}
MoveFromIllegalCmt(_) => {
self.bccx.span_err(
cmt.span,
fmt!("cannot move out of %s",
self.bccx.cmt_to_str(cmt)));
}
MoveWhileBorrowed(loan_path, loan_span) => {
MoveWhileBorrowed(move_path, loan_path, loan_span) => {
self.bccx.span_err(
cmt.span,
fmt!("cannot move out of `%s` \
because it is borrowed",
self.bccx.loan_path_to_str(loan_path)));
self.bccx.loan_path_to_str(move_path)));
self.bccx.span_note(
loan_span,
fmt!("borrow of `%s` occurs here",
@ -561,29 +574,7 @@ pub impl<'self> CheckLoanCtxt<'self> {
}
fn analyze_move_out_from_cmt(&self, cmt: mc::cmt) -> MoveError {
debug!("check_move_out_from_cmt(cmt=%s)", cmt.repr(self.tcx()));
match cmt.cat {
// Rvalues, locals, and arguments can be moved:
mc::cat_rvalue | mc::cat_local(_) |
mc::cat_arg(_) | mc::cat_self(_) => {}
// It seems strange to allow a move out of a static item,
// but what happens in practice is that you have a
// reference to a constant with a type that should be
// moved, like `None::<~int>`. The type of this constant
// is technically `Option<~int>`, which moves, but we know
// that the content of static items will never actually
// contain allocated pointers, so we can just memcpy it.
mc::cat_static_item => {}
mc::cat_deref(_, _, mc::unsafe_ptr(*)) => {}
// Nothing else.
_ => {
return MoveFromIllegalCmt(cmt);
}
}
debug!("analyze_move_out_from_cmt(cmt=%s)", cmt.repr(self.tcx()));
// FIXME(#4384) inadequare if/when we permit `move a.b`
@ -591,7 +582,7 @@ pub impl<'self> CheckLoanCtxt<'self> {
for opt_loan_path(cmt).each |&lp| {
for self.each_in_scope_restriction(cmt.id, lp) |loan, _| {
// Any restriction prevents moves.
return MoveWhileBorrowed(loan.loan_path, loan.span);
return MoveWhileBorrowed(lp, loan.loan_path, loan.span);
}
}
@ -631,54 +622,53 @@ fn check_loans_in_fn<'a>(fk: &visit::fn_kind,
visit::fk_anon(*) |
visit::fk_fn_block(*) => {
let fty = ty::node_id_to_type(this.tcx(), id);
let fty_sigil = ty::ty_closure_sigil(fty);
check_moves_from_captured_variables(this, id, fty_sigil);
check_captured_variables(this, id, sp);
}
}
visit::visit_fn(fk, decl, body, sp, id, this, visitor);
fn check_moves_from_captured_variables(this: @mut CheckLoanCtxt,
id: ast::node_id,
fty_sigil: ast::Sigil) {
match fty_sigil {
ast::ManagedSigil | ast::OwnedSigil => {
let cap_vars = this.bccx.capture_map.get(&id);
for cap_vars.each |cap_var| {
match cap_var.mode {
moves::CapRef | moves::CapCopy => { loop; }
moves::CapMove => { }
}
let def_id = ast_util::def_id_of_def(cap_var.def).node;
let ty = ty::node_id_to_type(this.tcx(), def_id);
let cmt = this.bccx.cat_def(id, cap_var.span,
ty, cap_var.def);
let move_err = this.analyze_move_out_from_cmt(cmt);
match move_err {
MoveOk => {}
MoveFromIllegalCmt(move_cmt) => {
this.bccx.span_err(
cap_var.span,
fmt!("illegal by-move capture of %s",
this.bccx.cmt_to_str(move_cmt)));
}
MoveWhileBorrowed(loan_path, loan_span) => {
this.bccx.span_err(
cap_var.span,
fmt!("cannot move `%s` into closure \
because it is borrowed",
this.bccx.loan_path_to_str(loan_path)));
this.bccx.span_note(
loan_span,
fmt!("borrow of `%s` occurs here",
this.bccx.loan_path_to_str(loan_path)));
}
}
fn check_captured_variables(this: @mut CheckLoanCtxt,
closure_id: ast::node_id,
span: span) {
let cap_vars = this.bccx.capture_map.get(&closure_id);
for cap_vars.each |cap_var| {
match cap_var.mode {
moves::CapRef | moves::CapCopy => {
let var_id = ast_util::def_id_of_def(cap_var.def).node;
let lp = @LpVar(var_id);
this.check_if_path_is_moved(closure_id, span,
MovedInCapture, lp);
}
moves::CapMove => {
check_by_move_capture(this, closure_id, cap_var);
}
}
}
return;
ast::BorrowedSigil => {}
fn check_by_move_capture(this: @mut CheckLoanCtxt,
closure_id: ast::node_id,
cap_var: &moves::CaptureVar) {
let var_id = ast_util::def_id_of_def(cap_var.def).node;
let ty = ty::node_id_to_type(this.tcx(), var_id);
let cmt = this.bccx.cat_def(closure_id, cap_var.span,
ty, cap_var.def);
let move_err = this.analyze_move_out_from_cmt(cmt);
match move_err {
MoveOk => {}
MoveWhileBorrowed(move_path, loan_path, loan_span) => {
this.bccx.span_err(
cap_var.span,
fmt!("cannot move `%s` into closure \
because it is borrowed",
this.bccx.loan_path_to_str(move_path)));
this.bccx.span_note(
loan_span,
fmt!("borrow of `%s` occurs here",
this.bccx.loan_path_to_str(loan_path)));
}
}
}
}
}
@ -692,11 +682,11 @@ fn check_loans_in_local<'a>(local: @ast::local,
fn check_loans_in_expr<'a>(expr: @ast::expr,
this: @mut CheckLoanCtxt<'a>,
vt: visit::vt<@mut CheckLoanCtxt<'a>>) {
visit::visit_expr(expr, this, vt);
debug!("check_loans_in_expr(expr=%s)",
expr.repr(this.tcx()));
visit::visit_expr(expr, this, vt);
this.check_for_conflicting_loans(expr.id);
if this.bccx.moves_map.contains(&expr.id) {
@ -704,6 +694,17 @@ fn check_loans_in_expr<'a>(expr: @ast::expr,
}
match expr.node {
ast::expr_self |
ast::expr_path(*) => {
if !this.move_data.is_assignee(expr.id) {
let cmt = this.bccx.cat_expr_unadjusted(expr);
debug!("path cmt=%s", cmt.repr(this.tcx()));
for opt_loan_path(cmt).each |&lp| {
this.check_if_path_is_moved(expr.id, expr.span,
MovedInUse, lp);
}
}
}
ast::expr_assign(dest, _) |
ast::expr_assign_op(_, dest, _) => {
this.check_assignment(dest);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,164 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
/*!
* Computes moves.
*/
use core::prelude::*;
use mc = middle::mem_categorization;
use middle::borrowck::*;
use middle::borrowck::move_data::*;
use middle::moves;
use middle::ty;
use syntax::ast;
use syntax::ast_util;
use syntax::codemap::span;
use util::ppaux::{UserString};
pub fn gather_decl(bccx: @BorrowckCtxt,
move_data: &mut MoveData,
decl_id: ast::node_id,
_decl_span: span,
var_id: ast::node_id) {
let loan_path = @LpVar(var_id);
move_data.add_move(bccx.tcx, loan_path, decl_id, Declared);
}
pub fn gather_move_from_expr(bccx: @BorrowckCtxt,
move_data: &mut MoveData,
move_expr: @ast::expr,
cmt: mc::cmt) {
gather_move_from_expr_or_pat(bccx, move_data, move_expr.id,
MoveExpr(move_expr), cmt);
}
pub fn gather_move_from_pat(bccx: @BorrowckCtxt,
move_data: &mut MoveData,
move_pat: @ast::pat,
cmt: mc::cmt) {
gather_move_from_expr_or_pat(bccx, move_data, move_pat.id,
MovePat(move_pat), cmt);
}
fn gather_move_from_expr_or_pat(bccx: @BorrowckCtxt,
move_data: &mut MoveData,
move_id: ast::node_id,
move_kind: MoveKind,
cmt: mc::cmt) {
if !check_is_legal_to_move_from(bccx, cmt, cmt) {
return;
}
match opt_loan_path(cmt) {
Some(loan_path) => {
move_data.add_move(bccx.tcx, loan_path, move_id, move_kind);
}
None => {
// move from rvalue or unsafe pointer, hence ok
}
}
}
pub fn gather_captures(bccx: @BorrowckCtxt,
move_data: &mut MoveData,
closure_expr: @ast::expr) {
let captured_vars = bccx.capture_map.get(&closure_expr.id);
for captured_vars.each |captured_var| {
match captured_var.mode {
moves::CapMove => {
let fvar_id = ast_util::def_id_of_def(captured_var.def).node;
let loan_path = @LpVar(fvar_id);
move_data.add_move(bccx.tcx, loan_path, closure_expr.id,
Captured(closure_expr));
}
moves::CapCopy | moves::CapRef => {}
}
}
}
pub fn gather_assignment(bccx: @BorrowckCtxt,
move_data: &mut MoveData,
assignment_id: ast::node_id,
assignment_span: span,
assignee_loan_path: @LoanPath,
assignee_id: ast::node_id) {
move_data.add_assignment(bccx.tcx,
assignee_loan_path,
assignment_id,
assignment_span,
assignee_id);
}
fn check_is_legal_to_move_from(bccx: @BorrowckCtxt,
cmt0: mc::cmt,
cmt: mc::cmt) -> bool {
match cmt.cat {
mc::cat_stack_upvar(*) |
mc::cat_implicit_self(*) |
mc::cat_copied_upvar(*) |
mc::cat_deref(_, _, mc::region_ptr(*)) |
mc::cat_deref(_, _, mc::gc_ptr(*)) => {
bccx.span_err(
cmt0.span,
fmt!("cannot move out of %s",
bccx.cmt_to_str(cmt)));
false
}
// It seems strange to allow a move out of a static item,
// but what happens in practice is that you have a
// reference to a constant with a type that should be
// moved, like `None::<~int>`. The type of this constant
// is technically `Option<~int>`, which moves, but we know
// that the content of static items will never actually
// contain allocated pointers, so we can just memcpy it.
// Since static items can never have allocated memory,
// this is ok. For now anyhow.
mc::cat_static_item => {
true
}
mc::cat_rvalue(*) |
mc::cat_local(*) |
mc::cat_arg(*) |
mc::cat_self(*) |
mc::cat_deref(_, _, mc::unsafe_ptr(*)) => {
true
}
mc::cat_downcast(b) |
mc::cat_interior(b, _) => {
match ty::get(b.ty).sty {
ty::ty_struct(did, _) | ty::ty_enum(did, _) => {
if ty::has_dtor(bccx.tcx, did) {
bccx.span_err(
cmt0.span,
fmt!("cannot move out of type `%s`, \
which defines the `Drop` trait",
b.ty.user_string(bccx.tcx)));
false
} else {
check_is_legal_to_move_from(bccx, cmt0, b)
}
}
_ => {
check_is_legal_to_move_from(bccx, cmt0, b)
}
}
}
mc::cat_deref(b, _, mc::uniq_ptr(*)) |
mc::cat_discr(b, _) => {
check_is_legal_to_move_from(bccx, cmt0, b)
}
}
}

View File

@ -70,11 +70,11 @@ impl GuaranteeLifetimeContext {
match cmt.cat {
mc::cat_rvalue |
mc::cat_implicit_self |
mc::cat_copied_upvar(*) |
mc::cat_local(*) |
mc::cat_arg(*) |
mc::cat_self(*) |
mc::cat_deref(_, _, mc::region_ptr(*)) |
mc::cat_copied_upvar(*) | // L-Local
mc::cat_local(*) | // L-Local
mc::cat_arg(*) | // L-Local
mc::cat_self(*) | // L-Local
mc::cat_deref(_, _, mc::region_ptr(*)) | // L-Deref-Borrowed
mc::cat_deref(_, _, mc::unsafe_ptr) => {
let scope = self.scope(cmt);
self.check_scope(scope)
@ -90,7 +90,7 @@ impl GuaranteeLifetimeContext {
mc::cat_deref(base, derefs, mc::gc_ptr(ptr_mutbl)) => {
let base_scope = self.scope(base);
// See rule Freeze-Imm-Managed-Ptr-2 in doc.rs
// L-Deref-Managed-Imm-User-Root
let omit_root = (
ptr_mutbl == m_imm &&
self.bccx.is_subregion_of(self.loan_region, base_scope) &&
@ -99,6 +99,8 @@ impl GuaranteeLifetimeContext {
);
if !omit_root {
// L-Deref-Managed-Imm-Compiler-Root
// L-Deref-Managed-Mut-Compiler-Root
self.check_root(cmt, base, derefs, ptr_mutbl, discr_scope);
} else {
debug!("omitting root, base=%s, base_scope=%?",
@ -107,8 +109,8 @@ impl GuaranteeLifetimeContext {
}
mc::cat_downcast(base) |
mc::cat_deref(base, _, mc::uniq_ptr(*)) |
mc::cat_interior(base, _) => {
mc::cat_deref(base, _, mc::uniq_ptr(*)) | // L-Deref-Owned
mc::cat_interior(base, _) => { // L-Field
self.check(base, discr_scope)
}
@ -321,6 +323,8 @@ impl GuaranteeLifetimeContext {
//! lvalue `cmt` is guaranteed to be valid without any
//! rooting etc, and presuming `cmt` is not mutated.
// See the SCOPE(LV) function in doc.rs
match cmt.cat {
mc::cat_rvalue => {
ty::re_scope(self.bccx.tcx.region_maps.cleanup_scope(cmt.id))

View File

@ -19,6 +19,7 @@
use core::prelude::*;
use middle::borrowck::*;
use middle::borrowck::move_data::MoveData;
use mc = middle::mem_categorization;
use middle::pat_util;
use middle::ty::{ty_region};
@ -35,6 +36,7 @@ use syntax::visit;
mod lifetime;
mod restrictions;
mod gather_moves;
/// Context used while gathering loans:
///
@ -65,28 +67,32 @@ mod restrictions;
struct GatherLoanCtxt {
bccx: @BorrowckCtxt,
id_range: id_range,
move_data: @mut move_data::MoveData,
all_loans: @mut ~[Loan],
item_ub: ast::node_id,
repeating_ids: ~[ast::node_id]
}
pub fn gather_loans(bccx: @BorrowckCtxt,
body: &ast::blk) -> (id_range, @mut ~[Loan]) {
body: &ast::blk)
-> (id_range, @mut ~[Loan], @mut move_data::MoveData) {
let glcx = @mut GatherLoanCtxt {
bccx: bccx,
id_range: id_range::max(),
all_loans: @mut ~[],
item_ub: body.node.id,
repeating_ids: ~[body.node.id]
repeating_ids: ~[body.node.id],
move_data: @mut MoveData::new()
};
let v = visit::mk_vt(@visit::Visitor {visit_expr: gather_loans_in_expr,
visit_block: gather_loans_in_block,
visit_fn: gather_loans_in_fn,
visit_stmt: add_stmt_to_map,
visit_pat: add_pat_to_id_range,
visit_local: gather_loans_in_local,
.. *visit::default_visitor()});
(v.visit_block)(body, glcx, v);
return (glcx.id_range, glcx.all_loans);
return (glcx.id_range, glcx.all_loans, glcx.move_data);
}
fn add_pat_to_id_range(p: @ast::pat,
@ -130,6 +136,35 @@ fn gather_loans_in_block(blk: &ast::blk,
visit::visit_block(blk, this, vt);
}
fn gather_loans_in_local(local: @ast::local,
this: @mut GatherLoanCtxt,
vt: visit::vt<@mut GatherLoanCtxt>) {
if local.node.init.is_none() {
// Variable declarations without initializers are considered "moves":
let tcx = this.bccx.tcx;
do pat_util::pat_bindings(tcx.def_map, local.node.pat) |_, id, span, _| {
gather_moves::gather_decl(this.bccx,
this.move_data,
id,
span,
id);
}
} else {
// Variable declarations with initializers are considered "assigns":
let tcx = this.bccx.tcx;
do pat_util::pat_bindings(tcx.def_map, local.node.pat) |_, id, span, _| {
gather_moves::gather_assignment(this.bccx,
this.move_data,
id,
span,
@LpVar(id),
id);
}
}
visit::visit_local(local, this, vt);
}
fn gather_loans_in_expr(ex: @ast::expr,
this: @mut GatherLoanCtxt,
vt: visit::vt<@mut GatherLoanCtxt>) {
@ -147,6 +182,13 @@ fn gather_loans_in_expr(ex: @ast::expr,
this.guarantee_adjustments(ex, *adjustments);
}
// If this expression is a move, gather it:
if this.bccx.is_move(ex.id) {
let cmt = this.bccx.cat_expr(ex);
gather_moves::gather_move_from_expr(
this.bccx, this.move_data, ex, cmt);
}
// Special checks for various kinds of expressions:
match ex.node {
ast::expr_addr_of(mutbl, base) => {
@ -159,6 +201,23 @@ fn gather_loans_in_expr(ex: @ast::expr,
visit::visit_expr(ex, this, vt);
}
ast::expr_assign(l, _) | ast::expr_assign_op(_, l, _) => {
let l_cmt = this.bccx.cat_expr(l);
match opt_loan_path(l_cmt) {
Some(l_lp) => {
gather_moves::gather_assignment(this.bccx, this.move_data,
ex.id, ex.span,
l_lp, l.id);
}
None => {
// This can occur with e.g. `*foo() = 5`. In such
// cases, there is no need to check for conflicts
// with moves etc, just ignore.
}
}
visit::visit_expr(ex, this, vt);
}
ast::expr_match(ex_v, ref arms) => {
let cmt = this.bccx.cat_expr(ex_v);
for arms.each |arm| {
@ -203,6 +262,11 @@ fn gather_loans_in_expr(ex: @ast::expr,
this.pop_repeating_id(body.node.id);
}
ast::expr_fn_block(*) => {
gather_moves::gather_captures(this.bccx, this.move_data, ex);
visit::visit_expr(ex, this, vt);
}
_ => {
visit::visit_expr(ex, this, vt);
}
@ -417,6 +481,8 @@ pub impl GatherLoanCtxt {
borrow_span: span,
cmt: mc::cmt,
req_mutbl: ast::mutability) {
//! Implements the M-* rules in doc.rs.
match req_mutbl {
m_const => {
// Data of any mutability can be lent as const.
@ -451,8 +517,8 @@ pub impl GatherLoanCtxt {
fn restriction_set(&self, req_mutbl: ast::mutability) -> RestrictionSet {
match req_mutbl {
m_const => RESTR_EMPTY,
m_imm => RESTR_EMPTY | RESTR_MUTATE,
m_mutbl => RESTR_EMPTY | RESTR_MUTATE | RESTR_FREEZE
m_imm => RESTR_EMPTY | RESTR_MUTATE | RESTR_CLAIM,
m_mutbl => RESTR_EMPTY | RESTR_MUTATE | RESTR_CLAIM | RESTR_FREEZE
}
}
@ -558,8 +624,11 @@ pub impl GatherLoanCtxt {
}
}
ast::bind_by_copy | ast::bind_infer => {
// Nothing to do here; neither copies nor moves induce
// borrows.
// No borrows here, but there may be moves
if self.bccx.is_move(pat.id) {
gather_moves::gather_move_from_pat(
self.bccx, self.move_data, pat, cmt);
}
}
}
}

View File

@ -33,7 +33,7 @@ pub fn compute_restrictions(bccx: @BorrowckCtxt,
cmt_original: cmt
};
ctxt.compute(cmt, restr)
ctxt.restrict(cmt, restr)
}
///////////////////////////////////////////////////////////////////////////
@ -50,9 +50,9 @@ impl RestrictionsContext {
self.bccx.tcx
}
fn compute(&self,
cmt: mc::cmt,
restrictions: RestrictionSet) -> RestrictionResult {
fn restrict(&self,
cmt: mc::cmt,
restrictions: RestrictionSet) -> RestrictionResult {
// Check for those cases where we cannot control the aliasing
// and make sure that we are not being asked to.
@ -76,6 +76,7 @@ impl RestrictionsContext {
mc::cat_local(local_id) |
mc::cat_arg(local_id) |
mc::cat_self(local_id) => {
// R-Variable
let lp = @LpVar(local_id);
SafeIf(lp, ~[Restriction {loan_path: lp,
set: restrictions}])
@ -85,22 +86,30 @@ impl RestrictionsContext {
// When we borrow the interior of an enum, we have to
// ensure the enum itself is not mutated, because that
// could cause the type of the memory to change.
self.compute(cmt_base, restrictions | RESTR_MUTATE)
self.restrict(
cmt_base,
restrictions | RESTR_MUTATE | RESTR_CLAIM)
}
mc::cat_interior(cmt_base, i) => {
// R-Field
//
// Overwriting the base would not change the type of
// the memory, so no additional restrictions are
// needed.
let result = self.compute(cmt_base, restrictions);
let result = self.restrict(cmt_base, restrictions);
self.extend(result, cmt.mutbl, LpInterior(i), restrictions)
}
mc::cat_deref(cmt_base, _, mc::uniq_ptr(*)) => {
// R-Deref-Owned-Pointer
//
// When we borrow the interior of an owned pointer, we
// cannot permit the base to be mutated, because that
// would cause the unique pointer to be freed.
let result = self.compute(cmt_base, restrictions | RESTR_MUTATE);
let result = self.restrict(
cmt_base,
restrictions | RESTR_MUTATE | RESTR_CLAIM);
self.extend(result, cmt.mutbl, LpDeref, restrictions)
}
@ -109,16 +118,20 @@ impl RestrictionsContext {
mc::cat_implicit_self(*) |
mc::cat_deref(_, _, mc::region_ptr(m_imm, _)) |
mc::cat_deref(_, _, mc::gc_ptr(m_imm)) => {
// R-Deref-Imm-Borrowed
Safe
}
mc::cat_deref(_, _, mc::region_ptr(m_const, _)) |
mc::cat_deref(_, _, mc::gc_ptr(m_const)) => {
// R-Deref-Const-Borrowed
self.check_no_mutability_control(cmt, restrictions);
Safe
}
mc::cat_deref(cmt_base, _, mc::gc_ptr(m_mutbl)) => {
// R-Deref-Managed-Borrowed
//
// Technically, no restrictions are *necessary* here.
// The validity of the borrow is guaranteed
// dynamically. However, nonetheless we add a
@ -169,12 +182,15 @@ impl RestrictionsContext {
// mutability, we can only prevent mutation or prevent
// freezing if it is not aliased. Therefore, in such
// cases we restrict aliasing on `cmt_base`.
if restrictions.intersects(RESTR_MUTATE | RESTR_FREEZE) {
let result = self.compute(cmt_base, restrictions | RESTR_ALIAS);
if restrictions != RESTR_EMPTY {
// R-Deref-Mut-Borrowed-1
let result = self.restrict(
cmt_base,
RESTR_ALIAS | RESTR_MUTATE | RESTR_CLAIM);
self.extend(result, cmt.mutbl, LpDeref, restrictions)
} else {
let result = self.compute(cmt_base, restrictions);
self.extend(result, cmt.mutbl, LpDeref, restrictions)
// R-Deref-Mut-Borrowed-2
Safe
}
}
@ -185,7 +201,7 @@ impl RestrictionsContext {
mc::cat_stack_upvar(cmt_base) |
mc::cat_discr(cmt_base, _) => {
self.compute(cmt_base, restrictions)
self.restrict(cmt_base, restrictions)
}
}
}
@ -233,7 +249,7 @@ impl RestrictionsContext {
fn check_no_mutability_control(&self,
cmt: mc::cmt,
restrictions: RestrictionSet) {
if restrictions.intersects(RESTR_MUTATE | RESTR_FREEZE) {
if restrictions.intersects(RESTR_MUTATE | RESTR_FREEZE | RESTR_CLAIM) {
self.bccx.report(BckError {span: self.span,
cmt: cmt,
code: err_freeze_aliasable_const});

View File

@ -19,7 +19,7 @@ use middle::moves;
use middle::dataflow::DataFlowContext;
use middle::dataflow::DataFlowOperator;
use util::common::stmt_set;
use util::ppaux::{note_and_explain_region, Repr};
use util::ppaux::{note_and_explain_region, Repr, UserString};
use core::hashmap::{HashSet, HashMap};
use core::io;
@ -46,6 +46,8 @@ pub mod check_loans;
#[path="gather_loans/mod.rs"]
pub mod gather_loans;
pub mod move_data;
pub struct LoanDataFlowOperator;
pub type LoanDataFlow = DataFlowContext<LoanDataFlowOperator>;
@ -121,21 +123,28 @@ fn borrowck_fn(fk: &visit::fn_kind,
debug!("borrowck_fn(id=%?)", id);
// Check the body of fn items.
let (id_range, all_loans) =
let (id_range, all_loans, move_data) =
gather_loans::gather_loans(this, body);
let all_loans: &~[Loan] = &*all_loans; // FIXME(#5074)
let mut dfcx =
let mut loan_dfcx =
DataFlowContext::new(this.tcx,
this.method_map,
LoanDataFlowOperator,
id_range,
all_loans.len());
for all_loans.eachi |loan_idx, loan| {
dfcx.add_gen(loan.gen_scope, loan_idx);
dfcx.add_kill(loan.kill_scope, loan_idx);
loan_dfcx.add_gen(loan.gen_scope, loan_idx);
loan_dfcx.add_kill(loan.kill_scope, loan_idx);
}
dfcx.propagate(body);
check_loans::check_loans(this, &dfcx, *all_loans, body);
loan_dfcx.propagate(body);
let flowed_moves = move_data::FlowedMoveData::new(move_data,
this.tcx,
this.method_map,
id_range,
body);
check_loans::check_loans(this, &loan_dfcx, flowed_moves,
*all_loans, body);
}
}
@ -226,13 +235,13 @@ pub struct Loan {
span: span,
}
#[deriving(Eq)]
#[deriving(Eq, IterBytes)]
pub enum LoanPath {
LpVar(ast::node_id), // `x` in doc.rs
LpExtend(@LoanPath, mc::MutabilityCategory, LoanPathElem)
}
#[deriving(Eq)]
#[deriving(Eq, IterBytes)]
pub enum LoanPathElem {
LpDeref, // `*LV` in doc.rs
LpInterior(mc::InteriorKind) // `LV.f` in doc.rs
@ -292,10 +301,10 @@ pub fn opt_loan_path(cmt: mc::cmt) -> Option<@LoanPath> {
// Borrowing an lvalue often results in *restrictions* that limit what
// can be done with this lvalue during the scope of the loan:
//
// - `RESTR_MUTATE`: The lvalue may not be modified and mutable pointers to
// the value cannot be created.
// - `RESTR_FREEZE`: Immutable pointers to the value cannot be created.
// - `RESTR_ALIAS`: The lvalue may not be aliased in any way.
// - `RESTR_MUTATE`: The lvalue may not be modified.
// - `RESTR_CLAIM`: `&mut` borrows of the lvalue are forbidden.
// - `RESTR_FREEZE`: `&` borrows of the lvalue are forbidden.
// - `RESTR_ALIAS`: All borrows of the lvalue are forbidden.
//
// In addition, no value which is restricted may be moved. Therefore,
// restrictions are meaningful even if the RestrictionSet is empty,
@ -306,14 +315,16 @@ pub struct Restriction {
set: RestrictionSet
}
#[deriving(Eq)]
pub struct RestrictionSet {
bits: u32
}
pub static RESTR_EMPTY: RestrictionSet = RestrictionSet {bits: 0b000};
pub static RESTR_MUTATE: RestrictionSet = RestrictionSet {bits: 0b001};
pub static RESTR_FREEZE: RestrictionSet = RestrictionSet {bits: 0b010};
pub static RESTR_ALIAS: RestrictionSet = RestrictionSet {bits: 0b100};
pub static RESTR_EMPTY: RestrictionSet = RestrictionSet {bits: 0b0000};
pub static RESTR_MUTATE: RestrictionSet = RestrictionSet {bits: 0b0001};
pub static RESTR_CLAIM: RestrictionSet = RestrictionSet {bits: 0b0010};
pub static RESTR_FREEZE: RestrictionSet = RestrictionSet {bits: 0b0100};
pub static RESTR_ALIAS: RestrictionSet = RestrictionSet {bits: 0b1000};
pub impl RestrictionSet {
fn intersects(&self, restr: RestrictionSet) -> bool {
@ -407,6 +418,11 @@ pub enum AliasableViolationKind {
BorrowViolation
}
pub enum MovedValueUseKind {
MovedInUse,
MovedInCapture,
}
///////////////////////////////////////////////////////////////////////////
// Misc
@ -419,6 +435,10 @@ pub impl BorrowckCtxt {
self.tcx.region_maps.is_subscope_of(r_sub, r_sup)
}
fn is_move(&self, id: ast::node_id) -> bool {
self.moves_map.contains(&id)
}
fn cat_expr(&self, expr: @ast::expr) -> mc::cmt {
mc::cat_expr(self.tcx, self.method_map, expr)
}
@ -478,6 +498,83 @@ pub impl BorrowckCtxt {
self.note_and_explain_bckerr(err);
}
fn report_use_of_moved_value(&self,
use_span: span,
use_kind: MovedValueUseKind,
lp: @LoanPath,
move: &move_data::Move,
moved_lp: @LoanPath) {
let verb = match use_kind {
MovedInUse => "use",
MovedInCapture => "capture",
};
match move.kind {
move_data::Declared => {
self.tcx.sess.span_err(
use_span,
fmt!("%s of possibly uninitialized value: `%s`",
verb,
self.loan_path_to_str(lp)));
}
_ => {
let partially = if lp == moved_lp {""} else {"partially "};
self.tcx.sess.span_err(
use_span,
fmt!("%s of %smoved value: `%s`",
verb,
partially,
self.loan_path_to_str(lp)));
}
}
match move.kind {
move_data::Declared => {}
move_data::MoveExpr(expr) => {
let expr_ty = ty::expr_ty_adjusted(self.tcx, expr);
self.tcx.sess.span_note(
expr.span,
fmt!("`%s` moved here because it has type `%s`, \
which is moved by default (use `copy` to override)",
self.loan_path_to_str(moved_lp),
expr_ty.user_string(self.tcx)));
}
move_data::MovePat(pat) => {
let pat_ty = ty::node_id_to_type(self.tcx, pat.id);
self.tcx.sess.span_note(
pat.span,
fmt!("`%s` moved here because it has type `%s`, \
which is moved by default (use `ref` to override)",
self.loan_path_to_str(moved_lp),
pat_ty.user_string(self.tcx)));
}
move_data::Captured(expr) => {
self.tcx.sess.span_note(
expr.span,
fmt!("`%s` moved into closure environment here \
because its type is moved by default \
(make a copy and capture that instead to override)",
self.loan_path_to_str(moved_lp)));
}
}
}
fn report_reassigned_immutable_variable(&self,
span: span,
lp: @LoanPath,
assign: &move_data::Assignment) {
self.tcx.sess.span_err(
span,
fmt!("re-assignment of immutable variable `%s`",
self.loan_path_to_str(lp)));
self.tcx.sess.span_note(
assign.span,
fmt!("prior assignment occurs here"));
}
fn span_err(&self, s: span, m: &str) {
self.tcx.sess.span_err(s, m);
}

View File

@ -0,0 +1,603 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
/*!
Data structures used for tracking moves. Please see the extensive
comments in the section "Moves and initialization" and in `doc.rs`.
*/
use core::prelude::*;
use core::hashmap::{HashMap, HashSet};
use middle::borrowck::*;
use middle::dataflow::DataFlowContext;
use middle::dataflow::DataFlowOperator;
use middle::ty;
use middle::typeck;
use syntax::ast;
use syntax::ast_util;
use syntax::codemap::span;
use syntax::opt_vec::OptVec;
use syntax::opt_vec;
use util::ppaux::Repr;
pub struct MoveData {
/// Move paths. See section "Move paths" in `doc.rs`.
paths: ~[MovePath],
/// Cache of loan path to move path index, for easy lookup.
path_map: HashMap<@LoanPath, MovePathIndex>,
/// Each move or uninitialized variable gets an entry here.
moves: ~[Move],
/// Assignments to a variable, like `x = foo`. These are assigned
/// bits for dataflow, since we must track them to ensure that
/// immutable variables are assigned at most once along each path.
var_assignments: ~[Assignment],
/// Assignments to a path, like `x.f = foo`. These are not
/// assigned dataflow bits, but we track them because they still
/// kill move bits.
path_assignments: ~[Assignment],
assignee_ids: HashSet<ast::node_id>,
}
pub struct FlowedMoveData {
move_data: @mut MoveData,
// ^~~~~~~~~~~~~
// It makes me sad to use @mut here, except that due to
// the visitor design, this is what gather_loans
// must produce.
dfcx_moves: MoveDataFlow,
// We could (and maybe should, for efficiency) combine both move
// and assign data flow into one, but this way it's easier to
// distinguish the bits that correspond to moves and assignments.
dfcx_assign: AssignDataFlow
}
/// Index into `MoveData.paths`, used like a pointer
#[deriving(Eq)]
pub struct MovePathIndex(uint);
static InvalidMovePathIndex: MovePathIndex =
MovePathIndex(uint::max_value);
/// Index into `MoveData.moves`, used like a pointer
#[deriving(Eq)]
pub struct MoveIndex(uint);
static InvalidMoveIndex: MoveIndex =
MoveIndex(uint::max_value);
pub struct MovePath {
/// Loan path corresponding to this move path
loan_path: @LoanPath,
/// Parent pointer, `InvalidMovePathIndex` if root
parent: MovePathIndex,
/// Head of linked list of moves to this path,
/// `InvalidMoveIndex` if not moved
first_move: MoveIndex,
/// First node in linked list of children, `InvalidMovePathIndex` if leaf
first_child: MovePathIndex,
/// Next node in linked list of parent's children (siblings),
/// `InvalidMovePathIndex` if none.
next_sibling: MovePathIndex,
}
pub enum MoveKind {
Declared, // When declared, variables start out "moved".
MoveExpr(@ast::expr), // Expression or binding that moves a variable
MovePat(@ast::pat), // By-move binding
Captured(@ast::expr), // Closure creation that moves a value
}
pub struct Move {
/// Path being moved.
path: MovePathIndex,
/// id of node that is doing the move.
id: ast::node_id,
/// Kind of move, for error messages.
kind: MoveKind,
/// Next node in linked list of moves from `path`, or `InvalidMoveIndex`
next_move: MoveIndex,
}
pub struct Assignment {
/// Path being assigned.
path: MovePathIndex,
/// id where assignment occurs
id: ast::node_id,
/// span of node where assignment occurs
span: span,
}
pub struct MoveDataFlowOperator;
pub type MoveDataFlow = DataFlowContext<MoveDataFlowOperator>;
pub struct AssignDataFlowOperator;
pub type AssignDataFlow = DataFlowContext<AssignDataFlowOperator>;
impl MoveData {
pub fn new() -> MoveData {
MoveData {
paths: ~[],
path_map: HashMap::new(),
moves: ~[],
path_assignments: ~[],
var_assignments: ~[],
assignee_ids: HashSet::new(),
}
}
fn path<'a>(&'a self, index: MovePathIndex) -> &'a MovePath {
//! Type safe indexing operator
&self.paths[*index]
}
fn mut_path<'a>(&'a mut self, index: MovePathIndex) -> &'a mut MovePath {
//! Type safe indexing operator
&mut self.paths[*index]
}
fn move<'a>(&'a self, index: MoveIndex) -> &'a Move {
//! Type safe indexing operator
&self.moves[*index]
}
fn is_var_path(&self, index: MovePathIndex) -> bool {
//! True if `index` refers to a variable
self.path(index).parent == InvalidMovePathIndex
}
pub fn move_path(&mut self,
tcx: ty::ctxt,
lp: @LoanPath) -> MovePathIndex {
/*!
* Returns the existing move path index for `lp`, if any,
* and otherwise adds a new index for `lp` and any of its
* base paths that do not yet have an index.
*/
match self.path_map.find(&lp) {
Some(&index) => {
return index;
}
None => {}
}
let index = match *lp {
LpVar(*) => {
let index = MovePathIndex(self.paths.len());
self.paths.push(MovePath {
loan_path: lp,
parent: InvalidMovePathIndex,
first_move: InvalidMoveIndex,
first_child: InvalidMovePathIndex,
next_sibling: InvalidMovePathIndex,
});
index
}
LpExtend(base, _, _) => {
let parent_index = self.move_path(tcx, base);
let index = MovePathIndex(self.paths.len());
let next_sibling = self.path(parent_index).first_child;
self.mut_path(parent_index).first_child = index;
self.paths.push(MovePath {
loan_path: lp,
parent: parent_index,
first_move: InvalidMoveIndex,
first_child: InvalidMovePathIndex,
next_sibling: next_sibling,
});
index
}
};
debug!("move_path(lp=%s, index=%?)",
lp.repr(tcx),
index);
assert_eq!(*index, self.paths.len() - 1);
self.path_map.insert(lp, index);
return index;
}
fn existing_move_path(&self,
lp: @LoanPath)
-> Option<MovePathIndex> {
self.path_map.find_copy(&lp)
}
fn existing_base_paths(&self,
lp: @LoanPath)
-> OptVec<MovePathIndex> {
let mut result = opt_vec::Empty;
self.add_existing_base_paths(lp, &mut result);
result
}
fn add_existing_base_paths(&self,
lp: @LoanPath,
result: &mut OptVec<MovePathIndex>) {
/*!
* Adds any existing move path indices for `lp` and any base
* paths of `lp` to `result`, but does not add new move paths
*/
match self.path_map.find_copy(&lp) {
Some(index) => {
for self.each_base_path(index) |p| {
result.push(p);
}
}
None => {
match *lp {
LpVar(*) => { }
LpExtend(b, _, _) => {
self.add_existing_base_paths(b, result);
}
}
}
}
}
pub fn add_move(&mut self,
tcx: ty::ctxt,
lp: @LoanPath,
id: ast::node_id,
kind: MoveKind) {
/*!
* Adds a new move entry for a move of `lp` that occurs at
* location `id` with kind `kind`.
*/
debug!("add_move(lp=%s, id=%?, kind=%?)",
lp.repr(tcx),
id,
kind);
let path_index = self.move_path(tcx, lp);
let move_index = MoveIndex(self.moves.len());
let next_move = self.path(path_index).first_move;
self.mut_path(path_index).first_move = move_index;
self.moves.push(Move {
path: path_index,
id: id,
kind: kind,
next_move: next_move
});
}
pub fn add_assignment(&mut self,
tcx: ty::ctxt,
lp: @LoanPath,
assign_id: ast::node_id,
span: span,
assignee_id: ast::node_id) {
/*!
* Adds a new record for an assignment to `lp` that occurs at
* location `id` with the given `span`.
*/
debug!("add_assignment(lp=%s, assign_id=%?, assignee_id=%?",
lp.repr(tcx), assign_id, assignee_id);
let path_index = self.move_path(tcx, lp);
self.assignee_ids.insert(assignee_id);
let assignment = Assignment {
path: path_index,
id: assign_id,
span: span,
};
if self.is_var_path(path_index) {
debug!("add_assignment[var](lp=%s, assignment=%u, path_index=%?)",
lp.repr(tcx), self.var_assignments.len(), path_index);
self.var_assignments.push(assignment);
} else {
debug!("add_assignment[path](lp=%s, path_index=%?)",
lp.repr(tcx), path_index);
self.path_assignments.push(assignment);
}
}
fn add_gen_kills(&self,
tcx: ty::ctxt,
dfcx_moves: &mut MoveDataFlow,
dfcx_assign: &mut AssignDataFlow) {
/*!
* Adds the gen/kills for the various moves and
* assignments into the provided data flow contexts.
* Moves are generated by moves and killed by assignments and
* scoping. Assignments are generated by assignment to variables and
* killed by scoping. See `doc.rs` for more details.
*/
for self.moves.eachi |i, move| {
dfcx_moves.add_gen(move.id, i);
}
for self.var_assignments.eachi |i, assignment| {
dfcx_assign.add_gen(assignment.id, i);
self.kill_moves(assignment.path, assignment.id, dfcx_moves);
}
for self.path_assignments.each |assignment| {
self.kill_moves(assignment.path, assignment.id, dfcx_moves);
}
// Kill all moves related to a variable `x` when it goes out
// of scope:
for self.paths.each |path| {
match *path.loan_path {
LpVar(id) => {
let kill_id = tcx.region_maps.encl_scope(id);
let path = *self.path_map.get(&path.loan_path);
self.kill_moves(path, kill_id, dfcx_moves);
}
LpExtend(*) => {}
}
}
// Kill all assignments when the variable goes out of scope:
for self.var_assignments.eachi |assignment_index, assignment| {
match *self.path(assignment.path).loan_path {
LpVar(id) => {
let kill_id = tcx.region_maps.encl_scope(id);
dfcx_assign.add_kill(kill_id, assignment_index);
}
LpExtend(*) => {
tcx.sess.bug("Var assignment for non var path");
}
}
}
}
fn each_base_path(&self,
index: MovePathIndex,
f: &fn(MovePathIndex) -> bool)
-> bool {
let mut p = index;
while p != InvalidMovePathIndex {
if !f(p) {
return false;
}
p = self.path(p).parent;
}
return true;
}
fn each_extending_path(&self,
index: MovePathIndex,
f: &fn(MovePathIndex) -> bool) -> bool {
if !f(index) {
return false;
}
let mut p = self.path(index).first_child;
while p != InvalidMovePathIndex {
if !self.each_extending_path(p, f) {
return false;
}
p = self.path(p).next_sibling;
}
return true;
}
fn each_applicable_move(&self,
index0: MovePathIndex,
f: &fn(MoveIndex) -> bool) -> bool {
for self.each_extending_path(index0) |index| {
let mut p = self.path(index).first_move;
while p != InvalidMoveIndex {
if !f(p) {
return false;
}
p = self.move(p).next_move;
}
}
return true;
}
fn kill_moves(&self,
path: MovePathIndex,
kill_id: ast::node_id,
dfcx_moves: &mut MoveDataFlow) {
for self.each_applicable_move(path) |move_index| {
dfcx_moves.add_kill(kill_id, *move_index);
}
}
}
impl FlowedMoveData {
pub fn new(move_data: @mut MoveData,
tcx: ty::ctxt,
method_map: typeck::method_map,
id_range: ast_util::id_range,
body: &ast::blk)
-> FlowedMoveData
{
let mut dfcx_moves =
DataFlowContext::new(tcx,
method_map,
MoveDataFlowOperator,
id_range,
move_data.moves.len());
let mut dfcx_assign =
DataFlowContext::new(tcx,
method_map,
AssignDataFlowOperator,
id_range,
move_data.var_assignments.len());
move_data.add_gen_kills(tcx, &mut dfcx_moves, &mut dfcx_assign);
dfcx_moves.propagate(body);
dfcx_assign.propagate(body);
FlowedMoveData {
move_data: move_data,
dfcx_moves: dfcx_moves,
dfcx_assign: dfcx_assign,
}
}
pub fn each_move_of(&self,
id: ast::node_id,
loan_path: @LoanPath,
f: &fn(&Move, @LoanPath) -> bool)
-> bool {
/*!
* Iterates through each move of `loan_path` (or some base path
* of `loan_path`) that *may* have occurred on entry to `id` without
* an intervening assignment. In other words, any moves that
* would invalidate a reference to `loan_path` at location `id`.
*/
// Bad scenarios:
//
// 1. Move of `a.b.c`, use of `a.b.c`
// 2. Move of `a.b.c`, use of `a.b.c.d`
// 3. Move of `a.b.c`, use of `a` or `a.b`
//
// OK scenario:
//
// 4. move of `a.b.c`, use of `a.b.d`
let base_indices = self.move_data.existing_base_paths(loan_path);
if base_indices.is_empty() {
return true;
}
let opt_loan_path_index = self.move_data.existing_move_path(loan_path);
for self.dfcx_moves.each_bit_on_entry(id) |index| {
let move = &self.move_data.moves[index];
let moved_path = move.path;
if base_indices.contains(&moved_path) {
// Scenario 1 or 2: `loan_path` or some base path of
// `loan_path` was moved.
if !f(move, self.move_data.path(moved_path).loan_path) {
return false;
}
loop;
}
for opt_loan_path_index.each |&loan_path_index| {
for self.move_data.each_base_path(moved_path) |p| {
if p == loan_path_index {
// Scenario 3: some extension of `loan_path`
// was moved
if !f(move, self.move_data.path(moved_path).loan_path) {
return false;
}
}
}
}
}
return true;
}
pub fn is_assignee(&self,
id: ast::node_id)
-> bool {
//! True if `id` is the id of the LHS of an assignment
self.move_data.assignee_ids.contains(&id)
}
pub fn each_assignment_of(&self,
id: ast::node_id,
loan_path: @LoanPath,
f: &fn(&Assignment) -> bool)
-> bool {
/*!
* Iterates through every assignment to `loan_path` that
* may have occurred on entry to `id`. `loan_path` must be
* a single variable.
*/
let loan_path_index = {
match self.move_data.existing_move_path(loan_path) {
Some(i) => i,
None => {
// if there were any assignments, it'd have an index
return true;
}
}
};
for self.dfcx_assign.each_bit_on_entry(id) |index| {
let assignment = &self.move_data.var_assignments[index];
if assignment.path == loan_path_index && !f(assignment) {
return false;
}
}
return true;
}
}
impl DataFlowOperator for MoveDataFlowOperator {
#[inline(always)]
fn initial_value(&self) -> bool {
false // no loans in scope by default
}
#[inline(always)]
fn join(&self, succ: uint, pred: uint) -> uint {
succ | pred // moves from both preds are in scope
}
#[inline(always)]
fn walk_closures(&self) -> bool {
true
}
}
impl DataFlowOperator for AssignDataFlowOperator {
#[inline(always)]
fn initial_value(&self) -> bool {
false // no assignments in scope by default
}
#[inline(always)]
fn join(&self, succ: uint, pred: uint) -> uint {
succ | pred // moves from both preds are in scope
}
#[inline(always)]
fn walk_closures(&self) -> bool {
true
}
}

View File

@ -866,7 +866,7 @@ pub fn check_legality_of_move_bindings(cx: @MatchCheckCtxt,
if !any_by_move { return; } // pointless micro-optimization
for pats.each |pat| {
do walk_pat(*pat) |p| {
for walk_pat(*pat) |p| {
if pat_is_binding(def_map, p) {
match p.node {
pat_ident(_, _, sub) => {
@ -884,66 +884,5 @@ pub fn check_legality_of_move_bindings(cx: @MatchCheckCtxt,
}
}
}
// Now check to ensure that any move binding is not behind an
// @ or &, or within a struct with a destructor. This is
// always illegal.
let vt = visit::mk_vt(@visit::Visitor {
visit_pat: |pat, (behind_bad_pointer, behind_dtor_struct): (bool, bool), v| {
match pat.node {
pat_ident(_, _, sub) => {
debug!("(check legality of move) checking pat \
ident with behind_bad_pointer %? and behind_dtor_struct %?",
behind_bad_pointer, behind_dtor_struct);
if behind_bad_pointer || behind_dtor_struct &&
cx.moves_map.contains(&pat.id)
{
let msg = if behind_bad_pointer {
"by-move pattern bindings may not occur behind @ or & bindings"
} else {
"cannot bind by-move within struct (it has a destructor)"
};
cx.tcx.sess.span_err(pat.span, msg);
}
match sub {
None => {}
Some(subpat) => {
(v.visit_pat)(subpat,
(behind_bad_pointer, behind_dtor_struct),
v);
}
}
}
pat_box(subpat) | pat_region(subpat) => {
(v.visit_pat)(subpat, (true, behind_dtor_struct), v);
}
pat_struct(_, ref fields, _) => {
let behind_dtor_struct = behind_dtor_struct ||
(match cx.tcx.def_map.find(&pat.id) {
Some(&def_struct(id)) => {
ty::has_dtor(cx.tcx, id)
}
_ => false
});
debug!("(check legality of move) checking pat \
struct with behind_bad_pointer %? and behind_dtor_struct %?",
behind_bad_pointer, behind_dtor_struct);
for fields.each |fld| {
(v.visit_pat)(fld.pat, (behind_bad_pointer,
behind_dtor_struct), v)
}
}
_ => visit::visit_pat(pat, (behind_bad_pointer, behind_dtor_struct), v)
}
},
.. *visit::default_visitor::<(bool, bool)>()
});
(vt.visit_pat)(*pat, (false, false), vt);
}
}

View File

@ -651,10 +651,10 @@ impl<'self, O:DataFlowOperator> PropagationContext<'self, O> {
}
ast::expr_struct(_, ref fields, with_expr) => {
self.walk_opt_expr(with_expr, in_out, loop_scopes);
for fields.each |field| {
self.walk_expr(field.node.expr, in_out, loop_scopes);
}
self.walk_opt_expr(with_expr, in_out, loop_scopes);
}
ast::expr_call(f, ref args, _) => {
@ -826,7 +826,7 @@ impl<'self, O:DataFlowOperator> PropagationContext<'self, O> {
debug!("DataFlowContext::walk_pat(pat=%s, in_out=%s)",
pat.repr(self.dfcx.tcx), bits_to_str(reslice(in_out)));
do ast_util::walk_pat(pat) |p| {
for ast_util::walk_pat(pat) |p| {
debug!(" p.id=%? in_out=%s", p.id, bits_to_str(reslice(in_out)));
self.merge_with_entry_set(p.id, in_out);
self.dfcx.apply_gen_kill(p.id, in_out);

View File

@ -109,7 +109,6 @@ use middle::pat_util;
use middle::ty;
use middle::typeck;
use middle::moves;
use util::ppaux::ty_to_str;
use core::cast::transmute;
use core::hashmap::HashMap;
@ -146,7 +145,6 @@ fn live_node_kind_to_str(lnk: LiveNodeKind, cx: ty::ctxt) -> ~str {
pub fn check_crate(tcx: ty::ctxt,
method_map: typeck::method_map,
variable_moves_map: moves::VariableMovesMap,
capture_map: moves::CaptureMap,
crate: @crate) {
let visitor = visit::mk_vt(@visit::Visitor {
@ -159,7 +157,6 @@ pub fn check_crate(tcx: ty::ctxt,
let initial_maps = @mut IrMaps(tcx,
method_map,
variable_moves_map,
capture_map);
visit::visit_crate(crate, initial_maps, visitor);
tcx.sess.abort_if_errors();
@ -229,7 +226,6 @@ enum VarKind {
struct IrMaps {
tcx: ty::ctxt,
method_map: typeck::method_map,
variable_moves_map: moves::VariableMovesMap,
capture_map: moves::CaptureMap,
num_live_nodes: uint,
@ -243,13 +239,11 @@ struct IrMaps {
fn IrMaps(tcx: ty::ctxt,
method_map: typeck::method_map,
variable_moves_map: moves::VariableMovesMap,
capture_map: moves::CaptureMap)
-> IrMaps {
IrMaps {
tcx: tcx,
method_map: method_map,
variable_moves_map: variable_moves_map,
capture_map: capture_map,
num_live_nodes: 0,
num_vars: 0,
@ -349,7 +343,6 @@ fn visit_fn(fk: &visit::fn_kind,
// swap in a new set of IR maps for this function body:
let fn_maps = @mut IrMaps(this.tcx,
this.method_map,
this.variable_moves_map,
this.capture_map);
unsafe {
@ -1399,11 +1392,7 @@ pub impl Liveness {
fn check_local(local: @local, this: @Liveness, vt: vt<@Liveness>) {
match local.node.init {
Some(_) => {
// Initializer:
this.warn_about_unused_or_dead_vars_in_pat(local.node.pat);
this.check_for_reassignments_in_pat(local.node.pat,
local.node.is_mutbl);
}
None => {
@ -1438,35 +1427,6 @@ fn check_arm(arm: &arm, this: @Liveness, vt: vt<@Liveness>) {
fn check_expr(expr: @expr, this: @Liveness, vt: vt<@Liveness>) {
match expr.node {
expr_path(_) | expr_self => {
for this.variable_from_def_map(expr.id, expr.span).each |var| {
let ln = this.live_node(expr.id, expr.span);
match this.ir.variable_moves_map.find(&expr.id) {
None => {}
Some(&entire_expr) => {
debug!("(checking expr) is a move: `%s`",
expr_to_str(expr, this.tcx.sess.intr()));
this.check_move_from_var(ln, *var, entire_expr);
}
}
}
visit::visit_expr(expr, this, vt);
}
expr_fn_block(*) => {
let caps = this.ir.captures(expr);
for caps.each |cap| {
let var = this.variable(cap.var_nid, expr.span);
if cap.is_move {
this.check_move_from_var(cap.ln, var, expr);
}
}
visit::visit_expr(expr, this, vt);
}
expr_assign(l, r) => {
this.check_lvalue(l, vt);
(vt.visit_expr)(r, this, vt);
@ -1507,7 +1467,7 @@ fn check_expr(expr: @expr, this: @Liveness, vt: vt<@Liveness>) {
expr_cast(*) | expr_unary(*) | expr_ret(*) | expr_break(*) |
expr_again(*) | expr_lit(_) | expr_block(*) |
expr_mac(*) | expr_addr_of(*) | expr_struct(*) | expr_repeat(*) |
expr_paren(*) => {
expr_paren(*) | expr_fn_block(*) | expr_path(*) | expr_self(*) => {
visit::visit_expr(expr, this, vt);
}
}
@ -1547,43 +1507,17 @@ pub impl Liveness {
}
}
fn check_move_from_var(&self,
ln: LiveNode,
var: Variable,
move_expr: @expr) {
/*!
* Checks whether `var` is live on entry to any of the
* successors of `ln`. If it is, report an error.
* `move_expr` is the expression which caused the variable
* to be moved.
*
* Note that `move_expr` is not necessarily a reference to the
* variable. It might be an expression like `x.f` which could
* cause a move of the variable `x`, or a closure creation.
*/
debug!("check_move_from_var(%s, %s)",
ln.to_str(), var.to_str());
match self.live_on_exit(ln, var) {
None => {}
Some(lnk) => self.report_illegal_move(lnk, var, move_expr)
}
}
fn check_lvalue(@self, expr: @expr, vt: vt<@Liveness>) {
match expr.node {
expr_path(_) => {
match self.tcx.def_map.get_copy(&expr.id) {
def_local(nid, mutbl) => {
def_local(nid, _) => {
// Assignment to an immutable variable or argument: only legal
// if there is no later assignment. If this local is actually
// mutable, then check for a reassignment to flag the mutability
// as being used.
let ln = self.live_node(expr.id, expr.span);
let var = self.variable(nid, expr.span);
self.check_for_reassignment(ln, var, expr.span,
if mutbl {Some(nid)} else {None});
self.warn_about_dead_assign(expr.span, expr.id, ln, var);
}
def => {
@ -1607,118 +1541,6 @@ pub impl Liveness {
}
}
fn check_for_reassignments_in_pat(&self, pat: @pat, mutbl: bool) {
do self.pat_bindings(pat) |ln, var, sp, id| {
self.check_for_reassignment(ln, var, sp,
if mutbl {Some(id)} else {None});
}
}
fn check_for_reassignment(&self, ln: LiveNode, var: Variable,
orig_span: span, mutbl: Option<node_id>) {
match self.assigned_on_exit(ln, var) {
Some(ExprNode(span)) => {
match mutbl {
Some(id) => { self.tcx.used_mut_nodes.insert(id); }
None => {
self.tcx.sess.span_err(
span,
"re-assignment of immutable variable");
self.tcx.sess.span_note(
orig_span,
"prior assignment occurs here");
}
}
}
Some(lnk) => {
self.tcx.sess.span_bug(
orig_span,
fmt!("illegal writer: %?", lnk));
}
None => {}
}
}
fn report_illegal_move(&self, lnk: LiveNodeKind,
var: Variable,
move_expr: @expr) {
// the only time that it is possible to have a moved variable
// used by ExitNode would be arguments or fields in a ctor.
// we give a slightly different error message in those cases.
if lnk == ExitNode {
// FIXME #4715: this seems like it should be reported in the
// borrow checker
let vk = self.ir.var_kinds[*var];
match vk {
Arg(_, name) => {
self.tcx.sess.span_err(
move_expr.span,
fmt!("illegal move from argument `%s`, which is not \
copy or move mode", *self.tcx.sess.str_of(name)));
return;
}
Local(*) | ImplicitRet => {
self.tcx.sess.span_bug(
move_expr.span,
fmt!("illegal reader (%?) for `%?`",
lnk, vk));
}
}
}
match move_expr.node {
expr_fn_block(*) => {
self.report_illegal_read(
move_expr.span, lnk, var, MovedValue);
let name = self.ir.variable_name(var);
self.tcx.sess.span_note(
move_expr.span,
fmt!("`%s` moved into closure environment here \
because its type is moved by default",
*name));
}
expr_path(*) => {
self.report_illegal_read(
move_expr.span, lnk, var, MovedValue);
self.report_move_location(
move_expr, var, "", "it");
}
expr_field(*) => {
self.report_illegal_read(
move_expr.span, lnk, var, PartiallyMovedValue);
self.report_move_location(
move_expr, var, "field of ", "the field");
}
expr_index(*) => {
self.report_illegal_read(
move_expr.span, lnk, var, PartiallyMovedValue);
self.report_move_location(
move_expr, var, "element of ", "the element");
}
_ => {
self.report_illegal_read(
move_expr.span, lnk, var, PartiallyMovedValue);
self.report_move_location(
move_expr, var, "subcomponent of ", "the subcomponent");
}
};
}
fn report_move_location(&self,
move_expr: @expr,
var: Variable,
expr_descr: &str,
pronoun: &str) {
let move_expr_ty = ty::expr_ty(self.tcx, move_expr);
let name = self.ir.variable_name(var);
self.tcx.sess.span_note(
move_expr.span,
fmt!("%s`%s` moved here because %s has type %s, \
which is moved by default (use `copy` to override)",
expr_descr, *name, pronoun,
ty_to_str(self.tcx, move_expr_ty)));
}
fn report_illegal_read(&self,
chk_span: span,
lnk: LiveNodeKind,

View File

@ -93,19 +93,26 @@ pub enum ptr_kind {
// We use the term "interior" to mean "something reachable from the
// base without a pointer dereference", e.g. a field
#[deriving(Eq)]
#[deriving(Eq, IterBytes)]
pub enum InteriorKind {
InteriorField(FieldName),
InteriorElement(ty::t), // ty::t is the type of the vec/str
InteriorElement(ElementKind),
}
#[deriving(Eq)]
#[deriving(Eq, IterBytes)]
pub enum FieldName {
NamedField(ast::ident),
PositionalField(uint)
}
#[deriving(Eq)]
#[deriving(Eq, IterBytes)]
pub enum ElementKind {
VecElement,
StrElement,
OtherElement,
}
#[deriving(Eq, IterBytes)]
pub enum MutabilityCategory {
McImmutable, // Immutable.
McReadOnly, // Read-only (`const`)
@ -192,7 +199,7 @@ pub fn opt_deref_kind(t: ty::t) -> Option<deref_kind> {
ty::ty_evec(_, ty::vstore_fixed(_)) |
ty::ty_estr(ty::vstore_fixed(_)) => {
Some(deref_interior(InteriorElement(t)))
Some(deref_interior(InteriorElement(element_kind(t))))
}
_ => None
@ -749,7 +756,7 @@ pub impl mem_categorization_ctxt {
@cmt_ {
id:elt.id(),
span:elt.span(),
cat:cat_interior(of_cmt, InteriorElement(vec_ty)),
cat:cat_interior(of_cmt, InteriorElement(element_kind(vec_ty))),
mutbl:mutbl,
ty:mt.ty
}
@ -993,12 +1000,14 @@ pub impl mem_categorization_ctxt {
cat_interior(_, InteriorField(PositionalField(_))) => {
~"anonymous field"
}
cat_interior(_, InteriorElement(t)) => {
match ty::get(t).sty {
ty::ty_evec(*) => ~"vec content",
ty::ty_estr(*) => ~"str content",
_ => ~"indexed content"
}
cat_interior(_, InteriorElement(VecElement)) => {
~"vec content"
}
cat_interior(_, InteriorElement(StrElement)) => {
~"str content"
}
cat_interior(_, InteriorElement(OtherElement)) => {
~"indexed content"
}
cat_stack_upvar(_) => {
~"captured outer variable"
@ -1193,3 +1202,11 @@ impl Repr for InteriorKind {
}
}
}
fn element_kind(t: ty::t) -> ElementKind {
match ty::get(t).sty {
ty::ty_evec(*) => VecElement,
ty::ty_estr(*) => StrElement,
_ => OtherElement
}
}

View File

@ -88,112 +88,32 @@ Similar reasoning can be applied to `let` expressions:
## Output
The pass results in the struct `MoveMaps` which contains two sets,
`moves_map` and `variable_moves_map`, and one map, `capture_map`.
The pass results in the struct `MoveMaps` which contains several
maps:
`moves_map` is a set containing the id of every *outermost
expression* or *binding* that is moved. Note that `moves_map` only
contains the *outermost expressions* that are moved. Therefore, if
you have a use of `x.b`, as in the example `y` above, the
expression `x.b` would be in the `moves_map` but not `x`. The
reason for this is that, for most purposes, it's only the outermost
expression that is needed. The borrow checker and trans, for
example, only care about the outermost expressions that are moved.
It is more efficient therefore just to store those entries.
`moves_map` is a set containing the id of every *outermost expression* or
*binding* that causes a move. Note that `moves_map` only contains the *outermost
expressions* that are moved. Therefore, if you have a use of `x.b`,
as in the example `y` above, the expression `x.b` would be in the
`moves_map` but not `x`. The reason for this is that, for most
purposes, it's only the outermost expression that is needed. The
borrow checker and trans, for example, only care about the outermost
expressions that are moved. It is more efficient therefore just to
store those entries.
In the case of the liveness pass, however, we need to know which
*variable references* are moved (see the Enforcement of Moves
section below for more details). That is, for the `x.b`
expression, liveness only cares about the `x`. For this purpose,
we have a second map, `variable_moves_map`, that contains the ids
of all variable references which is moved.
Sometimes though we want to know the variables that are moved (in
particular in the borrow checker). For these cases, the set
`moved_variables_set` just collects the ids of variables that are
moved.
The `capture_map` maps from the node_id of a closure expression to an
array of `CaptureVar` structs detailing which variables are captured
and how (by ref, by copy, by move).
Finally, the `capture_map` maps from the node_id of a closure
expression to an array of `CaptureVar` structs detailing which
variables are captured and how (by ref, by copy, by move).
## Enforcement of Moves
The enforcement of moves is somewhat complicated because it is divided
amongst the liveness and borrowck modules. In general, the borrow
checker is responsible for guaranteeing that *only owned data is
moved*. The liveness checker, in contrast, is responsible for
checking that *no variable is used after it is moved*.
To see the difference, let's look at a few examples. Here is a
program fragment where the error would be caught by liveness:
struct Foo { a: int, b: ~int }
let x: Foo = ...;
let y = x.b; // (1)
let z = x; // (2) //~ ERROR use of moved value `x`
Here the liveness checker will see the assignment to `y` moves
invalidates the variable `x` because it moves the expression `x.b`.
An error is resported because `x` is not dead at the point where it is
invalidated.
In more concrete terms, the `moves_map` generated from this example
would contain both the expression `x.b` (1) and the expression `x`
(2). Note that it would not contain `x` (1), because `moves_map` only
contains the outermost expressions that are moved. However,
`moves_map` is not used by liveness. It uses the
`variable_moves_map`, which would contain both references to `x`: (1)
and (2). Therefore, after computing which variables are live where,
liveness will see that the reference (1) to `x` is both present in
`variable_moves_map` and that `x` is live and report an error.
Now let's look at another illegal example, but one where liveness would
not catch the error:
struct Foo { a: int, b: ~int }
let x: @Foo = ...;
let y = x.b; //~ ERROR move from managed (@) box
This is an interesting example because the only change I've made is
to make `x` have type `@Foo` and not `Foo`. Thanks to auto-deref,
the expression `x.b` still works, but now it is short for `{x).b`,
and hence the move is actually moving out of the contents of a
managed box, which is illegal. However, liveness knows nothing of
this. It only tracks what variables are used where. The moves
pass (that is, this pass) is also ignorant of such details. From
the perspective of the moves pass, the `let y = x.b` line above
will be categorized as follows:
let y = {(x{Move}) {Move}).b; {Move}
Therefore, the reference to `x` will be present in
`variable_moves_map`, but liveness will not report an error because
there is no subsequent use.
This is where the borrow checker comes in. When the borrow checker
runs, it will see that `x.b` is present in the `moves_map`. It will
use the `mem_categorization` module to determine where the result of
this expression resides in memory and see that it is owned by managed
data, and report an error.
In principle, liveness could use the `mem_categorization` module
itself and check that moves always originate from owned data
(historically, of course, this was not the case; `mem_categorization`
used to be private to the borrow checker). However, there is another
kind of error which liveness could not possibly detect. Sometimes a
move is an error due to an outstanding loan, and it is borrow
checker's job to compute those loans. That is, consider *this*
example:
struct Foo { a: int, b: ~int }
let x: Foo = ...;
let y = &x.b; //~ NOTE loan issued here
let z = x.b; //~ ERROR move with outstanding loan
In this case, `y` is a pointer into `x`, so when `z` tries to move out
of `x`, we get an error. There is no way that liveness could compute
this information without redoing the efforts of the borrow checker.
### Closures
Liveness is somewhat complicated by having to deal with stack
closures. More information to come!
The enforcement of moves is done by the borrow checker. Please see
the section "Moves and initialization" in `middle/borrowck/doc.rs`.
## Distributive property
@ -213,6 +133,7 @@ use middle::freevars;
use middle::ty;
use middle::typeck::{method_map};
use util::ppaux;
use util::ppaux::Repr;
use util::common::indenter;
use core::hashmap::{HashSet, HashMap};
@ -220,7 +141,6 @@ use syntax::ast::*;
use syntax::ast_util;
use syntax::visit;
use syntax::visit::vt;
use syntax::print::pprust;
use syntax::codemap::span;
#[deriving(Encodable, Decodable)]
@ -241,11 +161,6 @@ pub type CaptureMap = @mut HashMap<node_id, @[CaptureVar]>;
pub type MovesMap = @mut HashSet<node_id>;
/**
* For each variable which will be moved, links to the
* expression */
pub type VariableMovesMap = @mut HashMap<node_id, @expr>;
/**
* Set of variable node-ids that are moved.
*
@ -257,7 +172,6 @@ pub type MovedVariablesSet = @mut HashSet<node_id>;
/** See the section Output on the module comment for explanation. */
pub struct MoveMaps {
moves_map: MovesMap,
variable_moves_map: VariableMovesMap,
moved_variables_set: MovedVariablesSet,
capture_map: CaptureMap
}
@ -269,9 +183,8 @@ struct VisitContext {
}
enum UseMode {
MoveInWhole, // Move the entire value.
MoveInPart(@expr), // Some subcomponent will be moved
Read // Read no matter what the type.
Move, // This value or something owned by it is moved.
Read // Read no matter what the type.
}
pub fn compute_moves(tcx: ty::ctxt,
@ -287,7 +200,6 @@ pub fn compute_moves(tcx: ty::ctxt,
method_map: method_map,
move_maps: MoveMaps {
moves_map: @mut HashSet::new(),
variable_moves_map: @mut HashMap::new(),
capture_map: @mut HashMap::new(),
moved_variables_set: @mut HashSet::new()
}
@ -317,21 +229,6 @@ fn compute_modes_for_expr(expr: @expr,
cx.consume_expr(expr, v);
}
pub impl UseMode {
fn component_mode(&self, expr: @expr) -> UseMode {
/*!
*
* Assuming that `self` is the mode for an expression E,
* returns the appropriate mode to use for a subexpression of E.
*/
match *self {
Read | MoveInPart(_) => *self,
MoveInWhole => MoveInPart(expr)
}
}
}
pub impl VisitContext {
fn consume_exprs(&self,
exprs: &[@expr],
@ -347,18 +244,20 @@ pub impl VisitContext {
visitor: vt<VisitContext>)
{
/*!
*
* Indicates that the value of `expr` will be consumed,
* meaning either copied or moved depending on its type.
*/
debug!("consume_expr(expr=%?/%s)",
expr.id,
pprust::expr_to_str(expr, self.tcx.sess.intr()));
debug!("consume_expr(expr=%s)",
expr.repr(self.tcx));
let expr_ty = ty::expr_ty_adjusted(self.tcx, expr);
let mode = self.consume_mode_for_ty(expr_ty);
self.use_expr(expr, mode, visitor);
if ty::type_moves_by_default(self.tcx, expr_ty) {
self.move_maps.moves_map.insert(expr.id);
self.use_expr(expr, Move, visitor);
} else {
self.use_expr(expr, Read, visitor);
};
}
fn consume_block(&self,
@ -366,7 +265,6 @@ pub impl VisitContext {
visitor: vt<VisitContext>)
{
/*!
*
* Indicates that the value of `blk` will be consumed,
* meaning either copied or moved depending on its type.
*/
@ -382,46 +280,20 @@ pub impl VisitContext {
}
}
fn consume_mode_for_ty(&self, ty: ty::t) -> UseMode {
/*!
*
* Selects the appropriate `UseMode` to consume a value with
* the type `ty`. This will be `MoveEntireMode` if `ty` is
* not implicitly copyable.
*/
let result = if ty::type_moves_by_default(self.tcx, ty) {
MoveInWhole
} else {
Read
};
debug!("consume_mode_for_ty(ty=%s) = %?",
ppaux::ty_to_str(self.tcx, ty), result);
return result;
}
fn use_expr(&self,
expr: @expr,
expr_mode: UseMode,
visitor: vt<VisitContext>)
{
/*!
*
* Indicates that `expr` is used with a given mode. This will
* in turn trigger calls to the subcomponents of `expr`.
*/
debug!("use_expr(expr=%?/%s, mode=%?)",
expr.id, pprust::expr_to_str(expr, self.tcx.sess.intr()),
debug!("use_expr(expr=%s, mode=%?)",
expr.repr(self.tcx),
expr_mode);
match expr_mode {
MoveInWhole => { self.move_maps.moves_map.insert(expr.id); }
MoveInPart(_) | Read => {}
}
// `expr_mode` refers to the post-adjustment value. If one of
// those adjustments is to take a reference, then it's only
// reading the underlying expression, not moving it.
@ -429,7 +301,7 @@ pub impl VisitContext {
Some(&@ty::AutoDerefRef(
ty::AutoDerefRef {
autoref: Some(_), _})) => Read,
_ => expr_mode.component_mode(expr)
_ => expr_mode
};
debug!("comp_mode = %?", comp_mode);
@ -437,21 +309,13 @@ pub impl VisitContext {
match expr.node {
expr_path(*) | expr_self => {
match comp_mode {
MoveInPart(entire_expr) => {
self.move_maps.variable_moves_map.insert(
expr.id, entire_expr);
Move => {
let def = self.tcx.def_map.get_copy(&expr.id);
for moved_variable_node_id_from_def(def).each |&id| {
self.move_maps.moved_variables_set.insert(id);
}
}
Read => {}
MoveInWhole => {
self.tcx.sess.span_bug(
expr.span,
"Component mode can never be MoveInWhole");
}
}
}
@ -546,19 +410,10 @@ pub impl VisitContext {
self.consume_arm(arm, visitor);
}
let by_move_bindings_present =
self.arms_have_by_move_bindings(
self.move_maps.moves_map, *arms);
if by_move_bindings_present {
// If one of the arms moves a value out of the
// discriminant, then the discriminant itself is
// moved.
self.consume_expr(discr, visitor);
} else {
// Otherwise, the discriminant is merely read.
self.use_expr(discr, Read, visitor);
}
// The discriminant may, in fact, be partially moved
// if there are by-move bindings, but borrowck deals
// with that itself.
self.use_expr(discr, Read, visitor);
}
expr_copy(base) => {
@ -719,18 +574,17 @@ pub impl VisitContext {
*/
do pat_bindings(self.tcx.def_map, pat) |bm, id, _span, _path| {
let mode = match bm {
bind_by_copy => Read,
bind_by_ref(_) => Read,
let binding_moves = match bm {
bind_by_copy => false,
bind_by_ref(_) => false,
bind_infer => {
let pat_ty = ty::node_id_to_type(self.tcx, id);
self.consume_mode_for_ty(pat_ty)
ty::type_moves_by_default(self.tcx, pat_ty)
}
};
match mode {
MoveInWhole => { self.move_maps.moves_map.insert(id); }
MoveInPart(_) | Read => {}
if binding_moves {
self.move_maps.moves_map.insert(id);
}
}
}
@ -759,20 +613,18 @@ pub impl VisitContext {
fn arms_have_by_move_bindings(&self,
moves_map: MovesMap,
arms: &[arm]) -> bool
arms: &[arm]) -> Option<@pat>
{
for arms.each |arm| {
for arm.pats.each |pat| {
let mut found = false;
do pat_bindings(self.tcx.def_map, *pat) |_, node_id, _, _| {
if moves_map.contains(&node_id) {
found = true;
for arm.pats.each |&pat| {
for ast_util::walk_pat(pat) |p| {
if moves_map.contains(&p.id) {
return Some(p);
}
}
if found { return true; }
}
}
return false;
return None;
}
fn compute_captures(&self, fn_expr_id: node_id) -> @[CaptureVar] {

View File

@ -72,8 +72,8 @@ pub fn pat_is_binding_or_wild(dm: resolve::DefMap, pat: @pat) -> bool {
}
pub fn pat_bindings(dm: resolve::DefMap, pat: @pat,
it: &fn(binding_mode, node_id, span, @Path)) {
do walk_pat(pat) |p| {
it: &fn(binding_mode, node_id, span, @Path)) {
for walk_pat(pat) |p| {
match p.node {
pat_ident(binding_mode, pth, _) if pat_is_binding(dm, p) => {
it(binding_mode, p.id, p.span, pth);

View File

@ -4151,7 +4151,7 @@ pub impl Resolver {
bindings_list: Option<@mut HashMap<ident,node_id>>,
visitor: ResolveVisitor) {
let pat_id = pattern.id;
do walk_pat(pattern) |pattern| {
for walk_pat(pattern) |pattern| {
match pattern.node {
pat_ident(binding_mode, path, _)
if !path.global && path.idents.len() == 1 => {

View File

@ -157,8 +157,8 @@ pub fn check_pat_variant(pcx: &pat_ctxt, pat: @ast::pat, path: @ast::Path,
None);
fcx.write_error(pat.id);
kind_name = "[error]";
arg_types = (copy subpats).get_or_default(~[]).map(|_|
ty::mk_err());
arg_types = (copy *subpats).get_or_default(~[]).map(|_|
ty::mk_err());
}
}
}
@ -199,8 +199,8 @@ pub fn check_pat_variant(pcx: &pat_ctxt, pat: @ast::pat, path: @ast::Path,
None);
fcx.write_error(pat.id);
kind_name = "[error]";
arg_types = (copy subpats).get_or_default(~[]).map(|_|
ty::mk_err());
arg_types = (copy *subpats).get_or_default(~[]).map(|_|
ty::mk_err());
}
}

View File

@ -801,3 +801,9 @@ impl UserString for ty::TraitRef {
}
}
}
impl UserString for ty::t {
fn user_string(&self, tcx: ctxt) -> ~str {
ty_to_str(tcx, *self)
}
}

View File

@ -74,7 +74,7 @@ fn build_mod_index(
) -> doc::Index {
doc::Index {
entries: doc.items.map(|doc| {
item_to_entry(copy *doc, copy config)
item_to_entry(copy *doc, &config)
})
}
}
@ -85,14 +85,14 @@ fn build_nmod_index(
) -> doc::Index {
doc::Index {
entries: doc.fns.map(|doc| {
item_to_entry(doc::FnTag(copy *doc), copy config)
item_to_entry(doc::FnTag(copy *doc), &config)
})
}
}
fn item_to_entry(
doc: doc::ItemTag,
config: config::Config
config: &config::Config
) -> doc::IndexEntry {
let link = match doc {
doc::ModTag(_) | doc::NmodTag(_)
@ -222,13 +222,13 @@ mod test {
config::DocPerCrate,
~"mod a { } fn b() { }"
);
assert!((&doc.cratemod().index).get().entries[0] == doc::IndexEntry {
assert!(doc.cratemod().index.get().entries[0] == doc::IndexEntry {
kind: ~"Module",
name: ~"a",
brief: None,
link: ~"#module-a"
});
assert!((&doc.cratemod().index).get().entries[1] == doc::IndexEntry {
assert!(doc.cratemod().index.get().entries[1] == doc::IndexEntry {
kind: ~"Function",
name: ~"b",
brief: None,
@ -242,13 +242,13 @@ mod test {
config::DocPerMod,
~"mod a { } fn b() { }"
);
assert!((&doc.cratemod().index).get().entries[0] == doc::IndexEntry {
assert!(doc.cratemod().index.get().entries[0] == doc::IndexEntry {
kind: ~"Module",
name: ~"a",
brief: None,
link: ~"a.html"
});
assert!((&doc.cratemod().index).get().entries[1] == doc::IndexEntry {
assert!(doc.cratemod().index.get().entries[1] == doc::IndexEntry {
kind: ~"Function",
name: ~"b",
brief: None,
@ -262,7 +262,7 @@ mod test {
config::DocPerMod,
~"#[doc = \"test\"] mod a { }"
);
assert!((&doc.cratemod().index).get().entries[0].brief
assert!(doc.cratemod().index.get().entries[0].brief
== Some(~"test"));
}
@ -272,7 +272,7 @@ mod test {
config::DocPerCrate,
~"extern { fn b(); }"
);
assert!((&doc.cratemod().nmods()[0].index).get().entries[0]
assert!(doc.cratemod().nmods()[0].index.get().entries[0]
== doc::IndexEntry {
kind: ~"Function",
name: ~"b",

View File

@ -181,12 +181,12 @@ pub fn header_name(doc: doc::ItemTag) -> ~str {
}
&doc::ImplTag(ref doc) => {
assert!(doc.self_ty.is_some());
let bounds = if (&doc.bounds_str).is_some() {
fmt!(" where %s", (&doc.bounds_str).get())
let bounds = if doc.bounds_str.is_some() {
fmt!(" where %s", *doc.bounds_str.get_ref())
} else {
~""
};
let self_ty = (&doc.self_ty).get();
let self_ty = doc.self_ty.get_ref();
let mut trait_part = ~"";
for doc.trait_types.eachi |i, trait_type| {
if i == 0 {
@ -196,7 +196,7 @@ pub fn header_name(doc: doc::ItemTag) -> ~str {
}
trait_part += *trait_type;
}
fmt!("%s for %s%s", trait_part, self_ty, bounds)
fmt!("%s for %s%s", trait_part, *self_ty, bounds)
}
_ => {
doc.name()
@ -208,17 +208,17 @@ pub fn header_text(doc: doc::ItemTag) -> ~str {
match &doc {
&doc::ImplTag(ref ImplDoc) => {
let header_kind = header_kind(copy doc);
let bounds = if (&ImplDoc.bounds_str).is_some() {
fmt!(" where `%s`", (&ImplDoc.bounds_str).get())
let bounds = if ImplDoc.bounds_str.is_some() {
fmt!(" where `%s`", *ImplDoc.bounds_str.get_ref())
} else {
~""
};
let desc = if ImplDoc.trait_types.is_empty() {
fmt!("for `%s`%s", (&ImplDoc.self_ty).get(), bounds)
fmt!("for `%s`%s", *ImplDoc.self_ty.get_ref(), bounds)
} else {
fmt!("of `%s` for `%s`%s",
ImplDoc.trait_types[0],
(&ImplDoc.self_ty).get(),
*ImplDoc.self_ty.get_ref(),
bounds)
};
return fmt!("%s %s", header_kind, desc);
@ -295,7 +295,7 @@ fn write_mod_contents(
) {
write_common(ctxt, doc.desc(), doc.sections());
if doc.index.is_some() {
write_index(ctxt, (&doc.index).get());
write_index(ctxt, doc.index.get_ref());
}
for doc.items.each |itemTag| {
@ -340,7 +340,7 @@ fn item_header_lvl(doc: &doc::ItemTag) -> Hlvl {
}
}
fn write_index(ctxt: &Ctxt, index: doc::Index) {
fn write_index(ctxt: &Ctxt, index: &doc::Index) {
if vec::is_empty(index.entries) {
return;
}
@ -353,7 +353,7 @@ fn write_index(ctxt: &Ctxt, index: doc::Index) {
let id = copy entry.link;
if entry.brief.is_some() {
ctxt.w.put_line(fmt!("* [%s](%s) - %s",
header, id, (&entry.brief).get()));
header, id, *entry.brief.get_ref()));
} else {
ctxt.w.put_line(fmt!("* [%s](%s)", header, id));
}
@ -366,7 +366,7 @@ fn write_index(ctxt: &Ctxt, index: doc::Index) {
fn write_nmod(ctxt: &Ctxt, doc: doc::NmodDoc) {
write_common(ctxt, doc.desc(), doc.sections());
if doc.index.is_some() {
write_index(ctxt, (&doc.index).get());
write_index(ctxt, doc.index.get_ref());
}
for doc.fns.each |FnDoc| {
@ -450,17 +450,17 @@ fn write_variants(
fn write_variant(ctxt: &Ctxt, doc: doc::VariantDoc) {
assert!(doc.sig.is_some());
let sig = (&doc.sig).get();
let sig = doc.sig.get_ref();
// space out list items so they all end up within paragraph elements
ctxt.w.put_line(~"");
match copy doc.desc {
Some(desc) => {
ctxt.w.put_line(list_item_indent(fmt!("* `%s` - %s", sig, desc)));
ctxt.w.put_line(list_item_indent(fmt!("* `%s` - %s", *sig, desc)));
}
None => {
ctxt.w.put_line(fmt!("* `%s`", sig));
ctxt.w.put_line(fmt!("* `%s`", *sig));
}
}
}

View File

@ -59,20 +59,20 @@ pub fn make_writer_factory(config: config::Config) -> WriterFactory {
fn markdown_writer_factory(config: config::Config) -> WriterFactory {
let result: ~fn(page: doc::Page) -> Writer = |page| {
markdown_writer(copy config, page)
markdown_writer(&config, page)
};
result
}
fn pandoc_writer_factory(config: config::Config) -> WriterFactory {
let result: ~fn(doc::Page) -> Writer = |page| {
pandoc_writer(copy config, page)
pandoc_writer(&config, page)
};
result
}
fn markdown_writer(
config: config::Config,
config: &config::Config,
page: doc::Page
) -> Writer {
let filename = make_local_filename(config, page);
@ -82,11 +82,11 @@ fn markdown_writer(
}
fn pandoc_writer(
config: config::Config,
config: &config::Config,
page: doc::Page
) -> Writer {
assert!(config.pandoc_cmd.is_some());
let pandoc_cmd = (&config.pandoc_cmd).get();
let pandoc_cmd = copy *config.pandoc_cmd.get_ref();
let filename = make_local_filename(config, page);
let pandoc_args = ~[
@ -136,15 +136,15 @@ fn generic_writer(process: ~fn(markdown: ~str)) -> Writer {
}
pub fn make_local_filename(
config: config::Config,
config: &config::Config,
page: doc::Page
) -> Path {
let filename = make_filename(copy config, page);
let filename = make_filename(config, page);
config.output_dir.push_rel(&filename)
}
pub fn make_filename(
config: config::Config,
config: &config::Config,
page: doc::Page
) -> Path {
let filename = {
@ -247,7 +247,7 @@ mod test {
};
let doc = mk_doc(~"test", ~"");
let page = doc::CratePage(doc.CrateDoc());
let filename = make_local_filename(config, page);
let filename = make_local_filename(&config, page);
assert_eq!(filename.to_str(), ~"output/dir/test.md");
}
@ -261,7 +261,7 @@ mod test {
};
let doc = mk_doc(~"", ~"");
let page = doc::CratePage(doc.CrateDoc());
let filename = make_local_filename(config, page);
let filename = make_local_filename(&config, page);
assert_eq!(filename.to_str(), ~"output/dir/index.html");
}
@ -276,7 +276,7 @@ mod test {
let doc = mk_doc(~"", ~"mod a { mod b { } }");
let modb = copy doc.cratemod().mods()[0].mods()[0];
let page = doc::ItemPage(doc::ModTag(modb));
let filename = make_local_filename(config, page);
let filename = make_local_filename(&config, page);
assert_eq!(filename, Path("output/dir/a_b.html"));
}
}

View File

@ -113,7 +113,7 @@ fn sectionalize(desc: Option<~str>) -> (Option<~str>, ~[doc::Section]) {
match parse_header(copy *line) {
Some(header) => {
if current_section.is_some() {
sections += [(&current_section).get()];
sections += [copy *current_section.get_ref()];
}
current_section = Some(doc::Section {
header: header,

View File

@ -434,7 +434,7 @@ mod test {
#[test]
fn should_add_struct_defs() {
let doc = mk_doc(~"struct S { field: () }");
assert!((&doc.cratemod().structs()[0].sig).get().contains(
assert!(doc.cratemod().structs()[0].sig.get().contains(
"struct S {"));
}
@ -442,6 +442,6 @@ mod test {
fn should_not_serialize_struct_attrs() {
// All we care about are the fields
let doc = mk_doc(~"#[wut] struct S { field: () }");
assert!(!(&doc.cratemod().structs()[0].sig).get().contains("wut"));
assert!(!doc.cratemod().structs()[0].sig.get().contains("wut"));
}
}

View File

@ -527,36 +527,31 @@ pub fn is_item_impl(item: @ast::item) -> bool {
}
}
pub fn walk_pat(pat: @pat, it: &fn(@pat)) {
it(pat);
pub fn walk_pat(pat: @pat, it: &fn(@pat) -> bool) -> bool {
if !it(pat) {
return false;
}
match pat.node {
pat_ident(_, _, Some(p)) => walk_pat(p, it),
pat_struct(_, ref fields, _) => {
for fields.each |f| {
walk_pat(f.pat, it)
}
fields.each(|f| walk_pat(f.pat, it))
}
pat_enum(_, Some(ref s)) | pat_tup(ref s) => {
for s.each |p| {
walk_pat(*p, it)
}
s.each(|&p| walk_pat(p, it))
}
pat_box(s) | pat_uniq(s) | pat_region(s) => {
walk_pat(s, it)
}
pat_vec(ref before, ref slice, ref after) => {
for before.each |p| {
walk_pat(*p, it)
}
for slice.each |p| {
walk_pat(*p, it)
}
for after.each |p| {
walk_pat(*p, it)
}
before.each(|&p| walk_pat(p, it)) &&
slice.each(|&p| walk_pat(p, it)) &&
after.each(|&p| walk_pat(p, it))
}
pat_wild | pat_lit(_) | pat_range(_, _) | pat_ident(_, _, _) |
pat_enum(_, _) => { }
pat_enum(_, _) => {
true
}
}
}

View File

@ -0,0 +1,15 @@
// Test that attempt to alias `&mut` pointer while pointee is borrowed
// yields an error.
//
// Example from src/middle/borrowck/doc.rs
use std::util::swap;
fn foo(t0: &mut int) {
let p: &int = &*t0; // Freezes `*t0`
let q: &const &mut int = &const t0; //~ ERROR cannot borrow `t0`
**q = 22; //~ ERROR cannot assign to an `&mut` in a `&const` pointer
}
fn main() {
}

View File

@ -0,0 +1,31 @@
// Test that attempt to reborrow an `&mut` pointer in an aliasable
// location yields an error.
//
// Example from src/middle/borrowck/doc.rs
use std::util::swap;
fn foo(t0: & &mut int) {
let t1 = t0;
let p: &int = &**t0; //~ ERROR cannot borrow an `&mut` in a `&` pointer
**t1 = 22; //~ ERROR cannot assign
}
fn foo2(t0: &const &mut int) {
// Note: reborrowing from an &const actually yields two errors, since it
// is unsafe in two ways: we can't control the aliasing, and we can't
// control the mutation.
let t1 = t0;
let p: &int = &**t0; //~ ERROR cannot borrow an `&mut` in a `&const` pointer
//~^ ERROR unsafe borrow of aliasable, const value
**t1 = 22; //~ ERROR cannot assign
}
fn foo3(t0: &mut &mut int) {
let t1 = &mut *t0;
let p: &int = &**t0; //~ ERROR cannot borrow
**t1 = 22;
}
fn main() {
}

View File

@ -7,8 +7,7 @@ fn main() {
//~^ ERROR cannot move `foo`
let bar = ~3;
let _g = || {
let _g = || { //~ ERROR capture of moved value
let _h: @fn() -> int = || *bar;
//~^ ERROR illegal by-move capture
};
}

View File

@ -0,0 +1,15 @@
// Test that attempt to move `&mut` pointer while pointee is borrowed
// yields an error.
//
// Example from src/middle/borrowck/doc.rs
use std::util::swap;
fn foo(t0: &mut int) {
let p: &int = &*t0; // Freezes `*t0`
let t1 = t0; //~ ERROR cannot move out of `t0`
*t1 = 22;
}
fn main() {
}

View File

@ -0,0 +1,31 @@
// Test that we do not permit moves from &[] matched by a vec pattern.
struct Foo {
string: ~str
}
pub fn main() {
let x = [
Foo { string: ~"foo" },
Foo { string: ~"bar" },
Foo { string: ~"baz" }
];
match x {
[first, ..tail] => {
match tail {
[Foo { string: a }, Foo { string: b }] => {
//~^ ERROR cannot move out of dereference of & pointer
//~^^ ERROR cannot move out of dereference of & pointer
}
_ => {
::std::util::unreachable();
}
}
let z = copy tail[0];
debug!(fmt!("%?", z));
}
_ => {
::std::util::unreachable();
}
}
}

View File

@ -0,0 +1,16 @@
// Test that attempt to swap `&mut` pointer while pointee is borrowed
// yields an error.
//
// Example from src/middle/borrowck/doc.rs
use std::util::swap;
fn foo<'a>(mut t0: &'a mut int,
mut t1: &'a mut int) {
let p: &int = &*t0; // Freezes `*t0`
swap(&mut t0, &mut t1); //~ ERROR cannot borrow `t0`
*t1 = 22;
}
fn main() {
}

View File

@ -10,7 +10,7 @@
fn foo(x: ~int) -> int {
let y = &*x;
free(x); //~ ERROR cannot move out of `*x` because it is borrowed
free(x); //~ ERROR cannot move out of `x` because it is borrowed
*y
}

View File

@ -13,7 +13,7 @@ fn main() {
let s = S { x: Bar(~"hello") };
match &s.x {
&Foo => {}
&Bar(identifier) => f(copy identifier) //~ ERROR by-move pattern bindings may not occur
&Bar(identifier) => f(copy identifier) //~ ERROR cannot move
};
match &s.x {
&Foo => {}

View File

@ -23,6 +23,6 @@ fn main() {
match x {
X { x: y } => error!("contents: %s", y)
//~^ ERROR cannot bind by-move within struct
//~^ ERROR cannot move out of type `X`, which defines the `Drop` trait
}
}

View File

@ -18,7 +18,7 @@ trait parse {
impl parse for parser {
fn parse(&self) -> ~[int] {
self.tokens //~ ERROR cannot move out of field
self.tokens //~ ERROR cannot move out of dereference of & pointer
}
}

View File

@ -16,10 +16,7 @@ fn main() {
loop {
loop {
loop {
// tjc: Not sure why it prints the same error twice
x = y; //~ ERROR use of moved value
//~^ ERROR use of moved value
copy x;
}
}

View File

@ -13,10 +13,8 @@ fn main() {
let y: ~int = ~42;
let mut x: ~int;
loop {
debug!(y);
// tjc: not sure why it prints the same error twice
debug!(y); //~ ERROR use of moved value: `y`
while true { while true { while true { x = y; copy x; } } }
//~^ ERROR use of moved value: `y`
//~^^ ERROR use of moved value: `y`
}
}

View File

@ -7,13 +7,13 @@ fn touch<A>(_a: &A) {}
fn f10() {
let x = Foo { f: ~"hi", y: 3 };
consume(x.f); //~ NOTE field of `x` moved here
consume(x.f); //~ NOTE `x.f` moved here
touch(&x.y); //~ ERROR use of partially moved value: `x`
}
fn f20() {
let x = ~[~"hi"];
consume(x[0]); //~ NOTE element of `x` moved here
consume(x[0]); //~ NOTE `(*x)[]` moved here
touch(&x[0]); //~ ERROR use of partially moved value: `x`
}

View File

@ -16,9 +16,9 @@ fn main() {
let s = S { x: ~Bar(~42) };
loop {
do f(&s) |hellothere| {
match hellothere.x { //~ ERROR cannot move out
match hellothere.x {
~Foo(_) => {}
~Bar(x) => io::println(x.to_str()),
~Bar(x) => io::println(x.to_str()), //~ ERROR cannot move out
~Baz => {}
}
}

View File

@ -86,7 +86,7 @@ fn f110() {
}
fn f120() {
let x = ~[~"hi", ~"ho"];
let mut x = ~[~"hi", ~"ho"];
vec::swap(x, 0, 1);
touch(&x[0]);
touch(&x[1]);

View File

@ -0,0 +1,19 @@
// Tests that bindings to move-by-default values trigger moves of the
// discriminant. Also tests that the compiler explains the move in
// terms of the binding, not the discriminant.
struct Foo<A> { f: A }
fn guard(_s: ~str) -> bool {fail!()}
fn touch<A>(_a: &A) {}
fn f10() {
let x = Foo {f: ~"hi"};
let y = match x {
Foo {f} => {} //~ NOTE moved here
};
touch(&x); //~ ERROR use of partially moved value: `x`
}
fn main() {}

View File

@ -15,12 +15,12 @@ fn main() {
let v = ~[1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let arc_v = arc::ARC(v);
do task::spawn() { //~ NOTE `arc_v` moved into closure environment here
do task::spawn() {
let v = arc_v.get();
assert_eq!(v[3], 4);
};
assert_eq!((arc_v.get())[2], 3); //~ ERROR use of moved value: `arc_v`
info!(arc_v);
info!(arc_v); //~ ERROR use of moved value: `arc_v`
}

View File

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

View File

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

View File

@ -28,5 +28,5 @@ struct wrapper(noncopyable);
fn main() {
let x1 = wrapper(noncopyable());
let _x2 = *x1; //~ ERROR cannot move out
let _x2 = *x1;
}

View File

@ -0,0 +1,23 @@
use std::str;
struct StringBuffer {
s: ~str
}
impl StringBuffer {
pub fn append(&mut self, v: &str) {
str::push_str(&mut self.s, v);
}
}
fn to_str(sb: StringBuffer) -> ~str {
sb.s
}
fn main() {
let mut sb = StringBuffer {s: ~""};
sb.append("Hello, ");
sb.append("World!");
let str = to_str(sb);
assert_eq!(str, ~"Hello, World!");
}

View File

@ -4,8 +4,8 @@ fn foldl<T, U: Copy+Clone>(
function: &fn(partial: U, element: &T) -> U
) -> U {
match values {
[head, ..tail] =>
foldl(tail, function(initial, &head), function),
[ref head, ..tail] =>
foldl(tail, function(initial, head), function),
[] => initial.clone()
}
}
@ -16,8 +16,8 @@ fn foldr<T, U: Copy+Clone>(
function: &fn(element: &T, partial: U) -> U
) -> U {
match values {
[..head, tail] =>
foldr(head, function(&tail, initial), function),
[..head, ref tail] =>
foldr(head, function(tail, initial), function),
[] => initial.clone()
}
}

View File

@ -19,9 +19,9 @@ pub fn main() {
[Foo { _ }, _, Foo { _ }, ..tail] => {
::std::util::unreachable();
}
[Foo { string: a }, Foo { string: b }] => {
assert_eq!(a, ~"bar");
assert_eq!(b, ~"baz");
[Foo { string: ref a }, Foo { string: ref b }] => {
assert_eq!("bar", a.slice(0, a.len()));
assert_eq!("baz", b.slice(0, b.len()));
}
_ => {
::std::util::unreachable();