use rustc_hir::{self as hir, LangItem};
use rustc_infer::infer::{BoundRegionConversionTime, DefineOpaqueTypes};
use rustc_infer::traits::{
    ImplDerivedHostCause, ImplSource, Obligation, ObligationCause, ObligationCauseCode,
    PredicateObligation,
};
use rustc_middle::span_bug;
use rustc_middle::traits::query::NoSolution;
use rustc_middle::ty::elaborate::elaborate;
use rustc_middle::ty::fast_reject::DeepRejectCtxt;
use rustc_middle::ty::{self, Ty, Unnormalized};
use thin_vec::{ThinVec, thin_vec};

use super::SelectionContext;
use super::normalize::normalize_with_depth_to;

pub type HostEffectObligation<'tcx> = Obligation<'tcx, ty::HostEffectPredicate<'tcx>>;

pub enum EvaluationFailure {
    Ambiguous,
    NoSolution,
}

pub fn evaluate_host_effect_obligation<'tcx>(
    selcx: &mut SelectionContext<'_, 'tcx>,
    obligation: &HostEffectObligation<'tcx>,
) -> Result<ThinVec<PredicateObligation<'tcx>>, EvaluationFailure> {
    if selcx.typing_mode().is_coherence() {
        span_bug!(
            obligation.cause.span,
            "should not select host obligation in old solver in intercrate mode"
        );
    }

    let ref obligation = selcx.infcx.resolve_vars_if_possible(obligation.clone());

    // Force ambiguity for infer self ty.
    if obligation.predicate.self_ty().is_ty_var() {
        return Err(EvaluationFailure::Ambiguous);
    }

    match evaluate_host_effect_from_bounds(selcx, obligation) {
        Ok(result) => return Ok(result),
        Err(EvaluationFailure::Ambiguous) => return Err(EvaluationFailure::Ambiguous),
        Err(EvaluationFailure::NoSolution) => {}
    }

    match evaluate_host_effect_from_conditionally_const_item_bounds(selcx, obligation) {
        Ok(result) => return Ok(result),
        Err(EvaluationFailure::Ambiguous) => return Err(EvaluationFailure::Ambiguous),
        Err(EvaluationFailure::NoSolution) => {}
    }

    match evaluate_host_effect_from_item_bounds(selcx, obligation) {
        Ok(result) => return Ok(result),
        Err(EvaluationFailure::Ambiguous) => return Err(EvaluationFailure::Ambiguous),
        Err(EvaluationFailure::NoSolution) => {}
    }

    match evaluate_host_effect_from_builtin_impls(selcx, obligation) {
        Ok(result) => return Ok(result),
        Err(EvaluationFailure::Ambiguous) => return Err(EvaluationFailure::Ambiguous),
        Err(EvaluationFailure::NoSolution) => {}
    }

    match evaluate_host_effect_from_selection_candidate(selcx, obligation) {
        Ok(result) => return Ok(result),
        Err(EvaluationFailure::Ambiguous) => return Err(EvaluationFailure::Ambiguous),
        Err(EvaluationFailure::NoSolution) => {}
    }

    match evaluate_host_effect_from_trait_alias(selcx, obligation) {
        Ok(result) => return Ok(result),
        Err(EvaluationFailure::Ambiguous) => return Err(EvaluationFailure::Ambiguous),
        Err(EvaluationFailure::NoSolution) => {}
    }

    Err(EvaluationFailure::NoSolution)
}

fn match_candidate<'tcx>(
    selcx: &mut SelectionContext<'_, 'tcx>,
    obligation: &HostEffectObligation<'tcx>,
    candidate: ty::Binder<'tcx, ty::HostEffectPredicate<'tcx>>,
    candidate_is_unnormalized: bool,
    more_nested: impl FnOnce(&mut SelectionContext<'_, 'tcx>, &mut ThinVec<PredicateObligation<'tcx>>),
) -> Result<ThinVec<PredicateObligation<'tcx>>, NoSolution> {
    if !candidate.skip_binder().constness.satisfies(obligation.predicate.constness) {
        return Err(NoSolution);
    }

    let mut candidate = selcx.infcx.instantiate_binder_with_fresh_vars(
        obligation.cause.span,
        BoundRegionConversionTime::HigherRankedType,
        candidate,
    );

    let mut nested = thin_vec![];

    // Unlike param-env bounds, item bounds may not be normalized.
    if candidate_is_unnormalized {
        candidate = normalize_with_depth_to(
            selcx,
            obligation.param_env,
            obligation.cause.clone(),
            obligation.recursion_depth,
            candidate,
            &mut nested,
        );
    }

    nested.extend(
        selcx
            .infcx
            .at(&obligation.cause, obligation.param_env)
            .eq(DefineOpaqueTypes::Yes, obligation.predicate.trait_ref, candidate.trait_ref)?
            .into_obligations(),
    );

    more_nested(selcx, &mut nested);

    Ok(nested)
}

fn evaluate_host_effect_from_bounds<'tcx>(
    selcx: &mut SelectionContext<'_, 'tcx>,
    obligation: &HostEffectObligation<'tcx>,
) -> Result<ThinVec<PredicateObligation<'tcx>>, EvaluationFailure> {
    let infcx = selcx.infcx;
    let drcx = DeepRejectCtxt::relate_rigid_rigid(selcx.tcx());
    let mut candidate = None;

    for clause in obligation.param_env.caller_bounds() {
        let bound_clause = clause.kind();
        let ty::ClauseKind::HostEffect(data) = bound_clause.skip_binder() else {
            continue;
        };
        let data = bound_clause.rebind(data);
        if data.skip_binder().trait_ref.def_id != obligation.predicate.trait_ref.def_id {
            continue;
        }

        if !drcx
            .args_may_unify(obligation.predicate.trait_ref.args, data.skip_binder().trait_ref.args)
        {
            continue;
        }

        let is_match =
            infcx.probe(|_| match_candidate(selcx, obligation, data, false, |_, _| {}).is_ok());

        if is_match {
            if candidate.is_some() {
                return Err(EvaluationFailure::Ambiguous);
            } else {
                candidate = Some(data);
            }
        }
    }

    if let Some(data) = candidate {
        Ok(match_candidate(selcx, obligation, data, false, |_, _| {})
            .expect("candidate matched before, so it should match again"))
    } else {
        Err(EvaluationFailure::NoSolution)
    }
}

/// Assembles constness bounds from `~const` item bounds on alias types, which only
/// hold if the `~const` where bounds also hold and the parent trait is `~const`.
fn evaluate_host_effect_from_conditionally_const_item_bounds<'tcx>(
    selcx: &mut SelectionContext<'_, 'tcx>,
    obligation: &HostEffectObligation<'tcx>,
) -> Result<ThinVec<PredicateObligation<'tcx>>, EvaluationFailure> {
    let infcx = selcx.infcx;
    let tcx = infcx.tcx;
    let drcx = DeepRejectCtxt::relate_rigid_rigid(selcx.tcx());
    let mut candidate = None;

    let mut consider_ty = obligation.predicate.self_ty();
    while let ty::Alias(
        alias_ty @ ty::AliasTy {
            kind: kind @ (ty::Projection { def_id } | ty::Opaque { def_id }),
            ..
        },
    ) = *consider_ty.kind()
    {
        if tcx.is_conditionally_const(def_id) {
            for clause in elaborate(
                tcx,
                tcx.explicit_implied_const_bounds(def_id)
                    .iter_instantiated_copied(tcx, alias_ty.args)
                    .map(Unnormalized::skip_norm_wip)
                    .map(|(trait_ref, _)| {
                        trait_ref.to_host_effect_clause(tcx, obligation.predicate.constness)
                    }),
            ) {
                let bound_clause = clause.kind();
                let ty::ClauseKind::HostEffect(data) = bound_clause.skip_binder() else {
                    unreachable!("should not elaborate non-HostEffect from HostEffect")
                };
                let data = bound_clause.rebind(data);
                if data.skip_binder().trait_ref.def_id != obligation.predicate.trait_ref.def_id {
                    continue;
                }

                if !drcx.args_may_unify(
                    obligation.predicate.trait_ref.args,
                    data.skip_binder().trait_ref.args,
                ) {
                    continue;
                }

                let is_match = infcx
                    .probe(|_| match_candidate(selcx, obligation, data, true, |_, _| {}).is_ok());

                if is_match {
                    if candidate.is_some() {
                        return Err(EvaluationFailure::Ambiguous);
                    } else {
                        candidate = Some((data, alias_ty));
                    }
                }
            }
        }

        if !matches!(kind, ty::Projection { .. }) {
            break;
        }

        consider_ty = alias_ty.self_ty();
    }

    if let Some((data, alias_ty)) = candidate {
        Ok(match_candidate(selcx, obligation, data, true, |selcx, nested| {
            // An alias bound only holds if we also check the const conditions
            // of the alias, so we need to register those, too.
            let const_conditions =
                tcx.const_conditions(alias_ty.kind.def_id()).instantiate(tcx, alias_ty.args);
            let const_conditions: Vec<_> = const_conditions
                .into_iter()
                .map(|(trait_ref, span)| {
                    let trait_ref = normalize_with_depth_to(
                        selcx,
                        obligation.param_env,
                        obligation.cause.clone(),
                        obligation.recursion_depth,
                        trait_ref.skip_norm_wip(),
                        nested,
                    );
                    (trait_ref, span)
                })
                .collect();
            nested.extend(const_conditions.into_iter().map(|(trait_ref, _)| {
                obligation
                    .with(tcx, trait_ref.to_host_effect_clause(tcx, obligation.predicate.constness))
            }));
        })
        .expect("candidate matched before, so it should match again"))
    } else {
        Err(EvaluationFailure::NoSolution)
    }
}

/// Assembles constness bounds "normal" item bounds on aliases, which may include
/// unconditionally `const` bounds that are *not* conditional and thus always hold.
fn evaluate_host_effect_from_item_bounds<'tcx>(
    selcx: &mut SelectionContext<'_, 'tcx>,
    obligation: &HostEffectObligation<'tcx>,
) -> Result<ThinVec<PredicateObligation<'tcx>>, EvaluationFailure> {
    let infcx = selcx.infcx;
    let tcx = infcx.tcx;
    let drcx = DeepRejectCtxt::relate_rigid_rigid(selcx.tcx());
    let mut candidate = None;

    let mut consider_ty = obligation.predicate.self_ty();
    while let ty::Alias(
        alias_ty @ ty::AliasTy {
            kind: kind @ (ty::Projection { def_id } | ty::Opaque { def_id }),
            ..
        },
    ) = *consider_ty.kind()
    {
        for clause in tcx
            .item_bounds(def_id)
            .iter_instantiated(tcx, alias_ty.args)
            .map(Unnormalized::skip_norm_wip)
        {
            let bound_clause = clause.kind();
            let ty::ClauseKind::HostEffect(data) = bound_clause.skip_binder() else {
                continue;
            };
            let data = bound_clause.rebind(data);
            if data.skip_binder().trait_ref.def_id != obligation.predicate.trait_ref.def_id {
                continue;
            }

            if !drcx.args_may_unify(
                obligation.predicate.trait_ref.args,
                data.skip_binder().trait_ref.args,
            ) {
                continue;
            }

            let is_match =
                infcx.probe(|_| match_candidate(selcx, obligation, data, true, |_, _| {}).is_ok());

            if is_match {
                if candidate.is_some() {
                    return Err(EvaluationFailure::Ambiguous);
                } else {
                    candidate = Some(data);
                }
            }
        }

        if !matches!(kind, ty::Projection { .. }) {
            break;
        }

        consider_ty = alias_ty.self_ty();
    }

    if let Some(data) = candidate {
        Ok(match_candidate(selcx, obligation, data, true, |_, _| {})
            .expect("candidate matched before, so it should match again"))
    } else {
        Err(EvaluationFailure::NoSolution)
    }
}

fn evaluate_host_effect_from_builtin_impls<'tcx>(
    selcx: &mut SelectionContext<'_, 'tcx>,
    obligation: &HostEffectObligation<'tcx>,
) -> Result<ThinVec<PredicateObligation<'tcx>>, EvaluationFailure> {
    match selcx.tcx().as_lang_item(obligation.predicate.def_id()) {
        Some(LangItem::Copy | LangItem::Clone) => {
            evaluate_host_effect_for_copy_clone_goal(selcx, obligation)
        }
        Some(LangItem::Destruct) => evaluate_host_effect_for_destruct_goal(selcx, obligation),
        Some(LangItem::Fn | LangItem::FnMut | LangItem::FnOnce) => {
            evaluate_host_effect_for_fn_goal(selcx, obligation)
        }
        _ => Err(EvaluationFailure::NoSolution),
    }
}

fn evaluate_host_effect_for_copy_clone_goal<'tcx>(
    selcx: &mut SelectionContext<'_, 'tcx>,
    obligation: &HostEffectObligation<'tcx>,
) -> Result<ThinVec<PredicateObligation<'tcx>>, EvaluationFailure> {
    let tcx = selcx.tcx();
    let self_ty = obligation.predicate.self_ty();
    let constituent_tys = match *self_ty.kind() {
        // impl Copy/Clone for FnDef, FnPtr
        ty::FnDef(..) | ty::FnPtr(..) | ty::Error(_) => Ok(ty::Binder::dummy(vec![])),

        // Implementations are provided in core
        ty::Uint(_)
        | ty::Int(_)
        | ty::Infer(ty::IntVar(_) | ty::FloatVar(_))
        | ty::Bool
        | ty::Float(_)
        | ty::Char
        | ty::RawPtr(..)
        | ty::Never
        | ty::Ref(_, _, ty::Mutability::Not)
        | ty::Array(..) => Err(EvaluationFailure::NoSolution),

        // Cannot implement in core, as we can't be generic over patterns yet,
        // so we'd have to list all patterns and type combinations.
        ty::Pat(ty, ..) => Ok(ty::Binder::dummy(vec![ty])),

        ty::Dynamic(..)
        | ty::Str
        | ty::Slice(_)
        | ty::Foreign(..)
        | ty::Ref(_, _, ty::Mutability::Mut)
        | ty::Adt(_, _)
        | ty::Alias(_)
        | ty::Param(_)
        | ty::Placeholder(..) => Err(EvaluationFailure::NoSolution),

        ty::Bound(..)
        | ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => {
            panic!("unexpected type `{self_ty:?}`")
        }

        // impl Copy/Clone for (T1, T2, .., Tn) where T1: Copy/Clone, T2: Copy/Clone, .. Tn: Copy/Clone
        ty::Tuple(tys) => Ok(ty::Binder::dummy(tys.to_vec())),

        // impl Copy/Clone for Closure where Self::TupledUpvars: Copy/Clone
        ty::Closure(_, args) => Ok(ty::Binder::dummy(vec![args.as_closure().tupled_upvars_ty()])),

        // impl Copy/Clone for CoroutineClosure where Self::TupledUpvars: Copy/Clone
        ty::CoroutineClosure(_, args) => {
            Ok(ty::Binder::dummy(vec![args.as_coroutine_closure().tupled_upvars_ty()]))
        }

        // only when `coroutine_clone` is enabled and the coroutine is movable
        // impl Copy/Clone for Coroutine where T: Copy/Clone forall T in (upvars, witnesses)
        ty::Coroutine(def_id, args) => {
            if selcx.should_stall_coroutine(def_id) {
                return Err(EvaluationFailure::Ambiguous);
            }
            match tcx.coroutine_movability(def_id) {
                ty::Movability::Static => Err(EvaluationFailure::NoSolution),
                ty::Movability::Movable => {
                    if tcx.features().coroutine_clone() {
                        Ok(ty::Binder::dummy(vec![
                            args.as_coroutine().tupled_upvars_ty(),
                            Ty::new_coroutine_witness_for_coroutine(tcx, def_id, args),
                        ]))
                    } else {
                        Err(EvaluationFailure::NoSolution)
                    }
                }
            }
        }

        ty::UnsafeBinder(_) => Err(EvaluationFailure::NoSolution),

        // impl Copy/Clone for CoroutineWitness where T: Copy/Clone forall T in coroutine_hidden_types
        ty::CoroutineWitness(def_id, args) => Ok(tcx
            .coroutine_hidden_types(def_id)
            .instantiate(tcx, args)
            .skip_norm_wip()
            .map_bound(|bound| bound.types.to_vec())),
    }?;

    Ok(constituent_tys
        .iter()
        .map(|ty| {
            obligation.with(
                tcx,
                ty.map_bound(|ty| ty::TraitRef::new(tcx, obligation.predicate.def_id(), [ty]))
                    .to_host_effect_clause(tcx, obligation.predicate.constness),
            )
        })
        .collect())
}

// NOTE: Keep this in sync with `const_conditions_for_destruct` in the new solver.
fn evaluate_host_effect_for_destruct_goal<'tcx>(
    selcx: &mut SelectionContext<'_, 'tcx>,
    obligation: &HostEffectObligation<'tcx>,
) -> Result<ThinVec<PredicateObligation<'tcx>>, EvaluationFailure> {
    let tcx = selcx.tcx();
    let destruct_def_id = tcx.require_lang_item(LangItem::Destruct, obligation.cause.span);
    let self_ty = obligation.predicate.self_ty();

    let const_conditions = match *self_ty.kind() {
        // `ManuallyDrop` is trivially `[const] Destruct` as we do not run any drop glue on it.
        ty::Adt(adt_def, _) if adt_def.is_manually_drop() => thin_vec![],

        // An ADT is `[const] Destruct` only if all of the fields are,
        // *and* if there is a `Drop` impl, that `Drop` impl is also `[const]`.
        ty::Adt(adt_def, args) => {
            let mut const_conditions: ThinVec<_> = adt_def
                .all_fields()
                .map(|field| {
                    ty::TraitRef::new(tcx, destruct_def_id, [field.ty(tcx, args).skip_norm_wip()])
                })
                .collect();
            match adt_def.destructor(tcx).map(|dtor| tcx.constness(dtor.did)) {
                // `Drop` impl exists, but it's not const. Type cannot be `[const] Destruct`.
                Some(hir::Constness::NotConst) => return Err(EvaluationFailure::NoSolution),
                // `Drop` impl exists, and it's const. Require `Ty: [const] Drop` to hold.
                Some(hir::Constness::Const) => {
                    let drop_def_id = tcx.require_lang_item(LangItem::Drop, obligation.cause.span);
                    let drop_trait_ref = ty::TraitRef::new(tcx, drop_def_id, [self_ty]);
                    const_conditions.push(drop_trait_ref);
                }
                // No `Drop` impl, no need to require anything else.
                None => {}
            }
            const_conditions
        }

        ty::Array(ty, _) | ty::Pat(ty, _) | ty::Slice(ty) => {
            thin_vec![ty::TraitRef::new(tcx, destruct_def_id, [ty])]
        }

        ty::Tuple(tys) => {
            tys.iter().map(|field_ty| ty::TraitRef::new(tcx, destruct_def_id, [field_ty])).collect()
        }

        // Trivially implement `[const] Destruct`
        ty::Bool
        | ty::Char
        | ty::Int(..)
        | ty::Uint(..)
        | ty::Float(..)
        | ty::Str
        | ty::RawPtr(..)
        | ty::Ref(..)
        | ty::FnDef(..)
        | ty::FnPtr(..)
        | ty::Never
        | ty::Infer(ty::InferTy::FloatVar(_) | ty::InferTy::IntVar(_))
        | ty::Error(_) => thin_vec![],

        // Closures are [const] Destruct when all of their upvars (captures) are [const] Destruct.
        ty::Closure(_, args) => {
            let closure_args = args.as_closure();
            thin_vec![ty::TraitRef::new(tcx, destruct_def_id, [closure_args.tupled_upvars_ty()])]
        }

        // Coroutines could implement `[const] Drop`,
        // but they don't really need to right now.
        ty::CoroutineClosure(_, _) | ty::Coroutine(_, _) | ty::CoroutineWitness(_, _) => {
            return Err(EvaluationFailure::NoSolution);
        }

        // FIXME(unsafe_binders): Unsafe binders could implement `[const] Drop`
        // if their inner type implements it.
        ty::UnsafeBinder(_) => return Err(EvaluationFailure::NoSolution),

        ty::Dynamic(..) | ty::Param(_) | ty::Alias(..) | ty::Placeholder(_) | ty::Foreign(_) => {
            return Err(EvaluationFailure::NoSolution);
        }

        ty::Bound(..)
        | ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => {
            panic!("unexpected type `{self_ty:?}`")
        }
    };

    Ok(const_conditions
        .into_iter()
        .map(|trait_ref| {
            obligation.with(
                tcx,
                ty::Binder::dummy(trait_ref)
                    .to_host_effect_clause(tcx, obligation.predicate.constness),
            )
        })
        .collect())
}

// NOTE: Keep this in sync with `extract_fn_def_from_const_callable` in the new solver.
fn evaluate_host_effect_for_fn_goal<'tcx>(
    selcx: &mut SelectionContext<'_, 'tcx>,
    obligation: &HostEffectObligation<'tcx>,
) -> Result<ThinVec<PredicateObligation<'tcx>>, EvaluationFailure> {
    let tcx = selcx.tcx();
    let self_ty = obligation.predicate.self_ty();

    let (def, args) = match *self_ty.kind() {
        ty::FnDef(def, args) => (def, args),

        // We may support function pointers at some point in the future
        ty::FnPtr(..) => return Err(EvaluationFailure::NoSolution),

        // Coroutines could implement `[const] Fn`,
        // but they don't really need to right now.
        ty::CoroutineClosure(_, _) => return Err(EvaluationFailure::NoSolution),

        ty::Closure(def, args) => (def, args),

        // Everything else needs explicit impls or cannot have an impl
        _ => return Err(EvaluationFailure::NoSolution),
    };

    match tcx.constness(def) {
        hir::Constness::Const => Ok(tcx
            .const_conditions(def)
            .instantiate(tcx, args)
            .into_iter()
            .map(|(c, span)| {
                let code = ObligationCauseCode::WhereClause(def, span);
                let cause =
                    ObligationCause::new(obligation.cause.span, obligation.cause.body_id, code);
                Obligation::new(
                    tcx,
                    cause,
                    obligation.param_env,
                    c.to_host_effect_clause(tcx, obligation.predicate.constness).skip_norm_wip(),
                )
            })
            .collect()),
        hir::Constness::NotConst => Err(EvaluationFailure::NoSolution),
    }
}

fn evaluate_host_effect_from_selection_candidate<'tcx>(
    selcx: &mut SelectionContext<'_, 'tcx>,
    obligation: &HostEffectObligation<'tcx>,
) -> Result<ThinVec<PredicateObligation<'tcx>>, EvaluationFailure> {
    let tcx = selcx.tcx();
    selcx.infcx.commit_if_ok(|_| {
        match selcx.select(&obligation.with(tcx, obligation.predicate.trait_ref)) {
            Ok(None) => Err(EvaluationFailure::Ambiguous),
            Err(_) => Err(EvaluationFailure::NoSolution),
            Ok(Some(source)) => match source {
                ImplSource::UserDefined(impl_) => {
                    if tcx.impl_trait_header(impl_.impl_def_id).constness != hir::Constness::Const {
                        return Err(EvaluationFailure::NoSolution);
                    }

                    let mut nested = impl_.nested;
                    nested.extend(
                        tcx.const_conditions(impl_.impl_def_id)
                            .instantiate(tcx, impl_.args)
                            .into_iter()
                            .map(|(trait_ref, span)| {
                                Obligation::new(
                                    tcx,
                                    obligation.cause.clone().derived_host_cause(
                                        ty::Binder::dummy(obligation.predicate),
                                        |derived| {
                                            ObligationCauseCode::ImplDerivedHost(Box::new(
                                                ImplDerivedHostCause {
                                                    derived,
                                                    impl_def_id: impl_.impl_def_id,
                                                    span,
                                                },
                                            ))
                                        },
                                    ),
                                    obligation.param_env,
                                    trait_ref
                                        .to_host_effect_clause(tcx, obligation.predicate.constness)
                                        .skip_norm_wip(),
                                )
                            }),
                    );

                    Ok(nested)
                }
                _ => Err(EvaluationFailure::NoSolution),
            },
        }
    })
}

fn evaluate_host_effect_from_trait_alias<'tcx>(
    selcx: &mut SelectionContext<'_, 'tcx>,
    obligation: &HostEffectObligation<'tcx>,
) -> Result<ThinVec<PredicateObligation<'tcx>>, EvaluationFailure> {
    let tcx = selcx.tcx();
    let def_id = obligation.predicate.def_id();
    if !tcx.trait_is_alias(def_id) {
        return Err(EvaluationFailure::NoSolution);
    }

    Ok(tcx
        .const_conditions(def_id)
        .instantiate(tcx, obligation.predicate.trait_ref.args)
        .into_iter()
        .map(|(trait_ref, span)| {
            Obligation::new(
                tcx,
                obligation.cause.clone().derived_host_cause(
                    ty::Binder::dummy(obligation.predicate),
                    |derived| {
                        ObligationCauseCode::ImplDerivedHost(Box::new(ImplDerivedHostCause {
                            derived,
                            impl_def_id: def_id,
                            span,
                        }))
                    },
                ),
                obligation.param_env,
                trait_ref
                    .to_host_effect_clause(tcx, obligation.predicate.constness)
                    .skip_norm_wip(),
            )
        })
        .collect())
}
