|
1 | 1 | # Canonicalization
|
2 | 2 |
|
3 |
| -While the exact approach to canonicalization for this solver will differ slightly |
4 |
| -wrt to lifetimes, please visit [the relevant chalk chapter][chalk] for now. |
| 3 | +Canonicalization is the process of *isolating* a value from its context and is necessary |
| 4 | +for global caching of goals which include inference variables. |
5 | 5 |
|
6 |
| -<!-- date-check: jan 2023 --> |
7 |
| -As of 10 January 2023, canonicalization is not yet fully implemented |
8 |
| -in the new solver. |
| 6 | +The idea is that given the goals `u32: Trait<?x>` and `u32: Trait<?y>`, where `?x` and `?y` |
| 7 | +are two different currently unconstrained inference variables, we should get the same result |
| 8 | +for both goals. We can therefore prove *the canonical query* `exists<T> u32: Trait<T>` once |
| 9 | +and reuse the result. |
9 | 10 |
|
10 |
| -[chalk]: https://rust-lang.github.io/chalk/book/canonical_queries/canonicalization.html#canonicalization |
| 11 | +Let's first go over the way canonical queries work and then dive into the specifics of |
| 12 | +how canonicalization works. |
| 13 | + |
| 14 | +## A walkthrough of canonical queries |
| 15 | + |
| 16 | +To make this a bit easier, let's use the trait goal `u32: Trait<?x>` as an example with the |
| 17 | +assumption that the only relevant impl is `impl<T> Trait<Vec<T>> for u32`. |
| 18 | + |
| 19 | +### Canonicalizing the input |
| 20 | + |
| 21 | +We start by *canonicalizing* the goal, replacing inference variables with existential and |
| 22 | +placeholders with universal bound variables. This would result in the *canonical goal* |
| 23 | +`exists<T> u32: Trait<T>`. |
| 24 | + |
| 25 | +We remember the original values of all bound variables in the original context. Here this would |
| 26 | +map `T` back to `?x`. These original values are used later on when dealing with the query |
| 27 | +response. |
| 28 | + |
| 29 | +We now call the canonical query with the canonical goal. |
| 30 | + |
| 31 | +### Instantiating the canonical goal inside of the query |
| 32 | + |
| 33 | +To actually try to prove the canonical goal we start by instantiating the bound variables with |
| 34 | +inference variables and placeholders again. |
| 35 | + |
| 36 | +This happens inside of the query in a completely separate `InferCtxt`. Inside of the query we |
| 37 | +now have a goal `u32: Trait<?0>`. We also remember which value we've used to instantiate the bound |
| 38 | +variables in the canonical goal, which maps `T` to `?0`. |
| 39 | + |
| 40 | +We now compute the goal `u32: Trait<?0>` and figure out that this holds, but we've constrained |
| 41 | +`?0` to `Vec<?1>`. We finally convert this result to something useful to the caller. |
| 42 | + |
| 43 | +### Canonicalizing the query response |
| 44 | + |
| 45 | +We have to return to the caller both whether the goal holds, and the inference constraints |
| 46 | +from inside of the query. |
| 47 | + |
| 48 | +To return the inference results to the caller we canonicalize the mapping from bound variables |
| 49 | +to the instantiated values in the query. This means that the query response is `Certainty::Yes` |
| 50 | +and a mapping from `T` to `exists<U> Vec<U>`. |
| 51 | + |
| 52 | +### Instantiating the query response |
| 53 | + |
| 54 | +The caller now has to apply the constraints returned by the query. For this they first |
| 55 | +instantiate the bound variables of the canonical response with inference variables and |
| 56 | +placeholders again, so the mapping in the response is now from `T` to `Vec<?z>`. |
| 57 | + |
| 58 | +It now equates the original value of `T` (`?x`) with the value for `T` in the |
| 59 | +response (`Vec<?z>`), which correctly constrains `?x` to `Vec<?z>`. |
| 60 | + |
| 61 | +## `ExternalConstraints` |
| 62 | + |
| 63 | +Computing a trait goal may not only constrain inference variables, it can also add region |
| 64 | +obligations, e.g. given a goal `(): AOutlivesB<'a, 'b>` we would like to return the fact that |
| 65 | +`'a: 'b` has to hold. |
| 66 | + |
| 67 | +This is done by not only returning the mapping from bound variables to the instantiated values |
| 68 | +from the query but also extracting additional `ExternalConstraints` from the `InferCtxt` context |
| 69 | +while building the response. |
| 70 | + |
| 71 | +## How exactly does canonicalization work |
| 72 | + |
| 73 | +TODO: link to code once the PR lands and elaborate |
| 74 | + |
| 75 | +- types and consts: infer to existentially bound var, placeholder to universally bound var, |
| 76 | + considering universes |
| 77 | +- generic parameters in the input get treated as placeholders in the root universe |
| 78 | +- all regions in the input get all mapped to existentially bound vars and we "uniquify" them. |
| 79 | + `&'a (): Trait<'a>` gets canonicalized to `exists<'0, '1> &'0 (): Trait<'1>`. We do not care |
| 80 | + about their universes and simply put all regions into the highest universe of the input. |
| 81 | +- once we collected all canonical vars we compress their universes, see comment in `finalize`. |
| 82 | +- in the output everything in a universe of the caller gets put into the root universe and only |
| 83 | + gets its correct universe when we unify the var values with the orig values of the caller |
| 84 | +- we do not uniquify regions in the response and don't canonicalize `'static` |
0 commit comments