diff options
| author | bors <bors@rust-lang.org> | 2022-12-24 09:41:11 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2022-12-24 09:41:11 +0000 |
| commit | d23554fae855d884761d549cd6ee6537450b0f3c (patch) | |
| tree | b44bd3e79fa5019f0e8648737e71c432bc784188 /compiler/rustc_trait_selection | |
| parent | 245357f61939d2b6d15f8c6b15f7026396f95871 (diff) | |
| parent | e52e0d855799fe651922be4a038fe84fe9009c72 (diff) | |
| download | rust-d23554fae855d884761d549cd6ee6537450b0f3c.tar.gz rust-d23554fae855d884761d549cd6ee6537450b0f3c.zip | |
Auto merge of #2738 - RalfJung:rustup, r=RalfJung
Rustup
Diffstat (limited to 'compiler/rustc_trait_selection')
21 files changed, 1509 insertions, 159 deletions
diff --git a/compiler/rustc_trait_selection/src/lib.rs b/compiler/rustc_trait_selection/src/lib.rs index 975ff31a607..a30d1df4ede 100644 --- a/compiler/rustc_trait_selection/src/lib.rs +++ b/compiler/rustc_trait_selection/src/lib.rs @@ -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; diff --git a/compiler/rustc_trait_selection/src/solve/assembly.rs b/compiler/rustc_trait_selection/src/solve/assembly.rs new file mode 100644 index 00000000000..e9ddad11ff2 --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/assembly.rs @@ -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), + ); + } +} diff --git a/compiler/rustc_trait_selection/src/solve/cache.rs b/compiler/rustc_trait_selection/src/solve/cache.rs new file mode 100644 index 00000000000..993b7989066 --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/cache.rs @@ -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!() +} diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs new file mode 100644 index 00000000000..80115d78d88 --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -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`") + } +} diff --git a/compiler/rustc_trait_selection/src/solve/infcx_ext.rs b/compiler/rustc_trait_selection/src/solve/infcx_ext.rs new file mode 100644 index 00000000000..436f4eea662 --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/infcx_ext.rs @@ -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, + }) +} diff --git a/compiler/rustc_trait_selection/src/solve/mod.rs b/compiler/rustc_trait_selection/src/solve/mod.rs new file mode 100644 index 00000000000..7f5e3208f4e --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/mod.rs @@ -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!() +} diff --git a/compiler/rustc_trait_selection/src/solve/overflow.rs b/compiler/rustc_trait_selection/src/solve/overflow.rs new file mode 100644 index 00000000000..8d73a83aec9 --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/overflow.rs @@ -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!() +} diff --git a/compiler/rustc_trait_selection/src/solve/project_goals.rs b/compiler/rustc_trait_selection/src/solve/project_goals.rs new file mode 100644 index 00000000000..b50f42c4d94 --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/project_goals.rs @@ -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 } +} diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals.rs b/compiler/rustc_trait_selection/src/solve/trait_goals.rs new file mode 100644 index 00000000000..10b45a77dab --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/trait_goals.rs @@ -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 + } +} diff --git a/compiler/rustc_trait_selection/src/traits/auto_trait.rs b/compiler/rustc_trait_selection/src/traits/auto_trait.rs index aef2f8ff991..948632ccc6c 100644 --- a/compiler/rustc_trait_selection/src/traits/auto_trait.rs +++ b/compiler/rustc_trait_selection/src/traits/auto_trait.rs @@ -159,13 +159,12 @@ impl<'tcx> AutoTraitFinder<'tcx> { orig_env, orig_env, &mut fresh_preds, - false, ) else { return AutoTraitResult::NegativeImpl; }; let (full_env, full_user_env) = self - .evaluate_predicates(&infcx, trait_did, ty, new_env, user_env, &mut fresh_preds, true) + .evaluate_predicates(&infcx, trait_did, ty, new_env, user_env, &mut fresh_preds) .unwrap_or_else(|| { panic!("Failed to fully process: {:?} {:?} {:?}", ty, trait_did, orig_env) }); @@ -247,7 +246,6 @@ impl<'tcx> AutoTraitFinder<'tcx> { param_env: ty::ParamEnv<'tcx>, user_env: ty::ParamEnv<'tcx>, fresh_preds: &mut FxHashSet<ty::Predicate<'tcx>>, - only_projections: bool, ) -> Option<(ty::ParamEnv<'tcx>, ty::ParamEnv<'tcx>)> { let tcx = infcx.tcx; @@ -322,7 +320,6 @@ impl<'tcx> AutoTraitFinder<'tcx> { fresh_preds, &mut predicates, &mut select, - only_projections, ) { return None; } @@ -600,7 +597,6 @@ impl<'tcx> AutoTraitFinder<'tcx> { fresh_preds: &mut FxHashSet<ty::Predicate<'tcx>>, predicates: &mut VecDeque<ty::PolyTraitPredicate<'tcx>>, selcx: &mut SelectionContext<'_, 'tcx>, - only_projections: bool, ) -> bool { let dummy_cause = ObligationCause::dummy(); @@ -744,7 +740,6 @@ impl<'tcx> AutoTraitFinder<'tcx> { fresh_preds, predicates, selcx, - only_projections, ) { return false; } diff --git a/compiler/rustc_trait_selection/src/traits/coherence.rs b/compiler/rustc_trait_selection/src/traits/coherence.rs index 7c569621cfe..26757965c95 100644 --- a/compiler/rustc_trait_selection/src/traits/coherence.rs +++ b/compiler/rustc_trait_selection/src/traits/coherence.rs @@ -66,13 +66,13 @@ pub fn add_placeholder_note(err: &mut Diagnostic) { /// with a suitably-freshened `ImplHeader` with those types /// substituted. Otherwise, returns `None`. #[instrument(skip(tcx, skip_leak_check), level = "debug")] -pub fn overlapping_impls<'tcx>( - tcx: TyCtxt<'tcx>, +pub fn overlapping_impls( + tcx: TyCtxt<'_>, impl1_def_id: DefId, impl2_def_id: DefId, skip_leak_check: SkipLeakCheck, overlap_mode: OverlapMode, -) -> Option<OverlapResult<'tcx>> { +) -> Option<OverlapResult<'_>> { // Before doing expensive operations like entering an inference context, do // a quick check via fast_reject to tell if the impl headers could possibly // unify. @@ -283,7 +283,7 @@ fn implicit_negative<'cx, 'tcx>( /// Given impl1 and impl2 check if both impls are never satisfied by a common type (including /// where-clauses) If so, return true, they are disjoint and false otherwise. -fn negative_impl<'tcx>(tcx: TyCtxt<'tcx>, impl1_def_id: DefId, impl2_def_id: DefId) -> bool { +fn negative_impl(tcx: TyCtxt<'_>, impl1_def_id: DefId, impl2_def_id: DefId) -> bool { debug!("negative_impl(impl1_def_id={:?}, impl2_def_id={:?})", impl1_def_id, impl2_def_id); // Create an infcx, taking the predicates of impl1 as assumptions: diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs index 7c9fde27420..f8efe9bfa9f 100644 --- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs +++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs @@ -138,10 +138,10 @@ pub fn is_const_evaluatable<'tcx>( } else if uv.has_non_region_param() { NotConstEvaluatable::MentionsParam } else { - let guar = infcx.tcx.sess.delay_span_bug( - span, - format!("Missing value for constant, but no error reported?"), - ); + let guar = infcx + .tcx + .sess + .delay_span_bug(span, "Missing value for constant, but no error reported?"); NotConstEvaluatable::Error(guar) }; diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/method_chain.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/method_chain.rs index cb373d65772..27c207528c7 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/method_chain.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/method_chain.rs @@ -14,21 +14,27 @@ impl<'a, 'tcx> TypeRelation<'tcx> for CollectAllMismatches<'a, 'tcx> { fn tag(&self) -> &'static str { "CollectAllMismatches" } + fn tcx(&self) -> TyCtxt<'tcx> { self.infcx.tcx } + fn intercrate(&self) -> bool { false } + fn param_env(&self) -> ty::ParamEnv<'tcx> { self.param_env } + fn a_is_expected(&self) -> bool { true - } // irrelevant + } + fn mark_ambiguous(&mut self) { bug!() } + fn relate_with_variance<T: Relate<'tcx>>( &mut self, _: ty::Variance, @@ -38,6 +44,7 @@ impl<'a, 'tcx> TypeRelation<'tcx> for CollectAllMismatches<'a, 'tcx> { ) -> RelateResult<'tcx, T> { self.relate(a, b) } + fn regions( &mut self, a: ty::Region<'tcx>, @@ -45,15 +52,20 @@ impl<'a, 'tcx> TypeRelation<'tcx> for CollectAllMismatches<'a, 'tcx> { ) -> RelateResult<'tcx, ty::Region<'tcx>> { Ok(a) } + fn tys(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { - if a == b || matches!(a.kind(), ty::Infer(_)) || matches!(b.kind(), ty::Infer(_)) { - return Ok(a); - } - relate::super_relate_tys(self, a, b).or_else(|e| { - self.errors.push(e); - Ok(a) + self.infcx.probe(|_| { + if a.is_ty_infer() || b.is_ty_infer() { + Ok(a) + } else { + self.infcx.super_combine_tys(self, a, b).or_else(|e| { + self.errors.push(e); + Ok(a) + }) + } }) } + fn consts( &mut self, a: ty::Const<'tcx>, @@ -64,6 +76,7 @@ impl<'a, 'tcx> TypeRelation<'tcx> for CollectAllMismatches<'a, 'tcx> { } relate::super_relate_consts(self, a, b) // could do something similar here for constants! } + fn binders<T: Relate<'tcx>>( &mut self, a: ty::Binder<'tcx, T>, diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs index 2dd2c568bab..8f317beaa77 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs @@ -226,7 +226,7 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { let arg_length = arguments.len(); let distinct = matches!(other, &[ArgKind::Tuple(..)]); match (arg_length, arguments.get(0)) { - (1, Some(&ArgKind::Tuple(_, ref fields))) => { + (1, Some(ArgKind::Tuple(_, fields))) => { format!("a single {}-tuple as argument", fields.len()) } _ => format!( @@ -1574,7 +1574,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { &error.obligation.cause, expected_found.expected, expected_found.found, - err.clone(), + *err, ) .emit(); } @@ -1583,7 +1583,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { &error.obligation.cause, expected_found.expected, expected_found.found, - err.clone(), + *err, ); let code = error.obligation.cause.code().peel_derives().peel_match_impls(); if let ObligationCauseCode::BindingObligation(..) @@ -1735,8 +1735,8 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { values.map(|(_, is_normalized_ty_expected, normalized_ty, expected_ty)| { infer::ValuePairs::Terms(ExpectedFound::new( is_normalized_ty_expected, - normalized_ty.into(), - expected_ty.into(), + normalized_ty, + expected_ty, )) }), err, @@ -2332,9 +2332,9 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // get rid of :: between Trait and <type> // must be '::' between them, otherwise the parser won't accept the code suggestions.push((between_span, "".to_string(),)); - suggestions.push((generic_arg.span_ext.shrink_to_hi(), format!(">"))); + suggestions.push((generic_arg.span_ext.shrink_to_hi(), ">".to_string())); } else { - suggestions.push((trait_path_segment.ident.span.shrink_to_hi(), format!(">"))); + suggestions.push((trait_path_segment.ident.span.shrink_to_hi(), ">".to_string())); } err.multipart_suggestion( message, diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs index d47a5ea3e37..9656bfbf4ec 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs @@ -335,7 +335,7 @@ pub trait TypeErrCtxtExt<'tcx> { err: &mut Diagnostic, trait_pred: ty::PolyTraitPredicate<'tcx>, ); - fn function_argument_obligation( + fn note_function_argument_obligation( &self, arg_hir_id: HirId, err: &mut Diagnostic, @@ -1789,7 +1789,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { self.note_conflicting_closure_bounds(cause, &mut err); if let Some(found_node) = found_node { - hint_missing_borrow(span, found_span, found, expected, found_node, &mut err); + hint_missing_borrow(span, found, expected, found_node, &mut err); } err @@ -2344,28 +2344,33 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } } GeneratorInteriorOrUpvar::Upvar(upvar_span) => { - // `Some(ref_ty)` if `target_ty` is `&T` and `T` fails to impl `Sync` - let refers_to_non_sync = match target_ty.kind() { - ty::Ref(_, ref_ty, _) => match self.evaluate_obligation(&obligation) { - Ok(eval) if !eval.may_apply() => Some(ref_ty), + // `Some((ref_ty, is_mut))` if `target_ty` is `&T` or `&mut T` and fails to impl `Send` + let non_send = match target_ty.kind() { + ty::Ref(_, ref_ty, mutability) => match self.evaluate_obligation(&obligation) { + Ok(eval) if !eval.may_apply() => Some((ref_ty, mutability.is_mut())), _ => None, }, _ => None, }; - let (span_label, span_note) = match refers_to_non_sync { - // if `target_ty` is `&T` and `T` fails to impl `Sync`, - // include suggestions to make `T: Sync` so that `&T: Send` - Some(ref_ty) => ( - format!( - "has type `{}` which {}, because `{}` is not `Sync`", - target_ty, trait_explanation, ref_ty - ), - format!( - "captured value {} because `&` references cannot be sent unless their referent is `Sync`", - trait_explanation - ), - ), + let (span_label, span_note) = match non_send { + // if `target_ty` is `&T` or `&mut T` and fails to impl `Send`, + // include suggestions to make `T: Sync` so that `&T: Send`, + // or to make `T: Send` so that `&mut T: Send` + Some((ref_ty, is_mut)) => { + let ref_ty_trait = if is_mut { "Send" } else { "Sync" }; + let ref_kind = if is_mut { "&mut" } else { "&" }; + ( + format!( + "has type `{}` which {}, because `{}` is not `{}`", + target_ty, trait_explanation, ref_ty, ref_ty_trait + ), + format!( + "captured value {} because `{}` references cannot be sent unless their referent is `{}`", + trait_explanation, ref_kind, ref_ty_trait + ), + ) + } None => ( format!("has type `{}` which {}", target_ty, trait_explanation), format!("captured value {}", trait_explanation), @@ -2740,7 +2745,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } ty::Closure(def_id, _) => err.span_note( self.tcx.def_span(def_id), - &format!("required because it's used within this closure"), + "required because it's used within this closure", ), _ => err.note(&msg), }; @@ -2904,7 +2909,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { ref parent_code, .. } => { - self.function_argument_obligation( + self.note_function_argument_obligation( arg_hir_id, err, parent_code, @@ -3136,23 +3141,20 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { ); } } - fn function_argument_obligation( + fn note_function_argument_obligation( &self, arg_hir_id: HirId, err: &mut Diagnostic, parent_code: &ObligationCauseCode<'tcx>, param_env: ty::ParamEnv<'tcx>, - predicate: ty::Predicate<'tcx>, + failed_pred: ty::Predicate<'tcx>, call_hir_id: HirId, ) { let tcx = self.tcx; let hir = tcx.hir(); - if let Some(Node::Expr(expr)) = hir.find(arg_hir_id) { - let parent_id = hir.get_parent_item(arg_hir_id); - let typeck_results: &TypeckResults<'tcx> = match &self.typeck_results { - Some(t) if t.hir_owner == parent_id => t, - _ => self.tcx.typeck(parent_id.def_id), - }; + if let Some(Node::Expr(expr)) = hir.find(arg_hir_id) + && let Some(typeck_results) = &self.typeck_results + { if let hir::Expr { kind: hir::ExprKind::Block(..), .. } = expr { let expr = expr.peel_blocks(); let ty = typeck_results.expr_ty_adjusted_opt(expr).unwrap_or(tcx.ty_error()); @@ -3177,37 +3179,29 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let mut type_diffs = vec![]; if let ObligationCauseCode::ExprBindingObligation(def_id, _, _, idx) = parent_code.deref() - && let predicates = self.tcx.predicates_of(def_id).instantiate_identity(self.tcx) - && let Some(pred) = predicates.predicates.get(*idx) + && let Some(node_substs) = typeck_results.node_substs_opt(call_hir_id) + && let where_clauses = self.tcx.predicates_of(def_id).instantiate(self.tcx, node_substs) + && let Some(where_pred) = where_clauses.predicates.get(*idx) { - if let Ok(trait_pred) = pred.kind().try_map_bound(|pred| match pred { - ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred)) => Ok(trait_pred), - _ => Err(()), - }) - && let Ok(trait_predicate) = predicate.kind().try_map_bound(|pred| match pred { - ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred)) => Ok(trait_pred), - _ => Err(()), - }) + if let Some(where_pred) = where_pred.to_opt_poly_trait_pred() + && let Some(failed_pred) = failed_pred.to_opt_poly_trait_pred() { let mut c = CollectAllMismatches { infcx: self.infcx, param_env, errors: vec![], }; - if let Ok(_) = c.relate(trait_pred, trait_predicate) { + if let Ok(_) = c.relate(where_pred, failed_pred) { type_diffs = c.errors; } - } else if let ty::PredicateKind::Clause( - ty::Clause::Projection(proj) - ) = pred.kind().skip_binder() - && let ty::PredicateKind::Clause( - ty::Clause::Projection(projection) - ) = predicate.kind().skip_binder() + } else if let Some(where_pred) = where_pred.to_opt_poly_projection_pred() + && let Some(failed_pred) = failed_pred.to_opt_poly_projection_pred() + && let Some(found) = failed_pred.skip_binder().term.ty() { type_diffs = vec![ Sorts(ty::error::ExpectedFound { - expected: self.tcx.mk_ty(ty::Alias(ty::Projection, proj.projection_ty)), - found: projection.term.ty().unwrap(), + expected: self.tcx.mk_ty(ty::Alias(ty::Projection, where_pred.skip_binder().projection_ty)), + found, }), ]; } @@ -3222,9 +3216,9 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // If the expression we're calling on is a binding, we want to point at the // `let` when talking about the type. Otherwise we'll point at every part // of the method chain with the type. - self.point_at_chain(binding_expr, typeck_results, type_diffs, param_env, err); + self.point_at_chain(binding_expr, &typeck_results, type_diffs, param_env, err); } else { - self.point_at_chain(expr, typeck_results, type_diffs, param_env, err); + self.point_at_chain(expr, &typeck_results, type_diffs, param_env, err); } } let call_node = hir.find(call_hir_id); @@ -3386,7 +3380,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } err.span_note( multi_span, - format!("the method call chain might not have had the expected associated types"), + "the method call chain might not have had the expected associated types", ); } } @@ -3455,7 +3449,6 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { /// Add a hint to add a missing borrow or remove an unnecessary one. fn hint_missing_borrow<'tcx>( span: Span, - found_span: Span, found: Ty<'tcx>, expected: Ty<'tcx>, found_node: Node<'_>, @@ -3474,13 +3467,12 @@ fn hint_missing_borrow<'tcx>( } }; - let fn_decl = found_node - .fn_decl() - .unwrap_or_else(|| span_bug!(found_span, "found node must be a function")); + // This could be a variant constructor, for example. + let Some(fn_decl) = found_node.fn_decl() else { return; }; let arg_spans = fn_decl.inputs.iter().map(|ty| ty.span); - fn get_deref_type_and_refs<'tcx>(mut ty: Ty<'tcx>) -> (Ty<'tcx>, usize) { + fn get_deref_type_and_refs(mut ty: Ty<'_>) -> (Ty<'_>, usize) { let mut refs = 0; while let ty::Ref(_, new_ty, _) = ty.kind() { diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs index 2566d793d78..c30531fa906 100644 --- a/compiler/rustc_trait_selection/src/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/mod.rs @@ -484,10 +484,7 @@ fn subst_and_check_impossible_predicates<'tcx>( /// /// This only considers predicates that reference the impl's generics, and not /// those that reference the method's generics. -fn is_impossible_method<'tcx>( - tcx: TyCtxt<'tcx>, - (impl_def_id, trait_item_def_id): (DefId, DefId), -) -> bool { +fn is_impossible_method(tcx: TyCtxt<'_>, (impl_def_id, trait_item_def_id): (DefId, DefId)) -> bool { struct ReferencesOnlyParentGenerics<'tcx> { tcx: TyCtxt<'tcx>, generics: &'tcx ty::Generics, diff --git a/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs b/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs index e1092a788e3..f2c5f730b31 100644 --- a/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs +++ b/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs @@ -34,7 +34,7 @@ impl<'a, 'tcx: 'a> InferCtxtExt<'a, 'tcx> for InferCtxt<'tcx> { /// argument types are well-formed. This may imply certain relationships /// between generic parameters. For example: /// ``` - /// fn foo<'a,T>(x: &'a T) {} + /// fn foo<T>(x: &T) {} /// ``` /// can only be called with a `'a` and `T` such that `&'a T` is WF. /// For `&'a T` to be WF, `T: 'a` must hold. So we can assume `T: 'a`. diff --git a/compiler/rustc_trait_selection/src/traits/project.rs b/compiler/rustc_trait_selection/src/traits/project.rs index 84d7244c1db..5276da2e49c 100644 --- a/compiler/rustc_trait_selection/src/traits/project.rs +++ b/compiler/rustc_trait_selection/src/traits/project.rs @@ -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>, diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs index 792933096b1..81e8f9e914c 100644 --- a/compiler/rustc_trait_selection/src/traits/select/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs @@ -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 diff --git a/compiler/rustc_trait_selection/src/traits/specialize/specialization_graph.rs b/compiler/rustc_trait_selection/src/traits/specialize/specialization_graph.rs index 4546c953393..02b06677740 100644 --- a/compiler/rustc_trait_selection/src/traits/specialize/specialization_graph.rs +++ b/compiler/rustc_trait_selection/src/traits/specialize/specialization_graph.rs @@ -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) + ) + } +} diff --git a/compiler/rustc_trait_selection/src/traits/vtable.rs b/compiler/rustc_trait_selection/src/traits/vtable.rs index 41ce6cdf789..5ec9c2a24cd 100644 --- a/compiler/rustc_trait_selection/src/traits/vtable.rs +++ b/compiler/rustc_trait_selection/src/traits/vtable.rs @@ -191,7 +191,7 @@ fn dump_vtable_entries<'tcx>( }); } -fn own_existential_vtable_entries<'tcx>(tcx: TyCtxt<'tcx>, trait_def_id: DefId) -> &'tcx [DefId] { +fn own_existential_vtable_entries(tcx: TyCtxt<'_>, trait_def_id: DefId) -> &[DefId] { let trait_methods = tcx .associated_items(trait_def_id) .in_definition_order() |
