//! The `HirDisplay` trait, which serves two purposes: Turning various bits from
//! HIR back into source code, and just displaying them for debugging/testing
//! purposes.

use std::{
    fmt::{self, Debug},
    mem,
};

use base_db::{Crate, FxIndexMap};
use either::Either;
use hir_def::{
    ExpressionStoreOwnerId, FindPathConfig, GenericDefId, GenericParamId, HasModule,
    ItemContainerId, LocalFieldId, Lookup, ModuleDefId, ModuleId, TraitId, TypeAliasId,
    expr_store::{ExpressionStore, path::Path},
    find_path::{self, PrefixKind},
    hir::{
        ClosureKind as HirClosureKind, CoroutineKind, PatId,
        generics::{GenericParams, TypeOrConstParamData, TypeParamProvenance, WherePredicate},
    },
    item_scope::ItemInNs,
    item_tree::FieldsShape,
    lang_item::LangItems,
    signatures::{
        ConstSignature, EnumSignature, FunctionSignature, StaticSignature, StructSignature,
        TraitSignature, TypeAliasSignature, UnionSignature, VariantFields,
    },
    type_ref::{
        ConstRef, LifetimeRef, LifetimeRefId, TraitBoundModifier, TypeBound, TypeRef, TypeRefId,
        UseArgRef,
    },
    visibility::Visibility,
};
use hir_expand::{mod_path::PathKind, name::Name};
use intern::{Internable, Interned, sym};
use itertools::Itertools;
use la_arena::ArenaMap;
use rustc_abi::ExternAbi;
use rustc_apfloat::{
    Float,
    ieee::{Half as f16, Quad as f128},
};
use rustc_ast_ir::FloatTy;
use rustc_hash::FxHashSet;
use rustc_type_ir::{
    AliasTyKind, BoundVarIndexKind, CoroutineArgsParts, RegionKind, Upcast,
    inherent::{GenericArgs as _, IntoKind, Term as _, Ty as _, Tys as _},
};
use smallvec::SmallVec;
use span::Edition;
use stdx::never;

use crate::{
    CallableDefId, ImplTraitId, MemoryMap, ParamEnvAndCrate, consteval,
    db::{GeneralConstId, HirDatabase},
    generics::{ProvenanceSplit, generics},
    layout::Layout,
    lower::GenericPredicates,
    mir::pad16,
    next_solver::{
        AliasTy, Allocation, Clause, ClauseKind, Const, ConstKind, DbInterner,
        ExistentialPredicate, FnSig, GenericArg, GenericArgKind, GenericArgs, ParamEnv, PolyFnSig,
        Region, StoredEarlyBinder, StoredTy, Term, TermId, TermKind, TraitPredicate, TraitRef, Ty,
        TyKind, TypingMode, Unnormalized, ValTree,
        abi::Safety,
        infer::{DbInternerInferExt, traits::ObligationCause},
    },
    primitive,
    utils::{detect_variant_from_bytes, fn_traits},
};

fn async_gen_item_ty_from_yield_ty<'db>(
    lang_items: &LangItems,
    yield_ty: Ty<'db>,
) -> Option<Ty<'db>> {
    let poll_id = lang_items.Poll.map(hir_def::AdtId::EnumId)?;
    let option_id = lang_items.Option.map(hir_def::AdtId::EnumId)?;

    let TyKind::Adt(poll_def, poll_args) = yield_ty.kind() else {
        return None;
    };
    if poll_def.def_id() != poll_id {
        return None;
    }
    let [poll_inner] = poll_args.as_slice() else {
        return None;
    };
    let poll_inner = poll_inner.ty()?;

    let TyKind::Adt(option_def, option_args) = poll_inner.kind() else {
        return None;
    };
    if option_def.def_id() != option_id {
        return None;
    }
    let [item] = option_args.as_slice() else {
        return None;
    };
    item.ty()
}

pub type Result<T = (), E = HirDisplayError> = std::result::Result<T, E>;

pub trait HirWrite: fmt::Write {
    fn start_location_link(&mut self, _location: ModuleDefId) {}
    fn start_location_link_generic(&mut self, _location: GenericParamId) {}
    fn end_location_link(&mut self) {}
}

// String will ignore link metadata
impl HirWrite for String {}

// `core::Formatter` will ignore metadata
impl HirWrite for fmt::Formatter<'_> {}

pub struct HirFormatter<'a, 'db> {
    /// The database handle
    pub db: &'db dyn HirDatabase,
    pub interner: DbInterner<'db>,
    /// The sink to write into
    fmt: &'a mut dyn HirWrite,
    /// A buffer to intercept writes with, this allows us to track the overall size of the formatted output.
    buf: String,
    /// The current size of the formatted output.
    curr_size: usize,
    /// Size from which we should truncate the output.
    max_size: Option<usize>,
    /// When rendering something that has a concept of "children" (like fields in a struct), this limits
    /// how many should be rendered.
    pub entity_limit: Option<usize>,
    /// When rendering functions, whether to show the constraint from the container
    show_container_bounds: bool,
    omit_verbose_types: bool,
    closure_style: ClosureStyle,
    display_lifetimes: DisplayLifetime,
    display_kind: DisplayKind,
    display_target: DisplayTarget,
    /// We can have recursive bounds like the following case:
    /// ```ignore
    /// where
    ///     T: Foo,
    ///     T::FooAssoc: Baz<<T::FooAssoc as Bar>::BarAssoc> + Bar
    /// ```
    /// So, record the projection types met while formatting bounds and
    /// prevent recursing into their bounds to avoid infinite loops.
    currently_formatting_bounds: FxHashSet<AliasTy<'db>>,
    /// Whether formatting `impl Trait1 + Trait2` or `dyn Trait1 + Trait2` needs parentheses around it,
    /// for example when formatting `&(impl Trait1 + Trait2)`.
    trait_bounds_need_parens: bool,
}

// FIXME: To consider, ref and dyn trait lifetimes can be omitted if they are `'_`, path args should
// not be when in signatures
// So this enum does not encode this well enough
// Also 'static can be omitted for ref and dyn trait lifetimes in static/const item types
// FIXME: Also named lifetimes may be rendered in places where their name is not in scope?
#[derive(Copy, Clone)]
pub enum DisplayLifetime {
    Always,
    OnlyStatic,
    OnlyNamed,
    OnlyNamedOrStatic,
    Never,
}

impl<'db> HirFormatter<'_, 'db> {
    pub fn start_location_link(&mut self, location: ModuleDefId) {
        self.fmt.start_location_link(location);
    }

    pub fn start_location_link_generic(&mut self, location: GenericParamId) {
        self.fmt.start_location_link_generic(location);
    }

    pub fn end_location_link(&mut self) {
        self.fmt.end_location_link();
    }

    fn format_bounds_with<F: FnOnce(&mut Self) -> Result>(
        &mut self,
        target: AliasTy<'db>,
        format_bounds: F,
    ) -> Result {
        if self.currently_formatting_bounds.insert(target) {
            let result = format_bounds(self);
            self.currently_formatting_bounds.remove(&target);
            result
        } else {
            if self.display_kind.is_source_code() {
                Err(HirDisplayError::DisplaySourceCodeError(DisplaySourceCodeError::Cycle))
            } else {
                match target.kind {
                    AliasTyKind::Projection { def_id } => {
                        let def_id = def_id.0;
                        let ItemContainerId::TraitId(trait_) = def_id.loc(self.db).container else {
                            panic!("expected an assoc type");
                        };
                        let trait_name = &TraitSignature::of(self.db, trait_).name;
                        let assoc_type_name = &TypeAliasSignature::of(self.db, def_id).name;
                        write!(
                            self,
                            "<… as {}>::{}",
                            trait_name.display(self.db, self.edition()),
                            assoc_type_name.display(self.db, self.edition()),
                        )?;
                        if target.args.len() > 1 {
                            self.write_str("<…>")?;
                        }
                        Ok(())
                    }
                    AliasTyKind::Inherent { .. }
                    | AliasTyKind::Opaque { .. }
                    | AliasTyKind::Free { .. } => self.write_str("…"),
                }
            }
        }
    }

    fn render_region(&self, lifetime: Region<'db>) -> bool {
        match self.display_lifetimes {
            DisplayLifetime::Always => true,
            DisplayLifetime::OnlyStatic => matches!(lifetime.kind(), RegionKind::ReStatic),
            DisplayLifetime::OnlyNamed => {
                matches!(lifetime.kind(), RegionKind::ReEarlyParam(_))
            }
            DisplayLifetime::OnlyNamedOrStatic => {
                matches!(lifetime.kind(), RegionKind::ReStatic | RegionKind::ReEarlyParam(_))
            }
            DisplayLifetime::Never => false,
        }
    }
}

pub trait HirDisplay<'db> {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result;

    /// Returns a `Display`able type that is human-readable.
    fn into_displayable<'a>(
        &'a self,
        db: &'db dyn HirDatabase,
        max_size: Option<usize>,
        limited_size: Option<usize>,
        omit_verbose_types: bool,
        display_target: DisplayTarget,
        display_kind: DisplayKind,
        closure_style: ClosureStyle,
        show_container_bounds: bool,
    ) -> HirDisplayWrapper<'a, 'db, Self>
    where
        Self: Sized,
    {
        assert!(
            !matches!(display_kind, DisplayKind::SourceCode { .. }),
            "HirDisplayWrapper cannot fail with DisplaySourceCodeError, use HirDisplay::hir_fmt directly instead"
        );
        HirDisplayWrapper {
            db,
            t: self,
            max_size,
            limited_size,
            omit_verbose_types,
            display_target,
            display_kind,
            closure_style,
            show_container_bounds,
            display_lifetimes: DisplayLifetime::OnlyNamedOrStatic,
        }
    }

    /// Returns a `Display`able type that is human-readable.
    /// Use this for showing types to the user (e.g. diagnostics)
    fn display<'a>(
        &'a self,
        db: &'db dyn HirDatabase,
        display_target: DisplayTarget,
    ) -> HirDisplayWrapper<'a, 'db, Self>
    where
        Self: Sized,
    {
        HirDisplayWrapper {
            db,
            t: self,
            max_size: None,
            limited_size: None,
            omit_verbose_types: false,
            closure_style: ClosureStyle::ImplFn,
            display_target,
            display_kind: DisplayKind::Diagnostics,
            show_container_bounds: false,
            display_lifetimes: DisplayLifetime::OnlyNamedOrStatic,
        }
    }

    /// Returns a `Display`able type that is human-readable and tries to be succinct.
    /// Use this for showing types to the user where space is constrained (e.g. doc popups)
    fn display_truncated<'a>(
        &'a self,
        db: &'db dyn HirDatabase,
        max_size: Option<usize>,
        display_target: DisplayTarget,
    ) -> HirDisplayWrapper<'a, 'db, Self>
    where
        Self: Sized,
    {
        HirDisplayWrapper {
            db,
            t: self,
            max_size,
            limited_size: None,
            omit_verbose_types: true,
            closure_style: ClosureStyle::ImplFn,
            display_target,
            display_kind: DisplayKind::Diagnostics,
            show_container_bounds: false,
            display_lifetimes: DisplayLifetime::OnlyNamedOrStatic,
        }
    }

    /// Returns a `Display`able type that is human-readable and tries to limit the number of items inside.
    /// Use this for showing definitions which may contain too many items, like `trait`, `struct`, `enum`
    fn display_limited<'a>(
        &'a self,
        db: &'db dyn HirDatabase,
        limited_size: Option<usize>,
        display_target: DisplayTarget,
    ) -> HirDisplayWrapper<'a, 'db, Self>
    where
        Self: Sized,
    {
        HirDisplayWrapper {
            db,
            t: self,
            max_size: None,
            limited_size,
            omit_verbose_types: true,
            closure_style: ClosureStyle::ImplFn,
            display_target,
            display_kind: DisplayKind::Diagnostics,
            show_container_bounds: false,
            display_lifetimes: DisplayLifetime::OnlyNamedOrStatic,
        }
    }

    /// Returns a String representation of `self` that can be inserted into the given module.
    /// Use this when generating code (e.g. assists)
    fn display_source_code<'a>(
        &'a self,
        db: &'db dyn HirDatabase,
        module_id: ModuleId,
        allow_opaque: bool,
    ) -> Result<String, DisplaySourceCodeError> {
        let mut result = String::new();
        let interner = DbInterner::new_with(db, module_id.krate(db));
        match self.hir_fmt(&mut HirFormatter {
            db,
            interner,
            fmt: &mut result,
            buf: String::with_capacity(20),
            curr_size: 0,
            max_size: None,
            entity_limit: None,
            omit_verbose_types: false,
            closure_style: ClosureStyle::ImplFn,
            display_target: DisplayTarget::from_crate(db, module_id.krate(db)),
            display_kind: DisplayKind::SourceCode { target_module_id: module_id, allow_opaque },
            show_container_bounds: false,
            display_lifetimes: DisplayLifetime::OnlyNamedOrStatic,
            currently_formatting_bounds: Default::default(),
            trait_bounds_need_parens: false,
        }) {
            Ok(()) => {}
            Err(HirDisplayError::FmtError) => panic!("Writing to String can't fail!"),
            Err(HirDisplayError::DisplaySourceCodeError(e)) => return Err(e),
        };
        Ok(result)
    }

    /// Returns a String representation of `self` for test purposes
    fn display_test<'a>(
        &'a self,
        db: &'db dyn HirDatabase,
        display_target: DisplayTarget,
    ) -> HirDisplayWrapper<'a, 'db, Self>
    where
        Self: Sized,
    {
        HirDisplayWrapper {
            db,
            t: self,
            max_size: None,
            limited_size: None,
            omit_verbose_types: false,
            closure_style: ClosureStyle::ImplFn,
            display_target,
            display_kind: DisplayKind::Test,
            show_container_bounds: false,
            display_lifetimes: DisplayLifetime::Always,
        }
    }

    /// Returns a String representation of `self` that shows the constraint from
    /// the container for functions
    fn display_with_container_bounds<'a>(
        &'a self,
        db: &'db dyn HirDatabase,
        show_container_bounds: bool,
        display_target: DisplayTarget,
    ) -> HirDisplayWrapper<'a, 'db, Self>
    where
        Self: Sized,
    {
        HirDisplayWrapper {
            db,
            t: self,
            max_size: None,
            limited_size: None,
            omit_verbose_types: false,
            closure_style: ClosureStyle::ImplFn,
            display_target,
            display_kind: DisplayKind::Diagnostics,
            show_container_bounds,
            display_lifetimes: DisplayLifetime::OnlyNamedOrStatic,
        }
    }
}

impl<'db> HirFormatter<'_, 'db> {
    pub fn krate(&self) -> Crate {
        self.display_target.krate
    }

    pub fn edition(&self) -> Edition {
        self.display_target.edition
    }

    #[inline]
    pub fn lang_items(&self) -> &'db LangItems {
        self.interner.lang_items()
    }

    pub fn write_joined<T: HirDisplay<'db>>(
        &mut self,
        iter: impl IntoIterator<Item = T>,
        sep: &str,
    ) -> Result {
        let mut first = true;
        for e in iter {
            if !first {
                write!(self, "{sep}")?;
            }
            first = false;

            // Abbreviate multiple omitted types with a single ellipsis.
            if self.should_truncate() {
                return write!(self, "{TYPE_HINT_TRUNCATION}");
            }

            e.hir_fmt(self)?;
        }
        Ok(())
    }

    /// This allows using the `write!` macro directly with a `HirFormatter`.
    pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result {
        // We write to a buffer first to track output size
        self.buf.clear();
        fmt::write(&mut self.buf, args)?;
        self.curr_size += self.buf.len();

        // Then we write to the internal formatter from the buffer
        self.fmt.write_str(&self.buf).map_err(HirDisplayError::from)
    }

    pub fn write_str(&mut self, s: &str) -> Result {
        self.fmt.write_str(s)?;
        Ok(())
    }

    pub fn write_char(&mut self, c: char) -> Result {
        self.fmt.write_char(c)?;
        Ok(())
    }

    pub fn should_truncate(&self) -> bool {
        match self.max_size {
            Some(max_size) => self.curr_size >= max_size,
            None => false,
        }
    }

    pub fn omit_verbose_types(&self) -> bool {
        self.omit_verbose_types
    }

    pub fn show_container_bounds(&self) -> bool {
        self.show_container_bounds
    }
}

#[derive(Debug, Clone, Copy)]
pub struct DisplayTarget {
    krate: Crate,
    pub edition: Edition,
}

impl DisplayTarget {
    pub fn from_crate(db: &dyn HirDatabase, krate: Crate) -> Self {
        let edition = krate.data(db).edition;
        Self { krate, edition }
    }
}

#[derive(Clone, Copy)]
pub enum DisplayKind {
    /// Display types for inlays, doc popups, autocompletion, etc...
    /// Showing `{unknown}` or not qualifying paths is fine here.
    /// There's no reason for this to fail.
    Diagnostics,
    /// Display types for inserting them in source files.
    /// The generated code should compile, so paths need to be qualified.
    SourceCode { target_module_id: ModuleId, allow_opaque: bool },
    /// Only for test purpose to keep real types
    Test,
}

impl DisplayKind {
    fn is_source_code(self) -> bool {
        matches!(self, Self::SourceCode { .. })
    }

    fn allows_opaque(self) -> bool {
        match self {
            Self::SourceCode { allow_opaque, .. } => allow_opaque,
            _ => true,
        }
    }
}

#[derive(Debug)]
pub enum DisplaySourceCodeError {
    PathNotFound,
    Coroutine,
    OpaqueType,
    Cycle,
}

pub enum HirDisplayError {
    /// Errors that can occur when generating source code
    DisplaySourceCodeError(DisplaySourceCodeError),
    /// `FmtError` is required to be compatible with std::fmt::Display
    FmtError,
}
impl From<fmt::Error> for HirDisplayError {
    fn from(_: fmt::Error) -> Self {
        Self::FmtError
    }
}

pub struct HirDisplayWrapper<'a, 'db, T> {
    db: &'db dyn HirDatabase,
    t: &'a T,
    max_size: Option<usize>,
    limited_size: Option<usize>,
    omit_verbose_types: bool,
    closure_style: ClosureStyle,
    display_kind: DisplayKind,
    display_target: DisplayTarget,
    show_container_bounds: bool,
    display_lifetimes: DisplayLifetime,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ClosureStyle {
    /// `impl FnX(i32, i32) -> i32`, where `FnX` is the most special trait between `Fn`, `FnMut`, `FnOnce` that the
    /// closure implements. This is the default.
    ImplFn,
    /// `|i32, i32| -> i32`
    RANotation,
    /// `{closure#14825}`, useful for some diagnostics (like type mismatch) and internal usage.
    ClosureWithId,
    /// `{closure#14825}<i32, ()>`, useful for internal usage.
    ClosureWithSubst,
    /// `…`, which is the `TYPE_HINT_TRUNCATION`
    Hide,
}

impl<'db, T: HirDisplay<'db>> HirDisplayWrapper<'_, 'db, T> {
    pub fn write_to<F: HirWrite>(&self, f: &mut F) -> Result {
        let krate = self.display_target.krate;
        let interner = DbInterner::new_with(self.db, krate);
        self.t.hir_fmt(&mut HirFormatter {
            db: self.db,
            interner,
            fmt: f,
            buf: String::with_capacity(self.max_size.unwrap_or(20)),
            curr_size: 0,
            max_size: self.max_size,
            entity_limit: self.limited_size,
            omit_verbose_types: self.omit_verbose_types,
            display_kind: self.display_kind,
            display_target: self.display_target,
            closure_style: self.closure_style,
            show_container_bounds: self.show_container_bounds,
            display_lifetimes: self.display_lifetimes,
            currently_formatting_bounds: Default::default(),
            trait_bounds_need_parens: false,
        })
    }

    pub fn with_closure_style(mut self, c: ClosureStyle) -> Self {
        self.closure_style = c;
        self
    }

    pub fn with_lifetime_display(mut self, l: DisplayLifetime) -> Self {
        self.display_lifetimes = l;
        self
    }
}

impl<'db, T> fmt::Display for HirDisplayWrapper<'_, 'db, T>
where
    T: HirDisplay<'db>,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self.write_to(f) {
            Ok(()) => Ok(()),
            Err(HirDisplayError::FmtError) => Err(fmt::Error),
            Err(HirDisplayError::DisplaySourceCodeError(_)) => {
                // This should never happen
                panic!(
                    "HirDisplay::hir_fmt failed with DisplaySourceCodeError when calling Display::fmt!"
                )
            }
        }
    }
}

const TYPE_HINT_TRUNCATION: &str = "…";

impl<'db, T: HirDisplay<'db>> HirDisplay<'db> for &T {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        HirDisplay::hir_fmt(*self, f)
    }
}

impl<'db, T: HirDisplay<'db> + Internable> HirDisplay<'db> for Interned<T> {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        HirDisplay::hir_fmt(&**self, f)
    }
}

fn write_projection<'db>(
    f: &mut HirFormatter<'_, 'db>,
    alias: &AliasTy<'db>,
    needs_parens_if_multi: bool,
    def_id: TypeAliasId,
) -> Result {
    f.format_bounds_with(*alias, |f| {
        if f.should_truncate() {
            return write!(f, "{TYPE_HINT_TRUNCATION}");
        }
        let trait_ref = alias.trait_ref(f.interner);
        let self_ty = trait_ref.self_ty();

        // if we are projection on a type parameter, check if the projection target has bounds
        // itself, if so, we render them directly as `impl Bound` instead of the less useful
        // `<Param as Trait>::Assoc`
        if !f.display_kind.is_source_code()
            && let TyKind::Param(param) = self_ty.kind()
        {
            // FIXME: We shouldn't use `param.id`, it should be removed. We should know the
            // `GenericDefId` from the formatted type (store it inside the `HirFormatter`).
            let bounds = GenericPredicates::query_all(f.db, param.id.parent())
                .iter_identity()
                .map(Unnormalized::skip_norm_wip)
                .filter(|wc| {
                    let ty = match wc.kind().skip_binder() {
                        ClauseKind::Trait(tr) => tr.self_ty(),
                        ClauseKind::TypeOutlives(t) => t.0,
                        _ => return false,
                    };
                    let TyKind::Alias(a) = ty.kind() else {
                        return false;
                    };
                    a == *alias
                })
                .collect::<Vec<_>>();
            if !bounds.is_empty() {
                return write_bounds_like_dyn_trait_with_prefix(
                    f,
                    "impl",
                    Either::Left(Ty::new_alias(f.interner, *alias)),
                    &bounds,
                    SizedByDefault::NotSized,
                    needs_parens_if_multi,
                );
            }
        }

        write!(f, "<")?;
        self_ty.hir_fmt(f)?;
        write!(f, " as ")?;
        trait_ref.hir_fmt(f)?;
        write!(f, ">::{}", TypeAliasSignature::of(f.db, def_id).name.display(f.db, f.edition()))?;
        let proj_params = &alias.args.as_slice()[trait_ref.args.len()..];
        hir_fmt_generics(f, proj_params, None, None)
    })
}

impl<'db> HirDisplay<'db> for GenericArg<'db> {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        match self.kind() {
            GenericArgKind::Type(ty) => ty.hir_fmt(f),
            GenericArgKind::Lifetime(lt) => lt.hir_fmt(f),
            GenericArgKind::Const(c) => c.hir_fmt(f),
        }
    }
}

impl<'db> HirDisplay<'db> for Allocation<'db> {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        render_const_scalar(f, &self.memory, &self.memory_map, self.ty)
    }
}

impl<'db> HirDisplay<'db> for Const<'db> {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        match self.kind() {
            ConstKind::Placeholder(_) => write!(f, "<placeholder>"),
            ConstKind::Bound(BoundVarIndexKind::Bound(db), bound_const) => {
                write!(f, "?{}.{}", db.as_u32(), bound_const.var.as_u32())
            }
            ConstKind::Bound(BoundVarIndexKind::Canonical, bound_const) => {
                write!(f, "?c.{}", bound_const.var.as_u32())
            }
            ConstKind::Infer(..) => write!(f, "#c#"),
            ConstKind::Param(param) => {
                let generics = GenericParams::of(f.db, param.id.parent());
                let param_data = &generics[param.id.local_id()];
                f.start_location_link_generic(param.id.into());
                write!(f, "{}", param_data.name().unwrap().display(f.db, f.edition()))?;
                f.end_location_link();
                Ok(())
            }
            ConstKind::Value(value) => render_const_scalar_from_valtree(f, value.ty, value.value),
            ConstKind::Unevaluated(unev) => {
                let c = unev.def.0;
                match c {
                    GeneralConstId::ConstId(id) => match &ConstSignature::of(f.db, id).name {
                        Some(name) => {
                            f.start_location_link(id.into());
                            write!(f, "{}", name.display(f.db, f.edition()))?;
                            f.end_location_link();
                        }
                        None => f.write_str("_")?,
                    },
                    GeneralConstId::StaticId(id) => {
                        let name = &StaticSignature::of(f.db, id).name;
                        f.start_location_link(id.into());
                        write!(f, "{}", name.display(f.db, f.edition()))?;
                        f.end_location_link();
                    }
                    GeneralConstId::AnonConstId(_) => {
                        f.write_str(if f.display_kind.is_source_code() { "_" } else { "{const}" })?
                    }
                };
                hir_fmt_generics(f, unev.args.as_slice(), c.generic_def(f.db), None)?;
                Ok(())
            }
            ConstKind::Error(..) => f.write_char('_'),
            ConstKind::Expr(..) => write!(f, "<const-expr>"),
        }
    }
}

fn render_const_scalar<'db>(
    f: &mut HirFormatter<'_, 'db>,
    b: &[u8],
    memory_map: &MemoryMap<'db>,
    ty: Ty<'db>,
) -> Result {
    let param_env = ParamEnv::empty(f.interner);
    let infcx = f.interner.infer_ctxt().build(TypingMode::PostAnalysis);
    let ty = infcx.at(&ObligationCause::dummy(), param_env).deeply_normalize(ty).unwrap_or(ty);
    render_const_scalar_inner(f, b, memory_map, ty, param_env)
}

fn render_const_scalar_inner<'db>(
    f: &mut HirFormatter<'_, 'db>,
    b: &[u8],
    memory_map: &MemoryMap<'db>,
    ty: Ty<'db>,
    param_env: ParamEnv<'db>,
) -> Result {
    use TyKind;
    let param_env = ParamEnvAndCrate { param_env, krate: f.krate() };
    match ty.kind() {
        TyKind::Bool => write!(f, "{}", b[0] != 0),
        TyKind::Char => {
            let it = u128::from_le_bytes(pad16(b, false)) as u32;
            let Ok(c) = char::try_from(it) else {
                return f.write_str("<unicode-error>");
            };
            write!(f, "{c:?}")
        }
        TyKind::Int(_) => {
            let it = i128::from_le_bytes(pad16(b, true));
            write!(f, "{it}")
        }
        TyKind::Uint(_) => {
            let it = u128::from_le_bytes(pad16(b, false));
            write!(f, "{it}")
        }
        TyKind::Float(fl) => match fl {
            FloatTy::F16 => {
                // FIXME(#17451): Replace with builtins once they are stabilised.
                let it = f16::from_bits(u16::from_le_bytes(b.try_into().unwrap()).into());
                let s = it.to_string();
                if s.strip_prefix('-').unwrap_or(&s).chars().all(|c| c.is_ascii_digit()) {
                    // Match Rust debug formatting
                    write!(f, "{s}.0")
                } else {
                    write!(f, "{s}")
                }
            }
            FloatTy::F32 => {
                let it = f32::from_le_bytes(b.try_into().unwrap());
                write!(f, "{it:?}")
            }
            FloatTy::F64 => {
                let it = f64::from_le_bytes(b.try_into().unwrap());
                write!(f, "{it:?}")
            }
            FloatTy::F128 => {
                // FIXME(#17451): Replace with builtins once they are stabilised.
                let it = f128::from_bits(u128::from_le_bytes(b.try_into().unwrap()));
                let s = it.to_string();
                if s.strip_prefix('-').unwrap_or(&s).chars().all(|c| c.is_ascii_digit()) {
                    // Match Rust debug formatting
                    write!(f, "{s}.0")
                } else {
                    write!(f, "{s}")
                }
            }
        },
        TyKind::Ref(_, t, _) => match t.kind() {
            TyKind::Str => {
                let addr = usize::from_le_bytes(b[0..b.len() / 2].try_into().unwrap());
                let size = usize::from_le_bytes(b[b.len() / 2..].try_into().unwrap());
                let Some(bytes) = memory_map.get(addr, size) else {
                    return f.write_str("<ref-data-not-available>");
                };
                let s = std::str::from_utf8(bytes).unwrap_or("<utf8-error>");
                write!(f, "{s:?}")
            }
            TyKind::Slice(ty) => {
                let addr = usize::from_le_bytes(b[0..b.len() / 2].try_into().unwrap());
                let count = usize::from_le_bytes(b[b.len() / 2..].try_into().unwrap());
                let Ok(layout) = f.db.layout_of_ty(ty.store(), param_env.store()) else {
                    return f.write_str("<layout-error>");
                };
                let size_one = layout.size.bytes_usize();
                let Some(bytes) = memory_map.get(addr, size_one * count) else {
                    return f.write_str("<ref-data-not-available>");
                };
                let expected_len = count * size_one;
                if bytes.len() < expected_len {
                    never!(
                        "Memory map size is too small. Expected {expected_len}, got {}",
                        bytes.len(),
                    );
                    return f.write_str("<layout-error>");
                }
                f.write_str("&[")?;
                let mut first = true;
                for i in 0..count {
                    if first {
                        first = false;
                    } else {
                        f.write_str(", ")?;
                    }
                    let offset = size_one * i;
                    render_const_scalar(f, &bytes[offset..offset + size_one], memory_map, ty)?;
                }
                f.write_str("]")
            }
            TyKind::Dynamic(_, _) => {
                let addr = usize::from_le_bytes(b[0..b.len() / 2].try_into().unwrap());
                let ty_id = usize::from_le_bytes(b[b.len() / 2..].try_into().unwrap());
                let Ok(t) = memory_map.vtable_ty(ty_id) else {
                    return f.write_str("<ty-missing-in-vtable-map>");
                };
                let Ok(layout) = f.db.layout_of_ty(t.store(), param_env.store()) else {
                    return f.write_str("<layout-error>");
                };
                let size = layout.size.bytes_usize();
                let Some(bytes) = memory_map.get(addr, size) else {
                    return f.write_str("<ref-data-not-available>");
                };
                f.write_str("&")?;
                render_const_scalar(f, bytes, memory_map, t)
            }
            TyKind::Adt(adt, _) if b.len() == 2 * size_of::<usize>() => match adt.def_id() {
                hir_def::AdtId::StructId(s) => {
                    let data = StructSignature::of(f.db, s);
                    write!(f, "&{}", data.name.display(f.db, f.edition()))?;
                    Ok(())
                }
                _ => f.write_str("<unsized-enum-or-union>"),
            },
            _ => {
                let addr = usize::from_le_bytes(match b.try_into() {
                    Ok(b) => b,
                    Err(_) => {
                        never!(
                            "tried rendering ty {:?} in const ref with incorrect byte count {}",
                            t,
                            b.len()
                        );
                        return f.write_str("<layout-error>");
                    }
                });
                let Ok(layout) = f.db.layout_of_ty(t.store(), param_env.store()) else {
                    return f.write_str("<layout-error>");
                };
                let size = layout.size.bytes_usize();
                let Some(bytes) = memory_map.get(addr, size) else {
                    return f.write_str("<ref-data-not-available>");
                };
                f.write_str("&")?;
                render_const_scalar(f, bytes, memory_map, t)
            }
        },
        TyKind::Tuple(tys) => {
            let Ok(layout) = f.db.layout_of_ty(ty.store(), param_env.store()) else {
                return f.write_str("<layout-error>");
            };
            f.write_str("(")?;
            let mut first = true;
            for (id, ty) in tys.iter().enumerate() {
                if first {
                    first = false;
                } else {
                    f.write_str(", ")?;
                }
                let offset = layout.fields.offset(id).bytes_usize();
                let Ok(layout) = f.db.layout_of_ty(ty.store(), param_env.store()) else {
                    f.write_str("<layout-error>")?;
                    continue;
                };
                let size = layout.size.bytes_usize();
                render_const_scalar(f, &b[offset..offset + size], memory_map, ty)?;
            }
            f.write_str(")")
        }
        TyKind::Adt(def, args) => {
            let def = def.def_id();
            let Ok(layout) = f.db.layout_of_adt(def, args.store(), param_env.store()) else {
                return f.write_str("<layout-error>");
            };
            match def {
                hir_def::AdtId::StructId(s) => {
                    let data = StructSignature::of(f.db, s);
                    write!(f, "{}", data.name.display(f.db, f.edition()))?;
                    let field_types = f.db.field_types(s.into());
                    render_variant_after_name(
                        s.fields(f.db),
                        f,
                        field_types,
                        f.db.trait_environment(ExpressionStoreOwnerId::from(GenericDefId::from(
                            def,
                        ))),
                        &layout,
                        args,
                        b,
                        memory_map,
                    )
                }
                hir_def::AdtId::UnionId(u) => {
                    write!(f, "{}", UnionSignature::of(f.db, u).name.display(f.db, f.edition()))
                }
                hir_def::AdtId::EnumId(e) => {
                    let Ok(target_data_layout) = f.db.target_data_layout(f.krate()) else {
                        return f.write_str("<target-layout-not-available>");
                    };
                    let Some((var_id, var_layout)) =
                        detect_variant_from_bytes(&layout, f.db, target_data_layout, b, e)
                    else {
                        return f.write_str("<failed-to-detect-variant>");
                    };
                    let loc = var_id.lookup(f.db);
                    write!(f, "{}", loc.name.display(f.db, f.edition()))?;
                    let field_types = f.db.field_types(var_id.into());
                    render_variant_after_name(
                        var_id.fields(f.db),
                        f,
                        field_types,
                        f.db.trait_environment(ExpressionStoreOwnerId::from(GenericDefId::from(
                            def,
                        ))),
                        var_layout,
                        args,
                        b,
                        memory_map,
                    )
                }
            }
        }
        TyKind::FnDef(..) => ty.hir_fmt(f),
        TyKind::FnPtr(_, _) | TyKind::RawPtr(_, _) => {
            let it = u128::from_le_bytes(pad16(b, false));
            write!(f, "{it:#X} as ")?;
            ty.hir_fmt(f)
        }
        TyKind::Array(ty, len) => {
            let Some(len) = consteval::try_const_usize(f.db, len) else {
                return f.write_str("<unknown-array-len>");
            };
            let Ok(layout) = f.db.layout_of_ty(ty.store(), param_env.store()) else {
                return f.write_str("<layout-error>");
            };
            let size_one = layout.size.bytes_usize();
            f.write_str("[")?;
            let mut first = true;
            for i in 0..len as usize {
                if first {
                    first = false;
                } else {
                    f.write_str(", ")?;
                }
                let offset = size_one * i;
                render_const_scalar(f, &b[offset..offset + size_one], memory_map, ty)?;
            }
            f.write_str("]")
        }
        TyKind::Never => f.write_str("!"),
        TyKind::Closure(_, _) => f.write_str("<closure>"),
        TyKind::Coroutine(_, _) => f.write_str("<coroutine>"),
        TyKind::CoroutineWitness(_, _) => f.write_str("<coroutine-witness>"),
        TyKind::CoroutineClosure(_, _) => f.write_str("<coroutine-closure>"),
        TyKind::UnsafeBinder(_) => f.write_str("<unsafe-binder>"),
        // The below arms are unreachable, since const eval will bail out before here.
        TyKind::Foreign(_) => f.write_str("<extern-type>"),
        TyKind::Pat(_, _) => f.write_str("<pat>"),
        TyKind::Error(..)
        | TyKind::Placeholder(_)
        | TyKind::Alias(..)
        | TyKind::Param(_)
        | TyKind::Bound(_, _)
        | TyKind::Infer(_) => f.write_str("<placeholder-or-unknown-type>"),
        // The below arms are unreachable, since we handled them in ref case.
        TyKind::Slice(_) | TyKind::Str | TyKind::Dynamic(_, _) => f.write_str("<unsized-value>"),
    }
}

fn render_const_scalar_from_valtree<'db>(
    f: &mut HirFormatter<'_, 'db>,
    ty: Ty<'db>,
    valtree: ValTree<'db>,
) -> Result {
    let param_env = ParamEnv::empty(f.interner);
    let infcx = f.interner.infer_ctxt().build(TypingMode::PostAnalysis);
    let ty = infcx.at(&ObligationCause::dummy(), param_env).deeply_normalize(ty).unwrap_or(ty);
    render_const_scalar_from_valtree_inner(f, ty, valtree, param_env)
}

fn render_const_scalar_from_valtree_inner<'db>(
    f: &mut HirFormatter<'_, 'db>,
    ty: Ty<'db>,
    valtree: ValTree<'db>,
    _param_env: ParamEnv<'db>,
) -> Result {
    use TyKind;
    match ty.kind() {
        TyKind::Bool => write!(f, "{}", valtree.inner().to_leaf().try_to_bool().unwrap()),
        TyKind::Char => {
            let it = valtree.inner().to_leaf().to_u32();
            let Ok(c) = char::try_from(it) else {
                return f.write_str("<unicode-error>");
            };
            write!(f, "{c:?}")
        }
        TyKind::Int(_) => {
            let it = valtree.inner().to_leaf().to_int_unchecked();
            write!(f, "{it}")
        }
        TyKind::Uint(_) => {
            let it = valtree.inner().to_leaf().to_uint_unchecked();
            write!(f, "{it}")
        }
        TyKind::Float(fl) => match fl {
            FloatTy::F16 => {
                // FIXME(#17451): Replace with builtins once they are stabilised.
                let it = f16::from_bits(valtree.inner().to_leaf().to_u16() as u128);
                let s = it.to_string();
                if s.strip_prefix('-').unwrap_or(&s).chars().all(|c| c.is_ascii_digit()) {
                    // Match Rust debug formatting
                    write!(f, "{s}.0")
                } else {
                    write!(f, "{s}")
                }
            }
            FloatTy::F32 => {
                let it = f32::from_bits(valtree.inner().to_leaf().to_u32());
                write!(f, "{it:?}")
            }
            FloatTy::F64 => {
                let it = f64::from_bits(valtree.inner().to_leaf().to_u64());
                write!(f, "{it:?}")
            }
            FloatTy::F128 => {
                // FIXME(#17451): Replace with builtins once they are stabilised.
                let it = f128::from_bits(valtree.inner().to_leaf().to_u128());
                let s = it.to_string();
                if s.strip_prefix('-').unwrap_or(&s).chars().all(|c| c.is_ascii_digit()) {
                    // Match Rust debug formatting
                    write!(f, "{s}.0")
                } else {
                    write!(f, "{s}")
                }
            }
        },
        TyKind::Ref(_, inner_ty, _) => {
            render_const_scalar_from_valtree_inner(f, inner_ty, valtree, _param_env)
        }
        TyKind::Str => {
            let bytes = valtree
                .inner()
                .to_branch()
                .iter()
                .map(|konst| match konst.kind() {
                    ConstKind::Value(value) => Some(value.value.inner().to_leaf().to_u8()),
                    _ => None,
                })
                .collect::<Option<Vec<_>>>();
            let Some(bytes) = bytes else { return f.write_str("<invalid-str>") };
            let s = std::str::from_utf8(&bytes).unwrap_or("<utf8-error>");
            write!(f, "{s:?}")
        }
        TyKind::Slice(inner_ty) | TyKind::Array(inner_ty, _) => {
            let mut first = true;
            write!(f, "[")?;
            for item in valtree.inner().to_branch() {
                if !first {
                    write!(f, ", ")?;
                } else {
                    first = false;
                }
                let ConstKind::Value(value) = item.kind() else {
                    return f.write_str("<invalid-const>");
                };
                render_const_scalar_from_valtree_inner(f, inner_ty, value.value, _param_env)?;
            }
            write!(f, "]")
        }
        TyKind::Tuple(tys) => {
            let mut first = true;
            write!(f, "(")?;
            for (inner_ty, item) in std::iter::zip(tys, valtree.inner().to_branch()) {
                if !first {
                    write!(f, ", ")?;
                } else {
                    first = false;
                }
                let ConstKind::Value(value) = item.kind() else {
                    return f.write_str("<invalid-const>");
                };
                render_const_scalar_from_valtree_inner(f, inner_ty, value.value, _param_env)?;
            }
            write!(f, ")")
        }
        TyKind::Adt(..) => {
            // FIXME: ADTs, requires `adt_const_params`.
            f.write_str("<adt>")
        }
        TyKind::FnDef(..) => ty.hir_fmt(f),
        TyKind::FnPtr(_, _) | TyKind::RawPtr(_, _) => {
            let it = valtree.inner().to_leaf().to_uint_unchecked();
            write!(f, "{it:#X} as ")?;
            ty.hir_fmt(f)
        }
        TyKind::Never => f.write_str("!"),
        TyKind::Closure(_, _) => f.write_str("<closure>"),
        TyKind::Coroutine(_, _) => f.write_str("<coroutine>"),
        TyKind::CoroutineWitness(_, _) => f.write_str("<coroutine-witness>"),
        TyKind::CoroutineClosure(_, _) => f.write_str("<coroutine-closure>"),
        TyKind::UnsafeBinder(_) => f.write_str("<unsafe-binder>"),
        // The below arms are unreachable, since const eval will bail out before here.
        TyKind::Foreign(_) => f.write_str("<extern-type>"),
        TyKind::Pat(_, _) => f.write_str("<pat>"),
        TyKind::Error(..)
        | TyKind::Placeholder(_)
        | TyKind::Alias(..)
        | TyKind::Param(_)
        | TyKind::Bound(_, _)
        | TyKind::Infer(_) => f.write_str("<placeholder-or-unknown-type>"),
        TyKind::Dynamic(_, _) => f.write_str("<dyn-trait>"),
    }
}

fn render_variant_after_name<'db>(
    data: &VariantFields,
    f: &mut HirFormatter<'_, 'db>,
    field_types: &'db ArenaMap<LocalFieldId, StoredEarlyBinder<StoredTy>>,
    param_env: ParamEnv<'db>,
    layout: &Layout,
    args: GenericArgs<'db>,
    b: &[u8],
    memory_map: &MemoryMap<'db>,
) -> Result {
    let param_env = ParamEnvAndCrate { param_env, krate: f.krate() };
    match data.shape {
        FieldsShape::Record | FieldsShape::Tuple => {
            let render_field = |f: &mut HirFormatter<'_, 'db>, id: LocalFieldId| {
                let offset = layout.fields.offset(u32::from(id.into_raw()) as usize).bytes_usize();
                let ty = field_types[id].get().instantiate(f.interner, args).skip_norm_wip();
                let Ok(layout) = f.db.layout_of_ty(ty.store(), param_env.store()) else {
                    return f.write_str("<layout-error>");
                };
                let size = layout.size.bytes_usize();
                render_const_scalar(f, &b[offset..offset + size], memory_map, ty)
            };
            let mut it = data.fields().iter();
            if matches!(data.shape, FieldsShape::Record) {
                write!(f, " {{")?;
                if let Some((id, data)) = it.next() {
                    write!(f, " {}: ", data.name.display(f.db, f.edition()))?;
                    render_field(f, id)?;
                }
                for (id, data) in it {
                    write!(f, ", {}: ", data.name.display(f.db, f.edition()))?;
                    render_field(f, id)?;
                }
                write!(f, " }}")?;
            } else {
                let mut it = it.map(|it| it.0);
                write!(f, "(")?;
                if let Some(id) = it.next() {
                    render_field(f, id)?;
                }
                for id in it {
                    write!(f, ", ")?;
                    render_field(f, id)?;
                }
                write!(f, ")")?;
            }
            Ok(())
        }
        FieldsShape::Unit => Ok(()),
    }
}

impl<'db> HirDisplay<'db> for Ty<'db> {
    fn hir_fmt(&self, f @ &mut HirFormatter { db, .. }: &mut HirFormatter<'_, 'db>) -> Result {
        let interner = f.interner;
        if f.should_truncate() {
            return write!(f, "{TYPE_HINT_TRUNCATION}");
        }

        let trait_bounds_need_parens = mem::replace(&mut f.trait_bounds_need_parens, false);
        match self.kind() {
            TyKind::Never => write!(f, "!")?,
            TyKind::Str => write!(f, "str")?,
            TyKind::Bool => write!(f, "bool")?,
            TyKind::Char => write!(f, "char")?,
            TyKind::Float(t) => write!(f, "{}", primitive::float_ty_to_string(t))?,
            TyKind::Int(t) => write!(f, "{}", primitive::int_ty_to_string(t))?,
            TyKind::Uint(t) => write!(f, "{}", primitive::uint_ty_to_string(t))?,
            TyKind::Slice(t) => {
                write!(f, "[")?;
                t.hir_fmt(f)?;
                write!(f, "]")?;
            }
            TyKind::Array(t, c) => {
                write!(f, "[")?;
                t.hir_fmt(f)?;
                write!(f, "; ")?;
                c.hir_fmt(f)?;
                write!(f, "]")?;
            }
            TyKind::Ref(l, t, m) => {
                f.write_char('&')?;
                if f.render_region(l) {
                    l.hir_fmt(f)?;
                    f.write_char(' ')?;
                }
                match m {
                    rustc_ast_ir::Mutability::Not => (),
                    rustc_ast_ir::Mutability::Mut => f.write_str("mut ")?,
                }

                f.trait_bounds_need_parens = true;
                t.hir_fmt(f)?;
                f.trait_bounds_need_parens = false;
            }
            TyKind::RawPtr(t, m) => {
                write!(
                    f,
                    "*{}",
                    match m {
                        rustc_ast_ir::Mutability::Not => "const ",
                        rustc_ast_ir::Mutability::Mut => "mut ",
                    }
                )?;

                f.trait_bounds_need_parens = true;
                t.hir_fmt(f)?;
                f.trait_bounds_need_parens = false;
            }
            TyKind::Tuple(tys) => {
                if tys.len() == 1 {
                    write!(f, "(")?;
                    tys.as_slice()[0].hir_fmt(f)?;
                    write!(f, ",)")?;
                } else {
                    write!(f, "(")?;
                    f.write_joined(tys.as_slice(), ", ")?;
                    write!(f, ")")?;
                }
            }
            TyKind::FnPtr(sig, header) => {
                let sig = sig.with(header);
                sig.hir_fmt(f)?;
            }
            TyKind::FnDef(def, args) => {
                let def = def.0;
                let sig =
                    db.callable_item_signature(def).instantiate(interner, args).skip_norm_wip();

                if f.display_kind.is_source_code() {
                    // `FnDef` is anonymous and there's no surface syntax for it. Show it as a
                    // function pointer type.
                    return sig.hir_fmt(f);
                }
                if let Safety::Unsafe = sig.safety() {
                    write!(f, "unsafe ")?;
                }
                if !sig.abi().is_rustic_abi() {
                    f.write_str("extern \"")?;
                    f.write_str(sig.abi().as_str())?;
                    f.write_str("\" ")?;
                }

                let sig = sig.skip_binder();
                write!(f, "fn ")?;
                f.start_location_link(def.into());
                match def {
                    CallableDefId::FunctionId(ff) => write!(
                        f,
                        "{}",
                        FunctionSignature::of(db, ff).name.display(f.db, f.edition())
                    )?,
                    CallableDefId::StructId(s) => {
                        write!(f, "{}", StructSignature::of(db, s).name.display(f.db, f.edition()))?
                    }
                    CallableDefId::EnumVariantId(e) => {
                        let loc = e.lookup(db);
                        write!(f, "{}", loc.name.display(db, f.edition()))?
                    }
                };
                f.end_location_link();

                if !args.is_empty() {
                    let generic_def_id = GenericDefId::from_callable(db, def);
                    let generics = generics(db, generic_def_id);
                    let ProvenanceSplit {
                        parent_total: parent_len,
                        has_self_param: self_param,
                        non_impl_trait_type_params: type_,
                        const_params: const_,
                        impl_trait_type_params: impl_,
                        lifetimes: lifetime,
                    } = generics.provenance_split();
                    let parameters = args.as_slice();
                    debug_assert_eq!(
                        parameters.len(),
                        parent_len + self_param as usize + type_ + const_ + impl_ + lifetime
                    );
                    // We print all params except implicit impl Trait params. Still a bit weird; should we leave out parent and self?
                    if parameters.len() - impl_ > 0 {
                        let params_len = parameters.len();
                        // `parameters` are in the order of fn's params (including impl traits), fn's lifetimes
                        let parameters =
                            generic_args_sans_defaults(f, Some(generic_def_id), parameters);
                        assert!(params_len >= parameters.len());
                        let defaults = params_len - parameters.len();

                        // Normally, functions cannot have default parameters, but they can,
                        // for function-like things such as struct names or enum variants.
                        // The former cannot have defaults but does have parents,
                        // but the latter cannot have parents but can have defaults.
                        //
                        // However, it's also true that *traits* can have defaults too.
                        // In this case, there can be no function params.
                        let parent_end = if parent_len > 0 {
                            // If `parent_len` > 0, then there cannot be defaults on the function
                            // and all defaults must come from the parent.
                            parent_len - defaults
                        } else {
                            parent_len
                        };
                        let fn_params_no_impl_or_defaults = parameters.len() - parent_end - impl_;
                        let (parent_params, fn_params) = parameters.split_at(parent_end);

                        write!(f, "<")?;
                        hir_fmt_generic_arguments(f, parent_params, None)?;
                        if !parent_params.is_empty() && !fn_params.is_empty() {
                            write!(f, ", ")?;
                        }
                        hir_fmt_generic_arguments(
                            f,
                            &fn_params[..fn_params_no_impl_or_defaults],
                            None,
                        )?;
                        write!(f, ">")?;
                    }
                }
                write!(f, "(")?;
                f.write_joined(sig.inputs(), ", ")?;
                write!(f, ")")?;
                let ret = sig.output();
                if !ret.is_unit() {
                    write!(f, " -> ")?;
                    ret.hir_fmt(f)?;
                }
            }
            TyKind::Adt(def, parameters) => {
                let def_id = def.def_id();
                f.start_location_link(def_id.into());
                match f.display_kind {
                    DisplayKind::Diagnostics | DisplayKind::Test => {
                        let name = match def_id {
                            hir_def::AdtId::StructId(it) => {
                                StructSignature::of(db, it).name.clone()
                            }
                            hir_def::AdtId::UnionId(it) => UnionSignature::of(db, it).name.clone(),
                            hir_def::AdtId::EnumId(it) => EnumSignature::of(db, it).name.clone(),
                        };
                        write!(f, "{}", name.display(f.db, f.edition()))?;
                    }
                    DisplayKind::SourceCode { target_module_id: module_id, allow_opaque: _ } => {
                        if let Some(path) = find_path::find_path(
                            db,
                            ItemInNs::Types(def_id.into()),
                            module_id,
                            PrefixKind::Plain,
                            false,
                            // FIXME: no_std Cfg?
                            FindPathConfig {
                                prefer_no_std: false,
                                prefer_prelude: true,
                                prefer_absolute: false,
                                allow_unstable: true,
                            },
                        ) {
                            write!(f, "{}", path.display(f.db, f.edition()))?;
                        } else {
                            return Err(HirDisplayError::DisplaySourceCodeError(
                                DisplaySourceCodeError::PathNotFound,
                            ));
                        }
                    }
                }
                f.end_location_link();

                hir_fmt_generics(f, parameters.as_slice(), Some(def.def_id().into()), None)?;
            }
            TyKind::Alias(alias_ty @ AliasTy { kind: AliasTyKind::Projection { def_id }, .. }) => {
                write_projection(f, &alias_ty, trait_bounds_need_parens, def_id.0)?
            }
            TyKind::Foreign(alias) => {
                let type_alias = TypeAliasSignature::of(db, alias.0);
                f.start_location_link(alias.0.into());
                write!(f, "{}", type_alias.name.display(f.db, f.edition()))?;
                f.end_location_link();
            }
            TyKind::Alias(alias_ty @ AliasTy { kind: AliasTyKind::Opaque { def_id }, .. }) => {
                let opaque_ty_id = def_id.0;
                if !f.display_kind.allows_opaque() {
                    return Err(HirDisplayError::DisplaySourceCodeError(
                        DisplaySourceCodeError::OpaqueType,
                    ));
                }
                let impl_trait_id = opaque_ty_id.loc(db);
                let data = impl_trait_id.predicates(db);
                let bounds = data
                    .iter_instantiated_copied(interner, alias_ty.args.as_slice())
                    .map(Unnormalized::skip_norm_wip)
                    .collect::<Vec<_>>();
                let krate = match impl_trait_id {
                    ImplTraitId::ReturnTypeImplTrait(func, _) => {
                        func.krate(db)
                        // FIXME: it would maybe be good to distinguish this from the alias type (when debug printing), and to show the substitution
                    }
                    ImplTraitId::TypeAliasImplTrait(alias, _) => alias.krate(db),
                };
                write_bounds_like_dyn_trait_with_prefix(
                    f,
                    "impl",
                    Either::Left(*self),
                    &bounds,
                    SizedByDefault::Sized { anchor: krate },
                    trait_bounds_need_parens,
                )?;
            }
            TyKind::Closure(id, substs) => {
                let id = id.0;
                if f.display_kind.is_source_code() {
                    if !f.display_kind.allows_opaque() {
                        return Err(HirDisplayError::DisplaySourceCodeError(
                            DisplaySourceCodeError::OpaqueType,
                        ));
                    } else if f.closure_style != ClosureStyle::ImplFn {
                        never!("Only `impl Fn` is valid for displaying closures in source code");
                    }
                }
                match f.closure_style {
                    ClosureStyle::Hide => return write!(f, "{TYPE_HINT_TRUNCATION}"),
                    ClosureStyle::ClosureWithId => {
                        return write!(
                            f,
                            "{{closure#{:?}}}",
                            salsa::plumbing::AsId::as_id(&id).index()
                        );
                    }
                    ClosureStyle::ClosureWithSubst => {
                        write!(f, "{{closure#{:?}}}", salsa::plumbing::AsId::as_id(&id).index())?;
                        return hir_fmt_generics(f, substs.as_slice(), None, None);
                    }
                    _ => (),
                }
                let sig = interner.signature_unclosure(substs.as_closure().sig(), Safety::Safe);
                let sig = sig.skip_binder();
                let kind = substs.as_closure().kind();
                match f.closure_style {
                    ClosureStyle::ImplFn => write!(f, "impl {kind:?}(")?,
                    ClosureStyle::RANotation => write!(f, "|")?,
                    _ => unreachable!(),
                }
                if sig.inputs().is_empty() {
                } else if f.should_truncate() {
                    write!(f, "{TYPE_HINT_TRUNCATION}")?;
                } else {
                    f.write_joined(sig.inputs(), ", ")?;
                };
                match f.closure_style {
                    ClosureStyle::ImplFn => write!(f, ")")?,
                    ClosureStyle::RANotation => write!(f, "|")?,
                    _ => unreachable!(),
                }
                if f.closure_style == ClosureStyle::RANotation || !sig.output().is_unit() {
                    write!(f, " -> ")?;
                    sig.output().hir_fmt(f)?;
                }
            }
            TyKind::CoroutineClosure(id, args) => {
                let id = id.0;
                let closure_kind = match id.loc(db).kind {
                    HirClosureKind::CoroutineClosure(kind) => kind,
                    kind => panic!("invalid kind for coroutine closure: {kind:?}"),
                };
                let closure_label = match closure_kind {
                    CoroutineKind::Async => "async closure",
                    CoroutineKind::Gen => "gen closure",
                    CoroutineKind::AsyncGen => "async gen closure",
                };
                if f.display_kind.is_source_code() {
                    if !f.display_kind.allows_opaque() {
                        return Err(HirDisplayError::DisplaySourceCodeError(
                            DisplaySourceCodeError::OpaqueType,
                        ));
                    } else if f.closure_style != ClosureStyle::ImplFn {
                        never!("Only `impl Fn` is valid for displaying closures in source code");
                    }
                }
                match f.closure_style {
                    ClosureStyle::Hide => return write!(f, "{TYPE_HINT_TRUNCATION}"),
                    ClosureStyle::ClosureWithId => {
                        return write!(
                            f,
                            "{{{closure_label}#{:?}}}",
                            salsa::plumbing::AsId::as_id(&id).index()
                        );
                    }
                    ClosureStyle::ClosureWithSubst => {
                        write!(
                            f,
                            "{{{closure_label}#{:?}}}",
                            salsa::plumbing::AsId::as_id(&id).index()
                        )?;
                        return hir_fmt_generics(f, args.as_slice(), None, None);
                    }
                    _ => (),
                }
                let callable_kind = args.as_coroutine_closure().kind();
                let kind = match (closure_kind, callable_kind) {
                    (CoroutineKind::Async, rustc_type_ir::ClosureKind::Fn) => "AsyncFn",
                    (CoroutineKind::Async, rustc_type_ir::ClosureKind::FnMut) => "AsyncFnMut",
                    (CoroutineKind::Async, rustc_type_ir::ClosureKind::FnOnce) => "AsyncFnOnce",
                    (_, rustc_type_ir::ClosureKind::Fn) => "Fn",
                    (_, rustc_type_ir::ClosureKind::FnMut) => "FnMut",
                    (_, rustc_type_ir::ClosureKind::FnOnce) => "FnOnce",
                };
                let coroutine_sig = args.as_coroutine_closure().coroutine_closure_sig();
                let coroutine_sig = coroutine_sig.skip_binder();
                let coroutine_inputs = coroutine_sig.tupled_inputs_ty.tuple_fields();
                let coroutine_output = coroutine_sig.return_ty;
                match f.closure_style {
                    ClosureStyle::ImplFn => write!(f, "impl {kind}(")?,
                    ClosureStyle::RANotation => match closure_kind {
                        CoroutineKind::Async => write!(f, "async |")?,
                        CoroutineKind::Gen => write!(f, "gen |")?,
                        CoroutineKind::AsyncGen => write!(f, "async gen |")?,
                    },
                    _ => unreachable!(),
                }
                if coroutine_inputs.is_empty() {
                } else if f.should_truncate() {
                    write!(f, "{TYPE_HINT_TRUNCATION}")?;
                } else {
                    f.write_joined(coroutine_inputs, ", ")?;
                };
                match f.closure_style {
                    ClosureStyle::ImplFn => write!(f, ")")?,
                    ClosureStyle::RANotation => write!(f, "|")?,
                    _ => unreachable!(),
                }
                if f.closure_style == ClosureStyle::RANotation || !coroutine_output.is_unit() {
                    write!(f, " -> ")?;
                    coroutine_output.hir_fmt(f)?;
                }
            }
            TyKind::Placeholder(_) => write!(f, "{{placeholder}}")?,
            TyKind::Param(param) => {
                // FIXME: We should not access `param.id`, it should be removed, and we should know the
                // parent from the formatted type.
                let generics = GenericParams::of(db, param.id.parent());
                let param_data = &generics[param.id.local_id()];
                match param_data {
                    TypeOrConstParamData::TypeParamData(p) => match p.provenance {
                        TypeParamProvenance::TypeParamList | TypeParamProvenance::TraitSelf => {
                            f.start_location_link_generic(param.id.into());
                            write!(
                                f,
                                "{}",
                                p.name
                                    .clone()
                                    .unwrap_or_else(Name::missing)
                                    .display(f.db, f.edition())
                            )?;
                            f.end_location_link();
                        }
                        TypeParamProvenance::ArgumentImplTrait => {
                            let bounds = GenericPredicates::query_all(f.db, param.id.parent())
                                .iter_identity()
                                .map(Unnormalized::skip_norm_wip)
                                .filter(|wc| match wc.kind().skip_binder() {
                                    ClauseKind::Trait(tr) => tr.self_ty() == *self,
                                    ClauseKind::Projection(proj) => proj.self_ty() == *self,
                                    ClauseKind::TypeOutlives(to) => to.0 == *self,
                                    _ => false,
                                })
                                .collect::<Vec<_>>();
                            let krate = param.id.parent().module(db).krate(db);
                            write_bounds_like_dyn_trait_with_prefix(
                                f,
                                "impl",
                                Either::Left(*self),
                                &bounds,
                                SizedByDefault::Sized { anchor: krate },
                                trait_bounds_need_parens,
                            )?;
                        }
                    },
                    TypeOrConstParamData::ConstParamData(p) => {
                        f.start_location_link_generic(param.id.into());
                        write!(f, "{}", p.name.display(f.db, f.edition()))?;
                        f.end_location_link();
                    }
                }
            }
            TyKind::Bound(BoundVarIndexKind::Bound(debruijn), ty) => {
                write!(f, "?{}.{}", debruijn.as_usize(), ty.var.as_usize())?
            }
            TyKind::Bound(BoundVarIndexKind::Canonical, ty) => {
                write!(f, "?c.{}", ty.var.as_usize())?
            }
            TyKind::Dynamic(bounds, region) => {
                // We want to put auto traits after principal traits, regardless of their written order.
                let mut bounds_to_display = SmallVec::<[_; 4]>::new();
                let mut auto_trait_bounds = SmallVec::<[_; 4]>::new();
                for bound in bounds.iter() {
                    let clause = bound.with_self_ty(interner, *self);
                    match bound.skip_binder() {
                        ExistentialPredicate::Trait(_) | ExistentialPredicate::Projection(_) => {
                            bounds_to_display.push(clause);
                        }
                        ExistentialPredicate::AutoTrait(_) => auto_trait_bounds.push(clause),
                    }
                }
                bounds_to_display.append(&mut auto_trait_bounds);

                if f.render_region(region) {
                    bounds_to_display
                        .push(rustc_type_ir::OutlivesPredicate(*self, region).upcast(interner));
                }

                write_bounds_like_dyn_trait_with_prefix(
                    f,
                    "dyn",
                    Either::Left(*self),
                    &bounds_to_display,
                    SizedByDefault::NotSized,
                    trait_bounds_need_parens,
                )?;
            }
            TyKind::Error(_) => {
                if f.display_kind.is_source_code() {
                    f.write_char('_')?;
                } else {
                    write!(f, "{{unknown}}")?;
                }
            }
            TyKind::Infer(..) => write!(f, "_")?,
            TyKind::Coroutine(coroutine_id, subst) => {
                let kind = coroutine_id.0.loc(db).kind;
                let CoroutineArgsParts { resume_ty, yield_ty, return_ty, .. } =
                    subst.split_coroutine_args();
                match kind {
                    HirClosureKind::Coroutine { kind: CoroutineKind::Async, .. } => {
                        let lang_items = f.lang_items();
                        let future_trait = lang_items.Future;
                        let output = lang_items.FutureOutput;
                        write!(f, "impl ")?;
                        if let Some(t) = future_trait {
                            f.start_location_link(t.into());
                        }
                        write!(f, "Future")?;
                        if future_trait.is_some() {
                            f.end_location_link();
                        }
                        write!(f, "<")?;
                        if let Some(t) = output {
                            f.start_location_link(t.into());
                        }
                        write!(f, "Output")?;
                        if output.is_some() {
                            f.end_location_link();
                        }
                        write!(f, " = ")?;
                        return_ty.hir_fmt(f)?;
                        write!(f, ">")?;
                    }
                    HirClosureKind::Coroutine { kind: CoroutineKind::Gen, .. } => {
                        let lang_items = f.lang_items();
                        let iterator_trait = lang_items.Iterator;
                        let item = lang_items.IteratorItem;
                        write!(f, "impl ")?;
                        if let Some(t) = iterator_trait {
                            f.start_location_link(t.into());
                        }
                        write!(f, "Iterator")?;
                        if iterator_trait.is_some() {
                            f.end_location_link();
                        }
                        write!(f, "<")?;
                        if let Some(t) = item {
                            f.start_location_link(t.into());
                        }
                        write!(f, "Item")?;
                        if item.is_some() {
                            f.end_location_link();
                        }
                        write!(f, " = ")?;
                        yield_ty.hir_fmt(f)?;
                        write!(f, ">")?;
                    }
                    HirClosureKind::Coroutine { kind: CoroutineKind::AsyncGen, .. } => {
                        let lang_items = f.lang_items();
                        let async_iterator_trait = lang_items.AsyncIterator;
                        let item = lang_items.AsyncIteratorItem;
                        write!(f, "impl ")?;
                        if let Some(t) = async_iterator_trait {
                            f.start_location_link(t.into());
                        }
                        write!(f, "AsyncIterator")?;
                        if async_iterator_trait.is_some() {
                            f.end_location_link();
                        }
                        write!(f, "<")?;
                        if let Some(t) = item {
                            f.start_location_link(t.into());
                        }
                        write!(f, "Item")?;
                        if item.is_some() {
                            f.end_location_link();
                        }
                        write!(f, " = ")?;
                        let item_ty = async_gen_item_ty_from_yield_ty(f.lang_items(), yield_ty)
                            .unwrap_or(yield_ty);
                        item_ty.hir_fmt(f)?;
                        write!(f, ">")?;
                    }
                    HirClosureKind::OldCoroutine(..) => {
                        if f.display_kind.is_source_code() {
                            return Err(HirDisplayError::DisplaySourceCodeError(
                                DisplaySourceCodeError::Coroutine,
                            ));
                        }
                        write!(f, "|")?;
                        resume_ty.hir_fmt(f)?;
                        write!(f, "|")?;

                        write!(f, " yields ")?;
                        yield_ty.hir_fmt(f)?;

                        write!(f, " -> ")?;
                        return_ty.hir_fmt(f)?;
                    }
                    _ => panic!("invalid kind for coroutine: {kind:?}"),
                }
            }
            TyKind::CoroutineWitness(..) => write!(f, "{{coroutine witness}}")?,
            TyKind::Pat(_, _) => write!(f, "{{pat}}")?,
            TyKind::UnsafeBinder(_) => write!(f, "{{unsafe binder}}")?,
            TyKind::Alias(..) => write!(f, "{{alias}}")?,
        }
        Ok(())
    }
}

fn hir_fmt_generics<'db>(
    f: &mut HirFormatter<'_, 'db>,
    parameters: &[GenericArg<'db>],
    generic_def: Option<hir_def::GenericDefId>,
    self_: Option<Ty<'db>>,
) -> Result {
    if parameters.is_empty() {
        return Ok(());
    }

    let parameters_to_write = generic_args_sans_defaults(f, generic_def, parameters);

    if !parameters_to_write.is_empty() {
        write!(f, "<")?;
        hir_fmt_generic_arguments(f, parameters_to_write, self_)?;
        write!(f, ">")?;
    }

    Ok(())
}

fn generic_args_sans_defaults<'ga, 'db>(
    f: &mut HirFormatter<'_, 'db>,
    generic_def: Option<hir_def::GenericDefId>,
    parameters: &'ga [GenericArg<'db>],
) -> &'ga [GenericArg<'db>] {
    if f.display_kind.is_source_code() || f.omit_verbose_types() {
        match generic_def.map(|generic_def_id| f.db.generic_defaults(generic_def_id)) {
            None => parameters,
            Some(default_parameters) => {
                let should_show = |arg: GenericArg<'db>, i: usize| match default_parameters.get(i) {
                    None => true,
                    Some(default_parameter) => {
                        arg != default_parameter
                            .instantiate(f.interner, &parameters[..i])
                            .skip_norm_wip()
                    }
                };
                let mut default_from = 0;
                for (i, &parameter) in parameters.iter().enumerate() {
                    if should_show(parameter, i) {
                        default_from = i + 1;
                    }
                }
                &parameters[0..default_from]
            }
        }
    } else {
        parameters
    }
}

fn hir_fmt_generic_args<'db>(
    f: &mut HirFormatter<'_, 'db>,
    parameters: &[GenericArg<'db>],
    generic_def: Option<hir_def::GenericDefId>,
    self_: Option<Ty<'db>>,
) -> Result {
    if parameters.is_empty() {
        return Ok(());
    }

    let parameters_to_write = generic_args_sans_defaults(f, generic_def, parameters);

    if !parameters_to_write.is_empty() {
        write!(f, "<")?;
        hir_fmt_generic_arguments(f, parameters_to_write, self_)?;
        write!(f, ">")?;
    }

    Ok(())
}

fn hir_fmt_generic_arguments<'db>(
    f: &mut HirFormatter<'_, 'db>,
    parameters: &[GenericArg<'db>],
    self_: Option<Ty<'db>>,
) -> Result {
    let mut first = true;
    let lifetime_offset = parameters.iter().position(|arg| arg.region().is_some());

    let (ty_or_const, lifetimes) = match lifetime_offset {
        Some(offset) => parameters.split_at(offset),
        None => (parameters, &[][..]),
    };
    for generic_arg in lifetimes.iter().chain(ty_or_const) {
        if !mem::take(&mut first) {
            write!(f, ", ")?;
        }
        match self_ {
            self_ @ Some(_) if generic_arg.ty() == self_ => write!(f, "Self")?,
            _ => generic_arg.hir_fmt(f)?,
        }
    }
    Ok(())
}

fn hir_fmt_tys<'db>(
    f: &mut HirFormatter<'_, 'db>,
    tys: &[Ty<'db>],
    self_: Option<Ty<'db>>,
) -> Result {
    let mut first = true;

    for ty in tys {
        if !mem::take(&mut first) {
            write!(f, ", ")?;
        }
        match self_ {
            Some(self_) if *ty == self_ => write!(f, "Self")?,
            _ => ty.hir_fmt(f)?,
        }
    }
    Ok(())
}

impl<'db> HirDisplay<'db> for PolyFnSig<'db> {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        let FnSig { inputs_and_output, fn_sig_kind } = self.skip_binder();
        if let Safety::Unsafe = fn_sig_kind.safety() {
            write!(f, "unsafe ")?;
        }
        // FIXME: Enable this when the FIXME on FnAbi regarding PartialEq is fixed.
        // if !matches!(abi, FnAbi::Rust) {
        //     f.write_str("extern \"")?;
        //     f.write_str(abi.as_str())?;
        //     f.write_str("\" ")?;
        // }
        write!(f, "fn(")?;
        f.write_joined(inputs_and_output.inputs(), ", ")?;
        if fn_sig_kind.c_variadic() {
            if inputs_and_output.inputs().is_empty() {
                write!(f, "...")?;
            } else {
                write!(f, ", ...")?;
            }
        }
        write!(f, ")")?;
        let ret = inputs_and_output.output();
        if !ret.is_unit() {
            write!(f, " -> ")?;
            ret.hir_fmt(f)?;
        }
        Ok(())
    }
}

impl<'db> HirDisplay<'db> for Term<'db> {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        match self.kind() {
            TermKind::Ty(it) => it.hir_fmt(f),
            TermKind::Const(it) => it.hir_fmt(f),
        }
    }
}

#[derive(Clone, Copy, PartialEq, Eq)]
pub enum SizedByDefault {
    NotSized,
    Sized { anchor: Crate },
}

impl SizedByDefault {
    fn is_sized_trait(self, trait_: TraitId, interner: DbInterner<'_>) -> bool {
        match self {
            Self::NotSized => false,
            Self::Sized { .. } => {
                let sized_trait = interner.lang_items().Sized;
                Some(trait_) == sized_trait
            }
        }
    }
}

pub fn write_bounds_like_dyn_trait_with_prefix<'db>(
    f: &mut HirFormatter<'_, 'db>,
    prefix: &str,
    this: Either<Ty<'db>, Region<'db>>,
    predicates: &[Clause<'db>],
    default_sized: SizedByDefault,
    needs_parens_if_multi: bool,
) -> Result {
    let needs_parens =
        needs_parens_if_multi && trait_bounds_need_parens(f, this, predicates, default_sized);
    if needs_parens {
        write!(f, "(")?;
    }
    write!(f, "{prefix}")?;
    if !predicates.is_empty()
        || predicates.is_empty() && matches!(default_sized, SizedByDefault::Sized { .. })
    {
        write!(f, " ")?;
        write_bounds_like_dyn_trait(f, this, predicates, default_sized)?;
    }
    if needs_parens {
        write!(f, ")")?;
    }
    Ok(())
}

fn trait_bounds_need_parens<'db>(
    f: &mut HirFormatter<'_, 'db>,
    this: Either<Ty<'db>, Region<'db>>,
    predicates: &[Clause<'db>],
    default_sized: SizedByDefault,
) -> bool {
    // This needs to be kept in sync with `write_bounds_like_dyn_trait()`.
    let mut distinct_bounds = 0usize;
    let mut is_sized = false;
    for p in predicates {
        match p.kind().skip_binder() {
            ClauseKind::Trait(trait_ref) => {
                let trait_ = trait_ref.def_id().0;
                if default_sized.is_sized_trait(trait_, f.interner) {
                    is_sized = true;
                    if matches!(default_sized, SizedByDefault::Sized { .. }) {
                        // Don't print +Sized, but rather +?Sized if absent.
                        continue;
                    }
                }

                distinct_bounds += 1;
            }
            ClauseKind::TypeOutlives(to) if Either::Left(to.0) == this => distinct_bounds += 1,
            ClauseKind::RegionOutlives(lo) if Either::Right(lo.0) == this => distinct_bounds += 1,
            _ => {}
        }
    }

    if let SizedByDefault::Sized { .. } = default_sized
        && !is_sized
    {
        distinct_bounds += 1;
    }

    distinct_bounds > 1
}

fn write_bounds_like_dyn_trait<'db>(
    f: &mut HirFormatter<'_, 'db>,
    this: Either<Ty<'db>, Region<'db>>,
    predicates: &[Clause<'db>],
    default_sized: SizedByDefault,
) -> Result {
    // Note: This code is written to produce nice results (i.e.
    // corresponding to surface Rust) for types that can occur in
    // actual Rust. It will have weird results if the predicates
    // aren't as expected (i.e. self types = $0, projection
    // predicates for a certain trait come after the Implemented
    // predicate for that trait).
    let mut first = true;
    let mut angle_open = false;
    let mut is_fn_trait = false;
    let mut is_sized = false;
    for p in predicates {
        match p.kind().skip_binder() {
            ClauseKind::Trait(trait_ref) => {
                let trait_ = trait_ref.def_id().0;
                if default_sized.is_sized_trait(trait_, f.interner) {
                    is_sized = true;
                    if matches!(default_sized, SizedByDefault::Sized { .. }) {
                        // Don't print +Sized, but rather +?Sized if absent.
                        continue;
                    }
                }
                if !is_fn_trait {
                    is_fn_trait = fn_traits(f.lang_items()).any(|it| it == trait_);
                }
                if !is_fn_trait && angle_open {
                    write!(f, ">")?;
                    angle_open = false;
                }
                if !first {
                    write!(f, " + ")?;
                }
                // We assume that the self type is ^0.0 (i.e. the
                // existential) here, which is the only thing that's
                // possible in actual Rust, and hence don't print it
                f.start_location_link(trait_.into());
                write!(f, "{}", TraitSignature::of(f.db, trait_).name.display(f.db, f.edition()))?;
                f.end_location_link();
                if is_fn_trait {
                    if let [_self, params @ ..] = trait_ref.trait_ref.args.as_slice()
                        && let Some(args) = params.first().and_then(|it| it.ty()?.as_tuple())
                    {
                        write!(f, "(")?;
                        hir_fmt_tys(f, args.as_slice(), Some(trait_ref.trait_ref.self_ty()))?;
                        write!(f, ")")?;
                    }
                } else {
                    let params = generic_args_sans_defaults(
                        f,
                        Some(trait_.into()),
                        trait_ref.trait_ref.args.as_slice(),
                    );
                    if let [_self, params @ ..] = params
                        && !params.is_empty()
                    {
                        write!(f, "<")?;
                        hir_fmt_generic_arguments(f, params, Some(trait_ref.trait_ref.self_ty()))?;
                        // there might be assoc type bindings, so we leave the angle brackets open
                        angle_open = true;
                    }
                }
            }
            ClauseKind::TypeOutlives(to) if Either::Left(to.0) == this => {
                if !is_fn_trait && angle_open {
                    write!(f, ">")?;
                    angle_open = false;
                }
                if !first {
                    write!(f, " + ")?;
                }
                to.1.hir_fmt(f)?;
            }
            ClauseKind::RegionOutlives(lo) if Either::Right(lo.0) == this => {
                if !is_fn_trait && angle_open {
                    write!(f, ">")?;
                    angle_open = false;
                }
                if !first {
                    write!(f, " + ")?;
                }
                lo.1.hir_fmt(f)?;
            }
            ClauseKind::Projection(projection) if is_fn_trait => {
                is_fn_trait = false;
                if !projection.term.as_type().is_some_and(|it| it.is_unit()) {
                    write!(f, " -> ")?;
                    projection.term.hir_fmt(f)?;
                }
            }
            ClauseKind::Projection(projection) => {
                let TermId::TypeAliasId(assoc_ty_id) = projection.def_id().0 else {
                    continue;
                };
                // in types in actual Rust, these will always come
                // after the corresponding Implemented predicate
                if angle_open {
                    write!(f, ", ")?;
                } else {
                    write!(f, "<")?;
                    angle_open = true;
                }
                let type_alias = TypeAliasSignature::of(f.db, assoc_ty_id);
                f.start_location_link(assoc_ty_id.into());
                write!(f, "{}", type_alias.name.display(f.db, f.edition()))?;
                f.end_location_link();

                let own_args = projection.projection_term.own_args(f.interner);
                if !own_args.is_empty() {
                    write!(f, "<")?;
                    hir_fmt_generic_arguments(f, own_args, None)?;
                    write!(f, ">")?;
                }
                write!(f, " = ")?;
                projection.term.hir_fmt(f)?;
            }
            _ => {}
        }
        first = false;
    }
    if angle_open {
        write!(f, ">")?;
    }
    if let SizedByDefault::Sized { anchor } = default_sized {
        let sized_trait = hir_def::lang_item::lang_items(f.db, anchor).Sized;
        if !is_sized {
            if !first {
                write!(f, " + ")?;
            }
            if let Some(sized_trait) = sized_trait {
                f.start_location_link(sized_trait.into());
            }
            write!(f, "?Sized")?;
        } else if first {
            if let Some(sized_trait) = sized_trait {
                f.start_location_link(sized_trait.into());
            }
            write!(f, "Sized")?;
        }
        if sized_trait.is_some() {
            f.end_location_link();
        }
    }
    Ok(())
}

pub fn write_params_bounds<'db>(
    f: &mut HirFormatter<'_, 'db>,
    predicates: &[Clause<'db>],
) -> Result {
    // Use an FxIndexMap to keep user's order, as far as possible.
    let mut per_type = FxIndexMap::<_, Vec<_>>::default();
    for &predicate in predicates {
        let base_ty = match predicate.kind().skip_binder() {
            ClauseKind::Trait(clause) => Either::Left(clause.self_ty()),
            ClauseKind::RegionOutlives(clause) => Either::Right(clause.0),
            ClauseKind::TypeOutlives(clause) => Either::Left(clause.0),
            ClauseKind::Projection(clause) => Either::Left(clause.self_ty()),
            ClauseKind::ConstArgHasType(..)
            | ClauseKind::WellFormed(_)
            | ClauseKind::ConstEvaluatable(_)
            | ClauseKind::HostEffect(..)
            | ClauseKind::UnstableFeature(_) => continue,
        };
        per_type.entry(base_ty).or_default().push(predicate);
    }

    for (base_ty, clauses) in per_type {
        f.write_str("    ")?;
        match base_ty {
            Either::Left(it) => it.hir_fmt(f)?,
            Either::Right(it) => it.hir_fmt(f)?,
        }
        f.write_str(": ")?;
        // Rudimentary approximation: type params are `Sized` by default, everything else not.
        // FIXME: This is not correct, really. But I'm not sure how we can from the ty representation
        // to extract the default sizedness, and if it's possible at all.
        let default_sized = match base_ty {
            Either::Left(ty) if matches!(ty.kind(), TyKind::Param(_)) => {
                SizedByDefault::Sized { anchor: f.krate() }
            }
            _ => SizedByDefault::NotSized,
        };
        write_bounds_like_dyn_trait(f, base_ty, &clauses, default_sized)?;
        f.write_str(",\n")?;
    }
    Ok(())
}

impl<'db> HirDisplay<'db> for TraitRef<'db> {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        let trait_ = self.def_id.0;
        f.start_location_link(trait_.into());
        write!(f, "{}", TraitSignature::of(f.db, trait_).name.display(f.db, f.edition()))?;
        f.end_location_link();
        let substs = self.args.as_slice();
        hir_fmt_generic_args(f, &substs[1..], None, Some(self.self_ty()))
    }
}

impl<'db> HirDisplay<'db> for TraitPredicate<'db> {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        self.self_ty().hir_fmt(f)?;
        f.write_str(": ")?;
        match self.polarity {
            rustc_type_ir::PredicatePolarity::Positive => {}
            rustc_type_ir::PredicatePolarity::Negative => f.write_char('!')?,
        }
        let trait_ = self.def_id().0;
        f.start_location_link(trait_.into());
        write!(f, "{}", TraitSignature::of(f.db, trait_).name.display(f.db, f.edition()))?;
        f.end_location_link();
        let substs = &self.trait_ref.args[1..];
        hir_fmt_generic_args(f, substs, None, None)
    }
}

impl<'db> HirDisplay<'db> for Region<'db> {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        match self.kind() {
            RegionKind::ReEarlyParam(param) => {
                let generics = GenericParams::of(f.db, param.id.parent);
                let param_data = &generics[param.id.local_id];
                f.start_location_link_generic(param.id.into());
                write!(f, "{}", param_data.name.display(f.db, f.edition()))?;
                f.end_location_link();
                Ok(())
            }
            RegionKind::ReBound(BoundVarIndexKind::Bound(db), idx) => {
                write!(f, "?{}.{}", db.as_u32(), idx.var.as_u32())
            }
            RegionKind::ReBound(BoundVarIndexKind::Canonical, idx) => {
                write!(f, "?c.{}", idx.var.as_u32())
            }
            RegionKind::ReVar(_) => write!(f, "_"),
            RegionKind::ReStatic => write!(f, "'static"),
            RegionKind::ReError(..) => {
                if cfg!(test) {
                    write!(f, "'?")
                } else {
                    write!(f, "'_")
                }
            }
            RegionKind::ReErased => write!(f, "'<erased>"),
            RegionKind::RePlaceholder(_) => write!(f, "<placeholder>"),
            RegionKind::ReLateParam(_) => write!(f, "<late-param>"),
        }
    }
}

pub fn write_visibility<'db>(
    module_id: ModuleId,
    vis: Visibility,
    f: &mut HirFormatter<'_, 'db>,
) -> Result {
    match vis {
        Visibility::Public => write!(f, "pub "),
        Visibility::PubCrate(_) => write!(f, "pub(crate) "),
        Visibility::Module(vis_id, _) => {
            let def_map = module_id.def_map(f.db);
            let root_module_id = def_map.root_module_id();
            if vis_id == module_id {
                // pub(self) or omitted
                Ok(())
            } else if root_module_id == vis_id && root_module_id.block(f.db).is_none() {
                write!(f, "pub(crate) ")
            } else if module_id.containing_module(f.db) == Some(vis_id)
                && !vis_id.is_block_module(f.db)
            {
                write!(f, "pub(super) ")
            } else {
                write!(f, "pub(in ...) ")
            }
        }
    }
}

pub trait HirDisplayWithExpressionStore<'db> {
    fn hir_fmt(
        &self,
        f: &mut HirFormatter<'_, 'db>,
        owner: ExpressionStoreOwnerId,
        store: &ExpressionStore,
    ) -> Result;
}

impl<'db, T: ?Sized + HirDisplayWithExpressionStore<'db>> HirDisplayWithExpressionStore<'db>
    for &'_ T
{
    fn hir_fmt(
        &self,
        f: &mut HirFormatter<'_, 'db>,
        owner: ExpressionStoreOwnerId,
        store: &ExpressionStore,
    ) -> Result {
        T::hir_fmt(&**self, f, owner, store)
    }
}

pub fn hir_display_with_store<'a, 'db, T: HirDisplayWithExpressionStore<'db> + 'a>(
    value: T,
    owner: ExpressionStoreOwnerId,
    store: &'a ExpressionStore,
) -> impl HirDisplay<'db> + 'a {
    ExpressionStoreAdapter(value, owner, store)
}

struct ExpressionStoreAdapter<'a, T>(T, ExpressionStoreOwnerId, &'a ExpressionStore);

impl<'a, T> ExpressionStoreAdapter<'a, T> {
    fn wrap(
        owner: ExpressionStoreOwnerId,
        store: &'a ExpressionStore,
    ) -> impl Fn(T) -> ExpressionStoreAdapter<'a, T> {
        move |value| ExpressionStoreAdapter(value, owner, store)
    }
}

impl<'db, T: HirDisplayWithExpressionStore<'db>> HirDisplay<'db> for ExpressionStoreAdapter<'_, T> {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        T::hir_fmt(&self.0, f, self.1, self.2)
    }
}
impl<'db> HirDisplayWithExpressionStore<'db> for LifetimeRefId {
    fn hir_fmt(
        &self,
        f: &mut HirFormatter<'_, 'db>,
        _owner: ExpressionStoreOwnerId,
        store: &ExpressionStore,
    ) -> Result {
        match &store[*self] {
            LifetimeRef::Named(name) => write!(f, "{}", name.display(f.db, f.edition())),
            LifetimeRef::Static => write!(f, "'static"),
            LifetimeRef::Placeholder => write!(f, "'_"),
            LifetimeRef::Error => write!(f, "'{{error}}"),
            &LifetimeRef::Param(lifetime_param_id) => {
                let generic_params = GenericParams::of(f.db, lifetime_param_id.parent);
                write!(
                    f,
                    "{}",
                    generic_params[lifetime_param_id.local_id].name.display(f.db, f.edition())
                )
            }
        }
    }
}

impl<'db> HirDisplayWithExpressionStore<'db> for TypeRefId {
    fn hir_fmt(
        &self,
        f: &mut HirFormatter<'_, 'db>,
        owner: ExpressionStoreOwnerId,
        store: &ExpressionStore,
    ) -> Result {
        match &store[*self] {
            TypeRef::Never => write!(f, "!")?,
            TypeRef::TypeParam(param) => {
                let generic_params = GenericParams::of(f.db, param.parent());
                match generic_params[param.local_id()].name() {
                    Some(name) => write!(f, "{}", name.display(f.db, f.edition()))?,
                    None => {
                        write!(f, "impl ")?;
                        f.write_joined(
                            generic_params
                                .where_predicates()
                                .iter()
                                .filter_map(|it| match it {
                                    WherePredicate::TypeBound { target, bound }
                                    | WherePredicate::ForLifetime { lifetimes: _, target, bound }
                                        if matches!(
                                            store[*target],
                                            TypeRef::TypeParam(t) if t == *param
                                        ) =>
                                    {
                                        Some(bound)
                                    }
                                    _ => None,
                                })
                                .map(ExpressionStoreAdapter::wrap(owner, store)),
                            " + ",
                        )?;
                    }
                }
            }
            TypeRef::Placeholder => write!(f, "_")?,
            TypeRef::Tuple(elems) => {
                write!(f, "(")?;
                f.write_joined(elems.iter().map(ExpressionStoreAdapter::wrap(owner, store)), ", ")?;
                if elems.len() == 1 {
                    write!(f, ",")?;
                }
                write!(f, ")")?;
            }
            TypeRef::Path(path) => path.hir_fmt(f, owner, store)?,
            TypeRef::RawPtr(inner, mutability) => {
                let mutability = match mutability {
                    hir_def::type_ref::Mutability::Shared => "*const ",
                    hir_def::type_ref::Mutability::Mut => "*mut ",
                };
                write!(f, "{mutability}")?;
                inner.hir_fmt(f, owner, store)?;
            }
            TypeRef::Reference(ref_) => {
                let mutability = match ref_.mutability {
                    hir_def::type_ref::Mutability::Shared => "",
                    hir_def::type_ref::Mutability::Mut => "mut ",
                };
                write!(f, "&")?;
                if let Some(lifetime) = &ref_.lifetime {
                    lifetime.hir_fmt(f, owner, store)?;
                    write!(f, " ")?;
                }
                write!(f, "{mutability}")?;
                ref_.ty.hir_fmt(f, owner, store)?;
            }
            TypeRef::Array(array) => {
                write!(f, "[")?;
                array.ty.hir_fmt(f, owner, store)?;
                write!(f, "; ")?;
                array.len.hir_fmt(f, owner, store)?;
                write!(f, "]")?;
            }
            TypeRef::Slice(inner) => {
                write!(f, "[")?;
                inner.hir_fmt(f, owner, store)?;
                write!(f, "]")?;
            }
            TypeRef::Fn(fn_) => {
                if fn_.is_unsafe {
                    write!(f, "unsafe ")?;
                }
                if fn_.abi != ExternAbi::Rust {
                    f.write_str("extern \"")?;
                    f.write_str(fn_.abi.as_str())?;
                    f.write_str("\" ")?;
                }
                write!(f, "fn(")?;
                if let Some(((_, return_type), function_parameters)) = fn_.params.split_last() {
                    for index in 0..function_parameters.len() {
                        let (param_name, param_type) = &function_parameters[index];
                        if let Some(name) = param_name {
                            write!(f, "{}: ", name.display(f.db, f.edition()))?;
                        }

                        param_type.hir_fmt(f, owner, store)?;

                        if index != function_parameters.len() - 1 {
                            write!(f, ", ")?;
                        }
                    }
                    if fn_.is_varargs {
                        write!(f, "{}...", if fn_.params.len() == 1 { "" } else { ", " })?;
                    }
                    write!(f, ")")?;
                    match &store[*return_type] {
                        TypeRef::Tuple(tup) if tup.is_empty() => {}
                        _ => {
                            write!(f, " -> ")?;
                            return_type.hir_fmt(f, owner, store)?;
                        }
                    }
                }
            }
            TypeRef::ImplTrait(bounds) => {
                write!(f, "impl ")?;
                f.write_joined(
                    bounds.iter().map(ExpressionStoreAdapter::wrap(owner, store)),
                    " + ",
                )?;
            }
            TypeRef::DynTrait(bounds) => {
                write!(f, "dyn ")?;
                f.write_joined(
                    bounds.iter().map(ExpressionStoreAdapter::wrap(owner, store)),
                    " + ",
                )?;
            }
            TypeRef::PatternType(ty, pat) => {
                ty.hir_fmt(f, owner, store)?;
                write!(f, " is ")?;
                pat.hir_fmt(f, owner, store)?;
            }
            TypeRef::Error => write!(f, "{{error}}")?,
        }
        Ok(())
    }
}

impl<'db> HirDisplayWithExpressionStore<'db> for ConstRef {
    fn hir_fmt(
        &self,
        f: &mut HirFormatter<'_, 'db>,
        _owner: ExpressionStoreOwnerId,
        _store: &ExpressionStore,
    ) -> Result {
        // FIXME
        write!(f, "{{const}}")?;

        Ok(())
    }
}

impl<'db> HirDisplayWithExpressionStore<'db> for PatId {
    fn hir_fmt(
        &self,
        f: &mut HirFormatter<'_, 'db>,
        owner: ExpressionStoreOwnerId,
        store: &ExpressionStore,
    ) -> Result {
        write!(
            f,
            "{}",
            hir_def::expr_store::pretty::print_pat_hir(
                f.db,
                store,
                owner,
                *self,
                false,
                f.edition()
            )
        )?;
        Ok(())
    }
}

impl<'db> HirDisplayWithExpressionStore<'db> for TypeBound {
    fn hir_fmt(
        &self,
        f: &mut HirFormatter<'_, 'db>,
        owner: ExpressionStoreOwnerId,
        store: &ExpressionStore,
    ) -> Result {
        match self {
            &TypeBound::Path(path, modifier) => {
                match modifier {
                    TraitBoundModifier::None => (),
                    TraitBoundModifier::Maybe => write!(f, "?")?,
                }
                store[path].hir_fmt(f, owner, store)
            }
            TypeBound::Lifetime(lifetime) => lifetime.hir_fmt(f, owner, store),
            TypeBound::ForLifetime(lifetimes, path) => {
                let edition = f.edition();
                write!(
                    f,
                    "for<{}> ",
                    lifetimes.iter().map(|it| it.display(f.db, edition)).format(", ")
                )?;
                store[*path].hir_fmt(f, owner, store)
            }
            TypeBound::Use(args) => {
                write!(f, "use<")?;
                let edition = f.edition();
                let last = args.len().saturating_sub(1);
                for (idx, arg) in args.iter().enumerate() {
                    match arg {
                        UseArgRef::Lifetime(lt) => lt.hir_fmt(f, owner, store)?,
                        UseArgRef::Name(n) => write!(f, "{}", n.display(f.db, edition))?,
                    }
                    if idx != last {
                        write!(f, ", ")?;
                    }
                }
                write!(f, "> ")
            }
            TypeBound::Error => write!(f, "{{error}}"),
        }
    }
}

impl<'db> HirDisplayWithExpressionStore<'db> for Path {
    fn hir_fmt(
        &self,
        f: &mut HirFormatter<'_, 'db>,
        owner: ExpressionStoreOwnerId,
        store: &ExpressionStore,
    ) -> Result {
        match (self.type_anchor(), self.kind()) {
            (Some(anchor), _) => {
                write!(f, "<")?;
                anchor.hir_fmt(f, owner, store)?;
                write!(f, ">")?;
            }
            (_, PathKind::Plain) => {}
            (_, PathKind::Abs) => {}
            (_, PathKind::Crate) => write!(f, "crate")?,
            (_, &PathKind::SELF) => write!(f, "self")?,
            (_, PathKind::Super(n)) => {
                for i in 0..*n {
                    if i > 0 {
                        write!(f, "::")?;
                    }
                    write!(f, "super")?;
                }
            }
            (_, PathKind::DollarCrate(id)) => {
                // Resolve `$crate` to the crate's display name.
                // FIXME: should use the dependency name instead if available, but that depends on
                // the crate invoking `HirDisplay`
                let crate_data = id.extra_data(f.db);
                let name = crate_data
                    .display_name
                    .as_ref()
                    .map(|name| (*name.canonical_name()).clone())
                    .unwrap_or(sym::dollar_crate);
                write!(f, "{name}")?
            }
        }

        // Convert trait's `Self` bound back to the surface syntax. Note there is no associated
        // trait, so there can only be one path segment that `has_self_type`. The `Self` type
        // itself can contain further qualified path through, which will be handled by recursive
        // `hir_fmt`s.
        //
        // `trait_mod::Trait<Self = type_mod::Type, Args>::Assoc`
        // =>
        // `<type_mod::Type as trait_mod::Trait<Args>>::Assoc`
        let trait_self_ty = self.segments().iter().find_map(|seg| {
            let generic_args = seg.args_and_bindings?;
            generic_args.has_self_type.then(|| &generic_args.args[0])
        });
        if let Some(ty) = trait_self_ty {
            write!(f, "<")?;
            ty.hir_fmt(f, owner, store)?;
            write!(f, " as ")?;
            // Now format the path of the trait...
        }

        for (seg_idx, segment) in self.segments().iter().enumerate() {
            if !matches!(self.kind(), PathKind::Plain) || seg_idx > 0 {
                write!(f, "::")?;
            }
            write!(f, "{}", segment.name.display(f.db, f.edition()))?;
            if let Some(generic_args) = segment.args_and_bindings {
                // We should be in type context, so format as `Foo<Bar>` instead of `Foo::<Bar>`.
                // Do we actually format expressions?
                match generic_args.parenthesized {
                    hir_def::expr_store::path::GenericArgsParentheses::ReturnTypeNotation => {
                        write!(f, "(..)")?;
                    }
                    hir_def::expr_store::path::GenericArgsParentheses::ParenSugar => {
                        // First argument will be a tuple, which already includes the parentheses.
                        // If the tuple only contains 1 item, write it manually to avoid the trailing `,`.
                        let tuple = match generic_args.args[0] {
                            hir_def::expr_store::path::GenericArg::Type(ty) => match &store[ty] {
                                TypeRef::Tuple(it) => Some(it),
                                _ => None,
                            },
                            _ => None,
                        };
                        if let Some(v) = tuple {
                            if v.len() == 1 {
                                write!(f, "(")?;
                                v[0].hir_fmt(f, owner, store)?;
                                write!(f, ")")?;
                            } else {
                                generic_args.args[0].hir_fmt(f, owner, store)?;
                            }
                        }
                        if let Some(ret) = generic_args.bindings[0].type_ref
                            && !matches!(&store[ret], TypeRef::Tuple(v) if v.is_empty())
                        {
                            write!(f, " -> ")?;
                            ret.hir_fmt(f, owner, store)?;
                        }
                    }
                    hir_def::expr_store::path::GenericArgsParentheses::No => {
                        let mut first = true;
                        // Skip the `Self` bound if exists. It's handled outside the loop.
                        for arg in &generic_args.args[generic_args.has_self_type as usize..] {
                            if first {
                                first = false;
                                write!(f, "<")?;
                            } else {
                                write!(f, ", ")?;
                            }
                            arg.hir_fmt(f, owner, store)?;
                        }
                        for binding in generic_args.bindings.iter() {
                            if first {
                                first = false;
                                write!(f, "<")?;
                            } else {
                                write!(f, ", ")?;
                            }
                            write!(f, "{}", binding.name.display(f.db, f.edition()))?;
                            match &binding.type_ref {
                                Some(ty) => {
                                    write!(f, " = ")?;
                                    ty.hir_fmt(f, owner, store)?
                                }
                                None => {
                                    write!(f, ": ")?;
                                    f.write_joined(
                                        binding
                                            .bounds
                                            .iter()
                                            .map(ExpressionStoreAdapter::wrap(owner, store)),
                                        " + ",
                                    )?;
                                }
                            }
                        }

                        // There may be no generic arguments to print, in case of a trait having only a
                        // single `Self` bound which is converted to `<Ty as Trait>::Assoc`.
                        if !first {
                            write!(f, ">")?;
                        }

                        // Current position: `<Ty as Trait<Args>|`
                        if generic_args.has_self_type {
                            write!(f, ">")?;
                        }
                    }
                }
            }
        }

        Ok(())
    }
}

impl<'db> HirDisplayWithExpressionStore<'db> for hir_def::expr_store::path::GenericArg {
    fn hir_fmt(
        &self,
        f: &mut HirFormatter<'_, 'db>,
        owner: ExpressionStoreOwnerId,
        store: &ExpressionStore,
    ) -> Result {
        match self {
            hir_def::expr_store::path::GenericArg::Type(ty) => ty.hir_fmt(f, owner, store),
            hir_def::expr_store::path::GenericArg::Const(_c) => {
                // write!(f, "{}", c.display(f.db, f.edition()))
                write!(f, "<expr>")
            }
            hir_def::expr_store::path::GenericArg::Lifetime(lifetime) => {
                lifetime.hir_fmt(f, owner, store)
            }
        }
    }
}
