From 4fbb43e70f7d4d35702f0b5b3069931123262b71 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 1 Jun 2023 19:56:46 +0000 Subject: [PATCH 1/4] No more TyCtxt::lazy_normalization --- compiler/rustc_hir_analysis/src/collect/generics_of.rs | 2 +- compiler/rustc_hir_analysis/src/collect/predicates_of.rs | 2 +- compiler/rustc_hir_analysis/src/outlives/mod.rs | 3 ++- compiler/rustc_infer/src/infer/combine.rs | 2 +- compiler/rustc_middle/src/ty/context.rs | 9 --------- compiler/rustc_trait_selection/src/traits/project.rs | 4 +++- 6 files changed, 8 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/collect/generics_of.rs b/compiler/rustc_hir_analysis/src/collect/generics_of.rs index ed60998ec8dcf..c6481022d7507 100644 --- a/compiler/rustc_hir_analysis/src/collect/generics_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/generics_of.rs @@ -50,7 +50,7 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { // We do not allow generic parameters in anon consts if we are inside // of a const parameter type, e.g. `struct Foo` is not allowed. None - } else if tcx.lazy_normalization() { + } else if tcx.features().generic_const_exprs { let parent_node = tcx.hir().get_parent(hir_id); if let Node::Variant(Variant { disr_expr: Some(constant), .. }) = parent_node && constant.hir_id == hir_id diff --git a/compiler/rustc_hir_analysis/src/collect/predicates_of.rs b/compiler/rustc_hir_analysis/src/collect/predicates_of.rs index 70d950eddd8a7..dcb5790292879 100644 --- a/compiler/rustc_hir_analysis/src/collect/predicates_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/predicates_of.rs @@ -463,7 +463,7 @@ pub(super) fn explicit_predicates_of<'tcx>( } } } else { - if matches!(def_kind, DefKind::AnonConst) && tcx.lazy_normalization() { + if matches!(def_kind, DefKind::AnonConst) && tcx.features().generic_const_exprs { let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); let parent_def_id = tcx.hir().get_parent_item(hir_id); diff --git a/compiler/rustc_hir_analysis/src/outlives/mod.rs b/compiler/rustc_hir_analysis/src/outlives/mod.rs index 2106d6ff07dcc..fdbb890ce3d47 100644 --- a/compiler/rustc_hir_analysis/src/outlives/mod.rs +++ b/compiler/rustc_hir_analysis/src/outlives/mod.rs @@ -20,7 +20,8 @@ pub fn provide(providers: &mut Providers) { fn inferred_outlives_of(tcx: TyCtxt<'_>, item_def_id: LocalDefId) -> &[(ty::Clause<'_>, Span)] { let id = tcx.hir().local_def_id_to_hir_id(item_def_id); - if matches!(tcx.def_kind(item_def_id), hir::def::DefKind::AnonConst) && tcx.lazy_normalization() + if matches!(tcx.def_kind(item_def_id), hir::def::DefKind::AnonConst) + && tcx.features().generic_const_exprs { if tcx.hir().opt_const_param_default_param_def_id(id).is_some() { // In `generics_of` we set the generics' parent to be our parent's parent which means that diff --git a/compiler/rustc_infer/src/infer/combine.rs b/compiler/rustc_infer/src/infer/combine.rs index b6b935de68c84..94a72e5a4bda6 100644 --- a/compiler/rustc_infer/src/infer/combine.rs +++ b/compiler/rustc_infer/src/infer/combine.rs @@ -227,7 +227,7 @@ impl<'tcx> InferCtxt<'tcx> { return self.unify_const_variable(vid, a, relation.param_env()); } (ty::ConstKind::Unevaluated(..), _) | (_, ty::ConstKind::Unevaluated(..)) - if self.tcx.lazy_normalization() => + if self.tcx.features().generic_const_exprs => { relation.register_const_equate_obligation(a, b); return Ok(b); diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index e7335104e619f..bf6f21968d71b 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -1015,15 +1015,6 @@ impl<'tcx> TyCtxt<'tcx> { self.query_system.on_disk_cache.as_ref().map_or(Ok(0), |c| c.serialize(self, encoder)) } - /// If `true`, we should use lazy normalization for constants, otherwise - /// we still evaluate them eagerly. - #[inline] - pub fn lazy_normalization(self) -> bool { - let features = self.features(); - // Note: We only use lazy normalization for generic const expressions. - features.generic_const_exprs - } - #[inline] pub fn local_crate_exports_generics(self) -> bool { debug_assert!(self.sess.opts.share_generics()); diff --git a/compiler/rustc_trait_selection/src/traits/project.rs b/compiler/rustc_trait_selection/src/traits/project.rs index 65af0bb1c4e1f..563cc257e0349 100644 --- a/compiler/rustc_trait_selection/src/traits/project.rs +++ b/compiler/rustc_trait_selection/src/traits/project.rs @@ -672,7 +672,9 @@ impl<'a, 'b, 'tcx> TypeFolder> for AssocTypeNormalizer<'a, 'b, 'tcx #[instrument(skip(self), level = "debug")] fn fold_const(&mut self, constant: ty::Const<'tcx>) -> ty::Const<'tcx> { let tcx = self.selcx.tcx(); - if tcx.lazy_normalization() || !needs_normalization(&constant, self.param_env.reveal()) { + if tcx.features().generic_const_exprs + || !needs_normalization(&constant, self.param_env.reveal()) + { constant } else { let constant = constant.super_fold_with(self); From 2c1473ca7042ab42489e11f3a1b97d7ecd39aa49 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 1 Jun 2023 20:23:44 +0000 Subject: [PATCH 2/4] Normalize anon consts in new solver --- compiler/rustc_infer/src/infer/combine.rs | 2 +- .../src/solve/eval_ctxt.rs | 17 +++++ .../src/solve/project_goals.rs | 64 ++++++++++++++----- tests/ui/traits/new-solver/array-default.rs | 8 +++ .../new-solver/structural-resolve-field.rs | 26 +------- ...valuated-const-impl-trait-ref.fails.stderr | 18 ++++++ .../unevaluated-const-impl-trait-ref.rs | 22 +++++++ 7 files changed, 115 insertions(+), 42 deletions(-) create mode 100644 tests/ui/traits/new-solver/array-default.rs create mode 100644 tests/ui/traits/new-solver/unevaluated-const-impl-trait-ref.fails.stderr create mode 100644 tests/ui/traits/new-solver/unevaluated-const-impl-trait-ref.rs diff --git a/compiler/rustc_infer/src/infer/combine.rs b/compiler/rustc_infer/src/infer/combine.rs index 94a72e5a4bda6..ed532aa2e8ba8 100644 --- a/compiler/rustc_infer/src/infer/combine.rs +++ b/compiler/rustc_infer/src/infer/combine.rs @@ -227,7 +227,7 @@ impl<'tcx> InferCtxt<'tcx> { return self.unify_const_variable(vid, a, relation.param_env()); } (ty::ConstKind::Unevaluated(..), _) | (_, ty::ConstKind::Unevaluated(..)) - if self.tcx.features().generic_const_exprs => + if self.tcx.features().generic_const_exprs || self.tcx.trait_solver_next() => { relation.register_const_equate_obligation(a, b); return Ok(b); diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs index f91c672775301..a004e903b5ea5 100644 --- a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs +++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs @@ -772,4 +772,21 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { } values } + + // Try to evaluate a const, or return `None` if the const is too generic. + // This doesn't mean the const isn't evaluatable, though, and should be treated + // as an ambiguity rather than no-solution. + pub(super) fn try_const_eval_resolve( + &self, + param_env: ty::ParamEnv<'tcx>, + unevaluated: ty::UnevaluatedConst<'tcx>, + ty: Ty<'tcx>, + ) -> Option> { + use rustc_middle::mir::interpret::ErrorHandled; + match self.infcx.try_const_eval_resolve(param_env, unevaluated, ty, None) { + Ok(ct) => Some(ct), + Err(ErrorHandled::Reported(e)) => Some(self.tcx().const_error(ty, e.into())), + Err(ErrorHandled::TooGeneric) => None, + } + } } diff --git a/compiler/rustc_trait_selection/src/solve/project_goals.rs b/compiler/rustc_trait_selection/src/solve/project_goals.rs index 7d7dfa2c83776..99ed9ac7b6208 100644 --- a/compiler/rustc_trait_selection/src/solve/project_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/project_goals.rs @@ -22,25 +22,55 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { &mut self, goal: Goal<'tcx, ProjectionPredicate<'tcx>>, ) -> QueryResult<'tcx> { - match goal.predicate.projection_ty.kind(self.tcx()) { - ty::AliasKind::Projection => { - // To only compute normalization once for each projection we only - // normalize if the expected term is an unconstrained inference variable. - // - // E.g. for `::Assoc == u32` we recursively compute the goal - // `exists ::Assoc == U` and then take the resulting type for - // `U` and equate it with `u32`. This means that we don't need a separate - // projection cache in the solver. - if self.term_is_fully_unconstrained(goal) { - let candidates = self.assemble_and_evaluate_candidates(goal); - self.merge_candidates(candidates) - } else { - self.set_normalizes_to_hack_goal(goal); - self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + let def_id = goal.predicate.def_id(); + match self.tcx().def_kind(def_id) { + DefKind::AssocTy | DefKind::AssocConst => { + match self.tcx().associated_item(def_id).container { + ty::AssocItemContainer::TraitContainer => { + // To only compute normalization once for each projection we only + // normalize if the expected term is an unconstrained inference variable. + // + // E.g. for `::Assoc == u32` we recursively compute the goal + // `exists ::Assoc == U` and then take the resulting type for + // `U` and equate it with `u32`. This means that we don't need a separate + // projection cache in the solver. + if self.term_is_fully_unconstrained(goal) { + let candidates = self.assemble_and_evaluate_candidates(goal); + self.merge_candidates(candidates) + } else { + self.set_normalizes_to_hack_goal(goal); + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + } + } + ty::AssocItemContainer::ImplContainer => bug!("IATs not supported here yet"), } } - ty::AliasKind::Opaque => self.normalize_opaque_type(goal), - ty::AliasKind::Inherent => bug!("IATs not supported here yet"), + DefKind::AnonConst => self.normalize_anon_const(goal), + DefKind::OpaqueTy => self.normalize_opaque_type(goal), + kind => bug!("uknown DefKind {} in projection goal: {goal:#?}", kind.descr(def_id)), + } + } + + #[instrument(level = "debug", skip(self), ret)] + fn normalize_anon_const( + &mut self, + goal: Goal<'tcx, ty::ProjectionPredicate<'tcx>>, + ) -> QueryResult<'tcx> { + if let Some(normalized_const) = self.try_const_eval_resolve( + goal.param_env, + ty::UnevaluatedConst::new( + goal.predicate.projection_ty.def_id, + goal.predicate.projection_ty.substs, + ), + self.tcx() + .type_of(goal.predicate.projection_ty.def_id) + .no_bound_vars() + .expect("const ty should not rely on other generics"), + ) { + self.eq(goal.param_env, normalized_const, goal.predicate.term.ct().unwrap())?; + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + } else { + self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS) } } } diff --git a/tests/ui/traits/new-solver/array-default.rs b/tests/ui/traits/new-solver/array-default.rs new file mode 100644 index 0000000000000..5077137b09b4b --- /dev/null +++ b/tests/ui/traits/new-solver/array-default.rs @@ -0,0 +1,8 @@ +// compile-flags: -Ztrait-solver=next +// check-pass + +fn has_default() where [(); N]: Default {} + +fn main() { + has_default::<1>(); +} diff --git a/tests/ui/traits/new-solver/structural-resolve-field.rs b/tests/ui/traits/new-solver/structural-resolve-field.rs index c492d927696bd..01899c9ad645f 100644 --- a/tests/ui/traits/new-solver/structural-resolve-field.rs +++ b/tests/ui/traits/new-solver/structural-resolve-field.rs @@ -1,35 +1,13 @@ // compile-flags: -Ztrait-solver=next // check-pass +#[derive(Default)] struct Foo { x: i32, } -impl MyDefault for Foo { - fn my_default() -> Self { - Self { - x: 0, - } - } -} - -trait MyDefault { - fn my_default() -> Self; -} - -impl MyDefault for [Foo; 0] { - fn my_default() -> Self { - [] - } -} -impl MyDefault for [Foo; 1] { - fn my_default() -> Self { - [Foo::my_default(); 1] - } -} - fn main() { - let mut xs = <[Foo; 1]>::my_default(); + let mut xs = <[Foo; 1]>::default(); xs[0].x = 1; (&mut xs[0]).x = 2; } diff --git a/tests/ui/traits/new-solver/unevaluated-const-impl-trait-ref.fails.stderr b/tests/ui/traits/new-solver/unevaluated-const-impl-trait-ref.fails.stderr new file mode 100644 index 0000000000000..072ac32a5de97 --- /dev/null +++ b/tests/ui/traits/new-solver/unevaluated-const-impl-trait-ref.fails.stderr @@ -0,0 +1,18 @@ +error[E0277]: the trait bound `(): Trait<1>` is not satisfied + --> $DIR/unevaluated-const-impl-trait-ref.rs:20:13 + | +LL | needs::<1>(); + | ^ the trait `Trait<1>` is not implemented for `()` + | + = help: the following other types implement trait `Trait`: + <() as Trait<0>> + <() as Trait<2>> +note: required by a bound in `needs` + --> $DIR/unevaluated-const-impl-trait-ref.rs:10:38 + | +LL | fn needs() where (): Trait {} + | ^^^^^^^^ required by this bound in `needs` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/traits/new-solver/unevaluated-const-impl-trait-ref.rs b/tests/ui/traits/new-solver/unevaluated-const-impl-trait-ref.rs new file mode 100644 index 0000000000000..26c595bc97428 --- /dev/null +++ b/tests/ui/traits/new-solver/unevaluated-const-impl-trait-ref.rs @@ -0,0 +1,22 @@ +// compile-flags: -Ztrait-solver=next +// revisions: works fails +//[works] check-pass + +trait Trait {} + +impl Trait<{ 1 - 1 }> for () {} +impl Trait<{ 1 + 1 }> for () {} + +fn needs() where (): Trait {} + +#[cfg(works)] +fn main() { + needs::<0>(); + needs::<2>(); +} + +#[cfg(fails)] +fn main() { + needs::<1>(); + //[fails]~^ ERROR the trait bound `(): Trait<1>` is not satisfied +} From 8912015f71a7013ad343fd7e29c355234f3540ac Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 1 Jun 2023 20:26:31 +0000 Subject: [PATCH 3/4] No const equate in new solver --- .../rustc_trait_selection/src/solve/eval_ctxt.rs | 7 +++++-- compiler/rustc_trait_selection/src/solve/fulfill.rs | 13 ++----------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs index a004e903b5ea5..bc93b9e99ad44 100644 --- a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs +++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs @@ -322,10 +322,13 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { ty::PredicateKind::Ambiguous => { self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS) } - // FIXME: implement these predicates :) - ty::PredicateKind::ConstEvaluatable(_) | ty::PredicateKind::ConstEquate(_, _) => { + // FIXME: implement this predicate :) + ty::PredicateKind::ConstEvaluatable(_) => { self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } + ty::PredicateKind::ConstEquate(_, _) => { + bug!("ConstEquate should not be emitted when `-Ztrait-solver=next` is active") + } ty::PredicateKind::TypeWellFormedFromEnv(..) => { bug!("TypeWellFormedFromEnv is only used for Chalk") } diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs index 4a403196c7e05..212327448c874 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -118,16 +118,6 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> { TypeError::Sorts(expected_found), ) } - ty::PredicateKind::ConstEquate(a, b) => { - let (a, b) = infcx.instantiate_binder_with_placeholders( - goal.predicate.kind().rebind((a, b)), - ); - let expected_found = ExpectedFound::new(true, a, b); - FulfillmentErrorCode::CodeConstEquateError( - expected_found, - TypeError::ConstMismatch(expected_found), - ) - } ty::PredicateKind::Clause(_) | ty::PredicateKind::WellFormed(_) | ty::PredicateKind::ObjectSafe(_) @@ -138,7 +128,8 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> { SelectionError::Unimplemented, ) } - ty::PredicateKind::TypeWellFormedFromEnv(_) => { + ty::PredicateKind::ConstEquate(..) + | ty::PredicateKind::TypeWellFormedFromEnv(_) => { bug!("unexpected goal: {goal:?}") } }, From 84196f33710e2797a576ddc5241e418b7a4f1f2c Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Fri, 2 Jun 2023 22:07:53 +0000 Subject: [PATCH 4/4] Elaborate comment, make sure we do normalizes-to hack eventually for IATs, don't partially support const projection for impls --- .../src/solve/project_goals.rs | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_trait_selection/src/solve/project_goals.rs b/compiler/rustc_trait_selection/src/solve/project_goals.rs index 99ed9ac7b6208..23601f668ff05 100644 --- a/compiler/rustc_trait_selection/src/solve/project_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/project_goals.rs @@ -25,29 +25,39 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { let def_id = goal.predicate.def_id(); match self.tcx().def_kind(def_id) { DefKind::AssocTy | DefKind::AssocConst => { - match self.tcx().associated_item(def_id).container { - ty::AssocItemContainer::TraitContainer => { - // To only compute normalization once for each projection we only - // normalize if the expected term is an unconstrained inference variable. - // - // E.g. for `::Assoc == u32` we recursively compute the goal - // `exists ::Assoc == U` and then take the resulting type for - // `U` and equate it with `u32`. This means that we don't need a separate - // projection cache in the solver. - if self.term_is_fully_unconstrained(goal) { + // To only compute normalization once for each projection we only + // assemble normalization candidates if the expected term is an + // unconstrained inference variable. + // + // Why: For better cache hits, since if we have an unconstrained RHS then + // there are only as many cache keys as there are (canonicalized) alias + // types in each normalizes-to goal. This also weakens inference in a + // forwards-compatible way so we don't use the value of the RHS term to + // affect candidate assembly for projections. + // + // E.g. for `::Assoc == u32` we recursively compute the goal + // `exists ::Assoc == U` and then take the resulting type for + // `U` and equate it with `u32`. This means that we don't need a separate + // projection cache in the solver, since we're piggybacking off of regular + // goal caching. + if self.term_is_fully_unconstrained(goal) { + match self.tcx().associated_item(def_id).container { + ty::AssocItemContainer::TraitContainer => { let candidates = self.assemble_and_evaluate_candidates(goal); self.merge_candidates(candidates) - } else { - self.set_normalizes_to_hack_goal(goal); - self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + } + ty::AssocItemContainer::ImplContainer => { + bug!("IATs not supported here yet") } } - ty::AssocItemContainer::ImplContainer => bug!("IATs not supported here yet"), + } else { + self.set_normalizes_to_hack_goal(goal); + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } } DefKind::AnonConst => self.normalize_anon_const(goal), DefKind::OpaqueTy => self.normalize_opaque_type(goal), - kind => bug!("uknown DefKind {} in projection goal: {goal:#?}", kind.descr(def_id)), + kind => bug!("unknown DefKind {} in projection goal: {goal:#?}", kind.descr(def_id)), } } @@ -203,17 +213,10 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { ); // Finally we construct the actual value of the associated type. - let is_const = matches!(tcx.def_kind(assoc_def.item.def_id), DefKind::AssocConst); - let ty = tcx.type_of(assoc_def.item.def_id); - let term: ty::EarlyBinder> = if is_const { - let identity_substs = - ty::InternalSubsts::identity_for_item(tcx, assoc_def.item.def_id); - let did = assoc_def.item.def_id; - let kind = - ty::ConstKind::Unevaluated(ty::UnevaluatedConst::new(did, identity_substs)); - ty.map_bound(|ty| tcx.mk_const(kind, ty).into()) - } else { - ty.map_bound(|ty| ty.into()) + let term = match assoc_def.item.kind { + ty::AssocKind::Type => tcx.type_of(assoc_def.item.def_id).map_bound(|ty| ty.into()), + ty::AssocKind::Const => bug!("associated const projection is not supported yet"), + ty::AssocKind::Fn => unreachable!("we should never project to a fn"), }; ecx.eq(goal.param_env, goal.predicate.term, term.subst(tcx, substs))