rust/src/librustdoc/html/format.rs
mu001999 f0a019bf90 Render final associated functions correctly in rustdoc
Co-authored-by: Michael Goulet <michael@errs.io>
2026-02-12 15:18:15 +08:00

1583 lines
57 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! HTML formatting module
//!
//! This module contains a large number of `Display` implementations for
//! various types in `rustdoc::clean`.
//!
//! These implementations all emit HTML. As an internal implementation detail,
//! some of them support an alternate format that emits text, but that should
//! not be used external to this module.
use std::cmp::Ordering;
use std::fmt::{self, Display, Write};
use std::iter::{self, once};
use std::slice;
use itertools::{Either, Itertools};
use rustc_abi::ExternAbi;
use rustc_ast::join_path_syms;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
use rustc_hir::def::{DefKind, MacroKinds};
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
use rustc_hir::{ConstStability, StabilityLevel, StableSince};
use rustc_metadata::creader::CStore;
use rustc_middle::ty::{self, TyCtxt, TypingMode};
use rustc_span::symbol::kw;
use rustc_span::{Symbol, sym};
use tracing::{debug, trace};
use super::url_parts_builder::UrlPartsBuilder;
use crate::clean::types::ExternalLocation;
use crate::clean::utils::find_nearest_parent_module;
use crate::clean::{self, ExternalCrate, PrimitiveType};
use crate::display::{Joined as _, MaybeDisplay as _, WithOpts, Wrapped};
use crate::formats::cache::Cache;
use crate::formats::item_type::ItemType;
use crate::html::escape::{Escape, EscapeBodyText};
use crate::html::render::Context;
use crate::passes::collect_intra_doc_links::UrlFragment;
pub(crate) fn print_generic_bounds(
bounds: &[clean::GenericBound],
cx: &Context<'_>,
) -> impl Display {
fmt::from_fn(move |f| {
let mut bounds_dup = FxHashSet::default();
bounds
.iter()
.filter(move |b| bounds_dup.insert(*b))
.map(|bound| print_generic_bound(bound, cx))
.joined(" + ", f)
})
}
pub(crate) fn print_generic_param_def(
generic_param: &clean::GenericParamDef,
cx: &Context<'_>,
) -> impl Display {
fmt::from_fn(move |f| match &generic_param.kind {
clean::GenericParamDefKind::Lifetime { outlives } => {
write!(f, "{}", generic_param.name)?;
if !outlives.is_empty() {
f.write_str(": ")?;
outlives.iter().map(|lt| print_lifetime(lt)).joined(" + ", f)?;
}
Ok(())
}
clean::GenericParamDefKind::Type { bounds, default, .. } => {
f.write_str(generic_param.name.as_str())?;
if !bounds.is_empty() {
f.write_str(": ")?;
print_generic_bounds(bounds, cx).fmt(f)?;
}
if let Some(ty) = default {
f.write_str(" = ")?;
print_type(ty, cx).fmt(f)?;
}
Ok(())
}
clean::GenericParamDefKind::Const { ty, default, .. } => {
write!(f, "const {}: ", generic_param.name)?;
print_type(ty, cx).fmt(f)?;
if let Some(default) = default {
f.write_str(" = ")?;
if f.alternate() {
write!(f, "{default}")?;
} else {
write!(f, "{}", Escape(default))?;
}
}
Ok(())
}
})
}
pub(crate) fn print_generics(generics: &clean::Generics, cx: &Context<'_>) -> impl Display {
let mut real_params = generics.params.iter().filter(|p| !p.is_synthetic_param()).peekable();
if real_params.peek().is_none() {
None
} else {
Some(Wrapped::with_angle_brackets().wrap_fn(move |f| {
real_params.clone().map(|g| print_generic_param_def(g, cx)).joined(", ", f)
}))
}
.maybe_display()
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum Ending {
Newline,
NoNewline,
}
fn print_where_predicate(predicate: &clean::WherePredicate, cx: &Context<'_>) -> impl Display {
fmt::from_fn(move |f| {
match predicate {
clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => {
print_higher_ranked_params_with_space(bound_params, cx, "for").fmt(f)?;
print_type(ty, cx).fmt(f)?;
f.write_str(":")?;
if !bounds.is_empty() {
f.write_str(" ")?;
print_generic_bounds(bounds, cx).fmt(f)?;
}
Ok(())
}
clean::WherePredicate::RegionPredicate { lifetime, bounds } => {
// We don't need to check `alternate` since we can be certain that neither
// the lifetime nor the bounds contain any characters which need escaping.
write!(f, "{}:", print_lifetime(lifetime))?;
if !bounds.is_empty() {
write!(f, " {}", print_generic_bounds(bounds, cx))?;
}
Ok(())
}
clean::WherePredicate::EqPredicate { lhs, rhs } => {
let opts = WithOpts::from(f);
write!(
f,
"{} == {}",
opts.display(print_qpath_data(lhs, cx)),
opts.display(print_term(rhs, cx)),
)
}
}
})
}
/// * The Generics from which to emit a where-clause.
/// * The number of spaces to indent each line with.
/// * Whether the where-clause needs to add a comma and newline after the last bound.
pub(crate) fn print_where_clause(
gens: &clean::Generics,
cx: &Context<'_>,
indent: usize,
ending: Ending,
) -> Option<impl Display> {
if gens.where_predicates.is_empty() {
return None;
}
Some(fmt::from_fn(move |f| {
let where_preds = fmt::from_fn(|f| {
gens.where_predicates
.iter()
.map(|predicate| {
fmt::from_fn(|f| {
if f.alternate() {
f.write_str(" ")?;
} else {
f.write_str("\n")?;
}
print_where_predicate(predicate, cx).fmt(f)
})
})
.joined(",", f)
});
let clause = if f.alternate() {
if ending == Ending::Newline {
format!(" where{where_preds},")
} else {
format!(" where{where_preds}")
}
} else {
let mut br_with_padding = String::with_capacity(6 * indent + 28);
br_with_padding.push('\n');
let where_indent = 3;
let padding_amount = if ending == Ending::Newline {
indent + 4
} else if indent == 0 {
4
} else {
indent + where_indent + "where ".len()
};
for _ in 0..padding_amount {
br_with_padding.push(' ');
}
let where_preds = where_preds.to_string().replace('\n', &br_with_padding);
if ending == Ending::Newline {
let mut clause = " ".repeat(indent.saturating_sub(1));
write!(clause, "<div class=\"where\">where{where_preds},</div>")?;
clause
} else {
// insert a newline after a single space but before multiple spaces at the start
if indent == 0 {
format!("\n<span class=\"where\">where{where_preds}</span>")
} else {
// put the first one on the same line as the 'where' keyword
let where_preds = where_preds.replacen(&br_with_padding, " ", 1);
let mut clause = br_with_padding;
// +1 is for `\n`.
clause.truncate(indent + 1 + where_indent);
write!(clause, "<span class=\"where\">where{where_preds}</span>")?;
clause
}
}
};
write!(f, "{clause}")
}))
}
#[inline]
pub(crate) fn print_lifetime(lt: &clean::Lifetime) -> &str {
lt.0.as_str()
}
pub(crate) fn print_constant_kind(
constant_kind: &clean::ConstantKind,
tcx: TyCtxt<'_>,
) -> impl Display {
let expr = constant_kind.expr(tcx);
fmt::from_fn(
move |f| {
if f.alternate() { f.write_str(&expr) } else { write!(f, "{}", Escape(&expr)) }
},
)
}
fn print_poly_trait(poly_trait: &clean::PolyTrait, cx: &Context<'_>) -> impl Display {
fmt::from_fn(move |f| {
print_higher_ranked_params_with_space(&poly_trait.generic_params, cx, "for").fmt(f)?;
print_path(&poly_trait.trait_, cx).fmt(f)
})
}
pub(crate) fn print_generic_bound(
generic_bound: &clean::GenericBound,
cx: &Context<'_>,
) -> impl Display {
fmt::from_fn(move |f| match generic_bound {
clean::GenericBound::Outlives(lt) => f.write_str(print_lifetime(lt)),
clean::GenericBound::TraitBound(ty, modifiers) => {
// `const` and `[const]` trait bounds are experimental; don't render them.
let hir::TraitBoundModifiers { polarity, constness: _ } = modifiers;
f.write_str(match polarity {
hir::BoundPolarity::Positive => "",
hir::BoundPolarity::Maybe(_) => "?",
hir::BoundPolarity::Negative(_) => "!",
})?;
print_poly_trait(ty, cx).fmt(f)
}
clean::GenericBound::Use(args) => {
f.write_str("use")?;
Wrapped::with_angle_brackets()
.wrap_fn(|f| args.iter().map(|arg| arg.name()).joined(", ", f))
.fmt(f)
}
})
}
fn print_generic_args(generic_args: &clean::GenericArgs, cx: &Context<'_>) -> impl Display {
fmt::from_fn(move |f| {
match generic_args {
clean::GenericArgs::AngleBracketed { args, constraints } => {
if !args.is_empty() || !constraints.is_empty() {
Wrapped::with_angle_brackets()
.wrap_fn(|f| {
[Either::Left(args), Either::Right(constraints)]
.into_iter()
.flat_map(Either::factor_into_iter)
.map(|either| {
either.map_either(
|arg| print_generic_arg(arg, cx),
|constraint| print_assoc_item_constraint(constraint, cx),
)
})
.joined(", ", f)
})
.fmt(f)?;
}
}
clean::GenericArgs::Parenthesized { inputs, output } => {
Wrapped::with_parens()
.wrap_fn(|f| inputs.iter().map(|ty| print_type(ty, cx)).joined(", ", f))
.fmt(f)?;
if let Some(ref ty) = *output {
f.write_str(if f.alternate() { " -> " } else { " -&gt; " })?;
print_type(ty, cx).fmt(f)?;
}
}
clean::GenericArgs::ReturnTypeNotation => {
f.write_str("(..)")?;
}
}
Ok(())
})
}
// Possible errors when computing href link source for a `DefId`
#[derive(PartialEq, Eq)]
pub(crate) enum HrefError {
/// This item is known to rustdoc, but from a crate that does not have documentation generated.
///
/// This can only happen for non-local items.
///
/// # Example
///
/// Crate `a` defines a public trait and crate `b` the target crate that depends on `a`
/// implements it for a local type.
/// We document `b` but **not** `a` (we only _build_ the latter with `rustc`):
///
/// ```sh
/// rustc a.rs --crate-type=lib
/// rustdoc b.rs --crate-type=lib --extern=a=liba.rlib
/// ```
///
/// Now, the associated items in the trait impl want to link to the corresponding item in the
/// trait declaration (see `html::render::assoc_href_attr`) but it's not available since their
/// *documentation (was) not built*.
DocumentationNotBuilt,
/// This can only happen for non-local items when `--document-private-items` is not passed.
Private,
// Not in external cache, href link should be in same page
NotInExternalCache,
/// Refers to an unnamable item, such as one defined within a function or const block.
UnnamableItem,
}
/// Type representing information of an `href` attribute.
pub(crate) struct HrefInfo {
/// URL to the item page.
pub(crate) url: String,
/// Kind of the item (used to generate the `title` attribute).
pub(crate) kind: ItemType,
/// Rust path to the item (used to generate the `title` attribute).
pub(crate) rust_path: Vec<Symbol>,
}
/// This function is to get the external macro path because they are not in the cache used in
/// `href_with_root_path`.
fn generate_macro_def_id_path(
def_id: DefId,
cx: &Context<'_>,
root_path: Option<&str>,
) -> Result<HrefInfo, HrefError> {
let tcx = cx.tcx();
let crate_name = tcx.crate_name(def_id.krate);
let cache = cx.cache();
let cstore = CStore::from_tcx(tcx);
// We need this to prevent a `panic` when this function is used from intra doc links...
if !cstore.has_crate_data(def_id.krate) {
debug!("No data for crate {crate_name}");
return Err(HrefError::NotInExternalCache);
}
let DefKind::Macro(kinds) = tcx.def_kind(def_id) else {
unreachable!();
};
let item_type = if kinds == MacroKinds::DERIVE {
ItemType::ProcDerive
} else if kinds == MacroKinds::ATTR {
ItemType::ProcAttribute
} else {
ItemType::Macro
};
let mut path = clean::inline::get_item_path(tcx, def_id, item_type);
if path.len() < 2 {
// The minimum we can have is the crate name followed by the macro name. If shorter, then
// it means that `relative` was empty, which is an error.
debug!("macro path cannot be empty!");
return Err(HrefError::NotInExternalCache);
}
// FIXME: Try to use `iter().chain().once()` instead.
let mut prev = None;
if let Some(last) = path.pop() {
path.push(Symbol::intern(&format!("{}.{last}.html", item_type.as_str())));
prev = Some(last);
}
let url = match cache.extern_locations[&def_id.krate] {
ExternalLocation::Remote(ref s) => {
// `ExternalLocation::Remote` always end with a `/`.
format!("{s}{path}", path = fmt::from_fn(|f| path.iter().joined("/", f)))
}
ExternalLocation::Local => {
// `root_path` always end with a `/`.
format!(
"{root_path}{path}",
root_path = root_path.unwrap_or(""),
path = fmt::from_fn(|f| path.iter().joined("/", f))
)
}
ExternalLocation::Unknown => {
debug!("crate {crate_name} not in cache when linkifying macros");
return Err(HrefError::NotInExternalCache);
}
};
if let Some(prev) = prev {
path.pop();
path.push(prev);
}
Ok(HrefInfo { url, kind: item_type, rust_path: path })
}
fn generate_item_def_id_path(
mut def_id: DefId,
original_def_id: DefId,
cx: &Context<'_>,
root_path: Option<&str>,
) -> Result<HrefInfo, HrefError> {
use rustc_middle::traits::ObligationCause;
use rustc_trait_selection::infer::TyCtxtInferExt;
use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt;
let tcx = cx.tcx();
let crate_name = tcx.crate_name(def_id.krate);
// No need to try to infer the actual parent item if it's not an associated item from the `impl`
// block.
if def_id != original_def_id && matches!(tcx.def_kind(def_id), DefKind::Impl { .. }) {
let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis());
def_id = infcx
.at(&ObligationCause::dummy(), tcx.param_env(def_id))
.query_normalize(ty::Binder::dummy(tcx.type_of(def_id).instantiate_identity()))
.map(|resolved| infcx.resolve_vars_if_possible(resolved.value))
.ok()
.and_then(|normalized| normalized.skip_binder().ty_adt_def())
.map(|adt| adt.did())
.unwrap_or(def_id);
}
let relative = clean::inline::item_relative_path(tcx, def_id);
let fqp: Vec<Symbol> = once(crate_name).chain(relative).collect();
let shortty = ItemType::from_def_id(def_id, tcx);
let module_fqp = to_module_fqp(shortty, &fqp);
let mut is_remote = false;
let url_parts = url_parts(cx.cache(), def_id, module_fqp, &cx.current, &mut is_remote)?;
let mut url_parts = make_href(root_path, shortty, url_parts, &fqp, is_remote);
if def_id != original_def_id {
let kind = ItemType::from_def_id(original_def_id, tcx);
url_parts = format!("{url_parts}#{kind}.{}", tcx.item_name(original_def_id))
};
Ok(HrefInfo { url: url_parts, kind: shortty, rust_path: fqp })
}
/// Checks if the given defid refers to an item that is unnamable, such as one defined in a const block.
fn is_unnamable(tcx: TyCtxt<'_>, did: DefId) -> bool {
let mut cur_did = did;
while let Some(parent) = tcx.opt_parent(cur_did) {
match tcx.def_kind(parent) {
// items defined in these can be linked to, as long as they are visible
DefKind::Mod | DefKind::ForeignMod => cur_did = parent,
// items in impls can be linked to,
// as long as we can link to the item the impl is on.
// since associated traits are not a thing,
// it should not be possible to refer to an impl item if
// the base type is not namable.
DefKind::Impl { .. } => return false,
// everything else does not have docs generated for it
_ => return true,
}
}
return false;
}
fn to_module_fqp(shortty: ItemType, fqp: &[Symbol]) -> &[Symbol] {
if shortty == ItemType::Module { fqp } else { &fqp[..fqp.len() - 1] }
}
fn url_parts(
cache: &Cache,
def_id: DefId,
module_fqp: &[Symbol],
relative_to: &[Symbol],
is_remote: &mut bool,
) -> Result<UrlPartsBuilder, HrefError> {
match cache.extern_locations[&def_id.krate] {
ExternalLocation::Remote(ref s) => {
*is_remote = true;
let s = s.trim_end_matches('/');
let mut builder = UrlPartsBuilder::singleton(s);
builder.extend(module_fqp.iter().copied());
Ok(builder)
}
ExternalLocation::Local => Ok(href_relative_parts(module_fqp, relative_to)),
ExternalLocation::Unknown => Err(HrefError::DocumentationNotBuilt),
}
}
fn make_href(
root_path: Option<&str>,
shortty: ItemType,
mut url_parts: UrlPartsBuilder,
fqp: &[Symbol],
is_remote: bool,
) -> String {
if !is_remote && let Some(root_path) = root_path {
let root = root_path.trim_end_matches('/');
url_parts.push_front(root);
}
debug!(?url_parts);
match shortty {
ItemType::Module => {
url_parts.push("index.html");
}
_ => {
let last = fqp.last().unwrap();
url_parts.push_fmt(format_args!("{shortty}.{last}.html"));
}
}
url_parts.finish()
}
pub(crate) fn href_with_root_path(
original_did: DefId,
cx: &Context<'_>,
root_path: Option<&str>,
) -> Result<HrefInfo, HrefError> {
let tcx = cx.tcx();
let def_kind = tcx.def_kind(original_did);
let did = match def_kind {
DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst | DefKind::Variant => {
// documented on their parent's page
tcx.parent(original_did)
}
// If this a constructor, we get the parent (either a struct or a variant) and then
// generate the link for this item.
DefKind::Ctor(..) => return href_with_root_path(tcx.parent(original_did), cx, root_path),
DefKind::ExternCrate => {
// Link to the crate itself, not the `extern crate` item.
if let Some(local_did) = original_did.as_local() {
tcx.extern_mod_stmt_cnum(local_did).unwrap_or(LOCAL_CRATE).as_def_id()
} else {
original_did
}
}
_ => original_did,
};
if is_unnamable(cx.tcx(), did) {
return Err(HrefError::UnnamableItem);
}
let cache = cx.cache();
let relative_to = &cx.current;
if !original_did.is_local() {
// If we are generating an href for the "jump to def" feature, then the only case we want
// to ignore is if the item is `doc(hidden)` because we can't link to it.
if root_path.is_some() {
if tcx.is_doc_hidden(original_did) {
return Err(HrefError::Private);
}
} else if !cache.effective_visibilities.is_directly_public(tcx, did)
&& !cache.document_private
&& !cache.primitive_locations.values().any(|&id| id == did)
{
return Err(HrefError::Private);
}
}
let mut is_remote = false;
let (fqp, shortty, url_parts) = match cache.paths.get(&did) {
Some(&(ref fqp, shortty)) => (fqp, shortty, {
let module_fqp = to_module_fqp(shortty, fqp.as_slice());
debug!(?fqp, ?shortty, ?module_fqp);
href_relative_parts(module_fqp, relative_to)
}),
None => {
// Associated items are handled differently with "jump to def". The anchor is generated
// directly here whereas for intra-doc links, we have some extra computation being
// performed there.
let def_id_to_get = if root_path.is_some() { original_did } else { did };
if let Some(&(ref fqp, shortty)) = cache.external_paths.get(&def_id_to_get) {
let module_fqp = to_module_fqp(shortty, fqp);
(fqp, shortty, url_parts(cache, did, module_fqp, relative_to, &mut is_remote)?)
} else if matches!(def_kind, DefKind::Macro(_)) {
return generate_macro_def_id_path(did, cx, root_path);
} else if did.is_local() {
return Err(HrefError::Private);
} else {
return generate_item_def_id_path(did, original_did, cx, root_path);
}
}
};
Ok(HrefInfo {
url: make_href(root_path, shortty, url_parts, fqp, is_remote),
kind: shortty,
rust_path: fqp.clone(),
})
}
pub(crate) fn href(did: DefId, cx: &Context<'_>) -> Result<HrefInfo, HrefError> {
href_with_root_path(did, cx, None)
}
/// Both paths should only be modules.
/// This is because modules get their own directories; that is, `std::vec` and `std::vec::Vec` will
/// both need `../iter/trait.Iterator.html` to get at the iterator trait.
pub(crate) fn href_relative_parts(fqp: &[Symbol], relative_to_fqp: &[Symbol]) -> UrlPartsBuilder {
for (i, (f, r)) in fqp.iter().zip(relative_to_fqp.iter()).enumerate() {
// e.g. linking to std::iter from std::vec (`dissimilar_part_count` will be 1)
if f != r {
let dissimilar_part_count = relative_to_fqp.len() - i;
let fqp_module = &fqp[i..];
return iter::repeat_n(sym::dotdot, dissimilar_part_count)
.chain(fqp_module.iter().copied())
.collect();
}
}
match relative_to_fqp.len().cmp(&fqp.len()) {
Ordering::Less => {
// e.g. linking to std::sync::atomic from std::sync
fqp[relative_to_fqp.len()..fqp.len()].iter().copied().collect()
}
Ordering::Greater => {
// e.g. linking to std::sync from std::sync::atomic
let dissimilar_part_count = relative_to_fqp.len() - fqp.len();
iter::repeat_n(sym::dotdot, dissimilar_part_count).collect()
}
Ordering::Equal => {
// linking to the same module
UrlPartsBuilder::new()
}
}
}
pub(crate) fn link_tooltip(
did: DefId,
fragment: &Option<UrlFragment>,
cx: &Context<'_>,
) -> impl fmt::Display {
fmt::from_fn(move |f| {
let cache = cx.cache();
let Some((fqp, shortty)) = cache.paths.get(&did).or_else(|| cache.external_paths.get(&did))
else {
return Ok(());
};
let fqp = if *shortty == ItemType::Primitive {
// primitives are documented in a crate, but not actually part of it
slice::from_ref(fqp.last().unwrap())
} else {
fqp
};
if let &Some(UrlFragment::Item(id)) = fragment {
write!(f, "{} ", cx.tcx().def_descr(id))?;
for component in fqp {
write!(f, "{component}::")?;
}
write!(f, "{}", cx.tcx().item_name(id))?;
} else if !fqp.is_empty() {
write!(f, "{shortty} ")?;
write!(f, "{}", join_path_syms(fqp))?;
}
Ok(())
})
}
/// Used to render a [`clean::Path`].
fn resolved_path(
w: &mut fmt::Formatter<'_>,
did: DefId,
path: &clean::Path,
print_all: bool,
use_absolute: bool,
cx: &Context<'_>,
) -> fmt::Result {
let last = path.segments.last().unwrap();
if print_all {
for seg in &path.segments[..path.segments.len() - 1] {
write!(w, "{}::", if seg.name == kw::PathRoot { "" } else { seg.name.as_str() })?;
}
}
if w.alternate() {
write!(w, "{}{:#}", last.name, print_generic_args(&last.args, cx))?;
} else {
let path = fmt::from_fn(|f| {
if use_absolute {
if let Ok(HrefInfo { rust_path, .. }) = href(did, cx) {
write!(
f,
"{path}::{anchor}",
path = join_path_syms(&rust_path[..rust_path.len() - 1]),
anchor = print_anchor(did, *rust_path.last().unwrap(), cx)
)
} else {
write!(f, "{}", last.name)
}
} else {
write!(f, "{}", print_anchor(did, last.name, cx))
}
});
write!(w, "{path}{args}", args = print_generic_args(&last.args, cx))?;
}
Ok(())
}
fn primitive_link(
f: &mut fmt::Formatter<'_>,
prim: clean::PrimitiveType,
name: fmt::Arguments<'_>,
cx: &Context<'_>,
) -> fmt::Result {
primitive_link_fragment(f, prim, name, "", cx)
}
fn primitive_link_fragment(
f: &mut fmt::Formatter<'_>,
prim: clean::PrimitiveType,
name: fmt::Arguments<'_>,
fragment: &str,
cx: &Context<'_>,
) -> fmt::Result {
let m = &cx.cache();
let mut needs_termination = false;
if !f.alternate() {
match m.primitive_locations.get(&prim) {
Some(&def_id) if def_id.is_local() => {
let len = cx.current.len();
let path = fmt::from_fn(|f| {
if len == 0 {
let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
write!(f, "{cname_sym}/")?;
} else {
for _ in 0..(len - 1) {
f.write_str("../")?;
}
}
Ok(())
});
write!(
f,
"<a class=\"primitive\" href=\"{path}primitive.{}.html{fragment}\">",
prim.as_sym()
)?;
needs_termination = true;
}
Some(&def_id) => {
let loc = match m.extern_locations[&def_id.krate] {
ExternalLocation::Remote(ref s) => {
let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
let builder: UrlPartsBuilder =
[s.as_str().trim_end_matches('/'), cname_sym.as_str()]
.into_iter()
.collect();
Some(builder)
}
ExternalLocation::Local => {
let cname_sym = ExternalCrate { crate_num: def_id.krate }.name(cx.tcx());
Some(if cx.current.first() == Some(&cname_sym) {
iter::repeat_n(sym::dotdot, cx.current.len() - 1).collect()
} else {
iter::repeat_n(sym::dotdot, cx.current.len())
.chain(iter::once(cname_sym))
.collect()
})
}
ExternalLocation::Unknown => None,
};
if let Some(mut loc) = loc {
loc.push_fmt(format_args!("primitive.{}.html", prim.as_sym()));
write!(f, "<a class=\"primitive\" href=\"{}{fragment}\">", loc.finish())?;
needs_termination = true;
}
}
None => {}
}
}
Display::fmt(&name, f)?;
if needs_termination {
write!(f, "</a>")?;
}
Ok(())
}
fn print_tybounds(
bounds: &[clean::PolyTrait],
lt: &Option<clean::Lifetime>,
cx: &Context<'_>,
) -> impl Display {
fmt::from_fn(move |f| {
bounds.iter().map(|bound| print_poly_trait(bound, cx)).joined(" + ", f)?;
if let Some(lt) = lt {
// We don't need to check `alternate` since we can be certain that
// the lifetime doesn't contain any characters which need escaping.
write!(f, " + {}", print_lifetime(lt))?;
}
Ok(())
})
}
fn print_higher_ranked_params_with_space(
params: &[clean::GenericParamDef],
cx: &Context<'_>,
keyword: &'static str,
) -> impl Display {
fmt::from_fn(move |f| {
if !params.is_empty() {
f.write_str(keyword)?;
Wrapped::with_angle_brackets()
.wrap_fn(|f| {
params.iter().map(|lt| print_generic_param_def(lt, cx)).joined(", ", f)
})
.fmt(f)?;
f.write_char(' ')?;
}
Ok(())
})
}
pub(crate) fn fragment(did: DefId, tcx: TyCtxt<'_>) -> impl Display {
fmt::from_fn(move |f| {
let def_kind = tcx.def_kind(did);
match def_kind {
DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst | DefKind::Variant => {
let item_type = ItemType::from_def_id(did, tcx);
write!(f, "#{}.{}", item_type.as_str(), tcx.item_name(did))
}
DefKind::Field => {
let parent_def_id = tcx.parent(did);
f.write_char('#')?;
if tcx.def_kind(parent_def_id) == DefKind::Variant {
write!(f, "variant.{}.field", tcx.item_name(parent_def_id).as_str())?;
} else {
f.write_str("structfield")?;
};
write!(f, ".{}", tcx.item_name(did))
}
_ => Ok(()),
}
})
}
pub(crate) fn print_anchor(did: DefId, text: Symbol, cx: &Context<'_>) -> impl Display {
fmt::from_fn(move |f| {
if let Ok(HrefInfo { url, kind, rust_path }) = href(did, cx) {
write!(
f,
r#"<a class="{kind}" href="{url}{anchor}" title="{kind} {path}">{text}</a>"#,
anchor = fragment(did, cx.tcx()),
path = join_path_syms(rust_path),
text = EscapeBodyText(text.as_str()),
)
} else {
f.write_str(text.as_str())
}
})
}
fn fmt_type(
t: &clean::Type,
f: &mut fmt::Formatter<'_>,
use_absolute: bool,
cx: &Context<'_>,
) -> fmt::Result {
trace!("fmt_type(t = {t:?})");
match t {
clean::Generic(name) => f.write_str(name.as_str()),
clean::SelfTy => f.write_str("Self"),
clean::Type::Path { path } => {
// Paths like `T::Output` and `Self::Output` should be rendered with all segments.
let did = path.def_id();
resolved_path(f, did, path, path.is_assoc_ty(), use_absolute, cx)
}
clean::DynTrait(bounds, lt) => {
f.write_str("dyn ")?;
print_tybounds(bounds, lt, cx).fmt(f)
}
clean::Infer => write!(f, "_"),
clean::Primitive(clean::PrimitiveType::Never) => {
primitive_link(f, PrimitiveType::Never, format_args!("!"), cx)
}
&clean::Primitive(prim) => primitive_link(f, prim, format_args!("{}", prim.as_sym()), cx),
clean::BareFunction(decl) => {
print_higher_ranked_params_with_space(&decl.generic_params, cx, "for").fmt(f)?;
decl.safety.print_with_space().fmt(f)?;
print_abi_with_space(decl.abi).fmt(f)?;
if f.alternate() {
f.write_str("fn")?;
} else {
primitive_link(f, PrimitiveType::Fn, format_args!("fn"), cx)?;
}
print_fn_decl(&decl.decl, cx).fmt(f)
}
clean::UnsafeBinder(binder) => {
print_higher_ranked_params_with_space(&binder.generic_params, cx, "unsafe").fmt(f)?;
print_type(&binder.ty, cx).fmt(f)
}
clean::Tuple(typs) => match &typs[..] {
&[] => primitive_link(f, PrimitiveType::Unit, format_args!("()"), cx),
[one] => {
if let clean::Generic(name) = one {
primitive_link(f, PrimitiveType::Tuple, format_args!("({name},)"), cx)
} else {
write!(f, "(")?;
print_type(one, cx).fmt(f)?;
write!(f, ",)")
}
}
many => {
let generic_names: Vec<Symbol> = many
.iter()
.filter_map(|t| match t {
clean::Generic(name) => Some(*name),
_ => None,
})
.collect();
let is_generic = generic_names.len() == many.len();
if is_generic {
primitive_link(
f,
PrimitiveType::Tuple,
format_args!(
"{}",
Wrapped::with_parens()
.wrap_fn(|f| generic_names.iter().joined(", ", f))
),
cx,
)
} else {
Wrapped::with_parens()
.wrap_fn(|f| many.iter().map(|item| print_type(item, cx)).joined(", ", f))
.fmt(f)
}
}
},
clean::Slice(box clean::Generic(name)) => {
primitive_link(f, PrimitiveType::Slice, format_args!("[{name}]"), cx)
}
clean::Slice(t) => Wrapped::with_square_brackets().wrap(print_type(t, cx)).fmt(f),
clean::Type::Pat(t, pat) => {
fmt::Display::fmt(&print_type(t, cx), f)?;
write!(f, " is {pat}")
}
clean::Array(box clean::Generic(name), n) if !f.alternate() => primitive_link(
f,
PrimitiveType::Array,
format_args!("[{name}; {n}]", n = Escape(n)),
cx,
),
clean::Array(t, n) => Wrapped::with_square_brackets()
.wrap(fmt::from_fn(|f| {
print_type(t, cx).fmt(f)?;
f.write_str("; ")?;
if f.alternate() {
f.write_str(n)
} else {
primitive_link(f, PrimitiveType::Array, format_args!("{n}", n = Escape(n)), cx)
}
}))
.fmt(f),
clean::RawPointer(m, t) => {
let m = m.ptr_str();
if matches!(**t, clean::Generic(_)) || t.is_assoc_ty() {
primitive_link(
f,
clean::PrimitiveType::RawPointer,
format_args!("*{m} {ty}", ty = WithOpts::from(f).display(print_type(t, cx))),
cx,
)
} else {
primitive_link(f, clean::PrimitiveType::RawPointer, format_args!("*{m} "), cx)?;
print_type(t, cx).fmt(f)
}
}
clean::BorrowedRef { lifetime: l, mutability, type_: ty } => {
let lt = fmt::from_fn(|f| match l {
Some(l) => write!(f, "{} ", print_lifetime(l)),
_ => Ok(()),
});
let m = mutability.print_with_space();
let amp = if f.alternate() { "&" } else { "&amp;" };
if let clean::Generic(name) = **ty {
return primitive_link(
f,
PrimitiveType::Reference,
format_args!("{amp}{lt}{m}{name}"),
cx,
);
}
write!(f, "{amp}{lt}{m}")?;
let needs_parens = match **ty {
clean::DynTrait(ref bounds, ref trait_lt)
if bounds.len() > 1 || trait_lt.is_some() =>
{
true
}
clean::ImplTrait(ref bounds) if bounds.len() > 1 => true,
_ => false,
};
Wrapped::with_parens()
.when(needs_parens)
.wrap_fn(|f| fmt_type(ty, f, use_absolute, cx))
.fmt(f)
}
clean::ImplTrait(bounds) => {
f.write_str("impl ")?;
print_generic_bounds(bounds, cx).fmt(f)
}
clean::QPath(qpath) => print_qpath_data(qpath, cx).fmt(f),
}
}
pub(crate) fn print_type(type_: &clean::Type, cx: &Context<'_>) -> impl Display {
fmt::from_fn(move |f| fmt_type(type_, f, false, cx))
}
pub(crate) fn print_path(path: &clean::Path, cx: &Context<'_>) -> impl Display {
fmt::from_fn(move |f| resolved_path(f, path.def_id(), path, false, false, cx))
}
fn print_qpath_data(qpath_data: &clean::QPathData, cx: &Context<'_>) -> impl Display {
let clean::QPathData { ref assoc, ref self_type, should_fully_qualify, ref trait_ } =
*qpath_data;
fmt::from_fn(move |f| {
// FIXME(inherent_associated_types): Once we support non-ADT self-types (#106719),
// we need to surround them with angle brackets in some cases (e.g. `<dyn …>::P`).
if let Some(trait_) = trait_
&& should_fully_qualify
{
let opts = WithOpts::from(f);
Wrapped::with_angle_brackets()
.wrap(format_args!(
"{} as {}",
opts.display(print_type(self_type, cx)),
opts.display(print_path(trait_, cx))
))
.fmt(f)?
} else {
print_type(self_type, cx).fmt(f)?;
}
f.write_str("::")?;
// It's pretty unsightly to look at `<A as B>::C` in output, and
// we've got hyperlinking on our side, so try to avoid longer
// notation as much as possible by making `C` a hyperlink to trait
// `B` to disambiguate.
//
// FIXME: this is still a lossy conversion and there should probably
// be a better way of representing this in general? Most of
// the ugliness comes from inlining across crates where
// everything comes in as a fully resolved QPath (hard to
// look at).
if !f.alternate() {
// FIXME(inherent_associated_types): We always link to the very first associated
// type (in respect to source order) that bears the given name (`assoc.name`) and that is
// affiliated with the computed `DefId`. This is obviously incorrect when we have
// multiple impl blocks. Ideally, we would thread the `DefId` of the assoc ty itself
// through here and map it to the corresponding HTML ID that was generated by
// `render::Context::derive_id` when the impl blocks were rendered.
// There is no such mapping unfortunately.
// As a hack, we could badly imitate `derive_id` here by keeping *count* when looking
// for the assoc ty `DefId` in `tcx.associated_items(self_ty_did).in_definition_order()`
// considering privacy, `doc(hidden)`, etc.
// I don't feel like that right now :cold_sweat:.
let parent_href = match trait_ {
Some(trait_) => href(trait_.def_id(), cx).ok(),
None => self_type.def_id(cx.cache()).and_then(|did| href(did, cx).ok()),
};
if let Some(HrefInfo { url, rust_path, .. }) = parent_href {
write!(
f,
"<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \
title=\"type {path}::{name}\">{name}</a>",
shortty = ItemType::AssocType,
name = assoc.name,
path = join_path_syms(rust_path),
)
} else {
write!(f, "{}", assoc.name)
}
} else {
write!(f, "{}", assoc.name)
}?;
print_generic_args(&assoc.args, cx).fmt(f)
})
}
pub(crate) fn print_impl(
impl_: &clean::Impl,
use_absolute: bool,
cx: &Context<'_>,
) -> impl Display {
fmt::from_fn(move |f| {
f.write_str("impl")?;
print_generics(&impl_.generics, cx).fmt(f)?;
f.write_str(" ")?;
if let Some(ref ty) = impl_.trait_ {
if impl_.is_negative_trait_impl() {
f.write_char('!')?;
}
if impl_.kind.is_fake_variadic()
&& let Some(generics) = ty.generics()
&& let Ok(inner_type) = generics.exactly_one()
{
let last = ty.last();
if f.alternate() {
write!(f, "{last}")?;
} else {
write!(f, "{}", print_anchor(ty.def_id(), last, cx))?;
};
Wrapped::with_angle_brackets()
.wrap_fn(|f| impl_.print_type(inner_type, f, use_absolute, cx))
.fmt(f)?;
} else {
print_path(ty, cx).fmt(f)?;
}
f.write_str(" for ")?;
}
if let Some(ty) = impl_.kind.as_blanket_ty() {
fmt_type(ty, f, use_absolute, cx)?;
} else {
impl_.print_type(&impl_.for_, f, use_absolute, cx)?;
}
print_where_clause(&impl_.generics, cx, 0, Ending::Newline).maybe_display().fmt(f)
})
}
impl clean::Impl {
fn print_type(
&self,
type_: &clean::Type,
f: &mut fmt::Formatter<'_>,
use_absolute: bool,
cx: &Context<'_>,
) -> Result<(), fmt::Error> {
if let clean::Type::Tuple(types) = type_
&& let [clean::Type::Generic(name)] = &types[..]
&& (self.kind.is_fake_variadic() || self.kind.is_auto())
{
// Hardcoded anchor library/core/src/primitive_docs.rs
// Link should match `# Trait implementations`
primitive_link_fragment(
f,
PrimitiveType::Tuple,
format_args!("({name}₁, {name}₂, …, {name}ₙ)"),
"#trait-implementations-1",
cx,
)?;
} else if let clean::Type::Array(ty, len) = type_
&& let clean::Type::Generic(name) = &**ty
&& &len[..] == "1"
&& (self.kind.is_fake_variadic() || self.kind.is_auto())
{
primitive_link(f, PrimitiveType::Array, format_args!("[{name}; N]"), cx)?;
} else if let clean::BareFunction(bare_fn) = &type_
&& let [clean::Parameter { type_: clean::Type::Generic(name), .. }] =
&bare_fn.decl.inputs[..]
&& (self.kind.is_fake_variadic() || self.kind.is_auto())
{
// Hardcoded anchor library/core/src/primitive_docs.rs
// Link should match `# Trait implementations`
print_higher_ranked_params_with_space(&bare_fn.generic_params, cx, "for").fmt(f)?;
bare_fn.safety.print_with_space().fmt(f)?;
print_abi_with_space(bare_fn.abi).fmt(f)?;
let ellipsis = if bare_fn.decl.c_variadic { ", ..." } else { "" };
primitive_link_fragment(
f,
PrimitiveType::Tuple,
format_args!("fn({name}₁, {name}₂, …, {name}{ellipsis})"),
"#trait-implementations-1",
cx,
)?;
// Write output.
if !bare_fn.decl.output.is_unit() {
write!(f, " -> ")?;
fmt_type(&bare_fn.decl.output, f, use_absolute, cx)?;
}
} else if let clean::Type::Path { path } = type_
&& let Some(generics) = path.generics()
&& let Ok(ty) = generics.exactly_one()
&& self.kind.is_fake_variadic()
{
print_anchor(path.def_id(), path.last(), cx).fmt(f)?;
Wrapped::with_angle_brackets()
.wrap_fn(|f| self.print_type(ty, f, use_absolute, cx))
.fmt(f)?;
} else {
fmt_type(type_, f, use_absolute, cx)?;
}
Ok(())
}
}
pub(crate) fn print_params(params: &[clean::Parameter], cx: &Context<'_>) -> impl Display {
fmt::from_fn(move |f| {
params
.iter()
.map(|param| {
fmt::from_fn(|f| {
if let Some(name) = param.name {
write!(f, "{name}: ")?;
}
print_type(&param.type_, cx).fmt(f)
})
})
.joined(", ", f)
})
}
// Implements Write but only counts the bytes "written".
struct WriteCounter(usize);
impl std::fmt::Write for WriteCounter {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.0 += s.len();
Ok(())
}
}
// Implements Display by emitting the given number of spaces.
#[derive(Clone, Copy)]
struct Indent(usize);
impl Display for Indent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for _ in 0..self.0 {
f.write_char(' ')?;
}
Ok(())
}
}
fn print_parameter(parameter: &clean::Parameter, cx: &Context<'_>) -> impl fmt::Display {
fmt::from_fn(move |f| {
if let Some(self_ty) = parameter.to_receiver() {
match self_ty {
clean::SelfTy => f.write_str("self"),
clean::BorrowedRef { lifetime, mutability, type_: box clean::SelfTy } => {
f.write_str(if f.alternate() { "&" } else { "&amp;" })?;
if let Some(lt) = lifetime {
write!(f, "{lt} ", lt = print_lifetime(lt))?;
}
write!(f, "{mutability}self", mutability = mutability.print_with_space())
}
_ => {
f.write_str("self: ")?;
print_type(self_ty, cx).fmt(f)
}
}
} else {
if parameter.is_const {
write!(f, "const ")?;
}
if let Some(name) = parameter.name {
write!(f, "{name}: ")?;
}
print_type(&parameter.type_, cx).fmt(f)
}
})
}
fn print_fn_decl(fn_decl: &clean::FnDecl, cx: &Context<'_>) -> impl Display {
fmt::from_fn(move |f| {
let ellipsis = if fn_decl.c_variadic { ", ..." } else { "" };
Wrapped::with_parens()
.wrap_fn(|f| {
print_params(&fn_decl.inputs, cx).fmt(f)?;
f.write_str(ellipsis)
})
.fmt(f)?;
fn_decl.print_output(cx).fmt(f)
})
}
/// * `header_len`: The length of the function header and name. In other words, the number of
/// characters in the function declaration up to but not including the parentheses.
/// This is expected to go into a `<pre>`/`code-header` block, so indentation and newlines
/// are preserved.
/// * `indent`: The number of spaces to indent each successive line with, if line-wrapping is
/// necessary.
pub(crate) fn full_print_fn_decl(
fn_decl: &clean::FnDecl,
header_len: usize,
indent: usize,
cx: &Context<'_>,
) -> impl Display {
fmt::from_fn(move |f| {
// First, generate the text form of the declaration, with no line wrapping, and count the bytes.
let mut counter = WriteCounter(0);
write!(&mut counter, "{:#}", fmt::from_fn(|f| { fn_decl.inner_full_print(None, f, cx) }))?;
// If the text form was over 80 characters wide, we will line-wrap our output.
let line_wrapping_indent = if header_len + counter.0 > 80 { Some(indent) } else { None };
// Generate the final output. This happens to accept `{:#}` formatting to get textual
// output but in practice it is only formatted with `{}` to get HTML output.
fn_decl.inner_full_print(line_wrapping_indent, f, cx)
})
}
impl clean::FnDecl {
fn inner_full_print(
&self,
// For None, the declaration will not be line-wrapped. For Some(n),
// the declaration will be line-wrapped, with an indent of n spaces.
line_wrapping_indent: Option<usize>,
f: &mut fmt::Formatter<'_>,
cx: &Context<'_>,
) -> fmt::Result {
Wrapped::with_parens()
.wrap_fn(|f| {
if !self.inputs.is_empty() {
let line_wrapping_indent = line_wrapping_indent.map(|n| Indent(n + 4));
if let Some(indent) = line_wrapping_indent {
write!(f, "\n{indent}")?;
}
let sep = fmt::from_fn(|f| {
if let Some(indent) = line_wrapping_indent {
write!(f, ",\n{indent}")
} else {
f.write_str(", ")
}
});
self.inputs.iter().map(|param| print_parameter(param, cx)).joined(sep, f)?;
if line_wrapping_indent.is_some() {
writeln!(f, ",")?
}
if self.c_variadic {
match line_wrapping_indent {
None => write!(f, ", ...")?,
Some(indent) => writeln!(f, "{indent}...")?,
};
}
}
if let Some(n) = line_wrapping_indent {
write!(f, "{}", Indent(n))?
}
Ok(())
})
.fmt(f)?;
self.print_output(cx).fmt(f)
}
fn print_output(&self, cx: &Context<'_>) -> impl Display {
fmt::from_fn(move |f| {
if self.output.is_unit() {
return Ok(());
}
f.write_str(if f.alternate() { " -> " } else { " -&gt; " })?;
print_type(&self.output, cx).fmt(f)
})
}
}
pub(crate) fn visibility_print_with_space(item: &clean::Item, cx: &Context<'_>) -> impl Display {
fmt::from_fn(move |f| {
let Some(vis) = item.visibility(cx.tcx()) else {
return Ok(());
};
match vis {
ty::Visibility::Public => f.write_str("pub ")?,
ty::Visibility::Restricted(vis_did) => {
// FIXME(camelid): This may not work correctly if `item_did` is a module.
// However, rustdoc currently never displays a module's
// visibility, so it shouldn't matter.
let parent_module =
find_nearest_parent_module(cx.tcx(), item.item_id.expect_def_id());
if vis_did.is_crate_root() {
f.write_str("pub(crate) ")?;
} else if parent_module == Some(vis_did) {
// `pub(in foo)` where `foo` is the parent module
// is the same as no visibility modifier; do nothing
} else if parent_module
.and_then(|parent| find_nearest_parent_module(cx.tcx(), parent))
== Some(vis_did)
{
f.write_str("pub(super) ")?;
} else {
let path = cx.tcx().def_path(vis_did);
debug!("path={path:?}");
// modified from `resolved_path()` to work with `DefPathData`
let last_name = path.data.last().unwrap().data.get_opt_name().unwrap();
let anchor = print_anchor(vis_did, last_name, cx);
f.write_str("pub(in ")?;
for seg in &path.data[..path.data.len() - 1] {
write!(f, "{}::", seg.data.get_opt_name().unwrap())?;
}
write!(f, "{anchor}) ")?;
}
}
}
Ok(())
})
}
pub(crate) trait PrintWithSpace {
fn print_with_space(&self) -> &str;
}
impl PrintWithSpace for hir::Safety {
fn print_with_space(&self) -> &str {
self.prefix_str()
}
}
impl PrintWithSpace for hir::HeaderSafety {
fn print_with_space(&self) -> &str {
match self {
hir::HeaderSafety::SafeTargetFeatures => "",
hir::HeaderSafety::Normal(safety) => safety.print_with_space(),
}
}
}
impl PrintWithSpace for hir::IsAsync {
fn print_with_space(&self) -> &str {
match self {
hir::IsAsync::Async(_) => "async ",
hir::IsAsync::NotAsync => "",
}
}
}
impl PrintWithSpace for hir::Mutability {
fn print_with_space(&self) -> &str {
match self {
hir::Mutability::Not => "",
hir::Mutability::Mut => "mut ",
}
}
}
pub(crate) fn print_constness_with_space(
c: &hir::Constness,
overall_stab: Option<StableSince>,
const_stab: Option<ConstStability>,
) -> &'static str {
match c {
hir::Constness::Const => match (overall_stab, const_stab) {
// const stable...
(_, Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }))
// ...or when feature(staged_api) is not set...
| (_, None)
// ...or when const unstable, but overall unstable too
| (None, Some(ConstStability { level: StabilityLevel::Unstable { .. }, .. })) => {
"const "
}
// const unstable (and overall stable)
(Some(_), Some(ConstStability { level: StabilityLevel::Unstable { .. }, .. })) => "",
},
// not const
hir::Constness::NotConst => "",
}
}
pub(crate) fn print_import(import: &clean::Import, cx: &Context<'_>) -> impl Display {
fmt::from_fn(move |f| match import.kind {
clean::ImportKind::Simple(name) => {
if name == import.source.path.last() {
write!(f, "use {};", print_import_source(&import.source, cx))
} else {
write!(
f,
"use {source} as {name};",
source = print_import_source(&import.source, cx)
)
}
}
clean::ImportKind::Glob => {
if import.source.path.segments.is_empty() {
write!(f, "use *;")
} else {
write!(f, "use {}::*;", print_import_source(&import.source, cx))
}
}
})
}
fn print_import_source(import_source: &clean::ImportSource, cx: &Context<'_>) -> impl Display {
fmt::from_fn(move |f| match import_source.did {
Some(did) => resolved_path(f, did, &import_source.path, true, false, cx),
_ => {
for seg in &import_source.path.segments[..import_source.path.segments.len() - 1] {
write!(f, "{}::", seg.name)?;
}
let name = import_source.path.last();
if let hir::def::Res::PrimTy(p) = import_source.path.res {
primitive_link(f, PrimitiveType::from(p), format_args!("{name}"), cx)?;
} else {
f.write_str(name.as_str())?;
}
Ok(())
}
})
}
fn print_assoc_item_constraint(
assoc_item_constraint: &clean::AssocItemConstraint,
cx: &Context<'_>,
) -> impl Display {
fmt::from_fn(move |f| {
f.write_str(assoc_item_constraint.assoc.name.as_str())?;
print_generic_args(&assoc_item_constraint.assoc.args, cx).fmt(f)?;
match assoc_item_constraint.kind {
clean::AssocItemConstraintKind::Equality { ref term } => {
f.write_str(" = ")?;
print_term(term, cx).fmt(f)?;
}
clean::AssocItemConstraintKind::Bound { ref bounds } => {
if !bounds.is_empty() {
f.write_str(": ")?;
print_generic_bounds(bounds, cx).fmt(f)?;
}
}
}
Ok(())
})
}
pub(crate) fn print_abi_with_space(abi: ExternAbi) -> impl Display {
fmt::from_fn(move |f| {
let quot = if f.alternate() { "\"" } else { "&quot;" };
match abi {
ExternAbi::Rust => Ok(()),
abi => write!(f, "extern {0}{1}{0} ", quot, abi.name()),
}
})
}
fn print_generic_arg(generic_arg: &clean::GenericArg, cx: &Context<'_>) -> impl Display {
fmt::from_fn(move |f| match generic_arg {
clean::GenericArg::Lifetime(lt) => f.write_str(print_lifetime(lt)),
clean::GenericArg::Type(ty) => print_type(ty, cx).fmt(f),
clean::GenericArg::Const(ct) => print_constant_kind(ct, cx.tcx()).fmt(f),
clean::GenericArg::Infer => f.write_char('_'),
})
}
fn print_term(term: &clean::Term, cx: &Context<'_>) -> impl Display {
fmt::from_fn(move |f| match term {
clean::Term::Type(ty) => print_type(ty, cx).fmt(f),
clean::Term::Constant(ct) => print_constant_kind(ct, cx.tcx()).fmt(f),
})
}