|
| 1 | +# Logic |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +`chalk-engine` solves a `Goal` using a hybrid search strategy with elements of depth- and breadth-first search. When asked to solve a |
| 6 | +particular `Goal` it hasn't seen before, it will first ask the [`Context`] to |
| 7 | +generate a set of program clauses, that get turned into [`Strand`]s, that could |
| 8 | +solve that goal. Otherwise, if asked to solve a `Goal` it has seen before, it |
| 9 | +will select the existing table. |
| 10 | + |
| 11 | +Once a table is selected, it will pick a `Strand` and a subgoal of that |
| 12 | +`Strand`, try to solve that `Goal`, repeating the process. |
| 13 | + |
| 14 | +When an `Answer` is found for a `Goal`, it is merged into the parent `Strand`, |
| 15 | +or returned if it was the root `Goal`. It will then go on to pick the next |
| 16 | +subgoal of the `Strand` and continue on. |
| 17 | + |
| 18 | +If at any point the solving stops being "successful" (i.e. we definitely found |
| 19 | +something to be unsolvable), the solving is restarted at the root `Goal`. |
| 20 | + |
| 21 | +## The stack |
| 22 | + |
| 23 | +In order to detect cycles (talked more about later), as well as keep track of |
| 24 | +the selected [`Strand`] for each table, `chalk-engine` stores a [`Stack`] on the |
| 25 | +`Forest`. Whenever a new goal is selected, a [`StackEntry`] is pushed onto the |
| 26 | +`Stack`, as well as the the "time" (which also gets incremented) that it was |
| 27 | +pushed. This "time" can be compared later to check if all the `Strands` of a |
| 28 | +[`Table`] have been checked in a single solve. |
| 29 | + |
| 30 | +As either `Answer`s are found for the selected `Table`, entries on the stack are |
| 31 | +`pop`ed. If something is found to be unsolvable, the complete stack is unwound. |
| 32 | + |
| 33 | +## Table creation |
| 34 | + |
| 35 | +As mentioned before, whenever a new `Goal` is encounted, a new [`Table`] is |
| 36 | +created to store current and future answers. First, the [`Goal`] is converted into |
| 37 | +an [`HhGoal`]. If it can be simplified, then a `Strand` with one or more |
| 38 | +subgoals will be generated and can be followed as above. Otherwise, if it is a |
| 39 | +`DomainGoal` (see above), then |
| 40 | +[`program_clauses`](https://rust-lang.github.io/chalk/chalk_engine/context/trait.ContextOps.html#tymethod.program_clauses) |
| 41 | +is called and each clause is converted into a `Strand` and can be followed. |
| 42 | + |
| 43 | +## `root_answer` and `ensure_root_answer` |
| 44 | + |
| 45 | +The [`root_answer`](https://rust-lang.github.io/chalk/chalk_engine/forest/struct.Forest.html#method.root_answer) function is the entry point to solve a `Goal`. Up until now, |
| 46 | +the idea of `Answer` versus `CompleteAnswer` have been ignored. However, in |
| 47 | +reality `Answer`s to `Goal`s may actually have delayed subgoals (see `ExClause` |
| 48 | +and [Coinduction and refinement strands]), whereas [`CompleteAnswer`]s may not. |
| 49 | +`root_answer` essentially just wraps [`ensure_root_answer`](https://rust-lang.github.io/chalk/chalk_engine/forest/struct.Forest.html#method.ensure_root_answer) and converts the |
| 50 | +`Goal`'s [`Answer`] to a [`CompleteAnswer`]. |
| 51 | + |
| 52 | +The [`ensure_root_answer`](https://rust-lang.github.io/chalk/chalk_engine/forest/struct.Forest.html#method.ensure_root_answer) function contains the core skeleton of the logic around |
| 53 | +`Strand` and subgoal selection. The majority of the logic, however, is split out |
| 54 | +into separate functions that branch out from `ensure_root_answer`. |
| 55 | + |
| 56 | +## Subgoal selection |
| 57 | + |
| 58 | +Once a given `Strand` for a table has been selected, a subgoal has to be |
| 59 | +selected. If there are no subgoals left, then there is nothing to do. Otherwise, |
| 60 | +if there are subgoals left, then a subgoal will attempt to be selected (from |
| 61 | +[`next_subgoal_index`](https://rust-lang.github.io/chalk/chalk_engine/context/trait.Context.html#tymethod.next_subgoal_index)). |
| 62 | +If the table for that subgoal had previously floundered (see next section), then |
| 63 | +we mark that subgoal as floundered and try the next subgoal. If all subgoals are |
| 64 | +marked as floundered, then this entire `Strand` is marked as floundered. If a |
| 65 | +subgoal is successfully selected, there is nothing left to do. |
| 66 | + |
| 67 | +## Floundering |
| 68 | + |
| 69 | +There a couple cases where we "give up" - here called floundering - on trying to |
| 70 | +solve a goal. The most easy to understand case is if the types for a `Goal` or |
| 71 | +`Answer` are too large. (Side note, we *could* actually handle this - by |
| 72 | +generalizing - but turns out to be quite buggy and probably unnecessary). |
| 73 | +Another case where we flounder is if we try to solve a `Goal` where we try to |
| 74 | +**enumerate** non-enumerable types (like auto traits). In general, floundering |
| 75 | +just means that we *can't* know any more answers about a `Goal`, for some |
| 76 | +reason. However, if there are other `Strands` that don't flounder, there may |
| 77 | +still be other `Answer`s available. |
| 78 | + |
| 79 | +## Answers |
| 80 | + |
| 81 | +After an answer has been found for a subgoal, it must be *applied* to the parent |
| 82 | +`Strand`. Specifically, it must be able to unify with any existing `Answers`. If |
| 83 | +the `Answer`s are incompatible, the `Strand` is dropped since it can't lead |
| 84 | +anywhere. |
| 85 | + |
| 86 | +## Cycles |
| 87 | + |
| 88 | +If while pursuing a `Goal`, the engine encounters the same `Table` twice, then a |
| 89 | +cycle has occured. If the cycle is not coinductive (see next), then there is |
| 90 | +nothing that can be gained from taking this route. We mark how far up the stack |
| 91 | +is in the cycle, and try the next `Strand`. If all `Strand`s for a table |
| 92 | +encounter a cycle, then we know that the current selected `Goal` has no more |
| 93 | +answers. |
| 94 | + |
| 95 | +## Coinduction and refinement strands |
| 96 | +[Coinduction and refinement strands]: #coinduction-and-refinement-strands |
| 97 | + |
| 98 | +Coinduction basically means that two statements can rely on each other being |
| 99 | +true, unless either is proven false. |
| 100 | + |
| 101 | +For example with the following program: |
| 102 | +```notrust |
| 103 | +#[coinductive] |
| 104 | +trait C1<T> { } |
| 105 | +forall<A, B> { A: C1<B> if B: C1<A> } |
| 106 | +``` |
| 107 | +Then the goal `exists<T, U> { T: C1<U> }` holds for all `T` and `U`. If the `C1` |
| 108 | +trait was not coinductive, this would be a simple cycle. |
| 109 | + |
| 110 | +To implement coinduction in the engine, delayed subgoals were introduced. |
| 111 | +Essentially, if a cycle is found, and the `Goal` is coinductive, then this is |
| 112 | +"delayed" until the stack unwinds back to the top `Goal` and all other |
| 113 | +non-coinductive cycles have been proven. Then, `Goal` has been proven itself. In |
| 114 | +some cases, it is the *root* `Goal` that has delayed coinductive subgoals (see |
| 115 | +above example). In this case, we create another "Refinement Strand" where the |
| 116 | +only subgoals are the delayed coinductive subgoals. If this new `Strand` can be |
| 117 | +proven, then any `Answer`s from that are valid answers for the root `Goal`. |
| 118 | +However, since there are currently delayed coinductive subgoals, there are no |
| 119 | +answers available yet. |
| 120 | + |
| 121 | +For much more in-depth |
| 122 | + |
| 123 | + |
| 124 | +[`Strand`]: https://rust-lang.github.io/chalk/chalk_engine/strand/struct.Strand.html |
| 125 | +[`Context`]: https://rust-lang.github.io/chalk/chalk_engine/context/trait.Context.html |
| 126 | +[`HhGoal`]: https://rust-lang.github.io/chalk/chalk_engine/hh/enum.HhGoal.html |
| 127 | +[`Stack`]: https://rust-lang.github.io/chalk/chalk_engine/stack/struct.Stack.html |
| 128 | +[`StackEntry`]: https://rust-lang.github.io/chalk/chalk_engine/stack/struct.StackEntry.html |
| 129 | +[`Table`]: https://rust-lang.github.io/chalk/chalk_engine/table/struct.Table.html |
| 130 | +[`Goal`]: https://rust-lang.github.io/chalk/chalk_engine/context/trait.Context.html#associatedtype.Goal |
| 131 | +[`Answer`]: https://rust-lang.github.io/chalk/chalk_engine/struct.Answer.html |
| 132 | +[`CompleteAnswer`]: https://rust-lang.github.io/chalk/chalk_engine/struct.CompleteAnswer.html |
0 commit comments