Skip to content

Commit 2f266b5

Browse files
committed
Arbitrary self types v2: dev guide updates.
1 parent 15929a3 commit 2f266b5

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

@@ -97,9 +116,14 @@ might have two candidates:
97116

98117
### Candidate search
99118

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

0 commit comments

Comments
 (0)