Rollup merge of #106718 - lcnr:solver-cycles, r=compiler-errors

finish trait solver skeleton work

### 648d661b4e0fcf55f7082894f577377eb451db4b

The previous implementation didn't remove provisional entries which depended on the current goal if we're forced to rerun in case the provisional result of that entry is different from the new result. For reference, see https://rust-lang.github.io/chalk/book/recursive/search_graph.html.

We should also treat inductive cycles as overflow, not ordinary ambiguity.

### 219a5de2517cebfe20a2c3417bd302f7c12db70c 6a1912be539dd5a3b3c10be669787c4bf0c1868a

These two commits move canonicalization to the start of the queries which simplifies a bunch of stuff. I originally intended to keep stuff canonicalized for a while because I expected us to add a additional caches the trait solver, either for candidate assembly or for projections. We ended up not adding (and expect to not need) any of them so this just ends up being easier to understand.

### d78d5ad0979e965afde6500bccfa119b47063506

adds a special `eq` for the solver which doesn't care about obligations or spans

### 18704e6a78b7703e1bbb3856f015cb76c0a07a06

implements https://rust-lang.zulipchat.com/#narrow/stream/364551-t-types.2Ftrait-system-refactor/topic/projection.20cache

r? `@compiler-errors`
This commit is contained in:
Dylan DPC 2023-01-18 15:55:38 +05:30 committed by GitHub
commit 6f5c3c9cdb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 922 additions and 650 deletions

View File

@ -4783,6 +4783,7 @@ dependencies = [
"rustc_middle",
"rustc_parse_format",
"rustc_query_system",
"rustc_serialize",
"rustc_session",
"rustc_span",
"rustc_target",

View File

@ -339,6 +339,12 @@ TrivialTypeTraversalAndLiftImpls! {
}
impl<'tcx> CanonicalVarValues<'tcx> {
/// Creates dummy var values which should not be used in a
/// canonical response.
pub fn dummy() -> CanonicalVarValues<'tcx> {
CanonicalVarValues { var_values: Default::default() }
}
#[inline]
pub fn len(&self) -> usize {
self.var_values.len()

View File

@ -1113,17 +1113,6 @@ impl<'tcx, T> Binder<'tcx, T> {
if self.0.has_escaping_bound_vars() { None } else { Some(self.skip_binder()) }
}
pub fn no_bound_vars_ignoring_escaping(self, tcx: TyCtxt<'tcx>) -> Option<T>
where
T: TypeFoldable<'tcx>,
{
if !self.0.has_escaping_bound_vars() {
Some(self.skip_binder())
} else {
self.0.try_fold_with(&mut SkipBindersAt { index: ty::INNERMOST, tcx }).ok()
}
}
/// Splits the contents into two things that share the same binder
/// level as the original, returning two distinct binders.
///

View File

@ -19,6 +19,7 @@ rustc_infer = { path = "../rustc_infer" }
rustc_lint_defs = { path = "../rustc_lint_defs" }
rustc_macros = { path = "../rustc_macros" }
rustc_query_system = { path = "../rustc_query_system" }
rustc_serialize = { path = "../rustc_serialize" }
rustc_session = { path = "../rustc_session" }
rustc_span = { path = "../rustc_span" }
rustc_target = { path = "../rustc_target" }

View File

@ -21,6 +21,7 @@
#![feature(never_type)]
#![feature(result_option_inspect)]
#![feature(type_alias_impl_trait)]
#![feature(min_specialization)]
#![recursion_limit = "512"] // For rustdoc
#[macro_use]

View File

@ -1,38 +1,84 @@
//! Code shared by trait and projection goals for candidate assembly.
use super::infcx_ext::InferCtxtExt;
use super::{
instantiate_canonical_query_response, CanonicalGoal, CanonicalResponse, Certainty, EvalCtxt,
Goal,
};
use super::{CanonicalResponse, Certainty, EvalCtxt, Goal};
use rustc_hir::def_id::DefId;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::infer::{
canonical::{CanonicalVarValues, OriginalQueryValues},
InferCtxt,
};
use rustc_infer::traits::query::NoSolution;
use rustc_middle::ty::TypeFoldable;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_span::DUMMY_SP;
use std::fmt::Debug;
/// A candidate is a possible way to prove a goal.
///
/// It consists of both the `source`, which describes how that goal would be proven,
/// and the `result` when using the given `source`.
///
/// For the list of possible candidates, please look at the documentation of
/// [super::trait_goals::CandidateSource] and [super::project_goals::CandidateSource].
#[derive(Debug, Clone)]
pub(super) struct Candidate<'tcx, G: GoalKind<'tcx>> {
pub(super) source: G::CandidateSource,
pub(super) struct Candidate<'tcx> {
pub(super) source: CandidateSource,
pub(super) result: CanonicalResponse<'tcx>,
}
pub(super) trait GoalKind<'tcx>: TypeFoldable<'tcx> + Copy {
type CandidateSource: Debug + Copy;
/// Possible ways the given goal can be proven.
#[derive(Debug, Clone, Copy)]
pub(super) enum CandidateSource {
/// A user written impl.
///
/// ## Examples
///
/// ```rust
/// fn main() {
/// let x: Vec<u32> = Vec::new();
/// // This uses the impl from the standard library to prove `Vec<T>: Clone`.
/// let y = x.clone();
/// }
/// ```
Impl(DefId),
/// A builtin impl generated by the compiler. When adding a new special
/// trait, try to use actual impls whenever possible. Builtin impls should
/// only be used in cases where the impl cannot be manually be written.
///
/// Notable examples are auto traits, `Sized`, and `DiscriminantKind`.
/// For a list of all traits with builtin impls, check out the
/// [`EvalCtxt::assemble_builtin_impl_candidates`] method. Not
BuiltinImpl,
/// An assumption from the environment.
///
/// More precicely we've used the `n-th` assumption in the `param_env`.
///
/// ## Examples
///
/// ```rust
/// fn is_clone<T: Clone>(x: T) -> (T, T) {
/// // This uses the assumption `T: Clone` from the `where`-bounds
/// // to prove `T: Clone`.
/// (x.clone(), x)
/// }
/// ```
ParamEnv(usize),
/// If the self type is an alias type, e.g. an opaque type or a projection,
/// we know the bounds on that alias to hold even without knowing its concrete
/// underlying type.
///
/// More precisely this candidate is using the `n-th` bound in the `item_bounds` of
/// the self type.
///
/// ## Examples
///
/// ```rust
/// trait Trait {
/// type Assoc: Clone;
/// }
///
/// fn foo<T: Trait>(x: <T as Trait>::Assoc) {
/// // We prove `<T as Trait>::Assoc` by looking at the bounds on `Assoc` in
/// // in the trait definition.
/// let _y = x.clone();
/// }
/// ```
AliasBound(usize),
}
pub(super) trait GoalKind<'tcx>: TypeFoldable<'tcx> + Copy {
fn self_ty(self) -> Ty<'tcx>;
fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self;
@ -40,47 +86,40 @@ pub(super) trait GoalKind<'tcx>: TypeFoldable<'tcx> + Copy {
fn trait_def_id(self, tcx: TyCtxt<'tcx>) -> DefId;
fn consider_impl_candidate(
acx: &mut AssemblyCtxt<'_, 'tcx, Self>,
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
impl_def_id: DefId,
);
) -> Result<Certainty, NoSolution>;
fn consider_builtin_sized_candidate(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> Result<Certainty, NoSolution>;
fn consider_assumption(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
assumption: ty::Predicate<'tcx>,
) -> Result<Certainty, NoSolution>;
}
/// An abstraction which correctly deals with the canonical results for candidates.
///
/// It also deduplicates the behavior between trait and projection predicates.
pub(super) struct AssemblyCtxt<'a, 'tcx, G: GoalKind<'tcx>> {
pub(super) cx: &'a mut EvalCtxt<'tcx>,
pub(super) infcx: &'a InferCtxt<'tcx>,
var_values: CanonicalVarValues<'tcx>,
candidates: Vec<Candidate<'tcx, G>>,
}
impl<'a, 'tcx, G: GoalKind<'tcx>> AssemblyCtxt<'a, 'tcx, G> {
pub(super) fn assemble_and_evaluate_candidates(
cx: &'a mut EvalCtxt<'tcx>,
goal: CanonicalGoal<'tcx, G>,
) -> Vec<Candidate<'tcx, G>> {
let (ref infcx, goal, var_values) =
cx.tcx.infer_ctxt().build_with_canonical(DUMMY_SP, &goal);
let mut acx = AssemblyCtxt { cx, infcx, var_values, candidates: Vec::new() };
acx.assemble_candidates_after_normalizing_self_ty(goal);
acx.assemble_impl_candidates(goal);
acx.candidates
}
pub(super) fn try_insert_candidate(
impl<'tcx> EvalCtxt<'_, 'tcx> {
pub(super) fn assemble_and_evaluate_candidates<G: GoalKind<'tcx>>(
&mut self,
source: G::CandidateSource,
certainty: Certainty,
) {
match self.infcx.make_canonical_response(self.var_values.clone(), certainty) {
Ok(result) => self.candidates.push(Candidate { source, result }),
Err(NoSolution) => debug!(?source, ?certainty, "failed leakcheck"),
}
goal: Goal<'tcx, G>,
) -> Vec<Candidate<'tcx>> {
let mut candidates = Vec::new();
self.assemble_candidates_after_normalizing_self_ty(goal, &mut candidates);
self.assemble_impl_candidates(goal, &mut candidates);
self.assemble_builtin_impl_candidates(goal, &mut candidates);
self.assemble_param_env_candidates(goal, &mut candidates);
self.assemble_alias_bound_candidates(goal, &mut candidates);
candidates
}
/// If the self type of a goal is a projection, computing the relevant candidates is difficult.
@ -88,8 +127,12 @@ impl<'a, 'tcx, G: GoalKind<'tcx>> AssemblyCtxt<'a, 'tcx, G> {
/// To deal with this, we first try to normalize the self type and add the candidates for the normalized
/// self type to the list of candidates in case that succeeds. Note that we can't just eagerly return in
/// this case as projections as self types add `
fn assemble_candidates_after_normalizing_self_ty(&mut self, goal: Goal<'tcx, G>) {
let tcx = self.cx.tcx;
fn assemble_candidates_after_normalizing_self_ty<G: GoalKind<'tcx>>(
&mut self,
goal: Goal<'tcx, G>,
candidates: &mut Vec<Candidate<'tcx>>,
) {
let tcx = self.tcx();
// FIXME: We also have to normalize opaque types, not sure where to best fit that in.
let &ty::Alias(ty::Projection, projection_ty) = goal.predicate.self_ty().kind() else {
return
@ -103,45 +146,136 @@ impl<'a, 'tcx, G: GoalKind<'tcx>> AssemblyCtxt<'a, 'tcx, G> {
term: normalized_ty.into(),
}),
);
let normalization_certainty =
match self.cx.evaluate_goal(&self.infcx, normalizes_to_goal) {
Ok((_, certainty)) => certainty,
Err(NoSolution) => return,
};
let normalization_certainty = match self.evaluate_goal(normalizes_to_goal) {
Ok((_, certainty)) => certainty,
Err(NoSolution) => return,
};
// 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 both winnowing and to resolve associated items.
// 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 mut orig_values = OriginalQueryValues::default();
let goal = self.infcx.canonicalize_query(goal, &mut orig_values);
let normalized_candidates =
AssemblyCtxt::assemble_and_evaluate_candidates(self.cx, goal);
// Map each candidate from being canonical wrt the current inference context to being
// canonical wrt the caller.
for Candidate { source, result } in normalized_candidates {
self.infcx.probe(|_| {
let candidate_certainty =
instantiate_canonical_query_response(&self.infcx, &orig_values, result);
// FIXME: This is a bit scary if the `normalizes_to_goal` overflows.
//
// If we have an ambiguous candidate it hides that normalization
// caused an overflow which may cause issues.
self.try_insert_candidate(
source,
normalization_certainty.unify_and(candidate_certainty),
)
})
// FIXME: This is broken if we care about the `usize` of `AliasBound` because the self type
// could be normalized to yet another projection with different item bounds.
let normalized_candidates = self.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);
}
})
}
fn assemble_impl_candidates(&mut self, goal: Goal<'tcx, G>) {
self.cx.tcx.for_each_relevant_impl(
goal.predicate.trait_def_id(self.cx.tcx),
fn assemble_impl_candidates<G: GoalKind<'tcx>>(
&mut self,
goal: Goal<'tcx, G>,
candidates: &mut Vec<Candidate<'tcx>>,
) {
let tcx = self.tcx();
tcx.for_each_relevant_impl(
goal.predicate.trait_def_id(tcx),
goal.predicate.self_ty(),
|impl_def_id| G::consider_impl_candidate(self, goal, impl_def_id),
|impl_def_id| match G::consider_impl_candidate(self, goal, impl_def_id)
.and_then(|certainty| self.make_canonical_response(certainty))
{
Ok(result) => candidates
.push(Candidate { source: CandidateSource::Impl(impl_def_id), result }),
Err(NoSolution) => (),
},
);
}
fn assemble_builtin_impl_candidates<G: GoalKind<'tcx>>(
&mut self,
goal: Goal<'tcx, G>,
candidates: &mut Vec<Candidate<'tcx>>,
) {
let lang_items = self.tcx().lang_items();
let trait_def_id = goal.predicate.trait_def_id(self.tcx());
let result = if lang_items.sized_trait() == Some(trait_def_id) {
G::consider_builtin_sized_candidate(self, goal)
} else {
Err(NoSolution)
};
match result.and_then(|certainty| self.make_canonical_response(certainty)) {
Ok(result) => {
candidates.push(Candidate { source: CandidateSource::BuiltinImpl, result })
}
Err(NoSolution) => (),
}
}
fn assemble_param_env_candidates<G: GoalKind<'tcx>>(
&mut self,
goal: Goal<'tcx, G>,
candidates: &mut Vec<Candidate<'tcx>>,
) {
for (i, assumption) in goal.param_env.caller_bounds().iter().enumerate() {
match G::consider_assumption(self, goal, assumption)
.and_then(|certainty| self.make_canonical_response(certainty))
{
Ok(result) => {
candidates.push(Candidate { source: CandidateSource::ParamEnv(i), result })
}
Err(NoSolution) => (),
}
}
}
fn assemble_alias_bound_candidates<G: GoalKind<'tcx>>(
&mut self,
goal: Goal<'tcx, G>,
candidates: &mut Vec<Candidate<'tcx>>,
) {
let alias_ty = match goal.predicate.self_ty().kind() {
ty::Bool
| ty::Char
| ty::Int(_)
| ty::Uint(_)
| ty::Float(_)
| ty::Adt(_, _)
| ty::Foreign(_)
| ty::Str
| ty::Array(_, _)
| ty::Slice(_)
| ty::RawPtr(_)
| ty::Ref(_, _, _)
| ty::FnDef(_, _)
| ty::FnPtr(_)
| ty::Dynamic(..)
| ty::Closure(..)
| ty::Generator(..)
| ty::GeneratorWitness(_)
| ty::Never
| ty::Tuple(_)
| ty::Param(_)
| ty::Placeholder(..)
| ty::Infer(_)
| ty::Error(_) => return,
ty::Bound(..) => bug!("unexpected bound type: {goal:?}"),
ty::Alias(_, alias_ty) => alias_ty,
};
for (i, (assumption, _)) in self
.tcx()
.bound_explicit_item_bounds(alias_ty.def_id)
.subst_iter_copied(self.tcx(), alias_ty.substs)
.enumerate()
{
match G::consider_assumption(self, goal, assumption)
.and_then(|certainty| self.make_canonical_response(certainty))
{
Ok(result) => {
candidates.push(Candidate { source: CandidateSource::AliasBound(i), result })
}
Err(NoSolution) => (),
}
}
}
}

View File

@ -1,291 +0,0 @@
//! This module both handles the global cache which stores "finished" goals,
//! and the provisional cache which contains partially computed goals.
//!
//! The provisional cache is necessary when dealing with coinductive cycles.
//!
//! For more information about the provisional cache and coinduction in general,
//! check out the relevant section of the rustc-dev-guide.
//!
//! FIXME(@lcnr): Write that section, feel free to ping me if you need help here
//! before then or if I still haven't done that before January 2023.
use super::overflow::OverflowData;
use super::{CanonicalGoal, Certainty, MaybeCause, Response};
use super::{EvalCtxt, QueryResult};
use rustc_data_structures::fx::FxHashMap;
use rustc_infer::infer::canonical::{Canonical, CanonicalVarKind, CanonicalVarValues};
use rustc_middle::ty::{self, TyCtxt};
use std::{cmp::Ordering, collections::hash_map::Entry};
#[derive(Debug, Clone)]
struct ProvisionalEntry<'tcx> {
// In case we have a coinductive cycle, this is the
// the currently least restrictive result of this goal.
response: QueryResult<'tcx>,
// The lowest element on the stack on which this result
// relies on. Starts out as just being the depth at which
// we've proven this obligation, but gets lowered to the
// depth of another goal if we rely on it in a cycle.
depth: usize,
}
struct StackElem<'tcx> {
goal: CanonicalGoal<'tcx>,
has_been_used: bool,
}
/// The cache used for goals which are currently in progress or which depend
/// on in progress results.
///
/// Once we're done with a goal we can store it in the global trait solver
/// cache of the `TyCtxt`. For goals which we're currently proving, or which
/// have only been proven via a coinductive cycle using a goal still on our stack
/// we have to use this separate data structure.
///
/// The current data structure is not perfect, so there may still be room for
/// improvement here. We have the following requirements:
///
/// ## Is there is a provisional entry for the given goal:
///
/// ```ignore (for syntax highlighting)
/// self.entries.get(goal)
/// ```
///
/// ## Get all goals on the stack involved in a cycle:
///
/// ```ignore (for syntax highlighting)
/// let entry = self.entries.get(goal).unwrap();
/// let involved_goals = self.stack.iter().skip(entry.depth);
/// ```
///
/// ## Capping the depth of all entries
///
/// Needed whenever we encounter a cycle. The current implementation always
/// iterates over all entries instead of only the ones with a larger depth.
/// Changing this may result in notable performance improvements.
///
/// ```ignore (for syntax highlighting)
/// let cycle_depth = self.entries.get(goal).unwrap().depth;
/// for e in &mut self.entries {
/// e.depth = e.depth.min(cycle_depth);
/// }
/// ```
///
/// ## Checking whether we have to rerun the current goal
///
/// A goal has to be rerun if its provisional result was used in a cycle
/// and that result is different from its final result. We update
/// [StackElem::has_been_used] for the deepest stack element involved in a cycle.
///
/// ## Moving all finished goals into the global cache
///
/// If `stack_elem.has_been_used` is true, iterate over all entries, moving the ones
/// with equal depth. If not, simply move this single entry.
pub(super) struct ProvisionalCache<'tcx> {
stack: Vec<StackElem<'tcx>>,
entries: FxHashMap<CanonicalGoal<'tcx>, ProvisionalEntry<'tcx>>,
}
impl<'tcx> ProvisionalCache<'tcx> {
pub(super) fn empty() -> ProvisionalCache<'tcx> {
ProvisionalCache { stack: Vec::new(), entries: Default::default() }
}
pub(super) fn current_depth(&self) -> usize {
self.stack.len()
}
}
impl<'tcx> EvalCtxt<'tcx> {
/// Tries putting the new goal on the stack, returning an error if it is already cached.
///
/// This correctly updates the provisional cache if there is a cycle.
pub(super) fn try_push_stack(
&mut self,
goal: CanonicalGoal<'tcx>,
) -> Result<(), QueryResult<'tcx>> {
// FIXME: start by checking the global cache
// Look at the provisional cache to check for cycles.
let cache = &mut self.provisional_cache;
match cache.entries.entry(goal) {
// No entry, simply push this goal on the stack after dealing with overflow.
Entry::Vacant(v) => {
if self.overflow_data.has_overflow(cache.stack.len()) {
return Err(self.deal_with_overflow(goal));
}
v.insert(ProvisionalEntry {
response: response_no_constraints(self.tcx, goal, Certainty::Yes),
depth: cache.stack.len(),
});
cache.stack.push(StackElem { goal, has_been_used: false });
Ok(())
}
// We have a nested goal which relies on a goal `root` deeper in the stack.
//
// We first store that we may have to rerun `evaluate_goal` for `root` in case the
// provisional response is not equal to the final response. We also update the depth
// of all goals which recursively depend on our current goal to depend on `root`
// instead.
//
// Finally we can return either the provisional response for that goal if we have a
// coinductive cycle or an ambiguous result if the cycle is inductive.
Entry::Occupied(entry) => {
// FIXME: `ProvisionalEntry` should be `Copy`.
let entry = entry.get().clone();
cache.stack[entry.depth].has_been_used = true;
for provisional_entry in cache.entries.values_mut() {
provisional_entry.depth = provisional_entry.depth.min(entry.depth);
}
// NOTE: The goals on the stack aren't the only goals involved in this cycle.
// We can also depend on goals which aren't part of the stack but coinductively
// depend on the stack themselves. We already checked whether all the goals
// between these goals and their root on the stack. This means that as long as
// each goal in a cycle is checked for coinductivity by itself simply checking
// the stack is enough.
if cache.stack[entry.depth..]
.iter()
.all(|g| g.goal.value.predicate.is_coinductive(self.tcx))
{
Err(entry.response)
} else {
Err(response_no_constraints(
self.tcx,
goal,
Certainty::Maybe(MaybeCause::Ambiguity),
))
}
}
}
}
/// We cannot simply store the result of [EvalCtxt::compute_goal] as we have to deal with
/// coinductive cycles.
///
/// When we encounter a coinductive cycle, we have to prove the final result of that cycle
/// while we are still computing that result. Because of this we continously recompute the
/// cycle until the result of the previous iteration is equal to the final result, at which
/// point we are done.
///
/// This function returns `true` if we were able to finalize the goal and `false` if it has
/// updated the provisional cache and we have to recompute the current goal.
///
/// FIXME: Refer to the rustc-dev-guide entry once it exists.
pub(super) fn try_finalize_goal(
&mut self,
actual_goal: CanonicalGoal<'tcx>,
response: QueryResult<'tcx>,
) -> bool {
let cache = &mut self.provisional_cache;
let StackElem { goal, has_been_used } = cache.stack.pop().unwrap();
assert_eq!(goal, actual_goal);
let provisional_entry = cache.entries.get_mut(&goal).unwrap();
// Check whether the current stack entry is the root of a cycle.
//
// If so, we either move all participants of that cycle to the global cache
// or, in case the provisional response used in the cycle is not equal to the
// final response, have to recompute the goal after updating the provisional
// response to the final response of this iteration.
if has_been_used {
if provisional_entry.response == response {
// We simply drop all entries according to an immutable condition, so
// query instability is not a concern here.
#[allow(rustc::potential_query_instability)]
cache.entries.retain(|goal, entry| match entry.depth.cmp(&cache.stack.len()) {
Ordering::Less => true,
Ordering::Equal => {
Self::try_move_finished_goal_to_global_cache(
self.tcx,
&mut self.overflow_data,
&cache.stack,
// FIXME: these should be `Copy` :(
goal.clone(),
entry.response.clone(),
);
false
}
Ordering::Greater => bug!("entry with greater depth than the current leaf"),
});
true
} else {
provisional_entry.response = response;
cache.stack.push(StackElem { goal, has_been_used: false });
false
}
} else {
Self::try_move_finished_goal_to_global_cache(
self.tcx,
&mut self.overflow_data,
&cache.stack,
goal,
response,
);
cache.entries.remove(&goal);
true
}
}
fn try_move_finished_goal_to_global_cache(
tcx: TyCtxt<'tcx>,
overflow_data: &mut OverflowData,
stack: &[StackElem<'tcx>],
goal: CanonicalGoal<'tcx>,
response: QueryResult<'tcx>,
) {
// We move goals to the global cache if we either did not hit an overflow or if it's
// the root goal as that will now always hit the same overflow limit.
//
// NOTE: We cannot move any non-root goals to the global cache even if their final result
// isn't impacted by the overflow as that goal still has unstable query dependencies
// because it didn't go its full depth.
//
// FIXME(@lcnr): We could still cache subtrees which are not impacted by overflow though.
// Tracking that info correctly isn't trivial, so I haven't implemented it for now.
let should_cache_globally = !overflow_data.did_overflow() || stack.is_empty();
if should_cache_globally {
// FIXME: move the provisional entry to the global cache.
let _ = (tcx, goal, response);
}
}
}
pub(super) fn response_no_constraints<'tcx>(
tcx: TyCtxt<'tcx>,
goal: Canonical<'tcx, impl Sized>,
certainty: Certainty,
) -> QueryResult<'tcx> {
let var_values = goal
.variables
.iter()
.enumerate()
.map(|(i, info)| match info.kind {
CanonicalVarKind::Ty(_) | CanonicalVarKind::PlaceholderTy(_) => {
tcx.mk_ty(ty::Bound(ty::INNERMOST, ty::BoundVar::from_usize(i).into())).into()
}
CanonicalVarKind::Region(_) | CanonicalVarKind::PlaceholderRegion(_) => {
let br = ty::BoundRegion {
var: ty::BoundVar::from_usize(i),
kind: ty::BrAnon(i as u32, None),
};
tcx.mk_region(ty::ReLateBound(ty::INNERMOST, br)).into()
}
CanonicalVarKind::Const(_, ty) | CanonicalVarKind::PlaceholderConst(_, ty) => tcx
.mk_const(ty::ConstKind::Bound(ty::INNERMOST, ty::BoundVar::from_usize(i)), ty)
.into(),
})
.collect();
Ok(Canonical {
max_universe: goal.max_universe,
variables: goal.variables,
value: Response {
var_values: CanonicalVarValues { var_values },
external_constraints: Default::default(),
certainty,
},
})
}

View File

@ -10,7 +10,7 @@ use rustc_infer::{
};
use rustc_middle::ty;
use super::{Certainty, EvalCtxt};
use super::{search_graph, Certainty, EvalCtxt};
/// A trait engine using the new trait solver.
///
@ -67,9 +67,10 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
let mut has_changed = false;
for obligation in mem::take(&mut self.obligations) {
let mut cx = EvalCtxt::new(infcx.tcx);
let (changed, certainty) = match cx.evaluate_goal(infcx, obligation.clone().into())
{
let goal = obligation.clone().into();
let search_graph = &mut search_graph::SearchGraph::new(infcx.tcx);
let mut ecx = EvalCtxt::new_outside_solver(infcx, search_graph);
let (changed, certainty) = match ecx.evaluate_goal(goal) {
Ok(result) => result,
Err(NoSolution) => {
errors.push(FulfillmentError {

View File

@ -1,23 +1,30 @@
use rustc_infer::infer::canonical::CanonicalVarValues;
use rustc_infer::infer::at::ToTrace;
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_infer::infer::InferCtxt;
use rustc_infer::infer::{InferCtxt, InferOk};
use rustc_infer::traits::query::NoSolution;
use rustc_middle::ty::Ty;
use rustc_infer::traits::ObligationCause;
use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind};
use rustc_middle::ty::{self, Ty};
use rustc_span::DUMMY_SP;
use crate::solve::ExternalConstraints;
use super::{Certainty, QueryResult, Response};
use super::Goal;
/// Methods used inside of the canonical queries of the solver.
///
/// Most notably these do not care about diagnostics information.
/// If you find this while looking for methods to use outside of the
/// solver, you may look at the implementation of these method for
/// help.
pub(super) trait InferCtxtExt<'tcx> {
fn next_ty_infer(&self) -> Ty<'tcx>;
fn next_const_infer(&self, ty: Ty<'tcx>) -> ty::Const<'tcx>;
fn make_canonical_response(
fn eq<T: ToTrace<'tcx>>(
&self,
var_values: CanonicalVarValues<'tcx>,
certainty: Certainty,
) -> QueryResult<'tcx>;
param_env: ty::ParamEnv<'tcx>,
lhs: T,
rhs: T,
) -> Result<Vec<Goal<'tcx, ty::Predicate<'tcx>>>, NoSolution>;
}
impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> {
@ -27,29 +34,29 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> {
span: DUMMY_SP,
})
}
fn next_const_infer(&self, ty: Ty<'tcx>) -> ty::Const<'tcx> {
self.next_const_var(
ty,
ConstVariableOrigin { kind: ConstVariableOriginKind::MiscVariable, span: DUMMY_SP },
)
}
fn make_canonical_response(
#[instrument(level = "debug", skip(self, param_env), ret)]
fn eq<T: ToTrace<'tcx>>(
&self,
var_values: CanonicalVarValues<'tcx>,
certainty: Certainty,
) -> QueryResult<'tcx> {
let external_constraints = take_external_constraints(self)?;
Ok(self.canonicalize_response(Response { var_values, external_constraints, certainty }))
param_env: ty::ParamEnv<'tcx>,
lhs: T,
rhs: T,
) -> Result<Vec<Goal<'tcx, ty::Predicate<'tcx>>>, NoSolution> {
self.at(&ObligationCause::dummy(), param_env)
.define_opaque_types(false)
.eq(lhs, rhs)
.map(|InferOk { value: (), obligations }| {
obligations.into_iter().map(|o| o.into()).collect()
})
.map_err(|e| {
debug!(?e, "failed to equate");
NoSolution
})
}
}
#[instrument(level = "debug", skip(infcx), ret)]
fn take_external_constraints<'tcx>(
infcx: &InferCtxt<'tcx>,
) -> Result<ExternalConstraints<'tcx>, NoSolution> {
let region_obligations = infcx.take_registered_region_obligations();
let opaque_types = infcx.take_opaque_types_for_query_response();
Ok(ExternalConstraints {
// FIXME: Now that's definitely wrong :)
//
// Should also do the leak check here I think
regions: drop(region_obligations),
opaque_types,
})
}

View File

@ -19,27 +19,23 @@
use std::mem;
use rustc_infer::infer::canonical::{Canonical, CanonicalVarKind, CanonicalVarValues};
use rustc_infer::infer::canonical::{OriginalQueryValues, QueryRegionConstraints, QueryResponse};
use rustc_infer::infer::{InferCtxt, InferOk, TyCtxtInferExt};
use rustc_infer::traits::query::NoSolution;
use rustc_infer::traits::Obligation;
use rustc_middle::infer::canonical::Certainty as OldCertainty;
use rustc_middle::infer::canonical::{Canonical, CanonicalVarValues};
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_middle::ty::{RegionOutlivesPredicate, ToPredicate, TypeOutlivesPredicate};
use rustc_span::DUMMY_SP;
use crate::traits::ObligationCause;
use self::cache::response_no_constraints;
use self::infcx_ext::InferCtxtExt;
mod assembly;
mod cache;
mod fulfill;
mod infcx_ext;
mod overflow;
mod project_goals;
mod search_graph;
mod trait_goals;
pub use fulfill::FulfillmentCtxt;
@ -146,45 +142,42 @@ pub trait TyCtxtExt<'tcx> {
impl<'tcx> TyCtxtExt<'tcx> for TyCtxt<'tcx> {
fn evaluate_goal(self, goal: CanonicalGoal<'tcx>) -> QueryResult<'tcx> {
let mut cx = EvalCtxt::new(self);
cx.evaluate_canonical_goal(goal)
let mut search_graph = search_graph::SearchGraph::new(self);
EvalCtxt::evaluate_canonical_goal(self, &mut search_graph, goal)
}
}
struct EvalCtxt<'tcx> {
tcx: TyCtxt<'tcx>,
struct EvalCtxt<'a, 'tcx> {
infcx: &'a InferCtxt<'tcx>,
var_values: CanonicalVarValues<'tcx>,
provisional_cache: cache::ProvisionalCache<'tcx>,
overflow_data: overflow::OverflowData,
search_graph: &'a mut search_graph::SearchGraph<'tcx>,
}
impl<'tcx> EvalCtxt<'tcx> {
fn new(tcx: TyCtxt<'tcx>) -> EvalCtxt<'tcx> {
EvalCtxt {
tcx,
provisional_cache: cache::ProvisionalCache::empty(),
overflow_data: overflow::OverflowData::new(tcx),
}
impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
fn tcx(&self) -> TyCtxt<'tcx> {
self.infcx.tcx
}
/// Recursively evaluates `goal`, returning whether any inference vars have
/// been constrained and the certainty of the result.
fn evaluate_goal(
&mut self,
infcx: &InferCtxt<'tcx>,
goal: Goal<'tcx, ty::Predicate<'tcx>>,
) -> Result<(bool, Certainty), NoSolution> {
let mut orig_values = OriginalQueryValues::default();
let canonical_goal = infcx.canonicalize_query(goal, &mut orig_values);
let canonical_response = self.evaluate_canonical_goal(canonical_goal)?;
Ok((
!canonical_response.value.var_values.is_identity(),
instantiate_canonical_query_response(infcx, &orig_values, canonical_response),
))
/// Creates a new evaluation context outside of the trait solver.
///
/// With this solver making a canonical response doesn't make much sense.
/// The `search_graph` for this solver has to be completely empty.
fn new_outside_solver(
infcx: &'a InferCtxt<'tcx>,
search_graph: &'a mut search_graph::SearchGraph<'tcx>,
) -> EvalCtxt<'a, 'tcx> {
assert!(search_graph.is_empty());
EvalCtxt { infcx, var_values: CanonicalVarValues::dummy(), search_graph }
}
fn evaluate_canonical_goal(&mut self, goal: CanonicalGoal<'tcx>) -> QueryResult<'tcx> {
match self.try_push_stack(goal) {
#[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> {
match search_graph.try_push_stack(tcx, canonical_goal) {
Ok(()) => {}
// Our goal is already on the stack, eager return.
Err(response) => return response,
@ -195,41 +188,61 @@ impl<'tcx> EvalCtxt<'tcx> {
//
// FIXME: Similar to `evaluate_all`, this has to check for overflow.
loop {
let result = self.compute_goal(goal);
let (ref infcx, goal, var_values) =
tcx.infer_ctxt().build_with_canonical(DUMMY_SP, &canonical_goal);
let mut ecx = EvalCtxt { infcx, var_values, search_graph };
let result = ecx.compute_goal(goal);
// FIXME: `Response` should be `Copy`
if self.try_finalize_goal(goal, result.clone()) {
if search_graph.try_finalize_goal(tcx, canonical_goal, result.clone()) {
return result;
}
}
}
fn compute_goal(&mut self, canonical_goal: CanonicalGoal<'tcx>) -> QueryResult<'tcx> {
// WARNING: We're looking at a canonical value without instantiating it here.
//
// We have to be incredibly careful to not change the order of bound variables or
// remove any. As we go from `Goal<'tcx, Predicate>` to `Goal` with the variants
// of `PredicateKind` this is the case and it is and faster than instantiating and
// recanonicalizing.
let Goal { param_env, predicate } = canonical_goal.value;
fn make_canonical_response(&self, certainty: Certainty) -> QueryResult<'tcx> {
let external_constraints = take_external_constraints(self.infcx)?;
if let Some(kind) = predicate.kind().no_bound_vars_ignoring_escaping(self.tcx) {
Ok(self.infcx.canonicalize_response(Response {
var_values: self.var_values.clone(),
external_constraints,
certainty,
}))
}
/// 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 mut orig_values = OriginalQueryValues::default();
let canonical_goal = self.infcx.canonicalize_query(goal, &mut orig_values);
let canonical_response =
EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?;
Ok((
!canonical_response.value.var_values.is_identity(),
instantiate_canonical_query_response(self.infcx, &orig_values, canonical_response),
))
}
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(
canonical_goal.unchecked_rebind(Goal { param_env, predicate }),
),
ty::PredicateKind::Clause(ty::Clause::Projection(predicate)) => self
.compute_projection_goal(
canonical_goal.unchecked_rebind(Goal { param_env, predicate }),
),
ty::PredicateKind::Clause(ty::Clause::TypeOutlives(predicate)) => self
.compute_type_outlives_goal(
canonical_goal.unchecked_rebind(Goal { param_env, predicate }),
),
ty::PredicateKind::Clause(ty::Clause::RegionOutlives(predicate)) => self
.compute_region_outlives_goal(
canonical_goal.unchecked_rebind(Goal { param_env, predicate }),
),
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 })
}
// FIXME: implement these predicates :)
ty::PredicateKind::WellFormed(_)
| ty::PredicateKind::ObjectSafe(_)
@ -239,49 +252,41 @@ impl<'tcx> EvalCtxt<'tcx> {
| ty::PredicateKind::ConstEvaluatable(_)
| ty::PredicateKind::ConstEquate(_, _)
| ty::PredicateKind::TypeWellFormedFromEnv(_)
| ty::PredicateKind::Ambiguous => {
// FIXME
response_no_constraints(self.tcx, canonical_goal, Certainty::Yes)
}
| ty::PredicateKind::Ambiguous => self.make_canonical_response(Certainty::Yes),
}
} else {
let (infcx, goal, var_values) =
self.tcx.infer_ctxt().build_with_canonical(DUMMY_SP, &canonical_goal);
let kind = infcx.replace_bound_vars_with_placeholders(goal.predicate.kind());
let goal = goal.with(self.tcx, ty::Binder::dummy(kind));
let (_, certainty) = self.evaluate_goal(&infcx, goal)?;
infcx.make_canonical_response(var_values, certainty)
let kind = self.infcx.replace_bound_vars_with_placeholders(kind);
let goal = goal.with(self.tcx(), ty::Binder::dummy(kind));
let (_, certainty) = self.evaluate_goal(goal)?;
self.make_canonical_response(certainty)
}
}
fn compute_type_outlives_goal(
&mut self,
goal: CanonicalGoal<'tcx, TypeOutlivesPredicate<'tcx>>,
_goal: Goal<'tcx, TypeOutlivesPredicate<'tcx>>,
) -> QueryResult<'tcx> {
// FIXME
response_no_constraints(self.tcx, goal, Certainty::Yes)
self.make_canonical_response(Certainty::Yes)
}
fn compute_region_outlives_goal(
&mut self,
goal: CanonicalGoal<'tcx, RegionOutlivesPredicate<'tcx>>,
_goal: Goal<'tcx, RegionOutlivesPredicate<'tcx>>,
) -> QueryResult<'tcx> {
// FIXME
response_no_constraints(self.tcx, goal, Certainty::Yes)
self.make_canonical_response(Certainty::Yes)
}
}
impl<'tcx> EvalCtxt<'tcx> {
impl<'tcx> EvalCtxt<'_, 'tcx> {
fn evaluate_all(
&mut self,
infcx: &InferCtxt<'tcx>,
mut goals: Vec<Goal<'tcx, ty::Predicate<'tcx>>>,
) -> Result<Certainty, NoSolution> {
let mut new_goals = Vec::new();
self.repeat_while_none(|this| {
let mut has_changed = Err(Certainty::Yes);
for goal in goals.drain(..) {
let (changed, certainty) = match this.evaluate_goal(infcx, goal) {
let (changed, certainty) = match this.evaluate_goal(goal) {
Ok(result) => result,
Err(NoSolution) => return Some(Err(NoSolution)),
};
@ -310,6 +315,21 @@ impl<'tcx> EvalCtxt<'tcx> {
}
}
#[instrument(level = "debug", skip(infcx), ret)]
fn take_external_constraints<'tcx>(
infcx: &InferCtxt<'tcx>,
) -> Result<ExternalConstraints<'tcx>, NoSolution> {
let region_obligations = infcx.take_registered_region_obligations();
let opaque_types = infcx.take_opaque_types_for_query_response();
Ok(ExternalConstraints {
// FIXME: Now that's definitely wrong :)
//
// Should also do the leak check here I think
regions: drop(region_obligations),
opaque_types,
})
}
fn instantiate_canonical_query_response<'tcx>(
infcx: &InferCtxt<'tcx>,
original_values: &OriginalQueryValues<'tcx>,
@ -334,3 +354,40 @@ fn instantiate_canonical_query_response<'tcx>(
assert!(obligations.is_empty());
value
}
pub(super) fn response_no_constraints<'tcx>(
tcx: TyCtxt<'tcx>,
goal: Canonical<'tcx, impl Sized>,
certainty: Certainty,
) -> QueryResult<'tcx> {
let var_values = goal
.variables
.iter()
.enumerate()
.map(|(i, info)| match info.kind {
CanonicalVarKind::Ty(_) | CanonicalVarKind::PlaceholderTy(_) => {
tcx.mk_ty(ty::Bound(ty::INNERMOST, ty::BoundVar::from_usize(i).into())).into()
}
CanonicalVarKind::Region(_) | CanonicalVarKind::PlaceholderRegion(_) => {
let br = ty::BoundRegion {
var: ty::BoundVar::from_usize(i),
kind: ty::BrAnon(i as u32, None),
};
tcx.mk_region(ty::ReLateBound(ty::INNERMOST, br)).into()
}
CanonicalVarKind::Const(_, ty) | CanonicalVarKind::PlaceholderConst(_, ty) => tcx
.mk_const(ty::ConstKind::Bound(ty::INNERMOST, ty::BoundVar::from_usize(i)), ty)
.into(),
})
.collect();
Ok(Canonical {
max_universe: goal.max_universe,
variables: goal.variables,
value: Response {
var_values: CanonicalVarValues { var_values },
external_constraints: Default::default(),
certainty,
},
})
}

View File

@ -1,38 +1,123 @@
use crate::traits::{specialization_graph, translate_substs};
use super::assembly::{self, AssemblyCtxt};
use super::{CanonicalGoal, EvalCtxt, Goal, QueryResult};
use super::assembly::{self, Candidate, CandidateSource};
use super::infcx_ext::InferCtxtExt;
use super::{Certainty, EvalCtxt, Goal, MaybeCause, QueryResult};
use rustc_errors::ErrorGuaranteed;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_infer::infer::{InferCtxt, InferOk};
use rustc_infer::infer::InferCtxt;
use rustc_infer::traits::query::NoSolution;
use rustc_infer::traits::specialization_graph::LeafDef;
use rustc_infer::traits::{ObligationCause, Reveal};
use rustc_infer::traits::Reveal;
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
use rustc_middle::ty::ProjectionPredicate;
use rustc_middle::ty::TypeVisitable;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_middle::ty::{ProjectionPredicate, TypeSuperVisitable, TypeVisitor};
use rustc_span::DUMMY_SP;
use std::iter;
use std::ops::ControlFlow;
#[allow(dead_code)] // FIXME: implement and use all variants.
#[derive(Debug, Clone, Copy)]
pub(super) enum CandidateSource {
Impl(DefId),
ParamEnv(usize),
Builtin,
}
type Candidate<'tcx> = assembly::Candidate<'tcx, ProjectionPredicate<'tcx>>;
impl<'tcx> EvalCtxt<'tcx> {
impl<'tcx> EvalCtxt<'_, 'tcx> {
pub(super) fn compute_projection_goal(
&mut self,
goal: CanonicalGoal<'tcx, ProjectionPredicate<'tcx>>,
goal: Goal<'tcx, ProjectionPredicate<'tcx>>,
) -> QueryResult<'tcx> {
let candidates = AssemblyCtxt::assemble_and_evaluate_candidates(self, goal);
self.merge_project_candidates(candidates)
// To only compute normalization ones for each projection we only
// normalize if the expected term is an unconstrained inference variable.
//
// E.g. for `<T as Trait>::Assoc = u32` we recursively compute the goal
// `exists<U> <T as Trait>::Assoc = U` and then take the resulting type for
// `U` and equate it with `u32`. This means that we don't need a separate
// projection cache in the solver.
if self.term_is_fully_unconstrained(goal) {
let candidates = self.assemble_and_evaluate_candidates(goal);
self.merge_project_candidates(candidates)
} else {
let predicate = goal.predicate;
let unconstrained_rhs = match predicate.term.unpack() {
ty::TermKind::Ty(_) => self.infcx.next_ty_infer().into(),
ty::TermKind::Const(ct) => self.infcx.next_const_infer(ct.ty()).into(),
};
let unconstrained_predicate = ty::Clause::Projection(ProjectionPredicate {
projection_ty: goal.predicate.projection_ty,
term: unconstrained_rhs,
});
let (_has_changed, normalize_certainty) =
self.evaluate_goal(goal.with(self.tcx(), unconstrained_predicate))?;
let nested_eq_goals =
self.infcx.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))
}
}
/// 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
/// and does not occur in any other part of the predicate.
fn term_is_fully_unconstrained(&self, goal: Goal<'tcx, ProjectionPredicate<'tcx>>) -> bool {
let infcx = self.infcx;
let term_is_infer = match goal.predicate.term.unpack() {
ty::TermKind::Ty(ty) => {
if let &ty::Infer(ty::TyVar(vid)) = ty.kind() {
match infcx.probe_ty_var(vid) {
Ok(value) => bug!("resolved var in query: {goal:?} {value:?}"),
Err(universe) => universe == infcx.universe(),
}
} else {
false
}
}
ty::TermKind::Const(ct) => {
if let ty::ConstKind::Infer(ty::InferConst::Var(vid)) = ct.kind() {
match self.infcx.probe_const_var(vid) {
Ok(value) => bug!("resolved var in query: {goal:?} {value:?}"),
Err(universe) => universe == infcx.universe(),
}
} else {
false
}
}
};
// Guard against `<T as Trait<?0>>::Assoc = ?0>`.
struct ContainsTerm<'tcx> {
term: ty::Term<'tcx>,
}
impl<'tcx> TypeVisitor<'tcx> for ContainsTerm<'tcx> {
type BreakTy = ();
fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
if t.needs_infer() {
if ty::Term::from(t) == self.term {
ControlFlow::BREAK
} else {
t.super_visit_with(self)
}
} else {
ControlFlow::CONTINUE
}
}
fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
if c.needs_infer() {
if ty::Term::from(c) == self.term {
ControlFlow::BREAK
} else {
c.super_visit_with(self)
}
} else {
ControlFlow::CONTINUE
}
}
}
let mut visitor = ContainsTerm { term: goal.predicate.term };
term_is_infer
&& goal.predicate.projection_ty.visit_with(&mut visitor).is_continue()
&& goal.param_env.visit_with(&mut visitor).is_continue()
}
fn merge_project_candidates(
@ -83,14 +168,13 @@ impl<'tcx> EvalCtxt<'tcx> {
match (candidate.source, other.source) {
(CandidateSource::Impl(_), _)
| (CandidateSource::ParamEnv(_), _)
| (CandidateSource::Builtin, _) => unimplemented!(),
| (CandidateSource::BuiltinImpl, _)
| (CandidateSource::AliasBound(_), _) => unimplemented!(),
}
}
}
impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
type CandidateSource = CandidateSource;
fn self_ty(self) -> Ty<'tcx> {
self.self_ty()
}
@ -104,33 +188,26 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
}
fn consider_impl_candidate(
acx: &mut AssemblyCtxt<'_, 'tcx, ProjectionPredicate<'tcx>>,
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, ProjectionPredicate<'tcx>>,
impl_def_id: DefId,
) {
let tcx = acx.cx.tcx;
) -> Result<Certainty, NoSolution> {
let tcx = ecx.tcx();
let goal_trait_ref = goal.predicate.projection_ty.trait_ref(tcx);
let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap();
let drcx = DeepRejectCtxt { treat_obligation_params: TreatParams::AsPlaceholder };
if iter::zip(goal_trait_ref.substs, impl_trait_ref.skip_binder().substs)
.any(|(goal, imp)| !drcx.generic_args_may_unify(goal, imp))
{
return;
return Err(NoSolution);
}
acx.infcx.probe(|_| {
let impl_substs = acx.infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id);
ecx.infcx.probe(|_| {
let impl_substs = ecx.infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id);
let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs);
let Ok(InferOk { obligations, .. }) = acx
.infcx
.at(&ObligationCause::dummy(), goal.param_env)
.define_opaque_types(false)
.eq(goal_trait_ref, impl_trait_ref)
.map_err(|e| debug!("failed to equate trait refs: {e:?}"))
else {
return
};
let mut nested_goals = ecx.infcx.eq(goal.param_env, goal_trait_ref, impl_trait_ref)?;
let where_clause_bounds = tcx
.predicates_of(impl_def_id)
.instantiate(tcx, impl_substs)
@ -138,17 +215,21 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
.into_iter()
.map(|pred| goal.with(tcx, pred));
let nested_goals = obligations.into_iter().map(|o| o.into()).chain(where_clause_bounds).collect();
let Ok(trait_ref_certainty) = acx.cx.evaluate_all(acx.infcx, nested_goals) else { return };
nested_goals.extend(where_clause_bounds);
let trait_ref_certainty = ecx.evaluate_all(nested_goals)?;
// In case the associated item is hidden due to specialization, we have to
// return ambiguity this would otherwise be incomplete, resulting in
// unsoundness during coherence (#105782).
let Some(assoc_def) = fetch_eligible_assoc_item_def(
acx.infcx,
ecx.infcx,
goal.param_env,
goal_trait_ref,
goal.predicate.def_id(),
impl_def_id
) else {
return
)? else {
let certainty = Certainty::Maybe(MaybeCause::Ambiguity);
return Ok(trait_ref_certainty.unify_and(certainty));
};
if !assoc_def.item.defaultness(tcx).has_value() {
@ -174,7 +255,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
impl_substs,
);
let substs = translate_substs(
acx.infcx,
ecx.infcx,
goal.param_env,
impl_def_id,
impl_substs_with_gat,
@ -185,7 +266,8 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
let is_const = matches!(tcx.def_kind(assoc_def.item.def_id), DefKind::AssocConst);
let ty = tcx.bound_type_of(assoc_def.item.def_id);
let term: ty::EarlyBinder<ty::Term<'tcx>> = if is_const {
let identity_substs = ty::InternalSubsts::identity_for_item(tcx, assoc_def.item.def_id);
let identity_substs =
ty::InternalSubsts::identity_for_item(tcx, assoc_def.item.def_id);
let did = ty::WithOptConstParam::unknown(assoc_def.item.def_id);
let kind =
ty::ConstKind::Unevaluated(ty::UnevaluatedConst::new(did, identity_substs));
@ -194,23 +276,38 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
ty.map_bound(|ty| ty.into())
};
let Ok(InferOk { obligations, .. }) = acx
// The term of our goal should be fully unconstrained, so this should never fail.
//
// It can however be ambiguous when the resolved type is a projection.
let nested_goals = ecx
.infcx
.at(&ObligationCause::dummy(), goal.param_env)
.define_opaque_types(false)
.eq(goal.predicate.term, term.subst(tcx, substs))
.map_err(|e| debug!("failed to equate trait refs: {e:?}"))
else {
return
};
.eq(goal.param_env, goal.predicate.term, term.subst(tcx, substs))
.expect("failed to unify with unconstrained term");
let rhs_certainty =
ecx.evaluate_all(nested_goals).expect("failed to unify with unconstrained term");
let nested_goals = obligations.into_iter().map(|o| o.into()).collect();
let Ok(rhs_certainty) = acx.cx.evaluate_all(acx.infcx, nested_goals) else { return };
let certainty = trait_ref_certainty.unify_and(rhs_certainty);
acx.try_insert_candidate(CandidateSource::Impl(impl_def_id), certainty);
Ok(trait_ref_certainty.unify_and(rhs_certainty))
})
}
fn consider_builtin_sized_candidate(
_ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> Result<Certainty, NoSolution> {
bug!("`Sized` does not have an associated type: {:?}", goal);
}
fn consider_assumption(
_ecx: &mut EvalCtxt<'_, 'tcx>,
_goal: Goal<'tcx, Self>,
assumption: ty::Predicate<'tcx>,
) -> Result<Certainty, NoSolution> {
if let Some(_poly_projection_pred) = assumption.to_opt_poly_projection_pred() {
unimplemented!()
} else {
Err(NoSolution)
}
}
}
/// This behavior is also implemented in `rustc_ty_utils` and in the old `project` code.
@ -224,10 +321,9 @@ fn fetch_eligible_assoc_item_def<'tcx>(
goal_trait_ref: ty::TraitRef<'tcx>,
trait_assoc_def_id: DefId,
impl_def_id: DefId,
) -> Option<LeafDef> {
) -> Result<Option<LeafDef>, NoSolution> {
let node_item = specialization_graph::assoc_def(infcx.tcx, impl_def_id, trait_assoc_def_id)
.map_err(|ErrorGuaranteed { .. }| ())
.ok()?;
.map_err(|ErrorGuaranteed { .. }| NoSolution)?;
let eligible = if node_item.is_final() {
// Non-specializable items are always projectable.
@ -246,5 +342,5 @@ fn fetch_eligible_assoc_item_def<'tcx>(
}
};
if eligible { Some(node_item) } else { None }
if eligible { Ok(Some(node_item)) } else { Ok(None) }
}

View File

@ -0,0 +1,123 @@
//! This module both handles the global cache which stores "finished" goals,
//! and the provisional cache which contains partially computed goals.
//!
//! The provisional cache is necessary when dealing with coinductive cycles.
//!
//! For more information about the provisional cache and coinduction in general,
//! check out the relevant section of the rustc-dev-guide.
//!
//! FIXME(@lcnr): Write that section, feel free to ping me if you need help here
//! before then or if I still haven't done that before January 2023.
use super::overflow::OverflowData;
use super::StackDepth;
use crate::solve::{CanonicalGoal, QueryResult};
use rustc_data_structures::fx::FxHashMap;
use rustc_index::vec::IndexVec;
use rustc_middle::ty::TyCtxt;
rustc_index::newtype_index! {
pub struct EntryIndex {}
}
#[derive(Debug, Clone)]
pub(super) struct ProvisionalEntry<'tcx> {
// In case we have a coinductive cycle, this is the
// the currently least restrictive result of this goal.
pub(super) response: QueryResult<'tcx>,
// In case of a cycle, the position of deepest stack entry involved
// in that cycle. This is monotonically decreasing in the stack as all
// elements between the current stack element in the deepest stack entry
// involved have to also be involved in that cycle.
//
// We can only move entries to the global cache once we're complete done
// with the cycle. If this entry has not been involved in a cycle,
// this is just its own depth.
pub(super) depth: StackDepth,
// The goal for this entry. Should always be equal to the corresponding goal
// in the lookup table.
pub(super) goal: CanonicalGoal<'tcx>,
}
pub(super) struct ProvisionalCache<'tcx> {
pub(super) entries: IndexVec<EntryIndex, ProvisionalEntry<'tcx>>,
// FIXME: This is only used to quickly check whether a given goal
// is in the cache. We should experiment with using something like
// `SsoHashSet` here because in most cases there are only a few entries.
pub(super) lookup_table: FxHashMap<CanonicalGoal<'tcx>, EntryIndex>,
}
impl<'tcx> ProvisionalCache<'tcx> {
pub(super) fn empty() -> ProvisionalCache<'tcx> {
ProvisionalCache { entries: Default::default(), lookup_table: Default::default() }
}
pub(super) fn is_empty(&self) -> bool {
self.entries.is_empty() && self.lookup_table.is_empty()
}
/// Adds a dependency from the current leaf to `target` in the cache
/// to prevent us from moving any goals which depend on the current leaf
/// to the global cache while we're still computing `target`.
///
/// Its important to note that `target` may already be part of a different cycle.
/// In this case we have to ensure that we also depend on all other goals
/// in the existing cycle in addition to the potentially direct cycle with `target`.
pub(super) fn add_dependency_of_leaf_on(&mut self, target: EntryIndex) {
let depth = self.entries[target].depth;
for provisional_entry in &mut self.entries.raw[target.index()..] {
// The depth of `target` is the position of the deepest goal in the stack
// on which `target` depends. That goal is the `root` of this cycle.
//
// Any entry which was added after `target` is either on the stack itself
// at which point its depth is definitely at least as high as the depth of
// `root`. If it's not on the stack itself it has to depend on a goal
// between `root` and `leaf`. If it were to depend on a goal deeper in the
// stack than `root`, then `root` would also depend on that goal, at which
// point `root` wouldn't be the root anymore.
debug_assert!(provisional_entry.depth >= depth);
provisional_entry.depth = depth;
}
// We only update entries which were added after `target` as no other
// entry should have a higher depth.
//
// Any entry which previously had a higher depth than target has to
// be between `target` and `root`. Because of this we would have updated
// its depth when calling `add_dependency_of_leaf_on(root)` for `target`.
if cfg!(debug_assertions) {
self.entries.iter().all(|e| e.depth <= depth);
}
}
pub(super) fn depth(&self, entry_index: EntryIndex) -> StackDepth {
self.entries[entry_index].depth
}
pub(super) fn provisional_result(&self, entry_index: EntryIndex) -> QueryResult<'tcx> {
self.entries[entry_index].response.clone()
}
}
pub(super) fn try_move_finished_goal_to_global_cache<'tcx>(
tcx: TyCtxt<'tcx>,
overflow_data: &mut OverflowData,
stack: &IndexVec<super::StackDepth, super::StackElem<'tcx>>,
goal: CanonicalGoal<'tcx>,
response: QueryResult<'tcx>,
) {
// We move goals to the global cache if we either did not hit an overflow or if it's
// the root goal as that will now always hit the same overflow limit.
//
// NOTE: We cannot move any non-root goals to the global cache even if their final result
// isn't impacted by the overflow as that goal still has unstable query dependencies
// because it didn't go its full depth.
//
// FIXME(@lcnr): We could still cache subtrees which are not impacted by overflow though.
// Tracking that info correctly isn't trivial, so I haven't implemented it for now.
let should_cache_globally = !overflow_data.did_overflow() || stack.is_empty();
if should_cache_globally {
// FIXME: move the provisional entry to the global cache.
let _ = (tcx, goal, response);
}
}

View File

@ -0,0 +1,178 @@
mod cache;
mod overflow;
use self::cache::ProvisionalEntry;
use super::{CanonicalGoal, Certainty, MaybeCause, QueryResult};
use cache::ProvisionalCache;
use overflow::OverflowData;
use rustc_index::vec::IndexVec;
use rustc_middle::ty::TyCtxt;
use std::collections::hash_map::Entry;
rustc_index::newtype_index! {
pub struct StackDepth {}
}
struct StackElem<'tcx> {
goal: CanonicalGoal<'tcx>,
has_been_used: bool,
}
pub(super) struct SearchGraph<'tcx> {
/// The stack of goals currently being computed.
///
/// An element is *deeper* in the stack if its index is *lower*.
stack: IndexVec<StackDepth, StackElem<'tcx>>,
overflow_data: OverflowData,
provisional_cache: ProvisionalCache<'tcx>,
}
impl<'tcx> SearchGraph<'tcx> {
pub(super) fn new(tcx: TyCtxt<'tcx>) -> SearchGraph<'tcx> {
Self {
stack: Default::default(),
overflow_data: OverflowData::new(tcx),
provisional_cache: ProvisionalCache::empty(),
}
}
pub(super) fn is_empty(&self) -> bool {
self.stack.is_empty()
&& self.provisional_cache.is_empty()
&& !self.overflow_data.did_overflow()
}
/// Tries putting the new goal on the stack, returning an error if it is already cached.
///
/// This correctly updates the provisional cache if there is a cycle.
pub(super) fn try_push_stack(
&mut self,
tcx: TyCtxt<'tcx>,
goal: CanonicalGoal<'tcx>,
) -> Result<(), QueryResult<'tcx>> {
// FIXME: start by checking the global cache
// Look at the provisional cache to check for cycles.
let cache = &mut self.provisional_cache;
match cache.lookup_table.entry(goal) {
// No entry, simply push this goal on the stack after dealing with overflow.
Entry::Vacant(v) => {
if self.overflow_data.has_overflow(self.stack.len()) {
return Err(self.deal_with_overflow(tcx, goal));
}
let depth = self.stack.push(StackElem { goal, has_been_used: false });
let response = super::response_no_constraints(tcx, goal, Certainty::Yes);
let entry_index = cache.entries.push(ProvisionalEntry { response, depth, goal });
v.insert(entry_index);
Ok(())
}
// We have a nested goal which relies on a goal `root` deeper in the stack.
//
// We first store that we may have to rerun `evaluate_goal` for `root` in case the
// provisional response is not equal to the final response. We also update the depth
// of all goals which recursively depend on our current goal to depend on `root`
// instead.
//
// Finally we can return either the provisional response for that goal if we have a
// coinductive cycle or an ambiguous result if the cycle is inductive.
Entry::Occupied(entry_index) => {
let entry_index = *entry_index.get();
cache.add_dependency_of_leaf_on(entry_index);
let stack_depth = cache.depth(entry_index);
self.stack[stack_depth].has_been_used = true;
// NOTE: The goals on the stack aren't the only goals involved in this cycle.
// We can also depend on goals which aren't part of the stack but coinductively
// depend on the stack themselves. We already checked whether all the goals
// between these goals and their root on the stack. This means that as long as
// each goal in a cycle is checked for coinductivity by itself, simply checking
// the stack is enough.
if self.stack.raw[stack_depth.index()..]
.iter()
.all(|g| g.goal.value.predicate.is_coinductive(tcx))
{
Err(cache.provisional_result(entry_index))
} else {
Err(super::response_no_constraints(
tcx,
goal,
Certainty::Maybe(MaybeCause::Overflow),
))
}
}
}
}
/// We cannot simply store the result of [super::EvalCtxt::compute_goal] as we have to deal with
/// coinductive cycles.
///
/// When we encounter a coinductive cycle, we have to prove the final result of that cycle
/// while we are still computing that result. Because of this we continously recompute the
/// cycle until the result of the previous iteration is equal to the final result, at which
/// point we are done.
///
/// This function returns `true` if we were able to finalize the goal and `false` if it has
/// updated the provisional cache and we have to recompute the current goal.
///
/// FIXME: Refer to the rustc-dev-guide entry once it exists.
pub(super) fn try_finalize_goal(
&mut self,
tcx: TyCtxt<'tcx>,
actual_goal: CanonicalGoal<'tcx>,
response: QueryResult<'tcx>,
) -> bool {
let StackElem { goal, has_been_used } = self.stack.pop().unwrap();
assert_eq!(goal, actual_goal);
let cache = &mut self.provisional_cache;
let provisional_entry_index = *cache.lookup_table.get(&goal).unwrap();
let provisional_entry = &mut cache.entries[provisional_entry_index];
let depth = provisional_entry.depth;
// Was the current goal the root of a cycle and was the provisional response
// different from the final one.
if has_been_used && provisional_entry.response != response {
// If so, update the provisional reponse for this goal...
provisional_entry.response = response;
// ...remove all entries whose result depends on this goal
// from the provisional cache...
//
// That's not completely correct, as a nested goal can also
// depend on a goal which is lower in the stack so it doesn't
// actually depend on the current goal. This should be fairly
// rare and is hopefully not relevant for performance.
#[allow(rustc::potential_query_instability)]
cache.lookup_table.retain(|_key, index| *index <= provisional_entry_index);
cache.entries.truncate(provisional_entry_index.index() + 1);
// ...and finally push our goal back on the stack and reevaluate it.
self.stack.push(StackElem { goal, has_been_used: false });
false
} else {
// If not, we're done with this goal.
//
// Check whether that this goal doesn't depend on a goal deeper on the stack
// and if so, move it and all nested goals to the global cache.
//
// Note that if any nested goal were to depend on something deeper on the stack,
// this would have also updated the depth of the current goal.
if depth == self.stack.next_index() {
for (i, entry) in cache.entries.drain_enumerated(provisional_entry_index.index()..)
{
let actual_index = cache.lookup_table.remove(&entry.goal);
debug_assert_eq!(Some(i), actual_index);
debug_assert!(entry.depth == depth);
cache::try_move_finished_goal_to_global_cache(
tcx,
&mut self.overflow_data,
&self.stack,
entry.goal,
entry.response,
);
}
}
true
}
}
}

View File

@ -3,8 +3,8 @@ use rustc_infer::traits::query::NoSolution;
use rustc_middle::ty::TyCtxt;
use rustc_session::Limit;
use super::cache::response_no_constraints;
use super::{Certainty, EvalCtxt, MaybeCause, QueryResult};
use super::SearchGraph;
use crate::solve::{response_no_constraints, Certainty, EvalCtxt, MaybeCause, QueryResult};
/// When detecting a solver overflow, we return ambiguity. Overflow can be
/// *hidden* by either a fatal error in an **AND** or a trivial success in an **OR**.
@ -50,32 +50,35 @@ impl OverflowData {
}
}
impl<'tcx> EvalCtxt<'tcx> {
pub(super) fn deal_with_overflow(
impl<'tcx> SearchGraph<'tcx> {
pub fn deal_with_overflow(
&mut self,
tcx: TyCtxt<'tcx>,
goal: Canonical<'tcx, impl Sized>,
) -> QueryResult<'tcx> {
self.overflow_data.deal_with_overflow();
response_no_constraints(self.tcx, goal, Certainty::Maybe(MaybeCause::Overflow))
response_no_constraints(tcx, goal, Certainty::Maybe(MaybeCause::Overflow))
}
}
impl<'tcx> EvalCtxt<'_, 'tcx> {
/// A `while`-loop which tracks overflow.
pub(super) fn repeat_while_none(
pub fn repeat_while_none(
&mut self,
mut loop_body: impl FnMut(&mut Self) -> Option<Result<Certainty, NoSolution>>,
) -> Result<Certainty, NoSolution> {
let start_depth = self.overflow_data.additional_depth;
let depth = self.provisional_cache.current_depth();
while !self.overflow_data.has_overflow(depth) {
let start_depth = self.search_graph.overflow_data.additional_depth;
let depth = self.search_graph.stack.len();
while !self.search_graph.overflow_data.has_overflow(depth) {
if let Some(result) = loop_body(self) {
self.overflow_data.additional_depth = start_depth;
self.search_graph.overflow_data.additional_depth = start_depth;
return result;
}
self.overflow_data.additional_depth += 1;
self.search_graph.overflow_data.additional_depth += 1;
}
self.overflow_data.additional_depth = start_depth;
self.overflow_data.deal_with_overflow();
self.search_graph.overflow_data.additional_depth = start_depth;
self.search_graph.overflow_data.deal_with_overflow();
Ok(Certainty::Maybe(MaybeCause::Overflow))
}
}

View File

@ -2,58 +2,17 @@
use std::iter;
use super::assembly::{self, AssemblyCtxt};
use super::{CanonicalGoal, EvalCtxt, Goal, QueryResult};
use super::assembly::{self, Candidate, CandidateSource};
use super::infcx_ext::InferCtxtExt;
use super::{Certainty, EvalCtxt, Goal, QueryResult};
use rustc_hir::def_id::DefId;
use rustc_infer::infer::InferOk;
use rustc_infer::traits::query::NoSolution;
use rustc_infer::traits::ObligationCause;
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
use rustc_middle::ty::TraitPredicate;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_span::DUMMY_SP;
#[allow(dead_code)] // FIXME: implement and use all variants.
#[derive(Debug, Clone, Copy)]
pub(super) enum CandidateSource {
/// Some user-defined impl with the given `DefId`.
Impl(DefId),
/// The n-th caller bound in the `param_env` of our goal.
///
/// This is pretty much always a bound from the `where`-clauses of the
/// currently checked item.
ParamEnv(usize),
/// A bound on the `self_ty` in case it is a projection or an opaque type.
///
/// # Examples
///
/// ```ignore (for syntax highlighting)
/// trait Trait {
/// type Assoc: OtherTrait;
/// }
/// ```
///
/// We know that `<Whatever as Trait>::Assoc: OtherTrait` holds by looking at
/// the bounds on `Trait::Assoc`.
AliasBound(usize),
/// A builtin implementation for some specific traits, used in cases
/// where we cannot rely an ordinary library implementations.
///
/// The most notable examples are `Sized`, `Copy` and `Clone`. This is also
/// used for the `DiscriminantKind` and `Pointee` trait, both of which have
/// an associated type.
Builtin,
/// An automatic impl for an auto trait, e.g. `Send`. These impls recursively look
/// at the constituent types of the `self_ty` to check whether the auto trait
/// is implemented for those.
AutoImpl,
}
type Candidate<'tcx> = assembly::Candidate<'tcx, TraitPredicate<'tcx>>;
impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
type CandidateSource = CandidateSource;
fn self_ty(self) -> Ty<'tcx> {
self.self_ty()
}
@ -67,55 +26,63 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
}
fn consider_impl_candidate(
acx: &mut AssemblyCtxt<'_, 'tcx, Self>,
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, TraitPredicate<'tcx>>,
impl_def_id: DefId,
) {
let tcx = acx.cx.tcx;
) -> Result<Certainty, NoSolution> {
let tcx = ecx.tcx();
let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap();
let drcx = DeepRejectCtxt { treat_obligation_params: TreatParams::AsPlaceholder };
if iter::zip(goal.predicate.trait_ref.substs, impl_trait_ref.skip_binder().substs)
.any(|(goal, imp)| !drcx.generic_args_may_unify(goal, imp))
{
return;
return Err(NoSolution);
}
acx.infcx.probe(|_| {
let impl_substs = acx.infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id);
ecx.infcx.probe(|_| {
let impl_substs = ecx.infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id);
let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs);
let Ok(InferOk { obligations, .. }) = acx
.infcx
.at(&ObligationCause::dummy(), goal.param_env)
.define_opaque_types(false)
.eq(goal.predicate.trait_ref, impl_trait_ref)
.map_err(|e| debug!("failed to equate trait refs: {e:?}"))
else {
return
};
let mut nested_goals =
ecx.infcx.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));
let nested_goals =
obligations.into_iter().map(|o| o.into()).chain(where_clause_bounds).collect();
let Ok(certainty) = acx.cx.evaluate_all(acx.infcx, nested_goals) else { return };
acx.try_insert_candidate(CandidateSource::Impl(impl_def_id), certainty);
nested_goals.extend(where_clause_bounds);
ecx.evaluate_all(nested_goals)
})
}
fn consider_builtin_sized_candidate(
_ecx: &mut EvalCtxt<'_, 'tcx>,
_goal: Goal<'tcx, Self>,
) -> Result<Certainty, NoSolution> {
unimplemented!();
}
fn consider_assumption(
_ecx: &mut EvalCtxt<'_, 'tcx>,
_goal: Goal<'tcx, Self>,
assumption: ty::Predicate<'tcx>,
) -> Result<Certainty, NoSolution> {
if let Some(_poly_trait_pred) = assumption.to_opt_poly_trait_pred() {
unimplemented!()
} else {
Err(NoSolution)
}
}
}
impl<'tcx> EvalCtxt<'tcx> {
impl<'tcx> EvalCtxt<'_, 'tcx> {
pub(super) fn compute_trait_goal(
&mut self,
goal: CanonicalGoal<'tcx, TraitPredicate<'tcx>>,
goal: Goal<'tcx, TraitPredicate<'tcx>>,
) -> QueryResult<'tcx> {
let candidates = AssemblyCtxt::assemble_and_evaluate_candidates(self, goal);
let candidates = self.assemble_and_evaluate_candidates(goal);
self.merge_trait_candidates_discard_reservation_impls(candidates)
}
@ -169,14 +136,13 @@ impl<'tcx> EvalCtxt<'tcx> {
(CandidateSource::Impl(_), _)
| (CandidateSource::ParamEnv(_), _)
| (CandidateSource::AliasBound(_), _)
| (CandidateSource::Builtin, _)
| (CandidateSource::AutoImpl, _) => unimplemented!(),
| (CandidateSource::BuiltinImpl, _) => unimplemented!(),
}
}
fn discard_reservation_impl(&self, candidate: Candidate<'tcx>) -> Candidate<'tcx> {
if let CandidateSource::Impl(def_id) = candidate.source {
if let ty::ImplPolarity::Reservation = self.tcx.impl_polarity(def_id) {
if let ty::ImplPolarity::Reservation = self.tcx().impl_polarity(def_id) {
debug!("Selected reservation impl");
// FIXME: reduce candidate to ambiguous
// FIXME: replace `var_values` with identity, yeet external constraints.