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 text, but that should
8//! not be used external to this module.
9
10use std::cmp::Ordering;
11use std::fmt::{self, Display, Write};
12use std::iter::{self, once};
13use std::slice;
14
15use itertools::{Either, Itertools};
16use rustc_abi::ExternAbi;
17use rustc_ast::join_path_syms;
18use rustc_data_structures::fx::FxHashSet;
19use rustc_hir as hir;
20use rustc_hir::def::DefKind;
21use rustc_hir::def_id::{DefId, LOCAL_CRATE};
22use rustc_hir::{ConstStability, StabilityLevel, StableSince};
23use rustc_metadata::creader::{CStore, LoadedMacro};
24use rustc_middle::ty::{self, TyCtxt, TypingMode};
25use rustc_span::symbol::kw;
26use rustc_span::{Symbol, sym};
27use tracing::{debug, trace};
28
29use super::url_parts_builder::UrlPartsBuilder;
30use crate::clean::types::ExternalLocation;
31use crate::clean::utils::find_nearest_parent_module;
32use crate::clean::{self, ExternalCrate, PrimitiveType};
33use crate::display::{Joined as _, MaybeDisplay as _, WithOpts, Wrapped};
34use crate::formats::cache::Cache;
35use crate::formats::item_type::ItemType;
36use crate::html::escape::{Escape, EscapeBodyText};
37use crate::html::render::Context;
38use crate::passes::collect_intra_doc_links::UrlFragment;
39
40pub(crate) fn print_generic_bounds(
41    bounds: &[clean::GenericBound],
42    cx: &Context<'_>,
43) -> impl Display {
44    fmt::from_fn(move |f| {
45        let mut bounds_dup = FxHashSet::default();
46
47        bounds
48            .iter()
49            .filter(move |b| bounds_dup.insert(*b))
50            .map(|bound| bound.print(cx))
51            .joined(" + ", f)
52    })
53}
54
55impl clean::GenericParamDef {
56    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
57        fmt::from_fn(move |f| match &self.kind {
58            clean::GenericParamDefKind::Lifetime { outlives } => {
59                write!(f, "{}", self.name)?;
60
61                if !outlives.is_empty() {
62                    f.write_str(": ")?;
63                    outlives.iter().map(|lt| lt.print()).joined(" + ", f)?;
64                }
65
66                Ok(())
67            }
68            clean::GenericParamDefKind::Type { bounds, default, .. } => {
69                f.write_str(self.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                    ty.print(cx).fmt(f)?;
79                }
80
81                Ok(())
82            }
83            clean::GenericParamDefKind::Const { ty, default, .. } => {
84                write!(f, "const {}: ", self.name)?;
85                ty.print(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}
101
102impl clean::Generics {
103    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
104        let mut real_params = self.params.iter().filter(|p| !p.is_synthetic_param()).peekable();
105        if real_params.peek().is_none() {
106            None
107        } else {
108            Some(
109                Wrapped::with_angle_brackets()
110                    .wrap_fn(move |f| real_params.clone().map(|g| g.print(cx)).joined(", ", f)),
111            )
112        }
113        .maybe_display()
114    }
115}
116
117#[derive(Clone, Copy, PartialEq, Eq)]
118pub(crate) enum Ending {
119    Newline,
120    NoNewline,
121}
122
123fn print_where_predicate(predicate: &clean::WherePredicate, cx: &Context<'_>) -> impl Display {
124    fmt::from_fn(move |f| {
125        match predicate {
126            clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => {
127                print_higher_ranked_params_with_space(bound_params, cx, "for").fmt(f)?;
128                ty.print(cx).fmt(f)?;
129                f.write_str(":")?;
130                if !bounds.is_empty() {
131                    f.write_str(" ")?;
132                    print_generic_bounds(bounds, cx).fmt(f)?;
133                }
134                Ok(())
135            }
136            clean::WherePredicate::RegionPredicate { lifetime, bounds } => {
137                // We don't need to check `alternate` since we can be certain that neither
138                // the lifetime nor the bounds contain any characters which need escaping.
139                write!(f, "{}:", lifetime.print())?;
140                if !bounds.is_empty() {
141                    write!(f, " {}", print_generic_bounds(bounds, cx))?;
142                }
143                Ok(())
144            }
145            clean::WherePredicate::EqPredicate { lhs, rhs } => {
146                let opts = WithOpts::from(f);
147                write!(f, "{} == {}", opts.display(lhs.print(cx)), opts.display(rhs.print(cx)))
148            }
149        }
150    })
151}
152
153/// * The Generics from which to emit a where-clause.
154/// * The number of spaces to indent each line with.
155/// * Whether the where-clause needs to add a comma and newline after the last bound.
156pub(crate) fn print_where_clause(
157    gens: &clean::Generics,
158    cx: &Context<'_>,
159    indent: usize,
160    ending: Ending,
161) -> Option<impl Display> {
162    if gens.where_predicates.is_empty() {
163        return None;
164    }
165
166    Some(fmt::from_fn(move |f| {
167        let where_preds = fmt::from_fn(|f| {
168            gens.where_predicates
169                .iter()
170                .map(|predicate| {
171                    fmt::from_fn(|f| {
172                        if f.alternate() {
173                            f.write_str(" ")?;
174                        } else {
175                            f.write_str("\n")?;
176                        }
177                        print_where_predicate(predicate, cx).fmt(f)
178                    })
179                })
180                .joined(",", f)
181        });
182
183        let clause = if f.alternate() {
184            if ending == Ending::Newline {
185                format!(" where{where_preds},")
186            } else {
187                format!(" where{where_preds}")
188            }
189        } else {
190            let mut br_with_padding = String::with_capacity(6 * indent + 28);
191            br_with_padding.push('\n');
192
193            let where_indent = 3;
194            let padding_amount = if ending == Ending::Newline {
195                indent + 4
196            } else if indent == 0 {
197                4
198            } else {
199                indent + where_indent + "where ".len()
200            };
201
202            for _ in 0..padding_amount {
203                br_with_padding.push(' ');
204            }
205            let where_preds = where_preds.to_string().replace('\n', &br_with_padding);
206
207            if ending == Ending::Newline {
208                let mut clause = " ".repeat(indent.saturating_sub(1));
209                write!(clause, "<div class=\"where\">where{where_preds},</div>")?;
210                clause
211            } else {
212                // insert a newline after a single space but before multiple spaces at the start
213                if indent == 0 {
214                    format!("\n<span class=\"where\">where{where_preds}</span>")
215                } else {
216                    // put the first one on the same line as the 'where' keyword
217                    let where_preds = where_preds.replacen(&br_with_padding, " ", 1);
218
219                    let mut clause = br_with_padding;
220                    // +1 is for `\n`.
221                    clause.truncate(indent + 1 + where_indent);
222
223                    write!(clause, "<span class=\"where\">where{where_preds}</span>")?;
224                    clause
225                }
226            }
227        };
228        write!(f, "{clause}")
229    }))
230}
231
232impl clean::Lifetime {
233    pub(crate) fn print(&self) -> impl Display {
234        self.0.as_str()
235    }
236}
237
238impl clean::ConstantKind {
239    pub(crate) fn print(&self, tcx: TyCtxt<'_>) -> impl Display {
240        let expr = self.expr(tcx);
241        fmt::from_fn(move |f| {
242            if f.alternate() { f.write_str(&expr) } else { write!(f, "{}", Escape(&expr)) }
243        })
244    }
245}
246
247impl clean::PolyTrait {
248    fn print(&self, cx: &Context<'_>) -> impl Display {
249        fmt::from_fn(move |f| {
250            print_higher_ranked_params_with_space(&self.generic_params, cx, "for").fmt(f)?;
251            self.trait_.print(cx).fmt(f)
252        })
253    }
254}
255
256impl clean::GenericBound {
257    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
258        fmt::from_fn(move |f| match self {
259            clean::GenericBound::Outlives(lt) => write!(f, "{}", lt.print()),
260            clean::GenericBound::TraitBound(ty, modifiers) => {
261                // `const` and `[const]` trait bounds are experimental; don't render them.
262                let hir::TraitBoundModifiers { polarity, constness: _ } = modifiers;
263                f.write_str(match polarity {
264                    hir::BoundPolarity::Positive => "",
265                    hir::BoundPolarity::Maybe(_) => "?",
266                    hir::BoundPolarity::Negative(_) => "!",
267                })?;
268                ty.print(cx).fmt(f)
269            }
270            clean::GenericBound::Use(args) => {
271                f.write_str("use")?;
272                Wrapped::with_angle_brackets()
273                    .wrap_fn(|f| args.iter().map(|arg| arg.name()).joined(", ", f))
274                    .fmt(f)
275            }
276        })
277    }
278}
279
280impl clean::GenericArgs {
281    fn print(&self, cx: &Context<'_>) -> impl Display {
282        fmt::from_fn(move |f| {
283            match self {
284                clean::GenericArgs::AngleBracketed { args, constraints } => {
285                    if !args.is_empty() || !constraints.is_empty() {
286                        Wrapped::with_angle_brackets()
287                            .wrap_fn(|f| {
288                                [Either::Left(args), Either::Right(constraints)]
289                                    .into_iter()
290                                    .flat_map(Either::factor_into_iter)
291                                    .map(|either| {
292                                        either.map_either(
293                                            |arg| arg.print(cx),
294                                            |constraint| constraint.print(cx),
295                                        )
296                                    })
297                                    .joined(", ", f)
298                            })
299                            .fmt(f)?;
300                    }
301                }
302                clean::GenericArgs::Parenthesized { inputs, output } => {
303                    Wrapped::with_parens()
304                        .wrap_fn(|f| inputs.iter().map(|ty| ty.print(cx)).joined(", ", f))
305                        .fmt(f)?;
306                    if let Some(ref ty) = *output {
307                        f.write_str(if f.alternate() { " -> " } else { " -&gt; " })?;
308                        ty.print(cx).fmt(f)?;
309                    }
310                }
311                clean::GenericArgs::ReturnTypeNotation => {
312                    f.write_str("(..)")?;
313                }
314            }
315            Ok(())
316        })
317    }
318}
319
320// Possible errors when computing href link source for a `DefId`
321#[derive(PartialEq, Eq)]
322pub(crate) enum HrefError {
323    /// This item is known to rustdoc, but from a crate that does not have documentation generated.
324    ///
325    /// This can only happen for non-local items.
326    ///
327    /// # Example
328    ///
329    /// Crate `a` defines a public trait and crate `b` – the target crate that depends on `a` –
330    /// implements it for a local type.
331    /// We document `b` but **not** `a` (we only _build_ the latter – with `rustc`):
332    ///
333    /// ```sh
334    /// rustc a.rs --crate-type=lib
335    /// rustdoc b.rs --crate-type=lib --extern=a=liba.rlib
336    /// ```
337    ///
338    /// Now, the associated items in the trait impl want to link to the corresponding item in the
339    /// trait declaration (see `html::render::assoc_href_attr`) but it's not available since their
340    /// *documentation (was) not built*.
341    DocumentationNotBuilt,
342    /// This can only happen for non-local items when `--document-private-items` is not passed.
343    Private,
344    // Not in external cache, href link should be in same page
345    NotInExternalCache,
346    /// Refers to an unnamable item, such as one defined within a function or const block.
347    UnnamableItem,
348}
349
350/// This function is to get the external macro path because they are not in the cache used in
351/// `href_with_root_path`.
352fn generate_macro_def_id_path(
353    def_id: DefId,
354    cx: &Context<'_>,
355    root_path: Option<&str>,
356) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
357    let tcx = cx.tcx();
358    let crate_name = tcx.crate_name(def_id.krate);
359    let cache = cx.cache();
360
361    let fqp = clean::inline::item_relative_path(tcx, def_id);
362    let mut relative = fqp.iter().copied();
363    let cstore = CStore::from_tcx(tcx);
364    // We need this to prevent a `panic` when this function is used from intra doc links...
365    if !cstore.has_crate_data(def_id.krate) {
366        debug!("No data for crate {crate_name}");
367        return Err(HrefError::NotInExternalCache);
368    }
369    // Check to see if it is a macro 2.0 or built-in macro.
370    // More information in <https://rust-lang.github.io/rfcs/1584-macros.html>.
371    let is_macro_2 = match cstore.load_macro_untracked(def_id, tcx) {
372        // If `def.macro_rules` is `true`, then it's not a macro 2.0.
373        LoadedMacro::MacroDef { def, .. } => !def.macro_rules,
374        _ => false,
375    };
376
377    let mut path = if is_macro_2 {
378        once(crate_name).chain(relative).collect()
379    } else {
380        vec![crate_name, relative.next_back().unwrap()]
381    };
382    if path.len() < 2 {
383        // The minimum we can have is the crate name followed by the macro name. If shorter, then
384        // it means that `relative` was empty, which is an error.
385        debug!("macro path cannot be empty!");
386        return Err(HrefError::NotInExternalCache);
387    }
388
389    if let Some(last) = path.last_mut() {
390        *last = Symbol::intern(&format!("macro.{last}.html"));
391    }
392
393    let url = match cache.extern_locations[&def_id.krate] {
394        ExternalLocation::Remote(ref s) => {
395            // `ExternalLocation::Remote` always end with a `/`.
396            format!("{s}{path}", path = fmt::from_fn(|f| path.iter().joined("/", f)))
397        }
398        ExternalLocation::Local => {
399            // `root_path` always end with a `/`.
400            format!(
401                "{root_path}{path}",
402                root_path = root_path.unwrap_or(""),
403                path = fmt::from_fn(|f| path.iter().joined("/", f))
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((url, ItemType::Macro, fqp))
412}
413
414fn generate_item_def_id_path(
415    mut def_id: DefId,
416    original_def_id: DefId,
417    cx: &Context<'_>,
418    root_path: Option<&str>,
419    original_def_kind: DefKind,
420) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
421    use rustc_middle::traits::ObligationCause;
422    use rustc_trait_selection::infer::TyCtxtInferExt;
423    use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt;
424
425    let tcx = cx.tcx();
426    let crate_name = tcx.crate_name(def_id.krate);
427
428    // No need to try to infer the actual parent item if it's not an associated item from the `impl`
429    // block.
430    if def_id != original_def_id && matches!(tcx.def_kind(def_id), DefKind::Impl { .. }) {
431        let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis());
432        def_id = infcx
433            .at(&ObligationCause::dummy(), tcx.param_env(def_id))
434            .query_normalize(ty::Binder::dummy(tcx.type_of(def_id).instantiate_identity()))
435            .map(|resolved| infcx.resolve_vars_if_possible(resolved.value))
436            .ok()
437            .and_then(|normalized| normalized.skip_binder().ty_adt_def())
438            .map(|adt| adt.did())
439            .unwrap_or(def_id);
440    }
441
442    let relative = clean::inline::item_relative_path(tcx, def_id);
443    let fqp: Vec<Symbol> = once(crate_name).chain(relative).collect();
444
445    let def_kind = tcx.def_kind(def_id);
446    let shortty = def_kind.into();
447    let module_fqp = to_module_fqp(shortty, &fqp);
448    let mut is_remote = false;
449
450    let url_parts = url_parts(cx.cache(), def_id, module_fqp, &cx.current, &mut is_remote)?;
451    let mut url_parts = make_href(root_path, shortty, url_parts, &fqp, is_remote);
452    if def_id != original_def_id {
453        let kind = ItemType::from_def_kind(original_def_kind, Some(def_kind));
454        url_parts = format!("{url_parts}#{kind}.{}", tcx.item_name(original_def_id))
455    };
456    Ok((url_parts, shortty, fqp))
457}
458
459/// Checks if the given defid refers to an item that is unnamable, such as one defined in a const block.
460fn is_unnamable(tcx: TyCtxt<'_>, did: DefId) -> bool {
461    let mut cur_did = did;
462    while let Some(parent) = tcx.opt_parent(cur_did) {
463        match tcx.def_kind(parent) {
464            // items defined in these can be linked to, as long as they are visible
465            DefKind::Mod | DefKind::ForeignMod => cur_did = parent,
466            // items in impls can be linked to,
467            // as long as we can link to the item the impl is on.
468            // since associated traits are not a thing,
469            // it should not be possible to refer to an impl item if
470            // the base type is not namable.
471            DefKind::Impl { .. } => return false,
472            // everything else does not have docs generated for it
473            _ => return true,
474        }
475    }
476    return false;
477}
478
479fn to_module_fqp(shortty: ItemType, fqp: &[Symbol]) -> &[Symbol] {
480    if shortty == ItemType::Module { fqp } else { &fqp[..fqp.len() - 1] }
481}
482
483fn url_parts(
484    cache: &Cache,
485    def_id: DefId,
486    module_fqp: &[Symbol],
487    relative_to: &[Symbol],
488    is_remote: &mut bool,
489) -> Result<UrlPartsBuilder, HrefError> {
490    match cache.extern_locations[&def_id.krate] {
491        ExternalLocation::Remote(ref s) => {
492            *is_remote = true;
493            let s = s.trim_end_matches('/');
494            let mut builder = UrlPartsBuilder::singleton(s);
495            builder.extend(module_fqp.iter().copied());
496            Ok(builder)
497        }
498        ExternalLocation::Local => Ok(href_relative_parts(module_fqp, relative_to)),
499        ExternalLocation::Unknown => Err(HrefError::DocumentationNotBuilt),
500    }
501}
502
503fn make_href(
504    root_path: Option<&str>,
505    shortty: ItemType,
506    mut url_parts: UrlPartsBuilder,
507    fqp: &[Symbol],
508    is_remote: bool,
509) -> String {
510    if !is_remote && let Some(root_path) = root_path {
511        let root = root_path.trim_end_matches('/');
512        url_parts.push_front(root);
513    }
514    debug!(?url_parts);
515    match shortty {
516        ItemType::Module => {
517            url_parts.push("index.html");
518        }
519        _ => {
520            let last = fqp.last().unwrap();
521            url_parts.push_fmt(format_args!("{shortty}.{last}.html"));
522        }
523    }
524    url_parts.finish()
525}
526
527pub(crate) fn href_with_root_path(
528    original_did: DefId,
529    cx: &Context<'_>,
530    root_path: Option<&str>,
531) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
532    let tcx = cx.tcx();
533    let def_kind = tcx.def_kind(original_did);
534    let did = match def_kind {
535        DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst | DefKind::Variant => {
536            // documented on their parent's page
537            tcx.parent(original_did)
538        }
539        // If this a constructor, we get the parent (either a struct or a variant) and then
540        // generate the link for this item.
541        DefKind::Ctor(..) => return href_with_root_path(tcx.parent(original_did), cx, root_path),
542        DefKind::ExternCrate => {
543            // Link to the crate itself, not the `extern crate` item.
544            if let Some(local_did) = original_did.as_local() {
545                tcx.extern_mod_stmt_cnum(local_did).unwrap_or(LOCAL_CRATE).as_def_id()
546            } else {
547                original_did
548            }
549        }
550        _ => original_did,
551    };
552    if is_unnamable(cx.tcx(), did) {
553        return Err(HrefError::UnnamableItem);
554    }
555    let cache = cx.cache();
556    let relative_to = &cx.current;
557
558    if !original_did.is_local() {
559        // If we are generating an href for the "jump to def" feature, then the only case we want
560        // to ignore is if the item is `doc(hidden)` because we can't link to it.
561        if root_path.is_some() {
562            if tcx.is_doc_hidden(original_did) {
563                return Err(HrefError::Private);
564            }
565        } else if !cache.effective_visibilities.is_directly_public(tcx, did)
566            && !cache.document_private
567            && !cache.primitive_locations.values().any(|&id| id == did)
568        {
569            return Err(HrefError::Private);
570        }
571    }
572
573    let mut is_remote = false;
574    let (fqp, shortty, url_parts) = match cache.paths.get(&did) {
575        Some(&(ref fqp, shortty)) => (fqp, shortty, {
576            let module_fqp = to_module_fqp(shortty, fqp.as_slice());
577            debug!(?fqp, ?shortty, ?module_fqp);
578            href_relative_parts(module_fqp, relative_to)
579        }),
580        None => {
581            // Associated items are handled differently with "jump to def". The anchor is generated
582            // directly here whereas for intra-doc links, we have some extra computation being
583            // performed there.
584            let def_id_to_get = if root_path.is_some() { original_did } else { did };
585            if let Some(&(ref fqp, shortty)) = cache.external_paths.get(&def_id_to_get) {
586                let module_fqp = to_module_fqp(shortty, fqp);
587                (fqp, shortty, url_parts(cache, did, module_fqp, relative_to, &mut is_remote)?)
588            } else if matches!(def_kind, DefKind::Macro(_)) {
589                return generate_macro_def_id_path(did, cx, root_path);
590            } else if did.is_local() {
591                return Err(HrefError::Private);
592            } else {
593                return generate_item_def_id_path(did, original_did, cx, root_path, def_kind);
594            }
595        }
596    };
597    let url_parts = make_href(root_path, shortty, url_parts, fqp, is_remote);
598    Ok((url_parts, shortty, fqp.clone()))
599}
600
601pub(crate) fn href(
602    did: DefId,
603    cx: &Context<'_>,
604) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
605    href_with_root_path(did, cx, None)
606}
607
608/// Both paths should only be modules.
609/// This is because modules get their own directories; that is, `std::vec` and `std::vec::Vec` will
610/// both need `../iter/trait.Iterator.html` to get at the iterator trait.
611pub(crate) fn href_relative_parts(fqp: &[Symbol], relative_to_fqp: &[Symbol]) -> UrlPartsBuilder {
612    for (i, (f, r)) in fqp.iter().zip(relative_to_fqp.iter()).enumerate() {
613        // e.g. linking to std::iter from std::vec (`dissimilar_part_count` will be 1)
614        if f != r {
615            let dissimilar_part_count = relative_to_fqp.len() - i;
616            let fqp_module = &fqp[i..];
617            return iter::repeat_n(sym::dotdot, dissimilar_part_count)
618                .chain(fqp_module.iter().copied())
619                .collect();
620        }
621    }
622    match relative_to_fqp.len().cmp(&fqp.len()) {
623        Ordering::Less => {
624            // e.g. linking to std::sync::atomic from std::sync
625            fqp[relative_to_fqp.len()..fqp.len()].iter().copied().collect()
626        }
627        Ordering::Greater => {
628            // e.g. linking to std::sync from std::sync::atomic
629            let dissimilar_part_count = relative_to_fqp.len() - fqp.len();
630            iter::repeat_n(sym::dotdot, dissimilar_part_count).collect()
631        }
632        Ordering::Equal => {
633            // linking to the same module
634            UrlPartsBuilder::new()
635        }
636    }
637}
638
639pub(crate) fn link_tooltip(
640    did: DefId,
641    fragment: &Option<UrlFragment>,
642    cx: &Context<'_>,
643) -> impl fmt::Display {
644    fmt::from_fn(move |f| {
645        let cache = cx.cache();
646        let Some((fqp, shortty)) = cache.paths.get(&did).or_else(|| cache.external_paths.get(&did))
647        else {
648            return Ok(());
649        };
650        let fqp = if *shortty == ItemType::Primitive {
651            // primitives are documented in a crate, but not actually part of it
652            slice::from_ref(fqp.last().unwrap())
653        } else {
654            fqp
655        };
656        if let &Some(UrlFragment::Item(id)) = fragment {
657            write!(f, "{} ", cx.tcx().def_descr(id))?;
658            for component in fqp {
659                write!(f, "{component}::")?;
660            }
661            write!(f, "{}", cx.tcx().item_name(id))?;
662        } else if !fqp.is_empty() {
663            write!(f, "{shortty} ")?;
664            write!(f, "{}", join_path_syms(fqp))?;
665        }
666        Ok(())
667    })
668}
669
670/// Used to render a [`clean::Path`].
671fn resolved_path(
672    w: &mut fmt::Formatter<'_>,
673    did: DefId,
674    path: &clean::Path,
675    print_all: bool,
676    use_absolute: bool,
677    cx: &Context<'_>,
678) -> fmt::Result {
679    let last = path.segments.last().unwrap();
680
681    if print_all {
682        for seg in &path.segments[..path.segments.len() - 1] {
683            write!(w, "{}::", if seg.name == kw::PathRoot { "" } else { seg.name.as_str() })?;
684        }
685    }
686    if w.alternate() {
687        write!(w, "{}{:#}", last.name, last.args.print(cx))?;
688    } else {
689        let path = fmt::from_fn(|f| {
690            if use_absolute {
691                if let Ok((_, _, fqp)) = href(did, cx) {
692                    write!(
693                        f,
694                        "{path}::{anchor}",
695                        path = join_path_syms(&fqp[..fqp.len() - 1]),
696                        anchor = print_anchor(did, *fqp.last().unwrap(), cx)
697                    )
698                } else {
699                    write!(f, "{}", last.name)
700                }
701            } else {
702                write!(f, "{}", print_anchor(did, last.name, cx))
703            }
704        });
705        write!(w, "{path}{args}", args = last.args.print(cx))?;
706    }
707    Ok(())
708}
709
710fn primitive_link(
711    f: &mut fmt::Formatter<'_>,
712    prim: clean::PrimitiveType,
713    name: fmt::Arguments<'_>,
714    cx: &Context<'_>,
715) -> fmt::Result {
716    primitive_link_fragment(f, prim, name, "", cx)
717}
718
719fn primitive_link_fragment(
720    f: &mut fmt::Formatter<'_>,
721    prim: clean::PrimitiveType,
722    name: fmt::Arguments<'_>,
723    fragment: &str,
724    cx: &Context<'_>,
725) -> fmt::Result {
726    let m = &cx.cache();
727    let mut needs_termination = false;
728    if !f.alternate() {
729        match m.primitive_locations.get(&prim) {
730            Some(&def_id) if def_id.is_local() => {
731                let len = cx.current.len();
732                let path = fmt::from_fn(|f| {
733                    if len == 0 {
734                        let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
735                        write!(f, "{cname_sym}/")?;
736                    } else {
737                        for _ in 0..(len - 1) {
738                            f.write_str("../")?;
739                        }
740                    }
741                    Ok(())
742                });
743                write!(
744                    f,
745                    "<a class=\"primitive\" href=\"{path}primitive.{}.html{fragment}\">",
746                    prim.as_sym()
747                )?;
748                needs_termination = true;
749            }
750            Some(&def_id) => {
751                let loc = match m.extern_locations[&def_id.krate] {
752                    ExternalLocation::Remote(ref s) => {
753                        let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
754                        let builder: UrlPartsBuilder =
755                            [s.as_str().trim_end_matches('/'), cname_sym.as_str()]
756                                .into_iter()
757                                .collect();
758                        Some(builder)
759                    }
760                    ExternalLocation::Local => {
761                        let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
762                        Some(if cx.current.first() == Some(&cname_sym) {
763                            iter::repeat_n(sym::dotdot, cx.current.len() - 1).collect()
764                        } else {
765                            iter::repeat_n(sym::dotdot, cx.current.len())
766                                .chain(iter::once(cname_sym))
767                                .collect()
768                        })
769                    }
770                    ExternalLocation::Unknown => None,
771                };
772                if let Some(mut loc) = loc {
773                    loc.push_fmt(format_args!("primitive.{}.html", prim.as_sym()));
774                    write!(f, "<a class=\"primitive\" href=\"{}{fragment}\">", loc.finish())?;
775                    needs_termination = true;
776                }
777            }
778            None => {}
779        }
780    }
781    Display::fmt(&name, f)?;
782    if needs_termination {
783        write!(f, "</a>")?;
784    }
785    Ok(())
786}
787
788fn print_tybounds(
789    bounds: &[clean::PolyTrait],
790    lt: &Option<clean::Lifetime>,
791    cx: &Context<'_>,
792) -> impl Display {
793    fmt::from_fn(move |f| {
794        bounds.iter().map(|bound| bound.print(cx)).joined(" + ", f)?;
795        if let Some(lt) = lt {
796            // We don't need to check `alternate` since we can be certain that
797            // the lifetime doesn't contain any characters which need escaping.
798            write!(f, " + {}", lt.print())?;
799        }
800        Ok(())
801    })
802}
803
804fn print_higher_ranked_params_with_space(
805    params: &[clean::GenericParamDef],
806    cx: &Context<'_>,
807    keyword: &'static str,
808) -> impl Display {
809    fmt::from_fn(move |f| {
810        if !params.is_empty() {
811            f.write_str(keyword)?;
812            Wrapped::with_angle_brackets()
813                .wrap_fn(|f| params.iter().map(|lt| lt.print(cx)).joined(", ", f))
814                .fmt(f)?;
815            f.write_char(' ')?;
816        }
817        Ok(())
818    })
819}
820
821pub(crate) fn print_anchor(did: DefId, text: Symbol, cx: &Context<'_>) -> impl Display {
822    fmt::from_fn(move |f| {
823        let parts = href(did, cx);
824        if let Ok((url, short_ty, fqp)) = parts {
825            write!(
826                f,
827                r#"<a class="{short_ty}" href="{url}" title="{short_ty} {path}">{text}</a>"#,
828                path = join_path_syms(fqp),
829                text = EscapeBodyText(text.as_str()),
830            )
831        } else {
832            f.write_str(text.as_str())
833        }
834    })
835}
836
837fn fmt_type(
838    t: &clean::Type,
839    f: &mut fmt::Formatter<'_>,
840    use_absolute: bool,
841    cx: &Context<'_>,
842) -> fmt::Result {
843    trace!("fmt_type(t = {t:?})");
844
845    match t {
846        clean::Generic(name) => f.write_str(name.as_str()),
847        clean::SelfTy => f.write_str("Self"),
848        clean::Type::Path { path } => {
849            // Paths like `T::Output` and `Self::Output` should be rendered with all segments.
850            let did = path.def_id();
851            resolved_path(f, did, path, path.is_assoc_ty(), use_absolute, cx)
852        }
853        clean::DynTrait(bounds, lt) => {
854            f.write_str("dyn ")?;
855            print_tybounds(bounds, lt, cx).fmt(f)
856        }
857        clean::Infer => write!(f, "_"),
858        clean::Primitive(clean::PrimitiveType::Never) => {
859            primitive_link(f, PrimitiveType::Never, format_args!("!"), cx)
860        }
861        &clean::Primitive(prim) => primitive_link(f, prim, format_args!("{}", prim.as_sym()), cx),
862        clean::BareFunction(decl) => {
863            print_higher_ranked_params_with_space(&decl.generic_params, cx, "for").fmt(f)?;
864            decl.safety.print_with_space().fmt(f)?;
865            print_abi_with_space(decl.abi).fmt(f)?;
866            if f.alternate() {
867                f.write_str("fn")?;
868            } else {
869                primitive_link(f, PrimitiveType::Fn, format_args!("fn"), cx)?;
870            }
871            decl.decl.print(cx).fmt(f)
872        }
873        clean::UnsafeBinder(binder) => {
874            print_higher_ranked_params_with_space(&binder.generic_params, cx, "unsafe").fmt(f)?;
875            binder.ty.print(cx).fmt(f)
876        }
877        clean::Tuple(typs) => match &typs[..] {
878            &[] => primitive_link(f, PrimitiveType::Unit, format_args!("()"), cx),
879            [one] => {
880                if let clean::Generic(name) = one {
881                    primitive_link(f, PrimitiveType::Tuple, format_args!("({name},)"), cx)
882                } else {
883                    write!(f, "(")?;
884                    one.print(cx).fmt(f)?;
885                    write!(f, ",)")
886                }
887            }
888            many => {
889                let generic_names: Vec<Symbol> = many
890                    .iter()
891                    .filter_map(|t| match t {
892                        clean::Generic(name) => Some(*name),
893                        _ => None,
894                    })
895                    .collect();
896                let is_generic = generic_names.len() == many.len();
897                if is_generic {
898                    primitive_link(
899                        f,
900                        PrimitiveType::Tuple,
901                        format_args!(
902                            "{}",
903                            Wrapped::with_parens()
904                                .wrap_fn(|f| generic_names.iter().joined(", ", f))
905                        ),
906                        cx,
907                    )
908                } else {
909                    Wrapped::with_parens()
910                        .wrap_fn(|f| many.iter().map(|item| item.print(cx)).joined(", ", f))
911                        .fmt(f)
912                }
913            }
914        },
915        clean::Slice(box clean::Generic(name)) => {
916            primitive_link(f, PrimitiveType::Slice, format_args!("[{name}]"), cx)
917        }
918        clean::Slice(t) => Wrapped::with_square_brackets().wrap(t.print(cx)).fmt(f),
919        clean::Type::Pat(t, pat) => {
920            fmt::Display::fmt(&t.print(cx), f)?;
921            write!(f, " is {pat}")
922        }
923        clean::Array(box clean::Generic(name), n) if !f.alternate() => primitive_link(
924            f,
925            PrimitiveType::Array,
926            format_args!("[{name}; {n}]", n = Escape(n)),
927            cx,
928        ),
929        clean::Array(t, n) => Wrapped::with_square_brackets()
930            .wrap(fmt::from_fn(|f| {
931                t.print(cx).fmt(f)?;
932                f.write_str("; ")?;
933                if f.alternate() {
934                    f.write_str(n)
935                } else {
936                    primitive_link(f, PrimitiveType::Array, format_args!("{n}", n = Escape(n)), cx)
937                }
938            }))
939            .fmt(f),
940        clean::RawPointer(m, t) => {
941            let m = m.ptr_str();
942
943            if matches!(**t, clean::Generic(_)) || t.is_assoc_ty() {
944                primitive_link(
945                    f,
946                    clean::PrimitiveType::RawPointer,
947                    format_args!("*{m} {ty}", ty = WithOpts::from(f).display(t.print(cx))),
948                    cx,
949                )
950            } else {
951                primitive_link(f, clean::PrimitiveType::RawPointer, format_args!("*{m} "), cx)?;
952                t.print(cx).fmt(f)
953            }
954        }
955        clean::BorrowedRef { lifetime: l, mutability, type_: ty } => {
956            let lt = fmt::from_fn(|f| match l {
957                Some(l) => write!(f, "{} ", l.print()),
958                _ => Ok(()),
959            });
960            let m = mutability.print_with_space();
961            let amp = if f.alternate() { "&" } else { "&amp;" };
962
963            if let clean::Generic(name) = **ty {
964                return primitive_link(
965                    f,
966                    PrimitiveType::Reference,
967                    format_args!("{amp}{lt}{m}{name}"),
968                    cx,
969                );
970            }
971
972            write!(f, "{amp}{lt}{m}")?;
973
974            let needs_parens = match **ty {
975                clean::DynTrait(ref bounds, ref trait_lt)
976                    if bounds.len() > 1 || trait_lt.is_some() =>
977                {
978                    true
979                }
980                clean::ImplTrait(ref bounds) if bounds.len() > 1 => true,
981                _ => false,
982            };
983            Wrapped::with_parens()
984                .when(needs_parens)
985                .wrap_fn(|f| fmt_type(ty, f, use_absolute, cx))
986                .fmt(f)
987        }
988        clean::ImplTrait(bounds) => {
989            f.write_str("impl ")?;
990            print_generic_bounds(bounds, cx).fmt(f)
991        }
992        clean::QPath(qpath) => qpath.print(cx).fmt(f),
993    }
994}
995
996impl clean::Type {
997    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
998        fmt::from_fn(move |f| fmt_type(self, f, false, cx))
999    }
1000}
1001
1002impl clean::Path {
1003    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
1004        fmt::from_fn(move |f| resolved_path(f, self.def_id(), self, false, false, cx))
1005    }
1006}
1007
1008impl clean::QPathData {
1009    fn print(&self, cx: &Context<'_>) -> impl Display {
1010        let Self { ref assoc, ref self_type, should_fully_qualify, ref trait_ } = *self;
1011
1012        fmt::from_fn(move |f| {
1013            // FIXME(inherent_associated_types): Once we support non-ADT self-types (#106719),
1014            // we need to surround them with angle brackets in some cases (e.g. `<dyn …>::P`).
1015
1016            if let Some(trait_) = trait_
1017                && should_fully_qualify
1018            {
1019                let opts = WithOpts::from(f);
1020                Wrapped::with_angle_brackets()
1021                    .wrap(format_args!(
1022                        "{} as {}",
1023                        opts.display(self_type.print(cx)),
1024                        opts.display(trait_.print(cx))
1025                    ))
1026                    .fmt(f)?
1027            } else {
1028                self_type.print(cx).fmt(f)?;
1029            }
1030            f.write_str("::")?;
1031            // It's pretty unsightly to look at `<A as B>::C` in output, and
1032            // we've got hyperlinking on our side, so try to avoid longer
1033            // notation as much as possible by making `C` a hyperlink to trait
1034            // `B` to disambiguate.
1035            //
1036            // FIXME: this is still a lossy conversion and there should probably
1037            //        be a better way of representing this in general? Most of
1038            //        the ugliness comes from inlining across crates where
1039            //        everything comes in as a fully resolved QPath (hard to
1040            //        look at).
1041            if !f.alternate() {
1042                // FIXME(inherent_associated_types): We always link to the very first associated
1043                // type (in respect to source order) that bears the given name (`assoc.name`) and that is
1044                // affiliated with the computed `DefId`. This is obviously incorrect when we have
1045                // multiple impl blocks. Ideally, we would thread the `DefId` of the assoc ty itself
1046                // through here and map it to the corresponding HTML ID that was generated by
1047                // `render::Context::derive_id` when the impl blocks were rendered.
1048                // There is no such mapping unfortunately.
1049                // As a hack, we could badly imitate `derive_id` here by keeping *count* when looking
1050                // for the assoc ty `DefId` in `tcx.associated_items(self_ty_did).in_definition_order()`
1051                // considering privacy, `doc(hidden)`, etc.
1052                // I don't feel like that right now :cold_sweat:.
1053
1054                let parent_href = match trait_ {
1055                    Some(trait_) => href(trait_.def_id(), cx).ok(),
1056                    None => self_type.def_id(cx.cache()).and_then(|did| href(did, cx).ok()),
1057                };
1058
1059                if let Some((url, _, path)) = parent_href {
1060                    write!(
1061                        f,
1062                        "<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \
1063                                    title=\"type {path}::{name}\">{name}</a>",
1064                        shortty = ItemType::AssocType,
1065                        name = assoc.name,
1066                        path = join_path_syms(path),
1067                    )
1068                } else {
1069                    write!(f, "{}", assoc.name)
1070                }
1071            } else {
1072                write!(f, "{}", assoc.name)
1073            }?;
1074
1075            assoc.args.print(cx).fmt(f)
1076        })
1077    }
1078}
1079
1080impl clean::Impl {
1081    pub(crate) fn print(&self, use_absolute: bool, cx: &Context<'_>) -> impl Display {
1082        fmt::from_fn(move |f| {
1083            f.write_str("impl")?;
1084            self.generics.print(cx).fmt(f)?;
1085            f.write_str(" ")?;
1086
1087            if let Some(ref ty) = self.trait_ {
1088                if self.is_negative_trait_impl() {
1089                    f.write_char('!')?;
1090                }
1091                if self.kind.is_fake_variadic()
1092                    && let Some(generics) = ty.generics()
1093                    && let Ok(inner_type) = generics.exactly_one()
1094                {
1095                    let last = ty.last();
1096                    if f.alternate() {
1097                        write!(f, "{last}")?;
1098                    } else {
1099                        write!(f, "{}", print_anchor(ty.def_id(), last, cx))?;
1100                    };
1101                    Wrapped::with_angle_brackets()
1102                        .wrap_fn(|f| self.print_type(inner_type, f, use_absolute, cx))
1103                        .fmt(f)?;
1104                } else {
1105                    ty.print(cx).fmt(f)?;
1106                }
1107                f.write_str(" for ")?;
1108            }
1109
1110            if let Some(ty) = self.kind.as_blanket_ty() {
1111                fmt_type(ty, f, use_absolute, cx)?;
1112            } else {
1113                self.print_type(&self.for_, f, use_absolute, cx)?;
1114            }
1115
1116            print_where_clause(&self.generics, cx, 0, Ending::Newline).maybe_display().fmt(f)
1117        })
1118    }
1119    fn print_type(
1120        &self,
1121        type_: &clean::Type,
1122        f: &mut fmt::Formatter<'_>,
1123        use_absolute: bool,
1124        cx: &Context<'_>,
1125    ) -> Result<(), fmt::Error> {
1126        if let clean::Type::Tuple(types) = type_
1127            && let [clean::Type::Generic(name)] = &types[..]
1128            && (self.kind.is_fake_variadic() || self.kind.is_auto())
1129        {
1130            // Hardcoded anchor library/core/src/primitive_docs.rs
1131            // Link should match `# Trait implementations`
1132            primitive_link_fragment(
1133                f,
1134                PrimitiveType::Tuple,
1135                format_args!("({name}₁, {name}₂, …, {name}ₙ)"),
1136                "#trait-implementations-1",
1137                cx,
1138            )?;
1139        } else if let clean::Type::Array(ty, len) = type_
1140            && let clean::Type::Generic(name) = &**ty
1141            && &len[..] == "1"
1142            && (self.kind.is_fake_variadic() || self.kind.is_auto())
1143        {
1144            primitive_link(f, PrimitiveType::Array, format_args!("[{name}; N]"), cx)?;
1145        } else if let clean::BareFunction(bare_fn) = &type_
1146            && let [clean::Parameter { type_: clean::Type::Generic(name), .. }] =
1147                &bare_fn.decl.inputs[..]
1148            && (self.kind.is_fake_variadic() || self.kind.is_auto())
1149        {
1150            // Hardcoded anchor library/core/src/primitive_docs.rs
1151            // Link should match `# Trait implementations`
1152
1153            print_higher_ranked_params_with_space(&bare_fn.generic_params, cx, "for").fmt(f)?;
1154            bare_fn.safety.print_with_space().fmt(f)?;
1155            print_abi_with_space(bare_fn.abi).fmt(f)?;
1156            let ellipsis = if bare_fn.decl.c_variadic { ", ..." } else { "" };
1157            primitive_link_fragment(
1158                f,
1159                PrimitiveType::Tuple,
1160                format_args!("fn({name}₁, {name}₂, …, {name}ₙ{ellipsis})"),
1161                "#trait-implementations-1",
1162                cx,
1163            )?;
1164            // Write output.
1165            if !bare_fn.decl.output.is_unit() {
1166                write!(f, " -> ")?;
1167                fmt_type(&bare_fn.decl.output, f, use_absolute, cx)?;
1168            }
1169        } else if let clean::Type::Path { path } = type_
1170            && let Some(generics) = path.generics()
1171            && let Ok(ty) = generics.exactly_one()
1172            && self.kind.is_fake_variadic()
1173        {
1174            print_anchor(path.def_id(), path.last(), cx).fmt(f)?;
1175            Wrapped::with_angle_brackets()
1176                .wrap_fn(|f| self.print_type(ty, f, use_absolute, cx))
1177                .fmt(f)?;
1178        } else {
1179            fmt_type(type_, f, use_absolute, cx)?;
1180        }
1181        Ok(())
1182    }
1183}
1184
1185pub(crate) fn print_params(params: &[clean::Parameter], cx: &Context<'_>) -> impl Display {
1186    fmt::from_fn(move |f| {
1187        params
1188            .iter()
1189            .map(|param| {
1190                fmt::from_fn(|f| {
1191                    if let Some(name) = param.name {
1192                        write!(f, "{name}: ")?;
1193                    }
1194                    param.type_.print(cx).fmt(f)
1195                })
1196            })
1197            .joined(", ", f)
1198    })
1199}
1200
1201// Implements Write but only counts the bytes "written".
1202struct WriteCounter(usize);
1203
1204impl std::fmt::Write for WriteCounter {
1205    fn write_str(&mut self, s: &str) -> fmt::Result {
1206        self.0 += s.len();
1207        Ok(())
1208    }
1209}
1210
1211// Implements Display by emitting the given number of spaces.
1212#[derive(Clone, Copy)]
1213struct Indent(usize);
1214
1215impl Display for Indent {
1216    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1217        (0..self.0).for_each(|_| {
1218            f.write_char(' ').unwrap();
1219        });
1220        Ok(())
1221    }
1222}
1223
1224impl clean::Parameter {
1225    fn print(&self, cx: &Context<'_>) -> impl fmt::Display {
1226        fmt::from_fn(move |f| {
1227            if let Some(self_ty) = self.to_receiver() {
1228                match self_ty {
1229                    clean::SelfTy => f.write_str("self"),
1230                    clean::BorrowedRef { lifetime, mutability, type_: box clean::SelfTy } => {
1231                        f.write_str(if f.alternate() { "&" } else { "&amp;" })?;
1232                        if let Some(lt) = lifetime {
1233                            write!(f, "{lt} ", lt = lt.print())?;
1234                        }
1235                        write!(f, "{mutability}self", mutability = mutability.print_with_space())
1236                    }
1237                    _ => {
1238                        f.write_str("self: ")?;
1239                        self_ty.print(cx).fmt(f)
1240                    }
1241                }
1242            } else {
1243                if self.is_const {
1244                    write!(f, "const ")?;
1245                }
1246                if let Some(name) = self.name {
1247                    write!(f, "{name}: ")?;
1248                }
1249                self.type_.print(cx).fmt(f)
1250            }
1251        })
1252    }
1253}
1254
1255impl clean::FnDecl {
1256    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
1257        fmt::from_fn(move |f| {
1258            let ellipsis = if self.c_variadic { ", ..." } else { "" };
1259            Wrapped::with_parens()
1260                .wrap_fn(|f| {
1261                    print_params(&self.inputs, cx).fmt(f)?;
1262                    f.write_str(ellipsis)
1263                })
1264                .fmt(f)?;
1265            self.print_output(cx).fmt(f)
1266        })
1267    }
1268
1269    /// * `header_len`: The length of the function header and name. In other words, the number of
1270    ///   characters in the function declaration up to but not including the parentheses.
1271    ///   This is expected to go into a `<pre>`/`code-header` block, so indentation and newlines
1272    ///   are preserved.
1273    /// * `indent`: The number of spaces to indent each successive line with, if line-wrapping is
1274    ///   necessary.
1275    pub(crate) fn full_print(
1276        &self,
1277        header_len: usize,
1278        indent: usize,
1279        cx: &Context<'_>,
1280    ) -> impl Display {
1281        fmt::from_fn(move |f| {
1282            // First, generate the text form of the declaration, with no line wrapping, and count the bytes.
1283            let mut counter = WriteCounter(0);
1284            write!(&mut counter, "{:#}", fmt::from_fn(|f| { self.inner_full_print(None, f, cx) }))?;
1285            // If the text form was over 80 characters wide, we will line-wrap our output.
1286            let line_wrapping_indent =
1287                if header_len + counter.0 > 80 { Some(indent) } else { None };
1288            // Generate the final output. This happens to accept `{:#}` formatting to get textual
1289            // output but in practice it is only formatted with `{}` to get HTML output.
1290            self.inner_full_print(line_wrapping_indent, f, cx)
1291        })
1292    }
1293
1294    fn inner_full_print(
1295        &self,
1296        // For None, the declaration will not be line-wrapped. For Some(n),
1297        // the declaration will be line-wrapped, with an indent of n spaces.
1298        line_wrapping_indent: Option<usize>,
1299        f: &mut fmt::Formatter<'_>,
1300        cx: &Context<'_>,
1301    ) -> fmt::Result {
1302        Wrapped::with_parens()
1303            .wrap_fn(|f| {
1304                if !self.inputs.is_empty() {
1305                    let line_wrapping_indent = line_wrapping_indent.map(|n| Indent(n + 4));
1306
1307                    if let Some(indent) = line_wrapping_indent {
1308                        write!(f, "\n{indent}")?;
1309                    }
1310
1311                    let sep = fmt::from_fn(|f| {
1312                        if let Some(indent) = line_wrapping_indent {
1313                            write!(f, ",\n{indent}")
1314                        } else {
1315                            f.write_str(", ")
1316                        }
1317                    });
1318
1319                    self.inputs.iter().map(|param| param.print(cx)).joined(sep, f)?;
1320
1321                    if line_wrapping_indent.is_some() {
1322                        writeln!(f, ",")?
1323                    }
1324
1325                    if self.c_variadic {
1326                        match line_wrapping_indent {
1327                            None => write!(f, ", ...")?,
1328                            Some(indent) => writeln!(f, "{indent}...")?,
1329                        };
1330                    }
1331                }
1332
1333                if let Some(n) = line_wrapping_indent {
1334                    write!(f, "{}", Indent(n))?
1335                }
1336
1337                Ok(())
1338            })
1339            .fmt(f)?;
1340
1341        self.print_output(cx).fmt(f)
1342    }
1343
1344    fn print_output(&self, cx: &Context<'_>) -> impl Display {
1345        fmt::from_fn(move |f| {
1346            if self.output.is_unit() {
1347                return Ok(());
1348            }
1349
1350            f.write_str(if f.alternate() { " -> " } else { " -&gt; " })?;
1351            self.output.print(cx).fmt(f)
1352        })
1353    }
1354}
1355
1356pub(crate) fn visibility_print_with_space(item: &clean::Item, cx: &Context<'_>) -> impl Display {
1357    fmt::from_fn(move |f| {
1358        if item.is_doc_hidden() {
1359            f.write_str("#[doc(hidden)] ")?;
1360        }
1361
1362        let Some(vis) = item.visibility(cx.tcx()) else {
1363            return Ok(());
1364        };
1365
1366        match vis {
1367            ty::Visibility::Public => f.write_str("pub ")?,
1368            ty::Visibility::Restricted(vis_did) => {
1369                // FIXME(camelid): This may not work correctly if `item_did` is a module.
1370                //                 However, rustdoc currently never displays a module's
1371                //                 visibility, so it shouldn't matter.
1372                let parent_module =
1373                    find_nearest_parent_module(cx.tcx(), item.item_id.expect_def_id());
1374
1375                if vis_did.is_crate_root() {
1376                    f.write_str("pub(crate) ")?;
1377                } else if parent_module == Some(vis_did) {
1378                    // `pub(in foo)` where `foo` is the parent module
1379                    // is the same as no visibility modifier; do nothing
1380                } else if parent_module
1381                    .and_then(|parent| find_nearest_parent_module(cx.tcx(), parent))
1382                    == Some(vis_did)
1383                {
1384                    f.write_str("pub(super) ")?;
1385                } else {
1386                    let path = cx.tcx().def_path(vis_did);
1387                    debug!("path={path:?}");
1388                    // modified from `resolved_path()` to work with `DefPathData`
1389                    let last_name = path.data.last().unwrap().data.get_opt_name().unwrap();
1390                    let anchor = print_anchor(vis_did, last_name, cx);
1391
1392                    f.write_str("pub(in ")?;
1393                    for seg in &path.data[..path.data.len() - 1] {
1394                        write!(f, "{}::", seg.data.get_opt_name().unwrap())?;
1395                    }
1396                    write!(f, "{anchor}) ")?;
1397                }
1398            }
1399        }
1400        Ok(())
1401    })
1402}
1403
1404pub(crate) trait PrintWithSpace {
1405    fn print_with_space(&self) -> &str;
1406}
1407
1408impl PrintWithSpace for hir::Safety {
1409    fn print_with_space(&self) -> &str {
1410        self.prefix_str()
1411    }
1412}
1413
1414impl PrintWithSpace for hir::HeaderSafety {
1415    fn print_with_space(&self) -> &str {
1416        match self {
1417            hir::HeaderSafety::SafeTargetFeatures => "",
1418            hir::HeaderSafety::Normal(safety) => safety.print_with_space(),
1419        }
1420    }
1421}
1422
1423impl PrintWithSpace for hir::IsAsync {
1424    fn print_with_space(&self) -> &str {
1425        match self {
1426            hir::IsAsync::Async(_) => "async ",
1427            hir::IsAsync::NotAsync => "",
1428        }
1429    }
1430}
1431
1432impl PrintWithSpace for hir::Mutability {
1433    fn print_with_space(&self) -> &str {
1434        match self {
1435            hir::Mutability::Not => "",
1436            hir::Mutability::Mut => "mut ",
1437        }
1438    }
1439}
1440
1441pub(crate) fn print_constness_with_space(
1442    c: &hir::Constness,
1443    overall_stab: Option<StableSince>,
1444    const_stab: Option<ConstStability>,
1445) -> &'static str {
1446    match c {
1447        hir::Constness::Const => match (overall_stab, const_stab) {
1448            // const stable...
1449            (_, Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }))
1450            // ...or when feature(staged_api) is not set...
1451            | (_, None)
1452            // ...or when const unstable, but overall unstable too
1453            | (None, Some(ConstStability { level: StabilityLevel::Unstable { .. }, .. })) => {
1454                "const "
1455            }
1456            // const unstable (and overall stable)
1457            (Some(_), Some(ConstStability { level: StabilityLevel::Unstable { .. }, .. })) => "",
1458        },
1459        // not const
1460        hir::Constness::NotConst => "",
1461    }
1462}
1463
1464impl clean::Import {
1465    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
1466        fmt::from_fn(move |f| match self.kind {
1467            clean::ImportKind::Simple(name) => {
1468                if name == self.source.path.last() {
1469                    write!(f, "use {};", self.source.print(cx))
1470                } else {
1471                    write!(f, "use {source} as {name};", source = self.source.print(cx))
1472                }
1473            }
1474            clean::ImportKind::Glob => {
1475                if self.source.path.segments.is_empty() {
1476                    write!(f, "use *;")
1477                } else {
1478                    write!(f, "use {}::*;", self.source.print(cx))
1479                }
1480            }
1481        })
1482    }
1483}
1484
1485impl clean::ImportSource {
1486    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
1487        fmt::from_fn(move |f| match self.did {
1488            Some(did) => resolved_path(f, did, &self.path, true, false, cx),
1489            _ => {
1490                for seg in &self.path.segments[..self.path.segments.len() - 1] {
1491                    write!(f, "{}::", seg.name)?;
1492                }
1493                let name = self.path.last();
1494                if let hir::def::Res::PrimTy(p) = self.path.res {
1495                    primitive_link(f, PrimitiveType::from(p), format_args!("{name}"), cx)?;
1496                } else {
1497                    f.write_str(name.as_str())?;
1498                }
1499                Ok(())
1500            }
1501        })
1502    }
1503}
1504
1505impl clean::AssocItemConstraint {
1506    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
1507        fmt::from_fn(move |f| {
1508            f.write_str(self.assoc.name.as_str())?;
1509            self.assoc.args.print(cx).fmt(f)?;
1510            match self.kind {
1511                clean::AssocItemConstraintKind::Equality { ref term } => {
1512                    f.write_str(" = ")?;
1513                    term.print(cx).fmt(f)?;
1514                }
1515                clean::AssocItemConstraintKind::Bound { ref bounds } => {
1516                    if !bounds.is_empty() {
1517                        f.write_str(": ")?;
1518                        print_generic_bounds(bounds, cx).fmt(f)?;
1519                    }
1520                }
1521            }
1522            Ok(())
1523        })
1524    }
1525}
1526
1527pub(crate) fn print_abi_with_space(abi: ExternAbi) -> impl Display {
1528    fmt::from_fn(move |f| {
1529        let quot = if f.alternate() { "\"" } else { "&quot;" };
1530        match abi {
1531            ExternAbi::Rust => Ok(()),
1532            abi => write!(f, "extern {0}{1}{0} ", quot, abi.name()),
1533        }
1534    })
1535}
1536
1537pub(crate) fn print_default_space(v: bool) -> &'static str {
1538    if v { "default " } else { "" }
1539}
1540
1541impl clean::GenericArg {
1542    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
1543        fmt::from_fn(move |f| match self {
1544            clean::GenericArg::Lifetime(lt) => lt.print().fmt(f),
1545            clean::GenericArg::Type(ty) => ty.print(cx).fmt(f),
1546            clean::GenericArg::Const(ct) => ct.print(cx.tcx()).fmt(f),
1547            clean::GenericArg::Infer => Display::fmt("_", f),
1548        })
1549    }
1550}
1551
1552impl clean::Term {
1553    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
1554        fmt::from_fn(move |f| match self {
1555            clean::Term::Type(ty) => ty.print(cx).fmt(f),
1556            clean::Term::Constant(ct) => ct.print(cx.tcx()).fmt(f),
1557        })
1558    }
1559}