use std::ops::ControlFlow; use rustc_infer::infer::InferCtxt; use rustc_infer::traits::solve::inspect::ProbeKind; use rustc_infer::traits::solve::{CandidateSource, Certainty, Goal}; use rustc_infer::traits::{ BuiltinImplSource, ImplSource, ImplSourceUserDefinedData, Obligation, ObligationCause, PolyTraitObligation, Selection, SelectionError, SelectionResult, }; use rustc_macros::extension; use rustc_middle::{bug, span_bug}; use rustc_span::Span; use crate::solve::inspect::{self, ProofTreeInferCtxtExt}; #[extension(pub trait InferCtxtSelectExt<'tcx>)] impl<'tcx> InferCtxt<'tcx> { fn select_in_new_trait_solver( &self, obligation: &PolyTraitObligation<'tcx>, ) -> SelectionResult<'tcx, Selection<'tcx>> { assert!(self.next_trait_solver()); self.visit_proof_tree( Goal::new(self.tcx, obligation.param_env, obligation.predicate), &mut Select { span: obligation.cause.span }, ) .break_value() .unwrap() } } struct Select { span: Span, } impl<'tcx> inspect::ProofTreeVisitor<'tcx> for Select { type Result = ControlFlow>>; fn span(&self) -> Span { self.span } fn visit_goal(&mut self, goal: &inspect::InspectGoal<'_, 'tcx>) -> Self::Result { let mut candidates = goal.candidates(); candidates.retain(|cand| cand.result().is_ok()); // No candidates -- not implemented. if candidates.is_empty() { return ControlFlow::Break(Err(SelectionError::Unimplemented)); } // One candidate, no need to winnow. if candidates.len() == 1 { return ControlFlow::Break(Ok(to_selection( self.span, candidates.into_iter().next().unwrap(), ))); } // Don't winnow until `Certainty::Yes` -- we don't need to winnow until // codegen, and only on the good path. if matches!(goal.result().unwrap(), Certainty::Maybe(..)) { return ControlFlow::Break(Ok(None)); } // We need to winnow. See comments on `candidate_should_be_dropped_in_favor_of`. let mut i = 0; while i < candidates.len() { let should_drop_i = (0..candidates.len()) .filter(|&j| i != j) .any(|j| candidate_should_be_dropped_in_favor_of(&candidates[i], &candidates[j])); if should_drop_i { candidates.swap_remove(i); } else { i += 1; if i > 1 { return ControlFlow::Break(Ok(None)); } } } ControlFlow::Break(Ok(to_selection(self.span, candidates.into_iter().next().unwrap()))) } } /// This is a lot more limited than the old solver's equivalent method. This may lead to more `Ok(None)` /// results when selecting traits in polymorphic contexts, but we should never rely on the lack of ambiguity, /// and should always just gracefully fail here. We shouldn't rely on this incompleteness. fn candidate_should_be_dropped_in_favor_of<'tcx>( victim: &inspect::InspectCandidate<'_, 'tcx>, other: &inspect::InspectCandidate<'_, 'tcx>, ) -> bool { // Don't winnow until `Certainty::Yes` -- we don't need to winnow until // codegen, and only on the good path. if matches!(other.result().unwrap(), Certainty::Maybe(..)) { return false; } let inspect::ProbeKind::TraitCandidate { source: victim_source, result: _ } = victim.kind() else { return false; }; let inspect::ProbeKind::TraitCandidate { source: other_source, result: _ } = other.kind() else { return false; }; match (victim_source, other_source) { (_, CandidateSource::CoherenceUnknowable) | (CandidateSource::CoherenceUnknowable, _) => { bug!("should not have assembled a CoherenceUnknowable candidate") } // In the old trait solver, we arbitrarily choose lower vtable candidates // over higher ones. ( CandidateSource::BuiltinImpl(BuiltinImplSource::Object(a)), CandidateSource::BuiltinImpl(BuiltinImplSource::Object(b)), ) => a >= b, ( CandidateSource::BuiltinImpl(BuiltinImplSource::TraitUpcasting(a)), CandidateSource::BuiltinImpl(BuiltinImplSource::TraitUpcasting(b)), ) => a >= b, // Prefer dyn candidates over non-dyn candidates. This is necessary to // handle the unsoundness between `impl Any for T` and `dyn Any: Any`. ( CandidateSource::Impl(_) | CandidateSource::ParamEnv(_) | CandidateSource::AliasBound, CandidateSource::BuiltinImpl(BuiltinImplSource::Object { .. }), ) => true, // Prefer specializing candidates over specialized candidates. (CandidateSource::Impl(victim_def_id), CandidateSource::Impl(other_def_id)) => { victim.goal().infcx().tcx.specializes((other_def_id, victim_def_id)) } _ => false, } } fn to_selection<'tcx>( span: Span, cand: inspect::InspectCandidate<'_, 'tcx>, ) -> Option> { if let Certainty::Maybe(..) = cand.shallow_certainty() { return None; } let (nested, impl_args) = cand.instantiate_nested_goals_and_opt_impl_args(span); let nested = nested .into_iter() .map(|nested| { Obligation::new( nested.infcx().tcx, ObligationCause::dummy_with_span(span), nested.goal().param_env, nested.goal().predicate, ) }) .collect(); Some(match cand.kind() { ProbeKind::TraitCandidate { source, result: _ } => match source { CandidateSource::Impl(impl_def_id) => { // FIXME: Remove this in favor of storing this in the tree // For impl candidates, we do the rematch manually to compute the args. ImplSource::UserDefined(ImplSourceUserDefinedData { impl_def_id, args: impl_args.expect("expected recorded impl args for impl candidate"), nested, }) } CandidateSource::BuiltinImpl(builtin) => ImplSource::Builtin(builtin, nested), CandidateSource::ParamEnv(_) | CandidateSource::AliasBound => ImplSource::Param(nested), CandidateSource::CoherenceUnknowable => { span_bug!(span, "didn't expect to select an unknowable candidate") } }, ProbeKind::NormalizedSelfTyAssembly | ProbeKind::UnsizeAssembly | ProbeKind::UpcastProjectionCompatibility | ProbeKind::OpaqueTypeStorageLookup { result: _ } | ProbeKind::Root { result: _ } | ProbeKind::ShadowedEnvProbing | ProbeKind::RigidAlias { result: _ } => { span_bug!(span, "didn't expect to assemble trait candidate from {:#?}", cand.kind()) } }) }