Skip to content

Commit 0ddce00

Browse files
committed
Document method-call expressions in pseudo-code as well as prose
1 parent 21a27e1 commit 0ddce00

File tree

1 file changed

+73
-14
lines changed

1 file changed

+73
-14
lines changed

src/expressions/method-call-expr.md

+73-14
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,81 @@ 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:
18+
The following procedure (described in pseudo-rust) is used:
1919

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`.
23-
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]`.
25-
26-
Then, for each candidate type `T`, search for a [visible] method with a receiver of that type in the following places:
20+
```rust
21+
fn lookup_method(mut T: Type, method_name: &str) -> Method {
22+
// The first step is to build a list of candidate receiver types.
23+
let mut candidate_receiver_types = vec![T];
24+
25+
// Obtain these by repeatedly dereferencing the receiver expression's
26+
// type, adding each type encountered to the list,
27+
while let Some(U) = <T as Deref>::Target {
28+
T = U;
29+
candidate_receiver_types.push(T);
30+
}
31+
32+
// then finally attempting an unsized coercion at the end, and adding the
33+
// result type if that is successful.
34+
if let Some(U) = T::UnsizedCoercion {
35+
candidate_receiver_types.push(U);
36+
}
37+
38+
// Then, for each candidate `T`, add `&T` and `&mut T` to the list
39+
// immediately after `T`.
40+
let candidate_receiver_types = candidate_receiver_types.map(|T| [T, &T, &mut T]).flatten();
41+
42+
// Then, for each candidate type `T`,
43+
for T in candidate_receiver_types {
44+
// search for a visible method with a receiver of that type
45+
let find_method = |methods: Map<&str, Method>| {
46+
methods.get(method_name).filter(|m| m.is_visible() && m.receiver == T)
47+
};
48+
49+
// in the following places:
50+
51+
// 1. `T`'s inherent methods (methods implemented directly on `T`).
52+
if let Some(method) = find_method(T.inherent_impl.methods) {
53+
return method;
54+
}
55+
56+
// 2. Any of the methods provided by a visible trait implemented by
57+
// `T`. If `T` is a type parameter, methods provided by trait
58+
// bounds on `T` are looked up first. Then all remaining methods in
59+
// scope are looked up.
60+
let mut prioritized_candidate_methods = vec![];
61+
let mut candidate_methods = vec![];
62+
for Trait in TRAITS.visible() {
63+
if let Some(TraitImpl) = T.implements(Trait) {
64+
if let Some(method) = find_method(TraitImpl.methods) {
65+
if T.is_type_parameter() && T.has_bounds_of(Trait) {
66+
prioritized_candidate_methods.push(method);
67+
} else {
68+
candidate_methods.push(method);
69+
}
70+
}
71+
}
72+
}
73+
// If this results in multiple possible candidates, then it is an error,
74+
// and the receiver must be [converted][disambiguate call] to an
75+
// appropriate receiver type to make the method call.
76+
match prioritized_candidate_methods {
77+
[] => {}, // Continue
78+
[method] => return method,
79+
_ => panic!("multiple applicable items in scope"),
80+
}
81+
match candidate_methods {
82+
[] => {}, // Continue
83+
[method] => return method,
84+
_ => panic!("multiple applicable items in scope"),
85+
}
86+
}
87+
88+
panic!("no method named `{method_name}` found in the current scope")
89+
}
90+
```
2791

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.
92+
As an example, 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]`.
3293

3394
> Note: the lookup is done for each type in order, which can occasionally lead to surprising results.
3495
> 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.
@@ -58,8 +119,6 @@ Then, for each candidate type `T`, search for a [visible] method with a receiver
58119
> }
59120
> ```
60121
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-
63122
This process does not take into account the mutability or lifetime of the receiver, or whether a method is `unsafe`.
64123
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.
65124

0 commit comments

Comments
 (0)