Skip to content

Commit b74208b

Browse files
committed
auto merge of #17669 : nikomatsakis/rust/multidispatch, r=pcwalton
Implement multidispatch and conditional dispatch. Because we do not attempt to preserve crate concatenation, this is a backwards compatible change. This is not yet fully integrated into method dispatch, so "UFCS"-style wrappers must be used to take advantage of the new features (see the run-pass tests). cc #17307 (multidispatch) cc #5527 (trait reform -- conditional dispatch) Because we no longer preserve crate concatenability, this deviates slightly from what was specified in the RFC. The motivation for this change is described in [this blog post](http://smallcultfollowing.com/babysteps/blog/2014/09/30/multi-and-conditional-dispatch-in-traits/). I will post an amendment to the RFC in due course but do not anticipate great controversy on this point -- particularly as the RFCs more important features (e.g., conditional dispatch) just don't work without the change.
2 parents f9fc49c + 7a07f2a commit b74208b

35 files changed

+1602
-770
lines changed

src/librustc/middle/subst.rs

+4
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,10 @@ impl<T> VecPerParamSpace<T> {
417417
self.content.iter()
418418
}
419419

420+
pub fn as_slice(&self) -> &[T] {
421+
self.content.as_slice()
422+
}
423+
420424
pub fn all_vecs(&self, pred: |&[T]| -> bool) -> bool {
421425
let spaces = [TypeSpace, SelfSpace, FnSpace];
422426
spaces.iter().all(|&space| { pred(self.get_slice(space)) })

src/librustc/middle/traits/coherence.rs

+13-10
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@
1010

1111
/*! See `doc.rs` for high-level documentation */
1212

13-
use super::{EvaluatedToMatch, EvaluatedToAmbiguity, EvaluatedToUnmatch};
14-
use super::{evaluate_impl};
15-
use super::ObligationCause;
13+
use super::SelectionContext;
14+
use super::Obligation;
1615
use super::util;
1716

1817
use middle::subst;
@@ -28,22 +27,26 @@ pub fn impl_can_satisfy(infcx: &InferCtxt,
2827
impl2_def_id: ast::DefId)
2928
-> bool
3029
{
30+
debug!("impl_can_satisfy(\
31+
impl1_def_id={}, \
32+
impl2_def_id={})",
33+
impl1_def_id.repr(infcx.tcx),
34+
impl2_def_id.repr(infcx.tcx));
35+
3136
// `impl1` provides an implementation of `Foo<X,Y> for Z`.
3237
let impl1_substs =
3338
util::fresh_substs_for_impl(infcx, DUMMY_SP, impl1_def_id);
34-
let impl1_self_ty =
39+
let impl1_trait_ref =
3540
ty::impl_trait_ref(infcx.tcx, impl1_def_id).unwrap()
36-
.self_ty()
3741
.subst(infcx.tcx, &impl1_substs);
3842

3943
// Determine whether `impl2` can provide an implementation for those
4044
// same types.
4145
let param_env = ty::empty_parameter_environment();
42-
match evaluate_impl(infcx, &param_env, infcx.tcx, ObligationCause::dummy(),
43-
impl2_def_id, impl1_self_ty) {
44-
EvaluatedToMatch | EvaluatedToAmbiguity => true,
45-
EvaluatedToUnmatch => false,
46-
}
46+
let mut selcx = SelectionContext::new(infcx, &param_env, infcx.tcx);
47+
let obligation = Obligation::misc(DUMMY_SP, impl1_trait_ref);
48+
debug!("impl_can_satisfy obligation={}", obligation.repr(infcx.tcx));
49+
selcx.evaluate_impl(impl2_def_id, &obligation)
4750
}
4851

4952
pub fn impl_is_local(tcx: &ty::ctxt,

src/librustc/middle/traits/doc.rs

+87-80
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ Trait resolution consists of three major parts:
5757
resolved by employing an impl which matches the self type, or by
5858
using a parameter bound. In the case of an impl, Selecting one
5959
obligation can create *nested obligations* because of where clauses
60-
on the impl itself.
60+
on the impl itself. It may also require evaluating those nested
61+
obligations to resolve ambiguities.
6162
6263
- FULFILLMENT: The fulfillment code is what tracks that obligations
6364
are completely fulfilled. Basically it is a worklist of obligations
@@ -100,80 +101,88 @@ candidate that is definitively applicable. In some cases, we may not
100101
know whether an impl/where-clause applies or not -- this occurs when
101102
the obligation contains unbound inference variables.
102103
103-
One important point is that candidate assembly considers *only the
104-
input types* of the obligation when deciding whether an impl applies
105-
or not. Consider the following example:
106-
107-
trait Convert<T> { // T is output, Self is input
108-
fn convert(&self) -> T;
109-
}
110-
111-
impl Convert<uint> for int { ... }
112-
113-
Now assume we have an obligation `int : Convert<char>`. During
114-
candidate assembly, the impl above would be considered a definitively
115-
applicable candidate, because it has the same self type (`int`). The
116-
fact that the output type parameter `T` is `uint` on the impl and
117-
`char` in the obligation is not considered.
118-
119-
#### Skolemization
120-
121-
We (at least currently) wish to guarantee "crate concatenability" --
122-
which basically means that you could take two crates, concatenate
123-
them textually, and the combined crate would continue to compile. The
124-
only real way that this relates to trait matching is with
125-
inference. We have to be careful not to influence unbound type
126-
variables during the selection process, basically.
127-
128-
Here is an example:
129-
130-
trait Foo { fn method() { ... }}
131-
impl Foo for int { ... }
132-
133-
fn something() {
134-
let mut x = None; // `x` has type `Option<?>`
135-
loop {
136-
match x {
137-
Some(ref y) => { // `y` has type ?
138-
y.method(); // (*)
139-
...
140-
}}}
141-
}
142-
143-
The question is, can we resolve the call to `y.method()`? We don't yet
144-
know what type `y` has. However, there is only one impl in scope, and
145-
it is for `int`, so perhaps we could deduce that `y` *must* have type
146-
`int` (and hence the type of `x` is `Option<int>`)? This is actually
147-
sound reasoning: `int` is the only type in scope that could possibly
148-
make this program type check. However, this deduction is a bit
149-
"unstable", though, because if we concatenated with another crate that
150-
defined a newtype and implemented `Foo` for this newtype, then the
151-
inference would fail, because there would be two potential impls, not
152-
one.
153-
154-
It is unclear how important this property is. It might be nice to drop it.
155-
But for the time being we maintain it.
156-
157-
The way we do this is by *skolemizing* the obligation self type during
158-
the selection process -- skolemizing means, basically, replacing all
159-
unbound type variables with a new "skolemized" type. Each skolemized
160-
type is basically considered "as if" it were some fresh type that is
161-
distinct from all other types. The skolemization process also replaces
162-
lifetimes with `'static`, see the section on lifetimes below for an
163-
explanation.
164-
165-
In the example above, this means that when matching `y.method()` we
166-
would convert the type of `y` from a type variable `?` to a skolemized
167-
type `X`. Then, since `X` cannot unify with `int`, the match would
168-
fail. Special code exists to check that the match failed because a
169-
skolemized type could not be unified with another kind of type -- this is
170-
not considered a definitive failure, but rather an ambiguous result,
171-
since if the type variable were later to be unified with int, then this
172-
obligation could be resolved then.
173-
174-
*Note:* Currently, method matching does not use the trait resolution
175-
code, so if you in fact type in the example above, it may
176-
compile. Hopefully this will be fixed in later patches.
104+
The basic idea for candidate assembly is to do a first pass in which
105+
we identify all possible candidates. During this pass, all that we do
106+
is try and unify the type parameters. (In particular, we ignore any
107+
nested where clauses.) Presuming that this unification succeeds, the
108+
impl is added as a candidate.
109+
110+
Once this first pass is done, we can examine the set of candidates. If
111+
it is a singleton set, then we are done: this is the only impl in
112+
scope that could possibly apply. Otherwise, we can winnow down the set
113+
of candidates by using where clauses and other conditions. If this
114+
reduced set yields a single, unambiguous entry, we're good to go,
115+
otherwise the result is considered ambiguous.
116+
117+
#### The basic process: Inferring based on the impls we see
118+
119+
This process is easier if we work through some examples. Consider
120+
the following trait:
121+
122+
```
123+
trait Convert<Target> {
124+
fn convert(&self) -> Target;
125+
}
126+
```
127+
128+
This trait just has one method. It's about as simple as it gets. It
129+
converts from the (implicit) `Self` type to the `Target` type. If we
130+
wanted to permit conversion between `int` and `uint`, we might
131+
implement `Convert` like so:
132+
133+
```rust
134+
impl Convert<uint> for int { ... } // int -> uint
135+
impl Convert<int> for uint { ... } // uint -> uint
136+
```
137+
138+
Now imagine there is some code like the following:
139+
140+
```rust
141+
let x: int = ...;
142+
let y = x.convert();
143+
```
144+
145+
The call to convert will generate a trait reference `Convert<$Y> for
146+
int`, where `$Y` is the type variable representing the type of
147+
`y`. When we match this against the two impls we can see, we will find
148+
that only one remains: `Convert<uint> for int`. Therefore, we can
149+
select this impl, which will cause the type of `$Y` to be unified to
150+
`uint`. (Note that while assembling candidates, we do the initial
151+
unifications in a transaction, so that they don't affect one another.)
152+
153+
There are tests to this effect in src/test/run-pass:
154+
155+
traits-multidispatch-infer-convert-source-and-target.rs
156+
traits-multidispatch-infer-convert-target.rs
157+
158+
#### Winnowing: Resolving ambiguities
159+
160+
But what happens if there are multiple impls where all the types
161+
unify? Consider this example:
162+
163+
```rust
164+
trait Get {
165+
fn get(&self) -> Self;
166+
}
167+
168+
impl<T:Copy> Get for T {
169+
fn get(&self) -> T { *self }
170+
}
171+
172+
impl<T:Get> Get for Box<T> {
173+
fn get(&self) -> Box<T> { box get_it(&**self) }
174+
}
175+
```
176+
177+
What happens when we invoke `get_it(&box 1_u16)`, for example? In this
178+
case, the `Self` type is `Box<u16>` -- that unifies with both impls,
179+
because the first applies to all types, and the second to all
180+
boxes. In the olden days we'd have called this ambiguous. But what we
181+
do now is do a second *winnowing* pass that considers where clauses
182+
and attempts to remove candidates -- in this case, the first impl only
183+
applies if `Box<u16> : Copy`, which doesn't hold. After winnowing,
184+
then, we are left with just one candidate, so we can proceed. There is
185+
a test of this in `src/test/run-pass/traits-conditional-dispatch.rs`.
177186
178187
#### Matching
179188
@@ -187,11 +196,9 @@ consider some of the nested obligations, in the case of an impl.
187196
Because of how that lifetime inference works, it is not possible to
188197
give back immediate feedback as to whether a unification or subtype
189198
relationship between lifetimes holds or not. Therefore, lifetime
190-
matching is *not* considered during selection. This is achieved by
191-
having the skolemization process just replace *ALL* lifetimes with
192-
`'static`. Later, during confirmation, the non-skolemized self-type
193-
will be unified with the type from the impl (or whatever). This may
194-
yield lifetime constraints that will later be found to be in error (in
199+
matching is *not* considered during selection. This is reflected in
200+
the fact that subregion assignment is infallible. This may yield
201+
lifetime constraints that will later be found to be in error (in
195202
contrast, the non-lifetime-constraints have already been checked
196203
during selection and can never cause an error, though naturally they
197204
may lead to other errors downstream).

0 commit comments

Comments
 (0)