From 98525aeee7b1f0ddf3573c2921a0d1ef914774a8 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 22 Feb 2023 01:11:57 +0000 Subject: [PATCH 1/3] Check object's supertrait and associated type bounds in new solver --- .../src/solve/assembly.rs | 11 +++- .../src/solve/project_goals.rs | 44 +++++++++++++ .../src/solve/trait_goals.rs | 39 ++++++++++++ .../solve/trait_goals/structural_traits.rs | 62 ++++++++++++++++++- tests/ui/traits/new-solver/object-unsafety.rs | 20 ++++++ .../traits/new-solver/object-unsafety.stderr | 19 ++++++ 6 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 tests/ui/traits/new-solver/object-unsafety.rs create mode 100644 tests/ui/traits/new-solver/object-unsafety.stderr diff --git a/compiler/rustc_trait_selection/src/solve/assembly.rs b/compiler/rustc_trait_selection/src/solve/assembly.rs index 6172a9539f64d..dec9f8016b0c1 100644 --- a/compiler/rustc_trait_selection/src/solve/assembly.rs +++ b/compiler/rustc_trait_selection/src/solve/assembly.rs @@ -99,6 +99,15 @@ pub(super) trait GoalKind<'tcx>: TypeFoldable> + Copy + Eq { requirements: impl IntoIterator>>, ) -> QueryResult<'tcx>; + // Consider a clause specifically for a `dyn Trait` self type. This requires + // additionally checking all of the supertraits and object bounds to hold, + // since they're not implied by the well-formedness of the object type. + fn consider_object_bound_candidate( + ecx: &mut EvalCtxt<'_, 'tcx>, + goal: Goal<'tcx, Self>, + assumption: ty::Predicate<'tcx>, + ) -> QueryResult<'tcx>; + fn consider_impl_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, @@ -455,7 +464,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { for assumption in elaborate_predicates(tcx, bounds.iter().map(|bound| bound.with_self_ty(tcx, self_ty))) { - match G::consider_implied_clause(self, goal, assumption.predicate, []) { + match G::consider_object_bound_candidate(self, goal, assumption.predicate) { Ok(result) => { candidates.push(Candidate { source: CandidateSource::BuiltinImpl, result }) } diff --git a/compiler/rustc_trait_selection/src/solve/project_goals.rs b/compiler/rustc_trait_selection/src/solve/project_goals.rs index d4fdd54573751..807527e866002 100644 --- a/compiler/rustc_trait_selection/src/solve/project_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/project_goals.rs @@ -128,6 +128,50 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { } } + fn consider_object_bound_candidate( + ecx: &mut EvalCtxt<'_, 'tcx>, + goal: Goal<'tcx, Self>, + assumption: ty::Predicate<'tcx>, + ) -> QueryResult<'tcx> { + if let Some(poly_projection_pred) = assumption.to_opt_poly_projection_pred() + && poly_projection_pred.projection_def_id() == goal.predicate.def_id() + { + ecx.probe(|ecx| { + let assumption_projection_pred = + ecx.instantiate_binder_with_infer(poly_projection_pred); + let mut nested_goals = ecx.eq( + goal.param_env, + goal.predicate.projection_ty, + assumption_projection_pred.projection_ty, + )?; + + let tcx = ecx.tcx(); + let ty::Dynamic(bounds, _, _) = *goal.predicate.self_ty().kind() else { + bug!("expected object type in `consider_object_bound_candidate`"); + }; + nested_goals.extend( + structural_traits::predicates_for_object_candidate( + tcx, + goal.predicate.projection_ty.trait_ref(tcx), + bounds, + ) + .into_iter() + .map(|pred| goal.with(tcx, pred)), + ); + + let subst_certainty = ecx.evaluate_all(nested_goals)?; + + ecx.eq_term_and_make_canonical_response( + goal, + subst_certainty, + assumption_projection_pred.term, + ) + }) + } else { + Err(NoSolution) + } + } + fn consider_impl_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, ProjectionPredicate<'tcx>>, diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals.rs b/compiler/rustc_trait_selection/src/solve/trait_goals.rs index aff79739d45ff..9aded080f20ba 100644 --- a/compiler/rustc_trait_selection/src/solve/trait_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/trait_goals.rs @@ -86,6 +86,45 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { } } + fn consider_object_bound_candidate( + ecx: &mut EvalCtxt<'_, 'tcx>, + goal: Goal<'tcx, Self>, + assumption: ty::Predicate<'tcx>, + ) -> QueryResult<'tcx> { + if let Some(poly_trait_pred) = assumption.to_opt_poly_trait_pred() + && poly_trait_pred.def_id() == goal.predicate.def_id() + { + // FIXME: Constness and polarity + ecx.probe(|ecx| { + let assumption_trait_pred = + ecx.instantiate_binder_with_infer(poly_trait_pred); + let mut nested_goals = ecx.eq( + goal.param_env, + goal.predicate.trait_ref, + assumption_trait_pred.trait_ref, + )?; + + let tcx = ecx.tcx(); + let ty::Dynamic(bounds, _, _) = *goal.predicate.self_ty().kind() else { + bug!("expected object type in `consider_object_bound_candidate`"); + }; + nested_goals.extend( + structural_traits::predicates_for_object_candidate( + tcx, + goal.predicate.trait_ref, + bounds, + ) + .into_iter() + .map(|pred| goal.with(tcx, pred)), + ); + + ecx.evaluate_all_and_make_canonical_response(nested_goals) + }) + } else { + Err(NoSolution) + } + } + fn consider_auto_trait_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals/structural_traits.rs b/compiler/rustc_trait_selection/src/solve/trait_goals/structural_traits.rs index 2c13465d347c4..3fcf96be203ca 100644 --- a/compiler/rustc_trait_selection/src/solve/trait_goals/structural_traits.rs +++ b/compiler/rustc_trait_selection/src/solve/trait_goals/structural_traits.rs @@ -1,6 +1,7 @@ +use rustc_data_structures::fx::FxHashMap; use rustc_hir::{Movability, Mutability}; use rustc_infer::traits::query::NoSolution; -use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable}; use crate::solve::EvalCtxt; @@ -233,3 +234,62 @@ pub(crate) fn extract_tupled_inputs_and_output_from_callable<'tcx>( } } } + +pub(crate) fn predicates_for_object_candidate<'tcx>( + tcx: TyCtxt<'tcx>, + trait_ref: ty::TraitRef<'tcx>, + object_bound: &'tcx ty::List>, +) -> Vec> { + let mut requirements = vec![]; + requirements.extend( + tcx.super_predicates_of(trait_ref.def_id).instantiate(tcx, trait_ref.substs).predicates, + ); + for item in tcx.associated_items(trait_ref.def_id).in_definition_order() { + if item.kind == ty::AssocKind::Type { + requirements.extend(tcx.item_bounds(item.def_id).subst(tcx, trait_ref.substs)); + } + } + + let mut replace_projection_with = FxHashMap::default(); + for bound in object_bound { + let bound = bound.no_bound_vars().expect("higher-ranked projections not supported, yet"); + if let ty::ExistentialPredicate::Projection(proj) = bound { + let proj = proj.with_self_ty(tcx, trait_ref.self_ty()); + let old_ty = replace_projection_with.insert( + proj.projection_ty, + proj.term.ty().expect("expected only types in dyn right now"), + ); + assert_eq!( + old_ty, + None, + "{} has two substitutions: {} and {}", + proj.projection_ty, + proj.term, + old_ty.unwrap() + ); + } + } + + requirements.fold_with(&mut ReplaceProjectionWith { tcx, mapping: replace_projection_with }) +} + +struct ReplaceProjectionWith<'tcx> { + tcx: TyCtxt<'tcx>, + mapping: FxHashMap, Ty<'tcx>>, +} + +impl<'tcx> TypeFolder> for ReplaceProjectionWith<'tcx> { + fn interner(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> { + if let ty::Alias(ty::Projection, alias_ty) = *ty.kind() + && let Some(replacement) = self.mapping.get(&alias_ty) + { + *replacement + } else { + ty.super_fold_with(self) + } + } +} diff --git a/tests/ui/traits/new-solver/object-unsafety.rs b/tests/ui/traits/new-solver/object-unsafety.rs new file mode 100644 index 0000000000000..7bdd863a762c4 --- /dev/null +++ b/tests/ui/traits/new-solver/object-unsafety.rs @@ -0,0 +1,20 @@ +// compile-flags: -Ztrait-solver=next + +trait Setup { + type From: Copy; +} + +fn copy(from: &U::From) -> U::From { + *from +} + +pub fn copy_any(t: &T) -> T { + copy::>(t) + //~^ ERROR the trait bound `dyn Setup: Setup` is not satisfied +} + +fn main() { + let x = String::from("Hello, world"); + let y = copy_any(&x); + println!("{y}"); +} diff --git a/tests/ui/traits/new-solver/object-unsafety.stderr b/tests/ui/traits/new-solver/object-unsafety.stderr new file mode 100644 index 0000000000000..198ac623df8a6 --- /dev/null +++ b/tests/ui/traits/new-solver/object-unsafety.stderr @@ -0,0 +1,19 @@ +error[E0277]: the trait bound `dyn Setup: Setup` is not satisfied + --> $DIR/object-unsafety.rs:12:12 + | +LL | copy::>(t) + | ^^^^^^^^^^^^^^^^^ the trait `Setup` is not implemented for `dyn Setup` + | +note: required by a bound in `copy` + --> $DIR/object-unsafety.rs:7:12 + | +LL | fn copy(from: &U::From) -> U::From { + | ^^^^^ required by this bound in `copy` +help: consider introducing a `where` clause, but there might be an alternative better way to express this requirement + | +LL | pub fn copy_any(t: &T) -> T where dyn Setup: Setup { + | ++++++++++++++++++++++++++++++++ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0277`. From 2540c2b76138b988629b48f54381835f13f18792 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 22 Feb 2023 01:26:01 +0000 Subject: [PATCH 2/3] Make higher-ranked projections in object types work in new solver --- .../src/solve/project_goals.rs | 3 +- .../src/solve/trait_goals.rs | 3 +- .../solve/trait_goals/structural_traits.rs | 43 ++++++++++++------- .../new-solver/higher-ranked-dyn-bounds.rs | 17 ++++++++ 4 files changed, 48 insertions(+), 18 deletions(-) create mode 100644 tests/ui/traits/new-solver/higher-ranked-dyn-bounds.rs diff --git a/compiler/rustc_trait_selection/src/solve/project_goals.rs b/compiler/rustc_trait_selection/src/solve/project_goals.rs index 807527e866002..88fd8bb8bd095 100644 --- a/compiler/rustc_trait_selection/src/solve/project_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/project_goals.rs @@ -151,7 +151,8 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { }; nested_goals.extend( structural_traits::predicates_for_object_candidate( - tcx, + ecx, + goal.param_env, goal.predicate.projection_ty.trait_ref(tcx), bounds, ) diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals.rs b/compiler/rustc_trait_selection/src/solve/trait_goals.rs index 9aded080f20ba..6048082228b35 100644 --- a/compiler/rustc_trait_selection/src/solve/trait_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/trait_goals.rs @@ -110,7 +110,8 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { }; nested_goals.extend( structural_traits::predicates_for_object_candidate( - tcx, + ecx, + goal.param_env, goal.predicate.trait_ref, bounds, ) diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals/structural_traits.rs b/compiler/rustc_trait_selection/src/solve/trait_goals/structural_traits.rs index 3fcf96be203ca..bd4950d8067cf 100644 --- a/compiler/rustc_trait_selection/src/solve/trait_goals/structural_traits.rs +++ b/compiler/rustc_trait_selection/src/solve/trait_goals/structural_traits.rs @@ -1,5 +1,5 @@ use rustc_data_structures::fx::FxHashMap; -use rustc_hir::{Movability, Mutability}; +use rustc_hir::{def_id::DefId, Movability, Mutability}; use rustc_infer::traits::query::NoSolution; use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable}; @@ -236,10 +236,12 @@ pub(crate) fn extract_tupled_inputs_and_output_from_callable<'tcx>( } pub(crate) fn predicates_for_object_candidate<'tcx>( - tcx: TyCtxt<'tcx>, + ecx: &EvalCtxt<'_, 'tcx>, + param_env: ty::ParamEnv<'tcx>, trait_ref: ty::TraitRef<'tcx>, object_bound: &'tcx ty::List>, ) -> Vec> { + let tcx = ecx.tcx(); let mut requirements = vec![]; requirements.extend( tcx.super_predicates_of(trait_ref.def_id).instantiate(tcx, trait_ref.substs).predicates, @@ -252,13 +254,9 @@ pub(crate) fn predicates_for_object_candidate<'tcx>( let mut replace_projection_with = FxHashMap::default(); for bound in object_bound { - let bound = bound.no_bound_vars().expect("higher-ranked projections not supported, yet"); - if let ty::ExistentialPredicate::Projection(proj) = bound { + if let ty::ExistentialPredicate::Projection(proj) = bound.skip_binder() { let proj = proj.with_self_ty(tcx, trait_ref.self_ty()); - let old_ty = replace_projection_with.insert( - proj.projection_ty, - proj.term.ty().expect("expected only types in dyn right now"), - ); + let old_ty = replace_projection_with.insert(proj.def_id(), bound.rebind(proj)); assert_eq!( old_ty, None, @@ -270,24 +268,37 @@ pub(crate) fn predicates_for_object_candidate<'tcx>( } } - requirements.fold_with(&mut ReplaceProjectionWith { tcx, mapping: replace_projection_with }) + requirements.fold_with(&mut ReplaceProjectionWith { + ecx, + param_env, + mapping: replace_projection_with, + }) } -struct ReplaceProjectionWith<'tcx> { - tcx: TyCtxt<'tcx>, - mapping: FxHashMap, Ty<'tcx>>, +struct ReplaceProjectionWith<'a, 'tcx> { + ecx: &'a EvalCtxt<'a, 'tcx>, + param_env: ty::ParamEnv<'tcx>, + mapping: FxHashMap>, } -impl<'tcx> TypeFolder> for ReplaceProjectionWith<'tcx> { +impl<'tcx> TypeFolder> for ReplaceProjectionWith<'_, 'tcx> { fn interner(&self) -> TyCtxt<'tcx> { - self.tcx + self.ecx.tcx() } fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> { if let ty::Alias(ty::Projection, alias_ty) = *ty.kind() - && let Some(replacement) = self.mapping.get(&alias_ty) + && let Some(replacement) = self.mapping.get(&alias_ty.def_id) { - *replacement + let proj = self.ecx.instantiate_binder_with_infer(*replacement); + // Technically this folder could be fallible? + let nested = self + .ecx + .eq(self.param_env, alias_ty, proj.projection_ty) + .expect("expected to be able to unify goal projection with dyn's projection"); + // Technically we could register these too.. + assert!(nested.is_empty(), "did not expect unification to have any nested goals"); + proj.term.ty().unwrap() } else { ty.super_fold_with(self) } diff --git a/tests/ui/traits/new-solver/higher-ranked-dyn-bounds.rs b/tests/ui/traits/new-solver/higher-ranked-dyn-bounds.rs new file mode 100644 index 0000000000000..c886aeeda3e46 --- /dev/null +++ b/tests/ui/traits/new-solver/higher-ranked-dyn-bounds.rs @@ -0,0 +1,17 @@ +// compile-flags: -Ztrait-solver=next +// check-pass + +trait Trait<'a> { + type Item: for<'b> Trait2<'b>; +} + +trait Trait2<'a> {} +impl Trait2<'_> for () {} + +fn needs_trait(_: Box Trait<'a> + ?Sized>) {} + +fn foo(x: Box Trait<'a, Item = ()>>) { + needs_trait(x); +} + +fn main() {} From ed30efff3b2d9e4636d3b1b9e9dc275362b64a62 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 22 Feb 2023 01:39:41 +0000 Subject: [PATCH 3/3] Comments, another test --- .../solve/trait_goals/structural_traits.rs | 43 ++++++++++++++++++- .../ui/traits/new-solver/more-object-bound.rs | 27 ++++++++++++ .../new-solver/more-object-bound.stderr | 19 ++++++++ 3 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 tests/ui/traits/new-solver/more-object-bound.rs create mode 100644 tests/ui/traits/new-solver/more-object-bound.stderr diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals/structural_traits.rs b/compiler/rustc_trait_selection/src/solve/trait_goals/structural_traits.rs index bd4950d8067cf..9b6cce86fffc4 100644 --- a/compiler/rustc_trait_selection/src/solve/trait_goals/structural_traits.rs +++ b/compiler/rustc_trait_selection/src/solve/trait_goals/structural_traits.rs @@ -235,6 +235,40 @@ pub(crate) fn extract_tupled_inputs_and_output_from_callable<'tcx>( } } +/// Assemble a list of predicates that would be present on a theoretical +/// user impl for an object type. These predicates must be checked any time +/// we assemble a built-in object candidate for an object type, since they +/// are not implied by the well-formedness of the type. +/// +/// For example, given the following traits: +/// +/// ```rust,ignore (theoretical code) +/// trait Foo: Baz { +/// type Bar: Copy; +/// } +/// +/// trait Baz {} +/// ``` +/// +/// For the dyn type `dyn Foo`, we can imagine there being a +/// pair of theoretical impls: +/// +/// ```rust,ignore (theoretical code) +/// impl Foo for dyn Foo +/// where +/// Self: Baz, +/// ::Bar: Copy, +/// { +/// type Bar = Ty; +/// } +/// +/// impl Baz for dyn Foo {} +/// ``` +/// +/// However, in order to make such impls well-formed, we need to do an +/// additional step of eagerly folding the associated types in the where +/// clauses of the impl. In this example, that means replacing +/// `::Bar` with `Ty` in the first impl. pub(crate) fn predicates_for_object_candidate<'tcx>( ecx: &EvalCtxt<'_, 'tcx>, param_env: ty::ParamEnv<'tcx>, @@ -247,6 +281,8 @@ pub(crate) fn predicates_for_object_candidate<'tcx>( tcx.super_predicates_of(trait_ref.def_id).instantiate(tcx, trait_ref.substs).predicates, ); for item in tcx.associated_items(trait_ref.def_id).in_definition_order() { + // FIXME(associated_const_equality): Also add associated consts to + // the requirements here. if item.kind == ty::AssocKind::Type { requirements.extend(tcx.item_bounds(item.def_id).subst(tcx, trait_ref.substs)); } @@ -290,13 +326,16 @@ impl<'tcx> TypeFolder> for ReplaceProjectionWith<'_, 'tcx> { if let ty::Alias(ty::Projection, alias_ty) = *ty.kind() && let Some(replacement) = self.mapping.get(&alias_ty.def_id) { + // We may have a case where our object type's projection bound is higher-ranked, + // but the where clauses we instantiated are not. We can solve this by instantiating + // the binder at the usage site. let proj = self.ecx.instantiate_binder_with_infer(*replacement); - // Technically this folder could be fallible? + // FIXME: Technically this folder could be fallible? let nested = self .ecx .eq(self.param_env, alias_ty, proj.projection_ty) .expect("expected to be able to unify goal projection with dyn's projection"); - // Technically we could register these too.. + // FIXME: Technically we could register these too.. assert!(nested.is_empty(), "did not expect unification to have any nested goals"); proj.term.ty().unwrap() } else { diff --git a/tests/ui/traits/new-solver/more-object-bound.rs b/tests/ui/traits/new-solver/more-object-bound.rs new file mode 100644 index 0000000000000..712759ef0e612 --- /dev/null +++ b/tests/ui/traits/new-solver/more-object-bound.rs @@ -0,0 +1,27 @@ +// compile-flags: -Ztrait-solver=next +// From #80800 + +trait SuperTrait { + type A; + type B; +} + +trait Trait: SuperTrait::B> {} + +fn transmute(x: A) -> B { + foo::>(x) + //~^ ERROR type annotations needed: cannot satisfy `dyn Trait: Trait` +} + +fn foo(x: T::A) -> B +where + T: Trait, +{ + x +} + +static X: u8 = 0; +fn main() { + let x = transmute::<&u8, &[u8; 1_000_000]>(&X); + println!("{:?}", x[100_000]); +} diff --git a/tests/ui/traits/new-solver/more-object-bound.stderr b/tests/ui/traits/new-solver/more-object-bound.stderr new file mode 100644 index 0000000000000..208fdecb08fc9 --- /dev/null +++ b/tests/ui/traits/new-solver/more-object-bound.stderr @@ -0,0 +1,19 @@ +error[E0283]: type annotations needed: cannot satisfy `dyn Trait: Trait` + --> $DIR/more-object-bound.rs:12:5 + | +LL | foo::>(x) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: cannot satisfy `dyn Trait: Trait` +note: required by a bound in `foo` + --> $DIR/more-object-bound.rs:18:8 + | +LL | fn foo(x: T::A) -> B + | --- required by a bound in this function +LL | where +LL | T: Trait, + | ^^^^^^^^^^^^ required by this bound in `foo` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0283`.