Skip to content

Commit a856dda

Browse files
committed
include guidance for floundered goals
1 parent 15daa1a commit a856dda

File tree

9 files changed

+174
-67
lines changed

9 files changed

+174
-67
lines changed

chalk-engine/src/context.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,11 @@ pub trait ContextOps<C: Context>: Sized + Clone + Debug + AggregateOps<C> {
251251
/// Upcast this domain goal into a more general goal.
252252
fn into_goal(&self, domain_goal: C::DomainGoal) -> C::Goal;
253253

254+
fn is_trivial_constrained_substitution(
255+
&self,
256+
constrained_subst: &C::CanonicalConstrainedSubst,
257+
) -> bool;
258+
254259
fn is_trivial_substitution(
255260
&self,
256261
u_canon: &C::UCanonicalGoalInEnvironment,

chalk-engine/src/forest.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,12 @@ impl<C: Context> Forest<C> {
8989
if !answer.ambiguous {
9090
SubstitutionResult::Definite(context.constrained_subst_from_answer(answer))
9191
} else {
92-
SubstitutionResult::Ambiguous(context.constrained_subst_from_answer(answer))
92+
let subst = context.constrained_subst_from_answer(answer);
93+
if context.is_trivial_constrained_substitution(&subst) {
94+
SubstitutionResult::Floundered
95+
} else {
96+
SubstitutionResult::Ambiguous(subst)
97+
}
9398
}
9499
}
95100
AnswerResult::Floundered => SubstitutionResult::Floundered,

chalk-engine/src/logic.rs

Lines changed: 99 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ pub(super) enum RootSearchFail {
4141
}
4242

4343
/// This is returned when we try to select a subgoal for a strand.
44+
#[derive(PartialEq)]
4445
enum SubGoalSelection {
4546
/// A subgoal was successfully selected. It has already been checked
4647
/// to not be floundering. However, it may have an answer already, be
@@ -101,6 +102,27 @@ impl<C: Context> Forest<C> {
101102
ambiguous: answer.ambiguous,
102103
})
103104
}
105+
Err(RootSearchFail::Floundered) => {
106+
if state.stack.is_empty() {
107+
if let Some(answer) = state.forest.tables[table].answer(answer_index) {
108+
debug!("floundered but still has answer {:?}", answer);
109+
let has_delayed_subgoals = C::has_delayed_subgoals(&answer.subst);
110+
if has_delayed_subgoals {
111+
return Err(RootSearchFail::InvalidAnswer);
112+
}
113+
Ok(CompleteAnswer {
114+
subst: C::canonical_constrained_subst_from_canonical_constrained_answer(
115+
&answer.subst,
116+
),
117+
ambiguous: answer.ambiguous,
118+
})
119+
} else {
120+
Err(RootSearchFail::Floundered)
121+
}
122+
} else {
123+
Err(RootSearchFail::Floundered)
124+
}
125+
}
104126
Err(err) => Err(err),
105127
}
106128
}
@@ -504,20 +526,18 @@ impl<'forest, C: Context + 'forest, CO: ContextOps<C> + 'forest> SolveState<'for
504526
self.on_subgoal_selected(strand)?;
505527
continue;
506528
}
507-
SubGoalSelection::NoRemainingSubgoals => {
508-
match self.on_no_remaining_subgoals(strand) {
529+
selection @ SubGoalSelection::NoRemainingSubgoals
530+
| selection @ SubGoalSelection::Floundered => {
531+
match self.on_no_remaining_subgoals(
532+
strand,
533+
selection == SubGoalSelection::Floundered,
534+
) {
509535
NoRemainingSubgoalsResult::RootAnswerAvailable => return Ok(()),
510536
NoRemainingSubgoalsResult::RootSearchFail(e) => return Err(e),
511537
NoRemainingSubgoalsResult::Success => {}
512538
};
513539
continue;
514540
}
515-
SubGoalSelection::Floundered => {
516-
// The strand floundered when trying to select a subgoal.
517-
// This will always return a `RootSearchFail`, either because the
518-
// root table floundered or we yield with `QuantumExceeded`.
519-
return Err(self.on_subgoal_selection_flounder(strand));
520-
}
521541
}
522542
}
523543
None => {
@@ -895,10 +915,19 @@ impl<'forest, C: Context + 'forest, CO: ContextOps<C> + 'forest> SolveState<'for
895915
Ok(())
896916
}
897917

898-
fn on_no_remaining_subgoals(&mut self, strand: Strand<C>) -> NoRemainingSubgoalsResult {
899-
debug!("no remaining subgoals for the table");
900-
901-
match self.pursue_answer(strand) {
918+
fn on_no_remaining_subgoals(
919+
&mut self,
920+
strand: Strand<C>,
921+
floundered: bool,
922+
) -> NoRemainingSubgoalsResult {
923+
let possible_answer = if !floundered {
924+
debug!("no remaining subgoals for the table");
925+
self.pursue_answer(strand)
926+
} else {
927+
debug!("all remaining subgoals floundered for the table");
928+
self.pursue_answer_from_floundered(strand)
929+
};
930+
match possible_answer {
902931
Some(answer_index) => {
903932
debug!("answer is available");
904933

@@ -946,6 +975,14 @@ impl<'forest, C: Context + 'forest, CO: ContextOps<C> + 'forest> SolveState<'for
946975
}
947976
}
948977
None => {
978+
if floundered {
979+
// The strand floundered when trying to select a subgoal.
980+
// This will always return a `RootSearchFail`, either because the
981+
// root table floundered or we yield with `QuantumExceeded`.
982+
return NoRemainingSubgoalsResult::RootSearchFail(
983+
self.on_subgoal_selection_flounder(),
984+
);
985+
}
949986
debug!("answer is not available (or not new)");
950987

951988
// This table ned nowhere of interest
@@ -1018,16 +1055,13 @@ impl<'forest, C: Context + 'forest, CO: ContextOps<C> + 'forest> SolveState<'for
10181055
Some(Forest::canonicalize_strand(self.context, strand))
10191056
}
10201057

1021-
fn on_subgoal_selection_flounder(&mut self, strand: Strand<C>) -> RootSearchFail {
1058+
fn on_subgoal_selection_flounder(&mut self) -> RootSearchFail {
10221059
debug!("all subgoals floundered");
10231060

10241061
// We were unable to select a subgoal for this strand
10251062
// because all of them had floundered or because any one
10261063
// that we dependended on negatively floundered
10271064

1028-
// We discard this strand because it led nowhere of interest
1029-
drop(strand);
1030-
10311065
loop {
10321066
// This table is marked as floundered
10331067
let table = self.stack.top().table;
@@ -1288,6 +1322,55 @@ impl<'forest, C: Context + 'forest, CO: ContextOps<C> + 'forest> SolveState<'for
12881322
}
12891323
}
12901324

1325+
fn pursue_answer_from_floundered(&mut self, strand: Strand<C>) -> Option<AnswerIndex> {
1326+
let table = self.stack.top().table;
1327+
let Strand {
1328+
mut infer,
1329+
ex_clause:
1330+
ExClause {
1331+
subst,
1332+
constraints,
1333+
ambiguous: _,
1334+
subgoals,
1335+
delayed_subgoals,
1336+
answer_time: _,
1337+
floundered_subgoals: _,
1338+
},
1339+
selected_subgoal: _,
1340+
last_pursued_time: _,
1341+
} = strand;
1342+
assert!(subgoals.is_empty());
1343+
let subst = infer.canonicalize_answer_subst(
1344+
self.context.interner(),
1345+
subst,
1346+
constraints,
1347+
delayed_subgoals,
1348+
);
1349+
debug!(
1350+
"answer from floundered goal: table={:?}, subst={:?}",
1351+
table, subst
1352+
);
1353+
1354+
let answer = Answer {
1355+
subst,
1356+
ambiguous: true,
1357+
};
1358+
let is_trivial_answer = self
1359+
.context
1360+
.is_trivial_substitution(&self.forest.tables[table].table_goal, &answer.subst);
1361+
1362+
if is_trivial_answer {
1363+
None
1364+
} else {
1365+
if let Some(answer_index) = self.forest.tables[table].push_answer(answer) {
1366+
Some(answer_index)
1367+
} else {
1368+
info!("answer: not a new answer, returning None");
1369+
None
1370+
}
1371+
}
1372+
}
1373+
12911374
/// Invoked when a strand represents an **answer**. This means
12921375
/// that the strand has no subgoals left. There are two possibilities:
12931376
///

chalk-solve/src/infer.rs

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -149,14 +149,6 @@ impl<I: Interner> InferenceTable<I> {
149149
Some(v1)
150150
}
151151

152-
/// Returns true if `var` has been bound.
153-
pub(crate) fn var_is_bound(&mut self, var: InferenceVar) -> bool {
154-
match self.unify.probe_value(EnaVariable::from(var)) {
155-
InferenceValue::Unbound(_) => false,
156-
InferenceValue::Bound(_) => true,
157-
}
158-
}
159-
160152
/// Finds the type to which `var` is bound, returning `None` if it is not yet
161153
/// bound.
162154
///
@@ -199,36 +191,6 @@ impl<I: Interner> InferenceTable<I> {
199191
InferenceValue::Bound(_) => panic!("var_universe invoked on bound variable"),
200192
}
201193
}
202-
203-
/// Check whether the given substitution is the identity substitution in this
204-
/// inference context.
205-
pub(crate) fn is_trivial_substitution(
206-
&mut self,
207-
interner: &I,
208-
subst: &Substitution<I>,
209-
) -> bool {
210-
for value in subst.as_parameters(interner) {
211-
match value.data(interner) {
212-
ParameterKind::Ty(ty) => {
213-
if let Some(var) = ty.inference_var(interner) {
214-
if self.var_is_bound(var) {
215-
return false;
216-
}
217-
}
218-
}
219-
220-
ParameterKind::Lifetime(lifetime) => {
221-
if let Some(var) = lifetime.inference_var(interner) {
222-
if self.var_is_bound(var) {
223-
return false;
224-
}
225-
}
226-
}
227-
}
228-
}
229-
230-
true
231-
}
232194
}
233195

234196
pub(crate) trait ParameterEnaVariableExt<I: Interner> {

chalk-solve/src/recursive/fulfill.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -421,8 +421,9 @@ impl<'s, 'db, I: Interner> Fulfill<'s, 'db, I> {
421421
// inference as an ambiguous solution.
422422

423423
let interner = self.solver.program.interner();
424+
let canonical_subst = self.infer.canonicalize(interner, &subst);
424425

425-
if self.infer.is_trivial_substitution(interner, &subst) {
426+
if canonical_subst.quantified.value.is_identity_subst(interner) {
426427
// In this case, we didn't learn *anything* definitively. So now, we
427428
// go one last time through the positive obligations, this time
428429
// applying even *tentative* inference suggestions, so that we can
@@ -439,10 +440,9 @@ impl<'s, 'db, I: Interner> Fulfill<'s, 'db, I> {
439440
} = self.prove(&goal, minimums).unwrap();
440441
if let Some(constrained_subst) = solution.constrained_subst() {
441442
self.apply_solution(free_vars, universes, constrained_subst);
442-
let subst = self
443-
.infer
444-
.canonicalize(self.solver.program.interner(), &subst);
445-
return Ok(Solution::Ambig(Guidance::Suggested(subst.quantified)));
443+
return Ok(Solution::Ambig(Guidance::Suggested(
444+
canonical_subst.quantified,
445+
)));
446446
}
447447
}
448448
}

chalk-solve/src/solve/slg.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,14 @@ impl<'me, I: Interner> context::ContextOps<SlgContext<I>> for SlgContextOps<'me,
275275
domain_goal.cast(self.program.interner())
276276
}
277277

278+
fn is_trivial_constrained_substitution(
279+
&self,
280+
constrained_subst: &Canonical<ConstrainedSubst<I>>,
281+
) -> bool {
282+
let interner = self.interner();
283+
constrained_subst.value.subst.is_identity_subst(interner)
284+
}
285+
278286
fn is_trivial_substitution(
279287
&self,
280288
u_canon: &UCanonical<InEnvironment<Goal<I>>>,

chalk-solve/src/solve/slg/aggregate.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,13 @@ impl<I: Interner> context::AggregateOps<SlgContext<I>> for SlgContextOps<'_, I>
4040
// Exactly 1 unconditional answer?
4141
let next_answer = answers.peek_answer(|| should_continue());
4242
if next_answer.is_quantum_exceeded() {
43-
return Some(Solution::Ambig(Guidance::Suggested(
44-
subst.map(interner, |cs| cs.subst),
45-
)));
43+
if subst.value.subst.is_identity_subst(interner) {
44+
return Some(Solution::Ambig(Guidance::Unknown));
45+
} else {
46+
return Some(Solution::Ambig(Guidance::Suggested(
47+
subst.map(interner, |cs| cs.subst),
48+
)));
49+
}
4650
}
4751
if next_answer.is_no_more_solutions() && !ambiguous {
4852
return Some(Solution::Unique(subst));

tests/test/impls.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,3 +639,41 @@ fn clauses_in_if_goals() {
639639
}
640640
}
641641
}
642+
643+
#[test]
644+
fn unify_types_in_ambiguous_impl() {
645+
test! {
646+
program {
647+
#[non_enumerable]
648+
trait Constraint {}
649+
trait Trait<T> {}
650+
struct A<T> {}
651+
impl<T> Trait<T> for A<T> where T: Constraint {}
652+
}
653+
654+
goal {
655+
exists<T,U> { A<T>: Trait<U> }
656+
} yields {
657+
"Ambiguous; definite substitution for<?U0> { [?0 := ^0.0, ?1 := ^0.0] }"
658+
}
659+
}
660+
}
661+
662+
#[test]
663+
fn unify_types_in_impl() {
664+
test! {
665+
program {
666+
#[non_enumerable]
667+
trait Constraint {}
668+
trait Trait<T> {}
669+
struct A<T> {}
670+
impl<T> Trait<T> for A<T> {}
671+
}
672+
673+
goal {
674+
exists<T,U> { A<T>: Trait<U> }
675+
} yields {
676+
"Unique; for<?U0> { substitution [?0 := ^0.0, ?1 := ^0.0], lifetime constraints [] }"
677+
}
678+
}
679+
}

tests/test/misc.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,11 @@ fn subgoal_cycle_uninhabited() {
176176
}
177177

178178
// Infinite recursion -> we flounder
179+
// Still return the necessary substitution T = Box<..>
179180
goal {
180181
exists<T> { T: Foo }
181182
} yields_first[SolverChoice::slg(2, None)] {
182-
"Floundered"
183+
"Ambiguous(for<?U0> { substitution [?0 := Box<^0.0>], lifetime constraints [] })"
183184
}
184185

185186
// Unsurprisingly, applying negation also flounders.
@@ -201,7 +202,7 @@ fn subgoal_cycle_uninhabited() {
201202
goal {
202203
exists<T> { T = Vec<Alice>, not { Vec<Vec<T>>: Foo } }
203204
} yields_first[SolverChoice::slg(2, None)] {
204-
"Floundered"
205+
"Ambiguous(substitution [?0 := Vec<Alice>], lifetime constraints [])"
205206
}
206207

207208
// Same query with larger threshold works fine, though.
@@ -216,7 +217,7 @@ fn subgoal_cycle_uninhabited() {
216217
forall<U> { if (U: Foo) { exists<T> { T: Foo } } }
217218
} yields_first[SolverChoice::slg(2, None)] {
218219
"substitution [?0 := !1_0], lifetime constraints []",
219-
"Floundered"
220+
"Ambiguous(for<?U1> { substitution [?0 := Box<^0.0>], lifetime constraints [] })"
220221
}
221222
}
222223
}
@@ -234,11 +235,12 @@ fn subgoal_cycle_inhabited() {
234235
}
235236

236237
// Exceeds size threshold -> flounder
238+
// Still return necessary substitution T = Box<..>
237239
goal {
238240
exists<T> { T: Foo }
239241
} yields_first[SolverChoice::slg(3, None)] {
240242
"substitution [?0 := Alice], lifetime constraints []",
241-
"Floundered"
243+
"Ambiguous(for<?U0> { substitution [?0 := Box<^0.0>], lifetime constraints [] })"
242244
}
243245
}
244246
}

0 commit comments

Comments
 (0)