Skip to content

Commit c248440

Browse files
Trait upcasting support in new solver
1 parent 085a48e commit c248440

File tree

6 files changed

+148
-59
lines changed

6 files changed

+148
-59
lines changed

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

+18-3
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,20 @@ pub(super) trait GoalKind<'tcx>: TypeFoldable<'tcx> + Copy + Eq {
174174
goal: Goal<'tcx, Self>,
175175
) -> QueryResult<'tcx>;
176176

177-
// Implement unsizing. The most common forms of unsizing are array to slice,
178-
// and concrete (Sized) type into a `dyn Trait`. ADTs and Tuples can also
179-
// have their final field unsized if it's generic.
177+
// The most common forms of unsizing are array to slice, and concrete (Sized)
178+
// type into a `dyn Trait`. ADTs and Tuples can also have their final field
179+
// unsized if it's generic.
180180
fn consider_builtin_unsize_candidate(
181181
ecx: &mut EvalCtxt<'_, 'tcx>,
182182
goal: Goal<'tcx, Self>,
183183
) -> QueryResult<'tcx>;
184+
185+
// `dyn Trait1` can be unsized to `dyn Trait2` if they are the same trait, or
186+
// if `Trait2` is a (transitive) supertrait of `Trait2`.
187+
fn consider_builtin_dyn_unsize_candidates(
188+
ecx: &mut EvalCtxt<'_, 'tcx>,
189+
goal: Goal<'tcx, Self>,
190+
) -> Vec<CanonicalResponse<'tcx>>;
184191
}
185192

186193
impl<'tcx> EvalCtxt<'_, 'tcx> {
@@ -323,6 +330,14 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
323330
}
324331
Err(NoSolution) => (),
325332
}
333+
334+
// There may be multiple unsize candidates for a trait with several supertraits:
335+
// `trait Foo: Bar<A> + Bar<B>` and `dyn Foo: Unsize<dyn Bar<_>>`
336+
if lang_items.unsize_trait() == Some(trait_def_id) {
337+
for result in G::consider_builtin_dyn_unsize_candidates(self, goal) {
338+
candidates.push(Candidate { source: CandidateSource::BuiltinImpl, result });
339+
}
340+
}
326341
}
327342

328343
fn assemble_param_env_candidates<G: GoalKind<'tcx>>(

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

+7
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,13 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
561561
) -> QueryResult<'tcx> {
562562
bug!("`Unsize` does not have an associated type: {:?}", goal);
563563
}
564+
565+
fn consider_builtin_dyn_unsize_candidates(
566+
_ecx: &mut EvalCtxt<'_, 'tcx>,
567+
goal: Goal<'tcx, Self>,
568+
) -> Vec<super::CanonicalResponse<'tcx>> {
569+
bug!("`Unsize` does not have an associated type: {:?}", goal);
570+
}
564571
}
565572

566573
/// This behavior is also implemented in `rustc_ty_utils` and in the old `project` code.

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

+82-56
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::iter;
44

55
use super::assembly::{self, Candidate, CandidateSource};
66
use super::infcx_ext::InferCtxtExt;
7-
use super::{Certainty, EvalCtxt, Goal, QueryResult};
7+
use super::{CanonicalResponse, Certainty, EvalCtxt, Goal, QueryResult};
88
use rustc_hir::def_id::DefId;
99
use rustc_infer::infer::InferCtxt;
1010
use rustc_infer::traits::query::NoSolution;
@@ -253,57 +253,11 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
253253
ecx.infcx.probe(|_| {
254254
match (a_ty.kind(), b_ty.kind()) {
255255
// Trait upcasting, or `dyn Trait + Auto + 'a` -> `dyn Trait + 'b`
256-
(
257-
&ty::Dynamic(a_data, a_region, ty::Dyn),
258-
&ty::Dynamic(b_data, b_region, ty::Dyn),
259-
) => {
260-
// All of a's auto traits need to be in b's auto traits.
261-
let auto_traits_compatible = b_data
262-
.auto_traits()
263-
.all(|b| a_data.auto_traits().any(|a| a == b));
264-
if !auto_traits_compatible {
265-
return Err(NoSolution);
266-
}
267-
268-
// If the principal def ids match (or are both none), then we're not doing
269-
// trait upcasting. We're just removing auto traits (or shortening the lifetime).
270-
if a_data.principal_def_id() == b_data.principal_def_id() {
271-
// Require that all of the trait predicates from A match B, except for
272-
// the auto traits. We do this by constructing a new A type with B's
273-
// auto traits, and equating these types.
274-
let new_a_data = a_data
275-
.iter()
276-
.filter(|a| {
277-
matches!(
278-
a.skip_binder(),
279-
ty::ExistentialPredicate::Trait(_) | ty::ExistentialPredicate::Projection(_)
280-
)
281-
})
282-
.chain(
283-
b_data
284-
.auto_traits()
285-
.map(ty::ExistentialPredicate::AutoTrait)
286-
.map(ty::Binder::dummy),
287-
);
288-
let new_a_data = tcx.mk_poly_existential_predicates(new_a_data);
289-
let new_a_ty = tcx.mk_dynamic(new_a_data, b_region, ty::Dyn);
290-
291-
// We also require that A's lifetime outlives B's lifetime.
292-
let mut nested_obligations = ecx.infcx.eq(goal.param_env, new_a_ty, b_ty)?;
293-
nested_obligations.push(goal.with(tcx, ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region))));
294-
295-
ecx.evaluate_all_and_make_canonical_response(nested_obligations)
296-
} else if let Some(a_principal) = a_data.principal()
297-
&& let Some(b_principal) = b_data.principal()
298-
&& supertraits(tcx, a_principal.with_self_ty(tcx, a_ty))
299-
.any(|trait_ref| trait_ref.def_id() == b_principal.def_id())
300-
{
301-
// FIXME: Intentionally ignoring `need_migrate_deref_output_trait_object` here for now.
302-
// Confirm upcasting candidate
303-
todo!()
304-
} else {
305-
Err(NoSolution)
306-
}
256+
(&ty::Dynamic(_, _, ty::Dyn), &ty::Dynamic(_, _, ty::Dyn)) => {
257+
// Dyn upcasting is handled separately, since due to upcasting,
258+
// when there are two supertraits that differ by substs, we
259+
// may return more than one query response.
260+
return Err(NoSolution);
307261
}
308262
// `T` -> `dyn Trait` unsizing
309263
(_, &ty::Dynamic(data, region, ty::Dyn)) => {
@@ -332,10 +286,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
332286
ty::Binder::dummy(tcx.mk_trait_ref(sized_def_id, [a_ty])),
333287
),
334288
// The type must outlive the lifetime of the `dyn` we're unsizing into.
335-
goal.with(
336-
tcx,
337-
ty::Binder::dummy(ty::OutlivesPredicate(a_ty, region)),
338-
),
289+
goal.with(tcx, ty::Binder::dummy(ty::OutlivesPredicate(a_ty, region))),
339290
])
340291
.collect();
341292

@@ -413,6 +364,81 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
413364
}
414365
})
415366
}
367+
368+
fn consider_builtin_dyn_unsize_candidates(
369+
ecx: &mut EvalCtxt<'_, 'tcx>,
370+
goal: Goal<'tcx, Self>,
371+
) -> Vec<CanonicalResponse<'tcx>> {
372+
let tcx = ecx.tcx();
373+
374+
let a_ty = goal.predicate.self_ty();
375+
let b_ty = goal.predicate.trait_ref.substs.type_at(1);
376+
let ty::Dynamic(a_data, a_region, ty::Dyn) = *a_ty.kind() else {
377+
return vec![];
378+
};
379+
let ty::Dynamic(b_data, b_region, ty::Dyn) = *b_ty.kind() else {
380+
return vec![];
381+
};
382+
383+
// All of a's auto traits need to be in b's auto traits.
384+
let auto_traits_compatible =
385+
b_data.auto_traits().all(|b| a_data.auto_traits().any(|a| a == b));
386+
if !auto_traits_compatible {
387+
return vec![];
388+
}
389+
390+
let mut responses = vec![];
391+
let mut unsize_dyn_to_principal = |principal: Option<ty::PolyExistentialTraitRef<'tcx>>| {
392+
let _ = ecx.infcx.probe(|_| -> Result<(), NoSolution> {
393+
// Require that all of the trait predicates from A match B, except for
394+
// the auto traits. We do this by constructing a new A type with B's
395+
// auto traits, and equating these types.
396+
let new_a_data = principal
397+
.into_iter()
398+
.map(|trait_ref| trait_ref.map_bound(ty::ExistentialPredicate::Trait))
399+
.chain(a_data.iter().filter(|a| {
400+
matches!(a.skip_binder(), ty::ExistentialPredicate::Projection(_))
401+
}))
402+
.chain(
403+
b_data
404+
.auto_traits()
405+
.map(ty::ExistentialPredicate::AutoTrait)
406+
.map(ty::Binder::dummy),
407+
);
408+
let new_a_data = tcx.mk_poly_existential_predicates(new_a_data);
409+
let new_a_ty = tcx.mk_dynamic(new_a_data, b_region, ty::Dyn);
410+
411+
// We also require that A's lifetime outlives B's lifetime.
412+
let mut nested_obligations = ecx.infcx.eq(goal.param_env, new_a_ty, b_ty)?;
413+
nested_obligations.push(
414+
goal.with(tcx, ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region))),
415+
);
416+
417+
responses.push(ecx.evaluate_all_and_make_canonical_response(nested_obligations)?);
418+
419+
Ok(())
420+
});
421+
};
422+
423+
// If the principal def ids match (or are both none), then we're not doing
424+
// trait upcasting. We're just removing auto traits (or shortening the lifetime).
425+
if a_data.principal_def_id() == b_data.principal_def_id() {
426+
unsize_dyn_to_principal(a_data.principal());
427+
} else if let Some(a_principal) = a_data.principal()
428+
&& let Some(b_principal) = b_data.principal()
429+
{
430+
for super_trait_ref in supertraits(tcx, a_principal.with_self_ty(tcx, a_ty)) {
431+
if super_trait_ref.def_id() != b_principal.def_id() {
432+
continue;
433+
}
434+
let erased_trait_ref = super_trait_ref
435+
.map_bound(|trait_ref| ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref));
436+
unsize_dyn_to_principal(Some(erased_trait_ref));
437+
}
438+
}
439+
440+
responses
441+
}
416442
}
417443

418444
impl<'tcx> EvalCtxt<'_, 'tcx> {

Diff for: tests/ui/traits/new-solver/upcast-right-substs.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// compile-flags: -Ztrait-solver=next
2+
// check-pass
3+
4+
#![feature(trait_upcasting)]
5+
6+
trait Foo: Bar<i32> + Bar<u32> {}
7+
8+
trait Bar<T> {}
9+
10+
fn main() {
11+
let x: &dyn Foo = todo!();
12+
let y: &dyn Bar<i32> = x;
13+
let z: &dyn Bar<u32> = x;
14+
}

Diff for: tests/ui/traits/new-solver/upcast-wrong-substs.rs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// compile-flags: -Ztrait-solver=next
2+
3+
#![feature(trait_upcasting)]
4+
5+
trait Foo: Bar<i32> + Bar<u32> {}
6+
7+
trait Bar<T> {}
8+
9+
fn main() {
10+
let x: &dyn Foo = todo!();
11+
let y: &dyn Bar<usize> = x;
12+
//~^ ERROR mismatched types
13+
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/upcast-wrong-substs.rs:11:30
3+
|
4+
LL | let y: &dyn Bar<usize> = x;
5+
| --------------- ^ expected trait `Bar`, found trait `Foo`
6+
| |
7+
| expected due to this
8+
|
9+
= note: expected reference `&dyn Bar<usize>`
10+
found reference `&dyn Foo`
11+
12+
error: aborting due to previous error
13+
14+
For more information about this error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)