Skip to content

Commit da43826

Browse files
authored
Rollup merge of #139774 - compiler-errors:supertrait-alias, r=lcnr
Fix replacing supertrait aliases in `ReplaceProjectionWith` The new solver has a procedure called `predicates_for_object_candidate`, which elaborates the super-bounds and item-bounds that are required to hold for a dyn trait to implement something via a built-in object impl. In that procedure, there is a folder called `ReplaceProjectionWith` which is responsible for replacing projections that reference `Self`, so that we don't encounter cycles when we then go on to normalize those projections in the process of proving these super-bounds. That folder had a few problems: Firstly, it wasn't actually checking that this was a super bound originating from `Self`. Secondly, it only accounted for a *single* projection type def id, but trait objects can have multiple (i.e. `trait Foo<A, B>: Bar<A, Assoc = A> + Bar<B, Assoc = B>`). To fix the first, it's simple enough to just add an equality check for the self ty. To fix the second, I implemented a matching step that's very similar to the `projection_may_match` check we have for upcasting, since on top of having multiple choices, we need to deal with both non-structural matches and ambiguity. This probably lacks a bit of documentation, but I think it works pretty well. Fixes rust-lang/trait-system-refactor-initiative#171 r? lcnr
2 parents 883f9f7 + e1936d2 commit da43826

File tree

10 files changed

+238
-74
lines changed

10 files changed

+238
-74
lines changed

Diff for: compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs

+14-10
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,20 @@ where
9292
let ty::Dynamic(bounds, _, _) = goal.predicate.self_ty().kind() else {
9393
panic!("expected object type in `probe_and_consider_object_bound_candidate`");
9494
};
95-
ecx.add_goals(
96-
GoalSource::ImplWhereBound,
97-
structural_traits::predicates_for_object_candidate(
98-
ecx,
99-
goal.param_env,
100-
goal.predicate.trait_ref(cx),
101-
bounds,
102-
),
103-
);
104-
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
95+
match structural_traits::predicates_for_object_candidate(
96+
ecx,
97+
goal.param_env,
98+
goal.predicate.trait_ref(cx),
99+
bounds,
100+
) {
101+
Ok(requirements) => {
102+
ecx.add_goals(GoalSource::ImplWhereBound, requirements);
103+
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
104+
}
105+
Err(_) => {
106+
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
107+
}
108+
}
105109
})
106110
}
107111

Diff for: compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs

+108-55
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ use derive_where::derive_where;
55
use rustc_type_ir::data_structures::HashMap;
66
use rustc_type_ir::inherent::*;
77
use rustc_type_ir::lang_items::TraitSolverLangItem;
8+
use rustc_type_ir::solve::inspect::ProbeKind;
89
use rustc_type_ir::{
9-
self as ty, Interner, Movability, Mutability, TypeFoldable, TypeFolder, TypeSuperFoldable,
10-
Upcast as _, elaborate,
10+
self as ty, FallibleTypeFolder, Interner, Movability, Mutability, TypeFoldable,
11+
TypeSuperFoldable, Upcast as _, elaborate,
1112
};
1213
use rustc_type_ir_macros::{TypeFoldable_Generic, TypeVisitable_Generic};
1314
use tracing::instrument;
@@ -822,22 +823,16 @@ pub(in crate::solve) fn const_conditions_for_destruct<I: Interner>(
822823
/// impl Baz for dyn Foo<Item = Ty> {}
823824
/// ```
824825
///
825-
/// However, in order to make such impls well-formed, we need to do an
826+
/// However, in order to make such impls non-cyclical, we need to do an
826827
/// additional step of eagerly folding the associated types in the where
827828
/// clauses of the impl. In this example, that means replacing
828829
/// `<Self as Foo>::Bar` with `Ty` in the first impl.
829-
///
830-
// FIXME: This is only necessary as `<Self as Trait>::Assoc: ItemBound`
831-
// bounds in impls are trivially proven using the item bound candidates.
832-
// This is unsound in general and once that is fixed, we don't need to
833-
// normalize eagerly here. See https://github.com/lcnr/solver-woes/issues/9
834-
// for more details.
835830
pub(in crate::solve) fn predicates_for_object_candidate<D, I>(
836-
ecx: &EvalCtxt<'_, D>,
831+
ecx: &mut EvalCtxt<'_, D>,
837832
param_env: I::ParamEnv,
838833
trait_ref: ty::TraitRef<I>,
839834
object_bounds: I::BoundExistentialPredicates,
840-
) -> Vec<Goal<I, I::Predicate>>
835+
) -> Result<Vec<Goal<I, I::Predicate>>, Ambiguous>
841836
where
842837
D: SolverDelegate<Interner = I>,
843838
I: Interner,
@@ -871,72 +866,130 @@ where
871866
.extend(cx.item_bounds(associated_type_def_id).iter_instantiated(cx, trait_ref.args));
872867
}
873868

874-
let mut replace_projection_with = HashMap::default();
869+
let mut replace_projection_with: HashMap<_, Vec<_>> = HashMap::default();
875870
for bound in object_bounds.iter() {
876871
if let ty::ExistentialPredicate::Projection(proj) = bound.skip_binder() {
872+
// FIXME: We *probably* should replace this with a dummy placeholder,
873+
// b/c don't want to replace literal instances of this dyn type that
874+
// show up in the bounds, but just ones that come from substituting
875+
// `Self` with the dyn type.
877876
let proj = proj.with_self_ty(cx, trait_ref.self_ty());
878-
let old_ty = replace_projection_with.insert(proj.def_id(), bound.rebind(proj));
879-
assert_eq!(
880-
old_ty,
881-
None,
882-
"{:?} has two generic parameters: {:?} and {:?}",
883-
proj.projection_term,
884-
proj.term,
885-
old_ty.unwrap()
886-
);
877+
replace_projection_with.entry(proj.def_id()).or_default().push(bound.rebind(proj));
887878
}
888879
}
889880

890-
let mut folder =
891-
ReplaceProjectionWith { ecx, param_env, mapping: replace_projection_with, nested: vec![] };
892-
let folded_requirements = requirements.fold_with(&mut folder);
881+
let mut folder = ReplaceProjectionWith {
882+
ecx,
883+
param_env,
884+
self_ty: trait_ref.self_ty(),
885+
mapping: &replace_projection_with,
886+
nested: vec![],
887+
};
893888

894-
folder
889+
let requirements = requirements.try_fold_with(&mut folder)?;
890+
Ok(folder
895891
.nested
896892
.into_iter()
897-
.chain(folded_requirements.into_iter().map(|clause| Goal::new(cx, param_env, clause)))
898-
.collect()
893+
.chain(requirements.into_iter().map(|clause| Goal::new(cx, param_env, clause)))
894+
.collect())
899895
}
900896

901-
struct ReplaceProjectionWith<'a, D: SolverDelegate<Interner = I>, I: Interner> {
902-
ecx: &'a EvalCtxt<'a, D>,
897+
struct ReplaceProjectionWith<'a, 'b, I: Interner, D: SolverDelegate<Interner = I>> {
898+
ecx: &'a mut EvalCtxt<'b, D>,
903899
param_env: I::ParamEnv,
904-
mapping: HashMap<I::DefId, ty::Binder<I, ty::ProjectionPredicate<I>>>,
900+
self_ty: I::Ty,
901+
mapping: &'a HashMap<I::DefId, Vec<ty::Binder<I, ty::ProjectionPredicate<I>>>>,
905902
nested: Vec<Goal<I, I::Predicate>>,
906903
}
907904

908-
impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I>
909-
for ReplaceProjectionWith<'_, D, I>
905+
impl<D, I> ReplaceProjectionWith<'_, '_, I, D>
906+
where
907+
D: SolverDelegate<Interner = I>,
908+
I: Interner,
910909
{
910+
fn projection_may_match(
911+
&mut self,
912+
source_projection: ty::Binder<I, ty::ProjectionPredicate<I>>,
913+
target_projection: ty::AliasTerm<I>,
914+
) -> bool {
915+
source_projection.item_def_id() == target_projection.def_id
916+
&& self
917+
.ecx
918+
.probe(|_| ProbeKind::ProjectionCompatibility)
919+
.enter(|ecx| -> Result<_, NoSolution> {
920+
let source_projection = ecx.instantiate_binder_with_infer(source_projection);
921+
ecx.eq(self.param_env, source_projection.projection_term, target_projection)?;
922+
ecx.try_evaluate_added_goals()
923+
})
924+
.is_ok()
925+
}
926+
927+
/// Try to replace an alias with the term present in the projection bounds of the self type.
928+
/// Returns `Ok<None>` if this alias is not eligible to be replaced, or bail with
929+
/// `Err(Ambiguous)` if it's uncertain which projection bound to replace the term with due
930+
/// to multiple bounds applying.
931+
fn try_eagerly_replace_alias(
932+
&mut self,
933+
alias_term: ty::AliasTerm<I>,
934+
) -> Result<Option<I::Term>, Ambiguous> {
935+
if alias_term.self_ty() != self.self_ty {
936+
return Ok(None);
937+
}
938+
939+
let Some(replacements) = self.mapping.get(&alias_term.def_id) else {
940+
return Ok(None);
941+
};
942+
943+
// This is quite similar to the `projection_may_match` we use in unsizing,
944+
// but here we want to unify a projection predicate against an alias term
945+
// so we can replace it with the the projection predicate's term.
946+
let mut matching_projections = replacements
947+
.iter()
948+
.filter(|source_projection| self.projection_may_match(**source_projection, alias_term));
949+
let Some(replacement) = matching_projections.next() else {
950+
// This shouldn't happen.
951+
panic!("could not replace {alias_term:?} with term from from {:?}", self.self_ty);
952+
};
953+
// FIXME: This *may* have issues with duplicated projections.
954+
if matching_projections.next().is_some() {
955+
// If there's more than one projection that we can unify here, then we
956+
// need to stall until inference constrains things so that there's only
957+
// one choice.
958+
return Err(Ambiguous);
959+
}
960+
961+
let replacement = self.ecx.instantiate_binder_with_infer(*replacement);
962+
self.nested.extend(
963+
self.ecx
964+
.eq_and_get_goals(self.param_env, alias_term, replacement.projection_term)
965+
.expect("expected to be able to unify goal projection with dyn's projection"),
966+
);
967+
968+
Ok(Some(replacement.term))
969+
}
970+
}
971+
972+
/// Marker for bailing with ambiguity.
973+
pub(crate) struct Ambiguous;
974+
975+
impl<D, I> FallibleTypeFolder<I> for ReplaceProjectionWith<'_, '_, I, D>
976+
where
977+
D: SolverDelegate<Interner = I>,
978+
I: Interner,
979+
{
980+
type Error = Ambiguous;
981+
911982
fn cx(&self) -> I {
912983
self.ecx.cx()
913984
}
914985

915-
fn fold_ty(&mut self, ty: I::Ty) -> I::Ty {
986+
fn try_fold_ty(&mut self, ty: I::Ty) -> Result<I::Ty, Ambiguous> {
916987
if let ty::Alias(ty::Projection, alias_ty) = ty.kind() {
917-
if let Some(replacement) = self.mapping.get(&alias_ty.def_id) {
918-
// We may have a case where our object type's projection bound is higher-ranked,
919-
// but the where clauses we instantiated are not. We can solve this by instantiating
920-
// the binder at the usage site.
921-
let proj = self.ecx.instantiate_binder_with_infer(*replacement);
922-
// FIXME: Technically this equate could be fallible...
923-
self.nested.extend(
924-
self.ecx
925-
.eq_and_get_goals(
926-
self.param_env,
927-
alias_ty,
928-
proj.projection_term.expect_ty(self.ecx.cx()),
929-
)
930-
.expect(
931-
"expected to be able to unify goal projection with dyn's projection",
932-
),
933-
);
934-
proj.term.expect_ty()
935-
} else {
936-
ty.super_fold_with(self)
988+
if let Some(term) = self.try_eagerly_replace_alias(alias_ty.into())? {
989+
return Ok(term.expect_ty());
937990
}
938-
} else {
939-
ty.super_fold_with(self)
940991
}
992+
993+
ty.try_super_fold_with(self)
941994
}
942995
}

Diff for: compiler/rustc_next_trait_solver/src/solve/trait_goals.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,7 @@ where
944944
target_projection: ty::Binder<I, ty::ExistentialProjection<I>>| {
945945
source_projection.item_def_id() == target_projection.item_def_id()
946946
&& ecx
947-
.probe(|_| ProbeKind::UpcastProjectionCompatibility)
947+
.probe(|_| ProbeKind::ProjectionCompatibility)
948948
.enter(|ecx| -> Result<_, NoSolution> {
949949
ecx.enter_forall(target_projection, |ecx, target_projection| {
950950
let source_projection =

Diff for: compiler/rustc_trait_selection/src/solve/inspect/analyse.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
292292
inspect::ProbeStep::NestedProbe(ref probe) => {
293293
match probe.kind {
294294
// These never assemble candidates for the goal we're trying to solve.
295-
inspect::ProbeKind::UpcastProjectionCompatibility
295+
inspect::ProbeKind::ProjectionCompatibility
296296
| inspect::ProbeKind::ShadowedEnvProbing => continue,
297297

298298
inspect::ProbeKind::NormalizedSelfTyAssembly
@@ -314,8 +314,10 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
314314
}
315315

316316
match probe.kind {
317-
inspect::ProbeKind::UpcastProjectionCompatibility
318-
| inspect::ProbeKind::ShadowedEnvProbing => bug!(),
317+
inspect::ProbeKind::ProjectionCompatibility
318+
| inspect::ProbeKind::ShadowedEnvProbing => {
319+
bug!()
320+
}
319321

320322
inspect::ProbeKind::NormalizedSelfTyAssembly | inspect::ProbeKind::UnsizeAssembly => {}
321323

Diff for: compiler/rustc_trait_selection/src/solve/select.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ fn to_selection<'tcx>(
177177
},
178178
ProbeKind::NormalizedSelfTyAssembly
179179
| ProbeKind::UnsizeAssembly
180-
| ProbeKind::UpcastProjectionCompatibility
180+
| ProbeKind::ProjectionCompatibility
181181
| ProbeKind::OpaqueTypeStorageLookup { result: _ }
182182
| ProbeKind::Root { result: _ }
183183
| ProbeKind::ShadowedEnvProbing

Diff for: compiler/rustc_type_ir/src/solve/inspect.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,12 @@ pub enum ProbeKind<I: Interner> {
118118
/// Used in the probe that wraps normalizing the non-self type for the unsize
119119
/// trait, which is also structurally matched on.
120120
UnsizeAssembly,
121-
/// During upcasting from some source object to target object type, used to
122-
/// do a probe to find out what projection type(s) may be used to prove that
123-
/// the source type upholds all of the target type's object bounds.
124-
UpcastProjectionCompatibility,
121+
/// Used to do a probe to find out what projection type(s) match a given
122+
/// alias bound or projection predicate. For trait upcasting, this is used
123+
/// to prove that the source type upholds all of the target type's object
124+
/// bounds. For object type bounds, this is used when eagerly replacing
125+
/// supertrait aliases.
126+
ProjectionCompatibility,
125127
/// Looking for param-env candidates that satisfy the trait ref for a projection.
126128
ShadowedEnvProbing,
127129
/// Try to unify an opaque type with an existing key in the storage.

Diff for: tests/ui/traits/next-solver/supertrait-alias-1.rs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//@ compile-flags: -Znext-solver
2+
//@ check-pass
3+
4+
// Regression test for <https://github.com/rust-lang/trait-system-refactor-initiative/issues/171>.
5+
// Tests that we don't try to replace `<V as Super>::Output` when replacing projections in the
6+
// required bounds for `dyn Trait`, b/c `V` is not relevant to the dyn type, which we were
7+
// previously encountering b/c we were walking into the existential projection bounds of the dyn
8+
// type itself.
9+
10+
pub trait Trait: Super {}
11+
12+
pub trait Super {
13+
type Output;
14+
}
15+
16+
fn bound<T: Trait + ?Sized>() {}
17+
18+
fn visit_simd_operator<V: Super + ?Sized>() {
19+
bound::<dyn Trait<Output = <V as Super>::Output>>();
20+
}
21+
22+
fn main() {}

Diff for: tests/ui/traits/next-solver/supertrait-alias-2.rs

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//@ compile-flags: -Znext-solver
2+
//@ check-pass
3+
4+
// Regression test for <https://github.com/rust-lang/trait-system-refactor-initiative/issues/171>.
5+
// Tests that we don't try to replace `<T as Other>::Assoc` when replacing projections in the
6+
// required bounds for `dyn Foo`, b/c `T` is not relevant to the dyn type, which we were
7+
// encountering when walking through the elaborated supertraits of `dyn Foo`.
8+
9+
trait Other<X> {}
10+
11+
trait Foo<T: Foo<T>>: Other<<T as Foo<T>>::Assoc> {
12+
type Assoc;
13+
}
14+
15+
impl<T> Foo<T> for T {
16+
type Assoc = ();
17+
}
18+
19+
impl<T: ?Sized> Other<()> for T {}
20+
21+
fn is_foo<T: Foo<()> + ?Sized>() {}
22+
23+
fn main() {
24+
is_foo::<dyn Foo<(), Assoc = ()>>();
25+
}

Diff for: tests/ui/traits/next-solver/supertrait-alias-3.rs

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//@ compile-flags: -Znext-solver
2+
//@ check-pass
3+
4+
// Regression test for <https://github.com/rust-lang/trait-system-refactor-initiative/issues/171>.
5+
// Exercises a case where structural equality is insufficient when replacing projections in a dyn's
6+
// bounds. In this case, the bound will contain `<Self as Super<<i32 as Mirror>:Assoc>::Assoc`, but
7+
// the existential projections from the dyn will have `<Self as Super<i32>>::Assoc` because as an
8+
// optimization we eagerly normalize aliases in goals.
9+
10+
trait Other<T> {}
11+
impl<T> Other<T> for T {}
12+
13+
trait Super<T> {
14+
type Assoc;
15+
}
16+
17+
trait Mirror {
18+
type Assoc;
19+
}
20+
impl<T> Mirror for T {
21+
type Assoc = T;
22+
}
23+
24+
trait Foo<A, B>: Super<<A as Mirror>::Assoc, Assoc = A> {
25+
type FooAssoc: Other<<Self as Super<<A as Mirror>::Assoc>>::Assoc>;
26+
}
27+
28+
fn is_foo<F: Foo<T, U> + ?Sized, T, U>() {}
29+
30+
fn main() {
31+
is_foo::<dyn Foo<i32, u32, FooAssoc = i32>, _, _>();
32+
}

0 commit comments

Comments
 (0)