Skip to content

Commit f21f094

Browse files
committed
Arbitrary self types v2: dev guide updates.
1 parent 1eac2a9 commit f21f094

File tree

1 file changed

+101
-12
lines changed

1 file changed

+101
-12
lines changed

src/method-lookup.md

+101-12
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,39 @@ inference variables or other information.
4242
The first thing that the probe phase does is to create a series of
4343
*steps*. This is done by progressively dereferencing the receiver type
4444
until it cannot be deref'd anymore, as well as applying an optional
45-
"unsize" step. So if the receiver has type `Rc<Box<[T; 3]>>`, this
45+
"unsize" step. This "dereferencing" in fact uses the `Receiver` trait
46+
rather than the normal `Deref` trait. There's a blanket implementation
47+
of `Receiver` for `T: Deref` so the answer is often the same.
48+
49+
So if the receiver has type `Rc<Box<[T; 3]>>`, this
4650
might yield:
4751

48-
1. `Rc<Box<[T; 3]>>`
49-
2. `Box<[T; 3]>`
50-
3. `[T; 3]`
51-
4. `[T]`
52+
1. `Rc<Box<[T; 3]>>` *
53+
2. `Box<[T; 3]>` *
54+
3. `[T; 3]` *
55+
4. `[T]` *
56+
57+
Some types might implement `Receiver` but not `Deref`. Imagine that
58+
`SmartPtr<T>` does this. If the receiver has type `&Rc<SmartPtr<T>>`
59+
the steps would be:
60+
61+
1. `&Rc<SmartPtr<T>>` *
62+
2. `Rc<SmartPtr<T>>` *
63+
3. `SmartPtr<T>` *
64+
4. `T`
65+
66+
The first three of those steps, marked with a *, can be reached using
67+
`Deref` as well as by `Receiver`. This fact is recorded against each step.
5268

5369
### Candidate assembly
5470

55-
We then search along those steps to create a list of *candidates*. A
56-
`Candidate` is a method item that might plausibly be the method being
57-
invoked. For each candidate, we'll derive a "transformed self type"
58-
that takes into account explicit self.
71+
We then search along these candidate steps to create a list of
72+
*candidates*. A `Candidate` is a method item that might plausibly be the
73+
method being invoked. For each candidate, we'll derive a "transformed self
74+
type" that takes into account explicit self.
75+
76+
At this point, we consider the whole list - all the steps reachable via
77+
`Receiver`, not just the shorter list reachable via `Deref`.
5978

6079
Candidates are grouped into two kinds, inherent and extension.
6180

@@ -95,9 +114,14 @@ might have two candidates:
95114

96115
### Candidate search
97116

98-
Finally, to actually pick the method, we will search down the steps,
99-
trying to match the receiver type against the candidate types. At
100-
each step, we also consider an auto-ref and auto-mut-ref to see whether
117+
Finally, to actually pick the method, we will search down the steps again,
118+
trying to match the receiver type against the candidate types. This time,
119+
we consider only the steps which can be reached via `Deref`, since we
120+
actually need to convert the receiver type to match the `self` type.
121+
In the examples above, that means we consider only the steps marked with
122+
an asterisk.
123+
124+
At each step, we also consider an auto-ref and auto-mut-ref to see whether
101125
that makes any of the candidates match. For each resulting receiver
102126
type, we consider inherent candidates before extension candidates.
103127
If there are multiple matching candidates in a group, we report an
@@ -111,3 +135,68 @@ recursively consider all where-clauses that appear on the impl: if
111135
those match (or we cannot rule out that they do), then this is the
112136
method we would pick. Otherwise, we would continue down the series of
113137
steps.
138+
139+
### `Deref` vs `Receiver`
140+
141+
Why have longer and shorter lists here? The use-case is smart pointers.
142+
For example:
143+
144+
```
145+
struct Inner;
146+
147+
// Assume this cannot implement Deref for some reason, e.g. because
148+
// we know other code may be accessing T and it's not safe to make
149+
// a reference to it
150+
struct Ptr<T>;
151+
152+
impl<T> Receiver for Ptr<T> {
153+
type Target = T;
154+
}
155+
156+
impl Inner {
157+
fn method1(self: &Ptr<Self>) {
158+
}
159+
160+
fn method2(&self) {}
161+
}
162+
163+
fn main() {
164+
let ptr = Ptr(Inner);
165+
ptr.method1();
166+
// ptr.method2();
167+
}
168+
```
169+
170+
In this case, the step list for the `method1` call would be:
171+
172+
1. `Ptr<Inner>` *
173+
2. `Inner`
174+
175+
Because the list of types reached via `Receiver` includes `Inner`, we can
176+
look for methods in the `impl Inner` block during candidate search.
177+
But, we can't dereference a `&Receiver` to make a `&Inner`, so the picking
178+
process won't allow us to call `method2` on a `Ptr<Inner>`.
179+
180+
### Deshadowing
181+
182+
Once we've made a pick, code in `pick_all_method` also checks for a couple
183+
of cases where one method may shadow another. That is, in the code example
184+
above, imagine there also exists:
185+
186+
```
187+
impl Inner {
188+
fn method3(self: &Ptr<Self>) {}
189+
}
190+
191+
impl<T> Ptr<T> {
192+
fn method3(self) {}
193+
}
194+
```
195+
196+
These can both be called using `ptr.method3()`. Without special care, we'd
197+
automatically use `Ptr::self` because we pick by value before even looking
198+
at by-reference candidates. This could be a problem if the caller previously
199+
was using `Inner::method3`: they'd get an unexpected behavior change.
200+
So, if we pick a by-value candidate we'll check to see if we might be
201+
shadowing a by-value candidate, and error if so. The same applies
202+
if a by-mut-ref candidate shadows a by-reference candidate.

0 commit comments

Comments
 (0)