Skip to content

Commit

Permalink
Auto merge of #780 - lowr:patch/supertrait-assoc-bounds, r=jackh726
Browse files Browse the repository at this point in the history
Generate `Normalize` clauses for dyn and opaque types

When a trait `Trait` has a transitive supertrait defined like `trait SuperTrait: AnotherTrait<Assoc = Ty>`, we've been generating `impl AnotherTrait` for `dyn Trait` and `opaque type T: Trait` but without accounting for the associated type bound. This patch adds `Normalize` clauses that correspond to such bounds.

For example, given the following definition

```rust
trait SuperTrait where WC {
   type Assoc where AssocWC;
}
trait Trait: SuperTrait<Assoc = Ty> {}
```

we generate the following clauses

```rust
// for dyn Trait
Implemented(dyn Trait: SuperTrait) :- WC
Normalize(<dyn Trait as SuperTrait>::Assoc -> Ty) :- WC, AssocWC // this patch adds this
// for placeholder !T for opaque type T: Trait
Implemented(!T: SuperTrait) :- WC
Normalize(<!T as SuperTrait>::Assoc -> Ty) :- WC, AssocWC // this patch adds this
```

I think this doesn't contradict #203 as "we may legitimately assume that all things talking directly about `?Self` are true", but I'm not really sure if this is the right direction. One caveat is that this patch exacerbates "wastefulness" as in [this comment](https://github.com/rust-lang/chalk/blob/1b32e5d9286ca48c50683177419b6f1512a49be5/chalk-solve/src/clauses.rs#L446-L449) as it adds the `Normalize` clauses even when we're trying to prove other kinds of goals.

Fixes #777
  • Loading branch information
bors committed Jun 13, 2023
2 parents df8aa32 + 1346728 commit 1635ed5
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 36 deletions.
17 changes: 16 additions & 1 deletion chalk-solve/src/clauses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,10 @@ pub fn program_clauses_that_could_match<I: Interner>(

if let Some(well_known) = trait_datum.well_known {
builtin_traits::add_builtin_assoc_program_clauses(
db, builder, well_known, self_ty,
db,
builder,
well_known,
self_ty.clone(),
)?;
}

Expand All @@ -645,6 +648,18 @@ pub fn program_clauses_that_could_match<I: Interner>(
proj.associated_ty_id,
);
}

// When `self_ty` is dyn type or opaque type, there may be associated type bounds
// for which we generate `Normalize` clauses.
match self_ty.kind(interner) {
// FIXME: see the fixme for the analogous code for Implemented goals.
TyKind::Dyn(_) => dyn_ty::build_dyn_self_ty_clauses(db, builder, self_ty),
TyKind::OpaqueType(id, _) => {
db.opaque_ty_data(*id)
.to_program_clauses(builder, environment);
}
_ => {}
}
}
AliasTy::Opaque(_) => (),
},
Expand Down
127 changes: 92 additions & 35 deletions chalk-solve/src/clauses/super_traits.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,43 @@
use itertools::{Either, Itertools};
use rustc_hash::FxHashSet;

use super::builder::ClauseBuilder;
use crate::RustIrDatabase;
use crate::{split::Split, RustIrDatabase};
use chalk_ir::{
fold::shift::Shift, interner::Interner, Binders, BoundVar, DebruijnIndex, TraitId, TraitRef,
WhereClause,
fold::shift::Shift, interner::Interner, AliasEq, AliasTy, Binders, BoundVar, DebruijnIndex,
Normalize, ProjectionTy, TraitId, TraitRef, Ty, WhereClause,
};

/// Generate `Implemented` clauses for `dyn Trait` and opaque types. We need to generate
/// `Implemented` clauses for all super traits, and for each trait we require
/// its where clauses. (See #203.)
/// Generate `Implemented` and `Normalize` clauses for `dyn Trait` and opaque types.
/// We need to generate those clauses for all super traits, and for each trait we
/// require its where clauses. (See #203)
pub(super) fn push_trait_super_clauses<I: Interner>(
db: &dyn RustIrDatabase<I>,
builder: &mut ClauseBuilder<'_, I>,
trait_ref: TraitRef<I>,
) {
let interner = db.interner();
// Given`trait SuperTrait: WC`, which is a super trait
// Given `trait SuperTrait: WC`, which is a super trait
// of `Trait` (including actually just being the same trait);
// then we want to push
// - for `dyn Trait`:
// `Implemented(dyn Trait: SuperTrait) :- WC`.
// - for placeholder `!T` of `opaque type T: Trait = HiddenTy`:
// `Implemented(!T: SuperTrait) :- WC`

let super_trait_refs =
//
// When `SuperTrait` has `AliasEq` bounds like `trait SuperTrait: AnotherTrait<Assoc = Ty>`,
// we also push
// - for `dyn Trait`:
// `Normalize(<dyn Trait as AnotherTrait>::Assoc -> Ty) :- AssocWC, WC`
// - for placeholder `!T` of `opaque type T: Trait = HiddenTy`:
// `Normalize(<!T as AnotherTrait>::Assoc -> Ty) :- AssocWC, WC`
// where `WC` and `AssocWC` are the where clauses for `AnotherTrait` and `AnotherTrait::Assoc`
// respectively.
let (super_trait_refs, super_trait_proj) =
super_traits(db, trait_ref.trait_id).substitute(interner, &trait_ref.substitution);

for q_super_trait_ref in super_trait_refs {
builder.push_binders(q_super_trait_ref.clone(), |builder, super_trait_ref| {
builder.push_binders(q_super_trait_ref, |builder, super_trait_ref| {
let trait_datum = db.trait_datum(super_trait_ref.trait_id);
let wc = trait_datum
.where_clauses()
Expand All @@ -37,12 +46,40 @@ pub(super) fn push_trait_super_clauses<I: Interner>(
builder.push_clause(super_trait_ref, wc);
});
}

for q_super_trait_proj in super_trait_proj {
builder.push_binders(q_super_trait_proj, |builder, (proj, ty)| {
let assoc_ty_datum = db.associated_ty_data(proj.associated_ty_id);
let trait_datum = db.trait_datum(assoc_ty_datum.trait_id);
let assoc_wc = assoc_ty_datum
.binders
.map_ref(|b| &b.where_clauses)
.into_iter()
.map(|wc| wc.cloned().substitute(interner, &proj.substitution));

let impl_params = db.trait_parameters_from_projection(&proj);
let impl_wc = trait_datum
.where_clauses()
.into_iter()
.map(|wc| wc.cloned().substitute(interner, impl_params));
builder.push_clause(
Normalize {
alias: AliasTy::Projection(proj.clone()),
ty,
},
impl_wc.chain(assoc_wc),
);
});
}
}

pub fn super_traits<I: Interner>(
fn super_traits<I: Interner>(
db: &dyn RustIrDatabase<I>,
trait_id: TraitId<I>,
) -> Binders<Vec<Binders<TraitRef<I>>>> {
) -> Binders<(
Vec<Binders<TraitRef<I>>>,
Vec<Binders<(ProjectionTy<I>, Ty<I>)>>,
)> {
let interner = db.interner();
let mut seen_traits = FxHashSet::default();
let trait_datum = db.trait_datum(trait_id);
Expand All @@ -57,13 +94,21 @@ pub fn super_traits<I: Interner>(
},
);
let mut trait_refs = Vec::new();
go(db, trait_ref, &mut seen_traits, &mut trait_refs);
let mut aliases = Vec::new();
go(
db,
trait_ref,
&mut seen_traits,
&mut trait_refs,
&mut aliases,
);

fn go<I: Interner>(
db: &dyn RustIrDatabase<I>,
trait_ref: Binders<TraitRef<I>>,
seen_traits: &mut FxHashSet<TraitId<I>>,
trait_refs: &mut Vec<Binders<TraitRef<I>>>,
aliases: &mut Vec<Binders<(ProjectionTy<I>, Ty<I>)>>,
) {
let interner = db.interner();
let trait_id = trait_ref.skip_binders().trait_id;
Expand All @@ -73,32 +118,39 @@ pub fn super_traits<I: Interner>(
}
trait_refs.push(trait_ref.clone());
let trait_datum = db.trait_datum(trait_id);
let super_trait_refs = trait_datum
let (super_trait_refs, super_trait_projs): (Vec<_>, Vec<_>) = 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());
.filter(|qwc| {
let trait_ref = match qwc.skip_binders() {
WhereClause::Implemented(tr) => tr.clone(),
WhereClause::AliasEq(AliasEq {
alias: AliasTy::Projection(p),
..
}) => db.trait_ref_from_projection(p),
_ => return false,
};
// We're looking for where clauses of the form
// `Self: Trait` or `<Self as Trait>::Assoc`. `Self` is
// ^1.0 because we're one binder in.
trait_ref.self_type_parameter(interner).bound_var(interner)
== Some(BoundVar::new(DebruijnIndex::ONE, 0))
})
.cloned()
.partition_map(|qwc| {
let (value, binders) = qwc.into_value_and_skipped_binders();

// 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_var(db.interner())
!= Some(BoundVar::new(DebruijnIndex::ONE, 0))
{
return None;
}
Some(tr.clone())
}
WhereClause::AliasEq(_) => None,
WhereClause::LifetimeOutlives(..) => None,
WhereClause::TypeOutlives(..) => None,
})
match value {
WhereClause::Implemented(tr) => Either::Left(Binders::new(binders, tr)),
WhereClause::AliasEq(AliasEq {
alias: AliasTy::Projection(p),
ty,
}) => Either::Right(Binders::new(binders, (p, ty))),
_ => unreachable!(),
}
})
.collect::<Vec<_>>()
})
// we skip binders on the trait_ref here and add them to the binders
// on the trait ref in the loop below. We could probably avoid this if
Expand All @@ -109,10 +161,15 @@ pub fn super_traits<I: Interner>(
// binders of super_trait_ref.
let actual_binders = Binders::new(trait_ref.binders.clone(), q_super_trait_ref);
let q_super_trait_ref = actual_binders.fuse_binders(interner);
go(db, q_super_trait_ref, seen_traits, trait_refs);
go(db, q_super_trait_ref, seen_traits, trait_refs, aliases);
}
for q_super_trait_proj in super_trait_projs {
let actual_binders = Binders::new(trait_ref.binders.clone(), q_super_trait_proj);
let q_super_trait_proj = actual_binders.fuse_binders(interner);
aliases.push(q_super_trait_proj);
}
seen_traits.remove(&trait_id);
}

Binders::new(trait_datum.binders.binders.clone(), trait_refs)
Binders::new(trait_datum.binders.binders.clone(), (trait_refs, aliases))
}
25 changes: 25 additions & 0 deletions tests/test/existential_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,31 @@ fn dyn_associated_type_binding() {
}
}

#[test]
fn dyn_assoc_in_super_trait_bounds() {
test! {
program {
trait Base { type Output; }
trait Trait where Self: Base<Output = usize> {}
}

goal {
forall<'s> {
dyn Trait + 's: Trait
}
} yields {
expect![[r#"Unique"#]]
}

goal {
forall<'s> {
dyn Trait + 's: Base
}
} yields {
expect![[r#"Unique"#]]
}
}
}
#[test]
fn dyn_well_formed() {
test! {
Expand Down
30 changes: 30 additions & 0 deletions tests/test/opaque_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,3 +283,33 @@ fn opaque_super_trait() {
}
}
}

#[test]
fn opaque_assoc_in_super_trait_bounds() {
test! {
program {
trait Foo {
type A;
}
trait EmptyFoo where Self: Foo<A = ()> { }
impl Foo for i32 {
type A = ();
}
impl<T> EmptyFoo for T where T: Foo<A = ()> { }

opaque type T: EmptyFoo = i32;
}

goal {
T: EmptyFoo
} yields {
expect![[r#"Unique"#]]
}

goal {
T: Foo
} yields {
expect![[r#"Unique"#]]
}
}
}
31 changes: 31 additions & 0 deletions tests/test/projection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1158,3 +1158,34 @@ fn projection_to_opaque() {
}
}
}

#[test]
fn projection_from_super_trait_bounds() {
test! {
program {
trait Foo {
type A;
}
trait Bar where Self: Foo<A = ()> {}
impl Foo for i32 {
type A = ();
}
impl Bar for i32 {}
opaque type Opaque: Bar = i32;
}

goal {
forall<'a> {
<dyn Bar + 'a as Foo>::A = ()
}
} yields {
expect![[r#"Unique"#]]
}

goal {
<Opaque as Foo>::A = ()
} yields {
expect![[r#"Unique"#]]
}
}
}

0 comments on commit 1635ed5

Please sign in to comment.