//! Code for projecting associated types out of trait references.

use std::ops::ControlFlow;

use rustc_data_structures::sso::SsoHashSet;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_errors::ErrorGuaranteed;
use rustc_hir::def_id::DefId;
use rustc_hir::lang_items::LangItem;
use rustc_infer::infer::DefineOpaqueTypes;
use rustc_infer::infer::resolve::OpportunisticRegionResolver;
use rustc_infer::traits::{ObligationCauseCode, PredicateObligations};
use rustc_middle::traits::select::OverflowError;
use rustc_middle::traits::{BuiltinImplSource, ImplSource, ImplSourceUserDefinedData};
use rustc_middle::ty::fast_reject::DeepRejectCtxt;
use rustc_middle::ty::{
    self, FieldInfo, Term, Ty, TyCtxt, TypeFoldable, TypeVisitableExt, TypingMode, Upcast,
};
use rustc_middle::{bug, span_bug};
use rustc_span::sym;
use tracing::{debug, instrument};

use super::{
    MismatchedProjectionTypes, Normalized, NormalizedTerm, Obligation, ObligationCause,
    PredicateObligation, ProjectionCacheEntry, ProjectionCacheKey, Selection, SelectionContext,
    SelectionError, specialization_graph, translate_args, util,
};
use crate::diagnostics::InherentProjectionNormalizationOverflow;
use crate::infer::{BoundRegionConversionTime, InferOk};
use crate::traits::normalize::{normalize_with_depth, normalize_with_depth_to};
use crate::traits::query::evaluate_obligation::InferCtxtExt as _;
use crate::traits::select::ProjectionMatchesProjection;

pub type PolyProjectionObligation<'tcx> = Obligation<'tcx, ty::PolyProjectionPredicate<'tcx>>;

pub type ProjectionObligation<'tcx> = Obligation<'tcx, ty::ProjectionPredicate<'tcx>>;

pub type ProjectionTermObligation<'tcx> = Obligation<'tcx, ty::AliasTerm<'tcx>>;

pub(super) struct InProgress;

/// When attempting to resolve `<T as TraitRef>::Name` ...
#[derive(Debug)]
pub enum ProjectionError<'tcx> {
    /// ...we found multiple sources of information and couldn't resolve the ambiguity.
    TooManyCandidates,

    /// ...an error occurred matching `T : TraitRef`
    TraitSelectionError(SelectionError<'tcx>),
}

#[derive(PartialEq, Eq, Debug)]
enum ProjectionCandidate<'tcx> {
    /// From a where-clause in the env or object type
    ParamEnv(ty::PolyProjectionPredicate<'tcx>),

    /// From the definition of `Trait` when you have something like
    /// `<<A as Trait>::B as Trait2>::C`.
    TraitDef(ty::PolyProjectionPredicate<'tcx>),

    /// Bounds specified on an object type
    Object(ty::PolyProjectionPredicate<'tcx>),

    /// From an "impl" (or a "pseudo-impl" returned by select)
    Select(Selection<'tcx>),
}

enum ProjectionCandidateSet<'tcx> {
    None,
    Single(ProjectionCandidate<'tcx>),
    Ambiguous,
    Error(SelectionError<'tcx>),
}

impl<'tcx> ProjectionCandidateSet<'tcx> {
    fn mark_ambiguous(&mut self) {
        *self = ProjectionCandidateSet::Ambiguous;
    }

    fn mark_error(&mut self, err: SelectionError<'tcx>) {
        *self = ProjectionCandidateSet::Error(err);
    }

    // Returns true if the push was successful, or false if the candidate
    // was discarded -- this could be because of ambiguity, or because
    // a higher-priority candidate is already there.
    fn push_candidate(&mut self, candidate: ProjectionCandidate<'tcx>) -> bool {
        // This wacky variable is just used to try and
        // make code readable and avoid confusing paths.
        // It is assigned a "value" of `()` only on those
        // paths in which we wish to convert `*self` to
        // ambiguous (and return false, because the candidate
        // was not used). On other paths, it is not assigned,
        // and hence if those paths *could* reach the code that
        // comes after the match, this fn would not compile.
        let convert_to_ambiguous;

        match self {
            ProjectionCandidateSet::None => {
                *self = ProjectionCandidateSet::Single(candidate);
                return true;
            }

            ProjectionCandidateSet::Single(current) => {
                // Duplicates can happen inside ParamEnv. In the case, we
                // perform a lazy deduplication.
                if current == &candidate {
                    return false;
                }

                // Prefer where-clauses. As in select, if there are multiple
                // candidates, we prefer where-clause candidates over impls. This
                // may seem a bit surprising, since impls are the source of
                // "truth" in some sense, but in fact some of the impls that SEEM
                // applicable are not, because of nested obligations. Where
                // clauses are the safer choice. See the comment on
                // `select::SelectionCandidate` and #21974 for more details.
                match (current, candidate) {
                    (ProjectionCandidate::ParamEnv(..), ProjectionCandidate::ParamEnv(..)) => {
                        convert_to_ambiguous = ()
                    }
                    (ProjectionCandidate::ParamEnv(..), _) => return false,
                    (_, ProjectionCandidate::ParamEnv(..)) => bug!(
                        "should never prefer non-param-env candidates over param-env candidates"
                    ),
                    (_, _) => convert_to_ambiguous = (),
                }
            }

            ProjectionCandidateSet::Ambiguous | ProjectionCandidateSet::Error(..) => {
                return false;
            }
        }

        // We only ever get here when we moved from a single candidate
        // to ambiguous.
        let () = convert_to_ambiguous;
        *self = ProjectionCandidateSet::Ambiguous;
        false
    }
}

/// States returned from `poly_project_and_unify_type`. Takes the place
/// of the old return type, which was:
/// ```ignore (not-rust)
/// Result<
///     Result<Option<PredicateObligations<'tcx>>, InProgress>,
///     MismatchedProjectionTypes<'tcx>,
/// >
/// ```
pub(super) enum ProjectAndUnifyResult<'tcx> {
    /// The projection bound holds subject to the given obligations. If the
    /// projection cannot be normalized because the required trait bound does
    /// not hold, this is returned, with `obligations` being a predicate that
    /// cannot be proven.
    Holds(PredicateObligations<'tcx>),
    /// The projection cannot be normalized due to ambiguity. Resolving some
    /// inference variables in the projection may fix this.
    FailedNormalization,
    /// The project cannot be normalized because `poly_project_and_unify_type`
    /// is called recursively while normalizing the same projection.
    Recursive,
    // the projection can be normalized, but is not equal to the expected type.
    // Returns the type error that arose from the mismatch.
    MismatchedProjectionTypes(MismatchedProjectionTypes<'tcx>),
}

/// Evaluates constraints of the form:
/// ```ignore (not-rust)
/// for<...> <T as Trait>::U == V
/// ```
/// If successful, this may result in additional obligations. Also returns
/// the projection cache key used to track these additional obligations.
// FIXME(mgca): While this supports constants, it is only used for types by default right now
#[instrument(level = "debug", skip(selcx))]
pub(super) fn poly_project_and_unify_term<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &PolyProjectionObligation<'tcx>,
) -> ProjectAndUnifyResult<'tcx> {
    let infcx = selcx.infcx;
    let r = infcx.commit_if_ok(|_snapshot| {
        let placeholder_predicate = infcx.enter_forall_and_leak_universe(obligation.predicate);

        let placeholder_obligation = obligation.with(infcx.tcx, placeholder_predicate);
        match project_and_unify_term(selcx, &placeholder_obligation) {
            ProjectAndUnifyResult::MismatchedProjectionTypes(e) => Err(e),
            other => Ok(other),
        }
    });

    match r {
        Ok(inner) => inner,
        Err(err) => ProjectAndUnifyResult::MismatchedProjectionTypes(err),
    }
}

/// Evaluates constraints of the form:
/// ```ignore (not-rust)
/// <T as Trait>::U == V
/// ```
/// If successful, this may result in additional obligations.
///
/// See [poly_project_and_unify_term] for an explanation of the return value.
// FIXME(mgca): While this supports constants, it is only used for types by default right now
#[instrument(level = "debug", skip(selcx))]
fn project_and_unify_term<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionObligation<'tcx>,
) -> ProjectAndUnifyResult<'tcx> {
    let mut obligations = PredicateObligations::new();

    let infcx = selcx.infcx;
    let normalized = match opt_normalize_projection_term(
        selcx,
        obligation.param_env,
        obligation.predicate.projection_term,
        obligation.cause.clone(),
        obligation.recursion_depth,
        &mut obligations,
    ) {
        Ok(Some(n)) => n,
        Ok(None) => return ProjectAndUnifyResult::FailedNormalization,
        Err(InProgress) => return ProjectAndUnifyResult::Recursive,
    };
    debug!(?normalized, ?obligations, "project_and_unify_type result");
    let actual = obligation.predicate.term;
    // For an example where this is necessary see tests/ui/impl-trait/nested-return-type2.rs
    // This allows users to omit re-mentioning all bounds on an associated type and just use an
    // `impl Trait` for the assoc type to add more bounds.
    let InferOk { value: actual, obligations: new } =
        selcx.infcx.replace_opaque_types_with_inference_vars(
            actual,
            obligation.cause.body_id,
            obligation.cause.span,
            obligation.param_env,
        );
    obligations.extend(new);

    // Need to define opaque types to support nested opaque types like `impl Fn() -> impl Trait`
    match infcx.at(&obligation.cause, obligation.param_env).eq(
        DefineOpaqueTypes::Yes,
        normalized,
        actual,
    ) {
        Ok(InferOk { obligations: inferred_obligations, value: () }) => {
            obligations.extend(inferred_obligations);
            ProjectAndUnifyResult::Holds(obligations)
        }
        Err(err) => {
            debug!("equating types encountered error {:?}", err);
            ProjectAndUnifyResult::MismatchedProjectionTypes(MismatchedProjectionTypes { err })
        }
    }
}

/// The guts of `normalize`: normalize a specific projection like `<T
/// as Trait>::Item`. The result is always a type (and possibly
/// additional obligations). If ambiguity arises, which implies that
/// there are unresolved type variables in the projection, we will
/// instantiate it with a fresh type variable `$X` and generate a new
/// obligation `<T as Trait>::Item == $X` for later.
// FIXME(mgca): While this supports constants, it is only used for types by default right now
pub fn normalize_projection_term<'a, 'b, 'tcx>(
    selcx: &'a mut SelectionContext<'b, 'tcx>,
    param_env: ty::ParamEnv<'tcx>,
    alias_term: ty::AliasTerm<'tcx>,
    cause: ObligationCause<'tcx>,
    depth: usize,
    obligations: &mut PredicateObligations<'tcx>,
) -> Term<'tcx> {
    opt_normalize_projection_term(selcx, param_env, alias_term, cause.clone(), depth, obligations)
        .ok()
        .flatten()
        .unwrap_or_else(move || {
            // if we bottom out in ambiguity, create a type variable
            // and a deferred predicate to resolve this when more type
            // information is available.

            selcx.infcx.projection_term_to_infer(
                param_env,
                alias_term,
                cause,
                depth + 1,
                obligations,
            )
        })
}

/// The guts of `normalize`: normalize a specific projection like `<T
/// as Trait>::Item`. The result is always a type (and possibly
/// additional obligations). Returns `None` in the case of ambiguity,
/// which indicates that there are unbound type variables.
///
/// This function used to return `Option<NormalizedTy<'tcx>>`, which contains a
/// `Ty<'tcx>` and an obligations vector. But that obligation vector was very
/// often immediately appended to another obligations vector. So now this
/// function takes an obligations vector and appends to it directly, which is
/// slightly uglier but avoids the need for an extra short-lived allocation.
// FIXME(mgca): While this supports constants, it is only used for types by default right now
#[instrument(level = "debug", skip(selcx, param_env, cause, obligations))]
pub(super) fn opt_normalize_projection_term<'a, 'b, 'tcx>(
    selcx: &'a mut SelectionContext<'b, 'tcx>,
    param_env: ty::ParamEnv<'tcx>,
    projection_term: ty::AliasTerm<'tcx>,
    cause: ObligationCause<'tcx>,
    depth: usize,
    obligations: &mut PredicateObligations<'tcx>,
) -> Result<Option<Term<'tcx>>, InProgress> {
    let infcx = selcx.infcx;
    debug_assert!(!selcx.infcx.next_trait_solver());
    let projection_term = infcx.resolve_vars_if_possible(projection_term);
    let cache_key = ProjectionCacheKey::new(projection_term, param_env);

    // FIXME(#20304) For now, I am caching here, which is good, but it
    // means we don't capture the type variables that are created in
    // the case of ambiguity. Which means we may create a large stream
    // of such variables. OTOH, if we move the caching up a level, we
    // would not benefit from caching when proving `T: Trait<U=Foo>`
    // bounds. It might be the case that we want two distinct caches,
    // or else another kind of cache entry.
    let cache_entry = infcx.inner.borrow_mut().projection_cache().try_start(cache_key);
    match cache_entry {
        Ok(()) => debug!("no cache"),
        Err(ProjectionCacheEntry::Ambiguous) => {
            // If we found ambiguity the last time, that means we will continue
            // to do so until some type in the key changes (and we know it
            // hasn't, because we just fully resolved it).
            debug!("found cache entry: ambiguous");
            return Ok(None);
        }
        Err(ProjectionCacheEntry::InProgress) => {
            // Under lazy normalization, this can arise when
            // bootstrapping. That is, imagine an environment with a
            // where-clause like `A::B == u32`. Now, if we are asked
            // to normalize `A::B`, we will want to check the
            // where-clauses in scope. So we will try to unify `A::B`
            // with `A::B`, which can trigger a recursive
            // normalization.

            debug!("found cache entry: in-progress");

            // Cache that normalizing this projection resulted in a cycle. This
            // should ensure that, unless this happens within a snapshot that's
            // rolled back, fulfillment or evaluation will notice the cycle.
            infcx.inner.borrow_mut().projection_cache().recur(cache_key);
            return Err(InProgress);
        }
        Err(ProjectionCacheEntry::Recur) => {
            debug!("recur cache");
            return Err(InProgress);
        }
        Err(ProjectionCacheEntry::NormalizedTerm { ty, complete: _ }) => {
            // This is the hottest path in this function.
            //
            // If we find the value in the cache, then return it along
            // with the obligations that went along with it. Note
            // that, when using a fulfillment context, these
            // obligations could in principle be ignored: they have
            // already been registered when the cache entry was
            // created (and hence the new ones will quickly be
            // discarded as duplicated). But when doing trait
            // evaluation this is not the case, and dropping the trait
            // evaluations can causes ICEs (e.g., #43132).
            debug!(?ty, "found normalized ty");
            obligations.extend(ty.obligations);
            return Ok(Some(ty.value));
        }
        Err(ProjectionCacheEntry::Error) => {
            debug!("opt_normalize_projection_type: found error");
            let result = normalize_to_error(selcx, param_env, projection_term, cause, depth);
            obligations.extend(result.obligations);
            return Ok(Some(result.value));
        }
    }

    let obligation =
        Obligation::with_depth(selcx.tcx(), cause.clone(), depth, param_env, projection_term);

    match project(selcx, &obligation) {
        Ok(Projected::Progress(Progress {
            term: projected_term,
            obligations: mut projected_obligations,
        })) => {
            debug!("opt_normalize_projection_type: progress");
            // if projection succeeded, then what we get out of this
            // is also non-normalized (consider: it was derived from
            // an impl, where-clause etc) and hence we must
            // re-normalize it

            let projected_term = selcx.infcx.resolve_vars_if_possible(projected_term);

            let mut result = if projected_term.has_aliases() {
                let normalized_ty = normalize_with_depth_to(
                    selcx,
                    param_env,
                    cause,
                    depth + 1,
                    projected_term,
                    &mut projected_obligations,
                );

                Normalized { value: normalized_ty, obligations: projected_obligations }
            } else {
                Normalized { value: projected_term, obligations: projected_obligations }
            };

            let mut deduped = SsoHashSet::with_capacity(result.obligations.len());
            result.obligations.retain(|obligation| deduped.insert(obligation.clone()));

            infcx.inner.borrow_mut().projection_cache().insert_term(cache_key, result.clone());
            obligations.extend(result.obligations);
            Ok(Some(result.value))
        }
        Ok(Projected::NoProgress(projected_ty)) => {
            debug!("opt_normalize_projection_type: no progress");
            let result =
                Normalized { value: projected_ty, obligations: PredicateObligations::new() };
            infcx.inner.borrow_mut().projection_cache().insert_term(cache_key, result.clone());
            // No need to extend `obligations`.
            Ok(Some(result.value))
        }
        Err(ProjectionError::TooManyCandidates) => {
            debug!("opt_normalize_projection_type: too many candidates");
            infcx.inner.borrow_mut().projection_cache().ambiguous(cache_key);
            Ok(None)
        }
        Err(ProjectionError::TraitSelectionError(_)) => {
            debug!("opt_normalize_projection_type: ERROR");
            // if we got an error processing the `T as Trait` part,
            // just return `ty::err` but add the obligation `T :
            // Trait`, which when processed will cause the error to be
            // reported later
            infcx.inner.borrow_mut().projection_cache().error(cache_key);
            let result = normalize_to_error(selcx, param_env, projection_term, cause, depth);
            obligations.extend(result.obligations);
            Ok(Some(result.value))
        }
    }
}

/// If we are projecting `<T as Trait>::Item`, but `T: Trait` does not
/// hold. In various error cases, we cannot generate a valid
/// normalized projection. Therefore, we create an inference variable
/// return an associated obligation that, when fulfilled, will lead to
/// an error.
///
/// Note that we used to return `Error` here, but that was quite
/// dubious -- the premise was that an error would *eventually* be
/// reported, when the obligation was processed. But in general once
/// you see an `Error` you are supposed to be able to assume that an
/// error *has been* reported, so that you can take whatever heuristic
/// paths you want to take. To make things worse, it was possible for
/// cycles to arise, where you basically had a setup like `<MyType<$0>
/// as Trait>::Foo == $0`. Here, normalizing `<MyType<$0> as
/// Trait>::Foo>` to `[type error]` would lead to an obligation of
/// `<MyType<[type error]> as Trait>::Foo`. We are supposed to report
/// an error for this obligation, but we legitimately should not,
/// because it contains `[type error]`. Yuck! (See issue #29857 for
/// one case where this arose.)
// FIXME(mgca): While this supports constants, it is only used for types by default right now
fn normalize_to_error<'a, 'tcx>(
    selcx: &SelectionContext<'a, 'tcx>,
    param_env: ty::ParamEnv<'tcx>,
    projection_term: ty::AliasTerm<'tcx>,
    cause: ObligationCause<'tcx>,
    depth: usize,
) -> NormalizedTerm<'tcx> {
    let trait_ref = ty::Binder::dummy(projection_term.trait_ref(selcx.tcx()));
    let new_value = match projection_term.kind {
        ty::AliasTermKind::ProjectionTy { .. }
        | ty::AliasTermKind::InherentTy { .. }
        | ty::AliasTermKind::OpaqueTy { .. }
        | ty::AliasTermKind::FreeTy { .. } => selcx.infcx.next_ty_var(cause.span).into(),
        ty::AliasTermKind::FreeConst { .. }
        | ty::AliasTermKind::InherentConst { .. }
        | ty::AliasTermKind::AnonConst { .. }
        | ty::AliasTermKind::ProjectionConst { .. } => {
            selcx.infcx.next_const_var(cause.span).into()
        }
    };
    let mut obligations = PredicateObligations::new();
    obligations.push(Obligation {
        cause,
        recursion_depth: depth,
        param_env,
        predicate: trait_ref.upcast(selcx.tcx()),
    });
    Normalized { value: new_value, obligations }
}

/// When normalizing a const alias, register a `ConstArgHasType` obligation
/// to ensure the const value's type matches the declared type.
fn push_const_arg_has_type_obligation<'tcx>(
    tcx: TyCtxt<'tcx>,
    obligations: &mut PredicateObligations<'tcx>,
    cause: &ObligationCause<'tcx>,
    depth: usize,
    param_env: ty::ParamEnv<'tcx>,
    term: Term<'tcx>,
    def_id: DefId,
    args: ty::GenericArgsRef<'tcx>,
) {
    if let Some(ct) = term.as_const() {
        let expected_ty = tcx.type_of(def_id).instantiate(tcx, args).skip_norm_wip();
        obligations.push(Obligation::with_depth(
            tcx,
            cause.clone(),
            depth,
            param_env,
            ty::ClauseKind::ConstArgHasType(ct, expected_ty),
        ));
    }
}

/// Confirm and normalize the given inherent projection.
// FIXME(mgca): While this supports constants, it is only used for types by default right now
#[instrument(level = "debug", skip(selcx, param_env, cause, obligations))]
pub fn normalize_inherent_projection<'a, 'b, 'tcx>(
    selcx: &'a mut SelectionContext<'b, 'tcx>,
    param_env: ty::ParamEnv<'tcx>,
    alias_term: ty::AliasTerm<'tcx>,
    cause: ObligationCause<'tcx>,
    depth: usize,
    obligations: &mut PredicateObligations<'tcx>,
) -> ty::Term<'tcx> {
    let tcx = selcx.tcx();

    if !tcx.recursion_limit().value_within_limit(depth) {
        // Halt compilation because it is important that overflows never be masked.
        tcx.dcx().emit_fatal(InherentProjectionNormalizationOverflow {
            span: cause.span,
            ty: alias_term.to_string(),
        });
    }

    let args = compute_inherent_assoc_term_args(
        selcx,
        param_env,
        alias_term,
        cause.clone(),
        depth,
        obligations,
    );

    // Register the obligations arising from the impl and from the associated type itself.
    let def_id = alias_term.expect_inherent_def_id();
    let predicates = tcx.predicates_of(def_id).instantiate(tcx, args);
    for (predicate, span) in predicates {
        let predicate = normalize_with_depth_to(
            selcx,
            param_env,
            cause.clone(),
            depth + 1,
            predicate.skip_norm_wip(),
            obligations,
        );

        let nested_cause = ObligationCause::new(
            cause.span,
            cause.body_id,
            // FIXME(inherent_associated_types): Since we can't pass along the self type to the
            // cause code, inherent projections will be printed with identity instantiation in
            // diagnostics which is not ideal.
            // Consider creating separate cause codes for this specific situation.
            ObligationCauseCode::WhereClause(def_id, span),
        );

        obligations.push(Obligation::with_depth(
            tcx,
            nested_cause,
            depth + 1,
            param_env,
            predicate,
        ));
    }

    let term: Term<'tcx> = if alias_term.kind.is_type() {
        tcx.type_of(def_id).instantiate(tcx, args).skip_norm_wip().into()
    } else {
        tcx.const_of_item(def_id).instantiate(tcx, args).skip_norm_wip().into()
    };

    push_const_arg_has_type_obligation(
        tcx,
        obligations,
        &cause,
        depth + 1,
        param_env,
        term,
        def_id,
        args,
    );

    let mut term = selcx.infcx.resolve_vars_if_possible(term);
    if term.has_aliases() {
        term =
            normalize_with_depth_to(selcx, param_env, cause.clone(), depth + 1, term, obligations);
    }

    term
}

// FIXME(mgca): While this supports constants, it is only used for types by default right now
pub fn compute_inherent_assoc_term_args<'a, 'b, 'tcx>(
    selcx: &'a mut SelectionContext<'b, 'tcx>,
    param_env: ty::ParamEnv<'tcx>,
    alias_term: ty::AliasTerm<'tcx>,
    cause: ObligationCause<'tcx>,
    depth: usize,
    obligations: &mut PredicateObligations<'tcx>,
) -> ty::GenericArgsRef<'tcx> {
    let tcx = selcx.tcx();

    let alias_def_id = alias_term.expect_inherent_def_id();
    let impl_def_id = tcx.parent(alias_def_id);
    let impl_args = selcx.infcx.fresh_args_for_item(cause.span, impl_def_id);

    let mut impl_ty = tcx.type_of(impl_def_id).instantiate(tcx, impl_args).skip_norm_wip();
    if !selcx.infcx.next_trait_solver() {
        impl_ty = normalize_with_depth_to(
            selcx,
            param_env,
            cause.clone(),
            depth + 1,
            impl_ty,
            obligations,
        );
    }

    // Infer the generic parameters of the impl by unifying the
    // impl type with the self type of the projection.
    let mut self_ty = alias_term.self_ty();
    if !selcx.infcx.next_trait_solver() {
        self_ty = normalize_with_depth_to(
            selcx,
            param_env,
            cause.clone(),
            depth + 1,
            self_ty,
            obligations,
        );
    }

    match selcx.infcx.at(&cause, param_env).eq(DefineOpaqueTypes::Yes, impl_ty, self_ty) {
        Ok(mut ok) => obligations.append(&mut ok.obligations),
        Err(_) => {
            tcx.dcx().span_bug(
                cause.span,
                format!("{self_ty:?} was equal to {impl_ty:?} during selection but now it is not"),
            );
        }
    }

    alias_term.rebase_inherent_args_onto_impl(impl_args, tcx)
}

enum Projected<'tcx> {
    Progress(Progress<'tcx>),
    NoProgress(ty::Term<'tcx>),
}

struct Progress<'tcx> {
    term: ty::Term<'tcx>,
    obligations: PredicateObligations<'tcx>,
}

impl<'tcx> Progress<'tcx> {
    fn error_for_term(
        tcx: TyCtxt<'tcx>,
        alias_term: ty::AliasTerm<'tcx>,
        guar: ErrorGuaranteed,
    ) -> Self {
        let err_term = if alias_term.kind.is_type() {
            Ty::new_error(tcx, guar).into()
        } else {
            ty::Const::new_error(tcx, guar).into()
        };
        Progress { term: err_term, obligations: PredicateObligations::new() }
    }

    fn with_addl_obligations(mut self, mut obligations: PredicateObligations<'tcx>) -> Self {
        self.obligations.append(&mut obligations);
        self
    }
}

/// Computes the result of a projection type (if we can).
///
/// IMPORTANT:
/// - `obligation` must be fully normalized
// FIXME(mgca): While this supports constants, it is only used for types by default right now
#[instrument(level = "info", skip(selcx))]
fn project<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
) -> Result<Projected<'tcx>, ProjectionError<'tcx>> {
    if !selcx.tcx().recursion_limit().value_within_limit(obligation.recursion_depth) {
        // This should really be an immediate error, but some existing code
        // relies on being able to recover from this.
        return Err(ProjectionError::TraitSelectionError(SelectionError::Overflow(
            OverflowError::Canonical,
        )));
    }

    // We can still compute a projection type when there are only region errors,
    // but type/const errors require early return.
    if let Err(guar) = obligation.predicate.non_region_error_reported() {
        return Ok(Projected::Progress(Progress::error_for_term(
            selcx.tcx(),
            obligation.predicate,
            guar,
        )));
    }

    let mut candidates = ProjectionCandidateSet::None;

    // Make sure that the following procedures are kept in order. ParamEnv
    // needs to be first because it has highest priority, and Select checks
    // the return value of push_candidate which assumes it's ran at last.
    assemble_candidates_from_param_env(selcx, obligation, &mut candidates);

    assemble_candidates_from_trait_def(selcx, obligation, &mut candidates);

    assemble_candidates_from_object_ty(selcx, obligation, &mut candidates);

    if let ProjectionCandidateSet::Single(ProjectionCandidate::Object(_)) = candidates {
        // Avoid normalization cycle from selection (see
        // `assemble_candidates_from_object_ty`).
        // FIXME(lazy_normalization): Lazy normalization should save us from
        // having to special case this.
    } else {
        assemble_candidates_from_impls(selcx, obligation, &mut candidates);
    };

    match candidates {
        ProjectionCandidateSet::Single(candidate) => {
            confirm_candidate(selcx, obligation, candidate)
        }
        ProjectionCandidateSet::None => {
            let tcx = selcx.tcx();
            let term = obligation.predicate.to_term(tcx, ty::IsRigid::No);
            Ok(Projected::NoProgress(term))
        }
        // Error occurred while trying to processing impls.
        ProjectionCandidateSet::Error(e) => Err(ProjectionError::TraitSelectionError(e)),
        // Inherent ambiguity that prevents us from even enumerating the
        // candidates.
        ProjectionCandidateSet::Ambiguous => Err(ProjectionError::TooManyCandidates),
    }
}

/// The first thing we have to do is scan through the parameter
/// environment to see whether there are any projection predicates
/// there that can answer this question.
fn assemble_candidates_from_param_env<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    candidate_set: &mut ProjectionCandidateSet<'tcx>,
) {
    assemble_candidates_from_predicates(
        selcx,
        obligation,
        candidate_set,
        ProjectionCandidate::ParamEnv,
        obligation.param_env.caller_bounds().iter(),
        false,
    );
}

/// In the case of a nested projection like `<<A as Foo>::FooT as Bar>::BarT`, we may find
/// that the definition of `Foo` has some clues:
///
/// ```ignore (illustrative)
/// trait Foo {
///     type FooT : Bar<BarT=i32>
/// }
/// ```
///
/// Here, for example, we could conclude that the result is `i32`.
fn assemble_candidates_from_trait_def<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    candidate_set: &mut ProjectionCandidateSet<'tcx>,
) {
    debug!("assemble_candidates_from_trait_def(..)");
    let mut ambiguous = false;
    let _ = selcx.for_each_item_bound(
        obligation.predicate.self_ty(),
        |selcx, clause, _, _| {
            let Some(clause) = clause.as_projection_clause() else {
                return ControlFlow::Continue(());
            };
            if clause.item_def_id() != obligation.predicate.expect_projection_def_id() {
                return ControlFlow::Continue(());
            }

            let is_match =
                selcx.infcx.probe(|_| selcx.match_projection_projections(obligation, clause, true));

            match is_match {
                ProjectionMatchesProjection::Yes => {
                    candidate_set.push_candidate(ProjectionCandidate::TraitDef(clause));

                    if !obligation.predicate.has_non_region_infer() {
                        // HACK: Pick the first trait def candidate for a fully
                        // inferred predicate. This is to allow duplicates that
                        // differ only in normalization.
                        return ControlFlow::Break(());
                    }
                }
                ProjectionMatchesProjection::Ambiguous => {
                    candidate_set.mark_ambiguous();
                }
                ProjectionMatchesProjection::No => {}
            }

            ControlFlow::Continue(())
        },
        // `ProjectionCandidateSet` is borrowed in the above closure,
        // so just mark ambiguous outside of the closure.
        || ambiguous = true,
    );

    if ambiguous {
        candidate_set.mark_ambiguous();
    }
}

/// In the case of a trait object like
/// `<dyn Iterator<Item = ()> as Iterator>::Item` we can use the existential
/// predicate in the trait object.
///
/// We don't go through the select candidate for these bounds to avoid cycles:
/// In the above case, `dyn Iterator<Item = ()>: Iterator` would create a
/// nested obligation of `<dyn Iterator<Item = ()> as Iterator>::Item: Sized`,
/// this then has to be normalized without having to prove
/// `dyn Iterator<Item = ()>: Iterator` again.
fn assemble_candidates_from_object_ty<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    candidate_set: &mut ProjectionCandidateSet<'tcx>,
) {
    debug!("assemble_candidates_from_object_ty(..)");

    let tcx = selcx.tcx();

    let self_ty = obligation.predicate.self_ty();
    let object_ty = selcx.infcx.shallow_resolve(self_ty);
    let data = match object_ty.kind() {
        ty::Dynamic(data, ..) => data,
        ty::Infer(ty::TyVar(_)) => {
            // If the self-type is an inference variable, then it MAY wind up
            // being an object type, so induce an ambiguity.
            candidate_set.mark_ambiguous();
            return;
        }
        _ => return,
    };
    let env_predicates = data
        .projection_bounds()
        .filter(|bound| bound.item_def_id() == obligation.predicate.expect_projection_def_id())
        .map(|p| p.with_self_ty(tcx, object_ty).upcast(tcx));

    assemble_candidates_from_predicates(
        selcx,
        obligation,
        candidate_set,
        ProjectionCandidate::Object,
        env_predicates,
        false,
    );
}

#[instrument(
    level = "debug",
    skip(selcx, candidate_set, ctor, env_predicates, potentially_unnormalized_candidates)
)]
fn assemble_candidates_from_predicates<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    candidate_set: &mut ProjectionCandidateSet<'tcx>,
    ctor: fn(ty::PolyProjectionPredicate<'tcx>) -> ProjectionCandidate<'tcx>,
    env_predicates: impl Iterator<Item = ty::Clause<'tcx>>,
    potentially_unnormalized_candidates: bool,
) {
    let infcx = selcx.infcx;
    let drcx = DeepRejectCtxt::relate_rigid_rigid(selcx.tcx());
    for predicate in env_predicates {
        let bound_predicate = predicate.kind();
        if let ty::ClauseKind::Projection(data) = predicate.kind().skip_binder() {
            let data = bound_predicate.rebind(data);
            if data.item_def_id() != obligation.predicate.expect_projection_def_id() {
                continue;
            }

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

            let is_match = infcx.probe(|_| {
                selcx.match_projection_projections(
                    obligation,
                    data,
                    potentially_unnormalized_candidates,
                )
            });

            match is_match {
                ProjectionMatchesProjection::Yes => {
                    candidate_set.push_candidate(ctor(data));

                    if potentially_unnormalized_candidates
                        && !obligation.predicate.has_non_region_infer()
                    {
                        // HACK: Pick the first trait def candidate for a fully
                        // inferred predicate. This is to allow duplicates that
                        // differ only in normalization.
                        return;
                    }
                }
                ProjectionMatchesProjection::Ambiguous => {
                    candidate_set.mark_ambiguous();
                }
                ProjectionMatchesProjection::No => {}
            }
        }
    }
}

#[instrument(level = "debug", skip(selcx, obligation, candidate_set))]
fn assemble_candidates_from_impls<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    candidate_set: &mut ProjectionCandidateSet<'tcx>,
) {
    // If we are resolving `<T as TraitRef<...>>::Item == Type`,
    // start out by selecting the predicate `T as TraitRef<...>`:
    let trait_ref = obligation.predicate.trait_ref(selcx.tcx());
    let trait_obligation = obligation.with(selcx.tcx(), trait_ref);
    let _ = selcx.infcx.commit_if_ok(|_| {
        let impl_source = match selcx.select(&trait_obligation) {
            Ok(Some(impl_source)) => impl_source,
            Ok(None) => {
                candidate_set.mark_ambiguous();
                return Err(());
            }
            Err(e) => {
                debug!(error = ?e, "selection error");
                candidate_set.mark_error(e);
                return Err(());
            }
        };

        let eligible = match &impl_source {
            ImplSource::UserDefined(impl_data) => {
                // We have to be careful when projecting out of an
                // impl because of specialization. If we are not in
                // codegen (i.e., `TypingMode` is not `PostAnalysis`), and the
                // impl's type is declared as default, then we disable
                // projection (even if the trait ref is fully
                // monomorphic). In the case where trait ref is not
                // fully monomorphic (i.e., includes type parameters),
                // this is because those type parameters may
                // ultimately be bound to types from other crates that
                // may have specialized impls we can't see. In the
                // case where the trait ref IS fully monomorphic, this
                // is a policy decision that we made in the RFC in
                // order to preserve flexibility for the crate that
                // defined the specializable impl to specialize later
                // for existing types.
                //
                // In either case, we handle this by not adding a
                // candidate for an impl if it contains a `default`
                // type.
                //
                // NOTE: This should be kept in sync with the similar code in
                // `rustc_ty_utils::instance::resolve_associated_item()`.
                match specialization_graph::assoc_def(
                    selcx.tcx(),
                    impl_data.impl_def_id,
                    obligation.predicate.expect_projection_def_id(),
                ) {
                    Ok(node_item) => {
                        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.
                            match selcx.typing_mode() {
                                TypingMode::Coherence
                                | TypingMode::Typeck { .. }
                                | TypingMode::PostTypeckUntilBorrowck { .. }
                                | TypingMode::PostBorrowck { .. } => {
                                    debug!(
                                        assoc_ty = ?selcx.tcx().def_path_str(node_item.item.def_id),
                                        ?obligation.predicate,
                                        "not eligible due to default",
                                    );
                                    false
                                }
                                TypingMode::PostAnalysis | TypingMode::Codegen => {
                                    // NOTE(eddyb) inference variables can resolve to parameters, so
                                    // assume `poly_trait_ref` isn't monomorphic, if it contains any.
                                    let poly_trait_ref =
                                        selcx.infcx.resolve_vars_if_possible(trait_ref);
                                    !poly_trait_ref.still_further_specializable()
                                }
                            }
                        }
                    }
                    // Always project `ErrorGuaranteed`, since this will just help
                    // us propagate `TyKind::Error` around which suppresses ICEs
                    // and spurious, unrelated inference errors.
                    Err(ErrorGuaranteed { .. }) => true,
                }
            }
            ImplSource::Builtin(BuiltinImplSource::Misc | BuiltinImplSource::Trivial, _) => {
                // While a builtin impl may be known to exist, the associated type may not yet
                // be known. Any type with multiple potential associated types is therefore
                // not eligible.
                let self_ty = selcx.infcx.shallow_resolve(obligation.predicate.self_ty());

                let tcx = selcx.tcx();
                match selcx.tcx().as_lang_item(trait_ref.def_id) {
                    Some(
                        LangItem::Coroutine
                        | LangItem::Future
                        | LangItem::Iterator
                        | LangItem::AsyncIterator
                        | LangItem::Field
                        | LangItem::Fn
                        | LangItem::FnMut
                        | LangItem::FnOnce
                        | LangItem::AsyncFn
                        | LangItem::AsyncFnMut
                        | LangItem::AsyncFnOnce,
                    ) => true,
                    Some(LangItem::AsyncFnKindHelper) => {
                        // FIXME(async_closures): Validity constraints here could be cleaned up.
                        if obligation.predicate.args.type_at(0).is_ty_var()
                            || obligation.predicate.args.type_at(4).is_ty_var()
                            || obligation.predicate.args.type_at(5).is_ty_var()
                        {
                            candidate_set.mark_ambiguous();
                            true
                        } else {
                            obligation.predicate.args.type_at(0).to_opt_closure_kind().is_some()
                                && obligation
                                    .predicate
                                    .args
                                    .type_at(1)
                                    .to_opt_closure_kind()
                                    .is_some()
                        }
                    }
                    Some(LangItem::DiscriminantKind) => match self_ty.kind() {
                        ty::Bool
                        | ty::Char
                        | ty::Int(_)
                        | ty::Uint(_)
                        | ty::Float(_)
                        | ty::Adt(..)
                        | ty::Foreign(_)
                        | ty::Str
                        | ty::Array(..)
                        | ty::Pat(..)
                        | ty::Slice(_)
                        | ty::RawPtr(..)
                        | ty::Ref(..)
                        | ty::FnDef(..)
                        | ty::FnPtr(..)
                        | ty::Dynamic(..)
                        | ty::Closure(..)
                        | ty::CoroutineClosure(..)
                        | ty::Coroutine(..)
                        | ty::CoroutineWitness(..)
                        | ty::Never
                        | ty::Tuple(..)
                        // Integers and floats always have `u8` as their discriminant.
                        | ty::Infer(ty::InferTy::IntVar(_) | ty::InferTy::FloatVar(..)) => true,

                        ty::UnsafeBinder(_) => todo!("FIXME(unsafe_binder)"),

                        // type parameters, opaques, and unnormalized projections don't have
                        // a known discriminant and may need to be normalized further or rely
                        // on param env for discriminant projections
                        ty::Param(_)
                        | ty::Alias(..)
                        | ty::Bound(..)
                        | ty::Placeholder(..)
                        | ty::Infer(..)
                        | ty::Error(_) => false,
                    },
                    Some(LangItem::PointeeTrait) => {
                        let tail = selcx.tcx().struct_tail_raw(
                            self_ty,
                            &obligation.cause,
                            |ty| {
                                // We throw away any obligations we get from this, since we normalize
                                // and confirm these obligations once again during confirmation
                                normalize_with_depth(
                                    selcx,
                                    obligation.param_env,
                                    obligation.cause.clone(),
                                    obligation.recursion_depth + 1,
                                    ty.skip_norm_wip(),
                                )
                                .value
                            },
                            || {},
                        );

                        match tail.kind() {
                            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::Dynamic(..)
                            | ty::Closure(..)
                            | ty::CoroutineClosure(..)
                            | ty::Coroutine(..)
                            | ty::CoroutineWitness(..)
                            | ty::Never
                            // Extern types have unit metadata, according to RFC 2850
                            | ty::Foreign(_)
                            // If returned by `struct_tail` this is a unit struct
                            // without any fields, or not a struct, and therefore is Sized.
                            | ty::Adt(..)
                            // If returned by `struct_tail` this is the empty tuple.
                            | ty::Tuple(..)
                            // Integers and floats are always Sized, and so have unit type metadata.
                            | ty::Infer(ty::InferTy::IntVar(_) | ty::InferTy::FloatVar(..))
                            // This happens if we reach the recursion limit when finding the struct tail.
                            | ty::Error(..) => true,

                            // We normalize from `Wrapper<Tail>::Metadata` to `Tail::Metadata` if able.
                            // Otherwise, type parameters, opaques, and unnormalized projections have
                            // unit metadata if they're known (e.g. by the param_env) to be sized.
                            ty::Param(_) | ty::Alias(..)
                                if self_ty != tail
                                    || selcx.infcx.predicate_must_hold_modulo_regions(
                                        &obligation.with(
                                            selcx.tcx(),
                                            ty::TraitRef::new(
                                                selcx.tcx(),
                                                selcx.tcx().require_lang_item(
                                                    LangItem::Sized,
                                                    obligation.cause.span,
                                                ),
                                                [self_ty],
                                            ),
                                        ),
                                    ) =>
                            {
                                true
                            }

                            ty::UnsafeBinder(_) => todo!("FIXME(unsafe_binder)"),

                            // FIXME(compiler-errors): are Bound and Placeholder types ever known sized?
                            ty::Param(_)
                            | ty::Alias(..)
                            | ty::Bound(..)
                            | ty::Placeholder(..)
                            | ty::Infer(..) => {
                                if tail.has_infer_types() {
                                    candidate_set.mark_ambiguous();
                                }
                                false
                            }
                        }
                    }
                    _ if tcx.trait_is_auto(trait_ref.def_id) => {
                        tcx.dcx().span_delayed_bug(
                            tcx.def_span(obligation.predicate.expect_projection_def_id()),
                            "associated types not allowed on auto traits",
                        );
                        false
                    }
                    _ => {
                        bug!("unexpected builtin trait with associated type: {trait_ref:?}")
                    }
                }
            }
            ImplSource::Param(..) => {
                // This case tell us nothing about the value of an
                // associated type. Consider:
                //
                // ```
                // trait SomeTrait { type Foo; }
                // fn foo<T:SomeTrait>(...) { }
                // ```
                //
                // If the user writes `<T as SomeTrait>::Foo`, then the `T
                // : SomeTrait` binding does not help us decide what the
                // type `Foo` is (at least, not more specifically than
                // what we already knew).
                //
                // But wait, you say! What about an example like this:
                //
                // ```
                // fn bar<T:SomeTrait<Foo=usize>>(...) { ... }
                // ```
                //
                // Doesn't the `T : SomeTrait<Foo=usize>` predicate help
                // resolve `T::Foo`? And of course it does, but in fact
                // that single predicate is desugared into two predicates
                // in the compiler: a trait predicate (`T : SomeTrait`) and a
                // projection. And the projection where clause is handled
                // in `assemble_candidates_from_param_env`.
                false
            }
            ImplSource::Builtin(BuiltinImplSource::Object { .. }, _) => {
                // Handled by the `Object` projection candidate. See
                // `assemble_candidates_from_object_ty` for an explanation of
                // why we special case object types.
                false
            }
            ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _) => {
                // These traits have no associated types.
                selcx.tcx().dcx().span_delayed_bug(
                    obligation.cause.span,
                    format!("Cannot project an associated type from `{impl_source:?}`"),
                );
                return Err(());
            }
        };

        if eligible {
            if candidate_set.push_candidate(ProjectionCandidate::Select(impl_source)) {
                Ok(())
            } else {
                Err(())
            }
        } else {
            Err(())
        }
    });
}

// FIXME(mgca): While this supports constants, it is only used for types by default right now
fn confirm_candidate<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    candidate: ProjectionCandidate<'tcx>,
) -> Result<Projected<'tcx>, ProjectionError<'tcx>> {
    debug!(?obligation, ?candidate, "confirm_candidate");
    let mut result = match candidate {
        ProjectionCandidate::ParamEnv(poly_projection)
        | ProjectionCandidate::Object(poly_projection) => Ok(Projected::Progress(
            confirm_param_env_candidate(selcx, obligation, poly_projection, false),
        )),
        ProjectionCandidate::TraitDef(poly_projection) => Ok(Projected::Progress(
            confirm_param_env_candidate(selcx, obligation, poly_projection, true),
        )),
        ProjectionCandidate::Select(impl_source) => {
            confirm_select_candidate(selcx, obligation, impl_source)
        }
    };

    // When checking for cycle during evaluation, we compare predicates with
    // "syntactic" equality. Since normalization generally introduces a type
    // with new region variables, we need to resolve them to existing variables
    // when possible for this to work. See `auto-trait-projection-recursion.rs`
    // for a case where this matters.
    if let Ok(Projected::Progress(progress)) = &mut result
        && progress.term.has_infer_regions()
    {
        progress.term = progress.term.fold_with(&mut OpportunisticRegionResolver::new(selcx.infcx));
    }

    result
}

// FIXME(mgca): While this supports constants, it is only used for types by default right now
fn confirm_select_candidate<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    impl_source: Selection<'tcx>,
) -> Result<Projected<'tcx>, ProjectionError<'tcx>> {
    match impl_source {
        ImplSource::UserDefined(data) => confirm_impl_candidate(selcx, obligation, data),
        ImplSource::Builtin(BuiltinImplSource::Misc | BuiltinImplSource::Trivial, data) => {
            let tcx = selcx.tcx();
            let trait_def_id = obligation.predicate.trait_def_id(tcx);
            let progress = if tcx.is_lang_item(trait_def_id, LangItem::Coroutine) {
                confirm_coroutine_candidate(selcx, obligation, data)
            } else if tcx.is_lang_item(trait_def_id, LangItem::Future) {
                confirm_future_candidate(selcx, obligation, data)
            } else if tcx.is_lang_item(trait_def_id, LangItem::Iterator) {
                confirm_iterator_candidate(selcx, obligation, data)
            } else if tcx.is_lang_item(trait_def_id, LangItem::AsyncIterator) {
                confirm_async_iterator_candidate(selcx, obligation, data)
            } else if selcx.tcx().fn_trait_kind_from_def_id(trait_def_id).is_some() {
                if obligation.predicate.self_ty().is_closure()
                    || obligation.predicate.self_ty().is_coroutine_closure()
                {
                    confirm_closure_candidate(selcx, obligation, data)
                } else {
                    confirm_fn_pointer_candidate(selcx, obligation, data)
                }
            } else if selcx.tcx().async_fn_trait_kind_from_def_id(trait_def_id).is_some() {
                confirm_async_closure_candidate(selcx, obligation, data)
            } else if tcx.is_lang_item(trait_def_id, LangItem::AsyncFnKindHelper) {
                confirm_async_fn_kind_helper_candidate(selcx, obligation, data)
            } else {
                confirm_builtin_candidate(selcx, obligation, data)
            };
            Ok(Projected::Progress(progress))
        }
        ImplSource::Builtin(BuiltinImplSource::Object { .. }, _)
        | ImplSource::Param(..)
        | ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _) => {
            // we don't create Select candidates with this kind of resolution
            span_bug!(
                obligation.cause.span,
                "Cannot project an associated type from `{:?}`",
                impl_source
            )
        }
    }
}

fn confirm_coroutine_candidate<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    nested: PredicateObligations<'tcx>,
) -> Progress<'tcx> {
    let self_ty = selcx.infcx.shallow_resolve(obligation.predicate.self_ty());
    let ty::Coroutine(_, args) = self_ty.kind() else {
        unreachable!(
            "expected coroutine self type for built-in coroutine candidate, found {self_ty}"
        )
    };
    let coroutine_sig = args.as_coroutine().sig();
    let Normalized { value: coroutine_sig, obligations } = normalize_with_depth(
        selcx,
        obligation.param_env,
        obligation.cause.clone(),
        obligation.recursion_depth + 1,
        coroutine_sig,
    );

    debug!(?obligation, ?coroutine_sig, ?obligations, "confirm_coroutine_candidate");

    let tcx = selcx.tcx();

    let coroutine_def_id = tcx.require_lang_item(LangItem::Coroutine, obligation.cause.span);

    let (trait_ref, yield_ty, return_ty) = super::util::coroutine_trait_ref_and_outputs(
        tcx,
        coroutine_def_id,
        obligation.predicate.self_ty(),
        coroutine_sig,
    );

    let def_id = obligation.predicate.expect_projection_def_id();
    let ty = if tcx.is_lang_item(def_id, LangItem::CoroutineReturn) {
        return_ty
    } else if tcx.is_lang_item(def_id, LangItem::CoroutineYield) {
        yield_ty
    } else {
        span_bug!(
            tcx.def_span(def_id),
            "unexpected associated type: `Coroutine::{}`",
            tcx.item_name(def_id),
        );
    };

    let predicate = ty::ProjectionPredicate {
        projection_term: obligation.predicate.with_args(tcx, trait_ref.args),
        term: ty.into(),
    };

    confirm_param_env_candidate(selcx, obligation, ty::Binder::dummy(predicate), false)
        .with_addl_obligations(nested)
        .with_addl_obligations(obligations)
}

fn confirm_future_candidate<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    nested: PredicateObligations<'tcx>,
) -> Progress<'tcx> {
    let self_ty = selcx.infcx.shallow_resolve(obligation.predicate.self_ty());
    let ty::Coroutine(_, args) = self_ty.kind() else {
        unreachable!(
            "expected coroutine self type for built-in async future candidate, found {self_ty}"
        )
    };
    let coroutine_sig = args.as_coroutine().sig();
    let Normalized { value: coroutine_sig, obligations } = normalize_with_depth(
        selcx,
        obligation.param_env,
        obligation.cause.clone(),
        obligation.recursion_depth + 1,
        coroutine_sig,
    );

    debug!(?obligation, ?coroutine_sig, ?obligations, "confirm_future_candidate");

    let tcx = selcx.tcx();
    let fut_def_id = tcx.require_lang_item(LangItem::Future, obligation.cause.span);

    let (trait_ref, return_ty) = super::util::future_trait_ref_and_outputs(
        tcx,
        fut_def_id,
        obligation.predicate.self_ty(),
        coroutine_sig,
    );

    debug_assert_eq!(
        tcx.associated_item(obligation.predicate.expect_projection_def_id()).name(),
        sym::Output
    );

    let predicate = ty::ProjectionPredicate {
        projection_term: obligation.predicate.with_args(tcx, trait_ref.args),
        term: return_ty.into(),
    };

    confirm_param_env_candidate(selcx, obligation, ty::Binder::dummy(predicate), false)
        .with_addl_obligations(nested)
        .with_addl_obligations(obligations)
}

fn confirm_iterator_candidate<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    nested: PredicateObligations<'tcx>,
) -> Progress<'tcx> {
    let self_ty = selcx.infcx.shallow_resolve(obligation.predicate.self_ty());
    let ty::Coroutine(_, args) = self_ty.kind() else {
        unreachable!("expected coroutine self type for built-in gen candidate, found {self_ty}")
    };
    let gen_sig = args.as_coroutine().sig();
    let Normalized { value: gen_sig, obligations } = normalize_with_depth(
        selcx,
        obligation.param_env,
        obligation.cause.clone(),
        obligation.recursion_depth + 1,
        gen_sig,
    );

    debug!(?obligation, ?gen_sig, ?obligations, "confirm_iterator_candidate");

    let tcx = selcx.tcx();
    let iter_def_id = tcx.require_lang_item(LangItem::Iterator, obligation.cause.span);

    let (trait_ref, yield_ty) = super::util::iterator_trait_ref_and_outputs(
        tcx,
        iter_def_id,
        obligation.predicate.self_ty(),
        gen_sig,
    );

    debug_assert_eq!(
        tcx.associated_item(obligation.predicate.expect_projection_def_id()).name(),
        sym::Item
    );

    let predicate = ty::ProjectionPredicate {
        projection_term: obligation.predicate.with_args(tcx, trait_ref.args),
        term: yield_ty.into(),
    };

    confirm_param_env_candidate(selcx, obligation, ty::Binder::dummy(predicate), false)
        .with_addl_obligations(nested)
        .with_addl_obligations(obligations)
}

fn confirm_async_iterator_candidate<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    nested: PredicateObligations<'tcx>,
) -> Progress<'tcx> {
    let ty::Coroutine(_, args) = selcx.infcx.shallow_resolve(obligation.predicate.self_ty()).kind()
    else {
        unreachable!()
    };
    let gen_sig = args.as_coroutine().sig();
    let Normalized { value: gen_sig, obligations } = normalize_with_depth(
        selcx,
        obligation.param_env,
        obligation.cause.clone(),
        obligation.recursion_depth + 1,
        gen_sig,
    );

    debug!(?obligation, ?gen_sig, ?obligations, "confirm_async_iterator_candidate");

    let tcx = selcx.tcx();
    let iter_def_id = tcx.require_lang_item(LangItem::AsyncIterator, obligation.cause.span);

    let (trait_ref, yield_ty) = super::util::async_iterator_trait_ref_and_outputs(
        tcx,
        iter_def_id,
        obligation.predicate.self_ty(),
        gen_sig,
    );

    debug_assert_eq!(
        tcx.associated_item(obligation.predicate.expect_projection_def_id()).name(),
        sym::Item
    );

    let ty::Adt(_poll_adt, args) = *yield_ty.kind() else {
        bug!();
    };
    let ty::Adt(_option_adt, args) = *args.type_at(0).kind() else {
        bug!();
    };
    let item_ty = args.type_at(0);

    let predicate = ty::ProjectionPredicate {
        projection_term: obligation.predicate.with_args(tcx, trait_ref.args),
        term: item_ty.into(),
    };

    confirm_param_env_candidate(selcx, obligation, ty::Binder::dummy(predicate), false)
        .with_addl_obligations(nested)
        .with_addl_obligations(obligations)
}

fn confirm_builtin_candidate<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    data: PredicateObligations<'tcx>,
) -> Progress<'tcx> {
    let tcx = selcx.tcx();
    let self_ty = obligation.predicate.self_ty();
    let item_def_id = obligation.predicate.expect_projection_def_id();
    let trait_def_id = tcx.parent(item_def_id);
    let args = tcx.mk_args(&[self_ty.into()]);
    let (term, obligations) = if tcx.is_lang_item(trait_def_id, LangItem::DiscriminantKind) {
        let discriminant_def_id =
            tcx.require_lang_item(LangItem::Discriminant, obligation.cause.span);
        assert_eq!(discriminant_def_id, item_def_id);

        (self_ty.discriminant_ty(tcx).into(), PredicateObligations::new())
    } else if tcx.is_lang_item(trait_def_id, LangItem::PointeeTrait) {
        let metadata_def_id = tcx.require_lang_item(LangItem::Metadata, obligation.cause.span);
        assert_eq!(metadata_def_id, item_def_id);

        let mut obligations = PredicateObligations::new();
        let normalize = |ty: ty::Unnormalized<'tcx, Ty<'tcx>>| {
            normalize_with_depth_to(
                selcx,
                obligation.param_env,
                obligation.cause.clone(),
                obligation.recursion_depth + 1,
                ty.skip_norm_wip(),
                &mut obligations,
            )
        };
        let metadata_ty = self_ty.ptr_metadata_ty_or_tail(tcx, normalize).unwrap_or_else(|tail| {
            if tail == self_ty {
                // This is the "fallback impl" for type parameters, unnormalizable projections
                // and opaque types: If the `self_ty` is `Sized`, then the metadata is `()`.
                // FIXME(ptr_metadata): This impl overlaps with the other impls and shouldn't
                // exist. Instead, `Pointee<Metadata = ()>` should be a supertrait of `Sized`.
                let sized_predicate = ty::TraitRef::new(
                    tcx,
                    tcx.require_lang_item(LangItem::Sized, obligation.cause.span),
                    [self_ty],
                );
                obligations.push(obligation.with(tcx, sized_predicate));
                tcx.types.unit
            } else {
                // We know that `self_ty` has the same metadata as `tail`. This allows us
                // to prove predicates like `Wrapper<Tail>::Metadata == Tail::Metadata`.
                Ty::new_projection(tcx, ty::IsRigid::No, metadata_def_id, [tail])
            }
        });
        (metadata_ty.into(), obligations)
    } else if tcx.is_lang_item(trait_def_id, LangItem::Field) {
        let ty::Adt(def, args) = self_ty.kind() else {
            bug!("only field representing types can implement `Field`")
        };
        let Some(FieldInfo { base, ty, .. }) = def.field_representing_type_info(tcx, args) else {
            bug!("only field representing types can implement `Field`")
        };
        if tcx.is_lang_item(item_def_id, LangItem::FieldBase) {
            (base.into(), PredicateObligations::new())
        } else if tcx.is_lang_item(item_def_id, LangItem::FieldType) {
            (ty.into(), PredicateObligations::new())
        } else {
            bug!("unexpected associated type {:?} in `Field`", obligation.predicate);
        }
    } else {
        bug!("unexpected builtin trait with associated type: {:?}", obligation.predicate);
    };

    let predicate = ty::ProjectionPredicate {
        projection_term: ty::AliasTerm::new_from_args(
            tcx,
            ty::AliasTermKind::ProjectionTy { def_id: item_def_id },
            args,
        ),
        term,
    };

    confirm_param_env_candidate(selcx, obligation, ty::Binder::dummy(predicate), false)
        .with_addl_obligations(obligations)
        .with_addl_obligations(data)
}

fn confirm_fn_pointer_candidate<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    nested: PredicateObligations<'tcx>,
) -> Progress<'tcx> {
    let tcx = selcx.tcx();
    let fn_type = selcx.infcx.shallow_resolve(obligation.predicate.self_ty());
    let sig = fn_type.fn_sig(tcx);
    let Normalized { value: sig, obligations } = normalize_with_depth(
        selcx,
        obligation.param_env,
        obligation.cause.clone(),
        obligation.recursion_depth + 1,
        sig,
    );

    confirm_callable_candidate(selcx, obligation, sig, util::TupleArgumentsFlag::Yes)
        .with_addl_obligations(nested)
        .with_addl_obligations(obligations)
}

fn confirm_closure_candidate<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    nested: PredicateObligations<'tcx>,
) -> Progress<'tcx> {
    let tcx = selcx.tcx();
    let self_ty = selcx.infcx.shallow_resolve(obligation.predicate.self_ty());
    let closure_sig = match *self_ty.kind() {
        ty::Closure(_, args) => args.as_closure().sig(),

        // Construct a "normal" `FnOnce` signature for coroutine-closure. This is
        // basically duplicated with the `AsyncFnOnce::CallOnce` confirmation, but
        // I didn't see a good way to unify those.
        ty::CoroutineClosure(def_id, args) => {
            let args = args.as_coroutine_closure();
            let kind_ty = args.kind_ty();
            args.coroutine_closure_sig().map_bound(|sig| {
                // If we know the kind and upvars, use that directly.
                // Otherwise, defer to `AsyncFnKindHelper::Upvars` to delay
                // the projection, like the `AsyncFn*` traits do.
                let output_ty = if let Some(_) = kind_ty.to_opt_closure_kind()
                    // Fall back to projection if upvars aren't constrained
                    && !args.tupled_upvars_ty().is_ty_var()
                {
                    sig.to_coroutine_given_kind_and_upvars(
                        tcx,
                        args.parent_args(),
                        tcx.coroutine_for_closure(def_id),
                        ty::ClosureKind::FnOnce,
                        tcx.lifetimes.re_static,
                        args.tupled_upvars_ty(),
                        args.coroutine_captures_by_ref_ty(),
                    )
                } else {
                    let upvars_projection_def_id =
                        tcx.require_lang_item(LangItem::AsyncFnKindUpvars, obligation.cause.span);
                    let tupled_upvars_ty = Ty::new_projection(
                        tcx,
                        ty::IsRigid::No,
                        upvars_projection_def_id,
                        [
                            ty::GenericArg::from(kind_ty),
                            Ty::from_closure_kind(tcx, ty::ClosureKind::FnOnce).into(),
                            tcx.lifetimes.re_static.into(),
                            sig.tupled_inputs_ty.into(),
                            args.tupled_upvars_ty().into(),
                            args.coroutine_captures_by_ref_ty().into(),
                        ],
                    );
                    sig.to_coroutine(
                        tcx,
                        args.parent_args(),
                        Ty::from_closure_kind(tcx, ty::ClosureKind::FnOnce),
                        tcx.coroutine_for_closure(def_id),
                        tupled_upvars_ty,
                    )
                };

                tcx.mk_fn_sig([sig.tupled_inputs_ty], output_ty, sig.fn_sig_kind)
            })
        }

        _ => {
            unreachable!("expected closure self type for closure candidate, found {self_ty}");
        }
    };

    let Normalized { value: closure_sig, obligations } = normalize_with_depth(
        selcx,
        obligation.param_env,
        obligation.cause.clone(),
        obligation.recursion_depth + 1,
        closure_sig,
    );

    debug!(?obligation, ?closure_sig, ?obligations, "confirm_closure_candidate");

    confirm_callable_candidate(selcx, obligation, closure_sig, util::TupleArgumentsFlag::No)
        .with_addl_obligations(nested)
        .with_addl_obligations(obligations)
}

fn confirm_callable_candidate<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    fn_sig: ty::PolyFnSig<'tcx>,
    flag: util::TupleArgumentsFlag,
) -> Progress<'tcx> {
    let tcx = selcx.tcx();

    debug!(?obligation, ?fn_sig, "confirm_callable_candidate");

    let fn_once_def_id = tcx.require_lang_item(LangItem::FnOnce, obligation.cause.span);
    let fn_once_output_def_id =
        tcx.require_lang_item(LangItem::FnOnceOutput, obligation.cause.span);

    let predicate = super::util::closure_trait_ref_and_return_type(
        tcx,
        fn_once_def_id,
        obligation.predicate.self_ty(),
        fn_sig,
        flag,
    )
    .map_bound(|(trait_ref, ret_type)| ty::ProjectionPredicate {
        projection_term: ty::AliasTerm::new_from_args(
            tcx,
            ty::AliasTermKind::ProjectionTy { def_id: fn_once_output_def_id },
            trait_ref.args,
        ),
        term: ret_type.into(),
    });

    confirm_param_env_candidate(selcx, obligation, predicate, true)
}

fn confirm_async_closure_candidate<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    nested: PredicateObligations<'tcx>,
) -> Progress<'tcx> {
    let tcx = selcx.tcx();
    let self_ty = selcx.infcx.shallow_resolve(obligation.predicate.self_ty());

    let goal_kind =
        tcx.async_fn_trait_kind_from_def_id(obligation.predicate.trait_def_id(tcx)).unwrap();
    let env_region = match goal_kind {
        ty::ClosureKind::Fn | ty::ClosureKind::FnMut => obligation.predicate.args.region_at(2),
        ty::ClosureKind::FnOnce => tcx.lifetimes.re_static,
    };
    let item_name = tcx.item_name(obligation.predicate.expect_projection_def_id());

    let poly_cache_entry = match *self_ty.kind() {
        ty::CoroutineClosure(def_id, args) => {
            let args = args.as_coroutine_closure();
            let kind_ty = args.kind_ty();
            let sig = args.coroutine_closure_sig().skip_binder();

            let term = match item_name {
                sym::CallOnceFuture | sym::CallRefFuture => {
                    if let Some(closure_kind) = kind_ty.to_opt_closure_kind()
                        // Fall back to projection if upvars aren't constrained
                        && !args.tupled_upvars_ty().is_ty_var()
                    {
                        if !closure_kind.extends(goal_kind) {
                            bug!("we should not be confirming if the closure kind is not met");
                        }
                        sig.to_coroutine_given_kind_and_upvars(
                            tcx,
                            args.parent_args(),
                            tcx.coroutine_for_closure(def_id),
                            goal_kind,
                            env_region,
                            args.tupled_upvars_ty(),
                            args.coroutine_captures_by_ref_ty(),
                        )
                    } else {
                        let upvars_projection_def_id = tcx
                            .require_lang_item(LangItem::AsyncFnKindUpvars, obligation.cause.span);
                        // When we don't know the closure kind (and therefore also the closure's upvars,
                        // which are computed at the same time), we must delay the computation of the
                        // generator's upvars. We do this using the `AsyncFnKindHelper`, which as a trait
                        // goal functions similarly to the old `ClosureKind` predicate, and ensures that
                        // the goal kind <= the closure kind. As a projection `AsyncFnKindHelper::Upvars`
                        // will project to the right upvars for the generator, appending the inputs and
                        // coroutine upvars respecting the closure kind.
                        // N.B. No need to register a `AsyncFnKindHelper` goal here, it's already in `nested`.
                        let tupled_upvars_ty = Ty::new_projection(
                            tcx,
                            ty::IsRigid::No,
                            upvars_projection_def_id,
                            [
                                ty::GenericArg::from(kind_ty),
                                Ty::from_closure_kind(tcx, goal_kind).into(),
                                env_region.into(),
                                sig.tupled_inputs_ty.into(),
                                args.tupled_upvars_ty().into(),
                                args.coroutine_captures_by_ref_ty().into(),
                            ],
                        );
                        sig.to_coroutine(
                            tcx,
                            args.parent_args(),
                            Ty::from_closure_kind(tcx, goal_kind),
                            tcx.coroutine_for_closure(def_id),
                            tupled_upvars_ty,
                        )
                    }
                }
                sym::Output => sig.return_ty,
                name => bug!("no such associated type: {name}"),
            };
            let projection_term = match item_name {
                sym::CallOnceFuture | sym::Output => ty::AliasTerm::new(
                    tcx,
                    obligation.predicate.kind,
                    [self_ty, sig.tupled_inputs_ty],
                ),
                sym::CallRefFuture => ty::AliasTerm::new(
                    tcx,
                    obligation.predicate.kind,
                    [ty::GenericArg::from(self_ty), sig.tupled_inputs_ty.into(), env_region.into()],
                ),
                name => bug!("no such associated type: {name}"),
            };

            args.coroutine_closure_sig()
                .rebind(ty::ProjectionPredicate { projection_term, term: term.into() })
        }
        ty::FnDef(..) | ty::FnPtr(..) => {
            let bound_sig = self_ty.fn_sig(tcx);
            let sig = bound_sig.skip_binder();

            let term = match item_name {
                sym::CallOnceFuture | sym::CallRefFuture => sig.output(),
                sym::Output => {
                    let future_output_def_id =
                        tcx.require_lang_item(LangItem::FutureOutput, obligation.cause.span);
                    Ty::new_projection(tcx, ty::IsRigid::No, future_output_def_id, [sig.output()])
                }
                name => bug!("no such associated type: {name}"),
            };
            let projection_term = match item_name {
                sym::CallOnceFuture | sym::Output => ty::AliasTerm::new(
                    tcx,
                    obligation.predicate.kind,
                    [self_ty, Ty::new_tup(tcx, sig.inputs())],
                ),
                sym::CallRefFuture => ty::AliasTerm::new(
                    tcx,
                    obligation.predicate.kind,
                    [
                        ty::GenericArg::from(self_ty),
                        Ty::new_tup(tcx, sig.inputs()).into(),
                        env_region.into(),
                    ],
                ),
                name => bug!("no such associated type: {name}"),
            };

            bound_sig.rebind(ty::ProjectionPredicate { projection_term, term: term.into() })
        }
        ty::Closure(_, args) => {
            let args = args.as_closure();
            let bound_sig = args.sig();
            let sig = bound_sig.skip_binder();

            let term = match item_name {
                sym::CallOnceFuture | sym::CallRefFuture => sig.output(),
                sym::Output => {
                    let future_output_def_id =
                        tcx.require_lang_item(LangItem::FutureOutput, obligation.cause.span);
                    Ty::new_projection(tcx, ty::IsRigid::No, future_output_def_id, [sig.output()])
                }
                name => bug!("no such associated type: {name}"),
            };
            let projection_term = match item_name {
                sym::CallOnceFuture | sym::Output => {
                    ty::AliasTerm::new(tcx, obligation.predicate.kind, [self_ty, sig.inputs()[0]])
                }
                sym::CallRefFuture => ty::AliasTerm::new(
                    tcx,
                    obligation.predicate.kind,
                    [ty::GenericArg::from(self_ty), sig.inputs()[0].into(), env_region.into()],
                ),
                name => bug!("no such associated type: {name}"),
            };

            bound_sig.rebind(ty::ProjectionPredicate { projection_term, term: term.into() })
        }
        _ => bug!("expected callable type for AsyncFn candidate"),
    };

    confirm_param_env_candidate(selcx, obligation, poly_cache_entry, true)
        .with_addl_obligations(nested)
}

fn confirm_async_fn_kind_helper_candidate<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    nested: PredicateObligations<'tcx>,
) -> Progress<'tcx> {
    let [
        // We already checked that the goal_kind >= closure_kind
        _closure_kind_ty,
        goal_kind_ty,
        borrow_region,
        tupled_inputs_ty,
        tupled_upvars_ty,
        coroutine_captures_by_ref_ty,
    ] = **obligation.predicate.args
    else {
        bug!();
    };

    let predicate = ty::ProjectionPredicate {
        projection_term: obligation.predicate.with_args(selcx.tcx(), obligation.predicate.args),
        term: ty::CoroutineClosureSignature::tupled_upvars_by_closure_kind(
            selcx.tcx(),
            goal_kind_ty.expect_ty().to_opt_closure_kind().unwrap(),
            tupled_inputs_ty.expect_ty(),
            tupled_upvars_ty.expect_ty(),
            coroutine_captures_by_ref_ty.expect_ty(),
            borrow_region.expect_region(),
        )
        .into(),
    };

    confirm_param_env_candidate(selcx, obligation, ty::Binder::dummy(predicate), false)
        .with_addl_obligations(nested)
}

// FIXME(mgca): While this supports constants, it is only used for types by default right now
fn confirm_param_env_candidate<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    poly_cache_entry: ty::PolyProjectionPredicate<'tcx>,
    potentially_unnormalized_candidate: bool,
) -> Progress<'tcx> {
    let infcx = selcx.infcx;
    let cause = &obligation.cause;
    let param_env = obligation.param_env;

    let cache_entry = infcx.instantiate_binder_with_fresh_vars(
        cause.span,
        BoundRegionConversionTime::HigherRankedType,
        poly_cache_entry,
    );

    let cache_projection = cache_entry.projection_term;
    let mut nested_obligations = PredicateObligations::new();
    let obligation_projection = obligation.predicate;
    let obligation_projection = ensure_sufficient_stack(|| {
        normalize_with_depth_to(
            selcx,
            obligation.param_env,
            obligation.cause.clone(),
            obligation.recursion_depth + 1,
            obligation_projection,
            &mut nested_obligations,
        )
    });
    let cache_projection = if potentially_unnormalized_candidate {
        ensure_sufficient_stack(|| {
            normalize_with_depth_to(
                selcx,
                obligation.param_env,
                obligation.cause.clone(),
                obligation.recursion_depth + 1,
                cache_projection,
                &mut nested_obligations,
            )
        })
    } else {
        cache_projection
    };

    debug!(?cache_projection, ?obligation_projection);

    match infcx.at(cause, param_env).eq(
        DefineOpaqueTypes::Yes,
        cache_projection,
        obligation_projection,
    ) {
        Ok(InferOk { value: _, obligations }) => {
            nested_obligations.extend(obligations);
            assoc_term_own_obligations(selcx, obligation, &mut nested_obligations);
            Progress { term: cache_entry.term, obligations: nested_obligations }
        }
        Err(e) => {
            let msg = format!(
                "Failed to unify obligation `{obligation:?}` with poly_projection `{poly_cache_entry:?}`: {e:?}",
            );
            debug!("confirm_param_env_candidate: {}", msg);
            let err = Ty::new_error_with_message(infcx.tcx, obligation.cause.span, msg);
            Progress { term: err.into(), obligations: PredicateObligations::new() }
        }
    }
}

// FIXME(mgca): While this supports constants, it is only used for types by default right now
fn confirm_impl_candidate<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    impl_impl_source: ImplSourceUserDefinedData<'tcx, PredicateObligation<'tcx>>,
) -> Result<Projected<'tcx>, ProjectionError<'tcx>> {
    let tcx = selcx.tcx();

    let ImplSourceUserDefinedData { impl_def_id, args, mut nested } = impl_impl_source;

    let assoc_item_id = obligation.predicate.expect_projection_def_id();
    let trait_def_id = tcx.impl_trait_id(impl_def_id);

    let param_env = obligation.param_env;
    let assoc_term = match specialization_graph::assoc_def(tcx, impl_def_id, assoc_item_id) {
        Ok(assoc_term) => assoc_term,
        Err(guar) => {
            return Ok(Projected::Progress(Progress::error_for_term(
                tcx,
                obligation.predicate,
                guar,
            )));
        }
    };

    // This means that the impl is missing a definition for the
    // associated type. This is either because the associate item
    // has impossible-to-satisfy predicates (since those were
    // allowed in <https://github.com/rust-lang/rust/pull/135480>),
    // or because the impl is literally missing the definition.
    if !assoc_term.item.defaultness(tcx).has_value() {
        debug!(
            "confirm_impl_candidate: no associated type {:?} for {:?}",
            assoc_term.item.name(),
            obligation.predicate
        );
        if tcx.impl_self_is_guaranteed_unsized(impl_def_id) {
            // We treat this projection as rigid here, which is represented via
            // `Projected::NoProgress`. This will ensure that the projection is
            // checked for well-formedness, and it's either satisfied by a trivial
            // where clause in its env or it results in an error.
            return Ok(Projected::NoProgress(obligation.predicate.to_term(tcx, ty::IsRigid::No)));
        } else {
            return Ok(Projected::Progress(Progress {
                term: if obligation.predicate.kind.is_type() {
                    Ty::new_misc_error(tcx).into()
                } else {
                    ty::Const::new_misc_error(tcx).into()
                },
                obligations: nested,
            }));
        }
    }

    // If we're trying to normalize `<Vec<u32> as X>::A<S>` using
    //`impl<T> X for Vec<T> { type A<Y> = Box<Y>; }`, then:
    //
    // * `obligation.predicate.args` is `[Vec<u32>, S]`
    // * `args` is `[u32]`
    // * `args` ends up as `[u32, S]`
    let args = obligation.predicate.args.rebase_onto(tcx, trait_def_id, args);
    let args = translate_args(selcx.infcx, param_env, impl_def_id, args, assoc_term.defining_node);

    let term = if obligation.predicate.kind.is_type() {
        tcx.type_of(assoc_term.item.def_id).map_bound(|ty| ty.into())
    } else {
        tcx.const_of_item(assoc_term.item.def_id).map_bound(|ct| ct.into())
    };

    let progress = if !tcx.check_args_compatible(assoc_term.item.def_id, args) {
        let msg = "impl item and trait item have different parameters";
        let span = obligation.cause.span;
        let err = if obligation.predicate.kind.is_type() {
            Ty::new_error_with_message(tcx, span, msg).into()
        } else {
            ty::Const::new_error_with_message(tcx, span, msg).into()
        };
        Progress { term: err, obligations: nested }
    } else {
        assoc_term_own_obligations(selcx, obligation, &mut nested);
        let instantiated_term: Term<'tcx> = term.instantiate(tcx, args).skip_norm_wip();
        push_const_arg_has_type_obligation(
            tcx,
            &mut nested,
            &obligation.cause,
            obligation.recursion_depth + 1,
            obligation.param_env,
            instantiated_term,
            assoc_term.item.def_id,
            args,
        );
        Progress { term: instantiated_term, obligations: nested }
    };
    Ok(Projected::Progress(progress))
}

// Get obligations corresponding to the predicates from the where-clause of the
// associated type itself.
//
// This is necessary for soundness until we properly handle implied bounds on binders.
// see tests/ui/generic-associated-types/must-prove-where-clauses-on-norm.rs.
// FIXME(mgca): While this supports constants, it is only used for types by default right now
fn assoc_term_own_obligations<'cx, 'tcx>(
    selcx: &mut SelectionContext<'cx, 'tcx>,
    obligation: &ProjectionTermObligation<'tcx>,
    nested: &mut PredicateObligations<'tcx>,
) {
    let tcx = selcx.tcx();
    let def_id = obligation.predicate.expect_projection_def_id();
    let predicates = tcx.predicates_of(def_id).instantiate_own(tcx, obligation.predicate.args);
    for (predicate, span) in predicates {
        let normalized = normalize_with_depth_to(
            selcx,
            obligation.param_env,
            obligation.cause.clone(),
            obligation.recursion_depth + 1,
            predicate.skip_norm_wip(),
            nested,
        );

        let nested_cause = if matches!(
            obligation.cause.code(),
            ObligationCauseCode::CompareImplItem { .. }
                | ObligationCauseCode::CheckAssociatedTypeBounds { .. }
                | ObligationCauseCode::AscribeUserTypeProvePredicate(..)
        ) {
            obligation.cause.clone()
        } else {
            ObligationCause::new(
                obligation.cause.span,
                obligation.cause.body_id,
                ObligationCauseCode::WhereClause(def_id, span),
            )
        };
        nested.push(Obligation::with_depth(
            tcx,
            nested_cause,
            obligation.recursion_depth + 1,
            obligation.param_env,
            normalized,
        ));
    }
}

pub(crate) trait ProjectionCacheKeyExt<'cx, 'tcx>: Sized {
    fn from_poly_projection_obligation(
        selcx: &mut SelectionContext<'cx, 'tcx>,
        obligation: &PolyProjectionObligation<'tcx>,
    ) -> Option<Self>;
}

impl<'cx, 'tcx> ProjectionCacheKeyExt<'cx, 'tcx> for ProjectionCacheKey<'tcx> {
    fn from_poly_projection_obligation(
        selcx: &mut SelectionContext<'cx, 'tcx>,
        obligation: &PolyProjectionObligation<'tcx>,
    ) -> Option<Self> {
        let infcx = selcx.infcx;
        // We don't do cross-snapshot caching of obligations with escaping regions,
        // so there's no cache key to use
        obligation.predicate.no_bound_vars().map(|predicate| {
            ProjectionCacheKey::new(
                // We don't attempt to match up with a specific type-variable state
                // from a specific call to `opt_normalize_projection_type` - if
                // there's no precise match, the original cache entry is "stranded"
                // anyway.
                infcx.resolve_vars_if_possible(predicate.projection_term),
                obligation.param_env,
            )
        })
    }
}
