From c24844048ff023aeaf47080634507677ffbe7001 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Mon, 23 Jan 2023 23:56:54 +0000 Subject: [PATCH] Trait upcasting support in new solver --- .../src/solve/assembly.rs | 21 ++- .../src/solve/project_goals.rs | 7 + .../src/solve/trait_goals.rs | 138 +++++++++++------- .../traits/new-solver/upcast-right-substs.rs | 14 ++ .../traits/new-solver/upcast-wrong-substs.rs | 13 ++ .../new-solver/upcast-wrong-substs.stderr | 14 ++ 6 files changed, 148 insertions(+), 59 deletions(-) create mode 100644 tests/ui/traits/new-solver/upcast-right-substs.rs create mode 100644 tests/ui/traits/new-solver/upcast-wrong-substs.rs create mode 100644 tests/ui/traits/new-solver/upcast-wrong-substs.stderr diff --git a/compiler/rustc_trait_selection/src/solve/assembly.rs b/compiler/rustc_trait_selection/src/solve/assembly.rs index e6c36d37ffb..1c4e498c57e 100644 --- a/compiler/rustc_trait_selection/src/solve/assembly.rs +++ b/compiler/rustc_trait_selection/src/solve/assembly.rs @@ -174,13 +174,20 @@ fn consider_builtin_generator_candidate( goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx>; - // Implement unsizing. The most common forms of unsizing are array to slice, - // and concrete (Sized) type into a `dyn Trait`. ADTs and Tuples can also - // have their final field unsized if it's generic. + // The most common forms of unsizing are array to slice, and concrete (Sized) + // type into a `dyn Trait`. ADTs and Tuples can also have their final field + // unsized if it's generic. fn consider_builtin_unsize_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx>; + + // `dyn Trait1` can be unsized to `dyn Trait2` if they are the same trait, or + // if `Trait2` is a (transitive) supertrait of `Trait2`. + fn consider_builtin_dyn_unsize_candidates( + ecx: &mut EvalCtxt<'_, 'tcx>, + goal: Goal<'tcx, Self>, + ) -> Vec>; } impl<'tcx> EvalCtxt<'_, 'tcx> { @@ -323,6 +330,14 @@ fn assemble_builtin_impl_candidates>( } Err(NoSolution) => (), } + + // There may be multiple unsize candidates for a trait with several supertraits: + // `trait Foo: Bar + Bar` and `dyn Foo: Unsize>` + if lang_items.unsize_trait() == Some(trait_def_id) { + for result in G::consider_builtin_dyn_unsize_candidates(self, goal) { + candidates.push(Candidate { source: CandidateSource::BuiltinImpl, result }); + } + } } fn assemble_param_env_candidates>( diff --git a/compiler/rustc_trait_selection/src/solve/project_goals.rs b/compiler/rustc_trait_selection/src/solve/project_goals.rs index 00b4fa67d68..f9506446aba 100644 --- a/compiler/rustc_trait_selection/src/solve/project_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/project_goals.rs @@ -561,6 +561,13 @@ fn consider_builtin_unsize_candidate( ) -> QueryResult<'tcx> { bug!("`Unsize` does not have an associated type: {:?}", goal); } + + fn consider_builtin_dyn_unsize_candidates( + _ecx: &mut EvalCtxt<'_, 'tcx>, + goal: Goal<'tcx, Self>, + ) -> Vec> { + bug!("`Unsize` does not have an associated type: {:?}", goal); + } } /// This behavior is also implemented in `rustc_ty_utils` and in the old `project` code. diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals.rs b/compiler/rustc_trait_selection/src/solve/trait_goals.rs index 28144da1b1e..c79f1f05512 100644 --- a/compiler/rustc_trait_selection/src/solve/trait_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/trait_goals.rs @@ -4,7 +4,7 @@ use super::assembly::{self, Candidate, CandidateSource}; use super::infcx_ext::InferCtxtExt; -use super::{Certainty, EvalCtxt, Goal, QueryResult}; +use super::{CanonicalResponse, Certainty, EvalCtxt, Goal, QueryResult}; use rustc_hir::def_id::DefId; use rustc_infer::infer::InferCtxt; use rustc_infer::traits::query::NoSolution; @@ -253,57 +253,11 @@ fn consider_builtin_unsize_candidate( ecx.infcx.probe(|_| { match (a_ty.kind(), b_ty.kind()) { // Trait upcasting, or `dyn Trait + Auto + 'a` -> `dyn Trait + 'b` - ( - &ty::Dynamic(a_data, a_region, ty::Dyn), - &ty::Dynamic(b_data, b_region, ty::Dyn), - ) => { - // All of a's auto traits need to be in b's auto traits. - let auto_traits_compatible = b_data - .auto_traits() - .all(|b| a_data.auto_traits().any(|a| a == b)); - if !auto_traits_compatible { - return Err(NoSolution); - } - - // If the principal def ids match (or are both none), then we're not doing - // trait upcasting. We're just removing auto traits (or shortening the lifetime). - if a_data.principal_def_id() == b_data.principal_def_id() { - // Require that all of the trait predicates from A match B, except for - // the auto traits. We do this by constructing a new A type with B's - // auto traits, and equating these types. - let new_a_data = a_data - .iter() - .filter(|a| { - matches!( - a.skip_binder(), - ty::ExistentialPredicate::Trait(_) | ty::ExistentialPredicate::Projection(_) - ) - }) - .chain( - b_data - .auto_traits() - .map(ty::ExistentialPredicate::AutoTrait) - .map(ty::Binder::dummy), - ); - let new_a_data = tcx.mk_poly_existential_predicates(new_a_data); - let new_a_ty = tcx.mk_dynamic(new_a_data, b_region, ty::Dyn); - - // We also require that A's lifetime outlives B's lifetime. - let mut nested_obligations = ecx.infcx.eq(goal.param_env, new_a_ty, b_ty)?; - nested_obligations.push(goal.with(tcx, ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region)))); - - ecx.evaluate_all_and_make_canonical_response(nested_obligations) - } else if let Some(a_principal) = a_data.principal() - && let Some(b_principal) = b_data.principal() - && supertraits(tcx, a_principal.with_self_ty(tcx, a_ty)) - .any(|trait_ref| trait_ref.def_id() == b_principal.def_id()) - { - // FIXME: Intentionally ignoring `need_migrate_deref_output_trait_object` here for now. - // Confirm upcasting candidate - todo!() - } else { - Err(NoSolution) - } + (&ty::Dynamic(_, _, ty::Dyn), &ty::Dynamic(_, _, ty::Dyn)) => { + // Dyn upcasting is handled separately, since due to upcasting, + // when there are two supertraits that differ by substs, we + // may return more than one query response. + return Err(NoSolution); } // `T` -> `dyn Trait` unsizing (_, &ty::Dynamic(data, region, ty::Dyn)) => { @@ -332,10 +286,7 @@ fn consider_builtin_unsize_candidate( ty::Binder::dummy(tcx.mk_trait_ref(sized_def_id, [a_ty])), ), // The type must outlive the lifetime of the `dyn` we're unsizing into. - goal.with( - tcx, - ty::Binder::dummy(ty::OutlivesPredicate(a_ty, region)), - ), + goal.with(tcx, ty::Binder::dummy(ty::OutlivesPredicate(a_ty, region))), ]) .collect(); @@ -413,6 +364,81 @@ fn consider_builtin_unsize_candidate( } }) } + + fn consider_builtin_dyn_unsize_candidates( + ecx: &mut EvalCtxt<'_, 'tcx>, + goal: Goal<'tcx, Self>, + ) -> Vec> { + let tcx = ecx.tcx(); + + let a_ty = goal.predicate.self_ty(); + let b_ty = goal.predicate.trait_ref.substs.type_at(1); + let ty::Dynamic(a_data, a_region, ty::Dyn) = *a_ty.kind() else { + return vec![]; + }; + let ty::Dynamic(b_data, b_region, ty::Dyn) = *b_ty.kind() else { + return vec![]; + }; + + // All of a's auto traits need to be in b's auto traits. + let auto_traits_compatible = + b_data.auto_traits().all(|b| a_data.auto_traits().any(|a| a == b)); + if !auto_traits_compatible { + return vec![]; + } + + let mut responses = vec![]; + let mut unsize_dyn_to_principal = |principal: Option>| { + let _ = ecx.infcx.probe(|_| -> Result<(), NoSolution> { + // Require that all of the trait predicates from A match B, except for + // the auto traits. We do this by constructing a new A type with B's + // auto traits, and equating these types. + let new_a_data = principal + .into_iter() + .map(|trait_ref| trait_ref.map_bound(ty::ExistentialPredicate::Trait)) + .chain(a_data.iter().filter(|a| { + matches!(a.skip_binder(), ty::ExistentialPredicate::Projection(_)) + })) + .chain( + b_data + .auto_traits() + .map(ty::ExistentialPredicate::AutoTrait) + .map(ty::Binder::dummy), + ); + let new_a_data = tcx.mk_poly_existential_predicates(new_a_data); + let new_a_ty = tcx.mk_dynamic(new_a_data, b_region, ty::Dyn); + + // We also require that A's lifetime outlives B's lifetime. + let mut nested_obligations = ecx.infcx.eq(goal.param_env, new_a_ty, b_ty)?; + nested_obligations.push( + goal.with(tcx, ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region))), + ); + + responses.push(ecx.evaluate_all_and_make_canonical_response(nested_obligations)?); + + Ok(()) + }); + }; + + // If the principal def ids match (or are both none), then we're not doing + // trait upcasting. We're just removing auto traits (or shortening the lifetime). + if a_data.principal_def_id() == b_data.principal_def_id() { + unsize_dyn_to_principal(a_data.principal()); + } else if let Some(a_principal) = a_data.principal() + && let Some(b_principal) = b_data.principal() + { + for super_trait_ref in supertraits(tcx, a_principal.with_self_ty(tcx, a_ty)) { + if super_trait_ref.def_id() != b_principal.def_id() { + continue; + } + let erased_trait_ref = super_trait_ref + .map_bound(|trait_ref| ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)); + unsize_dyn_to_principal(Some(erased_trait_ref)); + } + } + + responses + } } impl<'tcx> EvalCtxt<'_, 'tcx> { diff --git a/tests/ui/traits/new-solver/upcast-right-substs.rs b/tests/ui/traits/new-solver/upcast-right-substs.rs new file mode 100644 index 00000000000..c19c82acf24 --- /dev/null +++ b/tests/ui/traits/new-solver/upcast-right-substs.rs @@ -0,0 +1,14 @@ +// compile-flags: -Ztrait-solver=next +// check-pass + +#![feature(trait_upcasting)] + +trait Foo: Bar + Bar {} + +trait Bar {} + +fn main() { + let x: &dyn Foo = todo!(); + let y: &dyn Bar = x; + let z: &dyn Bar = x; +} diff --git a/tests/ui/traits/new-solver/upcast-wrong-substs.rs b/tests/ui/traits/new-solver/upcast-wrong-substs.rs new file mode 100644 index 00000000000..f2d04d932bb --- /dev/null +++ b/tests/ui/traits/new-solver/upcast-wrong-substs.rs @@ -0,0 +1,13 @@ +// compile-flags: -Ztrait-solver=next + +#![feature(trait_upcasting)] + +trait Foo: Bar + Bar {} + +trait Bar {} + +fn main() { + let x: &dyn Foo = todo!(); + let y: &dyn Bar = x; + //~^ ERROR mismatched types +} diff --git a/tests/ui/traits/new-solver/upcast-wrong-substs.stderr b/tests/ui/traits/new-solver/upcast-wrong-substs.stderr new file mode 100644 index 00000000000..8623f395f59 --- /dev/null +++ b/tests/ui/traits/new-solver/upcast-wrong-substs.stderr @@ -0,0 +1,14 @@ +error[E0308]: mismatched types + --> $DIR/upcast-wrong-substs.rs:11:30 + | +LL | let y: &dyn Bar = x; + | --------------- ^ expected trait `Bar`, found trait `Foo` + | | + | expected due to this + | + = note: expected reference `&dyn Bar` + found reference `&dyn Foo` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0308`.