]> git.lizzy.rs Git - rust.git/commitdiff
Refactor core specialization and subst translation code to avoid
authorAaron Turon <aturon@mozilla.com>
Fri, 19 Feb 2016 18:49:14 +0000 (10:49 -0800)
committerAaron Turon <aturon@mozilla.com>
Mon, 14 Mar 2016 22:04:40 +0000 (15:04 -0700)
danger of inference variables floating around without their inference
context.

The main insight here is that, when we are translating substitutions
between two impls, *we already know that the more specific impl holds*,
so we do not need to add its obligations to the parameter
environment. Instead, we can just thread through the inference context
we used to show select the more specific impl in the first place.

src/librustc/middle/traits/project.rs
src/librustc/middle/traits/specialize/mod.rs
src/librustc/middle/traits/specialize/specialization_graph.rs
src/librustc_trans/trans/collector.rs
src/librustc_trans/trans/meth.rs

index 20d6c0b6fc0384f0b6b56f31690f424d45dd332e..2df3184a0fba1647ded27b746a9ae95f585175c7 100644 (file)
@@ -1008,7 +1008,7 @@ fn confirm_impl_candidate<'cx,'tcx>(
                        obligation.predicate.trait_ref);
                 tcx.types.err
             });
-            let substs = translate_substs(tcx, impl_def_id, substs, node_item.node);
+            let substs = translate_substs(selcx.infcx(), impl_def_id, substs, node_item.node);
             (ty.subst(tcx, &substs), nested)
         }
         None => {
index 9350598763375fde3b90d6a78827e1e220e96377..5939128719c0146338a58bbac0577a2085aa0d2d 100644 (file)
@@ -17,7 +17,8 @@
 // See traits/README.md for a bit more detail on how specialization
 // fits together with the rest of the trait machinery.
 
-use super::{util, build_selcx, SelectionContext};
+use super::{build_selcx, SelectionContext, FulfillmentContext};
+use super::util::{fresh_type_vars_for_impl, impl_trait_ref_and_oblig};
 
 use middle::cstore::CrateStore;
 use middle::def_id::DefId;
@@ -40,117 +41,90 @@ pub struct Overlap<'a, 'tcx: 'a> {
 /// Given a subst for the requested impl, translate it to a subst
 /// appropriate for the actual item definition (whether it be in that impl,
 /// a parent impl, or the trait).
-pub fn translate_substs<'tcx>(tcx: &ty::ctxt<'tcx>,
-                              from_impl: DefId,
-                              from_impl_substs: Substs<'tcx>,
-                              to_node: specialization_graph::Node)
-                              -> Substs<'tcx> {
-    match to_node {
-        specialization_graph::Node::Impl(to_impl) => {
+// When we have selected one impl, but are actually using item definitions from
+// a parent impl providing a default, we need a way to translate between the
+// type parameters of the two impls. Here the `source_impl` is the one we've
+// selected, and `source_substs` is a substitution of its generics (and
+// possibly some relevant `FnSpace` variables as well). And `target_node` is
+// the impl/trait we're actually going to get the definition from. The resulting
+// substitution will map from `target_node`'s generics to `source_impl`'s
+// generics as instantiated by `source_subst`.
+//
+// For example, consider the following scenario:
+//
+// ```rust
+// trait Foo { ... }
+// impl<T, U> Foo for (T, U) { ... }  // target impl
+// impl<V> Foo for (V, V) { ... }     // source impl
+// ```
+//
+// Suppose we have selected "source impl" with `V` instantiated with `u32`.
+// This function will produce a substitution with `T` and `U` both mapping to `u32`.
+//
+// Where clauses add some trickiness here, because they can be used to "define"
+// an argument indirectly:
+//
+// ```rust
+// impl<'a, I, T: 'a> Iterator for Cloned<I>
+//    where I: Iterator<Item=&'a T>, T: Clone
+// ```
+//
+// In a case like this, the substitution for `T` is determined indirectly,
+// through associated type projection. We deal with such cases by using
+// *fulfillment* to relate the two impls, requiring that all projections are
+// resolved.
+pub fn translate_substs<'a, 'tcx>(infcx: &InferCtxt<'a, 'tcx>,
+                                  source_impl: DefId,
+                                  source_substs: Substs<'tcx>,
+                                  target_node: specialization_graph::Node)
+                                  -> Substs<'tcx>
+{
+    let source_trait_ref = infcx.tcx
+                                .impl_trait_ref(source_impl)
+                                .unwrap()
+                                .subst(infcx.tcx, &source_substs);
+
+    // translate the Self and TyParam parts of the substitution, since those
+    // vary across impls
+    let target_substs = match target_node {
+        specialization_graph::Node::Impl(target_impl) => {
             // no need to translate if we're targetting the impl we started with
-            if from_impl == to_impl {
-                return from_impl_substs;
+            if source_impl == target_impl {
+                return source_substs;
             }
 
-            translate_substs_between_impls(tcx, from_impl, from_impl_substs, to_impl)
-
-        }
-        specialization_graph::Node::Trait(..) => {
-            translate_substs_from_impl_to_trait(tcx, from_impl, from_impl_substs)
+            fulfill_implication(infcx, source_trait_ref, target_impl).unwrap_or_else(|_| {
+                infcx.tcx
+                     .sess
+                     .bug("When translating substitutions for specialization, the expected \
+                           specializaiton failed to hold")
+            })
         }
-    }
-}
-
-/// When we have selected one impl, but are actually using item definitions from
-/// a parent impl providing a default, we need a way to translate between the
-/// type parameters of the two impls. Here the `source_impl` is the one we've
-/// selected, and `source_substs` is a substitution of its generics (and
-/// possibly some relevant `FnSpace` variables as well). And `target_impl` is
-/// the impl we're actually going to get the definition from. The resulting
-/// substitution will map from `target_impl`'s generics to `source_impl`'s
-/// generics as instantiated by `source_subst`.
-///
-/// For example, consider the following scenario:
-///
-/// ```rust
-/// trait Foo { ... }
-/// impl<T, U> Foo for (T, U) { ... }  // target impl
-/// impl<V> Foo for (V, V) { ... }     // source impl
-/// ```
-///
-/// Suppose we have selected "source impl" with `V` instantiated with `u32`.
-/// This function will produce a substitution with `T` and `U` both mapping to `u32`.
-///
-/// Where clauses add some trickiness here, because they can be used to "define"
-/// an argument indirectly:
-///
-/// ```rust
-/// impl<'a, I, T: 'a> Iterator for Cloned<I>
-///    where I: Iterator<Item=&'a T>, T: Clone
-/// ```
-///
-/// In a case like this, the substitution for `T` is determined indirectly,
-/// through associated type projection. We deal with such cases by using
-/// *fulfillment* to relate the two impls, requiring that all projections are
-/// resolved.
-fn translate_substs_between_impls<'tcx>(tcx: &ty::ctxt<'tcx>,
-                                        source_impl: DefId,
-                                        source_substs: Substs<'tcx>,
-                                        target_impl: DefId)
-                                        -> Substs<'tcx> {
-
-    // We need to build a subst that covers all the generics of
-    // `target_impl`. Start by introducing fresh infer variables:
-    let target_generics = tcx.lookup_item_type(target_impl).generics;
-    let mut infcx = infer::normalizing_infer_ctxt(tcx, &tcx.tables);
-    let mut target_substs = infcx.fresh_substs_for_generics(DUMMY_SP, &target_generics);
-    if source_substs.regions.is_erased() {
-        target_substs = target_substs.erase_regions()
-    }
+        specialization_graph::Node::Trait(..) => source_trait_ref.substs.clone(),
+    };
 
-    if !fulfill_implication(&mut infcx,
-                            source_impl,
-                            source_substs.clone(),
-                            target_impl,
-                            target_substs.clone()) {
-        tcx.sess
-           .bug("When translating substitutions for specialization, the expected specializaiton \
-                 failed to hold")
-    }
+    // retain erasure mode
+    // NB: this must happen before inheriting method generics below
+    let target_substs = if source_substs.regions.is_erased() {
+        target_substs.erase_regions()
+    } else {
+        target_substs
+    };
 
-    // Now resolve the *substitution* we built for the target earlier, replacing
-    // the inference variables inside with whatever we got from fulfillment. We
-    // also carry along any FnSpace substitutions, which don't need to be
-    // adjusted when mapping from one impl to another.
-    infcx.resolve_type_vars_if_possible(&target_substs)
-         .with_method_from_subst(&source_substs)
+    // directly inherent the method generics, since those do not vary across impls
+    target_substs.with_method_from_subst(&source_substs)
 }
 
-/// When we've selected an impl but need to use an item definition provided by
-/// the trait itself, we need to translate the substitution applied to the impl
-/// to one that makes sense for the trait.
-fn translate_substs_from_impl_to_trait<'tcx>(tcx: &ty::ctxt<'tcx>,
-                                             source_impl: DefId,
-                                             source_substs: Substs<'tcx>)
-                                             -> Substs<'tcx> {
 
-    let source_trait_ref = tcx.impl_trait_ref(source_impl).unwrap().subst(tcx, &source_substs);
+fn skolemizing_subst_for_impl<'a, 'tcx>(infcx: &InferCtxt<'a, 'tcx>,
+                                        impl_def_id: DefId)
+                                        -> Substs<'tcx>
+{
+    let impl_generics = infcx.tcx.lookup_item_type(impl_def_id).generics;
 
-    let mut new_substs = source_trait_ref.substs.clone();
-    if source_substs.regions.is_erased() {
-        new_substs = new_substs.erase_regions()
-    }
-
-    // Carry any FnSpace substitutions along; they don't need to be adjusted
-    new_substs.with_method_from_subst(&source_substs)
-}
+    let types = impl_generics.types.map(|def| infcx.tcx.mk_param_from_def(def));
 
-fn skolemizing_subst_for_impl<'a>(tcx: &ty::ctxt<'a>, impl_def_id: DefId) -> Substs<'a> {
-    let impl_generics = tcx.lookup_item_type(impl_def_id).generics;
-
-    let types = impl_generics.types.map(|def| tcx.mk_param_from_def(def));
-
-    // FIXME: figure out what we actually want here
+    // TODO: figure out what we actually want here
     let regions = impl_generics.regions.map(|_| ty::Region::ReStatic);
     // |d| infcx.next_region_var(infer::RegionVariableOrigin::EarlyBoundRegion(span, d.name)));
 
@@ -170,101 +144,101 @@ pub fn specializes(tcx: &ty::ctxt, impl1_def_id: DefId, impl2_def_id: DefId) ->
     // We determine whether there's a subset relationship by:
     //
     // - skolemizing impl1,
-    // - instantiating impl2 with fresh inference variables,
     // - assuming the where clauses for impl1,
+    // - instantiating impl2 with fresh inference variables,
     // - unifying,
     // - attempting to prove the where clauses for impl2
     //
-    // The last three steps are essentially checking for an implication between two impls
-    // after appropriate substitutions. This is what `fulfill_implication` checks for.
+    // The last three steps are encapsulated in `fulfill_implication`.
     //
     // See RFC 1210 for more details and justification.
 
     let mut infcx = infer::normalizing_infer_ctxt(tcx, &tcx.tables);
 
-    let impl1_substs = skolemizing_subst_for_impl(tcx, impl1_def_id);
-    let impl2_substs = util::fresh_type_vars_for_impl(&infcx, DUMMY_SP, impl2_def_id);
-
-    fulfill_implication(&mut infcx,
-                        impl1_def_id,
-                        impl1_substs,
-                        impl2_def_id,
-                        impl2_substs)
-}
-
-/// Does impl1 (instantiated with the impl1_substs) imply impl2
-/// (instantiated with impl2_substs)?
-///
-/// Mutates the `infcx` in two ways:
-/// - by adding the obligations of impl1 to the parameter environment
-/// - via fulfillment, so that if the implication holds the various unifications
-fn fulfill_implication<'a, 'tcx>(infcx: &mut InferCtxt<'a, 'tcx>,
-                                 impl1_def_id: DefId,
-                                 impl1_substs: Substs<'tcx>,
-                                 impl2_def_id: DefId,
-                                 impl2_substs: Substs<'tcx>)
-                                 -> bool {
-    let tcx = &infcx.tcx;
-
+    // Skiolemize impl1: we want to prove that "for all types matched by impl1,
+    // those types are also matched by impl2".
+    let impl1_substs = skolemizing_subst_for_impl(&infcx, impl1_def_id);
     let (impl1_trait_ref, impl1_obligations) = {
         let selcx = &mut SelectionContext::new(&infcx);
-        util::impl_trait_ref_and_oblig(selcx, impl1_def_id, &impl1_substs)
+        impl_trait_ref_and_oblig(selcx, impl1_def_id, &impl1_substs)
     };
 
+    // Add impl1's obligations as assumptions to the environment.
     let impl1_predicates: Vec<_> = impl1_obligations.iter()
                                                     .cloned()
                                                     .map(|oblig| oblig.predicate)
                                                     .collect();
-
     infcx.parameter_environment = ty::ParameterEnvironment {
         tcx: tcx,
         free_substs: impl1_substs,
-        implicit_region_bound: ty::ReEmpty, // FIXME: is this OK?
+        implicit_region_bound: ty::ReEmpty, // TODO: is this OK?
         caller_bounds: impl1_predicates,
         selection_cache: traits::SelectionCache::new(),
         evaluation_cache: traits::EvaluationCache::new(),
-        free_id_outlive: region::DUMMY_CODE_EXTENT, // FIXME: is this OK?
+        free_id_outlive: region::DUMMY_CODE_EXTENT, // TODO: is this OK?
     };
 
-    let selcx = &mut build_selcx(&infcx).project_topmost().build();
-    let (impl2_trait_ref, impl2_obligations) = util::impl_trait_ref_and_oblig(selcx,
-                                                                              impl2_def_id,
-                                                                              &impl2_substs);
-
-    // do the impls unify? If not, no specialization.
-    if let Err(_) = infer::mk_eq_trait_refs(&infcx,
-                                            true,
-                                            TypeOrigin::Misc(DUMMY_SP),
-                                            impl1_trait_ref,
-                                            impl2_trait_ref) {
-        debug!("fulfill_implication: {:?} does not unify with {:?}",
-               impl1_trait_ref,
-               impl2_trait_ref);
-        return false;
-    }
+    // Attempt to prove that impl2 applies, given all of the above.
+    fulfill_implication(&infcx, impl1_trait_ref, impl2_def_id).is_ok()
+}
 
-    let mut fulfill_cx = infcx.fulfillment_cx.borrow_mut();
+/// Attempt to fulfill all obligations of `target_impl` after unification with
+/// `source_trait_ref`. If successful, returns a substitution for *all* the
+/// generics of `target_impl`, including both those needed to unify with
+/// `source_trait_ref` and those whose identity is determined via a where
+/// clause in the impl.
+fn fulfill_implication<'a, 'tcx>(infcx: &InferCtxt<'a, 'tcx>,
+                                 source_trait_ref: ty::TraitRef<'tcx>,
+                                 target_impl: DefId)
+                                 -> Result<Substs<'tcx>, ()>
+{
+    infcx.probe(|_| {
+        let selcx = &mut build_selcx(&infcx).project_topmost().build();
+        let target_substs = fresh_type_vars_for_impl(&infcx, DUMMY_SP, target_impl);
+        let (target_trait_ref, obligations) = impl_trait_ref_and_oblig(selcx,
+                                                                       target_impl,
+                                                                       &target_substs);
+
+        // do the impls unify? If not, no specialization.
+        if let Err(_) = infer::mk_eq_trait_refs(&infcx,
+                                                true,
+                                                TypeOrigin::Misc(DUMMY_SP),
+                                                source_trait_ref,
+                                                target_trait_ref) {
+            debug!("fulfill_implication: {:?} does not unify with {:?}",
+                   source_trait_ref,
+                   target_trait_ref);
+            return Err(());
+        }
 
-    // attempt to prove all of the predicates for impl2 given those for impl1
-    // (which are packed up in penv)
+        // attempt to prove all of the predicates for impl2 given those for impl1
+        // (which are packed up in penv)
 
-    for oblig in impl2_obligations.into_iter() {
-        fulfill_cx.register_predicate_obligation(&infcx, oblig);
-    }
+        let mut fulfill_cx = FulfillmentContext::new();
+        for oblig in obligations.into_iter() {
+            fulfill_cx.register_predicate_obligation(&infcx, oblig);
+        }
 
-    if let Err(errors) = infer::drain_fulfillment_cx(&infcx, &mut fulfill_cx, &()) {
-        // no dice!
-        debug!("fulfill_implication: for impls on {:?} and {:?}, could not fulfill: {:?} given \
-                {:?}",
-               impl1_trait_ref,
-               impl2_trait_ref,
-               errors,
-               infcx.parameter_environment.caller_bounds);
-        false
-    } else {
-        debug!("fulfill_implication: an impl for {:?} specializes {:?} (`where` clauses elided)",
-               impl1_trait_ref,
-               impl2_trait_ref);
-        true
-    }
+        if let Err(errors) = infer::drain_fulfillment_cx(&infcx, &mut fulfill_cx, &()) {
+            // no dice!
+            debug!("fulfill_implication: for impls on {:?} and {:?}, could not fulfill: {:?} \
+                    given {:?}",
+                   source_trait_ref,
+                   target_trait_ref,
+                   errors,
+                   infcx.parameter_environment.caller_bounds);
+            Err(())
+        } else {
+            debug!("fulfill_implication: an impl for {:?} specializes {:?} (`where` clauses \
+                    elided)",
+                   source_trait_ref,
+                   target_trait_ref);
+
+            // Now resolve the *substitution* we built for the target earlier, replacing
+            // the inference variables inside with whatever we got from fulfillment.
+
+            // TODO: should this use `fully_resolve` instead?
+            Ok(infcx.resolve_type_vars_if_possible(&target_substs))
+        }
+    })
 }
index 01f3b6333f84afcfb49f82815ee856f455ee31bd..411c98c6066cf392d750739b6b3456352ca34b56 100644 (file)
@@ -105,6 +105,7 @@ pub fn insert<'a, 'tcx>(&mut self,
                     } else if ge && !le {
                         // possible_sibling specializes the impl
                         *slot = impl_def_id;
+                        self.parent.insert(impl_def_id, parent);
                         self.parent.insert(possible_sibling, impl_def_id);
                         // we have to defer the insertion, because we can't
                         // relinquish the borrow of `self.children`
index abfd127f38860d9ff4026fbf813423cbc01d17ce..b7e107ae5e7834e615a425fce88e7f931762b2a1 100644 (file)
@@ -819,9 +819,10 @@ fn do_static_trait_method_dispatch<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
             nested: _ }) =>
         {
             let callee_substs = impl_substs.with_method_from(&rcvr_substs);
-            let impl_method = tcx.get_impl_method(impl_did,
-                                                  tcx.mk_substs(callee_substs),
-                                                  trait_method.name);
+            let impl_method = meth::get_impl_method(tcx,
+                                                    impl_did,
+                                                    tcx.mk_substs(callee_substs),
+                                                    trait_method.name);
             Some((impl_method.method.def_id, impl_method.substs))
         }
         // If we have a closure or a function pointer, we will also encounter
@@ -1160,9 +1161,10 @@ fn create_trans_items_for_default_impls<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
 
                     // The substitutions we have are on the impl, so we grab
                     // the method type from the impl to substitute into.
-                    let mth = tcx.get_impl_method(impl_def_id,
-                                                  callee_substs,
-                                                  default_impl.name);
+                    let mth = meth::get_impl_method(tcx,
+                                                    impl_def_id,
+                                                    callee_substs.clone(),
+                                                    default_impl.name);
 
                     assert!(mth.is_provided);
 
index 2f5f052a72a85a69d91adf9b345b52077d2b7a34..28e9fcd47ff02d19647524eced219ff52e89be27 100644 (file)
@@ -381,7 +381,7 @@ pub fn get_vtable<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
 pub fn get_vtable_methods<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
                                     impl_id: DefId,
                                     substs: &'tcx subst::Substs<'tcx>)
-                                    -> Vec<Option<ty::util::ImplMethod<'tcx>>>
+                                    -> Vec<Option<ImplMethod<'tcx>>>
 {
     let tcx = ccx.tcx();
 
@@ -488,12 +488,13 @@ pub fn get_impl_method<'tcx>(tcx: &ty::ctxt<'tcx>,
 
     let trait_def_id = tcx.trait_id_of_impl(impl_def_id).unwrap();
     let trait_def = tcx.lookup_trait_def(trait_def_id);
+    let infcx = infer::normalizing_infer_ctxt(tcx, &tcx.tables);
 
     match trait_def.ancestors(impl_def_id).fn_defs(tcx, name).next() {
         Some(node_item) => {
             ImplMethod {
                 method: node_item.item,
-                substs: traits::translate_substs(tcx, impl_def_id, substs, node_item.node),
+                substs: traits::translate_substs(&infcx, impl_def_id, substs, node_item.node),
                 is_provided: node_item.node.is_from_trait(),
             }
         }