Skip to content

Commit d3bee3c

Browse files
authored
new solver: write canonicalization chapter (rust-lang#1595)
* write canonicalization chapter first half * w * review * very good section * whatever * review * ok
1 parent 54fe64b commit d3bee3c

File tree

1 file changed

+80
-6
lines changed

1 file changed

+80
-6
lines changed

Diff for: src/solve/canonicalization.md

+80-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,84 @@
11
# Canonicalization
22

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.
55

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.
910

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

Comments
 (0)