use rustc_ast_ir::Movability;
use rustc_type_ir::data_structures::IndexSet;
use rustc_type_ir::fast_reject::{DeepRejectCtxt, TreatParams};
use rustc_type_ir::inherent::*;
use rustc_type_ir::lang_items::TraitSolverLangItem;
use rustc_type_ir::visit::TypeVisitableExt as _;
use rustc_type_ir::{self as ty, elaborate, Interner, TraitPredicate, Upcast as _};
use tracing::{instrument, trace};
use crate::delegate::SolverDelegate;
use crate::solve::assembly::structural_traits::{self, AsyncCallableRelevantTypes};
use crate::solve::assembly::{self, Candidate};
use crate::solve::inspect::ProbeKind;
use crate::solve::{
    BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, MaybeCause,
    NoSolution, QueryResult, Reveal, SolverMode,
};
impl<D, I> assembly::GoalKind<D> for TraitPredicate<I>
where
    D: SolverDelegate<Interner = I>,
    I: Interner,
{
    fn self_ty(self) -> I::Ty {
        self.self_ty()
    }
    fn trait_ref(self, _: I) -> ty::TraitRef<I> {
        self.trait_ref
    }
    fn with_self_ty(self, cx: I, self_ty: I::Ty) -> Self {
        self.with_self_ty(cx, self_ty)
    }
    fn trait_def_id(self, _: I) -> I::DefId {
        self.def_id()
    }
    fn consider_impl_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, TraitPredicate<I>>,
        impl_def_id: I::DefId,
    ) -> Result<Candidate<I>, NoSolution> {
        let cx = ecx.cx();
        let impl_trait_ref = cx.impl_trait_ref(impl_def_id);
        if !DeepRejectCtxt::new(ecx.cx(), TreatParams::ForLookup)
            .args_may_unify(goal.predicate.trait_ref.args, impl_trait_ref.skip_binder().args)
        {
            return Err(NoSolution);
        }
        let impl_polarity = cx.impl_polarity(impl_def_id);
        let maximal_certainty = match (impl_polarity, goal.predicate.polarity) {
            (ty::ImplPolarity::Reservation, _) => match ecx.solver_mode() {
                SolverMode::Coherence => Certainty::AMBIGUOUS,
                SolverMode::Normal => return Err(NoSolution),
            },
            (ty::ImplPolarity::Positive, ty::PredicatePolarity::Positive)
            | (ty::ImplPolarity::Negative, ty::PredicatePolarity::Negative) => Certainty::Yes,
            (ty::ImplPolarity::Positive, ty::PredicatePolarity::Negative)
            | (ty::ImplPolarity::Negative, ty::PredicatePolarity::Positive) => {
                return Err(NoSolution);
            }
        };
        ecx.probe_trait_candidate(CandidateSource::Impl(impl_def_id)).enter(|ecx| {
            let impl_args = ecx.fresh_args_for_item(impl_def_id);
            ecx.record_impl_args(impl_args);
            let impl_trait_ref = impl_trait_ref.instantiate(cx, impl_args);
            ecx.eq(goal.param_env, goal.predicate.trait_ref, impl_trait_ref)?;
            let where_clause_bounds = cx
                .predicates_of(impl_def_id)
                .iter_instantiated(cx, impl_args)
                .map(|pred| goal.with(cx, pred));
            ecx.add_goals(GoalSource::ImplWhereBound, where_clause_bounds);
            ecx.evaluate_added_goals_and_make_canonical_response(maximal_certainty)
        })
    }
    fn consider_error_guaranteed_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        _guar: I::ErrorGuaranteed,
    ) -> Result<Candidate<I>, NoSolution> {
        ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
            .enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
    }
    fn probe_and_match_goal_against_assumption(
        ecx: &mut EvalCtxt<'_, D>,
        source: CandidateSource<I>,
        goal: Goal<I, Self>,
        assumption: I::Clause,
        then: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
    ) -> Result<Candidate<I>, NoSolution> {
        if let Some(trait_clause) = assumption.as_trait_clause() {
            if trait_clause.def_id() == goal.predicate.def_id()
                && trait_clause.polarity() == goal.predicate.polarity
            {
                ecx.probe_trait_candidate(source).enter(|ecx| {
                    let assumption_trait_pred = ecx.instantiate_binder_with_infer(trait_clause);
                    ecx.eq(
                        goal.param_env,
                        goal.predicate.trait_ref,
                        assumption_trait_pred.trait_ref,
                    )?;
                    then(ecx)
                })
            } else {
                Err(NoSolution)
            }
        } else {
            Err(NoSolution)
        }
    }
    fn consider_auto_trait_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        if let Some(result) = ecx.disqualify_auto_trait_candidate_due_to_possible_impl(goal) {
            return result;
        }
        if let ty::Alias(ty::Opaque, opaque_ty) = goal.predicate.self_ty().kind() {
            if matches!(goal.param_env.reveal(), Reveal::All)
                || matches!(ecx.solver_mode(), SolverMode::Coherence)
                || opaque_ty
                    .def_id
                    .as_local()
                    .is_some_and(|def_id| ecx.can_define_opaque_ty(def_id))
            {
                return Err(NoSolution);
            }
        }
        ecx.probe_and_evaluate_goal_for_constituent_tys(
            CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
            goal,
            structural_traits::instantiate_constituent_tys_for_auto_trait,
        )
    }
    fn consider_trait_alias_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        let cx = ecx.cx();
        ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
            let nested_obligations = cx
                .predicates_of(goal.predicate.def_id())
                .iter_instantiated(cx, goal.predicate.trait_ref.args)
                .map(|p| goal.with(cx, p));
            ecx.add_goals(GoalSource::Misc, nested_obligations);
            ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
        })
    }
    fn consider_builtin_sized_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        ecx.probe_and_evaluate_goal_for_constituent_tys(
            CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
            goal,
            structural_traits::instantiate_constituent_tys_for_sized_trait,
        )
    }
    fn consider_builtin_copy_clone_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        ecx.probe_and_evaluate_goal_for_constituent_tys(
            CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
            goal,
            structural_traits::instantiate_constituent_tys_for_copy_clone_trait,
        )
    }
    fn consider_builtin_pointer_like_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        let cx = ecx.cx();
        if (goal.param_env, goal.predicate.self_ty()).has_non_region_infer() {
            return ecx.forced_ambiguity(MaybeCause::Ambiguity);
        }
        if cx.layout_is_pointer_like(goal.param_env, goal.predicate.self_ty()) {
            ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
                .enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
        } else {
            Err(NoSolution)
        }
    }
    fn consider_builtin_fn_ptr_trait_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        let self_ty = goal.predicate.self_ty();
        match goal.predicate.polarity {
            ty::PredicatePolarity::Positive => {
                if self_ty.is_fn_ptr() {
                    ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
                        ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
                    })
                } else {
                    Err(NoSolution)
                }
            }
            ty::PredicatePolarity::Negative => {
                if !self_ty.is_fn_ptr() && self_ty.is_known_rigid() {
                    ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
                        ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
                    })
                } else {
                    Err(NoSolution)
                }
            }
        }
    }
    fn consider_builtin_fn_trait_candidates(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
        goal_kind: ty::ClosureKind,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        let cx = ecx.cx();
        let tupled_inputs_and_output =
            match structural_traits::extract_tupled_inputs_and_output_from_callable(
                cx,
                goal.predicate.self_ty(),
                goal_kind,
            )? {
                Some(a) => a,
                None => {
                    return ecx.forced_ambiguity(MaybeCause::Ambiguity);
                }
            };
        let output_is_sized_pred = tupled_inputs_and_output.map_bound(|(_, output)| {
            ty::TraitRef::new(cx, cx.require_lang_item(TraitSolverLangItem::Sized), [output])
        });
        let pred = tupled_inputs_and_output
            .map_bound(|(inputs, _)| {
                ty::TraitRef::new(cx, goal.predicate.def_id(), [goal.predicate.self_ty(), inputs])
            })
            .upcast(cx);
        Self::probe_and_consider_implied_clause(
            ecx,
            CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
            goal,
            pred,
            [(GoalSource::ImplWhereBound, goal.with(cx, output_is_sized_pred))],
        )
    }
    fn consider_builtin_async_fn_trait_candidates(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
        goal_kind: ty::ClosureKind,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        let cx = ecx.cx();
        let (tupled_inputs_and_output_and_coroutine, nested_preds) =
            structural_traits::extract_tupled_inputs_and_output_from_async_callable(
                cx,
                goal.predicate.self_ty(),
                goal_kind,
                Region::new_static(cx),
            )?;
        let output_is_sized_pred = tupled_inputs_and_output_and_coroutine.map_bound(
            |AsyncCallableRelevantTypes { output_coroutine_ty, .. }| {
                ty::TraitRef::new(
                    cx,
                    cx.require_lang_item(TraitSolverLangItem::Sized),
                    [output_coroutine_ty],
                )
            },
        );
        let pred = tupled_inputs_and_output_and_coroutine
            .map_bound(|AsyncCallableRelevantTypes { tupled_inputs_ty, .. }| {
                ty::TraitRef::new(
                    cx,
                    goal.predicate.def_id(),
                    [goal.predicate.self_ty(), tupled_inputs_ty],
                )
            })
            .upcast(cx);
        Self::probe_and_consider_implied_clause(
            ecx,
            CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
            goal,
            pred,
            [goal.with(cx, output_is_sized_pred)]
                .into_iter()
                .chain(nested_preds.into_iter().map(|pred| goal.with(cx, pred)))
                .map(|goal| (GoalSource::ImplWhereBound, goal)),
        )
    }
    fn consider_builtin_async_fn_kind_helper_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        let [closure_fn_kind_ty, goal_kind_ty] = *goal.predicate.trait_ref.args.as_slice() else {
            panic!();
        };
        let Some(closure_kind) = closure_fn_kind_ty.expect_ty().to_opt_closure_kind() else {
            return Err(NoSolution);
        };
        let goal_kind = goal_kind_ty.expect_ty().to_opt_closure_kind().unwrap();
        if closure_kind.extends(goal_kind) {
            ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
                .enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
        } else {
            Err(NoSolution)
        }
    }
    fn consider_builtin_tuple_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        if let ty::Tuple(..) = goal.predicate.self_ty().kind() {
            ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
                .enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
        } else {
            Err(NoSolution)
        }
    }
    fn consider_builtin_pointee_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
            .enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
    }
    fn consider_builtin_future_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        let ty::Coroutine(def_id, _) = goal.predicate.self_ty().kind() else {
            return Err(NoSolution);
        };
        let cx = ecx.cx();
        if !cx.coroutine_is_async(def_id) {
            return Err(NoSolution);
        }
        ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
            .enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
    }
    fn consider_builtin_iterator_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        let ty::Coroutine(def_id, _) = goal.predicate.self_ty().kind() else {
            return Err(NoSolution);
        };
        let cx = ecx.cx();
        if !cx.coroutine_is_gen(def_id) {
            return Err(NoSolution);
        }
        ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
            .enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
    }
    fn consider_builtin_fused_iterator_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        let ty::Coroutine(def_id, _) = goal.predicate.self_ty().kind() else {
            return Err(NoSolution);
        };
        let cx = ecx.cx();
        if !cx.coroutine_is_gen(def_id) {
            return Err(NoSolution);
        }
        ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
            .enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
    }
    fn consider_builtin_async_iterator_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        let ty::Coroutine(def_id, _) = goal.predicate.self_ty().kind() else {
            return Err(NoSolution);
        };
        let cx = ecx.cx();
        if !cx.coroutine_is_async_gen(def_id) {
            return Err(NoSolution);
        }
        ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
            .enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
    }
    fn consider_builtin_coroutine_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        let self_ty = goal.predicate.self_ty();
        let ty::Coroutine(def_id, args) = self_ty.kind() else {
            return Err(NoSolution);
        };
        let cx = ecx.cx();
        if !cx.is_general_coroutine(def_id) {
            return Err(NoSolution);
        }
        let coroutine = args.as_coroutine();
        Self::probe_and_consider_implied_clause(
            ecx,
            CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
            goal,
            ty::TraitRef::new(cx, goal.predicate.def_id(), [self_ty, coroutine.resume_ty()])
                .upcast(cx),
            [],
        )
    }
    fn consider_builtin_discriminant_kind_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
            .enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
    }
    fn consider_builtin_async_destruct_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
            .enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
    }
    fn consider_builtin_destruct_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
            .enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
    }
    fn consider_builtin_transmute_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        if goal.has_non_region_placeholders() {
            return Err(NoSolution);
        }
        ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
            let certainty = ecx.is_transmutable(
                goal.param_env,
                goal.predicate.trait_ref.args.type_at(0),
                goal.predicate.trait_ref.args.type_at(1),
                goal.predicate.trait_ref.args.const_at(2),
            )?;
            ecx.evaluate_added_goals_and_make_canonical_response(certainty)
        })
    }
    fn consider_structural_builtin_unsize_candidates(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Vec<Candidate<I>> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return vec![];
        }
        let result_to_single = |result| match result {
            Ok(resp) => vec![resp],
            Err(NoSolution) => vec![],
        };
        ecx.probe(|_| ProbeKind::UnsizeAssembly).enter(|ecx| {
            let a_ty = goal.predicate.self_ty();
            let Ok(b_ty) = ecx.structurally_normalize_ty(
                goal.param_env,
                goal.predicate.trait_ref.args.type_at(1),
            ) else {
                return vec![];
            };
            let goal = goal.with(ecx.cx(), (a_ty, b_ty));
            match (a_ty.kind(), b_ty.kind()) {
                (ty::Infer(ty::TyVar(..)), ..) => panic!("unexpected infer {a_ty:?} {b_ty:?}"),
                (_, ty::Infer(ty::TyVar(..))) => {
                    result_to_single(ecx.forced_ambiguity(MaybeCause::Ambiguity))
                }
                (
                    ty::Dynamic(a_data, a_region, ty::Dyn),
                    ty::Dynamic(b_data, b_region, ty::Dyn),
                ) => ecx.consider_builtin_dyn_upcast_candidates(
                    goal, a_data, a_region, b_data, b_region,
                ),
                (_, ty::Dynamic(b_region, b_data, ty::Dyn)) => result_to_single(
                    ecx.consider_builtin_unsize_to_dyn_candidate(goal, b_region, b_data),
                ),
                (ty::Array(a_elem_ty, ..), ty::Slice(b_elem_ty)) => {
                    result_to_single(ecx.consider_builtin_array_unsize(goal, a_elem_ty, b_elem_ty))
                }
                (ty::Adt(a_def, a_args), ty::Adt(b_def, b_args))
                    if a_def.is_struct() && a_def == b_def =>
                {
                    result_to_single(
                        ecx.consider_builtin_struct_unsize(goal, a_def, a_args, b_args),
                    )
                }
                (ty::Tuple(a_tys), ty::Tuple(b_tys))
                    if a_tys.len() == b_tys.len() && !a_tys.is_empty() =>
                {
                    result_to_single(ecx.consider_builtin_tuple_unsize(goal, a_tys, b_tys))
                }
                _ => vec![],
            }
        })
    }
    fn consider_builtin_effects_intersection_candidate(
        ecx: &mut EvalCtxt<'_, D>,
        goal: Goal<I, Self>,
    ) -> Result<Candidate<I>, NoSolution> {
        if goal.predicate.polarity != ty::PredicatePolarity::Positive {
            return Err(NoSolution);
        }
        let ty::Tuple(types) = goal.predicate.self_ty().kind() else {
            return Err(NoSolution);
        };
        let cx = ecx.cx();
        let maybe_count = types
            .iter()
            .filter_map(|ty| ty::EffectKind::try_from_ty(cx, ty))
            .filter(|&ty| ty == ty::EffectKind::Maybe)
            .count();
        if types.len() - maybe_count > 1 {
            let mut min = ty::EffectKind::Maybe;
            for ty in types.iter() {
                let Some(kind) = ty::EffectKind::try_from_ty(ecx.cx(), ty) else {
                    return Err(NoSolution);
                };
                let Some(result) = ty::EffectKind::intersection(min, kind) else {
                    return Err(NoSolution);
                };
                min = result;
            }
        }
        ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
            .enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
    }
}
impl<D, I> EvalCtxt<'_, D>
where
    D: SolverDelegate<Interner = I>,
    I: Interner,
{
    fn consider_builtin_dyn_upcast_candidates(
        &mut self,
        goal: Goal<I, (I::Ty, I::Ty)>,
        a_data: I::BoundExistentialPredicates,
        a_region: I::Region,
        b_data: I::BoundExistentialPredicates,
        b_region: I::Region,
    ) -> Vec<Candidate<I>> {
        let cx = self.cx();
        let Goal { predicate: (a_ty, _b_ty), .. } = goal;
        let mut responses = vec![];
        if a_data.principal_def_id() == b_data.principal_def_id() {
            responses.extend(self.consider_builtin_upcast_to_principal(
                goal,
                CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
                a_data,
                a_region,
                b_data,
                b_region,
                a_data.principal(),
            ));
        } else if let Some(a_principal) = a_data.principal() {
            for new_a_principal in
                elaborate::supertraits(self.cx(), a_principal.with_self_ty(cx, a_ty)).skip(1)
            {
                responses.extend(self.consider_builtin_upcast_to_principal(
                    goal,
                    CandidateSource::BuiltinImpl(BuiltinImplSource::TraitUpcasting),
                    a_data,
                    a_region,
                    b_data,
                    b_region,
                    Some(new_a_principal.map_bound(|trait_ref| {
                        ty::ExistentialTraitRef::erase_self_ty(cx, trait_ref)
                    })),
                ));
            }
        }
        responses
    }
    fn consider_builtin_unsize_to_dyn_candidate(
        &mut self,
        goal: Goal<I, (I::Ty, I::Ty)>,
        b_data: I::BoundExistentialPredicates,
        b_region: I::Region,
    ) -> Result<Candidate<I>, NoSolution> {
        let cx = self.cx();
        let Goal { predicate: (a_ty, _), .. } = goal;
        if b_data.principal_def_id().is_some_and(|def_id| !cx.trait_is_object_safe(def_id)) {
            return Err(NoSolution);
        }
        self.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
            ecx.add_goals(
                GoalSource::ImplWhereBound,
                b_data.iter().map(|pred| goal.with(cx, pred.with_self_ty(cx, a_ty))),
            );
            ecx.add_goal(
                GoalSource::ImplWhereBound,
                goal.with(
                    cx,
                    ty::TraitRef::new(cx, cx.require_lang_item(TraitSolverLangItem::Sized), [a_ty]),
                ),
            );
            ecx.add_goal(GoalSource::Misc, goal.with(cx, ty::OutlivesPredicate(a_ty, b_region)));
            ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
        })
    }
    fn consider_builtin_upcast_to_principal(
        &mut self,
        goal: Goal<I, (I::Ty, I::Ty)>,
        source: CandidateSource<I>,
        a_data: I::BoundExistentialPredicates,
        a_region: I::Region,
        b_data: I::BoundExistentialPredicates,
        b_region: I::Region,
        upcast_principal: Option<ty::Binder<I, ty::ExistentialTraitRef<I>>>,
    ) -> Result<Candidate<I>, NoSolution> {
        let param_env = goal.param_env;
        let a_auto_traits: IndexSet<I::DefId> = a_data
            .auto_traits()
            .into_iter()
            .chain(a_data.principal_def_id().into_iter().flat_map(|principal_def_id| {
                elaborate::supertrait_def_ids(self.cx(), principal_def_id)
                    .into_iter()
                    .filter(|def_id| self.cx().trait_is_auto(*def_id))
            }))
            .collect();
        let projection_may_match =
            |ecx: &mut EvalCtxt<'_, D>,
             source_projection: ty::Binder<I, ty::ExistentialProjection<I>>,
             target_projection: ty::Binder<I, ty::ExistentialProjection<I>>| {
                source_projection.item_def_id() == target_projection.item_def_id()
                    && ecx
                        .probe(|_| ProbeKind::UpcastProjectionCompatibility)
                        .enter(|ecx| -> Result<(), NoSolution> {
                            ecx.eq(param_env, source_projection, target_projection)?;
                            let _ = ecx.try_evaluate_added_goals()?;
                            Ok(())
                        })
                        .is_ok()
            };
        self.probe_trait_candidate(source).enter(|ecx| {
            for bound in b_data.iter() {
                match bound.skip_binder() {
                    ty::ExistentialPredicate::Trait(target_principal) => {
                        ecx.eq(
                            param_env,
                            upcast_principal.unwrap(),
                            bound.rebind(target_principal),
                        )?;
                    }
                    ty::ExistentialPredicate::Projection(target_projection) => {
                        let target_projection = bound.rebind(target_projection);
                        let mut matching_projections =
                            a_data.projection_bounds().into_iter().filter(|source_projection| {
                                projection_may_match(ecx, *source_projection, target_projection)
                            });
                        let Some(source_projection) = matching_projections.next() else {
                            return Err(NoSolution);
                        };
                        if matching_projections.next().is_some() {
                            return ecx.evaluate_added_goals_and_make_canonical_response(
                                Certainty::AMBIGUOUS,
                            );
                        }
                        ecx.eq(param_env, source_projection, target_projection)?;
                    }
                    ty::ExistentialPredicate::AutoTrait(def_id) => {
                        if !a_auto_traits.contains(&def_id) {
                            return Err(NoSolution);
                        }
                    }
                }
            }
            ecx.add_goal(
                GoalSource::ImplWhereBound,
                Goal::new(ecx.cx(), param_env, ty::OutlivesPredicate(a_region, b_region)),
            );
            ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
        })
    }
    fn consider_builtin_array_unsize(
        &mut self,
        goal: Goal<I, (I::Ty, I::Ty)>,
        a_elem_ty: I::Ty,
        b_elem_ty: I::Ty,
    ) -> Result<Candidate<I>, NoSolution> {
        self.eq(goal.param_env, a_elem_ty, b_elem_ty)?;
        self.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
            .enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
    }
    fn consider_builtin_struct_unsize(
        &mut self,
        goal: Goal<I, (I::Ty, I::Ty)>,
        def: I::AdtDef,
        a_args: I::GenericArgs,
        b_args: I::GenericArgs,
    ) -> Result<Candidate<I>, NoSolution> {
        let cx = self.cx();
        let Goal { predicate: (_a_ty, b_ty), .. } = goal;
        let unsizing_params = cx.unsizing_params_for_adt(def.def_id());
        if unsizing_params.is_empty() {
            return Err(NoSolution);
        }
        let tail_field_ty = def.struct_tail_ty(cx).unwrap();
        let a_tail_ty = tail_field_ty.instantiate(cx, a_args);
        let b_tail_ty = tail_field_ty.instantiate(cx, b_args);
        let new_a_args = cx.mk_args_from_iter(a_args.iter().enumerate().map(|(i, a)| {
            if unsizing_params.contains(i as u32) { b_args.get(i).unwrap() } else { a }
        }));
        let unsized_a_ty = Ty::new_adt(cx, def, new_a_args);
        self.eq(goal.param_env, unsized_a_ty, b_ty)?;
        self.add_goal(
            GoalSource::ImplWhereBound,
            goal.with(
                cx,
                ty::TraitRef::new(
                    cx,
                    cx.require_lang_item(TraitSolverLangItem::Unsize),
                    [a_tail_ty, b_tail_ty],
                ),
            ),
        );
        self.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
            .enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
    }
    fn consider_builtin_tuple_unsize(
        &mut self,
        goal: Goal<I, (I::Ty, I::Ty)>,
        a_tys: I::Tys,
        b_tys: I::Tys,
    ) -> Result<Candidate<I>, NoSolution> {
        let cx = self.cx();
        let Goal { predicate: (_a_ty, b_ty), .. } = goal;
        let (&a_last_ty, a_rest_tys) = a_tys.split_last().unwrap();
        let b_last_ty = b_tys.last().unwrap();
        let unsized_a_ty = Ty::new_tup_from_iter(cx, a_rest_tys.iter().copied().chain([b_last_ty]));
        self.eq(goal.param_env, unsized_a_ty, b_ty)?;
        self.add_goal(
            GoalSource::ImplWhereBound,
            goal.with(
                cx,
                ty::TraitRef::new(
                    cx,
                    cx.require_lang_item(TraitSolverLangItem::Unsize),
                    [a_last_ty, b_last_ty],
                ),
            ),
        );
        self.probe_builtin_trait_candidate(BuiltinImplSource::TupleUnsizing)
            .enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
    }
    fn disqualify_auto_trait_candidate_due_to_possible_impl(
        &mut self,
        goal: Goal<I, TraitPredicate<I>>,
    ) -> Option<Result<Candidate<I>, NoSolution>> {
        let self_ty = goal.predicate.self_ty();
        match self_ty.kind() {
            ty::Infer(ty::IntVar(_) | ty::FloatVar(_)) => {
                Some(self.forced_ambiguity(MaybeCause::Ambiguity))
            }
            ty::Dynamic(..)
            | ty::Param(..)
            | ty::Foreign(..)
            | ty::Alias(ty::Projection | ty::Weak | ty::Inherent, ..)
            | ty::Placeholder(..) => Some(Err(NoSolution)),
            ty::Infer(_) | ty::Bound(_, _) => panic!("unexpected type `{self_ty:?}`"),
            ty::Coroutine(def_id, _)
                if self.cx().is_lang_item(goal.predicate.def_id(), TraitSolverLangItem::Unpin) =>
            {
                match self.cx().coroutine_movability(def_id) {
                    Movability::Static => Some(Err(NoSolution)),
                    Movability::Movable => Some(
                        self.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
                            ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
                        }),
                    ),
                }
            }
            ty::Alias(..) => None,
            ty::Bool
            | ty::Char
            | ty::Int(_)
            | ty::Uint(_)
            | ty::Float(_)
            | ty::Str
            | ty::Array(_, _)
            | ty::Pat(_, _)
            | ty::Slice(_)
            | ty::RawPtr(_, _)
            | ty::Ref(_, _, _)
            | ty::FnDef(_, _)
            | ty::FnPtr(_)
            | ty::Closure(..)
            | ty::CoroutineClosure(..)
            | ty::Coroutine(_, _)
            | ty::CoroutineWitness(..)
            | ty::Never
            | ty::Tuple(_)
            | ty::Adt(_, _) => {
                let mut disqualifying_impl = None;
                self.cx().for_each_relevant_impl(
                    goal.predicate.def_id(),
                    goal.predicate.self_ty(),
                    |impl_def_id| {
                        disqualifying_impl = Some(impl_def_id);
                    },
                );
                if let Some(def_id) = disqualifying_impl {
                    trace!(?def_id, ?goal, "disqualified auto-trait implementation");
                    return Some(Err(NoSolution));
                } else {
                    None
                }
            }
            ty::Error(_) => None,
        }
    }
    fn probe_and_evaluate_goal_for_constituent_tys(
        &mut self,
        source: CandidateSource<I>,
        goal: Goal<I, TraitPredicate<I>>,
        constituent_tys: impl Fn(
            &EvalCtxt<'_, D>,
            I::Ty,
        ) -> Result<Vec<ty::Binder<I, I::Ty>>, NoSolution>,
    ) -> Result<Candidate<I>, NoSolution> {
        self.probe_trait_candidate(source).enter(|ecx| {
            ecx.add_goals(
                GoalSource::ImplWhereBound,
                constituent_tys(ecx, goal.predicate.self_ty())?
                    .into_iter()
                    .map(|ty| {
                        ecx.enter_forall(ty, |ty| {
                            goal.with(ecx.cx(), goal.predicate.with_self_ty(ecx.cx(), ty))
                        })
                    })
                    .collect::<Vec<_>>(),
            );
            ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
        })
    }
    #[instrument(level = "trace", skip(self))]
    pub(super) fn compute_trait_goal(
        &mut self,
        goal: Goal<I, TraitPredicate<I>>,
    ) -> QueryResult<I> {
        let candidates = self.assemble_and_evaluate_candidates(goal);
        self.merge_candidates(candidates)
    }
}