Skip to content

Commit 91c24e3

Browse files
committed
Generalize program clause for AliasEq goal with nested alias
1 parent 1635ed5 commit 91c24e3

File tree

2 files changed

+98
-43
lines changed

2 files changed

+98
-43
lines changed

chalk-solve/src/clauses.rs

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -512,12 +512,7 @@ pub fn program_clauses_that_could_match<I: Interner>(
512512
// An alias could normalize to anything, including an
513513
// opaque type, so push a clause that asks for the self
514514
// type to be normalized and return.
515-
push_alias_alias_eq_clause(
516-
builder,
517-
proj.clone(),
518-
alias_eq.ty.clone(),
519-
alias.clone(),
520-
);
515+
push_alias_alias_eq_clause(builder, proj.clone(), alias.clone());
521516
return Ok(clauses);
522517
}
523518
TyKind::OpaqueType(opaque_ty_id, _) => {
@@ -781,12 +776,10 @@ fn push_alias_implemented_clause<I: Interner>(
781776
// TODO: instead generate clauses without reference to the specific type parameters of the goal?
782777
let generalized = generalize::Generalize::apply(interner, (trait_ref, alias));
783778
builder.push_binders(generalized, |builder, (trait_ref, alias)| {
784-
let binders = Binders::with_fresh_type_var(interner, |ty_var| ty_var);
785-
786779
// forall<..., T> {
787780
// <X as Y>::Z: Trait :- T: Trait, <X as Y>::Z == T
788781
// }
789-
builder.push_binders(binders, |builder, bound_var| {
782+
builder.push_bound_ty(|builder, bound_var| {
790783
let fresh_self_subst = Substitution::from_iter(
791784
interner,
792785
std::iter::once(bound_var.clone().cast(interner)).chain(
@@ -816,7 +809,6 @@ fn push_alias_implemented_clause<I: Interner>(
816809
fn push_alias_alias_eq_clause<I: Interner>(
817810
builder: &mut ClauseBuilder<'_, I>,
818811
projection_ty: ProjectionTy<I>,
819-
ty: Ty<I>,
820812
alias: AliasTy<I>,
821813
) {
822814
let interner = builder.interner();
@@ -827,43 +819,69 @@ fn push_alias_alias_eq_clause<I: Interner>(
827819
assert_eq!(*self_ty.kind(interner), TyKind::Alias(alias.clone()));
828820

829821
// TODO: instead generate clauses without reference to the specific type parameters of the goal?
830-
let generalized = generalize::Generalize::apply(interner, (projection_ty, ty, alias));
831-
builder.push_binders(generalized, |builder, (projection_ty, ty, alias)| {
832-
let binders = Binders::with_fresh_type_var(interner, |ty_var| ty_var);
833-
834-
// forall<..., T> {
822+
let generalized = generalize::Generalize::apply(interner, (projection_ty, alias));
823+
builder.push_binders(generalized, |builder, (projection_ty, alias)| {
824+
// Given the following canonical goal:
825+
//
826+
// ```
827+
// forall<...> {
828+
// <<X as Y>::A as Z>::B == W
829+
// }
830+
// ```
831+
//
832+
// we generate:
833+
//
834+
// ```
835+
// forall<..., T, U> {
835836
// <<X as Y>::A as Z>::B == U :- <T as Z>::B == U, <X as Y>::A == T
836837
// }
837-
builder.push_binders(binders, |builder, bound_var| {
838-
let (_, trait_args, assoc_args) = builder.db.split_projection(&projection_ty);
839-
let fresh_self_subst = Substitution::from_iter(
840-
interner,
841-
assoc_args
842-
.iter()
843-
.cloned()
844-
.chain(std::iter::once(bound_var.clone().cast(interner)))
845-
.chain(trait_args[1..].iter().cloned()),
846-
);
847-
let fresh_alias = AliasTy::Projection(ProjectionTy {
848-
associated_ty_id: projection_ty.associated_ty_id,
849-
substitution: fresh_self_subst,
850-
});
851-
builder.push_clause(
852-
DomainGoal::Holds(WhereClause::AliasEq(AliasEq {
853-
alias: AliasTy::Projection(projection_ty.clone()),
854-
ty: ty.clone(),
855-
})),
856-
&[
857-
DomainGoal::Holds(WhereClause::AliasEq(AliasEq {
858-
alias: fresh_alias,
859-
ty: ty.clone(),
860-
})),
838+
// ```
839+
//
840+
// `T` and `U` are `intermediate_eq_ty` and `eq_ty` respectively below.
841+
//
842+
// Note that we used to "reuse" `W` and push:
843+
//
844+
// ```
845+
// forall<..., T> {
846+
// <<X as Y>::A as Z>::B == W :- <T as Z>::B == W, <X as Y>::A == T
847+
// }
848+
// ```
849+
//
850+
// but it caused a cycle which led to false `NoSolution` under certain conditions, in
851+
// particular when `W` itself is a nested projection type. See test
852+
// `nested_proj_eq_nested_proj_should_flounder`.
853+
builder.push_bound_ty(|builder, intermediate_eq_ty| {
854+
builder.push_bound_ty(|builder, eq_ty| {
855+
let (_, trait_args, assoc_args) = builder.db.split_projection(&projection_ty);
856+
let fresh_self_subst = Substitution::from_iter(
857+
interner,
858+
assoc_args
859+
.iter()
860+
.cloned()
861+
.chain(std::iter::once(intermediate_eq_ty.clone().cast(interner)))
862+
.chain(trait_args[1..].iter().cloned()),
863+
);
864+
let fresh_alias = AliasTy::Projection(ProjectionTy {
865+
associated_ty_id: projection_ty.associated_ty_id,
866+
substitution: fresh_self_subst,
867+
});
868+
builder.push_clause(
861869
DomainGoal::Holds(WhereClause::AliasEq(AliasEq {
862-
alias: alias.clone(),
863-
ty: bound_var,
870+
alias: AliasTy::Projection(projection_ty.clone()),
871+
ty: eq_ty.clone(),
864872
})),
865-
],
866-
);
873+
&[
874+
DomainGoal::Holds(WhereClause::AliasEq(AliasEq {
875+
alias: fresh_alias,
876+
ty: eq_ty,
877+
})),
878+
DomainGoal::Holds(WhereClause::AliasEq(AliasEq {
879+
alias,
880+
ty: intermediate_eq_ty,
881+
})),
882+
],
883+
);
884+
});
867885
});
868886
});
869887
}

tests/test/projection.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,3 +1189,40 @@ fn projection_from_super_trait_bounds() {
11891189
}
11901190
}
11911191
}
1192+
1193+
#[test]
1194+
fn nested_proj_eq_nested_proj_should_flounder() {
1195+
test! {
1196+
program {
1197+
#[non_enumerable]
1198+
trait Trait1 {
1199+
type Assoc: Trait2;
1200+
}
1201+
#[non_enumerable]
1202+
trait Trait2 {
1203+
type Assoc;
1204+
}
1205+
1206+
impl Trait1 for () {
1207+
type Assoc = ();
1208+
}
1209+
impl Trait1 for i32 {
1210+
type Assoc = ();
1211+
}
1212+
impl Trait2 for () {
1213+
type Assoc = ();
1214+
}
1215+
}
1216+
1217+
goal {
1218+
exists<T, U> {
1219+
<<T as Trait1>::Assoc as Trait2>::Assoc = <<U as Trait1>::Assoc as Trait2>::Assoc
1220+
}
1221+
} yields[SolverChoice::slg_default()] {
1222+
// FIXME
1223+
expect![[r#"Ambiguous; definite substitution for<?U0> { [?0 := ^0.0, ?1 := ^0.0] }"#]]
1224+
} yields[SolverChoice::recursive_default()] {
1225+
expect![[r#"Ambiguous; no inference guidance"#]]
1226+
}
1227+
}
1228+
}

0 commit comments

Comments
 (0)