| use {DelayedLiteral, DelayedLiteralSet, DepthFirstNumber, ExClause, Literal, Minimums, |
| TableIndex}; |
| use fallible::NoSolution; |
| use context::{WithInstantiatedExClause, WithInstantiatedUCanonicalGoal, prelude::*}; |
| use forest::Forest; |
| use hh::HhGoal; |
| use stack::StackIndex; |
| use strand::{CanonicalStrand, SelectedSubgoal, Strand}; |
| use table::{Answer, AnswerIndex}; |
| use rustc_hash::FxHashSet; |
| use std::marker::PhantomData; |
| use std::mem; |
| |
| type RootSearchResult<T> = Result<T, RootSearchFail>; |
| |
| /// The different ways that a *root* search (which potentially pursues |
| /// many strands) can fail. A root search is one that begins with an |
| /// empty stack. |
| /// |
| /// (This is different from `RecursiveSearchFail` because nothing can |
| /// be on the stack, so cycles are ruled out.) |
| #[derive(Debug)] |
| pub(super) enum RootSearchFail { |
| /// The subgoal we were trying to solve cannot succeed. |
| NoMoreSolutions, |
| |
| /// We did not find a solution, but we still have things to try. |
| /// Repeat the request, and we'll give one of those a spin. |
| /// |
| /// (In a purely depth-first-based solver, like Prolog, this |
| /// doesn't appear.) |
| QuantumExceeded, |
| } |
| |
| type RecursiveSearchResult<T> = Result<T, RecursiveSearchFail>; |
| |
| /// The different ways that a recursive search (which potentially |
| /// pursues many strands) can fail -- a "recursive" search is one that |
| /// did not start with an empty stack. |
| #[derive(Debug)] |
| enum RecursiveSearchFail { |
| /// The subgoal we were trying to solve cannot succeed. |
| NoMoreSolutions, |
| |
| /// **All** avenues to solve the subgoal we were trying solve |
| /// encountered a cyclic dependency on something higher up in the |
| /// stack. The `Minimums` encodes how high up (and whether |
| /// positive or negative). |
| Cycle(Minimums), |
| |
| /// We did not find a solution, but we still have things to try. |
| /// Repeat the request, and we'll give one of those a spin. |
| /// |
| /// (In a purely depth-first-based solver, like Prolog, this |
| /// doesn't appear.) |
| QuantumExceeded, |
| } |
| |
| #[allow(type_alias_bounds)] |
| type StrandResult<C: Context, T> = Result<T, StrandFail<C>>; |
| |
| /// Possible failures from pursuing a particular strand. |
| #[derive(Debug)] |
| pub(super) enum StrandFail<C: Context> { |
| /// The strand has no solution. |
| NoSolution, |
| |
| /// We did not yet figure out a solution; the strand will have |
| /// been rescheduled for later. |
| QuantumExceeded, |
| |
| /// The strand hit a cyclic dependency. In this case, |
| /// we return the strand, as well as a `Minimums` struct. |
| Cycle(CanonicalStrand<C>, Minimums), |
| } |
| |
| #[derive(Debug)] |
| enum EnsureSuccess { |
| AnswerAvailable, |
| Coinductive, |
| } |
| |
| impl<C: Context, CO: ContextOps<C>> Forest<C, CO> { |
| /// Ensures that answer with the given index is available from the |
| /// given table. This may require activating a strand. Returns |
| /// `Ok(())` if the answer is available and otherwise a |
| /// `RootSearchFail` result. |
| pub(super) fn ensure_root_answer( |
| &mut self, |
| table: TableIndex, |
| answer: AnswerIndex, |
| ) -> RootSearchResult<()> { |
| assert!(self.stack.is_empty()); |
| |
| match self.ensure_answer_recursively(table, answer) { |
| Ok(EnsureSuccess::AnswerAvailable) => Ok(()), |
| Err(RecursiveSearchFail::NoMoreSolutions) => Err(RootSearchFail::NoMoreSolutions), |
| Err(RecursiveSearchFail::QuantumExceeded) => Err(RootSearchFail::QuantumExceeded), |
| |
| // Things involving cycles should be impossible since our |
| // stack was empty on entry: |
| Ok(EnsureSuccess::Coinductive) | Err(RecursiveSearchFail::Cycle(..)) => { |
| panic!("ensure_root_answer: nothing on the stack but cyclic result") |
| } |
| } |
| } |
| |
| pub(super) fn any_future_answer( |
| &mut self, |
| table: TableIndex, |
| answer: AnswerIndex, |
| mut test: impl FnMut(&C::InferenceNormalizedSubst) -> bool, |
| ) -> bool { |
| if let Some(answer) = self.tables[table].answer(answer) { |
| info!("answer cached = {:?}", answer); |
| return test(CO::inference_normalized_subst_from_subst(&answer.subst)); |
| } |
| |
| self.tables[table].strands_mut().any(|strand| { |
| test(CO::inference_normalized_subst_from_ex_clause(&strand.canonical_ex_clause)) |
| }) |
| } |
| |
| /// Ensures that answer with the given index is available from the |
| /// given table. Returns `Ok` if there is an answer: |
| /// |
| /// - `EnsureSuccess::AnswerAvailable` means that the answer is |
| /// cached in the table (and can be fetched with e.g. `self.answer()`). |
| /// - `EnsureSuccess::Coinductive` means that this was a cyclic |
| /// request of a coinductive goal and is thus considered true; |
| /// in this case, the answer is not cached in the table (it is |
| /// only true in this cyclic context). |
| /// |
| /// This function first attempts to fetch answer that is cached in |
| /// the table. If none is found, then we will if the table is on |
| /// the stack; if so, that constitutes a cycle (producing a new |
| /// result for the table X required producing a new result for the |
| /// table X), and we return a suitable result. Otherwise, we can |
| /// push the table onto the stack and select the next available |
| /// strand -- if none are available, then no more answers are |
| /// possible. |
| fn ensure_answer_recursively( |
| &mut self, |
| table: TableIndex, |
| answer: AnswerIndex, |
| ) -> RecursiveSearchResult<EnsureSuccess> { |
| info_heading!( |
| "ensure_answer_recursively(table={:?}, answer={:?})", |
| table, |
| answer |
| ); |
| info!("table goal = {:#?}", self.tables[table].table_goal); |
| |
| // First, check for a tabled answer. |
| if self.tables[table].answer(answer).is_some() { |
| info!("answer cached = {:?}", self.tables[table].answer(answer)); |
| return Ok(EnsureSuccess::AnswerAvailable); |
| } |
| |
| // If no tabled answer is present, we ought to be requesting |
| // the next available index. |
| assert_eq!(self.tables[table].next_answer_index(), answer); |
| |
| // Next, check if the table is already active. If so, then we |
| // have a recursive attempt. |
| if let Some(depth) = self.stack.is_active(table) { |
| info!("ensure_answer: cycle detected at depth {:?}", depth); |
| |
| if self.top_of_stack_is_coinductive_from(depth) { |
| return Ok(EnsureSuccess::Coinductive); |
| } |
| |
| return Err(RecursiveSearchFail::Cycle(Minimums { |
| positive: self.stack[depth].dfn, |
| negative: DepthFirstNumber::MAX, |
| })); |
| } |
| |
| let dfn = self.next_dfn(); |
| let depth = self.stack.push(table, dfn); |
| let result = self.pursue_next_strand(depth); |
| self.stack.pop(table, depth); |
| info!("ensure_answer: result = {:?}", result); |
| result.map(|()| EnsureSuccess::AnswerAvailable) |
| } |
| |
| pub(crate) fn answer(&self, table: TableIndex, answer: AnswerIndex) -> &Answer<C> { |
| self.tables[table].answer(answer).unwrap() |
| } |
| |
| /// Selects the next eligible strand from the table at depth |
| /// `depth` and pursues it. If that strand encounters a cycle, |
| /// then this function will loop and keep trying strands until it |
| /// reaches one that did not encounter a cycle; that result is |
| /// propagated. If all strands return a cycle, then the entire |
| /// subtree is "completed" by invoking `cycle`. |
| fn pursue_next_strand(&mut self, depth: StackIndex) -> RecursiveSearchResult<()> { |
| // This is a bit complicated because this is where we handle cycles. |
| let table = self.stack[depth].table; |
| |
| // Strands that encountered a cyclic error. |
| let mut cyclic_strands = vec![]; |
| |
| // The minimum of all cyclic strands. |
| let mut cyclic_minimums = Minimums::MAX; |
| |
| loop { |
| match self.tables[table].pop_next_strand() { |
| Some(canonical_strand) => { |
| let num_universes = CO::num_universes(&self.tables[table].table_goal); |
| let result = Self::with_instantiated_strand( |
| self.context.clone(), |
| num_universes, |
| &canonical_strand, |
| PursueStrand { |
| forest: self, |
| depth, |
| }, |
| ); |
| match result { |
| Ok(answer) => { |
| // Now that we produced an answer, these |
| // cyclic strands need to be retried. |
| self.tables[table].extend_strands(cyclic_strands); |
| return Ok(answer); |
| } |
| |
| Err(StrandFail::NoSolution) | Err(StrandFail::QuantumExceeded) => { |
| // This strand did not produce an answer, |
| // but either it (or some other, pending |
| // strands) may do so in the |
| // future. Enqueue the cyclic strands to |
| // be retried after that point. |
| self.tables[table].extend_strands(cyclic_strands); |
| return Err(RecursiveSearchFail::QuantumExceeded); |
| } |
| |
| Err(StrandFail::Cycle(canonical_strand, strand_minimums)) => { |
| // This strand encountered a cycle. Stash |
| // it for later and try the next one until |
| // we know that *all* available strands |
| // are hitting a cycle. |
| cyclic_strands.push(canonical_strand); |
| cyclic_minimums.take_minimums(&strand_minimums); |
| } |
| } |
| } |
| |
| None => { |
| // No more strands left to try! That means either we started |
| // with no strands, or all available strands encountered a cycle. |
| |
| if cyclic_strands.is_empty() { |
| // We started with no strands! |
| return Err(RecursiveSearchFail::NoMoreSolutions); |
| } else { |
| let c = mem::replace(&mut cyclic_strands, vec![]); |
| if let Some(err) = self.cycle(depth, c, cyclic_minimums) { |
| return Err(err); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| fn with_instantiated_strand<R>( |
| context: CO, |
| num_universes: usize, |
| canonical_strand: &CanonicalStrand<C>, |
| op: impl WithInstantiatedStrand<C, CO, Output = R>, |
| ) -> R { |
| let CanonicalStrand { |
| canonical_ex_clause, |
| selected_subgoal, |
| } = canonical_strand; |
| return context.instantiate_ex_clause( |
| num_universes, |
| &canonical_ex_clause, |
| With { |
| op, |
| selected_subgoal: selected_subgoal.clone(), |
| ops: PhantomData, |
| }, |
| ); |
| |
| struct With<C: Context, CO: ContextOps<C>, OP: WithInstantiatedStrand<C, CO>> { |
| op: OP, |
| selected_subgoal: Option<SelectedSubgoal<C>>, |
| ops: PhantomData<CO>, |
| } |
| |
| impl<C: Context, CO: ContextOps<C>, OP: WithInstantiatedStrand<C, CO>> |
| WithInstantiatedExClause<C> for With<C, CO, OP> { |
| type Output = OP::Output; |
| |
| fn with<I: Context>( |
| self, |
| infer: &mut dyn InferenceTable<C, I>, |
| ex_clause: ExClause<I>, |
| ) -> OP::Output { |
| self.op.with(Strand { |
| infer, |
| ex_clause, |
| selected_subgoal: self.selected_subgoal.clone(), |
| }) |
| } |
| } |
| } |
| |
| fn canonicalize_strand(strand: Strand<'_, C, impl Context>) -> CanonicalStrand<C> { |
| let Strand { |
| infer, |
| ex_clause, |
| selected_subgoal, |
| } = strand; |
| Self::canonicalize_strand_from(&mut *infer, &ex_clause, selected_subgoal) |
| } |
| |
| fn canonicalize_strand_from<I: Context>( |
| infer: &mut dyn InferenceTable<C, I>, |
| ex_clause: &ExClause<I>, |
| selected_subgoal: Option<SelectedSubgoal<C>>, |
| ) -> CanonicalStrand<C> { |
| let canonical_ex_clause = infer.canonicalize_ex_clause(&ex_clause); |
| CanonicalStrand { |
| canonical_ex_clause, |
| selected_subgoal, |
| } |
| } |
| |
| /// Invoked when all available strands for a table have |
| /// encountered a cycle. In this case, the vector `strands` are |
| /// the set of strands that encountered cycles, and `minimums` is |
| /// the minimum stack depths that they were dependent on. |
| /// |
| /// Returns `None` if we have resolved the cycle and should try to |
| /// pick a strand again. Returns `Some(_)` if the cycle indicates |
| /// an error that we can propagate higher up. |
| fn cycle( |
| &mut self, |
| depth: StackIndex, |
| strands: Vec<CanonicalStrand<C>>, |
| minimums: Minimums, |
| ) -> Option<RecursiveSearchFail> { |
| let table = self.stack[depth].table; |
| assert!(self.tables[table].pop_next_strand().is_none()); |
| |
| let dfn = self.stack[depth].dfn; |
| if minimums.positive == dfn && minimums.negative == DepthFirstNumber::MAX { |
| // If all the things that we recursively depend on have |
| // positive dependencies on things below us in the stack, |
| // then no more answers are forthcoming. We can clear all |
| // the strands for those things recursively. |
| self.clear_strands_after_cycle(table, strands); |
| Some(RecursiveSearchFail::NoMoreSolutions) |
| } else if minimums.positive >= dfn && minimums.negative >= dfn { |
| let mut visited = FxHashSet::default(); |
| visited.insert(table); |
| self.tables[table].extend_strands(strands); |
| self.delay_strands_after_cycle(table, &mut visited); |
| None |
| } else { |
| self.tables[table].extend_strands(strands); |
| Some(RecursiveSearchFail::Cycle(minimums)) |
| } |
| } |
| |
| /// Invoked after we have determined that every strand in `table` |
| /// encounters a cycle; `strands` is the set of strands (which |
| /// have been moved out of the table). This method then |
| /// recursively clears the active strands from the tables |
| /// referenced in `strands`, since all of them must encounter |
| /// cycles too. |
| fn clear_strands_after_cycle( |
| &mut self, |
| table: TableIndex, |
| strands: impl IntoIterator<Item = CanonicalStrand<C>>, |
| ) { |
| assert!(self.tables[table].pop_next_strand().is_none()); |
| for strand in strands { |
| let CanonicalStrand { |
| canonical_ex_clause, |
| selected_subgoal, |
| } = strand; |
| let selected_subgoal = selected_subgoal.unwrap_or_else(|| { |
| panic!( |
| "clear_strands_after_cycle invoked on strand in table {:?} \ |
| without a selected subgoal: {:?}", |
| table, canonical_ex_clause, |
| ) |
| }); |
| |
| let strand_table = selected_subgoal.subgoal_table; |
| let strands = self.tables[strand_table].take_strands(); |
| self.clear_strands_after_cycle(strand_table, strands); |
| } |
| } |
| |
| /// Invoked after we have determined that every strand in `table` |
| /// encounters a cycle, and that some of those cycles involve |
| /// negative edges. In that case, walks all negative edges and |
| /// converts them to delayed literals. |
| fn delay_strands_after_cycle(&mut self, table: TableIndex, visited: &mut FxHashSet<TableIndex>) { |
| let mut tables = vec![]; |
| |
| let num_universes = CO::num_universes(&self.tables[table].table_goal); |
| for canonical_strand in self.tables[table].strands_mut() { |
| // FIXME if CanonicalExClause were not held abstract, we |
| // could do this in place like we used to (and |
| // `instantiate_strand` could take ownership), since we |
| // don't really need to instantiate here to do this |
| // operation. |
| let (delayed_strand, subgoal_table) = Self::with_instantiated_strand( |
| self.context.clone(), |
| num_universes, |
| canonical_strand, |
| DelayStrandAfterCycle { table }, |
| ); |
| |
| *canonical_strand = delayed_strand; |
| |
| if visited.insert(subgoal_table) { |
| tables.push(subgoal_table); |
| } |
| } |
| |
| for table in tables { |
| self.delay_strands_after_cycle(table, visited); |
| } |
| } |
| |
| fn delay_strand_after_cycle( |
| table: TableIndex, |
| mut strand: Strand<'_, C, impl Context>, |
| ) -> (CanonicalStrand<C>, TableIndex) { |
| let (subgoal_index, subgoal_table) = match &strand.selected_subgoal { |
| Some(selected_subgoal) => ( |
| selected_subgoal.subgoal_index, |
| selected_subgoal.subgoal_table, |
| ), |
| None => { |
| panic!( |
| "delay_strands_after_cycle invoked on strand in table {:?} \ |
| without a selected subgoal: {:?}", |
| table, strand, |
| ); |
| } |
| }; |
| |
| // Delay negative literals. |
| if let Literal::Negative(_) = strand.ex_clause.subgoals[subgoal_index] { |
| strand.ex_clause.subgoals.remove(subgoal_index); |
| strand |
| .ex_clause |
| .delayed_literals |
| .push(DelayedLiteral::Negative(subgoal_table)); |
| strand.selected_subgoal = None; |
| } |
| |
| (Self::canonicalize_strand(strand), subgoal_table) |
| } |
| |
| /// Pursues `strand` to see if it leads us to a new answer, either |
| /// by selecting a new subgoal or by checking to see if the |
| /// selected subgoal has an answer. `strand` is associated with |
| /// the table on the stack at the given `depth`. |
| fn pursue_strand( |
| &mut self, |
| depth: StackIndex, |
| mut strand: Strand<'_, C, impl Context>, |
| ) -> StrandResult<C, ()> { |
| info_heading!( |
| "pursue_strand(table={:?}, depth={:?}, ex_clause={:#?}, selected_subgoal={:?})", |
| self.stack[depth].table, |
| depth, |
| strand.infer.debug_ex_clause(&strand.ex_clause), |
| strand.selected_subgoal, |
| ); |
| |
| // If no subgoal has yet been selected, select one. |
| while strand.selected_subgoal.is_none() { |
| if strand.ex_clause.subgoals.len() == 0 { |
| return self.pursue_answer(depth, strand); |
| } |
| |
| // For now, we always pick the last subgoal in the |
| // list. |
| // |
| // FIXME(rust-lang-nursery/chalk#80) -- we should be more |
| // selective. For example, we don't want to pick a |
| // negative literal that will flounder, and we don't want |
| // to pick things like `?T: Sized` if we can help it. |
| let subgoal_index = strand.ex_clause.subgoals.len() - 1; |
| |
| // Get or create table for this subgoal. |
| match self.get_or_create_table_for_subgoal( |
| &mut *strand.infer, |
| &strand.ex_clause.subgoals[subgoal_index], |
| ) { |
| Some((subgoal_table, universe_map)) => { |
| strand.selected_subgoal = Some(SelectedSubgoal { |
| subgoal_index, |
| subgoal_table, |
| universe_map, |
| answer_index: AnswerIndex::ZERO, |
| }); |
| } |
| |
| None => { |
| // If we failed to create a table for the subgoal, |
| // then the execution has "floundered" (cannot yield |
| // a complete result). We choose to handle this by |
| // removing the subgoal and inserting a |
| // `CannotProve` result. This can only happen with |
| // ill-formed negative literals or with overflow. |
| strand.ex_clause.subgoals.remove(subgoal_index); |
| strand |
| .ex_clause |
| .delayed_literals |
| .push(DelayedLiteral::CannotProve(())); |
| } |
| } |
| } |
| |
| // Find the selected subgoal and ask it for the next answer. |
| let selected_subgoal = strand.selected_subgoal.clone().unwrap(); |
| match strand.ex_clause.subgoals[selected_subgoal.subgoal_index] { |
| Literal::Positive(_) => self.pursue_positive_subgoal(depth, strand, &selected_subgoal), |
| Literal::Negative(_) => self.pursue_negative_subgoal(depth, strand, &selected_subgoal), |
| } |
| } |
| |
| /// Invoked when a strand represents an **answer**. This means |
| /// that the strand has no subgoals left. There are two possibilities: |
| /// |
| /// - the strand may represent an answer we have already found; in |
| /// that case, we can return `StrandFail::NoSolution`, as this |
| /// strand led nowhere of interest. |
| /// - the strand may represent a new answer, in which case it is |
| /// added to the table and `Ok` is returned. |
| fn pursue_answer( |
| &mut self, |
| depth: StackIndex, |
| strand: Strand<'_, C, impl Context>, |
| ) -> StrandResult<C, ()> { |
| let table = self.stack[depth].table; |
| let Strand { |
| infer, |
| ex_clause: |
| ExClause { |
| subst, |
| constraints, |
| delayed_literals, |
| subgoals, |
| }, |
| selected_subgoal: _, |
| } = strand; |
| assert!(subgoals.is_empty()); |
| |
| let answer_subst = infer.canonicalize_constrained_subst(subst, constraints); |
| debug!("answer: table={:?}, answer_subst={:?}", table, answer_subst); |
| |
| let delayed_literals = { |
| let mut delayed_literals: FxHashSet<_> = delayed_literals.into_iter() |
| .map(|dl| infer.lift_delayed_literal(dl)) |
| .collect(); |
| DelayedLiteralSet { delayed_literals } |
| }; |
| debug!("answer: delayed_literals={:?}", delayed_literals); |
| |
| let answer = Answer { |
| subst: answer_subst, |
| delayed_literals, |
| }; |
| |
| // A "trivial" answer is one that is 'just true for all cases' |
| // -- in other words, it gives no information back to the |
| // caller. For example, `Vec<u32>: Sized` is "just true". |
| // Such answers are important because they are the most |
| // general case, and after we provide a trivial answer, no |
| // further answers are useful -- therefore we can clear any |
| // further pending strands (this is a "green cut", in |
| // Prolog parlance). |
| // |
| // This optimization is *crucial* for performance: for |
| // example, `projection_from_env_slow` fails miserably without |
| // it. The reason is that we wind up (thanks to implied bounds) |
| // with a clause like this: |
| // |
| // ```ignore |
| // forall<T> { (<T as SliceExt>::Item: Clone) :- WF(T: SliceExt) } |
| // ``` |
| // |
| // we then apply that clause to `!1: Clone`, resulting in the |
| // table goal `!1: Clone :- <?0 as SliceExt>::Item = !1, |
| // WF(?0: SliceExt)`. This causes us to **enumerate all types |
| // `?0` that where `Slice<?0>` normalizes to `!1` -- this is |
| // an infinite set of types, effectively. Interestingly, |
| // though, we only need one and we are done, because (if you |
| // look) our goal (`!1: Clone`) doesn't have any output |
| // parameters. |
| // |
| // This is actually a kind of general case. Due to Rust's rule |
| // about constrained impl type parameters, generally speaking |
| // when we have some free inference variable (like `?0`) |
| // within our clause, it must appear in the head of the |
| // clause. This means that the values we create for it will |
| // propagate up to the caller, and they will quickly surmise |
| // that there is ambiguity and stop requesting more answers. |
| // Indeed, the only exception to this rule about constrained |
| // type parameters if with associated type projections, as in |
| // the case above! |
| // |
| // (Actually, because of the trivial answer cut off rule, we |
| // never even get to the point of asking the query above in |
| // `projection_from_env_slow`.) |
| // |
| // However, there is one fly in the ointment: answers include |
| // region constraints, and you might imagine that we could |
| // find future answers that are also trivial but with distinct |
| // sets of region constraints. **For this reason, we only |
| // apply this green cut rule if the set of generated |
| // constraints is empty.** |
| // |
| // The limitation on region constraints is quite a drag! We |
| // can probably do better, though: for example, coherence |
| // guarantees that, for any given set of types, only a single |
| // impl ought to be applicable, and that impl can only impose |
| // one set of region constraints. However, it's not quite that |
| // simple, thanks to specialization as well as the possibility |
| // of proving things from the environment (though the latter |
| // is a *bit* suspect; e.g., those things in the environment |
| // must be backed by an impl *eventually*). |
| let is_trivial_answer = { |
| answer.delayed_literals.is_empty() |
| && CO::is_trivial_substitution(&self.tables[table].table_goal, &answer.subst) |
| && CO::empty_constraints(&answer.subst) |
| }; |
| |
| if self.tables[table].push_answer(answer) { |
| if is_trivial_answer { |
| self.tables[table].take_strands(); |
| } |
| |
| Ok(()) |
| } else { |
| info!("answer: not a new answer, returning StrandFail::NoSolution"); |
| Err(StrandFail::NoSolution) |
| } |
| } |
| |
| /// Given a subgoal, converts the literal into u-canonical form |
| /// and searches for an existing table. If one is found, it is |
| /// returned, but otherwise a new table is created (and populated |
| /// with its initial set of strands). |
| /// |
| /// Returns `None` if the literal cannot be converted into a table |
| /// -- for example, this can occur when we have selected a |
| /// negative literal with free existential variables, in which |
| /// case the execution is said to "flounder". |
| /// |
| /// In terms of the NFTD paper, creating a new table corresponds |
| /// to the *New Subgoal* step as well as the *Program Clause |
| /// Resolution* steps. |
| fn get_or_create_table_for_subgoal<I: Context>( |
| &mut self, |
| infer: &mut dyn InferenceTable<C, I>, |
| subgoal: &Literal<I>, |
| ) -> Option<(TableIndex, C::UniverseMap)> { |
| debug_heading!("get_or_create_table_for_subgoal(subgoal={:?})", subgoal); |
| |
| // Subgoal abstraction: |
| let canonical_subgoal = match subgoal { |
| Literal::Positive(subgoal) => self.abstract_positive_literal(infer, subgoal), |
| Literal::Negative(subgoal) => self.abstract_negative_literal(infer, subgoal)?, |
| }; |
| |
| debug!("canonical_subgoal={:?}", canonical_subgoal); |
| |
| let (ucanonical_subgoal, universe_map) = infer.u_canonicalize_goal(&canonical_subgoal); |
| |
| let table = self.get_or_create_table_for_ucanonical_goal(ucanonical_subgoal); |
| |
| Some((table, universe_map)) |
| } |
| |
| /// Given a u-canonical goal, searches for an existing table. If |
| /// one is found, it is returned, but otherwise a new table is |
| /// created (and populated with its initial set of strands). |
| /// |
| /// In terms of the NFTD paper, creating a new table corresponds |
| /// to the *New Subgoal* step as well as the *Program Clause |
| /// Resolution* steps. |
| pub(crate) fn get_or_create_table_for_ucanonical_goal( |
| &mut self, |
| goal: C::UCanonicalGoalInEnvironment, |
| ) -> TableIndex { |
| debug_heading!("get_or_create_table_for_ucanonical_goal({:?})", goal); |
| |
| if let Some(table) = self.tables.index_of(&goal) { |
| debug!("found existing table {:?}", table); |
| return table; |
| } |
| |
| info_heading!( |
| "creating new table {:?} and goal {:#?}", |
| self.tables.next_index(), |
| goal |
| ); |
| let coinductive_goal = self.context.is_coinductive(&goal); |
| let table = self.tables.insert(goal, coinductive_goal); |
| self.push_initial_strands(table); |
| table |
| } |
| |
| /// When a table is first created, this function is invoked to |
| /// create the initial set of strands. If the table represents a |
| /// domain goal, these strands are created from the program |
| /// clauses as well as the clauses found in the environment. If |
| /// the table represents a non-domain goal, such as `for<T> G` |
| /// etc, then `simplify_hh_goal` is invoked to create a strand |
| /// that breaks the goal down. |
| /// |
| /// In terms of the NFTD paper, this corresponds to the *Program |
| /// Clause Resolution* step being applied eagerly, as many times |
| /// as possible. |
| fn push_initial_strands(&mut self, table: TableIndex) { |
| // Instantiate the table goal with fresh inference variables. |
| let table_goal = self.tables[table].table_goal.clone(); |
| self.context.clone().instantiate_ucanonical_goal( |
| &table_goal, |
| PushInitialStrandsInstantiated { table, this: self }, |
| ); |
| |
| struct PushInitialStrandsInstantiated<'a, C: Context + 'a, CO: ContextOps<C> + 'a> { |
| table: TableIndex, |
| this: &'a mut Forest<C, CO>, |
| } |
| |
| impl<C: Context, CO: ContextOps<C>> WithInstantiatedUCanonicalGoal<C> |
| for PushInitialStrandsInstantiated<'a, C, CO> { |
| type Output = (); |
| |
| fn with<I: Context>( |
| self, |
| infer: &mut dyn InferenceTable<C, I>, |
| subst: I::Substitution, |
| environment: I::Environment, |
| goal: I::Goal, |
| ) { |
| let PushInitialStrandsInstantiated { table, this } = self; |
| this.push_initial_strands_instantiated(table, infer, subst, environment, goal); |
| } |
| } |
| } |
| |
| fn push_initial_strands_instantiated<I: Context>( |
| &mut self, |
| table: TableIndex, |
| infer: &mut dyn InferenceTable<C, I>, |
| subst: I::Substitution, |
| environment: I::Environment, |
| goal: I::Goal, |
| ) { |
| let table_ref = &mut self.tables[table]; |
| match infer.into_hh_goal(goal) { |
| HhGoal::DomainGoal(domain_goal) => { |
| let clauses = infer.program_clauses(&environment, &domain_goal); |
| for clause in clauses { |
| debug!("program clause = {:#?}", clause); |
| if let Ok(resolvent) = |
| infer.resolvent_clause(&environment, &domain_goal, &subst, &clause) |
| { |
| info!("pushing initial strand with ex-clause: {:#?}", &resolvent,); |
| table_ref.push_strand(CanonicalStrand { |
| canonical_ex_clause: resolvent, |
| selected_subgoal: None, |
| }); |
| } |
| } |
| } |
| |
| hh_goal => { |
| // `canonical_goal` is an HH goal. We can simplify it |
| // into a series of *literals*, all of which must be |
| // true. Thus, in EWFS terms, we are effectively |
| // creating a single child of the `A :- A` goal that |
| // is like `A :- B, C, D` where B, C, and D are the |
| // simplified subgoals. You can think of this as |
| // applying built-in "meta program clauses" that |
| // reduce HH goals into Domain goals. |
| if let Ok(ex_clause) = |
| Self::simplify_hh_goal(&mut *infer, subst, &environment, hh_goal) |
| { |
| info!( |
| "pushing initial strand with ex-clause: {:#?}", |
| infer.debug_ex_clause(&ex_clause), |
| ); |
| table_ref.push_strand(Self::canonicalize_strand(Strand { |
| infer, |
| ex_clause, |
| selected_subgoal: None, |
| })); |
| } |
| } |
| } |
| } |
| |
| /// Given a selected positive subgoal, applies the subgoal |
| /// abstraction function to yield the canonical form that will be |
| /// used to pick a table. Typically, this abstraction has no |
| /// effect, and hence we are simply returning the canonical form |
| /// of `subgoal`, but if the subgoal is getting too big, we may |
| /// truncate the goal to ensure termination. |
| /// |
| /// This technique is described in the SA paper. |
| fn abstract_positive_literal<I: Context>( |
| &mut self, |
| infer: &mut dyn InferenceTable<C, I>, |
| subgoal: &I::GoalInEnvironment, |
| ) -> C::CanonicalGoalInEnvironment { |
| // Subgoal abstraction: Rather than looking up the table for |
| // `selected_goal` directly, first apply the truncation |
| // function. This may introduce fresh variables, making the |
| // goal that we are looking up more general, and forcing us to |
| // reuse an existing table. For example, if we had a selected |
| // goal of |
| // |
| // // Vec<Vec<Vec<Vec<i32>>>>: Sized |
| // |
| // we might now produce a truncated goal of |
| // |
| // // Vec<Vec<?T>>: Sized |
| // |
| // Obviously, the answer we are looking for -- if it exists -- will be |
| // found amongst the answers of this new, truncated goal. |
| // |
| // Subtle point: Note that the **selected goal** remains |
| // unchanged and will be carried over into the "pending |
| // clause" for the positive link on the new subgoal. This |
| // means that if our new, truncated subgoal produces |
| // irrelevant answers (e.g., `Vec<Vec<u32>>: Sized`), they |
| // will fail to unify with our selected goal, producing no |
| // resolvent. |
| match infer.truncate_goal(subgoal) { |
| None => infer.canonicalize_goal(subgoal), |
| Some(truncated_subgoal) => { |
| debug!("truncated={:?}", truncated_subgoal); |
| infer.canonicalize_goal(&truncated_subgoal) |
| } |
| } |
| } |
| |
| /// Given a selected negative subgoal, the subgoal is "inverted" |
| /// (see `InferenceTable<C, I>::invert`) and then potentially truncated |
| /// (see `abstract_positive_literal`). The result subgoal is |
| /// canonicalized. In some cases, this may return `None` and hence |
| /// fail to yield a useful result, for example if free existential |
| /// variables appear in `subgoal` (in which case the execution is |
| /// said to "flounder"). |
| fn abstract_negative_literal<I: Context>( |
| &mut self, |
| infer: &mut dyn InferenceTable<C, I>, |
| subgoal: &I::GoalInEnvironment, |
| ) -> Option<C::CanonicalGoalInEnvironment> { |
| // First, we have to check that the selected negative literal |
| // is ground, and invert any universally quantified variables. |
| // |
| // DIVERGENCE -- In the RR paper, to ensure completeness, they |
| // permit non-ground negative literals, but only consider |
| // them to succeed when the target table has no answers at |
| // all. This is equivalent inverting those free existentials |
| // into universals, as discussed in the comments of |
| // `invert`. This is clearly *sound*, but the completeness is |
| // a subtle point. In particular, it can cause **us** to reach |
| // false conclusions, because e.g. given a program like |
| // (selected left-to-right): |
| // |
| // not { ?T: Copy }, ?T = Vec<u32> |
| // |
| // we would select `not { ?T: Copy }` first. For this goal to |
| // succeed we would require that -- effectively -- `forall<T> |
| // { not { T: Copy } }`, which clearly doesn't hold. (In the |
| // terms of RR, we would require that the table for `?T: Copy` |
| // has failed before we can continue.) |
| // |
| // In the RR paper, this is acceptable because they assume all |
| // of their input programs are both **normal** (negative |
| // literals are selected after positive ones) and **safe** |
| // (all free variables in negative literals occur in positive |
| // literals). It is plausible for us to guarantee "normal" |
| // form, we can reorder clauses as we need. I suspect we can |
| // guarantee safety too, but I have to think about it. |
| // |
| // For now, we opt for the safer route of terming such |
| // executions as floundering, because I think our use of |
| // negative goals is sufficiently limited we can get away with |
| // it. The practical effect is that we will judge more |
| // executions as floundering than we ought to (i.e., where we |
| // could instead generate an (imprecise) result). As you can |
| // see a bit later, we also diverge in some other aspects that |
| // affect completeness when it comes to subgoal abstraction. |
| let inverted_subgoal = infer.invert_goal(subgoal)?; |
| |
| // DIVERGENCE |
| // |
| // If the negative subgoal has grown so large that we would have |
| // to truncate it, we currently just abort the computation |
| // entirely. This is not necessary -- the SA paper makes no |
| // such distinction, for example, and applies truncation equally |
| // for positive/negative literals. However, there are some complications |
| // that arise that I do not wish to deal with right now. |
| // |
| // Let's work through an example to show you what I |
| // mean. Imagine we have this (negative) selected literal; |
| // hence `selected_subgoal` will just be the inner part: |
| // |
| // // not { Vec<Vec<Vec<Vec<i32>>>>: Sized } |
| // // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| // // `selected_goal` |
| // |
| // (In this case, the `inverted_subgoal` would be the same, |
| // since there are no free universal variables.) |
| // |
| // If truncation **doesn't apply**, we would go and lookup the |
| // table for the selected goal (`Vec<Vec<..>>: Sized`) and see |
| // whether it has any answers. If it does, and they are |
| // definite, then this negative literal is false. We don't |
| // really care even how many answers there are and so forth |
| // (if the goal is ground, as in this case, there can be at |
| // most one definite answer, but if there are universals, then |
| // the inverted goal would have variables; even so, a single |
| // definite answer suffices to show that the `not { .. }` goal |
| // is false). |
| // |
| // Truncation muddies the water, because the table may |
| // generate answers that are not relevant to our original, |
| // untracted literal. Suppose that we truncate the selected |
| // goal to: |
| // |
| // // Vec<Vec<T>: Sized |
| // |
| // Clearly this table will have some solutions that don't |
| // apply to us. e.g., `Vec<Vec<u32>>: Sized` is a solution to |
| // this table, but that doesn't imply that `not { |
| // Vec<Vec<Vec<..>>>: Sized }` is false. |
| // |
| // This can be made to work -- we carry along the original |
| // selected goal when we establish links between tables, and |
| // we could use that to screen the resulting answers. (There |
| // are some further complications around the fact that |
| // selected goal may contain universally quantified free |
| // variables that have been inverted, as discussed in the |
| // prior paragraph above.) I just didn't feel like dealing |
| // with it yet. |
| match infer.truncate_goal(&inverted_subgoal) { |
| Some(_) => None, |
| None => Some(infer.canonicalize_goal(&inverted_subgoal)), |
| } |
| } |
| |
| /// Invoked when we have selected a positive literal, created its |
| /// table, and selected a particular answer index N we are looking |
| /// for. Searches for that answer. If we find one, we can do two things: |
| /// |
| /// - create a new strand with the same selected subgoal, but searching for the |
| /// answer with index N+1 |
| /// - use the answer to resolve our selected literal and select the next subgoal |
| /// in this strand to pursue |
| /// |
| /// When an answer is found, that corresponds to *Positive Return* |
| /// from the NFTD paper. |
| fn pursue_positive_subgoal( |
| &mut self, |
| depth: StackIndex, |
| mut strand: Strand<'_, C, impl Context>, |
| selected_subgoal: &SelectedSubgoal<C>, |
| ) -> StrandResult<C, ()> { |
| let table = self.stack[depth].table; |
| let SelectedSubgoal { |
| subgoal_index, |
| subgoal_table, |
| answer_index, |
| ref universe_map, |
| } = *selected_subgoal; |
| |
| match self.ensure_answer_recursively(subgoal_table, answer_index) { |
| Ok(EnsureSuccess::AnswerAvailable) => { |
| // The given answer is available; we'll process it below. |
| } |
| Ok(EnsureSuccess::Coinductive) => { |
| // This is a co-inductive cycle. That is, this table |
| // appears somewhere higher on the stack, and has now |
| // recursively requested an answer for itself. That |
| // means that our subgoal is unconditionally true, so |
| // we can drop it and pursue the next thing. |
| assert!( |
| self.tables[table].coinductive_goal |
| && self.tables[subgoal_table].coinductive_goal |
| ); |
| let Strand { |
| infer, |
| mut ex_clause, |
| selected_subgoal: _, |
| } = strand; |
| ex_clause.subgoals.remove(subgoal_index); |
| return self.pursue_strand_recursively( |
| depth, |
| Strand { |
| infer, |
| ex_clause, |
| selected_subgoal: None, |
| }, |
| ); |
| } |
| Err(RecursiveSearchFail::NoMoreSolutions) => { |
| info!("pursue_positive_subgoal: no more solutions"); |
| return Err(StrandFail::NoSolution); |
| } |
| Err(RecursiveSearchFail::QuantumExceeded) => { |
| // We'll have to revisit this strand later |
| info!("pursue_positive_subgoal: quantum exceeded"); |
| self.tables[table].push_strand(Self::canonicalize_strand(strand)); |
| return Err(StrandFail::QuantumExceeded); |
| } |
| Err(RecursiveSearchFail::Cycle(minimums)) => { |
| info!( |
| "pursue_positive_subgoal: cycle with minimums {:?}", |
| minimums |
| ); |
| let canonical_strand = Self::canonicalize_strand(strand); |
| return Err(StrandFail::Cycle(canonical_strand, minimums)); |
| } |
| } |
| |
| // Whichever way this particular answer turns out, there may |
| // yet be *more* answers. Enqueue that alternative for later. |
| self.push_strand_pursuing_next_answer(depth, &mut strand, selected_subgoal); |
| |
| // OK, let's follow *this* answer and see where it leads. |
| let Strand { |
| infer, |
| mut ex_clause, |
| selected_subgoal: _, |
| } = strand; |
| let subgoal = match ex_clause.subgoals.remove(subgoal_index) { |
| Literal::Positive(g) => g, |
| Literal::Negative(g) => panic!( |
| "pursue_positive_subgoal invoked with negative selected literal: {:?}", |
| g |
| ), |
| }; |
| |
| let table_goal = &CO::map_goal_from_canonical(&universe_map, |
| &CO::canonical(&self.tables[subgoal_table].table_goal)); |
| let answer_subst = |
| &CO::map_subst_from_canonical(&universe_map, &self.answer(subgoal_table, answer_index).subst); |
| match infer.apply_answer_subst(ex_clause, &subgoal, table_goal, answer_subst) { |
| Ok(mut ex_clause) => { |
| // If the answer had delayed literals, we have to |
| // ensure that `ex_clause` is also delayed. This is |
| // the SLG FACTOR operation, though NFTD just makes it |
| // part of computing the SLG resolvent. |
| { |
| let answer = self.answer(subgoal_table, answer_index); |
| if !answer.delayed_literals.is_empty() { |
| ex_clause.delayed_literals.push(DelayedLiteral::Positive( |
| subgoal_table, |
| infer.sink_answer_subset(&answer.subst), |
| )); |
| } |
| } |
| |
| // Apply answer abstraction. |
| let ex_clause = self.truncate_returned(ex_clause, &mut *infer); |
| |
| self.pursue_strand_recursively( |
| depth, |
| Strand { |
| infer, |
| ex_clause, |
| selected_subgoal: None, |
| }, |
| ) |
| } |
| |
| // This answer led nowhere. Give up for now, but of course |
| // there may still be other strands to pursue, so return |
| // `QuantumExceeded`. |
| Err(NoSolution) => { |
| info!("pursue_positive_subgoal: answer not unifiable -> NoSolution"); |
| Err(StrandFail::NoSolution) |
| } |
| } |
| } |
| |
| /// Used whenever we process an answer (whether new or cached) on |
| /// a positive edge (the SLG POSITIVE RETURN operation). Truncates |
| /// the resolvent (or factor) if it has grown too large. |
| fn truncate_returned<I: Context>( |
| &self, |
| ex_clause: ExClause<I>, |
| infer: &mut dyn InferenceTable<C, I>, |
| ) -> ExClause<I> { |
| // DIVERGENCE |
| // |
| // In the original RR paper, truncation is only applied |
| // when the result of resolution is a new answer (i.e., |
| // `ex_clause.subgoals.is_empty()`). I've chosen to be |
| // more aggressive here, precisely because or our extended |
| // semantics for unification. In particular, unification |
| // can insert new goals, so I fear that positive feedback |
| // loops could still run indefinitely in the original |
| // formulation. I would like to revise our unification |
| // mechanism to avoid that problem, in which case this could |
| // be tightened up to be more like the original RR paper. |
| // |
| // Still, I *believe* this more aggressive approx. should |
| // not interfere with any of the properties of the |
| // original paper. In particular, applying truncation only |
| // when the resolvent has no subgoals seems like it is |
| // aimed at giving us more times to eliminate this |
| // ambiguous answer. |
| |
| match infer.truncate_answer(&ex_clause.subst) { |
| // No need to truncate? Just propagate the resolvent back. |
| None => ex_clause, |
| |
| // Resolvent got too large. Have to introduce approximation. |
| Some(truncated_subst) => { |
| // DIVERGENCE |
| // |
| // In RR, `self.delayed_literals` would be |
| // preserved. I have chosen to drop them. Keeping |
| // them does allow for the possibility of |
| // eliminating this answer if any of them turn out |
| // to be satisfiable. However, it also introduces |
| // an annoying edge case I didn't want to think |
| // about -- one which, interestingly, the paper |
| // did not discuss, which may indicate it is |
| // impossible for some subtle reason. In |
| // particular, a truncated delayed literal has a |
| // sort of inverse semantics. i.e. if we convert |
| // `Foo :- ~Bar(Rc<Rc<u32>>) |` to `Foo :- |
| // ~Bar(Rc<X>), Unknown |`, then this could be |
| // invalidated by an instance of `Bar(Rc<i32>)`, |
| // which is irrelevant to the original |
| // clause. (There is an additional annoyance, |
| // which is that we may not have tried to solve |
| // `Bar(Rc<X>)` at all.) |
| |
| ExClause { |
| subst: truncated_subst, |
| delayed_literals: vec![DelayedLiteral::CannotProve(())], |
| constraints: vec![], |
| subgoals: vec![], |
| } |
| } |
| } |
| } |
| |
| // We can recursive arbitrarily deep while pursuing a strand, so |
| // check in case we have to grow the stack. |
| fn pursue_strand_recursively( |
| &mut self, |
| depth: StackIndex, |
| strand: Strand<'_, C, impl Context>, |
| ) -> StrandResult<C, ()> { |
| ::maybe_grow_stack(|| self.pursue_strand(depth, strand)) |
| } |
| |
| /// Invoked when we have found a successful answer to the given |
| /// table. Queues up a strand to look for the *next* answer from |
| /// that table. |
| fn push_strand_pursuing_next_answer( |
| &mut self, |
| depth: StackIndex, |
| strand: &mut Strand<'_, C, impl Context>, |
| selected_subgoal: &SelectedSubgoal<C>, |
| ) { |
| let table = self.stack[depth].table; |
| let mut selected_subgoal = selected_subgoal.clone(); |
| selected_subgoal.answer_index.increment(); |
| self.tables[table].push_strand(Self::canonicalize_strand_from( |
| &mut *strand.infer, |
| &strand.ex_clause, |
| Some(selected_subgoal), |
| )); |
| } |
| |
| fn pursue_negative_subgoal( |
| &mut self, |
| depth: StackIndex, |
| strand: Strand<'_, C, impl Context>, |
| selected_subgoal: &SelectedSubgoal<C>, |
| ) -> StrandResult<C, ()> { |
| let table = self.stack[depth].table; |
| let SelectedSubgoal { |
| subgoal_index: _, |
| subgoal_table, |
| answer_index, |
| universe_map: _, |
| } = *selected_subgoal; |
| |
| // In the match below, we will either (a) return early with an |
| // error or some kind or (b) continue on to pursue this strand |
| // further. We continue onward in the case where we either |
| // proved that `answer_index` does not exist (in which case |
| // the negative literal is true) or if we found a delayed |
| // literal (in which case the negative literal *may* be true). |
| // Before exiting the match, then, we set `delayed_literal` to |
| // either `Some` or `None` depending. |
| let delayed_literal: Option<DelayedLiteral<_>>; |
| match self.ensure_answer_recursively(subgoal_table, answer_index) { |
| Ok(EnsureSuccess::AnswerAvailable) => { |
| if self.answer(subgoal_table, answer_index).is_unconditional() { |
| // We want to disproval the subgoal, but we |
| // have an unconditional answer for the subgoal, |
| // therefore we have failed to disprove it. |
| info!("pursue_negative_subgoal: found unconditional answer to neg literal -> NoSolution"); |
| return Err(StrandFail::NoSolution); |
| } |
| |
| // Got back a conditional answer. We neither succeed |
| // nor fail yet; so what we do is to delay the |
| // selected literal and keep going. |
| // |
| // This corresponds to the Delaying action in NFTD. |
| // It also interesting to compare this with the EWFS |
| // paper; there, when we encounter a delayed cached |
| // answer in `negative_subgoal`, we do not immediately |
| // convert to a delayed literal, but instead simply |
| // stop. However, in EWFS, we *do* add the strand to |
| // the table as a negative pending subgoal, and also |
| // update the link to depend negatively on the |
| // table. Then later, when all pending work from that |
| // table is completed, all negative links are |
| // converted to delays. |
| delayed_literal = Some(DelayedLiteral::Negative(subgoal_table)); |
| } |
| |
| Ok(EnsureSuccess::Coinductive) => { |
| // This is a co-inductive cycle. That is, this table |
| // appears somewhere higher on the stack, and has now |
| // recursively requested an answer for itself. That |
| // means that our subgoal is unconditionally true, so |
| // our negative goal fails. |
| info!("pursue_negative_subgoal: found coinductive answer to neg literal -> NoSolution"); |
| return Err(StrandFail::NoSolution); |
| } |
| |
| Err(RecursiveSearchFail::Cycle(minimums)) => { |
| // We depend on `not(subgoal)`. For us to continue, |
| // `subgoal` must be completely evaluated. Therefore, |
| // we depend (negatively) on the minimum link of |
| // `subgoal` as a whole -- it doesn't matter whether |
| // it's pos or neg. |
| let min = minimums.minimum_of_pos_and_neg(); |
| info!( |
| "pursue_negative_subgoal: found neg cycle at depth {:?}", |
| min |
| ); |
| let canonical_strand = Self::canonicalize_strand(strand); |
| return Err(StrandFail::Cycle( |
| canonical_strand, |
| Minimums { |
| positive: self.stack[depth].dfn, |
| negative: min, |
| }, |
| )); |
| } |
| |
| Err(RecursiveSearchFail::NoMoreSolutions) => { |
| // This answer does not exist. Huzzah, happy days are |
| // here again! =) We can just remove this subgoal and continue |
| // with no need for a delayed literal. |
| delayed_literal = None; |
| } |
| |
| // Learned nothing yet. Have to try again some other time. |
| Err(RecursiveSearchFail::QuantumExceeded) => { |
| info!("pursue_negative_subgoal: quantum exceeded"); |
| self.tables[table].push_strand(Self::canonicalize_strand(strand)); |
| return Err(StrandFail::QuantumExceeded); |
| } |
| } |
| |
| // We have found that there is at least a *chance* that |
| // `answer_index` of the subgoal is a failure, so let's keep |
| // going. We can just remove the subgoal from the list without |
| // any need to unify things, because the subgoal must be |
| // ground (i). We may need to add a delayed literal, though (ii). |
| let Strand { |
| infer, |
| mut ex_clause, |
| selected_subgoal: _, |
| } = strand; |
| ex_clause.subgoals.remove(selected_subgoal.subgoal_index); // (i) |
| ex_clause.delayed_literals.extend(delayed_literal); // (ii) |
| self.pursue_strand_recursively( |
| depth, |
| Strand { |
| infer, |
| ex_clause, |
| selected_subgoal: None, |
| }, |
| ) |
| } |
| } |
| |
| trait WithInstantiatedStrand<C: Context, CO: AggregateOps<C>> { |
| type Output; |
| |
| fn with(self, strand: Strand<'_, C, impl Context>) -> Self::Output; |
| } |
| |
| struct PursueStrand<'a, C: Context + 'a, CO: ContextOps<C> + 'a> { |
| forest: &'a mut Forest<C, CO>, |
| depth: StackIndex, |
| } |
| |
| impl<C: Context, CO: ContextOps<C>> WithInstantiatedStrand<C, CO> for PursueStrand<'a, C, CO> { |
| type Output = StrandResult<C, ()>; |
| |
| fn with(self, strand: Strand<'_, C, impl Context>) -> Self::Output { |
| self.forest.pursue_strand(self.depth, strand) |
| } |
| } |
| |
| struct DelayStrandAfterCycle { |
| table: TableIndex, |
| } |
| |
| impl<C: Context, CO: ContextOps<C>> WithInstantiatedStrand<C, CO> for DelayStrandAfterCycle { |
| type Output = (CanonicalStrand<C>, TableIndex); |
| |
| fn with(self, strand: Strand<'_, C, impl Context>) -> Self::Output { |
| <Forest<C, CO>>::delay_strand_after_cycle(self.table, strand) |
| } |
| } |