Skip to content

Commit b73a724

Browse files
committed
Method call reference: major rewrite
This section of the reference has been oversimplistic for some time (rust-lang#1018 and rust-lang#1534) and various rewrites have been attempted (e.g. rust-lang#1394, rust-lang#1432). Here's another attempt! My approach here is: * Stop trying to keep it short and concise * Document what actually happens in the code, step by step This does result in a long explanation, because we're trying to document nearly 2400 lines of code in `probe.rs`, but doing otherwise feels as though we'll continue to run into criticisms of oversimplification. This rewrite documents the post-arbitrary-self-types v2 situation, i.e. it assumes rust-lang/rust#135881 has landed. We should not merge this until or unless that lands. This PR was inspired by discussion in rust-lang#1699. If we go ahead with this approach, rust-lang#1699 becomes irrelevant. There was also discussion at rust-lang/cargo#15117 (comment)
1 parent 293af99 commit b73a724

File tree

1 file changed

+144
-19
lines changed

1 file changed

+144
-19
lines changed

src/expressions/method-call-expr.md

+144-19
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,155 @@ let log_pi = pi.unwrap_or(1.0).log(2.72);
1515

1616
When looking up a method call, the receiver may be automatically dereferenced or borrowed in order to call a method.
1717
This requires a more complex lookup process than for other functions, since there may be a number of possible methods to call.
18-
The following procedure is used:
1918

20-
The first step is to build a list of candidate receiver types.
21-
Obtain these by repeatedly [dereferencing][dereference] the receiver expression's type, adding each type encountered to the list, then finally attempting an [unsized coercion] at the end, and adding the result type if that is successful.
22-
Then, for each candidate `T`, add `&T` and `&mut T` to the list immediately after `T`.
19+
The following procedure is used:
2320

24-
For instance, if the receiver has type `Box<[i32;2]>`, then the candidate types will be `Box<[i32;2]>`, `&Box<[i32;2]>`, `&mut Box<[i32;2]>`, `[i32; 2]` (by dereferencing), `&[i32; 2]`, `&mut [i32; 2]`, `[i32]` (by unsized coercion), `&[i32]`, and finally `&mut [i32]`.
21+
## Determining candidate types
22+
23+
First, a list of "candidate types" is assembled.
24+
25+
These types are found by taking the receiver type and iterating, following either:
26+
27+
* The built-in [dereference]; or
28+
* `<T as Receiver>::Target`
29+
30+
to the next type. (If a step involved following the `Receiver` target, we also
31+
note whether it would have been reachable by following `<T as Deref::Target>`
32+
- this information is used later).
33+
34+
At the end, an additional candidate step may be added for
35+
an [unsized coercion].
36+
37+
Each step of this chain provides a possible `self` type for methods that
38+
might be called. The list will be used in two different ways:
39+
40+
* To find types that might have methods. This is used in the
41+
"determining candidate methods" step, described below. This considers
42+
the full list.
43+
* To find types to which the receiver can be converted. This is used in the
44+
"picking a method from the candidates" step, also described below - in this
45+
case, we only consider the types reachable via `Deref` or built-in
46+
dereferencing.
47+
48+
There is a built-in implementation of `Receiver` for all `T: Deref`, so
49+
most of the time, every step can be reached through either mechanism.
50+
Sometimes, more types can be reached via the `Receiver` chain, and so
51+
more types will be considered for the former usage than the latter usage.
52+
53+
For instance, if the receiver has type `Box<[i32;2]>`, then the candidate types
54+
will be `Box<[i32;2]>`,`[i32; 2]` (by dereferencing), and `[i32]` (by unsized
55+
coercion).
56+
57+
If `SmartPtr<T>: Receiver<Target=T>`, and the receiver type is `&SmartPtr<Foo>`,
58+
then the candidate types would be `&SmartPtr<Foo>`, `SmartPtr<Foo>` and `Foo`.
59+
60+
## Determining candidate methods
61+
62+
This list of candidate types is then converted to a list of candidate methods.
63+
For each step, the `self` type is used to determine what searches to perform:
64+
65+
* For a trait object, there is first a search for for inherent candidates for
66+
the object, then inherent impl candidates for the type.
67+
* For a struct, enum, or foreign type, there is a search for inherent
68+
impl candidates for the type.
69+
* For a type param, there's a search for for inherent candidates on the param.
70+
* For other tyings (e.g. bools, chars) there's a search for inherent candidates
71+
for the incoherent type.
72+
* After any of these, there's a further search for extension candidates for
73+
traits in scope.
74+
75+
These searches contribute to list of all the candidate methods found;
76+
separate lists are maintained for inherent and extension candidates. Only
77+
[visible] candidates are included.
78+
79+
(For diagnostic purposes, the search may be performed slightly differently, for
80+
instance searching all traits not just those in scope, or also noting
81+
inaccessible candidates.)
82+
83+
## Picking a method from the candidates
84+
85+
Once the list of candidate methods is assembled, the "picking" process
86+
starts.
87+
88+
Once again, the candidate types are iterated. This time, only those types
89+
are iterated which can be reached via the `Deref` trait or built-in derefs;
90+
as noted above, this may be a shorter list than those that can be reached
91+
using the `Receiver` trait.
92+
93+
For each step, picking is attempted in this order:
94+
95+
* First, a by-value method, where the `self` type precisely matches
96+
* First for inherent methods
97+
* Then for extension methods
98+
* Then, a method where `self` is received by immutable reference (`&T`)
99+
* First for inherent methods
100+
* Then for extension methods
101+
* Then, a method where `self` is received by mutable reference (`&mut T`)
102+
* First for inherent methods
103+
* Then for extension methods
104+
* Then, a method where the `self` type is a `*const T` - this is only considered
105+
if the self type is `*mut T`)
106+
* First for inherent methods
107+
* Then for extension methods
108+
* And finally, a method with a `Pin` that's reborrowed, if the `pin_ergonomics`
109+
feature is enabled.
110+
* First for inherent methods
111+
* Then for extension methods
112+
113+
For each of those searches, if exactly one candidate is identified,
114+
it's picked, and the search stops. If this results in multiple possible candidates,
115+
then it is an error, and the receiver must be [converted][disambiguate call]
116+
to an appropriate receiver type to make the method call.
117+
118+
With the example above of `SmartPtr<T>: Receiver<Target=T>`, and the receiver
119+
type `&SmartPtr<Foo>`, this mechanism would pick:
120+
121+
```rust,ignore
122+
impl Foo {
123+
fn method(self: &SmartPtr<Foo>) {}
124+
}
125+
```
25126

26-
Then, for each candidate type `T`, search for a [visible] method with a receiver of that type in the following places:
127+
but would not pick
27128

28-
1. `T`'s inherent methods (methods implemented directly on `T`).
29-
1. Any of the methods provided by a [visible] trait implemented by `T`.
30-
If `T` is a type parameter, methods provided by trait bounds on `T` are looked up first.
31-
Then all remaining methods in scope are looked up.
129+
```rust,ignore
130+
impl Foo {
131+
fn method(self: &Foo) {}
132+
}
133+
```
32134

33-
> Note: the lookup is done for each type in order, which can occasionally lead to surprising results.
135+
because the receiver could not be converted to `&Foo` using the `Deref` chain,
136+
only the `Receiver` chain.
137+
138+
## Extra details
139+
140+
There are a few details not considered in this overview:
141+
142+
* The search for candidate methods will also consider searches for
143+
incoherent types if `rustc_has_incoherent_inherent_impls` is active for
144+
a `dyn`, struct, enum, or foreign type.
145+
* If there are multiple candidates from traits, they may in fact be
146+
identical, and the picking operation collapses them to a single pick to avoid
147+
reporting conflicts.
148+
* Extra searches are performed to spot "shadowing" of pointee methods
149+
by smart pointer methods, during the picking process. If a by-value pick
150+
is going to be returned, an extra search is performed for a `&T` or
151+
`&mut T` method. Similarly, if a `&T` method is to be returned, an extra
152+
search is performed for `&mut T` methods. These extra searches consider
153+
only inherent methods, where `T` is identical, but the method is
154+
found from a step further along the `Receiver` chain. If any such method
155+
is found, an ambiguity error is emitted.
156+
* An error is emitted if we reached a recursion limit.
157+
* The picking process emits some adjustments which must be made to the
158+
receiver type in order to get to the correct `self` type. This includes
159+
a number of dereferences, a possible autoreferencing, a conversion from
160+
a mutable pointer to a constant pointer, or a pin reborrow.
161+
* Extra lists are maintained for diagnostic purposes:
162+
unstable candidates, unsatisfied predicates, and static candidates.
163+
164+
## Net results
165+
166+
> The lookup is done for each type in order, which can occasionally lead to surprising results.
34167
> The below code will print "In trait impl!", because `&self` methods are looked up first, the trait method is found before the struct's `&mut self` method is found.
35168
>
36169
> ```rust
@@ -58,14 +191,6 @@ Then, for each candidate type `T`, search for a [visible] method with a receiver
58191
> }
59192
> ```
60193
61-
If this results in multiple possible candidates, then it is an error, and the receiver must be [converted][disambiguate call] to an appropriate receiver type to make the method call.
62-
63-
This process does not take into account the mutability or lifetime of the receiver, or whether a method is `unsafe`.
64-
Once a method is looked up, if it can't be called for one (or more) of those reasons, the result is a compiler error.
65-
66-
If a step is reached where there is more than one possible method, such as where generic methods or traits are considered the same, then it is a compiler error.
67-
These cases require a [disambiguating function call syntax] for method and function invocation.
68-
69194
> **Edition differences**: Before the 2021 edition, during the search for visible methods, if the candidate receiver type is an [array type], methods provided by the standard library [`IntoIterator`] trait are ignored.
70195
>
71196
> The edition used for this purpose is determined by the token representing the method name.

0 commit comments

Comments
 (0)