Rollup merge of #108896 - BoxyUwU:new_solver_add_goal_fn, r=lcnr
new solver: make all goal evaluation able to be automatically rerun It is generally wrong to call `evaluate_goal` multiple times or `evaluate_goal` and `evaluate_all` for the same `QueryResult` without correctly handling rerunning the goals when inference makes progress. Not doing so will result in the assertion in `evaluate_goal` firing because rerunning the goal will lead to a more accurate `QueryResult`. Currently there are lots of places that get this wrong and generally it is complex and error prone to handle correctly everywhere. This PR introduces a way to add goals to the `EvalCtxt` and then run all the added goals in a loop so that `evaluate_goal`/`evaluate_all` is not necessary to call manually. There are a few complications for making everything work "right": 1. the `normalizes-to` hack that replaces the rhs with an unconstrained infer var requires special casing in the new `try_evaluate_added_goals` function similar to how `evaluate_goal`'s assertion special cases that hack. 2. `assemble_candidates_after_normalizing_self_ty`'s normalization step needs to be reran for each candidate otherwise the found candidates will potentially get a more accurate `QueryResult` when rerunning the projection/trait goal which can effect the `QueryResult` of the projection/trait goal. This is implemented via `EvalCtxt::probe`'s closure's `EvalCtxt` inheriting the added goals of the `EvalCtxt` that `probe` is called on, allowing us to add goals in a probe, and then enter a nested probe for each candidate and evaluate added goals which include the normalization step's goals. I made `make_canonical_response` evaluate added goals so that it will be hard to mess up the impl of the solver by forgetting to evaluate added goals. Right now the only way to mess this up would be to call `response_no_constraints` (which from the name is obviously weird). The visibility of `evaluate_goal` means that it can be called from various `compute_x_goal` or candidate assembly functions, this is generally wrong and we should never call `evaluate_goal` manually, instead we should be calling `add_goal`/`add_goals`. This is solved by moving `evaluate_goal` `evaluate_canonical_goal` and `compute_goal` into `eval_ctxt`'s module and making them private so they cannot be called from elsewhere, forcing people to call `add_goal/s` and `evaluate_added_goals_and_make_canonical_resposne`/`try_evaluate_added_goals` --- Other changes: - removed the `&& false` that was introduced to the assertion in `evaluate_goal` in #108839 - remove a `!self.did_overflow()` requirement in `search_graph.is_empty()` which causes goals that overflow to ICE - made `EvalCtxt::eq` take `&mut self` and add all the nested goals via `add_goals` instead of returning them as 99% of call sites just immediately called `EvalCtxt::add_goals` manually. r? `````@lcnr`````
This commit is contained in:
commit
e79b182fca
8 changed files with 529 additions and 441 deletions
|
@ -224,7 +224,9 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
if goal.predicate.self_ty().is_ty_var() {
|
||||
return vec![Candidate {
|
||||
source: CandidateSource::BuiltinImpl,
|
||||
result: self.make_canonical_response(Certainty::AMBIGUOUS).unwrap(),
|
||||
result: self
|
||||
.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
|
||||
.unwrap(),
|
||||
}];
|
||||
}
|
||||
|
||||
|
@ -261,8 +263,9 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
let &ty::Alias(ty::Projection, projection_ty) = goal.predicate.self_ty().kind() else {
|
||||
return
|
||||
};
|
||||
self.probe(|this| {
|
||||
let normalized_ty = this.next_ty_infer();
|
||||
|
||||
self.probe(|ecx| {
|
||||
let normalized_ty = ecx.next_ty_infer();
|
||||
let normalizes_to_goal = goal.with(
|
||||
tcx,
|
||||
ty::Binder::dummy(ty::ProjectionPredicate {
|
||||
|
@ -270,28 +273,16 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
term: normalized_ty.into(),
|
||||
}),
|
||||
);
|
||||
let normalization_certainty = match this.evaluate_goal(normalizes_to_goal) {
|
||||
Ok((_, certainty)) => certainty,
|
||||
Err(NoSolution) => return,
|
||||
};
|
||||
let normalized_ty = this.resolve_vars_if_possible(normalized_ty);
|
||||
ecx.add_goal(normalizes_to_goal);
|
||||
if let Ok(_) = ecx.try_evaluate_added_goals() {
|
||||
let normalized_ty = ecx.resolve_vars_if_possible(normalized_ty);
|
||||
|
||||
// NOTE: Alternatively we could call `evaluate_goal` here and only have a `Normalized` candidate.
|
||||
// This doesn't work as long as we use `CandidateSource` in winnowing.
|
||||
let goal = goal.with(tcx, goal.predicate.with_self_ty(tcx, normalized_ty));
|
||||
let normalized_candidates = this.assemble_and_evaluate_candidates(goal);
|
||||
for mut normalized_candidate in normalized_candidates {
|
||||
normalized_candidate.result =
|
||||
normalized_candidate.result.unchecked_map(|mut response| {
|
||||
// FIXME: This currently hides overflow in the normalization step of the self type
|
||||
// which is probably wrong. Maybe `unify_and` should actually keep overflow as
|
||||
// we treat it as non-fatal anyways.
|
||||
response.certainty = response.certainty.unify_and(normalization_certainty);
|
||||
response
|
||||
});
|
||||
candidates.push(normalized_candidate);
|
||||
// NOTE: Alternatively we could call `evaluate_goal` here and only have a `Normalized` candidate.
|
||||
// This doesn't work as long as we use `CandidateSource` in winnowing.
|
||||
let goal = goal.with(tcx, goal.predicate.with_self_ty(tcx, normalized_ty));
|
||||
candidates.extend(ecx.assemble_and_evaluate_candidates(goal));
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn assemble_impl_candidates<G: GoalKind<'tcx>>(
|
||||
|
@ -516,7 +507,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
} else {
|
||||
Certainty::AMBIGUOUS
|
||||
};
|
||||
return self.make_canonical_response(certainty);
|
||||
return self.evaluate_added_goals_and_make_canonical_response(certainty);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -538,14 +529,16 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
fn discard_reservation_impl(&self, mut candidate: Candidate<'tcx>) -> Candidate<'tcx> {
|
||||
fn discard_reservation_impl(&mut self, mut candidate: Candidate<'tcx>) -> Candidate<'tcx> {
|
||||
if let CandidateSource::Impl(def_id) = candidate.source {
|
||||
if let ty::ImplPolarity::Reservation = self.tcx().impl_polarity(def_id) {
|
||||
debug!("Selected reservation impl");
|
||||
// We assemble all candidates inside of a probe so by
|
||||
// making a new canonical response here our result will
|
||||
// have no constraints.
|
||||
candidate.result = self.make_canonical_response(Certainty::AMBIGUOUS).unwrap();
|
||||
candidate.result = self
|
||||
.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,13 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
/// - `external_constraints`: additional constraints which aren't expressable
|
||||
/// using simple unification of inference variables.
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
pub(super) fn make_canonical_response(&self, certainty: Certainty) -> QueryResult<'tcx> {
|
||||
pub(super) fn evaluate_added_goals_and_make_canonical_response(
|
||||
&mut self,
|
||||
certainty: Certainty,
|
||||
) -> QueryResult<'tcx> {
|
||||
let goals_certainty = self.try_evaluate_added_goals()?;
|
||||
let certainty = certainty.unify_and(goals_certainty);
|
||||
|
||||
let external_constraints = self.compute_external_query_constraints()?;
|
||||
|
||||
let response = Response { var_values: self.var_values, external_constraints, certainty };
|
||||
|
@ -209,7 +215,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
// FIXME: To deal with #105787 I also expect us to emit nested obligations here at
|
||||
// some point. We can figure out how to deal with this once we actually have
|
||||
// an ICE.
|
||||
let nested_goals = self.eq(param_env, orig, response)?;
|
||||
let nested_goals = self.eq_and_get_goals(param_env, orig, response)?;
|
||||
assert!(nested_goals.is_empty(), "{nested_goals:?}");
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,11 @@ use rustc_hir::def_id::DefId;
|
|||
use rustc_infer::infer::at::ToTrace;
|
||||
use rustc_infer::infer::canonical::CanonicalVarValues;
|
||||
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
|
||||
use rustc_infer::infer::{DefineOpaqueTypes, InferCtxt, InferOk, LateBoundRegionConversionTime};
|
||||
use rustc_infer::infer::{
|
||||
DefineOpaqueTypes, InferCtxt, InferOk, LateBoundRegionConversionTime, TyCtxtInferExt,
|
||||
};
|
||||
use rustc_infer::traits::query::NoSolution;
|
||||
use rustc_infer::traits::solve::{CanonicalGoal, Certainty, MaybeCause, QueryResult};
|
||||
use rustc_infer::traits::ObligationCause;
|
||||
use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind};
|
||||
use rustc_middle::ty::{
|
||||
|
@ -13,8 +16,8 @@ use rustc_middle::ty::{
|
|||
use rustc_span::DUMMY_SP;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use super::search_graph::SearchGraph;
|
||||
use super::Goal;
|
||||
use super::search_graph::{self, OverflowHandler};
|
||||
use super::{search_graph::SearchGraph, Goal};
|
||||
|
||||
pub struct EvalCtxt<'a, 'tcx> {
|
||||
// FIXME: should be private.
|
||||
|
@ -33,14 +36,305 @@ pub struct EvalCtxt<'a, 'tcx> {
|
|||
|
||||
pub(super) search_graph: &'a mut SearchGraph<'tcx>,
|
||||
|
||||
/// This field is used by a debug assertion in [`EvalCtxt::evaluate_goal`],
|
||||
/// see the comment in that method for more details.
|
||||
pub in_projection_eq_hack: bool,
|
||||
pub(super) nested_goals: NestedGoals<'tcx>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub(super) enum IsNormalizesToHack {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct NestedGoals<'tcx> {
|
||||
pub(super) normalizes_to_hack_goal: Option<Goal<'tcx, ty::ProjectionPredicate<'tcx>>>,
|
||||
pub(super) goals: Vec<Goal<'tcx, ty::Predicate<'tcx>>>,
|
||||
}
|
||||
|
||||
impl NestedGoals<'_> {
|
||||
pub(super) fn new() -> Self {
|
||||
Self { normalizes_to_hack_goal: None, goals: Vec::new() }
|
||||
}
|
||||
|
||||
pub(super) fn is_empty(&self) -> bool {
|
||||
self.normalizes_to_hack_goal.is_none() && self.goals.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait InferCtxtEvalExt<'tcx> {
|
||||
/// Evaluates a goal from **outside** of the trait solver.
|
||||
///
|
||||
/// Using this while inside of the solver is wrong as it uses a new
|
||||
/// search graph which would break cycle detection.
|
||||
fn evaluate_root_goal(
|
||||
&self,
|
||||
goal: Goal<'tcx, ty::Predicate<'tcx>>,
|
||||
) -> Result<(bool, Certainty), NoSolution>;
|
||||
}
|
||||
|
||||
impl<'tcx> InferCtxtEvalExt<'tcx> for InferCtxt<'tcx> {
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
fn evaluate_root_goal(
|
||||
&self,
|
||||
goal: Goal<'tcx, ty::Predicate<'tcx>>,
|
||||
) -> Result<(bool, Certainty), NoSolution> {
|
||||
let mut search_graph = search_graph::SearchGraph::new(self.tcx);
|
||||
|
||||
let mut ecx = EvalCtxt {
|
||||
search_graph: &mut search_graph,
|
||||
infcx: self,
|
||||
// Only relevant when canonicalizing the response.
|
||||
max_input_universe: ty::UniverseIndex::ROOT,
|
||||
var_values: CanonicalVarValues::dummy(),
|
||||
nested_goals: NestedGoals::new(),
|
||||
};
|
||||
let result = ecx.evaluate_goal(IsNormalizesToHack::No, goal);
|
||||
|
||||
assert!(
|
||||
ecx.nested_goals.is_empty(),
|
||||
"root `EvalCtxt` should not have any goals added to it"
|
||||
);
|
||||
|
||||
assert!(search_graph.is_empty());
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
||||
/// The entry point of the solver.
|
||||
///
|
||||
/// This function deals with (coinductive) cycles, overflow, and caching
|
||||
/// and then calls [`EvalCtxt::compute_goal`] which contains the actual
|
||||
/// logic of the solver.
|
||||
///
|
||||
/// Instead of calling this function directly, use either [EvalCtxt::evaluate_goal]
|
||||
/// if you're inside of the solver or [InferCtxtEvalExt::evaluate_root_goal] if you're
|
||||
/// outside of it.
|
||||
#[instrument(level = "debug", skip(tcx, search_graph), ret)]
|
||||
fn evaluate_canonical_goal(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
search_graph: &'a mut search_graph::SearchGraph<'tcx>,
|
||||
canonical_goal: CanonicalGoal<'tcx>,
|
||||
) -> QueryResult<'tcx> {
|
||||
// Deal with overflow, caching, and coinduction.
|
||||
//
|
||||
// The actual solver logic happens in `ecx.compute_goal`.
|
||||
search_graph.with_new_goal(tcx, canonical_goal, |search_graph| {
|
||||
let (ref infcx, goal, var_values) =
|
||||
tcx.infer_ctxt().build_with_canonical(DUMMY_SP, &canonical_goal);
|
||||
let mut ecx = EvalCtxt {
|
||||
infcx,
|
||||
var_values,
|
||||
max_input_universe: canonical_goal.max_universe,
|
||||
search_graph,
|
||||
nested_goals: NestedGoals::new(),
|
||||
};
|
||||
ecx.compute_goal(goal)
|
||||
})
|
||||
}
|
||||
|
||||
/// Recursively evaluates `goal`, returning whether any inference vars have
|
||||
/// been constrained and the certainty of the result.
|
||||
fn evaluate_goal(
|
||||
&mut self,
|
||||
is_normalizes_to_hack: IsNormalizesToHack,
|
||||
goal: Goal<'tcx, ty::Predicate<'tcx>>,
|
||||
) -> Result<(bool, Certainty), NoSolution> {
|
||||
let (orig_values, canonical_goal) = self.canonicalize_goal(goal);
|
||||
let canonical_response =
|
||||
EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?;
|
||||
|
||||
let has_changed = !canonical_response.value.var_values.is_identity();
|
||||
let certainty = self.instantiate_and_apply_query_response(
|
||||
goal.param_env,
|
||||
orig_values,
|
||||
canonical_response,
|
||||
)?;
|
||||
|
||||
// Check that rerunning this query with its inference constraints applied
|
||||
// doesn't result in new inference constraints and has the same result.
|
||||
//
|
||||
// If we have projection goals like `<T as Trait>::Assoc == u32` we recursively
|
||||
// call `exists<U> <T as Trait>::Assoc == U` to enable better caching. This goal
|
||||
// could constrain `U` to `u32` which would cause this check to result in a
|
||||
// solver cycle.
|
||||
if cfg!(debug_assertions)
|
||||
&& has_changed
|
||||
&& is_normalizes_to_hack == IsNormalizesToHack::No
|
||||
&& !self.search_graph.in_cycle()
|
||||
{
|
||||
debug!("rerunning goal to check result is stable");
|
||||
let (_orig_values, canonical_goal) = self.canonicalize_goal(goal);
|
||||
let canonical_response =
|
||||
EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?;
|
||||
if !canonical_response.value.var_values.is_identity() {
|
||||
bug!("unstable result: {goal:?} {canonical_goal:?} {canonical_response:?}");
|
||||
}
|
||||
assert_eq!(certainty, canonical_response.value.certainty);
|
||||
}
|
||||
|
||||
Ok((has_changed, certainty))
|
||||
}
|
||||
|
||||
fn compute_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) -> QueryResult<'tcx> {
|
||||
let Goal { param_env, predicate } = goal;
|
||||
let kind = predicate.kind();
|
||||
if let Some(kind) = kind.no_bound_vars() {
|
||||
match kind {
|
||||
ty::PredicateKind::Clause(ty::Clause::Trait(predicate)) => {
|
||||
self.compute_trait_goal(Goal { param_env, predicate })
|
||||
}
|
||||
ty::PredicateKind::Clause(ty::Clause::Projection(predicate)) => {
|
||||
self.compute_projection_goal(Goal { param_env, predicate })
|
||||
}
|
||||
ty::PredicateKind::Clause(ty::Clause::TypeOutlives(predicate)) => {
|
||||
self.compute_type_outlives_goal(Goal { param_env, predicate })
|
||||
}
|
||||
ty::PredicateKind::Clause(ty::Clause::RegionOutlives(predicate)) => {
|
||||
self.compute_region_outlives_goal(Goal { param_env, predicate })
|
||||
}
|
||||
ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(ct, ty)) => {
|
||||
self.compute_const_arg_has_type_goal(Goal { param_env, predicate: (ct, ty) })
|
||||
}
|
||||
ty::PredicateKind::Subtype(predicate) => {
|
||||
self.compute_subtype_goal(Goal { param_env, predicate })
|
||||
}
|
||||
ty::PredicateKind::Coerce(predicate) => {
|
||||
self.compute_coerce_goal(Goal { param_env, predicate })
|
||||
}
|
||||
ty::PredicateKind::ClosureKind(def_id, substs, kind) => self
|
||||
.compute_closure_kind_goal(Goal {
|
||||
param_env,
|
||||
predicate: (def_id, substs, kind),
|
||||
}),
|
||||
ty::PredicateKind::ObjectSafe(trait_def_id) => {
|
||||
self.compute_object_safe_goal(trait_def_id)
|
||||
}
|
||||
ty::PredicateKind::WellFormed(arg) => {
|
||||
self.compute_well_formed_goal(Goal { param_env, predicate: arg })
|
||||
}
|
||||
ty::PredicateKind::Ambiguous => {
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
|
||||
}
|
||||
// FIXME: implement these predicates :)
|
||||
ty::PredicateKind::ConstEvaluatable(_) | ty::PredicateKind::ConstEquate(_, _) => {
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
ty::PredicateKind::TypeWellFormedFromEnv(..) => {
|
||||
bug!("TypeWellFormedFromEnv is only used for Chalk")
|
||||
}
|
||||
ty::PredicateKind::AliasEq(lhs, rhs) => {
|
||||
self.compute_alias_eq_goal(Goal { param_env, predicate: (lhs, rhs) })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let kind = self.infcx.instantiate_binder_with_placeholders(kind);
|
||||
let goal = goal.with(self.tcx(), ty::Binder::dummy(kind));
|
||||
self.add_goal(goal);
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively evaluates all the goals added to this `EvalCtxt` to completion, returning
|
||||
// the certainty of all the goals.
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
pub(super) fn try_evaluate_added_goals(&mut self) -> Result<Certainty, NoSolution> {
|
||||
let mut goals = core::mem::replace(&mut self.nested_goals, NestedGoals::new());
|
||||
let mut new_goals = NestedGoals::new();
|
||||
|
||||
let response = self.repeat_while_none(
|
||||
|_| Ok(Certainty::Maybe(MaybeCause::Overflow)),
|
||||
|this| {
|
||||
let mut has_changed = Err(Certainty::Yes);
|
||||
|
||||
if let Some(goal) = goals.normalizes_to_hack_goal.take() {
|
||||
let (_, certainty) = match this.evaluate_goal(
|
||||
IsNormalizesToHack::Yes,
|
||||
goal.with(this.tcx(), ty::Binder::dummy(goal.predicate)),
|
||||
) {
|
||||
Ok(r) => r,
|
||||
Err(NoSolution) => return Some(Err(NoSolution)),
|
||||
};
|
||||
|
||||
if goal.predicate.projection_ty
|
||||
!= this.resolve_vars_if_possible(goal.predicate.projection_ty)
|
||||
{
|
||||
has_changed = Ok(())
|
||||
}
|
||||
|
||||
match certainty {
|
||||
Certainty::Yes => {}
|
||||
Certainty::Maybe(_) => {
|
||||
let goal = this.resolve_vars_if_possible(goal);
|
||||
|
||||
// The rhs of this `normalizes-to` must always be an unconstrained infer var as it is
|
||||
// the hack used by `normalizes-to` to ensure that every `normalizes-to` behaves the same
|
||||
// regardless of the rhs.
|
||||
//
|
||||
// However it is important not to unconditionally replace the rhs with a new infer var
|
||||
// as otherwise we may replace the original unconstrained infer var with a new infer var
|
||||
// and never propagate any constraints on the new var back to the original var.
|
||||
let term = this
|
||||
.term_is_fully_unconstrained(goal)
|
||||
.then_some(goal.predicate.term)
|
||||
.unwrap_or_else(|| {
|
||||
this.next_term_infer_of_kind(goal.predicate.term)
|
||||
});
|
||||
let projection_pred = ty::ProjectionPredicate {
|
||||
term,
|
||||
projection_ty: goal.predicate.projection_ty,
|
||||
};
|
||||
new_goals.normalizes_to_hack_goal =
|
||||
Some(goal.with(this.tcx(), projection_pred));
|
||||
|
||||
has_changed = has_changed.map_err(|c| c.unify_and(certainty));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for nested_goal in goals.goals.drain(..) {
|
||||
let (changed, certainty) =
|
||||
match this.evaluate_goal(IsNormalizesToHack::No, nested_goal) {
|
||||
Ok(result) => result,
|
||||
Err(NoSolution) => return Some(Err(NoSolution)),
|
||||
};
|
||||
|
||||
if changed {
|
||||
has_changed = Ok(());
|
||||
}
|
||||
|
||||
match certainty {
|
||||
Certainty::Yes => {}
|
||||
Certainty::Maybe(_) => {
|
||||
new_goals.goals.push(nested_goal);
|
||||
has_changed = has_changed.map_err(|c| c.unify_and(certainty));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
core::mem::swap(&mut new_goals, &mut goals);
|
||||
match has_changed {
|
||||
Ok(()) => None,
|
||||
Err(certainty) => Some(Ok(certainty)),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
self.nested_goals = goals;
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||
pub(super) fn probe<T>(&mut self, f: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> T) -> T {
|
||||
self.infcx.probe(|_| f(self))
|
||||
let mut ecx = EvalCtxt {
|
||||
infcx: self.infcx,
|
||||
var_values: self.var_values,
|
||||
max_input_universe: self.max_input_universe,
|
||||
search_graph: self.search_graph,
|
||||
nested_goals: self.nested_goals.clone(),
|
||||
};
|
||||
self.infcx.probe(|_| f(&mut ecx))
|
||||
}
|
||||
|
||||
pub(super) fn tcx(&self) -> TyCtxt<'tcx> {
|
||||
|
@ -61,6 +355,15 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
)
|
||||
}
|
||||
|
||||
/// Returns a ty infer or a const infer depending on whether `kind` is a `Ty` or `Const`.
|
||||
/// If `kind` is an integer inference variable this will still return a ty infer var.
|
||||
pub(super) fn next_term_infer_of_kind(&self, kind: ty::Term<'tcx>) -> ty::Term<'tcx> {
|
||||
match kind.unpack() {
|
||||
ty::TermKind::Ty(_) => self.next_ty_infer().into(),
|
||||
ty::TermKind::Const(ct) => self.next_const_infer(ct.ty()).into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Is the projection predicate is of the form `exists<T> <Ty as Trait>::Assoc = T`.
|
||||
///
|
||||
/// This is the case if the `term` is an inference variable in the innermost universe
|
||||
|
@ -137,6 +440,30 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
|
||||
#[instrument(level = "debug", skip(self, param_env), ret)]
|
||||
pub(super) fn eq<T: ToTrace<'tcx>>(
|
||||
&mut self,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
lhs: T,
|
||||
rhs: T,
|
||||
) -> Result<(), NoSolution> {
|
||||
self.infcx
|
||||
.at(&ObligationCause::dummy(), param_env)
|
||||
.eq(DefineOpaqueTypes::No, lhs, rhs)
|
||||
.map(|InferOk { value: (), obligations }| {
|
||||
self.add_goals(obligations.into_iter().map(|o| o.into()));
|
||||
})
|
||||
.map_err(|e| {
|
||||
debug!(?e, "failed to equate");
|
||||
NoSolution
|
||||
})
|
||||
}
|
||||
|
||||
/// Equates two values returning the nested goals without adding them
|
||||
/// to the nested goals of the `EvalCtxt`.
|
||||
///
|
||||
/// If possible, try using `eq` instead which automatically handles nested
|
||||
/// goals correctly.
|
||||
#[instrument(level = "debug", skip(self, param_env), ret)]
|
||||
pub(super) fn eq_and_get_goals<T: ToTrace<'tcx>>(
|
||||
&self,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
lhs: T,
|
||||
|
|
|
@ -15,23 +15,19 @@
|
|||
|
||||
// FIXME: uses of `infcx.at` need to enable deferred projection equality once that's implemented.
|
||||
|
||||
use std::mem;
|
||||
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_infer::infer::canonical::{Canonical, CanonicalVarValues};
|
||||
use rustc_infer::infer::{DefineOpaqueTypes, InferCtxt, InferOk, TyCtxtInferExt};
|
||||
use rustc_infer::infer::{DefineOpaqueTypes, InferOk};
|
||||
use rustc_infer::traits::query::NoSolution;
|
||||
use rustc_middle::traits::solve::{
|
||||
CanonicalGoal, CanonicalResponse, Certainty, ExternalConstraints, ExternalConstraintsData,
|
||||
Goal, MaybeCause, QueryResult, Response,
|
||||
Goal, QueryResult, Response,
|
||||
};
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||
use rustc_middle::ty::{
|
||||
CoercePredicate, RegionOutlivesPredicate, SubtypePredicate, TypeOutlivesPredicate,
|
||||
};
|
||||
use rustc_span::DUMMY_SP;
|
||||
|
||||
use crate::solve::search_graph::OverflowHandler;
|
||||
use crate::traits::ObligationCause;
|
||||
|
||||
mod assembly;
|
||||
|
@ -42,7 +38,7 @@ mod project_goals;
|
|||
mod search_graph;
|
||||
mod trait_goals;
|
||||
|
||||
pub use eval_ctxt::EvalCtxt;
|
||||
pub use eval_ctxt::{EvalCtxt, InferCtxtEvalExt};
|
||||
pub use fulfill::FulfillmentCtxt;
|
||||
|
||||
trait CanonicalResponseExt {
|
||||
|
@ -57,180 +53,18 @@ impl<'tcx> CanonicalResponseExt for Canonical<'tcx, Response<'tcx>> {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait InferCtxtEvalExt<'tcx> {
|
||||
/// Evaluates a goal from **outside** of the trait solver.
|
||||
///
|
||||
/// Using this while inside of the solver is wrong as it uses a new
|
||||
/// search graph which would break cycle detection.
|
||||
fn evaluate_root_goal(
|
||||
&self,
|
||||
goal: Goal<'tcx, ty::Predicate<'tcx>>,
|
||||
) -> Result<(bool, Certainty), NoSolution>;
|
||||
}
|
||||
|
||||
impl<'tcx> InferCtxtEvalExt<'tcx> for InferCtxt<'tcx> {
|
||||
fn evaluate_root_goal(
|
||||
&self,
|
||||
goal: Goal<'tcx, ty::Predicate<'tcx>>,
|
||||
) -> Result<(bool, Certainty), NoSolution> {
|
||||
let mut search_graph = search_graph::SearchGraph::new(self.tcx);
|
||||
|
||||
let result = EvalCtxt {
|
||||
search_graph: &mut search_graph,
|
||||
infcx: self,
|
||||
// Only relevant when canonicalizing the response.
|
||||
max_input_universe: ty::UniverseIndex::ROOT,
|
||||
var_values: CanonicalVarValues::dummy(),
|
||||
in_projection_eq_hack: false,
|
||||
}
|
||||
.evaluate_goal(goal);
|
||||
|
||||
assert!(search_graph.is_empty());
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
||||
/// The entry point of the solver.
|
||||
///
|
||||
/// This function deals with (coinductive) cycles, overflow, and caching
|
||||
/// and then calls [`EvalCtxt::compute_goal`] which contains the actual
|
||||
/// logic of the solver.
|
||||
///
|
||||
/// Instead of calling this function directly, use either [EvalCtxt::evaluate_goal]
|
||||
/// if you're inside of the solver or [InferCtxtEvalExt::evaluate_root_goal] if you're
|
||||
/// outside of it.
|
||||
#[instrument(level = "debug", skip(tcx, search_graph), ret)]
|
||||
fn evaluate_canonical_goal(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
search_graph: &'a mut search_graph::SearchGraph<'tcx>,
|
||||
canonical_goal: CanonicalGoal<'tcx>,
|
||||
) -> QueryResult<'tcx> {
|
||||
// Deal with overflow, caching, and coinduction.
|
||||
//
|
||||
// The actual solver logic happens in `ecx.compute_goal`.
|
||||
search_graph.with_new_goal(tcx, canonical_goal, |search_graph| {
|
||||
let (ref infcx, goal, var_values) =
|
||||
tcx.infer_ctxt().build_with_canonical(DUMMY_SP, &canonical_goal);
|
||||
let mut ecx = EvalCtxt {
|
||||
infcx,
|
||||
var_values,
|
||||
max_input_universe: canonical_goal.max_universe,
|
||||
search_graph,
|
||||
in_projection_eq_hack: false,
|
||||
};
|
||||
ecx.compute_goal(goal)
|
||||
})
|
||||
}
|
||||
|
||||
/// Recursively evaluates `goal`, returning whether any inference vars have
|
||||
/// been constrained and the certainty of the result.
|
||||
fn evaluate_goal(
|
||||
&mut self,
|
||||
goal: Goal<'tcx, ty::Predicate<'tcx>>,
|
||||
) -> Result<(bool, Certainty), NoSolution> {
|
||||
let (orig_values, canonical_goal) = self.canonicalize_goal(goal);
|
||||
let canonical_response =
|
||||
EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?;
|
||||
|
||||
let has_changed = !canonical_response.value.var_values.is_identity();
|
||||
let certainty = self.instantiate_and_apply_query_response(
|
||||
goal.param_env,
|
||||
orig_values,
|
||||
canonical_response,
|
||||
)?;
|
||||
|
||||
// Check that rerunning this query with its inference constraints applied
|
||||
// doesn't result in new inference constraints and has the same result.
|
||||
//
|
||||
// If we have projection goals like `<T as Trait>::Assoc == u32` we recursively
|
||||
// call `exists<U> <T as Trait>::Assoc == U` to enable better caching. This goal
|
||||
// could constrain `U` to `u32` which would cause this check to result in a
|
||||
// solver cycle.
|
||||
if cfg!(debug_assertions)
|
||||
&& has_changed
|
||||
&& !self.in_projection_eq_hack
|
||||
&& !self.search_graph.in_cycle()
|
||||
&& false
|
||||
{
|
||||
let (_orig_values, canonical_goal) = self.canonicalize_goal(goal);
|
||||
let canonical_response =
|
||||
EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?;
|
||||
if !canonical_response.value.var_values.is_identity() {
|
||||
bug!("unstable result: {goal:?} {canonical_goal:?} {canonical_response:?}");
|
||||
}
|
||||
assert_eq!(certainty, canonical_response.value.certainty);
|
||||
}
|
||||
|
||||
Ok((has_changed, certainty))
|
||||
}
|
||||
|
||||
fn compute_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) -> QueryResult<'tcx> {
|
||||
let Goal { param_env, predicate } = goal;
|
||||
let kind = predicate.kind();
|
||||
if let Some(kind) = kind.no_bound_vars() {
|
||||
match kind {
|
||||
ty::PredicateKind::Clause(ty::Clause::Trait(predicate)) => {
|
||||
self.compute_trait_goal(Goal { param_env, predicate })
|
||||
}
|
||||
ty::PredicateKind::Clause(ty::Clause::Projection(predicate)) => {
|
||||
self.compute_projection_goal(Goal { param_env, predicate })
|
||||
}
|
||||
ty::PredicateKind::Clause(ty::Clause::TypeOutlives(predicate)) => {
|
||||
self.compute_type_outlives_goal(Goal { param_env, predicate })
|
||||
}
|
||||
ty::PredicateKind::Clause(ty::Clause::RegionOutlives(predicate)) => {
|
||||
self.compute_region_outlives_goal(Goal { param_env, predicate })
|
||||
}
|
||||
ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(ct, ty)) => {
|
||||
self.compute_const_arg_has_type_goal(Goal { param_env, predicate: (ct, ty) })
|
||||
}
|
||||
ty::PredicateKind::Subtype(predicate) => {
|
||||
self.compute_subtype_goal(Goal { param_env, predicate })
|
||||
}
|
||||
ty::PredicateKind::Coerce(predicate) => {
|
||||
self.compute_coerce_goal(Goal { param_env, predicate })
|
||||
}
|
||||
ty::PredicateKind::ClosureKind(def_id, substs, kind) => self
|
||||
.compute_closure_kind_goal(Goal {
|
||||
param_env,
|
||||
predicate: (def_id, substs, kind),
|
||||
}),
|
||||
ty::PredicateKind::ObjectSafe(trait_def_id) => {
|
||||
self.compute_object_safe_goal(trait_def_id)
|
||||
}
|
||||
ty::PredicateKind::WellFormed(arg) => {
|
||||
self.compute_well_formed_goal(Goal { param_env, predicate: arg })
|
||||
}
|
||||
ty::PredicateKind::Ambiguous => self.make_canonical_response(Certainty::AMBIGUOUS),
|
||||
// FIXME: implement these predicates :)
|
||||
ty::PredicateKind::ConstEvaluatable(_) | ty::PredicateKind::ConstEquate(_, _) => {
|
||||
self.make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
ty::PredicateKind::TypeWellFormedFromEnv(..) => {
|
||||
bug!("TypeWellFormedFromEnv is only used for Chalk")
|
||||
}
|
||||
ty::PredicateKind::AliasEq(lhs, rhs) => {
|
||||
self.compute_alias_eq_goal(Goal { param_env, predicate: (lhs, rhs) })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let kind = self.infcx.instantiate_binder_with_placeholders(kind);
|
||||
let goal = goal.with(self.tcx(), ty::Binder::dummy(kind));
|
||||
let (_, certainty) = self.evaluate_goal(goal)?;
|
||||
self.make_canonical_response(certainty)
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
fn compute_type_outlives_goal(
|
||||
&mut self,
|
||||
goal: Goal<'tcx, TypeOutlivesPredicate<'tcx>>,
|
||||
) -> QueryResult<'tcx> {
|
||||
let ty::OutlivesPredicate(ty, lt) = goal.predicate;
|
||||
self.infcx.register_region_obligation_with_cause(ty, lt, &ObligationCause::dummy());
|
||||
self.make_canonical_response(Certainty::Yes)
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
fn compute_region_outlives_goal(
|
||||
&mut self,
|
||||
goal: Goal<'tcx, RegionOutlivesPredicate<'tcx>>,
|
||||
|
@ -239,9 +73,10 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
|||
&ObligationCause::dummy(),
|
||||
ty::Binder::dummy(goal.predicate),
|
||||
);
|
||||
self.make_canonical_response(Certainty::Yes)
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
fn compute_coerce_goal(
|
||||
&mut self,
|
||||
goal: Goal<'tcx, CoercePredicate<'tcx>>,
|
||||
|
@ -256,6 +91,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
|||
})
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
fn compute_subtype_goal(
|
||||
&mut self,
|
||||
goal: Goal<'tcx, SubtypePredicate<'tcx>>,
|
||||
|
@ -263,18 +99,18 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
|||
if goal.predicate.a.is_ty_var() && goal.predicate.b.is_ty_var() {
|
||||
// FIXME: Do we want to register a subtype relation between these vars?
|
||||
// That won't actually reflect in the query response, so it seems moot.
|
||||
self.make_canonical_response(Certainty::AMBIGUOUS)
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
|
||||
} else {
|
||||
let InferOk { value: (), obligations } = self
|
||||
.infcx
|
||||
.at(&ObligationCause::dummy(), goal.param_env)
|
||||
.sub(DefineOpaqueTypes::No, goal.predicate.a, goal.predicate.b)?;
|
||||
self.evaluate_all_and_make_canonical_response(
|
||||
obligations.into_iter().map(|pred| pred.into()).collect(),
|
||||
)
|
||||
self.add_goals(obligations.into_iter().map(|pred| pred.into()));
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
fn compute_closure_kind_goal(
|
||||
&mut self,
|
||||
goal: Goal<'tcx, (DefId, ty::SubstsRef<'tcx>, ty::ClosureKind)>,
|
||||
|
@ -283,23 +119,25 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
|||
let found_kind = substs.as_closure().kind_ty().to_opt_closure_kind();
|
||||
|
||||
let Some(found_kind) = found_kind else {
|
||||
return self.make_canonical_response(Certainty::AMBIGUOUS);
|
||||
return self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS);
|
||||
};
|
||||
if found_kind.extends(expected_kind) {
|
||||
self.make_canonical_response(Certainty::Yes)
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
} else {
|
||||
Err(NoSolution)
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
fn compute_object_safe_goal(&mut self, trait_def_id: DefId) -> QueryResult<'tcx> {
|
||||
if self.tcx().check_is_object_safe(trait_def_id) {
|
||||
self.make_canonical_response(Certainty::Yes)
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
} else {
|
||||
Err(NoSolution)
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
fn compute_well_formed_goal(
|
||||
&mut self,
|
||||
goal: Goal<'tcx, ty::GenericArg<'tcx>>,
|
||||
|
@ -309,10 +147,11 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
|||
goal.param_env,
|
||||
goal.predicate,
|
||||
) {
|
||||
Some(obligations) => self.evaluate_all_and_make_canonical_response(
|
||||
obligations.into_iter().map(|o| o.into()).collect(),
|
||||
),
|
||||
None => self.make_canonical_response(Certainty::AMBIGUOUS),
|
||||
Some(obligations) => {
|
||||
self.add_goals(obligations.into_iter().map(|o| o.into()));
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
None => self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -326,14 +165,14 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
|||
let evaluate_normalizes_to = |ecx: &mut EvalCtxt<'_, 'tcx>, alias, other| {
|
||||
debug!("evaluate_normalizes_to(alias={:?}, other={:?})", alias, other);
|
||||
let r = ecx.probe(|ecx| {
|
||||
let (_, certainty) = ecx.evaluate_goal(goal.with(
|
||||
ecx.add_goal(goal.with(
|
||||
tcx,
|
||||
ty::Binder::dummy(ty::ProjectionPredicate {
|
||||
projection_ty: alias,
|
||||
term: other,
|
||||
}),
|
||||
))?;
|
||||
ecx.make_canonical_response(certainty)
|
||||
));
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
});
|
||||
debug!("evaluate_normalizes_to(..) -> {:?}", r);
|
||||
r
|
||||
|
@ -360,10 +199,10 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
|||
// Evaluate all 3 potential candidates for the alias' being equal
|
||||
candidates.push(evaluate_normalizes_to(self, alias_lhs, goal.predicate.1));
|
||||
candidates.push(evaluate_normalizes_to(self, alias_rhs, goal.predicate.0));
|
||||
candidates.push(self.probe(|this| {
|
||||
candidates.push(self.probe(|ecx| {
|
||||
debug!("compute_alias_eq_goal: alias defids are equal, equating substs");
|
||||
let nested_goals = this.eq(goal.param_env, alias_lhs, alias_rhs)?;
|
||||
this.evaluate_all_and_make_canonical_response(nested_goals)
|
||||
ecx.eq(goal.param_env, alias_lhs, alias_rhs)?;
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}));
|
||||
|
||||
debug!(?candidates);
|
||||
|
@ -379,62 +218,31 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
|||
goal: Goal<'tcx, (ty::Const<'tcx>, Ty<'tcx>)>,
|
||||
) -> QueryResult<'tcx> {
|
||||
let (ct, ty) = goal.predicate;
|
||||
let nested_goals = self.eq(goal.param_env, ct.ty(), ty)?;
|
||||
self.evaluate_all_and_make_canonical_response(nested_goals)
|
||||
self.eq(goal.param_env, ct.ty(), ty)?;
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||
// Recursively evaluates a list of goals to completion, returning the certainty
|
||||
// of all of the goals.
|
||||
fn evaluate_all(
|
||||
&mut self,
|
||||
mut goals: Vec<Goal<'tcx, ty::Predicate<'tcx>>>,
|
||||
) -> Result<Certainty, NoSolution> {
|
||||
let mut new_goals = Vec::new();
|
||||
self.repeat_while_none(
|
||||
|_| Ok(Certainty::Maybe(MaybeCause::Overflow)),
|
||||
|this| {
|
||||
let mut has_changed = Err(Certainty::Yes);
|
||||
for goal in goals.drain(..) {
|
||||
let (changed, certainty) = match this.evaluate_goal(goal) {
|
||||
Ok(result) => result,
|
||||
Err(NoSolution) => return Some(Err(NoSolution)),
|
||||
};
|
||||
|
||||
if changed {
|
||||
has_changed = Ok(());
|
||||
}
|
||||
|
||||
match certainty {
|
||||
Certainty::Yes => {}
|
||||
Certainty::Maybe(_) => {
|
||||
new_goals.push(goal);
|
||||
has_changed = has_changed.map_err(|c| c.unify_and(certainty));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match has_changed {
|
||||
Ok(()) => {
|
||||
mem::swap(&mut new_goals, &mut goals);
|
||||
None
|
||||
}
|
||||
Err(certainty) => Some(Ok(certainty)),
|
||||
}
|
||||
},
|
||||
)
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
fn set_normalizes_to_hack_goal(&mut self, goal: Goal<'tcx, ty::ProjectionPredicate<'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);
|
||||
}
|
||||
|
||||
// Recursively evaluates a list of goals to completion, making a query response.
|
||||
//
|
||||
// This is just a convenient way of calling [`EvalCtxt::evaluate_all`],
|
||||
// then [`EvalCtxt::make_canonical_response`].
|
||||
fn evaluate_all_and_make_canonical_response(
|
||||
&mut self,
|
||||
goals: Vec<Goal<'tcx, ty::Predicate<'tcx>>>,
|
||||
) -> QueryResult<'tcx> {
|
||||
self.evaluate_all(goals).and_then(|certainty| self.make_canonical_response(certainty))
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
fn add_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) {
|
||||
self.nested_goals.goals.push(goal);
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, goals))]
|
||||
fn add_goals(&mut self, goals: impl IntoIterator<Item = Goal<'tcx, ty::Predicate<'tcx>>>) {
|
||||
let current_len = self.nested_goals.goals.len();
|
||||
self.nested_goals.goals.extend(goals);
|
||||
debug!("added_goals={:?}", &self.nested_goals.goals[current_len..]);
|
||||
}
|
||||
|
||||
fn try_merge_responses(
|
||||
|
@ -466,7 +274,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
});
|
||||
// FIXME(-Ztrait-solver=next): We should take the intersection of the constraints on all the
|
||||
// responses and use that for the constraints of this ambiguous response.
|
||||
let response = self.make_canonical_response(certainty);
|
||||
let response = self.evaluate_added_goals_and_make_canonical_response(certainty);
|
||||
if let Ok(response) = &response {
|
||||
assert!(response.has_no_inference_or_external_constraints());
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ use rustc_span::{sym, DUMMY_SP};
|
|||
use std::iter;
|
||||
|
||||
impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||
#[instrument(level = "debug", skip(self), ret)]
|
||||
pub(super) fn compute_projection_goal(
|
||||
&mut self,
|
||||
goal: Goal<'tcx, ProjectionPredicate<'tcx>>,
|
||||
|
@ -36,54 +37,18 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
self.merge_candidates_and_discard_reservation_impls(candidates)
|
||||
} else {
|
||||
let predicate = goal.predicate;
|
||||
let unconstrained_rhs = match predicate.term.unpack() {
|
||||
ty::TermKind::Ty(_) => self.next_ty_infer().into(),
|
||||
ty::TermKind::Const(ct) => self.next_const_infer(ct.ty()).into(),
|
||||
};
|
||||
let unconstrained_predicate = ty::Clause::Projection(ProjectionPredicate {
|
||||
let unconstrained_rhs = self.next_term_infer_of_kind(predicate.term);
|
||||
let unconstrained_predicate = ProjectionPredicate {
|
||||
projection_ty: goal.predicate.projection_ty,
|
||||
term: unconstrained_rhs,
|
||||
});
|
||||
let (_has_changed, normalize_certainty) = self.in_projection_eq_hack(|this| {
|
||||
this.evaluate_goal(goal.with(this.tcx(), unconstrained_predicate))
|
||||
})?;
|
||||
};
|
||||
|
||||
let nested_eq_goals = self.eq(goal.param_env, unconstrained_rhs, predicate.term)?;
|
||||
let eval_certainty = self.evaluate_all(nested_eq_goals)?;
|
||||
self.make_canonical_response(normalize_certainty.unify_and(eval_certainty))
|
||||
self.set_normalizes_to_hack_goal(goal.with(self.tcx(), unconstrained_predicate));
|
||||
self.try_evaluate_added_goals()?;
|
||||
self.eq(goal.param_env, unconstrained_rhs, predicate.term)?;
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
}
|
||||
|
||||
/// This sets a flag used by a debug assert in [`EvalCtxt::evaluate_goal`],
|
||||
/// see the comment in that method for more details.
|
||||
fn in_projection_eq_hack<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
|
||||
self.in_projection_eq_hack = true;
|
||||
let result = f(self);
|
||||
self.in_projection_eq_hack = false;
|
||||
result
|
||||
}
|
||||
|
||||
/// After normalizing the projection to `normalized_alias` with the given
|
||||
/// `normalization_certainty`, constrain the inference variable `term` to it
|
||||
/// and return a query response.
|
||||
fn eq_term_and_make_canonical_response(
|
||||
&mut self,
|
||||
goal: Goal<'tcx, ProjectionPredicate<'tcx>>,
|
||||
normalization_certainty: Certainty,
|
||||
normalized_alias: impl Into<ty::Term<'tcx>>,
|
||||
) -> QueryResult<'tcx> {
|
||||
// The term of our goal should be fully unconstrained, so this should never fail.
|
||||
//
|
||||
// It can however be ambiguous when the `normalized_alias` contains a projection.
|
||||
let nested_goals = self
|
||||
.eq(goal.param_env, goal.predicate.term, normalized_alias.into())
|
||||
.expect("failed to unify with unconstrained term");
|
||||
|
||||
let unify_certainty =
|
||||
self.evaluate_all(nested_goals).expect("failed to unify with unconstrained term");
|
||||
|
||||
self.make_canonical_response(normalization_certainty.unify_and(unify_certainty))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
||||
|
@ -111,19 +76,14 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||
ecx.probe(|ecx| {
|
||||
let assumption_projection_pred =
|
||||
ecx.instantiate_binder_with_infer(poly_projection_pred);
|
||||
let mut nested_goals = ecx.eq(
|
||||
ecx.eq(
|
||||
goal.param_env,
|
||||
goal.predicate.projection_ty,
|
||||
assumption_projection_pred.projection_ty,
|
||||
)?;
|
||||
nested_goals.extend(requirements);
|
||||
let subst_certainty = ecx.evaluate_all(nested_goals)?;
|
||||
|
||||
ecx.eq_term_and_make_canonical_response(
|
||||
goal,
|
||||
subst_certainty,
|
||||
assumption_projection_pred.term,
|
||||
)
|
||||
ecx.eq(goal.param_env, goal.predicate.term, assumption_projection_pred.term)?;
|
||||
ecx.add_goals(requirements);
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
})
|
||||
} else {
|
||||
Err(NoSolution)
|
||||
|
@ -139,21 +99,22 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||
&& poly_projection_pred.projection_def_id() == goal.predicate.def_id()
|
||||
{
|
||||
ecx.probe(|ecx| {
|
||||
let tcx = ecx.tcx();
|
||||
|
||||
let assumption_projection_pred =
|
||||
ecx.instantiate_binder_with_infer(poly_projection_pred);
|
||||
let mut nested_goals = ecx.eq(
|
||||
ecx.eq(
|
||||
goal.param_env,
|
||||
goal.predicate.projection_ty,
|
||||
assumption_projection_pred.projection_ty,
|
||||
)?;
|
||||
|
||||
let tcx = ecx.tcx();
|
||||
let ty::Dynamic(bounds, _, _) = *goal.predicate.self_ty().kind() else {
|
||||
bug!("expected object type in `consider_object_bound_candidate`");
|
||||
};
|
||||
nested_goals.extend(
|
||||
ecx.add_goals(
|
||||
structural_traits::predicates_for_object_candidate(
|
||||
ecx,
|
||||
&ecx,
|
||||
goal.param_env,
|
||||
goal.predicate.projection_ty.trait_ref(tcx),
|
||||
bounds,
|
||||
|
@ -161,14 +122,8 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||
.into_iter()
|
||||
.map(|pred| goal.with(tcx, pred)),
|
||||
);
|
||||
|
||||
let subst_certainty = ecx.evaluate_all(nested_goals)?;
|
||||
|
||||
ecx.eq_term_and_make_canonical_response(
|
||||
goal,
|
||||
subst_certainty,
|
||||
assumption_projection_pred.term,
|
||||
)
|
||||
ecx.eq(goal.param_env, goal.predicate.term, assumption_projection_pred.term)?;
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
})
|
||||
} else {
|
||||
Err(NoSolution)
|
||||
|
@ -195,16 +150,15 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||
let impl_substs = ecx.fresh_substs_for_item(impl_def_id);
|
||||
let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs);
|
||||
|
||||
let mut nested_goals = ecx.eq(goal.param_env, goal_trait_ref, impl_trait_ref)?;
|
||||
ecx.eq(goal.param_env, goal_trait_ref, impl_trait_ref)?;
|
||||
|
||||
let where_clause_bounds = tcx
|
||||
.predicates_of(impl_def_id)
|
||||
.instantiate(tcx, impl_substs)
|
||||
.predicates
|
||||
.into_iter()
|
||||
.map(|pred| goal.with(tcx, pred));
|
||||
|
||||
nested_goals.extend(where_clause_bounds);
|
||||
let match_impl_certainty = ecx.evaluate_all(nested_goals)?;
|
||||
ecx.add_goals(where_clause_bounds);
|
||||
|
||||
// In case the associated item is hidden due to specialization, we have to
|
||||
// return ambiguity this would otherwise be incomplete, resulting in
|
||||
|
@ -216,7 +170,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||
goal.predicate.def_id(),
|
||||
impl_def_id
|
||||
)? else {
|
||||
return ecx.make_canonical_response(match_impl_certainty.unify_and(Certainty::AMBIGUOUS));
|
||||
return ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
|
||||
};
|
||||
|
||||
if !assoc_def.item.defaultness(tcx).has_value() {
|
||||
|
@ -263,7 +217,8 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||
ty.map_bound(|ty| ty.into())
|
||||
};
|
||||
|
||||
ecx.eq_term_and_make_canonical_response(goal, match_impl_certainty, term.subst(tcx, substs))
|
||||
ecx.eq(goal.param_env, goal.predicate.term, term.subst(tcx, substs))?;
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -308,14 +263,18 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||
goal_kind: ty::ClosureKind,
|
||||
) -> QueryResult<'tcx> {
|
||||
let tcx = ecx.tcx();
|
||||
let Some(tupled_inputs_and_output) =
|
||||
structural_traits::extract_tupled_inputs_and_output_from_callable(
|
||||
tcx,
|
||||
goal.predicate.self_ty(),
|
||||
goal_kind,
|
||||
)? else {
|
||||
return ecx.make_canonical_response(Certainty::AMBIGUOUS);
|
||||
};
|
||||
let tupled_inputs_and_output =
|
||||
match structural_traits::extract_tupled_inputs_and_output_from_callable(
|
||||
tcx,
|
||||
goal.predicate.self_ty(),
|
||||
goal_kind,
|
||||
)? {
|
||||
Some(tupled_inputs_and_output) => tupled_inputs_and_output,
|
||||
None => {
|
||||
return ecx
|
||||
.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS);
|
||||
}
|
||||
};
|
||||
let output_is_sized_pred = tupled_inputs_and_output
|
||||
.map_bound(|(_, output)| tcx.at(DUMMY_SP).mk_trait_ref(LangItem::Sized, [output]));
|
||||
|
||||
|
@ -380,13 +339,9 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||
[ty::GenericArg::from(goal.predicate.self_ty())],
|
||||
));
|
||||
|
||||
let (_, is_sized_certainty) =
|
||||
ecx.evaluate_goal(goal.with(tcx, sized_predicate))?;
|
||||
return ecx.eq_term_and_make_canonical_response(
|
||||
goal,
|
||||
is_sized_certainty,
|
||||
tcx.types.unit,
|
||||
);
|
||||
ecx.add_goal(goal.with(tcx, sized_predicate));
|
||||
ecx.eq(goal.param_env, goal.predicate.term, tcx.types.unit.into())?;
|
||||
return ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
|
||||
}
|
||||
|
||||
ty::Adt(def, substs) if def.is_struct() => {
|
||||
|
@ -394,12 +349,12 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||
None => tcx.types.unit,
|
||||
Some(field_def) => {
|
||||
let self_ty = field_def.ty(tcx, substs);
|
||||
let new_goal = goal.with(
|
||||
ecx.add_goal(goal.with(
|
||||
tcx,
|
||||
ty::Binder::dummy(goal.predicate.with_self_ty(tcx, self_ty)),
|
||||
);
|
||||
let (_, certainty) = ecx.evaluate_goal(new_goal)?;
|
||||
return ecx.make_canonical_response(certainty);
|
||||
));
|
||||
return ecx
|
||||
.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -408,12 +363,12 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||
ty::Tuple(elements) => match elements.last() {
|
||||
None => tcx.types.unit,
|
||||
Some(&self_ty) => {
|
||||
let new_goal = goal.with(
|
||||
ecx.add_goal(goal.with(
|
||||
tcx,
|
||||
ty::Binder::dummy(goal.predicate.with_self_ty(tcx, self_ty)),
|
||||
);
|
||||
let (_, certainty) = ecx.evaluate_goal(new_goal)?;
|
||||
return ecx.make_canonical_response(certainty);
|
||||
));
|
||||
return ecx
|
||||
.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -426,7 +381,8 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||
),
|
||||
};
|
||||
|
||||
ecx.eq_term_and_make_canonical_response(goal, Certainty::Yes, metadata_ty)
|
||||
ecx.eq(goal.param_env, goal.predicate.term, metadata_ty.into())?;
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -522,7 +478,10 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||
goal: Goal<'tcx, Self>,
|
||||
) -> QueryResult<'tcx> {
|
||||
let discriminant = goal.predicate.self_ty().discriminant_ty(ecx.tcx());
|
||||
ecx.probe(|ecx| ecx.eq_term_and_make_canonical_response(goal, Certainty::Yes, discriminant))
|
||||
ecx.probe(|ecx| {
|
||||
ecx.eq(goal.param_env, goal.predicate.term, discriminant.into())?;
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,9 +39,7 @@ impl<'tcx> SearchGraph<'tcx> {
|
|||
}
|
||||
|
||||
pub(super) fn is_empty(&self) -> bool {
|
||||
self.stack.is_empty()
|
||||
&& self.provisional_cache.is_empty()
|
||||
&& !self.overflow_data.did_overflow()
|
||||
self.stack.is_empty() && self.provisional_cache.is_empty()
|
||||
}
|
||||
|
||||
/// Whether we're currently in a cycle. This should only be used
|
||||
|
|
|
@ -47,16 +47,15 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
let impl_substs = ecx.fresh_substs_for_item(impl_def_id);
|
||||
let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs);
|
||||
|
||||
let mut nested_goals =
|
||||
ecx.eq(goal.param_env, goal.predicate.trait_ref, impl_trait_ref)?;
|
||||
ecx.eq(goal.param_env, goal.predicate.trait_ref, impl_trait_ref)?;
|
||||
let where_clause_bounds = tcx
|
||||
.predicates_of(impl_def_id)
|
||||
.instantiate(tcx, impl_substs)
|
||||
.predicates
|
||||
.into_iter()
|
||||
.map(|pred| goal.with(tcx, pred));
|
||||
nested_goals.extend(where_clause_bounds);
|
||||
ecx.evaluate_all_and_make_canonical_response(nested_goals)
|
||||
ecx.add_goals(where_clause_bounds);
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -73,13 +72,13 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
ecx.probe(|ecx| {
|
||||
let assumption_trait_pred =
|
||||
ecx.instantiate_binder_with_infer(poly_trait_pred);
|
||||
let mut nested_goals = ecx.eq(
|
||||
ecx.eq(
|
||||
goal.param_env,
|
||||
goal.predicate.trait_ref,
|
||||
assumption_trait_pred.trait_ref,
|
||||
)?;
|
||||
nested_goals.extend(requirements);
|
||||
ecx.evaluate_all_and_make_canonical_response(nested_goals)
|
||||
ecx.add_goals(requirements);
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
})
|
||||
} else {
|
||||
Err(NoSolution)
|
||||
|
@ -98,7 +97,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
ecx.probe(|ecx| {
|
||||
let assumption_trait_pred =
|
||||
ecx.instantiate_binder_with_infer(poly_trait_pred);
|
||||
let mut nested_goals = ecx.eq(
|
||||
ecx.eq(
|
||||
goal.param_env,
|
||||
goal.predicate.trait_ref,
|
||||
assumption_trait_pred.trait_ref,
|
||||
|
@ -108,9 +107,9 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
let ty::Dynamic(bounds, _, _) = *goal.predicate.self_ty().kind() else {
|
||||
bug!("expected object type in `consider_object_bound_candidate`");
|
||||
};
|
||||
nested_goals.extend(
|
||||
ecx.add_goals(
|
||||
structural_traits::predicates_for_object_candidate(
|
||||
ecx,
|
||||
&ecx,
|
||||
goal.param_env,
|
||||
goal.predicate.trait_ref,
|
||||
bounds,
|
||||
|
@ -118,8 +117,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
.into_iter()
|
||||
.map(|pred| goal.with(tcx, pred)),
|
||||
);
|
||||
|
||||
ecx.evaluate_all_and_make_canonical_response(nested_goals)
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
})
|
||||
} else {
|
||||
Err(NoSolution)
|
||||
|
@ -166,9 +164,8 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
let nested_obligations = tcx
|
||||
.predicates_of(goal.predicate.def_id())
|
||||
.instantiate(tcx, goal.predicate.trait_ref.substs);
|
||||
ecx.evaluate_all_and_make_canonical_response(
|
||||
nested_obligations.predicates.into_iter().map(|p| goal.with(tcx, p)).collect(),
|
||||
)
|
||||
ecx.add_goals(nested_obligations.predicates.into_iter().map(|p| goal.with(tcx, p)));
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -197,7 +194,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
goal: Goal<'tcx, Self>,
|
||||
) -> QueryResult<'tcx> {
|
||||
if goal.predicate.self_ty().has_non_region_infer() {
|
||||
return ecx.make_canonical_response(Certainty::AMBIGUOUS);
|
||||
return ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS);
|
||||
}
|
||||
|
||||
let tcx = ecx.tcx();
|
||||
|
@ -209,7 +206,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
&& layout.layout.align().abi == usize_layout.align().abi
|
||||
{
|
||||
// FIXME: We could make this faster by making a no-constraints response
|
||||
ecx.make_canonical_response(Certainty::Yes)
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
} else {
|
||||
Err(NoSolution)
|
||||
}
|
||||
|
@ -221,14 +218,18 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
goal_kind: ty::ClosureKind,
|
||||
) -> QueryResult<'tcx> {
|
||||
let tcx = ecx.tcx();
|
||||
let Some(tupled_inputs_and_output) =
|
||||
structural_traits::extract_tupled_inputs_and_output_from_callable(
|
||||
let tupled_inputs_and_output =
|
||||
match structural_traits::extract_tupled_inputs_and_output_from_callable(
|
||||
tcx,
|
||||
goal.predicate.self_ty(),
|
||||
goal_kind,
|
||||
)? else {
|
||||
return ecx.make_canonical_response(Certainty::AMBIGUOUS);
|
||||
};
|
||||
)? {
|
||||
Some(a) => a,
|
||||
None => {
|
||||
return ecx
|
||||
.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS);
|
||||
}
|
||||
};
|
||||
let output_is_sized_pred = tupled_inputs_and_output
|
||||
.map_bound(|(_, output)| tcx.at(DUMMY_SP).mk_trait_ref(LangItem::Sized, [output]));
|
||||
|
||||
|
@ -247,7 +248,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
goal: Goal<'tcx, Self>,
|
||||
) -> QueryResult<'tcx> {
|
||||
if let ty::Tuple(..) = goal.predicate.self_ty().kind() {
|
||||
ecx.make_canonical_response(Certainty::Yes)
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
} else {
|
||||
Err(NoSolution)
|
||||
}
|
||||
|
@ -257,7 +258,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
ecx: &mut EvalCtxt<'_, 'tcx>,
|
||||
_goal: Goal<'tcx, Self>,
|
||||
) -> QueryResult<'tcx> {
|
||||
ecx.make_canonical_response(Certainty::Yes)
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
|
||||
fn consider_builtin_future_candidate(
|
||||
|
@ -277,7 +278,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
// Async generator unconditionally implement `Future`
|
||||
// Technically, we need to check that the future output type is Sized,
|
||||
// but that's already proven by the generator being WF.
|
||||
ecx.make_canonical_response(Certainty::Yes)
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
|
||||
fn consider_builtin_generator_candidate(
|
||||
|
@ -317,7 +318,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
let a_ty = goal.predicate.self_ty();
|
||||
let b_ty = goal.predicate.trait_ref.substs.type_at(1);
|
||||
if b_ty.is_ty_var() {
|
||||
return ecx.make_canonical_response(Certainty::AMBIGUOUS);
|
||||
return ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS);
|
||||
}
|
||||
ecx.probe(|ecx| {
|
||||
match (a_ty.kind(), b_ty.kind()) {
|
||||
|
@ -326,7 +327,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
// Dyn upcasting is handled separately, since due to upcasting,
|
||||
// when there are two supertraits that differ by substs, we
|
||||
// may return more than one query response.
|
||||
return Err(NoSolution);
|
||||
Err(NoSolution)
|
||||
}
|
||||
// `T` -> `dyn Trait` unsizing
|
||||
(_, &ty::Dynamic(data, region, ty::Dyn)) => {
|
||||
|
@ -341,29 +342,26 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
let Some(sized_def_id) = tcx.lang_items().sized_trait() else {
|
||||
return Err(NoSolution);
|
||||
};
|
||||
let nested_goals: Vec<_> = data
|
||||
.iter()
|
||||
// Check that the type implements all of the predicates of the def-id.
|
||||
// (i.e. the principal, all of the associated types match, and any auto traits)
|
||||
.map(|pred| goal.with(tcx, pred.with_self_ty(tcx, a_ty)))
|
||||
.chain([
|
||||
// The type must be Sized to be unsized.
|
||||
goal.with(
|
||||
tcx,
|
||||
ty::Binder::dummy(tcx.mk_trait_ref(sized_def_id, [a_ty])),
|
||||
),
|
||||
// The type must outlive the lifetime of the `dyn` we're unsizing into.
|
||||
goal.with(tcx, ty::Binder::dummy(ty::OutlivesPredicate(a_ty, region))),
|
||||
])
|
||||
.collect();
|
||||
|
||||
ecx.evaluate_all_and_make_canonical_response(nested_goals)
|
||||
// Check that the type implements all of the predicates of the def-id.
|
||||
// (i.e. the principal, all of the associated types match, and any auto traits)
|
||||
ecx.add_goals(
|
||||
data.iter().map(|pred| goal.with(tcx, pred.with_self_ty(tcx, a_ty))),
|
||||
);
|
||||
// The type must be Sized to be unsized.
|
||||
ecx.add_goal(
|
||||
goal.with(tcx, ty::Binder::dummy(tcx.mk_trait_ref(sized_def_id, [a_ty]))),
|
||||
);
|
||||
// The type must outlive the lifetime of the `dyn` we're unsizing into.
|
||||
ecx.add_goal(
|
||||
goal.with(tcx, ty::Binder::dummy(ty::OutlivesPredicate(a_ty, region))),
|
||||
);
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
// `[T; n]` -> `[T]` unsizing
|
||||
(&ty::Array(a_elem_ty, ..), &ty::Slice(b_elem_ty)) => {
|
||||
// We just require that the element type stays the same
|
||||
let nested_goals = ecx.eq(goal.param_env, a_elem_ty, b_elem_ty)?;
|
||||
ecx.evaluate_all_and_make_canonical_response(nested_goals)
|
||||
ecx.eq(goal.param_env, a_elem_ty, b_elem_ty)?;
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
// Struct unsizing `Struct<T>` -> `Struct<U>` where `T: Unsize<U>`
|
||||
(&ty::Adt(a_def, a_substs), &ty::Adt(b_def, b_substs))
|
||||
|
@ -397,15 +395,14 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
|
||||
// Finally, we require that `TailA: Unsize<TailB>` for the tail field
|
||||
// types.
|
||||
let mut nested_goals = ecx.eq(goal.param_env, unsized_a_ty, b_ty)?;
|
||||
nested_goals.push(goal.with(
|
||||
ecx.eq(goal.param_env, unsized_a_ty, b_ty)?;
|
||||
ecx.add_goal(goal.with(
|
||||
tcx,
|
||||
ty::Binder::dummy(
|
||||
tcx.mk_trait_ref(goal.predicate.def_id(), [a_tail_ty, b_tail_ty]),
|
||||
),
|
||||
));
|
||||
|
||||
ecx.evaluate_all_and_make_canonical_response(nested_goals)
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
// Tuple unsizing `(.., T)` -> `(.., U)` where `T: Unsize<U>`
|
||||
(&ty::Tuple(a_tys), &ty::Tuple(b_tys))
|
||||
|
@ -417,17 +414,16 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
// Substitute just the tail field of B., and require that they're equal.
|
||||
let unsized_a_ty =
|
||||
tcx.mk_tup_from_iter(a_rest_tys.iter().chain([b_last_ty]).copied());
|
||||
let mut nested_goals = ecx.eq(goal.param_env, unsized_a_ty, b_ty)?;
|
||||
ecx.eq(goal.param_env, unsized_a_ty, b_ty)?;
|
||||
|
||||
// Similar to ADTs, require that the rest of the fields are equal.
|
||||
nested_goals.push(goal.with(
|
||||
ecx.add_goal(goal.with(
|
||||
tcx,
|
||||
ty::Binder::dummy(
|
||||
tcx.mk_trait_ref(goal.predicate.def_id(), [*a_last_ty, *b_last_ty]),
|
||||
),
|
||||
));
|
||||
|
||||
ecx.evaluate_all_and_make_canonical_response(nested_goals)
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
_ => Err(NoSolution),
|
||||
}
|
||||
|
@ -477,12 +473,11 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
let new_a_ty = tcx.mk_dynamic(new_a_data, b_region, ty::Dyn);
|
||||
|
||||
// We also require that A's lifetime outlives B's lifetime.
|
||||
let mut nested_obligations = ecx.eq(goal.param_env, new_a_ty, b_ty)?;
|
||||
nested_obligations.push(
|
||||
ecx.eq(goal.param_env, new_a_ty, b_ty)?;
|
||||
ecx.add_goal(
|
||||
goal.with(tcx, ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region))),
|
||||
);
|
||||
|
||||
ecx.evaluate_all_and_make_canonical_response(nested_obligations)
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
})
|
||||
};
|
||||
|
||||
|
@ -516,7 +511,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
|||
_goal: Goal<'tcx, Self>,
|
||||
) -> QueryResult<'tcx> {
|
||||
// `DiscriminantKind` is automatically implemented for every type.
|
||||
ecx.make_canonical_response(Certainty::Yes)
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -530,21 +525,23 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
goal: Goal<'tcx, TraitPredicate<'tcx>>,
|
||||
constituent_tys: impl Fn(&EvalCtxt<'_, 'tcx>, Ty<'tcx>) -> Result<Vec<Ty<'tcx>>, NoSolution>,
|
||||
) -> QueryResult<'tcx> {
|
||||
self.probe(|this| {
|
||||
this.evaluate_all_and_make_canonical_response(
|
||||
constituent_tys(this, goal.predicate.self_ty())?
|
||||
self.probe(|ecx| {
|
||||
ecx.add_goals(
|
||||
constituent_tys(ecx, goal.predicate.self_ty())?
|
||||
.into_iter()
|
||||
.map(|ty| {
|
||||
goal.with(
|
||||
this.tcx(),
|
||||
ty::Binder::dummy(goal.predicate.with_self_ty(this.tcx(), ty)),
|
||||
ecx.tcx(),
|
||||
ty::Binder::dummy(goal.predicate.with_self_ty(ecx.tcx(), ty)),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
pub(super) fn compute_trait_goal(
|
||||
&mut self,
|
||||
goal: Goal<'tcx, TraitPredicate<'tcx>>,
|
||||
|
|
|
@ -333,7 +333,7 @@ impl<'tcx> TypeFolder<TyCtxt<'tcx>> for ReplaceProjectionWith<'_, 'tcx> {
|
|||
// FIXME: Technically this folder could be fallible?
|
||||
let nested = self
|
||||
.ecx
|
||||
.eq(self.param_env, alias_ty, proj.projection_ty)
|
||||
.eq_and_get_goals(self.param_env, alias_ty, proj.projection_ty)
|
||||
.expect("expected to be able to unify goal projection with dyn's projection");
|
||||
// FIXME: Technically we could register these too..
|
||||
assert!(nested.is_empty(), "did not expect unification to have any nested goals");
|
||||
|
|
Loading…
Add table
Reference in a new issue