Skip to main content

rustdoc/html/
format.rs

1//! HTML formatting module
2//!
3//! This module contains a large number of `Display` implementations for
4//! various types in `rustdoc::clean`.
5//!
6//! These implementations all emit HTML. As an internal implementation detail,
7//! some of them support an alternate format that emits plain text.
8
9use std::cmp::Ordering;
10use std::fmt::{self, Display, Write};
11use std::{iter, slice};
12
13use itertools::{Either, Itertools};
14use rustc_abi::ExternAbi;
15use rustc_ast::join_path_syms;
16use rustc_attr_ir::{ConstStability, StabilityLevel, StableSince};
17use rustc_data_structures::fx::FxHashSet;
18use rustc_hir as hir;
19use rustc_hir::def::{DefKind, MacroKinds};
20use rustc_hir::def_id::{DefId, LOCAL_CRATE, ModId};
21use rustc_metadata::creader::CStore;
22use rustc_middle::ty::{self, Ty, TyCtxt, TypingMode};
23use rustc_span::symbol::kw;
24use rustc_span::{Ident, Symbol, sym};
25use tracing::{debug, trace};
26
27use super::url_parts_builder::UrlPartsBuilder;
28use crate::clean::types::ExternalLocation;
29use crate::clean::utils::find_nearest_parent_module;
30use crate::clean::{self, ExternalCrate, PrimitiveType, WherePredicate};
31use crate::display::{Joined as _, MaybeDisplay as _, WithOpts, Wrapped};
32use crate::formats::cache::Cache;
33use crate::formats::item_type::ItemType;
34use crate::html::escape::{Escape, EscapeBodyText};
35use crate::html::render::Context;
36use crate::passes::collect_intra_doc_links::UrlFragment;
37
38pub(crate) fn print_generic_bounds(
39    bounds: &[clean::GenericBound],
40    cx: &Context<'_>,
41) -> impl Display {
42    fmt::from_fn(move |f| {
43        let mut bounds_dup = FxHashSet::default();
44
45        bounds
46            .iter()
47            .filter(move |b| bounds_dup.insert(*b))
48            .map(|bound| print_generic_bound(bound, cx))
49            .joined(" + ", f)
50    })
51}
52
53pub(crate) fn print_generic_param_def(
54    generic_param: &clean::GenericParamDef,
55    cx: &Context<'_>,
56) -> impl Display {
57    fmt::from_fn(move |f| match &generic_param.kind {
58        clean::GenericParamDefKind::Lifetime { outlives } => {
59            write!(f, "{}", generic_param.name)?;
60
61            if !outlives.is_empty() {
62                f.write_str(": ")?;
63                outlives.iter().map(|lt| print_lifetime(lt)).joined(" + ", f)?;
64            }
65
66            Ok(())
67        }
68        clean::GenericParamDefKind::Type { bounds, default, .. } => {
69            f.write_str(generic_param.name.as_str())?;
70
71            if !bounds.is_empty() {
72                f.write_str(": ")?;
73                print_generic_bounds(bounds, cx).fmt(f)?;
74            }
75
76            if let Some(ty) = default {
77                f.write_str(" = ")?;
78                print_type(ty, cx).fmt(f)?;
79            }
80
81            Ok(())
82        }
83        clean::GenericParamDefKind::Const { ty, default, .. } => {
84            write!(f, "const {}: ", generic_param.name)?;
85            print_type(ty, cx).fmt(f)?;
86
87            if let Some(default) = default {
88                f.write_str(" = ")?;
89                if f.alternate() {
90                    write!(f, "{default}")?;
91                } else {
92                    write!(f, "{}", Escape(default))?;
93                }
94            }
95
96            Ok(())
97        }
98    })
99}
100
101pub(crate) fn print_generics(generics: &clean::Generics, cx: &Context<'_>) -> impl Display {
102    let mut real_params = generics.params.iter().filter(|p| !p.is_synthetic_param()).peekable();
103    if real_params.peek().is_none() {
104        None
105    } else {
106        Some(Wrapped::with_angle_brackets().wrap_fn(move |f| {
107            real_params.clone().map(|g| print_generic_param_def(g, cx)).joined(", ", f)
108        }))
109    }
110    .maybe_display()
111}
112
113#[derive(Clone, Copy, PartialEq, Eq)]
114pub(crate) enum Ending {
115    Newline,
116    NoNewline,
117}
118
119fn print_where_predicate(predicate: &clean::WherePredicate, cx: &Context<'_>) -> impl Display {
120    fmt::from_fn(move |f| {
121        match predicate {
122            clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => {
123                print_higher_ranked_params_with_space(bound_params, cx, "for").fmt(f)?;
124                print_type(ty, cx).fmt(f)?;
125                f.write_str(":")?;
126                if !bounds.is_empty() {
127                    f.write_str(" ")?;
128                    print_generic_bounds(bounds, cx).fmt(f)?;
129                }
130                Ok(())
131            }
132            clean::WherePredicate::RegionPredicate { lifetime, bounds } => {
133                // We don't need to check `alternate` since we can be certain that neither
134                // the lifetime nor the bounds contain any characters which need escaping.
135                write!(f, "{}:", print_lifetime(lifetime))?;
136                if !bounds.is_empty() {
137                    write!(f, " {}", print_generic_bounds(bounds, cx))?;
138                }
139                Ok(())
140            }
141            clean::WherePredicate::ProjectionPredicate { lhs, rhs } => {
142                let opts = WithOpts::from(f);
143                write!(
144                    f,
145                    "{} == {}",
146                    opts.display(print_qpath_data(lhs, cx)),
147                    opts.display(print_term(rhs, cx)),
148                )
149            }
150        }
151    })
152}
153
154/// * The Generics from which to emit a where-clause.
155/// * The number of spaces to indent each line with.
156/// * Whether the where-clause needs to add a comma and newline after the last bound.
157pub(crate) fn print_where_clause(
158    gens: &clean::Generics,
159    cx: &Context<'_>,
160    indent: usize,
161    ending: Ending,
162) -> Option<impl Display> {
163    if gens.where_predicates.is_empty() {
164        return None;
165    }
166
167    fn where_preds(
168        predicates: &[WherePredicate],
169        cx: &Context<'_>,
170        sep: impl Display,
171    ) -> impl Display {
172        fmt::from_fn(move |f| {
173            predicates.iter().map(|predicate| print_where_predicate(predicate, cx)).joined(&sep, f)
174        })
175    }
176
177    let spaces = |n: usize| crate::display::repeat(' ', n);
178
179    Some(fmt::from_fn(move |f| {
180        if f.alternate() {
181            write!(f, " where {:#}", where_preds(&gens.where_predicates, cx, ", "))?;
182            if ending == Ending::Newline {
183                f.write_char(',')?;
184            }
185            return Ok(());
186        }
187
188        const WHERE_INDENT: usize = 3;
189
190        let padding = {
191            let padding_amount = if ending == Ending::Newline {
192                indent + 4
193            } else if indent == 0 {
194                4
195            } else {
196                indent + WHERE_INDENT + "where ".len()
197            };
198            spaces(padding_amount)
199        };
200
201        let br_with_padding = format_args!("\n{padding}");
202        let sep = format_args!(",{br_with_padding}");
203        let where_preds = where_preds(&gens.where_predicates, cx, sep);
204
205        if ending == Ending::Newline {
206            write!(
207                f,
208                "{indent}<div class=\"where\">where{br_with_padding}{where_preds},</div>",
209                indent = spaces(indent.saturating_sub(1)),
210            )
211        } else if indent == 0 {
212            write!(f, "\n<span class=\"where\">where{br_with_padding}{where_preds}</span>")
213        } else {
214            write!(
215                f,
216                "\n{indent}<span class=\"where\">where {where_preds}</span>",
217                indent = spaces(indent + WHERE_INDENT),
218            )
219        }
220    }))
221}
222
223#[inline]
224pub(crate) fn print_lifetime(lt: &clean::Lifetime) -> &str {
225    lt.0.as_str()
226}
227
228pub(crate) fn print_constant_kind(
229    constant_kind: &clean::ConstantKind,
230    tcx: TyCtxt<'_>,
231) -> impl Display {
232    let expr = constant_kind.expr(tcx);
233    fmt::from_fn(
234        move |f| {
235            if f.alternate() { f.write_str(&expr) } else { write!(f, "{}", Escape(&expr)) }
236        },
237    )
238}
239
240fn print_poly_trait(poly_trait: &clean::PolyTrait, cx: &Context<'_>) -> impl Display {
241    fmt::from_fn(move |f| {
242        print_higher_ranked_params_with_space(&poly_trait.generic_params, cx, "for").fmt(f)?;
243        print_path(&poly_trait.trait_, cx).fmt(f)
244    })
245}
246
247pub(crate) fn print_generic_bound(
248    generic_bound: &clean::GenericBound,
249    cx: &Context<'_>,
250) -> impl Display {
251    fmt::from_fn(move |f| match generic_bound {
252        clean::GenericBound::Outlives(lt) => f.write_str(print_lifetime(lt)),
253        clean::GenericBound::TraitBound(ty, modifiers) => {
254            // `const` and `[const]` trait bounds are experimental; don't render them.
255            let hir::TraitBoundModifiers { polarity, constness: _ } = modifiers;
256            f.write_str(match polarity {
257                hir::BoundPolarity::Positive => "",
258                hir::BoundPolarity::Maybe(_) => "?",
259                hir::BoundPolarity::Negative(_) => "!",
260            })?;
261            print_poly_trait(ty, cx).fmt(f)
262        }
263        clean::GenericBound::Use(args) => {
264            f.write_str("use")?;
265            Wrapped::with_angle_brackets()
266                .wrap_fn(|f| args.iter().map(|arg| arg.name()).joined(", ", f))
267                .fmt(f)
268        }
269    })
270}
271
272fn print_generic_args(generic_args: &clean::GenericArgs, cx: &Context<'_>) -> impl Display {
273    fmt::from_fn(move |f| {
274        match generic_args {
275            clean::GenericArgs::AngleBracketed { args, constraints } => {
276                if !args.is_empty() || !constraints.is_empty() {
277                    Wrapped::with_angle_brackets()
278                        .wrap_fn(|f| {
279                            [Either::Left(args), Either::Right(constraints)]
280                                .into_iter()
281                                .flat_map(Either::factor_into_iter)
282                                .map(|either| {
283                                    either.map_either(
284                                        |arg| print_generic_arg(arg, cx),
285                                        |constraint| print_assoc_item_constraint(constraint, cx),
286                                    )
287                                })
288                                .joined(", ", f)
289                        })
290                        .fmt(f)?;
291                }
292            }
293            clean::GenericArgs::Parenthesized { inputs, output } => {
294                Wrapped::with_parens()
295                    .wrap_fn(|f| inputs.iter().map(|ty| print_type(ty, cx)).joined(", ", f))
296                    .fmt(f)?;
297                if let Some(ref ty) = *output {
298                    f.write_str(if f.alternate() { " -> " } else { " -&gt; " })?;
299                    print_type(ty, cx).fmt(f)?;
300                }
301            }
302            clean::GenericArgs::ReturnTypeNotation => {
303                f.write_str("(..)")?;
304            }
305        }
306        Ok(())
307    })
308}
309
310// Possible errors when computing href link source for a `DefId`
311#[derive(PartialEq, Eq, Debug)]
312pub(crate) enum HrefError {
313    /// This item is known to rustdoc, but from a crate that does not have documentation generated.
314    ///
315    /// This can only happen for non-local items.
316    ///
317    /// # Example
318    ///
319    /// Crate `a` defines a public trait and crate `b` – the target crate that depends on `a` –
320    /// implements it for a local type.
321    /// We document `b` but **not** `a` (we only _build_ the latter – with `rustc`):
322    ///
323    /// ```sh
324    /// rustc a.rs --crate-type=lib
325    /// rustdoc b.rs --crate-type=lib --extern=a=liba.rlib
326    /// ```
327    ///
328    /// Now, the associated items in the trait impl want to link to the corresponding item in the
329    /// trait declaration (see `html::render::assoc_href_attr`) but it's not available since their
330    /// *documentation (was) not built*.
331    DocumentationNotBuilt,
332    /// This can only happen for non-local items when `--document-private-items` is not passed.
333    Private,
334    // Not in external cache, href link should be in same page
335    NotInExternalCache,
336    /// Refers to an unnamable item, such as one defined within a function or const block.
337    UnnamableItem,
338}
339
340/// Type representing information of an `href` attribute.
341#[derive(Debug)]
342pub(crate) struct HrefInfo {
343    /// URL to the item page.
344    pub(crate) url: String,
345    /// Kind of the item (used to generate the `title` attribute).
346    pub(crate) kind: ItemType,
347    /// Rust path to the item (used to generate the `title` attribute).
348    pub(crate) rust_path: Vec<Symbol>,
349}
350
351/// This function is to get the external macro path because they are not in the cache used in
352/// `href_with_jump_to_def_path_depth`.
353fn generate_macro_def_id_path(
354    def_id: DefId,
355    cx: &Context<'_>,
356    jump_to_def_path_depth: Option<usize>,
357) -> Result<HrefInfo, HrefError> {
358    let tcx = cx.tcx();
359    let crate_name = tcx.crate_name(def_id.krate);
360    let cache = cx.cache();
361
362    let cstore = CStore::from_tcx(tcx);
363    // We need this to prevent a `panic` when this function is used from intra doc links...
364    if !cstore.has_crate_data(def_id.krate) {
365        debug!("No data for crate {crate_name}");
366        return Err(HrefError::NotInExternalCache);
367    }
368    let DefKind::Macro(kinds) = tcx.def_kind(def_id) else {
369        unreachable!();
370    };
371    let item_type = if kinds == MacroKinds::DERIVE {
372        ItemType::ProcDerive
373    } else if kinds == MacroKinds::ATTR {
374        ItemType::ProcAttribute
375    } else {
376        ItemType::Macro
377    };
378    let path = clean::inline::get_item_path(tcx, def_id, item_type);
379    // The minimum we can have is the crate name followed by the macro name. If shorter, then
380    // it means that `relative` was empty, which is an error.
381    let [module_path @ .., last] = path.as_slice() else {
382        debug!("macro path is empty!");
383        return Err(HrefError::NotInExternalCache);
384    };
385    if module_path.is_empty() {
386        debug!("macro path too short: missing crate prefix (got 1 element, need at least 2)");
387        return Err(HrefError::NotInExternalCache);
388    }
389
390    let url = match cache.extern_locations[&def_id.krate] {
391        ExternalLocation::Remote { ref url, is_absolute } => {
392            let depth = jump_to_def_path_depth.unwrap_or(cx.current.len());
393            let mut prefix = remote_url_prefix(url, is_absolute, depth);
394            prefix.extend(module_path.iter().copied());
395            prefix.push_fmt(format_args!("{}.{last}.html", item_type.as_str()));
396            prefix.finish()
397        }
398        ExternalLocation::Local => {
399            format!(
400                "{prefix}{path}/{item_type}.{last}.html",
401                prefix = "../".repeat(jump_to_def_path_depth.unwrap_or(0)),
402                path = fmt::from_fn(|f| module_path.iter().joined("/", f)),
403                item_type = item_type.as_str(),
404            )
405        }
406        ExternalLocation::Unknown => {
407            debug!("crate {crate_name} not in cache when linkifying macros");
408            return Err(HrefError::NotInExternalCache);
409        }
410    };
411    Ok(HrefInfo { url, kind: item_type, rust_path: path })
412}
413
414/// Takes an impl `DefId` and return the self `Ty` of the impl.
415fn impl_self_ty(tcx: TyCtxt<'_>, impl_def_id: DefId) -> Ty<'_> {
416    use rustc_middle::traits::ObligationCause;
417    use rustc_middle::ty;
418    use rustc_trait_selection::infer::TyCtxtInferExt;
419    use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt;
420
421    let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis());
422    let ty = tcx.type_of(impl_def_id);
423    infcx
424        .at(&ObligationCause::dummy(), tcx.param_env(impl_def_id))
425        .query_normalize(ty::Binder::dummy(ty.instantiate_identity().skip_norm_wip()))
426        .map(|resolved| infcx.deeply_resolve_ignoring_regions(resolved.value).skip_binder())
427        .unwrap_or(ty.skip_binder())
428}
429
430fn transitive_reexport_path(tcx: TyCtxt<'_>, def_id: DefId) -> Option<Vec<Symbol>> {
431    transitive_reexport_path_inner(tcx, def_id, &mut Vec::new())
432}
433
434/// Simplified implementation of `rustc_middle::ty::print::pretty::try_print_visible_def_path_recur`.
435fn transitive_reexport_path_inner(
436    tcx: TyCtxt<'_>,
437    def_id: DefId,
438    callers: &mut Vec<DefId>,
439) -> Option<Vec<Symbol>> {
440    use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
441
442    if let Some(cnum) = def_id.as_crate_root() {
443        return Some(vec![tcx.crate_name(cnum)]);
444    }
445
446    let visible_parent_map = tcx.visible_parent_map(());
447    let mut cur_def_key = tcx.def_key(def_id);
448
449    // For a constructor, we want the name of its parent rather than <unnamed>.
450    if let DefPathData::Ctor = cur_def_key.disambiguated_data.data {
451        let parent = DefId {
452            krate: def_id.krate,
453            index: cur_def_key
454                .parent
455                .expect("`DefPathData::Ctor` / `VariantData` missing a parent"),
456        };
457
458        cur_def_key = tcx.def_key(parent);
459    }
460
461    let visible_parent = visible_parent_map.get(&def_id).cloned()?;
462    // FIXME: Should we also check for private items?
463    if tcx.is_doc_hidden(visible_parent) {
464        return None;
465    }
466
467    let actual_parent = tcx.opt_parent(def_id);
468    let mut data = cur_def_key.disambiguated_data.data;
469    match data {
470        DefPathData::TypeNs(ref mut name) if Some(visible_parent) != actual_parent => {
471            // Item might be re-exported several times, but filter for the one
472            // that's public and whose identifier isn't `_`.
473            let reexport = tcx
474                .module_children(ModId::new_unchecked(visible_parent))
475                .iter()
476                .filter(|child| child.res.opt_def_id() == Some(def_id))
477                .find(|child| child.vis.is_public() && child.ident.name != kw::Underscore)
478                .map(|child| child.ident.name);
479
480            if let Some(new_name) = reexport {
481                *name = new_name;
482            } else {
483                // There is no name that is public and isn't `_`, so bail.
484                return None;
485            }
486        }
487        // Re-exported `extern crate`.
488        DefPathData::CrateRoot => {
489            data = DefPathData::TypeNs(tcx.crate_name(def_id.krate));
490        }
491        _ => {}
492    }
493
494    if callers.contains(&visible_parent) {
495        return None;
496    }
497    callers.push(visible_parent);
498    let mut path = transitive_reexport_path_inner(tcx, visible_parent, callers)?;
499    callers.pop();
500    path.push(DisambiguatedDefPathData { data, disambiguator: 0 }.as_sym(false));
501    Some(path)
502}
503
504fn generate_item_def_id_path(
505    mut def_id: DefId,
506    original_def_id: DefId,
507    cx: &Context<'_>,
508    jump_to_def_path_depth: Option<usize>,
509) -> Result<HrefInfo, HrefError> {
510    let tcx = cx.tcx();
511    let crate_name = tcx.crate_name(def_id.krate);
512    let mut prim = None;
513    let mut maybe_have_impl_not_in_def_crate = false;
514
515    // No need to try to infer the actual parent item if it's not an associated item from the `impl`
516    // block.
517    if def_id != original_def_id
518        && let DefKind::Impl { of_trait } = tcx.def_kind(def_id)
519    {
520        let ty = impl_self_ty(tcx, def_id);
521        // If this is a dyn trait, we want to get the actual trait from which the method comes from.
522        // Since a `dyn trait` (as of 2026) can only be composed of a trait plus auto traits, we
523        // look for the trait and ignore auto traits.
524        if let ty::Dynamic(traits, _) = ty.kind()
525            && let Some(trait_def_id) =
526                traits.iter().find_map(|trait_| match trait_.skip_binder() {
527                    ty::ExistentialPredicate::Trait(t) => Some(t.def_id),
528                    ty::ExistentialPredicate::Projection(p) => Some(p.trait_ref(tcx).def_id),
529                    ty::ExistentialPredicate::AutoTrait(_) => None,
530                })
531        {
532            def_id = trait_def_id;
533        } else if let Some(new_def_id) = ty.ty_adt_def().map(|adt| adt.did()) {
534            // If the inferred item is available in the local path map, it means we're reexporting
535            // from the current crate, so better use the local path directly.
536            if cx.cache().paths.contains_key(&new_def_id)
537                && let Ok(mut info) =
538                    href_with_jump_to_def_path_depth(new_def_id, cx, jump_to_def_path_depth, None)
539            {
540                let kind = ItemType::from_def_id(original_def_id, tcx);
541                // We need to append the anchor to the URL otherwise we're just linking to the
542                // parent item...
543                info.url = format!("{}#{kind}.{}", info.url, tcx.item_name(original_def_id));
544                return Ok(info);
545            }
546            def_id = new_def_id;
547            maybe_have_impl_not_in_def_crate = !of_trait
548                && !original_def_id.is_local()
549                && !def_id.is_local()
550                && def_id.krate != original_def_id.krate;
551        } else {
552            // This hack is because primitive types are only documented in `core` and `std`.
553            // However, with `#[rustc_allow_incoherent_impl]`, a lot of primitive methods are
554            // implemented in `core`. Some of them are documented in `core` (like `[]::sort`) while
555            // others aren't (like `[]::to_vec`). So in case we're not documenting `core`, we link
556            // to `std`.
557            if !of_trait && crate_name != sym::core {
558                if ![sym::alloc, sym::std].contains(&crate_name) {
559                    // We cannot link to this primitive's associated item as it's not part of
560                    // `core`, `alloc` or `std` so returning early.
561                    return Err(HrefError::UnnamableItem);
562                }
563                maybe_have_impl_not_in_def_crate = true;
564            }
565            prim = PrimitiveType::from_ty(ty);
566        }
567    }
568
569    let (shortty, fqp) = if let Some(prim) = prim {
570        let crate_name = if maybe_have_impl_not_in_def_crate { sym::std } else { crate_name };
571        (ItemType::Primitive, vec![crate_name, prim.as_sym()])
572    } else {
573        (
574            ItemType::from_def_id(def_id, tcx),
575            if maybe_have_impl_not_in_def_crate
576                // We have a method, not coming from a trait, implemented from a different crate
577                // where the original item is defined. So in short, the item is using
578                // `#[rustc_allow_incoherent_impl]` and we need to keep the non-final item path.
579                // Sadly if we use `item_relative_path` which uses `def_path`, it renders the final
580                // item path and not the intermediate one.
581                && let Some(fqp) = transitive_reexport_path(tcx, def_id)
582            {
583                fqp
584            } else {
585                let mut fqp = vec![crate_name];
586                fqp.append(&mut clean::inline::item_relative_path(tcx, def_id));
587                fqp
588            },
589        )
590    };
591    let module_fqp = to_module_fqp(shortty, &fqp);
592
593    let (parts, is_remote) =
594        url_parts(cx.cache(), def_id, module_fqp, &cx.current, jump_to_def_path_depth)?;
595    let mut url = make_href(jump_to_def_path_depth, shortty, parts, &fqp, is_remote);
596
597    if def_id != original_def_id {
598        let kind = ItemType::from_def_id(original_def_id, tcx);
599        url = format!("{url}#{kind}.{}", tcx.item_name(original_def_id))
600    };
601    Ok(HrefInfo { url, kind: shortty, rust_path: fqp })
602}
603
604/// Checks if the given defid refers to an item that is unnamable, such as one defined in a const block.
605fn is_unnamable(tcx: TyCtxt<'_>, did: DefId) -> bool {
606    let mut cur_did = did;
607    while let Some(parent) = tcx.opt_parent(cur_did) {
608        match tcx.def_kind(parent) {
609            // items defined in these can be linked to, as long as they are visible
610            DefKind::Mod | DefKind::ForeignMod => cur_did = parent,
611            // items in impls can be linked to,
612            // as long as we can link to the item the impl is on.
613            // since associated traits are not a thing,
614            // it should not be possible to refer to an impl item if
615            // the base type is not namable.
616            DefKind::Impl { .. } => return false,
617            // everything else does not have docs generated for it
618            _ => return true,
619        }
620    }
621    return false;
622}
623
624fn to_module_fqp(shortty: ItemType, fqp: &[Symbol]) -> &[Symbol] {
625    if shortty == ItemType::Module { fqp } else { &fqp[..fqp.len() - 1] }
626}
627
628fn remote_url_prefix(url: &str, is_absolute: bool, depth: usize) -> UrlPartsBuilder {
629    let url = url.trim_end_matches('/');
630    if is_absolute {
631        UrlPartsBuilder::singleton(url)
632    } else {
633        let extra = depth.saturating_sub(1);
634        let mut b: UrlPartsBuilder = iter::repeat_n("..", extra).collect();
635        b.push(url);
636        b
637    }
638}
639
640/// Returns `(url_parts, is_remote)` where `is_remote` indicates the URL points to an
641/// external location and should not use relative URLs.
642///
643/// This function tries to generate minimal, relative URLs. When generating an intra-doc
644/// link from one item page to another, it compares the paths of both items and generates
645/// exactly the right number of "../"'s to reach the deepest common parent, then fills in
646/// the rest of the path. This is done by comparing `module_fqp` with `relative_to`.
647///
648/// When generating a link for jump-to-def across crates, that won't work, because sources are
649/// stored under `src/<crate>/<path>.rs`. When linking jump-to-def for another crate, we just
650/// write enough "../"'s to reach the doc root, then generate the entire path for `module_fqp`.
651/// The right number of "../"'s is stored in `jump_to_def_path_depth`.
652fn url_parts(
653    cache: &Cache,
654    def_id: DefId,
655    module_fqp: &[Symbol],
656    relative_to: &[Symbol],
657    jump_to_def_path_depth: Option<usize>,
658) -> Result<(UrlPartsBuilder, bool), HrefError> {
659    let location = if def_id.krate == LOCAL_CRATE {
660        &ExternalLocation::Local
661    } else {
662        &cache.extern_locations[&def_id.krate]
663    };
664    match *location {
665        ExternalLocation::Remote { ref url, is_absolute } => {
666            let depth = jump_to_def_path_depth.unwrap_or(relative_to.len());
667            let mut builder = remote_url_prefix(url, is_absolute, depth);
668            builder.extend(module_fqp.iter().copied());
669            // Remote URLs (both absolute and relative) already encode the correct path;
670            // no additional depth prefix should be applied.
671            Ok((builder, true))
672        }
673        ExternalLocation::Local => Ok((href_relative_parts(module_fqp, relative_to), false)),
674        ExternalLocation::Unknown => Err(HrefError::DocumentationNotBuilt),
675    }
676}
677
678fn make_href(
679    jump_to_def_path_depth: Option<usize>,
680    shortty: ItemType,
681    mut url_parts: UrlPartsBuilder,
682    fqp: &[Symbol],
683    is_remote: bool,
684) -> String {
685    // Remote URLs already have the correct depth via `remote_url_prefix`;
686    // only local items need `jump_to_def_path_depth` to bridge from source pages to the doc tree.
687    if !is_remote && let Some(depth) = jump_to_def_path_depth {
688        url_parts.push_front(&iter::repeat_n("..", depth).join("/"));
689    }
690    debug!(?url_parts);
691    match shortty {
692        ItemType::Module => {
693            url_parts.push("index.html");
694        }
695        _ => {
696            let last = fqp.last().unwrap();
697            url_parts.push_fmt(format_args!("{shortty}.{last}.html"));
698        }
699    }
700    url_parts.finish()
701}
702
703fn ty_inherits_doc_hidden<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
704    match ty.kind() {
705        // If this is a dyn trait, we want to get the actual trait from which the method comes from.
706        // Since a `dyn trait` (as of 2026) can only be composed of a trait plus auto traits, we
707        // look for the trait and ignore auto traits.
708        ty::Dynamic(traits, _) => traits
709            .iter()
710            .find_map(|trait_| match trait_.skip_binder() {
711                ty::ExistentialPredicate::Trait(t) => Some(inherits_doc_hidden(tcx, t.def_id)),
712                ty::ExistentialPredicate::Projection(p) => {
713                    Some(inherits_doc_hidden(tcx, p.trait_ref(tcx).def_id))
714                }
715                ty::ExistentialPredicate::AutoTrait(_) => None,
716            })
717            .unwrap_or(false),
718        ty::Adt(adt, _) => inherits_doc_hidden(tcx, adt.did()),
719        ty::Foreign(def_id) => inherits_doc_hidden(tcx, *def_id),
720        ty::Ref(_, ty, _) => ty_inherits_doc_hidden(tcx, *ty),
721        // For now we consider that everything doesn't inherit `#[doc(hidden)]`. To be confirmed
722        // later.
723        _ => false,
724    }
725}
726
727fn inherits_doc_hidden(tcx: TyCtxt<'_>, mut def_id: DefId) -> bool {
728    loop {
729        if tcx.is_doc_hidden(def_id) {
730            return true;
731        } else if def_id.is_crate_root() {
732            return false;
733        } else if let DefKind::Impl { of_trait } = tcx.def_kind(def_id) {
734            // `impl` blocks stand a bit on their own: unless they have `#[doc(hidden)]` directly
735            // on them, they don't inherit it from the parent context.
736            // Instead we check that the `Self` item and the trait aren't hidden.
737            return ty_inherits_doc_hidden(tcx, impl_self_ty(tcx, def_id))
738                || (of_trait && inherits_doc_hidden(tcx, tcx.impl_trait_id(def_id)));
739        }
740        def_id = tcx.parent(def_id);
741    }
742}
743
744/// If `jump_to_def_path_depth` is `Some`, the link comes from a source page and needs that many
745/// `..` segments to reach the doc root; `None` generates a link relative to the current page.
746pub(crate) fn href_with_jump_to_def_path_depth(
747    original_did: DefId,
748    cx: &Context<'_>,
749    jump_to_def_path_depth: Option<usize>,
750    preferred_name: Option<&str>,
751) -> Result<HrefInfo, HrefError> {
752    let tcx = cx.tcx();
753    let def_kind = tcx.def_kind(original_did);
754    let did = match def_kind {
755        DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst | DefKind::Variant => {
756            // documented on their parent's page
757            tcx.parent(original_did)
758        }
759        // If this a constructor, we get the parent (either a struct or a variant) and then
760        // generate the link for this item.
761        DefKind::Ctor(..) => {
762            return href_with_jump_to_def_path_depth(
763                tcx.parent(original_did),
764                cx,
765                jump_to_def_path_depth,
766                preferred_name,
767            );
768        }
769        DefKind::ExternCrate => {
770            // Link to the crate itself, not the `extern crate` item.
771            if let Some(local_did) = original_did.as_local() {
772                tcx.extern_mod_stmt_cnum(local_did).unwrap_or(LOCAL_CRATE).as_def_id()
773            } else {
774                original_did
775            }
776        }
777        _ => original_did,
778    };
779    if is_unnamable(tcx, did) {
780        return Err(HrefError::UnnamableItem);
781    }
782    let cache = cx.cache();
783    let relative_to = &cx.current;
784
785    if !original_did.is_local() {
786        // If we are generating an href for the "jump to def" feature, then the only case we want
787        // to ignore is if the item is `doc(hidden)` because we can't link to it.
788        if jump_to_def_path_depth.is_some() {
789            if inherits_doc_hidden(tcx, original_did) {
790                return Err(HrefError::Private);
791            }
792        } else if !cache.effective_visibilities.is_directly_public(tcx, did)
793            && !cache.document_private
794            && !cache.primitive_locations.values().any(|&id| id == did)
795        {
796            return Err(HrefError::Private);
797        }
798    }
799
800    let (fqp, shortty, url_parts, is_remote) = match cache.paths.get(&did) {
801        Some(info) => (
802            info.get_preferred_path(preferred_name),
803            info.ty,
804            {
805                let module_fqp = to_module_fqp(info.ty, info.parts.as_slice());
806                debug!(?info.parts, ?info.ty, ?module_fqp);
807                href_relative_parts(module_fqp, relative_to)
808            },
809            false,
810        ),
811        None => {
812            // Associated items are handled differently with "jump to def": the anchor is generated
813            // directly here whereas for intra-doc links, we have some extra computation being
814            // performed there.
815            //
816            // So if `jump_to_def_path_depth` is `Some()` and it's an associated item, we enter to
817            // enter the `else` branch to have the anchor generated here directly.
818            if (jump_to_def_path_depth.is_none() || original_did == did)
819                && let Some(&(ref fqp, shortty)) = cache.external_paths.get(&did)
820            {
821                let module_fqp = to_module_fqp(shortty, fqp);
822                let (parts, is_remote) =
823                    url_parts(cache, did, module_fqp, relative_to, jump_to_def_path_depth)?;
824                (fqp.as_slice(), shortty, parts, is_remote)
825            } else if matches!(def_kind, DefKind::Macro(_)) {
826                return generate_macro_def_id_path(did, cx, jump_to_def_path_depth);
827            } else if did.is_local() {
828                return Err(HrefError::Private);
829            } else {
830                return generate_item_def_id_path(did, original_did, cx, jump_to_def_path_depth);
831            }
832        }
833    };
834    Ok(HrefInfo {
835        url: make_href(jump_to_def_path_depth, shortty, url_parts, fqp, is_remote),
836        kind: shortty,
837        rust_path: fqp.to_vec(),
838    })
839}
840
841pub(crate) fn href(did: DefId, cx: &Context<'_>) -> Result<HrefInfo, HrefError> {
842    href_with_jump_to_def_path_depth(did, cx, None, None)
843}
844
845pub(crate) fn href_with_path_check(
846    did: DefId,
847    cx: &Context<'_>,
848    text: &str,
849) -> Result<HrefInfo, HrefError> {
850    href_with_jump_to_def_path_depth(did, cx, None, Some(text))
851}
852
853/// Both paths should only be modules.
854/// This is because modules get their own directories; that is, `std::vec` and `std::vec::Vec` will
855/// both need `../iter/trait.Iterator.html` to get at the iterator trait.
856pub(crate) fn href_relative_parts(fqp: &[Symbol], relative_to_fqp: &[Symbol]) -> UrlPartsBuilder {
857    for (i, (f, r)) in fqp.iter().zip(relative_to_fqp.iter()).enumerate() {
858        // e.g. linking to std::iter from std::vec (`dissimilar_part_count` will be 1)
859        if f != r {
860            let dissimilar_part_count = relative_to_fqp.len() - i;
861            let fqp_module = &fqp[i..];
862            return iter::repeat_n("..", dissimilar_part_count)
863                .chain(fqp_module.iter().map(|s| s.as_str()))
864                .collect();
865        }
866    }
867    match relative_to_fqp.len().cmp(&fqp.len()) {
868        Ordering::Less => {
869            // e.g. linking to std::sync::atomic from std::sync
870            fqp[relative_to_fqp.len()..fqp.len()].iter().copied().collect()
871        }
872        Ordering::Greater => {
873            // e.g. linking to std::sync from std::sync::atomic
874            let dissimilar_part_count = relative_to_fqp.len() - fqp.len();
875            iter::repeat_n("..", dissimilar_part_count).collect()
876        }
877        Ordering::Equal => {
878            // linking to the same module
879            UrlPartsBuilder::new()
880        }
881    }
882}
883
884pub(crate) fn link_tooltip(
885    did: DefId,
886    fragment: &Option<UrlFragment>,
887    cx: &Context<'_>,
888    preferred_name: Option<&str>,
889) -> impl fmt::Display {
890    fmt::from_fn(move |f| {
891        let cache = cx.cache();
892        let Some((fqp, shortty)) = cache
893            .paths
894            .get(&did)
895            .map(|info| (info.get_preferred_path(preferred_name), info.ty))
896            .or_else(|| {
897                cache.external_paths.get(&did).map(|(fqp, shortty)| (fqp.as_slice(), *shortty))
898            })
899        else {
900            return Ok(());
901        };
902        let fqp = if shortty == ItemType::Primitive {
903            // primitives are documented in a crate, but not actually part of it
904            slice::from_ref(fqp.last().unwrap())
905        } else {
906            fqp
907        };
908        if let &Some(UrlFragment::Item(id)) = fragment {
909            let tcx = cx.tcx();
910            write!(f, "{} ", tcx.def_descr(id))?;
911            for component in fqp {
912                write!(f, "{component}::")?;
913            }
914            if shortty == ItemType::Enum && tcx.def_kind(id) == DefKind::Field {
915                write!(f, "{}::", tcx.item_name(tcx.parent(id)))?;
916            }
917            write!(f, "{}", tcx.item_name(id))?;
918        } else if !fqp.is_empty() {
919            write!(f, "{shortty} ")?;
920            write!(f, "{}", join_path_syms(fqp))?;
921        }
922        Ok(())
923    })
924}
925
926/// Used to render a [`clean::Path`].
927fn resolved_path(
928    w: &mut fmt::Formatter<'_>,
929    did: DefId,
930    path: &clean::Path,
931    print_all: bool,
932    use_absolute: bool,
933    cx: &Context<'_>,
934) -> fmt::Result {
935    let last = path.segments.last().unwrap();
936
937    if print_all {
938        for seg in &path.segments[..path.segments.len() - 1] {
939            write!(w, "{}::", if seg.name == kw::PathRoot { "" } else { seg.name.as_str() })?;
940        }
941    }
942    if w.alternate() {
943        write!(w, "{}{:#}", last.name, print_generic_args(&last.args, cx))?;
944    } else {
945        let path = fmt::from_fn(|f| {
946            if use_absolute {
947                if let Ok(HrefInfo { rust_path, .. }) = href(did, cx) {
948                    write!(
949                        f,
950                        "{path}::{anchor}",
951                        path = join_path_syms(&rust_path[..rust_path.len() - 1]),
952                        anchor = print_anchor(did, *rust_path.last().unwrap(), cx)
953                    )
954                } else {
955                    write!(f, "{}", last.name)
956                }
957            } else {
958                write!(f, "{}", print_anchor(did, last.name, cx))
959            }
960        });
961        write!(w, "{path}{args}", args = print_generic_args(&last.args, cx))?;
962    }
963    Ok(())
964}
965
966fn primitive_link(
967    f: &mut fmt::Formatter<'_>,
968    prim: clean::PrimitiveType,
969    name: fmt::Arguments<'_>,
970    cx: &Context<'_>,
971) -> fmt::Result {
972    primitive_link_fragment(f, prim, name, "", cx)
973}
974
975fn primitive_link_fragment(
976    f: &mut fmt::Formatter<'_>,
977    prim: clean::PrimitiveType,
978    name: fmt::Arguments<'_>,
979    fragment: &str,
980    cx: &Context<'_>,
981) -> fmt::Result {
982    let m = &cx.cache();
983    let mut needs_termination = false;
984    if !f.alternate() {
985        match m.primitive_locations.get(&prim) {
986            Some(&def_id) if def_id.is_local() => {
987                let len = cx.current.len();
988                let path = fmt::from_fn(|f| {
989                    if len == 0 {
990                        let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
991                        write!(f, "{cname_sym}/")?;
992                    } else {
993                        for _ in 0..(len - 1) {
994                            f.write_str("../")?;
995                        }
996                    }
997                    Ok(())
998                });
999                write!(
1000                    f,
1001                    "<a class=\"primitive\" href=\"{path}primitive.{}.html{fragment}\">",
1002                    prim.as_sym()
1003                )?;
1004                needs_termination = true;
1005            }
1006            Some(&def_id) => {
1007                let loc = match m.extern_locations[&def_id.krate] {
1008                    ExternalLocation::Remote { ref url, is_absolute } => {
1009                        let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
1010                        let mut builder = remote_url_prefix(url, is_absolute, cx.current.len());
1011                        builder.push(cname_sym.as_str());
1012                        Some(builder)
1013                    }
1014                    ExternalLocation::Local => {
1015                        let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
1016                        Some(if cx.current.first() == Some(&cname_sym) {
1017                            iter::repeat_n("..", cx.current.len() - 1).collect()
1018                        } else {
1019                            iter::repeat_n("..", cx.current.len())
1020                                .chain(iter::once(cname_sym.as_str()))
1021                                .collect()
1022                        })
1023                    }
1024                    ExternalLocation::Unknown => None,
1025                };
1026                if let Some(mut loc) = loc {
1027                    loc.push_fmt(format_args!("primitive.{}.html", prim.as_sym()));
1028                    write!(f, "<a class=\"primitive\" href=\"{}{fragment}\">", loc.finish())?;
1029                    needs_termination = true;
1030                }
1031            }
1032            None => {}
1033        }
1034    }
1035    Display::fmt(&name, f)?;
1036    if needs_termination {
1037        write!(f, "</a>")?;
1038    }
1039    Ok(())
1040}
1041
1042fn print_tybounds(
1043    bounds: &[clean::PolyTrait],
1044    lt: &Option<clean::Lifetime>,
1045    cx: &Context<'_>,
1046) -> impl Display {
1047    fmt::from_fn(move |f| {
1048        bounds.iter().map(|bound| print_poly_trait(bound, cx)).joined(" + ", f)?;
1049        if let Some(lt) = lt {
1050            // We don't need to check `alternate` since we can be certain that
1051            // the lifetime doesn't contain any characters which need escaping.
1052            write!(f, " + {}", print_lifetime(lt))?;
1053        }
1054        Ok(())
1055    })
1056}
1057
1058fn print_higher_ranked_params_with_space(
1059    params: &[clean::GenericParamDef],
1060    cx: &Context<'_>,
1061    keyword: &'static str,
1062) -> impl Display {
1063    fmt::from_fn(move |f| {
1064        if !params.is_empty() {
1065            f.write_str(keyword)?;
1066            Wrapped::with_angle_brackets()
1067                .wrap_fn(|f| {
1068                    params.iter().map(|lt| print_generic_param_def(lt, cx)).joined(", ", f)
1069                })
1070                .fmt(f)?;
1071            f.write_char(' ')?;
1072        }
1073        Ok(())
1074    })
1075}
1076
1077pub(crate) fn fragment(did: DefId, tcx: TyCtxt<'_>) -> impl Display {
1078    fmt::from_fn(move |f| {
1079        let def_kind = tcx.def_kind(did);
1080        match def_kind {
1081            DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst | DefKind::Variant => {
1082                let item_type = ItemType::from_def_id(did, tcx);
1083                write!(f, "#{}.{}", item_type.as_str(), tcx.item_name(did))
1084            }
1085            DefKind::Field => {
1086                let parent_def_id = tcx.parent(did);
1087                f.write_char('#')?;
1088                if tcx.def_kind(parent_def_id) == DefKind::Variant {
1089                    write!(f, "variant.{}.field", tcx.item_name(parent_def_id).as_str())?;
1090                } else {
1091                    f.write_str("structfield")?;
1092                };
1093                write!(f, ".{}", tcx.item_name(did))
1094            }
1095            _ => Ok(()),
1096        }
1097    })
1098}
1099
1100pub(crate) fn print_anchor(did: DefId, text: Symbol, cx: &Context<'_>) -> impl Display {
1101    fmt::from_fn(move |f| {
1102        if let Ok(HrefInfo { url, kind, rust_path }) = href(did, cx) {
1103            write!(
1104                f,
1105                r#"<a class="{kind}" href="{url}{anchor}" title="{kind} {path}">{text}</a>"#,
1106                anchor = fragment(did, cx.tcx()),
1107                path = join_path_syms(rust_path),
1108                text = EscapeBodyText(text.as_str()),
1109            )
1110        } else {
1111            f.write_str(text.as_str())
1112        }
1113    })
1114}
1115
1116fn fmt_type(
1117    t: &clean::Type,
1118    f: &mut fmt::Formatter<'_>,
1119    use_absolute: bool,
1120    cx: &Context<'_>,
1121) -> fmt::Result {
1122    trace!("fmt_type(t = {t:?})");
1123
1124    match t {
1125        clean::Generic(name) => f.write_str(name.as_str()),
1126        clean::SelfTy => f.write_str("Self"),
1127        clean::Type::Path { path } => {
1128            // Paths like `T::Output` and `Self::Output` should be rendered with all segments.
1129            let did = path.def_id();
1130            resolved_path(f, did, path, path.is_assoc_ty(), use_absolute, cx)
1131        }
1132        clean::DynTrait(bounds, lt) => {
1133            f.write_str("dyn ")?;
1134            print_tybounds(bounds, lt, cx).fmt(f)
1135        }
1136        clean::Infer => write!(f, "_"),
1137        clean::Primitive(clean::PrimitiveType::Never) => {
1138            primitive_link(f, PrimitiveType::Never, format_args!("!"), cx)
1139        }
1140        &clean::Primitive(prim) => primitive_link(f, prim, format_args!("{}", prim.as_sym()), cx),
1141        clean::BareFunction(decl) => {
1142            print_higher_ranked_params_with_space(&decl.generic_params, cx, "for").fmt(f)?;
1143            decl.safety.print_with_space().fmt(f)?;
1144            print_abi_with_space(decl.abi).fmt(f)?;
1145            if f.alternate() {
1146                f.write_str("fn")?;
1147            } else {
1148                primitive_link(f, PrimitiveType::Fn, format_args!("fn"), cx)?;
1149            }
1150            print_fn_decl(&decl.decl, cx).fmt(f)
1151        }
1152        clean::UnsafeBinder(binder) => {
1153            print_higher_ranked_params_with_space(&binder.generic_params, cx, "unsafe").fmt(f)?;
1154            print_type(&binder.ty, cx).fmt(f)
1155        }
1156        clean::Tuple(typs) => match &typs[..] {
1157            &[] => primitive_link(f, PrimitiveType::Unit, format_args!("()"), cx),
1158            [one] => {
1159                if let clean::Generic(name) = one {
1160                    primitive_link(f, PrimitiveType::Tuple, format_args!("({name},)"), cx)
1161                } else {
1162                    write!(f, "(")?;
1163                    print_type(one, cx).fmt(f)?;
1164                    write!(f, ",)")
1165                }
1166            }
1167            many => {
1168                let generic_names: Vec<Symbol> = many
1169                    .iter()
1170                    .filter_map(|t| match t {
1171                        clean::Generic(name) => Some(*name),
1172                        _ => None,
1173                    })
1174                    .collect();
1175                let is_generic = generic_names.len() == many.len();
1176                if is_generic {
1177                    primitive_link(
1178                        f,
1179                        PrimitiveType::Tuple,
1180                        format_args!(
1181                            "{}",
1182                            Wrapped::with_parens()
1183                                .wrap_fn(|f| generic_names.iter().joined(", ", f))
1184                        ),
1185                        cx,
1186                    )
1187                } else {
1188                    Wrapped::with_parens()
1189                        .wrap_fn(|f| many.iter().map(|item| print_type(item, cx)).joined(", ", f))
1190                        .fmt(f)
1191                }
1192            }
1193        },
1194        clean::Slice(clean::Generic(name)) => {
1195            primitive_link(f, PrimitiveType::Slice, format_args!("[{name}]"), cx)
1196        }
1197        clean::Slice(t) => Wrapped::with_square_brackets().wrap(print_type(t, cx)).fmt(f),
1198        clean::Type::Pat(t, pat) => {
1199            fmt::Display::fmt(&print_type(t, cx), f)?;
1200            write!(f, " is {pat}")
1201        }
1202        clean::Type::FieldOf(t, field) => {
1203            write!(f, "field_of!(")?;
1204            fmt::Display::fmt(&print_type(t, cx), f)?;
1205            write!(f, ", {field})")
1206        }
1207        clean::Array(clean::Generic(name), n) if !f.alternate() => primitive_link(
1208            f,
1209            PrimitiveType::Array,
1210            format_args!("[{name}; {n}]", n = Escape(n)),
1211            cx,
1212        ),
1213        clean::Array(t, n) => Wrapped::with_square_brackets()
1214            .wrap(fmt::from_fn(|f| {
1215                print_type(t, cx).fmt(f)?;
1216                f.write_str("; ")?;
1217                if f.alternate() {
1218                    f.write_str(n)
1219                } else {
1220                    primitive_link(f, PrimitiveType::Array, format_args!("{n}", n = Escape(n)), cx)
1221                }
1222            }))
1223            .fmt(f),
1224        clean::RawPointer(m, t) => {
1225            let m = m.ptr_str();
1226
1227            if matches!(**t, clean::Generic(_)) || t.is_assoc_ty() {
1228                primitive_link(
1229                    f,
1230                    clean::PrimitiveType::RawPointer,
1231                    format_args!("*{m} {ty}", ty = WithOpts::from(f).display(print_type(t, cx))),
1232                    cx,
1233                )
1234            } else {
1235                primitive_link(f, clean::PrimitiveType::RawPointer, format_args!("*{m} "), cx)?;
1236                print_type(t, cx).fmt(f)
1237            }
1238        }
1239        clean::BorrowedRef { lifetime: l, mutability, type_: ty } => {
1240            let lt = fmt::from_fn(|f| match l {
1241                Some(l) => write!(f, "{} ", print_lifetime(l)),
1242                _ => Ok(()),
1243            });
1244            let m = mutability.print_with_space();
1245            let amp = if f.alternate() { "&" } else { "&amp;" };
1246
1247            if let clean::Generic(name) = **ty {
1248                return primitive_link(
1249                    f,
1250                    PrimitiveType::Reference,
1251                    format_args!("{amp}{lt}{m}{name}"),
1252                    cx,
1253                );
1254            }
1255
1256            write!(f, "{amp}{lt}{m}")?;
1257
1258            let needs_parens = match **ty {
1259                clean::DynTrait(ref bounds, ref trait_lt)
1260                    if bounds.len() > 1 || trait_lt.is_some() =>
1261                {
1262                    true
1263                }
1264                clean::ImplTrait(ref bounds) if bounds.len() > 1 => true,
1265                _ => false,
1266            };
1267            Wrapped::with_parens()
1268                .when(needs_parens)
1269                .wrap_fn(|f| fmt_type(ty, f, use_absolute, cx))
1270                .fmt(f)
1271        }
1272        clean::ImplTrait(bounds) => {
1273            f.write_str("impl ")?;
1274            print_generic_bounds(bounds, cx).fmt(f)
1275        }
1276        clean::QPath(qpath) => print_qpath_data(qpath, cx).fmt(f),
1277    }
1278}
1279
1280pub(crate) fn print_type(type_: &clean::Type, cx: &Context<'_>) -> impl Display {
1281    fmt::from_fn(move |f| fmt_type(type_, f, false, cx))
1282}
1283
1284pub(crate) fn print_path(path: &clean::Path, cx: &Context<'_>) -> impl Display {
1285    fmt::from_fn(move |f| resolved_path(f, path.def_id(), path, false, false, cx))
1286}
1287
1288fn print_qpath_data(qpath_data: &clean::QPathData, cx: &Context<'_>) -> impl Display {
1289    let clean::QPathData { ref assoc, ref self_type, should_fully_qualify, ref trait_ } =
1290        *qpath_data;
1291
1292    fmt::from_fn(move |f| {
1293        // FIXME(inherent_associated_types): Once we support non-ADT self-types (#106719),
1294        // we need to surround them with angle brackets in some cases (e.g. `<dyn …>::P`).
1295
1296        if let Some(trait_) = trait_
1297            && should_fully_qualify
1298        {
1299            let opts = WithOpts::from(f);
1300            Wrapped::with_angle_brackets()
1301                .wrap(format_args!(
1302                    "{} as {}",
1303                    opts.display(print_type(self_type, cx)),
1304                    opts.display(print_path(trait_, cx))
1305                ))
1306                .fmt(f)?
1307        } else {
1308            print_type(self_type, cx).fmt(f)?;
1309        }
1310        f.write_str("::")?;
1311        // It's pretty unsightly to look at `<A as B>::C` in output, and
1312        // we've got hyperlinking on our side, so try to avoid longer
1313        // notation as much as possible by making `C` a hyperlink to trait
1314        // `B` to disambiguate.
1315        //
1316        // FIXME: this is still a lossy conversion and there should probably
1317        //        be a better way of representing this in general? Most of
1318        //        the ugliness comes from inlining across crates where
1319        //        everything comes in as a fully resolved QPath (hard to
1320        //        look at).
1321        if !f.alternate() {
1322            // FIXME(inherent_associated_types): We always link to the very first associated
1323            // type (in respect to source order) that bears the given name (`assoc.name`) and that is
1324            // affiliated with the computed `DefId`. This is obviously incorrect when we have
1325            // multiple impl blocks. Ideally, we would thread the `DefId` of the assoc ty itself
1326            // through here and map it to the corresponding HTML ID that was generated by
1327            // `render::Context::derive_id` when the impl blocks were rendered.
1328            // There is no such mapping unfortunately.
1329            // As a hack, we could badly imitate `derive_id` here by keeping *count* when looking
1330            // for the assoc ty `DefId` in `tcx.associated_items(self_ty_did).in_definition_order()`
1331            // considering privacy, `doc(hidden)`, etc.
1332            // I don't feel like that right now :cold_sweat:.
1333
1334            let parent_href = match trait_ {
1335                Some(trait_) => href(trait_.def_id(), cx).ok(),
1336                None => self_type.def_id(cx.cache()).and_then(|did| href(did, cx).ok()),
1337            };
1338            let tcx = cx.tcx();
1339            let assoc_type_is_hidden = !cx.cache().document_hidden
1340                && trait_.as_ref().is_some_and(|trait_| {
1341                    let trait_did = trait_.def_id();
1342                    tcx.associated_items(trait_did)
1343                        .find_by_ident_and_kind(
1344                            tcx,
1345                            Ident::with_dummy_span(assoc.name),
1346                            ty::AssocTag::Type,
1347                            trait_did,
1348                        )
1349                        .is_some_and(|assoc_item| tcx.is_doc_hidden(assoc_item.def_id))
1350                });
1351
1352            if let Some(HrefInfo { url, rust_path, .. }) = parent_href
1353                && !assoc_type_is_hidden
1354            {
1355                write!(
1356                    f,
1357                    "<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \
1358                                title=\"type {path}::{name}\">{name}</a>",
1359                    shortty = ItemType::AssocType,
1360                    name = assoc.name,
1361                    path = join_path_syms(rust_path),
1362                )
1363            } else {
1364                write!(f, "{}", assoc.name)
1365            }
1366        } else {
1367            write!(f, "{}", assoc.name)
1368        }?;
1369
1370        print_generic_args(&assoc.args, cx).fmt(f)
1371    })
1372}
1373
1374pub(crate) fn print_impl(
1375    impl_: &clean::Impl,
1376    use_absolute: bool,
1377    cx: &Context<'_>,
1378) -> impl Display {
1379    fmt::from_fn(move |f| {
1380        f.write_str("impl")?;
1381        print_generics(&impl_.generics, cx).fmt(f)?;
1382        f.write_str(" ")?;
1383
1384        if let Some(ref ty) = impl_.trait_ {
1385            if impl_.is_negative_trait_impl() {
1386                f.write_char('!')?;
1387            }
1388            if impl_.kind.is_fake_variadic()
1389                && let Some(generics) = ty.generics()
1390                && let Ok(inner_type) = generics.exactly_one()
1391            {
1392                let last = ty.last();
1393                if f.alternate() {
1394                    write!(f, "{last}")?;
1395                } else {
1396                    write!(f, "{}", print_anchor(ty.def_id(), last, cx))?;
1397                };
1398                Wrapped::with_angle_brackets()
1399                    .wrap_fn(|f| impl_.print_type(inner_type, f, use_absolute, cx))
1400                    .fmt(f)?;
1401            } else {
1402                print_path(ty, cx).fmt(f)?;
1403            }
1404            f.write_str(" for ")?;
1405        }
1406
1407        if let Some(ty) = impl_.kind.as_blanket_ty() {
1408            fmt_type(ty, f, use_absolute, cx)?;
1409        } else {
1410            impl_.print_type(&impl_.for_, f, use_absolute, cx)?;
1411        }
1412
1413        print_where_clause(&impl_.generics, cx, 0, Ending::Newline).maybe_display().fmt(f)
1414    })
1415}
1416
1417impl clean::Impl {
1418    fn print_type(
1419        &self,
1420        type_: &clean::Type,
1421        f: &mut fmt::Formatter<'_>,
1422        use_absolute: bool,
1423        cx: &Context<'_>,
1424    ) -> Result<(), fmt::Error> {
1425        if let clean::Type::Tuple(types) = type_
1426            && let [clean::Type::Generic(name)] = &types[..]
1427            && (self.kind.is_fake_variadic() || self.kind.is_auto())
1428        {
1429            // Hardcoded anchor library/core/src/primitive_docs.rs
1430            // Link should match `# Trait implementations`
1431            primitive_link_fragment(
1432                f,
1433                PrimitiveType::Tuple,
1434                format_args!("({name}₁, {name}₂, …, {name}ₙ)"),
1435                "#trait-implementations-1",
1436                cx,
1437            )?;
1438        } else if let clean::Type::Array(ty, len) = type_
1439            && let clean::Type::Generic(name) = &**ty
1440            && &len[..] == "1"
1441            && (self.kind.is_fake_variadic() || self.kind.is_auto())
1442        {
1443            primitive_link(f, PrimitiveType::Array, format_args!("[{name}; N]"), cx)?;
1444        } else if let clean::BareFunction(bare_fn) = &type_
1445            && let [clean::Parameter { type_: clean::Type::Generic(name), .. }] =
1446                &bare_fn.decl.inputs[..]
1447            && (self.kind.is_fake_variadic() || self.kind.is_auto())
1448        {
1449            // Hardcoded anchor library/core/src/primitive_docs.rs
1450            // Link should match `# Trait implementations`
1451
1452            print_higher_ranked_params_with_space(&bare_fn.generic_params, cx, "for").fmt(f)?;
1453            bare_fn.safety.print_with_space().fmt(f)?;
1454            print_abi_with_space(bare_fn.abi).fmt(f)?;
1455            let ellipsis = if bare_fn.decl.c_variadic { ", ..." } else { "" };
1456            primitive_link_fragment(
1457                f,
1458                PrimitiveType::Tuple,
1459                format_args!("fn({name}₁, {name}₂, …, {name}ₙ{ellipsis})"),
1460                "#trait-implementations-1",
1461                cx,
1462            )?;
1463            // Write output.
1464            if !bare_fn.decl.output.is_unit() {
1465                write!(f, " -> ")?;
1466                fmt_type(&bare_fn.decl.output, f, use_absolute, cx)?;
1467            }
1468        } else if let clean::Type::Path { path } = type_
1469            && let Some(generics) = path.generics()
1470            && let Ok(ty) = generics.exactly_one()
1471            && self.kind.is_fake_variadic()
1472        {
1473            print_anchor(path.def_id(), path.last(), cx).fmt(f)?;
1474            Wrapped::with_angle_brackets()
1475                .wrap_fn(|f| self.print_type(ty, f, use_absolute, cx))
1476                .fmt(f)?;
1477        } else {
1478            fmt_type(type_, f, use_absolute, cx)?;
1479        }
1480        Ok(())
1481    }
1482}
1483
1484pub(crate) fn print_params(params: &[clean::Parameter], cx: &Context<'_>) -> impl Display {
1485    fmt::from_fn(move |f| {
1486        params
1487            .iter()
1488            .map(|param| {
1489                fmt::from_fn(|f| {
1490                    if param.is_splat {
1491                        write!(f, "…: ")?;
1492                    } else if let Some(name) = param.name {
1493                        write!(f, "{name}: ")?;
1494                    }
1495                    print_type(&param.type_, cx).fmt(f)
1496                })
1497            })
1498            .joined(", ", f)
1499    })
1500}
1501
1502// Implements Write but only counts the bytes "written".
1503struct WriteCounter(usize);
1504
1505impl std::fmt::Write for WriteCounter {
1506    fn write_str(&mut self, s: &str) -> fmt::Result {
1507        self.0 += s.len();
1508        Ok(())
1509    }
1510}
1511
1512// Implements Display by emitting the given number of spaces.
1513#[derive(Clone, Copy)]
1514struct Indent(usize);
1515
1516impl Display for Indent {
1517    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1518        for _ in 0..self.0 {
1519            f.write_char(' ')?;
1520        }
1521        Ok(())
1522    }
1523}
1524
1525fn print_parameter(parameter: &clean::Parameter, cx: &Context<'_>) -> impl fmt::Display {
1526    fmt::from_fn(move |f| {
1527        if let Some(self_ty) = parameter.to_receiver() {
1528            match self_ty {
1529                clean::SelfTy => f.write_str("self"),
1530                clean::BorrowedRef { lifetime, mutability, type_: clean::SelfTy } => {
1531                    f.write_str(if f.alternate() { "&" } else { "&amp;" })?;
1532                    if let Some(lt) = lifetime {
1533                        write!(f, "{lt} ", lt = print_lifetime(lt))?;
1534                    }
1535                    write!(f, "{mutability}self", mutability = mutability.print_with_space())
1536                }
1537                _ => {
1538                    f.write_str("self: ")?;
1539                    print_type(self_ty, cx).fmt(f)
1540                }
1541            }
1542        } else {
1543            if parameter.is_const {
1544                write!(f, "const ")?;
1545            }
1546            if parameter.is_splat {
1547                write!(f, "…: ")?;
1548            } else if let Some(name) = parameter.name {
1549                write!(f, "{name}: ")?;
1550            }
1551            print_type(&parameter.type_, cx).fmt(f)
1552        }
1553    })
1554}
1555
1556fn print_fn_decl(fn_decl: &clean::FnDecl, cx: &Context<'_>) -> impl Display {
1557    fmt::from_fn(move |f| {
1558        let ellipsis = if fn_decl.c_variadic { ", ..." } else { "" };
1559        Wrapped::with_parens()
1560            .wrap_fn(|f| {
1561                print_params(&fn_decl.inputs, cx).fmt(f)?;
1562                f.write_str(ellipsis)
1563            })
1564            .fmt(f)?;
1565        fn_decl.print_output(cx).fmt(f)
1566    })
1567}
1568
1569/// * `header_len`: The length of the function header and name. In other words, the number of
1570///   characters in the function declaration up to but not including the parentheses.
1571///   This is expected to go into a `<pre>`/`code-header` block, so indentation and newlines
1572///   are preserved.
1573/// * `indent`: The number of spaces to indent each successive line with, if line-wrapping is
1574///   necessary.
1575pub(crate) fn full_print_fn_decl(
1576    fn_decl: &clean::FnDecl,
1577    header_len: usize,
1578    indent: usize,
1579    cx: &Context<'_>,
1580) -> impl Display {
1581    fmt::from_fn(move |f| {
1582        // First, generate the text form of the declaration, with no line wrapping, and count the bytes.
1583        let mut counter = WriteCounter(0);
1584        write!(&mut counter, "{:#}", fmt::from_fn(|f| { fn_decl.inner_full_print(None, f, cx) }))?;
1585        // If the text form was over 80 characters wide, we will line-wrap our output.
1586        let line_wrapping_indent = if header_len + counter.0 > 80 { Some(indent) } else { None };
1587        // Generate the final output. This happens to accept `{:#}` formatting to get textual
1588        // output but in practice it is only formatted with `{}` to get HTML output.
1589        fn_decl.inner_full_print(line_wrapping_indent, f, cx)
1590    })
1591}
1592
1593impl clean::FnDecl {
1594    fn inner_full_print(
1595        &self,
1596        // For None, the declaration will not be line-wrapped. For Some(n),
1597        // the declaration will be line-wrapped, with an indent of n spaces.
1598        line_wrapping_indent: Option<usize>,
1599        f: &mut fmt::Formatter<'_>,
1600        cx: &Context<'_>,
1601    ) -> fmt::Result {
1602        Wrapped::with_parens()
1603            .wrap_fn(|f| {
1604                if !self.inputs.is_empty() {
1605                    let line_wrapping_indent = line_wrapping_indent.map(|n| Indent(n + 4));
1606
1607                    if let Some(indent) = line_wrapping_indent {
1608                        write!(f, "\n{indent}")?;
1609                    }
1610
1611                    let sep = fmt::from_fn(|f| {
1612                        if let Some(indent) = line_wrapping_indent {
1613                            write!(f, ",\n{indent}")
1614                        } else {
1615                            f.write_str(", ")
1616                        }
1617                    });
1618
1619                    self.inputs.iter().map(|param| print_parameter(param, cx)).joined(sep, f)?;
1620
1621                    if line_wrapping_indent.is_some() {
1622                        writeln!(f, ",")?
1623                    }
1624
1625                    if self.c_variadic {
1626                        match line_wrapping_indent {
1627                            None => write!(f, ", ...")?,
1628                            Some(indent) => writeln!(f, "{indent}...")?,
1629                        };
1630                    }
1631                }
1632
1633                if let Some(n) = line_wrapping_indent {
1634                    write!(f, "{}", Indent(n))?
1635                }
1636
1637                Ok(())
1638            })
1639            .fmt(f)?;
1640
1641        self.print_output(cx).fmt(f)
1642    }
1643
1644    fn print_output(&self, cx: &Context<'_>) -> impl Display {
1645        fmt::from_fn(move |f| {
1646            if self.output.is_unit() {
1647                return Ok(());
1648            }
1649
1650            f.write_str(if f.alternate() { " -> " } else { " -&gt; " })?;
1651            print_type(&self.output, cx).fmt(f)
1652        })
1653    }
1654}
1655
1656pub(crate) fn visibility_print_with_space(item: &clean::Item, cx: &Context<'_>) -> impl Display {
1657    fmt::from_fn(move |f| {
1658        let Some(vis) = item.visibility(cx.tcx()) else {
1659            return Ok(());
1660        };
1661
1662        match vis {
1663            ty::Visibility::Public => f.write_str("pub ")?,
1664            ty::Visibility::Restricted(vis_mod_id) => {
1665                // FIXME(camelid): This may not work correctly if `item_did` is a module.
1666                //                 However, rustdoc currently never displays a module's
1667                //                 visibility, so it shouldn't matter.
1668                let parent_module =
1669                    find_nearest_parent_module(cx.tcx(), item.item_id.expect_def_id());
1670
1671                if vis_mod_id.is_crate_root() {
1672                    f.write_str("pub(crate) ")?;
1673                } else if parent_module == Some(vis_mod_id) {
1674                    // `pub(in foo)` where `foo` is the parent module
1675                    // is the same as no visibility modifier; do nothing
1676                } else if parent_module
1677                    .and_then(|parent| find_nearest_parent_module(cx.tcx(), parent.to_def_id()))
1678                    == Some(vis_mod_id)
1679                {
1680                    f.write_str("pub(super) ")?;
1681                } else {
1682                    let path = cx.tcx().def_path(vis_mod_id.to_def_id());
1683                    debug!("path={path:?}");
1684                    // modified from `resolved_path()` to work with `DefPathData`
1685                    let last_name = path.data.last().unwrap().data.get_opt_name().unwrap();
1686                    let anchor = print_anchor(vis_mod_id.to_def_id(), last_name, cx);
1687
1688                    f.write_str("pub(in ")?;
1689                    for seg in &path.data[..path.data.len() - 1] {
1690                        write!(f, "{}::", seg.data.get_opt_name().unwrap())?;
1691                    }
1692                    write!(f, "{anchor}) ")?;
1693                }
1694            }
1695        }
1696        Ok(())
1697    })
1698}
1699
1700pub(crate) trait PrintWithSpace {
1701    fn print_with_space(&self) -> &str;
1702}
1703
1704impl PrintWithSpace for hir::Safety {
1705    fn print_with_space(&self) -> &str {
1706        self.prefix_str()
1707    }
1708}
1709
1710impl PrintWithSpace for hir::HeaderSafety {
1711    fn print_with_space(&self) -> &str {
1712        match self {
1713            hir::HeaderSafety::SafeTargetFeatures => "",
1714            hir::HeaderSafety::Normal(safety) => safety.print_with_space(),
1715        }
1716    }
1717}
1718
1719impl PrintWithSpace for hir::IsAsync {
1720    fn print_with_space(&self) -> &str {
1721        match self {
1722            hir::IsAsync::Async(_) => "async ",
1723            hir::IsAsync::NotAsync => "",
1724        }
1725    }
1726}
1727
1728impl PrintWithSpace for hir::Mutability {
1729    fn print_with_space(&self) -> &str {
1730        match self {
1731            hir::Mutability::Not => "",
1732            hir::Mutability::Mut => "mut ",
1733        }
1734    }
1735}
1736
1737pub(crate) fn print_constness_with_space(
1738    c: &hir::Constness,
1739    overall_stab: Option<StableSince>,
1740    const_stab: Option<ConstStability>,
1741) -> &'static str {
1742    match *c {
1743        hir::Constness::Const { always } => match (overall_stab, const_stab) {
1744            // const stable...
1745            (_, Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }))
1746            // ...or when feature(staged_api) is not set...
1747            | (_, None)
1748            // ...or when const unstable, but overall unstable too
1749            | (None, Some(ConstStability { level: StabilityLevel::Unstable { .. }, .. })) => {
1750                if always {
1751                    // FIXME(comptime) show something when stable, currently relying on the attribute
1752                    // being rendered as part of the regular attribute list.
1753                    ""
1754                } else {
1755                    "const "
1756                }
1757            }
1758            // const unstable (and overall stable)
1759            (Some(_), Some(ConstStability { level: StabilityLevel::Unstable { .. }, .. })) => "",
1760        },
1761        // not const
1762        hir::Constness::NotConst => "",
1763    }
1764}
1765
1766pub(crate) fn print_import(import: &clean::Import, cx: &Context<'_>) -> impl Display {
1767    fmt::from_fn(move |f| match import.kind {
1768        clean::ImportKind::Simple(name) => {
1769            if name == import.source.path.last() {
1770                write!(f, "use {};", print_import_source(&import.source, cx))
1771            } else {
1772                write!(
1773                    f,
1774                    "use {source} as {name};",
1775                    source = print_import_source(&import.source, cx)
1776                )
1777            }
1778        }
1779        clean::ImportKind::Glob => {
1780            if import.source.path.segments.is_empty() {
1781                write!(f, "use *;")
1782            } else {
1783                write!(f, "use {}::*;", print_import_source(&import.source, cx))
1784            }
1785        }
1786    })
1787}
1788
1789fn print_import_source(import_source: &clean::ImportSource, cx: &Context<'_>) -> impl Display {
1790    fmt::from_fn(move |f| match import_source.did {
1791        Some(did) => resolved_path(f, did, &import_source.path, true, false, cx),
1792        _ => {
1793            for seg in &import_source.path.segments[..import_source.path.segments.len() - 1] {
1794                write!(f, "{}::", seg.name)?;
1795            }
1796            let name = import_source.path.last();
1797            if let hir::def::Res::PrimTy(p) = import_source.path.res {
1798                primitive_link(f, PrimitiveType::from(p), format_args!("{name}"), cx)?;
1799            } else {
1800                f.write_str(name.as_str())?;
1801            }
1802            Ok(())
1803        }
1804    })
1805}
1806
1807fn print_assoc_item_constraint(
1808    assoc_item_constraint: &clean::AssocItemConstraint,
1809    cx: &Context<'_>,
1810) -> impl Display {
1811    fmt::from_fn(move |f| {
1812        f.write_str(assoc_item_constraint.assoc.name.as_str())?;
1813        print_generic_args(&assoc_item_constraint.assoc.args, cx).fmt(f)?;
1814        match assoc_item_constraint.kind {
1815            clean::AssocItemConstraintKind::Equality { ref term } => {
1816                f.write_str(" = ")?;
1817                print_term(term, cx).fmt(f)?;
1818            }
1819            clean::AssocItemConstraintKind::Bound { ref bounds } => {
1820                if !bounds.is_empty() {
1821                    f.write_str(": ")?;
1822                    print_generic_bounds(bounds, cx).fmt(f)?;
1823                }
1824            }
1825        }
1826        Ok(())
1827    })
1828}
1829
1830pub(crate) fn print_abi_with_space(abi: ExternAbi) -> impl Display {
1831    fmt::from_fn(move |f| {
1832        let quot = if f.alternate() { "\"" } else { "&quot;" };
1833        match abi {
1834            ExternAbi::Rust => Ok(()),
1835            abi => write!(f, "extern {0}{1}{0} ", quot, abi.name()),
1836        }
1837    })
1838}
1839
1840fn print_generic_arg(generic_arg: &clean::GenericArg, cx: &Context<'_>) -> impl Display {
1841    fmt::from_fn(move |f| match generic_arg {
1842        clean::GenericArg::Lifetime(lt) => f.write_str(print_lifetime(lt)),
1843        clean::GenericArg::Type(ty) => print_type(ty, cx).fmt(f),
1844        clean::GenericArg::Const(ct) => print_constant_kind(ct, cx.tcx()).fmt(f),
1845        clean::GenericArg::Infer => f.write_char('_'),
1846    })
1847}
1848
1849fn print_term(term: &clean::Term, cx: &Context<'_>) -> impl Display {
1850    fmt::from_fn(move |f| match term {
1851        clean::Term::Type(ty) => print_type(ty, cx).fmt(f),
1852        clean::Term::Constant(ct) => print_constant_kind(ct, cx.tcx()).fmt(f),
1853    })
1854}