Skip to content

Commit eaf2f90

Browse files
committed
Refactor core specialization and subst translation code to avoid
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.
1 parent 8f20cbf commit eaf2f90

File tree

5 files changed

+156
-178
lines changed

5 files changed

+156
-178
lines changed

src/librustc/middle/traits/project.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1008,7 +1008,7 @@ fn confirm_impl_candidate<'cx,'tcx>(
10081008
obligation.predicate.trait_ref);
10091009
tcx.types.err
10101010
});
1011-
let substs = translate_substs(tcx, impl_def_id, substs, node_item.node);
1011+
let substs = translate_substs(selcx.infcx(), impl_def_id, substs, node_item.node);
10121012
(ty.subst(tcx, &substs), nested)
10131013
}
10141014
None => {

src/librustc/middle/traits/specialize/mod.rs

Lines changed: 143 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
// See traits/README.md for a bit more detail on how specialization
1818
// fits together with the rest of the trait machinery.
1919

20-
use super::{util, build_selcx, SelectionContext};
20+
use super::{build_selcx, SelectionContext, FulfillmentContext};
21+
use super::util::{fresh_type_vars_for_impl, impl_trait_ref_and_oblig};
2122

2223
use middle::cstore::CrateStore;
2324
use middle::def_id::DefId;
@@ -40,117 +41,90 @@ pub struct Overlap<'a, 'tcx: 'a> {
4041
/// Given a subst for the requested impl, translate it to a subst
4142
/// appropriate for the actual item definition (whether it be in that impl,
4243
/// a parent impl, or the trait).
43-
pub fn translate_substs<'tcx>(tcx: &ty::ctxt<'tcx>,
44-
from_impl: DefId,
45-
from_impl_substs: Substs<'tcx>,
46-
to_node: specialization_graph::Node)
47-
-> Substs<'tcx> {
48-
match to_node {
49-
specialization_graph::Node::Impl(to_impl) => {
44+
// When we have selected one impl, but are actually using item definitions from
45+
// a parent impl providing a default, we need a way to translate between the
46+
// type parameters of the two impls. Here the `source_impl` is the one we've
47+
// selected, and `source_substs` is a substitution of its generics (and
48+
// possibly some relevant `FnSpace` variables as well). And `target_node` is
49+
// the impl/trait we're actually going to get the definition from. The resulting
50+
// substitution will map from `target_node`'s generics to `source_impl`'s
51+
// generics as instantiated by `source_subst`.
52+
//
53+
// For example, consider the following scenario:
54+
//
55+
// ```rust
56+
// trait Foo { ... }
57+
// impl<T, U> Foo for (T, U) { ... } // target impl
58+
// impl<V> Foo for (V, V) { ... } // source impl
59+
// ```
60+
//
61+
// Suppose we have selected "source impl" with `V` instantiated with `u32`.
62+
// This function will produce a substitution with `T` and `U` both mapping to `u32`.
63+
//
64+
// Where clauses add some trickiness here, because they can be used to "define"
65+
// an argument indirectly:
66+
//
67+
// ```rust
68+
// impl<'a, I, T: 'a> Iterator for Cloned<I>
69+
// where I: Iterator<Item=&'a T>, T: Clone
70+
// ```
71+
//
72+
// In a case like this, the substitution for `T` is determined indirectly,
73+
// through associated type projection. We deal with such cases by using
74+
// *fulfillment* to relate the two impls, requiring that all projections are
75+
// resolved.
76+
pub fn translate_substs<'a, 'tcx>(infcx: &InferCtxt<'a, 'tcx>,
77+
source_impl: DefId,
78+
source_substs: Substs<'tcx>,
79+
target_node: specialization_graph::Node)
80+
-> Substs<'tcx>
81+
{
82+
let source_trait_ref = infcx.tcx
83+
.impl_trait_ref(source_impl)
84+
.unwrap()
85+
.subst(infcx.tcx, &source_substs);
86+
87+
// translate the Self and TyParam parts of the substitution, since those
88+
// vary across impls
89+
let target_substs = match target_node {
90+
specialization_graph::Node::Impl(target_impl) => {
5091
// no need to translate if we're targetting the impl we started with
51-
if from_impl == to_impl {
52-
return from_impl_substs;
92+
if source_impl == target_impl {
93+
return source_substs;
5394
}
5495

55-
translate_substs_between_impls(tcx, from_impl, from_impl_substs, to_impl)
56-
57-
}
58-
specialization_graph::Node::Trait(..) => {
59-
translate_substs_from_impl_to_trait(tcx, from_impl, from_impl_substs)
96+
fulfill_implication(infcx, source_trait_ref, target_impl).unwrap_or_else(|_| {
97+
infcx.tcx
98+
.sess
99+
.bug("When translating substitutions for specialization, the expected \
100+
specializaiton failed to hold")
101+
})
60102
}
61-
}
62-
}
63-
64-
/// When we have selected one impl, but are actually using item definitions from
65-
/// a parent impl providing a default, we need a way to translate between the
66-
/// type parameters of the two impls. Here the `source_impl` is the one we've
67-
/// selected, and `source_substs` is a substitution of its generics (and
68-
/// possibly some relevant `FnSpace` variables as well). And `target_impl` is
69-
/// the impl we're actually going to get the definition from. The resulting
70-
/// substitution will map from `target_impl`'s generics to `source_impl`'s
71-
/// generics as instantiated by `source_subst`.
72-
///
73-
/// For example, consider the following scenario:
74-
///
75-
/// ```rust
76-
/// trait Foo { ... }
77-
/// impl<T, U> Foo for (T, U) { ... } // target impl
78-
/// impl<V> Foo for (V, V) { ... } // source impl
79-
/// ```
80-
///
81-
/// Suppose we have selected "source impl" with `V` instantiated with `u32`.
82-
/// This function will produce a substitution with `T` and `U` both mapping to `u32`.
83-
///
84-
/// Where clauses add some trickiness here, because they can be used to "define"
85-
/// an argument indirectly:
86-
///
87-
/// ```rust
88-
/// impl<'a, I, T: 'a> Iterator for Cloned<I>
89-
/// where I: Iterator<Item=&'a T>, T: Clone
90-
/// ```
91-
///
92-
/// In a case like this, the substitution for `T` is determined indirectly,
93-
/// through associated type projection. We deal with such cases by using
94-
/// *fulfillment* to relate the two impls, requiring that all projections are
95-
/// resolved.
96-
fn translate_substs_between_impls<'tcx>(tcx: &ty::ctxt<'tcx>,
97-
source_impl: DefId,
98-
source_substs: Substs<'tcx>,
99-
target_impl: DefId)
100-
-> Substs<'tcx> {
101-
102-
// We need to build a subst that covers all the generics of
103-
// `target_impl`. Start by introducing fresh infer variables:
104-
let target_generics = tcx.lookup_item_type(target_impl).generics;
105-
let mut infcx = infer::normalizing_infer_ctxt(tcx, &tcx.tables);
106-
let mut target_substs = infcx.fresh_substs_for_generics(DUMMY_SP, &target_generics);
107-
if source_substs.regions.is_erased() {
108-
target_substs = target_substs.erase_regions()
109-
}
103+
specialization_graph::Node::Trait(..) => source_trait_ref.substs.clone(),
104+
};
110105

111-
if !fulfill_implication(&mut infcx,
112-
source_impl,
113-
source_substs.clone(),
114-
target_impl,
115-
target_substs.clone()) {
116-
tcx.sess
117-
.bug("When translating substitutions for specialization, the expected specializaiton \
118-
failed to hold")
119-
}
106+
// retain erasure mode
107+
// NB: this must happen before inheriting method generics below
108+
let target_substs = if source_substs.regions.is_erased() {
109+
target_substs.erase_regions()
110+
} else {
111+
target_substs
112+
};
120113

121-
// Now resolve the *substitution* we built for the target earlier, replacing
122-
// the inference variables inside with whatever we got from fulfillment. We
123-
// also carry along any FnSpace substitutions, which don't need to be
124-
// adjusted when mapping from one impl to another.
125-
infcx.resolve_type_vars_if_possible(&target_substs)
126-
.with_method_from_subst(&source_substs)
114+
// directly inherent the method generics, since those do not vary across impls
115+
target_substs.with_method_from_subst(&source_substs)
127116
}
128117

129-
/// When we've selected an impl but need to use an item definition provided by
130-
/// the trait itself, we need to translate the substitution applied to the impl
131-
/// to one that makes sense for the trait.
132-
fn translate_substs_from_impl_to_trait<'tcx>(tcx: &ty::ctxt<'tcx>,
133-
source_impl: DefId,
134-
source_substs: Substs<'tcx>)
135-
-> Substs<'tcx> {
136118

137-
let source_trait_ref = tcx.impl_trait_ref(source_impl).unwrap().subst(tcx, &source_substs);
119+
fn skolemizing_subst_for_impl<'a, 'tcx>(infcx: &InferCtxt<'a, 'tcx>,
120+
impl_def_id: DefId)
121+
-> Substs<'tcx>
122+
{
123+
let impl_generics = infcx.tcx.lookup_item_type(impl_def_id).generics;
138124

139-
let mut new_substs = source_trait_ref.substs.clone();
140-
if source_substs.regions.is_erased() {
141-
new_substs = new_substs.erase_regions()
142-
}
143-
144-
// Carry any FnSpace substitutions along; they don't need to be adjusted
145-
new_substs.with_method_from_subst(&source_substs)
146-
}
125+
let types = impl_generics.types.map(|def| infcx.tcx.mk_param_from_def(def));
147126

148-
fn skolemizing_subst_for_impl<'a>(tcx: &ty::ctxt<'a>, impl_def_id: DefId) -> Substs<'a> {
149-
let impl_generics = tcx.lookup_item_type(impl_def_id).generics;
150-
151-
let types = impl_generics.types.map(|def| tcx.mk_param_from_def(def));
152-
153-
// FIXME: figure out what we actually want here
127+
// TODO: figure out what we actually want here
154128
let regions = impl_generics.regions.map(|_| ty::Region::ReStatic);
155129
// |d| infcx.next_region_var(infer::RegionVariableOrigin::EarlyBoundRegion(span, d.name)));
156130

@@ -170,101 +144,101 @@ pub fn specializes(tcx: &ty::ctxt, impl1_def_id: DefId, impl2_def_id: DefId) ->
170144
// We determine whether there's a subset relationship by:
171145
//
172146
// - skolemizing impl1,
173-
// - instantiating impl2 with fresh inference variables,
174147
// - assuming the where clauses for impl1,
148+
// - instantiating impl2 with fresh inference variables,
175149
// - unifying,
176150
// - attempting to prove the where clauses for impl2
177151
//
178-
// The last three steps are essentially checking for an implication between two impls
179-
// after appropriate substitutions. This is what `fulfill_implication` checks for.
152+
// The last three steps are encapsulated in `fulfill_implication`.
180153
//
181154
// See RFC 1210 for more details and justification.
182155

183156
let mut infcx = infer::normalizing_infer_ctxt(tcx, &tcx.tables);
184157

185-
let impl1_substs = skolemizing_subst_for_impl(tcx, impl1_def_id);
186-
let impl2_substs = util::fresh_type_vars_for_impl(&infcx, DUMMY_SP, impl2_def_id);
187-
188-
fulfill_implication(&mut infcx,
189-
impl1_def_id,
190-
impl1_substs,
191-
impl2_def_id,
192-
impl2_substs)
193-
}
194-
195-
/// Does impl1 (instantiated with the impl1_substs) imply impl2
196-
/// (instantiated with impl2_substs)?
197-
///
198-
/// Mutates the `infcx` in two ways:
199-
/// - by adding the obligations of impl1 to the parameter environment
200-
/// - via fulfillment, so that if the implication holds the various unifications
201-
fn fulfill_implication<'a, 'tcx>(infcx: &mut InferCtxt<'a, 'tcx>,
202-
impl1_def_id: DefId,
203-
impl1_substs: Substs<'tcx>,
204-
impl2_def_id: DefId,
205-
impl2_substs: Substs<'tcx>)
206-
-> bool {
207-
let tcx = &infcx.tcx;
208-
158+
// Skiolemize impl1: we want to prove that "for all types matched by impl1,
159+
// those types are also matched by impl2".
160+
let impl1_substs = skolemizing_subst_for_impl(&infcx, impl1_def_id);
209161
let (impl1_trait_ref, impl1_obligations) = {
210162
let selcx = &mut SelectionContext::new(&infcx);
211-
util::impl_trait_ref_and_oblig(selcx, impl1_def_id, &impl1_substs)
163+
impl_trait_ref_and_oblig(selcx, impl1_def_id, &impl1_substs)
212164
};
213165

166+
// Add impl1's obligations as assumptions to the environment.
214167
let impl1_predicates: Vec<_> = impl1_obligations.iter()
215168
.cloned()
216169
.map(|oblig| oblig.predicate)
217170
.collect();
218-
219171
infcx.parameter_environment = ty::ParameterEnvironment {
220172
tcx: tcx,
221173
free_substs: impl1_substs,
222-
implicit_region_bound: ty::ReEmpty, // FIXME: is this OK?
174+
implicit_region_bound: ty::ReEmpty, // TODO: is this OK?
223175
caller_bounds: impl1_predicates,
224176
selection_cache: traits::SelectionCache::new(),
225177
evaluation_cache: traits::EvaluationCache::new(),
226-
free_id_outlive: region::DUMMY_CODE_EXTENT, // FIXME: is this OK?
178+
free_id_outlive: region::DUMMY_CODE_EXTENT, // TODO: is this OK?
227179
};
228180

229-
let selcx = &mut build_selcx(&infcx).project_topmost().build();
230-
let (impl2_trait_ref, impl2_obligations) = util::impl_trait_ref_and_oblig(selcx,
231-
impl2_def_id,
232-
&impl2_substs);
233-
234-
// do the impls unify? If not, no specialization.
235-
if let Err(_) = infer::mk_eq_trait_refs(&infcx,
236-
true,
237-
TypeOrigin::Misc(DUMMY_SP),
238-
impl1_trait_ref,
239-
impl2_trait_ref) {
240-
debug!("fulfill_implication: {:?} does not unify with {:?}",
241-
impl1_trait_ref,
242-
impl2_trait_ref);
243-
return false;
244-
}
181+
// Attempt to prove that impl2 applies, given all of the above.
182+
fulfill_implication(&infcx, impl1_trait_ref, impl2_def_id).is_ok()
183+
}
245184

246-
let mut fulfill_cx = infcx.fulfillment_cx.borrow_mut();
185+
/// Attempt to fulfill all obligations of `target_impl` after unification with
186+
/// `source_trait_ref`. If successful, returns a substitution for *all* the
187+
/// generics of `target_impl`, including both those needed to unify with
188+
/// `source_trait_ref` and those whose identity is determined via a where
189+
/// clause in the impl.
190+
fn fulfill_implication<'a, 'tcx>(infcx: &InferCtxt<'a, 'tcx>,
191+
source_trait_ref: ty::TraitRef<'tcx>,
192+
target_impl: DefId)
193+
-> Result<Substs<'tcx>, ()>
194+
{
195+
infcx.probe(|_| {
196+
let selcx = &mut build_selcx(&infcx).project_topmost().build();
197+
let target_substs = fresh_type_vars_for_impl(&infcx, DUMMY_SP, target_impl);
198+
let (target_trait_ref, obligations) = impl_trait_ref_and_oblig(selcx,
199+
target_impl,
200+
&target_substs);
201+
202+
// do the impls unify? If not, no specialization.
203+
if let Err(_) = infer::mk_eq_trait_refs(&infcx,
204+
true,
205+
TypeOrigin::Misc(DUMMY_SP),
206+
source_trait_ref,
207+
target_trait_ref) {
208+
debug!("fulfill_implication: {:?} does not unify with {:?}",
209+
source_trait_ref,
210+
target_trait_ref);
211+
return Err(());
212+
}
247213

248-
// attempt to prove all of the predicates for impl2 given those for impl1
249-
// (which are packed up in penv)
214+
// attempt to prove all of the predicates for impl2 given those for impl1
215+
// (which are packed up in penv)
250216

251-
for oblig in impl2_obligations.into_iter() {
252-
fulfill_cx.register_predicate_obligation(&infcx, oblig);
253-
}
217+
let mut fulfill_cx = FulfillmentContext::new();
218+
for oblig in obligations.into_iter() {
219+
fulfill_cx.register_predicate_obligation(&infcx, oblig);
220+
}
254221

255-
if let Err(errors) = infer::drain_fulfillment_cx(&infcx, &mut fulfill_cx, &()) {
256-
// no dice!
257-
debug!("fulfill_implication: for impls on {:?} and {:?}, could not fulfill: {:?} given \
258-
{:?}",
259-
impl1_trait_ref,
260-
impl2_trait_ref,
261-
errors,
262-
infcx.parameter_environment.caller_bounds);
263-
false
264-
} else {
265-
debug!("fulfill_implication: an impl for {:?} specializes {:?} (`where` clauses elided)",
266-
impl1_trait_ref,
267-
impl2_trait_ref);
268-
true
269-
}
222+
if let Err(errors) = infer::drain_fulfillment_cx(&infcx, &mut fulfill_cx, &()) {
223+
// no dice!
224+
debug!("fulfill_implication: for impls on {:?} and {:?}, could not fulfill: {:?} \
225+
given {:?}",
226+
source_trait_ref,
227+
target_trait_ref,
228+
errors,
229+
infcx.parameter_environment.caller_bounds);
230+
Err(())
231+
} else {
232+
debug!("fulfill_implication: an impl for {:?} specializes {:?} (`where` clauses \
233+
elided)",
234+
source_trait_ref,
235+
target_trait_ref);
236+
237+
// Now resolve the *substitution* we built for the target earlier, replacing
238+
// the inference variables inside with whatever we got from fulfillment.
239+
240+
// TODO: should this use `fully_resolve` instead?
241+
Ok(infcx.resolve_type_vars_if_possible(&target_substs))
242+
}
243+
})
270244
}

src/librustc/middle/traits/specialize/specialization_graph.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ impl Graph {
105105
} else if ge && !le {
106106
// possible_sibling specializes the impl
107107
*slot = impl_def_id;
108+
self.parent.insert(impl_def_id, parent);
108109
self.parent.insert(possible_sibling, impl_def_id);
109110
// we have to defer the insertion, because we can't
110111
// relinquish the borrow of `self.children`

0 commit comments

Comments
 (0)