Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Normalize anon consts in new solver #112183

Merged
merged 4 commits into from
Jun 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/rustc_hir_analysis/src/collect/generics_of.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<const N: usize, const M: [u8; N]>` 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
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir_analysis/src/collect/predicates_of.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_hir_analysis/src/outlives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_infer/src/infer/combine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 || self.tcx.trait_solver_next() =>
{
relation.register_const_equate_obligation(a, b);
return Ok(b);
Expand Down
9 changes: 0 additions & 9 deletions compiler/rustc_middle/src/ty/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
24 changes: 22 additions & 2 deletions compiler/rustc_trait_selection/src/solve/eval_ctxt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down Expand Up @@ -772,4 +775,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<ty::Const<'tcx>> {
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,
}
}
}
13 changes: 2 additions & 11 deletions compiler/rustc_trait_selection/src/solve/fulfill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(_)
Expand All @@ -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:?}")
}
},
Expand Down
71 changes: 52 additions & 19 deletions compiler/rustc_trait_selection/src/solve/project_goals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,65 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
&mut self,
goal: Goal<'tcx, ProjectionPredicate<'tcx>>,
) -> QueryResult<'tcx> {
match goal.predicate.projection_ty.kind(self.tcx()) {
ty::AliasKind::Projection => {
let def_id = goal.predicate.def_id();
match self.tcx().def_kind(def_id) {
DefKind::AssocTy | DefKind::AssocConst => {
// To only compute normalization once for each projection we only
// normalize if the expected term is an unconstrained inference variable.
// 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 `<T as Trait>::Assoc == u32` we recursively compute the goal
// `exists<U> <T as Trait>::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.
// projection cache in the solver, since we're piggybacking off of regular
// goal caching.
if self.term_is_fully_unconstrained(goal) {
let candidates = self.assemble_and_evaluate_candidates(goal);
self.merge_candidates(candidates)
match self.tcx().associated_item(def_id).container {
ty::AssocItemContainer::TraitContainer => {
let candidates = self.assemble_and_evaluate_candidates(goal);
self.merge_candidates(candidates)
}
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)
}
}
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!("unknown 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)
}
}
}
Expand Down Expand Up @@ -173,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<ty::Term<'tcx>> = 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))
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_trait_selection/src/traits/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,9 @@ impl<'a, 'b, 'tcx> TypeFolder<TyCtxt<'tcx>> 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);
Expand Down
8 changes: 8 additions & 0 deletions tests/ui/traits/new-solver/array-default.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// compile-flags: -Ztrait-solver=next
// check-pass

fn has_default<const N: usize>() where [(); N]: Default {}

fn main() {
has_default::<1>();
}
26 changes: 2 additions & 24 deletions tests/ui/traits/new-solver/structural-resolve-field.rs
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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<N>`:
<() 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<const N: usize>() where (): Trait<N> {}
| ^^^^^^^^ required by this bound in `needs`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
22 changes: 22 additions & 0 deletions tests/ui/traits/new-solver/unevaluated-const-impl-trait-ref.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// compile-flags: -Ztrait-solver=next
// revisions: works fails
//[works] check-pass

trait Trait<const N: usize> {}

impl Trait<{ 1 - 1 }> for () {}
impl Trait<{ 1 + 1 }> for () {}

fn needs<const N: usize>() where (): Trait<N> {}

#[cfg(works)]
fn main() {
needs::<0>();
needs::<2>();
}

#[cfg(fails)]
fn main() {
needs::<1>();
//[fails]~^ ERROR the trait bound `(): Trait<1>` is not satisfied
}