diff options
Diffstat (limited to 'src/doc/rustc-dev-guide/src/method-lookup.md')
| -rw-r--r-- | src/doc/rustc-dev-guide/src/method-lookup.md | 115 |
1 files changed, 115 insertions, 0 deletions
diff --git a/src/doc/rustc-dev-guide/src/method-lookup.md b/src/doc/rustc-dev-guide/src/method-lookup.md new file mode 100644 index 00000000000..c8d529a32b5 --- /dev/null +++ b/src/doc/rustc-dev-guide/src/method-lookup.md @@ -0,0 +1,115 @@ +# Method lookup + +Method lookup can be rather complex due to the interaction of a number +of factors, such as self types, autoderef, trait lookup, etc. This +file provides an overview of the process. More detailed notes are in +the code itself, naturally. + +One way to think of method lookup is that we convert an expression of +the form `receiver.method(...)` into a more explicit [fully-qualified syntax][] +(formerly called [UFCS][]): + +- `Trait::method(ADJ(receiver), ...)` for a trait call +- `ReceiverType::method(ADJ(receiver), ...)` for an inherent method call + +Here `ADJ` is some kind of adjustment, which is typically a series of +autoderefs and then possibly an autoref (e.g., `&**receiver`). However +we sometimes do other adjustments and coercions along the way, in +particular unsizing (e.g., converting from `[T; n]` to `[T]`). + +Method lookup is divided into two major phases: + +1. Probing ([`probe.rs`][probe]). The probe phase is when we decide what method + to call and how to adjust the receiver. +2. Confirmation ([`confirm.rs`][confirm]). The confirmation phase "applies" + this selection, updating the side-tables, unifying type variables, and + otherwise doing side-effectful things. + +One reason for this division is to be more amenable to caching. The +probe phase produces a "pick" (`probe::Pick`), which is designed to be +cacheable across method-call sites. Therefore, it does not include +inference variables or other information. + +[fully-qualified syntax]: https://doc.rust-lang.org/nightly/book/ch19-03-advanced-traits.html#fully-qualified-syntax-for-disambiguation-calling-methods-with-the-same-name +[UFCS]: https://github.com/rust-lang/rfcs/blob/master/text/0132-ufcs.md +[probe]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_typeck/method/probe/ +[confirm]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_typeck/method/confirm/ + +## The Probe phase + +### Steps + +The first thing that the probe phase does is to create a series of +*steps*. This is done by progressively dereferencing the receiver type +until it cannot be deref'd anymore, as well as applying an optional +"unsize" step. So if the receiver has type `Rc<Box<[T; 3]>>`, this +might yield: + +1. `Rc<Box<[T; 3]>>` +2. `Box<[T; 3]>` +3. `[T; 3]` +4. `[T]` + +### Candidate assembly + +We then search along those steps to create a list of *candidates*. A +`Candidate` is a method item that might plausibly be the method being +invoked. For each candidate, we'll derive a "transformed self type" +that takes into account explicit self. + +Candidates are grouped into two kinds, inherent and extension. + +**Inherent candidates** are those that are derived from the +type of the receiver itself. So, if you have a receiver of some +nominal type `Foo` (e.g., a struct), any methods defined within an +impl like `impl Foo` are inherent methods. Nothing needs to be +imported to use an inherent method, they are associated with the type +itself (note that inherent impls can only be defined in the same +crate as the type itself). + +<!-- +FIXME: Inherent candidates are not always derived from impls. If you +have a trait object, such as a value of type `Box<ToString>`, then the +trait methods (`to_string()`, in this case) are inherently associated +with it. Another case is type parameters, in which case the methods of +their bounds are inherent. However, this part of the rules is subject +to change: when DST's "impl Trait for Trait" is complete, trait object +dispatch could be subsumed into trait matching, and the type parameter +behavior should be reconsidered in light of where clauses. + +Is this still accurate? +--> + +**Extension candidates** are derived from imported traits. If I have +the trait `ToString` imported, and I call `to_string()` as a method, +then we will list the `to_string()` definition in each impl of +`ToString` as a candidate. These kinds of method calls are called +"extension methods". + +So, let's continue our example. Imagine that we were calling a method +`foo` with the receiver `Rc<Box<[T; 3]>>` and there is a trait `Foo` +that defines it with `&self` for the type `Rc<U>` as well as a method +on the type `Box` that defines `foo` but with `&mut self`. Then we +might have two candidates: + +- `&Rc<U>` as an extension candidate +- `&mut Box<U>` as an inherent candidate + +### Candidate search + +Finally, to actually pick the method, we will search down the steps, +trying to match the receiver type against the candidate types. At +each step, we also consider an auto-ref and auto-mut-ref to see whether +that makes any of the candidates match. For each resulting receiver +type, we consider inherent candidates before extension candidates. +If there are multiple matching candidates in a group, we report an +error, except that multiple impls of the same trait are treated as a +single match. Otherwise we pick the first match we find. + +In the case of our example, the first step is `Rc<Box<[T; 3]>>`, +which does not itself match any candidate. But when we autoref it, we +get the type `&Rc<Box<[T; 3]>>` which matches `&Rc<U>`. We would then +recursively consider all where-clauses that appear on the impl: if +those match (or we cannot rule out that they do), then this is the +method we would pick. Otherwise, we would continue down the series of +steps. |
