diff --git a/src/compiletest/errors.rs b/src/compiletest/errors.rs index c795e69a44dea..f15db7d9371de 100644 --- a/src/compiletest/errors.rs +++ b/src/compiletest/errors.rs @@ -7,6 +7,7 @@ // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. +use self::WhichLine::*; use std::ascii::AsciiExt; use std::io::{BufferedReader, File}; @@ -18,28 +19,74 @@ pub struct ExpectedError { pub msg: String, } -pub static EXPECTED_PATTERN : &'static str = r"//~(?P\^*)\s*(?P\S*)\s*(?P.*)"; +/// Looks for either "//~| KIND MESSAGE" or "//~^^... KIND MESSAGE" +/// The former is a "follow" that inherits its target from the preceding line; +/// the latter is an "adjusts" that goes that many lines up. +/// +/// Goal is to enable tests both like: //~^^^ ERROR go up three +/// and also //~^ ERROR message one for the preceding line, and +/// //~| ERROR message two for that same line. + +pub static EXPECTED_PATTERN : &'static str = + r"//~(?P\|)?(?P\^*)\s*(?P\S*)\s*(?P.*)"; + +#[deriving(PartialEq, Show)] +enum WhichLine { ThisLine, FollowPrevious(uint), AdjustBackward(uint) } // Load any test directives embedded in the file pub fn load_errors(re: &Regex, testfile: &Path) -> Vec { let mut rdr = BufferedReader::new(File::open(testfile).unwrap()); + // `last_nonfollow_error` tracks the most recently seen + // line with an error template that did not use the + // follow-syntax, "//~| ...". + // + // (pnkfelix could not find an easy way to compose Iterator::scan + // and Iterator::filter_map to pass along this information into + // `parse_expected`. So instead I am storing that state here and + // updating it in the map callback below.) + let mut last_nonfollow_error = None; + rdr.lines().enumerate().filter_map(|(line_no, ln)| { - parse_expected(line_no + 1, ln.unwrap().as_slice(), re) + parse_expected(last_nonfollow_error, + line_no + 1, + ln.unwrap().as_slice(), re) + .map(|(which, error)| { + match which { + FollowPrevious(_) => {} + _ => last_nonfollow_error = Some(error.line), + } + error + }) }).collect() } -fn parse_expected(line_num: uint, line: &str, re: &Regex) -> Option { +fn parse_expected(last_nonfollow_error: Option, + line_num: uint, + line: &str, + re: &Regex) -> Option<(WhichLine, ExpectedError)> { re.captures(line).and_then(|caps| { let adjusts = caps.name("adjusts").len(); let kind = caps.name("kind").to_ascii_lower(); let msg = caps.name("msg").trim().to_string(); + let follow = caps.name("follow").len() > 0; + + let (which, line) = if follow { + assert!(adjusts == 0, "use either //~| or //~^, not both."); + let line = last_nonfollow_error.unwrap_or_else(|| { + panic!("encountered //~| without preceding //~^ line.") + }); + (FollowPrevious(line), line) + } else { + let which = + if adjusts > 0 { AdjustBackward(adjusts) } else { ThisLine }; + let line = line_num - adjusts; + (which, line) + }; - debug!("line={} kind={} msg={}", line_num, kind, msg); - Some(ExpectedError { - line: line_num - adjusts, - kind: kind, - msg: msg, - }) + debug!("line={} which={} kind={} msg={}", line_num, which, kind, msg); + Some((which, ExpectedError { line: line, + kind: kind, + msg: msg, })) }) } diff --git a/src/librustc/middle/borrowck/check_loans.rs b/src/librustc/middle/borrowck/check_loans.rs index 238a4ca7bd69e..afcc533ffb81c 100644 --- a/src/librustc/middle/borrowck/check_loans.rs +++ b/src/librustc/middle/borrowck/check_loans.rs @@ -19,6 +19,8 @@ use self::UseError::*; use middle::borrowck::*; +use middle::borrowck::LoanPathElem::*; +use middle::borrowck::LoanPathKind::*; use middle::expr_use_visitor as euv; use middle::mem_categorization as mc; use middle::region; @@ -33,49 +35,51 @@ use std::rc::Rc; // be less precise in its handling of Box while still allowing moves out of a // Box. They should be removed when OwnedPtr is removed from LoanPath. -fn owned_ptr_base_path<'a>(loan_path: &'a LoanPath) -> &'a LoanPath { +fn owned_ptr_base_path<'a, 'tcx>(loan_path: &'a LoanPath<'tcx>) -> &'a LoanPath<'tcx> { //! Returns the base of the leftmost dereference of an OwnedPtr in //! `loan_path`. If there is no dereference of an OwnedPtr in `loan_path`, //! then it just returns `loan_path` itself. - return match owned_ptr_base_path_helper(loan_path) { + return match helper(loan_path) { Some(new_loan_path) => new_loan_path, None => loan_path.clone() }; - fn owned_ptr_base_path_helper<'a>(loan_path: &'a LoanPath) -> Option<&'a LoanPath> { - match *loan_path { + fn helper<'a, 'tcx>(loan_path: &'a LoanPath<'tcx>) -> Option<&'a LoanPath<'tcx>> { + match loan_path.kind { LpVar(_) | LpUpvar(_) => None, LpExtend(ref lp_base, _, LpDeref(mc::OwnedPtr)) => { - match owned_ptr_base_path_helper(&**lp_base) { + match helper(&**lp_base) { v @ Some(_) => v, None => Some(&**lp_base) } } - LpExtend(ref lp_base, _, _) => owned_ptr_base_path_helper(&**lp_base) + LpDowncast(ref lp_base, _) | + LpExtend(ref lp_base, _, _) => helper(&**lp_base) } } } -fn owned_ptr_base_path_rc(loan_path: &Rc) -> Rc { +fn owned_ptr_base_path_rc<'tcx>(loan_path: &Rc>) -> Rc> { //! The equivalent of `owned_ptr_base_path` for an &Rc rather than //! a &LoanPath. - return match owned_ptr_base_path_helper(loan_path) { + return match helper(loan_path) { Some(new_loan_path) => new_loan_path, None => loan_path.clone() }; - fn owned_ptr_base_path_helper(loan_path: &Rc) -> Option> { - match **loan_path { + fn helper<'tcx>(loan_path: &Rc>) -> Option>> { + match loan_path.kind { LpVar(_) | LpUpvar(_) => None, LpExtend(ref lp_base, _, LpDeref(mc::OwnedPtr)) => { - match owned_ptr_base_path_helper(lp_base) { + match helper(lp_base) { v @ Some(_) => v, None => Some(lp_base.clone()) } } - LpExtend(ref lp_base, _, _) => owned_ptr_base_path_helper(lp_base) + LpDowncast(ref lp_base, _) | + LpExtend(ref lp_base, _, _) => helper(lp_base) } } } @@ -84,7 +88,7 @@ struct CheckLoanCtxt<'a, 'tcx: 'a> { bccx: &'a BorrowckCtxt<'a, 'tcx>, dfcx_loans: &'a LoanDataFlow<'a, 'tcx>, move_data: move_data::FlowedMoveData<'a, 'tcx>, - all_loans: &'a [Loan], + all_loans: &'a [Loan<'tcx>], } impl<'a, 'tcx> euv::Delegate<'tcx> for CheckLoanCtxt<'a, 'tcx> { @@ -99,6 +103,11 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for CheckLoanCtxt<'a, 'tcx> { self.consume_common(consume_id, consume_span, cmt, mode); } + fn matched_pat(&mut self, + _matched_pat: &ast::Pat, + _cmt: mc::cmt, + _mode: euv::MatchMode) { } + fn consume_pat(&mut self, consume_pat: &ast::Pat, cmt: mc::cmt<'tcx>, @@ -183,7 +192,7 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for CheckLoanCtxt<'a, 'tcx> { pub fn check_loans<'a, 'b, 'c, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, dfcx_loans: &LoanDataFlow<'b, 'tcx>, move_data: move_data::FlowedMoveData<'c, 'tcx>, - all_loans: &[Loan], + all_loans: &[Loan<'tcx>], decl: &ast::FnDecl, body: &ast::Block) { debug!("check_loans(body id={})", body.id); @@ -202,9 +211,9 @@ pub fn check_loans<'a, 'b, 'c, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, } #[deriving(PartialEq)] -enum UseError { +enum UseError<'tcx> { UseOk, - UseWhileBorrowed(/*loan*/Rc, /*loan*/Span) + UseWhileBorrowed(/*loan*/Rc>, /*loan*/Span) } fn compatible_borrow_kinds(borrow_kind1: ty::BorrowKind, @@ -216,7 +225,7 @@ fn compatible_borrow_kinds(borrow_kind1: ty::BorrowKind, impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { pub fn tcx(&self) -> &'a ty::ctxt<'tcx> { self.bccx.tcx } - pub fn each_issued_loan(&self, scope: region::CodeExtent, op: |&Loan| -> bool) + pub fn each_issued_loan(&self, scope: region::CodeExtent, op: |&Loan<'tcx>| -> bool) -> bool { //! Iterates over each loan that has been issued //! on entrance to `scope`, regardless of whether it is @@ -232,7 +241,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { pub fn each_in_scope_loan(&self, scope: region::CodeExtent, - op: |&Loan| -> bool) + op: |&Loan<'tcx>| -> bool) -> bool { //! Like `each_issued_loan()`, but only considers loans that are //! currently in scope. @@ -249,8 +258,8 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { fn each_in_scope_loan_affecting_path(&self, scope: region::CodeExtent, - loan_path: &LoanPath, - op: |&Loan| -> bool) + loan_path: &LoanPath<'tcx>, + op: |&Loan<'tcx>| -> bool) -> bool { //! Iterates through all of the in-scope loans affecting `loan_path`, //! calling `op`, and ceasing iteration if `false` is returned. @@ -294,10 +303,11 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { let mut loan_path = loan_path; loop { - match *loan_path { + match loan_path.kind { LpVar(_) | LpUpvar(_) => { break; } + LpDowncast(ref lp_base, _) | LpExtend(ref lp_base, _, _) => { loan_path = &**lp_base; } @@ -363,8 +373,8 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { } pub fn report_error_if_loans_conflict(&self, - old_loan: &Loan, - new_loan: &Loan) { + old_loan: &Loan<'tcx>, + new_loan: &Loan<'tcx>) { //! Checks whether `old_loan` and `new_loan` can safely be issued //! simultaneously. @@ -383,10 +393,10 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { } pub fn report_error_if_loan_conflicts_with_restriction(&self, - loan1: &Loan, - loan2: &Loan, - old_loan: &Loan, - new_loan: &Loan) + loan1: &Loan<'tcx>, + loan2: &Loan<'tcx>, + old_loan: &Loan<'tcx>, + new_loan: &Loan<'tcx>) -> bool { //! Checks whether the restrictions introduced by `loan1` would //! prohibit `loan2`. Returns false if an error is reported. @@ -549,7 +559,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { true } - fn is_local_variable_or_arg(&self, cmt: mc::cmt) -> bool { + fn is_local_variable_or_arg(&self, cmt: mc::cmt<'tcx>) -> bool { match cmt.cat { mc::cat_local(_) => true, _ => false @@ -559,7 +569,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { fn consume_common(&self, id: ast::NodeId, span: Span, - cmt: mc::cmt, + cmt: mc::cmt<'tcx>, mode: euv::ConsumeMode) { match opt_loan_path(&cmt) { Some(lp) => { @@ -600,7 +610,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { fn check_for_copy_of_frozen_path(&self, id: ast::NodeId, span: Span, - copy_path: &LoanPath) { + copy_path: &LoanPath<'tcx>) { match self.analyze_restrictions_on_use(id, copy_path, ty::ImmBorrow) { UseOk => { } UseWhileBorrowed(loan_path, loan_span) => { @@ -621,7 +631,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { fn check_for_move_of_borrowed_path(&self, id: ast::NodeId, span: Span, - move_path: &LoanPath, + move_path: &LoanPath<'tcx>, move_kind: move_data::MoveKind) { // We want to detect if there are any loans at all, so we search for // any loans incompatible with MutBorrrow, since all other kinds of @@ -652,9 +662,9 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { pub fn analyze_restrictions_on_use(&self, expr_id: ast::NodeId, - use_path: &LoanPath, + use_path: &LoanPath<'tcx>, borrow_kind: ty::BorrowKind) - -> UseError { + -> UseError<'tcx> { debug!("analyze_restrictions_on_use(expr_id={}, use_path={})", self.tcx().map.node_to_string(expr_id), use_path.repr(self.tcx())); @@ -678,7 +688,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { id: ast::NodeId, span: Span, use_kind: MovedValueUseKind, - lp: &Rc) { + lp: &Rc>) { /*! * Reports an error if `expr` (which should be a path) * is using a moved/uninitialized value @@ -702,7 +712,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { id: ast::NodeId, span: Span, use_kind: MovedValueUseKind, - lp: &Rc) + lp: &Rc>) { /*! * Reports an error if assigning to `lp` will use a @@ -722,10 +732,15 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { * (*p).x = 22; // not ok, p is uninitialized, can't deref */ - match **lp { + match lp.kind { LpVar(_) | LpUpvar(_) => { // assigning to `x` does not require that `x` is initialized } + LpDowncast(ref lp_base, _) => { + // assigning to `(P->Variant).f` is ok if assigning to `P` is ok + self.check_if_assigned_path_is_moved(id, span, + use_kind, lp_base); + } LpExtend(ref lp_base, _, LpInterior(_)) => { // assigning to `P.f` is ok if assigning to `P` is ok self.check_if_assigned_path_is_moved(id, span, @@ -864,7 +879,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { cmt = b; } - mc::cat_downcast(b) | + mc::cat_downcast(b, _) | mc::cat_interior(b, _) => { assert_eq!(cmt.mutbl, mc::McInherited); cmt = b; @@ -915,11 +930,11 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { } } - fn check_for_assignment_to_borrowed_path( - this: &CheckLoanCtxt, + fn check_for_assignment_to_borrowed_path<'a, 'tcx>( + this: &CheckLoanCtxt<'a, 'tcx>, assignment_id: ast::NodeId, assignment_span: Span, - assignee_cmt: mc::cmt) + assignee_cmt: mc::cmt<'tcx>) { //! Check for assignments that violate the terms of an //! outstanding loan. @@ -939,7 +954,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { pub fn report_illegal_mutation(&self, span: Span, - loan_path: &LoanPath, + loan_path: &LoanPath<'tcx>, loan: &Loan) { self.bccx.span_err( span, diff --git a/src/librustc/middle/borrowck/doc.rs b/src/librustc/middle/borrowck/doc.rs index 882b6bc842644..5b70d97b40276 100644 --- a/src/librustc/middle/borrowck/doc.rs +++ b/src/librustc/middle/borrowck/doc.rs @@ -27,6 +27,7 @@ These docs are long. Search for the section you are interested in. - Formal model - Borrowing and loans - Moves and initialization +- Drop flags and structural fragments - Future work # Overview @@ -1019,6 +1020,175 @@ walk back over, identify all uses, assignments, and captures, and check that they are legal given the set of dataflow bits we have computed for that program point. +# Drop flags and structural fragments + +In addition to the job of enforcing memory safety, the borrow checker +code is also responsible for identifying the *structural fragments* of +data in the function, to support out-of-band dynamic drop flags +allocated on the stack. (For background, see [RFC PR #320].) + +[RFC PR #320]: https://github.com/rust-lang/rfcs/pull/320 + +Semantically, each piece of data that has a destructor may need a +boolean flag to indicate whether or not its destructor has been run +yet. However, in many cases there is no need to actually maintain such +a flag: It can be apparent from the code itself that a given path is +always initialized (or always deinitialized) when control reaches the +end of its owner's scope, and thus we can unconditionally emit (or +not) the destructor invocation for that path. + +A simple example of this is the following: + +```rust +struct D { p: int } +impl D { fn new(x: int) -> D { ... } +impl Drop for D { ... } + +fn foo(a: D, b: D, t: || -> bool) { + let c: D; + let d: D; + if t() { c = b; } +} +``` + +At the end of the body of `foo`, the compiler knows that `a` is +initialized, introducing a drop obligation (deallocating the boxed +integer) for the end of `a`'s scope that is run unconditionally. +Likewise the compiler knows that `d` is not initialized, and thus it +leave out the drop code for `d`. + +The compiler cannot statically know the drop-state of `b` nor `c` at +the end of their scope, since that depends on the value of +`t`. Therefore, we need to insert boolean flags to track whether we +need to drop `b` and `c`. + +However, the matter is not as simple as just mapping local variables +to their corresponding drop flags when necessary. In particular, in +addition to being able to move data out of local variables, Rust +allows one to move values in and out of structured data. + +Consider the following: + +```rust +struct S { x: D, y: D, z: D } + +fn foo(a: S, mut b: S, t: || -> bool) { + let mut c: S; + let d: S; + let e: S = a.clone(); + if t() { + c = b; + b.x = e.y; + } + if t() { c.y = D::new(4); } +} +``` + +As before, the drop obligations of `a` and `d` can be statically +determined, and again the state of `b` and `c` depend on dynamic +state. But additionally, the dynamic drop obligations introduced by +`b` and `c` are not just per-local boolean flags. For example, if the +first call to `t` returns `false` and the second call `true`, then at +the end of their scope, `b` will be completely initialized, but only +`c.y` in `c` will be initialized. If both calls to `t` return `true`, +then at the end of their scope, `c` will be completely initialized, +but only `b.x` will be initialized in `b`, and only `e.x` and `e.z` +will be initialized in `e`. + +Note that we need to cover the `z` field in each case in some way, +since it may (or may not) need to be dropped, even though `z` is never +directly mentioned in the body of the `foo` function. We call a path +like `b.z` a *fragment sibling* of `b.x`, since the field `z` comes +from the same structure `S` that declared the field `x` in `b.x`. + +In general we need to maintain boolean flags that match the +`S`-structure of both `b` and `c`. In addition, we need to consult +such a flag when doing an assignment (such as `c.y = D::new(4);` +above), in order to know whether or not there is a previous value that +needs to be dropped before we do the assignment. + +So for any given function, we need to determine what flags are needed +to track its drop obligations. Our strategy for determining the set of +flags is to represent the fragmentation of the structure explicitly: +by starting initially from the paths that are explicitly mentioned in +moves and assignments (such as `b.x` and `c.y` above), and then +traversing the structure of the path's type to identify leftover +*unmoved fragments*: assigning into `c.y` means that `c.x` and `c.z` +are leftover unmoved fragments. Each fragment represents a drop +obligation that may need to be tracked. Paths that are only moved or +assigned in their entirety (like `a` and `d`) are treated as a single +drop obligation. + +The fragment construction process works by piggy-backing on the +existing `move_data` module. We already have callbacks that visit each +direct move and assignment; these form the basis for the sets of +moved_leaf_paths and assigned_leaf_paths. From these leaves, we can +walk up their parent chain to identify all of their parent paths. +We need to identify the parents because of cases like the following: + +```rust +struct Pair{ x: X, y: Y } +fn foo(dd_d_d: Pair, D>, D>) { + other_function(dd_d_d.x.y); +} +``` + +In this code, the move of the path `dd_d.x.y` leaves behind not only +the fragment drop-obligation `dd_d.x.x` but also `dd_d.y` as well. + +Once we have identified the directly-referenced leaves and their +parents, we compute the left-over fragments, in the function +`fragments::add_fragment_siblings`. As of this writing this works by +looking at each directly-moved or assigned path P, and blindly +gathering all sibling fields of P (as well as siblings for the parents +of P, etc). After accumulating all such siblings, we filter out the +entries added as siblings of P that turned out to be +directly-referenced paths (or parents of directly referenced paths) +themselves, thus leaving the never-referenced "left-overs" as the only +thing left from the gathering step. + +## Array structural fragments + +A special case of the structural fragments discussed above are +the elements of an array that has been passed by value, such as +the following: + +```rust +fn foo(a: [D, ..10], i: uint) -> D { + a[i] +} +``` + +The above code moves a single element out of the input array `a`. +The remainder of the array still needs to be dropped; i.e., it +is a structural fragment. Note that after performing such a move, +it is not legal to read from the array `a`. There are a number of +ways to deal with this, but the important thing to note is that +the semantics needs to distinguish in some manner between a +fragment that is the *entire* array versus a fragment that represents +all-but-one element of the array. A place where that distinction +would arise is the following: + +```rust +fn foo(a: [D, ..10], b: [D, ..10], i: uint, t: bool) -> D { + if t { + a[i] + } else { + b[i] + } + + // When control exits, we will need either to drop all of `a` + // and all-but-one of `b`, or to drop all of `b` and all-but-one + // of `a`. +} +``` + +There are a number of ways that the trans backend could choose to +compile this (e.g. a `[bool, ..10]` array for each such moved array; +or an `Option` for each moved array). From the viewpoint of the +borrow-checker, the important thing is to record what kind of fragment +is implied by the relevant moves. + # Future work While writing up these docs, I encountered some rules I believe to be diff --git a/src/librustc/middle/borrowck/fragments.rs b/src/librustc/middle/borrowck/fragments.rs new file mode 100644 index 0000000000000..7e766e9138e35 --- /dev/null +++ b/src/librustc/middle/borrowck/fragments.rs @@ -0,0 +1,491 @@ +// Copyright 2012-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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/*! + +Helper routines used for fragmenting structural paths due to moves for +tracking drop obligations. Please see the extensive comments in the +section "Structural fragments" in `doc.rs`. + +*/ +use self::Fragment::*; + +use session::config; +use middle::borrowck::{LoanPath}; +use middle::borrowck::LoanPathKind::{LpVar, LpUpvar, LpDowncast, LpExtend}; +use middle::borrowck::LoanPathElem::{LpDeref, LpInterior}; +use middle::borrowck::move_data::{InvalidMovePathIndex}; +use middle::borrowck::move_data::{MoveData, MovePathIndex}; +use middle::ty; +use middle::mem_categorization as mc; +use util::ppaux::{Repr, UserString}; + +use std::mem; +use std::rc::Rc; +use std::slice; +use syntax::ast; +use syntax::ast_map; +use syntax::attr::AttrMetaMethods; +use syntax::codemap::Span; + +#[deriving(PartialEq, Eq, PartialOrd, Ord)] +enum Fragment { + // This represents the path described by the move path index + Just(MovePathIndex), + + // This represents the collection of all but one of the elements + // from an array at the path described by the move path index. + // Note that attached MovePathIndex should have mem_categorization + // of InteriorElement (i.e. array dereference `[]`). + AllButOneFrom(MovePathIndex), +} + +impl Fragment { + fn loan_path_repr<'tcx>(&self, move_data: &MoveData<'tcx>, tcx: &ty::ctxt<'tcx>) -> String { + let repr = |mpi| move_data.path_loan_path(mpi).repr(tcx); + match *self { + Just(mpi) => repr(mpi), + AllButOneFrom(mpi) => format!("$(allbutone {})", repr(mpi)), + } + } + + fn loan_path_user_string<'tcx>(&self, + move_data: &MoveData<'tcx>, + tcx: &ty::ctxt<'tcx>) -> String { + let user_string = |mpi| move_data.path_loan_path(mpi).user_string(tcx); + match *self { + Just(mpi) => user_string(mpi), + AllButOneFrom(mpi) => format!("$(allbutone {})", user_string(mpi)), + } + } +} + +pub struct FragmentSets { + /// During move_data construction, `moved_leaf_paths` tracks paths + /// that have been used directly by being moved out of. When + /// move_data construction has been completed, `moved_leaf_paths` + /// tracks such paths that are *leaf fragments* (e.g. `a.j` if we + /// never move out any child like `a.j.x`); any parent paths + /// (e.g. `a` for the `a.j` example) are moved over to + /// `parents_of_fragments`. + moved_leaf_paths: Vec, + + /// `assigned_leaf_paths` tracks paths that have been used + /// directly by being overwritten, but is otherwise much like + /// `moved_leaf_paths`. + assigned_leaf_paths: Vec, + + /// `parents_of_fragments` tracks paths that are definitely + /// parents of paths that have been moved. + /// + /// FIXME(pnkfelix) probably do not want/need + /// `parents_of_fragments` at all, if we can avoid it. + /// + /// Update: I do not see a way to to avoid it. Maybe just remove + /// above fixme, or at least document why doing this may be hard. + parents_of_fragments: Vec, + + /// During move_data construction (specifically the + /// fixup_fragment_sets call), `unmoved_fragments` tracks paths + /// that have been "left behind" after a sibling has been moved or + /// assigned. When move_data construction has been completed, + /// `unmoved_fragments` tracks paths that were *only* results of + /// being left-behind, and never directly moved themselves. + unmoved_fragments: Vec, +} + +impl FragmentSets { + pub fn new() -> FragmentSets { + FragmentSets { + unmoved_fragments: Vec::new(), + moved_leaf_paths: Vec::new(), + assigned_leaf_paths: Vec::new(), + parents_of_fragments: Vec::new(), + } + } + + pub fn add_move(&mut self, path_index: MovePathIndex) { + self.moved_leaf_paths.push(path_index); + } + + pub fn add_assignment(&mut self, path_index: MovePathIndex) { + self.assigned_leaf_paths.push(path_index); + } +} + +pub fn instrument_move_fragments<'tcx>(this: &MoveData<'tcx>, + tcx: &ty::ctxt<'tcx>, + sp: Span, + id: ast::NodeId) { + let (span_err, print) = { + let attrs : &[ast::Attribute]; + attrs = match tcx.map.find(id) { + Some(ast_map::NodeItem(ref item)) => + item.attrs.as_slice(), + Some(ast_map::NodeImplItem(&ast::MethodImplItem(ref m))) => + m.attrs.as_slice(), + Some(ast_map::NodeTraitItem(&ast::ProvidedMethod(ref m))) => + m.attrs.as_slice(), + _ => [].as_slice(), + }; + + let span_err = + attrs.iter().any(|a| a.check_name("rustc_move_fragments")); + let print = tcx.sess.debugging_opt(config::PRINT_MOVE_FRAGMENTS); + + (span_err, print) + }; + + if !span_err && !print { return; } + + let instrument_all_paths = |kind, vec_rc: &Vec| { + for (i, mpi) in vec_rc.iter().enumerate() { + let render = || this.path_loan_path(*mpi).user_string(tcx); + if span_err { + tcx.sess.span_err(sp, format!("{}: `{}`", kind, render()).as_slice()); + } + if print { + println!("id:{} {}[{}] `{}`", id, kind, i, render()); + } + } + }; + + let instrument_all_fragments = |kind, vec_rc: &Vec| { + for (i, f) in vec_rc.iter().enumerate() { + let render = || f.loan_path_user_string(this, tcx); + if span_err { + tcx.sess.span_err(sp, format!("{}: `{}`", kind, render()).as_slice()); + } + if print { + println!("id:{} {}[{}] `{}`", id, kind, i, render()); + } + } + }; + + let fragments = this.fragments.borrow(); + instrument_all_paths("moved_leaf_path", &fragments.moved_leaf_paths); + instrument_all_fragments("unmoved_fragment", &fragments.unmoved_fragments); + instrument_all_paths("parent_of_fragments", &fragments.parents_of_fragments); + instrument_all_paths("assigned_leaf_path", &fragments.assigned_leaf_paths); +} + +pub fn fixup_fragment_sets<'tcx>(this: &MoveData<'tcx>, tcx: &ty::ctxt<'tcx>) { + /*! + * Normalizes the fragment sets in `this`; i.e., removes + * duplicate entries, constructs the set of parents, and + * constructs the left-over fragments. + * + * Note: "left-over fragments" means paths that were not + * directly referenced in moves nor assignments, but must + * nonetheless be tracked as potential drop obligations. + */ + + let mut fragments = this.fragments.borrow_mut(); + + // Swap out contents of fragments so that we can modify the fields + // without borrowing the common fragments. + let mut unmoved = mem::replace(&mut fragments.unmoved_fragments, vec![]); + let mut parents = mem::replace(&mut fragments.parents_of_fragments, vec![]); + let mut moved = mem::replace(&mut fragments.moved_leaf_paths, vec![]); + let mut assigned = mem::replace(&mut fragments.assigned_leaf_paths, vec![]); + + let path_lps = |mpis: &[MovePathIndex]| -> Vec { + mpis.iter().map(|mpi| this.path_loan_path(*mpi).repr(tcx)).collect() + }; + + let frag_lps = |fs: &[Fragment]| -> Vec { + fs.iter().map(|f| f.loan_path_repr(this, tcx)).collect() + }; + + // First, filter out duplicates + moved.sort(); + moved.dedup(); + debug!("fragments 1 moved: {}", path_lps(moved.as_slice())); + + assigned.sort(); + assigned.dedup(); + debug!("fragments 1 assigned: {}", path_lps(assigned.as_slice())); + + // Second, build parents from the moved and assigned. + for m in moved.iter() { + let mut p = this.path_parent(*m); + while p != InvalidMovePathIndex { + parents.push(p); + p = this.path_parent(p); + } + } + for a in assigned.iter() { + let mut p = this.path_parent(*a); + while p != InvalidMovePathIndex { + parents.push(p); + p = this.path_parent(p); + } + } + + parents.sort(); + parents.dedup(); + debug!("fragments 2 parents: {}", path_lps(parents.as_slice())); + + // Third, filter the moved and assigned fragments down to just the non-parents + moved.retain(|f| non_member(*f, parents.as_slice())); + debug!("fragments 3 moved: {}", path_lps(moved.as_slice())); + + assigned.retain(|f| non_member(*f, parents.as_slice())); + debug!("fragments 3 assigned: {}", path_lps(assigned.as_slice())); + + // Fourth, build the leftover from the moved, assigned, and parents. + for m in moved.as_slice().iter() { + let lp = this.path_loan_path(*m); + add_fragment_siblings(this, tcx, &mut unmoved, lp, None); + } + for a in assigned.as_slice().iter() { + let lp = this.path_loan_path(*a); + add_fragment_siblings(this, tcx, &mut unmoved, lp, None); + } + for p in parents.as_slice().iter() { + let lp = this.path_loan_path(*p); + add_fragment_siblings(this, tcx, &mut unmoved, lp, None); + } + + unmoved.sort(); + unmoved.dedup(); + debug!("fragments 4 unmoved: {}", frag_lps(unmoved.as_slice())); + + // Fifth, filter the leftover fragments down to its core. + unmoved.retain(|f| match *f { + AllButOneFrom(_) => true, + Just(mpi) => non_member(mpi, parents.as_slice()) && + non_member(mpi, moved.as_slice()) && + non_member(mpi, assigned.as_slice()) + }); + debug!("fragments 5 unmoved: {}", frag_lps(unmoved.as_slice())); + + // Swap contents back in. + fragments.unmoved_fragments = unmoved; + fragments.parents_of_fragments = parents; + fragments.moved_leaf_paths = moved; + fragments.assigned_leaf_paths = assigned; + + return; + + fn non_member(elem: MovePathIndex, set: &[MovePathIndex]) -> bool { + match set.binary_search_elem(&elem) { + slice::Found(_) => false, + slice::NotFound(_) => true, + } + } +} + +fn add_fragment_siblings<'tcx>(this: &MoveData<'tcx>, + tcx: &ty::ctxt<'tcx>, + gathered_fragments: &mut Vec, + lp: Rc>, + origin_id: Option) { + /*! + * Adds all of the precisely-tracked siblings of `lp` as + * potential move paths of interest. For example, if `lp` + * represents `s.x.j`, then adds moves paths for `s.x.i` and + * `s.x.k`, the siblings of `s.x.j`. + */ + + match lp.kind { + LpVar(_) | LpUpvar(..) => {} // Local variables have no siblings. + + // Consuming a downcast is like consuming the original value, so propage inward. + LpDowncast(ref loan_parent, _) => { + add_fragment_siblings(this, tcx, gathered_fragments, loan_parent.clone(), origin_id); + } + + // *LV for OwnedPtr consumes the contents of the box (at + // least when it is non-copy...), so propagate inward. + LpExtend(ref loan_parent, _, LpDeref(mc::OwnedPtr)) => { + add_fragment_siblings(this, tcx, gathered_fragments, loan_parent.clone(), origin_id); + } + + // *LV for unsafe and borrowed pointers do not consume their loan path, so stop here. + LpExtend(_, _, LpDeref(mc::UnsafePtr(..))) | + LpExtend(_, _, LpDeref(mc::Implicit(..))) | + LpExtend(_, _, LpDeref(mc::BorrowedPtr(..))) => {} + + // FIXME(pnkfelix): LV[j] should be tracked, at least in the + // sense of we will track the remaining drop obligation of the + // rest of the array. + // + // LV[j] is not tracked precisely + LpExtend(_, _, LpInterior(mc::InteriorElement(_))) => { + let mp = this.move_path(tcx, lp.clone()); + gathered_fragments.push(AllButOneFrom(mp)); + } + + // field access LV.x and tuple access LV#k are the cases + // we are interested in + LpExtend(ref loan_parent, mc, + LpInterior(mc::InteriorField(ref field_name))) => { + let enum_variant_info = match loan_parent.kind { + LpDowncast(ref loan_parent_2, variant_def_id) => + Some((variant_def_id, loan_parent_2.clone())), + LpExtend(..) | LpVar(..) | LpUpvar(..) => + None, + }; + add_fragment_siblings_for_extension( + this, + tcx, + gathered_fragments, + loan_parent, mc, field_name, &lp, origin_id, enum_variant_info); + } + } +} + +fn add_fragment_siblings_for_extension<'tcx>(this: &MoveData<'tcx>, + tcx: &ty::ctxt<'tcx>, + gathered_fragments: &mut Vec, + parent_lp: &Rc>, + mc: mc::MutabilityCategory, + origin_field_name: &mc::FieldName, + origin_lp: &Rc>, + origin_id: Option, + enum_variant_info: Option<(ast::DefId, + Rc>)>) { + /*! + * We have determined that `origin_lp` destructures to + * LpExtend(parent, original_field_name). Based on this, + * add move paths for all of the siblings of `origin_lp`. + */ + + let parent_ty = parent_lp.to_type(); + + let add_fragment_sibling_local = |field_name| { + add_fragment_sibling_core( + this, tcx, gathered_fragments, parent_lp.clone(), mc, field_name, origin_lp); + }; + + match (&parent_ty.sty, enum_variant_info) { + (&ty::ty_tup(ref v), None) => { + let tuple_idx = match *origin_field_name { + mc::PositionalField(tuple_idx) => tuple_idx, + mc::NamedField(_) => + panic!("tuple type {} should not have named fields.", + parent_ty.repr(tcx)), + }; + let tuple_len = v.len(); + for i in range(0, tuple_len) { + if i == tuple_idx { continue } + let field_name = mc::PositionalField(i); + add_fragment_sibling_local(field_name); + } + } + + (&ty::ty_struct(def_id, ref _substs), None) => { + let fields = ty::lookup_struct_fields(tcx, def_id); + match *origin_field_name { + mc::NamedField(ast_name) => { + for f in fields.iter() { + if f.name == ast_name { + continue; + } + let field_name = mc::NamedField(f.name); + add_fragment_sibling_local(field_name); + } + } + mc::PositionalField(tuple_idx) => { + for (i, _f) in fields.iter().enumerate() { + if i == tuple_idx { + continue + } + let field_name = mc::PositionalField(i); + add_fragment_sibling_local(field_name); + } + } + } + } + + (&ty::ty_enum(enum_def_id, ref substs), ref enum_variant_info) => { + let variant_info = { + let mut variants = ty::substd_enum_variants(tcx, enum_def_id, substs); + match *enum_variant_info { + Some((variant_def_id, ref _lp2)) => + variants.iter() + .find(|variant| variant.id == variant_def_id) + .expect("enum_variant_with_id(): no variant exists with that ID") + .clone(), + None => { + assert_eq!(variants.len(), 1); + variants.pop().unwrap() + } + } + }; + match *origin_field_name { + mc::NamedField(ast_name) => { + let variant_arg_names = variant_info.arg_names.as_ref().unwrap(); + for variant_arg_ident in variant_arg_names.iter() { + if variant_arg_ident.name == ast_name { + continue; + } + let field_name = mc::NamedField(variant_arg_ident.name); + add_fragment_sibling_local(field_name); + } + } + mc::PositionalField(tuple_idx) => { + let variant_arg_types = &variant_info.args; + for (i, _variant_arg_ty) in variant_arg_types.iter().enumerate() { + if tuple_idx == i { + continue; + } + let field_name = mc::PositionalField(i); + add_fragment_sibling_local(field_name); + } + } + } + } + + ref sty_and_variant_info => { + let msg = format!("type {} ({}) is not fragmentable", + parent_ty.repr(tcx), sty_and_variant_info); + let opt_span = origin_id.and_then(|id|tcx.map.opt_span(id)); + tcx.sess.opt_span_bug(opt_span, msg.as_slice()) + } + } +} + +fn add_fragment_sibling_core<'tcx>(this: &MoveData<'tcx>, + tcx: &ty::ctxt<'tcx>, + gathered_fragments: &mut Vec, + parent: Rc>, + mc: mc::MutabilityCategory, + new_field_name: mc::FieldName, + origin_lp: &Rc>) -> MovePathIndex { + /*! + * Adds the single sibling `LpExtend(parent, new_field_name)` + * of `origin_lp` (the original loan-path). + */ + let opt_variant_did = match parent.kind { + LpDowncast(_, variant_did) => Some(variant_did), + LpVar(..) | LpUpvar(..) | LpExtend(..) => None, + }; + + let loan_path_elem = LpInterior(mc::InteriorField(new_field_name)); + let new_lp_type = match new_field_name { + mc::NamedField(ast_name) => + ty::named_element_ty(tcx, parent.to_type(), ast_name, opt_variant_did), + mc::PositionalField(idx) => + ty::positional_element_ty(tcx, parent.to_type(), idx, opt_variant_did), + }; + let new_lp_variant = LpExtend(parent, mc, loan_path_elem); + let new_lp = LoanPath::new(new_lp_variant, new_lp_type.unwrap()); + debug!("add_fragment_sibling_core(new_lp={}, origin_lp={})", + new_lp.repr(tcx), origin_lp.repr(tcx)); + let mp = this.move_path(tcx, Rc::new(new_lp)); + + // Do not worry about checking for duplicates here; we will sort + // and dedup after all are added. + gathered_fragments.push(Just(mp)); + + mp +} diff --git a/src/librustc/middle/borrowck/gather_loans/gather_moves.rs b/src/librustc/middle/borrowck/gather_loans/gather_moves.rs index 4caea5ae423bd..1d0b0558bb16c 100644 --- a/src/librustc/middle/borrowck/gather_loans/gather_moves.rs +++ b/src/librustc/middle/borrowck/gather_loans/gather_moves.rs @@ -13,6 +13,7 @@ */ use middle::borrowck::*; +use middle::borrowck::LoanPathKind::*; use middle::borrowck::gather_loans::move_error::MoveSpanAndPath; use middle::borrowck::gather_loans::move_error::{MoveError, MoveErrorCollector}; use middle::borrowck::move_data::*; @@ -32,17 +33,18 @@ struct GatherMoveInfo<'tcx> { span_path_opt: Option } -pub fn gather_decl(bccx: &BorrowckCtxt, - move_data: &MoveData, - decl_id: ast::NodeId, - _decl_span: Span, - var_id: ast::NodeId) { - let loan_path = Rc::new(LpVar(var_id)); +pub fn gather_decl<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, + move_data: &MoveData<'tcx>, + decl_id: ast::NodeId, + _decl_span: Span, + var_id: ast::NodeId) { + let ty = ty::node_id_to_type(bccx.tcx, var_id); + let loan_path = Rc::new(LoanPath::new(LpVar(var_id), ty)); move_data.add_move(bccx.tcx, loan_path, decl_id, Declared); } pub fn gather_move_from_expr<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, - move_data: &MoveData, + move_data: &MoveData<'tcx>, move_error_collector: &MoveErrorCollector<'tcx>, move_expr_id: ast::NodeId, cmt: mc::cmt<'tcx>, @@ -60,8 +62,39 @@ pub fn gather_move_from_expr<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, gather_move(bccx, move_data, move_error_collector, move_info); } +pub fn gather_match_variant<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, + move_data: &MoveData<'tcx>, + _move_error_collector: &MoveErrorCollector<'tcx>, + move_pat: &ast::Pat, + cmt: mc::cmt<'tcx>, + mode: euv::MatchMode) { + let tcx = bccx.tcx; + debug!("gather_match_variant(move_pat={}, cmt={}, mode={})", + move_pat.id, cmt.repr(tcx), mode); + + let opt_lp = opt_loan_path(&cmt); + match opt_lp { + Some(lp) => { + match lp.kind { + LpDowncast(ref base_lp, _) => + move_data.add_variant_match( + tcx, lp.clone(), move_pat.id, base_lp.clone(), mode), + _ => panic!("should only call gather_match_variant \ + for cat_downcast cmt"), + } + } + None => { + // We get None when input to match is non-path (e.g. + // temporary result like a function call). Since no + // loan-path is being matched, no need to record a + // downcast. + return; + } + } +} + pub fn gather_move_from_pat<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, - move_data: &MoveData, + move_data: &MoveData<'tcx>, move_error_collector: &MoveErrorCollector<'tcx>, move_pat: &ast::Pat, cmt: mc::cmt<'tcx>) { @@ -82,7 +115,7 @@ pub fn gather_move_from_pat<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, } fn gather_move<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, - move_data: &MoveData, + move_data: &MoveData<'tcx>, move_error_collector: &MoveErrorCollector<'tcx>, move_info: GatherMoveInfo<'tcx>) { debug!("gather_move(move_id={}, cmt={})", @@ -112,13 +145,13 @@ fn gather_move<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, } } -pub fn gather_assignment(bccx: &BorrowckCtxt, - move_data: &MoveData, - assignment_id: ast::NodeId, - assignment_span: Span, - assignee_loan_path: Rc, - assignee_id: ast::NodeId, - mode: euv::MutateMode) { +pub fn gather_assignment<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, + move_data: &MoveData<'tcx>, + assignment_id: ast::NodeId, + assignment_span: Span, + assignee_loan_path: Rc>, + assignee_id: ast::NodeId, + mode: euv::MutateMode) { move_data.add_assignment(bccx.tcx, assignee_loan_path, assignment_id, @@ -144,7 +177,7 @@ fn check_and_get_illegal_move_origin<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, None } - mc::cat_downcast(ref b) | + mc::cat_downcast(ref b, _) | mc::cat_interior(ref b, _) => { match b.ty.sty { ty::ty_struct(did, _) | ty::ty_enum(did, _) => { diff --git a/src/librustc/middle/borrowck/gather_loans/lifetime.rs b/src/librustc/middle/borrowck/gather_loans/lifetime.rs index 5b8cb0608b39c..7a7ed3e75d20e 100644 --- a/src/librustc/middle/borrowck/gather_loans/lifetime.rs +++ b/src/librustc/middle/borrowck/gather_loans/lifetime.rs @@ -85,7 +85,7 @@ impl<'a, 'tcx> GuaranteeLifetimeContext<'a, 'tcx> { Ok(()) } - mc::cat_downcast(ref base) | + mc::cat_downcast(ref base, _) | mc::cat_deref(ref base, _, mc::OwnedPtr) | // L-Deref-Send mc::cat_interior(ref base, _) => { // L-Field self.check(base, discr_scope) @@ -130,7 +130,7 @@ impl<'a, 'tcx> GuaranteeLifetimeContext<'a, 'tcx> { mc::cat_deref(_, _, mc::Implicit(_, r)) => { r } - mc::cat_downcast(ref cmt) | + mc::cat_downcast(ref cmt, _) | mc::cat_deref(ref cmt, _, mc::OwnedPtr) | mc::cat_interior(ref cmt, _) => { self.scope(cmt) diff --git a/src/librustc/middle/borrowck/gather_loans/mod.rs b/src/librustc/middle/borrowck/gather_loans/mod.rs index c36a466919b45..088b62a12cf98 100644 --- a/src/librustc/middle/borrowck/gather_loans/mod.rs +++ b/src/librustc/middle/borrowck/gather_loans/mod.rs @@ -17,6 +17,7 @@ // sure that all of these loans are honored. use middle::borrowck::*; +use middle::borrowck::LoanPathKind::*; use middle::borrowck::move_data::MoveData; use middle::expr_use_visitor as euv; use middle::mem_categorization as mc; @@ -35,10 +36,10 @@ mod restrictions; mod gather_moves; mod move_error; -pub fn gather_loans_in_fn(bccx: &BorrowckCtxt, - decl: &ast::FnDecl, - body: &ast::Block) - -> (Vec, move_data::MoveData) +pub fn gather_loans_in_fn<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, + decl: &ast::FnDecl, + body: &ast::Block) + -> (Vec>, move_data::MoveData<'tcx>) { let mut glcx = GatherLoanCtxt { bccx: bccx, @@ -60,9 +61,9 @@ pub fn gather_loans_in_fn(bccx: &BorrowckCtxt, struct GatherLoanCtxt<'a, 'tcx: 'a> { bccx: &'a BorrowckCtxt<'a, 'tcx>, - move_data: move_data::MoveData, + move_data: move_data::MoveData<'tcx>, move_error_collector: move_error::MoveErrorCollector<'tcx>, - all_loans: Vec, + all_loans: Vec>, /// `item_ub` is used as an upper-bound on the lifetime whenever we /// ask for the scope of an expression categorized as an upvar. item_ub: region::CodeExtent, @@ -87,6 +88,24 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for GatherLoanCtxt<'a, 'tcx> { } } + fn matched_pat(&mut self, + matched_pat: &ast::Pat, + cmt: mc::cmt<'tcx>, + mode: euv::MatchMode) { + debug!("matched_pat(matched_pat={}, cmt={}, mode={})", + matched_pat.repr(self.tcx()), + cmt.repr(self.tcx()), + mode); + + match cmt.cat { + mc::cat_downcast(..) => + gather_moves::gather_match_variant( + self.bccx, &self.move_data, &self.move_error_collector, + matched_pat, cmt, mode), + _ => {} + } + } + fn consume_pat(&mut self, consume_pat: &ast::Pat, cmt: mc::cmt<'tcx>, @@ -395,11 +414,12 @@ impl<'a, 'tcx> GatherLoanCtxt<'a, 'tcx> { //! For mutable loans of content whose mutability derives //! from a local variable, mark the mutability decl as necessary. - match *loan_path { + match loan_path.kind { LpVar(local_id) | LpUpvar(ty::UpvarId{ var_id: local_id, closure_expr_id: _ }) => { self.tcx().used_mut_nodes.borrow_mut().insert(local_id); } + LpDowncast(ref base, _) | LpExtend(ref base, mc::McInherited, _) | LpExtend(ref base, mc::McDeclared, _) => { self.mark_loan_path_as_mutated(&**base); @@ -426,7 +446,7 @@ impl<'a, 'tcx> GatherLoanCtxt<'a, 'tcx> { } } - pub fn compute_kill_scope(&self, loan_scope: region::CodeExtent, lp: &LoanPath) + pub fn compute_kill_scope(&self, loan_scope: region::CodeExtent, lp: &LoanPath<'tcx>) -> region::CodeExtent { //! Determine when the loan restrictions go out of scope. //! This is either when the lifetime expires or when the diff --git a/src/librustc/middle/borrowck/gather_loans/move_error.rs b/src/librustc/middle/borrowck/gather_loans/move_error.rs index c7ffcc3ac1d15..aaa0fa88242b0 100644 --- a/src/librustc/middle/borrowck/gather_loans/move_error.rs +++ b/src/librustc/middle/borrowck/gather_loans/move_error.rs @@ -124,7 +124,7 @@ fn report_cannot_move_out_of<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, bccx.cmt_to_string(&*move_from)).as_slice()); } - mc::cat_downcast(ref b) | + mc::cat_downcast(ref b, _) | mc::cat_interior(ref b, _) => { match b.ty.sty { ty::ty_struct(did, _) diff --git a/src/librustc/middle/borrowck/gather_loans/restrictions.rs b/src/librustc/middle/borrowck/gather_loans/restrictions.rs index 9b9a5e61393f8..adae34b49dca2 100644 --- a/src/librustc/middle/borrowck/gather_loans/restrictions.rs +++ b/src/librustc/middle/borrowck/gather_loans/restrictions.rs @@ -15,6 +15,8 @@ pub use self::RestrictionResult::*; use middle::borrowck::*; +use middle::borrowck::LoanPathElem::*; +use middle::borrowck::LoanPathKind::*; use middle::expr_use_visitor as euv; use middle::mem_categorization as mc; use middle::ty; @@ -24,9 +26,9 @@ use util::ppaux::Repr; use std::rc::Rc; #[deriving(Show)] -pub enum RestrictionResult { +pub enum RestrictionResult<'tcx> { Safe, - SafeIf(Rc, Vec>) + SafeIf(Rc>, Vec>>) } pub fn compute_restrictions<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, @@ -34,7 +36,7 @@ pub fn compute_restrictions<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, cause: euv::LoanCause, cmt: mc::cmt<'tcx>, loan_region: ty::Region) - -> RestrictionResult { + -> RestrictionResult<'tcx> { let ctxt = RestrictionsContext { bccx: bccx, span: span, @@ -57,9 +59,11 @@ struct RestrictionsContext<'a, 'tcx: 'a> { impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> { fn restrict(&self, - cmt: mc::cmt<'tcx>) -> RestrictionResult { + cmt: mc::cmt<'tcx>) -> RestrictionResult<'tcx> { debug!("restrict(cmt={})", cmt.repr(self.bccx.tcx)); + let new_lp = |v: LoanPathKind<'tcx>| Rc::new(LoanPath::new(v, cmt.ty)); + match cmt.cat.clone() { mc::cat_rvalue(..) => { // Effectively, rvalues are stored into a @@ -72,17 +76,17 @@ impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> { mc::cat_local(local_id) => { // R-Variable, locally declared - let lp = Rc::new(LpVar(local_id)); + let lp = new_lp(LpVar(local_id)); SafeIf(lp.clone(), vec![lp]) } mc::cat_upvar(mc::Upvar { id, .. }) => { // R-Variable, captured into closure - let lp = Rc::new(LpUpvar(id)); + let lp = new_lp(LpUpvar(id)); SafeIf(lp.clone(), vec![lp]) } - mc::cat_downcast(cmt_base) => { + mc::cat_downcast(cmt_base, _) => { // 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. @@ -96,10 +100,9 @@ impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> { // the memory, so no additional restrictions are // needed. let result = self.restrict(cmt_base); - self.extend(result, cmt.mutbl, LpInterior(i)) + self.extend(result, &cmt, LpInterior(i)) } - mc::cat_static_item(..) => { Safe } @@ -116,7 +119,7 @@ impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> { // Eventually we should make these non-special and // just rely on Deref implementation. let result = self.restrict(cmt_base); - self.extend(result, cmt.mutbl, LpDeref(pk)) + self.extend(result, &cmt, LpDeref(pk)) } mc::Implicit(bk, lt) | mc::BorrowedPtr(bk, lt) => { // R-Deref-[Mut-]Borrowed @@ -140,7 +143,7 @@ impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> { // references lifetime ends (by a newly-unfrozen // borrow). let result = self.restrict(cmt_base); - self.extend(result, cmt.mutbl, LpDeref(pk)) + self.extend(result, &cmt, LpDeref(pk)) } } } @@ -152,13 +155,14 @@ impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> { } fn extend(&self, - result: RestrictionResult, - mc: mc::MutabilityCategory, - elem: LoanPathElem) -> RestrictionResult { + result: RestrictionResult<'tcx>, + cmt: &mc::cmt<'tcx>, + elem: LoanPathElem) -> RestrictionResult<'tcx> { match result { Safe => Safe, SafeIf(base_lp, mut base_vec) => { - let lp = Rc::new(LpExtend(base_lp, mc, elem)); + let v = LpExtend(base_lp, cmt.mutbl, elem); + let lp = Rc::new(LoanPath::new(v, cmt.ty)); base_vec.push(lp.clone()); SafeIf(lp, base_vec) } diff --git a/src/librustc/middle/borrowck/graphviz.rs b/src/librustc/middle/borrowck/graphviz.rs index 4a2f57735e18d..41f7b7dba1da6 100644 --- a/src/librustc/middle/borrowck/graphviz.rs +++ b/src/librustc/middle/borrowck/graphviz.rs @@ -80,7 +80,7 @@ impl<'a, 'tcx> DataflowLabeller<'a, 'tcx> { e: EntryOrExit, cfgidx: CFGIndex, dfcx: &DataFlowContext<'a, 'tcx, O>, - to_lp: |uint| -> Rc) -> String { + to_lp: |uint| -> Rc>) -> String { let mut saw_some = false; let mut set = "{".to_string(); dfcx.each_bit_for_node(e, cfgidx, |index| { diff --git a/src/librustc/middle/borrowck/mod.rs b/src/librustc/middle/borrowck/mod.rs index 1133bc41edfc0..45040cd7b102e 100644 --- a/src/librustc/middle/borrowck/mod.rs +++ b/src/librustc/middle/borrowck/mod.rs @@ -12,7 +12,7 @@ #![allow(non_camel_case_types)] -pub use self::LoanPath::*; +pub use self::LoanPathKind::*; pub use self::LoanPathElem::*; pub use self::bckerr_code::*; pub use self::AliasableViolationKind::*; @@ -125,7 +125,7 @@ fn borrowck_item(this: &mut BorrowckCtxt, item: &ast::Item) { /// Collection of conclusions determined via borrow checker analyses. pub struct AnalysisData<'a, 'tcx: 'a> { - pub all_loans: Vec, + pub all_loans: Vec>, pub loans: DataFlowContext<'a, 'tcx, LoanDataFlowOperator>, pub move_data: move_data::FlowedMoveData<'a, 'tcx>, } @@ -143,6 +143,9 @@ fn borrowck_fn(this: &mut BorrowckCtxt, move_data:flowed_moves } = build_borrowck_dataflow_data(this, fk, decl, &cfg, body, sp, id); + move_data::fragments::instrument_move_fragments(&flowed_moves.move_data, + this.tcx, sp, id); + check_loans::check_loans(this, &loan_dfcx, flowed_moves, all_loans.as_slice(), decl, body); @@ -254,11 +257,11 @@ pub type BckResult<'tcx, T> = Result>; // Loans and loan paths /// Record of a loan that was issued. -pub struct Loan { +pub struct Loan<'tcx> { index: uint, - loan_path: Rc, + loan_path: Rc>, kind: ty::BorrowKind, - restricted_paths: Vec>, + restricted_paths: Vec>>, /// gen_scope indicates where loan is introduced. Typically the /// loan is introduced at the point of the borrow, but in some @@ -276,19 +279,60 @@ pub struct Loan { cause: euv::LoanCause, } -impl Loan { - pub fn loan_path(&self) -> Rc { +impl<'tcx> Loan<'tcx> { + pub fn loan_path(&self) -> Rc> { self.loan_path.clone() } } +#[deriving(Eq, Hash, Show)] +pub struct LoanPath<'tcx> { + kind: LoanPathKind<'tcx>, + ty: ty::Ty<'tcx>, +} + +impl<'tcx> LoanPath<'tcx> { + pub fn eq_debug(&self, that: &LoanPath<'tcx>, tcx: &ty::ctxt<'tcx>) -> bool { + let r = self.kind == that.kind; + if r && self.ty != that.ty { + panic!("eq variants ineq types: {} == {}, {} != {}", + self.repr(tcx), that.repr(tcx), + self.ty.repr(tcx), that.ty.repr(tcx)); + } + r + } +} + +impl<'tcx> PartialEq for LoanPath<'tcx> { + fn eq(&self, that: &LoanPath<'tcx>) -> bool { + let r = self.kind == that.kind; + debug_assert!(self.ty == that.ty || !r, + "Somehow loan paths are equal though their tys are not."); + r + } +} + #[deriving(PartialEq, Eq, Hash, Show)] -pub enum LoanPath { - LpVar(ast::NodeId), // `x` in doc.rs - LpUpvar(ty::UpvarId), // `x` captured by-value into closure - LpExtend(Rc, mc::MutabilityCategory, LoanPathElem) +pub enum LoanPathKind<'tcx> { + LpVar(ast::NodeId), // `x` in doc.rs + LpUpvar(ty::UpvarId), // `x` captured by-value into closure + LpDowncast(Rc>, ast::DefId), // `x` downcast to particular enum variant + LpExtend(Rc>, mc::MutabilityCategory, LoanPathElem) } +impl<'tcx> LoanPath<'tcx> { + fn new(kind: LoanPathKind<'tcx>, ty: ty::Ty<'tcx>) -> LoanPath<'tcx> { + LoanPath { kind: kind, ty: ty } + } + + fn to_type(&self) -> ty::Ty<'tcx> { self.ty } +} + +// FIXME (pnkfelix): See discussion here +// https://github.com/pnkfelix/rust/commit/ +// b2b39e8700e37ad32b486b9a8409b50a8a53aa51#commitcomment-7892003 +static DOWNCAST_PRINTED_OPERATOR : &'static str = " as "; + #[deriving(PartialEq, Eq, Hash, Show)] pub enum LoanPathElem { LpDeref(mc::PointerKind), // `*LV` in doc.rs @@ -296,7 +340,7 @@ pub enum LoanPathElem { } pub fn closure_to_block(closure_id: ast::NodeId, - tcx: &ty::ctxt) -> ast::NodeId { + tcx: &ty::ctxt) -> ast::NodeId { match tcx.map.get(closure_id) { ast_map::NodeExpr(expr) => match expr.node { ast::ExprProc(_, ref block) | @@ -311,20 +355,21 @@ pub fn closure_to_block(closure_id: ast::NodeId, } } -impl LoanPath { - pub fn kill_scope(&self, tcx: &ty::ctxt) -> region::CodeExtent { - match *self { +impl<'tcx> LoanPath<'tcx> { + pub fn kill_scope(&self, tcx: &ty::ctxt<'tcx>) -> region::CodeExtent { + match self.kind { LpVar(local_id) => tcx.region_maps.var_scope(local_id), LpUpvar(upvar_id) => { let block_id = closure_to_block(upvar_id.closure_expr_id, tcx); region::CodeExtent::from_node_id(block_id) } + LpDowncast(ref base, _) | LpExtend(ref base, _, _) => base.kill_scope(tcx), } } - fn has_fork(&self, other: &LoanPath) -> bool { - match (self, other) { + fn has_fork(&self, other: &LoanPath<'tcx>) -> bool { + match (&self.kind, &other.kind) { (&LpExtend(ref base, _, LpInterior(id)), &LpExtend(ref base2, _, LpInterior(id2))) => if id == id2 { base.has_fork(&**base2) @@ -338,44 +383,67 @@ impl LoanPath { } fn depth(&self) -> uint { - match *self { + match self.kind { LpExtend(ref base, _, LpDeref(_)) => base.depth(), LpExtend(ref base, _, LpInterior(_)) => base.depth() + 1, _ => 0, } } - fn common(&self, other: &LoanPath) -> Option { - match (self, other) { - (&LpExtend(ref base, a, LpInterior(id)), &LpExtend(ref base2, _, LpInterior(id2))) => + fn common(&self, other: &LoanPath<'tcx>) -> Option> { + match (&self.kind, &other.kind) { + (&LpExtend(ref base, a, LpInterior(id)), + &LpExtend(ref base2, _, LpInterior(id2))) => { if id == id2 { base.common(&**base2).map(|x| { let xd = x.depth(); if base.depth() == xd && base2.depth() == xd { - LpExtend(Rc::new(x), a, LpInterior(id)) + assert_eq!(base.ty, base2.ty); + assert_eq!(self.ty, other.ty); + LoanPath { + kind: LpExtend(Rc::new(x), a, LpInterior(id)), + ty: self.ty, + } } else { x } }) } else { base.common(&**base2) - }, + } + } (&LpExtend(ref base, _, LpDeref(_)), _) => base.common(other), (_, &LpExtend(ref other, _, LpDeref(_))) => self.common(&**other), - (&LpVar(id), &LpVar(id2)) => if id == id2 { Some(LpVar(id)) } else { None }, - (&LpUpvar(id), &LpUpvar(id2)) => if id == id2 { Some(LpUpvar(id)) } else { None }, + (&LpVar(id), &LpVar(id2)) => { + if id == id2 { + assert_eq!(self.ty, other.ty); + Some(LoanPath { kind: LpVar(id), ty: self.ty }) + } else { + None + } + } + (&LpUpvar(id), &LpUpvar(id2)) => { + if id == id2 { + assert_eq!(self.ty, other.ty); + Some(LoanPath { kind: LpUpvar(id), ty: self.ty }) + } else { + None + } + } _ => None, } } } -pub fn opt_loan_path(cmt: &mc::cmt) -> Option> { +pub fn opt_loan_path<'tcx>(cmt: &mc::cmt<'tcx>) -> Option>> { //! Computes the `LoanPath` (if any) for a `cmt`. //! Note that this logic is somewhat duplicated in //! the method `compute()` found in `gather_loans::restrictions`, //! which allows it to share common loan path pieces as it //! traverses the CMT. + let new_lp = |v: LoanPathKind<'tcx>| Rc::new(LoanPath::new(v, cmt.ty)); + match cmt.cat { mc::cat_rvalue(..) | mc::cat_static_item => { @@ -383,28 +451,31 @@ pub fn opt_loan_path(cmt: &mc::cmt) -> Option> { } mc::cat_local(id) => { - Some(Rc::new(LpVar(id))) + Some(new_lp(LpVar(id))) } mc::cat_upvar(mc::Upvar { id, .. }) => { - Some(Rc::new(LpUpvar(id))) + Some(new_lp(LpUpvar(id))) } mc::cat_deref(ref cmt_base, _, pk) => { opt_loan_path(cmt_base).map(|lp| { - Rc::new(LpExtend(lp, cmt.mutbl, LpDeref(pk))) + new_lp(LpExtend(lp, cmt.mutbl, LpDeref(pk))) }) } mc::cat_interior(ref cmt_base, ik) => { opt_loan_path(cmt_base).map(|lp| { - Rc::new(LpExtend(lp, cmt.mutbl, LpInterior(ik))) + new_lp(LpExtend(lp, cmt.mutbl, LpInterior(ik))) }) } - mc::cat_downcast(ref cmt_base) => { + mc::cat_downcast(ref cmt_base, variant_def_id) => opt_loan_path(cmt_base) - } + .map(|lp| { + new_lp(LpDowncast(lp, variant_def_id)) + }), + } } @@ -472,9 +543,9 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> { pub fn report_use_of_moved_value(&self, use_span: Span, use_kind: MovedValueUseKind, - lp: &LoanPath, + lp: &LoanPath<'tcx>, the_move: &move_data::Move, - moved_lp: &LoanPath) { + moved_lp: &LoanPath<'tcx>) { let verb = match use_kind { MovedInUse => "use", MovedInCapture => "capture", @@ -623,7 +694,7 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> { pub fn report_reassigned_immutable_variable(&self, span: Span, - lp: &LoanPath, + lp: &LoanPath<'tcx>, assign: &move_data::Assignment) { self.tcx.sess.span_err( @@ -854,14 +925,23 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> { } pub fn append_loan_path_to_string(&self, - loan_path: &LoanPath, - out: &mut String) { - match *loan_path { + loan_path: &LoanPath<'tcx>, + out: &mut String) { + match loan_path.kind { LpUpvar(ty::UpvarId{ var_id: id, closure_expr_id: _ }) | LpVar(id) => { out.push_str(ty::local_var_name_str(self.tcx, id).get()); } + LpDowncast(ref lp_base, variant_def_id) => { + out.push('('); + self.append_loan_path_to_string(&**lp_base, out); + out.push_str(DOWNCAST_PRINTED_OPERATOR); + out.push_str(ty::item_path_str(self.tcx, variant_def_id).as_slice()); + out.push(')'); + } + + LpExtend(ref lp_base, _, LpInterior(mc::InteriorField(fname))) => { self.append_autoderefd_loan_path_to_string(&**lp_base, out); match fname { @@ -889,9 +969,9 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> { } pub fn append_autoderefd_loan_path_to_string(&self, - loan_path: &LoanPath, - out: &mut String) { - match *loan_path { + loan_path: &LoanPath<'tcx>, + out: &mut String) { + match loan_path.kind { LpExtend(ref lp_base, _, LpDeref(_)) => { // For a path like `(*x).f` or `(*x)[3]`, autoderef // rules would normally allow users to omit the `*x`. @@ -899,13 +979,21 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> { self.append_autoderefd_loan_path_to_string(&**lp_base, out) } + LpDowncast(ref lp_base, variant_def_id) => { + out.push('('); + self.append_autoderefd_loan_path_to_string(&**lp_base, out); + out.push(':'); + out.push_str(ty::item_path_str(self.tcx, variant_def_id).as_slice()); + out.push(')'); + } + LpVar(..) | LpUpvar(..) | LpExtend(_, _, LpInterior(..)) => { self.append_loan_path_to_string(loan_path, out) } } } - pub fn loan_path_to_string(&self, loan_path: &LoanPath) -> String { + pub fn loan_path_to_string(&self, loan_path: &LoanPath<'tcx>) -> String { let mut result = String::new(); self.append_loan_path_to_string(loan_path, &mut result); result @@ -942,8 +1030,8 @@ impl DataFlowOperator for LoanDataFlowOperator { } } -impl<'tcx> Repr<'tcx> for Loan { - fn repr(&self, tcx: &ty::ctxt) -> String { +impl<'tcx> Repr<'tcx> for Loan<'tcx> { + fn repr(&self, tcx: &ty::ctxt<'tcx>) -> String { format!("Loan_{}({}, {}, {}-{}, {})", self.index, self.loan_path.repr(tcx), @@ -954,25 +1042,66 @@ impl<'tcx> Repr<'tcx> for Loan { } } -impl<'tcx> Repr<'tcx> for LoanPath { - fn repr(&self, tcx: &ty::ctxt) -> String { - match self { - &LpVar(id) => { +impl<'tcx> Repr<'tcx> for LoanPath<'tcx> { + fn repr(&self, tcx: &ty::ctxt<'tcx>) -> String { + match self.kind { + LpVar(id) => { format!("$({})", tcx.map.node_to_string(id)) } - &LpUpvar(ty::UpvarId{ var_id, closure_expr_id }) => { + LpUpvar(ty::UpvarId{ var_id, closure_expr_id }) => { let s = tcx.map.node_to_string(var_id); format!("$({} captured by id={})", s, closure_expr_id) } - &LpExtend(ref lp, _, LpDeref(_)) => { + LpDowncast(ref lp, variant_def_id) => { + let variant_str = if variant_def_id.krate == ast::LOCAL_CRATE { + ty::item_path_str(tcx, variant_def_id) + } else { + variant_def_id.repr(tcx) + }; + format!("({}{}{})", lp.repr(tcx), DOWNCAST_PRINTED_OPERATOR, variant_str) + } + + LpExtend(ref lp, _, LpDeref(_)) => { format!("{}.*", lp.repr(tcx)) } - &LpExtend(ref lp, _, LpInterior(ref interior)) => { + LpExtend(ref lp, _, LpInterior(ref interior)) => { format!("{}.{}", lp.repr(tcx), interior.repr(tcx)) } } } } + +impl<'tcx> UserString<'tcx> for LoanPath<'tcx> { + fn user_string(&self, tcx: &ty::ctxt<'tcx>) -> String { + match self.kind { + LpVar(id) => { + format!("$({})", tcx.map.node_to_user_string(id)) + } + + LpUpvar(ty::UpvarId{ var_id, closure_expr_id: _ }) => { + let s = tcx.map.node_to_user_string(var_id); + format!("$({} captured by closure)", s) + } + + LpDowncast(ref lp, variant_def_id) => { + let variant_str = if variant_def_id.krate == ast::LOCAL_CRATE { + ty::item_path_str(tcx, variant_def_id) + } else { + variant_def_id.repr(tcx) + }; + format!("({}{}{})", lp.user_string(tcx), DOWNCAST_PRINTED_OPERATOR, variant_str) + } + + LpExtend(ref lp, _, LpDeref(_)) => { + format!("{}.*", lp.user_string(tcx)) + } + + LpExtend(ref lp, _, LpInterior(ref interior)) => { + format!("{}.{}", lp.user_string(tcx), interior.repr(tcx)) + } + } + } +} diff --git a/src/librustc/middle/borrowck/move_data.rs b/src/librustc/middle/borrowck/move_data.rs index ab4c7787fe8fb..dc9516ccc5da2 100644 --- a/src/librustc/middle/borrowck/move_data.rs +++ b/src/librustc/middle/borrowck/move_data.rs @@ -11,7 +11,7 @@ /*! Data structures used for tracking moves. Please see the extensive -comments in the section "Moves and initialization" and in `doc.rs`. +comments in the section "Moves and initialization" in `doc.rs`. */ @@ -21,6 +21,8 @@ use std::cell::RefCell; use std::rc::Rc; use std::uint; use middle::borrowck::*; +use middle::borrowck::LoanPathKind::{LpVar, LpUpvar, LpDowncast, LpExtend}; +use middle::borrowck::LoanPathElem::{LpInterior}; use middle::cfg; use middle::dataflow::DataFlowContext; use middle::dataflow::BitwiseOperator; @@ -34,12 +36,15 @@ use syntax::codemap::Span; use util::nodemap::{FnvHashMap, NodeSet}; use util::ppaux::Repr; -pub struct MoveData { +#[path="fragments.rs"] +pub mod fragments; + +pub struct MoveData<'tcx> { /// Move paths. See section "Move paths" in `doc.rs`. - pub paths: RefCell>, + pub paths: RefCell>>, /// Cache of loan path to move path index, for easy lookup. - pub path_map: RefCell, MovePathIndex>>, + pub path_map: RefCell>, MovePathIndex>>, /// Each move or uninitialized variable gets an entry here. pub moves: RefCell>, @@ -54,12 +59,19 @@ pub struct MoveData { /// kill move bits. pub path_assignments: RefCell>, + /// Enum variant matched within a pattern on some match arm, like + /// `SomeStruct{ f: Variant1(x, y) } => ...` + pub variant_matches: RefCell>, + /// Assignments to a variable or path, like `x = foo`, but not `x += foo`. pub assignee_ids: RefCell, + + /// Path-fragments from moves in to or out of parts of structured data. + pub fragments: RefCell, } pub struct FlowedMoveData<'a, 'tcx: 'a> { - pub move_data: MoveData, + pub move_data: MoveData<'tcx>, pub dfcx_moves: MoveDataFlow<'a, 'tcx>, @@ -70,7 +82,7 @@ pub struct FlowedMoveData<'a, 'tcx: 'a> { } /// Index into `MoveData.paths`, used like a pointer -#[deriving(PartialEq, Show)] +#[deriving(PartialEq, Eq, PartialOrd, Ord, Show)] pub struct MovePathIndex(uint); impl MovePathIndex { @@ -103,9 +115,9 @@ impl MoveIndex { static InvalidMoveIndex: MoveIndex = MoveIndex(uint::MAX); -pub struct MovePath { +pub struct MovePath<'tcx> { /// Loan path corresponding to this move path - pub loan_path: Rc, + pub loan_path: Rc>, /// Parent pointer, `InvalidMovePathIndex` if root pub parent: MovePathIndex, @@ -155,6 +167,20 @@ pub struct Assignment { pub span: Span, } +pub struct VariantMatch { + /// downcast to the variant. + pub path: MovePathIndex, + + /// path being downcast to the variant. + pub base_path: MovePathIndex, + + /// id where variant's pattern occurs + pub id: ast::NodeId, + + /// says if variant established by move (and why), by copy, or by borrow. + pub mode: euv::MatchMode +} + #[deriving(Clone)] pub struct MoveDataFlowOperator; @@ -166,7 +192,7 @@ pub struct AssignDataFlowOperator; pub type AssignDataFlow<'a, 'tcx> = DataFlowContext<'a, 'tcx, AssignDataFlowOperator>; fn loan_path_is_precise(loan_path: &LoanPath) -> bool { - match *loan_path { + match loan_path.kind { LpVar(_) | LpUpvar(_) => { true } @@ -175,25 +201,59 @@ fn loan_path_is_precise(loan_path: &LoanPath) -> bool { // location, as there is no accurate tracking of the indices. false } + LpDowncast(ref lp_base, _) | LpExtend(ref lp_base, _, _) => { loan_path_is_precise(&**lp_base) } } } -impl MoveData { - pub fn new() -> MoveData { +impl Move { + pub fn to_string<'tcx>(&self, move_data: &MoveData<'tcx>, tcx: &ty::ctxt<'tcx>) -> String { + format!("Move{} path: {}, id: {}, kind: {} {}", + "{", + move_data.path_loan_path(self.path).repr(tcx), + self.id, + self.kind, + "}") + } +} + +impl Assignment { + pub fn to_string<'tcx>(&self, move_data: &MoveData<'tcx>, tcx: &ty::ctxt<'tcx>) -> String { + format!("Assignment{} path: {}, id: {} {}", + "{", + move_data.path_loan_path(self.path).repr(tcx), + self.id, + "}") + } +} + +impl VariantMatch { + pub fn to_string<'tcx>(&self, move_data: &MoveData<'tcx>, tcx: &ty::ctxt<'tcx>) -> String { + format!("VariantMatch{} path: {}, id: {} {}", + "{", + move_data.path_loan_path(self.path).repr(tcx), + self.id, + "}") + } +} + +impl<'tcx> MoveData<'tcx> { + pub fn new() -> MoveData<'tcx> { MoveData { paths: RefCell::new(Vec::new()), path_map: RefCell::new(FnvHashMap::new()), moves: RefCell::new(Vec::new()), path_assignments: RefCell::new(Vec::new()), var_assignments: RefCell::new(Vec::new()), + variant_matches: RefCell::new(Vec::new()), assignee_ids: RefCell::new(NodeSet::new()), + fragments: RefCell::new(fragments::FragmentSets::new()), } } - pub fn path_loan_path(&self, index: MovePathIndex) -> Rc { + pub fn path_loan_path(&self, index: MovePathIndex) -> Rc> { (*self.paths.borrow())[index.get()].loan_path.clone() } @@ -205,6 +265,8 @@ impl MoveData { (*self.paths.borrow())[index.get()].first_move } + /// Returns the index of first child, or `InvalidMovePathIndex` if + /// `index` is leaf. fn path_first_child(&self, index: MovePathIndex) -> MovePathIndex { (*self.paths.borrow())[index.get()].first_child } @@ -236,8 +298,8 @@ impl MoveData { } pub fn move_path(&self, - tcx: &ty::ctxt, - lp: Rc) -> MovePathIndex { + tcx: &ty::ctxt<'tcx>, + lp: Rc>) -> MovePathIndex { /*! * Returns the existing move path index for `lp`, if any, * and otherwise adds a new index for `lp` and any of its @@ -251,7 +313,7 @@ impl MoveData { None => {} } - let index = match *lp { + let index = match lp.kind { LpVar(..) | LpUpvar(..) => { let index = MovePathIndex(self.paths.borrow().len()); @@ -266,6 +328,7 @@ impl MoveData { index } + LpDowncast(ref base, _) | LpExtend(ref base, _, _) => { let parent_index = self.move_path(tcx, base.clone()); @@ -295,19 +358,19 @@ impl MoveData { return index; } - fn existing_move_path(&self, lp: &Rc) + fn existing_move_path(&self, lp: &Rc>) -> Option { self.path_map.borrow().get(lp).cloned() } - fn existing_base_paths(&self, lp: &Rc) + fn existing_base_paths(&self, lp: &Rc>) -> Vec { let mut result = vec!(); self.add_existing_base_paths(lp, &mut result); result } - fn add_existing_base_paths(&self, lp: &Rc, + fn add_existing_base_paths(&self, lp: &Rc>, result: &mut Vec) { /*! * Adds any existing move path indices for `lp` and any base @@ -322,8 +385,9 @@ impl MoveData { }); } None => { - match **lp { + match lp.kind { LpVar(..) | LpUpvar(..) => { } + LpDowncast(ref b, _) | LpExtend(ref b, _, _) => { self.add_existing_base_paths(b, result); } @@ -334,8 +398,8 @@ impl MoveData { } pub fn add_move(&self, - tcx: &ty::ctxt, - lp: Rc, + tcx: &ty::ctxt<'tcx>, + lp: Rc>, id: ast::NodeId, kind: MoveKind) { /*! @@ -348,9 +412,11 @@ impl MoveData { id, kind); - let path_index = self.move_path(tcx, lp); + let path_index = self.move_path(tcx, lp.clone()); let move_index = MoveIndex(self.moves.borrow().len()); + self.fragments.borrow_mut().add_move(path_index); + let next_move = self.path_first_move(path_index); self.set_path_first_move(path_index, move_index); @@ -363,8 +429,8 @@ impl MoveData { } pub fn add_assignment(&self, - tcx: &ty::ctxt, - lp: Rc, + tcx: &ty::ctxt<'tcx>, + lp: Rc>, assign_id: ast::NodeId, span: Span, assignee_id: ast::NodeId, @@ -379,6 +445,8 @@ impl MoveData { let path_index = self.move_path(tcx, lp.clone()); + self.fragments.borrow_mut().add_assignment(path_index); + match mode { euv::Init | euv::JustWrite => { self.assignee_ids.borrow_mut().insert(assignee_id); @@ -405,8 +473,42 @@ impl MoveData { } } + pub fn add_variant_match(&self, + tcx: &ty::ctxt<'tcx>, + lp: Rc>, + pattern_id: ast::NodeId, + base_lp: Rc>, + mode: euv::MatchMode) { + /*! + * Adds a new record for a match of `base_lp`, downcast to + * variant `lp`, that occurs at location `pattern_id`. (One + * should be able to recover the span info from the + * `pattern_id` and the ast_map, I think.) + */ + debug!("add_variant_match(lp={}, pattern_id={})", + lp.repr(tcx), pattern_id); + + let path_index = self.move_path(tcx, lp.clone()); + let base_path_index = self.move_path(tcx, base_lp.clone()); + + self.fragments.borrow_mut().add_assignment(path_index); + + let variant_match = VariantMatch { + path: path_index, + base_path: base_path_index, + id: pattern_id, + mode: mode, + }; + + self.variant_matches.borrow_mut().push(variant_match); + } + + fn fixup_fragment_sets(&self, tcx: &ty::ctxt<'tcx>) { + fragments::fixup_fragment_sets(self, tcx) + } + fn add_gen_kills(&self, - tcx: &ty::ctxt, + tcx: &ty::ctxt<'tcx>, dfcx_moves: &mut MoveDataFlow, dfcx_assign: &mut AssignDataFlow) { /*! @@ -430,20 +532,15 @@ impl MoveData { self.kill_moves(assignment.path, assignment.id, dfcx_moves); } - // Kill all moves related to a variable `x` when it goes out - // of scope: + // Kill all moves related to a variable `x` when + // it goes out of scope: for path in self.paths.borrow().iter() { - match *path.loan_path { - LpVar(id) => { - let kill_scope = tcx.region_maps.var_scope(id); - let path = (*self.path_map.borrow())[path.loan_path]; + match path.loan_path.kind { + LpVar(..) | LpUpvar(..) | LpDowncast(..) => { + let kill_scope = path.loan_path.kill_scope(tcx); + let path = self.path_map.borrow()[path.loan_path]; self.kill_moves(path, kill_scope.node_id(), dfcx_moves); } - LpUpvar(ty::UpvarId { var_id: _, closure_expr_id }) => { - let kill_id = closure_to_block(closure_expr_id, tcx); - let path = (*self.path_map.borrow())[path.loan_path]; - self.kill_moves(path, kill_id, dfcx_moves); - } LpExtend(..) => {} } } @@ -451,15 +548,12 @@ impl MoveData { // Kill all assignments when the variable goes out of scope: for (assignment_index, assignment) in self.var_assignments.borrow().iter().enumerate() { - match *self.path_loan_path(assignment.path) { - LpVar(id) => { - let kill_scope = tcx.region_maps.var_scope(id); + let lp = self.path_loan_path(assignment.path); + match lp.kind { + LpVar(..) | LpUpvar(..) | LpDowncast(..) => { + let kill_scope = lp.kill_scope(tcx); dfcx_assign.add_kill(kill_scope.node_id(), assignment_index); } - LpUpvar(ty::UpvarId { var_id: _, closure_expr_id }) => { - let kill_id = closure_to_block(closure_expr_id, tcx); - dfcx_assign.add_kill(kill_id, assignment_index); - } LpExtend(..) => { tcx.sess.bug("var assignment for non var path"); } @@ -536,7 +630,7 @@ impl MoveData { } impl<'a, 'tcx> FlowedMoveData<'a, 'tcx> { - pub fn new(move_data: MoveData, + pub fn new(move_data: MoveData<'tcx>, tcx: &'a ty::ctxt<'tcx>, cfg: &cfg::CFG, id_range: ast_util::IdRange, @@ -559,9 +653,16 @@ impl<'a, 'tcx> FlowedMoveData<'a, 'tcx> { AssignDataFlowOperator, id_range, move_data.var_assignments.borrow().len()); - move_data.add_gen_kills(tcx, &mut dfcx_moves, &mut dfcx_assign); + + move_data.fixup_fragment_sets(tcx); + + move_data.add_gen_kills(tcx, + &mut dfcx_moves, + &mut dfcx_assign); + dfcx_moves.add_kills_from_flow_exits(cfg); dfcx_assign.add_kills_from_flow_exits(cfg); + dfcx_moves.propagate(cfg, body); dfcx_assign.propagate(cfg, body); @@ -574,7 +675,7 @@ impl<'a, 'tcx> FlowedMoveData<'a, 'tcx> { pub fn kind_of_move_of_path(&self, id: ast::NodeId, - loan_path: &Rc) + loan_path: &Rc>) -> Option { //! Returns the kind of a move of `loan_path` by `id`, if one exists. @@ -596,8 +697,8 @@ impl<'a, 'tcx> FlowedMoveData<'a, 'tcx> { pub fn each_move_of(&self, id: ast::NodeId, - loan_path: &Rc, - f: |&Move, &LoanPath| -> bool) + loan_path: &Rc>, + f: |&Move, &LoanPath<'tcx>| -> bool) -> bool { /*! * Iterates through each move of `loan_path` (or some base path @@ -656,7 +757,7 @@ impl<'a, 'tcx> FlowedMoveData<'a, 'tcx> { pub fn each_assignment_of(&self, id: ast::NodeId, - loan_path: &Rc, + loan_path: &Rc>, f: |&Assignment| -> bool) -> bool { /*! diff --git a/src/librustc/middle/check_match.rs b/src/librustc/middle/check_match.rs index 099ac34d2f29f..806fea3b54fd5 100644 --- a/src/librustc/middle/check_match.rs +++ b/src/librustc/middle/check_match.rs @@ -18,6 +18,7 @@ use middle::def::*; use middle::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, Init}; use middle::expr_use_visitor::{JustWrite, LoanCause, MutateMode}; use middle::expr_use_visitor::{WriteAndRead}; +use middle::expr_use_visitor as euv; use middle::mem_categorization::cmt; use middle::pat_util::*; use middle::ty::*; @@ -1024,6 +1025,7 @@ struct MutationChecker<'a, 'tcx: 'a> { } impl<'a, 'tcx> Delegate<'tcx> for MutationChecker<'a, 'tcx> { + fn matched_pat(&mut self, _: &Pat, _: cmt, _: euv::MatchMode) {} fn consume(&mut self, _: NodeId, _: Span, _: cmt, _: ConsumeMode) {} fn consume_pat(&mut self, _: &Pat, _: cmt, _: ConsumeMode) {} fn borrow(&mut self, diff --git a/src/librustc/middle/check_rvalues.rs b/src/librustc/middle/check_rvalues.rs index dbba9288cbbc4..dae76ba125e60 100644 --- a/src/librustc/middle/check_rvalues.rs +++ b/src/librustc/middle/check_rvalues.rs @@ -59,6 +59,11 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for RvalueContext<'a, 'tcx> { } } + fn matched_pat(&mut self, + _matched_pat: &ast::Pat, + _cmt: mc::cmt, + _mode: euv::MatchMode) {} + fn consume_pat(&mut self, _consume_pat: &ast::Pat, _cmt: mc::cmt, diff --git a/src/librustc/middle/check_static.rs b/src/librustc/middle/check_static.rs index afba72cea99a2..d3c7ccf65dd72 100644 --- a/src/librustc/middle/check_static.rs +++ b/src/librustc/middle/check_static.rs @@ -270,7 +270,7 @@ impl<'tcx> euv::Delegate<'tcx> for GlobalChecker { break } mc::cat_deref(ref cmt, _, _) | - mc::cat_downcast(ref cmt) | + mc::cat_downcast(ref cmt, _) | mc::cat_interior(ref cmt, _) => cur = cmt, mc::cat_rvalue(..) | @@ -325,6 +325,12 @@ impl<'tcx> euv::Delegate<'tcx> for GlobalChecker { _assignment_span: Span, _assignee_cmt: mc::cmt, _mode: euv::MutateMode) {} + + fn matched_pat(&mut self, + _: &ast::Pat, + _: mc::cmt, + _: euv::MatchMode) {} + fn consume_pat(&mut self, _consume_pat: &ast::Pat, _cmt: mc::cmt, diff --git a/src/librustc/middle/expr_use_visitor.rs b/src/librustc/middle/expr_use_visitor.rs index fa0f59f686049..656feb51a1d3c 100644 --- a/src/librustc/middle/expr_use_visitor.rs +++ b/src/librustc/middle/expr_use_visitor.rs @@ -18,6 +18,8 @@ pub use self::MutateMode::*; pub use self::LoanCause::*; pub use self::ConsumeMode::*; pub use self::MoveReason::*; +pub use self::MatchMode::*; +use self::TrackMatchMode::*; use self::OverloadedCallType::*; use middle::{def, region, pat_util}; @@ -48,6 +50,23 @@ pub trait Delegate<'tcx> { cmt: mc::cmt<'tcx>, mode: ConsumeMode); + // The value found at `cmt` has been determined to match the + // pattern binding `matched_pat`, and its subparts are being + // copied or moved depending on `mode`. Note that `matched_pat` + // is called on all variant/structs in the pattern (i.e., the + // interior nodes of the pattern's tree structure) while + // consume_pat is called on the binding identifiers in the pattern + // (which are leaves of the pattern's tree structure). + // + // Note that variants/structs and identifiers are disjoint; thus + // `matched_pat` and `consume_pat` are never both called on the + // same input pattern structure (though of `consume_pat` can be + // called on a subpart of an input passed to `matched_pat). + fn matched_pat(&mut self, + matched_pat: &ast::Pat, + cmt: mc::cmt<'tcx>, + mode: MatchMode); + // The value found at `cmt` is either copied or moved via the // pattern binding `consume_pat`, depending on mode. fn consume_pat(&mut self, @@ -103,6 +122,79 @@ pub enum MoveReason { CaptureMove, } +#[deriving(PartialEq,Show)] +pub enum MatchMode { + NonBindingMatch, + BorrowingMatch, + CopyingMatch, + MovingMatch, +} + +#[deriving(PartialEq,Show)] +enum TrackMatchMode { + Unknown, Definite(MatchMode), Conflicting, +} + +impl TrackMatchMode { + // Builds up the whole match mode for a pattern from its constituent + // parts. The lattice looks like this: + // + // Conflicting + // / \ + // / \ + // Borrowing Moving + // \ / + // \ / + // Copying + // | + // NonBinding + // | + // Unknown + // + // examples: + // + // * `(_, some_int)` pattern is Copying, since + // NonBinding + Copying => Copying + // + // * `(some_int, some_box)` pattern is Moving, since + // Copying + Moving => Moving + // + // * `(ref x, some_box)` pattern is Conflicting, since + // Borrowing + Moving => Conflicting + // + // Note that the `Unknown` and `Conflicting` states are + // represented separately from the other more interesting + // `Definite` states, which simplifies logic here somewhat. + fn lub(&mut self, mode: MatchMode) { + *self = match (*self, mode) { + // Note that clause order below is very significant. + (Unknown, new) => Definite(new), + (Definite(old), new) if old == new => Definite(old), + + (Definite(old), NonBindingMatch) => Definite(old), + (Definite(NonBindingMatch), new) => Definite(new), + + (Definite(old), CopyingMatch) => Definite(old), + (Definite(CopyingMatch), new) => Definite(new), + + (Definite(_), _) => Conflicting, + (Conflicting, _) => *self, + }; + } + + fn match_mode(&self) -> MatchMode { + match *self { + Unknown => NonBindingMatch, + Definite(mode) => mode, + Conflicting => { + // Conservatively return MovingMatch to let the + // compiler continue to make progress. + MovingMatch + } + } + } +} + #[deriving(PartialEq,Show)] pub enum MutateMode { Init, @@ -251,7 +343,7 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,'tcx,TYPER> { ty::ReScope(fn_body_scope), // Args live only as long as the fn body. arg_ty); - self.walk_pat(arg_cmt, &*arg.pat); + self.walk_irrefutable_pat(arg_cmt, &*arg.pat); } } @@ -390,7 +482,9 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,'tcx,TYPER> { // treatment of the discriminant is handled while walking the arms. for arm in arms.iter() { - self.walk_arm(discr_cmt.clone(), arm); + let mode = self.arm_move_mode(discr_cmt.clone(), arm); + let mode = mode.match_mode(); + self.walk_arm(discr_cmt.clone(), arm, mode); } } @@ -448,7 +542,7 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,'tcx,TYPER> { pat.span, ty::ReScope(blk_scope), pattern_type); - self.walk_pat(pat_cmt, &**pat); + self.walk_irrefutable_pat(pat_cmt, &**pat); self.walk_block(&**blk); } @@ -617,7 +711,7 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,'tcx,TYPER> { // `walk_pat`: self.walk_expr(&**expr); let init_cmt = return_if_err!(self.mc.cat_expr(&**expr)); - self.walk_pat(init_cmt, &*local.pat); + self.walk_irrefutable_pat(init_cmt, &*local.pat); } } } @@ -824,9 +918,17 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,'tcx,TYPER> { return true; } - fn walk_arm(&mut self, discr_cmt: mc::cmt<'tcx>, arm: &ast::Arm) { + fn arm_move_mode(&mut self, discr_cmt: mc::cmt<'tcx>, arm: &ast::Arm) -> TrackMatchMode { + let mut mode = Unknown; for pat in arm.pats.iter() { - self.walk_pat(discr_cmt.clone(), &**pat); + self.determine_pat_move_mode(discr_cmt.clone(), &**pat, &mut mode); + } + mode + } + + fn walk_arm(&mut self, discr_cmt: mc::cmt<'tcx>, arm: &ast::Arm, mode: MatchMode) { + for pat in arm.pats.iter() { + self.walk_pat(discr_cmt.clone(), &**pat, mode); } for guard in arm.guard.iter() { @@ -836,21 +938,71 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,'tcx,TYPER> { self.consume_expr(&*arm.body); } - fn walk_pat(&mut self, cmt_discr: mc::cmt<'tcx>, pat: &ast::Pat) { + /// Walks an pat that occurs in isolation (i.e. top-level of fn + /// arg or let binding. *Not* a match arm or nested pat.) + fn walk_irrefutable_pat(&mut self, cmt_discr: mc::cmt<'tcx>, pat: &ast::Pat) { + let mut mode = Unknown; + self.determine_pat_move_mode(cmt_discr.clone(), pat, &mut mode); + let mode = mode.match_mode(); + self.walk_pat(cmt_discr, pat, mode); + } + + /// Identifies any bindings within `pat` and accumulates within + /// `mode` whether the overall pattern/match structure is a move, + /// copy, or borrow. + fn determine_pat_move_mode(&mut self, + cmt_discr: mc::cmt<'tcx>, + pat: &ast::Pat, + mode: &mut TrackMatchMode) { + debug!("determine_pat_move_mode cmt_discr={} pat={}", cmt_discr.repr(self.tcx()), + pat.repr(self.tcx())); + return_if_err!(self.mc.cat_pattern(cmt_discr, pat, |_mc, cmt_pat, pat| { + let tcx = self.typer.tcx(); + let def_map = &self.typer.tcx().def_map; + if pat_util::pat_is_binding(def_map, pat) { + match pat.node { + ast::PatIdent(ast::BindByRef(_), _, _) => + mode.lub(BorrowingMatch), + ast::PatIdent(ast::BindByValue(_), _, _) => { + match copy_or_move(tcx, cmt_pat.ty, PatBindingMove) { + Copy => mode.lub(CopyingMatch), + Move(_) => mode.lub(MovingMatch), + } + } + _ => { + tcx.sess.span_bug( + pat.span, + "binding pattern not an identifier"); + } + } + } + })); + } + + /// The core driver for walking a pattern; `match_mode` must be + /// established up front, e.g. via `determine_pat_move_mode` (see + /// also `walk_irrefutable_pat` for patterns that stand alone). + fn walk_pat(&mut self, + cmt_discr: mc::cmt<'tcx>, + pat: &ast::Pat, + match_mode: MatchMode) { debug!("walk_pat cmt_discr={} pat={}", cmt_discr.repr(self.tcx()), pat.repr(self.tcx())); + let mc = &self.mc; let typer = self.typer; let tcx = typer.tcx(); let def_map = &self.typer.tcx().def_map; let delegate = &mut self.delegate; - return_if_err!(mc.cat_pattern(cmt_discr, &*pat, |mc, cmt_pat, pat| { + + return_if_err!(mc.cat_pattern(cmt_discr.clone(), pat, |mc, cmt_pat, pat| { if pat_util::pat_is_binding(def_map, pat) { let tcx = typer.tcx(); - debug!("binding cmt_pat={} pat={}", + debug!("binding cmt_pat={} pat={} match_mode={}", cmt_pat.repr(tcx), - pat.repr(tcx)); + pat.repr(tcx), + match_mode); // pat_ty: the type of the binding being produced. let pat_ty = return_if_err!(typer.node_ty(pat.id)); @@ -933,6 +1085,93 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,'tcx,TYPER> { } } })); + + // Do a second pass over the pattern, calling `matched_pat` on + // the interior nodes (enum variants and structs), as opposed + // to the above loop's visit of than the bindings that form + // the leaves of the pattern tree structure. + return_if_err!(mc.cat_pattern(cmt_discr, pat, |mc, cmt_pat, pat| { + let def_map = def_map.borrow(); + let tcx = typer.tcx(); + + match pat.node { + ast::PatEnum(_, _) | ast::PatIdent(_, _, None) | ast::PatStruct(..) => { + match def_map.get(&pat.id) { + None => { + // no definition found: pat is not a + // struct or enum pattern. + } + + Some(&def::DefVariant(enum_did, variant_did, _is_struct)) => { + let downcast_cmt = + if ty::enum_is_univariant(tcx, enum_did) { + cmt_pat + } else { + let cmt_pat_ty = cmt_pat.ty; + mc.cat_downcast(pat, cmt_pat, cmt_pat_ty, variant_did) + }; + + debug!("variant downcast_cmt={} pat={}", + downcast_cmt.repr(tcx), + pat.repr(tcx)); + + delegate.matched_pat(pat, downcast_cmt, match_mode); + } + + Some(&def::DefStruct(..)) | Some(&def::DefTy(_, false)) => { + // A struct (in either the value or type + // namespace; we encounter the former on + // e.g. patterns for unit structs). + + debug!("struct cmt_pat={} pat={}", + cmt_pat.repr(tcx), + pat.repr(tcx)); + + delegate.matched_pat(pat, cmt_pat, match_mode); + } + + Some(&def::DefConst(..)) | + Some(&def::DefLocal(..)) => { + // This is a leaf (i.e. identifier binding + // or constant value to match); thus no + // `matched_pat` call. + } + + Some(def @ &def::DefTy(_, true)) => { + // An enum's type -- should never be in a + // pattern. + + let msg = format!("Pattern has unexpected type: {}", def); + tcx.sess.span_bug(pat.span, msg.as_slice()) + } + + Some(def) => { + // Remaining cases are e.g. DefFn, to + // which identifiers within patterns + // should not resolve. + + let msg = format!("Pattern has unexpected def: {}", def); + tcx.sess.span_bug(pat.span, msg.as_slice()) + } + } + } + + ast::PatIdent(_, _, Some(_)) => { + // Do nothing; this is a binding (not a enum + // variant or struct), and the cat_pattern call + // will visit the substructure recursively. + } + + ast::PatWild(_) | ast::PatTup(..) | ast::PatBox(..) | + ast::PatRegion(..) | ast::PatLit(..) | ast::PatRange(..) | + ast::PatVec(..) | ast::PatMac(..) => { + // Similarly, each of these cases does not + // correspond to a enum variant or struct, so we + // do not do any `matched_pat` calls for these + // cases either. + } + } + })); } fn walk_captures(&mut self, closure_expr: &ast::Expr) { diff --git a/src/librustc/middle/mem_categorization.rs b/src/librustc/middle/mem_categorization.rs index e9986e47e4a21..046ab162cfcb0 100644 --- a/src/librustc/middle/mem_categorization.rs +++ b/src/librustc/middle/mem_categorization.rs @@ -98,7 +98,7 @@ pub enum categorization<'tcx> { cat_local(ast::NodeId), // local variable cat_deref(cmt<'tcx>, uint, PointerKind), // deref of a ptr cat_interior(cmt<'tcx>, InteriorKind), // something interior: field, tuple, etc - cat_downcast(cmt<'tcx>), // selects a particular enum variant (*1) + cat_downcast(cmt<'tcx>, ast::DefId), // selects a particular enum variant (*1) // (*1) downcast is only required if the enum has more than one variant } @@ -410,7 +410,28 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> { } fn pat_ty(&self, pat: &ast::Pat) -> McResult> { - self.typer.node_ty(pat.id) + let tcx = self.typer.tcx(); + let base_ty = self.typer.node_ty(pat.id); + // FIXME (Issue #18207): This code detects whether we are + // looking at a `ref x`, and if so, figures out what the type + // *being borrowed* is. But ideally we would put in a more + // fundamental fix to this conflated use of the node id. + let ret_ty = match pat.node { + ast::PatIdent(ast::BindByRef(_), _, _) => { + // a bind-by-ref means that the base_ty will be the type of the ident itself, + // but what we want here is the type of the underlying value being borrowed. + // So peel off one-level, turning the &T into T. + base_ty.map(|t| { + ty::deref(t, false).unwrap_or_else(|| { + panic!("encountered BindByRef with non &-type"); + }).ty + }) + } + _ => base_ty, + }; + debug!("pat_ty(pat={}) base_ty={} ret_ty={}", + pat.repr(tcx), base_ty.repr(tcx), ret_ty.repr(tcx)); + ret_ty } pub fn cat_expr(&self, expr: &ast::Expr) -> McResult> { @@ -1102,13 +1123,14 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> { pub fn cat_downcast(&self, node: &N, base_cmt: cmt<'tcx>, - downcast_ty: Ty<'tcx>) + downcast_ty: Ty<'tcx>, + variant_did: ast::DefId) -> cmt<'tcx> { Rc::new(cmt_ { id: node.id(), span: node.span(), mutbl: base_cmt.mutbl.inherit(), - cat: cat_downcast(base_cmt), + cat: cat_downcast(base_cmt, variant_did), ty: downcast_ty, note: NoteNone }) @@ -1117,7 +1139,7 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> { pub fn cat_pattern(&self, cmt: cmt<'tcx>, pat: &ast::Pat, - op: |&MemCategorizationContext, + op: |&MemCategorizationContext<'t,TYPER>, cmt<'tcx>, &ast::Pat|) -> McResult<()> { @@ -1172,6 +1194,21 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> { op(self, cmt.clone(), pat); + let def_map = self.tcx().def_map.borrow(); + let opt_def = def_map.get(&pat.id); + + // Note: This goes up here (rather than within the PatEnum arm + // alone) because struct patterns can refer to struct types or + // to struct variants within enums. + let cmt = match opt_def { + Some(&def::DefVariant(enum_did, variant_did, _)) + // univariant enums do not need downcasts + if !ty::enum_is_univariant(self.tcx(), enum_did) => { + self.cat_downcast(pat, cmt.clone(), cmt.ty, variant_did) + } + _ => cmt + }; + match pat.node { ast::PatWild(_) => { // _ @@ -1181,24 +1218,15 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> { // variant(..) } ast::PatEnum(_, Some(ref subpats)) => { - match self.tcx().def_map.borrow().get(&pat.id) { - Some(&def::DefVariant(enum_did, _, _)) => { + match opt_def { + Some(&def::DefVariant(..)) => { // variant(x, y, z) - - let downcast_cmt = { - if ty::enum_is_univariant(self.tcx(), enum_did) { - cmt // univariant, no downcast needed - } else { - self.cat_downcast(pat, cmt.clone(), cmt.ty) - } - }; - for (i, subpat) in subpats.iter().enumerate() { let subpat_ty = if_ok!(self.pat_ty(&**subpat)); // see (*2) let subcmt = self.cat_imm_interior( - pat, downcast_cmt.clone(), subpat_ty, + pat, cmt.clone(), subpat_ty, InteriorField(PositionalField(i))); if_ok!(self.cat_pattern(subcmt, &**subpat, |x,y,z| op(x,y,z))); @@ -1356,7 +1384,7 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> { cat_upvar(ref var) => { upvar_to_string(var, true) } - cat_downcast(ref cmt) => { + cat_downcast(ref cmt, _) => { self.cmt_to_string(&**cmt) } } @@ -1392,7 +1420,7 @@ impl<'tcx> cmt_<'tcx> { cat_upvar(..) => { Rc::new((*self).clone()) } - cat_downcast(ref b) | + cat_downcast(ref b, _) | cat_interior(ref b, _) | cat_deref(ref b, _, OwnedPtr) => { b.guarantor() @@ -1416,7 +1444,7 @@ impl<'tcx> cmt_<'tcx> { cat_deref(ref b, _, Implicit(ty::MutBorrow, _)) | cat_deref(ref b, _, BorrowedPtr(ty::UniqueImmBorrow, _)) | cat_deref(ref b, _, Implicit(ty::UniqueImmBorrow, _)) | - cat_downcast(ref b) | + cat_downcast(ref b, _) | cat_deref(ref b, _, OwnedPtr) | cat_interior(ref b, _) => { // Aliasability depends on base cmt @@ -1500,7 +1528,7 @@ impl<'tcx> Repr<'tcx> for categorization<'tcx> { cat_interior(ref cmt, interior) => { format!("{}.{}", cmt.cat.repr(tcx), interior.repr(tcx)) } - cat_downcast(ref cmt) => { + cat_downcast(ref cmt, _) => { format!("{}->(enum)", cmt.cat.repr(tcx)) } } diff --git a/src/librustc/middle/ty.rs b/src/librustc/middle/ty.rs index 47b296e46cd30..98b958749d55e 100644 --- a/src/librustc/middle/ty.rs +++ b/src/librustc/middle/ty.rs @@ -3442,6 +3442,62 @@ pub fn array_element_ty<'tcx>(ty: Ty<'tcx>) -> Option> { } } +/// Returns the type of element at index `i` in tuple or tuple-like type `t`. +/// For an enum `t`, `variant` is None only if `t` is a univariant enum. +pub fn positional_element_ty<'tcx>(cx: &ctxt<'tcx>, + ty: Ty<'tcx>, + i: uint, + variant: Option) -> Option> { + + match (&ty.sty, variant) { + (&ty_tup(ref v), None) => v.as_slice().get(i).map(|&t| t), + + + (&ty_struct(def_id, ref substs), None) => lookup_struct_fields(cx, def_id) + .as_slice().get(i) + .map(|&t|lookup_item_type(cx, t.id).ty.subst(cx, substs)), + + (&ty_enum(def_id, ref substs), Some(variant_def_id)) => { + let variant_info = enum_variant_with_id(cx, def_id, variant_def_id); + variant_info.args.as_slice().get(i).map(|t|t.subst(cx, substs)) + } + + (&ty_enum(def_id, ref substs), None) => { + assert!(enum_is_univariant(cx, def_id)); + let enum_variants = enum_variants(cx, def_id); + let variant_info = &(*enum_variants)[0]; + variant_info.args.as_slice().get(i).map(|t|t.subst(cx, substs)) + } + + _ => None + } +} + +/// Returns the type of element at field `n` in struct or struct-like type `t`. +/// For an enum `t`, `variant` must be some def id. +pub fn named_element_ty<'tcx>(cx: &ctxt<'tcx>, + ty: Ty<'tcx>, + n: ast::Name, + variant: Option) -> Option> { + + match (&ty.sty, variant) { + (&ty_struct(def_id, ref substs), None) => { + let r = lookup_struct_fields(cx, def_id); + r.iter().find(|f| f.name == n) + .map(|&f| lookup_field_type(cx, def_id, f.id, substs)) + } + (&ty_enum(def_id, ref substs), Some(variant_def_id)) => { + let variant_info = enum_variant_with_id(cx, def_id, variant_def_id); + variant_info.arg_names.as_ref() + .expect("must have struct enum variant if accessing a named fields") + .iter().zip(variant_info.args.iter()) + .find(|&(ident, _)| ident.name == n) + .map(|(_ident, arg_t)| arg_t.subst(cx, substs)) + } + _ => None + } +} + pub fn node_id_to_trait_ref<'tcx>(cx: &ctxt<'tcx>, id: ast::NodeId) -> Rc> { match cx.trait_refs.borrow().get(&id) { diff --git a/src/librustc/middle/typeck/check/regionck.rs b/src/librustc/middle/typeck/check/regionck.rs index 1e92dc573a954..f12b5cdad9886 100644 --- a/src/librustc/middle/typeck/check/regionck.rs +++ b/src/librustc/middle/typeck/check/regionck.rs @@ -1503,7 +1503,7 @@ fn link_region<'a, 'tcx>(rcx: &Rcx<'a, 'tcx>, } } - mc::cat_downcast(cmt_base) | + mc::cat_downcast(cmt_base, _) | mc::cat_deref(cmt_base, _, mc::OwnedPtr) | mc::cat_interior(cmt_base, _) => { // Borrowing interior or owned data requires the base @@ -1744,7 +1744,7 @@ fn adjust_upvar_borrow_kind_for_mut<'a, 'tcx>(rcx: &Rcx<'a, 'tcx>, match cmt.cat.clone() { mc::cat_deref(base, _, mc::OwnedPtr) | mc::cat_interior(base, _) | - mc::cat_downcast(base) => { + mc::cat_downcast(base, _) => { // Interior or owned data is mutable if base is // mutable, so iterate to the base. cmt = base; @@ -1795,7 +1795,7 @@ fn adjust_upvar_borrow_kind_for_unique<'a, 'tcx>(rcx: &Rcx<'a, 'tcx>, cmt: mc::c match cmt.cat.clone() { mc::cat_deref(base, _, mc::OwnedPtr) | mc::cat_interior(base, _) | - mc::cat_downcast(base) => { + mc::cat_downcast(base, _) => { // Interior or owned data is unique if base is // unique. cmt = base; diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs index e10a1a4342c45..82cf8f28e3dfe 100644 --- a/src/librustc/session/config.rs +++ b/src/librustc/session/config.rs @@ -208,6 +208,7 @@ debugging_opts!( AST_JSON_NOEXPAND, LS, SAVE_ANALYSIS, + PRINT_MOVE_FRAGMENTS, FLOWGRAPH_PRINT_LOANS, FLOWGRAPH_PRINT_MOVES, FLOWGRAPH_PRINT_ASSIGNS, @@ -246,6 +247,8 @@ pub fn debugging_opts_map() -> Vec<(&'static str, &'static str, u64)> { ("ls", "List the symbols defined by a library crate", LS), ("save-analysis", "Write syntax and type analysis information \ in addition to normal output", SAVE_ANALYSIS), + ("print-move-fragments", "Print out move-fragment data for every fn", + PRINT_MOVE_FRAGMENTS), ("flowgraph-print-loans", "Include loan analysis data in \ --pretty flowgraph output", FLOWGRAPH_PRINT_LOANS), ("flowgraph-print-moves", "Include move analysis data in \ diff --git a/src/librustc/session/mod.rs b/src/librustc/session/mod.rs index 72a9f23aa1f94..89f6cda64d9d3 100644 --- a/src/librustc/session/mod.rs +++ b/src/librustc/session/mod.rs @@ -90,6 +90,12 @@ impl Session { pub fn warn(&self, msg: &str) { self.diagnostic().handler().warn(msg) } + pub fn opt_span_warn(&self, opt_sp: Option, msg: &str) { + match opt_sp { + Some(sp) => self.span_warn(sp, msg), + None => self.warn(msg), + } + } pub fn span_note(&self, sp: Span, msg: &str) { self.diagnostic().span_note(sp, msg) } @@ -108,6 +114,12 @@ impl Session { pub fn help(&self, msg: &str) { self.diagnostic().handler().note(msg) } + pub fn opt_span_bug(&self, opt_sp: Option, msg: &str) -> ! { + match opt_sp { + Some(sp) => self.span_bug(sp, msg), + None => self.bug(msg), + } + } pub fn span_bug(&self, sp: Span, msg: &str) -> ! { self.diagnostic().span_bug(sp, msg) } diff --git a/src/librustc_trans/trans/_match.rs b/src/librustc_trans/trans/_match.rs index d23e53d2e69bf..381220d587cbc 100644 --- a/src/librustc_trans/trans/_match.rs +++ b/src/librustc_trans/trans/_match.rs @@ -1274,6 +1274,7 @@ struct ReassignmentChecker { impl<'tcx> euv::Delegate<'tcx> for ReassignmentChecker { fn consume(&mut self, _: ast::NodeId, _: Span, _: mc::cmt, _: euv::ConsumeMode) {} + fn matched_pat(&mut self, _: &ast::Pat, _: mc::cmt, _: euv::MatchMode) {} fn consume_pat(&mut self, _: &ast::Pat, _: mc::cmt, _: euv::ConsumeMode) {} fn borrow(&mut self, _: ast::NodeId, _: Span, _: mc::cmt, _: ty::Region, _: ty::BorrowKind, _: euv::LoanCause) {} diff --git a/src/libsyntax/ast_map/mod.rs b/src/libsyntax/ast_map/mod.rs index 472331bc9e15d..8a2202d28d5c7 100644 --- a/src/libsyntax/ast_map/mod.rs +++ b/src/libsyntax/ast_map/mod.rs @@ -551,7 +551,11 @@ impl<'ast> Map<'ast> { } pub fn node_to_string(&self, id: NodeId) -> String { - node_id_to_string(self, id) + node_id_to_string(self, id, true) + } + + pub fn node_to_user_string(&self, id: NodeId) -> String { + node_id_to_string(self, id, false) } } @@ -1028,7 +1032,10 @@ impl<'a> NodePrinter for pprust::State<'a> { } } -fn node_id_to_string(map: &Map, id: NodeId) -> String { +fn node_id_to_string(map: &Map, id: NodeId, include_id: bool) -> String { + let id_str = format!(" (id={})", id); + let id_str = if include_id { id_str.as_slice() } else { "" }; + match map.find(id) { Some(NodeItem(item)) => { let path_str = map.path_to_str_with_ident(id, item.ident); @@ -1045,30 +1052,30 @@ fn node_id_to_string(map: &Map, id: NodeId) -> String { ItemImpl(..) => "impl", ItemMac(..) => "macro" }; - format!("{} {} (id={})", item_str, path_str, id) + format!("{} {}{}", item_str, path_str, id_str) } Some(NodeForeignItem(item)) => { let path_str = map.path_to_str_with_ident(id, item.ident); - format!("foreign item {} (id={})", path_str, id) + format!("foreign item {}{}", path_str, id_str) } Some(NodeImplItem(ref ii)) => { match **ii { MethodImplItem(ref m) => { match m.node { MethDecl(ident, _, _, _, _, _, _, _) => - format!("method {} in {} (id={})", + format!("method {} in {}{}", token::get_ident(ident), - map.path_to_string(id), id), + map.path_to_string(id), id_str), MethMac(ref mac) => - format!("method macro {} (id={})", - pprust::mac_to_string(mac), id) + format!("method macro {}{}", + pprust::mac_to_string(mac), id_str) } } TypeImplItem(ref t) => { - format!("typedef {} in {} (id={})", + format!("typedef {} in {}{}", token::get_ident(t.ident), map.path_to_string(id), - id) + id_str) } } } @@ -1076,51 +1083,51 @@ fn node_id_to_string(map: &Map, id: NodeId) -> String { match **tm { RequiredMethod(_) | ProvidedMethod(_) => { let m = ast_util::trait_item_to_ty_method(&**tm); - format!("method {} in {} (id={})", + format!("method {} in {}{}", token::get_ident(m.ident), map.path_to_string(id), - id) + id_str) } TypeTraitItem(ref t) => { - format!("type item {} in {} (id={})", + format!("type item {} in {}{}", token::get_ident(t.ty_param.ident), map.path_to_string(id), - id) + id_str) } } } Some(NodeVariant(ref variant)) => { - format!("variant {} in {} (id={})", + format!("variant {} in {}{}", token::get_ident(variant.node.name), - map.path_to_string(id), id) + map.path_to_string(id), id_str) } Some(NodeExpr(ref expr)) => { - format!("expr {} (id={})", pprust::expr_to_string(&**expr), id) + format!("expr {}{}", pprust::expr_to_string(&**expr), id_str) } Some(NodeStmt(ref stmt)) => { - format!("stmt {} (id={})", pprust::stmt_to_string(&**stmt), id) + format!("stmt {}{}", pprust::stmt_to_string(&**stmt), id_str) } Some(NodeArg(ref pat)) => { - format!("arg {} (id={})", pprust::pat_to_string(&**pat), id) + format!("arg {}{}", pprust::pat_to_string(&**pat), id_str) } Some(NodeLocal(ref pat)) => { - format!("local {} (id={})", pprust::pat_to_string(&**pat), id) + format!("local {}{}", pprust::pat_to_string(&**pat), id_str) } Some(NodePat(ref pat)) => { - format!("pat {} (id={})", pprust::pat_to_string(&**pat), id) + format!("pat {}{}", pprust::pat_to_string(&**pat), id_str) } Some(NodeBlock(ref block)) => { - format!("block {} (id={})", pprust::block_to_string(&**block), id) + format!("block {}{}", pprust::block_to_string(&**block), id_str) } Some(NodeStructCtor(_)) => { - format!("struct_ctor {} (id={})", map.path_to_string(id), id) + format!("struct_ctor {}{}", map.path_to_string(id), id_str) } Some(NodeLifetime(ref l)) => { - format!("lifetime {} (id={})", - pprust::lifetime_to_string(&**l), id) + format!("lifetime {}{}", + pprust::lifetime_to_string(&**l), id_str) } None => { - format!("unknown node (id={})", id) + format!("unknown node{}", id_str) } } } diff --git a/src/test/compile-fail/move-fragments-1.rs b/src/test/compile-fail/move-fragments-1.rs new file mode 100644 index 0000000000000..ccf12cf79e1d6 --- /dev/null +++ b/src/test/compile-fail/move-fragments-1.rs @@ -0,0 +1,58 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(tuple_indexing)] + +// Test that we correctly compute the move fragments for a fn. +// +// Note that the code below is not actually incorrect; the +// `rustc_move_fragments` attribute is a hack that uses the error +// reporting mechanisms as a channel for communicating from the +// internals of the compiler. + +// These are all fairly trivial cases: unused variables or direct +// drops of substructure. + +pub struct D { d: int } +impl Drop for D { fn drop(&mut self) { } } + +#[rustc_move_fragments] +pub fn test_noop() { +} + +#[rustc_move_fragments] +pub fn test_take(_x: D) { + //~^ ERROR assigned_leaf_path: `$(local _x)` +} + +pub struct Pair { x: X, y: Y } + +#[rustc_move_fragments] +pub fn test_take_struct(_p: Pair) { + //~^ ERROR assigned_leaf_path: `$(local _p)` +} + +#[rustc_move_fragments] +pub fn test_drop_struct_part(p: Pair) { + //~^ ERROR parent_of_fragments: `$(local p)` + //~| ERROR moved_leaf_path: `$(local p).x` + //~| ERROR unmoved_fragment: `$(local p).y` + drop(p.x); +} + +#[rustc_move_fragments] +pub fn test_drop_tuple_part(p: (D, D)) { + //~^ ERROR parent_of_fragments: `$(local p)` + //~| ERROR moved_leaf_path: `$(local p).#0` + //~| ERROR unmoved_fragment: `$(local p).#1` + drop(p.0); +} + +pub fn main() { } diff --git a/src/test/compile-fail/move-fragments-2.rs b/src/test/compile-fail/move-fragments-2.rs new file mode 100644 index 0000000000000..ceb1d5a0f0910 --- /dev/null +++ b/src/test/compile-fail/move-fragments-2.rs @@ -0,0 +1,85 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Test that we correctly compute the move fragments for a fn. +// +// Note that the code below is not actually incorrect; the +// `rustc_move_fragments` attribute is a hack that uses the error +// reporting mechanisms as a channel for communicating from the +// internals of the compiler. + +// These are checking that enums are tracked; note that their output +// paths include "downcasts" of the path to a particular enum. + +use self::Lonely::{Zero, One, Two}; + +pub struct D { d: int } +impl Drop for D { fn drop(&mut self) { } } + +pub enum Lonely { Zero, One(X), Two(X, Y) } + +#[rustc_move_fragments] +pub fn test_match_partial(p: Lonely) { + //~^ ERROR parent_of_fragments: `$(local p)` + //~| ERROR assigned_leaf_path: `($(local p) as Zero)` + match p { + Zero(..) => {} + _ => {} + } +} + +#[rustc_move_fragments] +pub fn test_match_full(p: Lonely) { + //~^ ERROR parent_of_fragments: `$(local p)` + //~| ERROR assigned_leaf_path: `($(local p) as Zero)` + //~| ERROR assigned_leaf_path: `($(local p) as One)` + //~| ERROR assigned_leaf_path: `($(local p) as Two)` + match p { + Zero(..) => {} + One(..) => {} + Two(..) => {} + } +} + +#[rustc_move_fragments] +pub fn test_match_bind_one(p: Lonely) { + //~^ ERROR parent_of_fragments: `$(local p)` + //~| ERROR assigned_leaf_path: `($(local p) as Zero)` + //~| ERROR parent_of_fragments: `($(local p) as One)` + //~| ERROR moved_leaf_path: `($(local p) as One).#0` + //~| ERROR assigned_leaf_path: `($(local p) as Two)` + //~| ERROR assigned_leaf_path: `$(local data)` + match p { + Zero(..) => {} + One(data) => {} + Two(..) => {} + } +} + +#[rustc_move_fragments] +pub fn test_match_bind_many(p: Lonely) { + //~^ ERROR parent_of_fragments: `$(local p)` + //~| ERROR assigned_leaf_path: `($(local p) as Zero)` + //~| ERROR parent_of_fragments: `($(local p) as One)` + //~| ERROR moved_leaf_path: `($(local p) as One).#0` + //~| ERROR assigned_leaf_path: `$(local data)` + //~| ERROR parent_of_fragments: `($(local p) as Two)` + //~| ERROR moved_leaf_path: `($(local p) as Two).#0` + //~| ERROR moved_leaf_path: `($(local p) as Two).#1` + //~| ERROR assigned_leaf_path: `$(local left)` + //~| ERROR assigned_leaf_path: `$(local right)` + match p { + Zero(..) => {} + One(data) => {} + Two(left, right) => {} + } +} + +pub fn main() { } diff --git a/src/test/compile-fail/move-fragments-3.rs b/src/test/compile-fail/move-fragments-3.rs new file mode 100644 index 0000000000000..4540b0c5a9132 --- /dev/null +++ b/src/test/compile-fail/move-fragments-3.rs @@ -0,0 +1,47 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Test that we correctly compute the move fragments for a fn. +// +// Note that the code below is not actually incorrect; the +// `rustc_move_fragments` attribute is a hack that uses the error +// reporting mechanisms as a channel for communicating from the +// internals of the compiler. + +// This checks the handling of `_` within variants, especially when mixed +// with bindings. + +use self::Lonely::{Zero, One, Two}; + +pub struct D { d: int } +impl Drop for D { fn drop(&mut self) { } } + +pub enum Lonely { Zero, One(X), Two(X, Y) } + +#[rustc_move_fragments] +pub fn test_match_bind_and_underscore(p: Lonely) { + //~^ ERROR parent_of_fragments: `$(local p)` + //~| ERROR assigned_leaf_path: `($(local p) as Zero)` + //~| ERROR assigned_leaf_path: `($(local p) as One)` + //~| ERROR parent_of_fragments: `($(local p) as Two)` + //~| ERROR moved_leaf_path: `($(local p) as Two).#0` + //~| ERROR unmoved_fragment: `($(local p) as Two).#1` + //~| ERROR assigned_leaf_path: `$(local left)` + + match p { + Zero(..) => {} + + One(_) => {} // <-- does not fragment `($(local p) as One)` ... + + Two(left, _) => {} // <-- ... *does* fragment `($(local p) as Two)`. + } +} + +pub fn main() { } diff --git a/src/test/compile-fail/move-fragments-4.rs b/src/test/compile-fail/move-fragments-4.rs new file mode 100644 index 0000000000000..dc43dcb9b0e23 --- /dev/null +++ b/src/test/compile-fail/move-fragments-4.rs @@ -0,0 +1,39 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Test that we correctly compute the move fragments for a fn. +// +// Note that the code below is not actually incorrect; the +// `rustc_move_fragments` attribute is a hack that uses the error +// reporting mechanisms as a channel for communicating from the +// internals of the compiler. + +// This checks that a move of deep structure is properly tracked. (An +// early draft of the code did not properly traverse up through all of +// the parents of the leaf fragment.) + +pub struct D { d: int } +impl Drop for D { fn drop(&mut self) { } } + +pub struct Pair { x: X, y: Y } + +#[rustc_move_fragments] +pub fn test_move_substructure(pppp: Pair, D>, D>, D>) { + //~^ ERROR parent_of_fragments: `$(local pppp)` + //~| ERROR parent_of_fragments: `$(local pppp).x` + //~| ERROR parent_of_fragments: `$(local pppp).x.x` + //~| ERROR unmoved_fragment: `$(local pppp).x.x.x` + //~| ERROR moved_leaf_path: `$(local pppp).x.x.y` + //~| ERROR unmoved_fragment: `$(local pppp).x.y` + //~| ERROR unmoved_fragment: `$(local pppp).y` + drop(pppp.x.x.y); +} + +pub fn main() { } diff --git a/src/test/compile-fail/move-fragments-5.rs b/src/test/compile-fail/move-fragments-5.rs new file mode 100644 index 0000000000000..df069461ab74f --- /dev/null +++ b/src/test/compile-fail/move-fragments-5.rs @@ -0,0 +1,92 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Test that we correctly compute the move fragments for a fn. +// +// Note that the code below is not actually incorrect; the +// `rustc_move_fragments` attribute is a hack that uses the error +// reporting mechanisms as a channel for communicating from the +// internals of the compiler. + +// This is the first test that checks moving into local variables. + +pub struct D { d: int } +impl Drop for D { fn drop(&mut self) { } } + +pub struct Pair { x: X, y: Y } + +#[rustc_move_fragments] +pub fn test_move_field_to_local(p: Pair) { + //~^ ERROR parent_of_fragments: `$(local p)` + //~| ERROR moved_leaf_path: `$(local p).x` + //~| ERROR unmoved_fragment: `$(local p).y` + //~| ERROR assigned_leaf_path: `$(local _x)` + let _x = p.x; +} + +#[rustc_move_fragments] +pub fn test_move_field_to_local_to_local(p: Pair) { + //~^ ERROR parent_of_fragments: `$(local p)` + //~| ERROR moved_leaf_path: `$(local p).x` + //~| ERROR unmoved_fragment: `$(local p).y` + //~| ERROR assigned_leaf_path: `$(local _x)` + //~| ERROR moved_leaf_path: `$(local _x)` + //~| ERROR assigned_leaf_path: `$(local _y)` + let _x = p.x; + let _y = _x; +} + +// In the following fn's `test_move_field_to_local_delayed` and +// `test_uninitialized_local` , the instrumentation reports that `_x` +// is moved. This is unlike `test_move_field_to_local`, where `_x` is +// just reported as an assigned_leaf_path. Presumably because this is +// how we represent that it did not have an initalizing expression at +// the binding site. + +#[rustc_move_fragments] +pub fn test_uninitialized_local(_p: Pair) { + //~^ ERROR assigned_leaf_path: `$(local _p)` + //~| ERROR moved_leaf_path: `$(local _x)` + let _x: D; +} + +#[rustc_move_fragments] +pub fn test_move_field_to_local_delayed(p: Pair) { + //~^ ERROR parent_of_fragments: `$(local p)` + //~| ERROR moved_leaf_path: `$(local p).x` + //~| ERROR unmoved_fragment: `$(local p).y` + //~| ERROR assigned_leaf_path: `$(local _x)` + //~| ERROR moved_leaf_path: `$(local _x)` + let _x; + _x = p.x; +} + +#[rustc_move_fragments] +pub fn test_move_field_mut_to_local(mut p: Pair) { + //~^ ERROR parent_of_fragments: `$(local mut p)` + //~| ERROR moved_leaf_path: `$(local mut p).x` + //~| ERROR unmoved_fragment: `$(local mut p).y` + //~| ERROR assigned_leaf_path: `$(local _x)` + let _x = p.x; +} + +#[rustc_move_fragments] +pub fn test_move_field_to_local_to_local_mut(p: Pair) { + //~^ ERROR parent_of_fragments: `$(local p)` + //~| ERROR moved_leaf_path: `$(local p).x` + //~| ERROR unmoved_fragment: `$(local p).y` + //~| ERROR assigned_leaf_path: `$(local mut _x)` + //~| ERROR moved_leaf_path: `$(local mut _x)` + //~| ERROR assigned_leaf_path: `$(local _y)` + let mut _x = p.x; + let _y = _x; +} + +pub fn main() {} diff --git a/src/test/compile-fail/move-fragments-6.rs b/src/test/compile-fail/move-fragments-6.rs new file mode 100644 index 0000000000000..b249d0d739789 --- /dev/null +++ b/src/test/compile-fail/move-fragments-6.rs @@ -0,0 +1,59 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Test that we correctly compute the move fragments for a fn. +// +// Note that the code below is not actually incorrect; the +// `rustc_move_fragments` attribute is a hack that uses the error +// reporting mechanisms as a channel for communicating from the +// internals of the compiler. + +// Test that moving into a field (i.e. overwriting it) fragments the +// receiver. + +use std::mem::drop; + +pub struct Pair { x: X, y: Y } + +#[rustc_move_fragments] +pub fn test_overwrite_uninit_field(z: Z) { + //~^ ERROR parent_of_fragments: `$(local mut p)` + //~| ERROR assigned_leaf_path: `$(local z)` + //~| ERROR moved_leaf_path: `$(local z)` + //~| ERROR assigned_leaf_path: `$(local mut p).x` + //~| ERROR unmoved_fragment: `$(local mut p).y` + + let mut p: Pair; + p.x = z; +} + +#[rustc_move_fragments] +pub fn test_overwrite_moved_field(mut p: Pair, z: Z) { + //~^ ERROR parent_of_fragments: `$(local mut p)` + //~| ERROR assigned_leaf_path: `$(local z)` + //~| ERROR moved_leaf_path: `$(local z)` + //~| ERROR assigned_leaf_path: `$(local mut p).y` + //~| ERROR unmoved_fragment: `$(local mut p).x` + + drop(p); + p.y = z; +} + +#[rustc_move_fragments] +pub fn test_overwrite_same_field(mut p: Pair) { + //~^ ERROR parent_of_fragments: `$(local mut p)` + //~| ERROR moved_leaf_path: `$(local mut p).x` + //~| ERROR assigned_leaf_path: `$(local mut p).x` + //~| ERROR unmoved_fragment: `$(local mut p).y` + + p.x = p.x; +} + +pub fn main() { } diff --git a/src/test/compile-fail/move-fragments-7.rs b/src/test/compile-fail/move-fragments-7.rs new file mode 100644 index 0000000000000..6b2c77bcac15e --- /dev/null +++ b/src/test/compile-fail/move-fragments-7.rs @@ -0,0 +1,46 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Test that we correctly compute the move fragments for a fn. +// +// Note that the code below is not actually incorrect; the +// `rustc_move_fragments` attribute is a hack that uses the error +// reporting mechanisms as a channel for communicating from the +// internals of the compiler. + +// Test that moving a Box fragments its containing structure, for +// both moving out of the structure (i.e. reading `*p.x`) and writing +// into the container (i.e. writing `*p.x`). + +pub struct D { d: int } +impl Drop for D { fn drop(&mut self) { } } + +pub struct Pair { x: X, y: Y } + +#[rustc_move_fragments] +pub fn test_deref_box_field(p: Pair, Box>) { + //~^ ERROR parent_of_fragments: `$(local p)` + //~| ERROR parent_of_fragments: `$(local p).x` + //~| ERROR moved_leaf_path: `$(local p).x.*` + //~| ERROR unmoved_fragment: `$(local p).y` + //~| ERROR assigned_leaf_path: `$(local i)` + let i : D = *p.x; +} + +#[rustc_move_fragments] +pub fn test_overwrite_deref_box_field(mut p: Pair, Box>) { + //~^ ERROR parent_of_fragments: `$(local mut p)` + //~| ERROR parent_of_fragments: `$(local mut p).x` + //~| ERROR assigned_leaf_path: `$(local mut p).x.*` + //~| ERROR unmoved_fragment: `$(local mut p).y` + *p.x = D { d: 3 }; +} + +pub fn main() { } diff --git a/src/test/compile-fail/move-fragments-8.rs b/src/test/compile-fail/move-fragments-8.rs new file mode 100644 index 0000000000000..40ab541128c3f --- /dev/null +++ b/src/test/compile-fail/move-fragments-8.rs @@ -0,0 +1,39 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Test that we correctly compute the move fragments for a fn. +// +// Note that the code below is not actually incorrect; the +// `rustc_move_fragments` attribute is a hack that uses the error +// reporting mechanisms as a channel for communicating from the +// internals of the compiler. + +// Test that assigning into a `&T` within structured container does +// *not* fragment its containing structure. +// +// Compare against the `Box` handling in move-fragments-7.rs. Note +// also that in this case we cannot do a move out of `&T`, so we only +// test writing `*p.x` here. + +pub struct D { d: int } +impl Drop for D { fn drop(&mut self) { } } + +pub struct Pair { x: X, y: Y } + +#[rustc_move_fragments] +pub fn test_overwrite_deref_ampersand_field<'a>(p: Pair<&'a mut D, &'a D>) { + //~^ ERROR parent_of_fragments: `$(local p)` + //~| ERROR parent_of_fragments: `$(local p).x` + //~| ERROR assigned_leaf_path: `$(local p).x.*` + //~| ERROR unmoved_fragment: `$(local p).y` + *p.x = D { d: 3 }; +} + +pub fn main() { } diff --git a/src/test/compile-fail/move-fragments-9.rs b/src/test/compile-fail/move-fragments-9.rs new file mode 100644 index 0000000000000..ce05087f65979 --- /dev/null +++ b/src/test/compile-fail/move-fragments-9.rs @@ -0,0 +1,100 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Test moving array structures, e.g. `[T, ..3]` as well as moving +// elements in and out of such arrays. +// +// Note also that the `test_move_array_then_overwrite` tests represent +// cases that we probably should make illegal. + +pub struct D { d: int } +impl Drop for D { fn drop(&mut self) { } } + +#[rustc_move_fragments] +pub fn test_move_array_via_return(a: [D, ..3]) -> [D, ..3] { + //~^ ERROR assigned_leaf_path: `$(local a)` + //~| ERROR moved_leaf_path: `$(local a)` + return a; +} + +#[rustc_move_fragments] +pub fn test_move_array_into_recv(a: [D, ..3], recv: &mut [D, ..3]) { + //~^ ERROR parent_of_fragments: `$(local recv)` + //~| ERROR assigned_leaf_path: `$(local a)` + //~| ERROR moved_leaf_path: `$(local a)` + //~| ERROR assigned_leaf_path: `$(local recv).*` + *recv = a; +} + +#[rustc_move_fragments] +pub fn test_extract_array_elem(a: [D, ..3], i: uint) -> D { + //~^ ERROR parent_of_fragments: `$(local a)` + //~| ERROR assigned_leaf_path: `$(local i)` + //~| ERROR moved_leaf_path: `$(local a).[]` + //~| ERROR unmoved_fragment: `$(allbutone $(local a).[])` + a[i] +} + +#[rustc_move_fragments] +pub fn test_overwrite_array_elem(mut a: [D, ..3], i: uint, d: D) { + //~^ ERROR parent_of_fragments: `$(local mut a)` + //~| ERROR assigned_leaf_path: `$(local i)` + //~| ERROR assigned_leaf_path: `$(local d)` + //~| ERROR moved_leaf_path: `$(local d)` + //~| ERROR assigned_leaf_path: `$(local mut a).[]` + //~| ERROR unmoved_fragment: `$(allbutone $(local mut a).[])` + a[i] = d; +} + +// FIXME (pnkfelix): Both test_move_array_then_overwrite_elem1 and +// test_move_array_then_overwrite_elem2 illustrate a behavior that +// we need to make illegal if we want to get rid of drop-flags. +// See RFC PR 320 for more discussion. + +#[rustc_move_fragments] +pub fn test_move_array_then_overwrite_elem1(mut a: [D, ..3], i: uint, recv: &mut [D, ..3], d: D) { + //~^ ERROR parent_of_fragments: `$(local mut a)` + //~| ERROR parent_of_fragments: `$(local recv)` + //~| ERROR assigned_leaf_path: `$(local recv).*` + //~| ERROR assigned_leaf_path: `$(local i)` + //~| ERROR assigned_leaf_path: `$(local d)` + //~| ERROR moved_leaf_path: `$(local d)` + //~| ERROR assigned_leaf_path: `$(local mut a).[]` + //~| ERROR unmoved_fragment: `$(allbutone $(local mut a).[])` + + // This test covers the case where the array contents have been all moved away, but + // we still need to deal with new initializing writes into the array. + *recv = a; + a[i] = d; +} + +#[rustc_move_fragments] +pub fn test_move_array_then_overwrite_elem2(mut a: [D, ..3], i: uint, j: uint, + recv: &mut [D, ..3], d1: D, d2: D) { + //~^^ ERROR parent_of_fragments: `$(local mut a)` + //~| ERROR parent_of_fragments: `$(local recv)` + //~| ERROR assigned_leaf_path: `$(local recv).*` + //~| ERROR assigned_leaf_path: `$(local i)` + //~| ERROR assigned_leaf_path: `$(local j)` + //~| ERROR assigned_leaf_path: `$(local d1)` + //~| ERROR assigned_leaf_path: `$(local d2)` + //~| ERROR moved_leaf_path: `$(local d1)` + //~| ERROR moved_leaf_path: `$(local d2)` + //~| ERROR assigned_leaf_path: `$(local mut a).[]` + //~| ERROR unmoved_fragment: `$(allbutone $(local mut a).[])` + + // This test covers the case where the array contents have been all moved away, but + // we still need to deal with new initializing writes into the array. + *recv = a; + a[i] = d1; + a[j] = d2; +} + +pub fn main() { }