Rollup merge of #123363 - lcnr:normalizes-to-zero-to-inf, r=BoxyUwU

change `NormalizesTo` to fully structurally normalize

notes in https://hackmd.io/wZ016dE4QKGIhrOnHLlThQ

need to also update the dev-guide once this PR lands. in short, the setup is now as follows:

`normalizes-to` internally implements one step normalization, applying that normalization to the `goal.predicate.term` causes the projected term to get recursively normalized. With this `normalizes-to` normalizes until the projected term is rigid, meaning that we normalize as many steps necessary, but at least 1.

To handle rigid aliases, we add another candidate only if the 1 to inf step normalization failed. With this `normalizes-to` is now full structural normalization. We can now change `AliasRelate` to simply emit `normalizes-to` goals for the rhs and lhs.

This avoids the concerns from https://github.com/rust-lang/trait-system-refactor-initiative/issues/103 and generally feels cleaner
This commit is contained in:
Jacob Pratt 2024-04-04 21:16:56 -04:00 committed by GitHub
commit fcb0e9d07a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 175 additions and 311 deletions

View file

@ -121,8 +121,6 @@ pub enum ProbeStep<'tcx> {
/// used whenever there are multiple candidates to prove the /// used whenever there are multiple candidates to prove the
/// current goalby . /// current goalby .
NestedProbe(Probe<'tcx>), NestedProbe(Probe<'tcx>),
CommitIfOkStart,
CommitIfOkSuccess,
} }
/// What kind of probe we're in. In case the probe represents a candidate, or /// What kind of probe we're in. In case the probe represents a candidate, or
@ -132,6 +130,8 @@ pub enum ProbeStep<'tcx> {
pub enum ProbeKind<'tcx> { pub enum ProbeKind<'tcx> {
/// The root inference context while proving a goal. /// The root inference context while proving a goal.
Root { result: QueryResult<'tcx> }, Root { result: QueryResult<'tcx> },
/// Trying to normalize an alias by at least one stpe in `NormalizesTo`.
TryNormalizeNonRigid { result: QueryResult<'tcx> },
/// Probe entered when normalizing the self ty during candidate assembly /// Probe entered when normalizing the self ty during candidate assembly
NormalizedSelfTyAssembly, NormalizedSelfTyAssembly,
/// Some candidate to prove the current goal. /// Some candidate to prove the current goal.
@ -143,9 +143,6 @@ pub enum ProbeKind<'tcx> {
/// Used in the probe that wraps normalizing the non-self type for the unsize /// Used in the probe that wraps normalizing the non-self type for the unsize
/// trait, which is also structurally matched on. /// trait, which is also structurally matched on.
UnsizeAssembly, UnsizeAssembly,
/// A call to `EvalCtxt::commit_if_ok` which failed, causing the work
/// to be discarded.
CommitIfOk,
/// During upcasting from some source object to target object type, used to /// During upcasting from some source object to target object type, used to
/// do a probe to find out what projection type(s) may be used to prove that /// do a probe to find out what projection type(s) may be used to prove that
/// the source type upholds all of the target type's object bounds. /// the source type upholds all of the target type's object bounds.

View file

@ -100,6 +100,9 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> {
ProbeKind::Root { result } => { ProbeKind::Root { result } => {
write!(self.f, "ROOT RESULT: {result:?}") write!(self.f, "ROOT RESULT: {result:?}")
} }
ProbeKind::TryNormalizeNonRigid { result } => {
write!(self.f, "TRY NORMALIZE NON-RIGID: {result:?}")
}
ProbeKind::NormalizedSelfTyAssembly => { ProbeKind::NormalizedSelfTyAssembly => {
write!(self.f, "NORMALIZING SELF TY FOR ASSEMBLY:") write!(self.f, "NORMALIZING SELF TY FOR ASSEMBLY:")
} }
@ -109,9 +112,6 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> {
ProbeKind::UpcastProjectionCompatibility => { ProbeKind::UpcastProjectionCompatibility => {
write!(self.f, "PROBING FOR PROJECTION COMPATIBILITY FOR UPCASTING:") write!(self.f, "PROBING FOR PROJECTION COMPATIBILITY FOR UPCASTING:")
} }
ProbeKind::CommitIfOk => {
write!(self.f, "COMMIT_IF_OK:")
}
ProbeKind::MiscCandidate { name, result } => { ProbeKind::MiscCandidate { name, result } => {
write!(self.f, "CANDIDATE {name}: {result:?}") write!(self.f, "CANDIDATE {name}: {result:?}")
} }
@ -132,8 +132,6 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> {
} }
ProbeStep::EvaluateGoals(eval) => this.format_added_goals_evaluation(eval)?, ProbeStep::EvaluateGoals(eval) => this.format_added_goals_evaluation(eval)?,
ProbeStep::NestedProbe(probe) => this.format_probe(probe)?, ProbeStep::NestedProbe(probe) => this.format_probe(probe)?,
ProbeStep::CommitIfOkStart => writeln!(this.f, "COMMIT_IF_OK START")?,
ProbeStep::CommitIfOkSuccess => writeln!(this.f, "COMMIT_IF_OK SUCCESS")?,
} }
} }
Ok(()) Ok(())

View file

@ -2,8 +2,8 @@
//! Doing this via a separate goal is called "deferred alias relation" and part //! Doing this via a separate goal is called "deferred alias relation" and part
//! of our more general approach to "lazy normalization". //! of our more general approach to "lazy normalization".
//! //!
//! This is done by first normalizing both sides of the goal, ending up in //! This is done by first structurally normalizing both sides of the goal, ending
//! either a concrete type, rigid alias, or an infer variable. //! up in either a concrete type, rigid alias, or an infer variable.
//! These are related further according to the rules below: //! These are related further according to the rules below:
//! //!
//! (1.) If we end up with two rigid aliases, then we relate them structurally. //! (1.) If we end up with two rigid aliases, then we relate them structurally.
@ -14,18 +14,10 @@
//! //!
//! (3.) Otherwise, if we end with two rigid (non-projection) or infer types, //! (3.) Otherwise, if we end with two rigid (non-projection) or infer types,
//! relate them structurally. //! relate them structurally.
//!
//! Subtle: when relating an opaque to another type, we emit a
//! `NormalizesTo(opaque, ?fresh_var)` goal when trying to normalize the opaque.
//! This nested goal starts out as ambiguous and does not actually define the opaque.
//! 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; 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::traits::solve::{Certainty, Goal, QueryResult};
use rustc_middle::ty::{self, Ty}; use rustc_middle::ty;
impl<'tcx> EvalCtxt<'_, 'tcx> { impl<'tcx> EvalCtxt<'_, 'tcx> {
#[instrument(level = "debug", skip(self), ret)] #[instrument(level = "debug", skip(self), ret)]
@ -36,21 +28,34 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
let tcx = self.tcx(); let tcx = self.tcx();
let Goal { param_env, predicate: (lhs, rhs, direction) } = goal; let Goal { param_env, predicate: (lhs, rhs, direction) } = goal;
let Some(lhs) = self.try_normalize_term(param_env, lhs)? else { // Structurally normalize the lhs.
return self let lhs = if let Some(alias) = lhs.to_alias_ty(self.tcx()) {
.evaluate_added_goals_and_make_canonical_response(Certainty::overflow(true)); let term = self.next_term_infer_of_kind(lhs);
self.add_normalizes_to_goal(goal.with(tcx, ty::NormalizesTo { alias, term }));
term
} else {
lhs
}; };
let Some(rhs) = self.try_normalize_term(param_env, rhs)? else { // Structurally normalize the rhs.
return self let rhs = if let Some(alias) = rhs.to_alias_ty(self.tcx()) {
.evaluate_added_goals_and_make_canonical_response(Certainty::overflow(true)); let term = self.next_term_infer_of_kind(rhs);
self.add_normalizes_to_goal(goal.with(tcx, ty::NormalizesTo { alias, term }));
term
} else {
rhs
}; };
// Apply the constraints.
self.try_evaluate_added_goals()?;
let lhs = self.resolve_vars_if_possible(lhs);
let rhs = self.resolve_vars_if_possible(rhs);
debug!(?lhs, ?rhs);
let variance = match direction { let variance = match direction {
ty::AliasRelationDirection::Equate => ty::Variance::Invariant, ty::AliasRelationDirection::Equate => ty::Variance::Invariant,
ty::AliasRelationDirection::Subtype => ty::Variance::Covariant, ty::AliasRelationDirection::Subtype => ty::Variance::Covariant,
}; };
match (lhs.to_alias_ty(tcx), rhs.to_alias_ty(tcx)) { match (lhs.to_alias_ty(tcx), rhs.to_alias_ty(tcx)) {
(None, None) => { (None, None) => {
self.relate(param_env, lhs, variance, rhs)?; self.relate(param_env, lhs, variance, rhs)?;
@ -58,14 +63,18 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
} }
(Some(alias), None) => { (Some(alias), None) => {
self.relate_rigid_alias_non_alias(param_env, alias, variance, rhs) self.relate_rigid_alias_non_alias(param_env, alias, variance, rhs)?;
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
}
(None, Some(alias)) => {
self.relate_rigid_alias_non_alias(
param_env,
alias,
variance.xform(ty::Variance::Contravariant),
lhs,
)?;
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
} }
(None, Some(alias)) => self.relate_rigid_alias_non_alias(
param_env,
alias,
variance.xform(ty::Variance::Contravariant),
lhs,
),
(Some(alias_lhs), Some(alias_rhs)) => { (Some(alias_lhs), Some(alias_rhs)) => {
self.relate(param_env, alias_lhs, variance, alias_rhs)?; self.relate(param_env, alias_lhs, variance, alias_rhs)?;
@ -73,104 +82,4 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
} }
} }
} }
/// Relate a rigid alias with another type. This is the same as
/// an ordinary relate except that we treat the outer most alias
/// constructor as rigid.
#[instrument(level = "debug", skip(self, param_env), ret)]
fn relate_rigid_alias_non_alias(
&mut self,
param_env: ty::ParamEnv<'tcx>,
alias: ty::AliasTy<'tcx>,
variance: ty::Variance,
term: ty::Term<'tcx>,
) -> QueryResult<'tcx> {
// NOTE: this check is purely an optimization, the structural eq would
// always fail if the term is not an inference variable.
if term.is_infer() {
let tcx = self.tcx();
// We need to relate `alias` to `term` treating only the outermost
// constructor as rigid, relating any contained generic arguments as
// normal. We do this by first structurally equating the `term`
// with the alias constructor instantiated with unconstrained infer vars,
// and then relate this with the whole `alias`.
//
// Alternatively we could modify `Equate` for this case by adding another
// variant to `StructurallyRelateAliases`.
let identity_args = self.fresh_args_for_item(alias.def_id);
let rigid_ctor = ty::AliasTy::new(tcx, alias.def_id, identity_args);
self.eq_structurally_relating_aliases(param_env, term, rigid_ctor.to_ty(tcx).into())?;
self.eq(param_env, alias, rigid_ctor)?;
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
} else {
Err(NoSolution)
}
}
// FIXME: This needs a name that reflects that it's okay to bottom-out with an inference var.
/// Normalize the `term` to equate it later.
#[instrument(level = "debug", skip(self, param_env), ret)]
fn try_normalize_term(
&mut self,
param_env: ty::ParamEnv<'tcx>,
term: ty::Term<'tcx>,
) -> Result<Option<ty::Term<'tcx>>, NoSolution> {
match term.unpack() {
ty::TermKind::Ty(ty) => {
Ok(self.try_normalize_ty_recur(param_env, 0, ty).map(Into::into))
}
ty::TermKind::Const(_) => {
if let Some(alias) = term.to_alias_ty(self.tcx()) {
let term = self.next_term_infer_of_kind(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 {
Ok(Some(term))
}
}
}
}
#[instrument(level = "debug", skip(self, param_env), ret)]
fn try_normalize_ty_recur(
&mut self,
param_env: ty::ParamEnv<'tcx>,
depth: usize,
ty: Ty<'tcx>,
) -> Option<Ty<'tcx>> {
if !self.tcx().recursion_limit().value_within_limit(depth) {
return None;
}
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 = 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))
}) {
Ok(ty) => self.try_normalize_ty_recur(param_env, depth + 1, ty),
Err(NoSolution) => Some(ty),
}
}
} }

View file

@ -332,7 +332,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
/// whether an alias is rigid by using the trait solver. When instantiating a response /// whether an alias is rigid by using the trait solver. When instantiating a response
/// from the solver we assume that the solver correctly handled aliases and therefore /// from the solver we assume that the solver correctly handled aliases and therefore
/// always relate them structurally here. /// always relate them structurally here.
#[instrument(level = "debug", skip(infcx), ret)] #[instrument(level = "debug", skip(infcx))]
fn unify_query_var_values( fn unify_query_var_values(
infcx: &InferCtxt<'tcx>, infcx: &InferCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>, param_env: ty::ParamEnv<'tcx>,

View file

@ -1,47 +0,0 @@
use super::{EvalCtxt, NestedGoals};
use crate::solve::inspect;
use rustc_middle::traits::query::NoSolution;
impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
pub(in crate::solve) fn commit_if_ok<T>(
&mut self,
f: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> Result<T, NoSolution>,
) -> Result<T, NoSolution> {
let mut nested_ecx = EvalCtxt {
infcx: self.infcx,
variables: self.variables,
var_values: self.var_values,
is_normalizes_to_goal: self.is_normalizes_to_goal,
predefined_opaques_in_body: self.predefined_opaques_in_body,
max_input_universe: self.max_input_universe,
search_graph: self.search_graph,
nested_goals: NestedGoals::new(),
tainted: self.tainted,
inspect: self.inspect.new_probe(),
};
let result = nested_ecx.infcx.commit_if_ok(|_| f(&mut nested_ecx));
if result.is_ok() {
let EvalCtxt {
infcx: _,
variables: _,
var_values: _,
is_normalizes_to_goal: _,
predefined_opaques_in_body: _,
max_input_universe: _,
search_graph: _,
nested_goals,
tainted,
inspect,
} = nested_ecx;
self.nested_goals.extend(nested_goals);
self.tainted = tainted;
self.inspect.integrate_snapshot(inspect);
} else {
nested_ecx.inspect.probe_kind(inspect::ProbeKind::CommitIfOk);
self.inspect.finish_probe(nested_ecx.inspect);
}
result
}
}

View file

@ -24,7 +24,6 @@ use rustc_middle::ty::{
use rustc_session::config::DumpSolverProofTree; use rustc_session::config::DumpSolverProofTree;
use rustc_span::DUMMY_SP; use rustc_span::DUMMY_SP;
use std::io::Write; use std::io::Write;
use std::iter;
use std::ops::ControlFlow; use std::ops::ControlFlow;
use crate::traits::vtable::{count_own_vtable_entries, prepare_vtable_segments, VtblSegment}; use crate::traits::vtable::{count_own_vtable_entries, prepare_vtable_segments, VtblSegment};
@ -36,7 +35,6 @@ use super::{GoalSource, SolverMode};
pub use select::InferCtxtSelectExt; pub use select::InferCtxtSelectExt;
mod canonical; mod canonical;
mod commit_if_ok;
mod probe; mod probe;
mod select; mod select;
@ -124,11 +122,6 @@ impl<'tcx> NestedGoals<'tcx> {
pub(super) fn is_empty(&self) -> bool { pub(super) fn is_empty(&self) -> bool {
self.normalizes_to_goals.is_empty() && self.goals.is_empty() self.normalizes_to_goals.is_empty() && self.goals.is_empty()
} }
pub(super) fn extend(&mut self, other: NestedGoals<'tcx>) {
self.normalizes_to_goals.extend(other.normalizes_to_goals);
self.goals.extend(other.goals)
}
} }
#[derive(PartialEq, Eq, Debug, Hash, HashStable, Clone, Copy)] #[derive(PartialEq, Eq, Debug, Hash, HashStable, Clone, Copy)]
@ -511,12 +504,6 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
self.inspect.evaluate_added_goals_loop_start(); self.inspect.evaluate_added_goals_loop_start();
fn with_misc_source<'tcx>(
it: impl IntoIterator<Item = Goal<'tcx, ty::Predicate<'tcx>>>,
) -> impl Iterator<Item = (GoalSource, Goal<'tcx, ty::Predicate<'tcx>>)> {
iter::zip(iter::repeat(GoalSource::Misc), it)
}
// If this loop did not result in any progress, what's our final certainty. // If this loop did not result in any progress, what's our final certainty.
let mut unchanged_certainty = Some(Certainty::Yes); let mut unchanged_certainty = Some(Certainty::Yes);
for goal in goals.normalizes_to_goals { for goal in goals.normalizes_to_goals {
@ -534,16 +521,28 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
unconstrained_goal, unconstrained_goal,
)?; )?;
// Add the nested goals from normalization to our own nested goals. // Add the nested goals from normalization to our own nested goals.
debug!(?nested_goals);
goals.goals.extend(nested_goals); goals.goals.extend(nested_goals);
// Finally, equate the goal's RHS with the unconstrained var. // Finally, equate the goal's RHS with the unconstrained var.
// We put the nested goals from this into goals instead of //
// next_goals to avoid needing to process the loop one extra // SUBTLE:
// time if this goal returns something -- I don't think this // We structurally relate aliases here. This is necessary
// matters in practice, though. // as we otherwise emit a nested `AliasRelate` goal in case the
let eq_goals = // returned term is a rigid alias, resulting in overflow.
self.eq_and_get_goals(goal.param_env, goal.predicate.term, unconstrained_rhs)?; //
goals.goals.extend(with_misc_source(eq_goals)); // It is correct as both `goal.predicate.term` and `unconstrained_rhs`
// start out as an unconstrained inference variable so any aliases get
// fully normalized when instantiating it.
//
// FIXME: Strictly speaking this may be incomplete if the normalized-to
// type contains an ambiguous alias referencing bound regions. We should
// consider changing this to only use "shallow structural equality".
self.eq_structurally_relating_aliases(
goal.param_env,
goal.predicate.term,
unconstrained_rhs,
)?;
// We only look at the `projection_ty` part here rather than // We only look at the `projection_ty` part here rather than
// looking at the "has changed" return from evaluate_goal, // looking at the "has changed" return from evaluate_goal,
@ -731,6 +730,46 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
}) })
} }
/// This should be used when relating a rigid alias with another type.
///
/// Normally we emit a nested `AliasRelate` when equating an inference
/// variable and an alias. This causes us to instead constrain the inference
/// variable to the alias without emitting a nested alias relate goals.
#[instrument(level = "debug", skip(self, param_env), ret)]
pub(super) fn relate_rigid_alias_non_alias(
&mut self,
param_env: ty::ParamEnv<'tcx>,
alias: ty::AliasTy<'tcx>,
variance: ty::Variance,
term: ty::Term<'tcx>,
) -> Result<(), NoSolution> {
// NOTE: this check is purely an optimization, the structural eq would
// always fail if the term is not an inference variable.
if term.is_infer() {
let tcx = self.tcx();
// We need to relate `alias` to `term` treating only the outermost
// constructor as rigid, relating any contained generic arguments as
// normal. We do this by first structurally equating the `term`
// with the alias constructor instantiated with unconstrained infer vars,
// and then relate this with the whole `alias`.
//
// Alternatively we could modify `Equate` for this case by adding another
// variant to `StructurallyRelateAliases`.
let identity_args = self.fresh_args_for_item(alias.def_id);
let rigid_ctor = ty::AliasTy::new(tcx, alias.def_id, identity_args);
let ctor_ty = rigid_ctor.to_ty(tcx);
let InferOk { value: (), obligations } = self
.infcx
.at(&ObligationCause::dummy(), param_env)
.trace(term, ctor_ty.into())
.eq_structurally_relating_aliases(term, ctor_ty.into())?;
debug_assert!(obligations.is_empty());
self.relate(param_env, alias, variance, rigid_ctor)
} else {
Err(NoSolution)
}
}
/// This sohuld only be used when we're either instantiating a previously /// This sohuld only be used when we're either instantiating a previously
/// unconstrained "return value" or when we're sure that all aliases in /// unconstrained "return value" or when we're sure that all aliases in
/// the types are rigid. /// the types are rigid.

View file

@ -130,17 +130,14 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
self.candidates_recur(candidates, nested_goals, probe); self.candidates_recur(candidates, nested_goals, probe);
nested_goals.truncate(num_goals); nested_goals.truncate(num_goals);
} }
inspect::ProbeStep::EvaluateGoals(_) inspect::ProbeStep::EvaluateGoals(_) => (),
| inspect::ProbeStep::CommitIfOkStart
| inspect::ProbeStep::CommitIfOkSuccess => (),
} }
} }
match probe.kind { match probe.kind {
inspect::ProbeKind::NormalizedSelfTyAssembly inspect::ProbeKind::NormalizedSelfTyAssembly
| inspect::ProbeKind::UnsizeAssembly | inspect::ProbeKind::UnsizeAssembly
| inspect::ProbeKind::UpcastProjectionCompatibility | inspect::ProbeKind::UpcastProjectionCompatibility => (),
| inspect::ProbeKind::CommitIfOk => (),
// We add a candidate for the root evaluation if there // We add a candidate for the root evaluation if there
// is only one way to prove a given goal, e.g. for `WellFormed`. // is only one way to prove a given goal, e.g. for `WellFormed`.
// //
@ -157,7 +154,8 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
}); });
} }
} }
inspect::ProbeKind::MiscCandidate { name: _, result } inspect::ProbeKind::TryNormalizeNonRigid { result }
| inspect::ProbeKind::MiscCandidate { name: _, result }
| inspect::ProbeKind::TraitCandidate { source: _, result } => { | inspect::ProbeKind::TraitCandidate { source: _, result } => {
candidates.push(InspectCandidate { candidates.push(InspectCandidate {
goal: self, goal: self,

View file

@ -220,8 +220,6 @@ enum WipProbeStep<'tcx> {
AddGoal(GoalSource, inspect::CanonicalState<'tcx, Goal<'tcx, ty::Predicate<'tcx>>>), AddGoal(GoalSource, inspect::CanonicalState<'tcx, Goal<'tcx, ty::Predicate<'tcx>>>),
EvaluateGoals(WipAddedGoalsEvaluation<'tcx>), EvaluateGoals(WipAddedGoalsEvaluation<'tcx>),
NestedProbe(WipProbe<'tcx>), NestedProbe(WipProbe<'tcx>),
CommitIfOkStart,
CommitIfOkSuccess,
} }
impl<'tcx> WipProbeStep<'tcx> { impl<'tcx> WipProbeStep<'tcx> {
@ -230,8 +228,6 @@ impl<'tcx> WipProbeStep<'tcx> {
WipProbeStep::AddGoal(source, goal) => inspect::ProbeStep::AddGoal(source, goal), WipProbeStep::AddGoal(source, goal) => inspect::ProbeStep::AddGoal(source, goal),
WipProbeStep::EvaluateGoals(eval) => inspect::ProbeStep::EvaluateGoals(eval.finalize()), WipProbeStep::EvaluateGoals(eval) => inspect::ProbeStep::EvaluateGoals(eval.finalize()),
WipProbeStep::NestedProbe(probe) => inspect::ProbeStep::NestedProbe(probe.finalize()), WipProbeStep::NestedProbe(probe) => inspect::ProbeStep::NestedProbe(probe.finalize()),
WipProbeStep::CommitIfOkStart => inspect::ProbeStep::CommitIfOkStart,
WipProbeStep::CommitIfOkSuccess => inspect::ProbeStep::CommitIfOkSuccess,
} }
} }
} }
@ -467,29 +463,6 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
} }
} }
/// Used by `EvalCtxt::commit_if_ok` to flatten the work done inside
/// of the probe into the parent.
pub fn integrate_snapshot(&mut self, probe: ProofTreeBuilder<'tcx>) {
if let Some(this) = self.as_mut() {
match (this, *probe.state.unwrap()) {
(
DebugSolver::Probe(WipProbe { steps, .. })
| DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep {
evaluation: WipProbe { steps, .. },
..
}),
DebugSolver::Probe(probe),
) => {
steps.push(WipProbeStep::CommitIfOkStart);
assert_eq!(probe.kind, None);
steps.extend(probe.steps);
steps.push(WipProbeStep::CommitIfOkSuccess);
}
_ => unreachable!(),
}
}
}
pub fn new_evaluate_added_goals(&mut self) -> ProofTreeBuilder<'tcx> { pub fn new_evaluate_added_goals(&mut self) -> ProofTreeBuilder<'tcx> {
self.nested(|| WipAddedGoalsEvaluation { evaluations: vec![], result: None }) self.nested(|| WipAddedGoalsEvaluation { evaluations: vec![], result: None })
} }

View file

@ -7,6 +7,7 @@ use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_hir::LangItem; use rustc_hir::LangItem;
use rustc_infer::traits::query::NoSolution; use rustc_infer::traits::query::NoSolution;
use rustc_infer::traits::solve::inspect::ProbeKind;
use rustc_infer::traits::specialization_graph::LeafDef; use rustc_infer::traits::specialization_graph::LeafDef;
use rustc_infer::traits::Reveal; use rustc_infer::traits::Reveal;
use rustc_middle::traits::solve::{ use rustc_middle::traits::solve::{
@ -30,14 +31,41 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
&mut self, &mut self,
goal: Goal<'tcx, NormalizesTo<'tcx>>, goal: Goal<'tcx, NormalizesTo<'tcx>>,
) -> QueryResult<'tcx> { ) -> QueryResult<'tcx> {
let def_id = goal.predicate.def_id(); self.set_is_normalizes_to_goal();
let def_kind = self.tcx().def_kind(def_id);
match def_kind {
DefKind::OpaqueTy => return self.normalize_opaque_type(goal),
_ => self.set_is_normalizes_to_goal(),
}
debug_assert!(self.term_is_fully_unconstrained(goal)); debug_assert!(self.term_is_fully_unconstrained(goal));
let normalize_result = self
.probe(|&result| ProbeKind::TryNormalizeNonRigid { result })
.enter(|this| this.normalize_at_least_one_step(goal));
match normalize_result {
Ok(res) => Ok(res),
Err(NoSolution) => {
let Goal { param_env, predicate: NormalizesTo { alias, term } } = goal;
if alias.opt_kind(self.tcx()).is_some() {
self.relate_rigid_alias_non_alias(
param_env,
alias,
ty::Variance::Invariant,
term,
)?;
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
} else {
// FIXME(generic_const_exprs): we currently do not support rigid
// unevaluated constants.
Err(NoSolution)
}
}
}
}
/// Normalize the given alias by at least one step. If the alias is rigid, this
/// returns `NoSolution`.
#[instrument(level = "debug", skip(self), ret)]
fn normalize_at_least_one_step(
&mut self,
goal: Goal<'tcx, NormalizesTo<'tcx>>,
) -> QueryResult<'tcx> {
let def_id = goal.predicate.def_id();
match self.tcx().def_kind(def_id) { match self.tcx().def_kind(def_id) {
DefKind::AssocTy | DefKind::AssocConst => { DefKind::AssocTy | DefKind::AssocConst => {
match self.tcx().associated_item(def_id).container { match self.tcx().associated_item(def_id).container {
@ -52,35 +80,22 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
} }
DefKind::AnonConst => self.normalize_anon_const(goal), DefKind::AnonConst => self.normalize_anon_const(goal),
DefKind::TyAlias => self.normalize_weak_type(goal), DefKind::TyAlias => self.normalize_weak_type(goal),
DefKind::OpaqueTy => self.normalize_opaque_type(goal),
kind => bug!("unknown DefKind {} in normalizes-to goal: {goal:#?}", kind.descr(def_id)), kind => bug!("unknown DefKind {} in normalizes-to goal: {goal:#?}", kind.descr(def_id)),
} }
} }
/// When normalizing an associated item, constrain the result to `term`. /// When normalizing an associated item, constrain the expected term to `term`.
/// ///
/// While `NormalizesTo` goals have the normalized-to term as an argument, /// We know `term` to always be a fully unconstrained inference variable, so
/// this argument is always fully unconstrained for associated items. /// `eq` should never fail here. However, in case `term` contains aliases, we
/// It is therefore appropriate to instead think of these `NormalizesTo` goals /// emit nested `AliasRelate` goals to structurally normalize the alias.
/// as function returning a term after normalizing.
///
/// When equating an inference variable and an alias, we tend to emit `alias-relate`
/// goals and only actually instantiate the inference variable with an alias if the
/// alias is rigid. However, this means that constraining the expected term of
/// such goals ends up fully structurally normalizing the resulting type instead of
/// only by one step. To avoid this we instead use structural equality here, resulting
/// in each `NormalizesTo` only projects by a single step.
///
/// Not doing so, currently causes issues because trying to normalize an opaque type
/// during alias-relate doesn't actually constrain the opaque if the concrete type
/// is an inference variable. This means that `NormalizesTo` for associated types
/// normalizing to an opaque type always resulted in ambiguity, breaking tests e.g.
/// tests/ui/type-alias-impl-trait/issue-78450.rs.
pub fn instantiate_normalizes_to_term( pub fn instantiate_normalizes_to_term(
&mut self, &mut self,
goal: Goal<'tcx, NormalizesTo<'tcx>>, goal: Goal<'tcx, NormalizesTo<'tcx>>,
term: ty::Term<'tcx>, term: ty::Term<'tcx>,
) { ) {
self.eq_structurally_relating_aliases(goal.param_env, goal.predicate.term, term) self.eq(goal.param_env, goal.predicate.term, term)
.expect("expected goal term to be fully unconstrained"); .expect("expected goal term to be fully unconstrained");
} }
} }

View file

@ -58,12 +58,6 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
} }
} }
let expected = self.structurally_normalize_ty(goal.param_env, expected)?;
if expected.is_ty_var() {
return self
.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS);
}
// Otherwise, define a new opaque type // Otherwise, define a new opaque type
self.insert_hidden_type(opaque_type_key, goal.param_env, expected)?; self.insert_hidden_type(opaque_type_key, goal.param_env, expected)?;
self.add_item_bounds_for_hidden_type( self.add_item_bounds_for_hidden_type(

View file

@ -6,7 +6,7 @@ LL | cmp_eq
| |
= note: cannot satisfy `_: Scalar` = note: cannot satisfy `_: Scalar`
note: required by a bound in `cmp_eq` note: required by a bound in `cmp_eq`
--> $DIR/ambig-hr-projection-issue-93340.rs:9:22 --> $DIR/ambig-hr-projection-issue-93340.rs:10:22
| |
LL | fn cmp_eq<'a, 'b, A: Scalar, B: Scalar, O: Scalar>(a: A::RefType<'a>, b: B::RefType<'b>) -> O { LL | fn cmp_eq<'a, 'b, A: Scalar, B: Scalar, O: Scalar>(a: A::RefType<'a>, b: B::RefType<'b>) -> O {
| ^^^^^^ required by this bound in `cmp_eq` | ^^^^^^ required by this bound in `cmp_eq`
@ -15,34 +15,6 @@ help: consider specifying the generic arguments
LL | cmp_eq::<A, B, O> LL | cmp_eq::<A, B, O>
| +++++++++++ | +++++++++++
error[E0275]: overflow evaluating the requirement `impl for<'a, 'b> Fn(<A as Scalar>::RefType<'a>, <B as Scalar>::RefType<'b>) -> O == for<'a, 'b> fn(..., ...) -> ... {cmp_eq::<..., ..., ...>}` error: aborting due to 1 previous error
--> $DIR/ambig-hr-projection-issue-93340.rs:16:5
|
LL | cmp_eq
| ^^^^^^
error[E0275]: overflow evaluating the requirement `impl for<'a, 'b> Fn(<A as Scalar>::RefType<'a>, <B as Scalar>::RefType<'b>) -> O == for<'a, 'b> fn(..., ...) -> ... {cmp_eq::<..., ..., ...>}` For more information about this error, try `rustc --explain E0283`.
--> $DIR/ambig-hr-projection-issue-93340.rs:16:5
|
LL | cmp_eq
| ^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error[E0275]: overflow evaluating the requirement `for<'a, 'b> fn(<O as Scalar>::RefType<'a>, <_ as Scalar>::RefType<'b>) -> _ {cmp_eq::<O, ..., ...>} <: ...`
--> $DIR/ambig-hr-projection-issue-93340.rs:14:51
|
LL | ) -> impl Fn(A::RefType<'_>, B::RefType<'_>) -> O {
| ___________________________________________________^
LL | |
LL | | cmp_eq
LL | |
LL | |
LL | |
LL | | }
| |_^
error: aborting due to 4 previous errors
Some errors have detailed explanations: E0275, E0283.
For more information about an error, try `rustc --explain E0275`.

View file

@ -6,7 +6,7 @@ LL | cmp_eq
| |
= note: cannot satisfy `_: Scalar` = note: cannot satisfy `_: Scalar`
note: required by a bound in `cmp_eq` note: required by a bound in `cmp_eq`
--> $DIR/ambig-hr-projection-issue-93340.rs:9:22 --> $DIR/ambig-hr-projection-issue-93340.rs:10:22
| |
LL | fn cmp_eq<'a, 'b, A: Scalar, B: Scalar, O: Scalar>(a: A::RefType<'a>, b: B::RefType<'b>) -> O { LL | fn cmp_eq<'a, 'b, A: Scalar, B: Scalar, O: Scalar>(a: A::RefType<'a>, b: B::RefType<'b>) -> O {
| ^^^^^^ required by this bound in `cmp_eq` | ^^^^^^ required by this bound in `cmp_eq`

View file

@ -1,4 +1,5 @@
//@ revisions: old next //@ revisions: old next
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[next] compile-flags: -Znext-solver //@[next] compile-flags: -Znext-solver
pub trait Scalar: 'static { pub trait Scalar: 'static {
type RefType<'a>: ScalarRef<'a>; type RefType<'a>: ScalarRef<'a>;
@ -12,11 +13,8 @@ fn cmp_eq<'a, 'b, A: Scalar, B: Scalar, O: Scalar>(a: A::RefType<'a>, b: B::RefT
fn build_expression<A: Scalar, B: Scalar, O: Scalar>( fn build_expression<A: Scalar, B: Scalar, O: Scalar>(
) -> impl Fn(A::RefType<'_>, B::RefType<'_>) -> O { ) -> impl Fn(A::RefType<'_>, B::RefType<'_>) -> O {
//[next]~^ ERROR overflow evaluating the requirement
cmp_eq cmp_eq
//~^ ERROR type annotations needed //~^ ERROR type annotations needed
//[next]~| ERROR overflow evaluating the requirement
//[next]~| ERROR overflow evaluating the requirement
} }
fn main() {} fn main() {}

View file

@ -1,5 +1,5 @@
error[E0282]: type annotations needed error[E0282]: type annotations needed
--> $DIR/recursive-coroutine-boxed.rs:12:23 --> $DIR/recursive-coroutine-boxed.rs:14:23
| |
LL | let mut gen = Box::pin(foo()); LL | let mut gen = Box::pin(foo());
| ^^^^^^^^ cannot infer type of the type parameter `T` declared on the struct `Box` | ^^^^^^^^ cannot infer type of the type parameter `T` declared on the struct `Box`
@ -12,12 +12,28 @@ help: consider specifying the generic argument
LL | let mut gen = Box::<T>::pin(foo()); LL | let mut gen = Box::<T>::pin(foo());
| +++++ | +++++
error[E0282]: type annotations needed error[E0308]: mismatched types
--> $DIR/recursive-coroutine-boxed.rs:9:13 --> $DIR/recursive-coroutine-boxed.rs:13:5
| |
LL | fn foo() -> impl Coroutine<Yield = (), Return = ()> { LL | fn foo() -> impl Coroutine<Yield = (), Return = ()> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type for opaque type `impl Coroutine<Yield = (), Return = ()>` | ---------------------------------------
| |
| the expected opaque type
| expected `impl Coroutine<Yield = (), Return = ()>` because of return type
...
LL | / || {
LL | | let mut gen = Box::pin(foo());
LL | |
LL | | let mut r = gen.as_mut().resume(());
... |
LL | | }
LL | | }
| |_____^ types differ
|
= note: expected opaque type `impl Coroutine<Yield = (), Return = ()>`
found coroutine `{coroutine@$DIR/recursive-coroutine-boxed.rs:13:5: 13:7}`
error: aborting due to 2 previous errors error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0282`. Some errors have detailed explanations: E0282, E0308.
For more information about an error, try `rustc --explain E0282`.

View file

@ -7,8 +7,10 @@
use std::ops::{Coroutine, CoroutineState}; use std::ops::{Coroutine, CoroutineState};
fn foo() -> impl Coroutine<Yield = (), Return = ()> { fn foo() -> impl Coroutine<Yield = (), Return = ()> {
//[next]~^ ERROR type annotations needed // FIXME(-Znext-solver): this fails with a mismatched types as the
|| { // hidden type of the opaque ends up as {type error}. We should not
// emit errors for such goals.
|| { //[next]~ ERROR mismatched types
let mut gen = Box::pin(foo()); let mut gen = Box::pin(foo());
//[next]~^ ERROR type annotations needed //[next]~^ ERROR type annotations needed
let mut r = gen.as_mut().resume(()); let mut r = gen.as_mut().resume(());