Skip to content

Generate Normalize clauses for dyn and opaque types #780

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1134,3 +1134,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"#]]
}
}
}