| # The solver | |
| Also consider reading the documentation for [the recursive solver in chalk][chalk] | |
| as it is very similar to this implementation and also talks about limitations of this approach. | |
| [chalk]: https://rust-lang.github.io/chalk/book/recursive.html | |
| ## A rough walkthrough | |
| The entry-point of the solver is `InferCtxtEvalExt::evaluate_root_goal`. | |
| This function sets up the root `EvalCtxt` and then calls `EvalCtxt::evaluate_goal`, | |
| to actually enter the trait solver. | |
| `EvalCtxt::evaluate_goal` handles [canonicalization](./canonicalization.md), caching, | |
| overflow, and solver cycles. | |
| Once that is done, it creates a nested `EvalCtxt` with a | |
| separate local `InferCtxt` and calls `EvalCtxt::compute_goal`, which is responsible for the | |
| 'actual solver behavior'. | |
| We match on the `PredicateKind`, delegating to a separate function for each one. | |
| For trait goals, such a `Vec<T>: Clone`, `EvalCtxt::compute_trait_goal` has | |
| to collect all the possible ways this goal can be proven via | |
| `EvalCtxt::assemble_and_evaluate_candidates`. | |
| Each candidate is handled in a separate "probe", | |
| to not leak inference constraints to the other candidates. | |
| We then try to merge the assembled candidates via `EvalCtxt::merge_candidates`. | |
| ## Important concepts and design patterns | |
| ### `EvalCtxt::add_goal` | |
| To prove nested goals, we don't directly call `EvalCtxt::compute_goal`, but instead | |
| add the goal to the `EvalCtxt` with `EvalCtxt::add_goal`. | |
| We then prove all nested goals together in either `EvalCtxt::try_evaluate_added_goals` or | |
| `EvalCtxt::evaluate_added_goals_and_make_canonical_response`. | |
| This allows us to handle inference constraints from later goals. | |
| E.g. if we have both `?x: Debug` and `(): ConstrainToU8<?x>` as nested goals, | |
| then proving `?x: Debug` is initially ambiguous, but after proving `(): ConstrainToU8<?x>` | |
| we constrain `?x` to `u8` and proving `u8: Debug` succeeds. | |
| ### Matching on `TyKind` | |
| We lazily normalize types in the solver, so we always have to assume that any types | |
| and constants are potentially unnormalized. | |
| This means that matching on `TyKind` can easily be incorrect. | |
| We handle normalization in two different ways. | |
| When proving `Trait` goals when normalizing associated types, | |
| we separately assemble candidates depending on whether they structurally | |
| match the self type. | |
| Candidates which match on the self type are handled in | |
| `EvalCtxt::assemble_candidates_via_self_ty` which recurses via | |
| `EvalCtxt::assemble_candidates_after_normalizing_self_ty`, | |
| which normalizes the self type by one level. | |
| In all other cases where we have to match on a `TyKind`, | |
| we first use `EvalCtxt::try_normalize_ty` to normalize the type as much as possible. | |
| ### Higher ranked goals | |
| In case the goal is higher-ranked, e.g. `for<'a> F: FnOnce(&'a ())`, `EvalCtxt::compute_goal` | |
| eagerly instantiates `'a` with a placeholder and then recursively proves | |
| `F: FnOnce(&'!a ())` as a nested goal. | |
| ### Dealing with choice | |
| Some goals can be proven in multiple ways. | |
| In these cases, | |
| we try each option in a separate "probe", | |
| and then attempt to merge the resulting responses by using `EvalCtxt::try_merge_responses`. | |
| If merging the responses fails, we use `EvalCtxt::flounder` instead, returning ambiguity. | |
| For some goals, | |
| we try to incompletely prefer some choices over others | |
| in case `EvalCtxt::try_merge_responses` fails. | |
| ## Learning more | |
| The solver should be fairly self-contained. | |
| I hope that the above information provides a good foundation when looking at the code itself. | |
| Please reach out on Zulip if you get stuck | |
| while doing so or there are some quirks and design decisions which were unclear and deserve | |
| better comments or should be mentioned here. |