blob: 6b839d64b87ca43daec33097fb256ee22fa0ffb3 [file] [log] [blame]
//! Implements the `AliasRelate` goal, which is used when unifying aliases.
//! Doing this via a separate goal is called "deferred alias relation" and part
//! of our more general approach to "lazy normalization".
//!
//! This goal, e.g. `A alias-relate B`, may be satisfied by one of three branches:
//! * normalizes-to: If `A` is a projection, we can prove the equivalent
//! projection predicate with B as the right-hand side of the projection.
//! This goal is computed in both directions, if both are aliases.
//! * subst-relate: Equate `A` and `B` by their substs, if they're both
//! aliases with the same def-id.
//! * bidirectional-normalizes-to: If `A` and `B` are both projections, and both
//! may apply, then we can compute the "intersection" of both normalizes-to by
//! performing them together. This is used specifically to resolve ambiguities.
use super::{EvalCtxt, SolverMode};
use rustc_infer::traits::query::NoSolution;
use rustc_middle::traits::solve::{Certainty, Goal, QueryResult};
use rustc_middle::ty;
/// We may need to invert the alias relation direction if dealing an alias on the RHS.
#[derive(Debug)]
enum Invert {
No,
Yes,
}
impl<'tcx> EvalCtxt<'_, 'tcx> {
#[instrument(level = "debug", skip(self), ret)]
pub(super) fn compute_alias_relate_goal(
&mut self,
goal: Goal<'tcx, (ty::Term<'tcx>, ty::Term<'tcx>, ty::AliasRelationDirection)>,
) -> QueryResult<'tcx> {
let tcx = self.tcx();
let Goal { param_env, predicate: (lhs, rhs, direction) } = goal;
if lhs.is_infer() || rhs.is_infer() {
bug!(
"`AliasRelate` goal with an infer var on lhs or rhs which should have been instantiated"
);
}
match (lhs.to_alias_ty(tcx), rhs.to_alias_ty(tcx)) {
(None, None) => bug!("`AliasRelate` goal without an alias on either lhs or rhs"),
// RHS is not a projection, only way this is true is if LHS normalizes-to RHS
(Some(alias_lhs), None) => self.assemble_normalizes_to_candidate(
param_env,
alias_lhs,
rhs,
direction,
Invert::No,
),
// LHS is not a projection, only way this is true is if RHS normalizes-to LHS
(None, Some(alias_rhs)) => self.assemble_normalizes_to_candidate(
param_env,
alias_rhs,
lhs,
direction,
Invert::Yes,
),
(Some(alias_lhs), Some(alias_rhs)) => {
debug!("both sides are aliases");
let mut candidates = Vec::new();
// LHS normalizes-to RHS
candidates.extend(self.assemble_normalizes_to_candidate(
param_env,
alias_lhs,
rhs,
direction,
Invert::No,
));
// RHS normalizes-to RHS
candidates.extend(self.assemble_normalizes_to_candidate(
param_env,
alias_rhs,
lhs,
direction,
Invert::Yes,
));
// Relate via args
candidates.extend(
self.assemble_subst_relate_candidate(
param_env, alias_lhs, alias_rhs, direction,
),
);
debug!(?candidates);
if let Some(merged) = self.try_merge_responses(&candidates) {
Ok(merged)
} else {
// When relating two aliases and we have ambiguity, if both
// aliases can be normalized to something, we prefer
// "bidirectionally normalizing" both of them within the same
// candidate.
//
// See <https://github.com/rust-lang/trait-system-refactor-initiative/issues/25>.
//
// As this is incomplete, we must not do so during coherence.
match self.solver_mode() {
SolverMode::Normal => {
if let Ok(bidirectional_normalizes_to_response) = self
.assemble_bidirectional_normalizes_to_candidate(
param_env, lhs, rhs, direction,
)
{
Ok(bidirectional_normalizes_to_response)
} else {
self.flounder(&candidates)
}
}
SolverMode::Coherence => self.flounder(&candidates),
}
}
}
}
}
#[instrument(level = "debug", skip(self), ret)]
fn assemble_normalizes_to_candidate(
&mut self,
param_env: ty::ParamEnv<'tcx>,
alias: ty::AliasTy<'tcx>,
other: ty::Term<'tcx>,
direction: ty::AliasRelationDirection,
invert: Invert,
) -> QueryResult<'tcx> {
self.probe_candidate("normalizes-to").enter(|ecx| {
ecx.normalizes_to_inner(param_env, alias, other, direction, invert)?;
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
}
// Computes the normalizes-to branch, with side-effects. This must be performed
// in a probe in order to not taint the evaluation context.
fn normalizes_to_inner(
&mut self,
param_env: ty::ParamEnv<'tcx>,
alias: ty::AliasTy<'tcx>,
other: ty::Term<'tcx>,
direction: ty::AliasRelationDirection,
invert: Invert,
) -> Result<(), NoSolution> {
let other = match direction {
// This is purely an optimization. No need to instantiate a new
// infer var and equate the RHS to it.
ty::AliasRelationDirection::Equate => other,
// Instantiate an infer var and subtype our RHS to it, so that we
// properly represent a subtype relation between the LHS and RHS
// of the goal.
ty::AliasRelationDirection::Subtype => {
let fresh = self.next_term_infer_of_kind(other);
let (sub, sup) = match invert {
Invert::No => (fresh, other),
Invert::Yes => (other, fresh),
};
self.sub(param_env, sub, sup)?;
fresh
}
};
self.add_goal(Goal::new(
self.tcx(),
param_env,
ty::ProjectionPredicate { projection_ty: alias, term: other },
));
Ok(())
}
fn assemble_subst_relate_candidate(
&mut self,
param_env: ty::ParamEnv<'tcx>,
alias_lhs: ty::AliasTy<'tcx>,
alias_rhs: ty::AliasTy<'tcx>,
direction: ty::AliasRelationDirection,
) -> QueryResult<'tcx> {
self.probe_candidate("args relate").enter(|ecx| {
match direction {
ty::AliasRelationDirection::Equate => {
ecx.eq(param_env, alias_lhs, alias_rhs)?;
}
ty::AliasRelationDirection::Subtype => {
ecx.sub(param_env, alias_lhs, alias_rhs)?;
}
}
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
}
fn assemble_bidirectional_normalizes_to_candidate(
&mut self,
param_env: ty::ParamEnv<'tcx>,
lhs: ty::Term<'tcx>,
rhs: ty::Term<'tcx>,
direction: ty::AliasRelationDirection,
) -> QueryResult<'tcx> {
self.probe_candidate("bidir normalizes-to").enter(|ecx| {
ecx.normalizes_to_inner(
param_env,
lhs.to_alias_ty(ecx.tcx()).unwrap(),
rhs,
direction,
Invert::No,
)?;
ecx.normalizes_to_inner(
param_env,
rhs.to_alias_ty(ecx.tcx()).unwrap(),
lhs,
direction,
Invert::Yes,
)?;
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
}
}