diff --git a/src/librustc/driver/session.rs b/src/librustc/driver/session.rs index d7ed5d3e1ffa4..2c6f6160ea1ca 100644 --- a/src/librustc/driver/session.rs +++ b/src/librustc/driver/session.rs @@ -69,6 +69,9 @@ impl Session { pub fn span_err_with_code(&self, sp: Span, msg: &str, code: &str) { self.diagnostic().span_err_with_code(sp, msg, code) } + pub fn span_end_err(&self, sp: Span, msg: &str) { + self.diagnostic().span_end_err(sp, msg) + } pub fn err(&self, msg: &str) { self.diagnostic().handler().err(msg) } @@ -90,6 +93,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) } @@ -102,6 +111,12 @@ impl Session { pub fn note(&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/middle/borrowck/check_loans.rs b/src/librustc/middle/borrowck/check_loans.rs index df637e7a052dd..9267597582ce6 100644 --- a/src/librustc/middle/borrowck/check_loans.rs +++ b/src/librustc/middle/borrowck/check_loans.rs @@ -43,14 +43,15 @@ fn owned_ptr_base_path<'a>(loan_path: &'a LoanPath) -> &'a LoanPath { }; fn owned_ptr_base_path_helper<'a>(loan_path: &'a LoanPath) -> Option<&'a LoanPath> { - match *loan_path { - LpVar(_) | LpUpvar(_) => None, + match loan_path.variant { + LpVar(_) | LpUpvar(..) => None, LpExtend(ref lp_base, _, LpDeref(mc::OwnedPtr)) => { match owned_ptr_base_path_helper(&**lp_base) { v @ Some(_) => v, None => Some(&**lp_base) } } + LpDowncast(ref lp_base, _) | LpExtend(ref lp_base, _, _) => owned_ptr_base_path_helper(&**lp_base) } } @@ -66,14 +67,15 @@ fn owned_ptr_base_path_rc(loan_path: &Rc) -> Rc { }; fn owned_ptr_base_path_helper(loan_path: &Rc) -> Option> { - match **loan_path { - LpVar(_) | LpUpvar(_) => None, + match loan_path.variant { + LpVar(_) | LpUpvar(..) => None, LpExtend(ref lp_base, _, LpDeref(mc::OwnedPtr)) => { match owned_ptr_base_path_helper(lp_base) { v @ Some(_) => v, None => Some(lp_base.clone()) } } + LpDowncast(ref lp_base, _) | LpExtend(ref lp_base, _, _) => owned_ptr_base_path_helper(lp_base) } } @@ -98,6 +100,11 @@ impl<'a, 'tcx> euv::Delegate 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, @@ -293,10 +300,11 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { let mut loan_path = loan_path; loop { - match *loan_path { - LpVar(_) | LpUpvar(_) => { + match loan_path.variant { + LpVar(_) | LpUpvar(..) => { break; } + LpDowncast(ref lp_base, _) | LpExtend(ref lp_base, _, _) => { loan_path = &**lp_base; } @@ -686,10 +694,15 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { * (*p).x = 22; // not ok, p is uninitialized, can't deref */ - match **lp { - LpVar(_) | LpUpvar(_) => { + match lp.variant { + 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, @@ -774,7 +787,7 @@ impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> { loop { debug!("mark_variable_as_used_mut(cmt={})", cmt.repr(this.tcx())); match cmt.cat.clone() { - mc::cat_copied_upvar(mc::CopiedUpvar { upvar_id: id, .. }) | + mc::cat_copied_upvar(mc::CopiedUpvar { upvar_id: ty::UpvarId { var_id: id, .. }, .. }) | mc::cat_local(id) | mc::cat_arg(id) => { this.tcx().used_mut_nodes.borrow_mut().insert(id); return; @@ -804,7 +817,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; diff --git a/src/librustc/middle/borrowck/gather_loans/gather_moves.rs b/src/librustc/middle/borrowck/gather_loans/gather_moves.rs index f58cc95038382..5b6da70455e88 100644 --- a/src/librustc/middle/borrowck/gather_loans/gather_moves.rs +++ b/src/librustc/middle/borrowck/gather_loans/gather_moves.rs @@ -37,7 +37,8 @@ pub fn gather_decl(bccx: &BorrowckCtxt, decl_id: ast::NodeId, _decl_span: Span, var_id: ast::NodeId) { - let loan_path = Rc::new(LpVar(var_id)); + 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); } @@ -60,6 +61,35 @@ pub fn gather_move_from_expr(bccx: &BorrowckCtxt, gather_move(bccx, move_data, move_error_collector, move_info); } +pub fn gather_match_variant(bccx: &BorrowckCtxt, + move_data: &MoveData, + _move_error_collector: &MoveErrorCollector, + move_pat: &ast::Pat, + cmt: mc::cmt, + mode: euv::MatchMode) { + debug!("gather_match_variant(move_pat={}, cmt={}, mode={})", + move_pat.id, cmt.repr(bccx.tcx), mode); + + let opt_lp = opt_loan_path(&cmt); + let opt_base_lp = match cmt.cat { + mc::cat_downcast(ref base_cmt, _variant_def_id) => opt_loan_path(base_cmt), + _ => fail!("should only encounter move_into_variant on cat_downcast."), + }; + match (opt_lp, opt_base_lp) { + (Some(loan_path), Some(base_loan_path)) => { + move_data.add_variant_match(bccx.tcx, + loan_path, + move_pat.id, + base_loan_path, + mode); + } + (lp, base_lp) => { + debug!("add_variant_match body for ({:?}, {:?}) NOT YET IMPLEMENTED", lp, base_lp); + } + } + +} + pub fn gather_move_from_pat(bccx: &BorrowckCtxt, move_data: &MoveData, move_error_collector: &MoveErrorCollector, @@ -152,7 +182,7 @@ fn check_and_get_illegal_move_origin(bccx: &BorrowckCtxt, None } - mc::cat_downcast(ref b) | + mc::cat_downcast(ref b, _) | mc::cat_interior(ref b, _) => { match ty::get(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 e13717e5abd31..103a09917b2c3 100644 --- a/src/librustc/middle/borrowck/gather_loans/lifetime.rs +++ b/src/librustc/middle/borrowck/gather_loans/lifetime.rs @@ -81,7 +81,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 mc::cat_deref(ref base, _, mc::GcPtr) => { @@ -185,7 +185,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_deref(ref cmt, _, mc::GcPtr) | mc::cat_interior(ref cmt, _) | diff --git a/src/librustc/middle/borrowck/gather_loans/mod.rs b/src/librustc/middle/borrowck/gather_loans/mod.rs index cd003432ef22c..90ea02e0a31da 100644 --- a/src/librustc/middle/borrowck/gather_loans/mod.rs +++ b/src/librustc/middle/borrowck/gather_loans/mod.rs @@ -84,6 +84,24 @@ impl<'a, 'tcx> euv::Delegate for GatherLoanCtxt<'a, 'tcx> { } } + fn matched_pat(&mut self, + matched_pat: &ast::Pat, + cmt: mc::cmt, + 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, @@ -396,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.variant { LpVar(local_id) | - LpUpvar(ty::UpvarId{ var_id: local_id, closure_expr_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, _) => { self.mark_loan_path_as_mutated(&**base); } diff --git a/src/librustc/middle/borrowck/gather_loans/move_error.rs b/src/librustc/middle/borrowck/gather_loans/move_error.rs index b826768c93728..e0936599611b1 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(bccx: &BorrowckCtxt, move_from: mc::cmt) { bccx.cmt_to_string(&*move_from)).as_slice()); } - mc::cat_downcast(ref b) | + mc::cat_downcast(ref b, _) | mc::cat_interior(ref b, _) => { match ty::get(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 90e17e4d79cbb..f3f873cc26ec0 100644 --- a/src/librustc/middle/borrowck/gather_loans/restrictions.rs +++ b/src/librustc/middle/borrowck/gather_loans/restrictions.rs @@ -56,6 +56,8 @@ impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> { cmt: mc::cmt) -> RestrictionResult { debug!("restrict(cmt={})", cmt.repr(self.bccx.tcx)); + let new_lp = |v: LoanPathVariant| Rc::new(LoanPath::new(v, cmt.ty)); + match cmt.cat.clone() { mc::cat_rvalue(..) => { // Effectively, rvalues are stored into a @@ -69,23 +71,23 @@ impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> { mc::cat_local(local_id) | mc::cat_arg(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(upvar_id, _) => { // R-Variable, captured into closure - let lp = Rc::new(LpUpvar(upvar_id)); + let lp = new_lp(LpUpvar(upvar_id, CaptureByRef)); SafeIf(lp.clone(), vec![lp]) } mc::cat_copied_upvar(mc::CopiedUpvar { upvar_id, .. }) => { // R-Variable, copied/moved into closure - let lp = Rc::new(LpVar(upvar_id)); + let lp = new_lp(LpUpvar(upvar_id, CaptureByVal)); 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. @@ -99,7 +101,7 @@ 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.mutbl, LpInterior(i), cmt.ty) } mc::cat_deref(cmt_base, _, pk @ mc::OwnedPtr) | @@ -115,7 +117,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.mutbl, LpDeref(pk), cmt.ty) } mc::cat_static_item(..) => { @@ -157,7 +159,7 @@ impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> { } let result = self.restrict(cmt_base); - self.extend(result, cmt.mutbl, LpDeref(pk)) + self.extend(result, cmt.mutbl, LpDeref(pk), cmt.ty) } mc::UnsafePtr(..) => { // We are very trusting when working with unsafe @@ -181,11 +183,13 @@ impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> { fn extend(&self, result: RestrictionResult, mc: mc::MutabilityCategory, - elem: LoanPathElem) -> RestrictionResult { + elem: LoanPathElem, + ty: ty::t) -> RestrictionResult { match result { Safe => Safe, SafeIf(base_lp, mut base_vec) => { - let lp = Rc::new(LpExtend(base_lp, mc, elem)); + let v = LpExtend(base_lp, mc, elem); + let lp = Rc::new(LoanPath::new(v, ty)); base_vec.push(lp.clone()); SafeIf(lp, base_vec) } diff --git a/src/librustc/middle/borrowck/mod.rs b/src/librustc/middle/borrowck/mod.rs index 0d584a7664f67..fb9b2a5fba019 100644 --- a/src/librustc/middle/borrowck/mod.rs +++ b/src/librustc/middle/borrowck/mod.rs @@ -20,6 +20,7 @@ use middle::def; use middle::expr_use_visitor as euv; use middle::mem_categorization as mc; use middle::ty; +use middle::subst::Subst; use util::ppaux::{note_and_explain_region, Repr, UserString}; use std::rc::Rc; @@ -33,6 +34,7 @@ use syntax::parse::token; use syntax::visit; use syntax::visit::{Visitor, FnKind}; use syntax::ast::{FnDecl, Block, NodeId}; +use syntax::attr::AttrMetaMethods; macro_rules! if_ok( ($inp: expr) => ( @@ -136,6 +138,8 @@ fn borrowck_fn(this: &mut BorrowckCtxt, move_data:flowed_moves } = build_borrowck_dataflow_data(this, fk, decl, &cfg, body, sp, id); + // check_drops::check_drops(this, &flowed_moves, id, &cfg, decl, body); + check_loans::check_loans(this, &loan_dfcx, flowed_moves, all_loans.as_slice(), decl, body); @@ -169,12 +173,33 @@ fn build_borrowck_dataflow_data<'a, 'tcx>(this: &mut BorrowckCtxt<'a, 'tcx>, loan_dfcx.add_kills_from_flow_exits(cfg); loan_dfcx.propagate(cfg, body); + let instrument_drop_obligations_as_errors = { + let attrs : &[ast::Attribute]; + attrs = match this.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(), + }; + + attrs.iter().any(|a| a.check_name("rustc_drop_obligations")) + }; + + let mut flags = vec![]; + if instrument_drop_obligations_as_errors { + flags.push(move_data::InstrumentDropObligationsAsErrors) + } + let flowed_moves = move_data::FlowedMoveData::new(move_data, this.tcx, cfg, id_range, decl, - body); + body, + flags.as_slice()); AnalysisData { all_loans: all_loans, loans: loan_dfcx, @@ -271,12 +296,155 @@ impl Loan { } #[deriving(PartialEq, Eq, Hash)] -pub enum LoanPath { - LpVar(ast::NodeId), // `x` in doc.rs - LpUpvar(ty::UpvarId), // `x` captured by-value into closure +pub enum CaptureKind { CaptureByVal, CaptureByRef } + +#[deriving(PartialEq, Eq, Hash)] +pub struct LoanPath { + variant: LoanPathVariant, + ty: ty::t, +} + +#[deriving(PartialEq, Eq, Hash)] +pub enum LoanPathVariant { + LpVar(ast::NodeId), // `x` in doc.rs + LpUpvar(ty::UpvarId, CaptureKind), // `x` captured into closure + LpDowncast(Rc, ast::DefId), // `x` downcast to particular enum variant LpExtend(Rc, mc::MutabilityCategory, LoanPathElem) } +impl LoanPath { + fn new(variant: LoanPathVariant, ty: ty::t) -> LoanPath { + LoanPath { variant: variant, ty: ty } + } + + fn kill_id(&self, tcx: &ty::ctxt) -> ast::NodeId { + //! Returns the lifetime of the local variable that forms the base of this path. + self.variant.kill_id(tcx) + } +} + +impl LoanPathVariant { + fn kill_id(&self, tcx: &ty::ctxt) -> ast::NodeId { + //! Returns the lifetime of the local variable that forms the base of this path. + match *self { + LpVar(id) => + tcx.region_maps.var_scope(id), + LpUpvar(ty::UpvarId { var_id: _, closure_expr_id }, _) => + closure_to_block(closure_expr_id, tcx), + LpDowncast(ref base_lp, _) | LpExtend(ref base_lp, _, _) => + base_lp.kill_id(tcx), + } + } +} + +impl LoanPath { + fn to_type(&self) -> ty::t { self.ty } + + fn needs_drop(&self, tcx: &ty::ctxt) -> bool { + //! Returns true if and only if assigning to this loan path + //! introduces a new drop obligation. + + debug!("needs_drop(tcx) self={}", self.repr(tcx)); + + match self.variant { + LpVar(_) | LpUpvar(..) => + // Variables are the easiest case: just use their + // types to determine whether they introduce a drop + // obligation when assigned. (FSK well, at the + // *moment* they are easy; we may put in + // flow-sensitivity in some form. Or maybe not, we + // will see.) + self.ty.is_drop_obligation(tcx), + + LpExtend(_, _, LpDeref(mc::BorrowedPtr(..))) | + LpExtend(_, _, LpDeref(mc::Implicit(..))) => + // A path through a `&` or `&mut` reference cannot + // introduce a drop obligation; e.g. the assignment + // `*p = box 3u` installs a pointer elsewhere that is + // the responsibility of someone else (e.g. a caller). + false, + + LpExtend(_, _, LpDeref(mc::OwnedPtr)) => + // However, an assignment to a deref of a Box is + // conceptually owned by the parent and thus does + // introduce a drop obligation. + true, + + LpExtend(_, _, LpDeref(mc::GcPtr)) | + LpExtend(_, _, LpDeref(mc::UnsafePtr(_))) => + // An assignment through a GcPtr or UnsafePtr cannot + // affect the local drop obligation state. + false, + + LpExtend(ref base_lp, _cat, LpInterior(_)) => + // 1. Ensure base_lp does not nullify the drop + // obligation (e.g. if it is through a LpDeref, + // such as an example like `*x.p = box 3u` (which + // in the source code may look like `x.p = box 3u` + // due to autoderef). + base_lp.needs_drop(tcx) && + + // 2. Even if the base_lp needs drop, this particular + // field might not. E.g. for `x.q = 3u`, `x` may + // itself introduce a drop obligation, but the type + // of `q` means that that particular field does not + // affect dropping. + self.ty.is_drop_obligation(tcx), + + LpDowncast(_, def_id) => self.enum_variant_needs_drop(tcx, def_id), + } + } + + fn enum_variant_needs_drop(&self, + tcx: &ty::ctxt, + variant_def_id: ast::DefId) -> bool { + //! Handle a particular enum variant as a special case, since + //! the type of an enum variant, like `None` has type + //! `Option`, can indicate that it needs-drop, even though + //! that particular variant does not introduce a + //! drop-obligation. + + match ty::get(self.ty).sty { + ty::ty_enum(enum_def_id, ref substs) => { + let variant_info = ty::enum_variant_with_id(tcx, enum_def_id, variant_def_id); + let type_contents = ty::TypeContents::union( + variant_info.args.as_slice(), + |arg_ty| { + let arg_ty_subst = arg_ty.subst(tcx, substs); + debug!("needs_drop(tcx) self={} arg_ty={:s} arg_ty_subst={:s}", + self.repr(tcx), arg_ty.repr(tcx), arg_ty_subst.repr(tcx)); + ty::type_contents(tcx, arg_ty_subst) + }); + + type_contents.is_drop_obligation(tcx) + } + _ => { + debug!("needs_drop encountered LpDowncast on non-enum base type: {}", + self.ty.repr(tcx)); + let msg = format!("encountered LpDowncast on non-enum base type: {}.", + self.ty.repr(tcx)); + tcx.sess.opt_span_warn(tcx.map.opt_span(self.kill_id(tcx)), + msg.as_slice()); + false + } + } + } +} + +trait DropObligation { + fn is_drop_obligation(&self, tcx: &ty::ctxt) -> bool; +} +impl DropObligation for ty::TypeContents { + fn is_drop_obligation(&self, tcx: &ty::ctxt) -> bool { + self.moves_by_default(tcx) + } +} +impl DropObligation for ty::t { + fn is_drop_obligation(&self, tcx: &ty::ctxt) -> bool { + ty::type_contents(tcx, *self).is_drop_obligation(tcx) + } +} + #[deriving(PartialEq, Eq, Hash)] pub enum LoanPathElem { LpDeref(mc::PointerKind), // `*LV` in doc.rs @@ -298,10 +466,11 @@ pub fn closure_to_block(closure_id: ast::NodeId, impl LoanPath { pub fn kill_scope(&self, tcx: &ty::ctxt) -> ast::NodeId { - match *self { + match self.variant { LpVar(local_id) => tcx.region_maps.var_scope(local_id), - LpUpvar(upvar_id) => + LpUpvar(upvar_id, _) => closure_to_block(upvar_id.closure_expr_id, tcx), + LpDowncast(ref base, _) | LpExtend(ref base, _, _) => base.kill_scope(tcx), } } @@ -314,6 +483,8 @@ pub fn opt_loan_path(cmt: &mc::cmt) -> Option> { //! which allows it to share common loan path pieces as it //! traverses the CMT. + let new_lp = |v: LoanPathVariant| Rc::new(LoanPath::new(v, cmt.ty)); + match cmt.cat { mc::cat_rvalue(..) | mc::cat_static_item | @@ -323,30 +494,35 @@ pub fn opt_loan_path(cmt: &mc::cmt) -> Option> { mc::cat_local(id) | mc::cat_arg(id) => { - Some(Rc::new(LpVar(id))) + Some(new_lp(LpVar(id))) + } + + mc::cat_upvar(upvar_id, _) => { + Some(new_lp(LpUpvar(upvar_id, CaptureByRef))) } - mc::cat_upvar(ty::UpvarId {var_id: id, closure_expr_id: proc_id}, _) | - mc::cat_copied_upvar(mc::CopiedUpvar { upvar_id: id, - onceness: _, - capturing_proc: proc_id }) => { - let upvar_id = ty::UpvarId{ var_id: id, closure_expr_id: proc_id }; - Some(Rc::new(LpUpvar(upvar_id))) + mc::cat_copied_upvar(mc::CopiedUpvar { upvar_id, onceness: _}) => { + Some(new_lp(LpUpvar(upvar_id, CaptureByVal))) } 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)) + }), + mc::cat_discr(ref cmt_base, _) => { opt_loan_path(cmt_base) } @@ -790,12 +966,21 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> { pub fn append_loan_path_to_string(&self, loan_path: &LoanPath, out: &mut String) { - match *loan_path { - LpUpvar(ty::UpvarId{ var_id: id, closure_expr_id: _ }) | + match loan_path.variant { + 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_char('('); + self.append_loan_path_to_string(&**lp_base, out); + out.push_str("->"); + out.push_str(ty::item_path_str(self.tcx, variant_def_id).as_slice()); + out.push_char(')'); + } + + LpExtend(ref lp_base, _, LpInterior(mc::InteriorField(fname))) => { self.append_autoderefd_loan_path_to_string(&**lp_base, out); match fname { @@ -825,7 +1010,7 @@ impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> { pub fn append_autoderefd_loan_path_to_string(&self, loan_path: &LoanPath, out: &mut String) { - match *loan_path { + match loan_path.variant { LpExtend(ref lp_base, _, LpDeref(_)) => { // For a path like `(*x).f` or `(*x)[3]`, autoderef // rules would normally allow users to omit the `*x`. @@ -833,6 +1018,14 @@ 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_char('('); + self.append_autoderefd_loan_path_to_string(&**lp_base, out); + out.push_char(':'); + out.push_str(ty::item_path_str(self.tcx, variant_def_id).as_slice()); + out.push_char(')'); + } + LpVar(..) | LpUpvar(..) | LpExtend(_, _, LpInterior(..)) => { self.append_loan_path_to_string(loan_path, out) } @@ -890,23 +1083,64 @@ impl Repr for Loan { impl Repr for LoanPath { fn repr(&self, tcx: &ty::ctxt) -> String { - match self { - &LpVar(id) => { + match self.variant { + 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), 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 UserString for LoanPath { + fn user_string(&self, tcx: &ty::ctxt) -> String { + match self.variant { + 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 id={})", s, closure_expr_id) + } + + 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), 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 fdd16c886866d..b8cb59774bc48 100644 --- a/src/librustc/middle/borrowck/move_data.rs +++ b/src/librustc/middle/borrowck/move_data.rs @@ -30,7 +30,7 @@ use middle::ty; use syntax::ast; use syntax::ast_util; use syntax::codemap::Span; -use util::ppaux::Repr; +use util::ppaux::{Repr, UserString}; pub struct MoveData { /// Move paths. See section "Move paths" in `doc.rs`. @@ -52,8 +52,22 @@ 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>, + + /// During move_data construction, `fragments` tracks paths that + /// *might* be needs-drop leftovers. When move_data has been + /// completed, `fragments` tracks paths that are *definitely* + /// needs-drop left-overs. + pub fragments: RefCell>, + + /// `nonfragments` always tracks paths that have been definitely + /// used directly (in moves). + pub nonfragments: RefCell>, } pub struct FlowedMoveData<'a, 'tcx: 'a> { @@ -151,6 +165,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; @@ -162,8 +190,8 @@ pub struct AssignDataFlowOperator; pub type AssignDataFlow<'a, 'tcx> = DataFlowContext<'a, 'tcx, AssignDataFlowOperator>; fn loan_path_is_precise(loan_path: &LoanPath) -> bool { - match *loan_path { - LpVar(_) | LpUpvar(_) => { + match loan_path.variant { + LpVar(_) | LpUpvar(..) => { true } LpExtend(_, _, LpInterior(mc::InteriorElement(_))) => { @@ -171,12 +199,44 @@ 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 Move { + pub fn to_string(&self, move_data: &MoveData, tcx: &ty::ctxt) -> String { + format!("Move{:s} path: {}, id: {}, kind: {:?} {:s}", + "{", + move_data.path_loan_path(self.path).repr(tcx), + self.id, + self.kind, + "}") + } +} + +impl Assignment { + pub fn to_string(&self, move_data: &MoveData, tcx: &ty::ctxt) -> String { + format!("Assignment{:s} path: {}, id: {} {:s}", + "{", + move_data.path_loan_path(self.path).repr(tcx), + self.id, + "}") + } +} + +impl VariantMatch { + pub fn to_string(&self, move_data: &MoveData, tcx: &ty::ctxt) -> String { + format!("VariantMatch{:s} path: {}, id: {} {:s}", + "{", + move_data.path_loan_path(self.path).repr(tcx), + self.id, + "}") + } +} + impl MoveData { pub fn new() -> MoveData { MoveData { @@ -185,7 +245,10 @@ impl MoveData { 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(HashSet::new()), + fragments: RefCell::new(Vec::new()), + nonfragments: RefCell::new(Vec::new()), } } @@ -201,6 +264,35 @@ impl MoveData { self.paths.borrow().get(index.get()).first_move } + /// Returns true iff `index` itself cannot be directly split into + /// child fragments. This means it is an atomic value (like a + /// pointer or an integer), or it a non-downcasted enum (and so we + /// can only split off subparts when we narrow it to a particular + /// variant), or it is a struct whose fields are never accessed in + /// the function being compiled. + fn path_is_leaf(&self, index: MovePathIndex, _tcx: &ty::ctxt) -> bool { + let first_child = self.path_first_child(index); + if first_child == InvalidMovePathIndex { + true + } else { + match self.path_loan_path(first_child).variant { + LpDowncast(..) => true, + LpExtend(..) => false, + LpVar(..) | LpUpvar(..) => false, + } + } + } + + /// Returns true iff `index` represents downcast to an enum variant (i.e. LpDowncast). + fn path_is_downcast_to_variant(&self, index: MovePathIndex) -> bool { + match self.path_loan_path(index).variant { + LpDowncast(..) => true, + _ => false, + } + } + + /// Returns the index of first child, or `InvalidMovePathIndex` if + /// `index` is leaf. fn path_first_child(&self, index: MovePathIndex) -> MovePathIndex { self.paths.borrow().get(index.get()).first_child } @@ -247,7 +339,7 @@ impl MoveData { None => {} } - let index = match *lp { + let index = match lp.variant { LpVar(..) | LpUpvar(..) => { let index = MovePathIndex(self.paths.borrow().len()); @@ -262,6 +354,7 @@ impl MoveData { index } + LpDowncast(ref base, _) | LpExtend(ref base, _, _) => { let parent_index = self.move_path(tcx, base.clone()); @@ -318,8 +411,9 @@ impl MoveData { }); } None => { - match **lp { + match lp.variant { LpVar(..) | LpUpvar(..) => { } + LpDowncast(ref b, _) | LpExtend(ref b, _, _) => { self.add_existing_base_paths(b, result); } @@ -344,9 +438,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.nonfragments.borrow_mut().push(path_index); + let next_move = self.path_first_move(path_index); self.set_path_first_move(path_index, move_index); @@ -356,6 +452,8 @@ impl MoveData { kind: kind, next_move: next_move }); + + self.add_fragment_siblings(tcx, lp, id); } pub fn add_assignment(&self, @@ -375,6 +473,8 @@ impl MoveData { let path_index = self.move_path(tcx, lp.clone()); + self.nonfragments.borrow_mut().push(path_index); + match mode { euv::Init | euv::JustWrite => { self.assignee_ids.borrow_mut().insert(assignee_id); @@ -401,65 +501,364 @@ impl MoveData { } } + pub fn add_variant_match(&self, + tcx: &ty::ctxt, + 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.nonfragments.borrow_mut().push(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 add_fragment_siblings(&self, + tcx: &ty::ctxt, + lp: Rc, + origin_id: ast::NodeId) { + /*! 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`. + */ + debug!("add_fragment_siblings(lp={}, origin_id={})", + lp.repr(tcx), origin_id); + + match lp.variant { + LpVar(_) | LpUpvar(..) => {} // Local variables have no siblings. + + LpDowncast(..) => {} // an enum variant (on its own) has no siblings. + + // *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)) => { + self.add_fragment_siblings(tcx, loan_parent.clone(), origin_id); + } + + // *LV has no siblings + LpExtend(_, _, LpDeref(_)) => {} + + // LV[j] is not tracked precisely + LpExtend(_, _, LpInterior(mc::InteriorElement(_))) => {} + + // 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.variant { + LpDowncast(ref loan_parent_2, variant_def_id) => + Some((variant_def_id, loan_parent_2.clone())), + LpExtend(..) | LpVar(..) | LpUpvar(..) => + None, + }; + self.add_fragment_siblings_for_extension( + tcx, loan_parent, mc, field_name, &lp, origin_id, enum_variant_info); + } + } + } + + fn add_fragment_siblings_for_extension(&self, + tcx: &ty::ctxt, + parent_lp: &Rc, + mc: mc::MutabilityCategory, + origin_field_name: &mc::FieldName, + origin_lp: &Rc, + origin_id: ast::NodeId, + 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 = |field_name, _field_type| { + self.add_fragment_sibling( + tcx, parent_lp.clone(), mc, field_name, origin_lp); + }; + + match (&ty::get(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(_) => + fail!("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_type = + // v[i]; + (); + let field_name = mc::PositionalField(i); + add_fragment_sibling(field_name, field_type); + } + } + + (&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); + let field_type = (); + add_fragment_sibling(field_name, field_type); + } + } + mc::PositionalField(tuple_idx) => { + for (i, _f) in fields.iter().enumerate() { + if i == tuple_idx { + continue + } + let field_name = mc::PositionalField(i); + let field_type = (); + add_fragment_sibling(field_name, field_type); + } + } + } + } + + (&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(); + let variant_arg_types = &variant_info.args; + for (variant_arg_ident, _variant_arg_ty) in variant_arg_names.iter().zip(variant_arg_types.iter()) { + if variant_arg_ident.name == ast_name { + continue; + } + let field_name = mc::NamedField(variant_arg_ident.name); + let field_type = (); + add_fragment_sibling(field_name, field_type); + } + } + 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); + let field_type = (); + add_fragment_sibling(field_name, field_type); + } + } + } + } + + ref sty_and_variant_info => { + let msg = format!("type {} ({:?}) is not fragmentable", + parent_ty.repr(tcx), sty_and_variant_info); + tcx.sess.opt_span_bug(tcx.map.opt_span(origin_id), + msg.as_slice()) + } + } + } + + fn add_fragment_sibling(&self, + tcx: &ty::ctxt, + 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.variant { + 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(new_lp={}, origin_lp={})", + new_lp.repr(tcx), origin_lp.repr(tcx)); + let mp = self.move_path(tcx, Rc::new(new_lp)); + + // Do not worry about checking for duplicates here; if + // necessary, we will sort and dedup after all are added. + self.fragments.borrow_mut().push(mp); + + mp + } + fn add_gen_kills(&self, tcx: &ty::ctxt, dfcx_moves: &mut MoveDataFlow, - dfcx_assign: &mut AssignDataFlow) { + dfcx_assign: &mut AssignDataFlow, + flags: &[FlowedMoveDataFlag]) { /*! * Adds the gen/kills for the various moves and * assignments into the provided data flow contexts. * Moves are generated by moves and killed by assignments and * scoping. Assignments are generated by assignment to variables and - * killed by scoping. See `doc.rs` for more details. + * killed by scoping. Drop obligations (aka "Needs-Drop") are + * generated by assignments and killed by moves and scoping. by + * See `doc.rs` for more details. */ + { + let mut nonfragments = { + let mut nonfragments = self.nonfragments.borrow_mut(); + nonfragments.sort_by(|a, b| a.get().cmp(&b.get())); + nonfragments.dedup(); + nonfragments + }; + let mut fragments = { + let mut maybe_fragments = self.fragments.borrow_mut(); + maybe_fragments.sort_by(|a, b| a.get().cmp(&b.get())); + maybe_fragments.dedup(); + maybe_fragments.retain(|f| !nonfragments.contains(f)); + maybe_fragments + }; + + for (i, &nf) in nonfragments.iter().enumerate() { + let lp = self.path_loan_path(nf); + debug!("add_gen_kills nonfragment {:u}: {:s}", i, lp.repr(tcx)); + } + + for (i, &f) in fragments.iter().enumerate() { + let lp = self.path_loan_path(f); + debug!("add_gen_kills fragment {:u}: {:s}", i, lp.repr(tcx)); + } + } + + let instrument_drop_obligations_as_errors = + flags.iter().any(|f| *f == InstrumentDropObligationsAsErrors); + + // Each move ` = ` overwrites ``, removing future obligation to drop it. for (i, move) in self.moves.borrow().iter().enumerate() { dfcx_moves.add_gen(move.id, i); + debug!("remove_drop_obligations move {}", move.to_string(self, tcx)); + self.remove_drop_obligations( + tcx, + move, + instrument_drop_obligations_as_errors); } + // A moving match replaces the general drop obligation (of any + // of the potential variants) with the more specific drop + // obligation (of the particular variant we have matched). + // + // In addition, when the particular variant is itself not a + // drop-obligation, then we register the original path (before + // the downcast) as ignored (aka whitelisted), denoting that + // on this particular control flow branch, we know that any + // attempt to drop would be a no-op anyway, and thus we can + // treat it as a "wildcard", that can successfully merge with + // another arm that either drops the path or does not drop it. + + for variant_match in self.variant_matches.borrow().iter() { + match variant_match.mode { + euv::NonBindingMatch | + euv::BorrowingMatch | + euv::CopyingMatch => {} + euv::MovingMatch => { + + debug!("remove_drop_obligations variant_match {}", variant_match.to_string(self, tcx)); + self.remove_drop_obligations( + tcx, + variant_match, + instrument_drop_obligations_as_errors); + debug!("add_drop_obligations variant_match {}", variant_match.to_string(self, tcx)); + self.add_drop_obligations( + tcx, + variant_match, + instrument_drop_obligations_as_errors); + } + } + + debug!("add_ignored_drops variant_match {}", variant_match.to_string(self, tcx)); + self.add_ignored_drops( + tcx, + variant_match, + instrument_drop_obligations_as_errors); + } + for (i, assignment) in self.var_assignments.borrow().iter().enumerate() { dfcx_assign.add_gen(assignment.id, i); self.kill_moves(assignment.path, assignment.id, dfcx_moves); + debug!("add_drop_obligations var_assignment {}", assignment.to_string(self, tcx)); + self.add_drop_obligations( + tcx, + assignment, + instrument_drop_obligations_as_errors); } for assignment in self.path_assignments.borrow().iter() { self.kill_moves(assignment.path, assignment.id, dfcx_moves); + debug!("add_drop_obligations path_assignment {}", assignment.to_string(self, tcx)); + self.add_drop_obligations( + tcx, + assignment, + instrument_drop_obligations_as_errors); } - // Kill all moves related to a variable `x` when it goes out - // of scope: + // Kill all moves and drop-obligations 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_id = tcx.region_maps.var_scope(id); - let path = *self.path_map.borrow().get(&path.loan_path); - self.kill_moves(path, kill_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().get(&path.loan_path); - self.kill_moves(path, kill_id, dfcx_moves); + let kill_id = path.loan_path.kill_id(tcx); + match path.loan_path.variant { + LpVar(..) | LpUpvar(..) | LpDowncast(..) => { + let move_path_index = *self.path_map.borrow().get(&path.loan_path); + self.kill_moves(move_path_index, kill_id, dfcx_moves); + debug!("remove_drop_obligations scope {} {}", + kill_id, path.loan_path.repr(tcx)); + let rm = ScopeRemoved { where_: kill_id, what_path: move_path_index }; + self.remove_drop_obligations(tcx, + &rm, + instrument_drop_obligations_as_errors); } LpExtend(..) => {} } } // 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_id = tcx.region_maps.var_scope(id); - dfcx_assign.add_kill(kill_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"); - } - } + for (assignment_index, assignment) in self.var_assignments.borrow().iter().enumerate() { + let kill_id = self.path_loan_path(assignment.path).kill_id(tcx); + dfcx_assign.add_kill(kill_id, assignment_index); } } @@ -529,6 +928,215 @@ impl MoveData { }); } } + + fn path_needs_drop(&self, tcx: &ty::ctxt, move_path_index: MovePathIndex) -> bool { + //! Returns true iff move_path_index needs drop. + self.path_loan_path(move_path_index).needs_drop(tcx) + } + + fn type_moves_by_default(&self, tcx: &ty::ctxt, move_path_index: MovePathIndex) -> bool { + //! Returns true iff move_path_index moves on assignment (rather than copies). + let path_type = self.path_loan_path(move_path_index).to_type(); + ty::type_contents(tcx, path_type).moves_by_default(tcx) + } + + fn for_each_leaf(&self, + tcx: &ty::ctxt, + root: MovePathIndex, + found_leaf: |MovePathIndex|, + _found_variant: |MovePathIndex|) { + //! Here we normalize a path so that it is unraveled to its + //! consituent droppable pieces that might be independently + //! handled by the function being compiled: e.g. `s.a.j` + //! unravels to `{ s.a.j.x, s.a.j.y, s.a.j.z }` (assuming the + //! function never moves out any part of those unraveled + //! elements). + //! + //! Note that the callback is only invoked on unraveled leaves + //! that also need to be dropped. + + let root_lp = self.path_loan_path(root); + debug!("for_each_leaf(root_lp={:s})", root_lp.repr(tcx)); + + if self.path_is_leaf(root, tcx) { + found_leaf(root); + return; + } + + let mut stack = vec![]; + stack.push(root); + loop { + let top = match stack.pop() { None => break, Some(elem) => elem }; + assert!(!self.path_is_leaf(top, tcx)); + let mut child = self.path_first_child(top); + while child != InvalidMovePathIndex { + { + let top_lp = self.path_loan_path(top); + let child_lp = self.path_loan_path(child); + debug!("for_each_leaf(root_lp={:s}){:s} top_lp={:s} child_lp={:s}", + root_lp.repr(tcx), + " ".repeat(stack.len()), + top_lp.repr(tcx), + child_lp.repr(tcx)); + } + + if self.path_is_leaf(child, tcx) { + found_leaf(child); + } else { + stack.push(child); + } + + child = self.path_next_sibling(child); + } + } + } + + fn add_drop_obligations( + &self, + tcx: &ty::ctxt, + a: &A, + instrument_drop_obligations_as_errors: bool) { + let a_path = a.path_being_established(); + + let add_gen = |move_path_index| { + if self.path_is_downcast_to_variant(a_path) { + debug!("add_drop_obligations(a={}) {} is variant on match arm", + a.to_string_(self, tcx), + self.path_loan_path(move_path_index).repr(tcx)); + } + + if self.path_needs_drop(tcx, move_path_index) { + let print = || self.path_loan_path(move_path_index).user_string(tcx); + debug!("add_drop_obligations(a={}) adds {}", + a.to_string_(self, tcx), print()) + + if instrument_drop_obligations_as_errors { + let sp = tcx.map.span(a.node_id_adding_obligation()); + let msg = format!("{} adds drop-obl `{}`", a.kind(), print()); + tcx.sess.span_err(sp, msg.as_slice()); + } + } else { + debug!("add_drop_obligations(a={}) skips non-drop {}", + a.to_string_(self, tcx), + self.path_loan_path(move_path_index).repr(tcx)); + } + }; + + let report_variant = |move_path_index| { + debug!("add_drop_obligations(a={}) skips variant {}", + a.to_string_(self, tcx), + self.path_loan_path(move_path_index).repr(tcx)); + }; + + self.for_each_leaf(tcx, a_path, add_gen, report_variant); + } + + fn remove_drop_obligations( + &self, + tcx: &ty::ctxt, + a: &A, + instrument_drop_obligations_as_errors: bool) { + //! Kills all of the fragment leaves of path. + //! + //! Also kills all parents of path: while we do normalize a + //! path to its fragment leaves, (e.g. `a.j` to `{a.j.x, + //! a.j.y, a.j.z}`, an enum variant's path `(b:Variant1).x` + //! has the parent `b` that is itself considered a "leaf" for + //! the purposes of tracking drop obligations. + + let id = a.node_id_removing_obligation(); + let path : MovePathIndex = a.path_being_moved(); + + let add_kill = |move_path_index| { + let print = || self.path_loan_path(move_path_index).user_string(tcx); + + if self.type_moves_by_default(tcx, move_path_index) { + debug!("remove_drop_obligations(id={}) removes {}", id, print()); + if instrument_drop_obligations_as_errors { + let sp = tcx.map.span(id); + let msg = format!("{} removes drop-obl `{}`", a.kind(), print()); + tcx.sess.span_end_err(sp, msg.as_slice()); + } + } else { + debug!("remove_drop_obligations(id={}) skips copyable {}", + id, self.path_loan_path(move_path_index).repr(tcx)); + } + }; + + let report_variant = |move_path_index| { + debug!("remove_drop_obligations(id={}) skips variant {}", + id, self.path_loan_path(move_path_index).repr(tcx)); + }; + + self.for_each_leaf(tcx, path, add_kill, report_variant); + } + + fn add_ignored_drops(&self, + tcx: &ty::ctxt, + variant_match: &VariantMatch, + instrument_drop_obligations_as_errors: bool) { + let path_lp = self.path_loan_path(variant_match.path); + let base_path_lp = self.path_loan_path(variant_match.base_path); + let print = || base_path_lp.user_string(tcx); + + if !self.path_needs_drop(tcx, variant_match.path) { + debug!("add_ignored_drops(id={} lp={}) adds {}", + variant_match.id, path_lp.repr(tcx), print()); + if instrument_drop_obligations_as_errors { + let sp = tcx.map.span(variant_match.id); + tcx.sess.span_err(sp, format!("match whitelists drop-obl `{}`", print()).as_slice()) + } + } else { + debug!("add_ignored_drops(id={} lp={}) skipped {}", + variant_match.id, path_lp.repr(tcx), base_path_lp.repr(tcx)); + } + } +} + +trait AddNeedsDropArg { + fn kind(&self) -> &'static str; + fn node_id_adding_obligation(&self) -> ast::NodeId; + fn path_being_established(&self) -> MovePathIndex; + fn to_string_(&self, move_data: &MoveData, tcx: &ty::ctxt) -> String; +} +impl AddNeedsDropArg for Assignment { + fn kind(&self) -> &'static str { "assignment" } + fn node_id_adding_obligation(&self) -> ast::NodeId { self.id } + fn path_being_established(&self) -> MovePathIndex { self.path } + fn to_string_(&self, md: &MoveData, tcx: &ty::ctxt) -> String { self.to_string(md, tcx) } +} +impl AddNeedsDropArg for VariantMatch { + fn kind(&self) -> &'static str { "match" } + fn node_id_adding_obligation(&self) -> ast::NodeId { self.id } + fn path_being_established(&self) -> MovePathIndex { self.path } + fn to_string_(&self, md: &MoveData, tcx: &ty::ctxt) -> String { self.to_string(md, tcx) } +} + +trait RemoveNeedsDropArg { + fn kind(&self) -> &'static str; + fn node_id_removing_obligation(&self) -> ast::NodeId; + fn path_being_moved(&self) -> MovePathIndex; +} +struct ScopeRemoved { where_: ast::NodeId, what_path: MovePathIndex } +impl RemoveNeedsDropArg for ScopeRemoved { + fn kind(&self) -> &'static str { "scope-end" } + fn node_id_removing_obligation(&self) -> ast::NodeId { self.where_ } + fn path_being_moved(&self) -> MovePathIndex { self.what_path } +} +impl<'a> RemoveNeedsDropArg for Move { + fn kind(&self) -> &'static str { "move" } + fn node_id_removing_obligation(&self) -> ast::NodeId { self.id } + fn path_being_moved(&self) -> MovePathIndex { self.path } +} +impl<'a> RemoveNeedsDropArg for VariantMatch { + fn kind(&self) -> &'static str { "refinement" } + fn node_id_removing_obligation(&self) -> ast::NodeId { self.id } + fn path_being_moved(&self) -> MovePathIndex { self.base_path } +} + +#[deriving(PartialEq, Clone, Show)] +pub enum FlowedMoveDataFlag { + InstrumentDropObligationsAsErrors, } impl<'a, 'tcx> FlowedMoveData<'a, 'tcx> { @@ -537,7 +1145,8 @@ impl<'a, 'tcx> FlowedMoveData<'a, 'tcx> { cfg: &cfg::CFG, id_range: ast_util::IdRange, decl: &ast::FnDecl, - body: &ast::Block) + body: &ast::Block, + flags: &[FlowedMoveDataFlag]) -> FlowedMoveData<'a, 'tcx> { let mut dfcx_moves = DataFlowContext::new(tcx, @@ -555,9 +1164,15 @@ 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.add_gen_kills(tcx, + &mut dfcx_moves, + &mut dfcx_assign, + flags); + 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); diff --git a/src/librustc/middle/check_match.rs b/src/librustc/middle/check_match.rs index d3321e555a40a..cf6a5c81c2378 100644 --- a/src/librustc/middle/check_match.rs +++ b/src/librustc/middle/check_match.rs @@ -14,6 +14,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::*; @@ -982,6 +983,7 @@ struct MutationChecker<'a, 'tcx: 'a> { } impl<'a, 'tcx> Delegate 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 21023986e1c2e..89f4b1707ff53 100644 --- a/src/librustc/middle/check_rvalues.rs +++ b/src/librustc/middle/check_rvalues.rs @@ -56,6 +56,11 @@ impl<'a, 'tcx> euv::Delegate 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/expr_use_visitor.rs b/src/librustc/middle/expr_use_visitor.rs index c8c5284022d99..c38f1af6795e4 100644 --- a/src/librustc/middle/expr_use_visitor.rs +++ b/src/librustc/middle/expr_use_visitor.rs @@ -14,6 +14,7 @@ * `ExprUseVisitor` determines how expressions are being used. */ +use driver::session::Session; use middle::mem_categorization as mc; use middle::def; use middle::freevars; @@ -42,6 +43,18 @@ pub trait Delegate { cmt: mc::cmt, 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) + fn matched_pat(&mut self, + matched_pat: &ast::Pat, + cmt: mc::cmt, + 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, @@ -96,6 +109,82 @@ pub enum MoveReason { CaptureMove, } +#[deriving(PartialEq,Show)] +pub enum MatchMode { + NonBindingMatch, + BorrowingMatch, + CopyingMatch, + MovingMatch, +} + +#[deriving(PartialEq,Show)] +enum TrackMatchMode { + Unknown, Definite(MatchMode, Span), Conflicting(Span, Span), +} + +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 meet(&mut self, mode: MatchMode, new_span: Span) { + *self = match (*self, mode) { + // Note that clause order below is very significant. + (Unknown, new) => Definite(new, new_span), + (Definite(old, span), new) if old == new => Definite(old, span), + + (Definite(old, span), NonBindingMatch) => Definite(old, span), + (Definite(NonBindingMatch, _), new) => Definite(new, new_span), + + (Definite(old, span), CopyingMatch) => Definite(old, span), + (Definite(CopyingMatch, _), new) => Definite(new, new_span), + + (Definite(_, old_span), _) => Conflicting(old_span, new_span), + (Conflicting(..), _) => *self, + }; + } + + fn mode(&self, sess: &Session) -> MatchMode { + match *self { + Unknown => NonBindingMatch, + Definite(mode, _) => mode, + Conflicting(old_span, new_span) => { + sess.span_err(new_span, "conflicting pattern introduced here."); + sess.span_note(old_span, "pattern move mode established here."); + + // Conservatively return MovingMatch to let the + // compiler continue to make progress. + MovingMatch + } + } + } +} + #[deriving(PartialEq,Show)] pub enum MutateMode { Init, @@ -243,7 +332,7 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,TYPER> { ty::ReScope(body.id), // Args live only as long as the fn body. arg_ty); - self.walk_pat(arg_cmt, &*arg.pat); + self.walk_pat_isolated(arg_cmt, &*arg.pat); } } @@ -367,7 +456,10 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,TYPER> { self.walk_expr(&**discr); let discr_cmt = return_if_err!(self.mc.cat_expr(&**discr)); for arm in arms.iter() { - self.walk_arm(discr_cmt.clone(), arm); + let mut mode = Unknown; + self.walk_arm_prepass(discr_cmt.clone(), arm, &mut mode); + let mode = mode.mode(&self.typer.tcx().sess); + self.walk_arm(discr_cmt.clone(), arm, mode); } } @@ -424,7 +516,7 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,TYPER> { pat.span, ty::ReScope(blk.id), pattern_type); - self.walk_pat(pat_cmt, &**pat); + self.walk_pat_isolated(pat_cmt, &**pat); self.walk_block(&**blk); } @@ -593,7 +685,7 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,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_pat_isolated(init_cmt, &*local.pat); } } } @@ -796,9 +888,15 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,TYPER> { return true; } - fn walk_arm(&mut self, discr_cmt: mc::cmt, arm: &ast::Arm) { + fn walk_arm_prepass(&mut self, discr_cmt: mc::cmt, arm: &ast::Arm, mode: &mut TrackMatchMode) { for pat in arm.pats.iter() { - self.walk_pat(discr_cmt.clone(), &**pat); + self.walk_pat_prepass(discr_cmt.clone(), &**pat, mode); + } + } + + fn walk_arm(&mut self, discr_cmt: mc::cmt, 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() { @@ -808,21 +906,71 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,TYPER> { self.consume_expr(&*arm.body); } - fn walk_pat(&mut self, cmt_discr: mc::cmt, pat: &ast::Pat) { + /// Walks a pat that occurs in isolation (i.e. top-level of a fn + /// arg or let binding. *Not* a match arm or nested pat.) + fn walk_pat_isolated(&mut self, cmt_discr: mc::cmt, pat: &ast::Pat) { + let mut mode = Unknown; + self.walk_pat_prepass(cmt_discr.clone(), pat, &mut mode); + let mode = mode.mode(&self.typer.tcx().sess); + 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 walk_pat_prepass(&mut self, + cmt_discr: mc::cmt, + pat: &ast::Pat, + mode: &mut TrackMatchMode) { + debug!("walk_pat_prepass 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.meet(BorrowingMatch, pat.span), + ast::PatIdent(ast::BindByValue(_), _, _) => { + match copy_or_move(tcx, cmt_pat.ty, PatBindingMove) { + Copy => mode.meet(CopyingMatch, pat.span), + Move(_) => mode.meet(MovingMatch, pat.span), + } + } + _ => { + tcx.sess.span_bug( + pat.span, + "binding pattern not an identifier"); + } + } + } + })); + } + + /// The core driver for walking a pattern; outer_context_mode must + /// be established ahead of time via `walk_pat_prepass` (see also + /// `walk_pat_isolated` for patterns that stand alone). + fn walk_pat(&mut self, + cmt_discr: mc::cmt, + 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)); @@ -852,6 +1000,37 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,TYPER> { debug!("walk_pat binding consuming pat"); delegate.consume_pat(pat, cmt_pat, mode); } + + ast::PatWild(_) => { + match match_mode { + NonBindingMatch | + BorrowingMatch | + CopyingMatch => {} + + MovingMatch => { + // On `enum E { Variant(Box) }`, both of + // the match arms: + // + // Variant(a) => ... + // Variant(_) => ... + // + // are conceptually moving into the `Variant` + // pattern, while the match arm: + // + // a @ Variant(_) => ... + // + // is *not* moving into the `Variant(_)`. + // Thus check the `pat_already_bound` flag to + // distinguish the latter two cases. + + if !mc.pat_is_already_bound_by_value { + let mode = copy_or_move(typer.tcx(), cmt_pat.ty, PatBindingMove); + delegate.consume_pat(pat, cmt_pat, mode); + } + } + } + } + _ => { typer.tcx().sess.span_bug( pat.span, @@ -905,6 +1084,54 @@ impl<'d,'t,'tcx,TYPER:mc::Typer<'tcx>> ExprUseVisitor<'d,'t,TYPER> { } } })); + + return_if_err!(mc.cat_pattern(cmt_discr, pat, |mc, cmt_pat, pat| { + let def_map = def_map.borrow(); + let tcx = typer.tcx(); + let match_mode = if mc.pat_is_already_bound_by_value { + // A pat in the context of `id @ ... pat ...` cannot + // be moved into, it can at most copy or borrow. + // + // Actually, post PR #16053, we cannot do *any* + // binding in such a context, but I am writing this + // code in anticipation of loosening that rule (FSK). + BorrowingMatch + } else { + match_mode + }; + + match pat.node { + ast::PatEnum(_, _) | ast::PatIdent(_, _, None) | ast::PatStruct(..) => { + match def_map.find(&pat.id) { + 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(..)) => { + debug!("struct cmt_pat={} pat={}", + cmt_pat.repr(tcx), + pat.repr(tcx)); + + delegate.matched_pat(pat, cmt_pat, match_mode); + } + _ => {} + } + } + _ => {} + } + })); } 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 17d941b5958a7..dac0627432eb5 100644 --- a/src/librustc/middle/mem_categorization.rs +++ b/src/librustc/middle/mem_categorization.rs @@ -88,7 +88,7 @@ pub enum categorization { cat_arg(ast::NodeId), // formal argument cat_deref(cmt, uint, PointerKind), // deref of a ptr cat_interior(cmt, InteriorKind), // something interior: field, tuple, etc - cat_downcast(cmt), // selects a particular enum variant (*1) + cat_downcast(cmt, ast::DefId), // selects a particular enum variant (*1) cat_discr(cmt, ast::NodeId), // match discriminant (see preserve()) // (*1) downcast is only required if the enum has more than one variant @@ -96,9 +96,8 @@ pub enum categorization { #[deriving(Clone, PartialEq)] pub struct CopiedUpvar { - pub upvar_id: ast::NodeId, + pub upvar_id: ty::UpvarId, pub onceness: ast::Onceness, - pub capturing_proc: ast::NodeId, } // different kinds of pointers: @@ -241,7 +240,11 @@ impl ast_node for ast::Pat { } pub struct MemCategorizationContext<'t,TYPER:'t> { - typer: &'t TYPER + typer: &'t TYPER, + + // tracks when looking at `pat` in context of `id @ (... pat ...)` + // (it affects whether we move into a wildcard or not). + pub pat_is_already_bound_by_value: bool, } pub type McResult = Result; @@ -379,7 +382,20 @@ macro_rules! if_ok( impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> { pub fn new(typer: &'t TYPER) -> MemCategorizationContext<'t,TYPER> { - MemCategorizationContext { typer: typer } + MemCategorizationContext { + typer: typer, + pat_is_already_bound_by_value: false, + } + } + + fn already_bound(&self, mode: ast::BindingMode) -> MemCategorizationContext<'t,TYPER> { + match mode { + ast::BindByRef(_) => *self, + ast::BindByValue(_) => MemCategorizationContext { + pat_is_already_bound_by_value: true, + ..*self + } + } } fn tcx(&self) -> &'t ty::ctxt<'tcx> { @@ -593,9 +609,11 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> { id:id, span:span, cat:cat_copied_upvar(CopiedUpvar { - upvar_id: var_id, + upvar_id: ty::UpvarId { + var_id: var_id, + closure_expr_id: fn_node_id + }, onceness: closure_ty.onceness, - capturing_proc: fn_node_id, }), mutbl: MutabilityCategory::from_def(&def), ty:expr_ty @@ -616,9 +634,11 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> { id: id, span: span, cat: cat_copied_upvar(CopiedUpvar { - upvar_id: var_id, + upvar_id: ty::UpvarId { + var_id: var_id, + closure_expr_id: fn_node_id, + }, onceness: onceness, - capturing_proc: fn_node_id, }), mutbl: MutabilityCategory::from_def(&def), ty: expr_ty @@ -996,13 +1016,14 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> { pub fn cat_downcast(&self, node: &N, base_cmt: cmt, - downcast_ty: ty::t) + downcast_ty: ty::t, + variant_did: ast::DefId) -> cmt { 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 }) } @@ -1075,14 +1096,14 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> { } ast::PatEnum(_, Some(ref subpats)) => { match self.tcx().def_map.borrow().find(&pat.id) { - Some(&def::DefVariant(enum_did, _, _)) => { + Some(&def::DefVariant(enum_did, variant_did, _)) => { // 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) + self.cat_downcast(pat, cmt.clone(), cmt.ty, variant_did) } }; @@ -1122,8 +1143,8 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> { } } - ast::PatIdent(_, _, Some(ref subpat)) => { - if_ok!(self.cat_pattern(cmt, &**subpat, op)); + ast::PatIdent(binding_mode, _, Some(ref subpat)) => { + if_ok!(self.already_bound(binding_mode).cat_pattern(cmt, &**subpat, op)); } ast::PatIdent(_, _, None) => { @@ -1132,9 +1153,21 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> { ast::PatStruct(_, ref field_pats, _) => { // {f1: p1, ..., fN: pN} + let downcast_cmt = match self.tcx().def_map.borrow().find(&pat.id) { + Some(&def::DefVariant(enum_did, variant_did, _)) => { + // variant{ a: x, b: y, c: z } + if ty::enum_is_univariant(self.tcx(), enum_did) { + cmt // univariant, no downcast needed + } else { + self.cat_downcast(pat, cmt.clone(), cmt.ty, variant_did) + } + } + _ => cmt, + }; + for fp in field_pats.iter() { let field_ty = if_ok!(self.pat_ty(&*fp.pat)); // see (*2) - let cmt_field = self.cat_field(pat, cmt.clone(), fp.ident, field_ty); + let cmt_field = self.cat_field(pat, downcast_cmt.clone(), fp.ident, field_ty); if_ok!(self.cat_pattern(cmt_field, &*fp.pat, |x,y,z| op(x,y,z))); } } @@ -1236,7 +1269,7 @@ impl<'t,'tcx,TYPER:Typer<'tcx>> MemCategorizationContext<'t,TYPER> { cat_discr(ref cmt, _) => { self.cmt_to_string(&**cmt) } - cat_downcast(ref cmt) => { + cat_downcast(ref cmt, _) => { self.cmt_to_string(&**cmt) } } @@ -1275,7 +1308,7 @@ impl cmt_ { cat_upvar(..) => { Rc::new((*self).clone()) } - cat_downcast(ref b) | + cat_downcast(ref b, _) | cat_discr(ref b, _) | cat_interior(ref b, _) | cat_deref(ref b, _, OwnedPtr) => { @@ -1299,7 +1332,7 @@ impl cmt_ { 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, _) | cat_discr(ref b, _) => { @@ -1373,8 +1406,8 @@ impl Repr for categorization { cat_interior(ref cmt, interior) => { format!("{}.{}", cmt.cat.repr(tcx), interior.repr(tcx)) } - cat_downcast(ref cmt) => { - format!("{}->(enum)", cmt.cat.repr(tcx)) + cat_downcast(ref cmt, ref variant_did) => { + format!("({}->{})", cmt.cat.repr(tcx), variant_did.repr(tcx)) } cat_discr(ref cmt, _) => { cmt.cat.repr(tcx) diff --git a/src/librustc/middle/trans/_match.rs b/src/librustc/middle/trans/_match.rs index 51023d03c23b0..d7f8175a3fc17 100644 --- a/src/librustc/middle/trans/_match.rs +++ b/src/librustc/middle/trans/_match.rs @@ -1251,6 +1251,7 @@ struct ReassignmentChecker { impl euv::Delegate 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) {} @@ -1258,7 +1259,7 @@ impl euv::Delegate for ReassignmentChecker { fn mutate(&mut self, _: ast::NodeId, _: Span, cmt: mc::cmt, _: euv::MutateMode) { match cmt.cat { - mc::cat_copied_upvar(mc::CopiedUpvar { upvar_id: vid, .. }) | + mc::cat_copied_upvar(mc::CopiedUpvar { upvar_id: ty::UpvarId { var_id: vid, .. }, .. }) | mc::cat_arg(vid) | mc::cat_local(vid) => self.reassigned = self.node == vid, _ => {} } diff --git a/src/librustc/middle/ty.rs b/src/librustc/middle/ty.rs index 897bc4517f4e1..75488a6519a1d 100644 --- a/src/librustc/middle/ty.rs +++ b/src/librustc/middle/ty.rs @@ -3194,6 +3194,56 @@ pub fn array_element_ty(t: t) -> Option { } } +/// Returns the type of element at index `i` in tuple or tuple-like type. +/// requires variant_id be Some(_) iff t represents a ty_enum. +pub fn positional_element_ty(cx: &ctxt, t: t, i: uint, variant_id: Option) -> Option { + + match (&get(t).sty, variant_id) { + (&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.get(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. +/// requires variant_id is Some(_) iff t represents a ty_enum. +pub fn named_element_ty(cx: &ctxt, t: t, n: ast::Name, variant_id: Option) -> Option { + + match (&get(t).sty, variant_id) { + (&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(cx: &ctxt, id: ast::NodeId) -> Rc { match cx.trait_refs.borrow().find(&id) { Some(t) => t.clone(), diff --git a/src/librustc/middle/typeck/check/regionck.rs b/src/librustc/middle/typeck/check/regionck.rs index a8f2a8f6f1dbb..fbb57887d3dcd 100644 --- a/src/librustc/middle/typeck/check/regionck.rs +++ b/src/librustc/middle/typeck/check/regionck.rs @@ -1461,7 +1461,7 @@ fn link_region(rcx: &Rcx, } mc::cat_discr(cmt_base, _) | - mc::cat_downcast(cmt_base) | + mc::cat_downcast(cmt_base, _) | mc::cat_deref(cmt_base, _, mc::GcPtr(..)) | mc::cat_deref(cmt_base, _, mc::OwnedPtr) | mc::cat_interior(cmt_base, _) => { @@ -1663,7 +1663,7 @@ fn adjust_upvar_borrow_kind_for_mut(rcx: &Rcx, match cmt.cat.clone() { mc::cat_deref(base, _, mc::OwnedPtr) | mc::cat_interior(base, _) | - mc::cat_downcast(base) | + mc::cat_downcast(base, _) | mc::cat_discr(base, _) => { // Interior or owned data is mutable if base is // mutable, so iterate to the base. @@ -1718,7 +1718,7 @@ fn adjust_upvar_borrow_kind_for_unique(rcx: &Rcx, cmt: mc::cmt) { match cmt.cat.clone() { mc::cat_deref(base, _, mc::OwnedPtr) | mc::cat_interior(base, _) | - mc::cat_downcast(base) | + mc::cat_downcast(base, _) | mc::cat_discr(base, _) => { // Interior or owned data is unique if base is // unique. diff --git a/src/libsyntax/ast_map/mod.rs b/src/libsyntax/ast_map/mod.rs index a5458461a8b25..d24e24cb86b68 100644 --- a/src/libsyntax/ast_map/mod.rs +++ b/src/libsyntax/ast_map/mod.rs @@ -536,7 +536,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) } } @@ -1012,7 +1016,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); @@ -1028,30 +1035,30 @@ fn node_id_to_string(map: &Map, id: NodeId) -> String { ItemImpl(..) => "impl", ItemMac(..) => "macro" }; - format!("{} {} (id={})", item_str, path_str, id) + format!("{} {}{:s}", 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 {}{:s}", 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 {}{:s}", 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 {}{:s}", + pprust::mac_to_string(mac), id_str) } } TypeImplItem(ref t) => { - format!("typedef {} in {} (id={})", + format!("typedef {} in {}{:s}", token::get_ident(t.ident), map.path_to_string(id), - id) + id_str) } } } @@ -1059,51 +1066,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 {}{:s}", 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 {}{:s}", token::get_ident(t.ident), map.path_to_string(id), - id) + id_str) } } } Some(NodeVariant(ref variant)) => { - format!("variant {} in {} (id={})", + format!("variant {} in {}{:s}", 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 {}{:s}", pprust::expr_to_string(&**expr), id_str) } Some(NodeStmt(ref stmt)) => { - format!("stmt {} (id={})", pprust::stmt_to_string(&**stmt), id) + format!("stmt {}{:s}", pprust::stmt_to_string(&**stmt), id_str) } Some(NodeArg(ref pat)) => { - format!("arg {} (id={})", pprust::pat_to_string(&**pat), id) + format!("arg {}{:s}", pprust::pat_to_string(&**pat), id_str) } Some(NodeLocal(ref pat)) => { - format!("local {} (id={})", pprust::pat_to_string(&**pat), id) + format!("local {}{:s}", pprust::pat_to_string(&**pat), id_str) } Some(NodePat(ref pat)) => { - format!("pat {} (id={})", pprust::pat_to_string(&**pat), id) + format!("pat {}{:s}", pprust::pat_to_string(&**pat), id_str) } Some(NodeBlock(ref block)) => { - format!("block {} (id={})", pprust::block_to_string(&**block), id) + format!("block {}{:s}", pprust::block_to_string(&**block), id_str) } Some(NodeStructCtor(_)) => { - format!("struct_ctor {} (id={})", map.path_to_string(id), id) + format!("struct_ctor {}{:s}", map.path_to_string(id), id_str) } Some(NodeLifetime(ref l)) => { - format!("lifetime {} (id={})", - pprust::lifetime_to_string(&**l), id) + format!("lifetime {}{:s}", + pprust::lifetime_to_string(&**l), id_str) } None => { - format!("unknown node (id={})", id) + format!("unknown node{:s}", id_str) } } } diff --git a/src/libsyntax/diagnostic.rs b/src/libsyntax/diagnostic.rs index faa3946b74d0f..4b3186c986270 100644 --- a/src/libsyntax/diagnostic.rs +++ b/src/libsyntax/diagnostic.rs @@ -93,6 +93,10 @@ impl SpanHandler { self.handler.emit_with_code(Some((&self.cm, sp)), msg, code, Error); self.handler.bump_err_count(); } + pub fn span_end_err(&self, sp: Span, msg: &str) { + self.handler.custom_emit(&self.cm, FullSpan(sp), msg, Error); + self.handler.bump_err_count(); + } pub fn span_warn(&self, sp: Span, msg: &str) { self.handler.emit(Some((&self.cm, sp)), msg, Warning); } diff --git a/src/test/compile-fail/drop-obligations-1.rs b/src/test/compile-fail/drop-obligations-1.rs new file mode 100644 index 0000000000000..f44a92c15d924 --- /dev/null +++ b/src/test/compile-fail/drop-obligations-1.rs @@ -0,0 +1,138 @@ +// 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 changes to drop obligations +// from bindings, moves, and assignments. + +// Note that this is not a true compile-fail test, in the sense that +// the code below is not meant to have any actual errors that are +// being detected by the comipiler. We are just re-purposing the +// compiletest error parsing as an easy way to annotate a test file +// with the expected operations from guts of the compiler. +// +// Note also that this is *not* testing the control-flow analysis +// itself (i.e. the computation of the actual drop-obligations at each +// control-flow merge point). It is just testing (and, perhaps more +// importantly, showing rustc developers) how each expression will +// affect the eventual control-flow analysis, i.e. what are the +// so-called "gen/kill" sets, which are inputs that drive the +// control-flow analysis. +// +// This latter point means that it is not so important here to check +// how things like if-expressions and loops work, since those tend to +// only matter in the control-flow analysis itself. But binding, +// assignment, and referencing constructs like `let`, `match`, and +// field-deref all *do* matter in this test. + +// The tests are written in terms of a generic type T for the values +// being moved/dropped since that is the simplest way to represent a +// potential drop obligation. + +// In all of the tests below, the arguments to the test are +// functions that: +// +// - d: move away an input +// - c: create an instance (usually of `T`) + +#[rustc_drop_obligations] +fn simplest(d: fn(T), c: fn() -> T) +{ + { + let x: T; //~ ERROR move removes drop-obl `$(local x)` + let y: T; //~ ERROR move removes drop-obl `$(local y)` + x = c(); //~ ERROR assignment adds drop-obl `$(local x)` + + } //~ ERROR scope-end removes drop-obl `$(local x)` + //~^ ERROR scope-end removes drop-obl `$(local y)` + +} + +struct Pair { x: X, y: Y } + +#[rustc_drop_obligations] +fn struct_as_unit(d: fn(Pair), c: fn() -> T) +{ + // `struct_as_unit` is illustrating how a struct like `Pair` + // is treated as an atomic entity if you do not move its fields + // independently from the struct as a whole. + + { + let p; //~ ERROR move removes drop-obl `$(local p)` + + p = Pair { //~ ERROR assignment adds drop-obl `$(local p)` + x: c(), y: c() + }; + + d(p); //~ ERROR move removes drop-obl `$(local p)` + + } //~ ERROR scope-end removes drop-obl `$(local p)` + +} + +#[rustc_drop_obligations] +fn struct_decomposes(d: fn(T), c: fn() -> T) +{ + // `struct_decomposes` is illustrating how `Pair` will break + // down into fragment for each individual drop obligation if you + // do move its fields independently from the struct as a whole. + + { + let p; //~ ERROR move removes drop-obl `$(local p).x` + //~^ ERROR move removes drop-obl `$(local p).y` + + p = Pair { //~ ERROR assignment adds drop-obl `$(local p).x` + //~^ ERROR assignment adds drop-obl `$(local p).y` + x: c(), y: c() + }; + + d(p.x); //~ ERROR move removes drop-obl `$(local p).x` + + } //~ ERROR scope-end removes drop-obl `$(local p).x` + //~^ ERROR scope-end removes drop-obl `$(local p).y` +} + +#[rustc_drop_obligations] +fn struct_copy_fields_ignorable(d_t: fn(T), d_k: fn(K), c: fn() -> Pair) +{ + // `struct_copy_fields_ignorable` illustrates copying a field + // (i.e. moving one that implements Copy) does not cause the + // struct to fragment. + + { + let p; //~ ERROR move removes drop-obl `$(local p)` + + p = c(); //~ ERROR assignment adds drop-obl `$(local p)` + + d_k(p.y); + + } //~ ERROR scope-end removes drop-obl `$(local p)` +} + +#[rustc_drop_obligations] +fn simple_enum(c: fn() -> Option, d: fn(Option)) { + // `simple_enum` just shows the basic (non-match) operations work + // on enum types. + // + // Note in particular that, in the current analysis, since we do + // not have refinement types, initializing `e` with a Copy variant + // like `None` (or `Zero` from E above) still introduces a + // drop-obligation on `e`. + + { + let e : Option; //~ ERROR move removes drop-obl `$(local e)` + + e = None; //~ ERROR assignment adds drop-obl `$(local e)` + + d(e); //~ ERROR move removes drop-obl `$(local e)` + + } //~ ERROR scope-end removes drop-obl `$(local e)` +} + +fn main() { } diff --git a/src/test/compile-fail/drop-obligations-2.rs b/src/test/compile-fail/drop-obligations-2.rs new file mode 100644 index 0000000000000..c69ceabc0c6ce --- /dev/null +++ b/src/test/compile-fail/drop-obligations-2.rs @@ -0,0 +1,81 @@ +// 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 changes to drop obligations +// from matches. + +// All opening remarks from drop-obligations-1.rs also apply here. +// in particular: +// +// Note that this is not a true compile-fail test, in the sense that +// the code below is not meant to have any actual errors that are +// being detected by the comipiler. We are just re-purposing the +// compiletest error parsing as an easy way to annotate a test file +// with the expected operations from guts of the compiler. + +// In all of the tests below, the arguments to the test are +// functions that: +// +// - d: move away an input +// - c: create an instance (usually of `T`) + +pub enum E { Zero, One(X), Two(X,Y), } + +#[rustc_drop_obligations] +fn matching_enum_by_move(c: fn() -> E, d: fn(T)) { + // `matching_enum_by_move` shows how variant matching manipulates + // the drop obligation state. + + { + let e : E; //~ ERROR move removes drop-obl `$(local e)` + + e = c(); //~ ERROR assignment adds drop-obl `$(local e)` + + // (match arms do not have their own spans, which partly + // explains why all the bindings for each arm are reported as + // being scoped by the match itself.) + + match e { + Zero => { //~ ERROR match whitelists drop-obl `$(local e)` + + } + + One(s_0) => { //~ ERROR refinement removes drop-obl `$(local e)` + //~^ ERROR match adds drop-obl `($(local e)->One).#0u` + //~^^ ERROR move removes drop-obl `($(local e)->One).#0u` + //~^^^ ERROR assignment adds drop-obl `$(local s_0)` + + d(s_0); //~ ERROR move removes drop-obl `$(local s_0)` + + } + + Two(t_0, t_1) => { //~ ERROR refinement removes drop-obl `$(local e)` + //~^ ERROR match adds drop-obl `($(local e)->Two).#0u` + //~^^ ERROR move removes drop-obl `($(local e)->Two).#0u` + //~^^^ ERROR assignment adds drop-obl `$(local t_0)` + //~^^^^ ERROR match adds drop-obl `($(local e)->Two).#1u` + //~^^^^^ ERROR move removes drop-obl `($(local e)->Two).#1u` + //~^^^^^^ ERROR assignment adds drop-obl `$(local t_1)` + + d(t_0); //~ ERROR move removes drop-obl `$(local t_0)` + } + } //~ ERROR scope-end removes drop-obl `$(local s_0)` + //~^ ERROR scope-end removes drop-obl `$(local t_0)` + //~^^ ERROR scope-end removes drop-obl `$(local t_1)` + + + } //~ ERROR scope-end removes drop-obl `$(local e)` + //~^ ERROR scope-end removes drop-obl `($(local e)->Zero)` + //~^^ ERROR scope-end removes drop-obl `($(local e)->One).#0u` + //~^^^ ERROR scope-end removes drop-obl `($(local e)->Two).#1u` + //~^^^^ ERROR scope-end removes drop-obl `($(local e)->Two).#0u` +} + +fn main() { } diff --git a/src/test/compile-fail/drop-obligations-3.rs b/src/test/compile-fail/drop-obligations-3.rs new file mode 100644 index 0000000000000..ea775605b14a8 --- /dev/null +++ b/src/test/compile-fail/drop-obligations-3.rs @@ -0,0 +1,142 @@ +// 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 changes to drop obligations +// from matches. + +// All opening remarks from drop-obligations-1.rs also apply here. +// in particular: +// +// Note that this is not a true compile-fail test, in the sense that +// the code below is not meant to have any actual errors that are +// being detected by the comipiler. We are just re-purposing the +// compiletest error parsing as an easy way to annotate a test file +// with the expected operations from guts of the compiler. + +// In all of the tests below, the arguments to the test are +// functions that: +// +// - c: create an instance (usually of `T`) + +pub enum E { Zero, One(X), Two(X,Y), } + +fn drop(t: T) { } + +#[rustc_drop_obligations] +fn matching_enum_copy_left(c: fn() -> E) { + // `matching_enum_by_move` shows how variant matching manipulates + // the drop obligation state. + + { + let e : E; //~ ERROR move removes drop-obl `$(local e)` + + e = c(); //~ ERROR assignment adds drop-obl `$(local e)` + + // (match arms do not have their own spans, which partly + // explains why all the bindings for each arm are reported as + // being scoped by the match itself.) + + match e { + Zero => { //~ ERROR match whitelists drop-obl `$(local e)` + + } + + // All of the fields (`c_0`) are `Copy`, so matching this + // variant does not remove the drop-obligation, but rather + // expands the whiteslist in the same way that `Zero` + // above does. + + One(c_0) => { //~ ERROR match whitelists drop-obl `$(local e)` + + // `c_0` is Copy, so dropping it has no effect on the + // drop-obligations. + drop(c_0); + } + + // `d_0` is `Copy` but `t_1` is not, so this does not + // whitelist `e`; but it *only* manipulates the + // drop-obligations associated with `t_1` in the matched + // variant (and not anything associated with `d_0`). + + Two(d_0, t_1) => { //~ ERROR refinement removes drop-obl `$(local e)` + //~^ ERROR match adds drop-obl `($(local e)->Two).#1u` + //~^^ ERROR move removes drop-obl `($(local e)->Two).#1u` + //~^^^ ERROR assignment adds drop-obl `$(local t_1)` + + // `d_0` is Copy, so dropping it has no effect on the + // drop-obligations. + drop(d_0); + + drop(t_1); //~ ERROR move removes drop-obl `$(local t_1)` + } + + // `c_0` and `d_0` are both `Copy`, so the scope-end here + // does not treat them as part of the drop-obligations. + + } //~ ERROR scope-end removes drop-obl `$(local t_1)` + + // The first component of variant `Two` is `Copy`, so it is not part of the + // removed drop obligations here. + + } //~ ERROR scope-end removes drop-obl `$(local e)` + //~^ ERROR scope-end removes drop-obl `($(local e)->Zero)` + //~^^ ERROR scope-end removes drop-obl `($(local e)->One)` + //~^^^ ERROR scope-end removes drop-obl `($(local e)->Two).#1u` +} + +#[rustc_drop_obligations] +fn matching_enum_copy_right(c: fn() -> E) { + // `matching_enum_by_move` shows how variant matching manipulates + // the drop obligation state. + + { + let e : E; //~ ERROR move removes drop-obl `$(local e)` + + e = c(); //~ ERROR assignment adds drop-obl `$(local e)` + + // (match arms do not have their own spans, which partly + // explains why all the bindings for each arm are reported as + // being scoped by the match itself.) + + match e { + Zero => { //~ ERROR match whitelists drop-obl `$(local e)` + + } + + One(s_0) => { //~ ERROR refinement removes drop-obl `$(local e)` + //~^ ERROR match adds drop-obl `($(local e)->One).#0u` + //~^^ ERROR move removes drop-obl `($(local e)->One).#0u` + //~^^^ ERROR assignment adds drop-obl `$(local s_0)` + + drop(s_0); //~ ERROR move removes drop-obl `$(local s_0)` + + } + + // `d_1`, the 2nd component of `Two` aka (..->Two).#1u, is + // `Copy`, so it is not part of the drop obligations. + + Two(t_0, d_1) => { //~ ERROR refinement removes drop-obl `$(local e)` + //~^ ERROR match adds drop-obl `($(local e)->Two).#0u` + //~^^ ERROR move removes drop-obl `($(local e)->Two).#0u` + //~^^^ ERROR assignment adds drop-obl `$(local t_0)` + + drop(t_0); //~ ERROR move removes drop-obl `$(local t_0)` + } + } //~ ERROR scope-end removes drop-obl `$(local s_0)` + //~^ ERROR scope-end removes drop-obl `$(local t_0)` + + + } //~ ERROR scope-end removes drop-obl `$(local e)` + //~^ ERROR scope-end removes drop-obl `($(local e)->Zero)` + //~^^ ERROR scope-end removes drop-obl `($(local e)->One).#0u` + //~^^^ ERROR scope-end removes drop-obl `($(local e)->Two).#0u` +} + +fn main() { } diff --git a/src/test/compile-fail/drop-obligations-4.rs b/src/test/compile-fail/drop-obligations-4.rs new file mode 100644 index 0000000000000..8b9288b946398 --- /dev/null +++ b/src/test/compile-fail/drop-obligations-4.rs @@ -0,0 +1,186 @@ +// 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 changes to drop obligations +// from matches that take input by-reference instead of by-move. + +// All opening remarks from drop-obligations-1.rs also apply here. +// in particular: +// +// Note that this is not a true compile-fail test, in the sense that +// the code below is not meant to have any actual errors that are +// being detected by the comipiler. We are just re-purposing the +// compiletest error parsing as an easy way to annotate a test file +// with the expected operations from guts of the compiler. + +// In all of the tests below, the arguments to the test are +// functions that: +// +// - c: create an instance (usually of `T`) + +pub enum E { Zero, Two(X,Y), } + +fn drop(t: T) { } + +#[rustc_drop_obligations] +fn matching_enum_copy_left(c: fn() -> E<(uint,C),T>) { + // `matching_enum_by_move` shows how variant matching manipulates + // the drop obligation state. + + { + let e : E<(uint,C),T>; //~ ERROR move removes drop-obl `$(local e)` + + e = c(); //~ ERROR assignment adds drop-obl `$(local e)` + + // (match arms do not have their own spans, which partly + // explains why all the bindings for each arm are reported as + // being scoped by the match itself.) + + match e { + Zero => { //~ ERROR match whitelists drop-obl `$(local e)` + + } + + // Arms that match by reference do not remove any + // drop-obligation for the input to the match. + + Two((1, c_0), ref t_1) => { + + // `c_0` is Copy, so dropping it has no effect on the + // drop-obligations. + drop(c_0); + + // `t_1` is only a `&T`, so it has no drop-obligation. + drop(t_1); + + } + + Two((_, ref d_0), ref t_1) => { + // (ref or not for d_0 doesn't really matter) + + drop(t_1); + } + + // `c_0`, `d_0`, and `t_1` are all `Copy`, so the scope-end here + // does not treat them as part of the drop-obligations. + + } + + } //~ ERROR scope-end removes drop-obl `$(local e)` + //~^ ERROR scope-end removes drop-obl `($(local e)->Zero)` + //~^^ ERROR scope-end removes drop-obl `($(local e)->Two)` + +} + +#[rustc_drop_obligations] +fn matching_enum_copy_right(c: fn() -> E) { + // `matching_enum_by_move` shows how variant matching manipulates + // the drop obligation state. + + { + let e : E; //~ ERROR move removes drop-obl `$(local e)` + + e = c(); //~ ERROR assignment adds drop-obl `$(local e)` + + // (match arms do not have their own spans, which partly + // explains why all the bindings for each arm are reported as + // being scoped by the match itself.) + + match e { + Zero => { //~ ERROR match whitelists drop-obl `$(local e)` + + } + + // `d_1`, the 2nd component of `Two` aka (..->Two).#1u, is + // `Copy`, so it is not part of the drop obligations. + + Two(t_0, d_1) => { //~ ERROR refinement removes drop-obl `$(local e)` + //~^ ERROR match adds drop-obl `($(local e)->Two).#0u` + //~^^ ERROR move removes drop-obl `($(local e)->Two).#0u` + //~^^^ ERROR assignment adds drop-obl `$(local t_0)` + + drop(t_0); //~ ERROR move removes drop-obl `$(local t_0)` + } + } //~ ERROR scope-end removes drop-obl `$(local t_0)` + + + } //~ ERROR scope-end removes drop-obl `$(local e)` + //~^ ERROR scope-end removes drop-obl `($(local e)->Zero)` + //~^^ ERROR scope-end removes drop-obl `($(local e)->Two).#0u` +} + +#[rustc_drop_obligations] +fn matching_enum_one_arm_moves(c: fn() -> E<(uint,C),T>) { + // `matching_enum_by_move` shows how variant matching manipulates + // the drop obligation state. + + { + let e : E<(uint,C),T>; //~ ERROR move removes drop-obl `$(local e)` + + e = c(); //~ ERROR assignment adds drop-obl `$(local e)` + + // (match arms do not have their own spans, which partly + // explains why all the bindings for each arm are reported as + // being scoped by the match itself.) + + match e { + Zero => { //~ ERROR match whitelists drop-obl `$(local e)` + + } + + // `d_0` is `Copy` but `t_1` is not, so this does not + // whitelist `e`; but it *only* manipulates the + // drop-obligations associated with `t_1` in the matched + // variant (and not anything associated with `d_0`). + + Two((0, d_0), s_1) => { + //~^ ERROR refinement removes drop-obl `$(local e)` + //~^^ ERROR match adds drop-obl `($(local e)->Two).#1u` + //~^^^ ERROR move removes drop-obl `($(local e)->Two).#1u` + //~^^^^ ERROR assignment adds drop-obl `$(local s_1)` + + // `d_0` is Copy, so dropping it has no effect on the + // drop-obligations. + drop(d_0); + + drop(s_1); //~ ERROR move removes drop-obl `$(local s_1)` + } + + // Arms that match by reference do not remove any + // drop-obligation for the input to the match. + + Two((1, d_0), ref t_1) => { + + drop(d_0); + + drop(t_1); + } + + Two((_, ref d_0), ref u_1) => { + + drop(d_0); + + drop(u_1); + } + + // The only non-Copy binding introduced by a match arm is `s_1`. + + } //~ ERROR scope-end removes drop-obl `$(local s_1)` + + // The first component of variant `Two` is `Copy`, so it is not part of the + // removed drop obligations here. + + } //~ ERROR scope-end removes drop-obl `$(local e)` + //~^ ERROR scope-end removes drop-obl `($(local e)->Zero)` + //~^^ ERROR scope-end removes drop-obl `($(local e)->Two).#1u` +} + + +fn main() { } diff --git a/src/test/compile-fail/drop-obligations-5.rs b/src/test/compile-fail/drop-obligations-5.rs new file mode 100644 index 0000000000000..f64047fabda45 --- /dev/null +++ b/src/test/compile-fail/drop-obligations-5.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 respect both `|\` and `proc` closures. + +// All opening remarks from drop-obligations-1.rs also apply here. +// in particular: +// +// Note that this is not a true compile-fail test, in the sense that +// the code below is not meant to have any actual errors that are +// being detected by the comipiler. We are just re-purposing the +// compiletest error parsing as an easy way to annotate a test file +// with the expected operations from guts of the compiler. + +// In all of the tests below, the three arguments to the test are +// functions that: +// +// - c: create an instance (usually of `T`) +// - i: does "something" with a given closure +// - b: does "something" wih borrowed `&T` +// - m: does "something" wih borrowed `&mut T` + +#[rustc_drop_obligations] +fn stack_closure(c: fn() -> T, i: fn(||), b: fn (&T)) +{ + { + let x: T; //~ ERROR move removes drop-obl `$(local x)` + let y: T; //~ ERROR move removes drop-obl `$(local y)` + x = c(); //~ ERROR assignment adds drop-obl `$(local x)` + + // A || captures free-variables by reference, so we + // get no note here. + i(|| { b(&x); }); + + } //~ ERROR scope-end removes drop-obl `$(local x)` + //~^ ERROR scope-end removes drop-obl `$(local y)` + +} + +#[rustc_drop_obligations] +fn proc_closure_1(c: fn() -> T, i: fn(proc ())) +{ + { + let x: T; //~ ERROR move removes drop-obl `$(local x)` + let y: T; //~ ERROR move removes drop-obl `$(local y)` + x = c(); //~ ERROR assignment adds drop-obl `$(local x)` + + // A proc captures free-variables by moving them, so we + // get a note here. + + i(proc() { x; }); + //~^ ERROR move removes drop-obl `$(local x)` + + } //~ ERROR scope-end removes drop-obl `$(local x)` + //~^ ERROR scope-end removes drop-obl `$(local y)` + +} + +#[rustc_drop_obligations] +fn proc_closure_2(c: fn() -> T, i: fn(proc ()), b: fn (&T)) +{ + { + let x: T; //~ ERROR move removes drop-obl `$(local x)` + let y: T; //~ ERROR move removes drop-obl `$(local y)` + x = c(); //~ ERROR assignment adds drop-obl `$(local x)` + + // A proc captures free-variables by moving them, so we + // get a note here. + + i(proc() { b(&x); }); + //~^ ERROR move removes drop-obl `$(local x)` + + } //~ ERROR scope-end removes drop-obl `$(local x)` + //~^ ERROR scope-end removes drop-obl `$(local y)` + +} + +fn main() { }