diff --git a/compiler/rustc_trait_selection/src/solve/alias_relate.rs b/compiler/rustc_trait_selection/src/solve/alias_relate.rs index 67657c81cf6..e081a9100e2 100644 --- a/compiler/rustc_trait_selection/src/solve/alias_relate.rs +++ b/compiler/rustc_trait_selection/src/solve/alias_relate.rs @@ -21,8 +21,9 @@ //! However, if `?fresh_var` ends up geteting equated to another type, we retry the //! `NormalizesTo` goal, at which point the opaque is actually defined. -use super::{EvalCtxt, GoalSource}; +use super::EvalCtxt; use rustc_infer::traits::query::NoSolution; +use rustc_infer::traits::solve::GoalSource; use rustc_middle::traits::solve::{Certainty, Goal, QueryResult}; use rustc_middle::ty::{self, Ty}; @@ -121,10 +122,11 @@ fn try_normalize_term( ty::TermKind::Const(_) => { if let Some(alias) = term.to_alias_ty(self.tcx()) { let term = self.next_term_infer_of_kind(term); - self.add_goal( - GoalSource::Misc, - Goal::new(self.tcx(), param_env, ty::NormalizesTo { alias, term }), - ); + self.add_normalizes_to_goal(Goal::new( + self.tcx(), + param_env, + ty::NormalizesTo { alias, term }, + )); self.try_evaluate_added_goals()?; Ok(Some(self.resolve_vars_if_possible(term))) } else { @@ -145,18 +147,25 @@ fn try_normalize_ty_recur( return None; } - let ty::Alias(_, alias) = *ty.kind() else { + let ty::Alias(kind, alias) = *ty.kind() else { return Some(ty); }; match self.commit_if_ok(|this| { + let tcx = this.tcx(); let normalized_ty = this.next_ty_infer(); - let normalizes_to_goal = Goal::new( - this.tcx(), - param_env, - ty::NormalizesTo { alias, term: normalized_ty.into() }, - ); - this.add_goal(GoalSource::Misc, normalizes_to_goal); + let normalizes_to = ty::NormalizesTo { alias, term: normalized_ty.into() }; + match kind { + ty::AliasKind::Opaque => { + // HACK: Unlike for associated types, `normalizes-to` for opaques + // is currently not treated as a function. We do not erase the + // expected term. + this.add_goal(GoalSource::Misc, Goal::new(tcx, param_env, normalizes_to)); + } + ty::AliasKind::Projection | ty::AliasKind::Inherent | ty::AliasKind::Weak => { + this.add_normalizes_to_goal(Goal::new(tcx, param_env, normalizes_to)) + } + } this.try_evaluate_added_goals()?; Ok(this.resolve_vars_if_possible(normalized_ty)) }) { diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs index 3b858cb449f..6444f12493e 100644 --- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs @@ -93,7 +93,7 @@ pub struct EvalCtxt<'a, 'tcx> { #[derive(Debug, Clone)] pub(super) struct NestedGoals<'tcx> { - /// This normalizes-to goal that is treated specially during the evaluation + /// These normalizes-to goals are treated specially during the evaluation /// loop. In each iteration we take the RHS of the projection, replace it with /// a fresh inference variable, and only after evaluating that goal do we /// equate the fresh inference variable with the actual RHS of the predicate. @@ -101,26 +101,24 @@ pub(super) struct NestedGoals<'tcx> { /// This is both to improve caching, and to avoid using the RHS of the /// projection predicate to influence the normalizes-to candidate we select. /// - /// This is not a 'real' nested goal. We must not forget to replace the RHS - /// with a fresh inference variable when we evaluate this goal. That can result - /// in a trait solver cycle. This would currently result in overflow but can be - /// can be unsound with more powerful coinduction in the future. - pub(super) normalizes_to_hack_goal: Option>>, + /// Forgetting to replace the RHS with a fresh inference variable when we evaluate + /// this goal results in an ICE.. + pub(super) normalizes_to_goals: Vec>>, /// The rest of the goals which have not yet processed or remain ambiguous. pub(super) goals: Vec<(GoalSource, Goal<'tcx, ty::Predicate<'tcx>>)>, } impl<'tcx> NestedGoals<'tcx> { pub(super) fn new() -> Self { - Self { normalizes_to_hack_goal: None, goals: Vec::new() } + Self { normalizes_to_goals: Vec::new(), goals: Vec::new() } } pub(super) fn is_empty(&self) -> bool { - self.normalizes_to_hack_goal.is_none() && self.goals.is_empty() + self.normalizes_to_goals.is_empty() && self.goals.is_empty() } pub(super) fn extend(&mut self, other: NestedGoals<'tcx>) { - assert_eq!(other.normalizes_to_hack_goal, None); + self.normalizes_to_goals.extend(other.normalizes_to_goals); self.goals.extend(other.goals) } } @@ -508,7 +506,7 @@ fn with_misc_source<'tcx>( // If this loop did not result in any progress, what's our final certainty. let mut unchanged_certainty = Some(Certainty::Yes); - if let Some(goal) = goals.normalizes_to_hack_goal.take() { + for goal in goals.normalizes_to_goals { // Replace the goal with an unconstrained infer var, so the // RHS does not affect projection candidate assembly. let unconstrained_rhs = self.next_term_infer_of_kind(goal.predicate.term); @@ -536,22 +534,21 @@ fn with_misc_source<'tcx>( // looking at the "has changed" return from evaluate_goal, // because we expect the `unconstrained_rhs` part of the predicate // to have changed -- that means we actually normalized successfully! - if goal.predicate.alias != self.resolve_vars_if_possible(goal.predicate.alias) { + let with_resolved_vars = self.resolve_vars_if_possible(goal); + if goal.predicate.alias != with_resolved_vars.predicate.alias { unchanged_certainty = None; } match certainty { Certainty::Yes => {} Certainty::Maybe(_) => { - // We need to resolve vars here so that we correctly - // deal with `has_changed` in the next iteration. - self.set_normalizes_to_hack_goal(self.resolve_vars_if_possible(goal)); + self.nested_goals.normalizes_to_goals.push(with_resolved_vars); unchanged_certainty = unchanged_certainty.map(|c| c.unify_with(certainty)); } } } - for (source, goal) in goals.goals.drain(..) { + for (source, goal) in goals.goals { let (has_changed, certainty) = self.evaluate_goal( GoalEvaluationKind::Nested { is_normalizes_to_hack: IsNormalizesToHack::No }, source, diff --git a/compiler/rustc_trait_selection/src/solve/inspect/build.rs b/compiler/rustc_trait_selection/src/solve/inspect/build.rs index f7b310a7abe..02a8585d701 100644 --- a/compiler/rustc_trait_selection/src/solve/inspect/build.rs +++ b/compiler/rustc_trait_selection/src/solve/inspect/build.rs @@ -419,6 +419,17 @@ pub fn probe_kind(&mut self, probe_kind: inspect::ProbeKind<'tcx>) { } } + pub fn add_normalizes_to_goal( + ecx: &mut EvalCtxt<'_, 'tcx>, + goal: Goal<'tcx, ty::NormalizesTo<'tcx>>, + ) { + if ecx.inspect.is_noop() { + return; + } + + Self::add_goal(ecx, GoalSource::Misc, goal.with(ecx.tcx(), goal.predicate)); + } + pub fn add_goal( ecx: &mut EvalCtxt<'_, 'tcx>, source: GoalSource, diff --git a/compiler/rustc_trait_selection/src/solve/mod.rs b/compiler/rustc_trait_selection/src/solve/mod.rs index 0bf28f520a4..e40ccd4cbce 100644 --- a/compiler/rustc_trait_selection/src/solve/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/mod.rs @@ -202,12 +202,9 @@ fn compute_const_arg_has_type_goal( impl<'tcx> EvalCtxt<'_, 'tcx> { #[instrument(level = "debug", skip(self))] - fn set_normalizes_to_hack_goal(&mut self, goal: Goal<'tcx, ty::NormalizesTo<'tcx>>) { - assert!( - self.nested_goals.normalizes_to_hack_goal.is_none(), - "attempted to set the projection eq hack goal when one already exists" - ); - self.nested_goals.normalizes_to_hack_goal = Some(goal); + fn add_normalizes_to_goal(&mut self, goal: Goal<'tcx, ty::NormalizesTo<'tcx>>) { + inspect::ProofTreeBuilder::add_normalizes_to_goal(self, goal); + self.nested_goals.normalizes_to_goals.push(goal); } #[instrument(level = "debug", skip(self))] diff --git a/compiler/rustc_trait_selection/src/solve/normalizes_to/anon_const.rs b/compiler/rustc_trait_selection/src/solve/normalizes_to/anon_const.rs index 911462f4b9a..37d56452893 100644 --- a/compiler/rustc_trait_selection/src/solve/normalizes_to/anon_const.rs +++ b/compiler/rustc_trait_selection/src/solve/normalizes_to/anon_const.rs @@ -16,7 +16,7 @@ pub(super) fn normalize_anon_const( .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.instantiate_normalizes_to_term(goal, normalized_const.into()); self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } else { self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS) diff --git a/compiler/rustc_trait_selection/src/solve/normalizes_to/inherent.rs b/compiler/rustc_trait_selection/src/solve/normalizes_to/inherent.rs index 52d2fe1e3ec..d60490bce44 100644 --- a/compiler/rustc_trait_selection/src/solve/normalizes_to/inherent.rs +++ b/compiler/rustc_trait_selection/src/solve/normalizes_to/inherent.rs @@ -16,7 +16,6 @@ pub(super) fn normalize_inherent_associated_type( ) -> QueryResult<'tcx> { let tcx = self.tcx(); let inherent = goal.predicate.alias; - let expected = goal.predicate.term.ty().expect("inherent consts are treated separately"); let impl_def_id = tcx.parent(inherent.def_id); let impl_args = self.fresh_args_for_item(impl_def_id); @@ -30,12 +29,6 @@ pub(super) fn normalize_inherent_associated_type( // Equate IAT with the RHS of the project goal let inherent_args = inherent.rebase_inherent_args_onto_impl(impl_args, tcx); - self.eq( - goal.param_env, - expected, - tcx.type_of(inherent.def_id).instantiate(tcx, inherent_args), - ) - .expect("expected goal term to be fully unconstrained"); // Check both where clauses on the impl and IAT // @@ -51,6 +44,8 @@ pub(super) fn normalize_inherent_associated_type( .map(|(pred, _)| goal.with(tcx, pred)), ); + let normalized = tcx.type_of(inherent.def_id).instantiate(tcx, inherent_args); + self.instantiate_normalizes_to_term(goal, normalized.into()); self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } } diff --git a/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs b/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs index a45c1c34410..4ef54dcf21a 100644 --- a/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs @@ -31,32 +31,17 @@ pub(super) fn compute_normalizes_to_goal( goal: Goal<'tcx, NormalizesTo<'tcx>>, ) -> QueryResult<'tcx> { let def_id = goal.predicate.def_id(); + let def_kind = self.tcx().def_kind(def_id); + if cfg!(debug_assertions) && !matches!(def_kind, DefKind::OpaqueTy) { + assert!(self.term_is_fully_unconstrained(goal)); + } + 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 - // 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) { - 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 candidates = self.assemble_and_evaluate_candidates(goal); + self.merge_candidates(candidates) } ty::AssocItemContainer::ImplContainer => { self.normalize_inherent_associated_type(goal) diff --git a/compiler/rustc_trait_selection/src/solve/normalizes_to/weak_types.rs b/compiler/rustc_trait_selection/src/solve/normalizes_to/weak_types.rs index 9f91c02c1ab..13af5068b6c 100644 --- a/compiler/rustc_trait_selection/src/solve/normalizes_to/weak_types.rs +++ b/compiler/rustc_trait_selection/src/solve/normalizes_to/weak_types.rs @@ -15,10 +15,6 @@ pub(super) fn normalize_weak_type( ) -> QueryResult<'tcx> { let tcx = self.tcx(); let weak_ty = goal.predicate.alias; - let expected = goal.predicate.term.ty().expect("no such thing as a const alias"); - - let actual = tcx.type_of(weak_ty.def_id).instantiate(tcx, weak_ty.args); - self.eq(goal.param_env, expected, actual)?; // Check where clauses self.add_goals( @@ -30,6 +26,9 @@ pub(super) fn normalize_weak_type( .map(|pred| goal.with(tcx, pred)), ); + let actual = tcx.type_of(weak_ty.def_id).instantiate(tcx, weak_ty.args); + self.instantiate_normalizes_to_term(goal, actual.into()); + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } }