Skip to content

Commit 5b196f9

Browse files
authored
Merge pull request #321 from jackh726/cleanups
Some cleanups and add a `solve_limited`
2 parents 42a83d9 + 8a30731 commit 5b196f9

File tree

8 files changed

+154
-94
lines changed

8 files changed

+154
-94
lines changed

chalk-engine/src/context.rs

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,26 @@ pub trait Context: Clone + Debug {
179179
fn identity_constrained_subst(
180180
goal: &Self::UCanonicalGoalInEnvironment,
181181
) -> Self::CanonicalConstrainedSubst;
182+
183+
/// Convert the context's goal type into the `HhGoal` type that
184+
/// the SLG solver understands. The expectation is that the
185+
/// context's goal type has the same set of variants, but with
186+
/// different names and a different setup. If you inspect
187+
/// `HhGoal`, you will see that this is a "shallow" or "lazy"
188+
/// conversion -- that is, we convert the outermost goal into an
189+
/// `HhGoal`, but the goals contained within are left as context
190+
/// goals.
191+
fn into_hh_goal(goal: Self::Goal) -> HhGoal<Self>;
192+
193+
// Used by: simplify
194+
fn add_clauses(env: &Self::Environment, clauses: Self::ProgramClauses) -> Self::Environment;
195+
196+
/// Upcast this domain goal into a more general goal.
197+
fn into_goal(domain_goal: Self::DomainGoal) -> Self::Goal;
198+
199+
/// Selects the next appropriate subgoal index for evaluation.
200+
/// Used by: logic
201+
fn next_subgoal_index(ex_clause: &ExClause<Self>) -> usize;
182202
}
183203

184204
pub trait ContextOps<C: Context>: Sized + Clone + Debug + AggregateOps<C> {
@@ -247,32 +267,13 @@ pub trait AggregateOps<C: Context> {
247267
&self,
248268
root_goal: &C::UCanonicalGoalInEnvironment,
249269
answers: impl AnswerStream<C>,
270+
should_continue: impl Fn() -> bool,
250271
) -> Option<C::Solution>;
251272
}
252273

253274
/// An "inference table" contains the state to support unification and
254275
/// other operations on terms.
255-
pub trait InferenceTable<C: Context>: ResolventOps<C> + TruncateOps<C> + UnificationOps<C> {
256-
/// Convert the context's goal type into the `HhGoal` type that
257-
/// the SLG solver understands. The expectation is that the
258-
/// context's goal type has the same set of variants, but with
259-
/// different names and a different setup. If you inspect
260-
/// `HhGoal`, you will see that this is a "shallow" or "lazy"
261-
/// conversion -- that is, we convert the outermost goal into an
262-
/// `HhGoal`, but the goals contained within are left as context
263-
/// goals.
264-
fn into_hh_goal(&mut self, goal: C::Goal) -> HhGoal<C>;
265-
266-
// Used by: simplify
267-
fn add_clauses(&mut self, env: &C::Environment, clauses: C::ProgramClauses) -> C::Environment;
268-
269-
/// Upcast this domain goal into a more general goal.
270-
fn into_goal(&self, domain_goal: C::DomainGoal) -> C::Goal;
271-
272-
/// Selects the next appropriate subgoal index for evaluation.
273-
/// Used by: logic
274-
fn next_subgoal_index(&mut self, ex_clause: &ExClause<C>) -> usize;
275-
}
276+
pub trait InferenceTable<C: Context>: ResolventOps<C> + TruncateOps<C> + UnificationOps<C> {}
276277

277278
/// Error type for the `UnificationOps::program_clauses` method --
278279
/// indicates that the complete set of program clauses for this goal
@@ -388,6 +389,10 @@ pub enum AnswerResult<C: Context> {
388389

389390
/// No answer could be returned because the goal has floundered.
390391
Floundered,
392+
393+
// No answer could be returned *yet*, because we exceeded our
394+
// quantum (`should_continue` returned false).
395+
QuantumExceeded,
391396
}
392397

393398
impl<C: Context> AnswerResult<C> {
@@ -411,6 +416,13 @@ impl<C: Context> AnswerResult<C> {
411416
_ => false,
412417
}
413418
}
419+
420+
pub fn is_quantum_exceeded(&self) -> bool {
421+
match self {
422+
Self::QuantumExceeded => true,
423+
_ => false,
424+
}
425+
}
414426
}
415427

416428
impl<C: Context> Debug for AnswerResult<C> {
@@ -419,21 +431,21 @@ impl<C: Context> Debug for AnswerResult<C> {
419431
AnswerResult::Answer(answer) => write!(fmt, "{:?}", answer),
420432
AnswerResult::Floundered => write!(fmt, "Floundered"),
421433
AnswerResult::NoMoreSolutions => write!(fmt, "None"),
434+
AnswerResult::QuantumExceeded => write!(fmt, "QuantumExceeded"),
422435
}
423436
}
424437
}
425438

426439
pub trait AnswerStream<C: Context> {
427440
/// Gets the next answer for a given goal, but doesn't increment the answer index.
428441
/// Calling this or `next_answer` again will give the same answer.
429-
fn peek_answer(&mut self) -> AnswerResult<C>;
442+
fn peek_answer(&mut self, should_continue: impl Fn() -> bool) -> AnswerResult<C>;
430443

431444
/// Gets the next answer for a given goal, incrementing the answer index.
432445
/// Calling this or `peek_answer` again will give the next answer.
433-
fn next_answer(&mut self) -> AnswerResult<C>;
446+
fn next_answer(&mut self, should_continue: impl Fn() -> bool) -> AnswerResult<C>;
434447

435448
/// Invokes `test` with each possible future answer, returning true immediately
436449
/// if we find any answer for which `test` returns true.
437-
fn any_future_answer(&mut self, test: impl FnMut(&C::InferenceNormalizedSubst) -> bool)
438-
-> bool;
450+
fn any_future_answer(&self, test: impl Fn(&C::InferenceNormalizedSubst) -> bool) -> bool;
439451
}

chalk-engine/src/forest.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@ impl<C: Context> Forest<C> {
5959
&mut self,
6060
context: &impl ContextOps<C>,
6161
goal: &C::UCanonicalGoalInEnvironment,
62+
should_continue: impl Fn() -> bool,
6263
) -> Option<C::Solution> {
63-
context.make_solution(&goal, self.iter_answers(context, goal))
64+
context.make_solution(&goal, self.iter_answers(context, goal), should_continue)
6465
}
6566

6667
/// Solves a given goal, producing the solution. This will do only
@@ -75,7 +76,7 @@ impl<C: Context> Forest<C> {
7576
) -> bool {
7677
let mut answers = self.iter_answers(context, goal);
7778
loop {
78-
let subst = match answers.next_answer() {
79+
let subst = match answers.next_answer(|| true) {
7980
AnswerResult::Answer(answer) => {
8081
if !answer.ambiguous {
8182
SubstitutionResult::Definite(context.constrained_subst_from_answer(answer))
@@ -87,9 +88,10 @@ impl<C: Context> Forest<C> {
8788
AnswerResult::NoMoreSolutions => {
8889
return true;
8990
}
91+
AnswerResult::QuantumExceeded => continue,
9092
};
9193

92-
if !f(subst, !answers.peek_answer().is_no_more_solutions()) {
94+
if !f(subst, !answers.peek_answer(|| true).is_no_more_solutions()) {
9395
return false;
9496
}
9597
}
@@ -157,7 +159,7 @@ impl<'me, C: Context, CO: ContextOps<C>> AnswerStream<C> for ForestSolver<'me, C
157159
/// # Panics
158160
///
159161
/// Panics if a negative cycle was detected.
160-
fn peek_answer(&mut self) -> AnswerResult<C> {
162+
fn peek_answer(&mut self, should_continue: impl Fn() -> bool) -> AnswerResult<C> {
161163
loop {
162164
match self
163165
.forest
@@ -178,7 +180,11 @@ impl<'me, C: Context, CO: ContextOps<C>> AnswerStream<C> for ForestSolver<'me, C
178180
return AnswerResult::NoMoreSolutions;
179181
}
180182

181-
Err(RootSearchFail::QuantumExceeded) => {}
183+
Err(RootSearchFail::QuantumExceeded) => {
184+
if !should_continue() {
185+
return AnswerResult::QuantumExceeded;
186+
}
187+
}
182188

183189
Err(RootSearchFail::NegativeCycle) => {
184190
// Negative cycles *ought* to be avoided by construction. Hence panic
@@ -192,16 +198,13 @@ impl<'me, C: Context, CO: ContextOps<C>> AnswerStream<C> for ForestSolver<'me, C
192198
}
193199
}
194200

195-
fn next_answer(&mut self) -> AnswerResult<C> {
196-
let answer = self.peek_answer();
201+
fn next_answer(&mut self, should_continue: impl Fn() -> bool) -> AnswerResult<C> {
202+
let answer = self.peek_answer(should_continue);
197203
self.answer.increment();
198204
answer
199205
}
200206

201-
fn any_future_answer(
202-
&mut self,
203-
test: impl FnMut(&C::InferenceNormalizedSubst) -> bool,
204-
) -> bool {
207+
fn any_future_answer(&self, test: impl Fn(&C::InferenceNormalizedSubst) -> bool) -> bool {
205208
self.forest.any_future_answer(self.table, self.answer, test)
206209
}
207210
}

chalk-engine/src/logic.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ impl<C: Context> Forest<C> {
204204
}
205205

206206
pub(super) fn any_future_answer(
207-
&mut self,
207+
&self,
208208
table: TableIndex,
209209
answer: AnswerIndex,
210210
mut test: impl FnMut(&C::InferenceNormalizedSubst) -> bool,
@@ -214,7 +214,7 @@ impl<C: Context> Forest<C> {
214214
return test(C::inference_normalized_subst_from_subst(&answer.subst));
215215
}
216216

217-
self.tables[table].strands_mut().any(|strand| {
217+
self.tables[table].strands().any(|strand| {
218218
test(C::inference_normalized_subst_from_ex_clause(
219219
&strand.canonical_ex_clause,
220220
))
@@ -966,7 +966,7 @@ impl<C: Context> Forest<C> {
966966
continue;
967967
}
968968

969-
let subgoal_index = strand.infer.next_subgoal_index(&strand.ex_clause);
969+
let subgoal_index = C::next_subgoal_index(&strand.ex_clause);
970970

971971
// Get or create table for this subgoal.
972972
match self.get_or_create_table_for_subgoal(
@@ -1228,7 +1228,7 @@ impl<C: Context> Forest<C> {
12281228
goal: C::Goal,
12291229
) {
12301230
let table_ref = &mut self.tables[table];
1231-
match infer.into_hh_goal(goal) {
1231+
match C::into_hh_goal(goal) {
12321232
HhGoal::DomainGoal(domain_goal) => {
12331233
match context.program_clauses(&environment, &domain_goal, &mut infer) {
12341234
Ok(clauses) => {
@@ -1418,7 +1418,7 @@ impl<C: Context> Forest<C> {
14181418
// untruncated literal. Suppose that we truncate the selected
14191419
// goal to:
14201420
//
1421-
// // Vec<Vec<T>: Sized
1421+
// // Vec<Vec<T>>: Sized
14221422
//
14231423
// Clearly this table will have some solutions that don't
14241424
// apply to us. e.g., `Vec<Vec<u32>>: Sized` is a solution to

chalk-engine/src/simplify.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,19 @@ impl<C: Context> Forest<C> {
3131
match hh_goal {
3232
HhGoal::ForAll(subgoal) => {
3333
let subgoal = infer.instantiate_binders_universally(&subgoal);
34-
pending_goals.push((environment, infer.into_hh_goal(subgoal)));
34+
pending_goals.push((environment, C::into_hh_goal(subgoal)));
3535
}
3636
HhGoal::Exists(subgoal) => {
3737
let subgoal = infer.instantiate_binders_existentially(&subgoal);
38-
pending_goals.push((environment, infer.into_hh_goal(subgoal)))
38+
pending_goals.push((environment, C::into_hh_goal(subgoal)))
3939
}
4040
HhGoal::Implies(wc, subgoal) => {
41-
let new_environment = infer.add_clauses(&environment, wc);
42-
pending_goals.push((new_environment, infer.into_hh_goal(subgoal)));
41+
let new_environment = C::add_clauses(&environment, wc);
42+
pending_goals.push((new_environment, C::into_hh_goal(subgoal)));
4343
}
4444
HhGoal::All(subgoals) => {
4545
for subgoal in subgoals {
46-
pending_goals.push((environment.clone(), infer.into_hh_goal(subgoal)));
46+
pending_goals.push((environment.clone(), C::into_hh_goal(subgoal)));
4747
}
4848
}
4949
HhGoal::Not(subgoal) => {
@@ -66,7 +66,7 @@ impl<C: Context> Forest<C> {
6666
.subgoals
6767
.push(Literal::Positive(C::goal_in_environment(
6868
&environment,
69-
infer.into_goal(domain_goal),
69+
C::into_goal(domain_goal),
7070
)));
7171
}
7272
HhGoal::CannotProve => {

chalk-engine/src/table.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ impl<C: Context> Table<C> {
7070
self.strands.iter_mut()
7171
}
7272

73+
pub(crate) fn strands(&self) -> impl Iterator<Item = &CanonicalStrand<C>> {
74+
self.strands.iter()
75+
}
76+
7377
pub(crate) fn take_strands(&mut self) -> VecDeque<CanonicalStrand<C>> {
7478
mem::replace(&mut self.strands, VecDeque::new())
7579
}

chalk-solve/src/solve.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,39 @@ impl<TF: TypeFamily> Solver<TF> {
135135
goal: &UCanonical<InEnvironment<Goal<TF>>>,
136136
) -> Option<Solution<TF>> {
137137
let ops = self.forest.context().ops(program);
138-
self.forest.solve(&ops, goal)
138+
self.forest.solve(&ops, goal, || true)
139+
}
140+
141+
/// Attempts to solve the given goal, which must be in canonical
142+
/// form. Returns a unique solution (if one exists). This will do
143+
/// only as much work towards `goal` as it has to (and that work
144+
/// is cached for future attempts). In addition, the solving of the
145+
/// goal can be limited by returning `false` from `should_continue`.
146+
///
147+
/// # Parameters
148+
///
149+
/// - `program` -- defines the program clauses in scope.
150+
/// - **Important:** You must supply the same set of program clauses
151+
/// each time you invoke `solve`, as otherwise the cached data may be
152+
/// invalid.
153+
/// - `goal` the goal to solve
154+
/// - `should_continue` if `false` is returned, the no further solving
155+
/// will be done. A `Guidance(Suggested(...))` will be returned a
156+
/// `Solution`, using any answers that were generated up to that point.
157+
///
158+
/// # Returns
159+
///
160+
/// - `None` is the goal cannot be proven.
161+
/// - `Some(solution)` if we succeeded in finding *some* answers,
162+
/// although `solution` may reflect ambiguity and unknowns.
163+
pub fn solve_limited(
164+
&mut self,
165+
program: &dyn RustIrDatabase<TF>,
166+
goal: &UCanonical<InEnvironment<Goal<TF>>>,
167+
should_continue: impl std::ops::Fn() -> bool,
168+
) -> Option<Solution<TF>> {
169+
let ops = self.forest.context().ops(program);
170+
self.forest.solve(&ops, goal, should_continue)
139171
}
140172

141173
/// Attempts to solve the given goal, which must be in canonical

0 commit comments

Comments
 (0)