Skip to content

Commit b591d04

Browse files
nikomatsakisnathanwhit
authored andcommitted
expand coinductive reasoning
1 parent 9fc1594 commit b591d04

File tree

3 files changed

+54
-6
lines changed

3 files changed

+54
-6
lines changed

chalk-engine/src/logic.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,9 +1531,14 @@ impl<'forest, I: Interner, C: Context<I> + 'forest, CO: ContextOps<I, C> + 'fore
15311531
/// would be true, since `Send` is an auto trait, which yields a
15321532
/// coinductive goal. But `top_of_stack_is_coinductive_from(0)` is
15331533
/// false, since `XXX` is not an auto trait.
1534+
#[instrument(level = "debug", skip(self))]
15341535
pub(super) fn top_of_stack_is_coinductive_from(&self, depth: StackIndex) -> bool {
15351536
StackIndex::iterate_range(self.stack.top_of_stack_from(depth)).all(|d| {
15361537
let table = self.stack[d].table;
1538+
debug!(
1539+
"d = {:?}, table = {:?}",
1540+
d, self.forest.tables[table].table_goal
1541+
);
15371542
self.forest.tables[table].coinductive_goal
15381543
})
15391544
}

chalk-solve/src/clauses.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,11 +383,19 @@ fn program_clauses_that_could_match<I: Interner>(
383383
})
384384
});
385385
}
386-
DomainGoal::WellFormed(WellFormed::Trait(trait_ref))
387-
| DomainGoal::LocalImplAllowed(trait_ref) => {
386+
DomainGoal::LocalImplAllowed(trait_ref) => {
388387
db.trait_datum(trait_ref.trait_id)
389388
.to_program_clauses(builder, environment);
390389
}
390+
391+
DomainGoal::WellFormed(WellFormed::Trait(trait_predicate)) => {
392+
let self_ty = trait_predicate.self_type_parameter(interner);
393+
if self_ty.bound_var(interner).is_some() || self_ty.inference_var(interner).is_some() {
394+
return Err(Floundered);
395+
}
396+
db.trait_datum(trait_predicate.trait_id)
397+
.to_program_clauses(builder, environment);
398+
}
391399
DomainGoal::ObjectSafe(trait_id) => {
392400
if builder.db.is_object_safe(*trait_id) {
393401
builder.push_fact(DomainGoal::ObjectSafe(*trait_id));

chalk-solve/src/coinductive_goal.rs

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@ pub trait IsCoinductive<I: Interner> {
1515
}
1616

1717
impl<I: Interner> IsCoinductive<I> for Goal<I> {
18+
/// A "coinductive" goal `G` is a goal where `G :- G` should be considered
19+
/// true. When we are doing trait solving, if we encounter a cycle of goals
20+
/// where solving `G1` requires `G2..Gn` and solving `Gn` requires `G1`,
21+
/// then our behavior depends on whether each goal `Gi` in that cycle is
22+
/// coinductive.
23+
///
24+
/// If all the goals are coinductive, then `G1` is considered provable,
25+
/// presuming that all the other subgoals for `G2..Gn` within can be fully
26+
/// proven.
27+
///
28+
/// If any goal `Gi` in the cycle is inductive, however, then the cycle is
29+
/// considered unprovable.
1830
fn is_coinductive(&self, db: &dyn RustIrDatabase<I>) -> bool {
1931
let interner = db.interner();
2032
match self.data(interner) {
@@ -28,10 +40,33 @@ impl<I: Interner> IsCoinductive<I> for Goal<I> {
2840
WhereClause::TypeOutlives(..) => false,
2941
},
3042
GoalData::DomainGoal(DomainGoal::WellFormed(WellFormed::Trait(..))) => true,
31-
GoalData::Quantified(QuantifierKind::ForAll, goal) => {
32-
goal.skip_binders().is_coinductive(db)
33-
}
34-
_ => false,
43+
GoalData::DomainGoal(_) => false,
44+
45+
// Goals like `forall<..> { G }` or `... => G` we will consider to
46+
// be coinductive if G is coinductive, although in practice I think
47+
// it would be ok to simply consider them coinductive all the time.
48+
GoalData::Quantified(_, goal) => goal.skip_binders().is_coinductive(db),
49+
GoalData::Implies(_, goal) => goal.is_coinductive(db),
50+
51+
// The "All(...)" quantifier is considered coinductive. This could
52+
// be somewhat surprising as you might have `All(Gc, Gi)` where `Gc`
53+
// is coinductive and `Gi` is inductive. This however is really no
54+
// different than defining a fresh coinductive goal like
55+
//
56+
// ```notrust
57+
// Gx :- Gc, Gi
58+
// ```
59+
//
60+
// and requiring `Gx` in place of `All(Gc, Gi)`, and that would be
61+
// perfectly reasonable.
62+
GoalData::All(..) => true,
63+
64+
// For simplicity, just assume these remaining types of goals must
65+
// be inductive, since there is no pressing reason to consider them
66+
// coinductive.
67+
GoalData::Not(_) => false,
68+
GoalData::EqGoal(_) => false,
69+
GoalData::CannotProve => false,
3570
}
3671
}
3772
}

0 commit comments

Comments
 (0)