use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
use rustc_data_structures::sorted_map::SortedMap;
use rustc_data_structures::unord::UnordMap;
use rustc_errors::codes::*;
use rustc_errors::{
    Applicability, Diag, ErrorGuaranteed, MultiSpan, SuggestionStyle, listify, msg, pluralize,
    struct_span_code_err,
};
use rustc_hir::def::{CtorOf, DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::{self as hir, HirId};
use rustc_middle::bug;
use rustc_middle::ty::fast_reject::{TreatParams, simplify_type};
use rustc_middle::ty::print::{PrintPolyTraitRefExt as _, PrintTraitRefExt as _};
use rustc_middle::ty::{
    self, AdtDef, GenericParamDefKind, Ty, TyCtxt, TypeVisitableExt,
    suggest_constraining_type_param,
};
use rustc_session::errors::feature_err;
use rustc_span::edit_distance::find_best_match_for_name;
use rustc_span::{BytePos, DUMMY_SP, Ident, Span, Symbol, kw, sym};
use rustc_trait_selection::error_reporting::traits::report_dyn_incompatibility;
use rustc_trait_selection::traits::{
    FulfillmentError, dyn_compatibility_violations_for_assoc_item,
};
use smallvec::SmallVec;
use tracing::debug;

use super::InherentAssocCandidate;
use crate::errors::{
    self, AssocItemConstraintsNotAllowedHere, ManualImplementation, ParenthesizedFnTraitExpansion,
    TraitObjectDeclaredWithNoTraits,
};
use crate::hir_ty_lowering::{AssocItemQSelf, HirTyLowerer};

impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
    pub(crate) fn report_missing_generic_params(
        &self,
        missing_generic_params: Vec<(Symbol, ty::GenericParamDefKind)>,
        def_id: DefId,
        span: Span,
        empty_generic_args: bool,
    ) {
        if missing_generic_params.is_empty() {
            return;
        }

        self.dcx().emit_err(errors::MissingGenericParams {
            span,
            def_span: self.tcx().def_span(def_id),
            span_snippet: self.tcx().sess.source_map().span_to_snippet(span).ok(),
            missing_generic_params,
            empty_generic_args,
        });
    }

    /// When the code is using the `Fn` traits directly, instead of the `Fn(A) -> B` syntax, emit
    /// an error and attempt to build a reasonable structured suggestion.
    pub(crate) fn report_internal_fn_trait(
        &self,
        span: Span,
        trait_def_id: DefId,
        trait_segment: &'_ hir::PathSegment<'_>,
        is_impl: bool,
    ) {
        if self.tcx().features().unboxed_closures() {
            return;
        }

        let trait_def = self.tcx().trait_def(trait_def_id);
        if !trait_def.paren_sugar {
            if trait_segment.args().parenthesized == hir::GenericArgsParentheses::ParenSugar {
                // For now, require that parenthetical notation be used only with `Fn()` etc.
                feature_err(
                    &self.tcx().sess,
                    sym::unboxed_closures,
                    span,
                    "parenthetical notation is only stable when used with `Fn`-family traits",
                )
                .emit();
            }

            return;
        }

        let sess = self.tcx().sess;

        if trait_segment.args().parenthesized != hir::GenericArgsParentheses::ParenSugar {
            // For now, require that parenthetical notation be used only with `Fn()` etc.
            let mut err = feature_err(
                sess,
                sym::unboxed_closures,
                span,
                "the precise format of `Fn`-family traits' type parameters is subject to change",
            );
            // Do not suggest the other syntax if we are in trait impl:
            // the desugaring would contain an associated type constraint.
            if !is_impl {
                err.span_suggestion(
                    span,
                    "use parenthetical notation instead",
                    fn_trait_to_string(self.tcx(), trait_segment, true),
                    Applicability::MaybeIncorrect,
                );
            }
            err.emit();
        }

        if is_impl {
            let trait_name = self.tcx().def_path_str(trait_def_id);
            self.dcx().emit_err(ManualImplementation { span, trait_name });
        }
    }

    pub(super) fn report_unresolved_assoc_item<I>(
        &self,
        all_candidates: impl Fn() -> I,
        qself: AssocItemQSelf,
        assoc_tag: ty::AssocTag,
        assoc_ident: Ident,
        span: Span,
        constraint: Option<&hir::AssocItemConstraint<'tcx>>,
    ) -> ErrorGuaranteed
    where
        I: Iterator<Item = ty::PolyTraitRef<'tcx>>,
    {
        let tcx = self.tcx();

        // First and foremost, provide a more user-friendly & “intuitive” error on kind mismatches.
        if let Some(assoc_item) = all_candidates().find_map(|r| {
            tcx.associated_items(r.def_id())
                .filter_by_name_unhygienic(assoc_ident.name)
                .find(|item| tcx.hygienic_eq(assoc_ident, item.ident(tcx), r.def_id()))
        }) {
            return self.report_assoc_kind_mismatch(
                assoc_item,
                assoc_tag,
                assoc_ident,
                span,
                constraint,
            );
        }

        let assoc_kind = assoc_tag_str(assoc_tag);
        let qself_str = qself.to_string(tcx);

        // The fallback span is needed because `assoc_name` might be an `Fn()`'s `Output` without a
        // valid span, so we point at the whole path segment instead.
        let is_dummy = assoc_ident.span == DUMMY_SP;

        let mut err = errors::AssocItemNotFound {
            span: if is_dummy { span } else { assoc_ident.span },
            assoc_ident,
            assoc_kind,
            qself: &qself_str,
            label: None,
            sugg: None,
            // Try to get the span of the identifier within the path's syntax context
            // (if that's different).
            within_macro_span: assoc_ident.span.within_macro(span, tcx.sess.source_map()),
        };

        if is_dummy {
            err.label =
                Some(errors::AssocItemNotFoundLabel::NotFound { span, assoc_ident, assoc_kind });
            return self.dcx().emit_err(err);
        }

        let all_candidate_names: Vec<_> = all_candidates()
            .flat_map(|r| tcx.associated_items(r.def_id()).in_definition_order())
            .filter_map(|item| {
                if !item.is_impl_trait_in_trait() && item.tag() == assoc_tag {
                    item.opt_name()
                } else {
                    None
                }
            })
            .collect();

        if let Some(suggested_name) =
            find_best_match_for_name(&all_candidate_names, assoc_ident.name, None)
        {
            err.sugg = Some(errors::AssocItemNotFoundSugg::Similar {
                span: assoc_ident.span,
                assoc_kind,
                suggested_name,
            });
            return self.dcx().emit_err(err);
        }

        // If we didn't find a good item in the supertraits (or couldn't get
        // the supertraits), like in ItemCtxt, then look more generally from
        // all visible traits. If there's one clear winner, just suggest that.

        let visible_traits: Vec<_> = tcx
            .visible_traits()
            .filter(|trait_def_id| {
                let viz = tcx.visibility(*trait_def_id);
                let def_id = self.item_def_id();
                viz.is_accessible_from(def_id, tcx)
            })
            .collect();

        let wider_candidate_names: Vec<_> = visible_traits
            .iter()
            .flat_map(|trait_def_id| tcx.associated_items(*trait_def_id).in_definition_order())
            .filter_map(|item| {
                (!item.is_impl_trait_in_trait() && item.tag() == assoc_tag).then(|| item.name())
            })
            .collect();

        if let Some(suggested_name) =
            find_best_match_for_name(&wider_candidate_names, assoc_ident.name, None)
        {
            if let [best_trait] = visible_traits
                .iter()
                .copied()
                .filter(|&trait_def_id| {
                    tcx.associated_items(trait_def_id)
                        .filter_by_name_unhygienic(suggested_name)
                        .any(|item| item.tag() == assoc_tag)
                })
                .collect::<Vec<_>>()[..]
            {
                let trait_name = tcx.def_path_str(best_trait);
                err.label = Some(errors::AssocItemNotFoundLabel::FoundInOtherTrait {
                    span: assoc_ident.span,
                    assoc_kind,
                    trait_name: &trait_name,
                    suggested_name,
                    identically_named: suggested_name == assoc_ident.name,
                });
                if let AssocItemQSelf::TyParam(ty_param_def_id, ty_param_span) = qself
                    // Not using `self.item_def_id()` here as that would yield the opaque type itself if we're
                    // inside an opaque type while we're interested in the overarching type alias (TAIT).
                    // FIXME: However, for trait aliases, this incorrectly returns the enclosing module...
                    && let item_def_id =
                        tcx.hir_get_parent_item(tcx.local_def_id_to_hir_id(ty_param_def_id))
                    // FIXME: ...which obviously won't have any generics.
                    && let Some(generics) = tcx.hir_get_generics(item_def_id.def_id)
                {
                    // FIXME: Suggest adding supertrait bounds if we have a `Self` type param.
                    // FIXME(trait_alias): Suggest adding `Self: Trait` to
                    // `trait Alias = where Self::Proj:;` with `trait Trait { type Proj; }`.
                    if generics
                        .bounds_for_param(ty_param_def_id)
                        .flat_map(|pred| pred.bounds.iter())
                        .any(|b| match b {
                            hir::GenericBound::Trait(t, ..) => {
                                t.trait_ref.trait_def_id() == Some(best_trait)
                            }
                            _ => false,
                        })
                    {
                        // The type param already has a bound for `trait_name`, we just need to
                        // change the associated item.
                        err.sugg = Some(errors::AssocItemNotFoundSugg::SimilarInOtherTrait {
                            span: assoc_ident.span,
                            trait_name: &trait_name,
                            assoc_kind,
                            suggested_name,
                        });
                        return self.dcx().emit_err(err);
                    }

                    let trait_args = &ty::GenericArgs::identity_for_item(tcx, best_trait)[1..];
                    let mut trait_ref = trait_name.clone();
                    let applicability = if let [arg, args @ ..] = trait_args {
                        use std::fmt::Write;
                        write!(trait_ref, "</* {arg}").unwrap();
                        args.iter().try_for_each(|arg| write!(trait_ref, ", {arg}")).unwrap();
                        trait_ref += " */>";
                        Applicability::HasPlaceholders
                    } else {
                        Applicability::MaybeIncorrect
                    };

                    let identically_named = suggested_name == assoc_ident.name;

                    if let DefKind::TyAlias = tcx.def_kind(item_def_id)
                        && !tcx.type_alias_is_lazy(item_def_id)
                    {
                        err.sugg = Some(errors::AssocItemNotFoundSugg::SimilarInOtherTraitQPath {
                            lo: ty_param_span.shrink_to_lo(),
                            mi: ty_param_span.shrink_to_hi(),
                            hi: (!identically_named).then_some(assoc_ident.span),
                            trait_ref,
                            identically_named,
                            suggested_name,
                            assoc_kind,
                            applicability,
                        });
                    } else {
                        let mut err = self.dcx().create_err(err);
                        if suggest_constraining_type_param(
                            tcx,
                            generics,
                            &mut err,
                            &qself_str,
                            &trait_ref,
                            Some(best_trait),
                            None,
                        ) && !identically_named
                        {
                            // We suggested constraining a type parameter, but the associated item on it
                            // was also not an exact match, so we also suggest changing it.
                            err.span_suggestion_verbose(
                                assoc_ident.span,
                                msg!("...and changing the associated {$assoc_kind} name"),
                                suggested_name,
                                Applicability::MaybeIncorrect,
                            );
                        }
                        return err.emit();
                    }
                }
                return self.dcx().emit_err(err);
            }
        }

        // If we still couldn't find any associated item, and only one associated item exists,
        // suggest using it.
        if let [candidate_name] = all_candidate_names.as_slice() {
            err.sugg = Some(errors::AssocItemNotFoundSugg::Other {
                span: assoc_ident.span,
                qself: &qself_str,
                assoc_kind,
                suggested_name: *candidate_name,
            });
        } else {
            err.label = Some(errors::AssocItemNotFoundLabel::NotFound {
                span: assoc_ident.span,
                assoc_ident,
                assoc_kind,
            });
        }

        self.dcx().emit_err(err)
    }

    fn report_assoc_kind_mismatch(
        &self,
        assoc_item: &ty::AssocItem,
        assoc_tag: ty::AssocTag,
        ident: Ident,
        span: Span,
        constraint: Option<&hir::AssocItemConstraint<'tcx>>,
    ) -> ErrorGuaranteed {
        let tcx = self.tcx();

        let bound_on_assoc_const_label = if let ty::AssocKind::Const { .. } = assoc_item.kind
            && let Some(constraint) = constraint
            && let hir::AssocItemConstraintKind::Bound { .. } = constraint.kind
        {
            let lo = if constraint.gen_args.span_ext.is_dummy() {
                ident.span
            } else {
                constraint.gen_args.span_ext
            };
            Some(lo.between(span.shrink_to_hi()))
        } else {
            None
        };

        // FIXME(mgca): This has quite a few false positives and negatives.
        let wrap_in_braces_sugg = if let Some(constraint) = constraint
            && let Some(hir_ty) = constraint.ty()
            && let ty = self.lower_ty(hir_ty)
            && (ty.is_enum() || ty.references_error())
            && tcx.features().min_generic_const_args()
        {
            Some(errors::AssocKindMismatchWrapInBracesSugg {
                lo: hir_ty.span.shrink_to_lo(),
                hi: hir_ty.span.shrink_to_hi(),
            })
        } else {
            None
        };

        // For equality constraints, we want to blame the term (RHS) instead of the item (LHS) since
        // one can argue that that's more “intuitive” to the user.
        let (span, expected_because_label, expected, got) = if let Some(constraint) = constraint
            && let hir::AssocItemConstraintKind::Equality { term } = constraint.kind
        {
            let span = match term {
                hir::Term::Ty(ty) => ty.span,
                hir::Term::Const(ct) => ct.span,
            };
            (span, Some(ident.span), assoc_item.tag(), assoc_tag)
        } else {
            (ident.span, None, assoc_tag, assoc_item.tag())
        };

        self.dcx().emit_err(errors::AssocKindMismatch {
            span,
            expected: assoc_tag_str(expected),
            got: assoc_tag_str(got),
            expected_because_label,
            assoc_kind: assoc_tag_str(assoc_item.tag()),
            def_span: tcx.def_span(assoc_item.def_id),
            bound_on_assoc_const_label,
            wrap_in_braces_sugg,
        })
    }

    pub(super) fn report_ambiguous_assoc_item(
        &self,
        bound1: ty::PolyTraitRef<'tcx>,
        bound2: ty::PolyTraitRef<'tcx>,
        matching_candidates: impl Iterator<Item = ty::PolyTraitRef<'tcx>>,
        qself: AssocItemQSelf,
        assoc_tag: ty::AssocTag,
        assoc_ident: Ident,
        span: Span,
        constraint: Option<&hir::AssocItemConstraint<'tcx>>,
    ) -> ErrorGuaranteed {
        let tcx = self.tcx();

        let assoc_kind_str = assoc_tag_str(assoc_tag);
        let qself_str = qself.to_string(tcx);
        let mut err = self.dcx().create_err(crate::errors::AmbiguousAssocItem {
            span,
            assoc_kind: assoc_kind_str,
            assoc_ident,
            qself: &qself_str,
        });
        // Provide a more specific error code index entry for equality bindings.
        err.code(
            if let Some(constraint) = constraint
                && let hir::AssocItemConstraintKind::Equality { .. } = constraint.kind
            {
                E0222
            } else {
                E0221
            },
        );

        // FIXME(#97583): Print associated item bindings properly (i.e., not as equality
        // predicates!).
        // FIXME: Turn this into a structured, translatable & more actionable suggestion.
        let mut where_bounds = vec![];
        for bound in [bound1, bound2].into_iter().chain(matching_candidates) {
            let bound_id = bound.def_id();
            let assoc_item = tcx.associated_items(bound_id).find_by_ident_and_kind(
                tcx,
                assoc_ident,
                assoc_tag,
                bound_id,
            );
            let bound_span = assoc_item.and_then(|item| tcx.hir_span_if_local(item.def_id));

            if let Some(bound_span) = bound_span {
                err.span_label(
                    bound_span,
                    format!("ambiguous `{assoc_ident}` from `{}`", bound.print_trait_sugared(),),
                );
                if let Some(constraint) = constraint {
                    match constraint.kind {
                        hir::AssocItemConstraintKind::Equality { term } => {
                            let term: ty::Term<'_> = match term {
                                hir::Term::Ty(ty) => self.lower_ty(ty).into(),
                                hir::Term::Const(ct) => {
                                    let assoc_item =
                                        assoc_item.expect("assoc_item should be present");
                                    let projection_term = bound.map_bound(|trait_ref| {
                                        let item_segment = hir::PathSegment {
                                            ident: constraint.ident,
                                            hir_id: constraint.hir_id,
                                            res: Res::Err,
                                            args: Some(constraint.gen_args),
                                            infer_args: false,
                                        };

                                        let alias_args = self.lower_generic_args_of_assoc_item(
                                            constraint.ident.span,
                                            assoc_item.def_id,
                                            &item_segment,
                                            trait_ref.args,
                                        );
                                        ty::AliasTerm::new_from_def_id(
                                            tcx,
                                            assoc_item.def_id,
                                            alias_args,
                                        )
                                    });

                                    // FIXME(mgca): code duplication with other places we lower
                                    // the rhs' of associated const bindings
                                    let ty = projection_term.map_bound(|alias| {
                                        tcx.type_of(alias.def_id())
                                            .instantiate(tcx, alias.args)
                                            .skip_norm_wip()
                                    });
                                    let ty = super::bounds::check_assoc_const_binding_type(
                                        self,
                                        constraint.ident,
                                        ty,
                                        constraint.hir_id,
                                    );

                                    self.lower_const_arg(ct, ty).into()
                                }
                            };
                            if term.references_error() {
                                continue;
                            }
                            // FIXME(#97583): This isn't syntactically well-formed!
                            where_bounds.push(format!(
                                "        T: {trait}::{assoc_ident} = {term}",
                                trait = bound.print_only_trait_path(),
                            ));
                        }
                        // FIXME: Provide a suggestion.
                        hir::AssocItemConstraintKind::Bound { bounds: _ } => {}
                    }
                } else {
                    err.span_suggestion_verbose(
                        span.with_hi(assoc_ident.span.lo()),
                        "use fully-qualified syntax to disambiguate",
                        format!("<{qself_str} as {}>::", bound.print_only_trait_path()),
                        Applicability::MaybeIncorrect,
                    );
                }
            } else {
                let trait_ = tcx.short_string(bound.print_only_trait_path(), err.long_ty_path());
                err.note(format!(
                    "associated {assoc_kind_str} `{assoc_ident}` could derive from `{trait_}`",
                ));
            }
        }
        if !where_bounds.is_empty() {
            err.help(format!(
                "consider introducing a new type parameter `T` and adding `where` constraints:\
                     \n    where\n        T: {qself_str},\n{}",
                where_bounds.join(",\n"),
            ));
        }
        err.emit()
    }

    pub(crate) fn report_missing_self_ty_for_resolved_path(
        &self,
        trait_def_id: DefId,
        span: Span,
        item_segment: &hir::PathSegment<'tcx>,
        assoc_tag: ty::AssocTag,
    ) -> ErrorGuaranteed {
        let tcx = self.tcx();
        let path_str = tcx.def_path_str(trait_def_id);

        let def_id = self.item_def_id();
        debug!(item_def_id = ?def_id);

        // FIXME: document why/how this is different from `tcx.local_parent(def_id)`
        let parent_def_id = tcx.hir_get_parent_item(tcx.local_def_id_to_hir_id(def_id)).to_def_id();
        debug!(?parent_def_id);

        // If the trait in segment is the same as the trait defining the item,
        // use the `<Self as ..>` syntax in the error.
        let is_part_of_self_trait_constraints = def_id.to_def_id() == trait_def_id;
        let is_part_of_fn_in_self_trait = parent_def_id == trait_def_id;

        let type_names = if is_part_of_self_trait_constraints || is_part_of_fn_in_self_trait {
            vec!["Self".to_string()]
        } else {
            // Find all the types that have an `impl` for the trait.
            tcx.all_impls(trait_def_id)
                .map(|impl_def_id| tcx.impl_trait_header(impl_def_id))
                .filter(|header| {
                    // Consider only accessible traits
                    tcx.visibility(trait_def_id).is_accessible_from(self.item_def_id(), tcx)
                        && header.polarity != ty::ImplPolarity::Negative
                })
                .map(|header| header.trait_ref.instantiate_identity().skip_norm_wip().self_ty())
                // We don't care about blanket impls.
                .filter(|self_ty| !self_ty.has_non_region_param())
                .map(|self_ty| tcx.erase_and_anonymize_regions(self_ty).to_string())
                .collect()
        };
        // FIXME: also look at `tcx.generics_of(self.item_def_id()).params` any that
        // references the trait. Relevant for the first case in
        // `src/test/ui/associated-types/associated-types-in-ambiguous-context.rs`
        self.report_ambiguous_assoc_item_path(
            span,
            &type_names,
            &[path_str],
            item_segment.ident,
            assoc_tag,
        )
    }

    pub(super) fn report_unresolved_type_relative_path(
        &self,
        self_ty: Ty<'tcx>,
        hir_self_ty: &hir::Ty<'_>,
        assoc_tag: ty::AssocTag,
        ident: Ident,
        qpath_hir_id: HirId,
        span: Span,
        variant_def_id: Option<DefId>,
    ) -> ErrorGuaranteed {
        let tcx = self.tcx();
        let kind_str = assoc_tag_str(assoc_tag);
        if variant_def_id.is_some() {
            // Variant in type position
            let msg = format!("expected {kind_str}, found variant `{ident}`");
            self.dcx().span_err(span, msg)
        } else if self_ty.is_enum() {
            let mut err = self.dcx().create_err(errors::NoVariantNamed {
                span: ident.span,
                ident,
                ty: self_ty,
            });

            let adt_def = self_ty.ty_adt_def().expect("enum is not an ADT");
            if let Some(variant_name) = find_best_match_for_name(
                &adt_def.variants().iter().map(|variant| variant.name).collect::<Vec<Symbol>>(),
                ident.name,
                None,
            ) && let Some(variant) = adt_def.variants().iter().find(|s| s.name == variant_name)
            {
                let mut suggestion = vec![(ident.span, variant_name.to_string())];
                if let hir::Node::Stmt(&hir::Stmt { kind: hir::StmtKind::Semi(expr), .. })
                | hir::Node::Expr(expr) = tcx.parent_hir_node(qpath_hir_id)
                    && let hir::ExprKind::Struct(..) = expr.kind
                {
                    match variant.ctor {
                        None => {
                            // struct
                            suggestion = vec![(
                                ident.span.with_hi(expr.span.hi()),
                                if variant.fields.is_empty() {
                                    format!("{variant_name} {{}}")
                                } else {
                                    format!(
                                        "{variant_name} {{ {} }}",
                                        variant
                                            .fields
                                            .iter()
                                            .map(|f| format!("{}: /* value */", f.name))
                                            .collect::<Vec<_>>()
                                            .join(", ")
                                    )
                                },
                            )];
                        }
                        Some((hir::def::CtorKind::Fn, def_id)) => {
                            // tuple
                            let fn_sig = tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip();
                            let inputs = fn_sig.inputs().skip_binder();
                            suggestion = vec![(
                                ident.span.with_hi(expr.span.hi()),
                                format!(
                                    "{variant_name}({})",
                                    inputs
                                        .iter()
                                        .map(|i| format!("/* {i} */"))
                                        .collect::<Vec<_>>()
                                        .join(", ")
                                ),
                            )];
                        }
                        Some((hir::def::CtorKind::Const, _)) => {
                            // unit
                            suggestion = vec![(
                                ident.span.with_hi(expr.span.hi()),
                                variant_name.to_string(),
                            )];
                        }
                    }
                }
                err.multipart_suggestion(
                    "there is a variant with a similar name",
                    suggestion,
                    Applicability::HasPlaceholders,
                );
            } else {
                err.span_label(ident.span, format!("variant not found in `{self_ty}`"));
            }

            if let Some(sp) = tcx.hir_span_if_local(adt_def.did()) {
                err.span_label(sp, format!("variant `{ident}` not found here"));
            }

            err.emit()
        } else if let Err(reported) = self_ty.error_reported() {
            reported
        } else {
            match self.maybe_report_similar_assoc_fn(span, self_ty, hir_self_ty) {
                Ok(()) => {}
                Err(reported) => return reported,
            }

            let traits: Vec<_> = self.probe_traits_that_match_assoc_ty(self_ty, ident);

            self.report_ambiguous_assoc_item_path(
                span,
                &[self_ty.to_string()],
                &traits,
                ident,
                assoc_tag,
            )
        }
    }

    fn report_ambiguous_assoc_item_path(
        &self,
        span: Span,
        types: &[String],
        traits: &[String],
        ident: Ident,
        assoc_tag: ty::AssocTag,
    ) -> ErrorGuaranteed {
        let kind_str = assoc_tag_str(assoc_tag);
        let mut err =
            struct_span_code_err!(self.dcx(), span, E0223, "ambiguous associated {kind_str}");
        if self
            .tcx()
            .resolutions(())
            .confused_type_with_std_module
            .keys()
            .any(|full_span| full_span.contains(span))
        {
            err.span_suggestion_verbose(
                span.shrink_to_lo(),
                "you are looking for the module in `std`, not the primitive type",
                "std::",
                Applicability::MachineApplicable,
            );
        } else {
            let sugg_sp = span.until(ident.span);

            let mut types = types.to_vec();
            types.sort();
            let mut traits = traits.to_vec();
            traits.sort();
            match (&types[..], &traits[..]) {
                ([], []) => {
                    err.span_suggestion_verbose(
                        sugg_sp,
                        format!(
                            "if there were a type named `Type` that implements a trait named \
                             `Trait` with associated {kind_str} `{ident}`, you could use the \
                             fully-qualified path",
                        ),
                        "<Type as Trait>::",
                        Applicability::HasPlaceholders,
                    );
                }
                ([], [trait_str]) => {
                    err.span_suggestion_verbose(
                        sugg_sp,
                        format!(
                            "if there were a type named `Example` that implemented `{trait_str}`, \
                             you could use the fully-qualified path",
                        ),
                        format!("<Example as {trait_str}>::"),
                        Applicability::HasPlaceholders,
                    );
                }
                ([], traits) => {
                    err.span_suggestions_with_style(
                        sugg_sp,
                        format!(
                            "if there were a type named `Example` that implemented one of the \
                             traits with associated {kind_str} `{ident}`, you could use the \
                             fully-qualified path",
                        ),
                        traits.iter().map(|trait_str| format!("<Example as {trait_str}>::")),
                        Applicability::HasPlaceholders,
                        SuggestionStyle::ShowAlways,
                    );
                }
                ([type_str], []) => {
                    err.span_suggestion_verbose(
                        sugg_sp,
                        format!(
                            "if there were a trait named `Example` with associated {kind_str} `{ident}` \
                             implemented for `{type_str}`, you could use the fully-qualified path",
                        ),
                        format!("<{type_str} as Example>::"),
                        Applicability::HasPlaceholders,
                    );
                }
                (types, []) => {
                    err.span_suggestions_with_style(
                        sugg_sp,
                        format!(
                            "if there were a trait named `Example` with associated {kind_str} `{ident}` \
                             implemented for one of the types, you could use the fully-qualified \
                             path",
                        ),
                        types
                            .into_iter()
                            .map(|type_str| format!("<{type_str} as Example>::")),
                        Applicability::HasPlaceholders,
                        SuggestionStyle::ShowAlways,
                    );
                }
                (types, traits) => {
                    let mut suggestions = vec![];
                    for type_str in types {
                        for trait_str in traits {
                            suggestions.push(format!("<{type_str} as {trait_str}>::"));
                        }
                    }
                    err.span_suggestions_with_style(
                        sugg_sp,
                        "use fully-qualified syntax",
                        suggestions,
                        Applicability::MachineApplicable,
                        SuggestionStyle::ShowAlways,
                    );
                }
            }
        }
        err.emit()
    }

    pub(crate) fn report_ambiguous_inherent_assoc_item(
        &self,
        name: Ident,
        candidates: Vec<DefId>,
        span: Span,
    ) -> ErrorGuaranteed {
        let mut err = struct_span_code_err!(
            self.dcx(),
            name.span,
            E0034,
            "multiple applicable items in scope"
        );
        err.span_label(name.span, format!("multiple `{name}` found"));
        self.note_ambiguous_inherent_assoc_item(&mut err, candidates, span);
        err.emit()
    }

    // FIXME(fmease): Heavily adapted from `rustc_hir_typeck::method::suggest`. Deduplicate.
    fn note_ambiguous_inherent_assoc_item(
        &self,
        err: &mut Diag<'_>,
        candidates: Vec<DefId>,
        span: Span,
    ) {
        let tcx = self.tcx();

        // Dynamic limit to avoid hiding just one candidate, which is silly.
        let limit = if candidates.len() == 5 { 5 } else { 4 };

        for (index, &item) in candidates.iter().take(limit).enumerate() {
            let impl_ = tcx.parent(item);

            let note_span = if item.is_local() {
                Some(tcx.def_span(item))
            } else if impl_.is_local() {
                Some(tcx.def_span(impl_))
            } else {
                None
            };

            let title = if candidates.len() > 1 {
                format!("candidate #{}", index + 1)
            } else {
                "the candidate".into()
            };

            let impl_ty = tcx.at(span).type_of(impl_).instantiate_identity().skip_norm_wip();
            let note = format!("{title} is defined in an impl for the type `{impl_ty}`");

            if let Some(span) = note_span {
                err.span_note(span, note);
            } else {
                err.note(note);
            }
        }
        if candidates.len() > limit {
            err.note(format!("and {} others", candidates.len() - limit));
        }
    }

    // FIXME(inherent_associated_types): Find similarly named associated types and suggest them.
    pub(crate) fn report_unresolved_inherent_assoc_item(
        &self,
        name: Ident,
        self_ty: Ty<'tcx>,
        candidates: Vec<InherentAssocCandidate>,
        fulfillment_errors: Vec<FulfillmentError<'tcx>>,
        span: Span,
        assoc_tag: ty::AssocTag,
    ) -> ErrorGuaranteed {
        // FIXME(fmease): This was copied in parts from an old version of `rustc_hir_typeck::method::suggest`.
        // Either
        // * update this code by applying changes similar to #106702 or by taking a
        //   Vec<(DefId, (DefId, DefId), Option<Vec<FulfillmentError<'tcx>>>)> or
        // * deduplicate this code across the two crates.

        let tcx = self.tcx();

        let assoc_tag_str = assoc_tag_str(assoc_tag);
        let adt_did = self_ty.ty_adt_def().map(|def| def.did());
        let add_def_label = |err: &mut Diag<'_>| {
            if let Some(did) = adt_did {
                err.span_label(
                    tcx.def_span(did),
                    format!(
                        "associated {assoc_tag_str} `{name}` not found for this {}",
                        tcx.def_descr(did)
                    ),
                );
            }
        };

        if fulfillment_errors.is_empty() {
            // FIXME(fmease): Copied from `rustc_hir_typeck::method::probe`. Deduplicate.

            let limit = if candidates.len() == 5 { 5 } else { 4 };
            let type_candidates = candidates
                .iter()
                .take(limit)
                .map(|cand| {
                    format!(
                        "- `{}`",
                        tcx.at(span).type_of(cand.impl_).instantiate_identity().skip_norm_wip()
                    )
                })
                .collect::<Vec<_>>()
                .join("\n");
            let additional_types = if candidates.len() > limit {
                format!("\nand {} more types", candidates.len() - limit)
            } else {
                String::new()
            };

            let mut err = struct_span_code_err!(
                self.dcx(),
                name.span,
                E0220,
                "associated {assoc_tag_str} `{name}` not found for `{self_ty}` in the current scope"
            );
            err.span_label(name.span, format!("associated item not found in `{self_ty}`"));
            err.note(format!(
                "the associated {assoc_tag_str} was found for\n{type_candidates}{additional_types}",
            ));
            add_def_label(&mut err);
            return err.emit();
        }

        let mut bound_spans: SortedMap<Span, Vec<String>> = Default::default();

        let mut bound_span_label = |self_ty: Ty<'_>, obligation: &str, quiet: &str| {
            let msg = format!("`{}`", if obligation.len() > 50 { quiet } else { obligation });
            match self_ty.kind() {
                // Point at the type that couldn't satisfy the bound.
                ty::Adt(def, _) => {
                    bound_spans.get_mut_or_insert_default(tcx.def_span(def.did())).push(msg)
                }
                // Point at the trait object that couldn't satisfy the bound.
                ty::Dynamic(preds, _) => {
                    for pred in preds.iter() {
                        match pred.skip_binder() {
                            ty::ExistentialPredicate::Trait(tr) => {
                                bound_spans
                                    .get_mut_or_insert_default(tcx.def_span(tr.def_id))
                                    .push(msg.clone());
                            }
                            ty::ExistentialPredicate::Projection(_)
                            | ty::ExistentialPredicate::AutoTrait(_) => {}
                        }
                    }
                }
                // Point at the closure that couldn't satisfy the bound.
                ty::Closure(def_id, _) => {
                    bound_spans
                        .get_mut_or_insert_default(tcx.def_span(*def_id))
                        .push(format!("`{quiet}`"));
                }
                _ => {}
            }
        };

        let format_pred = |pred: ty::Predicate<'tcx>| {
            let bound_predicate = pred.kind();
            match bound_predicate.skip_binder() {
                ty::PredicateKind::Clause(ty::ClauseKind::Projection(pred)) => {
                    // `<Foo as Iterator>::Item = String`.
                    let projection_term = pred.projection_term;
                    let quiet_projection_term = projection_term
                        .with_replaced_self_ty(tcx, Ty::new_var(tcx, ty::TyVid::ZERO));

                    let term = pred.term;
                    let obligation = format!("{projection_term} = {term}");
                    let quiet = format!("{quiet_projection_term} = {term}");

                    bound_span_label(projection_term.self_ty(), &obligation, &quiet);
                    Some((obligation, projection_term.self_ty()))
                }
                ty::PredicateKind::Clause(ty::ClauseKind::Trait(poly_trait_ref)) => {
                    let p = poly_trait_ref.trait_ref;
                    let self_ty = p.self_ty();
                    let path = p.print_only_trait_path();
                    let obligation = format!("{self_ty}: {path}");
                    let quiet = format!("_: {path}");
                    bound_span_label(self_ty, &obligation, &quiet);
                    Some((obligation, self_ty))
                }
                _ => None,
            }
        };

        // FIXME(fmease): `rustc_hir_typeck::method::suggest` uses a `skip_list` to filter out some bounds.
        // I would do the same here if it didn't mean more code duplication.
        let mut bounds: Vec<_> = fulfillment_errors
            .into_iter()
            .map(|error| error.root_obligation.predicate)
            .filter_map(format_pred)
            .map(|(p, _)| format!("`{p}`"))
            .collect();
        bounds.sort();
        bounds.dedup();

        let mut err = self.dcx().struct_span_err(
            name.span,
            format!("the associated {assoc_tag_str} `{name}` exists for `{self_ty}`, but its trait bounds were not satisfied")
        );
        if !bounds.is_empty() {
            err.note(format!(
                "the following trait bounds were not satisfied:\n{}",
                bounds.join("\n")
            ));
        }
        err.span_label(
            name.span,
            format!("associated {assoc_tag_str} cannot be referenced on `{self_ty}` due to unsatisfied trait bounds")
        );

        for (span, mut bounds) in bound_spans {
            if !tcx.sess.source_map().is_span_accessible(span) {
                continue;
            }
            bounds.sort();
            bounds.dedup();
            let msg = match &bounds[..] {
                [bound] => format!("doesn't satisfy {bound}"),
                bounds if bounds.len() > 4 => format!("doesn't satisfy {} bounds", bounds.len()),
                [bounds @ .., last] => format!("doesn't satisfy {} or {last}", bounds.join(", ")),
                [] => unreachable!(),
            };
            err.span_label(span, msg);
        }
        add_def_label(&mut err);
        err.emit()
    }

    /// If there are any missing associated items, emit an error instructing the user to provide
    /// them unless that's impossible due to shadowing. Moreover, if any corresponding trait refs
    /// are dyn incompatible due to associated items we emit an dyn incompatibility error instead.
    pub(crate) fn check_for_required_assoc_items(
        &self,
        spans: SmallVec<[Span; 1]>,
        missing_assoc_items: FxIndexSet<(DefId, ty::PolyTraitRef<'tcx>)>,
        potential_assoc_items: Vec<usize>,
        trait_bounds: &[hir::PolyTraitRef<'_>],
    ) -> Result<(), ErrorGuaranteed> {
        if missing_assoc_items.is_empty() {
            return Ok(());
        }

        let tcx = self.tcx();
        let principal_span = *spans.first().unwrap();

        // FIXME: This logic needs some more care w.r.t handling of conflicts
        let missing_assoc_items: Vec<_> = missing_assoc_items
            .into_iter()
            .map(|(def_id, trait_ref)| (tcx.associated_item(def_id), trait_ref))
            .collect();
        let mut names: FxIndexMap<_, Vec<_>> = Default::default();
        let mut names_len = 0;
        let mut descr = None;

        enum Descr {
            Item,
            Tag(ty::AssocTag),
        }

        for &(assoc_item, trait_ref) in &missing_assoc_items {
            // We don't want to suggest specifying associated items if there's something wrong with
            // any of them that renders the trait dyn incompatible; providing them certainly won't
            // fix the issue and we could also risk suggesting invalid code.
            //
            // Note that this check is only truly necessary in item ctxts where we merely perform
            // *minimal* dyn compatibility checks. In fn ctxts we would've already bailed out with
            // an error by this point if the trait was dyn incompatible.
            let violations =
                dyn_compatibility_violations_for_assoc_item(tcx, trait_ref.def_id(), assoc_item);
            if !violations.is_empty() {
                return Err(report_dyn_incompatibility(
                    tcx,
                    principal_span,
                    None,
                    trait_ref.def_id(),
                    &violations,
                )
                .emit());
            }

            names.entry(trait_ref).or_default().push(assoc_item.name());
            names_len += 1;

            descr = match descr {
                None => Some(Descr::Tag(assoc_item.tag())),
                Some(Descr::Tag(tag)) if tag != assoc_item.tag() => Some(Descr::Item),
                _ => continue,
            };
        }

        // related to issue #91997, turbofishes added only when in an expr or pat
        let mut in_expr_or_pat = false;
        if let ([], [bound]) = (&potential_assoc_items[..], &trait_bounds) {
            let grandparent = tcx.parent_hir_node(tcx.parent_hir_id(bound.trait_ref.hir_ref_id));
            in_expr_or_pat = match grandparent {
                hir::Node::Expr(_) | hir::Node::Pat(_) => true,
                _ => false,
            };
        }

        // We get all the associated items that *are* set, so that we can check if any of
        // their names match one of the ones we are missing.
        // This would mean that they are shadowing the associated item we are missing, and
        // we can then use their span to indicate this to the user.
        //
        // FIXME: This does not account for trait aliases. I think we should just make
        //        `lower_trait_object_ty` compute the list of all specified items or give us the
        //        necessary ingredients if it's too expensive to compute in the happy path.
        let bound_names: UnordMap<_, _> =
            trait_bounds
                .iter()
                .filter_map(|poly_trait_ref| {
                    let path = poly_trait_ref.trait_ref.path.segments.last()?;
                    let args = path.args?;
                    let Res::Def(DefKind::Trait, trait_def_id) = path.res else { return None };

                    Some(args.constraints.iter().filter_map(move |constraint| {
                        let hir::AssocItemConstraintKind::Equality { term } = constraint.kind
                        else {
                            return None;
                        };
                        let tag = match term {
                            hir::Term::Ty(_) => ty::AssocTag::Type,
                            hir::Term::Const(_) => ty::AssocTag::Const,
                        };
                        let assoc_item = tcx
                            .associated_items(trait_def_id)
                            .find_by_ident_and_kind(tcx, constraint.ident, tag, trait_def_id)?;
                        Some(((constraint.ident.name, tag), assoc_item.def_id))
                    }))
                })
                .flatten()
                .collect();

        let mut names: Vec<_> = names
            .into_iter()
            .map(|(trait_, mut assocs)| {
                assocs.sort();
                let trait_ = trait_.print_trait_sugared();
                format!(
                    "{} in `{trait_}`",
                    listify(&assocs[..], |a| format!("`{a}`")).unwrap_or_default()
                )
            })
            .collect();
        names.sort();
        let names = names.join(", ");

        let descr = match descr.unwrap() {
            Descr::Item => "associated item",
            Descr::Tag(tag) => tag.descr(),
        };
        let mut err = struct_span_code_err!(
            self.dcx(),
            principal_span,
            E0191,
            "the value of the {descr}{s} {names} must be specified",
            s = pluralize!(names_len),
        );
        let mut suggestions = vec![];
        let mut items_count = 0;
        let mut where_constraints = vec![];
        let mut already_has_generics_args_suggestion = false;

        let mut names: UnordMap<_, usize> = Default::default();
        for (item, _) in &missing_assoc_items {
            items_count += 1;
            *names.entry((item.name(), item.tag())).or_insert(0) += 1;
        }
        let mut dupes = false;
        let mut shadows = false;
        for (item, trait_ref) in &missing_assoc_items {
            let name = item.name();
            let key = (name, item.tag());

            if names[&key] > 1 {
                dupes = true;
            } else if bound_names.get(&key).is_some_and(|&def_id| def_id != item.def_id) {
                shadows = true;
            }

            let prefix = if dupes || shadows {
                format!("{}::", tcx.def_path_str(trait_ref.def_id()))
            } else {
                String::new()
            };
            let mut is_shadowed = false;

            if let Some(&def_id) = bound_names.get(&key)
                && def_id != item.def_id
            {
                is_shadowed = true;

                let rename_message = if def_id.is_local() { ", consider renaming it" } else { "" };
                err.span_label(
                    tcx.def_span(def_id),
                    format!("`{prefix}{name}` shadowed here{rename_message}"),
                );
            }

            let rename_message = if is_shadowed { ", consider renaming it" } else { "" };

            if let Some(sp) = tcx.hir_span_if_local(item.def_id) {
                err.span_label(sp, format!("`{prefix}{name}` defined here{rename_message}"));
            }
        }
        if potential_assoc_items.len() == missing_assoc_items.len() {
            // When the amount of missing associated types equals the number of
            // extra type arguments present. A suggesting to replace the generic args with
            // associated types is already emitted.
            already_has_generics_args_suggestion = true;
        } else if let (Ok(snippet), false, false) =
            (tcx.sess.source_map().span_to_snippet(principal_span), dupes, shadows)
        {
            let bindings: Vec<_> = missing_assoc_items
                .iter()
                .map(|(item, _)| {
                    format!(
                        "{} = /* {} */",
                        item.name(),
                        match item.kind {
                            ty::AssocKind::Const { .. } => "CONST",
                            ty::AssocKind::Type { .. } => "Type",
                            ty::AssocKind::Fn { .. } => unreachable!(),
                        }
                    )
                })
                .collect();
            let code = if let Some(snippet) = snippet.strip_suffix("<>") {
                // Empty generics
                format!("{snippet}<{}>", bindings.join(", "))
            } else if let Some(snippet) = snippet.strip_suffix('>') {
                // Non-empty generics
                format!("{snippet}, {}>", bindings.join(", "))
            } else if in_expr_or_pat {
                // The user wrote `Trait`, so we don't have a term we can suggest, but at least we
                // can clue them to the correct syntax `Trait::<Item = /* ... */>`.
                format!("{}::<{}>", snippet, bindings.join(", "))
            } else {
                // The user wrote `Trait`, so we don't have a term we can suggest, but at least we
                // can clue them to the correct syntax `Trait<Item = /* ... */>`.
                format!("{}<{}>", snippet, bindings.join(", "))
            };
            suggestions.push((principal_span, code));
        } else if dupes {
            where_constraints.push(principal_span);
        }

        // FIXME: This note doesn't make sense, get rid of this outright.
        //        I don't see how adding a type param (to the trait?) would help.
        //        If the user can modify the trait, they should just rename one of the assoc tys.
        //        What does it mean with the rest of the message?
        //        Does it suggest adding equality predicates (unimplemented) to the trait object
        //        type? (pseudo) "dyn B + <Self as B>::X = T + <Self as A>::X = U"?
        //        Instead, maybe mention shadowing if applicable (yes, even when no "relevant"
        //        bindings were provided).
        let where_msg = "consider introducing a new type parameter, adding `where` constraints \
                         using the fully-qualified path to the associated types";
        if !where_constraints.is_empty() && suggestions.is_empty() {
            // If there are duplicates associated type names and a single trait bound do not
            // use structured suggestion, it means that there are multiple supertraits with
            // the same associated type name.
            err.help(where_msg);
        }
        if suggestions.len() != 1 || already_has_generics_args_suggestion {
            // We don't need this label if there's an inline suggestion, show otherwise.
            let mut names: FxIndexMap<_, usize> = FxIndexMap::default();
            for (item, _) in &missing_assoc_items {
                items_count += 1;
                *names.entry(item.name()).or_insert(0) += 1;
            }
            let mut label = vec![];
            for (item, trait_ref) in &missing_assoc_items {
                let name = item.name();
                let postfix = if names[&name] > 1 {
                    format!(" (from trait `{}`)", trait_ref.print_trait_sugared())
                } else {
                    String::new()
                };
                label.push(format!("`{}`{}", name, postfix));
            }
            if !label.is_empty() {
                err.span_label(
                    principal_span,
                    format!(
                        "{descr}{s} {names} must be specified",
                        s = pluralize!(label.len()),
                        names = label.join(", "),
                    ),
                );
            }
        }
        suggestions.sort_by_key(|&(span, _)| span);
        // There are cases where one bound points to a span within another bound's span, like when
        // you have code like the following (#115019), so we skip providing a suggestion in those
        // cases to avoid having a malformed suggestion.
        //
        // pub struct Flatten<I> {
        //     inner: <IntoIterator<Item: IntoIterator<Item: >>::IntoIterator as Item>::core,
        //             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        //             |                  ^^^^^^^^^^^^^^^^^^^^^
        //             |                  |
        //             |                  associated types `Item`, `IntoIter` must be specified
        //             associated types `Item`, `IntoIter` must be specified
        // }
        let overlaps = suggestions.windows(2).any(|pair| pair[0].0.overlaps(pair[1].0));
        if !suggestions.is_empty() && !overlaps {
            err.multipart_suggestion(
                format!("specify the {descr}{s}", s = pluralize!(items_count)),
                suggestions,
                Applicability::HasPlaceholders,
            );
            if !where_constraints.is_empty() {
                err.span_help(where_constraints, where_msg);
            }
        }

        Err(err.emit())
    }

    /// On ambiguous associated type, look for an associated function whose name matches the
    /// extended path and, if found, emit an E0223 error with a structured suggestion.
    /// e.g. for `String::from::utf8`, suggest `String::from_utf8` (#109195)
    pub(crate) fn maybe_report_similar_assoc_fn(
        &self,
        span: Span,
        qself_ty: Ty<'tcx>,
        qself: &hir::Ty<'_>,
    ) -> Result<(), ErrorGuaranteed> {
        let tcx = self.tcx();
        if let Some((_, node)) = tcx.hir_parent_iter(qself.hir_id).skip(1).next()
            && let hir::Node::Expr(hir::Expr {
                kind:
                    hir::ExprKind::Path(hir::QPath::TypeRelative(
                        hir::Ty {
                            kind:
                                hir::TyKind::Path(hir::QPath::TypeRelative(
                                    _,
                                    hir::PathSegment { ident: ident2, .. },
                                )),
                            ..
                        },
                        hir::PathSegment { ident: ident3, .. },
                    )),
                ..
            }) = node
            && let Some(inherent_impls) = qself_ty
                .ty_adt_def()
                .map(|adt_def| tcx.inherent_impls(adt_def.did()))
                .or_else(|| {
                    simplify_type(tcx, qself_ty, TreatParams::InstantiateWithInfer)
                        .map(|simple_ty| tcx.incoherent_impls(simple_ty))
                })
            && let name = Symbol::intern(&format!("{ident2}_{ident3}"))
            && let Some(item) = inherent_impls
                .iter()
                .flat_map(|&inherent_impl| {
                    tcx.associated_items(inherent_impl).filter_by_name_unhygienic(name)
                })
                .next()
            && item.is_fn()
        {
            Err(struct_span_code_err!(self.dcx(), span, E0223, "ambiguous associated type")
                .with_span_suggestion_verbose(
                    ident2.span.to(ident3.span),
                    format!("there is an associated function with a similar name: `{name}`"),
                    name,
                    Applicability::MaybeIncorrect,
                )
                .emit())
        } else {
            Ok(())
        }
    }

    pub fn report_prohibited_generic_args<'a>(
        &self,
        segments: impl Iterator<Item = &'a hir::PathSegment<'a>> + Clone,
        args_visitors: impl Iterator<Item = &'a hir::GenericArg<'a>> + Clone,
        err_extend: GenericsArgsErrExtend<'a>,
    ) -> ErrorGuaranteed {
        #[derive(PartialEq, Eq, Hash)]
        enum ProhibitGenericsArg {
            Lifetime,
            Type,
            Const,
            Infer,
        }

        let mut prohibit_args = FxIndexSet::default();
        args_visitors.for_each(|arg| {
            match arg {
                hir::GenericArg::Lifetime(_) => prohibit_args.insert(ProhibitGenericsArg::Lifetime),
                hir::GenericArg::Type(_) => prohibit_args.insert(ProhibitGenericsArg::Type),
                hir::GenericArg::Const(_) => prohibit_args.insert(ProhibitGenericsArg::Const),
                hir::GenericArg::Infer(_) => prohibit_args.insert(ProhibitGenericsArg::Infer),
            };
        });

        let segments: Vec<_> = segments.collect();
        let types_and_spans: Vec<_> = segments
            .iter()
            .flat_map(|segment| {
                if segment.args().args.is_empty() {
                    None
                } else {
                    Some((
                        match segment.res {
                            Res::PrimTy(ty) => {
                                format!("{} `{}`", segment.res.descr(), ty.name())
                            }
                            Res::Def(_, def_id)
                                if let Some(name) = self.tcx().opt_item_name(def_id) =>
                            {
                                format!("{} `{name}`", segment.res.descr())
                            }
                            Res::Err => "this type".to_string(),
                            _ => segment.res.descr().to_string(),
                        },
                        segment.ident.span,
                    ))
                }
            })
            .collect();
        let this_type = listify(&types_and_spans, |(t, _)| t.to_string())
            .expect("expected one segment to deny");

        let arg_spans: Vec<Span> =
            segments.iter().flat_map(|segment| segment.args().args).map(|arg| arg.span()).collect();

        let mut kinds = Vec::with_capacity(4);
        prohibit_args.iter().for_each(|arg| match arg {
            ProhibitGenericsArg::Lifetime => kinds.push("lifetime"),
            ProhibitGenericsArg::Type => kinds.push("type"),
            ProhibitGenericsArg::Const => kinds.push("const"),
            ProhibitGenericsArg::Infer => kinds.push("generic"),
        });

        let s = pluralize!(kinds.len());
        let kind =
            listify(&kinds, |k| k.to_string()).expect("expected at least one generic to prohibit");
        let last_span = *arg_spans.last().unwrap();
        let span: MultiSpan = arg_spans.into();
        let mut err = struct_span_code_err!(
            self.dcx(),
            span,
            E0109,
            "{kind} arguments are not allowed on {this_type}",
        );
        err.span_label(last_span, format!("{kind} argument{s} not allowed"));
        for (what, span) in types_and_spans {
            err.span_label(span, format!("not allowed on {what}"));
        }
        generics_args_err_extend(self.tcx(), segments.into_iter(), &mut err, err_extend);
        err.emit()
    }

    pub fn report_trait_object_addition_traits(
        &self,
        regular_traits: &Vec<(ty::PolyTraitPredicate<'tcx>, SmallVec<[Span; 1]>)>,
    ) -> ErrorGuaranteed {
        // we use the last span to point at the traits themselves,
        // and all other preceding spans are trait alias expansions.
        let (&first_span, first_alias_spans) = regular_traits[0].1.split_last().unwrap();
        let (&second_span, second_alias_spans) = regular_traits[1].1.split_last().unwrap();
        let mut err = struct_span_code_err!(
            self.dcx(),
            *regular_traits[1].1.first().unwrap(),
            E0225,
            "only auto traits can be used as additional traits in a trait object"
        );
        err.span_label(first_span, "first non-auto trait");
        for &alias_span in first_alias_spans {
            err.span_label(alias_span, "first non-auto trait comes from this alias");
        }
        err.span_label(second_span, "additional non-auto trait");
        for &alias_span in second_alias_spans {
            err.span_label(alias_span, "second non-auto trait comes from this alias");
        }
        err.help(format!(
            "consider creating a new trait with all of these as supertraits and using that \
             trait here instead: `trait NewTrait: {} {{}}`",
            regular_traits
                .iter()
                // FIXME: This should `print_sugared`, but also needs to integrate projection bounds...
                .map(|(pred, _)| pred
                    .map_bound(|pred| pred.trait_ref)
                    .print_only_trait_path()
                    .to_string())
                .collect::<Vec<_>>()
                .join(" + "),
        ));
        err.note(
            "auto-traits like `Send` and `Sync` are traits that have special properties; \
             for more information on them, visit \
             <https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits>",
        );
        err.emit()
    }

    pub fn report_trait_object_with_no_traits(
        &self,
        span: Span,
        user_written_clauses: impl IntoIterator<Item = (ty::Clause<'tcx>, Span)>,
    ) -> ErrorGuaranteed {
        let tcx = self.tcx();
        let trait_alias_span = user_written_clauses
            .into_iter()
            .filter_map(|(clause, _)| clause.as_trait_clause())
            .find(|trait_ref| tcx.is_trait_alias(trait_ref.def_id()))
            .map(|trait_ref| tcx.def_span(trait_ref.def_id()));

        self.dcx().emit_err(TraitObjectDeclaredWithNoTraits { span, trait_alias_span })
    }
}

/// Emit an error for the given associated item constraint.
pub fn prohibit_assoc_item_constraint(
    cx: &dyn HirTyLowerer<'_>,
    constraint: &hir::AssocItemConstraint<'_>,
    segment: Option<(DefId, &hir::PathSegment<'_>, Span)>,
) -> ErrorGuaranteed {
    let tcx = cx.tcx();
    let mut err = cx.dcx().create_err(AssocItemConstraintsNotAllowedHere {
        span: constraint.span,
        fn_trait_expansion: if let Some((_, segment, span)) = segment
            && segment.args().parenthesized == hir::GenericArgsParentheses::ParenSugar
        {
            Some(ParenthesizedFnTraitExpansion {
                span,
                expanded_type: fn_trait_to_string(tcx, segment, false),
            })
        } else {
            None
        },
    });

    // Emit a suggestion to turn the assoc item binding into a generic arg
    // if the relevant item has a generic param whose name matches the binding name;
    // otherwise suggest the removal of the binding.
    if let Some((def_id, segment, _)) = segment
        && segment.args().parenthesized == hir::GenericArgsParentheses::No
    {
        // Suggests removal of the offending binding
        let suggest_removal = |e: &mut Diag<'_>| {
            let constraints = segment.args().constraints;
            let args = segment.args().args;

            // Compute the span to remove based on the position
            // of the binding. We do that as follows:
            //  1. Find the index of the binding in the list of bindings
            //  2. Locate the spans preceding and following the binding.
            //     If it's the first binding the preceding span would be
            //     that of the last arg
            //  3. Using this information work out whether the span
            //     to remove will start from the end of the preceding span,
            //     the start of the next span or will simply be the
            //     span encomassing everything within the generics brackets

            let Some(index) = constraints.iter().position(|b| b.hir_id == constraint.hir_id) else {
                bug!("a type binding exists but its HIR ID not found in generics");
            };

            let preceding_span = if index > 0 {
                Some(constraints[index - 1].span)
            } else {
                args.last().map(|a| a.span())
            };

            let next_span = constraints.get(index + 1).map(|constraint| constraint.span);

            let removal_span = match (preceding_span, next_span) {
                (Some(prec), _) => constraint.span.with_lo(prec.hi()),
                (None, Some(next)) => constraint.span.with_hi(next.lo()),
                (None, None) => {
                    let Some(generics_span) = segment.args().span_ext() else {
                        bug!("a type binding exists but generic span is empty");
                    };

                    generics_span
                }
            };

            // Now emit the suggestion
            e.span_suggestion_verbose(
                removal_span,
                format!("consider removing this associated item {}", constraint.kind.descr()),
                "",
                Applicability::MaybeIncorrect,
            );
        };

        // Suggest replacing the associated item binding with a generic argument.
        // i.e., replacing `<..., T = A, ...>` with `<..., A, ...>`.
        let suggest_direct_use = |e: &mut Diag<'_>, sp: Span| {
            if let Ok(snippet) = tcx.sess.source_map().span_to_snippet(sp) {
                e.span_suggestion_verbose(
                    constraint.span,
                    format!("to use `{snippet}` as a generic argument specify it directly"),
                    snippet,
                    Applicability::MaybeIncorrect,
                );
            }
        };

        // Check if the type has a generic param with the same name
        // as the assoc type name in the associated item binding.
        let generics = tcx.generics_of(def_id);
        let matching_param = generics.own_params.iter().find(|p| p.name == constraint.ident.name);

        // Now emit the appropriate suggestion
        if let Some(matching_param) = matching_param {
            match (constraint.kind, &matching_param.kind) {
                (
                    hir::AssocItemConstraintKind::Equality { term: hir::Term::Ty(ty) },
                    GenericParamDefKind::Type { .. },
                ) => suggest_direct_use(&mut err, ty.span),
                (
                    hir::AssocItemConstraintKind::Equality { term: hir::Term::Const(c) },
                    GenericParamDefKind::Const { .. },
                ) => {
                    suggest_direct_use(&mut err, c.span);
                }
                (hir::AssocItemConstraintKind::Bound { bounds }, _) => {
                    // Suggest `impl<T: Bound> Trait<T> for Foo` when finding
                    // `impl Trait<T: Bound> for Foo`

                    // Get the parent impl block based on the binding we have
                    // and the trait DefId
                    let impl_block = tcx
                        .hir_parent_iter(constraint.hir_id)
                        .find_map(|(_, node)| node.impl_block_of_trait(def_id));

                    let type_with_constraints =
                        tcx.sess.source_map().span_to_snippet(constraint.span);

                    if let Some(impl_block) = impl_block
                        && let Ok(type_with_constraints) = type_with_constraints
                    {
                        // Filter out the lifetime parameters because
                        // they should be declared before the type parameter
                        let lifetimes: String = bounds
                            .iter()
                            .filter_map(|bound| {
                                if let hir::GenericBound::Outlives(lifetime) = bound {
                                    Some(format!("{lifetime}, "))
                                } else {
                                    None
                                }
                            })
                            .collect();
                        // Figure out a span and suggestion string based on
                        // whether there are any existing parameters
                        let param_decl = if let Some(param_span) =
                            impl_block.generics.span_for_param_suggestion()
                        {
                            (param_span, format!(", {lifetimes}{type_with_constraints}"))
                        } else {
                            (
                                impl_block.generics.span.shrink_to_lo(),
                                format!("<{lifetimes}{type_with_constraints}>"),
                            )
                        };
                        let suggestions = vec![
                            param_decl,
                            (constraint.span.with_lo(constraint.ident.span.hi()), String::new()),
                        ];

                        err.multipart_suggestion(
                            "declare the type parameter right after the `impl` keyword",
                            suggestions,
                            Applicability::MaybeIncorrect,
                        );
                    }
                }
                _ => suggest_removal(&mut err),
            }
        } else {
            suggest_removal(&mut err);
        }
    }

    err.emit()
}

pub(crate) fn fn_trait_to_string(
    tcx: TyCtxt<'_>,
    trait_segment: &hir::PathSegment<'_>,
    parenthesized: bool,
) -> String {
    let args = trait_segment
        .args
        .and_then(|args| args.args.first())
        .and_then(|arg| match arg {
            hir::GenericArg::Type(ty) => match ty.kind {
                hir::TyKind::Tup(t) => t
                    .iter()
                    .map(|e| tcx.sess.source_map().span_to_snippet(e.span))
                    .collect::<Result<Vec<_>, _>>()
                    .map(|a| a.join(", ")),
                _ => tcx.sess.source_map().span_to_snippet(ty.span),
            }
            .map(|s| {
                // `is_empty()` checks to see if the type is the unit tuple, if so we don't want a comma
                if parenthesized || s.is_empty() { format!("({s})") } else { format!("({s},)") }
            })
            .ok(),
            _ => None,
        })
        .unwrap_or_else(|| "()".to_string());

    let ret = trait_segment
        .args()
        .constraints
        .iter()
        .find_map(|c| {
            if c.ident.name == sym::Output
                && let Some(ty) = c.ty()
                && ty.span != tcx.hir_span(trait_segment.hir_id)
            {
                tcx.sess.source_map().span_to_snippet(ty.span).ok()
            } else {
                None
            }
        })
        .unwrap_or_else(|| "()".to_string());

    if parenthesized {
        format!("{}{} -> {}", trait_segment.ident, args, ret)
    } else {
        format!("{}<{}, Output={}>", trait_segment.ident, args, ret)
    }
}

/// Used for generics args error extend.
pub enum GenericsArgsErrExtend<'tcx> {
    EnumVariant {
        qself: &'tcx hir::Ty<'tcx>,
        assoc_segment: &'tcx hir::PathSegment<'tcx>,
        adt_def: AdtDef<'tcx>,
    },
    OpaqueTy,
    PrimTy(hir::PrimTy),
    SelfTyAlias {
        def_id: DefId,
        span: Span,
    },
    SelfTyParam(Span),
    Param(DefId),
    DefVariant(&'tcx [hir::PathSegment<'tcx>]),
    None,
}

fn generics_args_err_extend<'a>(
    tcx: TyCtxt<'_>,
    segments: impl Iterator<Item = &'a hir::PathSegment<'a>> + Clone,
    err: &mut Diag<'_>,
    err_extend: GenericsArgsErrExtend<'a>,
) {
    match err_extend {
        GenericsArgsErrExtend::EnumVariant { qself, assoc_segment, adt_def } => {
            err.note("enum variants can't have type parameters");
            let type_name = tcx.item_name(adt_def.did());
            let msg = format!(
                "you might have meant to specify type parameters on enum \
                `{type_name}`"
            );
            let Some(args) = assoc_segment.args else {
                return;
            };
            // Get the span of the generics args *including* the leading `::`.
            // We do so by stretching args.span_ext to the left by 2. Earlier
            // it was done based on the end of assoc segment but that sometimes
            // led to impossible spans and caused issues like #116473
            let args_span = args.span_ext.with_lo(args.span_ext.lo() - BytePos(2));
            if tcx.generics_of(adt_def.did()).is_empty() {
                // FIXME(estebank): we could also verify that the arguments being
                // work for the `enum`, instead of just looking if it takes *any*.
                err.span_suggestion_verbose(
                    args_span,
                    format!("{type_name} doesn't have generic parameters"),
                    "",
                    Applicability::MachineApplicable,
                );
                return;
            }
            let Ok(snippet) = tcx.sess.source_map().span_to_snippet(args_span) else {
                err.note(msg);
                return;
            };
            let (qself_sugg_span, is_self) =
                if let hir::TyKind::Path(hir::QPath::Resolved(_, path)) = &qself.kind {
                    // If the path segment already has type params, we want to overwrite
                    // them.
                    match &path.segments {
                        // `segment` is the previous to last element on the path,
                        // which would normally be the `enum` itself, while the last
                        // `_` `PathSegment` corresponds to the variant.
                        [
                            ..,
                            hir::PathSegment {
                                ident, args, res: Res::Def(DefKind::Enum, _), ..
                            },
                            _,
                        ] => (
                            // We need to include the `::` in `Type::Variant::<Args>`
                            // to point the span to `::<Args>`, not just `<Args>`.
                            ident
                                .span
                                .shrink_to_hi()
                                .to(args.map_or(ident.span.shrink_to_hi(), |a| a.span_ext)),
                            false,
                        ),
                        [segment] => {
                            (
                                // We need to include the `::` in `Type::Variant::<Args>`
                                // to point the span to `::<Args>`, not just `<Args>`.
                                segment.ident.span.shrink_to_hi().to(segment
                                    .args
                                    .map_or(segment.ident.span.shrink_to_hi(), |a| a.span_ext)),
                                kw::SelfUpper == segment.ident.name,
                            )
                        }
                        _ => {
                            err.note(msg);
                            return;
                        }
                    }
                } else {
                    err.note(msg);
                    return;
                };
            let suggestion = vec![
                if is_self {
                    // Account for people writing `Self::Variant::<Args>`, where
                    // `Self` is the enum, and suggest replacing `Self` with the
                    // appropriate type: `Type::<Args>::Variant`.
                    (qself.span, format!("{type_name}{snippet}"))
                } else {
                    (qself_sugg_span, snippet)
                },
                (args_span, String::new()),
            ];
            err.multipart_suggestion(msg, suggestion, Applicability::MaybeIncorrect);
        }
        GenericsArgsErrExtend::DefVariant(segments) => {
            let args: Vec<Span> = segments
                .iter()
                .filter_map(|segment| match segment.res {
                    Res::Def(
                        DefKind::Ctor(CtorOf::Variant, _) | DefKind::Variant | DefKind::Enum,
                        _,
                    ) => segment.args().span_ext().map(|s| s.with_lo(segment.ident.span.hi())),
                    _ => None,
                })
                .collect();
            if args.len() > 1
                && let Some(span) = args.into_iter().next_back()
            {
                err.note(
                    "generic arguments are not allowed on both an enum and its variant's path \
                     segments simultaneously; they are only valid in one place or the other",
                );
                err.span_suggestion_verbose(
                    span,
                    "remove the generics arguments from one of the path segments",
                    String::new(),
                    Applicability::MaybeIncorrect,
                );
            }
        }
        GenericsArgsErrExtend::PrimTy(prim_ty) => {
            let name = prim_ty.name_str();
            for segment in segments {
                if let Some(args) = segment.args {
                    err.span_suggestion_verbose(
                        segment.ident.span.shrink_to_hi().to(args.span_ext),
                        format!("primitive type `{name}` doesn't have generic parameters"),
                        "",
                        Applicability::MaybeIncorrect,
                    );
                }
            }
        }
        GenericsArgsErrExtend::OpaqueTy => {
            err.note("`impl Trait` types can't have type parameters");
        }
        GenericsArgsErrExtend::Param(def_id) => {
            let span = tcx.def_ident_span(def_id).unwrap();
            let kind = tcx.def_descr(def_id);
            let name = tcx.item_name(def_id);
            err.span_note(span, format!("{kind} `{name}` defined here"));
        }
        GenericsArgsErrExtend::SelfTyParam(span) => {
            err.span_suggestion_verbose(
                span,
                "the `Self` type doesn't accept type parameters",
                "",
                Applicability::MaybeIncorrect,
            );
        }
        GenericsArgsErrExtend::SelfTyAlias { def_id, span } => {
            let ty = tcx.at(span).type_of(def_id).instantiate_identity().skip_norm_wip();
            let span_of_impl = tcx.span_of_impl(def_id);
            let ty::Adt(self_def, _) = *ty.kind() else { return };
            let def_id = self_def.did();

            let type_name = tcx.item_name(def_id);
            let span_of_ty = tcx.def_ident_span(def_id);
            let generics = tcx.generics_of(def_id).count();

            let msg = format!("`Self` is of type `{ty}`");
            if let (Ok(i_sp), Some(t_sp)) = (span_of_impl, span_of_ty) {
                let mut span: MultiSpan = vec![t_sp].into();
                span.push_span_label(
                    i_sp,
                    format!("`Self` is on type `{type_name}` in this `impl`"),
                );
                let mut postfix = "";
                if generics == 0 {
                    postfix = ", which doesn't have generic parameters";
                }
                span.push_span_label(t_sp, format!("`Self` corresponds to this type{postfix}"));
                err.span_note(span, msg);
            } else {
                err.note(msg);
            }
            for segment in segments {
                if let Some(args) = segment.args
                    && segment.ident.name == kw::SelfUpper
                {
                    if generics == 0 {
                        // FIXME(estebank): we could also verify that the arguments being
                        // work for the `enum`, instead of just looking if it takes *any*.
                        err.span_suggestion_verbose(
                            segment.ident.span.shrink_to_hi().to(args.span_ext),
                            "the `Self` type doesn't accept type parameters",
                            "",
                            Applicability::MachineApplicable,
                        );
                        return;
                    } else {
                        err.span_suggestion_verbose(
                            segment.ident.span,
                            format!(
                                "the `Self` type doesn't accept type parameters, use the \
                                concrete type's name `{type_name}` instead if you want to \
                                specify its type parameters"
                            ),
                            type_name,
                            Applicability::MaybeIncorrect,
                        );
                    }
                }
            }
        }
        _ => {}
    }
}

pub(super) struct AmbiguityBetweenVariantAndAssocItem<'tcx> {
    pub(super) variant_def_id: DefId,
    pub(super) item_def_id: DefId,
    pub(super) span: Span,
    pub(super) segment_ident: Ident,
    pub(super) bound_def_id: DefId,
    pub(super) self_ty: Ty<'tcx>,
    pub(super) tcx: TyCtxt<'tcx>,
    pub(super) mode: super::LowerTypeRelativePathMode,
}

impl<'a, 'tcx> rustc_errors::Diagnostic<'a, ()> for AmbiguityBetweenVariantAndAssocItem<'tcx> {
    fn into_diag(
        self,
        dcx: rustc_errors::DiagCtxtHandle<'a>,
        level: rustc_errors::Level,
    ) -> Diag<'a, ()> {
        let Self {
            variant_def_id,
            item_def_id,
            span,
            segment_ident,
            bound_def_id,
            self_ty,
            tcx,
            mode,
        } = self;
        let mut lint = Diag::new(dcx, level, "ambiguous associated item");

        let mut could_refer_to = |kind: DefKind, def_id, also| {
            let note_msg = format!(
                "`{}` could{} refer to the {} defined here",
                segment_ident,
                also,
                tcx.def_kind_descr(kind, def_id)
            );
            lint.span_note(tcx.def_span(def_id), note_msg);
        };

        could_refer_to(DefKind::Variant, variant_def_id, "");
        could_refer_to(mode.def_kind_for_diagnostics(), item_def_id, " also");

        lint.span_suggestion(
            span,
            "use fully-qualified syntax",
            format!("<{} as {}>::{}", self_ty, tcx.item_name(bound_def_id), segment_ident),
            Applicability::MachineApplicable,
        );
        lint
    }
}

fn assoc_tag_str(assoc_tag: ty::AssocTag) -> &'static str {
    match assoc_tag {
        ty::AssocTag::Fn => "function",
        ty::AssocTag::Const => "constant",
        ty::AssocTag::Type => "type",
    }
}
