mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-26 08:44:35 +00:00
Rollup merge of #105661 - lcnr:evaluate-new, r=compiler-errors
implement the skeleton of the updated trait solver cc ```@rust-lang/initiative-trait-system-refactor``` This is mostly following the architecture discussed in the types team meetup. After discussing the desired changes for the trait solver, we encountered cyclic dependencies between them. Most notably between changing evaluate to be canonical and returning inference constraints. We cannot canonicalize evaluate without returning inference constraints due to coinductive cycles. However, caching inference constraints also relies on canonicalization. Implementing both of these changes at once in-place is not feasible. This somewhat closely mirrors the current `evaluate` implementation with the following notable differences: - it moves `project` into the core solver, allowing us to correctly deal with coinductive projections (will be required for implied bounds, perfect derive) - it changes trait solver overflow to be non-fatal (required to backcompat breakage from changes to the iteration order of nested goals, deferred projection equality, generally very useful) - it returns inference constraints and canonicalizes inputs and outputs (required for a lot things, most notably merging fulfill and evaluate, and deferred projection equality) - it is implemented to work with lazy normalization A lot of things aren't yet implemented, but the remaining FIXMEs should all be fairly self-contained and parallelizable. If the architecture looks correct and is what we want here, I would like to quickly merge this and then split the work. r? ```@compiler-errors``` / ```@rust-lang/types``` :3
This commit is contained in:
commit
fd5af8cc23
@ -151,7 +151,11 @@ impl<'tcx> InferCtxt<'tcx> {
|
||||
})
|
||||
}
|
||||
|
||||
fn take_opaque_types_for_query_response(&self) -> Vec<(Ty<'tcx>, Ty<'tcx>)> {
|
||||
/// FIXME: This method should only be used for canonical queries and therefore be private.
|
||||
///
|
||||
/// As the new solver does canonicalization slightly differently, this is also used there
|
||||
/// for now. This should hopefully change fairly soon.
|
||||
pub fn take_opaque_types_for_query_response(&self) -> Vec<(Ty<'tcx>, Ty<'tcx>)> {
|
||||
self.inner
|
||||
.borrow_mut()
|
||||
.opaque_type_storage
|
||||
|
@ -300,6 +300,16 @@ impl<'tcx, V> Canonical<'tcx, V> {
|
||||
let Canonical { max_universe, variables, value } = self;
|
||||
Canonical { max_universe, variables, value: map_op(value) }
|
||||
}
|
||||
|
||||
/// Allows you to map the `value` of a canonical while keeping the same set of
|
||||
/// bound variables.
|
||||
///
|
||||
/// **WARNING:** This function is very easy to mis-use, hence the name! See
|
||||
/// the comment of [Canonical::unchecked_map] for more details.
|
||||
pub fn unchecked_rebind<W>(self, value: W) -> Canonical<'tcx, W> {
|
||||
let Canonical { max_universe, variables, value: _ } = self;
|
||||
Canonical { max_universe, variables, value }
|
||||
}
|
||||
}
|
||||
|
||||
pub type QueryOutlivesConstraint<'tcx> = (
|
||||
|
@ -96,7 +96,7 @@ pub type CanonicalTypeOpProvePredicateGoal<'tcx> =
|
||||
pub type CanonicalTypeOpNormalizeGoal<'tcx, T> =
|
||||
Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::Normalize<T>>>;
|
||||
|
||||
#[derive(Copy, Clone, Debug, HashStable)]
|
||||
#[derive(Copy, Clone, Debug, HashStable, PartialEq, Eq)]
|
||||
pub struct NoSolution;
|
||||
|
||||
pub type Fallible<T> = Result<T, NoSolution>;
|
||||
|
@ -180,6 +180,7 @@ impl Iterator for Ancestors<'_> {
|
||||
}
|
||||
|
||||
/// Information about the most specialized definition of an associated item.
|
||||
#[derive(Debug)]
|
||||
pub struct LeafDef {
|
||||
/// The associated item described by this `LeafDef`.
|
||||
pub item: ty::AssocItem,
|
||||
|
@ -535,6 +535,17 @@ impl<'tcx> Predicate<'tcx> {
|
||||
self
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(tcx), ret)]
|
||||
pub fn is_coinductive(self, tcx: TyCtxt<'tcx>) -> bool {
|
||||
match self.kind().skip_binder() {
|
||||
ty::PredicateKind::Clause(ty::Clause::Trait(data)) => {
|
||||
tcx.trait_is_coinductive(data.def_id())
|
||||
}
|
||||
ty::PredicateKind::WellFormed(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this projection can be soundly normalized.
|
||||
///
|
||||
/// Wf predicates must not be normalized, as normalization
|
||||
@ -1018,6 +1029,24 @@ pub struct ProjectionPredicate<'tcx> {
|
||||
pub term: Term<'tcx>,
|
||||
}
|
||||
|
||||
impl<'tcx> ProjectionPredicate<'tcx> {
|
||||
pub fn self_ty(self) -> Ty<'tcx> {
|
||||
self.projection_ty.self_ty()
|
||||
}
|
||||
|
||||
pub fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> ProjectionPredicate<'tcx> {
|
||||
Self { projection_ty: self.projection_ty.with_self_ty(tcx, self_ty), ..self }
|
||||
}
|
||||
|
||||
pub fn trait_def_id(self, tcx: TyCtxt<'tcx>) -> DefId {
|
||||
self.projection_ty.trait_def_id(tcx)
|
||||
}
|
||||
|
||||
pub fn def_id(self) -> DefId {
|
||||
self.projection_ty.def_id
|
||||
}
|
||||
}
|
||||
|
||||
pub type PolyProjectionPredicate<'tcx> = Binder<'tcx, ProjectionPredicate<'tcx>>;
|
||||
|
||||
impl<'tcx> PolyProjectionPredicate<'tcx> {
|
||||
@ -1054,18 +1083,6 @@ impl<'tcx> PolyProjectionPredicate<'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> ProjectionPredicate<'tcx> {
|
||||
pub fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self {
|
||||
Self {
|
||||
projection_ty: tcx.mk_alias_ty(
|
||||
self.projection_ty.def_id,
|
||||
[self_ty.into()].into_iter().chain(self.projection_ty.substs.iter().skip(1)),
|
||||
),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToPolyTraitRef<'tcx> {
|
||||
fn to_poly_trait_ref(&self) -> PolyTraitRef<'tcx>;
|
||||
}
|
||||
|
@ -1169,7 +1169,7 @@ pub struct AliasTy<'tcx> {
|
||||
}
|
||||
|
||||
impl<'tcx> AliasTy<'tcx> {
|
||||
pub fn trait_def_id(&self, tcx: TyCtxt<'tcx>) -> DefId {
|
||||
pub fn trait_def_id(self, tcx: TyCtxt<'tcx>) -> DefId {
|
||||
match tcx.def_kind(self.def_id) {
|
||||
DefKind::AssocTy | DefKind::AssocConst => tcx.parent(self.def_id),
|
||||
DefKind::ImplTraitPlaceholder => {
|
||||
@ -1183,7 +1183,7 @@ impl<'tcx> AliasTy<'tcx> {
|
||||
/// For example, if this is a projection of `<T as StreamingIterator>::Item<'a>`,
|
||||
/// then this function would return a `T: Iterator` trait reference and `['a]` as the own substs
|
||||
pub fn trait_ref_and_own_substs(
|
||||
&self,
|
||||
self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
) -> (ty::TraitRef<'tcx>, &'tcx [ty::GenericArg<'tcx>]) {
|
||||
debug_assert!(matches!(tcx.def_kind(self.def_id), DefKind::AssocTy | DefKind::AssocConst));
|
||||
@ -1202,14 +1202,18 @@ impl<'tcx> AliasTy<'tcx> {
|
||||
/// WARNING: This will drop the substs for generic associated types
|
||||
/// consider calling [Self::trait_ref_and_own_substs] to get those
|
||||
/// as well.
|
||||
pub fn trait_ref(&self, tcx: TyCtxt<'tcx>) -> ty::TraitRef<'tcx> {
|
||||
pub fn trait_ref(self, tcx: TyCtxt<'tcx>) -> ty::TraitRef<'tcx> {
|
||||
let def_id = self.trait_def_id(tcx);
|
||||
tcx.mk_trait_ref(def_id, self.substs.truncate_to(tcx, tcx.generics_of(def_id)))
|
||||
}
|
||||
|
||||
pub fn self_ty(&self) -> Ty<'tcx> {
|
||||
pub fn self_ty(self) -> Ty<'tcx> {
|
||||
self.substs.type_at(0)
|
||||
}
|
||||
|
||||
pub fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self {
|
||||
tcx.mk_alias_ty(self.def_id, [self_ty.into()].into_iter().chain(self.substs.iter().skip(1)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, TypeFoldable, TypeVisitable, Lift)]
|
||||
|
@ -577,6 +577,10 @@ impl<T> EarlyBinder<T> {
|
||||
pub fn rebind<U>(&self, value: U) -> EarlyBinder<U> {
|
||||
EarlyBinder(value)
|
||||
}
|
||||
|
||||
pub fn skip_binder(self) -> T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EarlyBinder<Option<T>> {
|
||||
|
@ -19,6 +19,7 @@
|
||||
#![feature(let_chains)]
|
||||
#![feature(if_let_guard)]
|
||||
#![feature(never_type)]
|
||||
#![feature(result_option_inspect)]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
#![recursion_limit = "512"] // For rustdoc
|
||||
|
||||
@ -37,4 +38,5 @@ extern crate smallvec;
|
||||
pub mod autoderef;
|
||||
pub mod errors;
|
||||
pub mod infer;
|
||||
pub mod solve;
|
||||
pub mod traits;
|
||||
|
150
compiler/rustc_trait_selection/src/solve/assembly.rs
Normal file
150
compiler/rustc_trait_selection/src/solve/assembly.rs
Normal file
@ -0,0 +1,150 @@
|
||||
//! Code shared by trait and projection goals for candidate assembly.
|
||||
|
||||
use super::infcx_ext::InferCtxtExt;
|
||||
use super::{
|
||||
fixme_instantiate_canonical_query_response, CanonicalGoal, 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) result: CanonicalResponse<'tcx>,
|
||||
}
|
||||
|
||||
pub(super) trait GoalKind<'tcx>: TypeFoldable<'tcx> + Copy {
|
||||
type CandidateSource: Debug + Copy;
|
||||
|
||||
fn self_ty(self) -> Ty<'tcx>;
|
||||
|
||||
fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self;
|
||||
|
||||
fn trait_def_id(self, tcx: TyCtxt<'tcx>) -> DefId;
|
||||
|
||||
fn consider_impl_candidate(
|
||||
acx: &mut AssemblyCtxt<'_, 'tcx, Self>,
|
||||
goal: Goal<'tcx, Self>,
|
||||
impl_def_id: DefId,
|
||||
);
|
||||
}
|
||||
|
||||
/// 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(
|
||||
&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"),
|
||||
}
|
||||
}
|
||||
|
||||
/// If the self type of a goal is a projection, computing the relevant candidates is difficult.
|
||||
///
|
||||
/// 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;
|
||||
// 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
|
||||
};
|
||||
self.infcx.probe(|_| {
|
||||
let normalized_ty = self.infcx.next_ty_infer();
|
||||
let normalizes_to_goal = goal.with(
|
||||
tcx,
|
||||
ty::Binder::dummy(ty::ProjectionPredicate {
|
||||
projection_ty,
|
||||
term: normalized_ty.into(),
|
||||
}),
|
||||
);
|
||||
let normalization_certainty =
|
||||
match self.cx.evaluate_goal(&self.infcx, 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.
|
||||
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 = fixme_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),
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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),
|
||||
goal.predicate.self_ty(),
|
||||
|impl_def_id| G::consider_impl_candidate(self, goal, impl_def_id),
|
||||
);
|
||||
}
|
||||
}
|
257
compiler/rustc_trait_selection/src/solve/cache.rs
Normal file
257
compiler/rustc_trait_selection/src/solve/cache.rs
Normal file
@ -0,0 +1,257 @@
|
||||
//! 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;
|
||||
use super::{EvalCtxt, QueryResult};
|
||||
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_middle::ty::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());
|
||||
}
|
||||
|
||||
v.insert(ProvisionalEntry {
|
||||
response: fixme_response_yes_no_constraints(),
|
||||
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(fixme_response_maybe_no_constraints())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fixme_response_yes_no_constraints<'tcx>() -> QueryResult<'tcx> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn fixme_response_maybe_no_constraints<'tcx>() -> QueryResult<'tcx> {
|
||||
unimplemented!()
|
||||
}
|
92
compiler/rustc_trait_selection/src/solve/fulfill.rs
Normal file
92
compiler/rustc_trait_selection/src/solve/fulfill.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use std::mem;
|
||||
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_infer::{
|
||||
infer::InferCtxt,
|
||||
traits::{query::NoSolution, FulfillmentError, PredicateObligation, TraitEngine},
|
||||
};
|
||||
use rustc_middle::ty;
|
||||
|
||||
use super::{Certainty, EvalCtxt};
|
||||
|
||||
/// A trait engine using the new trait solver.
|
||||
///
|
||||
/// This is mostly identical to how `evaluate_all` works inside of the
|
||||
/// solver, except that the requirements are slightly different.
|
||||
///
|
||||
/// Unlike `evaluate_all` it is possible to add new obligations later on
|
||||
/// and we also have to track diagnostics information by using `Obligation`
|
||||
/// instead of `Goal`.
|
||||
///
|
||||
/// It is also likely that we want to use slightly different datastructures
|
||||
/// here as this will have to deal with far more root goals than `evaluate_all`.
|
||||
pub struct FulfillmentCtxt<'tcx> {
|
||||
obligations: Vec<PredicateObligation<'tcx>>,
|
||||
}
|
||||
|
||||
impl<'tcx> FulfillmentCtxt<'tcx> {
|
||||
pub fn new() -> FulfillmentCtxt<'tcx> {
|
||||
FulfillmentCtxt { obligations: Vec::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
|
||||
fn register_predicate_obligation(
|
||||
&mut self,
|
||||
_infcx: &InferCtxt<'tcx>,
|
||||
obligation: PredicateObligation<'tcx>,
|
||||
) {
|
||||
self.obligations.push(obligation);
|
||||
}
|
||||
|
||||
fn select_all_or_error(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<FulfillmentError<'tcx>> {
|
||||
let errors = self.select_where_possible(infcx);
|
||||
if !errors.is_empty() {
|
||||
return errors;
|
||||
}
|
||||
|
||||
if self.obligations.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
unimplemented!("ambiguous obligations")
|
||||
}
|
||||
}
|
||||
|
||||
fn select_where_possible(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<FulfillmentError<'tcx>> {
|
||||
let errors = Vec::new();
|
||||
for i in 0.. {
|
||||
if !infcx.tcx.recursion_limit().value_within_limit(i) {
|
||||
unimplemented!("overflow")
|
||||
}
|
||||
|
||||
let mut has_changed = false;
|
||||
for o in mem::take(&mut self.obligations) {
|
||||
let mut cx = EvalCtxt::new(infcx.tcx);
|
||||
let (changed, certainty) = match cx.evaluate_goal(infcx, o.clone().into()) {
|
||||
Ok(result) => result,
|
||||
Err(NoSolution) => unimplemented!("error"),
|
||||
};
|
||||
|
||||
has_changed |= changed;
|
||||
match certainty {
|
||||
Certainty::Yes => {}
|
||||
Certainty::Maybe(_) => self.obligations.push(o),
|
||||
}
|
||||
}
|
||||
|
||||
if !has_changed {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
errors
|
||||
}
|
||||
|
||||
fn pending_obligations(&self) -> Vec<PredicateObligation<'tcx>> {
|
||||
self.obligations.clone()
|
||||
}
|
||||
|
||||
fn relationships(&mut self) -> &mut FxHashMap<ty::TyVid, ty::FoundRelationships> {
|
||||
unimplemented!("Should be moved out of `TraitEngine`")
|
||||
}
|
||||
}
|
55
compiler/rustc_trait_selection/src/solve/infcx_ext.rs
Normal file
55
compiler/rustc_trait_selection/src/solve/infcx_ext.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use rustc_infer::infer::canonical::CanonicalVarValues;
|
||||
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
|
||||
use rustc_infer::infer::InferCtxt;
|
||||
use rustc_infer::traits::query::NoSolution;
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_span::DUMMY_SP;
|
||||
|
||||
use crate::solve::ExternalConstraints;
|
||||
|
||||
use super::{Certainty, QueryResult, Response};
|
||||
|
||||
/// Methods used inside of the canonical queries of the solver.
|
||||
pub(super) trait InferCtxtExt<'tcx> {
|
||||
fn next_ty_infer(&self) -> Ty<'tcx>;
|
||||
|
||||
fn make_canonical_response(
|
||||
&self,
|
||||
var_values: CanonicalVarValues<'tcx>,
|
||||
certainty: Certainty,
|
||||
) -> QueryResult<'tcx>;
|
||||
}
|
||||
|
||||
impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> {
|
||||
fn next_ty_infer(&self) -> Ty<'tcx> {
|
||||
self.next_ty_var(TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::MiscVariable,
|
||||
span: DUMMY_SP,
|
||||
})
|
||||
}
|
||||
|
||||
fn make_canonical_response(
|
||||
&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 }))
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
})
|
||||
}
|
309
compiler/rustc_trait_selection/src/solve/mod.rs
Normal file
309
compiler/rustc_trait_selection/src/solve/mod.rs
Normal file
@ -0,0 +1,309 @@
|
||||
//! The new trait solver, currently still WIP.
|
||||
//!
|
||||
//! As a user of the trait system, you can use `TyCtxt::evaluate_goal` to
|
||||
//! interact with this solver.
|
||||
//!
|
||||
//! For a high-level overview of how this solver works, check out the relevant
|
||||
//! section of the rustc-dev-guide.
|
||||
//!
|
||||
//! FIXME(@lcnr): Write that section. If you read this before then ask me
|
||||
//! about it on zulip.
|
||||
|
||||
// FIXME: Instead of using `infcx.canonicalize_query` we have to add a new routine which
|
||||
// preserves universes and creates a unique var (in the highest universe) for each
|
||||
// appearance of a region.
|
||||
|
||||
// FIXME: `CanonicalVarValues` should be interned and `Copy`.
|
||||
|
||||
// FIXME: uses of `infcx.at` need to enable deferred projection equality once that's implemented.
|
||||
|
||||
use std::mem;
|
||||
|
||||
use rustc_infer::infer::canonical::OriginalQueryValues;
|
||||
use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
|
||||
use rustc_infer::traits::query::NoSolution;
|
||||
use rustc_infer::traits::Obligation;
|
||||
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 self::infcx_ext::InferCtxtExt;
|
||||
|
||||
mod assembly;
|
||||
mod cache;
|
||||
mod fulfill;
|
||||
mod infcx_ext;
|
||||
mod overflow;
|
||||
mod project_goals;
|
||||
mod trait_goals;
|
||||
|
||||
pub use fulfill::FulfillmentCtxt;
|
||||
|
||||
/// A goal is a statement, i.e. `predicate`, we want to prove
|
||||
/// given some assumptions, i.e. `param_env`.
|
||||
///
|
||||
/// Most of the time the `param_env` contains the `where`-bounds of the function
|
||||
/// we're currently typechecking while the `predicate` is some trait bound.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, TypeFoldable, TypeVisitable)]
|
||||
pub struct Goal<'tcx, P> {
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
predicate: P,
|
||||
}
|
||||
|
||||
impl<'tcx, P> Goal<'tcx, P> {
|
||||
pub fn new(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
predicate: impl ToPredicate<'tcx, P>,
|
||||
) -> Goal<'tcx, P> {
|
||||
Goal { param_env, predicate: predicate.to_predicate(tcx) }
|
||||
}
|
||||
|
||||
/// Updates the goal to one with a different `predicate` but the same `param_env`.
|
||||
fn with<Q>(self, tcx: TyCtxt<'tcx>, predicate: impl ToPredicate<'tcx, Q>) -> Goal<'tcx, Q> {
|
||||
Goal { param_env: self.param_env, predicate: predicate.to_predicate(tcx) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx, P> From<Obligation<'tcx, P>> for Goal<'tcx, P> {
|
||||
fn from(obligation: Obligation<'tcx, P>) -> Goal<'tcx, P> {
|
||||
Goal { param_env: obligation.param_env, predicate: obligation.predicate }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, TypeFoldable, TypeVisitable)]
|
||||
pub struct Response<'tcx> {
|
||||
pub var_values: CanonicalVarValues<'tcx>,
|
||||
/// Additional constraints returned by this query.
|
||||
pub external_constraints: ExternalConstraints<'tcx>,
|
||||
pub certainty: Certainty,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, TypeFoldable, TypeVisitable)]
|
||||
pub enum Certainty {
|
||||
Yes,
|
||||
Maybe(MaybeCause),
|
||||
}
|
||||
|
||||
impl Certainty {
|
||||
/// When proving multiple goals using **AND**, e.g. nested obligations for an impl,
|
||||
/// use this function to unify the certainty of these goals
|
||||
pub fn unify_and(self, other: Certainty) -> Certainty {
|
||||
match (self, other) {
|
||||
(Certainty::Yes, Certainty::Yes) => Certainty::Yes,
|
||||
(Certainty::Yes, Certainty::Maybe(_)) => other,
|
||||
(Certainty::Maybe(_), Certainty::Yes) => self,
|
||||
(Certainty::Maybe(MaybeCause::Overflow), Certainty::Maybe(MaybeCause::Overflow)) => {
|
||||
Certainty::Maybe(MaybeCause::Overflow)
|
||||
}
|
||||
// If at least one of the goals is ambiguous, hide the overflow as the ambiguous goal
|
||||
// may still result in failure.
|
||||
(Certainty::Maybe(MaybeCause::Ambiguity), Certainty::Maybe(_))
|
||||
| (Certainty::Maybe(_), Certainty::Maybe(MaybeCause::Ambiguity)) => {
|
||||
Certainty::Maybe(MaybeCause::Ambiguity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Why we failed to evaluate a goal.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, TypeFoldable, TypeVisitable)]
|
||||
pub enum MaybeCause {
|
||||
/// We failed due to ambiguity. This ambiguity can either
|
||||
/// be a true ambiguity, i.e. there are multiple different answers,
|
||||
/// or we hit a case where we just don't bother, e.g. `?x: Trait` goals.
|
||||
Ambiguity,
|
||||
/// We gave up due to an overflow, most often by hitting the recursion limit.
|
||||
Overflow,
|
||||
}
|
||||
|
||||
/// Additional constraints returned on success.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, TypeFoldable, TypeVisitable)]
|
||||
pub struct ExternalConstraints<'tcx> {
|
||||
// FIXME: implement this.
|
||||
regions: (),
|
||||
opaque_types: Vec<(Ty<'tcx>, Ty<'tcx>)>,
|
||||
}
|
||||
|
||||
type CanonicalGoal<'tcx, T = ty::Predicate<'tcx>> = Canonical<'tcx, Goal<'tcx, T>>;
|
||||
type CanonicalResponse<'tcx> = Canonical<'tcx, Response<'tcx>>;
|
||||
/// The result of evaluating a canonical query.
|
||||
///
|
||||
/// FIXME: We use a different type than the existing canonical queries. This is because
|
||||
/// we need to add a `Certainty` for `overflow` and may want to restructure this code without
|
||||
/// having to worry about changes to currently used code. Once we've made progress on this
|
||||
/// solver, merge the two responses again.
|
||||
pub type QueryResult<'tcx> = Result<CanonicalResponse<'tcx>, NoSolution>;
|
||||
|
||||
pub trait TyCtxtExt<'tcx> {
|
||||
fn evaluate_goal(self, goal: CanonicalGoal<'tcx>) -> QueryResult<'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)
|
||||
}
|
||||
}
|
||||
|
||||
struct EvalCtxt<'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
|
||||
provisional_cache: cache::ProvisionalCache<'tcx>,
|
||||
overflow_data: overflow::OverflowData,
|
||||
}
|
||||
|
||||
impl<'tcx> EvalCtxt<'tcx> {
|
||||
fn new(tcx: TyCtxt<'tcx>) -> EvalCtxt<'tcx> {
|
||||
EvalCtxt {
|
||||
tcx,
|
||||
provisional_cache: cache::ProvisionalCache::empty(),
|
||||
overflow_data: overflow::OverflowData::new(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((
|
||||
true, // FIXME: check whether `var_values` are an identity substitution.
|
||||
fixme_instantiate_canonical_query_response(infcx, &orig_values, canonical_response),
|
||||
))
|
||||
}
|
||||
|
||||
fn evaluate_canonical_goal(&mut self, goal: CanonicalGoal<'tcx>) -> QueryResult<'tcx> {
|
||||
match self.try_push_stack(goal) {
|
||||
Ok(()) => {}
|
||||
// Our goal is already on the stack, eager return.
|
||||
Err(response) => return response,
|
||||
}
|
||||
|
||||
// We may have to repeatedly recompute the goal in case of coinductive cycles,
|
||||
// check out the `cache` module for more information.
|
||||
//
|
||||
// FIXME: Similar to `evaluate_all`, this has to check for overflow.
|
||||
loop {
|
||||
let result = self.compute_goal(goal);
|
||||
|
||||
// FIXME: `Response` should be `Copy`
|
||||
if self.try_finalize_goal(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;
|
||||
if let Some(kind) = predicate.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 }),
|
||||
),
|
||||
// FIXME: implement these predicates :)
|
||||
ty::PredicateKind::WellFormed(_)
|
||||
| ty::PredicateKind::ObjectSafe(_)
|
||||
| ty::PredicateKind::ClosureKind(_, _, _)
|
||||
| ty::PredicateKind::Subtype(_)
|
||||
| ty::PredicateKind::Coerce(_)
|
||||
| ty::PredicateKind::ConstEvaluatable(_)
|
||||
| ty::PredicateKind::ConstEquate(_, _)
|
||||
| ty::PredicateKind::TypeWellFormedFromEnv(_)
|
||||
| ty::PredicateKind::Ambiguous => unimplemented!(),
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_type_outlives_goal(
|
||||
&mut self,
|
||||
_goal: CanonicalGoal<'tcx, TypeOutlivesPredicate<'tcx>>,
|
||||
) -> QueryResult<'tcx> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn compute_region_outlives_goal(
|
||||
&mut self,
|
||||
_goal: CanonicalGoal<'tcx, RegionOutlivesPredicate<'tcx>>,
|
||||
) -> QueryResult<'tcx> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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)),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn fixme_instantiate_canonical_query_response<'tcx>(
|
||||
_: &InferCtxt<'tcx>,
|
||||
_: &OriginalQueryValues<'tcx>,
|
||||
_: CanonicalResponse<'tcx>,
|
||||
) -> Certainty {
|
||||
unimplemented!()
|
||||
}
|
80
compiler/rustc_trait_selection/src/solve/overflow.rs
Normal file
80
compiler/rustc_trait_selection/src/solve/overflow.rs
Normal file
@ -0,0 +1,80 @@
|
||||
use rustc_infer::traits::query::NoSolution;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::Limit;
|
||||
|
||||
use super::{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**.
|
||||
///
|
||||
/// This is in issue in case of exponential blowup, e.g. if each goal on the stack
|
||||
/// has multiple nested (overflowing) candidates. To deal with this, we reduce the limit
|
||||
/// used by the solver when hitting the default limit for the first time.
|
||||
///
|
||||
/// FIXME: Get tests where always using the `default_limit` results in a hang and refer
|
||||
/// to them here. We can also improve the overflow strategy if necessary.
|
||||
pub(super) struct OverflowData {
|
||||
default_limit: Limit,
|
||||
current_limit: Limit,
|
||||
/// When proving an **AND** we have to repeatedly iterate over the yet unproven goals.
|
||||
///
|
||||
/// Because of this each iteration also increases the depth in addition to the stack
|
||||
/// depth.
|
||||
additional_depth: usize,
|
||||
}
|
||||
|
||||
impl OverflowData {
|
||||
pub(super) fn new(tcx: TyCtxt<'_>) -> OverflowData {
|
||||
let default_limit = tcx.recursion_limit();
|
||||
OverflowData { default_limit, current_limit: default_limit, additional_depth: 0 }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn did_overflow(&self) -> bool {
|
||||
self.default_limit.0 != self.current_limit.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn has_overflow(&self, depth: usize) -> bool {
|
||||
self.current_limit.value_within_limit(depth + self.additional_depth)
|
||||
}
|
||||
|
||||
/// Updating the current limit when hitting overflow.
|
||||
fn deal_with_overflow(&mut self) {
|
||||
// When first hitting overflow we reduce the overflow limit
|
||||
// for all future goals to prevent hangs if there's an exponental
|
||||
// blowup.
|
||||
self.current_limit.0 = self.default_limit.0 / 8;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> EvalCtxt<'tcx> {
|
||||
pub(super) fn deal_with_overflow(&mut self) -> QueryResult<'tcx> {
|
||||
self.overflow_data.deal_with_overflow();
|
||||
fixme_response_overflow_no_constraints()
|
||||
}
|
||||
|
||||
/// A `while`-loop which tracks overflow.
|
||||
pub(super) 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) {
|
||||
if let Some(result) = loop_body(self) {
|
||||
self.overflow_data.additional_depth = start_depth;
|
||||
return result;
|
||||
}
|
||||
|
||||
self.overflow_data.additional_depth += 1;
|
||||
}
|
||||
self.overflow_data.additional_depth = start_depth;
|
||||
self.overflow_data.deal_with_overflow();
|
||||
Ok(Certainty::Maybe(MaybeCause::Overflow))
|
||||
}
|
||||
}
|
||||
|
||||
fn fixme_response_overflow_no_constraints<'tcx>() -> QueryResult<'tcx> {
|
||||
unimplemented!()
|
||||
}
|
244
compiler/rustc_trait_selection/src/solve/project_goals.rs
Normal file
244
compiler/rustc_trait_selection/src/solve/project_goals.rs
Normal file
@ -0,0 +1,244 @@
|
||||
use crate::traits::{specialization_graph, translate_substs};
|
||||
|
||||
use super::assembly::{self, AssemblyCtxt};
|
||||
use super::{CanonicalGoal, EvalCtxt, Goal, 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::traits::query::NoSolution;
|
||||
use rustc_infer::traits::specialization_graph::LeafDef;
|
||||
use rustc_infer::traits::{ObligationCause, 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_span::DUMMY_SP;
|
||||
use std::iter;
|
||||
|
||||
#[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> {
|
||||
pub(super) fn compute_projection_goal(
|
||||
&mut self,
|
||||
goal: CanonicalGoal<'tcx, ProjectionPredicate<'tcx>>,
|
||||
) -> QueryResult<'tcx> {
|
||||
let candidates = AssemblyCtxt::assemble_and_evaluate_candidates(self, goal);
|
||||
self.merge_project_candidates(candidates)
|
||||
}
|
||||
|
||||
fn merge_project_candidates(
|
||||
&mut self,
|
||||
mut candidates: Vec<Candidate<'tcx>>,
|
||||
) -> QueryResult<'tcx> {
|
||||
match candidates.len() {
|
||||
0 => return Err(NoSolution),
|
||||
1 => return Ok(candidates.pop().unwrap().result),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if candidates.len() > 1 {
|
||||
let mut i = 0;
|
||||
'outer: while i < candidates.len() {
|
||||
for j in (0..candidates.len()).filter(|&j| i != j) {
|
||||
if self.project_candidate_should_be_dropped_in_favor_of(
|
||||
&candidates[i],
|
||||
&candidates[j],
|
||||
) {
|
||||
debug!(candidate = ?candidates[i], "Dropping candidate #{}/{}", i, candidates.len());
|
||||
candidates.swap_remove(i);
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
|
||||
debug!(candidate = ?candidates[i], "Retaining candidate #{}/{}", i, candidates.len());
|
||||
// If there are *STILL* multiple candidates, give up
|
||||
// and report ambiguity.
|
||||
i += 1;
|
||||
if i > 1 {
|
||||
debug!("multiple matches, ambig");
|
||||
// FIXME: return overflow if all candidates overflow, otherwise return ambiguity.
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(candidates.pop().unwrap().result)
|
||||
}
|
||||
|
||||
fn project_candidate_should_be_dropped_in_favor_of(
|
||||
&self,
|
||||
candidate: &Candidate<'tcx>,
|
||||
other: &Candidate<'tcx>,
|
||||
) -> bool {
|
||||
// FIXME: implement this
|
||||
match (candidate.source, other.source) {
|
||||
(CandidateSource::Impl(_), _)
|
||||
| (CandidateSource::ParamEnv(_), _)
|
||||
| (CandidateSource::Builtin, _) => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
||||
type CandidateSource = CandidateSource;
|
||||
|
||||
fn self_ty(self) -> Ty<'tcx> {
|
||||
self.self_ty()
|
||||
}
|
||||
|
||||
fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self {
|
||||
self.with_self_ty(tcx, self_ty)
|
||||
}
|
||||
|
||||
fn trait_def_id(self, tcx: TyCtxt<'tcx>) -> DefId {
|
||||
self.trait_def_id(tcx)
|
||||
}
|
||||
|
||||
fn consider_impl_candidate(
|
||||
acx: &mut AssemblyCtxt<'_, 'tcx, ProjectionPredicate<'tcx>>,
|
||||
goal: Goal<'tcx, ProjectionPredicate<'tcx>>,
|
||||
impl_def_id: DefId,
|
||||
) {
|
||||
let tcx = acx.cx.tcx;
|
||||
let goal_trait_ref = goal.predicate.projection_ty.trait_ref(tcx);
|
||||
let impl_trait_ref = tcx.bound_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;
|
||||
}
|
||||
|
||||
acx.infcx.probe(|_| {
|
||||
let impl_substs = acx.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 nested_goals = obligations.into_iter().map(|o| o.into()).collect();
|
||||
let Ok(trait_ref_certainty) = acx.cx.evaluate_all(acx.infcx, nested_goals) else { return };
|
||||
|
||||
let Some(assoc_def) = fetch_eligible_assoc_item_def(
|
||||
acx.infcx,
|
||||
goal.param_env,
|
||||
goal_trait_ref,
|
||||
goal.predicate.def_id(),
|
||||
impl_def_id
|
||||
) else {
|
||||
return
|
||||
};
|
||||
|
||||
if !assoc_def.item.defaultness(tcx).has_value() {
|
||||
tcx.sess.delay_span_bug(
|
||||
tcx.def_span(assoc_def.item.def_id),
|
||||
"missing value for assoc item in impl",
|
||||
);
|
||||
}
|
||||
|
||||
// Getting the right substitutions here is complex, e.g. given:
|
||||
// - a goal `<Vec<u32> as Trait<i32>>::Assoc<u64>`
|
||||
// - the applicable impl `impl<T> Trait<i32> for Vec<T>`
|
||||
// - and the impl which defines `Assoc` being `impl<T, U> Trait<U> for Vec<T>`
|
||||
//
|
||||
// We first rebase the goal substs onto the impl, going from `[Vec<u32>, i32, u64]`
|
||||
// to `[u32, u64]`.
|
||||
//
|
||||
// And then map these substs to the substs of the defining impl of `Assoc`, going
|
||||
// from `[u32, u64]` to `[u32, i32, u64]`.
|
||||
let impl_substs_with_gat = goal.predicate.projection_ty.substs.rebase_onto(
|
||||
tcx,
|
||||
goal_trait_ref.def_id,
|
||||
impl_trait_ref.substs,
|
||||
);
|
||||
let substs = translate_substs(
|
||||
acx.infcx,
|
||||
goal.param_env,
|
||||
impl_def_id,
|
||||
impl_substs_with_gat,
|
||||
assoc_def.defining_node,
|
||||
);
|
||||
|
||||
// Finally we construct the actual value of the associated type.
|
||||
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 did = ty::WithOptConstParam::unknown(assoc_def.item.def_id);
|
||||
let kind =
|
||||
ty::ConstKind::Unevaluated(ty::UnevaluatedConst::new(did, identity_substs));
|
||||
ty.map_bound(|ty| tcx.mk_const(kind, ty).into())
|
||||
} else {
|
||||
ty.map_bound(|ty| ty.into())
|
||||
};
|
||||
|
||||
let Ok(InferOk { obligations, .. }) = acx
|
||||
.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
|
||||
};
|
||||
|
||||
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);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// This behavior is also implemented in `rustc_ty_utils` and in the old `project` code.
|
||||
///
|
||||
/// FIXME: We should merge these 3 implementations as it's likely that they otherwise
|
||||
/// diverge.
|
||||
#[instrument(level = "debug", skip(infcx, param_env), ret)]
|
||||
fn fetch_eligible_assoc_item_def<'tcx>(
|
||||
infcx: &InferCtxt<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
goal_trait_ref: ty::TraitRef<'tcx>,
|
||||
trait_assoc_def_id: DefId,
|
||||
impl_def_id: DefId,
|
||||
) -> Option<LeafDef> {
|
||||
let node_item = specialization_graph::assoc_def(infcx.tcx, impl_def_id, trait_assoc_def_id)
|
||||
.map_err(|ErrorGuaranteed { .. }| ())
|
||||
.ok()?;
|
||||
|
||||
let eligible = if node_item.is_final() {
|
||||
// Non-specializable items are always projectable.
|
||||
true
|
||||
} else {
|
||||
// Only reveal a specializable default if we're past type-checking
|
||||
// and the obligation is monomorphic, otherwise passes such as
|
||||
// transmute checking and polymorphic MIR optimizations could
|
||||
// get a result which isn't correct for all monomorphizations.
|
||||
if param_env.reveal() == Reveal::All {
|
||||
let poly_trait_ref = infcx.resolve_vars_if_possible(goal_trait_ref);
|
||||
!poly_trait_ref.still_further_specializable()
|
||||
} else {
|
||||
debug!(?node_item.item.def_id, "not eligible due to default");
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if eligible { Some(node_item) } else { None }
|
||||
}
|
180
compiler/rustc_trait_selection/src/solve/trait_goals.rs
Normal file
180
compiler/rustc_trait_selection/src/solve/trait_goals.rs
Normal file
@ -0,0 +1,180 @@
|
||||
//! Dealing with trait goals, i.e. `T: Trait<'a, U>`.
|
||||
|
||||
use std::iter;
|
||||
|
||||
use super::assembly::{self, AssemblyCtxt};
|
||||
use super::{CanonicalGoal, 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()
|
||||
}
|
||||
|
||||
fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self {
|
||||
self.with_self_ty(tcx, self_ty)
|
||||
}
|
||||
|
||||
fn trait_def_id(self, _: TyCtxt<'tcx>) -> DefId {
|
||||
self.def_id()
|
||||
}
|
||||
|
||||
fn consider_impl_candidate(
|
||||
acx: &mut AssemblyCtxt<'_, 'tcx, Self>,
|
||||
goal: Goal<'tcx, TraitPredicate<'tcx>>,
|
||||
impl_def_id: DefId,
|
||||
) {
|
||||
let impl_trait_ref = acx.cx.tcx.bound_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;
|
||||
}
|
||||
|
||||
acx.infcx.probe(|_| {
|
||||
let impl_substs = acx.infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id);
|
||||
let impl_trait_ref = impl_trait_ref.subst(acx.cx.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 nested_goals = obligations.into_iter().map(|o| o.into()).collect();
|
||||
|
||||
let Ok(certainty) = acx.cx.evaluate_all(acx.infcx, nested_goals) else { return };
|
||||
acx.try_insert_candidate(CandidateSource::Impl(impl_def_id), certainty);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> EvalCtxt<'tcx> {
|
||||
pub(super) fn compute_trait_goal(
|
||||
&mut self,
|
||||
goal: CanonicalGoal<'tcx, TraitPredicate<'tcx>>,
|
||||
) -> QueryResult<'tcx> {
|
||||
let candidates = AssemblyCtxt::assemble_and_evaluate_candidates(self, goal);
|
||||
self.merge_trait_candidates_discard_reservation_impls(candidates)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self), ret)]
|
||||
pub(super) fn merge_trait_candidates_discard_reservation_impls(
|
||||
&mut self,
|
||||
mut candidates: Vec<Candidate<'tcx>>,
|
||||
) -> QueryResult<'tcx> {
|
||||
match candidates.len() {
|
||||
0 => return Err(NoSolution),
|
||||
1 => return Ok(self.discard_reservation_impl(candidates.pop().unwrap()).result),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if candidates.len() > 1 {
|
||||
let mut i = 0;
|
||||
'outer: while i < candidates.len() {
|
||||
for j in (0..candidates.len()).filter(|&j| i != j) {
|
||||
if self.trait_candidate_should_be_dropped_in_favor_of(
|
||||
&candidates[i],
|
||||
&candidates[j],
|
||||
) {
|
||||
debug!(candidate = ?candidates[i], "Dropping candidate #{}/{}", i, candidates.len());
|
||||
candidates.swap_remove(i);
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
|
||||
debug!(candidate = ?candidates[i], "Retaining candidate #{}/{}", i, candidates.len());
|
||||
// If there are *STILL* multiple candidates, give up
|
||||
// and report ambiguity.
|
||||
i += 1;
|
||||
if i > 1 {
|
||||
debug!("multiple matches, ambig");
|
||||
// FIXME: return overflow if all candidates overflow, otherwise return ambiguity.
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self.discard_reservation_impl(candidates.pop().unwrap()).result)
|
||||
}
|
||||
|
||||
fn trait_candidate_should_be_dropped_in_favor_of(
|
||||
&self,
|
||||
candidate: &Candidate<'tcx>,
|
||||
other: &Candidate<'tcx>,
|
||||
) -> bool {
|
||||
// FIXME: implement this
|
||||
match (candidate.source, other.source) {
|
||||
(CandidateSource::Impl(_), _)
|
||||
| (CandidateSource::ParamEnv(_), _)
|
||||
| (CandidateSource::AliasBound(_), _)
|
||||
| (CandidateSource::Builtin, _)
|
||||
| (CandidateSource::AutoImpl, _) => 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) {
|
||||
debug!("Selected reservation impl");
|
||||
// FIXME: reduce candidate to ambiguous
|
||||
// FIXME: replace `var_values` with identity, yeet external constraints.
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
candidate
|
||||
}
|
||||
}
|
@ -25,7 +25,6 @@ use rustc_data_structures::sso::SsoHashSet;
|
||||
use rustc_data_structures::stack::ensure_sufficient_stack;
|
||||
use rustc_errors::ErrorGuaranteed;
|
||||
use rustc_hir::def::DefKind;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::lang_items::LangItem;
|
||||
use rustc_infer::infer::at::At;
|
||||
use rustc_infer::infer::resolve::OpportunisticRegionResolver;
|
||||
@ -1553,7 +1552,7 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
|
||||
// NOTE: This should be kept in sync with the similar code in
|
||||
// `rustc_ty_utils::instance::resolve_associated_item()`.
|
||||
let node_item =
|
||||
assoc_def(selcx, impl_data.impl_def_id, obligation.predicate.def_id)
|
||||
specialization_graph::assoc_def(selcx.tcx(), impl_data.impl_def_id, obligation.predicate.def_id)
|
||||
.map_err(|ErrorGuaranteed { .. }| ())?;
|
||||
|
||||
if node_item.is_final() {
|
||||
@ -2113,7 +2112,7 @@ fn confirm_impl_candidate<'cx, 'tcx>(
|
||||
let trait_def_id = tcx.trait_id_of_impl(impl_def_id).unwrap();
|
||||
|
||||
let param_env = obligation.param_env;
|
||||
let Ok(assoc_ty) = assoc_def(selcx, impl_def_id, assoc_item_id) else {
|
||||
let Ok(assoc_ty) = specialization_graph::assoc_def(tcx, impl_def_id, assoc_item_id) else {
|
||||
return Progress { term: tcx.ty_error().into(), obligations: nested };
|
||||
};
|
||||
|
||||
@ -2210,7 +2209,7 @@ fn confirm_impl_trait_in_trait_candidate<'tcx>(
|
||||
let mut obligations = data.nested;
|
||||
|
||||
let trait_fn_def_id = tcx.impl_trait_in_trait_parent(obligation.predicate.def_id);
|
||||
let Ok(leaf_def) = assoc_def(selcx, data.impl_def_id, trait_fn_def_id) else {
|
||||
let Ok(leaf_def) = specialization_graph::assoc_def(tcx, data.impl_def_id, trait_fn_def_id) else {
|
||||
return Progress { term: tcx.ty_error().into(), obligations };
|
||||
};
|
||||
if !leaf_def.item.defaultness(tcx).has_value() {
|
||||
@ -2347,58 +2346,6 @@ fn assoc_ty_own_obligations<'cx, 'tcx>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Locate the definition of an associated type in the specialization hierarchy,
|
||||
/// starting from the given impl.
|
||||
///
|
||||
/// Based on the "projection mode", this lookup may in fact only examine the
|
||||
/// topmost impl. See the comments for `Reveal` for more details.
|
||||
fn assoc_def(
|
||||
selcx: &SelectionContext<'_, '_>,
|
||||
impl_def_id: DefId,
|
||||
assoc_def_id: DefId,
|
||||
) -> Result<specialization_graph::LeafDef, ErrorGuaranteed> {
|
||||
let tcx = selcx.tcx();
|
||||
let trait_def_id = tcx.impl_trait_ref(impl_def_id).unwrap().def_id;
|
||||
let trait_def = tcx.trait_def(trait_def_id);
|
||||
|
||||
// This function may be called while we are still building the
|
||||
// specialization graph that is queried below (via TraitDef::ancestors()),
|
||||
// so, in order to avoid unnecessary infinite recursion, we manually look
|
||||
// for the associated item at the given impl.
|
||||
// If there is no such item in that impl, this function will fail with a
|
||||
// cycle error if the specialization graph is currently being built.
|
||||
if let Some(&impl_item_id) = tcx.impl_item_implementor_ids(impl_def_id).get(&assoc_def_id) {
|
||||
let item = tcx.associated_item(impl_item_id);
|
||||
let impl_node = specialization_graph::Node::Impl(impl_def_id);
|
||||
return Ok(specialization_graph::LeafDef {
|
||||
item: *item,
|
||||
defining_node: impl_node,
|
||||
finalizing_node: if item.defaultness(tcx).is_default() {
|
||||
None
|
||||
} else {
|
||||
Some(impl_node)
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let ancestors = trait_def.ancestors(tcx, impl_def_id)?;
|
||||
if let Some(assoc_item) = ancestors.leaf_def(tcx, assoc_def_id) {
|
||||
Ok(assoc_item)
|
||||
} else {
|
||||
// This is saying that neither the trait nor
|
||||
// the impl contain a definition for this
|
||||
// associated type. Normally this situation
|
||||
// could only arise through a compiler bug --
|
||||
// if the user wrote a bad item name, it
|
||||
// should have failed in astconv.
|
||||
bug!(
|
||||
"No associated type `{}` for {}",
|
||||
tcx.item_name(assoc_def_id),
|
||||
tcx.def_path_str(impl_def_id)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait ProjectionCacheKeyExt<'cx, 'tcx>: Sized {
|
||||
fn from_poly_projection_predicate(
|
||||
selcx: &mut SelectionContext<'cx, 'tcx>,
|
||||
|
@ -1171,19 +1171,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
where
|
||||
I: Iterator<Item = ty::Predicate<'tcx>>,
|
||||
{
|
||||
cycle.all(|predicate| self.coinductive_predicate(predicate))
|
||||
}
|
||||
|
||||
fn coinductive_predicate(&self, predicate: ty::Predicate<'tcx>) -> bool {
|
||||
let result = match predicate.kind().skip_binder() {
|
||||
ty::PredicateKind::Clause(ty::Clause::Trait(ref data)) => {
|
||||
self.tcx().trait_is_coinductive(data.def_id())
|
||||
}
|
||||
ty::PredicateKind::WellFormed(_) => true,
|
||||
_ => false,
|
||||
};
|
||||
debug!(?predicate, ?result, "coinductive_predicate");
|
||||
result
|
||||
cycle.all(|predicate| predicate.is_coinductive(self.tcx()))
|
||||
}
|
||||
|
||||
/// Further evaluates `candidate` to decide whether all type parameters match and whether nested
|
||||
|
@ -1,6 +1,7 @@
|
||||
use super::OverlapError;
|
||||
|
||||
use crate::traits;
|
||||
use rustc_errors::ErrorGuaranteed;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_middle::ty::fast_reject::{self, SimplifiedType, TreatParams};
|
||||
use rustc_middle::ty::{self, TyCtxt, TypeVisitable};
|
||||
@ -379,3 +380,51 @@ impl<'tcx> GraphExt<'tcx> for Graph {
|
||||
self.children.entry(parent).or_default().insert_blindly(tcx, child);
|
||||
}
|
||||
}
|
||||
|
||||
/// Locate the definition of an associated type in the specialization hierarchy,
|
||||
/// starting from the given impl.
|
||||
pub(crate) fn assoc_def(
|
||||
tcx: TyCtxt<'_>,
|
||||
impl_def_id: DefId,
|
||||
assoc_def_id: DefId,
|
||||
) -> Result<LeafDef, ErrorGuaranteed> {
|
||||
let trait_def_id = tcx.impl_trait_ref(impl_def_id).unwrap().def_id;
|
||||
let trait_def = tcx.trait_def(trait_def_id);
|
||||
|
||||
// This function may be called while we are still building the
|
||||
// specialization graph that is queried below (via TraitDef::ancestors()),
|
||||
// so, in order to avoid unnecessary infinite recursion, we manually look
|
||||
// for the associated item at the given impl.
|
||||
// If there is no such item in that impl, this function will fail with a
|
||||
// cycle error if the specialization graph is currently being built.
|
||||
if let Some(&impl_item_id) = tcx.impl_item_implementor_ids(impl_def_id).get(&assoc_def_id) {
|
||||
let &item = tcx.associated_item(impl_item_id);
|
||||
let impl_node = Node::Impl(impl_def_id);
|
||||
return Ok(LeafDef {
|
||||
item,
|
||||
defining_node: impl_node,
|
||||
finalizing_node: if item.defaultness(tcx).is_default() {
|
||||
None
|
||||
} else {
|
||||
Some(impl_node)
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let ancestors = trait_def.ancestors(tcx, impl_def_id)?;
|
||||
if let Some(assoc_item) = ancestors.leaf_def(tcx, assoc_def_id) {
|
||||
Ok(assoc_item)
|
||||
} else {
|
||||
// This is saying that neither the trait nor
|
||||
// the impl contain a definition for this
|
||||
// associated type. Normally this situation
|
||||
// could only arise through a compiler bug --
|
||||
// if the user wrote a bad item name, it
|
||||
// should have failed in astconv.
|
||||
bug!(
|
||||
"No associated type `{}` for {}",
|
||||
tcx.item_name(assoc_def_id),
|
||||
tcx.def_path_str(impl_def_id)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
// known-bug
|
||||
|
||||
// This should compile but fails with the current solver.
|
||||
//
|
||||
// This checks that the new solver uses `Ambiguous` when hitting the
|
||||
// inductive cycle here when proving `exists<^0, ^1> (): Trait<^0, ^1>`
|
||||
// which requires proving `Trait<?1, ?0>` but that has the same
|
||||
// canonical representation.
|
||||
trait Trait<T, U> {}
|
||||
|
||||
impl<T, U> Trait<T, U> for ()
|
||||
where
|
||||
(): Trait<U, T>,
|
||||
T: OtherTrait,
|
||||
{}
|
||||
|
||||
trait OtherTrait {}
|
||||
impl OtherTrait for u32 {}
|
||||
|
||||
fn require_trait<T, U>()
|
||||
where
|
||||
(): Trait<T, U>
|
||||
{}
|
||||
|
||||
fn main() {
|
||||
require_trait::<_, _>();
|
||||
//~^ ERROR overflow evaluating
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
error[E0275]: overflow evaluating the requirement `_: Sized`
|
||||
--> $DIR/inductive-canonical-cycle.rs:26:5
|
||||
|
|
||||
LL | require_trait::<_, _>();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`inductive_canonical_cycle`)
|
||||
note: required for `()` to implement `Trait<_, _>`
|
||||
--> $DIR/inductive-canonical-cycle.rs:11:12
|
||||
|
|
||||
LL | impl<T, U> Trait<T, U> for ()
|
||||
| ^^^^^^^^^^^ ^^
|
||||
= note: 128 redundant requirements hidden
|
||||
= note: required for `()` to implement `Trait<_, _>`
|
||||
note: required by a bound in `require_trait`
|
||||
--> $DIR/inductive-canonical-cycle.rs:22:9
|
||||
|
|
||||
LL | fn require_trait<T, U>()
|
||||
| ------------- required by a bound in this
|
||||
LL | where
|
||||
LL | (): Trait<T, U>
|
||||
| ^^^^^^^^^^^ required by this bound in `require_trait`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0275`.
|
@ -342,6 +342,10 @@ cc = ["@BoxyUwU"]
|
||||
message = "Some changes occured in `rustc_ty_utils::consts.rs`"
|
||||
cc = ["@BoxyUwU"]
|
||||
|
||||
[mentions."compiler/rustc_trait_selection/src/solve]
|
||||
message = "Some changes occurred to the core trait solver"
|
||||
cc = ["@lcnr"]
|
||||
|
||||
[mentions."compiler/rustc_trait_selection/src/traits/engine.rs"]
|
||||
message = """
|
||||
Some changes occurred in engine.rs, potentially modifying the public API \
|
||||
|
Loading…
Reference in New Issue
Block a user