diff --git a/src/librustc_typeck/check/dropck.rs b/src/librustc_typeck/check/dropck.rs index a1e0f91984b..983c3f52838 100644 --- a/src/librustc_typeck/check/dropck.rs +++ b/src/librustc_typeck/check/dropck.rs @@ -12,6 +12,7 @@ use check::regionck::{self, Rcx}; use middle::infer; use middle::region; +use middle::subst; use middle::ty::{self, Ty}; use util::ppaux::{Repr}; @@ -46,6 +47,10 @@ fn iterate_over_potentially_unsafe_regions_in_type<'a, 'tcx>( { let origin = |&:| infer::SubregionOrigin::SafeDestructor(span); let mut walker = ty_root.walk(); + let opt_phantom_data_def_id = rcx.tcx().lang_items.phantom_data(); + + let destructor_for_type = rcx.tcx().destructor_for_type.borrow(); + while let Some(typ) = walker.next() { // Avoid recursing forever. if breadcrumbs.contains(&typ) { @@ -53,24 +58,196 @@ fn iterate_over_potentially_unsafe_regions_in_type<'a, 'tcx>( } breadcrumbs.push(typ); - let has_dtor = match typ.sty { - ty::ty_struct(struct_did, _) => ty::has_dtor(rcx.tcx(), struct_did), - ty::ty_enum(enum_did, _) => ty::has_dtor(rcx.tcx(), enum_did), - _ => false, + // If we encounter `PhantomData`, then we should replace it + // with `T`, the type it represents as owned by the + // surrounding context, before doing further analysis. + let typ = if let ty::ty_struct(struct_did, substs) = typ.sty { + if opt_phantom_data_def_id == Some(struct_did) { + let item_type = ty::lookup_item_type(rcx.tcx(), struct_did); + let tp_def = item_type.generics.types + .opt_get(subst::TypeSpace, 0).unwrap(); + let new_typ = substs.type_for_def(tp_def); + debug!("replacing phantom {} with {}", + typ.repr(rcx.tcx()), new_typ.repr(rcx.tcx())); + new_typ + } else { + typ + } + } else { + typ }; - debug!("iterate_over_potentially_unsafe_regions_in_type \ - {}typ: {} scope: {:?} has_dtor: {}", - (0..depth).map(|_| ' ').collect::(), - typ.repr(rcx.tcx()), scope, has_dtor); + let opt_type_did = match typ.sty { + ty::ty_struct(struct_did, _) => Some(struct_did), + ty::ty_enum(enum_did, _) => Some(enum_did), + _ => None, + }; - if has_dtor { + let opt_dtor = + opt_type_did.and_then(|did| destructor_for_type.get(&did)); + + debug!("iterate_over_potentially_unsafe_regions_in_type \ + {}typ: {} scope: {:?} opt_dtor: {:?}", + (0..depth).map(|_| ' ').collect::(), + typ.repr(rcx.tcx()), scope, opt_dtor); + + // If `typ` has a destructor, then we must ensure that all + // borrowed data reachable via `typ` must outlive the parent + // of `scope`. This is handled below. + // + // However, there is an important special case: by + // parametricity, any generic type parameters have *no* trait + // bounds in the Drop impl can not be used in any way (apart + // from being dropped), and thus we can treat data borrowed + // via such type parameters remains unreachable. + // + // For example, consider `impl Drop for Vec { ... }`, + // which does have to be able to drop instances of `T`, but + // otherwise cannot read data from `T`. + // + // Of course, for the type expression passed in for any such + // unbounded type parameter `T`, we must resume the recursive + // analysis on `T` (since it would be ignored by + // type_must_outlive). + // + // FIXME (pnkfelix): Long term, we could be smart and actually + // feed which generic parameters can be ignored *into* `fn + // type_must_outlive` (or some generalization thereof). But + // for the short term, it probably covers most cases of + // interest to just special case Drop impls where: (1.) there + // are no generic lifetime parameters and (2.) *all* generic + // type parameters are unbounded. If both conditions hold, we + // simply skip the `type_must_outlive` call entirely (but + // resume the recursive checking of the type-substructure). + + let has_dtor_of_interest; + + if let Some(&dtor_method_did) = opt_dtor { + let impl_did = ty::impl_of_method(rcx.tcx(), dtor_method_did) + .unwrap_or_else(|| { + rcx.tcx().sess.span_bug( + span, "no Drop impl found for drop method") + }); + + let dtor_typescheme = ty::lookup_item_type(rcx.tcx(), impl_did); + let dtor_generics = dtor_typescheme.generics; + + let has_pred_of_interest = dtor_generics.predicates.iter().any(|pred| { + // In `impl Drop where ...`, we automatically + // assume some predicate will be meaningful and thus + // represents a type through which we could reach + // borrowed data. However, there can be implicit + // predicates (namely for Sized), and so we still need + // to walk through and filter out those cases. + + let result = match *pred { + ty::Predicate::Trait(ty::Binder(ref t_pred)) => { + let def_id = t_pred.trait_ref.def_id; + match rcx.tcx().lang_items.to_builtin_kind(def_id) { + Some(ty::BoundSend) | + Some(ty::BoundSized) | + Some(ty::BoundCopy) | + Some(ty::BoundSync) => false, + _ => true, + } + } + ty::Predicate::Equate(..) | + ty::Predicate::RegionOutlives(..) | + ty::Predicate::TypeOutlives(..) | + ty::Predicate::Projection(..) => { + // we assume all of these where-clauses may + // give the drop implementation the capabilty + // to access borrowed data. + true + } + }; + + if result { + debug!("typ: {} has interesting dtor due to generic preds, e.g. {}", + typ.repr(rcx.tcx()), pred.repr(rcx.tcx())); + } + + result + }); + + let has_type_param_of_interest = dtor_generics.types.iter().any(|t| { + let &ty::ParamBounds { + ref region_bounds, builtin_bounds, ref trait_bounds, + ref projection_bounds, + } = &t.bounds; + + // Belt-and-suspenders: The current set of builtin + // bounds {Send, Sized, Copy, Sync} do not introduce + // any new capability to access borrowed data hidden + // behind a type parameter. + // + // In case new builtin bounds get added that do not + // satisfy that property, ensure `builtin_bounds \ + // {Send,Sized,Copy,Sync}` is empty. + + let mut builtin_bounds = builtin_bounds; + builtin_bounds.remove(&ty::BoundSend); + builtin_bounds.remove(&ty::BoundSized); + builtin_bounds.remove(&ty::BoundCopy); + builtin_bounds.remove(&ty::BoundSync); + + let has_bounds = + !region_bounds.is_empty() || + !builtin_bounds.is_empty() || + !trait_bounds.is_empty() || + !projection_bounds.is_empty(); + + if has_bounds { + debug!("typ: {} has interesting dtor due to \ + bounds on param {}", + typ.repr(rcx.tcx()), t.name); + } + + has_bounds + + }); + + // In `impl<'a> Drop ...`, we automatically assume + // `'a` is meaningful and thus represents a bound + // through which we could reach borrowed data. + // + // FIXME (pnkfelix): In the future it would be good to + // extend the language to allow the user to express, + // in the impl signature, that a lifetime is not + // actually used (something like `where 'a: ?Live`). + let has_region_param_of_interest = + dtor_generics.has_region_params(subst::TypeSpace); + + has_dtor_of_interest = + has_region_param_of_interest || + has_type_param_of_interest || + has_pred_of_interest; + + if has_dtor_of_interest { + debug!("typ: {} has interesting dtor, due to \ + region params: {} type params: {} or pred: {}", + typ.repr(rcx.tcx()), + has_region_param_of_interest, + has_type_param_of_interest, + has_pred_of_interest); + } else { + debug!("typ: {} has dtor, but it is uninteresting", + typ.repr(rcx.tcx())); + } + + } else { + debug!("typ: {} has no dtor, and thus is uninteresting", + typ.repr(rcx.tcx())); + has_dtor_of_interest = false; + } + + if has_dtor_of_interest { // If `typ` has a destructor, then we must ensure that all // borrowed data reachable via `typ` must outlive the // parent of `scope`. (It does not suffice for it to // outlive `scope` because that could imply that the // borrowed data is torn down in between the end of - // `scope` and when the destructor itself actually runs. + // `scope` and when the destructor itself actually runs.) let parent_region = match rcx.tcx().region_maps.opt_encl_scope(scope) {