Skip to content

Commit

Permalink
Make dyn Trait implement its super traits as well
Browse files Browse the repository at this point in the history
This implements the impl rules from rust-lang#203, as far as I understood them. It
doesn't do the well-formedness or object safety rules.
  • Loading branch information
flodiebold committed Apr 26, 2020
1 parent f5d1427 commit 47fc080
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 5 deletions.
12 changes: 12 additions & 0 deletions chalk-ir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1286,6 +1286,18 @@ impl<T: HasInterner> Binders<T> {
}
}

pub fn filter_map<U, OP>(self, op: OP) -> Option<Binders<U>>
where
OP: FnOnce(T) -> Option<U>,
U: HasInterner<Interner = T::Interner>,
{
let value = op(self.value)?;
Some(Binders {
binders: self.binders,
value,
})
}

pub fn map_ref<'a, U, OP>(&'a self, op: OP) -> Binders<U>
where
OP: FnOnce(&'a T) -> U,
Expand Down
84 changes: 79 additions & 5 deletions chalk-solve/src/clauses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,8 @@ fn program_clauses_that_could_match<I: Interner>(
// not `Clone`.
let self_ty = trait_ref.self_type_parameter(interner);
if let TyData::Dyn(dyn_ty) = self_ty.data(interner) {
// In this arm, `self_ty` is the `dyn Fn(&u8)`,
// and `bounded_ty` is the `exists<T> { .. }`
// clauses shown above.
// In this arm, `self_ty` is the `dyn Fn(&u8)`, and `dyn_ty` is
// the `exists<T> { .. }` clauses shown above.

// Turn free BoundVars in the type into new existentials. E.g.
// we might get some `dyn Foo<?X>`, and we don't want to return
Expand All @@ -262,8 +261,19 @@ fn program_clauses_that_could_match<I: Interner>(
let qwc =
exists_qwc.substitute(interner, &[self_ty.clone().cast(interner)]);

builder.push_binders(&qwc, |builder, wc| {
builder.push_fact(wc);
builder.push_binders(&qwc, |builder, wc| match &wc {
// For the implemented traits, we need to elaborate super traits and add where clauses from the trait
WhereClause::Implemented(trait_ref) => {
let mut seen_traits = FxHashSet::default();
push_dyn_ty_impl_clauses(
db,
builder,
trait_ref.clone(),
&mut seen_traits,
)
}
// Associated item bindings are just taken as facts (?)
WhereClause::AliasEq(_) => builder.push_fact(wc),
});
}
});
Expand Down Expand Up @@ -355,6 +365,70 @@ fn program_clauses_that_could_match<I: Interner>(
Ok(())
}

/// Generate `Implemented` clauses for a `dyn Trait` type. We need to generate
/// `Implemented` clauses for all super traits, and for each trait we require
/// its where clauses. (See #203.)
fn push_dyn_ty_impl_clauses<I: Interner>(
db: &dyn RustIrDatabase<I>,
builder: &mut ClauseBuilder<'_, I>,
trait_ref: TraitRef<I>,
seen_traits: &mut FxHashSet<TraitId<I>>,
) {
// We have some `dyn Trait`, and some `trait SuperTrait: WC`
// which is a super trait of `Trait` (including actually
// just being the same trait); then we want to push
// `Implemented(dyn Trait: SuperTrait) :- WC`.
let trait_datum = db.trait_datum(trait_ref.trait_id);
let wc = trait_datum
.binders
.map_ref(|td| &td.where_clauses)
.substitute(db.interner(), &trait_ref.substitution);

// Also, we need to find super traits and recurse
let super_trait_refs = trait_datum
.binders
.map_ref(|td| {
td.where_clauses
.iter()
.filter_map(|qwc| {
qwc.as_ref().filter_map(|wc| match wc {
WhereClause::Implemented(tr) => {
let self_ty = tr.self_type_parameter(db.interner());

// We're looking for where clauses
// of the form `Self: Trait`. That's
// ^1.0 because we're one binder in.
if self_ty.bound(db.interner())
!= Some(BoundVar::new(DebruijnIndex::ONE, 0))
{
return None;
}
// Avoid cycles
if !seen_traits.insert(tr.trait_id) {
return None;
}
Some(tr.clone())
}
WhereClause::AliasEq(_) => None,
})
})
.collect::<Vec<_>>()
})
.substitute(db.interner(), &trait_ref.substitution);

// Add our clause: (here because we still needed the substitution)
builder.push_clause(trait_ref, wc);

for q_super_trait_ref in super_trait_refs {
// XXX really not sure about the handling of binders
// here, maybe it would be more correct to skip where
// clauses with actual binders?
builder.push_binders(&q_super_trait_ref, |builder, super_trait_ref| {
push_dyn_ty_impl_clauses(db, builder, super_trait_ref, seen_traits);
});
}
}

/// Generate program clauses from the associated-type values
/// found in impls of the given trait. i.e., if `trait_id` = Iterator,
/// then we would generate program clauses from each `type Item = ...`
Expand Down
60 changes: 60 additions & 0 deletions tests/test/existential_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,66 @@ fn dyn_Foo_Bar() {
}
}

#[test]
fn dyn_super_trait_simple() {
test! {
program {
trait Foo<T> {}
trait Bar<T> where Self: Foo<T> {}

struct A {}
struct B {}
}

goal {
dyn Bar<A>: Bar<A>
} yields {
"Unique"
}

goal {
dyn Bar<A>: Foo<A>
} yields {
"Unique"
}

goal {
dyn Bar<A>: Foo<B>
} yields {
"No possible solution"
}

goal {
exists<T> {
dyn Bar<T>: Foo<B>
}
} yields {
"Unique; substitution [?0 := B], lifetime constraints []"
}
}
}

#[test]
fn dyn_super_trait_cycle() {
test! {
program {
trait Foo<T> where Self: Bar<T> {}
trait Bar<T> where Self: Foo<T> {}

struct A {}
struct B {}
}

// It doesn't matter that much what comes out here since such a cycle is
// disallowed; mostly this is making sure that we don't crash
goal {
dyn Bar<A>: Bar<A>
} yields {
"No possible solution"
}
}
}

#[test]
fn dyn_higher_ranked_type_arguments() {
test! {
Expand Down

0 comments on commit 47fc080

Please sign in to comment.