Skip to content

Commit b6322c3

Browse files
authored
Merge pull request #433 from adamrk/add-more-guidance
Include more guidance in ambiguous results
2 parents 5a3b871 + 22978b4 commit b6322c3

File tree

14 files changed

+669
-425
lines changed

14 files changed

+669
-425
lines changed

Cargo.lock

Lines changed: 296 additions & 242 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

chalk-engine/src/context.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ pub trait ContextOps<I: Interner, C: Context<I>>:
136136
/// Upcast this domain goal into a more general goal.
137137
fn into_goal(&self, domain_goal: DomainGoal<I>) -> Goal<I>;
138138

139+
fn is_trivial_constrained_substitution(
140+
&self,
141+
constrained_subst: &Canonical<ConstrainedSubst<I>>,
142+
) -> bool;
143+
139144
fn is_trivial_substitution(
140145
&self,
141146
u_canon: &UCanonical<InEnvironment<Goal<I>>>,

chalk-engine/src/forest.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,11 @@ impl<I: Interner, C: Context<I>> Forest<I, C> {
8383
if !answer.ambiguous {
8484
SubstitutionResult::Definite(answer.subst)
8585
} else {
86-
SubstitutionResult::Ambiguous(answer.subst)
86+
if context.is_trivial_constrained_substitution(&answer.subst) {
87+
SubstitutionResult::Floundered
88+
} else {
89+
SubstitutionResult::Ambiguous(answer.subst)
90+
}
8791
}
8892
}
8993
AnswerResult::Floundered => SubstitutionResult::Floundered,
@@ -155,7 +159,8 @@ impl<'me, I: Interner, C: Context<I>, CO: ContextOps<I, C>> AnswerStream<I>
155159
.root_answer(self.context, self.table, self.answer)
156160
{
157161
Ok(answer) => {
158-
return AnswerResult::Answer(answer.clone());
162+
debug!("Answer: {:?}", &answer);
163+
return AnswerResult::Answer(answer);
159164
}
160165

161166
Err(RootSearchFail::InvalidAnswer) => {

chalk-engine/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,12 @@ impl Minimums {
274274
}
275275
}
276276

277+
#[derive(Copy, Clone, Debug)]
278+
pub(crate) enum AnswerMode {
279+
Complete,
280+
Ambiguous,
281+
}
282+
277283
chalk_ir::copy_fold!(TableIndex);
278284
chalk_ir::copy_fold!(TimeStamp);
279285

chalk-engine/src/logic.rs

Lines changed: 182 additions & 108 deletions
Large diffs are not rendered by default.

chalk-engine/src/simplify.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ impl<I: Interner, C: Context<I>> Forest<I, C> {
7575
)));
7676
}
7777
GoalData::CannotProve(()) => {
78+
debug!("Marking Strand as ambiguous because of a `CannotProve` subgoal");
7879
ex_clause.ambiguous = true;
7980
}
8081
}

chalk-engine/src/table.rs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::strand::CanonicalStrand;
2-
use crate::Answer;
2+
use crate::{Answer, AnswerMode};
33
use rustc_hash::FxHashMap;
44
use std::collections::hash_map::Entry;
55
use std::collections::VecDeque;
@@ -40,6 +40,8 @@ pub(crate) struct Table<I: Interner> {
4040
/// Stores the active strands that we can "pull on" to find more
4141
/// answers.
4242
strands: VecDeque<CanonicalStrand<I>>,
43+
44+
pub(crate) answer_mode: AnswerMode,
4345
}
4446

4547
index_struct! {
@@ -60,6 +62,7 @@ impl<I: Interner> Table<I> {
6062
floundered: false,
6163
answers_hash: FxHashMap::default(),
6264
strands: VecDeque::new(),
65+
answer_mode: AnswerMode::Complete,
6366
}
6467
}
6568

@@ -80,20 +83,29 @@ impl<I: Interner> Table<I> {
8083
mem::replace(&mut self.strands, VecDeque::new())
8184
}
8285

83-
/// Remove the next strand from the queue as long as it meets the
84-
/// given criteria.
85-
pub(crate) fn dequeue_next_strand_if(
86+
pub(crate) fn drain_strands(
87+
&mut self,
88+
test: impl Fn(&CanonicalStrand<I>) -> bool,
89+
) -> VecDeque<CanonicalStrand<I>> {
90+
let old = mem::replace(&mut self.strands, VecDeque::new());
91+
let (test_in, test_out): (VecDeque<CanonicalStrand<I>>, VecDeque<CanonicalStrand<I>>) =
92+
old.into_iter().partition(test);
93+
let _ = mem::replace(&mut self.strands, test_out);
94+
test_in
95+
}
96+
97+
/// Remove the next strand from the queue that meets the given criteria
98+
pub(crate) fn dequeue_next_strand_that(
8699
&mut self,
87100
test: impl Fn(&CanonicalStrand<I>) -> bool,
88101
) -> Option<CanonicalStrand<I>> {
89-
let strand = self.strands.pop_front();
90-
if let Some(strand) = strand {
91-
if test(&strand) {
92-
return Some(strand);
93-
}
94-
self.strands.push_front(strand);
102+
let first = self.strands.iter().position(test);
103+
if let Some(first) = first {
104+
self.strands.rotate_left(first);
105+
self.strands.pop_front()
106+
} else {
107+
None
95108
}
96-
None
97109
}
98110

99111
/// Mark the table as floundered -- this also discards all pre-existing answers,

chalk-solve/src/infer.rs

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -150,14 +150,6 @@ impl<I: Interner> InferenceTable<I> {
150150
}
151151
}
152152

153-
/// Returns true if `var` has been bound.
154-
pub(crate) fn var_is_bound(&mut self, var: InferenceVar) -> bool {
155-
match self.unify.probe_value(EnaVariable::from(var)) {
156-
InferenceValue::Unbound(_) => false,
157-
InferenceValue::Bound(_) => true,
158-
}
159-
}
160-
161153
/// Given an unbound variable, returns its universe.
162154
///
163155
/// # Panics
@@ -169,44 +161,6 @@ impl<I: Interner> InferenceTable<I> {
169161
InferenceValue::Bound(_) => panic!("var_universe invoked on bound variable"),
170162
}
171163
}
172-
173-
/// Check whether the given substitution is the identity substitution in this
174-
/// inference context.
175-
pub(crate) fn is_trivial_substitution(
176-
&mut self,
177-
interner: &I,
178-
subst: &Substitution<I>,
179-
) -> bool {
180-
for value in subst.as_parameters(interner) {
181-
match value.data(interner) {
182-
GenericArgData::Ty(ty) => {
183-
if let Some(var) = ty.inference_var(interner) {
184-
if self.var_is_bound(var) {
185-
return false;
186-
}
187-
}
188-
}
189-
190-
GenericArgData::Lifetime(lifetime) => {
191-
if let Some(var) = lifetime.inference_var(interner) {
192-
if self.var_is_bound(var) {
193-
return false;
194-
}
195-
}
196-
}
197-
198-
GenericArgData::Const(constant) => {
199-
if let Some(var) = constant.inference_var(interner) {
200-
if self.var_is_bound(var) {
201-
return false;
202-
}
203-
}
204-
}
205-
}
206-
}
207-
208-
true
209-
}
210164
}
211165

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

chalk-solve/src/recursive/fulfill.rs

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

425425
let interner = self.solver.program.interner();
426+
let canonical_subst = self.infer.canonicalize(interner, &subst);
426427

427-
if self.infer.is_trivial_substitution(interner, &subst) {
428+
if canonical_subst.quantified.value.is_identity_subst(interner) {
428429
// In this case, we didn't learn *anything* definitively. So now, we
429430
// go one last time through the positive obligations, this time
430431
// applying even *tentative* inference suggestions, so that we can
@@ -441,10 +442,9 @@ impl<'s, 'db, I: Interner> Fulfill<'s, 'db, I> {
441442
} = self.prove(&goal, minimums).unwrap();
442443
if let Some(constrained_subst) = solution.constrained_subst() {
443444
self.apply_solution(free_vars, universes, constrained_subst);
444-
let subst = self
445-
.infer
446-
.canonicalize(self.solver.program.interner(), &subst);
447-
return Ok(Solution::Ambig(Guidance::Suggested(subst.quantified)));
445+
return Ok(Solution::Ambig(Guidance::Suggested(
446+
canonical_subst.quantified,
447+
)));
448448
}
449449
}
450450
}
@@ -471,10 +471,9 @@ impl<'s, 'db, I: Interner> Fulfill<'s, 'db, I> {
471471
// for sure what `T` must be (it could be either `Foo<Bar>` or
472472
// `Foo<Baz>`, but we *can* say for sure that it must be of the
473473
// form `Foo<?0>`.
474-
let subst = self
475-
.infer
476-
.canonicalize(self.solver.program.interner(), &subst);
477-
Ok(Solution::Ambig(Guidance::Definite(subst.quantified)))
474+
Ok(Solution::Ambig(Guidance::Definite(
475+
canonical_subst.quantified,
476+
)))
478477
}
479478
}
480479

chalk-solve/src/solve/slg.rs

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

193+
fn is_trivial_constrained_substitution(
194+
&self,
195+
constrained_subst: &Canonical<ConstrainedSubst<I>>,
196+
) -> bool {
197+
let interner = self.interner();
198+
constrained_subst.value.subst.is_identity_subst(interner)
199+
}
200+
193201
fn is_trivial_substitution(
194202
&self,
195203
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<I, SlgContext<I>> for SlgContextOps<'_,
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: 61 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
}
@@ -555,3 +557,58 @@ fn builtin_impl_enumeration() {
555557
}
556558
}
557559
}
560+
561+
/// Don't return definite guidance if we flounder after finding one solution.
562+
#[test]
563+
fn flounder_ambiguous() {
564+
test! {
565+
program {
566+
trait IntoIterator { }
567+
#[non_enumerable]
568+
trait OtherTrait { }
569+
570+
struct Ref<T> { }
571+
struct A { }
572+
573+
impl IntoIterator for Ref<A> { }
574+
impl<T> IntoIterator for Ref<T> where T: OtherTrait { }
575+
}
576+
577+
goal {
578+
exists<T> { Ref<T>: IntoIterator }
579+
} yields {
580+
"Ambiguous; no inference guidance"
581+
}
582+
}
583+
}
584+
585+
/// Don't return definite guidance if we are able to merge two solutions and the
586+
/// third one matches that as well (the fourth may not).
587+
#[test]
588+
fn normalize_ambiguous() {
589+
test! {
590+
program {
591+
trait IntoIterator { type Item; }
592+
593+
struct Ref<T> { }
594+
struct A { }
595+
struct B { }
596+
struct C { }
597+
598+
struct D { }
599+
600+
impl IntoIterator for Ref<A> { type Item = Ref<A>; }
601+
impl IntoIterator for Ref<B> { type Item = Ref<B>; }
602+
impl IntoIterator for Ref<C> { type Item = Ref<C>; }
603+
impl IntoIterator for Ref<D> { type Item = D; }
604+
}
605+
606+
goal {
607+
exists<T, U> {
608+
Normalize(<Ref<T> as IntoIterator>::Item -> U)
609+
}
610+
} yields {
611+
"Ambiguous; no inference guidance"
612+
}
613+
}
614+
}

0 commit comments

Comments
 (0)