Skip to content

change NormalizesTo to fully structurally normalize #123363

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

Merged
merged 2 commits into from
Apr 5, 2024
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
7 changes: 2 additions & 5 deletions compiler/rustc_middle/src/traits/solve/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,6 @@ pub enum ProbeStep<'tcx> {
/// used whenever there are multiple candidates to prove the
/// current goalby .
NestedProbe(Probe<'tcx>),
CommitIfOkStart,
CommitIfOkSuccess,
}

/// What kind of probe we're in. In case the probe represents a candidate, or
Expand All @@ -132,6 +130,8 @@ pub enum ProbeStep<'tcx> {
pub enum ProbeKind<'tcx> {
/// The root inference context while proving a goal.
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
NormalizedSelfTyAssembly,
/// Some candidate to prove the current goal.
Expand All @@ -143,9 +143,6 @@ pub enum ProbeKind<'tcx> {
/// Used in the probe that wraps normalizing the non-self type for the unsize
/// trait, which is also structurally matched on.
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
/// 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.
Expand Down
8 changes: 3 additions & 5 deletions compiler/rustc_middle/src/traits/solve/inspect/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> {
ProbeKind::Root { result } => {
write!(self.f, "ROOT RESULT: {result:?}")
}
ProbeKind::TryNormalizeNonRigid { result } => {
write!(self.f, "TRY NORMALIZE NON-RIGID: {result:?}")
}
ProbeKind::NormalizedSelfTyAssembly => {
write!(self.f, "NORMALIZING SELF TY FOR ASSEMBLY:")
}
Expand All @@ -109,9 +112,6 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> {
ProbeKind::UpcastProjectionCompatibility => {
write!(self.f, "PROBING FOR PROJECTION COMPATIBILITY FOR UPCASTING:")
}
ProbeKind::CommitIfOk => {
write!(self.f, "COMMIT_IF_OK:")
}
ProbeKind::MiscCandidate { name, result } => {
write!(self.f, "CANDIDATE {name}: {result:?}")
}
Expand All @@ -132,8 +132,6 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> {
}
ProbeStep::EvaluateGoals(eval) => this.format_added_goals_evaluation(eval)?,
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(())
Expand Down
159 changes: 34 additions & 125 deletions compiler/rustc_trait_selection/src/solve/alias_relate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
//! Doing this via a separate goal is called "deferred alias relation" and part
//! of our more general approach to "lazy normalization".
//!
//! This is done by first normalizing both sides of the goal, ending up in
//! either a concrete type, rigid alias, or an infer variable.
//! This is done by first structurally normalizing both sides of the goal, ending
//! up in either a concrete type, rigid alias, or an infer variable.
//! These are related further according to the rules below:
//!
//! (1.) If we end up with two rigid aliases, then we relate them structurally.
Expand All @@ -14,18 +14,10 @@
//!
//! (3.) Otherwise, if we end with two rigid (non-projection) or infer types,
//! 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 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};
use rustc_middle::ty;

impl<'tcx> EvalCtxt<'_, 'tcx> {
#[instrument(level = "debug", skip(self), ret)]
Expand All @@ -36,141 +28,58 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
let tcx = self.tcx();
let Goal { param_env, predicate: (lhs, rhs, direction) } = goal;

let Some(lhs) = self.try_normalize_term(param_env, lhs)? else {
return self
.evaluate_added_goals_and_make_canonical_response(Certainty::overflow(true));
// Structurally normalize the lhs.
let lhs = if let Some(alias) = lhs.to_alias_ty(self.tcx()) {
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 {
return self
.evaluate_added_goals_and_make_canonical_response(Certainty::overflow(true));
// Structurally normalize the rhs.
let rhs = if let Some(alias) = rhs.to_alias_ty(self.tcx()) {
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 {
ty::AliasRelationDirection::Equate => ty::Variance::Invariant,
ty::AliasRelationDirection::Subtype => ty::Variance::Covariant,
};

match (lhs.to_alias_ty(tcx), rhs.to_alias_ty(tcx)) {
(None, None) => {
self.relate(param_env, lhs, variance, rhs)?;
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
}

(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)) => {
self.relate(param_env, alias_lhs, variance, alias_rhs)?;
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
}
}
}

/// 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),
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
/// 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
/// always relate them structurally here.
#[instrument(level = "debug", skip(infcx), ret)]
#[instrument(level = "debug", skip(infcx))]
fn unify_query_var_values(
infcx: &InferCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
Expand Down
47 changes: 0 additions & 47 deletions compiler/rustc_trait_selection/src/solve/eval_ctxt/commit_if_ok.rs

This file was deleted.

Loading
Loading